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
- Designed for sBPFv2 as accepted by LLVM's BPF backend (the assembler
used by
rustcfortarget_os = "solana"programs). - The macro emits whatever you write — it does not validate mnemonics. If you
use a name LLVM doesn't recognize (e.g.
jset,*64-suffixed jump aliases,uhmul32), assembly will fail at compile time with the LLVM error. - sBPFv0/v1-only mnemonics (
mul*,div*,mod*,neg*,lddw,le*) are accepted by the formatter but rejected by the v2 verifier at load time.
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.