diff --git a/llvm/test/tools/llvm-exegesis/X86/latency/Inputs/abnormal-exit-code.s b/llvm/test/tools/llvm-exegesis/X86/latency/Inputs/abnormal-exit-code.s new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/X86/latency/Inputs/abnormal-exit-code.s @@ -0,0 +1,3 @@ +movl $60, %eax +movl $1, %edi +syscall diff --git a/llvm/test/tools/llvm-exegesis/X86/latency/Inputs/base-snippet.s b/llvm/test/tools/llvm-exegesis/X86/latency/Inputs/base-snippet.s new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/X86/latency/Inputs/base-snippet.s @@ -0,0 +1,4 @@ +# LLVM-EXEGESIS-DEFREG XMM1 42 +# LLVM-EXEGESIS-DEFREG XMM2 42 +# LLVM-EXEGESIS-DEFREG XMM3 42 +vhaddps %xmm2, %xmm2, %xmm3 diff --git a/llvm/test/tools/llvm-exegesis/X86/latency/Inputs/snippet-segfault.s b/llvm/test/tools/llvm-exegesis/X86/latency/Inputs/snippet-segfault.s new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/X86/latency/Inputs/snippet-segfault.s @@ -0,0 +1,6 @@ +# LLVM-EXEGESIS-LIVEIN RDI +# LLVM-EXEGESIS-DEFREG XMM1 42 +# LLVM-EXEGESIS-DEFREG RBX 0 +vmulps (%rbx), %xmm1, %xmm2 +vhaddps %xmm2, %xmm2, %xmm3 +addq $0x10, %rbx diff --git a/llvm/test/tools/llvm-exegesis/X86/latency/subprocess-abnormal-exit-code.test b/llvm/test/tools/llvm-exegesis/X86/latency/subprocess-abnormal-exit-code.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/X86/latency/subprocess-abnormal-exit-code.test @@ -0,0 +1,5 @@ +# REQUIRES: exegesis-can-execute-x86_64, exegesis-can-measure-latency, x86_64-linux + +# RUN: llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=latency -snippets-file=%S/Inputs/abnormal-exit-code.s --benchmark-subprocess > /test.out + +CHECK: error: Child benchmarking process exited with non-zero exit code1 diff --git a/llvm/test/tools/llvm-exegesis/X86/latency/subprocess-segfault.test b/llvm/test/tools/llvm-exegesis/X86/latency/subprocess-segfault.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/X86/latency/subprocess-segfault.test @@ -0,0 +1,5 @@ +# REQUIRES: exegesis-can-execute-x86_64, exegesis-can-measure-latency, x86_64-linux + +# RUN: llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=latency -snippets-file=%S/Inputs/snippet-segfault.s --benchmark-subprocess | FileCheck %s + +CHECK: error: 'The benchmarking subprocess sent unexpected signal: Segmentation fault' diff --git a/llvm/test/tools/llvm-exegesis/X86/latency/subprocess.test b/llvm/test/tools/llvm-exegesis/X86/latency/subprocess.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/X86/latency/subprocess.test @@ -0,0 +1,6 @@ +# REQUIRES: exegesis-can-execute-x86_64, exegesis-can-measure-latency, x86_64-linux + +# RUN: llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=latency -snippets-file=%S/Inputs/base-snippet.s --benchmark-subprocess | FileCheck %s + +CHECK: measurements: +CHECK-NEXT: value: {{.*}}, per_snippet_value: {{.*}} diff --git a/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp b/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp --- a/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp +++ b/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp @@ -25,10 +25,21 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Program.h" +#include "llvm/Support/Signals.h" + +#ifdef LLVM_ON_UNIX +#include "sys/wait.h" +#include "sys/ptrace.h" +#endif namespace llvm { namespace exegesis { +static cl::opt BenchmarkInSubprocess( + "benchmark-subprocess", + cl::desc("Enabling this makes benchmarking occur in a subprocess"), + cl::cat(BenchmarkOptions), cl::init(false)); + BenchmarkRunner::BenchmarkRunner(const LLVMState &State, Benchmark::ModeE Mode, BenchmarkPhaseSelectorE BenchmarkPhaseSelector) @@ -129,6 +140,115 @@ const ExecutableFunction Function; BenchmarkRunner::ScratchSpace *const Scratch; }; + +#ifdef LLVM_ON_UNIX +class SubProcessFunctionExecutorImpl + : public BenchmarkRunner::FunctionExecutor { +public: + SubProcessFunctionExecutorImpl(const LLVMState &State, + object::OwningBinary Obj) + : State(State), Function(State.createTargetMachine(), std::move(Obj)) {} + +private: + Error + createProcessAndRunBenchmark(StringRef CounterName, + SmallVectorImpl &CounterValues) const { + int PipeFiles[2]; + int PipeSuccessOrErr = pipe(PipeFiles); + if (PipeSuccessOrErr != 0) { + return make_error( + "Failed to create a pipe for interprocess communication between " + "llvm-exegesis and the benchmarking subprocess"); + } + pid_t ParentOrChildPID = fork(); + if(ParentOrChildPID == 0) { + // We are in the child process + close(PipeFiles[0]); + // Unregister handlers, signal handling is now handled through ptrace in + // the host process + llvm::sys::unregisterHandlers(); + prepareAndRunBenchmark(CounterName, PipeFiles[1]); + } + + ptrace(PTRACE_SEIZE, ParentOrChildPID, NULL, NULL); + + close(PipeFiles[1]); + + int ChildStatus; + wait(&ChildStatus); + + if(WIFEXITED(ChildStatus)) { + if (WEXITSTATUS(ChildStatus) == 0) { + // The child exited succesfully, read counter values and return + // success + int64_t CounterValue; + ssize_t BytesRead = + read(PipeFiles[0], &CounterValue, sizeof(CounterValue)); + if (BytesRead != sizeof(CounterValue)) { + return make_error( + "Counter data size sent by the subprocess " + "does not match the expected size"); + } + CounterValues[0] = CounterValue; + return Error::success(); + } + // The child exited, but not successfully + return make_error( + "Child benchmarking process exited with non-zero exit code" + + Twine(WEXITSTATUS(ChildStatus))); + } + + // An error was encountered running the snippet, process it + siginfo_t ChildSignalInfo; + ptrace(PTRACE_GETSIGINFO, ParentOrChildPID, NULL, &ChildSignalInfo); + + return make_error( + "The benchmarking subprocess sent unexpected signal: " + + Twine(strsignal(ChildSignalInfo.si_signo))); + } + + void prepareAndRunBenchmark(StringRef CounterName, int Pipe) const { + // The following occurs within the benchmarking subprocess + auto Scratch = std::make_unique(); + Scratch->clear(); + + const ExegesisTarget &ET = State.getExegesisTarget(); + auto CounterOrError = ET.createCounter(CounterName, State); + if(CounterOrError) { + pfm::Counter *Counter = CounterOrError.get().get(); + + StringRef FunctionData = Function.getFunctionBytes(); + std::string FunctionDataCopy = FunctionData.str(); + Counter->start(); + this->Function(Scratch->ptr()); + Counter->stop(); + int64_t CounterValue = Counter->read(); + ssize_t BytesWritten = write(Pipe, &CounterValue, sizeof(CounterValue)); + if(BytesWritten != sizeof(CounterValue)) { + exit(1); + } + } + + exit(0); + } + + Expected> + runWithCounter(StringRef CounterName) const override { + SmallVector Value(1, 0); + Error PossibleBenchmarkError = + createProcessAndRunBenchmark(CounterName, Value); + + if (PossibleBenchmarkError) { + return std::move(PossibleBenchmarkError); + } + + return Value; + } + + const LLVMState &State; + const ExecutableFunction Function; +}; +#endif // LLVM_ON_UNIX } // namespace Expected> BenchmarkRunner::assembleSnippet( @@ -215,9 +335,19 @@ return std::move(InstrBenchmark); } - const FunctionExecutorImpl Executor(State, std::move(ObjectFile), - Scratch.get()); - auto NewMeasurements = runMeasurements(Executor); + FunctionExecutor *Executor; + if (!BenchmarkInSubprocess) { + Executor = + new FunctionExecutorImpl(State, std::move(ObjectFile), Scratch.get()); + } else { +#ifdef LLVM_ON_UNIX + Executor = new SubProcessFunctionExecutorImpl(State, std::move(ObjectFile)); +#else + return make_error( + "--benchmark-subprocess is not supported on non-UNIX platforms"); +#endif + } + auto NewMeasurements = runMeasurements(*Executor); if (Error E = NewMeasurements.takeError()) { if (!E.isA()) return std::move(E); @@ -235,6 +365,15 @@ } InstrBenchmark.Measurements = std::move(*NewMeasurements); +#ifdef LLVM_ON_UNIX + if (BenchmarkInSubprocess) + delete (SubProcessFunctionExecutorImpl *)Executor; + else + delete (FunctionExecutorImpl *)Executor; +#else + delete (FunctionExecutorImpl *)Executor; +#endif + return std::move(InstrBenchmark); }