Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions proposals/0377-ebpf-isa-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
---
simd: '0377'
title: eBPF ISA compatibility
authors:
- Lucas Steuernagel (Anza)
- Alexander Meißner (Anza)
category: Standard
type: Core
status: Review
created: 2025-10-09
feature: (fill in with feature key and github tracking issues once accepted)
---

## Summary

This SIMD introduces instruction set architecture (ISA) changes to make the
sBPF virtual machine compatible with the latest existing version of eBPF ISA
generated by its LLVM backend.

It reverts past ISA changes, modifies the encoding of existing instructions
and brings new instructions to the Solana virtual machine.

## Motivation

The eBPF target on the Rust compiler emits code by default for eBPFv1, whose
only incompatibility with the Solana virtual machine is the `callx`
instruction. Aiming to prioritize Solana programs and decrease their CU
consumption, we want to be compatible with at least the current eBPF version
(v3), which brings in new instructions. In order for that to be possible, we
must modify our virtual machine to support eBPF integrally.

## New Terminology

The set containing these new instructions will form an sBPFv3 program.

## Detailed Design

### ELF Identification

Programs containing the instructions mentioned in this SIMD must have the
`0x03` value in the `e_flags` field of their header.

### Revert SIMD-0166

SIMD-0166 must be reverted beginning with sBPFv3, since we will introduce a
new design for dynamic stack frames that is closer to the eBPF code generation.

### Revert SIMD-0173

All changes proposed in SIMD-173 will no longer take effect in sBPFv3.
Consequently, the verifier must accept the following opcodes:

- `0x18`, `0x00` (`LDDW`)
- `0x72`, `0x71`, `0x73` (`STB`, `LDXB`, `STXB`)
- `0x6A`, `0x69`, `0x6B` (`STH`, `LDXH`, `STXH`)
- `0x62`, `0x61`, `0x63` (`STW`, `LDXW`, `STXW`)
- `0x7A`, `0x79`, `0x7B` (`STDW`, `LDXDW`, `STXDW`)
- `0xD4` (`LE`)

The new opcodes introduced in SIMD-173 must be rejected in the verifier with `VerifierError::UnknownOpCode`:

- the `HOR64` instruction (opcode `0xF7`)
- the moved opcodes:
- `0x27`, `0x2C`, `0x2F` (`STB`, `LDXB`, `STXB`)
- `0x37`, `0x3C`, `0x3F` (`STH`, `LDXH`, `STXH`)
- `0x87`, `0x8C`, `0x8F` (`STW`, `LDXW`, `STXW`)
- `0x97`, `0x9C`, `0x9F` (`STDW`, `LDXDW`, `STXDW`)

### Revert SIMD-0174

All changes proposed in SIMD-174 will no longer take effect in sBPFv3.
Consequently, the verifier must accept the following opcodes:

- the `MUL` instruction (opcodes `0x24`, `0x2C`, `0x27` and `0x2F`)
- the `DIV` instruction (opcodes `0x34`, `0x3C`, `0x37` and `0x3F`)
- the `MOD` instruction (opcodes `0x94`, `0x9C`, `0x97` and `0x9F`)
- the `NEG` instruction (opcodes `0x84` and `0x87`)

The verifier must reject programs and throw `VerifierError::UnknownOpCode` for
programs that contain any of the following opcodes.

- the `UHMUL64` instruction (opcode `0x36` and `0x3E`)
- the `UDIV32` instruction (opcode `0x46` and `0x4E`)
- the `UDIV64` instruction (opcode `0x56` and `0x5E`)
- the `UREM32` instruction (opcode `0x66` and `0x6E`)
- the `UREM64` instruction (opcode `0x76` and `0x7E`)
- the `LMUL32` instruction (opcode `0x86` and `0x8E`)
- the `LMUL64` instruction (opcode `0x96` and `0x9E`)
- the `SHMUL64` instruction (opcode `0xB6` and `0xBE`)
- the `SDIV32` instruction (opcode `0xC6` and `0xCE`)
- the `SDIV64` instruction (opcode `0xD6` and `0xDE`)
- the `SREM32` instruction (opcode `0xE6` and `0xEE`)
- the `SREM64` instruction (opcode `0xF6` and `0xFE`)

### Execution changes

MOV32_REG (opcode `0x14`) must NOT perform sign extension.

