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!