diff --git a/prover/src/tables/trace_builder.rs b/prover/src/tables/trace_builder.rs index 6cbcd0735..94d3d5021 100644 --- a/prover/src/tables/trace_builder.rs +++ b/prover/src/tables/trace_builder.rs @@ -1628,6 +1628,23 @@ pub struct Traces { pub memw_registers: Vec>, } +/// Intermediate state from Phase 2: all ops collected from CPU, ready for +/// Phases 3-5 (LT extension, bitwise, trace generation). +struct CollectedOps { + cpu_ops: Vec, + memw_ops: Vec, + memw_aligned_ops: Vec, + memw_register_ops: Vec, + load_ops: Vec, + lt_ops: Vec, + shift_ops: Vec, + bitwise_ops: Vec, + branch_ops: Vec, + mul_ops: Vec<(MulOperation, bool)>, + dvrm_ops: Vec<(DvrmOperation, bool)>, + commit_ops: Vec, +} + /// Chunk raw ops and generate one trace table per chunk. fn chunk_and_generate( ops: &[T], @@ -1641,6 +1658,288 @@ fn chunk_and_generate( } } +/// Phase 2: Collect and route all operations from CPU ops. +/// +/// Takes the raw output of `collect_ops_from_cpu` plus `register_state` +/// (for HALT finalization), and returns fully-routed ops ready for Phase 3+. +#[allow(clippy::too_many_arguments)] +fn collect_all_ops( + cpu_ops: Vec, + mut memw_ops: Vec, + load_ops: Vec, + mut lt_ops: Vec, + shift_ops: Vec, + bitwise_ops: Vec, + commit_ops: Vec, + register_state: &mut RegisterState, +) -> CollectedOps { + // HALT finalization: 33 register MEMW operations at timestamp u64::MAX. + // Must come before Phase 3 (LT from MEMW) so HALT ops get timestamp checks. + let halt_memw_ops = collect_halt_ops(register_state); + memw_ops.extend(halt_memw_ops); + + // Route MEMW_R (register fast-path) first, then MEMW_A (aligned), rest → MEMW. + // Order matters: register ops would also pass is_aligned_op, so check first. + let (memw_register_ops, memw_ops): (Vec<_>, Vec<_>) = + memw_ops.into_iter().partition(is_register_op); + let (memw_aligned_ops, memw_ops): (Vec<_>, Vec<_>) = + memw_ops.into_iter().partition(is_aligned_op); + + // Collect BRANCH operations from CPU ops where branch_cond = true + let branch_ops: Vec = cpu_ops + .iter() + .filter(|op| op.branch_cond) + .map(|op| { + BranchOperation::new( + op.decode.pc, + op.decode.imm, // offset as full 64-bit DWordWL (already sign-extended) + op.compute_arg1(), // register value must match CPU's arg1 for bus signature + op.decode.op_jalr, + ) + }) + .collect(); + + // Collect MUL operations from CPU ops where op_mul = true + let mut mul_ops: Vec<(MulOperation, bool)> = cpu_ops + .iter() + .filter(|op| op.decode.op_mul) + .map(|op| { + let lhs = op.compute_arg1(); + let lhs_signed = op.decode.signed; + // rhs_signed = mp_selector per spec CPU-CA44: + // MUL/MULH have mp_selector=1 (both signed), MULHU/MULHSU have mp_selector=0 (rhs unsigned) + let rhs_signed = op.decode.mp_selector; + let rhs = op.compute_arg2(); + let wants_hi = op.decode.muldiv_selector; + ( + MulOperation::new(lhs, lhs_signed, rhs, rhs_signed), + wants_hi, + ) + }) + .collect(); + + // Collect DVRM operations from CPU ops where op_divrem = true + let dvrm_ops: Vec<(DvrmOperation, bool)> = cpu_ops + .iter() + .filter(|op| op.decode.op_divrem) + .map(|op| { + let n = op.compute_arg1(); + let d = op.compute_arg2(); + let signed = op.decode.signed; + let wants_remainder = op.decode.muldiv_selector; + (DvrmOperation::new(n, d, signed), wants_remainder) + }) + .collect(); + + // Collect LT operations from DVRM: |r| < |d| (unsigned comparison) + for (op, _wants_remainder) in &dvrm_ops { + lt_ops.push(LtOperation::new(op.abs_r(), op.abs_d(), false)); + } + + // Collect MUL operations from DVRM: d * q = n_sub_r (C13 lo, C14 hi) + for (op, _wants_remainder) in &dvrm_ops { + let d = op.d; + let d_signed = op.signed; + let q = op.compute_quotient(); + let q_signed = op.sign_q(); + let mul_op = MulOperation::new(d, d_signed, q, q_signed); + mul_ops.push((mul_op.clone(), false)); // C13: lo (muldiv_selector=0) + mul_ops.push((mul_op, true)); // C14: hi (muldiv_selector=1) + } + + CollectedOps { + cpu_ops, + memw_ops, + memw_aligned_ops, + memw_register_ops, + load_ops, + lt_ops, + shift_ops, + bitwise_ops, + branch_ops, + mul_ops, + dvrm_ops, + commit_ops, + } +} + +/// Phases 3-5: From routed ops, produce all traces and assemble `Traces`. +/// +/// `elf` controls PAGE table generation: `Some(elf)` generates real PAGE tables +/// and PAGE bitwise lookups; `None` produces empty page tables. +#[allow(clippy::too_many_arguments)] +fn build_traces( + ops: CollectedOps, + elf: Option<&Elf>, + memory_state: &MemoryState, + entry_point: u64, + decode_trace: TraceTable, + decode_pc_to_row: HashMap, + register_state: RegisterState, + max_rows: &super::MaxRowsConfig, +) -> Result { + let CollectedOps { + cpu_ops, + memw_ops, + memw_aligned_ops, + memw_register_ops, + load_ops, + mut lt_ops, + shift_ops, + mut bitwise_ops, + branch_ops, + mul_ops, + dvrm_ops, + commit_ops, + } = ops; + + // ===================================================================== + // PHASE 3: MEMW → LT (timestamp ordering and overflow checks) + // ===================================================================== + lt_ops.extend(collect_lt_from_memw(&memw_ops)); + lt_ops.extend(collect_lt_from_memw_aligned(&memw_aligned_ops)); + + // ===================================================================== + // PHASE 4: All → Bitwise lookups + // ===================================================================== + bitwise_ops.extend(collect_bitwise_from_lt(<_ops)); + bitwise_ops.extend(collect_bitwise_from_mul(&mul_ops)); + bitwise_ops.extend(collect_bitwise_from_dvrm(&dvrm_ops)); + bitwise_ops.extend(collect_bitwise_from_branch(&branch_ops)); + bitwise_ops.extend(shift::collect_bitwise_from_shift(&shift_ops)); + bitwise_ops.extend(collect_bitwise_from_memw_aligned(&memw_aligned_ops)); + // MEMW_R sends IS_HALFWORD[timestamp_0 - old_timestamp_lo - 1] + bitwise_ops.extend(collect_bitwise_from_memw_register(&memw_register_ops)); + // PAGE tables do IS_BYTE lookups for init and fini values (C1, C2) + if let Some(elf) = elf { + bitwise_ops.extend(collect_bitwise_from_page(elf, memory_state)); + } + + let public_output_bytes: Vec = commit_ops + .iter() + .filter(|op| !op.end) + .map(|op| op.value) + .collect(); + // COMMIT table sends IsByte and IsHalfword lookups + bitwise_ops.extend(collect_bitwise_from_commit(&commit_ops)); + + // CPU padding rows send IS_BYTE with all-zero values. + // Add corresponding ops so the bitwise table multiplicities balance. + let num_padding_rows: usize = cpu_ops + .chunks(max_rows.cpu) + .map(|chunk| chunk.len().next_power_of_two().max(4) - chunk.len()) + .sum(); + bitwise_ops.extend(collect_byte_check_ops_for_padding(num_padding_rows)); + + // ===================================================================== + // PHASE 5: Generate final traces (parallelized) + // ===================================================================== + + // Extract halt timestamp from the last ECALL instruction + let halt_op = cpu_ops + .iter() + .rev() + .find(|op| op.decode.op_ecall) + .ok_or(Error::MissingHaltEcall)?; + let halt_timestamp = halt_op.timestamp; + + let cpus = chunk_and_generate(&cpu_ops, max_rows.cpu, cpu::generate_cpu_trace); + let memws = chunk_and_generate(&memw_ops, max_rows.memw, memw::generate_memw_trace); + let memw_aligneds = chunk_and_generate( + &memw_aligned_ops, + max_rows.memw_aligned, + memw_aligned::generate_memw_aligned_trace, + ); + let memw_registers = chunk_and_generate( + &memw_register_ops, + max_rows.memw_register, + memw_register::generate_memw_register_trace, + ); + let loads = chunk_and_generate(&load_ops, max_rows.load, load::generate_load_trace); + let lts = chunk_and_generate(<_ops, max_rows.lt, lt::generate_lt_trace); + let shifts = chunk_and_generate(&shift_ops, max_rows.shift, shift::generate_shift_trace); + let muls = chunk_and_generate(&mul_ops, max_rows.mul, mul::generate_mul_trace); + let dvrms = chunk_and_generate(&dvrm_ops, max_rows.dvrm, dvrm::generate_dvrm_trace); + let branches = chunk_and_generate(&branch_ops, max_rows.branch, branch::generate_branch_trace); + + let mut bitwise = bitwise::generate_bitwise_trace(); + bitwise::update_multiplicities(&mut bitwise, &bitwise_ops); + + // Update DECODE multiplicities + // Each CPU operation looks up the DECODE table once + // Padding rows also look up pc=1 (the CPU padding entry) + // When CPU is split, each chunk pads independently + let mut decode = decode_trace; + let mut decode_lookups: Vec = cpu_ops.iter().map(|op| op.decode.pc).collect(); + decode_lookups.extend(std::iter::repeat_n(cpu::CPU_PADDING_PC, num_padding_rows)); + decode::update_multiplicities(&mut decode, &decode_pc_to_row, &decode_lookups); + + // Prepare register final state before scope (needs register_state ownership) + let register_final_state = register_state.to_final_state_map(); + + // Generate remaining traces in parallel (page, register, halt, commit). + // chunk_and_generate already handled cpu, lt, memw, load, mul, dvrm, branch above. + let commit_trace = commit::generate_commit_trace(&commit_ops); + let (pages, page_configs, register_trace, halt_trace); + #[cfg(feature = "parallel")] + { + let ((pages_val, register_val), halt_val) = rayon::join( + || { + rayon::join( + || match elf { + Some(elf) => generate_page_tables(elf, memory_state), + None => (Vec::new(), Vec::new()), + }, + || register::generate_register_trace(®ister_final_state, entry_point), + ) + }, + || halt::generate_halt_trace(halt_timestamp), + ); + let (pages_v, page_configs_v) = pages_val; + pages = pages_v; + page_configs = page_configs_v; + register_trace = register_val; + halt_trace = halt_val; + } + #[cfg(not(feature = "parallel"))] + { + match elf { + Some(elf) => { + let (p, c) = generate_page_tables(elf, memory_state); + pages = p; + page_configs = c; + } + None => { + pages = Vec::new(); + page_configs = Vec::new(); + } + } + register_trace = register::generate_register_trace(®ister_final_state, entry_point); + halt_trace = halt::generate_halt_trace(halt_timestamp); + } + + Ok(Traces { + cpus, + bitwise, + lts, + shifts, + memws, + memw_aligneds, + loads, + decode, + muls, + dvrms, + pages, + page_configs, + register: register_trace, + public_output_bytes, + branches, + halt: halt_trace, + commit: commit_trace, + memw_registers, + }) +} + impl Traces { /// Returns the number of chunks for each split table. pub fn table_counts(&self) -> crate::TableCounts { @@ -1780,250 +2079,44 @@ impl Traces { logs: &[Log], max_rows: &super::MaxRowsConfig, ) -> Result { - // ===================================================================== - // PHASE 0: ELF → DECODE + instructions - // ===================================================================== + // Phase 0: ELF → DECODE + instructions // IMPORTANT: Use generate_decode_trace (same as compute_precomputed_commitment) // so the DECODE trace row ordering matches the AIR's hardcoded commitment. - // tables_from_elf iterates ELF segments sequentially, but the commitment - // is computed via HashMap iteration which may have different ordering. let instructions = decode::instructions_from_elf(elf) .map_err(|e| Error::Execution(format!("Failed to parse instructions: {e}")))?; let (decode_trace, decode_pc_to_row) = decode::generate_decode_trace(&instructions); - // ===================================================================== - // PHASE 1: Logs → CPU operations - // ===================================================================== + // Phase 1: Logs → CPU operations let cpu_ops = collect_cpu_ops(logs, &instructions)?; - // ===================================================================== - // PHASE 2: CPU ops → MEMW, LOAD, LT, Bitwise, Branch - // ===================================================================== - // Processes cpu_ops in order. MEMW/LOAD need state tracking, LT/Bitwise don't. - // Initialize memory state from ELF so first accesses get correct old_value. + // Phase 2: Collect + route all ops let mut memory_state = MemoryState::from_elf(elf); let mut register_state = RegisterState::new(elf.entry_point); - let (mut memw_ops, load_ops, mut lt_ops, shift_ops, mut bitwise_ops, commit_ops) = + let (memw_ops, load_ops, lt_ops, shift_ops, bitwise_ops, commit_ops) = collect_ops_from_cpu(&cpu_ops, &mut memory_state, &mut register_state); - // HALT finalization: 33 register MEMW operations at timestamp u64::MAX. - // Must come before Phase 3 (LT from MEMW) so HALT ops get timestamp checks. - let halt_memw_ops = collect_halt_ops(&mut register_state); - memw_ops.extend(halt_memw_ops); - - // Route MEMW_R (register fast-path) first, then MEMW_A (aligned), rest → MEMW. - // Order matters: register ops would also pass is_aligned_op, so check first. - let (memw_register_ops, memw_ops): (Vec<_>, Vec<_>) = - memw_ops.into_iter().partition(is_register_op); - let (memw_aligned_ops, memw_ops): (Vec<_>, Vec<_>) = - memw_ops.into_iter().partition(is_aligned_op); - - // Collect BRANCH operations from CPU ops where branch_cond = true - let branch_ops: Vec = cpu_ops - .iter() - .filter(|op| op.branch_cond) - .map(|op| { - BranchOperation::new( - op.decode.pc, - op.decode.imm, // offset as full 64-bit DWordWL (already sign-extended) - op.compute_arg1(), // register value must match CPU's arg1 for bus signature - op.decode.op_jalr, - ) - }) - .collect(); - - // Collect MUL operations from CPU ops where op_mul = true - let mut mul_ops: Vec<(MulOperation, bool)> = cpu_ops - .iter() - .filter(|op| op.decode.op_mul) - .map(|op| { - let lhs = op.compute_arg1(); - let lhs_signed = op.decode.signed; - // rhs_signed = mp_selector per spec CPU-CA44: - // MUL/MULH have mp_selector=1 (both signed), MULHU/MULHSU have mp_selector=0 (rhs unsigned) - let rhs_signed = op.decode.mp_selector; - let rhs = op.compute_arg2(); - let wants_hi = op.decode.muldiv_selector; - ( - MulOperation::new(lhs, lhs_signed, rhs, rhs_signed), - wants_hi, - ) - }) - .collect(); - - // Collect DVRM operations from CPU ops where op_divrem = true - let dvrm_ops: Vec<(DvrmOperation, bool)> = cpu_ops - .iter() - .filter(|op| op.decode.op_divrem) - .map(|op| { - let n = op.compute_arg1(); - let d = op.compute_arg2(); - let signed = op.decode.signed; - let wants_remainder = op.decode.muldiv_selector; - (DvrmOperation::new(n, d, signed), wants_remainder) - }) - .collect(); - - // Collect LT operations from DVRM: |r| < |d| (unsigned comparison) - for (op, _wants_remainder) in &dvrm_ops { - lt_ops.push(LtOperation::new(op.abs_r(), op.abs_d(), false)); - } - - // Collect MUL operations from DVRM: d * q = n_sub_r (C13 lo, C14 hi) - for (op, _wants_remainder) in &dvrm_ops { - let d = op.d; - let d_signed = op.signed; - let q = op.compute_quotient(); - let q_signed = op.sign_q(); - let mul_op = MulOperation::new(d, d_signed, q, q_signed); - mul_ops.push((mul_op.clone(), false)); // C13: lo (muldiv_selector=0) - mul_ops.push((mul_op, true)); // C14: hi (muldiv_selector=1) - } - - // ===================================================================== - // PHASE 3: MEMW → LT (timestamp ordering and overflow checks) - // ===================================================================== - lt_ops.extend(collect_lt_from_memw(&memw_ops)); - lt_ops.extend(collect_lt_from_memw_aligned(&memw_aligned_ops)); - - // ===================================================================== - // PHASE 4: All → Bitwise lookups - // ===================================================================== - bitwise_ops.extend(collect_bitwise_from_lt(<_ops)); - bitwise_ops.extend(collect_bitwise_from_mul(&mul_ops)); - bitwise_ops.extend(collect_bitwise_from_dvrm(&dvrm_ops)); - bitwise_ops.extend(collect_bitwise_from_branch(&branch_ops)); - bitwise_ops.extend(shift::collect_bitwise_from_shift(&shift_ops)); - bitwise_ops.extend(collect_bitwise_from_memw_aligned(&memw_aligned_ops)); - // MEMW_R sends IS_HALFWORD[timestamp_0 - old_timestamp_lo - 1] - bitwise_ops.extend(collect_bitwise_from_memw_register(&memw_register_ops)); - // PAGE tables do IS_BYTE lookups for init and fini values (C1, C2) - bitwise_ops.extend(collect_bitwise_from_page(elf, &memory_state)); - - let public_output_bytes: Vec = commit_ops - .iter() - .filter(|op| !op.end) - .map(|op| op.value) - .collect(); - // COMMIT table sends IsByte and IsHalfword lookups - bitwise_ops.extend(collect_bitwise_from_commit(&commit_ops)); - - // CPU padding rows send IS_BYTE with all-zero values. - // Add corresponding ops so the bitwise table multiplicities balance. - let num_padding_rows: usize = cpu_ops - .chunks(max_rows.cpu) - .map(|chunk| chunk.len().next_power_of_two().max(4) - chunk.len()) - .sum(); - bitwise_ops.extend(collect_byte_check_ops_for_padding(num_padding_rows)); - - // ===================================================================== - // PHASE 5: Generate final traces (parallelized) - // ===================================================================== - - // Extract halt timestamp from the last ECALL instruction - let halt_op = cpu_ops - .iter() - .rev() - .find(|op| op.decode.op_ecall) - .ok_or(Error::MissingHaltEcall)?; - let halt_timestamp = halt_op.timestamp; - - let cpus = chunk_and_generate(&cpu_ops, max_rows.cpu, cpu::generate_cpu_trace); - let memws = chunk_and_generate(&memw_ops, max_rows.memw, memw::generate_memw_trace); - let memw_aligneds = chunk_and_generate( - &memw_aligned_ops, - max_rows.memw_aligned, - memw_aligned::generate_memw_aligned_trace, - ); - let memw_registers = chunk_and_generate( - &memw_register_ops, - max_rows.memw_register, - memw_register::generate_memw_register_trace, + let ops = collect_all_ops( + cpu_ops, + memw_ops, + load_ops, + lt_ops, + shift_ops, + bitwise_ops, + commit_ops, + &mut register_state, ); - let loads = chunk_and_generate(&load_ops, max_rows.load, load::generate_load_trace); - let lts = chunk_and_generate(<_ops, max_rows.lt, lt::generate_lt_trace); - let shifts = chunk_and_generate(&shift_ops, max_rows.shift, shift::generate_shift_trace); - let muls = chunk_and_generate(&mul_ops, max_rows.mul, mul::generate_mul_trace); - let dvrms = chunk_and_generate(&dvrm_ops, max_rows.dvrm, dvrm::generate_dvrm_trace); - let branches = - chunk_and_generate(&branch_ops, max_rows.branch, branch::generate_branch_trace); - - let mut bitwise = bitwise::generate_bitwise_trace(); - bitwise::update_multiplicities(&mut bitwise, &bitwise_ops); - - // Update DECODE multiplicities - // Each CPU operation looks up the DECODE table once - // Padding rows also look up pc=1 (the CPU padding entry) - // When CPU is split, each chunk pads independently - let mut decode = decode_trace; - let pc_to_row = decode_pc_to_row; - let num_padding_rows: usize = cpu_ops - .chunks(max_rows.cpu) - .map(|chunk| chunk.len().next_power_of_two().max(4) - chunk.len()) - .sum(); - let mut decode_lookups: Vec = cpu_ops.iter().map(|op| op.decode.pc).collect(); - decode_lookups.extend(std::iter::repeat_n(cpu::CPU_PADDING_PC, num_padding_rows)); - decode::update_multiplicities(&mut decode, &pc_to_row, &decode_lookups); - - // Prepare register final state before scope (needs register_state ownership) - let register_final_state = register_state.to_final_state_map(); - - // Generate remaining traces in parallel (page, register, halt, commit). - // chunk_and_generate already handled cpu, lt, memw, load, mul, dvrm, branch above. - let commit_trace = commit::generate_commit_trace(&commit_ops); - let (pages, page_configs, register_trace, halt_trace); - #[cfg(feature = "parallel")] - { - let ((pages_val, register_val), halt_val) = rayon::join( - || { - rayon::join( - || generate_page_tables(elf, &memory_state), - || { - register::generate_register_trace( - ®ister_final_state, - elf.entry_point, - ) - }, - ) - }, - || halt::generate_halt_trace(halt_timestamp), - ); - let (pages_v, page_configs_v) = pages_val; - pages = pages_v; - page_configs = page_configs_v; - register_trace = register_val; - halt_trace = halt_val; - } - #[cfg(not(feature = "parallel"))] - { - let (pages_v, page_configs_v) = generate_page_tables(elf, &memory_state); - pages = pages_v; - page_configs = page_configs_v; - register_trace = - register::generate_register_trace(®ister_final_state, elf.entry_point); - halt_trace = halt::generate_halt_trace(halt_timestamp); - } - Ok(Traces { - cpus, - bitwise, - lts, - shifts, - memws, - memw_aligneds, - loads, - decode, - muls, - dvrms, - pages, - page_configs, - register: register_trace, - public_output_bytes, - branches, - halt: halt_trace, - commit: commit_trace, - memw_registers, - }) + // Phases 3-5 + build_traces( + ops, + Some(elf), + &memory_state, + elf.entry_point, + decode_trace, + decode_pc_to_row, + register_state, + max_rows, + ) } /// Generates all traces from execution logs (legacy API). @@ -2037,222 +2130,41 @@ impl Traces { instructions: U64HashMap, max_rows: &super::MaxRowsConfig, ) -> Result { - // ===================================================================== - // PHASE 1: Logs → CPU operations - // ===================================================================== + // Phase 1: Logs → CPU operations let cpu_ops = collect_cpu_ops(logs, &instructions)?; - // ===================================================================== - // PHASE 2: CPU ops → MEMW, LOAD, LT, Bitwise, Branch - // ===================================================================== - // Processes cpu_ops in order. MEMW/LOAD need state tracking, LT/Bitwise don't. + // Phase 2: Collect + route all ops let mut memory_state = MemoryState::new(); - // Entry point = first instruction's PC (start of execution) let entry_point = cpu_ops.first().map_or(0, |op| op.decode.pc); let mut register_state = RegisterState::new(entry_point); - let (mut memw_ops, load_ops, mut lt_ops, shift_ops, mut bitwise_ops, commit_ops) = + let (memw_ops, load_ops, lt_ops, shift_ops, bitwise_ops, commit_ops) = collect_ops_from_cpu(&cpu_ops, &mut memory_state, &mut register_state); - // HALT finalization: 33 register MEMW operations at timestamp u64::MAX. - // Must come before Phase 3 (LT from MEMW) so HALT ops get timestamp checks. - let halt_memw_ops = collect_halt_ops(&mut register_state); - memw_ops.extend(halt_memw_ops); - - // Route MEMW_R (register fast-path) first, then MEMW_A (aligned), rest → MEMW. - let (memw_register_ops, memw_ops): (Vec<_>, Vec<_>) = - memw_ops.into_iter().partition(is_register_op); - let (memw_aligned_ops, memw_ops): (Vec<_>, Vec<_>) = - memw_ops.into_iter().partition(is_aligned_op); - - // Collect MUL operations from CPU ops where op_mul = true - let mut mul_ops: Vec<(MulOperation, bool)> = cpu_ops - .iter() - .filter(|op| op.decode.op_mul) - .map(|op| { - let lhs = op.compute_arg1(); - let lhs_signed = op.decode.signed; - // rhs_signed = mp_selector per spec CPU-CA44: - // MUL/MULH have mp_selector=1 (both signed), MULHU/MULHSU have mp_selector=0 (rhs unsigned) - let rhs_signed = op.decode.mp_selector; - let rhs = op.compute_arg2(); - let wants_hi = op.decode.muldiv_selector; - ( - MulOperation::new(lhs, lhs_signed, rhs, rhs_signed), - wants_hi, - ) - }) - .collect(); - - // Collect DVRM operations from CPU ops where op_divrem = true - let dvrm_ops: Vec<(DvrmOperation, bool)> = cpu_ops - .iter() - .filter(|op| op.decode.op_divrem) - .map(|op| { - let n = op.compute_arg1(); - let d = op.compute_arg2(); - let signed = op.decode.signed; - let wants_remainder = op.decode.muldiv_selector; - (DvrmOperation::new(n, d, signed), wants_remainder) - }) - .collect(); - - // Collect BRANCH operations from CPU ops where branch_cond = true - let branch_ops: Vec = cpu_ops - .iter() - .filter(|op| op.branch_cond) - .map(|op| { - BranchOperation::new( - op.decode.pc, - op.decode.imm, // offset as full 64-bit DWordWL (already sign-extended) - op.compute_arg1(), // register value must match CPU's arg1 for bus signature - op.decode.op_jalr, - ) - }) - .collect(); - - // Collect LT operations from DVRM: |r| < |d| (unsigned comparison) - for (op, _wants_remainder) in &dvrm_ops { - lt_ops.push(LtOperation::new(op.abs_r(), op.abs_d(), false)); - } - - // Collect MUL operations from DVRM: d * q = n_sub_r (C13 lo, C14 hi) - for (op, _wants_remainder) in &dvrm_ops { - let d = op.d; - let d_signed = op.signed; - let q = op.compute_quotient(); - let q_signed = op.sign_q(); - let mul_op = MulOperation::new(d, d_signed, q, q_signed); - mul_ops.push((mul_op.clone(), false)); // C13: lo (muldiv_selector=0) - mul_ops.push((mul_op, true)); // C14: hi (muldiv_selector=1) - } - - // ===================================================================== - // PHASE 3: MEMW → LT (timestamp ordering and overflow checks) - // ===================================================================== - lt_ops.extend(collect_lt_from_memw(&memw_ops)); - lt_ops.extend(collect_lt_from_memw_aligned(&memw_aligned_ops)); - - // ===================================================================== - // PHASE 4: All → Bitwise lookups - // ===================================================================== - bitwise_ops.extend(collect_bitwise_from_lt(<_ops)); - bitwise_ops.extend(collect_bitwise_from_mul(&mul_ops)); - bitwise_ops.extend(collect_bitwise_from_dvrm(&dvrm_ops)); - bitwise_ops.extend(collect_bitwise_from_branch(&branch_ops)); - bitwise_ops.extend(shift::collect_bitwise_from_shift(&shift_ops)); - bitwise_ops.extend(collect_bitwise_from_memw_aligned(&memw_aligned_ops)); - // MEMW_R sends IS_HALFWORD[timestamp_0 - old_timestamp_lo - 1] - bitwise_ops.extend(collect_bitwise_from_memw_register(&memw_register_ops)); - - let public_output_bytes: Vec = commit_ops - .iter() - .filter(|op| !op.end) - .map(|op| op.value) - .collect(); - // COMMIT table sends IsHalfword lookups - bitwise_ops.extend(collect_bitwise_from_commit(&commit_ops)); - - // CPU padding rows send IS_BYTE with all-zero values. - let num_padding_rows: usize = cpu_ops - .chunks(max_rows.cpu) - .map(|chunk| chunk.len().next_power_of_two().max(4) - chunk.len()) - .sum(); - bitwise_ops.extend(collect_byte_check_ops_for_padding(num_padding_rows)); - - // ===================================================================== - // PHASE 5: Generate final traces (parallelized) - // ===================================================================== - - // Extract halt timestamp from the last ECALL instruction - let halt_op = cpu_ops - .iter() - .rev() - .find(|op| op.decode.op_ecall) - .ok_or(Error::MissingHaltEcall)?; - let halt_timestamp = halt_op.timestamp; - - let cpus = chunk_and_generate(&cpu_ops, max_rows.cpu, cpu::generate_cpu_trace); - let memws = chunk_and_generate(&memw_ops, max_rows.memw, memw::generate_memw_trace); - let memw_aligneds = chunk_and_generate( - &memw_aligned_ops, - max_rows.memw_aligned, - memw_aligned::generate_memw_aligned_trace, + let ops = collect_all_ops( + cpu_ops, + memw_ops, + load_ops, + lt_ops, + shift_ops, + bitwise_ops, + commit_ops, + &mut register_state, ); - let memw_registers = chunk_and_generate( - &memw_register_ops, - max_rows.memw_register, - memw_register::generate_memw_register_trace, - ); - let loads = chunk_and_generate(&load_ops, max_rows.load, load::generate_load_trace); - let lts = chunk_and_generate(<_ops, max_rows.lt, lt::generate_lt_trace); - let shifts = chunk_and_generate(&shift_ops, max_rows.shift, shift::generate_shift_trace); - let muls = chunk_and_generate(&mul_ops, max_rows.mul, mul::generate_mul_trace); - let dvrms = chunk_and_generate(&dvrm_ops, max_rows.dvrm, dvrm::generate_dvrm_trace); - let branches = - chunk_and_generate(&branch_ops, max_rows.branch, branch::generate_branch_trace); - - let mut bitwise = bitwise::generate_bitwise_trace(); - bitwise::update_multiplicities(&mut bitwise, &bitwise_ops); - - // Generate DECODE trace and update multiplicities - // Each CPU operation looks up the DECODE table once - // Padding rows also look up pc=1 (the CPU padding entry) - // When CPU is split, each chunk pads independently - let (mut decode, pc_to_row) = decode::generate_decode_trace(&instructions); - let num_padding_rows: usize = cpu_ops - .chunks(max_rows.cpu) - .map(|chunk| chunk.len().next_power_of_two().max(4) - chunk.len()) - .sum(); - let mut decode_lookups: Vec = cpu_ops.iter().map(|op| op.decode.pc).collect(); - decode_lookups.extend(std::iter::repeat_n(cpu::CPU_PADDING_PC, num_padding_rows)); - decode::update_multiplicities(&mut decode, &pc_to_row, &decode_lookups); - let register_final_state = register_state.to_final_state_map(); - - let commit_trace = commit::generate_commit_trace(&commit_ops); - - // Generate remaining traces in parallel (register, halt). - // chunk_and_generate already handled cpu, lt, memw, load, mul, dvrm, branch above. - let (register_trace, halt_trace); - #[cfg(feature = "parallel")] - { - let (register_val, halt_val) = rayon::join( - || register::generate_register_trace(®ister_final_state, entry_point), - || halt::generate_halt_trace(halt_timestamp), - ); - register_trace = register_val; - halt_trace = halt_val; - } - #[cfg(not(feature = "parallel"))] - { - register_trace = register::generate_register_trace(®ister_final_state, entry_point); - halt_trace = halt::generate_halt_trace(halt_timestamp); - } - // Create empty PAGE tables for legacy API - // (caller should use from_elf_and_logs for proper PAGE table support) - let pages = Vec::new(); - let page_configs = Vec::new(); - - Ok(Traces { - cpus, - bitwise, - lts, - shifts, - memws, - memw_aligneds, - loads, - decode, - muls, - dvrms, - pages, - page_configs, - register: register_trace, - public_output_bytes, - branches, - halt: halt_trace, - commit: commit_trace, - memw_registers, - }) + // DECODE (from_elf_and_logs does this in Phase 0; same result either way) + let (decode_trace, decode_pc_to_row) = decode::generate_decode_trace(&instructions); + + // Phases 3-5 (elf=None → empty PAGE tables) + build_traces( + ops, + None, + &memory_state, + entry_point, + decode_trace, + decode_pc_to_row, + register_state, + max_rows, + ) } /// Generates all traces with a trimmed bitwise table (TEST ONLY).