SUB32_IMM and SUB64_IMM must perform the operation `src = src - imm`.

### Dynamic stack frames

Aiming a closer compatibility to eBPF, the implementation of dynamic stack
frames is going to change.

The R10 register must continue to be the frame pointer, i.e. pointing to the
highest address accessible in a function. As such, the stack will grow upwards.

The virtual machine must initialize the frame pointer `R10` at 4096 bytes to
allow stack space for the entry function. After that, the it must increment
the frame pointer in 4096 at each internall call (`callx` or `call` with
source field set to one).

Functions may optionally include an instruction `add64 r10, imm` on their
prologue to increment the frame pointer relative to the already supplied 4096
bytes, providing `4096 + imm` bytes of frame space for them.

When a function returns, the virtual machine must automatically restore the
frame pointer register with the value used in the caller, so programs do not
need to emit any instruction to adjust the pointer in the epilogue of each
function.

### JMP32 instruction class

The JMP32 instruction class utilizes 32 bit wide operands for the same
operations as the JMP class.

The following opcodes must be allowed in the verifier and the virtual machine
must implement the behavior described below for each one of them.

- `JEQ32_IMM` -> opcode = `0x16` -> `pc += offset if dst as u32 == IMM as u32`
- `JGT32_IMM` -> opcode = `0x26` -> `pc += offset if dst as u32 > IMM as u32`
- `JGE32_IMM` -> opcode = `0x36` -> `pc += offset if dst as u32 >= IMM as u32`
- `JSET32_IMM` -> opcode = `0x46` ->
`pc += offset if (dst as u32 & IMM as u32) != 0`
- `JNE32_IMM` -> opcode = `0x56` -> `pc += offset if dst as u32 != IMM as u32`
- `JSGT32_IMM` -> opcode = `0x66` -> `pc += offset if dst as i32 > IMM as i32`
- `JSGE32_IMM` -> opcode = `0x76` -> `pc += offset if dst as i32 > IMM as i32`
- `JLT32_IMM` -> opcode = `0xa6` -> `pc += offset if dst as u32 < IMM as u32`
- `JLE32_IMM` -> opcode = `0xb6` -> `pc += offset if dst as u32 <= IMM as u32`
- `JSLT32_IMM` -> opcode = `0xc6` -> `pc += offset if dst as i32 < IMM as i32`
- `JSLE32_IMM` -> opcode = `0xd6` -> `pc += offset if dst as i32 <= IMM as i32`

- `JEQ32_REG` -> opcode = `0x1e` -> `pc += offset if dst as u32 == src as u32`
- `JGT32_REG` -> opcode = `0x2e` -> `pc += offset if dst as u32 > src as u32`
- `JGE32_REG` -> opcode = `0x3e` -> `pc += offset if dst as u32 >= src as u32`
- `JSET32_REG` -> opcode = `0x4e` ->
`pc += offset if (dst as u32 & src as u32) != 0`
- `JNE32_REG` -> opcode = `0x56` -> `pc += offset if dst as u32 != src as u32`
- `JSGT32_REG` -> opcode = `0x66` -> `pc += offset if dst as i32 > src as i32`
- `JSGE32_REG` -> opcode = `0x76` -> `pc += offset if dst as i32 > src as i32`
- `JLT32_REG` -> opcode = `0xa6` -> `pc += offset if dst as u32 < src as u32`
- `JLE32_REG` -> opcode = `0xb6` -> `pc += offset if dst as u32 <= src as u32`
- `JSLT32_REG` -> opcode = `0xc6` -> `pc += offset if dst as i32 < src as i32`
- `JSLE32_REG` -> opcode = `0xd6` -> `pc += offset if dst as i32 <= src as i32`

### callx encoding

The encoding of callx must change so that the register containing the address
to jump to is in the destination register.

- `callx` -> opcode = `0x9d` -> pc = `dst`

## Alternatives Considered

We have considered diverging from the eBPF standard by introducing new opcodes
and creating specific instructions to the Solana environment. We discarded
such an approach to be compatible with the existing LLVM eBPF code generation.

Another consideration was bringing new eBPFv4 instructions to the Solana
environment. They are a superset of eBPFv3 and does not conflict with it,
so any additions may be included in the future without a breaking change.

## Impact

These changes permit a straightforward management of the compiler toolchain,
permitting the usage of most of existing upstream tooling.

## Security Considerations

None

Loading