Index: llvm/test/tools/llvm-exegesis/X86/latency/subprocess-abnormal-exit-code.s =================================================================== --- /dev/null +++ llvm/test/tools/llvm-exegesis/X86/latency/subprocess-abnormal-exit-code.s @@ -0,0 +1,9 @@ +# 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 -execution-mode=subprocess | FileCheck %s + +# CHECK: error: 'Child benchmarking process exited with non-zero exit code: Child process returned with unknown exit code' + +movl $60, %eax +movl $127, %edi +syscall Index: llvm/test/tools/llvm-exegesis/X86/latency/subprocess-segfault.s =================================================================== --- /dev/null +++ llvm/test/tools/llvm-exegesis/X86/latency/subprocess-segfault.s @@ -0,0 +1,8 @@ +# 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 -execution-mode=subprocess | FileCheck %s + +# CHECK: error: 'The benchmarking subprocess sent unexpected signal: Segmentation fault' + +# LLVM-EXEGESIS-DEFREG RBX 0 +movq (%rbx), %rax Index: llvm/test/tools/llvm-exegesis/X86/latency/subprocess.s =================================================================== --- /dev/null +++ llvm/test/tools/llvm-exegesis/X86/latency/subprocess.s @@ -0,0 +1,11 @@ +# 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 -execution-mode=subprocess | FileCheck %s + +# CHECK: measurements: +# CHECK-NEXT: value: {{.*}}, per_snippet_value: {{.*}} + +# LLVM-EXEGESIS-DEFREG XMM1 42 +# LLVM-EXEGESIS-DEFREG XMM2 42 +# LLVM-EXEGESIS-DEFREG XMM3 42 +vhaddps %xmm2, %xmm2, %xmm3 Index: llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h =================================================================== --- llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h +++ llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h @@ -34,7 +34,7 @@ // Common code for all benchmark modes. class BenchmarkRunner { public: - enum ExecutionModeE { InProcess }; + enum ExecutionModeE { InProcess, SubProcess }; explicit BenchmarkRunner(const LLVMState &State, Benchmark::ModeE Mode, BenchmarkPhaseSelectorE BenchmarkPhaseSelector, Index: llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp =================================================================== --- llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp +++ llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp @@ -25,6 +25,16 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Program.h" +#include "llvm/Support/Signals.h" + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#endif // __linux__ namespace llvm { namespace exegesis { @@ -129,6 +139,168 @@ const ExecutableFunction Function; BenchmarkRunner::ScratchSpace *const Scratch; }; + +#ifdef __linux__ +// The following class implements a function executor that executes the +// benchmark code within a subprocess rather than within the main llvm-exegesis +// process. This allows for much more control over the execution context of the +// snippet, particularly in regards to memory. This class performs all the +// necessary functions to create the subprocess, execute the snippet in the +// subprocess, and report results/handle errors. +class SubProcessFunctionExecutorImpl + : public BenchmarkRunner::FunctionExecutor { +public: + SubProcessFunctionExecutorImpl(const LLVMState &State, + object::OwningBinary Obj, + const BenchmarkKey &Key) + : State(State), Function(State.createTargetMachine(), std::move(Obj)), + Key(Key) {} + +private: + enum ChildProcessExitCodeE { + CounterFDReadFailed = 1, + TranslatingCounterFDFailed + }; + + StringRef childProcessExitCodeToString(int ExitCode) const { + switch (ExitCode) { + case ChildProcessExitCodeE::CounterFDReadFailed: + return "Counter file descriptor read failed"; + case ChildProcessExitCodeE::TranslatingCounterFDFailed: + return "Translating counter file descriptor into a file descriptor in " + "the child process failed"; + default: + return "Child process returned with unknown exit code"; + } + } + + Error createSubProcessAndRunBenchmark( + 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 the write end of the pipe + close(PipeFiles[1]); + // Unregister handlers, signal handling is now handled through ptrace in + // the host process + llvm::sys::unregisterHandlers(); + prepareAndRunBenchmark(PipeFiles[0], Key); + // The child process terminates in the above function, so we should never + // get to this point. + llvm_unreachable("Child process didn't exit when expected."); + } + + const ExegesisTarget &ET = State.getExegesisTarget(); + auto CounterOrError = + ET.createCounter(CounterName, State, ParentOrChildPID); + + if (!CounterOrError) + return CounterOrError.takeError(); + + pfm::Counter *Counter = CounterOrError.get().get(); + + close(PipeFiles[0]); + + int CounterFileDescriptor = Counter->getFileDescriptor(); + ssize_t BytesWritten = + write(PipeFiles[1], &CounterFileDescriptor, sizeof(int)); + + if (BytesWritten != sizeof(int)) + return make_error("Writing peformance counter file descriptor " + "to child process failed: " + + Twine(strerror(errno))); + + if (ptrace(PTRACE_SEIZE, ParentOrChildPID, NULL, NULL) != 0) + return make_error("Failed to seize the child process: " + + Twine(strerror(errno))); + + int ChildStatus; + if (wait(&ChildStatus) == -1) { + return make_error( + "Waiting for the child process to complete failed: " + + Twine(strerror(errno))); + } + + if (WIFEXITED(ChildStatus)) { + int ChildExitCode = WEXITSTATUS(ChildStatus); + if (ChildExitCode == 0) { + // The child exited succesfully, read counter values and return + // success + CounterValues[0] = Counter->read(); + return Error::success(); + } + // The child exited, but not successfully + return make_error( + "Child benchmarking process exited with non-zero exit code: " + + childProcessExitCodeToString(ChildExitCode)); + } + + // An error was encountered running the snippet, process it + siginfo_t ChildSignalInfo; + if (ptrace(PTRACE_GETSIGINFO, ParentOrChildPID, NULL, &ChildSignalInfo) == + -1) { + return make_error("Getting signal info from the child failed: " + + Twine(strerror(errno))); + } + + return make_error( + "The benchmarking subprocess sent unexpected signal: " + + Twine(strsignal(ChildSignalInfo.si_signo))); + } + + [[noreturn]] void prepareAndRunBenchmark(int Pipe, + const BenchmarkKey &Key) const { + // The following occurs within the benchmarking subprocess + + int ParentCounterFileDescriptor = -1; + ssize_t BytesRead = read(Pipe, &ParentCounterFileDescriptor, sizeof(int)); + + if (BytesRead != sizeof(int)) { + exit(ChildProcessExitCodeE::CounterFDReadFailed); + } + + pid_t ParentPID = getppid(); + + int ParentPIDFD = syscall(SYS_pidfd_open, ParentPID, 0); + int CounterFileDescriptor = + syscall(SYS_pidfd_getfd, ParentPIDFD, ParentCounterFileDescriptor, 0); + + if (CounterFileDescriptor == -1) { + exit(ChildProcessExitCodeE::TranslatingCounterFDFailed); + } + + ioctl(CounterFileDescriptor, PERF_EVENT_IOC_RESET); + this->Function(nullptr); + ioctl(CounterFileDescriptor, PERF_EVENT_IOC_DISABLE); + + exit(0); + } + + Expected> + runWithCounter(StringRef CounterName) const override { + SmallVector Value(1, 0); + Error PossibleBenchmarkError = + createSubProcessAndRunBenchmark(CounterName, Value); + + if (PossibleBenchmarkError) { + return std::move(PossibleBenchmarkError); + } + + return Value; + } + + const LLVMState &State; + const ExecutableFunction Function; + const BenchmarkKey &Key; +}; +#endif // __linux__ } // namespace Expected> BenchmarkRunner::assembleSnippet( @@ -201,6 +373,14 @@ case ExecutionModeE::InProcess: return std::make_unique( State, std::move(ObjectFile), Scratch.get()); + case ExecutionModeE::SubProcess: +#ifdef __linux__ + return std::make_unique( + State, std::move(ObjectFile), Key); +#else + return make_error( + "The subprocess execution mode is only supported on Linux"); +#endif } llvm_unreachable("ExecutionMode is outside expected range"); } Index: llvm/tools/llvm-exegesis/llvm-exegesis.cpp =================================================================== --- llvm/tools/llvm-exegesis/llvm-exegesis.cpp +++ llvm/tools/llvm-exegesis/llvm-exegesis.cpp @@ -254,7 +254,11 @@ cl::cat(BenchmarkOptions), cl::values(clEnumValN(BenchmarkRunner::ExecutionModeE::InProcess, "inprocess", - "Executes the snippets within the same process")), + "Executes the snippets within the same process"), + clEnumValN(BenchmarkRunner::ExecutionModeE::SubProcess, + "subprocess", + "Spawns a subprocess for each snippet execution, " + "allows for the use of memory annotations")), cl::init(BenchmarkRunner::ExecutionModeE::InProcess)); static ExitOnError ExitOnErr("llvm-exegesis error: ");