Skip to content

Commit 2c2a68a

Browse files
Update JIT invariant documentation (#503)
There are several invariants associated with JIT code generation. Those invariants were not well documented. This code updates that documentation. Signed-off-by: Will Hawkins <[email protected]> Co-authored-by: Alan Jowett <[email protected]>
1 parent f3123b7 commit 2c2a68a

File tree

2 files changed

+60
-19
lines changed

2 files changed

+60
-19
lines changed

Diff for: vm/ubpf_jit_arm64.c

+17-7
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,15 @@ to_condition(int opcode)
961961
}
962962
}
963963

964+
/*
965+
* The layout of the JIT'd code follows a certain pattern. There are
966+
* several invariants in the JIT'd code as well. Those are documented
967+
* in the translate function of the x86_64 JIT.
968+
* Note: The amount of space used to store the eBPF program's stack
969+
* usage in a location function is 8 bytes for both x86 and Arm. However,
970+
* in the Arm case, the value is pushed to the stack twice to maintain
971+
* 16-byte stack alignment.
972+
*/
964973
static int
965974
translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
966975
{
@@ -978,10 +987,13 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
978987
// occur at the end of the loop.
979988
struct ebpf_inst inst = ubpf_fetch_instruction(vm, i);
980989

981-
982-
// If a) the previous instruction could fallthrough to this instruction and
983-
// b) this instruction starts a local function, then
984-
// we have to "jump around" the code that manipulates the stack!
990+
// If
991+
// a) the previous instruction in the eBPF program could fallthrough
992+
// to this instruction and
993+
// b) the current instruction starts a local function,
994+
// then there has to be a means to "jump around" the code that
995+
// manipulates the stack when the program executes in the fallthrough
996+
// path.
985997
uint32_t fallthrough_jump_source = 0;
986998
bool fallthrough_jump_present = false;
987999
if (i != 0 && vm->int_funcs[i]) {
@@ -992,7 +1004,6 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
9921004
}
9931005
}
9941006

995-
9961007
if (i == 0 || vm->int_funcs[i]) {
9971008
size_t prolog_start = state->offset;
9981009
emit_movewide_immediate(state, true, temp_register, ubpf_stack_usage_for_local_func(vm, i));
@@ -1089,8 +1100,7 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
10891100
if (inst.imm == 16) {
10901101
/* UXTH dst, dst. */
10911102
emit_instruction(state, 0x53003c00 | (dst << 5) | dst);
1092-
}
1093-
else if (inst.imm == 32) {
1103+
} else if (inst.imm == 32) {
10941104
/* UXTW dst, dst. */
10951105
emit_instruction(state, 0x53007c00 | (dst << 5) | dst);
10961106
}

Diff for: vm/ubpf_jit_x86_64.c

+43-12
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,11 @@ static inline void
116116
emit_local_call(struct ubpf_vm* vm, struct jit_state* state, uint32_t target_pc)
117117
{
118118
UNUSED_PARAMETER(vm);
119-
// Because the top of the stack holds the stack usage of the calling function,
120-
// we adjust the base pointer down by that value!
119+
// Invariant: The top of the host stack always holds the amount of space needed
120+
// by the currently-executing eBPF function.
121+
122+
// Because the top of the host stack holds the stack usage of the currently-executing
123+
// function, we adjust the eBPF base pointer down by that value!
121124
// sub r15, [rsp]
122125
emit1(state, 0x4c);
123126
emit1(state, 0x2B);
@@ -146,8 +149,8 @@ emit_local_call(struct ubpf_vm* vm, struct jit_state* state, uint32_t target_pc)
146149
emit_pop(state, map_register(BPF_REG_7));
147150
emit_pop(state, map_register(BPF_REG_6));
148151

149-
// Because the top of the stack holds the stack usage of the calling function,
150-
// we adjust the base pointer back up by that value!
152+
// Because the top of the host tack holds the stack usage of the currently-executing
153+
// function, we adjust the eBPF base pointer back up by that value!
151154
// add r15, [rsp]
152155
emit1(state, 0x4c);
153156
emit1(state, 0x03);
@@ -236,6 +239,9 @@ ubpf_set_register_offset(int x)
236239
}
237240

238241
/*
242+
* JIT'd Code Layout & Invariants:
243+
*
244+
* 1. Layout of external dispatcher/helpers pointers
239245
* In order to make it so that the generated code is completely standalone, all the necessary
240246
* function pointers for external helpers are embedded in the jitted code. The layout looks like:
241247
*
@@ -251,7 +257,13 @@ ubpf_set_register_offset(int x)
251257
* External Helper Function Pointer Idx MAX_EXT_FUNCS-1 (8 bytes, maybe NULL)
252258
* state->buffer + state->offset:
253259
*
254-
* The layout and operation of this mechanism is identical for code JIT compiled for Arm.
260+
* 2. Invariants
261+
* a. The top of the host stack always contains an 8-byte value which is the size
262+
* of the eBPF stack usage of currently-executing eBPF function. The invariant
263+
* is maintained in the code generated for the EXIT, and CALL opcodes and in the
264+
* code generated for the first instruction in an eBPF function.
265+
*
266+
* The layout and invariants are identical for code JIT compiled for Arm.
255267
*/
256268

257269
static int
@@ -341,9 +353,13 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
341353
int src = map_register(inst.src);
342354
uint32_t target_pc = i + inst.offset + 1;
343355

344-
// If a) the previous instruction could fallthrough to this instruction and
345-
// b) this instruction starts a local function, then
346-
// we have to "jump around" the code that manipulates the stack!
356+
// If
357+
// a) the previous instruction in the eBPF program could fallthrough
358+
// to this instruction and
359+
// b) the current instruction starts a local function,
360+
// then there has to be a means to "jump around" the code that
361+
// manipulates the stack when the program executes in the fallthrough
362+
// path.
347363
uint32_t fallthrough_jump_source = 0;
348364
bool fallthrough_jump_present = false;
349365
if (i != 0 && vm->int_funcs[i]) {
@@ -354,12 +370,23 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
354370
}
355371
}
356372

