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 | 4 KiB per stack frame, with a maximum call depth of 64 |
| Heap | 0x300000000 | Dynamic allocation | 32 KiB |
| Input | 0x400000000 | Program parameters | Serialized accounts and instruction data |
The text region contains your executable code and read-only data like string constants. Data defined with
.quaddirectives typically resides here.The stack region houses local variables. With
r10pointing to the stack base, you access locals using negative offsets:[r10 - 16],[r10 - 24], etc.The input region contains your program's parameters. On entry,
r1points 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
exitThis program demonstrates the typical pattern:
Uses register
r1(input pointer) to read from memoryUses registers
r0andr2for calculationsUses register
r10(frame pointer) to access stack memoryReturns 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 slotStack 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)
// r10 = 0x200001000 (0x200000000 stack start + 0x1000 4KiB frame size)
// r0, r2-r9 = 0 (all other registers zeroed)
// Your program logic here
mov64 r0, 0 // Success code (0 = SUCCESS)
exit // Return to runtimeExit behavior depends on call depth:
At call depth 0, exit terminates the program with
r0as 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.