BPF JIT Compiler
Once the verifier has proven a program safe, the JIT (Just-In-Time) compiler is what turns its architecture-neutral bytecode into native machine instructions so it runs at the speed of compiled kernel C rather than crawling through a software interpreter. Each CPU architecture ships its own backend —
arch/x86/net/bpf_jit_comp.cfor x86-64,arch/arm64/net/bpf_jit_comp.cfor arm64, and so on — but all are driven through the same entry point,bpf_int_jit_compile(), called frombpf_prog_select_runtime()right after verification (perkernel/bpf/core.c, v6.12 LTS). The translation is largely mechanical because the BPF instruction set and calling convention were deliberately designed to mirror native 64-bit ABIs: BPF’s argument registers map one-to-one onto the host’s argument registers, so most BPF instructions become a short, fixed x86-64 or arm64 instruction sequence with no register shuffling. The payoff is enormous — a JIT-compiled XDP program processes on the order of tens of millions of packets per second on a single core (Høiland-Jørgensen et al., CoNEXT ‘18). On most modern, security-hardened kernels the interpreter is compiled out entirely (CONFIG_BPF_JIT_ALWAYS_ON), making the JIT not an optimization but the execution engine.
This note covers the JIT as a translator and as kernel text: the per-architecture backend, the iterative image-sizing-then-emission loop, the BPF→native register mapping, the prologue/epilogue and tail-call handling, why translation is mechanical, the bpf_prog_pack huge-page packing of JITed images, and how BPF programs become first-class kernel symbols visible to perf and bpftool. The defensive side of the JIT — constant blinding, the W^X memory protections, the bpf_jit_limit DoS cap — lives in its sibling JIT Hardening and Constant Blinding. The choice between JIT and the software interpreter is covered in BPF Interpreter vs JIT.
Kernel version
All code paths and line references in this note are pinned to Linux 6.12 LTS (released 2024-11-17), read from the stable tree at tag
v6.12. Behavior on 6.18 LTS is believed materially unchanged for the mechanisms described, but specific 6.18 line numbers were not re-verified — treat 6.12 as the authoritative reference here.
Mental Model
Think of the JIT as a transliterator, not a translator. A general-purpose compiler (say, the one that built the kernel from C) does heavy optimization: register allocation, instruction scheduling, inlining, loop transformations. The BPF JIT does almost none of that. It walks the verified bytecode instruction by instruction and emits a short, fixed native sequence for each one — the BPF program already is a register machine with a small, RISC-like instruction set, and the verifier already did the hard analysis. The JIT’s only real cleverness is in sizing the output image (native instructions are variable-length on x86-64, and the size of a jump depends on how far it jumps, which depends on the sizes of the instructions in between — a chicken-and-egg problem solved by iterating to a fixpoint) and in emitting a correct prologue/epilogue that bridges BPF’s calling convention to the host ABI.
flowchart TB V["Verifier approves<br/>(safe bytecode + insn rewrites)"] SR["bpf_prog_select_runtime()<br/>(core.c)"] BLIND["bpf_jit_blind_constants()<br/>(optional: constant blinding,<br/>bytecode-level rewrite)"] subgraph JIT["bpf_int_jit_compile() (per-arch, e.g. arch/x86/net)"] SIZE["Pass 0: estimate<br/>64 bytes / BPF insn"] EMIT["Passes 1..N: emit native code,<br/>image SHRINKS each pass"] CONV{"image stopped<br/>shrinking?"} SIZE --> EMIT --> CONV CONV -->|no, < 20 passes| EMIT CONV -->|yes| DONE["final image"] end PACK["bpf_jit_binary_pack_alloc()<br/>(prog_pack: 2 MB huge page,<br/>RO+X executable copy)"] KSYM["bpf_prog_ksym_add()<br/>(register in kallsyms:<br/>bpf_prog_<tag>_<name>)"] RUN["prog->bpf_func points at<br/>native code; runs at near-native speed"] V --> SR --> BLIND --> SIZE DONE --> PACK --> KSYM --> RUN
The path from verified bytecode to running native code. What it shows: after the verifier approves a program, bpf_prog_select_runtime() optionally applies constant blinding (a bytecode-level rewrite, covered in the sibling note) and then calls the architecture’s bpf_int_jit_compile(), which sizes the image with a rough estimate and then re-emits it repeatedly until the image stops shrinking; the final native code is copied into a read-only-executable huge page and registered as a kernel symbol. The insight to take: there is no single “compile” step — the JIT iterates to a fixpoint because native instruction sizes depend on jump distances which depend on instruction sizes. And the output is not scratch memory: it is first-class kernel text, named in kallsyms and protected W^X like the rest of .text.
Where the JIT Sits in the Load Pipeline
A program reaches the JIT through bpf_prog_select_runtime() in kernel/bpf/core.c. After the verifier has run and (for programs with subprograms) done its preparation, this function decides whether the program runs as native code or interpreted bytecode. The relevant logic (v6.12, core.c):
struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err)
{
bool jit_needed = false;
...
if (IS_ENABLED(CONFIG_BPF_JIT_ALWAYS_ON) ||
bpf_prog_has_kfunc_call(fp))
jit_needed = true;
bpf_prog_select_func(fp); /* set interpreter entry as fallback */
...
fp = bpf_int_jit_compile(fp); /* <-- the per-arch JIT */
bpf_prog_jit_attempt_done(fp);
if (!fp->jited && jit_needed) {
*err = -ENOTSUPP; /* JIT mandatory but failed -> refuse */
...
}
}Two facts are load-bearing here. First, the JIT is best-effort by default: bpf_int_jit_compile() is documented to “always return a valid program” — if anything goes wrong (out of memory, an unsupported instruction, a blinding failure), it returns the unmodified, non-JITed program and the kernel falls back to the interpreter. Second, two situations make the JIT mandatory: when the kernel is built with CONFIG_BPF_JIT_ALWAYS_ON (standard on hardened distributions, and the configuration that lets the interpreter be compiled out entirely as a security measure), and when the program calls a kfunc (which the interpreter cannot dispatch). In those cases a JIT failure is fatal: the program is refused with -ENOTSUPP.
The bpf_int_jit_compile() function is __weak in the generic code and overridden by each architecture that has a JIT backend. The sysctl documentation lists the eBPF-JIT architectures as x86_64, x86_32, arm64, arm32, ppc64, ppc32, sparc64, mips64, s390x, riscv64, riscv32, loongarch64, and arc (per net.rst, v6.12). The rest of this note uses the x86-64 backend as the worked example, with arm64 as a contrast.
The Iterative Sizing Loop — Not Really “Two Passes”
It is common shorthand to say the JIT does “two passes: one to size the image, one to emit it.” That is a useful mental model but it is not what the code does. The x86-64 backend (bpf_int_jit_compile() in arch/x86/net/bpf_jit_comp.c, v6.12) runs an iterative convergence loop of up to MAX_PASSES (defined as 20) iterations.
The problem it is solving: on x86-64, instruction lengths are variable, and the encoding of a jump depends on its displacement. A jump to a target 100 bytes away uses a short 2-byte form; a jump 5,000 bytes away needs a 6-byte form. But the displacement itself depends on the total size of all the instructions between the jump and its target — which the JIT does not know until it has emitted them. This is a classic branch displacement fixpoint problem.
The kernel resolves it by over-estimating, then shrinking:
/* Before first pass, make a rough estimation of addrs[]:
* each BPF instruction is translated to less than 64 bytes
*/
for (proglen = 0, i = 0; i <= prog->len; i++) {
proglen += 64;
addrs[i] = proglen;
}
...
/* JITed image shrinks with every pass and the loop iterates
* until the image stops shrinking. Very large BPF programs
* may converge on the last pass. In such case do one more pass.
*/
for (pass = 0; pass < MAX_PASSES || image; pass++) {
...
proglen = do_jit(prog, addrs, image, rw_image, oldproglen, &ctx, padding);
...
if (proglen == oldproglen) {
/* converged: image stopped changing -> done */
}
oldproglen = proglen;
}Pass 0 assumes every BPF instruction needs at most 64 bytes of native code and fills the addrs[] table (the byte offset of each instruction in the output) with that pessimistic estimate. With this conservative addrs[], every jump is assumed to need its longest encoding, so the first emitted image is larger than necessary. Each subsequent pass uses the actual offsets measured in the previous pass, so jumps shrink to their true encoding, the whole image shrinks, and addrs[] is refined. The loop terminates when proglen == oldproglen — the image has reached a fixpoint and re-emitting it produces no change. The padding flag (enabled after PADDING_PASSES) inserts a few bytes of NOP padding to prevent a pathological case where an image could oscillate in size between passes and never converge.
Crucially, the JIT emits into a real buffer on every pass, not just the last. The early passes emit into a temporary read-write buffer (rw_image); only once the size has converged is the final image committed to its executable home. The extra_pass path supports re-JITing for BPF-to-BPF calls, where the addresses of callee subprograms are only known after they too have been JITed.
arm64 uses the same idea but a fixed two-build structure: build_body() is called once with a NULL image purely to compute the offset of each instruction, then the executable buffer is allocated and build_prologue()/build_body()/build_epilogue() are called to emit. The principle — measure, then emit at known offsets — is identical; only the loop shape differs.
BPF Registers → Native Registers: Why It’s Mechanical
The single most important reason the JIT is simple is that the BPF calling convention was designed in 2014 to match the native 64-bit ABIs. BPF has eleven registers: R0 (return value), R1–R5 (arguments), R6–R9 (callee-saved), and R10 (the read-only frame pointer). The x86-64 backend’s mapping (reg2hex[], v6.12) is:
static const int reg2hex[] = {
[BPF_REG_0] = 0, /* RAX */
[BPF_REG_1] = 7, /* RDI */
[BPF_REG_2] = 6, /* RSI */
[BPF_REG_3] = 2, /* RDX */
[BPF_REG_4] = 1, /* RCX */
[BPF_REG_5] = 0, /* R8 */
[BPF_REG_6] = 3, /* RBX callee saved */
[BPF_REG_7] = 5, /* R13 callee saved */
[BPF_REG_8] = 6, /* R14 callee saved */
[BPF_REG_9] = 7, /* R15 callee saved */
[BPF_REG_FP] = 5, /* RBP readonly */
[BPF_REG_AX] = 2, /* R10 temp register */
...
};Now compare against the System V AMD64 ABI — the calling convention Linux uses for ordinary kernel C functions. SysV passes the first six integer arguments in rdi, rsi, rdx, rcx, r8, r9, returns values in rax, and treats rbx, r12–r15 as callee-saved. Line up BPF’s mapping against it:
R1 → rdi,R2 → rsi,R3 → rdx,R4 → rcx,R5 → r8— these are exactly the first five SysV argument registers, in order.R0 → rax— exactly the SysV return register.R6–R9 → rbx, r13, r14, r15— all SysV callee-saved registers.
This alignment is not a coincidence; it is the whole point. Because BPF’s argument registers land on the host’s argument registers, a call from a BPF program to a kernel function (a helper or kfunc) needs no argument shuffling at all — the values are already in the right hardware registers, and the JIT emits a plain call instruction (emit_call() → opcode 0xE8 with a 32-bit relative displacement). A BPF_ALU64 | BPF_ADD | BPF_X instruction (R1 += R2) becomes a single x86-64 add rdi, rsi. A BPF_MOV64 becomes a mov. This is what “the translation is largely mechanical” means concretely: most BPF opcodes have a one-to-few native instruction expansion, looked up through small tables like simple_alu_opcodes[].
The arm64 backend (bpf2a64[], v6.12) makes the same design choice against the AArch64 procedure call standard: R0 → x7 (return), R1–R5 → x0–x4 (the first five argument registers), R6–R9 → x19–x22 (callee-saved), R10/FP → x25. Same principle, different ABI: arguments map to argument registers, callee-saved map to callee-saved, so translation stays mechanical.
There are a handful of registers the BPF architecture does not expose but the JIT needs internally: BPF_REG_AX (mapped to x86 r10 / arm64 x9) is a hidden scratch register used for constant blinding and a few rewrites, and AUX_REG (x86 r11) is a temporary for multi-step instruction expansions. R10, BPF’s frame pointer, maps to the host rbp/x25 and is read-only to the program — it points at the program’s 512-byte BPF stack, which the prologue carves out of the native stack.
Prologue, Epilogue, and Tail-Call Handling
Every JITed program begins with a prologue that establishes the native stack frame and bridges into the BPF execution model. The x86-64 emit_prologue() (v6.12) emits, in order:
emit_cfi(&prog, ...); /* CFI/kCFI type hash for indirect-call protection */
emit_nops(&prog, X86_PATCH_SIZE); /* 5-byte NOP: patch site for fentry/trampolines */
...
EMIT1(0x55); /* push rbp */
EMIT3(0x48, 0x89, 0xE5); /* mov rbp, rsp (set up frame pointer) */
/* X86_TAIL_CALL_OFFSET is here */
EMIT_ENDBR(); /* endbr64: CET indirect-branch landing pad */
if (stack_depth)
EMIT3_off32(0x48, 0x81, 0xEC, round_up(stack_depth, 8)); /* sub rsp, N */Three things in this prologue are worth understanding because they connect to other kernel features:
-
The 5-byte NOP patch site. Right at the entry the JIT emits a 5-byte run of NOPs (
X86_PATCH_SIZE). This is a patch target: when an fexit program or a BPF trampoline wants to hook this program, it atomically overwrites those NOPs with acallto the trampoline usingtext_poke. Pre-reserving the exact bytes is what makes attach/detach cheap and atomic — no recompilation. -
endbr64and CFI.EMIT_ENDBR()emits the Intel CET (Control-flow Enforcement Technology) “end branch” landing pad, andemit_cfi()emits a kernel Control Flow Integrity type hash. Because JITed programs are entered through indirect calls, they need the same control-flow-integrity markings as ordinary kernel functions or CET/kCFI would trap the call. This is concrete evidence that JITed BPF is treated as genuine kernel text, not a sandboxed blob. -
The BPF stack.
sub rsp, stack_depthreserves the program’s stack belowrbp. The verifier computedstack_depth(≤ 512 bytes) by tracking the highest stack slot the program touches, so the JIT reserves exactly that much.
For programs that use tail calls, the prologue additionally manages a tail-call counter. A tail call jumps from one program into another without returning, and to prevent unbounded chains the kernel enforces MAX_TAIL_CALL_CNT (33). emit_prologue_tail_call() initializes a counter (zeroing rax at the entry of a tail-call chain) and the tail-call emission code increments and bounds-checks it:
EMIT4(0x48, 0x83, 0x38, MAX_TAIL_CALL_CNT); /* cmp qword ptr [rax], MAX_TAIL_CALL_CNT */
/* if (*tcc_ptr)++ >= MAX_TAIL_CALL_CNT -> fall through, do NOT tail-call */The tail call itself does not use a call instruction — it patches the program-array map lookup, skips the callee’s prologue (jumping past X86_TAIL_CALL_OFFSET so the stack frame is reused rather than re-established), and jmps into the callee’s body. That offset is precisely why the prologue records “X86_TAIL_CALL_OFFSET is here” after the frame setup: tail-callees must enter after the push rbp; mov rbp,rsp to share the caller’s frame.
The epilogue restores callee-saved registers, tears down the frame (leave), and returns (ret) with the program’s return value already in rax (BPF R0).
JITed Programs as First-Class Kernel Text
A JITed program is not anonymous executable memory — it is registered with the kernel’s symbol table so that profilers, tracers, and bpftool can attribute samples and stack frames to it. After compilation, bpf_prog_ksym_set_addr() and bpf_prog_ksym_set_name() (v6.12, core.c) populate a bpf_ksym with the program’s start address, length, and a synthesized name of the form bpf_prog_<tag>_<name> where the tag is the program’s 8-byte (16-hex-digit) content hash (e.g. bpf_prog_6deef7357e7b4530_xdp_drop), and bpf_prog_ksym_add() inserts it into the kallsyms index.
The visibility of these symbols is gated by the bpf_jit_kallsyms sysctl (per net.rst, v6.12):
bpf_jit_kallsyms
0 - disable JIT kallsyms export (default value)
1 - enable JIT kallsyms export for privileged users only
Uncertain
The default for
bpf_jit_kallsyms. The v6.12net.rstdocuments the value semantics (0 = disabled, 1 = privileged-only) and labels 0 as “(default value)”, but distributions frequently ship a different runtime default, and some kernel configs enable it implicitly. Reason: documented default vs. distro default routinely diverge for BPF sysctls. To resolve: read/proc/sys/net/core/bpf_jit_kallsymson the target system.#uncertain
When enabled, the practical consequence is that perf top shows bpf_prog_<tag>_<name> frames instead of opaque 0xffffffffc0xxxxxx addresses, and bpftool prog can correlate a loaded program with its in-kernel symbol. This is observability; the security tradeoff of exposing JIT addresses — and why turning on bpf_jit_harden force-disables bpf_jit_kallsyms — is covered in JIT Hardening and Constant Blinding.
bpf_prog_pack — Packing JITed Code into Huge Pages
Naively, each JITed program would get its own page (or more) of executable memory via vmalloc. That wastes memory (most BPF programs are tiny, far smaller than a 4 KB page) and, more importantly, fragments the instruction TLB: scattering many small programs across many 4 KB pages means each lives in its own TLB entry, and on a busy system running hundreds of BPF programs the iTLB thrashes. The bpf_prog_pack allocator (introduced in 5.18; the design comment lives in v6.12 core.c) fixes both:
/*
* Most BPF programs are pretty small. Allocating a whole page for each
* program is sometimes a waste. Many small bpf programs also add pressure
* to instruction TLB. To solve this issue, we introduce a BPF program pack
* allocator. The prog_pack allocator uses HPAGE_PMD_SIZE page (2MB on x86)
* to host BPF programs.
*/
#define BPF_PROG_CHUNK_SHIFT 6
#define BPF_PROG_CHUNK_SIZE (1 << BPF_PROG_CHUNK_SHIFT) /* 64 bytes */
#define BPF_PROG_PACK_SIZE (SZ_2M * num_possible_nodes()) /* 2MB per NUMA node */The mechanism: the allocator reserves a 2 MB region (one PMD-sized huge page on x86-64), maps it as a single huge-page mapping so all the programs inside it share one iTLB entry, and sub-allocates 64-byte chunks from it using a bitmap (bpf_prog_pack_alloc()). Many small programs share one huge page; the iTLB pressure collapses from N entries to roughly one. The reserved space is pre-filled with illegal/trap instructions (bpf_fill_ill_insns → 0xcc INT3 on x86) so that any control flow straying into an unused gap traps immediately rather than executing garbage.
Because the executable copy lives in a shared, read-only huge page, the JIT cannot simply emit into it. The bpf_jit_binary_pack_alloc() path therefore allocates a pair: a read-write scratch buffer the JIT emits into, and the read-only-executable home in the prog_pack. After emission the finished bytes are copied into the executable region with bpf_arch_text_copy() (which uses text_poke under the hood to write to RO+X memory), and the RW scratch is freed. The pack page itself is set to read-only-and-executable at allocation time via set_memory_rox() — this is the W^X (write XOR execute) guarantee that the page is never simultaneously writable and executable. The defensive significance of W^X belongs to JIT Hardening and Constant Blinding; here the point is that huge-page packing and W^X are inseparable — the same set_memory_rox() call that hardens the page is what lets many programs share it.
Near-Native Performance
The reason all of this exists is speed. The original cBPF JIT (Eric Dumazet, 2011, LWN 437981) demonstrated the core idea — “every BPF instruction maps to a straightforward x86 instruction sequence … the accumulator and index registers are stored directly in processor registers” — and measured roughly a 50 ns saving per filter invocation versus the interpreter. For eBPF the stakes are far higher because the programs sit on hot paths. The XDP data path runs a BPF program on every received packet, in the NIC driver, before the kernel allocates an sk_buff. The XDP paper (Høiland-Jørgensen et al., CoNEXT ‘18) reports XDP sustaining 24 million packets per second on a single core for a drop workload — about a fivefold improvement over the fastest in-stack processing mode. That number is only achievable because each packet is processed by native code, not interpreted bytecode: at 24 Mpps a single core has roughly 40 ns per packet, a budget the interpreter’s per-instruction dispatch overhead would blow instantly.
This is also why hardened kernels set CONFIG_BPF_JIT_ALWAYS_ON and compile the interpreter out: on those systems there is no interpreter to fall back to, so the JIT is mandatory, and the absence of an interpreter removes a class of speculation attack surface as a bonus. The performance story and the security story converge on the same answer — always JIT.
Failure Modes and Common Misunderstandings
- “The JIT optimizes my program.” It does not, beyond branch-displacement minimization. There is no instruction scheduling, no inlining of helpers (helpers are real
calls), no constant folding. All optimization happens in the LLVM/Clang frontend that produced the bytecode, and in the verifier’s instruction rewrites. If your hot loop is slow, the JIT will not save you — restructure the BPF code. - “
bpf_int_jit_compilefailing means my program is rejected.” Only if the JIT is mandatory (CONFIG_BPF_JIT_ALWAYS_ONor a kfunc call). Otherwise a JIT failure silently falls back to the interpreter, andprog->jitedis left 0. Check it:bpftool prog showreports whether a program is JITed. - Confusing “two passes” with the real loop. As shown above, the x86-64 JIT iterates up to 20 times to a fixpoint. A program that fails to converge (extremely rare, and guarded against by the padding mechanism) would be rejected — but in practice convergence happens within a handful of passes.
- Assuming the JIT image is writable. It is W^X. You cannot patch a JITed program by writing to
prog->bpf_func; all modifications (fentry attach, tail-call patching) go throughtext_poke/bpf_arch_text_pokeagainst RO+X memory. -EPERMat load that looks like a verifier error but isbpf_jit_limit. If the global JIT memory budget is exhausted, an unprivileged load fails with-EPERMeven though the program is perfectly valid. This is the DoS cap, covered in JIT Hardening and Constant Blinding.
Alternatives and When to Choose Them
The only real alternative execution engine is the software interpreter (___bpf_prog_run in core.c), a giant computed-goto dispatch loop over the bytecode. It is portable (works on architectures with no JIT backend), needs no executable-memory allocation, and is the fallback when the JIT is disabled or fails. Its cost is roughly an order of magnitude in per-instruction overhead and a larger speculative-execution attack surface — which is why hardened kernels remove it. The full comparison is in BPF Interpreter vs JIT.
It is worth contrasting the BPF JIT against a language-runtime JIT such as CPython’s tier-2 copy-and-patch JIT. The contrast is instructive precisely because they share a name and almost nothing else. CPython’s JIT is adaptive and speculative: it traces hot bytecode at runtime, specializes on observed types, emits guards, and can deoptimize back to the interpreter when a guard fails — because Python is dynamically typed and the runtime cannot know in advance what types flow through a loop. The BPF JIT is the opposite: it is static, eager, and total. It compiles the entire program once, before first execution, with no speculation, no specialization, no deoptimization, and no guards — because the verifier has already proven every type and bound statically, so there is nothing to discover at runtime. CPython JITs to recover type information dynamic typing threw away; BPF JITs because the type information is already proven, and the only job left is transliteration. They sit at opposite ends of the JIT design space.
Production Notes
In production, three JIT-adjacent observations recur. First, always confirm programs are actually JITed — bpftool prog show lists jited/not jited; an accidentally interpreted XDP program on a hardened kernel will instead fail to load (mandatory JIT) or, on a non-hardened kernel, silently run an order of magnitude slower. Second, perf attribution depends on bpf_jit_kallsyms — without it, BPF-heavy workloads show large unattributed [unknown] time in flame graphs; Cilium and Katran deployments routinely enable kallsyms export on trusted hosts for exactly this reason. Third, iTLB pressure was a real scaling problem before bpf_prog_pack — environments running hundreds of small BPF programs (large Cilium clusters, dense tracing deployments) saw measurable iTLB-miss overhead that the huge-page packing allocator (5.18+) largely eliminated; on a 6.12/6.18 kernel this is handled transparently, but it explains why the allocator exists.
See Also
- BPF Interpreter vs JIT — the software interpreter, when it is used, and why hardened kernels remove it
- JIT Hardening and Constant Blinding — the defensive sibling: JIT-spray, constant blinding,
bpf_jit_limit, the W^X and kallsyms security tradeoffs - eBPF Verifier — what runs before the JIT and proves the program safe (and computes
stack_depth) - BPF Virtual Machine and Registers — the eleven-register BPF machine the JIT maps onto hardware
- The BPF Calling Convention and Stack — why the register mapping makes helper/kfunc calls free of argument shuffling
- BPF Tail Calls — the prog-array jump mechanism the prologue’s tail-call offset supports
- fentry fexit and BPF Trampolines — what patches the 5-byte prologue NOP site
- The CPython JIT Compiler — a contrasting runtime JIT: adaptive/speculative vs. BPF’s static/total compilation
- XDP (eXpress Data Path) — the data path whose tens-of-Mpps throughput the JIT makes possible
- Linux eBPF MOC — the parent map