Back to Blog

Source code panels transforming into a compiled program binary over a desert landscape, representing Solana's SBPFv3 bytecode migration.

Table of Contents

Highlights

Migrating Solana Programs to SBPFv3

Written By

Joe Caulfield

TL;DR

SBPFv3 is the new bytecode format for Solana programs.

Once SIMD-0500 activates, the network will reject new deployments, upgrades, and finalizations of programs built for older bytecode versions. Already-deployed programs will keep executing.

This guide covers what SBPFv3 is, how to build for it, and pitfalls that can break your program.

What Is SBPFv3?

SBPFv3 is a new bytecode format comprised of three separate SIMDs:

It has been available since early 2026 and the network recently gained support for it on mainnet-beta with the activation of feature 5cC3foj77CWun58pC51ebHFUWavHWKarWyR5UUik7dnC in June.

Some highlights from what’s changed between SBPFv0 - the most popular bytecode format currently - and SBPFv3:


SBPFv0

SBPFv3

Syscalls

Resolved at load time via relocations against the symbol table

Resolved at compile time; murmur32 hash in the call immediate

Runtime Relocations

Required

Not supported

ELF layout

Permissive

Fixed segment layout and order, strict header validation

callx

Target register named by the immediate field

Target register named by the destination field

e_machine

0x107 (EM_SBF) or 0xf7 (EM_BPF)

0xf7 (EM_BPF)

JMP32 class

Not supported

Supported

Stack frame gaps

Enabled

Disabled

SBPFv3 is not a superset of SBPFv2. Instead it reverts SIMD-0173 (encoding changes) and SIMD-0174 (arithmetic changes):

  • The PQR class (udiv, urem, sdiv, srem, lmul) is removed.

  • lddw, neg, and le are restored.

  • The encoding of memory instructions reverts back to the SBPFv0 standard.

  • sub reg, imm returns to its original operand order, and results are no longer explicitly sign-extended.

  • callx names its register in the destination field rather than the source field.

Toolchains

We always recommend upgrading to the latest version for any toolchain or component. However, the suggested minimums for building SBPFv3 programs are as follows:

Component

Minimum

platform-tools

v1.56

cargo-build-sbf

v4.2.0

solana-define-syscall

v3.0.0

platform-tools v1.56 ships with Rust 1.89.0 and LLVM 20. You can view other versions of platform-tools and their corresponding Rust and LLVM versions in their release notes.

cargo-build-sbf can be installed and updated separately from the Solana CLI:

You can specify a platform-tools version and a target architecture (bytecode format) like so:

cargo build-sbf --arch v3 --tools-version

Warning: do not pass --arch v3 to any platform-tools older than v1.53. This could produce an outdated bytecode format not compatible with SBPFv3. See the cargo-build-sbf README for more information.

solana-define-syscall v3.0.0 is the first version to use the new syntax for static syscalls compatible with SBPFv3.

Warning: using an older version of solana-define-syscall, or declaring syscall bindings with extern “C”, can brick your program. See Static Syscalls and Unresolved Symbols below for more information.

Building for SBPFv3

A hello-world repository demonstrating every step below is available at buffalojoec/hello-sbpfv3.

cargo build-sbf --arch v3 --tools-version

Your program will declare its bytecode version in the ELF header's e_flags field, where 0x3 means SBPFv3:

$ readelf -h program.so | grep Flags:
  Flags:                             0x3, CPU Version: 3

Static Syscalls

Declare a syscall using solana-define-syscall:

use solana_define_syscall::define_syscall;

define_syscall!(fn sol_log_(message: *const u8, len: u64));

Under target_feature = "static-syscalls", the macro no longer emits an extern "C" import to be relocated at load time. It transmutes the syscall's murmur32 hash straight into a function pointer:

#[inline]
pub unsafe fn sol_log_(message: *const u8, len: u64) {
    #[repr(usize)]
    enum Syscall { Code = sys_hash("sol_log_") }
    let syscall: extern "C" fn(*const u8, u64) = core::mem::transmute(Syscall::Code);
    syscall(message, len)
}

