kv-asm
Quote-free inline and global sBPF assembly macros for Solana programs.
kv-asm provides three procedural macros — kv_asm!, kv_global_asm!, and
kv_asm_array! — that let you write sBPF assembly the way you'd write it in
a .s file (one instruction per line, no quotes, no manually concatenated
strings) and have it expand to a standard core::arch::asm! /
core::arch::global_asm! invocation.
use ;
// Global entrypoint, written entirely in asm.
kv_global_asm!;
pub extern "C"
Why
core::arch::asm! and core::arch::global_asm! take a sequence of
string-literal templates. Hand-writing those strings for non-trivial sBPF
programs is painful:
asm!;
kv-asm lets you drop the quotes:
kv_asm!;
The macro is a pure token-formatter: it parses the input into one logical line
per source line, renders each line into the canonical assembler spelling, joins
them with \n, and hands the resulting string straight to core::arch::asm!
(or core::arch::global_asm!) with options(raw). Labels and relocations
are resolved by the LLVM BPF assembler exactly as they would be in a .s
file — kv-asm never computes offsets itself.
Macros
kv_asm!
Inline assembly. Wraps core::arch::asm!(template, …, options(raw)) in an
unsafe block. Operands (in(…), out(…), lateout(…), inlateout(…),
const, sym, options(…), clobber_abi(…)) are passed through verbatim
and may appear after the instructions, comma-separated.
kv_asm!;
kv_global_asm!
Module-level assembly. Wraps core::arch::global_asm!(template, …) (no
unsafe, no implicit options — supply options(raw) yourself if you want
it). Useful for defining the program entrypoint and any other naked symbols.
kv_global_asm!;
kv_asm_array!
Returns the formatted instruction strings as a &'static [&'static str],
without invoking any asm!. Primarily useful for testing — see
tests/equivalence.rs.
let lines: & = kv_asm_array!;
assert_eq!;
Formatter rules
The formatter aims to match the canonical spelling produced by handwriting an
asm! template:
| Source | Emitted |
|---|---|
mov64 r0, 1 |
mov64 r0, 1 |
ldxdw r1, [r2 + 8] |
ldxdw r1, [r2+8] |
ldxdw r1, [r2 - 8] |
ldxdw r1, [r2-8] |
ja +5 / ja -3 |
ja +5 / ja -3 |
cal_a: / call cal_a |
cal_a: / call cal_a |
.globl entrypoint |
.globl entrypoint |
.type entrypoint, @function |
.type entrypoint, @function |
Comments (// …) and blank source lines are stripped.
Scope and limitations
- Targets sBPFv0 — the version mainnet runs and the default for
cargo-build-sbf(--arch=v0). The macro itself is version-agnostic (it's just a token formatter); what's "valid" is decided by LLVM's BPF assembler at compile time and by the on-chain verifier at load time. - The macro emits whatever you write — it does not validate mnemonics. If you use a name LLVM doesn't recognize, assembly will fail at compile time with the LLVM error.
- Mnemonics LLVM's BPF backend does not accept (so unreachable from
kv_asm!/kv_global_asm!regardless of sBPF version):*64-suffixed jump aliases (jeq64,jne64, …,jsle64) — use the base spelling (jeq,jne, …), which already encodes to the v0 64-bit jump opcodes.jset,jset32,jset64.le16,le32,le64(the BPF VM is little-endian, so they'd be a no-op anyway — usebe*).uhmul32,shmul32,hor32.
- Mnemonics that only exist in v2+ and will be rejected by the v0
verifier at load time:
- PQR family:
lmul*,uhmul64,shmul64,udiv*,urem*,sdiv*,srem*(instruction classBPF_PQR = 0x06, gated onenable_pqr()). hor64(gated ondisable_lddw()). Uselddwfor wide immediates.- The new store-from-imm/store-from-reg classes (
ST_*B_*) which share bytes withmul*/div*/neg*/mod*and only kick in undermove_memory_instruction_classes().
- PQR family:
- Mnemonics that only exist in v3+:
*32jump variants (jeq32,jne32, …) underenable_jmp32().
Testing strategy
The tests/ directory contains class-by-class equivalence tests
(equivalence.rs) that pin down the formatter's output character-for-character
against the templates a human would hand-write inside asm!. Combined with an
on-chain integration test of the assembled bytes, this gives a strong
end-to-end equivalence guarantee.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.