Nội dung này đang được dịch và sẽ hiển thị ở đây khi sẵn sàng.
sBPF Registers and Memory Model
When your sBPF program executes, it operates with two primary storage mechanisms: 11 high-speed registers built into the processor and organized memory regions that hold everything else.
Registers
Registers are like 11 numbered storage slots (r0
through r10
) built directly into the processor. Each holds a single 64-bit value and provides instant access without delays. When you execute add64 r1, r2
, the processor immediately retrieves values from both registers and performs the calculation.
The tradeoff is simple: you only get 11 boxes total, so you need to use them strategically for the values you're actively working with.
sBPF assigns specific roles to each register:
Register | Role | Callee-saved? | Usage Notes |
---|---|---|---|
r0 | Return value | No | Function results, syscall return values |
r1 | Input buffer pointer | No | Points to 0x400000000 on entry |
r2-r5 | Scratch/arguments | No | Helper function arguments, temporary values |
r6-r9 | General purpose | Yes | Must preserve across calls |
r10 | Frame pointer | Yes, read-only | Stack base, cannot modify directly |
Registers r6
through r9
are "callee-saved," meaning their values persist across function calls. Use these for important data that must survive function invocations.
The remaining registers (r0
-r5
) get overwritten during syscalls and function calls.
Register r10
is "special" because it serves as your frame pointer, pointing to your stack space base and remaining read-only. You access stack variables using negative offsets like [r10 - 8]
and [r10 - 16]
.
Memory
While registers hold actively used values, memory stores everything else. sBPF organizes memory into fixed regions with identical layouts across all programs:
Region | Start Address | Purpose | Size/Notes |
---|---|---|---|
Text | 0x100000000 | Code and read-only data | Program binary |
Stack | 0x200000000 | Local variables | 32 KiB, grows downward |
Heap | 0x300000000 | Dynamic allocation | Via sol_alloc_free |
Input | 0x400000000 | Program parameters | Serialized arguments |
- The text region contains your executable code and read-only data like string constants. Data defined with
.quad
directives typically resides here. - The stack region houses local variables. With
r10
pointing to the stack base, you access locals using negative offsets:[r10 - 16]
,[r10 - 24]
, etc. The stack is limited to 32 KiB total. - The input region contains your program's parameters. On entry,
r1
points to this region (0x400000000
), allowing you to read serialized account data and instruction parameters passed to your program.
This fixed layout provides three key benefits: security through memory isolation between programs, determinism with identical addresses across all validators, and performance through compiler optimizations for known locations.
Using Registers and Memory
Here's how registers and memory work together in practice:
.globl entrypoint
entrypoint:
# On entry: r1 points to input data at 0x400000000
ldxdw r0, [r1 + 0] # Load first 8 bytes from input into r0
mov64 r2, 42 # Put 42 in register r2
add64 r0, r2 # Add them: r0 = r0 + r2
stxdw [r10 - 8], r0 # Store result on stack
mov64 r0, 0 # Return success
exit
This program demonstrates the typical pattern:
- Uses register
r1
(input pointer) to read from memory - Uses registers
r0
andr2
for calculations - Uses register
r10
(frame pointer) to access stack memory - Returns the result in register
r0
The workflow follows a consistent pattern: load data from memory into registers, perform calculations with registers, then store results back to memory when needed.
Stack Usage
The stack operates with r10
as the frame pointer, pointing to the base of your current stack frame (the highest address). Local variables use negative offsets:
# Store a value on the stack
mov64 r0, 42
stxdw [r10 - 8], r0 ; Store at first stack slot
# Load it back
ldxdw r1, [r10 - 8] ; Load from first stack slot
Stack slots are typically 8 bytes wide, so consecutive variables use offsets like [r10 - 8]
, [r10 - 16]
, [r10 - 24]
, and so on.
Program Entry and Exit
Your program begins execution at the symbol marked with .globl
(typically entrypoint). The initial register state is minimal and predictable:
.globl entrypoint
entrypoint:
# On entry:
# r1 = 0x400000000 (input buffer pointer)
# r0, r2-r10 = 0 (all other registers zeroed)
# Your program logic here
mov64 r0, 0 # Success code (0 = SUCCESS)
exit # Return to runtime
Exit behavior depends on call depth:
- At call depth 0, exit terminates the program with
r0
as the result code. - At deeper call levels, exit acts like a return statement, restoring caller-saved registers (
r6
-r9
) and the caller's frame pointer (r10
) before continuing execution at the return address.
The runtime automatically handles setup and teardown through implicit prologue and epilogue code, including initial register state configuration and final return processing.