From 46f898ec747e174776e592316d501735455c1c9a Mon Sep 17 00:00:00 2001 From: MauroFab Date: Tue, 23 Jun 2026 16:59:20 -0300 Subject: [PATCH] refactor(prover): add VM table trace writers --- prover/src/tables/bitwise.rs | 41 ++++--- prover/src/tables/branch.rs | 55 +++++----- prover/src/tables/bytewise.rs | 24 +++-- prover/src/tables/commit.rs | 47 ++++---- prover/src/tables/cpu.rs | 110 ++++++++++--------- prover/src/tables/cpu32.rs | 82 +++++++------- prover/src/tables/decode.rs | 88 +++++++-------- prover/src/tables/dvrm.rs | 65 +++++------ prover/src/tables/ec_scalar.rs | 26 ++--- prover/src/tables/ecdas.rs | 62 +++++------ prover/src/tables/ecsm.rs | 71 ++++++------ prover/src/tables/eq.rs | 32 +++--- prover/src/tables/halt.rs | 16 ++- prover/src/tables/keccak.rs | 42 +++----- prover/src/tables/keccak_rc.rs | 20 ++-- prover/src/tables/keccak_rnd.rs | 77 ++++++------- prover/src/tables/load.rs | 36 +++---- prover/src/tables/lt.rs | 54 ++++------ prover/src/tables/memw.rs | 40 +++---- prover/src/tables/memw_aligned.rs | 48 ++++----- prover/src/tables/memw_register.rs | 36 ++++--- prover/src/tables/mul.rs | 53 ++++----- prover/src/tables/page.rs | 21 ++-- prover/src/tables/register.rs | 25 ++--- prover/src/tables/shift.rs | 62 ++++++----- prover/src/tables/store.rs | 31 +++--- prover/src/tables/types.rs | 167 +++++++++++++++++++++++++++++ 27 files changed, 757 insertions(+), 674 deletions(-) diff --git a/prover/src/tables/bitwise.rs b/prover/src/tables/bitwise.rs index cb92e37ce..10ac42e21 100644 --- a/prover/src/tables/bitwise.rs +++ b/prover/src/tables/bitwise.rs @@ -36,7 +36,7 @@ use stark::trace::{TraceTable, columns2rows}; #[cfg(feature = "parallel")] use rayon::prelude::*; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, alu_op}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable, alu_op}; // ========================================================================= // Column indices for BITWISE table @@ -357,38 +357,37 @@ pub fn preprocessed_commitment(options: &ProofOptions) -> Commitment { /// All output columns are precomputed. Multiplicity columns are initialized /// to zero and will be updated when other tables send lookups. pub fn generate_bitwise_trace() -> TraceTable { - let mut data = vec![FE::zero(); NUM_ROWS * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); NUM_ROWS * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for x in 0u32..256 { for y in 0u32..256 { for z in 0u32..16 { let row_idx = (x as usize) + (y as usize) * 256 + (z as usize) * 256 * 256; - let base = row_idx * cols::NUM_COLUMNS; // Input columns - data[base + cols::X] = FE::from(x as u64); - data[base + cols::Y] = FE::from(y as u64); - data[base + cols::Z] = FE::from(z as u64); + table.set_byte(row_idx, cols::X, x as u8); + table.set_byte(row_idx, cols::Y, y as u8); + table.set_byte(row_idx, cols::Z, z as u8); // Bitwise operation results - data[base + cols::AND] = FE::from((x & y) as u64); - data[base + cols::OR] = FE::from((x | y) as u64); - data[base + cols::XOR] = FE::from((x ^ y) as u64); + table.set_byte(row_idx, cols::AND, (x & y) as u8); + table.set_byte(row_idx, cols::OR, (x | y) as u8); + table.set_byte(row_idx, cols::XOR, (x ^ y) as u8); // MSB extractions let msb8 = (x >> 7) & 1; let halfword = x + y * 256; let msb16 = (halfword >> 15) & 1; - data[base + cols::MSB8] = FE::from(msb8 as u64); - data[base + cols::MSB16] = FE::from(msb16 as u64); + table.set_bool(row_idx, cols::MSB8, msb8 == 1); + table.set_bool(row_idx, cols::MSB16, msb16 == 1); // Zero check (X + 256*Y + 65536*Z must be zero) - let is_zero = if x == 0 && y == 0 && z == 0 { - 1u64 - } else { - 0u64 - }; - data[base + cols::ZERO] = FE::from(is_zero); + table.set_bool(row_idx, cols::ZERO, x == 0 && y == 0 && z == 0); // Shift operations on halfword let sll = if z == 0 { @@ -397,8 +396,8 @@ pub fn generate_bitwise_trace() -> TraceTable> (16 - z) }; - data[base + cols::SLL] = FE::from(sll as u64); - data[base + cols::SLLC] = FE::from(sllc as u64); + table.set_half(row_idx, cols::SLL, sll as u16); + table.set_half(row_idx, cols::SLLC, sllc as u16); // Multiplicity columns start at zero // They will be updated by update_multiplicities() @@ -406,7 +405,7 @@ pub fn generate_bitwise_trace() -> TraceTable = op_map.into_iter().collect(); let num_rows = unique_ops.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, (op, multiplicity)) in unique_ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - - // Extract pc as DWordWL: [Word, Word] - let pc_0 = (op.pc & 0xFFFF_FFFF) as u32; - let pc_1 = (op.pc >> 32) as u32; - - // Extract offset as DWordWL: [Word, Word] - let offset_0 = (op.offset & 0xFFFF_FFFF) as u32; - let offset_1 = (op.offset >> 32) as u32; - - // Extract register as DWordWL: [Word, Word] - let register_0 = (op.register & 0xFFFF_FFFF) as u32; - let register_1 = (op.register >> 32) as u32; - // Compute next_pc let next_pc_unmasked = op.compute_next_pc_unmasked(); let next_pc = op.compute_next_pc(); @@ -203,23 +194,25 @@ pub fn generate_branch_trace( let next_pc_high_2 = ((next_pc >> 48) & 0xFFFF) as u16; // Store columns - data[base + cols::PC_0] = FE::from(pc_0 as u64); - data[base + cols::PC_1] = FE::from(pc_1 as u64); - data[base + cols::OFFSET_0] = FE::from(offset_0 as u64); - data[base + cols::OFFSET_1] = FE::from(offset_1 as u64); - data[base + cols::REGISTER_0] = FE::from(register_0 as u64); - data[base + cols::REGISTER_1] = FE::from(register_1 as u64); - data[base + cols::JALR] = FE::from(if op.jalr { 1u64 } else { 0u64 }); - data[base + cols::NEXT_PC_HIGH_0] = FE::from(next_pc_high_0 as u64); - data[base + cols::NEXT_PC_HIGH_1] = FE::from(next_pc_high_1 as u64); - data[base + cols::NEXT_PC_HIGH_2] = FE::from(next_pc_high_2 as u64); - data[base + cols::NEXT_PC_LOW_0] = FE::from(next_pc_low_0 as u64); - data[base + cols::NEXT_PC_LOW_1] = FE::from(next_pc_low_1 as u64); - data[base + cols::UNMASKED_LOW_BYTE] = FE::from(unmasked_low_byte as u64); - data[base + cols::MU] = FE::from(*multiplicity); + table.set_dword_wl(row_idx, cols::PC_0, op.pc); + table.set_dword_wl(row_idx, cols::OFFSET_0, op.offset); + table.set_dword_wl(row_idx, cols::REGISTER_0, op.register); + table.set_bool(row_idx, cols::JALR, op.jalr); + table.set_halves( + row_idx, + cols::NEXT_PC_HIGH_0, + &[next_pc_high_0, next_pc_high_1, next_pc_high_2], + ); + table.set_bytes( + row_idx, + cols::NEXT_PC_LOW_0, + &[next_pc_low_0, next_pc_low_1], + ); + table.set_byte(row_idx, cols::UNMASKED_LOW_BYTE, unmasked_low_byte); + table.set_u64(row_idx, cols::MU, *multiplicity); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/bytewise.rs b/prover/src/tables/bytewise.rs index 16c811cfb..82d7c8772 100644 --- a/prover/src/tables/bytewise.rs +++ b/prover/src/tables/bytewise.rs @@ -19,7 +19,7 @@ use stark::lookup::{BusInteraction, BusValue, Multiplicity, Packing}; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, alu_op}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable, alu_op}; // ========================================================================= // Column indices for BYTEWISE table @@ -106,22 +106,24 @@ pub fn generate_bytewise_trace( let unique_ops: Vec<_> = op_map.into_iter().collect(); let num_rows = unique_ops.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, (op, multiplicity)) in unique_ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; let res = op.compute_res(); - for i in 0..8 { - data[base + cols::A[i]] = FE::from((op.a >> (8 * i)) & 0xFF); - data[base + cols::B[i]] = FE::from((op.b >> (8 * i)) & 0xFF); - data[base + cols::RES[i]] = FE::from((res >> (8 * i)) & 0xFF); - } - data[base + cols::OP] = FE::from(op.op as u64); - data[base + cols::MU] = FE::from(*multiplicity); + table.set_dword_bl(row_idx, cols::A[0], op.a); + table.set_dword_bl(row_idx, cols::B[0], op.b); + table.set_dword_bl(row_idx, cols::RES[0], res); + table.set_byte(row_idx, cols::OP, op.op); + table.set_u64(row_idx, cols::MU, *multiplicity); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/commit.rs b/prover/src/tables/commit.rs index 8c979b664..c1663711e 100644 --- a/prover/src/tables/commit.rs +++ b/prover/src/tables/commit.rs @@ -52,7 +52,7 @@ use stark::trace::TraceTable; use crate::constraints::templates::{AddConstraint, AddOperand}; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; // ========================================================================= // Column indices for COMMIT table @@ -164,32 +164,29 @@ pub fn generate_commit_trace( ) -> TraceTable { let n = ops.len(); let num_rows = n.next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - // Timestamp (DWordWL) - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); // Index (BaseField) - data[base + cols::INDEX] = FE::from(op.index); + table.set_u64(row_idx, cols::INDEX, op.index); // Address (DWordWL) - data[base + cols::ADDRESS_0] = FE::from(op.address & 0xFFFF_FFFF); - data[base + cols::ADDRESS_1] = FE::from(op.address >> 32); + table.set_dword_wl(row_idx, cols::ADDRESS_0, op.address); // address_incr = address + 1 (DWordHL: 4 halfwords) let address_incr = op.address.wrapping_add(1); - data[base + cols::ADDRESS_INCR_0] = FE::from(address_incr & 0xFFFF); - data[base + cols::ADDRESS_INCR_1] = FE::from((address_incr >> 16) & 0xFFFF); - data[base + cols::ADDRESS_INCR_2] = FE::from((address_incr >> 32) & 0xFFFF); - data[base + cols::ADDRESS_INCR_3] = FE::from((address_incr >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::ADDRESS_INCR_0, address_incr); // Count (DWordWL) - data[base + cols::COUNT_0] = FE::from(op.count & 0xFFFF_FFFF); - data[base + cols::COUNT_1] = FE::from(op.count >> 32); + table.set_dword_wl(row_idx, cols::COUNT_0, op.count); // count_decr: if count == 0, use 0xFFFF_FFFF_FFFF_FFFF; else count - 1 let count_decr = if op.count == 0 { @@ -197,37 +194,33 @@ pub fn generate_commit_trace( } else { op.count - 1 }; - data[base + cols::COUNT_DECR_0] = FE::from(count_decr & 0xFFFF); - data[base + cols::COUNT_DECR_1] = FE::from((count_decr >> 16) & 0xFFFF); - data[base + cols::COUNT_DECR_2] = FE::from((count_decr >> 32) & 0xFFFF); - data[base + cols::COUNT_DECR_3] = FE::from((count_decr >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::COUNT_DECR_0, count_decr); // Control bits - data[base + cols::FIRST] = FE::from(op.first as u64); - data[base + cols::END] = FE::from(op.end as u64); + table.set_bool(row_idx, cols::FIRST, op.first); + table.set_bool(row_idx, cols::END, op.end); // Value - data[base + cols::VALUE] = FE::from(op.value as u64); + table.set_byte(row_idx, cols::VALUE, op.value); // mu = 1 for all real rows (first, middle, and end rows) - data[base + cols::MU] = FE::one(); + table.set_fe(row_idx, cols::MU, FE::one()); } // Padding rows: spec requires count=1 and address_incr=[1,0,0,0] so // the unconditional ADD/SUB templates have valid carry values. // count=1 → count_decr=0 (all halfwords zero), address=0 → address_incr=1. for row_idx in n..num_rows { - let base = row_idx * cols::NUM_COLUMNS; // count = 1 (low word) - data[base + cols::COUNT_0] = FE::one(); + table.set_fe(row_idx, cols::COUNT_0, FE::one()); // address_incr halfword 0 = 1 (address=0, so address+1 = 1) - data[base + cols::ADDRESS_INCR_0] = FE::one(); + table.set_fe(row_idx, cols::ADDRESS_INCR_0, FE::one()); // All other fields remain zero: timestamp=0, address=0, count_1=0, // count_decr=[0,0,0,0], first=0, end=0, value=0, mu=0, // address_incr_1..3=0 } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/cpu.rs b/prover/src/tables/cpu.rs index 450595ec9..1752022b9 100644 --- a/prover/src/tables/cpu.rs +++ b/prover/src/tables/cpu.rs @@ -24,7 +24,7 @@ //! JALR bit (the memory-width bits are 0), so `mem_flags ∈ {0,1} = JALR` and the //! `mem_flags` column is used directly as `JALR` wherever it is gated by `BRANCH`. -use super::types::{BusId, DecodeEntry, FE, GoldilocksExtension, GoldilocksField, alu_op}; +use super::types::{BusId, DecodeEntry, FE, GoldilocksExtension, GoldilocksField, VmTable, alu_op}; use crate::Error; use executor::vm::{ instruction::{decoding::Instruction, execution::SyscallNumbers}, @@ -439,20 +439,23 @@ pub fn generate_cpu_trace( ) -> TraceTable { let n = operations.len(); let num_rows = n.next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in operations.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; let f = &op.decode.fields; let word = f.word_instr; // For a word_instr delegate row the operational flags/register I/O are // suppressed (CPU32 owns them); only the PC-advancing columns are set. - let effective = |flag: bool| (!word && flag) as u64; + let effective = |flag: bool| !word && flag; - data[base + cols::TIMESTAMP] = FE::from(op.timestamp); - data[base + cols::PC_0] = FE::from(op.decode.pc & 0xFFFF_FFFF); - data[base + cols::PC_1] = FE::from(op.decode.pc >> 32); + table.set_u64(row_idx, cols::TIMESTAMP, op.timestamp); + table.set_dword_wl(row_idx, cols::PC_0, op.decode.pc); // rs1/rs2/rd and read/write flags are only present on non-word rows. let (rs1, rs2, rd) = if word { @@ -460,15 +463,27 @@ pub fn generate_cpu_trace( } else { (f.rs1, f.rs2, f.rd) }; - data[base + cols::RS1] = FE::from(rs1 as u64); - data[base + cols::RS2] = FE::from(rs2 as u64); - data[base + cols::RD] = FE::from(rd as u64); + table.set_byte(row_idx, cols::RS1, rs1); + table.set_byte(row_idx, cols::RS2, rs2); + table.set_byte(row_idx, cols::RD, rd); // x0 is hardwired zero (never read/written); x255 is the PC register and // must be read (read_register1=1) so its MEMW interaction fires. - data[base + cols::READ_REGISTER1] = FE::from(effective(f.read_register1 && f.rs1 != 0)); - data[base + cols::READ_REGISTER2] = FE::from(effective(f.read_register2 && f.rs2 != 0)); - data[base + cols::WRITE_REGISTER] = FE::from(effective(f.write_register && f.rd != 0)); + table.set_bool( + row_idx, + cols::READ_REGISTER1, + effective(f.read_register1 && f.rs1 != 0), + ); + table.set_bool( + row_idx, + cols::READ_REGISTER2, + effective(f.read_register2 && f.rs2 != 0), + ); + table.set_bool( + row_idx, + cols::WRITE_REGISTER, + effective(f.write_register && f.rd != 0), + ); // On word delegate rows, all operational data columns are 0 (CPU32 owns // the real values); the register-zero / arg2 / rvd=res constraints all @@ -480,52 +495,44 @@ pub fn generate_cpu_trace( (op.decode.imm, op.rvd, op.rv1, op.rv2, op.arg2, op.res) }; - data[base + cols::IMM_0] = FE::from(imm & 0xFFFF_FFFF); - data[base + cols::IMM_1] = FE::from(imm >> 32); + table.set_dword_wl(row_idx, cols::IMM_0, imm); - data[base + cols::HALF_INSTRUCTION_LENGTH] = FE::from(f.half_instruction_length as u64); - data[base + cols::WORD_INSTR] = FE::from(word as u64); + table.set_byte( + row_idx, + cols::HALF_INSTRUCTION_LENGTH, + f.half_instruction_length, + ); + table.set_bool(row_idx, cols::WORD_INSTR, word); - data[base + cols::ALU] = FE::from(effective(f.alu)); - data[base + cols::ALU_FLAGS] = FE::from(if word { 0 } else { f.alu_flags as u64 }); - data[base + cols::ADD] = FE::from(effective(f.add)); - data[base + cols::SUB] = FE::from(effective(f.sub)); - data[base + cols::MEMORY] = FE::from(effective(f.memory)); - data[base + cols::MEM_FLAGS] = FE::from(if word { 0 } else { f.mem_flags as u64 }); - data[base + cols::BRANCH] = FE::from(effective(f.branch)); - data[base + cols::ECALL] = FE::from(effective(f.ecall)); + table.set_bool(row_idx, cols::ALU, effective(f.alu)); + table.set_byte(row_idx, cols::ALU_FLAGS, if word { 0 } else { f.alu_flags }); + table.set_bool(row_idx, cols::ADD, effective(f.add)); + table.set_bool(row_idx, cols::SUB, effective(f.sub)); + table.set_bool(row_idx, cols::MEMORY, effective(f.memory)); + table.set_byte(row_idx, cols::MEM_FLAGS, if word { 0 } else { f.mem_flags }); + table.set_bool(row_idx, cols::BRANCH, effective(f.branch)); + table.set_bool(row_idx, cols::ECALL, effective(f.ecall)); - data[base + cols::NEXT_PC_0] = FE::from(op.next_pc & 0xFFFF_FFFF); - data[base + cols::NEXT_PC_1] = FE::from(op.next_pc >> 32); + table.set_dword_wl(row_idx, cols::NEXT_PC_0, op.next_pc); - data[base + cols::RVD_0] = FE::from(rvd & 0xFFFF_FFFF); - data[base + cols::RVD_1] = FE::from(rvd >> 32); + table.set_dword_wl(row_idx, cols::RVD_0, rvd); // rv1/rv2/arg2 as DWordWL (2 × 32-bit words). - data[base + cols::RV1_0] = FE::from(rv1 & 0xFFFF_FFFF); - data[base + cols::RV1_1] = FE::from(rv1 >> 32); - data[base + cols::RV2_0] = FE::from(rv2 & 0xFFFF_FFFF); - data[base + cols::RV2_1] = FE::from(rv2 >> 32); - data[base + cols::ARG2_0] = FE::from(arg2 & 0xFFFF_FFFF); - data[base + cols::ARG2_1] = FE::from(arg2 >> 32); + table.set_dword_wl(row_idx, cols::RV1_0, rv1); + table.set_dword_wl(row_idx, cols::RV2_0, rv2); + table.set_dword_wl(row_idx, cols::ARG2_0, arg2); // res as DWordHL (4 × 16-bit halves). - for i in 0..4 { - data[base + cols::RES[i]] = FE::from((res >> (i * 16)) & 0xFFFF); - } + table.set_dword_hl(row_idx, cols::RES_0, res); - data[base + cols::BRANCH_COND] = FE::from(op.branch_cond as u64); + table.set_bool(row_idx, cols::BRANCH_COND, op.branch_cond); // Inline-PC coordination columns. - let pc_double_read = (!word && f.read_register1 && f.rs1 == 255) as u64; + let pc_double_read = !word && f.read_register1 && f.rs1 == 255; let ts_lo = op.timestamp & 0xFFFF_FFFF; - let prev_pc_ts_borrow = if pc_double_read == 0 && ts_lo < 3 { - 1 - } else { - 0 - }; - data[base + cols::PC_DOUBLE_READ] = FE::from(pc_double_read); - data[base + cols::PREV_PC_TIMESTAMP_BORROW] = FE::from(prev_pc_ts_borrow); + let prev_pc_ts_borrow = !pc_double_read && ts_lo < 3; + table.set_bool(row_idx, cols::PC_DOUBLE_READ, pc_double_read); + table.set_bool(row_idx, cols::PREV_PC_TIMESTAMP_BORROW, prev_pc_ts_borrow); } // Padding rows: pc = next_pc = 1 (odd, unreachable), half_instruction_length = 0 so @@ -538,14 +545,13 @@ pub fn generate_cpu_trace( // lands on last_ts + 1, where the HALT chip's emit_pc deposited pc = 1. let last_ts = operations.last().map(|op| op.timestamp).unwrap_or(0); for row_idx in n..num_rows { - let base = row_idx * cols::NUM_COLUMNS; let j = (row_idx - n + 1) as u64; - data[base + cols::TIMESTAMP] = FE::from(last_ts + 4 * j); - data[base + cols::PC_0] = FE::from(CPU_PADDING_PC); - data[base + cols::NEXT_PC_0] = FE::from(CPU_PADDING_PC); + table.set_u64(row_idx, cols::TIMESTAMP, last_ts + 4 * j); + table.set_u64(row_idx, cols::PC_0, CPU_PADDING_PC); + table.set_u64(row_idx, cols::NEXT_PC_0, CPU_PADDING_PC); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } /// Generates the CPU trace table directly from executor logs. diff --git a/prover/src/tables/cpu32.rs b/prover/src/tables/cpu32.rs index 2aa9c87a3..d7dbd5d6f 100644 --- a/prover/src/tables/cpu32.rs +++ b/prover/src/tables/cpu32.rs @@ -25,7 +25,8 @@ use stark::table::TableView; use stark::trace::TraceTable; use super::types::{ - BusId, FE, GoldilocksExtension, GoldilocksField, SHIFT_16, alu_op, packed_decode_shrunk, + BusId, FE, GoldilocksExtension, GoldilocksField, SHIFT_16, VmTable, alu_op, + packed_decode_shrunk, }; use crate::constraints::templates::{AddConstraint, AddOperand, new_is_bit_constraints}; @@ -197,65 +198,60 @@ pub fn generate_cpu32_trace( operations: &[Cpu32Operation], ) -> TraceTable { let num_rows = operations.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in operations.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; let aux = op.compute_aux(); // Inputs - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); - data[base + cols::PC_0] = FE::from(op.pc & 0xFFFF_FFFF); - data[base + cols::PC_1] = FE::from(op.pc >> 32); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); + table.set_dword_wl(row_idx, cols::PC_0, op.pc); // rv1 as DWordWHH: [Half, Half, Word] - data[base + cols::RS1] = FE::from(op.rs1 as u64); - data[base + cols::READ_REGISTER1] = FE::from(op.read_register1 as u64); - data[base + cols::RV1_0] = FE::from(op.rv1 & 0xFFFF); - data[base + cols::RV1_1] = FE::from((op.rv1 >> 16) & 0xFFFF); - data[base + cols::RV1_2] = FE::from(op.rv1 >> 32); - data[base + cols::RV1_SIGN] = FE::from(aux.rv1_sign as u64); - data[base + cols::ARG1_0] = FE::from(aux.arg1 & 0xFFFF_FFFF); - data[base + cols::ARG1_1] = FE::from(aux.arg1 >> 32); + table.set_byte(row_idx, cols::RS1, op.rs1); + table.set_bool(row_idx, cols::READ_REGISTER1, op.read_register1); + table.set_dword_whh(row_idx, cols::RV1_0, op.rv1); + table.set_bool(row_idx, cols::RV1_SIGN, aux.rv1_sign); + table.set_dword_wl(row_idx, cols::ARG1_0, aux.arg1); // rv2 as DWordWHH - data[base + cols::RS2] = FE::from(op.rs2 as u64); - data[base + cols::READ_REGISTER2] = FE::from(op.read_register2 as u64); - data[base + cols::RV2_0] = FE::from(op.rv2 & 0xFFFF); - data[base + cols::RV2_1] = FE::from((op.rv2 >> 16) & 0xFFFF); - data[base + cols::RV2_2] = FE::from(op.rv2 >> 32); - data[base + cols::RV2_SIGN] = FE::from(aux.rv2_sign as u64); - data[base + cols::IMM_0] = FE::from(op.imm & 0xFFFF_FFFF); - data[base + cols::IMM_1] = FE::from(op.imm >> 32); - data[base + cols::ARG2_0] = FE::from(aux.arg2 & 0xFFFF_FFFF); - data[base + cols::ARG2_1] = FE::from(aux.arg2 >> 32); + table.set_byte(row_idx, cols::RS2, op.rs2); + table.set_bool(row_idx, cols::READ_REGISTER2, op.read_register2); + table.set_dword_whh(row_idx, cols::RV2_0, op.rv2); + table.set_bool(row_idx, cols::RV2_SIGN, aux.rv2_sign); + table.set_dword_wl(row_idx, cols::IMM_0, op.imm); + table.set_dword_wl(row_idx, cols::ARG2_0, aux.arg2); // res as DWordHL: 4 halves - data[base + cols::RES_0] = FE::from(op.res & 0xFFFF); - data[base + cols::RES_1] = FE::from((op.res >> 16) & 0xFFFF); - data[base + cols::RES_2] = FE::from((op.res >> 32) & 0xFFFF); - data[base + cols::RES_3] = FE::from((op.res >> 48) & 0xFFFF); - data[base + cols::RES_SIGN] = FE::from(aux.res_sign as u64); + table.set_dword_hl(row_idx, cols::RES_0, op.res); + table.set_bool(row_idx, cols::RES_SIGN, aux.res_sign); // rd write - data[base + cols::RD] = FE::from(op.rd as u64); - data[base + cols::WRITE_REGISTER] = FE::from(op.write_register as u64); - data[base + cols::RVD_0] = FE::from(aux.rvd & 0xFFFF_FFFF); - data[base + cols::RVD_1] = FE::from(aux.rvd >> 32); + table.set_byte(row_idx, cols::RD, op.rd); + table.set_bool(row_idx, cols::WRITE_REGISTER, op.write_register); + table.set_dword_wl(row_idx, cols::RVD_0, aux.rvd); // ALU control - data[base + cols::ALU] = FE::from(op.alu as u64); - data[base + cols::ALU_FLAGS] = FE::from(op.alu_flags as u64); - data[base + cols::ADD] = FE::from(op.add as u64); - data[base + cols::SUB] = FE::from(op.sub as u64); - data[base + cols::HALF_INSTRUCTION_LENGTH] = FE::from(op.half_instruction_length as u64); - data[base + cols::SIGNED] = FE::from(aux.signed as u64); - - data[base + cols::MU] = FE::one(); + table.set_bool(row_idx, cols::ALU, op.alu); + table.set_byte(row_idx, cols::ALU_FLAGS, op.alu_flags); + table.set_bool(row_idx, cols::ADD, op.add); + table.set_bool(row_idx, cols::SUB, op.sub); + table.set_byte( + row_idx, + cols::HALF_INSTRUCTION_LENGTH, + op.half_instruction_length, + ); + table.set_bool(row_idx, cols::SIGNED, aux.signed); + + table.set_fe(row_idx, cols::MU, FE::one()); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/decode.rs b/prover/src/tables/decode.rs index f1fe14e03..6cef6a482 100644 --- a/prover/src/tables/decode.rs +++ b/prover/src/tables/decode.rs @@ -42,7 +42,7 @@ use stark::proof::options::ProofOptions; use stark::prover::evaluate_polynomial_on_lde_domain; use stark::trace::{TraceTable, columns2rows}; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; // Re-export DecodeEntry from types for backwards compatibility pub use super::types::DecodeEntry; @@ -128,51 +128,49 @@ pub fn generate_decode_trace( // +1 for the CPU padding entry let num_entries = entries.len() + 1; let num_rows = num_entries.next_power_of_two().max(2); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; // Fill actual entries (MU = 0 initially) for (row_idx, entry) in entries.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - // PC as DWordWL - data[base + cols::PC_0] = FE::from(entry.pc & 0xFFFF_FFFF); - data[base + cols::PC_1] = FE::from(entry.pc >> 32); + table.set_dword_wl(row_idx, cols::PC_0, entry.pc); // packed_decode - data[base + cols::PACKED_DECODE] = FE::from(entry.packed_decode()); + table.set_u64(row_idx, cols::PACKED_DECODE, entry.packed_decode()); // imm as DWordWL - data[base + cols::IMM_0] = FE::from(entry.imm & 0xFFFF_FFFF); - data[base + cols::IMM_1] = FE::from(entry.imm >> 32); + table.set_dword_wl(row_idx, cols::IMM_0, entry.imm); // MU = 0 (already zero from vec initialization) } // Write CPU padding entry (pc=1, all flags=0) { - let base = cpu_padding_row * cols::NUM_COLUMNS; - data[base + cols::PC_0] = FE::from(cpu_padding_entry.pc & 0xFFFF_FFFF); - data[base + cols::PC_1] = FE::from(cpu_padding_entry.pc >> 32); - data[base + cols::PACKED_DECODE] = FE::from(cpu_padding_entry.packed_decode()); - data[base + cols::IMM_0] = FE::from(cpu_padding_entry.imm & 0xFFFF_FFFF); - data[base + cols::IMM_1] = FE::from(cpu_padding_entry.imm >> 32); + table.set_dword_wl(cpu_padding_row, cols::PC_0, cpu_padding_entry.pc); + table.set_u64( + cpu_padding_row, + cols::PACKED_DECODE, + cpu_padding_entry.packed_decode(), + ); + table.set_dword_wl(cpu_padding_row, cols::IMM_0, cpu_padding_entry.imm); } // Fill padding rows with the DECODE padding pattern: odd pc=1, all flags 0 // (unprovable as a fetch target; same row the CPU pads to). let padding_entry = DecodeEntry::padding_entry(); for row_idx in num_entries..num_rows { - let base = row_idx * cols::NUM_COLUMNS; - - data[base + cols::PC_0] = FE::from(padding_entry.pc & 0xFFFF_FFFF); - data[base + cols::PC_1] = FE::from(padding_entry.pc >> 32); - data[base + cols::PACKED_DECODE] = FE::from(padding_entry.packed_decode()); - data[base + cols::IMM_0] = FE::from(padding_entry.imm & 0xFFFF_FFFF); - data[base + cols::IMM_1] = FE::from(padding_entry.imm >> 32); + table.set_dword_wl(row_idx, cols::PC_0, padding_entry.pc); + table.set_u64(row_idx, cols::PACKED_DECODE, padding_entry.packed_decode()); + table.set_dword_wl(row_idx, cols::IMM_0, padding_entry.imm); // MU = 0 for padding rows (already zero from vec initialization) } - (TraceTable::new_main(data, cols::NUM_COLUMNS, 1), pc_to_row) + (trace, pc_to_row) } /// Updates multiplicities in the DECODE trace table. @@ -186,7 +184,9 @@ pub fn update_multiplicities( for &pc in lookups { if let Some(&row_idx) = pc_to_row.get(&pc) { let current = trace.main_table.get(row_idx, cols::MU); - trace.main_table.set(row_idx, cols::MU, current + FE::one()); + trace + .main_table + .set_fe(row_idx, cols::MU, current + FE::one()); } } } @@ -402,38 +402,38 @@ fn build_decode_table( // Pad to next power of 2, minimum 2 let num_entries = entries.len() + 1; let num_rows = num_entries.next_power_of_two().max(2); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; // Fill actual entries for (row_idx, entry) in entries.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - data[base + cols::PC_0] = FE::from(entry.pc & 0xFFFF_FFFF); - data[base + cols::PC_1] = FE::from(entry.pc >> 32); - data[base + cols::PACKED_DECODE] = FE::from(entry.packed_decode()); - data[base + cols::IMM_0] = FE::from(entry.imm & 0xFFFF_FFFF); - data[base + cols::IMM_1] = FE::from(entry.imm >> 32); + table.set_dword_wl(row_idx, cols::PC_0, entry.pc); + table.set_u64(row_idx, cols::PACKED_DECODE, entry.packed_decode()); + table.set_dword_wl(row_idx, cols::IMM_0, entry.imm); } // Write CPU padding entry { - let base = cpu_padding_row * cols::NUM_COLUMNS; - data[base + cols::PC_0] = FE::from(cpu_padding_entry.pc & 0xFFFF_FFFF); - data[base + cols::PC_1] = FE::from(cpu_padding_entry.pc >> 32); - data[base + cols::PACKED_DECODE] = FE::from(cpu_padding_entry.packed_decode()); - data[base + cols::IMM_0] = FE::from(cpu_padding_entry.imm & 0xFFFF_FFFF); - data[base + cols::IMM_1] = FE::from(cpu_padding_entry.imm >> 32); + table.set_dword_wl(cpu_padding_row, cols::PC_0, cpu_padding_entry.pc); + table.set_u64( + cpu_padding_row, + cols::PACKED_DECODE, + cpu_padding_entry.packed_decode(), + ); + table.set_dword_wl(cpu_padding_row, cols::IMM_0, cpu_padding_entry.imm); } // Fill padding rows with DECODE padding pattern let padding_entry = DecodeEntry::padding_entry(); for row_idx in num_entries..num_rows { - let base = row_idx * cols::NUM_COLUMNS; - data[base + cols::PC_0] = FE::from(padding_entry.pc & 0xFFFF_FFFF); - data[base + cols::PC_1] = FE::from(padding_entry.pc >> 32); - data[base + cols::PACKED_DECODE] = FE::from(padding_entry.packed_decode()); - data[base + cols::IMM_0] = FE::from(padding_entry.imm & 0xFFFF_FFFF); - data[base + cols::IMM_1] = FE::from(padding_entry.imm >> 32); + table.set_dword_wl(row_idx, cols::PC_0, padding_entry.pc); + table.set_u64(row_idx, cols::PACKED_DECODE, padding_entry.packed_decode()); + table.set_dword_wl(row_idx, cols::IMM_0, padding_entry.imm); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } diff --git a/prover/src/tables/dvrm.rs b/prover/src/tables/dvrm.rs index b74416010..d3adbdc53 100644 --- a/prover/src/tables/dvrm.rs +++ b/prover/src/tables/dvrm.rs @@ -40,7 +40,7 @@ use stark::trace::TraceTable; use super::types::{ BusId, FE, GoldilocksExtension, GoldilocksField, NEG_INV_2_16, NEG_INV_2_32, NEG_INV_2_48, - NEG_INV_2_64, SHIFT_16, alu_op, + NEG_INV_2_64, SHIFT_16, VmTable, alu_op, }; // ========================================================================= @@ -301,11 +301,14 @@ pub fn generate_dvrm_trace( let unique_ops: Vec<_> = op_map.into_iter().collect(); let num_rows = unique_ops.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, (op, multiplicities)) in unique_ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - let q = op.compute_quotient(); let r = op.compute_remainder(); let n_sub_r = op.n_sub_r(); @@ -313,59 +316,41 @@ pub fn generate_dvrm_trace( let abs_d = op.abs_d(); // Fill n as DWordHL (4 halfwords) - data[base + cols::N_0] = FE::from(op.n & 0xFFFF); - data[base + cols::N_1] = FE::from((op.n >> 16) & 0xFFFF); - data[base + cols::N_2] = FE::from((op.n >> 32) & 0xFFFF); - data[base + cols::N_3] = FE::from((op.n >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::N_0, op.n); // Fill d as DWordHL (4 halfwords) - data[base + cols::D_0] = FE::from(op.d & 0xFFFF); - data[base + cols::D_1] = FE::from((op.d >> 16) & 0xFFFF); - data[base + cols::D_2] = FE::from((op.d >> 32) & 0xFFFF); - data[base + cols::D_3] = FE::from((op.d >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::D_0, op.d); - data[base + cols::SIGNED] = FE::from(op.signed as u64); + table.set_bool(row_idx, cols::SIGNED, op.signed); // Fill q as DWordHL (4 halfwords) - data[base + cols::Q_0] = FE::from(q & 0xFFFF); - data[base + cols::Q_1] = FE::from((q >> 16) & 0xFFFF); - data[base + cols::Q_2] = FE::from((q >> 32) & 0xFFFF); - data[base + cols::Q_3] = FE::from((q >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::Q_0, q); // Fill r as DWordHL (4 halfwords) - data[base + cols::R_0] = FE::from(r & 0xFFFF); - data[base + cols::R_1] = FE::from((r >> 16) & 0xFFFF); - data[base + cols::R_2] = FE::from((r >> 32) & 0xFFFF); - data[base + cols::R_3] = FE::from((r >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::R_0, r); // Fill auxiliary columns - data[base + cols::DIV_BY_ZERO] = FE::from(op.is_div_by_zero() as u64); - data[base + cols::OVERFLOW] = FE::from(op.is_overflow() as u64); - - data[base + cols::ABS_R_0] = FE::from(abs_r & 0xFFFF_FFFF); - data[base + cols::ABS_R_1] = FE::from(abs_r >> 32); + table.set_bool(row_idx, cols::DIV_BY_ZERO, op.is_div_by_zero()); + table.set_bool(row_idx, cols::OVERFLOW, op.is_overflow()); - data[base + cols::ABS_D_0] = FE::from(abs_d & 0xFFFF_FFFF); - data[base + cols::ABS_D_1] = FE::from(abs_d >> 32); + table.set_dword_wl(row_idx, cols::ABS_R_0, abs_r); + table.set_dword_wl(row_idx, cols::ABS_D_0, abs_d); // Fill n_sub_r as DWordHL (4 halfwords) - data[base + cols::N_SUB_R_0] = FE::from(n_sub_r & 0xFFFF); - data[base + cols::N_SUB_R_1] = FE::from((n_sub_r >> 16) & 0xFFFF); - data[base + cols::N_SUB_R_2] = FE::from((n_sub_r >> 32) & 0xFFFF); - data[base + cols::N_SUB_R_3] = FE::from((n_sub_r >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::N_SUB_R_0, n_sub_r); - data[base + cols::SIGN_N_SUB_R] = FE::from(op.sign_n_sub_r() as u64); - data[base + cols::SIGN_N] = FE::from(op.sign_n() as u64); - data[base + cols::SIGN_D] = FE::from(op.sign_d() as u64); - data[base + cols::SIGN_Q] = FE::from(op.sign_q() as u64); - data[base + cols::SIGN_R] = FE::from(op.sign_r() as u64); + table.set_bool(row_idx, cols::SIGN_N_SUB_R, op.sign_n_sub_r()); + table.set_bool(row_idx, cols::SIGN_N, op.sign_n()); + table.set_bool(row_idx, cols::SIGN_D, op.sign_d()); + table.set_bool(row_idx, cols::SIGN_Q, op.sign_q()); + table.set_bool(row_idx, cols::SIGN_R, op.sign_r()); // Multiplicities - data[base + cols::MU_Q] = FE::from(multiplicities.mu_q); - data[base + cols::MU_R] = FE::from(multiplicities.mu_r); + table.set_u64(row_idx, cols::MU_Q, multiplicities.mu_q); + table.set_u64(row_idx, cols::MU_R, multiplicities.mu_r); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/ec_scalar.rs b/prover/src/tables/ec_scalar.rs index 9ec20377d..dd8d483a2 100644 --- a/prover/src/tables/ec_scalar.rs +++ b/prover/src/tables/ec_scalar.rs @@ -23,7 +23,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing} use stark::table::TableView; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; use crate::constraints::templates::new_is_bit_constraints; // ========================================================================= @@ -87,25 +87,27 @@ pub fn generate_ec_scalar_trace( ) -> TraceTable { let n = ops.len(); let num_rows = n.next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); - data[base + cols::PTR_0] = FE::from(op.ptr & 0xFFFF_FFFF); - data[base + cols::PTR_1] = FE::from(op.ptr >> 32); - data[base + cols::OFFSET] = FE::from(op.offset as u64); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); + table.set_dword_wl(row_idx, cols::PTR_0, op.ptr); + table.set_byte(row_idx, cols::OFFSET, op.offset); for i in 0..8 { - data[base + cols::limb_bit(i)] = FE::from(((op.limb >> i) & 1) as u64); + table.set_bool(row_idx, cols::limb_bit(i), ((op.limb >> i) & 1) != 0); } - data[base + cols::LAST_LIMB] = FE::from(op.last_limb as u64); - data[base + cols::MU] = FE::one(); + table.set_bool(row_idx, cols::LAST_LIMB, op.last_limb); + table.set_fe(row_idx, cols::MU, FE::one()); } // Padding rows keep every field 0: all IS_BIT constraints hold (0 is a bit) and the // implication constraints (a·b = 0) hold trivially. - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/ecdas.rs b/prover/src/tables/ecdas.rs index 059245073..6d508d363 100644 --- a/prover/src/tables/ecdas.rs +++ b/prover/src/tables/ecdas.rs @@ -17,7 +17,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing} use stark::table::TableView; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; use crate::constraints::templates::IsBitConstraint; use crate::tables::ecsm::ecdas_tuple; use ecsm::{EcdasStep, P_BYTES}; @@ -93,59 +93,55 @@ fn fe_from_i64(c: i64) -> FE { } } -fn write_bytes(data: &mut [FE], base: usize, col: usize, bytes: &[u8]) { - for (i, &b) in bytes.iter().enumerate() { - data[base + col + i] = FE::from(b as u64); - } -} - pub fn generate_ecdas_trace( ops: &[EcdasOperation], ) -> TraceTable { let n = ops.len(); let num_rows = n.next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; let s = &op.step; - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); - write_bytes(&mut data, base, cols::XG, &s.x_g); - write_bytes(&mut data, base, cols::YG, &s.y_g); - write_bytes(&mut data, base, cols::XA, &s.x_a); - write_bytes(&mut data, base, cols::YA, &s.y_a); - data[base + cols::ROUND] = FE::from(s.round as u64); - data[base + cols::OP] = FE::from(s.op as u64); - write_bytes(&mut data, base, cols::XR, &s.x_r); - write_bytes(&mut data, base, cols::YR, &s.y_r); - write_bytes(&mut data, base, cols::LAMBDA, &s.lambda); - write_bytes(&mut data, base, cols::Q0, &s.q0); - write_bytes(&mut data, base, cols::Q1, &s.q1); - write_bytes(&mut data, base, cols::Q2, &s.q2); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); + table.set_bytes(row_idx, cols::XG, &s.x_g); + table.set_bytes(row_idx, cols::YG, &s.y_g); + table.set_bytes(row_idx, cols::XA, &s.x_a); + table.set_bytes(row_idx, cols::YA, &s.y_a); + table.set_byte(row_idx, cols::ROUND, s.round); + table.set_byte(row_idx, cols::OP, s.op); + table.set_bytes(row_idx, cols::XR, &s.x_r); + table.set_bytes(row_idx, cols::YR, &s.y_r); + table.set_bytes(row_idx, cols::LAMBDA, &s.lambda); + table.set_bytes(row_idx, cols::Q0, &s.q0); + table.set_bytes(row_idx, cols::Q1, &s.q1); + table.set_bytes(row_idx, cols::Q2, &s.q2); for i in 0..64 { debug_assert!((0..1 << 16).contains(&(s.c0[i] + CARRY_OFFSET_LAMBDA))); debug_assert!((0..1 << 16).contains(&(s.c1[i] + CARRY_OFFSET_XR))); debug_assert!((0..1 << 16).contains(&(s.c2[i] + CARRY_OFFSET_YR))); - data[base + cols::c0(i)] = fe_from_i64(s.c0[i]); - data[base + cols::c1(i)] = fe_from_i64(s.c1[i]); - data[base + cols::c2(i)] = fe_from_i64(s.c2[i]); + table.set_fe(row_idx, cols::c0(i), fe_from_i64(s.c0[i])); + table.set_fe(row_idx, cols::c1(i), fe_from_i64(s.c1[i])); + table.set_fe(row_idx, cols::c2(i), fe_from_i64(s.c2[i])); } - data[base + cols::NEXT_OP] = FE::from(s.next_op as u64); - data[base + cols::MU] = FE::one(); + table.set_byte(row_idx, cols::NEXT_OP, s.next_op); + table.set_fe(row_idx, cols::MU, FE::one()); } // Padding rows: q0 = q1 = q2 = r, op = 0, everything else 0. This makes every // (unconditional) convolution relation hold with zero carries. for row_idx in n..num_rows { - let base = row_idx * cols::NUM_COLUMNS; - write_bytes(&mut data, base, cols::Q0, &R_BYTES); - write_bytes(&mut data, base, cols::Q1, &R_BYTES); - write_bytes(&mut data, base, cols::Q2, &R_BYTES); + table.set_bytes(row_idx, cols::Q0, &R_BYTES); + table.set_bytes(row_idx, cols::Q1, &R_BYTES); + table.set_bytes(row_idx, cols::Q2, &R_BYTES); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/ecsm.rs b/prover/src/tables/ecsm.rs index eb23998d5..f8ec0859d 100644 --- a/prover/src/tables/ecsm.rs +++ b/prover/src/tables/ecsm.rs @@ -25,7 +25,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing} use stark::table::TableView; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; use crate::constraints::templates::{INV_SHIFT_32, IsBitConstraint}; use ecsm::{B, EcsmWitness, N_BYTES, P_BYTES}; @@ -137,23 +137,13 @@ fn fe_from_i64(c: i64) -> FE { } } -fn write_dword_wl(data: &mut [FE], base: usize, lo_col: usize, value: u64) { - data[base + lo_col] = FE::from(value & 0xFFFF_FFFF); - data[base + lo_col + 1] = FE::from(value >> 32); -} - -fn write_bytes(data: &mut [FE], base: usize, col: usize, bytes: &[u8]) { - for (i, &b) in bytes.iter().enumerate() { - data[base + col + i] = FE::from(b as u64); - } -} - /// Writes a 32-byte little-endian value as 16 halfwords (U256HL). -fn write_halfwords(data: &mut [FE], base: usize, col: usize, bytes: &[u8; 32]) { +fn write_halfwords(table: &mut impl VmTable, row: usize, col: usize, bytes: &[u8; 32]) { + let mut halfwords = [0u16; 16]; for j in 0..16 { - let hw = bytes[2 * j] as u64 + ((bytes[2 * j + 1] as u64) << 8); - data[base + col + j] = FE::from(hw); + halfwords[j] = u16::from_le_bytes([bytes[2 * j], bytes[2 * j + 1]]); } + table.set_halves(row, col, &halfwords); } pub fn generate_ecsm_trace( @@ -161,48 +151,51 @@ pub fn generate_ecsm_trace( ) -> TraceTable { let n = ops.len(); let num_rows = n.next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; let w = &op.witness; - write_dword_wl(&mut data, base, cols::TIMESTAMP_0, op.timestamp); - write_dword_wl(&mut data, base, cols::ADDR_XG_0, op.addr_xg); - write_dword_wl(&mut data, base, cols::ADDR_K_0, op.addr_k); - write_dword_wl(&mut data, base, cols::ADDR_XR_0, op.addr_xr); - - write_bytes(&mut data, base, cols::XR, &w.x_r); - write_bytes(&mut data, base, cols::YR, &w.y_r); - write_bytes(&mut data, base, cols::K, &w.k); - data[base + cols::LEN_K] = FE::from(w.len_k as u64); - write_bytes(&mut data, base, cols::XG, &w.x_g); - write_bytes(&mut data, base, cols::YG, &w.y_g); - write_bytes(&mut data, base, cols::X2, &w.x2); - write_bytes(&mut data, base, cols::Q0, &w.q0); - write_bytes(&mut data, base, cols::Q1, &w.q1); - write_halfwords(&mut data, base, cols::K_SUB_N, &w.k_sub_n); - write_halfwords(&mut data, base, cols::XR_SUB_P, &w.x_r_sub_p); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); + table.set_dword_wl(row_idx, cols::ADDR_XG_0, op.addr_xg); + table.set_dword_wl(row_idx, cols::ADDR_K_0, op.addr_k); + table.set_dword_wl(row_idx, cols::ADDR_XR_0, op.addr_xr); + + table.set_bytes(row_idx, cols::XR, &w.x_r); + table.set_bytes(row_idx, cols::YR, &w.y_r); + table.set_bytes(row_idx, cols::K, &w.k); + table.set_u64(row_idx, cols::LEN_K, w.len_k as u64); + table.set_bytes(row_idx, cols::XG, &w.x_g); + table.set_bytes(row_idx, cols::YG, &w.y_g); + table.set_bytes(row_idx, cols::X2, &w.x2); + table.set_bytes(row_idx, cols::Q0, &w.q0); + table.set_bytes(row_idx, cols::Q1, &w.q1); + write_halfwords(table, row_idx, cols::K_SUB_N, &w.k_sub_n); + write_halfwords(table, row_idx, cols::XR_SUB_P, &w.x_r_sub_p); for i in 0..64 { debug_assert!((0..1 << 16).contains(&(w.c0[i] + CARRY_OFFSET_X2))); debug_assert!((0..1 << 16).contains(&(w.c1[i] + CARRY_OFFSET_YG))); - data[base + cols::c0(i)] = fe_from_i64(w.c0[i]); - data[base + cols::c1(i)] = fe_from_i64(w.c1[i]); + table.set_fe(row_idx, cols::c0(i), fe_from_i64(w.c0[i])); + table.set_fe(row_idx, cols::c1(i), fe_from_i64(w.c1[i])); } - data[base + cols::MU] = FE::one(); + table.set_fe(row_idx, cols::MU, FE::one()); } // Padding rows (`mu = 0`) must carry `q1 = p` so the yG carry relation closes: the // `p² − q1·p` offset cancels and the µ-gated `b` term drops. Bytes 0..31 hold p; byte 32 // stays 0 (a valid IS_BIT value). for row_idx in n..num_rows { - let base = row_idx * cols::NUM_COLUMNS; - write_bytes(&mut data, base, cols::Q1, &P_BYTES); + table.set_bytes(row_idx, cols::Q1, &P_BYTES); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/eq.rs b/prover/src/tables/eq.rs index f60ed2e58..453caa928 100644 --- a/prover/src/tables/eq.rs +++ b/prover/src/tables/eq.rs @@ -28,7 +28,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing} use stark::table::TableView; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, alu_op}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable, alu_op}; use crate::constraints::templates::{AddConstraint, AddOperand, new_is_bit_constraints}; // ========================================================================= @@ -129,32 +129,30 @@ pub fn generate_eq_trace( let unique_ops: Vec<_> = op_map.into_iter().collect(); let num_rows = unique_ops.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, (op, multiplicity)) in unique_ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - // a, b as DWordWL (2 words each) - data[base + cols::A_0] = FE::from(op.a & 0xFFFF_FFFF); - data[base + cols::A_1] = FE::from(op.a >> 32); - data[base + cols::B_0] = FE::from(op.b & 0xFFFF_FFFF); - data[base + cols::B_1] = FE::from(op.b >> 32); + table.set_dword_wl(row_idx, cols::A_0, op.a); + table.set_dword_wl(row_idx, cols::B_0, op.b); - data[base + cols::INVERT] = FE::from(op.invert as u64); - data[base + cols::RES] = FE::from(op.compute_res() as u64); + table.set_bool(row_idx, cols::INVERT, op.invert); + table.set_bool(row_idx, cols::RES, op.compute_res()); // diff = a - b (wrapping) as DWordHL (4 halves) let diff = op.a.wrapping_sub(op.b); - data[base + cols::DIFF_0] = FE::from(diff & 0xFFFF); - data[base + cols::DIFF_1] = FE::from((diff >> 16) & 0xFFFF); - data[base + cols::DIFF_2] = FE::from((diff >> 32) & 0xFFFF); - data[base + cols::DIFF_3] = FE::from((diff >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::DIFF_0, diff); - data[base + cols::EQ] = FE::from(op.compute_eq() as u64); - data[base + cols::MU] = FE::from(*multiplicity); + table.set_bool(row_idx, cols::EQ, op.compute_eq()); + table.set_u64(row_idx, cols::MU, *multiplicity); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/halt.rs b/prover/src/tables/halt.rs index 946268e24..44bbf26cb 100644 --- a/prover/src/tables/halt.rs +++ b/prover/src/tables/halt.rs @@ -30,7 +30,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing}; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; // ========================================================================= // Column indices for HALT table @@ -72,17 +72,13 @@ pub fn generate_halt_trace( timestamp <= u32::MAX as u64, "HALT timestamp {timestamp} exceeds u32 range" ); - let timestamp_lo = timestamp & 0xFFFF_FFFF; - let timestamp_hi = timestamp >> 32; + let mut trace = TraceTable::new_main(vec![FE::zero(); cols::NUM_COLUMNS], cols::NUM_COLUMNS, 1); + let table = &mut trace.main_table; - let data = vec![ - FE::from(timestamp_lo), - FE::from(timestamp_hi), - FE::from(next_pc & 0xFFFF_FFFF), - FE::from(next_pc >> 32), - ]; + table.set_dword_wl(0, cols::TIMESTAMP_0, timestamp); + table.set_dword_wl(0, cols::PC_0, next_pc); - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/keccak.rs b/prover/src/tables/keccak.rs index 0eaf3c6b2..0f305255b 100644 --- a/prover/src/tables/keccak.rs +++ b/prover/src/tables/keccak.rs @@ -23,7 +23,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing} use stark::table::TableView; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, alu_op}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable, alu_op}; use crate::constraints::templates::{AddConstraint, AddOperand, INV_SHIFT_32}; // ========================================================================= @@ -94,36 +94,30 @@ pub struct KeccakOperation { // Trace generation // ========================================================================= -fn byte_of(val: u64, b: usize) -> u8 { - ((val >> (b * 8)) & 0xFF) as u8 -} - pub fn generate_keccak_trace( ops: &[KeccakOperation], ) -> TraceTable { let n = ops.len(); let num_rows = n.next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - // Timestamp - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); // Address as 8 bytes - for b in 0..8 { - data[base + cols::addr(b)] = FE::from(byte_of(op.state_addr, b) as u64); - } + table.set_dword_bl(row_idx, cols::addr(0), op.state_addr); // Input state as bytes for x in 0..5 { for y in 0..5 { let lane = op.input[x + 5 * y]; - for b in 0..8 { - data[base + cols::input_state(x, y, b)] = FE::from(byte_of(lane, b) as u64); - } + table.set_dword_bl(row_idx, cols::input_state(x, y, 0), lane); } } @@ -131,9 +125,7 @@ pub fn generate_keccak_trace( for x in 0..5 { for y in 0..5 { let lane = op.output[x + 5 * y]; - for b in 0..8 { - data[base + cols::output_state(x, y, b)] = FE::from(byte_of(lane, b) as u64); - } + table.set_dword_bl(row_idx, cols::output_state(x, y, 0), lane); } } @@ -143,14 +135,11 @@ pub fn generate_keccak_trace( .state_addr .checked_add(lane_idx as u64 * 8) .expect("keccak state address range must be validated by the executor"); - data[base + cols::state_ptr(lane_idx, 0)] = FE::from(ptr & 0xFFFF); - data[base + cols::state_ptr(lane_idx, 1)] = FE::from((ptr >> 16) & 0xFFFF); - data[base + cols::state_ptr(lane_idx, 2)] = FE::from((ptr >> 32) & 0xFFFF); - data[base + cols::state_ptr(lane_idx, 3)] = FE::from((ptr >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::state_ptr(lane_idx, 0), ptr); } // mu = 1 (real row) - data[base + cols::MU] = FE::one(); + table.set_fe(row_idx, cols::MU, FE::one()); } // Padding rows: state_ptr[lane][0] = 8 * lane_idx (per spec keccak.toml pad). @@ -158,13 +147,12 @@ pub fn generate_keccak_trace( // mu = 0 gates all bus interactions and the ADD constraint, so these values // only need to satisfy the pad requirement, not reconstruct a real address. for row_idx in n..num_rows { - let base = row_idx * cols::NUM_COLUMNS; for lane_idx in 0..25 { - data[base + cols::state_ptr(lane_idx, 0)] = FE::from((lane_idx as u64) * 8); + table.set_u64(row_idx, cols::state_ptr(lane_idx, 0), (lane_idx as u64) * 8); } } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/keccak_rc.rs b/prover/src/tables/keccak_rc.rs index c2dde9e16..3575c8ba1 100644 --- a/prover/src/tables/keccak_rc.rs +++ b/prover/src/tables/keccak_rc.rs @@ -9,7 +9,6 @@ //! `ProofOptions` not covered by the static table). use math::fft::bit_reversing::in_place_bit_reverse_permute; -use math::field::element::FieldElement; use math::polynomial::Polynomial; use stark::config::{BatchedMerkleTree, Commitment}; use stark::lookup::{BusInteraction, BusValue, Multiplicity, Packing}; @@ -19,7 +18,7 @@ use stark::trace::{TraceTable, columns2rows}; use executor::vm::instruction::execution::KECCAK_RC; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; // ========================================================================= // Column indices @@ -198,18 +197,22 @@ pub fn preprocessed_commitment(options: &ProofOptions) -> Commitment { /// All precomputed columns are filled; MU is initialized to zero and must be /// updated via `update_multiplicities` after all round-chip lookups are known. pub fn generate_keccak_rc_trace() -> TraceTable { - let mut data = vec![FE::zero(); NUM_ROWS * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); NUM_ROWS * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for idx in 0..NUM_ROWS { - let base = idx * cols::NUM_COLUMNS; let row = generate_row(idx); for (col_idx, &value) in row.iter().enumerate() { - data[base + col_idx] = FE::from(value); + table.set_u64(idx, col_idx, value); } // MU = 0 (will be updated later) } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } /// Increment MU for each round lookup. @@ -221,9 +224,10 @@ pub fn update_multiplicities( trace: &mut TraceTable, num_keccak_ops: usize, ) { - let mu = FieldElement::from(num_keccak_ops as u64); for round in 0..NUM_REAL_ROWS { - trace.set_main(round, cols::MU, mu); + trace + .main_table + .set_u64(round, cols::MU, num_keccak_ops as u64); } } diff --git a/prover/src/tables/keccak_rnd.rs b/prover/src/tables/keccak_rnd.rs index 3e9b9815b..279b5c152 100644 --- a/prover/src/tables/keccak_rnd.rs +++ b/prover/src/tables/keccak_rnd.rs @@ -33,7 +33,7 @@ use stark::constraints::transition::{TransitionConstraint, TransitionConstraintE use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing}; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, alu_op}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable, alu_op}; // ========================================================================= // Column indices @@ -243,7 +243,12 @@ pub fn generate_keccak_rnd_trace( ops: &[KeccakRoundOperation], ) -> TraceTable { let n_rows = (ops.len() * 24).next_power_of_two().max(4); - let mut data = vec![FE::zero(); n_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); n_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (op_idx, op) in ops.iter().enumerate() { // Execute round-by-round, tracking the state @@ -251,20 +256,16 @@ pub fn generate_keccak_rnd_trace( for round in 0..24 { let row_idx = op_idx * 24 + round; - let base = row_idx * cols::NUM_COLUMNS; // Timestamp & round - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); - data[base + cols::ROUND] = FE::from(round as u64); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); + table.set_u64(row_idx, cols::ROUND, round as u64); // start = current state as bytes for x in 0..5 { for y in 0..5 { let lane = state[x + 5 * y]; - for b in 0..8 { - data[base + cols::start(x, y, b)] = FE::from(byte_of(lane, b) as u64); - } + table.set_dword_bl(row_idx, cols::start(x, y, 0), lane); } } @@ -280,8 +281,9 @@ pub fn generate_keccak_rnd_trace( let v0 = byte_of(state[x], b); let v1 = byte_of(state[x + 5], b); cxz[x][0][b] = v0 ^ v1; - data[base + cols::cxz(x, 0, b)] = FE::from(cxz[x][0][b] as u64); } + table.set_bytes(row_idx, cols::cxz(x, 0, 0), &cxz[x][0]); + // Stages 1..3: XOR(Cxz[x][k-1], start[x, k+1]) for stage in 1..4 { let y = stage + 1; @@ -289,8 +291,8 @@ pub fn generate_keccak_rnd_trace( let prev = cxz[x][stage - 1][b]; let sv = byte_of(state[x + 5 * y], b); cxz[x][stage][b] = prev ^ sv; - data[base + cols::cxz(x, stage, b)] = FE::from(cxz[x][stage][b] as u64); } + table.set_bytes(row_idx, cols::cxz(x, stage, 0), &cxz[x][stage]); } c_bytes[x] = cxz[x][3]; } @@ -313,13 +315,10 @@ pub fn generate_keccak_rnd_trace( cxz_left_bytes[x][hw * 2 + 1] = (shifted >> 8) as u8; // For shift=1, carry ∈ {0, 1}. cxz_right_bits[x][hw] = carry as u8; - data[base + cols::cxz_left(x, hw * 2)] = - FE::from(cxz_left_bytes[x][hw * 2] as u64); - data[base + cols::cxz_left(x, hw * 2 + 1)] = - FE::from(cxz_left_bytes[x][hw * 2 + 1] as u64); - data[base + cols::cxz_right_bit(x, hw)] = - FE::from(cxz_right_bits[x][hw] as u64); } + table.set_bytes(row_idx, cols::cxz_left(x, 0), &cxz_left_bytes[x]); + table.set_bytes(row_idx, cols::cxz_right_bit(x, 0), &cxz_right_bits[x]); + // Reconstruct: left[b] + (1 - b%2) * right[(b/2 + 3) mod 4] for b in 0..8 { let right_contribution = match cols::cxz_right_bit_for_byte(b) { @@ -336,8 +335,8 @@ pub fn generate_keccak_rnd_trace( for b in 0..8 { let val = c_bytes[(x + 4) % 5][b] ^ rotated_c[(x + 1) % 5][b]; d_bytes[x][b] = val; - data[base + cols::dxz(x, b)] = FE::from(val as u64); } + table.set_bytes(row_idx, cols::dxz(x, 0), &d_bytes[x]); } // theta[x][y] = start[x][y] XOR D[x] @@ -350,10 +349,7 @@ pub fn generate_keccak_rnd_trace( d_lane |= (d_bytes[x][b] as u64) << (b * 8); } theta_lanes[x + 5 * y] = lane ^ d_lane; - for b in 0..8 { - data[base + cols::theta(x, y, b)] = - FE::from(byte_of(theta_lanes[x + 5 * y], b) as u64); - } + table.set_dword_bl(row_idx, cols::theta(x, y, 0), theta_lanes[x + 5 * y]); } } @@ -367,18 +363,18 @@ pub fn generate_keccak_rnd_trace( let rho_offset = KECCAK_RHO[x][y] as usize; let rnc_val = (rho_offset % 16) as u8; let theta_lane = theta_lanes[x + 5 * y]; + let mut rot_left_bytes = [0u8; 8]; + let mut rot_right_bytes = [0u8; 8]; for hw in 0..4 { let halfword = ((theta_lane >> (hw * 16)) & 0xFFFF) as u16; let (shifted, carry) = hwsl(halfword, rnc_val); - data[base + cols::rot_left(x, y, hw * 2)] = - FE::from((shifted & 0xFF) as u64); - data[base + cols::rot_left(x, y, hw * 2 + 1)] = - FE::from((shifted >> 8) as u64); - data[base + cols::rot_right(x, y, hw * 2)] = - FE::from((carry & 0xFF) as u64); - data[base + cols::rot_right(x, y, hw * 2 + 1)] = - FE::from((carry >> 8) as u64); + rot_left_bytes[hw * 2] = (shifted & 0xFF) as u8; + rot_left_bytes[hw * 2 + 1] = (shifted >> 8) as u8; + rot_right_bytes[hw * 2] = (carry & 0xFF) as u8; + rot_right_bytes[hw * 2 + 1] = (carry >> 8) as u8; } + table.set_bytes(row_idx, cols::rot_left(x, y, 0), &rot_left_bytes); + table.set_bytes(row_idx, cols::rot_right(x, y, 0), &rot_right_bytes); } } @@ -408,33 +404,28 @@ pub fn generate_keccak_rnd_trace( let next2 = pi_lanes[(x + 2) % 5 + 5 * y]; let and_val = not_next & next2; chi_lanes[x + 5 * y] = pi_lanes[x + 5 * y] ^ and_val; - for b in 0..8 { - data[base + cols::chi_ands(x, y, b)] = FE::from(byte_of(and_val, b) as u64); - data[base + cols::chi(x, y, b)] = - FE::from(byte_of(chi_lanes[x + 5 * y], b) as u64); - } + table.set_dword_bl(row_idx, cols::chi_ands(x, y, 0), and_val); + table.set_dword_bl(row_idx, cols::chi(x, y, 0), chi_lanes[x + 5 * y]); } } // === ι (iota) === let rc_val = KECCAK_RC[round]; - for b in 0..8 { - data[base + cols::rc(b)] = FE::from(byte_of(rc_val, b) as u64); - let iota_byte = byte_of(chi_lanes[0], b) ^ byte_of(rc_val, b); - data[base + cols::iota(b)] = FE::from(iota_byte as u64); - } + let iota_lane = chi_lanes[0] ^ rc_val; + table.set_dword_bl(row_idx, cols::rc(0), rc_val); + table.set_dword_bl(row_idx, cols::iota(0), iota_lane); // Update state for next round - chi_lanes[0] ^= rc_val; + chi_lanes[0] = iota_lane; state = chi_lanes; // mu = 1 (real row) - data[base + cols::MU] = FE::one(); + table.set_fe(row_idx, cols::MU, FE::one()); } } // Padding rows have mu=0 and all zeros (default) - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/load.rs b/prover/src/tables/load.rs index 8795a6494..250d565b2 100644 --- a/prover/src/tables/load.rs +++ b/prover/src/tables/load.rs @@ -30,7 +30,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing} use stark::table::TableView; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; // ========================================================================= // Column indices for LOAD table @@ -183,42 +183,40 @@ pub fn generate_load_trace( operations: &[LoadOperation], ) -> TraceTable { let num_rows = operations.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in operations.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - // Input columns - // base_address as DWordWL (2 words) - data[base + cols::BASE_ADDRESS_0] = FE::from(op.base_address & 0xFFFF_FFFF); - data[base + cols::BASE_ADDRESS_1] = FE::from(op.base_address >> 32); - - // timestamp as DWordWL (2 words) - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); + table.set_dword_wl(row_idx, cols::BASE_ADDRESS_0, op.base_address); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); // read flags let (r2, r4, r8) = op.read_flags(); - data[base + cols::READ2] = FE::from(r2 as u64); - data[base + cols::READ4] = FE::from(r4 as u64); - data[base + cols::READ8] = FE::from(r8 as u64); + table.set_bool(row_idx, cols::READ2, r2); + table.set_bool(row_idx, cols::READ4, r4); + table.set_bool(row_idx, cols::READ8, r8); // signed - data[base + cols::SIGNED] = FE::from(op.signed as u64); + table.set_bool(row_idx, cols::SIGNED, op.signed); // Output: res[8] for i in 0..8 { - data[base + cols::RES[i]] = FE::from(op.res[i]); + table.set_u64(row_idx, cols::RES[i], op.res[i]); } // Auxiliary: sign_bit - data[base + cols::SIGN_BIT] = FE::from(op.compute_sign_bit() as u64); + table.set_bool(row_idx, cols::SIGN_BIT, op.compute_sign_bit()); // Multiplicity: active row - data[base + cols::MU] = FE::one(); + table.set_fe(row_idx, cols::MU, FE::one()); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/lt.rs b/prover/src/tables/lt.rs index 921f6279a..0b1a57616 100644 --- a/prover/src/tables/lt.rs +++ b/prover/src/tables/lt.rs @@ -33,7 +33,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing} use stark::table::TableView; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, SHIFT_16, alu_op}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, SHIFT_16, VmTable, alu_op}; // ========================================================================= // Column indices for LT table @@ -171,63 +171,45 @@ pub fn generate_lt_trace( let unique_ops: Vec<_> = op_map.into_iter().collect(); let num_rows = unique_ops.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, (op, multiplicity)) in unique_ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - - // Extract lhs as DWordHHW: [Word, Half, Half] - let lhs_0 = (op.lhs & 0xFFFF_FFFF) as u32; // bits 0-31 - let lhs_1 = ((op.lhs >> 32) & 0xFFFF) as u16; // bits 32-47 - let lhs_2 = ((op.lhs >> 48) & 0xFFFF) as u16; // bits 48-63 - - // Extract rhs as DWordHHW: [Word, Half, Half] - let rhs_0 = (op.rhs & 0xFFFF_FFFF) as u32; // bits 0-31 - let rhs_1 = ((op.rhs >> 32) & 0xFFFF) as u16; // bits 32-47 - let rhs_2 = ((op.rhs >> 48) & 0xFFFF) as u16; // bits 48-63 - // Store input columns - data[base + cols::LHS_0] = FE::from(lhs_0 as u64); - data[base + cols::LHS_1] = FE::from(lhs_1 as u64); - data[base + cols::LHS_2] = FE::from(lhs_2 as u64); - data[base + cols::RHS_0] = FE::from(rhs_0 as u64); - data[base + cols::RHS_1] = FE::from(rhs_1 as u64); - data[base + cols::RHS_2] = FE::from(rhs_2 as u64); - data[base + cols::SIGNED] = FE::from(if op.signed { 1u64 } else { 0u64 }); + table.set_dword_hhw(row_idx, cols::LHS_0, op.lhs); + table.set_dword_hhw(row_idx, cols::RHS_0, op.rhs); + table.set_bool(row_idx, cols::SIGNED, op.signed); // Compute lt result let lt = op.compute_lt(); - data[base + cols::LT] = FE::from(if lt { 1u64 } else { 0u64 }); + table.set_bool(row_idx, cols::LT, lt); // Compute lhs_sub_rhs = lhs - rhs (wrapping) // Note: We compute this as a 64-bit wrapping subtraction let lhs_sub_rhs = op.lhs.wrapping_sub(op.rhs); // Store lhs_sub_rhs as DWordHL: [Half, Half, Half, Half] - let sub_0 = (lhs_sub_rhs & 0xFFFF) as u16; - let sub_1 = ((lhs_sub_rhs >> 16) & 0xFFFF) as u16; - let sub_2 = ((lhs_sub_rhs >> 32) & 0xFFFF) as u16; - let sub_3 = ((lhs_sub_rhs >> 48) & 0xFFFF) as u16; - data[base + cols::LHS_SUB_RHS_0] = FE::from(sub_0 as u64); - data[base + cols::LHS_SUB_RHS_1] = FE::from(sub_1 as u64); - data[base + cols::LHS_SUB_RHS_2] = FE::from(sub_2 as u64); - data[base + cols::LHS_SUB_RHS_3] = FE::from(sub_3 as u64); + table.set_dword_hl(row_idx, cols::LHS_SUB_RHS_0, lhs_sub_rhs); // Compute MSBs (bit 63 of each value) let lhs_msb = (op.lhs >> 63) & 1; let rhs_msb = (op.rhs >> 63) & 1; - data[base + cols::LHS_MSB] = FE::from(lhs_msb); - data[base + cols::RHS_MSB] = FE::from(rhs_msb); + table.set_u64(row_idx, cols::LHS_MSB, lhs_msb); + table.set_u64(row_idx, cols::RHS_MSB, rhs_msb); // ALU-bus fields: invert + the inverted output. - data[base + cols::INVERT] = FE::from(op.invert as u64); - data[base + cols::OUT] = FE::from(op.compute_out() as u64); + table.set_bool(row_idx, cols::INVERT, op.invert); + table.set_bool(row_idx, cols::OUT, op.compute_out()); // All LT lookups go through the unified ALU bus → single multiplicity. - data[base + cols::MU] = FE::from(*multiplicity); + table.set_u64(row_idx, cols::MU, *multiplicity); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/memw.rs b/prover/src/tables/memw.rs index 39a02ead4..2b240747c 100644 --- a/prover/src/tables/memw.rs +++ b/prover/src/tables/memw.rs @@ -36,7 +36,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing} use stark::table::TableView; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, alu_op}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable, alu_op}; use crate::constraints::templates::IsBitConstraint; /// Maximum number of rows per MEMW table chunk. @@ -175,59 +175,59 @@ pub fn generate_memw_trace( operations: &[MemwOperation], ) -> TraceTable { let num_rows = operations.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in operations.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - // Input columns - data[base + cols::IS_REGISTER] = FE::from(op.is_register as u64); + table.set_bool(row_idx, cols::IS_REGISTER, op.is_register); // base_address as DWordWL (2 words) let base_addr_lo = op.base_address & 0xFFFF_FFFF; - data[base + cols::BASE_ADDRESS_0] = FE::from(base_addr_lo); - data[base + cols::BASE_ADDRESS_1] = FE::from(op.base_address >> 32); + table.set_dword_wl(row_idx, cols::BASE_ADDRESS_0, op.base_address); // value[8] for i in 0..8 { - data[base + cols::VALUE[i]] = FE::from(op.value[i]); + table.set_u64(row_idx, cols::VALUE[i], op.value[i]); } // timestamp as DWordWL (2 words) - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); // write flags let (w2, w4, w8) = op.write_flags(); - data[base + cols::WRITE2] = FE::from(w2 as u64); - data[base + cols::WRITE4] = FE::from(w4 as u64); - data[base + cols::WRITE8] = FE::from(w8 as u64); + table.set_bool(row_idx, cols::WRITE2, w2); + table.set_bool(row_idx, cols::WRITE4, w4); + table.set_bool(row_idx, cols::WRITE8, w8); // Output: old[8] for i in 0..8 { - data[base + cols::OLD[i]] = FE::from(op.old[i]); + table.set_u64(row_idx, cols::OLD[i], op.old[i]); } // Auxiliary: carry[7] // carry[i] = 1 if (base_address_lo + i+1) >= 2^32 for i in 0..7 { let overflows = base_addr_lo + (i as u64 + 1) >= (1u64 << 32); - data[base + cols::CARRY[i]] = FE::from(overflows as u64); + table.set_bool(row_idx, cols::CARRY[i], overflows); } // Auxiliary: old_timestamp[8] - each as DWordWL (2 words) for i in 0..8 { let cols_i = cols::old_timestamp(i); - data[base + cols_i[0]] = FE::from(op.old_timestamp[i] & 0xFFFF_FFFF); - data[base + cols_i[1]] = FE::from(op.old_timestamp[i] >> 32); + table.set_dword_wl(row_idx, cols_i[0], op.old_timestamp[i]); } // Multiplicity - data[base + cols::MU_READ] = FE::from(op.is_read as u64); - data[base + cols::MU_WRITE] = FE::from(!op.is_read as u64); + table.set_bool(row_idx, cols::MU_READ, op.is_read); + table.set_bool(row_idx, cols::MU_WRITE, !op.is_read); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/memw_aligned.rs b/prover/src/tables/memw_aligned.rs index 91a9e8fd8..8042d9052 100644 --- a/prover/src/tables/memw_aligned.rs +++ b/prover/src/tables/memw_aligned.rs @@ -42,7 +42,7 @@ use stark::table::TableView; use stark::trace::TraceTable; use super::memw::MemwOperation; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, alu_op}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable, alu_op}; use crate::constraints::templates::IsBitConstraint; /// Maximum number of rows per MEMW_A table chunk. @@ -94,51 +94,41 @@ pub fn generate_memw_aligned_trace( operations: &[MemwOperation], ) -> TraceTable { let num_rows = operations.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in operations.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; + table.set_bool(row_idx, cols::IS_REGISTER, op.is_register); - data[base + cols::IS_REGISTER] = FE::from(op.is_register as u64); - - // Decompose base_address as DWordWHH: - // base_address[0] = low half (bits 0-15) - // base_address[1] = mid half (bits 16-31) - // base_address[2] = high word (bits 32-63) - let addr = op.base_address; - let addr_low_half = addr & 0xFFFF; - let addr_mid_half = (addr >> 16) & 0xFFFF; - let addr_high_word = addr >> 32; - - data[base + cols::BASE_ADDRESS[0]] = FE::from(addr_low_half); - data[base + cols::BASE_ADDRESS[1]] = FE::from(addr_mid_half); - data[base + cols::BASE_ADDRESS[2]] = FE::from(addr_high_word); + table.set_dword_whh(row_idx, cols::BASE_ADDRESS[0], op.base_address); for i in 0..8 { - data[base + cols::VALUE[i]] = FE::from(op.value[i]); + table.set_u64(row_idx, cols::VALUE[i], op.value[i]); } - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); let (w2, w4, w8) = op.write_flags(); - data[base + cols::WRITE2] = FE::from(w2 as u64); - data[base + cols::WRITE4] = FE::from(w4 as u64); - data[base + cols::WRITE8] = FE::from(w8 as u64); + table.set_bool(row_idx, cols::WRITE2, w2); + table.set_bool(row_idx, cols::WRITE4, w4); + table.set_bool(row_idx, cols::WRITE8, w8); for i in 0..8 { - data[base + cols::OLD[i]] = FE::from(op.old[i]); + table.set_u64(row_idx, cols::OLD[i], op.old[i]); } // Single old_timestamp (from old_timestamp[0], verified equal for all bytes) - data[base + cols::OLD_TIMESTAMP_0] = FE::from(op.old_timestamp[0] & 0xFFFF_FFFF); - data[base + cols::OLD_TIMESTAMP_1] = FE::from(op.old_timestamp[0] >> 32); + table.set_dword_wl(row_idx, cols::OLD_TIMESTAMP_0, op.old_timestamp[0]); - data[base + cols::MU_READ] = FE::from(op.is_read as u64); - data[base + cols::MU_WRITE] = FE::from(!op.is_read as u64); + table.set_bool(row_idx, cols::MU_READ, op.is_read); + table.set_bool(row_idx, cols::MU_WRITE, !op.is_read); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/memw_register.rs b/prover/src/tables/memw_register.rs index 599fe7ed5..14a696cb9 100644 --- a/prover/src/tables/memw_register.rs +++ b/prover/src/tables/memw_register.rs @@ -46,7 +46,7 @@ use stark::table::TableView; use stark::trace::TraceTable; use super::memw::MemwOperation; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; // ========================================================================= // Column indices (10 columns) @@ -94,11 +94,14 @@ pub fn generate_memw_register_trace( operations: &[MemwOperation], ) -> TraceTable { let num_rows = operations.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in operations.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - debug_assert_eq!( op.base_address % 2, 0, @@ -116,29 +119,32 @@ pub fn generate_memw_register_trace( ); // ADDRESS = base_address / 2 (CPU sends 2 * register_index) - data[base + cols::ADDRESS] = FE::from(op.base_address / 2); + table.set_u64(row_idx, cols::ADDRESS, op.base_address / 2); // Timestamp split into lo/hi 32-bit words - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); // Value: registers are DWordWL = 2 words - data[base + cols::VAL_0] = FE::from(op.value[0]); - data[base + cols::VAL_1] = FE::from(op.value[1]); + table.set_u64(row_idx, cols::VAL_0, op.value[0]); + table.set_u64(row_idx, cols::VAL_1, op.value[1]); // Old value - data[base + cols::OLD_0] = FE::from(op.old[0]); - data[base + cols::OLD_1] = FE::from(op.old[1]); + table.set_u64(row_idx, cols::OLD_0, op.old[0]); + table.set_u64(row_idx, cols::OLD_1, op.old[1]); // Old timestamp low (upper limb shared with TIMESTAMP_1) - data[base + cols::OLD_TIMESTAMP_LO] = FE::from(op.old_timestamp[0] & 0xFFFF_FFFF); + table.set_u64( + row_idx, + cols::OLD_TIMESTAMP_LO, + op.old_timestamp[0] & 0xFFFF_FFFF, + ); // Multiplicity - data[base + cols::MU_READ] = FE::from(op.is_read as u64); - data[base + cols::MU_WRITE] = FE::from(!op.is_read as u64); + table.set_bool(row_idx, cols::MU_READ, op.is_read); + table.set_bool(row_idx, cols::MU_WRITE, !op.is_read); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/mul.rs b/prover/src/tables/mul.rs index ac2329ebd..ba414dc63 100644 --- a/prover/src/tables/mul.rs +++ b/prover/src/tables/mul.rs @@ -42,7 +42,7 @@ use stark::trace::TraceTable; use super::types::{ BusId, FE, GoldilocksExtension, GoldilocksField, INV_2_32, INV_2_64, INV_2_96, INV_2_128, NEG_INV_2_16, NEG_INV_2_32, NEG_INV_2_48, NEG_INV_2_64, NEG_INV_2_80, NEG_INV_2_96, - NEG_INV_2_112, NEG_INV_2_128, SHIFT_16, alu_op, + NEG_INV_2_112, NEG_INV_2_128, SHIFT_16, VmTable, alu_op, }; /// Total row multiplicity (`ALU` bus, lo + hi), used by the internal @@ -309,57 +309,48 @@ pub fn generate_mul_trace( let unique_ops: Vec<_> = op_map.into_iter().collect(); let num_rows = unique_ops.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, (op, multiplicities)) in unique_ops.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - // Compute product let (lo, hi) = op.compute_product(); // Fill lhs as DWordHL (4 halfwords) - data[base + cols::LHS_0] = FE::from(op.lhs & 0xFFFF); - data[base + cols::LHS_1] = FE::from((op.lhs >> 16) & 0xFFFF); - data[base + cols::LHS_2] = FE::from((op.lhs >> 32) & 0xFFFF); - data[base + cols::LHS_3] = FE::from((op.lhs >> 48) & 0xFFFF); - data[base + cols::LHS_SIGNED] = FE::from(op.lhs_signed as u64); + table.set_dword_hl(row_idx, cols::LHS_0, op.lhs); + table.set_bool(row_idx, cols::LHS_SIGNED, op.lhs_signed); // Fill rhs as DWordHL (4 halfwords) - data[base + cols::RHS_0] = FE::from(op.rhs & 0xFFFF); - data[base + cols::RHS_1] = FE::from((op.rhs >> 16) & 0xFFFF); - data[base + cols::RHS_2] = FE::from((op.rhs >> 32) & 0xFFFF); - data[base + cols::RHS_3] = FE::from((op.rhs >> 48) & 0xFFFF); - data[base + cols::RHS_SIGNED] = FE::from(op.rhs_signed as u64); + table.set_dword_hl(row_idx, cols::RHS_0, op.rhs); + table.set_bool(row_idx, cols::RHS_SIGNED, op.rhs_signed); // Fill lo as DWordHL (4 halfwords) - data[base + cols::LO_0] = FE::from(lo & 0xFFFF); - data[base + cols::LO_1] = FE::from((lo >> 16) & 0xFFFF); - data[base + cols::LO_2] = FE::from((lo >> 32) & 0xFFFF); - data[base + cols::LO_3] = FE::from((lo >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::LO_0, lo); // Fill hi as DWordHL (4 halfwords) - data[base + cols::HI_0] = FE::from(hi & 0xFFFF); - data[base + cols::HI_1] = FE::from((hi >> 16) & 0xFFFF); - data[base + cols::HI_2] = FE::from((hi >> 32) & 0xFFFF); - data[base + cols::HI_3] = FE::from((hi >> 48) & 0xFFFF); + table.set_dword_hl(row_idx, cols::HI_0, hi); // Fill auxiliary columns - data[base + cols::LHS_IS_NEGATIVE] = FE::from(op.lhs_is_negative() as u64); - data[base + cols::RHS_IS_NEGATIVE] = FE::from(op.rhs_is_negative() as u64); + table.set_bool(row_idx, cols::LHS_IS_NEGATIVE, op.lhs_is_negative()); + table.set_bool(row_idx, cols::RHS_IS_NEGATIVE, op.rhs_is_negative()); // Fill raw_product columns let raw = op.compute_raw_products(); - data[base + cols::RAW_PRODUCT_0] = FE::from(raw[0]); - data[base + cols::RAW_PRODUCT_1] = FE::from(raw[1]); - data[base + cols::RAW_PRODUCT_2] = FE::from(raw[2]); - data[base + cols::RAW_PRODUCT_3] = FE::from(raw[3]); + table.set_u64(row_idx, cols::RAW_PRODUCT_0, raw[0]); + table.set_u64(row_idx, cols::RAW_PRODUCT_1, raw[1]); + table.set_u64(row_idx, cols::RAW_PRODUCT_2, raw[2]); + table.set_u64(row_idx, cols::RAW_PRODUCT_3, raw[3]); // Fill multiplicities (ALU bus, lo/hi) - data[base + cols::MU_LO] = FE::from(multiplicities.mu_lo); - data[base + cols::MU_HI] = FE::from(multiplicities.mu_hi); + table.set_u64(row_idx, cols::MU_LO, multiplicities.mu_lo); + table.set_u64(row_idx, cols::MU_HI, multiplicities.mu_hi); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/page.rs b/prover/src/tables/page.rs index 3997e8c22..174225ffa 100644 --- a/prover/src/tables/page.rs +++ b/prover/src/tables/page.rs @@ -40,7 +40,7 @@ use stark::proof::options::ProofOptions; use stark::prover::evaluate_polynomial_on_lde_domain; use stark::trace::{TraceTable, columns2rows}; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; // ========================================================================= // Constants @@ -177,14 +177,18 @@ pub fn generate_page_trace( ); let num_rows = page_size; // One row per byte in the page - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for offset in 0..page_size { let byte_addr = page_base + (offset as u64); - let base = offset * cols::NUM_COLUMNS; // Offset (preprocessed) - address is virtual: page_base + offset - data[base + cols::OFFSET] = FE::from(offset as u64); + table.set_u64(offset, cols::OFFSET, offset as u64); // Initial value (init_values may be shorter than the page → trailing zeros) let init_value = config @@ -192,7 +196,7 @@ pub fn generate_page_trace( .as_ref() .and_then(|v| v.get(offset).copied()) .unwrap_or(0); - data[base + cols::INIT] = FE::from(init_value as u64); + table.set_byte(offset, cols::INIT, init_value); // Final state: if accessed use final, otherwise use initial let (timestamp, fini_value) = if let Some(state) = final_state.get(&byte_addr) { @@ -202,12 +206,11 @@ pub fn generate_page_trace( (0, init_value) }; - data[base + cols::FINI] = FE::from(fini_value as u64); - data[base + cols::TIMESTAMP_LO] = FE::from(timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_HI] = FE::from(timestamp >> 32); + table.set_byte(offset, cols::FINI, fini_value); + table.set_dword_wl(offset, cols::TIMESTAMP_LO, timestamp); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/register.rs b/prover/src/tables/register.rs index 2907c924a..5a09fb2fa 100644 --- a/prover/src/tables/register.rs +++ b/prover/src/tables/register.rs @@ -29,7 +29,7 @@ use stark::prover::evaluate_polynomial_on_lde_domain; use stark::trace::{TraceTable, columns2rows}; use super::page::STACK_TOP; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; // ========================================================================= // Constants @@ -149,17 +149,20 @@ pub fn generate_register_trace( entry_point: u64, ) -> TraceTable { let num_rows = NUM_REGISTER_ADDRESSES.next_power_of_two(); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; let addr_list = register_word_address_list(); for (row, &word_addr) in addr_list.iter().enumerate().take(NUM_REGISTER_ADDRESSES) { - let base = row * cols::NUM_COLUMNS; - // Offset = actual Word address in register space - data[base + cols::OFFSET] = FE::from(word_addr); + table.set_u64(row, cols::OFFSET, word_addr); let init_value = init_value_for_address(word_addr, entry_point); - data[base + cols::INIT] = FE::from(init_value as u64); + table.set_word(row, cols::INIT, init_value); // Final state: if accessed use final, otherwise use initial (timestamp 1) let (timestamp, fini_value) = if let Some(state) = final_state.get(&word_addr) { @@ -169,20 +172,18 @@ pub fn generate_register_trace( (1, init_value) }; - data[base + cols::FINI] = FE::from(fini_value as u64); - data[base + cols::TIMESTAMP_LO] = FE::from(timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_HI] = FE::from(timestamp >> 32); + table.set_word(row, cols::FINI, fini_value); + table.set_dword_wl(row, cols::TIMESTAMP_LO, timestamp); } // Padding rows (if num_rows > NUM_REGISTER_ADDRESSES): set TIMESTAMP_LO=1 so // REG-C1's constant ts=1 emission matches REG-C2's ts=TIMESTAMP_LO consumption, // keeping padding rows self-cancelling on the bus. for row in NUM_REGISTER_ADDRESSES..num_rows { - let base = row * cols::NUM_COLUMNS; - data[base + cols::TIMESTAMP_LO] = FE::from(1u64); + table.set_u64(row, cols::TIMESTAMP_LO, 1); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/shift.rs b/prover/src/tables/shift.rs index c8cd5df62..9b851e123 100644 --- a/prover/src/tables/shift.rs +++ b/prover/src/tables/shift.rs @@ -24,7 +24,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing} use stark::table::TableView; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, SHIFT_16, alu_op}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, SHIFT_16, VmTable, alu_op}; // ========================================================================= // Column indices @@ -359,58 +359,62 @@ pub fn generate_shift_trace( // No deduplication: each operation gets its own row with μ=1. // Spec declares μ: Bit. let num_rows = operations.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in operations.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; let aux = op.compute_aux(); // Input columns - for i in 0..4 { - data[base + cols::IN[i]] = FE::from(op.in_halves[i] as u64); - } - data[base + cols::SHIFT_AMOUNT] = FE::from(op.shift as u64); + table.set_halves(row_idx, cols::IN_0, &op.in_halves); + table.set_byte(row_idx, cols::SHIFT_AMOUNT, op.shift); // High bits of the full shift amount (for the ALU bus in2 = arg2). - data[base + cols::SHIFT_B1] = FE::from((op.shift_amount >> 8) & 0xFF); - data[base + cols::SHIFT_H1] = FE::from((op.shift_amount >> 16) & 0xFFFF); - data[base + cols::SHIFT_HIGH] = FE::from(op.shift_amount >> 32); - data[base + cols::DIRECTION] = FE::from(op.direction as u64); - data[base + cols::SIGNED] = FE::from(op.signed as u64); - data[base + cols::WORD_INSTR] = FE::from(op.word_instr as u64); + table.set_byte( + row_idx, + cols::SHIFT_B1, + ((op.shift_amount >> 8) & 0xFF) as u8, + ); + table.set_half( + row_idx, + cols::SHIFT_H1, + ((op.shift_amount >> 16) & 0xFFFF) as u16, + ); + table.set_word(row_idx, cols::SHIFT_HIGH, (op.shift_amount >> 32) as u32); + table.set_bool(row_idx, cols::DIRECTION, op.direction); + table.set_bool(row_idx, cols::SIGNED, op.signed); + table.set_bool(row_idx, cols::WORD_INSTR, op.word_instr); // Output columns - data[base + cols::OUT_0] = FE::from(aux.out[0] as u64); - data[base + cols::OUT_1] = FE::from(aux.out[1] as u64); + table.set_words(row_idx, cols::OUT_0, &aux.out); // Auxiliary columns - data[base + cols::IS_NEGATIVE] = FE::from(aux.is_negative as u64); - data[base + cols::BIT_SHIFT] = FE::from(aux.bit_shift as u64); - data[base + cols::ZBS] = FE::from(aux.zbs as u64); + table.set_bool(row_idx, cols::IS_NEGATIVE, aux.is_negative); + table.set_byte(row_idx, cols::BIT_SHIFT, aux.bit_shift); + table.set_bool(row_idx, cols::ZBS, aux.zbs); - for i in 0..5 { - data[base + cols::X[i]] = FE::from(aux.x[i] as u64); - } - for i in 0..4 { - data[base + cols::Y[i]] = FE::from(aux.y[i] as u64); - } + table.set_halves(row_idx, cols::X_0, &aux.x); + table.set_halves(row_idx, cols::Y_0, &aux.y); for i in 0..3 { - data[base + cols::LIMB_SHIFT_RAW[i]] = FE::from(aux.limb_shift[i] as u64); + table.set_bool(row_idx, cols::LIMB_SHIFT_RAW[i], aux.limb_shift[i]); } // limb_shift[3] is virtual: not stored in the trace // μ = 1 for all active rows (Bit) - data[base + cols::MU] = FE::one(); + table.set_bool(row_idx, cols::MU, true); } // Padding rows: set ZBS=1 per spec. All other columns remain 0. // μ=0 so C13 (limb_shift encoding) is inactive. left=right=0 so shifted=0, // making C14 (out=shifted) trivially satisfied regardless of limb_shift. for row_idx in operations.len()..num_rows { - let base = row_idx * cols::NUM_COLUMNS; - data[base + cols::ZBS] = FE::one(); + table.set_bool(row_idx, cols::ZBS, true); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/store.rs b/prover/src/tables/store.rs index 7eea3656f..1cdf0334e 100644 --- a/prover/src/tables/store.rs +++ b/prover/src/tables/store.rs @@ -26,7 +26,7 @@ use stark::lookup::{BusInteraction, BusValue, LinearTerm, Multiplicity, Packing} use stark::table::TableView; use stark::trace::TraceTable; -use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField}; +use super::types::{BusId, FE, GoldilocksExtension, GoldilocksField, VmTable}; use crate::constraints::templates::new_is_bit_constraints; // ========================================================================= @@ -98,25 +98,24 @@ pub fn generate_store_trace( operations: &[StoreOperation], ) -> TraceTable { let num_rows = operations.len().next_power_of_two().max(4); - let mut data = vec![FE::zero(); num_rows * cols::NUM_COLUMNS]; + let mut trace = TraceTable::new_main( + vec![FE::zero(); num_rows * cols::NUM_COLUMNS], + cols::NUM_COLUMNS, + 1, + ); + let table = &mut trace.main_table; for (row_idx, op) in operations.iter().enumerate() { - let base = row_idx * cols::NUM_COLUMNS; - - data[base + cols::BASE_ADDRESS_0] = FE::from(op.base_address & 0xFFFF_FFFF); - data[base + cols::BASE_ADDRESS_1] = FE::from(op.base_address >> 32); - data[base + cols::TIMESTAMP_0] = FE::from(op.timestamp & 0xFFFF_FFFF); - data[base + cols::TIMESTAMP_1] = FE::from(op.timestamp >> 32); - data[base + cols::WRITE2] = FE::from(op.write2 as u64); - data[base + cols::WRITE4] = FE::from(op.write4 as u64); - data[base + cols::WRITE8] = FE::from(op.write8 as u64); - for i in 0..8 { - data[base + cols::VALUE[i]] = FE::from((op.value >> (8 * i)) & 0xFF); - } - data[base + cols::MU] = FE::one(); + table.set_dword_wl(row_idx, cols::BASE_ADDRESS_0, op.base_address); + table.set_dword_wl(row_idx, cols::TIMESTAMP_0, op.timestamp); + table.set_bool(row_idx, cols::WRITE2, op.write2); + table.set_bool(row_idx, cols::WRITE4, op.write4); + table.set_bool(row_idx, cols::WRITE8, op.write8); + table.set_dword_bl(row_idx, cols::VALUE[0], op.value); + table.set_fe(row_idx, cols::MU, FE::one()); } - TraceTable::new_main(data, cols::NUM_COLUMNS, 1) + trace } // ========================================================================= diff --git a/prover/src/tables/types.rs b/prover/src/tables/types.rs index bc16ce780..d6091d0fd 100644 --- a/prover/src/tables/types.rs +++ b/prover/src/tables/types.rs @@ -19,6 +19,7 @@ use executor::vm::instruction::decoding::{ArithOp, Comparison, Instruction, Load use math::field::element::FieldElement; use math::field::extensions_goldilocks::Degree3GoldilocksExtensionField; use math::field::goldilocks::GoldilocksField as GoldilocksBaseField; +use stark::table::Table; /// Base field type: Goldilocks prime field (p = 2^64 - 2^32 + 1) pub type GoldilocksField = GoldilocksBaseField; @@ -32,6 +33,172 @@ pub type FE = FieldElement; /// Field element in the Goldilocks extension field pub type FEE = FieldElement; +/// Decompose a `u64` into its two little-endian 32-bit limbs as field elements: +/// `[x[0..32], x[32..64]]` (the `DWordWL` column encoding). +/// +/// Lives in the prover (not the generic `Table`) because the decomposition is +/// field-size-specific: a 32-bit limb only fits because Goldilocks is ~64-bit. +#[inline] +pub fn dword_wl(x: u64) -> [FE; 2] { + [FE::from(x & 0xFFFF_FFFF), FE::from(x >> 32)] +} + +/// Decompose a `u64` into its four little-endian 16-bit limbs as field elements: +/// `[x[0..16], x[16..32], x[32..48], x[48..64]]` (the `DWordHL` column encoding). +#[inline] +pub fn dword_hl(x: u64) -> [FE; 4] { + [ + FE::from(x & 0xFFFF), + FE::from((x >> 16) & 0xFFFF), + FE::from((x >> 32) & 0xFFFF), + FE::from((x >> 48) & 0xFFFF), + ] +} + +/// VM-specific trace writes for Goldilocks-backed tables. +/// +/// These helpers live at the VM prover layer because encodings like `DWordWL` +/// assume field elements can faithfully represent the corresponding limbs. +/// +/// Width names follow the VM table specs: +/// - `Byte`: 8 bits. +/// - `Half`: 16 bits. +/// - `Word`: 32 bits. +/// - `DWord`: 64 bits. +/// +/// Trace columns are written in little-endian order: `start_col` always receives +/// the least-significant chunk. For homogeneous encodings like `DWordWL`, +/// `DWordHL`, and `DWordBL`, the final `L` means "little-endian limbs". Mixed +/// encodings like `DWordWHH` and `DWordHHW` keep the spec's packing name, while +/// still storing the low chunk first in the trace. +pub trait VmTable { + /// Write an already-constructed field element into one trace cell. + fn set_fe(&mut self, row: usize, col: usize, value: FE); + + /// Convert `value` with `FE::from` and write it into one trace cell. + #[inline] + fn set_u64(&mut self, row: usize, col: usize, value: u64) { + self.set_fe(row, col, FE::from(value)); + } + + /// Write a bit column as `0` or `1`. + #[inline] + fn set_bool(&mut self, row: usize, col: usize, value: bool) { + self.set_u64(row, col, u64::from(value)); + } + + /// Write an 8-bit VM `Byte` column. + #[inline] + fn set_byte(&mut self, row: usize, col: usize, value: u8) { + self.set_u64(row, col, u64::from(value)); + } + + /// Write a 16-bit VM `Half` column. + #[inline] + fn set_half(&mut self, row: usize, col: usize, value: u16) { + self.set_u64(row, col, u64::from(value)); + } + + /// Write a 32-bit VM `Word` column. + #[inline] + fn set_word(&mut self, row: usize, col: usize, value: u32) { + self.set_u64(row, col, u64::from(value)); + } + + /// Write contiguous `Byte` columns starting at `start_col`. + #[inline] + fn set_bytes(&mut self, row: usize, start_col: usize, values: &[u8]) { + for (offset, &value) in values.iter().enumerate() { + self.set_byte(row, start_col + offset, value); + } + } + + /// Write contiguous `Half` columns starting at `start_col`. + #[inline] + fn set_halves(&mut self, row: usize, start_col: usize, values: &[u16]) { + for (offset, &value) in values.iter().enumerate() { + self.set_half(row, start_col + offset, value); + } + } + + /// Write contiguous `Word` columns starting at `start_col`. + #[inline] + fn set_words(&mut self, row: usize, start_col: usize, values: &[u32]) { + for (offset, &value) in values.iter().enumerate() { + self.set_word(row, start_col + offset, value); + } + } + + /// Write a `DWordBL`: eight little-endian bytes of a 64-bit value. + /// + /// Columns receive bits `[0..8]`, `[8..16]`, ..., `[56..64]`. + #[inline] + fn set_dword_bl(&mut self, row: usize, start_col: usize, value: u64) { + self.set_bytes(row, start_col, &value.to_le_bytes()); + } + + /// Write a `DWordWL`: two little-endian 32-bit words of a 64-bit value. + /// + /// Columns receive bits `[0..32]` and `[32..64]`. + #[inline] + fn set_dword_wl(&mut self, row: usize, start_col: usize, value: u64) { + let [lo, hi] = dword_wl(value); + self.set_fe(row, start_col, lo); + self.set_fe(row, start_col + 1, hi); + } + + /// Write a `DWordHL`: four little-endian 16-bit halves of a 64-bit value. + /// + /// Columns receive bits `[0..16]`, `[16..32]`, `[32..48]`, and `[48..64]`. + #[inline] + fn set_dword_hl(&mut self, row: usize, start_col: usize, value: u64) { + let [h0, h1, h2, h3] = dword_hl(value); + self.set_fe(row, start_col, h0); + self.set_fe(row, start_col + 1, h1); + self.set_fe(row, start_col + 2, h2); + self.set_fe(row, start_col + 3, h3); + } + + /// Write a mixed `DWordWHH` layout. + /// + /// The spec name describes a 64-bit value split as a high `Word` followed by + /// two lower `Half`s. Trace columns are still low-first, so they receive bits + /// `[0..16]`, `[16..32]`, and `[32..64]`. + #[inline] + fn set_dword_whh(&mut self, row: usize, start_col: usize, value: u64) { + let low_half = (value & 0xFFFF) as u16; + let mid_half = ((value >> 16) & 0xFFFF) as u16; + let high_word = ((value >> 32) & 0xFFFF_FFFF) as u32; + + self.set_half(row, start_col, low_half); + self.set_half(row, start_col + 1, mid_half); + self.set_word(row, start_col + 2, high_word); + } + + /// Write a mixed `DWordHHW` layout. + /// + /// The spec name describes a 64-bit value split as two high `Half`s followed + /// by a low `Word`. Trace columns are still low-first, so they receive bits + /// `[0..32]`, `[32..48]`, and `[48..64]`. + #[inline] + fn set_dword_hhw(&mut self, row: usize, start_col: usize, value: u64) { + let low_word = (value & 0xFFFF_FFFF) as u32; + let mid_half = ((value >> 32) & 0xFFFF) as u16; + let high_half = ((value >> 48) & 0xFFFF) as u16; + + self.set_word(row, start_col, low_word); + self.set_half(row, start_col + 1, mid_half); + self.set_half(row, start_col + 2, high_half); + } +} + +impl VmTable for Table { + #[inline] + fn set_fe(&mut self, row: usize, col: usize, value: FE) { + self.set(row, col, value); + } +} + /// Bus identifiers for LogUp interactions between tables. /// /// Each bus connects senders (tables that produce values) with receivers