The Bytecode Ra Polynomial
Summary
For our specific example we have
Input: - Bytecode array (94 instructions at
positions 0..93) - Trace (63 cycles) - BytecodePCMapper
(address → bytecode position) discussed in detail here
Process: For each cycle i: 1. Get instruction
address from cycle.address 2. Map address → bytecode
position using get_pc() which uses
BytecodePCMapper. This gives us the index/position in the
bytecode array (0..93) 3. Handle virtual sequences by calculating offset
from virtual_sequence_remaining 4. Store in
BytecodeRa[0][i] = Some(pc as u8)
Output: One polynomial with 63 entries, getting ready for the Shout stages.
Our Simple Case: d = 1
Why? Because we have 94 instructions which is strictly less than 256, all bytecode positions fit in a single 8-bit value.
BytecodeRa[0]: Vec<Option<u8>> // One polynomial, length = 63 (trace length)
Each entry BytecodeRa[0][i] directly stores which
bytecode instruction executed at cycle i.
What is BytecodeRa?
BytecodeRa tracks which bytecode instruction is
executing at each cycle. For a small program like yours:
Structure:
BytecodeRa[0] = [
Some(1), // Cycle 0 executes bytecode[1]
Some(2), // Cycle 1 executes bytecode[2]
Some(3), // Cycle 2 executes bytecode[3]
Some(4), // Cycle 3 executes bytecode[4]
...
Some(5), // Cycle 62 executes bytecode[5]
] // Length = 63 cycles
This is a witness polynomial that tells the
verifier: “At cycle i, we executed the instruction at bytecode position
BytecodeRa[0][i].” You can verify this by looking at the
actual BytecodeRa
generated by Jolt for our program.
The Construction Process
Input Data
1. Bytecode Array (94 instructions):
[0] NOOP { address: 0x00000000, ... } // Reserved
[1] AUIPC { address: 0x80000000, ... }
[2] ADDI { address: 0x80000004, ... }
[3] AUIPC { address: 0x80000008, ... }
...
[36] ADDI { address: 0x80000042, virtual_sequence_remaining: Some(7), ... }
[37] ANDI { address: 0x80000042, virtual_sequence_remaining: Some(6), ... }
[38] LD { address: 0x80000042, virtual_sequence_remaining: Some(5), ... }
...
[93] JALR { address: 0x80000316, ... }
2. BytecodePCMapper - Converts instruction addresses → bytecode positions:
BytecodePCMapper {
indices[34] = Some((36, 7)), // Address 0x80000042 → position 36, 8-step sequence
...
}
3. Execution Trace (63 cycles):
Cycle 0: AUIPC at address 0x80000000
Cycle 1: ADDI at address 0x80000004
...
Cycle 8: ADDI at address 0x80000042 (virtual seq 7)
Cycle 9: ANDI at address 0x80000042 (virtual seq 6)
Cycle 10: LD at address 0x80000042 (virtual seq 5)
...
Cycle 62: JAL at address 0x80000010
Step-by-Step Construction
For each cycle i in the trace:
let pc = preprocessing.shared.bytecode.get_pc(cycle);
BytecodeRa[0][i] = Some(pc as u8);
That’s it! Since d = 1, we directly store the bytecode
position without any chunking.
How get_pc() Works
The get_pc() function uses the BytecodePCMapper to
convert the cycle’s instruction address to its bytecode position,
accounting for virtual instruction sequences.
Example 1: Simple Instruction (Cycle 0)
// Cycle 0: AUIPC at address 0x80000000
Step 1: Get mapper index
get_index(0x80000000) = 1
Step 2: Look up in mapper
mapper.indices[1] = Some((1, 0))
| |
| └─ No virtual sequence
└──── Bytecode position 1
Step 3: Store result
BytecodeRa[0][0] = Some(1)
Result: Cycle 0 executed bytecode instruction 1.
Example 2: Virtual Instruction Sequence (Cycle 8)
// Cycle 8: ADDI at address 0x80000042, virtual_sequence_remaining = Some(7)
Step 1: Get mapper index
get_index(0x80000042) = 34
Step 2: Look up in mapper
mapper.indices[34] = Some((36, 7))
| |
| └─ First instruction has 7 more following
└──── Sequence starts at bytecode position 36
Step 3: Calculate position in sequence
// This is the FIRST instruction (virtual_sequence_remaining = 7)
// So offset = 0
pc = 36 + 0 = 36
Step 4: Store result
BytecodeRa[0][8] = Some(36)
Result: Cycle 8 executed bytecode instruction 36 (the first ADDI in the virtual sequence).
What If d > 1? (Larger Programs)
For larger programs with more than 256 bytecode instructions, we’d need multiple chunks:
Example with 500 instructions (d = 2):
BytecodeRa[0][i] = (pc >> 8) % 256 // High byte
BytecodeRa[1][i] = (pc >> 0) % 256 // Low byte
// To reconstruct: pc = BytecodeRa[0][i] * 256 + BytecodeRa[1][i]
But for our 94-instruction program, d = 1 is all we
need!