In the bytecode, this becomes an ordinary call (opcode 0x85) with source register 0 and the hashed function name in the immediate:

85 00 00 00 bd 59 75 20
^opcode     ^murmur32("sol_log_"), little-endian

If you’re registering a custom syscall, you should also use define_syscall! to define its static pointer.

Unresolved Symbols

It’s imperative that you do not declare syscalls in your program with extern “C”. Doing so will result in an unresolved symbol remaining in your final bytecode, represented by call -1, which can brick your program.

The presence of this call -1 does not violate the runtime’s verification ruleset, therefore deployments with unresolved symbols will succeed. When your program is invoked, if execution encounters that unresolved symbol, your program will loop over itself until you reach the maximum number of instruction frames, finally returning error CallDepthExceeded.

Luckily, this issue is trivial to detect. We strongly recommend testing your programs before deploying them to any network, and most testing frameworks like Mollusk, LiteSVM, Surfpool, and Anchor will surface this issue.

cargo-build-sbf v4.2.0 is actually outfitted to pass the -z defs rust flag to emit an error at build time when your program contains an unresolved symbol. You can also pass this flag in RUSTFLAGS to any build tool you use.

callx Register Field Moved Again

The fields of the callx opcode have moved again.

Version

Register Field

SBPFv0/SBPFv1

immediate

SBPFv2

source

SBPFv3

destination

This is most important for assembly developers, toolchain maintainers, or anyone working with ELFs by hand. For developers programming in Rust, the platform-tools compiler will emit the correct form for whichever --arch you pass.

Null Reads Can Silently Succeed

This is another important caveat in the new bytecode format that affects assembly developers and toolchain maintainers.

SBPFv3 moves read-only data to VM address 0. The linker script pads .rodata with a leading zero byte, 8-aligned, so nothing is placed at address 0. However, the region is still mapped and readable, so a load that aborted under SBPFv0 now succeeds and execution continues.

In SBPFv0, VM address 0 sat in the bytecode region, and the loader never mapped a readable data page there, so a read always produced an access violation.

Safe Rust will not construct such a dereference. However, in unsafe Rust, C, or assembly, a null pointer that would fail loudly in testing will now return a value and likely fail somewhere else.

Stack Behavior Changed

SBPFv3 has removed stack frame gaps, meaning all stack frames are now contiguous. If your program was previously running close to the 4096-byte limit, you should re-measure.

The platform-tools compiler will emit a warning about stack overflow, but it is not a rustc diagnostic, so it will not fail the build. Instead it just emits an Error: in your build logs.

Why Migrate Now: Old Bytecode Deployments Will Be Rejected

Once SIMD-0500 is activated, the network will reject deployments, upgrades, and finalizations of any program built for a bytecode version older than SBPFv3.

You'll need to rebuild your program for SBPFv3 and upgrade it before then. Already-deployed programs running SBPFv0, v1, or v2 will continue to execute without restriction.

The feature gate for SIMD-0500 is planned for activation in Agave v4.4.

Next Steps

Now that you know what SBPFv3 is and how to build your program for it, you should have everything you need to build or migrate your program.

To migrate your program:

  1. Update your toolchain: platform-tools v1.56+, cargo-build-sbf v4.2.0+, solana-define-syscall v3.0.0+.

  2. Rebuild with cargo build-sbf --arch v3.

  3. Build with -z defs to catch unresolved symbols.

  4. Verify the bytecode version: readelf -h program.so should show CPU Version: 3.

  5. Test before deploying to any network. Mollusk, LiteSVM, Surfpool, and Anchor all surface the issues covered in this guide.

  6. Deploy and upgrade your program.

For a working end-to-end example, see buffalojoec/hello-sbpfv3.

We strongly recommend testing your programs before deploying them to any cluster.

Don't wait for an activation date. Migrate now, and follow @anza_xyz or the Solana Tech Discord for SIMD-0500 activation announcements.