373+
/*
374+
* There is an invariant that the top of the host stack always contains
375+
* the amount of local space used by the currently-executing eBPF program.
376+
* So, if we are at the start of an eBPF function, we will need to put the
377+
* amount of its local space usage on the top of the host stack. It is safe
378+
* to adjust the stack by only 8 bytes here because the `call` pushed the
379+
* return address (8 bytes). In combination, there is a 16-byte change
380+
* to the stack pointer which maintains the 16-byte stack alignment.
381+
*/
357382
if (i == 0 || vm->int_funcs[i]) {
358383
size_t prolog_start = state->offset;
359384
uint16_t stack_usage = ubpf_stack_usage_for_local_func(vm, i);
385+
// Move the stack pointer to make space for a 64-bit integer ...
360386
emit_alu64_imm32(state, 0x81, 5, RSP, 8);
387+
// ... that is filled with the amount of space needed for the local function.
361388
emit1(state, 0x48);
362-
emit1(state, 0xC7);
389+
emit1(state, 0xC7); // mov immediate to [rsp]
363390
emit1(state, 0x04); // Mod: 00b Reg: 000b RM: 100b
364391
emit1(state, 0x24); // Scale: 00b Index: 100b Base: 100b
365392
emit4(state, stack_usage);
@@ -370,9 +397,10 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
370397
} else {
371398
assert(state->bpf_function_prolog_size == state->offset - prolog_start);
372399
}
373-
374400
}
375401

402+
// If there was a jump inserted to bypass the host stack manipulation code,
403+
// we need to update its target.
376404
if (fallthrough_jump_present) {
377405
fixup_jump_target(state->jumps, state->num_jumps, fallthrough_jump_source, state->offset);
378406
}
@@ -737,8 +765,11 @@ translate(struct ubpf_vm* vm, struct jit_state* state, char** errmsg)
737765
}
738766
break;
739767
case EBPF_OP_EXIT:
740-
/* On entry to every local function we add an additional 8 bytes.
741-
* Undo that here!
768+
/* There is an invariant that the top of the host stack contains
769+
* the amout of space used by the currently-executing eBPF function.
770+
* 8 bytes are required for the storage. So, anytime that we leave an
771+
* eBPF function, we must pop its local usage from the top of the
772+
* host stack.
742773
*/
743774
emit_alu64_imm32(state, 0x81, 0, RSP, 8);
744775
emit_ret(state);

0 commit comments

Comments
 (0)