Overview
The tracer is Jolt’s RISC-V emulator that executes guest programs and produces detailed execution traces. It serves as the first stage in the proving pipeline, converting a compiled RISC-V ELF binary into a sequence ofCycle structs that capture every instruction executed.
Role in the Proving System
The tracer is the foundation of Jolt’s zkVM:- Input: Takes a RISC-V ELF binary (RV64IMAC instruction set) and program inputs
- Execution: Emulates the program instruction-by-instruction, tracking all state changes
- Output: Produces a
Vec<Cycle>execution trace and final memory state - Handoff: The execution trace becomes input to witness generation in the next stage
Architecture
Core Components
Emulator (tracer/src/emulator/)
- Full RISC-V CPU emulation with registers, memory management unit (MMU), and program counter
- Supports both 32-bit and 64-bit XLEN modes
- Handles compressed (16-bit) and standard (32-bit) instruction formats
tracer/src/instruction/)
- Complete RV64IMAC instruction implementations
- Virtual instruction extensions for optimized cryptographic operations (via jolt-inlines)
- Instruction normalization and decoding
- Memory layout configuration for inputs, outputs, bytecode, and advice regions
- Address remapping for efficient constraint checking
- I/O device (
JoltDevice) tracking program inputs, outputs, panic state
Execution Trace Format
EachCycle in the trace represents a single instruction execution and contains:
- Program counter (PC)
- Instruction opcode and operands
- Register reads and writes
- Memory accesses (address, value)
- Instruction-specific metadata (e.g., virtual sequence remaining for inlined instructions)
Lazy Tracing
Jolt uses lazy trace generation to reduce memory overhead:- Traces are generated on-demand via iterator pattern
- Checkpointing support for parallel proving
- Avoids materializing the entire trace in memory upfront
Checkpointing
For large programs, the tracer can save periodic checkpoints:- Each checkpoint captures emulator state at a specific cycle count
- Enables parallel proof generation by splitting trace into chunks
- Checkpoints can independently regenerate their trace segment
Bytecode Preprocessing
The tracer preprocesses bytecode for efficient constraint checking: PC Mapping (bytecode/mod.rs)
- Maps physical instruction addresses to virtual PC values
- Handles inline instruction sequences (multi-cycle operations)
- Prepends a no-op instruction at PC=0 for padding
- Instructions must be aligned to
ALIGNMENT_FACTOR_BYTECODEboundaries - Pads bytecode to next power of 2 for efficient polynomial encoding
Integration with Proving System
The execution trace flows into witness generation:-
Trace → Witness Polynomials:
- PC values → bytecode lookup polynomials
- Register reads/writes → register checking polynomials
- Memory accesses → RAM checking polynomials
- Instruction flags → instruction lookup polynomials
-
Memory State:
- Initial memory: bytecode + inputs + advice
- Final memory: outputs + termination/panic bits
- Used to derive RAM checking constraints
Key Files
tracer/src/lib.rs: Main trace generation functions (trace,trace_lazy,trace_checkpoints)tracer/src/emulator/: RISC-V CPU emulator implementationtracer/src/instruction/: Instruction set implementationsjolt-core/src/zkvm/bytecode/: Bytecode preprocessing and PC mapping
Performance Considerations
- Lazy evaluation: Traces generated on-demand reduce peak memory
- Checkpointing overhead: Tracking memory state adds ~10-20% overhead
- Parallel proving: Checkpoints enable splitting large traces across workers
- ELF decoding: Bytecode extraction happens once during setup
Next Steps
- Witness Generation: How traces become committed polynomials
- RAM Checking: Memory consistency verification
- Instruction Lookups: Proving instruction correctness