Syscalls
The runtime syscalls available via the call instruction. Signatures, return semantics, and CU costs.
Every syscall is invoked with call <name>. Arguments live in r1 through r5 in order. The return value comes back in r0. Registers r6 through r9 are preserved; r1 through r5 should be assumed clobbered.
All syscall names below are resolved by the sBPF assembler against the runtime's syscall table.
How dispatch actually works
The assembler does not embed the name in the instruction. The call opcode (0x85) carries a 32-bit immediate, and the assembler fills it with the Murmur3 hash of the syscall name. At program load, the runtime resolves the hash against an immutable registry of built-in functions; an unknown hash fails verification before the program runs. This is why you cannot define your own name: label and reach it with call name syntax: the registry is fixed, and any hash that does not appear in it is rejected.
A practical consequence when reading disassembly: a call whose immediate looks like a meaningless 32-bit number (e.g. 0x207559bd) is the Murmur3 hash of a syscall name (sol_log_ in that case). The dasmac and bpf.wtf syscall references list the hashes if you need to invert one by hand.
Why r6 to r9 survive a syscall
The BPF VM does not allocate a call frame when dispatching a syscall. There is no register save area, no spill, no implicit push or pop. Your r1 to r5 are handed to the syscall as is; the syscall writes its return into r0 and returns; r6 to r9 and r10 are untouched because nothing ever touches them.
This is the invariant behind the calling convention. The rule "park values you need past a call in r6 to r9" is not a contract the syscall promises to honour, it is a consequence of frameless dispatch. A syscall does not "preserve" r6 to r9 so much as it has no mechanism to disturb them.
Logging
sol_log_
sol_log_(message_ptr: *const u8, message_len: u64)Writes a UTF-8 string to the transaction log.
r1: pointer to bytesr2: length in bytesr0: not meaningful
Cost: ~100 CU base + ~1 CU per byte. A 15-byte log is roughly 115 CU.
sol_log_64_
sol_log_64_(a: u64, b: u64, c: u64, d: u64, e: u64)Logs five u64 values as hexadecimal, space-separated, on one line. Useful for printf-style debugging.
r1–r5: the five valuesr0: not meaningful
Cost: ~100 CU.
Sysvar reads
Each of these writes a struct of fixed size into a buffer at r1. The buffer must be at least the struct's size and 8-byte aligned.
sol_get_clock_sysvar
sol_get_clock_sysvar(out: *mut Clock)Writes 40 bytes: slot (u64) | epoch_start_timestamp (i64) | epoch (u64) | leader_schedule_epoch (u64) | unix_timestamp (i64).
r1: pointer to a 40-byte bufferr0: 0 on success
Cost: 100 CU base + 40 CU for the struct size = ~140 CU.
sol_get_rent_sysvar
sol_get_rent_sysvar(out: *mut Rent)Writes 17 bytes: lamports_per_byte_year (u64) | exemption_threshold (f64) | burn_percent (u8).
r1: pointer to a 17-byte buffer (round up to 24 for alignment)r0: 0 on success
Cost: ~117 CU.
sol_get_epoch_schedule_sysvar
sol_get_epoch_schedule_sysvar(out: *mut EpochSchedule)Writes 33 bytes describing the cluster's epoch schedule.
Cost: ~133 CU.
sol_get_epoch_rewards_sysvar
Writes the current epoch rewards struct (16 bytes).
Cost: ~116 CU.
sol_get_last_restart_slot_sysvar
Writes the last restart slot (8 bytes).
Cost: ~108 CU.
Memory operations
sol_memcmp_
sol_memcmp_(a: *const u8, b: *const u8, len: u64, result: *mut u32)Compares len bytes at a and b. Writes the result as a u32 into the buffer at result (not into r0). Result is 0 if all bytes match, non-zero otherwise.
r1: pointer to first bufferr2: pointer to second bufferr3: byte length to comparer4: pointer to a 4-byte result buffer
Cost: depends on length; roughly ~10 CU base + ~1 CU per 8 bytes.
Convention gotcha: the comparison result is in the 4-byte buffer at r4, not in r0. r0 is always 0. The most-missed convention in asm-on-Solana.
sol_memcpy_
sol_memcpy_(dst: *mut u8, src: *const u8, len: u64)Copies len bytes from src to dst. The buffers must not overlap.
r1: dstr2: srcr3: length
Cost: roughly ~10 CU base + ~1 CU per 8 bytes.
sol_memmove_
Same as sol_memcpy_ but tolerates overlapping buffers. Marginally more expensive.
sol_memset_
sol_memset_(dst: *mut u8, byte: u64, len: u64)Fills len bytes at dst with the low byte of byte.
PDA derivation
sol_create_program_address
sol_create_program_address(
seeds: *const SolBytes, // array of {ptr, len}
seeds_len: u64, // count of seed entries
program_id: *const [u8; 32], // 32-byte pubkey of deriving program
out: *mut [u8; 32] // where to write the resulting PDA
)Derives a PDA from program_id and the ordered seed bytes. The seed bytes typically include the canonical bump as the last seed. If the seeds + program ID hash falls on the ed25519 curve, the syscall fails.
r1: pointer to SolBytes arrayr2: number of seedsr3: pointer to program ID (32 bytes)r4: pointer to output buffer (32 bytes)r0: 0 on success
Cost: ~1500 CU.
sol_try_find_program_address
sol_try_find_program_address(
seeds: *const SolBytes,
seeds_len: u64,
program_id: *const [u8; 32],
out: *mut [u8; 32],
bump: *mut u8
)Like sol_create_program_address but searches downward from bump 255 for a valid PDA. Writes the canonical bump into *bump. Expensive; off-chain code typically does this search once and stores the bump.
Cost: ~1500 CU per bump tried (up to ~256 iterations). Avoid on-chain.
Cross-program invocation
sol_invoke_signed_c
sol_invoke_signed_c(
instruction: *const SolInstruction,
account_infos: *const SolAccountInfo,
account_infos_len: u64,
signer_seeds: *const SolSignerSeeds,
signer_seeds_len: u64
)Invokes another program with the given instruction and accounts, optionally signing for PDAs derived from signer_seeds.
r1: pointer toSolInstruction(40 bytes)r2: pointer toSolAccountInfo[](56 bytes per entry)r3: account countr4: pointer toSolSignerSeeds[]or 0 for no PDA signingr5: signer seed entry count or 0r0: 0 if the callee succeeded, non-zero if it failed
If the callee fails, your program's transaction also fails regardless of what you do with r0. The runtime propagates the failure.
Cost: ~1000 CU base + the callee's CU. A System Program transfer via CPI costs roughly ~1500 CU total.
sol_invoke_signed_rust
Same as sol_invoke_signed_c but uses the Rust ABI layout for the structs (slightly different padding). Almost never needed in pure asm; _c is the canonical choice.
Hashing
| Syscall | Hash | Cost |
|---|---|---|
sol_sha256 | SHA-256 | ~85 CU + 0.5 CU per byte |
sol_keccak256 | Keccak-256 | ~85 CU + 0.5 CU per byte |
sol_blake3 | Blake3 | ~85 CU + 0.5 CU per byte |
sol_poseidon | Poseidon | ~10000 CU per round |
Each takes (slices: *const SolBytes[], slices_len: u64, out: *mut [u8; 32]).
Cryptographic verification
| Syscall | What | Cost |
|---|---|---|
sol_secp256k1_recover | Recover secp256k1 pubkey from signature | ~25000 CU |
sol_curve_validate_point | Check ed25519 / Ristretto point validity | ~140 CU |
sol_alt_bn128_compression | Decompress alt-bn128 points | varies |
Program identity
sol_set_return_data
sol_set_return_data(data: *const u8, len: u64)Sets the return data the caller can read after the CPI returns. Up to 1024 bytes.
Cost: ~10 CU + 1 CU per byte.
sol_get_return_data
sol_get_return_data(out: *mut u8, len: u64, program_id: *mut [u8; 32]) -> u64Reads the return data from the most recent CPI. Returns the length of the data.
Cost: ~10 CU.
All costs are approximate
These costs come from the published Solana compute budget table. They may change between runtime versions. Always benchmark your real program before relying on a specific number.