Demystify Ebpf Jit Compiler
Demystify Ebpf Jit Compiler
© 2018 NETRONOME
Agenda
Ø What is JIT Compiler?
Ø What is eBPF JIT Compiler?
Ø eBPF JIT Compiler – Verification Stage
Ø eBPF JIT Compiler – Code Gen Stage
Ø Netronome Flow Processor (NFP) Code Gen Back-end
Ø eBPF JIT Compiler Emerging Features
© 2018 NETRONOME 2
Types of Compilers
A Traditional Static Compiler
Ø Translates source language to machine instructions before execution
Ø Good fit for statically typed language
Ø When people mention compiler they might actually mean toolchain
Ø Compilation → Assembling → Linking → Loading → Execution
Before Execution
Source IR IR ARM
Language (Intermediate Optimization
Representation) Verification MIPS
PPC
4
© 2018 NETRONOME
Types of Compilers (Continued)
JIT Compiler
Ø Just In Time During Execution Runtime
Ø Machine instruction generation depends on runtime information
• Dynamically typed language
• Portable without re-compilation
• Low level IR to expose low level information for unified check
Before Execution x86
Source IR IR Arm
Language (Intermediate Optimization
Representation) Verification MIPS
PPC
5
© 2018 NETRONOME
Types of Compilers (Continued)
JIT Compiler
Ø Just In Time During Execution Runtime
Ø Machine instruction generation depends on runtime information
• Dynamically typed language
• Portable without re-compilation
• Low level IR to expose low level information for unified check
Before Execution x86
Source IR IR Arm
Language (Intermediate Optimization
Representation) Verification MIPS
PPC
6
© 2018 NETRONOME
What is a JIT Compiler?
int main(int argc, char *argv[]) {
Ø How does a JIT // mov eax, 0
compiler really look unsigned char mov[] = {0xb8, 0x00, 0x00, 0x00, 0x00};
// ret
like? Here is a very unsigned char ret[] = {0xc3};
simple example. int num = atoi(argv[1]);
memcpy(&mov[1], &num, 4);
return func();
}
© 2018 NETRONOME 7
What is a JIT Compiler?
Ø How does a JIT int main(int argc, char *argv[]) {
// mov eax, 0
compiler really look unsigned char mov[] = {0xb8, 0x00, 0x00, 0x00, 0x00};
// ret
like? Here is a very unsigned char ret[] = {0xc3};
Any Program
simple example.
int num = atoi(argv[1]);
memcpy(&mov[1], &num, 4);
Allocate
void *mem = mmap(NULL, sizeof(mov) +sizeof(ret),
PROT_WRITE | PROT_EXEC,
MAP_ANON | MAP_PRIVATE, -1, 0);
return func();
}
© 2018 NETRONOME 8
What is a JIT Compiler?
Ø How does a JIT int main(int argc, char *argv[]) {
// mov eax, 0
compiler really look unsigned char mov[] = {0xb8, 0x00, 0x00, 0x00, 0x00};
// ret
like? Here is a very unsigned char ret[] = {0xc3};
Any Program
simple example.
int num = atoi(argv[1]);
memcpy(&mov[1], &num, 4); Write
void *mem = mmap(NULL, sizeof(mov) +sizeof(ret), Instruction
PROT_WRITE | PROT_EXEC,
MAP_ANON | MAP_PRIVATE, -1, 0);
return func();
}
© 2018 NETRONOME 9
What is a JIT Compiler?
Ø How does a JIT int main(int argc, char *argv[]) {
// mov eax, 0
compiler really look unsigned char mov[] = {0xb8, 0x00, 0x00, 0x00, 0x00};
// ret
like? Here is a very unsigned char ret[] = {0xc3};
Any Program
simple example.
int num = atoi(argv[1]);
memcpy(&mov[1], &num, 4); Write
void *mem = mmap(NULL, sizeof(mov) +sizeof(ret), Instruction
PROT_WRITE | PROT_EXEC,
MAP_ANON | MAP_PRIVATE, -1, 0);
return func();
}
© 2018 NETRONOME 10
What is a JIT Compiler?
Ø How does a JIT int main(int argc, char *argv[]) {
// mov eax, 0
compiler really look unsigned char mov[] = {0xb8, 0x00, 0x00, 0x00, 0x00};
// ret
like? Here is a very unsigned char ret[] = {0xc3};
Any Program
simple example.
int num = atoi(argv[1]);
memcpy(&mov[1], &num, 4); Jump &
void *mem = mmap(NULL, sizeof(mov) +sizeof(ret), Execute
PROT_WRITE | PROT_EXEC,
MAP_ANON | MAP_PRIVATE, -1, 0);
return func();
}
© 2018 NETRONOME 11
What is a JIT Compiler - Summary
Ø Generate machine instructions at runtime, write them to executable memory and
execute them there directly
• No assembling stage, direct encoding generation
• No linking stage, direct absolute address generation
Ø Memory region allocated should be protected
• Very risky to leave it as both executable and writable
• Classic buffer overflow writing to stack could be seen as JIT compilation
Ø Runtime overhead
• Only JIT compiles hot code, trace compilation etc.
© 2018 NETRONOME 12
What is an eBPF JIT Compiler?
Ø The whole idea is to run user-supplied
programs inside kernel
Ø Why not kernel module .ko? Kernel Space
• C is permissive, pointer is exploitable
© 2018 NETRONOME 13
What is an eBPF JIT Compiler?
eBPF representation
Ø Designed to be set of instructions and are close to x86-64, AArch64 etc.
Ø 64-bit instruction encoding and 64-bit register with 32-bit sub-register
Ø Usual data manipulation instructions and control transfer instructions
Ø Support both interpretation execution and JIT execution
Ø KERNEL/Documentation/bpf/bpf_design_QA.rst
© 2018 NETRONOME 14
What is an eBPF JIT Compiler?
eBPF software stack for JIT execution
Ø Compile C into eBPF sequence
Ø Check eBPF sequence
Ø JIT compile and execute the sequence
© 2018 NETRONOME 15
What is an eBPF JIT Compiler?
Ø Components in yellow are sitting inside kernel space
Ø Verifier needs to walk instructions, quite a few information are collected and
shared with architecture code generation back-ends
Ø They work closely, and form an eBPF JIT compiler as a whole
© 2018 NETRONOME 16
eBPF JIT Compiler - Verification Stage
Ø Control flow check
• No function call to an unknown function
• No fall through from one function to the next one
• No jump destination is out of range
• No unreachable instruction
• No loop
© 2018 NETRONOME 17
eBPF JIT Compiler - Verification Stage
Ø Individual instruction check based on static information
• Divide by zero
• Shifts with invalid shifting amount
• Invalid stack access (unaligned, out of range etc)
if ((opcode == BPF_LSH || opcode == BPF_RSH || /* BPF program can access up to 512 bytes of stack space. */
opcode == BPF_ARSH) && BPF_SRC(insn->code) == BPF_K) { #define MAX_BPF_STACK 512
int size = BPF_CLASS(insn->code) == BPF_ALU64 ? 64 : 32;
if (insn->imm < 0 || insn->imm >= size) { if (off >= 0 || off < -MAX_BPF_STACK) {
verbose(env, "invalid shift %d\n", insn->imm); verbose(env, "invalid stack off=%d size=%d\n", off, size);
return -EINVAL; return -EACCES;
} }
}
© 2018 NETRONOME 18
eBPF JIT Compiler - Verification Stage
Ø Individual instruction check based on dynamic information
• Value range based checks. For example, out of range access to packet data
• Register status based checks. For example, read from uninitialized register
mov r0, r2
exit
• Stack status based checks. For example, a corruption spilled pointer on stack
*(u64 *)(r10, -8) = r1
/* mess up with R1 pointer on stack */
*(u8 *)(r10, -7) = 0x23
/* fill back into R0 should fail */
r0 = *(u64 *)(r10, -8)
exit
© 2018 NETRONOME 19
eBPF JIT Compiler - Verification Stage
Ø Kernel space pointer leak checks under unprivileged mode
• Such information is highly useful to an attacker once leaked to userspace
• No pointer arithmetic and comparison
• No store back to user space accessible storage, like map, packet etc.
• https://lwn.net/Articles/660331/ https://lkml.org/lkml/2015/10/5/687
"unpriv: return pointer", "unpriv: spill/fill of ctx 2",
"unpriv: add const to pointer", "unpriv: spill/fill of ctx 3",
"unpriv: add pointer to pointer", "unpriv: spill/fill of ctx 4",
"unpriv: neg pointer", "unpriv: spill/fill of different pointers stx",
"unpriv: cmp pointer with const", "unpriv: spill/fill of different pointers ldx",
"unpriv: cmp pointer with pointer", "unpriv: write pointer into map elem value",
"unpriv: check that printk is disallowed", "unpriv: partial copy of pointer",
"unpriv: pass pointer to helper function", "unpriv: pass pointer to tail_call",
"unpriv: indirectly pass pointer on stack to helper function", "unpriv: cmp map pointer with zero",
"unpriv: mangle pointer on stack 1", "unpriv: write into frame pointer",
"unpriv: mangle pointer on stack 2", "unpriv: spill/fill frame pointer",
"unpriv: read pointer from stack in small chunks", "unpriv: cmp of frame pointer",
"unpriv: write pointer into ctx", "unpriv: adding of fp",
"unpriv: spill/fill of ctx", "unpriv: cmp of stack pointer",
© 2018 NETRONOME 20
eBPF JIT Compiler - Verification Stage
Ø Any list for all supported verifications?
• KERNEL/toos/testing/selftests/bpf/test_verifier.c is a good reference
• ~900 tests
• Tests are categorized to some extent using prefix, for example verifications related
with another major feature bpf-to-bpf function could be listed using
cat tools/testing//selftestsbpf/test_verifier.c | grep "\"calls:"
"calls: basic sanity",
"calls: not on unpriviledged",
"calls: div by 0 in subprog",
"calls: multiple ret types in subprog 1",
"calls: multiple ret types in subprog 2",
"calls: overlapping caller/callee",
…
© 2018 NETRONOME 21
Verification Stage - Algorithms
Ø Control flow checks
• Function partition
• Depth first walk to detect loop and unreachable instructions
© 2018 NETRONOME 22
Verification Stage - Algorithms
Ø Control flow checks
• Function partition
• Depth first walk to detect loop and unreachable instructions
© 2018 NETRONOME 23
Verification Stage - Algorithms
Ø Control flow checks
• Function partition
• Depth first walk to detect loop and unreachable instructions
© 2018 NETRONOME 24
Algorithm - Function Partition
Ø Input eBPF sequence is a sequence of functions
Ø Partition the sequence to make them explicit
0: r3 += 1 0: r3 += 1
6: r0 = r4 6: r0 = r4
8: r2 += r3 8: r2 += r3
call destination = insn_index + imm + 1 9: call +3 9: call +3
13: r0 += 1 13: r0 += 1
func 2
... ...
© 2018 NETRONOME 25
Algorithm - Depth First Walk
Ø Depth first walk is used to detect loop and unreachable instruction
Branch not taken Branch taken
0: r3 += 1
0: r3 += 1 0: r3 += 1
1: if r0 < r1 goto L1 1: if r0 < r1 goto L1 1: if r0 < r1 goto L1
2: r1 += r2
3: goto L2
L1: 2: r1 += r2 L1:
3: goto L2 4: r4 += r3
4: r4 += r3
L2:
5: r0 = r4 L2: L2:
6: exit 5: r0 = r4 5: r0 = r4
6: exit 6: exit
Two auxiliary array to help the walk:
insn_stack [MAX_INSN_NUM]
insn_status[MAX_INSN_NUM]
© 2018 NETRONOME 26
Algorithm - Depth First Walk
Ø Depth first walk is used to detect loop and unreachable instruction
Branch not taken Branch taken
0: r3 += 1
0: r3 += 1 0: r3 += 1
1: if r0 < r1 goto L1 1: if r0 < r1 goto L1 1: if r0 < r1 goto L1
2: r1 += r2
3: goto L2
L1: 2: r1 += r2 L1:
3: goto L2 4: r4 += r3
4: r4 += r3
L2:
L2: L2:
5: if r4 < 10 goto L1 5: if r4 < 10 goto L1 5: if r4 < 10 goto L1
6: exit
Back Edge
6: exit L1:
Two auxiliary array to help the walk:
insn_stack [MAX_INSN_NUM] 4: r4 += r3
insn_status[MAX_INSN_NUM]
© 2018 NETRONOME 27
Algorithm - Depth First Walk
Ø Depth first walk is used to detect loop and unreachable instruction
Branch not taken Branch taken
0: r3 += 1
0: r3 += 1 0: r3 += 1
1: if r0 < r1 goto L1 1: if r0 < r1 goto L1 1: if r0 < r1 goto L1
2: r1 += r2
3: goto L2
L1: 2: r1 += r2 L1:
3: goto L2 4: r4 += r3
4: r4 += r3
L2:
L2: L2:
5: if r4 < 10 goto L1 5: if r4 < 10 goto L1 5: if r4 < 10 goto L1
6: exit
Back Edge
6: exit L1:
Two auxiliary array to help the walk:
insn_stack [MAX_INSN_NUM] 4: r4 += r3
insn_status[MAX_INSN_NUM]
© 2018 NETRONOME 28
Algorithm - Code Path Walker
Ø Execute all code path
• Accurately knows program status (registers, memory etc.) at any point
• Track and propagate scalar value range, pointer types etc.
• Code path walker is time intensive
• Program will be rejected if it is too complex (BPF_COMPLEXITY_LIMIT_INSNS)
© 2018 NETRONOME 29
Algorithm - Code Path Walker
Input BPF sequence Branch taken Branch not taken
r3 += 1 r3 += 1 r3 += 1
if r0 < r1 goto L1 if r0 < r1 goto L1 if r0 < r1 goto L1
r4 = *(u8 *)(r2 - 8) r4 = *(u8 *)(r2 - 8) r4 = *(u8 *)(r2 - 8)
goto L2 goto L2 goto L2
L1: L1: L1:
r4 += 2 r4 += 2 r4 += 2
L2: L2: L2:
r4 += r3 r4 += r3 r4 += r3
r0 = r4 r0 = r4 r0 = r4
exit exit exit
30
© 2018 NETRONOME
Algorithm - Code Path Walker
Input BPF sequence Branch taken Branch not taken
r3 += 1 r3 += 1 r3 += 1
if r0 < r1 goto L1 if r0 < r1 goto L1 if r0 < r1 goto L1
r4 = *(u8 *)(r2 - 8) r4 = *(u8 *)(r2 - 8) r4 = *(u8 *)(r2 - 8)
goto L2 goto L2 goto L2
L1: L1: L1:
r4 += 2 r4 += 2 r4 += 2
L2: L2: L2:
r4 += r3 r4 += r3 r4 += r3
r0 = r4 r0 = r4 r0 = r4
exit exit exit
This sub-sequence starting from L2 towards the exit is executed for both.
Can we avoid walking through it again?
© 2018 NETRONOME
31
Algorithm - Code Path Prune
Ø No need to walk the sequence again given we have SAFER status when reaching
the starting point of the sequence. Those points are where paths merged.
Branch taken Branch not taken
r3 += 1 r3 += 1
if r0 < r1 goto L1 if r0 < r1 goto L1
r4 = *(u8 *)(r2 - 8) r4 = *(u8 *)(r2 - 8)
goto L2 Path State B goto L2 ß B
L1: (reg, mem)
L1:
Path State A
r4 += 2 ß A (reg, mem) r4 += 2
L2: L2:
r4 += r3 N, walk again r4 += r3
eq?
r0 = r4 r0 = r4
Y, skip the walk
exit exit
© 2018 NETRONOME 32
Algorithm - Register Liveness Tracking
Ø The purpose of tracking register liveness is to make more path prune happen
Branch taken Branch not taken
r3 += 1 r3 += 1
if r0 < r1 goto L1 if r0 < r1 goto L1
r4 = *(u8 *)(r2 - 8) r4 = *(u8 *)(r2 - 8)
goto L2 goto L2 ß B
0 < r0 < 20
L1: L1:
r4 += 2 0 < r0 < 10 r4 += 2
ßA
L2: L2:
r4 += r3 r4 += r3
eq?
r0 = r4 r0 = r4
r0 += 1 It doesn’t matter when we have a r0 += 1
exit unsafe value in the code path, given exit
the value is NOT used in the to-be-
pruned path
33
© 2018 NETRONOME
Verification Stage - Challenges
Ø Complex program will require too much verification resources
Ø Use classic static flow analysis techniques might lower resource consuming and
making verifier more scalable, however would be challenging to guarantee the
same level of securities.
© 2018 NETRONOME 34
Code Gen Stage - Overview
Ø After eBPF sequence passed verification, Code Gen stage start which translates
eBPF into native machine instruction
Ø This is fairly straightforward as eBPF instructions could be one-to-one mapped to
native machine instruction on most modern architectures
Ø Architecture Code Gen back-ends, for example x86-64/AArch64 etc., typically
share the same Code Gen flow.
© 2018 NETRONOME 35
Code Gen Stage - General Flow
© 2018 NETRONOME 36
Code Gen Stage - Linking
Ø eBPF does not allow global variables and random function calls, no external symbol
access in general which save linking jobs
Ø eBPF does allow call to special runtime helper functions (map), helper address
needs fixup
• Function ID is encoded in “imm” field of eBPF call instruction
• ID needs to be mapped to address and call instruction should be relocated
verifier perpare absoluate address back-end relocate call instruction
case BPF_JMP | BPF_CALL: case BPF_JMP | BPF_CALL:
{ {
switch (insn->imm) { const u8 r0 = bpf2a64[BPF_REG_0];
case BPF_FUNC_map_lookup_elem: const u64 func = (u64)__bpf_call_base + imm;
insn->imm =
BPF_CAST_CALL(ops->map_lookup_elem) - __bpf_call_base; emit_a64_mov_i64(tmp, func, ctx);
continue; emit(A64_BLR(tmp), ctx);
case BPF_FUNC_map_update_elem:
...
64-bit address is too long to encode, keep offset instead
© 2018 NETRONOME 37
Code Gen Stage - Linking for BPF-to-BPF Call
Ø bpf-to-bpf function call requires relocating function call address as well
Ø bpf-to-bpf function call requires JIT compile all eBPF functions first, then we know start
address for each function, and need a second round compilation to relocate all call
instructions.
Ø Architecture back-end doesn’t know function partition, Verifier drives the whole Code Gen
© 2018 NETRONOME 38
Code Gen Stage - Summary
Ø Architecture Code Gen hooks do instruction mapping
Ø Verifier do linking and all other jobs which are generic for all architectures
Ø Verifier and architecture back-ends work closely to form the whole eBPF JIT
compiler
© 2018 NETRONOME 39
NFP Code Gen Back-end - NFP Introduction
Ø Netronome Agilio® SmartNIC contains a powerful Network Flow Processor (NFP)
Ø NFP and eBPF matches each other, perfect to offload eBPF to NFP, yes we can!
Ø NFP Code Gen back-end translates eBPF instructions into NFP instructions, write
them to Agilio SmartNIC, and run them there on the fly
Ø In terms of the JIT compiler Code Gen, instruction set architecture is the most
relevant part, so we will focus on this in the following slides
Ø NFP itself is a very powerful chip which offers many other capabilities, please see
our website (Document Library) for details
© 2018 NETRONOME 40
NFP Instruction Set Architecture (ISA)
Ø 32 x 32-bit general purpose registers for each eBPF context
Ø 8K instructions for each eBPF context
Ø Rich arithmetic and logic instructions
Ø Powerful memory unit allowing unaligned access and bulk access
Ø Global absolute jump within the whole instruction buffer
Ø Clustered register sets offers generous registers other than general purpose
registers
© 2018 NETRONOME 41
NFP Code Gen Back-end - Overview
Ø Generally works the same way as the other eBPF arch back-ends.
• Have verified eBPF sequence as input
• Allocate JIT image on Agilio SmartNIC
• One to one map eBPF instruction, and writes to JIT image
• NFP has other registers besides general purpose registers, need to utilize them
• NFP has 32-bit general purpose register instead of 64-bit
• NFP wants to do all linking jobs by itself
© 2018 NETRONOME 42
NFP Code Gen Back-end - Overview
Ø Differences (continued):
• More strict verification
• Maps are now located on SmartNIC, need to redirect all map access. Call firmware
functions and get results. Extra Code Gen about function call and a couple of
instruction relocation.
eBPF NFP
© 2018 NETRONOME 43
NFP Code Gen Back-end - Memory Support
Ø Memcpy Optimization
• Classic RISC arch use load/store model, can only access register width at maximum
per instruction
• NFP has special internal bus allows bulk memory access and could access 128 bytes
at maximum per instruction
• eBPF ISA follows RISC load/store model, memcpy function are expanded into
load/store pair by LLVM compiler due to inline memcpy for small copy size is common
techniques and eBPF does not allow call external function
• This is not good for NFP, or actually might be not good for other architectures. It is
better to let the architecture back-ends decide how to expand memcpy
© 2018 NETRONOME 44
NFP Code Gen Back-end - Memory Support
Ø Memcpy Optimization
LLVM NFP JIT Compiler
C source eBPF sequence reconstructed semantics
What's this?
© 2018 NETRONOME 48
NFP Code Gen Back-end - Memory Support
Ø Memcpy Optimization gpr0 tr_in0
NFP memory bulk access gpr1 tr_in1
gpr2 tr_in2
mem[read32_swap, $xfer_0, gprA_2, 0xc, 7]
alu[$xfer_0, --, B, $xfer_0] gpr3 tr_in0
NFP has clustered register sets and have other registers other than
general purpose registers tr_out0
Memory read are into transfer in registers first, then moved to tr_out1
general purpose registers. The content is not clobbered if there is no tr_out2
other memory read memory
tr_out0
NFP eBPF JIT Compiler can use 32 transfer in registers, meaning ...
could cache 128 bytes memory contents there
© 2018 NETRONOME 50
NFP Code Gen Back-end - Memory Support
Ø Memcpy Optimization gpr0 tr_in0
NFP memory bulk access gpr1 tr_in1
gpr2 tr_in2
mem[read32_swap, $xfer_0, gprA_2, 0xc, 7]
alu[$xfer_0, --, B, $xfer_0] gpr3 tr_in0
© 2018 NETRONOME 51
NFP Code Gen Back-end - 32-bit Optimization
Ø eBPF ISA defined 32-bit sub-register and associated instructions
• eBPF register is 64-bit, the lower half could be used as 32-bit sub-register
• Any write to the lower half must zero the higher half. This is to match x86-64 and
AArch64 ISA feature
• A set of ALU32 instructions will operate on 32-bit sub-register
program operating on 32-bit type default eBPF code gen from LLVM JITed AArch64 sequence
void cal(unsigned int *a,
unsigned int *b, cal: cal:
unsigned int *c) r1 = *(u32 *)(r1 + 0) ld4u r1, [r1, 0]
{ r2 = *(u32 *)(r2 + 0) ld4u r2, [r2, 0]
unsigned int sum = *a + *b; r2 += r1 addu r2, r2, r1
*(u32 *)(r3 + 0) = r2 st4 [r3, 0], r2
*c = sum; exit ret
}
© 2018 NETRONOME 52
NFP Code Gen Back-end - 32-bit Optimization
Ø 32-bit optimization
• NFP (or ARM etc.) has 32-bit register, must use register pair to model eBPF register
• NFP data manipulation instructions operates on 32-bit data. Operating on register pair
needs two instructions
program operating on 32-bit type default eBPF code gen from LLVM JITed NFP sequence (pseudo code)
void cal(unsigned int *a, cal:
unsigned int *b, ld4 r2, [r2, 0]
cal:
unsigned int *c) mov r3, 0
r1 = *(u32 *)(r1 + 0)
{ ld4 r4, [r4, 0]
r2 = *(u32 *)(r2 + 0)
unsigned int sum = *a + *b; mov r5, 0
r2 += r1
add r4, r4, r2
*(u32 *)(r3 + 0) = r2
*c = sum; addc r5, r5, r3
exit
} st4 [r6, 0], r4
ret
© 2018 NETRONOME 55
NFP Code Gen Back-end - 32-bit Optimization
Ø 32-bit optimization - Solution 2
• JIT compiler figures out data flow from scratch, no rely on external information.
• Netronome has done initial support on this. Define-Use chain will be built for input
eBPF sequence, a series of analysis could be done along the chain.
r1 = *(u32 *)(r1 + 0) r1 r1
r2 = *(u32 *)(r2 + 0)
r2 r2
r2 += r1
r2 r2 r1
*(u32 *)(r3 + 0) = r2
r2 r3
exit
r0
© 2018 NETRONOME 56
NFP Code Gen Back-end - 32-bit Optimization
Ø 32-bit optimization - Solution 2
• JIT compiler figures out data flow from scratch, no rely on external information.
• Netronome has done initial support on this. Define-Use chain will be built for input
eBPF sequence, a series of analysis could be done along the chain.
r1 = *(u32 *)(r1 + 0) r1 r1
r2 = *(u32 *)(r2 + 0)
r2 r2
r2 += r1
r2 r2 r1
*(u32 *)(r3 + 0) = r2
r2 r3
exit
r0 seed information
then backward propagate
© 2018 NETRONOME 57
NFP Code Gen Back-end - 32-bit Optimization
Ø 32-bit optimization - Solution 2
• JIT compiler figures out data flow from scratch, no rely on external information.
• Netronome has done initial support on this. Define-Use chain will be built for input
eBPF sequence, a series of analysis could be done along the chain.
r1 = *(u32 *)(r1 + 0) r1 r1
r2 = *(u32 *)(r2 + 0)
r2 r2
r2 += r1
r2 r2 r1
*(u32 *)(r3 + 0) = r2
r2 r3
exit
r0
© 2018 NETRONOME 58
NFP Code Gen Back-end - 32-bit Optimization
Ø 32-bit optimization - Solution 2
• JIT compiler figures out data flow from scratch, no rely on external information.
• Netronome has done initial support on this. Define-Use chain will be built for input
eBPF sequence, a series of analysis could be done along the chain.
r1 = *(u32 *)(r1 + 0) r1 r1
r2 = *(u32 *)(r2 + 0)
r2 r2
r2 += r1
r2 r2 r1
*(u32 *)(r3 + 0) = r2
r2 r3
exit
r0
© 2018 NETRONOME 59
NFP Code Gen Back-end - Summary
Ø Share the same flow with other host back-ends
Ø Has special optimization for NFP architecture features
Ø Targets offload execution environment
© 2018 NETRONOME 60
eBPF JIT Compiler - Emerging Features
Ø Verification Stage
• Strong requirement of a more scalable verification analysis infrastructure
• Support bounded loops. Netronome had some proof of concept work with community
to bring modern Control Flow Graph (CFG) infrastructure to eBPF verifier, then we
could build a bounded loop detector on top of it
Ø Programming Model
• Shared library support (dynamic/runtime linking)
Ø Debuggability
• Debug JITed image through DWARF annotations. Debug information could be saved
separately from executable image
© 2018 NETRONOME 61
Thank You!
62
© 2018 NETRONOME