Skip to content

Commit 4d618b5

Browse files
[llvm-exegesis] Introduce Subprocess Executor Mode
This patch introduces the subprocess executor mode. Currently, this new mode doesn't do anything fancy, just executing the same code that the inprocess executor would do, but within a subprocess. This sets up the ability to add in many more memory-related features in the future.
1 parent 6f654a8 commit 4d618b5

File tree

6 files changed

+230
-2
lines changed

6 files changed

+230
-2
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# REQUIRES: exegesis-can-execute-x86_64, exegesis-can-measure-latency, x86_64-linux
2+
3+
# RUN: llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=latency -snippets-file=%s -execution-mode=subprocess | FileCheck %s
4+
5+
# CHECK: error: 'Child benchmarking process exited with non-zero exit code: Child process returned with unknown exit code'
6+
7+
movl $60, %eax
8+
movl $127, %edi
9+
syscall
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# REQUIRES: exegesis-can-execute-x86_64, exegesis-can-measure-latency, x86_64-linux
2+
3+
# RUN: llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=latency -snippets-file=%s -execution-mode=subprocess | FileCheck %s
4+
5+
# CHECK: error: 'The benchmarking subprocess sent unexpected signal: Segmentation fault'
6+
7+
# LLVM-EXEGESIS-DEFREG RBX 0
8+
movq (%rbx), %rax
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# REQUIRES: exegesis-can-execute-x86_64, exegesis-can-measure-latency, x86_64-linux
2+
3+
# RUN: llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=latency -snippets-file=%s -execution-mode=subprocess | FileCheck %s
4+
5+
# CHECK: measurements:
6+
# CHECK-NEXT: value: {{.*}}, per_snippet_value: {{.*}}
7+
8+
# LLVM-EXEGESIS-DEFREG XMM1 42
9+
# LLVM-EXEGESIS-DEFREG XMM2 42
10+
# LLVM-EXEGESIS-DEFREG XMM3 42
11+
vhaddps %xmm2, %xmm2, %xmm3

llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@
2525
#include "llvm/Support/FileSystem.h"
2626
#include "llvm/Support/MemoryBuffer.h"
2727
#include "llvm/Support/Program.h"
28+
#include "llvm/Support/Signals.h"
29+
30+
#ifdef __linux__
31+
#ifdef HAVE_LIBPFM
32+
#include <perfmon/perf_event.h>
33+
#endif
34+
#include <sys/mman.h>
35+
#include <sys/ptrace.h>
36+
#include <sys/syscall.h>
37+
#include <sys/wait.h>
38+
#include <unistd.h>
39+
#endif // __linux__
2840

