Scope. This describes each project's guest strategy as of July 27, 2026, pinned to specific commits — full SHAs and line numbers are in the references — because these codebases move: two facts here changed while we were writing. Nothing below predicts what any team will do next; if a team's situation changes, the corresponding statements stop applying. Comparative statements are not criticisms — each strategy answers a different product question. Results from our own builds are labeled as ours; everything else is pinned to public sources.
A zkVM proves that a program executed correctly on some instruction set. Between "we can prove RISC-V execution" and "a developer's Rust program produces a proof" sits the layer that decides what a zkVM is like to use: how the guest compiles, what it targets, how input reaches it, and how much of the crate ecosystem survives.
Two findings organize this survey of Jolt, SP1, RISC Zero, OpenVM, Ceno and ZKsync's Airbender. First, the apparent diversity of toolchain strategies hides a convergence: five of the six inject the same LLVM pass — lower-atomic — into every guest build; the sixth avoids the problem by restricting what guests may be. Second, where the strategies differ, they track product scope almost exactly.
1 · What a guest program is
A guest is a self-contained program compiled to the VM's ISA with its own entry point. Two consequences are routinely missed. The trace is free: the executor records a witness row per retired instruction as a side effect of executing, so nothing must be arranged to make a program provable. A library function is not a program: the VM loads an ELF and jumps to its entry symbol, so every project ships scaffolding:
| Project | Entry mechanism | Halt / output |
|---|---|---|
| Airbender | Hand-written _start in shared assembly (section .init), linker script places it first at address 0 [11] | Registers x10–x17 are the output, by convention [12] |
| RISC Zero | __start in the risc0-zkvm-platform crate [24] | ecall HALT (0); journal on fd 3 [26] |
| SP1 | entrypoint! macro plus _start asm in a no_std runtime crate; inits a TLSF allocator and a public-values hasher [34] | syscall_halt(exit_code) [34] |
| OpenVM | entry! macro; _start from global_asm! sets gp/sp [42] | process::exit() [42] |
| Ceno | No macro: global_asm! _start does call main; guests write a plain fn main() [49] | ecall, t0 = 0, exit code in a0 [49] |
| Jolt | ENTRY(_start) in a CLI-embedded linker template; boot asm from the external ZeroOS dependency [60] | Panic and termination words in the I/O region [57] |
The scaffolding is per-platform, not per-program: written once, every later guest is an ordinary Rust project with one dependency.
2 · The input channel: unconstrained advice
Every zkVM feeds its guest through a non-determinism channel resting on one principle: the tape is unconstrained advice. The circuit constrains not what the channel delivers, only what the program later checks about it.
Airbender is the purest case: a read is one instruction, csrrw rd, 0x7c0, x0 [4][7], and in the circuit the value reaches rd as unchecked variables from an external-oracle placeholder, constrained only by 16-bit range checks [5]. Its docs state the intent: compute witnesses outside, verify inside, for fewer cycles than recomputation [6]. Everyone else builds framing on that idea — always in the guest, never in the circuit:
| Project | Channel | Framing (runs inside the guest) |
|---|---|---|
| Airbender | CSR 0x7c0 oracle, raw u32 words | None [4] |
| OpenVM | Hint stream via custom-0 (0x0b) instructions: a phantom hint_input resets it, hint_buffer pulls words [43] | Length-prefixed bytes, or its own word-oriented serde codec [43] |
| Ceno | Read-only hints MMIO region, pre-filled by the host | Word-sized length header + blobs; ceno_serde, whose crate doc calls it "extracted from OpenVM" [50] |
| RISC Zero | zkVM stdin (fd 0) via the SOFTWARE ecall | env::read over a 32-bit-word serde codec [25] |
| SP1 | Hint syscalls | bincode over serde; raw-byte variants bypass it [35] |
| Jolt | Dedicated memory region below RAM_START_ADDRESS, filled by the host [57] | postcard, via the #[jolt::provable] macro [56] |
Jolt is the partial exception: its function arguments sit in an input region bound to the statement the verifier checks, while its separate trusted and untrusted advice regions are the unconstrained channel [57].
Note what is absent: no project decodes input in circuit. Beyond the base ISA and system extensions the acceleration surfaces are crypto-only — OpenVM's extension list [44], SP1's syscall table [36], Ceno's dispatcher [51] and Jolt's inlines [59] are hashes, curves and big integers. Deserialization is ordinary proved execution.
3 · The atomics problem
Ask why a production Rust crate fails to build for a bare RISC-V target and the answer is usually compare-and-swap. A zkVM guest is single-threaded but needs atomics anyway, because libraries are written for the general case: Arc refcounts with atomic read-modify-write, OnceLock and once_cell and spin are built on CAS, and none can know its user is single-threaded.
3.1 · Ground truth from rustc and LLVM
alloc::sync — where Arc lives — compiles only under target_has_atomic = "ptr":
// library/alloc/src/lib.rs, rust-lang/rust @ ad0c9dce
#[cfg(all(not(no_rc), not(no_sync), target_has_atomic = "ptr"))]
pub mod sync;
So without pointer-width CAS, Arc is not a link error: the type does not exist [14]. rustc sets that cfg only when the spec declares atomic_cas: true; targets with atomic loads and stores but no CAS get only target_has_atomic_load_store [16] — two capabilities, and conflating them produces wrong conclusions (§6).
The stock bare-metal targets, riscv32i- and riscv32im-unknown-none-elf, declare max_atomic_width: Some(32), atomic_cas: false, and LLVM's +forced-atomics [15]. That feature means "assume lock-free native-width atomics are available": loads and stores become plain ones with fences, while RMW and CAS route to __sync_* libcalls — the backend's comment reads "Force __sync libcalls to be emitted for atomic rmw/cas operations" [17][18]. With only +m, the backend sets its maximum atomic size to zero and AtomicExpand converts every atomic operation into __atomic_* libcalls [19][20]. compiler-builtins supplies neither family for RISC-V [21].
3.2 · The audacious target spec
Here is what RISC Zero put into upstream rustc as the built-in riscv32im-risc0-zkvm-elf target, Tier 3 [22][23]:
// Some crates (*cough* crossbeam) assume you have 64 bit
// atomics if the target name is not in a hardcoded list.
// Since zkvm is singlethreaded and all operations are
// atomic, I guess we can just say we support 64-bit
// atomics.
max_atomic_width: Some(64),
atomic_cas: true,
features: "+m".into(),
...
singlethread: true,
Full 64-bit atomics with CAS, on an RV32IM machine with no A extension, justified by single-threadedness — and the whole API surface unlocks at a stroke. SP1's fork carries the same posture, its legacy RV32 spec reproducing that comment verbatim while the RV64 spec it ships keeps the values without it [33]; Ceno's JSON declares the same three fields [48].
But declaring atomics does not implement them. With +m the emitted libcalls are __atomic_* — Ceno, which also keeps +forced-atomics, would emit __sync_* for RMW and CAS instead — and full-tree greps at pinned commits confirm no project ships either family: not RISC Zero [27], SP1 [32], OpenVM [45], Ceno [48], or Airbender [13]. Nor does singlethread: true rescue anything: rustc plumbs it to LLVM's ThreadModel::Single, and while ARM's pass config responds by swapping in LowerAtomic, the RISC-V backend never reads the thread model and always runs AtomicExpand [28].
3.3 · The convergent answer: one LLVM pass
Every RISC-V zkVM here that supports the ordinary CAS-using ecosystem injects LLVM's lower-atomic pass into guest builds. LowerAtomic is what ARM applies under a single-threaded model: it rewrites atomic loads, stores, RMW and compare-exchange into plain operations at the IR level, so no atomic IR reaches the backend and no libcall is ever emitted [28]. The RISC-V backend will not do this unprompted, so the build systems pass -C passes=lower-atomic explicitly:
| Project | Where the pass is injected |
|---|---|
| RISC Zero | risc0-build, for every guest build — "Replace atomic ops with nonatomic versions since the guest is single threaded" [29] — and rzup builds the distributed toolchain's std with the same flag [30] |
| SP1 | sp1-build for every guest build; the toolchain-build command sets it for std on both succinct targets [33] |
| OpenVM | encode_rust_flags in openvm-build — and via build-std, the std the guest links [46] |
| Ceno | Both build paths: the examples' cargo config and the cargo ceno CLI [47] |
| Jolt | The jolt CLI — even though Jolt's VM implements the A extension for real (§4) [55] |
| Zisk | Toolchain build sets it per-target, despite the A in riscv64ima [70] |
The standard story is therefore a spec-level claim plus IR-level erasure: the spec asserts atomics so the APIs exist, the pass ensures the claim is never tested. It is sound — on a single-threaded, non-preemptive machine a plain read-modify-write is atomic — but it is correctness maintained in build scripts rather than in the compiler or the machine, and the seams show. With one codegen unit, std's embedded LTO bitcode can reintroduce atomics the pass already stripped from std's object code, so RISC Zero builds std with 16 codegen units [30]. A guest built with plain cargo build, bypassing the vendor RUSTFLAGS, never gets the pass for its own crates — the failure §5 reproduces. And crossbeam assumes any target absent from its hardcoded list of atomics-lacking targets has 64-bit atomics, so a narrower spec produced unresolved AtomicU64 imports rather than link errors; max_atomic_width: Some(64) exists to make that assumption true [22][27].
A FENCE footnote. The pass strips fences too, so these pipelines should emit none — and the decoders show how far each team trusts "should": OpenVM transpiles FENCE to a nop [52], Jolt decodes it as a real no-effect instruction [58], Ceno maps it to unimplemented [53], and Airbender has the opcode arm commented out — "nothing to do in fence, our memory is linear" — making it illegal [13]. On one core a fence orders nothing, so nop is correct semantics, not a concession. (RISC Zero's and SP1's handling: not pinned.)
4 · Four toolchain strategies
With the mechanism in view, the field's chaos of targets resolves into four strategies — the fourth, shipping a prebuilt toolchain, being both the most common and the easiest to miss, because most "forked compilers" here barely patch the compiler at all.
A — constrain the guest (Airbender). Stock riscv32i-unknown-none-elf with M via rustflags [1]: no custom spec, no atomics claim, no pass, no shims — and therefore no Arc, no std, no CAS-using crates, the examples containing no alloc::sync anywhere [13]. Examples build with plain cargo plus objcopy [3]; the workspace selects the floating nightly channel, and only the recursion-verifier guest pins a dated nightly and uses build-std [2]. Coherent when the guests that matter are written in-house (§7).
B — CAS-declaring spec plus pinned nightly and build-std (OpenVM, Ceno). OpenVM compiles guests for RISC Zero's built-in target and says so plainly: a comment notes the borrowed std PAL ABI "will be removed once a dedicated rust toolchain is used" [40][41]. Ceno ships its own JSON [48]. Neither has a prebuilt std for its target, so guests rebuild std on a pinned nightly — OpenVM at nightly-2026-01-18 at HEAD, from nightly-2025-08-02 at our June read [39], Ceno at nightly-2025-11-20 [47]. The cost lands on every user's machine; the benefit is having no compiler artifact to distribute.
C — ship a prebuilt toolchain (RISC Zero, SP1; also Pico, Ziren, Zisk). The most widespread and most misunderstood. Both distribute through an installer built from a fork of rust-lang/rust — but the forks are distribution vehicles, not compiler patches: risc0/rust is upstream 1.94.1 plus eight commits touching only CI [31], SP1's is upstream stable plus two or three small ones with the LLVM submodule stock [33]. What the fork buys is a prebuilt std with lower-atomic already applied [30][33], so users need neither nightly nor build-std, and guests get experimental std through the zkvm PAL upstream rustc carries for the risc0 target [24]. The cost is permanent release engineering.
D — implement the ISA (Jolt). Stock riscv64imac-unknown-none-elf, with real A and C extensions, on pinned stable Rust 1.95 and no build-std anywhere [54]; the tracer implements 22 AMO/LR/SC instructions with genuine reservation semantics plus a full RVC decoder [58]. Two nuances: AMOs are proven by expansion into virtual instruction sequences rather than first-class lookups [58], and the CLI injects -Cpasses=lower-atomic anyway [55] — though with no build-std the prebuilt core and alloc never see that flag, so real AMOs can still reach shipped binaries (we did not disassemble one to count). What survives proves correctly either way; the ISA support is what makes that unconditional.
| ISA / target | Toolchain a user needs | std? | Atomics mechanism | |
|---|---|---|---|---|
| Airbender | RV32I+M, stock riscv32i-unknown-none-elf [1] | Nightly channel; plain cargo for examples, build-std for the verifier guest [2][3] | ✗ | Avoided: guests don't use the CAS ecosystem [13] |
| RISC Zero | RV32IM, built-in riscv32im-risc0-zkvm-elf, Tier 3 [22] | Prebuilt via rzup [30][31] | ✓ [24] | Spec claim + lower-atomic, guests and shipped std [29][30] |
| SP1 | RV64IM since v6 (Feb 2026); RV32IM through v5 [37] | Prebuilt succinct toolchain via sp1up [33] | ✓ experimental [33] | Spec claim + lower-atomic, guests and toolchain std [33] |
| OpenVM | RV32IM, reuses risc0's built-in target [40] | Pinned nightly + build-std [39] | ✓ via risc0 PAL ABI [41] | Spec claim + lower-atomic [46] |
| Ceno | RV32IM, custom JSON spec [48] | Pinned nightly + build-std [47] | std in the build-std list [47] | Spec claim + lower-atomic [47] |
| Jolt | RV64IMAC, stock riscv64imac-unknown-none-elf [54] | Stock stable 1.95, no build-std [54] | Opt-in via ZeroOS [60] | Real A extension in the VM; lower-atomic injected as well [55][58] |
Acceleration divides the same way. Airbender triggers delegation circuits through CSR writes, Blake2 at 0x7c7 and 256-bit integer ops at 0x7ca [8][9]. RISC Zero and Ceno dispatch on ecall with a function code [26], and Ceno deliberately reuses SP1's syscall numbers — its comment says so, and five shared codes are byte-identical at the SP1 commit it links [51]; SP1's table is the largest of the six [36]. OpenVM and Jolt skip syscalls for custom instructions on the reserved custom-0/1 opcodes: OpenVM transpiles them into its extension ISA [43][44], while Jolt's "inlines" expand at trace time into ordinary virtual instructions — acceleration with no precompile circuits at all [59].
5 · Production revm as a guest — our own results
Lab notes from our own RV32IM zkVM, not pinned public fact. We used revm — the production EVM interpreter OpenVM benchmarks — to find where "ordinary Rust" breaks.
| Configuration | Outcome | Why |
|---|---|---|
revm 42, stock riscv32im-unknown-none-elf | ✗ fails in spin, bytes, once_cell | atomic_cas: false → no CAS API at the cfg level |
same + -C target-feature=+forced-atomics | ✗ unchanged | changes codegen, not the cfg — the API is still absent |
| revm 42, CAS-declaring custom target, no shims, no pass | ✗ undefined __atomic_* at link | APIs exist, libcalls emitted, nothing provides them |
revm 42 on riscv32im-risc0-zkvm-elf via plain cargo build on the rzup toolchain, bypassing risc0-build | ✗ identical failure | the pass lives in the vendor tooling, not the target |
| revm 18, custom target, no shims | ✓ links, 37 KB | no atomic libcall survives in live code for that tree — presumably eliminated as dead; we did not bisect |
revm 42, custom target + ~40 lines of __atomic_* shims | ✓ links, 28 KB | link-time emulation instead of IR-level erasure |
| that ELF, decoded by our VM | ✓ 3,081 / 3,081 instructions, all RV32IM | — |
The whole stack compiled, ark_bn254 and k256 through aurora_engine_modexp and alloy_eip7702. Our shims are the road the industry did not take — the same soundness argument at link time instead of in IR, with one visible difference: atomic IR survives to codegen, so barriers do too, and a 1,797-instruction demo guest carried ten FENCE instructions our VM had to accept as no-ops. Erasure yields smaller traces; shims keep the toolchain stock.
Version choices matter more than they look. OpenVM's revm benchmark declares revm = "18.0.0" [44] — unlocked, since that guest workspace commits no lockfile. That line was released November 6, 2024 and predates Prague's mainnet activation: in its SpecId enum, where each fork's trailing comment carries its activation block, Prague's still reads // Prague TBD. Current revm is 42.0.1 [62]. Copying a benchmark's dependency choice into production would quietly ship an EVM more than a year behind mainnet.
6 · Plausible mechanisms that are false
Every one below is a belief we held, and wrote down in our own working notes, before verification corrected it.
"+forced-atomics fixes CAS on riscv32i." Wrong twice: it does not add the missing API (atomic_cas is a spec field, false on the stock targets [15]), and at codegen it routes RMW and CAS to __sync_* libcalls the user must supply [17][18].
"singlethread: true makes LLVM lower atomics to plain ops." Not on RISC-V — ARM's pass config responds to it, the RISC-V backend never reads it [28]. Which is exactly why these zkVMs inject the pass by hand, doing explicitly what ARM does implicitly. (Jolt injects it too, by choice rather than necessity.)
"The zkVM std PAL provides the shims." The upstream PAL is two small files of syscall bindings with no atomics content [24], and no zkVM repository defines either libcall family (§3.2).
"compiler-builtins covers it." No __atomic_* at all; __sync_* only for ARM Linux/Android and Thumb-v6k [21].
"OpenVM and Ceno get away with it by pinning an old revm." Our own explanation, wrong on both counts. The mechanism is the lower-atomic injection both apply [46][47]; with it, current CAS-using crates need neither shims nor a special revm — Ceno builds a current revm-precompile 27.0.0 this way [51]. And Ceno pins no revm at all; the interpreter is not a dependency.
"Ordinary Rust on stable requires a compressed-instruction decoder." True only of Jolt's route, because stock riscv64imac includes C [58]. RISC Zero and SP1 deliver ordinary Rust on stable-based prebuilt toolchains, OpenVM and Ceno on pinned nightlies, none adding an instruction for the purpose.
The lesson: a plausible mechanism is not evidence. Each was settleable in minutes by compiling something.
7 · Airbender and the single-guest thesis
Airbender is the clearest case of guest strategy following product strategy. Evidence first, our reading second.
Evidence. The repository calls itself "RISC-V compilation and proving tools for the ZKsync project" and its docs "the Proving Layer for ZKsync OS"; the circuit assumes trusted code, illegal operations being unprovable rather than trapped — which the docs tie to "general-purpose kernel code like ZKsync OS" [63]. The flagship guest is not compiled in-tree at all: examples/zksync_os is a prebuilt 815,744-byte binary from a separate repository [10]. ZKsync OS is a state transition function "compiled into a RISC-V binary, which can later be proven using the zksync-airbender", carrying its own EVM interpreter crate, and its build script's variants are all flavors of that same block-executor guest on Airbender's target and rustflags [64] — one codebase compiled twice, x86 for the sequencer and RISC-V for proving [61]. It is a stateless executor in all but name: block data and pre-state enter through the CSR oracle, storage reads are Merkle-verified in proving mode, the public input in x10–x17 commits to before-and-after state roots, and the same guest replays Ethereum L1 blocks [65]. The surrounding org treats provers as swappable behind the guest — zksync-os-zisk is a "second proof system for ZKsync OS chains" [67].
Counter-evidence, honestly. The announcement claims Airbender suits "any type of program", and marketing says it "proves plain Rust code and any program compiled to RISC-V 32I+M" [66]. Since February 2026 a separate airbender-platform repository has been building a genuine bring-your-own-guest SDK, at v0.2.x and not linked from the main README [68].
Our reading, marked as a reading rather than a pinned fact: Airbender is organized — deliberately — around a single guest, and §4's toolchain choices follow, since constraining the guest costs nothing when the only guest that matters is written in-house. The general-purpose ambitions live in marketing copy and a v0.2 side repository, whose release cadence (v0.2.4 landed three days before we published) is the one live signal pointing the other way.
8 · The wider design space
| Project | Guest ISA / target | Toolchain | Note |
|---|---|---|---|
| Nexus | RV32IM, stock riscv32im-unknown-none-elf | Plain pinned nightly, no fork | README: experimental, not for production [69] |
| Pico (Brevis) | RV64, riscv64im-pico-zkvm-elf | Prebuilt fork (Strategy C) | Attributes recursion and precompiles to SP1, toolchain lineage to RISC Zero [69] |
| Ziren (ex-zkMIPS) | MIPS32r2, mipsel-zkm-zkvm-elf | rustc fork + zkmup | The family's ISA outlier [69] |
| Zisk (Polygon) | RV64IMA, riscv64ima-zisk-zkvm-elf | Forked 1.94.0, prebuilt | Sets lower-atomic anyway, despite the A [70] |
| Valida (Lita) | Custom zk-native ISA — no registers, no stack, 31-bit field, degree-3 constraints | Own LLVM backend + rustc toolchain | Maximal "change the ISA to fit the prover"; release notes at our pin list an unsound prover as a known issue [71] |
| Cairo (StarkWare) | None — Cairo → Sierra → CASM | Bespoke language pipeline | Abandons ISA targeting; Sierra guarantees no panics and bounded execution by construction. Production path for Starknet [71] |
| Miden | zk-native stack VM, Miden Assembly | Rust → stock wasm32 → Wasm frontend → MASM | Mainstream reuse without an LLVM backend; no floats or V128 [71] |
| zkWasm (Delphinus) | Wasm itself | 100% stock (wasm-pack) | Arithmetizes the whole Wasm VM; the ISA-fit cost moves into the circuits [71] |
The field's center of gravity has moved to RV64: SP1 flipped from RV32 with v6 [37], and Pico, Zisk and Jolt all target 64-bit today [69][70][54]. We did not trace whether those three ever shipped 32-bit, and found no pinned statement of rationale from any team.
9 · Synthesis
1. The guest ABI, not the proof system, is where zkVMs differentiate as products. Entry conventions, I/O framing, std availability and toolchain friction are what a developer touches.
2. The atomics story standardized: claim in the spec, erase in the IR. The RV32IM-class zkVMs declare full atomics in specs justified only by single-threadedness — RISC Zero's comment, reproduced verbatim in SP1's legacy spec, admits it — then run lower-atomic on every guest build so the claim never meets the machine. Jolt reaches the same endpoint from the opposite side: a real A extension, and the pass anyway.
3. "Forked toolchain" almost never means a forked compiler. The forks that ship are upstream Rust plus single-digit commit counts; what they distribute is a prebuilt std with the erasure already applied. Strategy B versus C is really "who pays for std — the user's nightly, or the vendor's release pipeline."
4. Guest strategy is product strategy. Constrain the guest when you write the guest; buy the ecosystem when the ecosystem is the pitch; implement the ISA when stock-stable developer experience is the differentiator.
5. For L2s, the guest is converging on a single artifact. Witness in, state root out, EVM interpreter in the middle — to the point that one team organizes its stack around it and swaps provers behind it, and that Ethereum's proving roadmap treats the block executor as the guest that matters [38]. When the guest is fixed, generality stops earning its complexity.
Nearly every wrong belief we started with was correctable by pinning a commit or compiling twenty lines — the references below are arranged to make checking us equally cheap.
References
All GitHub citations are permalinks pinned to full commit SHAs; quoted line numbers were verified against file contents at those exact revisions during July 26–27, 2026. Live web pages (marketing, docs, blogs) cannot be commit-pinned; their retrieval date is given. Facts labeled "our lab" in §6 are from our internal zkVM and are reproducible from the described configurations but are not externally pinned.
- Airbender guest target — zksync-airbender@7e05fe2 · examples/basic_fibonacci/.cargo/config.toml L1–L9. Other examples identical;
experimentsadds+zimop. - Toolchain — rust-toolchain.toml:
channel = "nightly", floating. - Build flow — examples/basic_fibonacci/README.md L11–L20. Verifier guest: rust-toolchain.toml (
nightly-2025-07-25), build.sh L17 (-Z build-std). - Non-determinism read — non_determinism_source/src/lib.rs L24–L37:
csrrw {rd}, 0x7c0, x0. - Oracle value unchecked — cs/src/machine/ops/common_impls/csr.rs L33–L37:
new_unchecked_from_placeholder(…ExternalOracle), 16-bit range checks only. - Free-witness principle — docs/philosophy_and_logic.md L21.
NON_DETERMINISM_CSR = 0x7c0— cs/src/machine/mod.rs L29, risc_v_simulator/src/cycle/state.rs L26.- Delegation CSRs — blake2 = +7 (0x7c7), u256 = +10 (0x7ca); guest trigger constant L10, asm L29. (docs/tutorial.md cites a different number and is stale.)
- Delegation whitelist — risc_v_simulator/src/cycle/mod.rs L57–L64.
- Prebuilt flagship guest — examples/zksync_os/README.md: built from ZKsync OS tag 0.0.2; directory holds only README,
app.bin(815,744 bytes) and a vk. Still true at HEAD 6ec4ea7. - Entry — asm_reduced.S, link.x L54–L58, memory.x.
- Output convention — riscv_common/src/lib.rs L47–L58: registers 10–17 are "output of the computation".
- Airbender atomics/FENCE (at 7e05fe2): repo-wide grep for
__atomic_/__sync_→ zero; no+a; noalloc::syncorArc<under examples/. FENCE arm commented out — state.rs L1145–L1148. Arccfg gate — rust-lang/rust@ad0c9dc · library/alloc/src/lib.rs L263–L264.- Stock specs — riscv32i L23–L25, riscv32im L23–L25:
atomic_cas: false,+forced-atomics. - cfg derivation — rustc_session/src/config/cfg.rs L291–L298.
- forced-atomics — llvm@c18296f · RISCVFeatures.td L1954–L1958; contract at docs/Atomics.md L593–L598 ("the user is responsible for ensuring that necessary
__sync_*implementations are available"). - RMW/CAS →
__sync_*— RISCVISelLowering.cpp L1905–L1913; IR expansion suppressed at L27121–L27123, L27226–L27228. - No A, no forced-atomics ⇒
setMaxAtomicSizeInBitsSupported(0)⇒__atomic_*— RISCVISelLowering.cpp L954–L964. - The two libcall families — docs/Atomics.md L490–L495. (Atomics.md at this revision; the historical .rst is gone.)
- compiler-builtins — @9c7d3e8 · src/sync/mod.rs:
__sync_*only for ARM Linux/Android and Thumb-v6k; no__atomic_*anywhere. - risc0 target spec — rust-lang/rust@ad0c9dc · riscv32im_risc0_zkvm_elf.rs L20–L41 (the quoted comment,
atomic_cas: true,features: "+m",singlethread: true). - Tier 3 — platform-support.md L402 and the target doc page.
- Upstream zkvm std PAL — pal/zkvm/mod.rs, abi.rs: syscall bindings only, no atomics.
- RISC Zero guest I/O — risc0@3bbcd44 · guest/env/mod.rs L209–L211, serde/deserializer.rs L26–L34.
- Ecall numbers — platform/src/syscall.rs L25–L39; journal fd at platform/src/lib.rs L46.
- No shims in risc0: full-tree grep of the source tarball at 3bbcd44 for
__atomic→ zero. Historical cfg-level failure: issue #444; forward signal: PR #3496 (emulated atomics for the rv32im-m3 circuit). - singlethread ⇒ ThreadModel only — rustc PassWrapper.cpp L412–L414; ARM lowers, ARMTargetMachine.cpp L353–L357; RISC-V does not, RISCVTargetMachine.cpp L478–L479 (no ThreadModel reference anywhere in its atomic-lowering path). Pass semantics: LowerAtomicPass.cpp L9–L10.
- RISC Zero injects the pass — risc0/build/src/lib.rs L461–L481: "Replace atomic ops with nonatomic versions since the guest is single threaded".
- Distributed std carries the pass; LTO caveat — rzup/src/build.rs L219–L244, rust-toolchain-build-config.toml L12–L14 ("std's embedded LTO bitcode … keeps atomics that -C passes=lower-atomic already stripped"); PR #3783.
- risc0/rust is a distribution vehicle: compare rust-lang/rust@1.94.1 … risc0:rust:risc0-1.94.1 → ahead 8, behind 0, all eight commits touching only .github/*; head risc0/rust@06e01cb. Compare API, 2026-07-27.
- SP1 ships no shims: full-tree grep of the sp1 tarball at c5360b9 for
__atomic/__sync_→ zero definitions. - SP1 toolchain, specs, pass — name command/mod.rs L4; release tag cli/src/lib.rs L12; RV64 spec succinctlabs/rust@c714940, legacy RV32 spec with the same comment verbatim @0a16dfe; fork = upstream 1.96.0 + two commits, LLVM submodule stock. lower-atomic: command/utils.rs L51–L70 (guest builds) and build_toolchain.rs L98–L99 (distributed std, both targets).
- SP1 entry/runtime — entrypoint/src/lib.rs L218–L329.
- SP1 guest I/O (bincode) — zkvm/lib/src/io.rs L102–L164.
- SP1 syscall table — entrypoint/src/syscalls/mod.rs L58–L201.
- RV32→RV64 flip — v5.2.4 vs v6.0.0 (released 2026-02-12) and current main.
- SP1 v6 "Hypercube": docs.succinct.xyz and blog.succinct.xyz/sp1-hypercube ("Proving Ethereum in Real-Time"). Retrieved 2026-07-27; not commit-pinnable.
- OpenVM toolchain pin — @425913b (2026-06-26)
nightly-2025-08-02→ @725018c (2026-07-17)nightly-2026-01-18; build-std args at L262–L276. - OpenVM reuses the risc0 target — build/src/lib.rs L22–L23; unchanged at HEAD.
- …documented as temporary — openvm/src/pal_abi.rs L1–L6.
- OpenVM entry — openvm/src/lib.rs L76–L139.
- Hint stream — io/mod.rs L20–L31, io/read.rs L22–L34, rv32im/guest/src/lib.rs L12–L36 (custom-0 opcode 0x0b).
- Extensions and revm — extensions/ at 425913b = algebra, bigint, ecc, keccak256, native, pairing, rv32-adapters, rv32im, sha256 (git ls-tree; no RLP/serialization chip); benchmarks/guest/revm_transfer/Cargo.toml L9 declares
revm = "18.0.0", same at HEAD, no guest lockfile committed. - OpenVM ships no shims: grep of crates/, extensions/, guest-libs/ at 425913b → zero matches.
- OpenVM injects the pass — build/src/lib.rs, encode_rust_flags; unchanged at HEAD L296–L298.
- Ceno toolchain/build — rust-toolchain.toml (
nightly-2025-11-20, unchanged at master HEAD 8f7006e), examples/.cargo/config.toml and ceno_cli/src/utils.rs L78–L120 (build-std incl. std,-C passes=lower-atomic). - Ceno target spec — ceno_rt/riscv32im-ceno-zkvm-elf.json (unchanged at HEAD). No shims in-tree (git grep → zero).
- Ceno runtime — ceno_rt/src/lib.rs L132–L161:
_start,call main, halt viaecallt0=0; bump allocator from_sheap. - Hints MMIO and serde — platform.rs L89–L98 (region), ceno_host/src/lib.rs L18–L83 (header format), ceno_rt/src/mmio.rs L32–L106 (guest reader), ceno_serde/src/lib.rs ("extracted from OpenVM").
- Ceno syscalls / SP1 compatibility — ceno_emul/src/syscalls.rs ("Using the same function codes as sp1"); codes in the lockfile-pinned ceno-patch@6e90bc8; five verified byte-identical against sp1@013c24e. Only revm-family dep is
revm-precompile27.0.0; the interpreter is not in-tree. - OpenVM FENCE → nop — transpiler/src/rrs.rs L359–L362.
- Ceno FENCE → unimplemented — disassemble/mod.rs L408–L410.
- Jolt targets/toolchain — jolt@e0d5d7e · host/program.rs L240–L244, rust-toolchain.toml (stable 1.95, stock riscv targets); zero build-std repo-wide. Unchanged at HEAD 3ab638d.
- Jolt injects the pass — src/main.rs L216–L227.
- Jolt postcard I/O — jolt-sdk/macros/src/lib.rs L957–L992 (guest), L330–L341 (host), L1030–L1035 (outputs).
- Jolt memory layout — common/src/jolt_device.rs L361–L397, constants.rs (
XLEN = 64,RAM_START_ADDRESS = 0x80000000). - Jolt A and C — 22 AMO/LR/SC files (e.g. amoaddw.rs), reservation semantics scw.rs L24–L28, AMOs via
inline_sequence(amoaddw.rs L39–L47), RVC decode cpu.rs L507–L519. FENCE is a real decoded instruction (tracer/src/instruction/fence.rs). - Jolt inlines — tracer/src/instruction/inline.rs: custom-0/1 opcodes, link-time registration, typed advice. Crates: bigint, blake2, blake3, grumpkin, keccak256, p256, secp256k1, sha2.
- Jolt entry via ZeroOS — src/linker.ld.template, jolt-sdk/src/runtime/boot.rs L31–L35; boot asm from LayerZero-Labs/ZeroOS, pinned by rev.
- ZKsync OS dual compilation — zksync-airbender@6ec4ea7 · docs/overview.md L9.
- revm ages — 18.0.0 released 2024-11-06, 42.0.1 on 2026-07-23 (crates.io, retrieved 2026-07-27); 18.0.0's crate metadata pins its source to bluealloy/revm@900409f · specification.rs L31–L32.
- Airbender positioning — zksync-airbender@6ec4ea7 (main HEAD) · README.md L5, docs/overview.md L3 and L35/L45 (trusted-code model; RV32IM "suitable for proving general-purpose kernel code like ZKsync OS").
- ZKsync OS as the guest — zksync-os@21ad8f6 · README.md L5 and L22 (
evm_interpreter), zksync_os/.cargo/config.toml, dump_bin.sh L4 (all build types are variants of the same guest, plus test/debug builds). - Witness in, state root out — docs/overview.md L5–L7 and L41, oracles.md L23–L27, l1_integration.md L12 (x10–x17 as public input);
ChainStateCommitmentbefore/after in basic_system public_input.rs; L1 replay in docs/proving_ethereum.md. - General-purpose claims (counter-evidence) — paragraph.com/@zksync "Introducing ZKsync Airbender" (2025-06-24) and zksync.io/airbender. Retrieved 2026-07-27; not commit-pinnable.
- Prover-swappable ecosystem — matter-labs org repo descriptions (GitHub API, 2026-07-27):
zksync-os-zisk"Second proof system for ZKsync OS chains". Mutable metadata. - airbender-platform — @a350ee3 (main HEAD, 2026-07-24) · README.md: guest SDK with entry macro and std support, "under active development"; created 2026-02-27, v0.2.0–v0.2.4; not linked from the zksync-airbender README.
- Register-machine landscape (main-branch HEADs, 2026-07-27) — Nexus @f2ad126 · runtime/.cargo/config.toml (stock riscv32im,
nightly-2025-05-09; README: experimental); Pico @f9986aa · build.rs L11–L12 (riscv64im-pico-zkvm-elf, prebuilt from brevis-network/rust; README credits SP1 and RISC0); Ziren @e6945a7 · lib.rs L11 (mipsel-zkm-zkvm-elf, rustc fork + zkmup). - Zisk — @5cbb2cb · ziskbuild/src/lib.rs L10–L24; toolchain from 0xPolygonHermez/rust (branch zisk, 1.94.0) with toolchain/build.rs setting
-Cpasses=lower-atomic; README: undergoing audits. - Alt-ISA landscape (default-branch HEADs, 2026-07-27) — Valida valida-vm@c36d317, valida-releases@3ba909c L94–L100 (
cargo +valida build), L278 (clang -target valida), L347 ("The prover is unsound"); Cairo starkware-libs/cairo@39ad12d (Sierra safety guarantees at crates/cairo-lang-sierra/src/lib.rs L1–L8); Miden compiler@6def83a L9–L20, frontend/wasm L5–L8 (no floats/V128), miden-vm@4c8fe58 L12 (alpha); zkWasm zkWasm@1fb46ec (stockwasm-pack).
