Summary of the full flow of zkvm compiling
| Stage | What happens | Output |
|---|---|---|
| 1. Compilation | Rust → LLVM IR → RISC-V machine code | ELF file |
| 2. Parsing / loading | zkVM loads ELF into memory | memory layout |
| 3. Execution / simulation | zkVM runs RISC-V instructions symbolically | execution trace |
| 4. Constraint generation | Each step → algebraic constraint | AIR / PLONK polynomials |
| 5. Proof generation | Apply STARK/SNARK | zk-proof |
| 6. Verification | Verify proof on-chain or off-chain | ✅ or ❌ |
Why RISC-V?
- Open standard — no licensing, anyone can implement it.
- Simple ISA — small number of instructions, easy to reason about in circuits.
- LLVM-supported — you can compile Rust, C, Go, etc. to RISC-V easily.
- Deterministic — good for reproducible computation proofs.
LLVM
LLVM is a compiler infrastructure that can turn code into optimized machine code for many architectures (x86, ARM, RISC-V, etc.).
What LLVM actually is
Think of LLVM as the middle and backend of a compiler pipeline.
If you imagine compiling code as a 3-stage process:
| Stage | Name | What it does | Example |
|---|---|---|---|
| 1️⃣ | Frontend | Parses source code into syntax trees | Rust → rustc, C → clang |
| 2️⃣ | Optimizer / IR | Translates to intermediate representation (LLVM IR) and optimizes | LLVM |
| 3️⃣ | Backend | Generates target-specific machine code | x86, ARM, or RISC-V |
How it connects to Rust
Rust uses LLVM internally.
When you run:
rustc main.rs
Rustc does:
- Parse your code
- Build the abstract syntax tree
- Generate LLVM IR
- Ask LLVM to optimize it
- Ask LLVM to emit machine code for your chosen target (x86, ARM, RISC-V, WebAssembly, etc.)
That’s why you can cross-compile Rust easily:
rustc --target=riscv32im-unknown-none-elf main.rs
→ It just tells LLVM to emit RISC-V machine code instead of x86.
LLVM IR (Intermediate Representation) is the same language for all frontends that use LLVM —
but each high-level language might generate it a little differently depending on its semantics.