2941
namespace llvm {
3042
namespace exegesis {
@@ -129,6 +141,182 @@ class InProcessFunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor {
129141
const ExecutableFunction Function;
130142
BenchmarkRunner::ScratchSpace *const Scratch;
131143
};
144+
145+
#ifdef __linux__
146+
// The following class implements a function executor that executes the
147+
// benchmark code within a subprocess rather than within the main llvm-exegesis
148+
// process. This allows for much more control over the execution context of the
149+
// snippet, particularly with regard to memory. This class performs all the
150+
// necessary functions to create the subprocess, execute the snippet in the
151+
// subprocess, and report results/handle errors.
152+
class SubProcessFunctionExecutorImpl
153+
: public BenchmarkRunner::FunctionExecutor {
154+
public:
155+
SubProcessFunctionExecutorImpl(const LLVMState &State,
156+
object::OwningBinary<object::ObjectFile> Obj,
157+
const BenchmarkKey &Key)
158+
: State(State), Function(State.createTargetMachine(), std::move(Obj)),
159+
Key(Key) {}
160+
161+
private:
162+
enum ChildProcessExitCodeE {
163+
CounterFDReadFailed = 1,
164+
TranslatingCounterFDFailed
165+
};
166+
167+
StringRef childProcessExitCodeToString(int ExitCode) const {
168+
switch (ExitCode) {
169+
case ChildProcessExitCodeE::CounterFDReadFailed:
170+
return "Counter file descriptor read failed";
171+
case ChildProcessExitCodeE::TranslatingCounterFDFailed:
172+
return "Translating counter file descriptor into a file descriptor in "
173+
"the child process failed. This might be due running an older "
174+
"Linux kernel that doesn't support the pidfd_getfd system call "
175+
"(anything before Linux 5.6).";
176+
default:
177+
return "Child process returned with unknown exit code";
178+
}
179+
}
180+
181+
Error createSubProcessAndRunBenchmark(
182+
StringRef CounterName, SmallVectorImpl<int64_t> &CounterValues) const {
183+
int PipeFiles[2];
184+
int PipeSuccessOrErr = pipe(PipeFiles);
185+
if (PipeSuccessOrErr != 0) {
186+
return make_error<Failure>(
187+
"Failed to create a pipe for interprocess communication between "
188+
"llvm-exegesis and the benchmarking subprocess");
189+
}
190+
191+
pid_t ParentOrChildPID = fork();
192+
if (ParentOrChildPID == 0) {
193+
// We are in the child process, close the write end of the pipe
194+
close(PipeFiles[1]);
195+
// Unregister handlers, signal handling is now handled through ptrace in
196+
// the host process
197+
llvm::sys::unregisterHandlers();
198+
prepareAndRunBenchmark(PipeFiles[0], Key);
199+
// The child process terminates in the above function, so we should never
200+
// get to this point.
201+
llvm_unreachable("Child process didn't exit when expected.");
202+
}
203+
204+
const ExegesisTarget &ET = State.getExegesisTarget();
205+
auto CounterOrError =
206+
ET.createCounter(CounterName, State, ParentOrChildPID);
207+
208+
if (!CounterOrError)
209+
return CounterOrError.takeError();
210+
211+
pfm::Counter *Counter = CounterOrError.get().get();
212+
213+
close(PipeFiles[0]);
214+
215+
int CounterFileDescriptor = Counter->getFileDescriptor();
216+
ssize_t BytesWritten =
217+
write(PipeFiles[1], &CounterFileDescriptor, sizeof(int));
218+
219+
if (BytesWritten != sizeof(int))
220+
return make_error<Failure>("Writing peformance counter file descriptor "
221+
"to child process failed: " +
222+
Twine(strerror(errno)));
223+
224+
if (ptrace(PTRACE_SEIZE, ParentOrChildPID, NULL, NULL) != 0)
225+
return make_error<Failure>("Failed to seize the child process: " +
226+
Twine(strerror(errno)));
227+
228+
int ChildStatus;
229+
if (wait(&ChildStatus) == -1) {
230+
return make_error<Failure>(
231+
"Waiting for the child process to complete failed: " +
232+
Twine(strerror(errno)));
233+
}
234+
235+
if (WIFEXITED(ChildStatus)) {
236+
int ChildExitCode = WEXITSTATUS(ChildStatus);
237+
if (ChildExitCode == 0) {
238+
// The child exited succesfully, read counter values and return
239+
// success
240+
CounterValues[0] = Counter->read();
241+
return Error::success();
242+
}
243+
// The child exited, but not successfully
244+
return make_error<SnippetCrash>(
245+
"Child benchmarking process exited with non-zero exit code: " +
246+
childProcessExitCodeToString(ChildExitCode));
247+
}
248+
249+
// An error was encountered running the snippet, process it
250+
siginfo_t ChildSignalInfo;
251+
if (ptrace(PTRACE_GETSIGINFO, ParentOrChildPID, NULL, &ChildSignalInfo) ==
252+
-1) {
253+
return make_error<Failure>("Getting signal info from the child failed: " +
254+
Twine(strerror(errno)));
255+
}
256+
257+
return make_error<SnippetCrash>(
258+
"The benchmarking subprocess sent unexpected signal: " +
259+
Twine(strsignal(ChildSignalInfo.si_signo)));
260+
}
261+
262+
[[noreturn]] void prepareAndRunBenchmark(int Pipe,
263+
const BenchmarkKey &Key) const {
264+
// The following occurs within the benchmarking subprocess
265+
266+
int ParentCounterFileDescriptor = -1;
267+
ssize_t BytesRead = read(Pipe, &ParentCounterFileDescriptor, sizeof(int));
268+
269+
if (BytesRead != sizeof(int)) {
270+
exit(ChildProcessExitCodeE::CounterFDReadFailed);
271+
}
272+
273+
// Make sure the following two syscalls are defined on the platform that
274+
// we're building on as they were introduced to the kernel fairly recently
275+
// (v5.6 for the second one).
276+
#if defined SYS_pidfd_open && defined SYS_pidfd_getfd
277+
pid_t ParentPID = getppid();
278+
279+
int ParentPIDFD = syscall(SYS_pidfd_open, ParentPID, 0);
280+
int CounterFileDescriptor =
281+
syscall(SYS_pidfd_getfd, ParentPIDFD, ParentCounterFileDescriptor, 0);
282+
#else
283+
int CounterFileDescriptor = 0;
284+
exit(ChildProcessExitCodeE::TranslatingCounterFDFailed);
285+
#endif
286+
287+
if (CounterFileDescriptor == -1) {
288+
exit(ChildProcessExitCodeE::TranslatingCounterFDFailed);
289+
}
290+
291+
#ifdef HAVE_LIBPFM
292+
ioctl(CounterFileDescriptor, PERF_EVENT_IOC_RESET);
293+
#endif
294+
this->Function(nullptr);
295+
#ifdef HAVE_LIBPFM
296+
ioctl(CounterFileDescriptor, PERF_EVENT_IOC_DISABLE);
297+
#endif
298+
299+
exit(0);
300+
}
301+
302+
Expected<llvm::SmallVector<int64_t, 4>>
303+
runWithCounter(StringRef CounterName) const override {
304+
SmallVector<int64_t, 4> Value(1, 0);
305+
Error PossibleBenchmarkError =
306+
createSubProcessAndRunBenchmark(CounterName, Value);
307+
308+
if (PossibleBenchmarkError) {
309+
return std::move(PossibleBenchmarkError);
310+
}
311+
312+
return Value;
313+
}
314+
315+
const LLVMState &State;
316+
const ExecutableFunction Function;
317+
const BenchmarkKey &Key;
318+
};
319+
#endif // __linux__
132320
} // namespace
133321

134322
Expected<SmallString<0>> BenchmarkRunner::assembleSnippet(
@@ -201,6 +389,14 @@ BenchmarkRunner::createFunctionExecutor(
201389
case ExecutionModeE::InProcess:
202390
return std::make_unique<InProcessFunctionExecutorImpl>(
203391
State, std::move(ObjectFile), Scratch.get());
392+
case ExecutionModeE::SubProcess:
393+
#ifdef __linux__
394+
return std::make_unique<SubProcessFunctionExecutorImpl>(
395+
State, std::move(ObjectFile), Key);
396+
#else
397+
return make_error<Failure>(
398+
"The subprocess execution mode is only supported on Linux");
399+
#endif
204400
}
205401
llvm_unreachable("ExecutionMode is outside expected range");
206402
}

llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ namespace exegesis {
3434
// Common code for all benchmark modes.
3535
class BenchmarkRunner {
3636
public:
37-
enum ExecutionModeE { InProcess };
37+
enum ExecutionModeE { InProcess, SubProcess };
3838

3939
explicit BenchmarkRunner(const LLVMState &State, Benchmark::ModeE Mode,
4040
BenchmarkPhaseSelectorE BenchmarkPhaseSelector,

llvm/tools/llvm-exegesis/llvm-exegesis.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,11 @@ static cl::opt<BenchmarkRunner::ExecutionModeE> ExecutionMode(
254254
cl::cat(BenchmarkOptions),
255255
cl::values(clEnumValN(BenchmarkRunner::ExecutionModeE::InProcess,
256256
"inprocess",
257-
"Executes the snippets within the same process")),
257+
"Executes the snippets within the same process"),
258+
clEnumValN(BenchmarkRunner::ExecutionModeE::SubProcess,
259+
"subprocess",
260+
"Spawns a subprocess for each snippet execution, "
261+
"allows for the use of memory annotations")),
258262
cl::init(BenchmarkRunner::ExecutionModeE::InProcess));
259263

260264
static ExitOnError ExitOnErr("llvm-exegesis error: ");

0 commit comments

Comments
 (0)