diff --git a/CHANGELOG.md b/CHANGELOG.md index 826433283c..284b570277 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ - Added chainable `Test` builders for common test setup in `miden-utils-testing` ([#2957](https://github.com/0xMiden/miden-vm/pull/2957)). - Speed-up AUX range check trace generation by changing divisors to a flat Vec layout ([#2966](https://github.com/0xMiden/miden-vm/pull/2966)). - Removed AIR constraint tagging instrumentation, applied a uniform constraint description style across components, and optimized constraint evaluation ([#2856](https://github.com/0xMiden/miden-vm/pull/2856)). +- [BREAKING] Unified all auxiliary-trace buses under a single declarative LogUp `LookupAir` shared by the verifier, prover aux-trace generator, and recursive ACE circuit; reduced committed boundary values to one per trace ([#2962](https://github.com/0xMiden/miden-vm/pull/2962)). +- Collapsed the kernel ROM chiplet to one row per digest with a LogUp multiplicity, eliminating duplicate-callsite rows ([#2962](https://github.com/0xMiden/miden-vm/pull/2962)). ## 0.22.1 diff --git a/air/src/ace.rs b/air/src/ace.rs index 16f7d85083..6c69d6f8fa 100644 --- a/air/src/ace.rs +++ b/air/src/ace.rs @@ -1,18 +1,25 @@ //! ACE circuit integration for ProcessorAir. //! -//! This module contains: -//! - Batching types and functions (`MessageElement`, `ReducedAuxBatchConfig`, -//! `batch_reduced_aux_values`) that extend a constraint check DAG with the auxiliary trace -//! boundary checks. -//! - The AIR-specific `reduced_aux_batch_config()` that describes the Miden VM's auxiliary trace -//! boundary checks. -//! - The convenience function `build_batched_ace_circuit()` that builds the full batched circuit in -//! one call. +//! This module extends the constraint-evaluation DAG produced by +//! `build_ace_dag_for_air` with the LogUp auxiliary-trace boundary check: //! -//! The formula checked by the batched circuit is: -//! `constraint_check + gamma * product_check + gamma^2 * sum_check = 0` +//! ```text +//! 0 = Σ aux_bound[0..NUM_LOGUP_COMMITTED_FINALS] +//! + c_block_hash +//! + c_log_precompile +//! + c_kernel_rom +//! ``` +//! +//! Two of the three corrections depend only on fixed-length public inputs +//! (`c_bh`, `c_lp`), so they are rebuilt directly inside the DAG as rational +//! fractions `(n, d)` and folded into a running rational `(N, D)` without any +//! in-circuit inversion. The kernel-ROM correction depends on the variable- +//! length kernel digest list which the circuit can't walk, so MASM computes +//! it (one final `ext2inv`) and hands it in as a single scalar via the +//! existing `VlpiReduction(0)` input. The final boundary check is the +//! quadratic identity `(Σ aux_bound + c_kr) · D + N = 0`. -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use miden_ace_codegen::{ AceCircuit, AceConfig, AceDag, AceError, DagBuilder, InputKey, NodeId, build_ace_dag_for_air, @@ -23,14 +30,14 @@ use miden_crypto::{ stark::air::{LiftedAir, symbolic::SymbolicExpressionExt}, }; -use crate::trace; +use crate::{PV_PROGRAM_HASH, PV_TRANSCRIPT_STATE}; // BATCHING TYPES // ================================================================================================ /// An element in a bus message encoding. /// -/// Bus messages are encoded as `alpha + sum(beta^i * elements[i])` using the +/// Bus messages are encoded as `bus_prefix + sum(beta^i * elements[i])` using the /// aux randomness challenges. Each element is either a constant base-field value /// or a reference to a fixed-length public input. #[derive(Debug, Clone)] @@ -41,54 +48,64 @@ pub enum MessageElement { PublicInput(usize), } -/// A multiplicative factor in the product check. +/// Sign applied to a `BusFraction` numerator. +#[derive(Debug, Clone, Copy)] +pub enum Sign { + Plus, + Minus, +} + +/// A rational `±1 / bus_message` contribution to the LogUp boundary sum. /// -/// The product check verifies: -/// `product(numerator) - product(denominator) = 0` +/// The bus message denominator is rebuilt inside the ACE DAG from public inputs +/// and aux randomness, so no external input is needed for this term. #[derive(Debug, Clone)] -pub enum ProductFactor { - /// Claimed final value of an auxiliary trace column, by column index. - BusBoundary(usize), - /// A bus message computed from its elements as `bus_prefix[bus] + sum(beta^i * elements[i])`. - /// The first field is the bus type index (see `trace::bus_types`). - Message(usize, Vec), - /// Multiset product reduced from variable-length public inputs, by group index. - Vlpi(usize), +pub struct BusFraction { + pub sign: Sign, + pub bus: usize, + pub message: Vec, } -/// Configuration for building the reduced_aux_values batching in the ACE DAG. +/// Configuration for the LogUp auxiliary-trace boundary batching. /// -/// Describes the auxiliary trace boundary checks (product_check and sum_check). -/// Constructed by AIR-specific code (see [`reduced_aux_batch_config`]) and -/// consumed by [`batch_reduced_aux_values`]. -/// -/// The product check verifies: -/// `product(numerator) - product(denominator) = 0` +/// Consumed by [`batch_logup_boundary`] to extend the constraint-check DAG with +/// the boundary identity checked by `ProcessorAir::reduced_aux_values`. #[derive(Debug, Clone)] -pub struct ReducedAuxBatchConfig { - /// Factors multiplied into the numerator of the product check. - pub numerator: Vec, - /// Factors multiplied into the denominator of the product check. - pub denominator: Vec, - /// Auxiliary trace column indices whose claimed final values are summed in the sum check. +pub struct LogUpBoundaryConfig { + /// Aux-bus-boundary column indices summed as `Σ aux_bound[col]`. pub sum_columns: Vec, + /// Aux-bus-boundary columns that must individually equal zero. Absorbed at + /// an independent γ power from the accumulator sum so a malicious prover + /// cannot cancel them against `sum_columns` or the rational corrections. + /// Mirrors the native runtime check in `ProcessorAir::reduced_aux_values`. + pub zero_columns: Vec, + /// Rational `(±1, d_i)` fractions folded into the running rational `(N, D)`. + pub fractions: Vec, + /// Scalar EF inputs added directly to the aux-boundary sum. Trusted from + /// MASM (the VM's own constraint system covers the procedure that produced + /// them). Typically one entry pointing at `VlpiReduction(0)` for `c_kr`. + pub scalar_corrections: Vec, } // BATCHING FUNCTIONS // ================================================================================================ -/// Extend an existing constraint DAG with auxiliary trace boundary checks. +/// Extend a constraint DAG with the LogUp auxiliary-trace boundary check. /// -/// Takes the constraint DAG and appends the running-product identity check -/// (product_check) and the LogUp sum check (sum_check), combining all three -/// checks into a single root with gamma: +/// Builds: /// -/// `root = constraint_check + gamma * product_check + gamma^2 * sum_check` +/// `sum_aux = Σ AuxBusBoundary(col) + Σ scalar_corrections` +/// `(N, D) = fold((0, 1), fractions)` via `(N', D') = (N·d_i + D·n_i, D·d_i)` +/// `boundary = sum_aux · D + N` +/// `zero_sum = Σ AuxBusBoundary(col)` for `col` in `zero_columns` +/// `root = constraint_check + γ · boundary + γ² · zero_sum` /// -/// Returns the new DAG with the batched root. -pub fn batch_reduced_aux_values( +/// Returns the new DAG with the batched root. The zero-columns identity lives at +/// γ² so a nonzero padding slot cannot cancel against the γ¹-batched boundary +/// accumulator. +pub fn batch_logup_boundary( constraint_dag: AceDag, - config: &ReducedAuxBatchConfig, + config: &LogUpBoundaryConfig, ) -> AceDag where EF: ExtensionField, @@ -96,69 +113,66 @@ where let constraint_root = constraint_dag.root; let mut builder = DagBuilder::from_dag(constraint_dag); - // Build product_check. - let product_check = build_product_check(&mut builder, config); - - // Build sum_check. - let sum_check = build_sum_check(&mut builder, config); + // sum_aux = Σ aux_bound[col] + Σ scalar_corrections + let mut sum_aux = builder.constant(EF::ZERO); + for &col in &config.sum_columns { + let node = builder.input(InputKey::AuxBusBoundary(col)); + sum_aux = builder.add(sum_aux, node); + } + for &scalar in &config.scalar_corrections { + let node = builder.input(scalar); + sum_aux = builder.add(sum_aux, node); + } - // Batch: root = constraint_check + gamma * product_check + gamma^2 * sum_check - let gamma = builder.input(InputKey::Gamma); - let gamma2 = builder.mul(gamma, gamma); - let term2 = builder.mul(gamma, product_check); - let term3 = builder.mul(gamma2, sum_check); - let partial = builder.add(constraint_root, term2); - let root = builder.add(partial, term3); + // Fold all rational fractions into a single (N, D) running rational. + let mut num = builder.constant(EF::ZERO); + let mut den = builder.constant(EF::ONE); + for fraction in &config.fractions { + let d_i = encode_bus_message(&mut builder, fraction.bus, &fraction.message); + let sign_value = match fraction.sign { + Sign::Plus => EF::ONE, + Sign::Minus => -EF::ONE, + }; + let n_i = builder.constant(sign_value); - builder.build(root) -} + // (num', den') = (num * d_i + den * n_i, den * d_i) + let num_d = builder.mul(num, d_i); + let den_n = builder.mul(den, n_i); + num = builder.add(num_d, den_n); + den = builder.mul(den, d_i); + } -/// Build the running-product identity check. -fn build_product_check(builder: &mut DagBuilder, config: &ReducedAuxBatchConfig) -> NodeId -where - EF: ExtensionField, -{ - let numerator = build_product(builder, &config.numerator); - let denominator = build_product(builder, &config.denominator); - builder.sub(numerator, denominator) -} + // boundary = sum_aux · D + N + let sum_times_den = builder.mul(sum_aux, den); + let boundary = builder.add(sum_times_den, num); -/// Build a product of factors as a single DAG node. -fn build_product(builder: &mut DagBuilder, factors: &[ProductFactor]) -> NodeId -where - EF: ExtensionField, -{ - let mut acc = builder.constant(EF::ONE); - for factor in factors { - let node = match factor { - ProductFactor::BusBoundary(idx) => builder.input(InputKey::AuxBusBoundary(*idx)), - ProductFactor::Message(bus, elements) => encode_bus_message(builder, *bus, elements), - ProductFactor::Vlpi(idx) => builder.input(InputKey::VlpiReduction(*idx)), - }; - acc = builder.mul(acc, node); + // zero_sum = Σ aux_bound[col] for col in zero_columns. Each column's identity + // `aux_bound[col] = 0` is batched at γ² so it cannot cancel against boundary + // terms at γ¹ or against other zero columns in the same sum (the whole sum is + // the coefficient of a single γ power, which forces the sum to zero — and since + // the boundary check is linear in each slot, each slot is independently zero with + // high probability over γ). + let mut zero_sum = builder.constant(EF::ZERO); + for &col in &config.zero_columns { + let node = builder.input(InputKey::AuxBusBoundary(col)); + zero_sum = builder.add(zero_sum, node); } - acc -} -/// Build the LogUp sum check (sum_check). -/// -/// Verifies that the LogUp auxiliary columns sum to zero at the boundary. -fn build_sum_check(builder: &mut DagBuilder, config: &ReducedAuxBatchConfig) -> NodeId -where - EF: ExtensionField, -{ - let mut sum = builder.constant(EF::ZERO); - for &col_idx in &config.sum_columns { - let col = builder.input(InputKey::AuxBusBoundary(col_idx)); - sum = builder.add(sum, col); - } - sum + // Batch with the constraint root using gamma. + let gamma = builder.input(InputKey::Gamma); + let gamma_boundary = builder.mul(gamma, boundary); + let constraint_plus_boundary = builder.add(constraint_root, gamma_boundary); + let gamma_sq = builder.mul(gamma, gamma); + let gamma_sq_zero = builder.mul(gamma_sq, zero_sum); + let root = builder.add(constraint_plus_boundary, gamma_sq_zero); + + builder.build(root) } /// Encode a bus message as `bus_prefix[bus] + sum(beta^i * elements[i])`. /// -/// The bus prefix provides domain separation: `bus_prefix[bus] = alpha + (bus+1) * gamma` -/// where `gamma = beta^MAX_MESSAGE_WIDTH`. This matches [`trace::Challenges::encode`]. +/// The bus prefix provides domain separation: `bus_prefix[bus] = alpha + (bus+1) * gamma_bus` +/// where `gamma_bus = beta^MIDEN_MAX_MESSAGE_WIDTH`. This matches [`lookup::Challenges::encode`]. fn encode_bus_message( builder: &mut DagBuilder, bus: usize, @@ -167,23 +181,25 @@ fn encode_bus_message( where EF: ExtensionField, { + use crate::constraints::lookup::messages::MIDEN_MAX_MESSAGE_WIDTH; + let alpha = builder.input(InputKey::AuxRandAlpha); let beta = builder.input(InputKey::AuxRandBeta); - // Compute gamma = beta^MAX_MESSAGE_WIDTH. - let mut gamma = builder.constant(EF::ONE); - for _ in 0..trace::MAX_MESSAGE_WIDTH { - gamma = builder.mul(gamma, beta); + // gamma_bus = beta^MIDEN_MAX_MESSAGE_WIDTH. + let mut gamma_bus = builder.constant(EF::ONE); + for _ in 0..MIDEN_MAX_MESSAGE_WIDTH { + gamma_bus = builder.mul(gamma_bus, beta); } - // bus_prefix = alpha + (bus + 1) * gamma + // bus_prefix = alpha + (bus + 1) * gamma_bus let scale = builder.constant(EF::from(Felt::from_u32((bus as u32) + 1))); - let offset = builder.mul(gamma, scale); + let offset = builder.mul(gamma_bus, scale); let bus_prefix = builder.add(alpha, offset); // acc = bus_prefix + sum(beta^i * elem_i) // - // Beta powers are built incrementally. The DagBuilder is hash-consed, so + // Beta powers are built incrementally; the DagBuilder is hash-consed, so // identical beta^i nodes across multiple message encodings are shared // automatically. let mut acc = bus_prefix; @@ -203,79 +219,85 @@ where // AIR-SPECIFIC CONFIG // ================================================================================================ -/// Build the [`ReducedAuxBatchConfig`] for the Miden VM ProcessorAir. +/// Build the [`LogUpBoundaryConfig`] for the Miden VM ProcessorAir. +/// +/// This mirrors `ProcessorAir::reduced_aux_values` in `air/src/lib.rs`: it sums +/// the sole accumulator column's committed final value, adds the scalar kernel-ROM +/// correction supplied by MASM via `VlpiReduction(0)`, and folds the two open- +/// bus corrections `c_block_hash` and `c_log_precompile` as rational fractions +/// whose denominators are rebuilt from public inputs inside the DAG. /// -/// This encodes the `reduced_aux_values` formula in the Miden VM AIR. -pub fn reduced_aux_batch_config() -> ReducedAuxBatchConfig { +/// The three fractions are: +/// 1. `c_bh = +1 / encode(BLOCK_HASH_TABLE, [ph[0..4], 0, 0, 0])` +/// 2. `c_lp_init = +1 / encode(LOG_PRECOMPILE, [0, 0, 0, 0])` +/// 3. `c_lp_final = −1 / encode(LOG_PRECOMPILE, ts[0..4])` +/// +/// `c_lp_init − c_lp_final` matches `transcript_messages` in `lib.rs` (initial +/// minus final contribution). Splitting it into two rationals keeps the code +/// uniform at the cost of two extra mul gates. +pub fn logup_boundary_config() -> LogUpBoundaryConfig { use MessageElement::{Constant, PublicInput}; - use ProductFactor::{BusBoundary, Message, Vlpi}; - use trace::bus_types; - - // Aux boundary column indices. - let p1 = trace::DECODER_AUX_TRACE_OFFSET; - let p2 = trace::DECODER_AUX_TRACE_OFFSET + 1; - let p3 = trace::DECODER_AUX_TRACE_OFFSET + 2; - let s_aux = trace::STACK_AUX_TRACE_OFFSET; - let b_range = trace::RANGE_CHECK_AUX_TRACE_OFFSET; - let b_hash_kernel = trace::HASH_KERNEL_VTABLE_AUX_TRACE_OFFSET; - let b_chiplets = trace::CHIPLETS_BUS_AUX_TRACE_OFFSET; - let v_wiring = trace::ACE_CHIPLET_WIRING_BUS_OFFSET; - // Public input layout offsets. - // [0..4] program hash, [4..20] stack inputs, [20..36] stack outputs, [36..40] transcript state - let pv_program_hash = super::PV_PROGRAM_HASH; - let pv_transcript_state = super::PV_TRANSCRIPT_STATE; + use crate::constraints::lookup::messages::BusId; - // Bus message constants. - let log_precompile_label = Felt::from_u8(trace::LOG_PRECOMPILE_LABEL); - - // ph_msg = encode([0, ph[0], ph[1], ph[2], ph[3], 0, 0]) - // Matches program_hash_message() in lib.rs. + // ph_msg = encode([ph[0], ph[1], ph[2], ph[3], 0, 0, 0]) + // Matches `program_hash_message` in lib.rs. let ph_msg = vec![ - Constant(Felt::ZERO), // parent_id = 0 - PublicInput(pv_program_hash), // hash[0] - PublicInput(pv_program_hash + 1), // hash[1] - PublicInput(pv_program_hash + 2), // hash[2] - PublicInput(pv_program_hash + 3), // hash[3] - Constant(Felt::ZERO), // is_first_child = false - Constant(Felt::ZERO), // is_loop_body = false + PublicInput(PV_PROGRAM_HASH), + PublicInput(PV_PROGRAM_HASH + 1), + PublicInput(PV_PROGRAM_HASH + 2), + PublicInput(PV_PROGRAM_HASH + 3), + Constant(Felt::ZERO), // parent_id = 0 (root block) + Constant(Felt::ZERO), // is_first_child = false + Constant(Felt::ZERO), // is_loop_body = false ]; - // default_msg = encode([LOG_PRECOMPILE_LABEL, 0, 0, 0, 0]) - // Matches transcript_message(challenges, PrecompileTranscriptState::default()). - let default_msg = vec![ - Constant(log_precompile_label), + // default_lp_msg = encode([0, 0, 0, 0]) + // Matches `transcript_messages(..).0` (initial default state). + let default_lp_msg = vec![ Constant(Felt::ZERO), Constant(Felt::ZERO), Constant(Felt::ZERO), Constant(Felt::ZERO), ]; - // final_msg = encode([LOG_PRECOMPILE_LABEL, ts[0], ts[1], ts[2], ts[3]]) - // Matches transcript_message(challenges, pc_transcript_state). - let final_msg = vec![ - Constant(log_precompile_label), - PublicInput(pv_transcript_state), - PublicInput(pv_transcript_state + 1), - PublicInput(pv_transcript_state + 2), - PublicInput(pv_transcript_state + 3), + // final_lp_msg = encode(ts[0..4]) + // Matches `transcript_messages(..).1` (final public-input state). + let final_lp_msg = vec![ + PublicInput(PV_TRANSCRIPT_STATE), + PublicInput(PV_TRANSCRIPT_STATE + 1), + PublicInput(PV_TRANSCRIPT_STATE + 2), + PublicInput(PV_TRANSCRIPT_STATE + 3), ]; - // product_check: product(numerator) - product(denominator) = 0 - // sum_check: sum(sum_columns) = 0 - ReducedAuxBatchConfig { - numerator: vec![ - BusBoundary(p1), - BusBoundary(p2), - BusBoundary(p3), - BusBoundary(s_aux), - BusBoundary(b_hash_kernel), - BusBoundary(b_chiplets), - Message(bus_types::BLOCK_HASH_TABLE, ph_msg), - Message(bus_types::LOG_PRECOMPILE_TRANSCRIPT, default_msg), + // TODO(#3032): only col 0 carries a real final accumulator; slot 1 is the + // placeholder — see `NUM_LOGUP_COMMITTED_FINALS`. Once trace splitting lands, + // move slot 1 from `zero_columns` to `sum_columns`. + // + // Slot 1 is in `zero_columns` so the recursive verifier independently rejects + // any nonzero padding value. The native verifier applies the matching runtime + // check in `ProcessorAir::reduced_aux_values` (see `lib.rs`). + LogUpBoundaryConfig { + sum_columns: vec![0], + zero_columns: vec![1], + fractions: vec![ + BusFraction { + sign: Sign::Plus, + bus: BusId::BlockHashTable as usize, + message: ph_msg, + }, + BusFraction { + sign: Sign::Plus, + bus: BusId::LogPrecompileTranscript as usize, + message: default_lp_msg, + }, + BusFraction { + sign: Sign::Minus, + bus: BusId::LogPrecompileTranscript as usize, + message: final_lp_msg, + }, ], - denominator: vec![Message(bus_types::LOG_PRECOMPILE_TRANSCRIPT, final_msg), Vlpi(0)], - sum_columns: vec![b_range, v_wiring], + scalar_corrections: vec![InputKey::VlpiReduction(0)], } } @@ -284,16 +306,17 @@ pub fn reduced_aux_batch_config() -> ReducedAuxBatchConfig { /// Build a batched ACE circuit for the provided AIR. /// -/// This is the highest-level entry point for building the ACE circuit for Miden VM AIR. -/// It builds the constraint-evaluation DAG, extends it with the auxiliary trace -/// boundary checks and emits the off-VM circuit representation. +/// Builds the constraint-evaluation DAG, extends it with the LogUp auxiliary +/// trace boundary check via [`batch_logup_boundary`], and emits the off-VM +/// circuit representation. /// -/// The output circuit checks: -/// `constraint_check + gamma * product_check + gamma^2 * sum_check = 0` +/// The output circuit checks `constraint_check + γ · boundary = 0`, where +/// `boundary = (Σ aux_bound + c_kr) · D + N` and `(N, D)` is the rational sum +/// of the in-DAG open-bus corrections. pub fn build_batched_ace_circuit( air: &A, config: AceConfig, - batch_config: &ReducedAuxBatchConfig, + boundary_config: &LogUpBoundaryConfig, ) -> Result, AceError> where A: LiftedAir, @@ -301,6 +324,6 @@ where SymbolicExpressionExt: Algebra, { let artifacts = build_ace_dag_for_air::(air, config)?; - let batched_dag = batch_reduced_aux_values(artifacts.dag, batch_config); + let batched_dag = batch_logup_boundary(artifacts.dag, boundary_config); miden_ace_codegen::emit_circuit(&batched_dag, artifacts.layout) } diff --git a/air/src/config.rs b/air/src/config.rs index f8276a31d6..dc94fa7543 100644 --- a/air/src/config.rs +++ b/air/src/config.rs @@ -85,10 +85,10 @@ pub fn pcs_params() -> PcsParams { /// Compile-time constant binding the Fiat-Shamir transcript to the Miden VM AIR. /// Must match the constants in `crates/lib/core/asm/sys/vm/mod.masm`. pub const RELATION_DIGEST: [Felt; 4] = [ - Felt::new_unchecked(3886624411320157031), - Felt::new_unchecked(5903371486919752653), - Felt::new_unchecked(170319297396068280), - Felt::new_unchecked(5221005507035467697), + Felt::new_unchecked(9778406675021743003), + Felt::new_unchecked(1249738292041492479), + Felt::new_unchecked(18129162047168657414), + Felt::new_unchecked(9359130512581117656), ]; /// Observes PCS protocol parameters into the challenger. @@ -314,9 +314,9 @@ mod tests { layout: LayoutKind::Masm, }; let air = ProcessorAir; - let batch_config = ace::reduced_aux_batch_config(); + let boundary_config = ace::logup_boundary_config(); let circuit = - ace::build_batched_ace_circuit::<_, QuadFelt>(&air, config, &batch_config).unwrap(); + ace::build_batched_ace_circuit::<_, QuadFelt>(&air, config, &boundary_config).unwrap(); let encoded = circuit.to_ace().unwrap(); let circuit_commitment: [Felt; 4] = encoded.circuit_hash().into(); diff --git a/air/src/constraints/bus.rs b/air/src/constraints/bus.rs deleted file mode 100644 index a6183d3e0c..0000000000 --- a/air/src/constraints/bus.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Bus shared definitions. -//! -//! This module provides shared indices for auxiliary (bus) constraints. -//! The bus columns live in the auxiliary trace and are ordered as follows: -//! - p1/p2/p3 (decoder) -//! - p1 (stack overflow, stack aux segment) -//! - b_range (range checker LogUp) -//! - b_hash_kernel (chiplets virtual table) -//! - b_chiplets (chiplets bus) -//! - v_wiring (ACE wiring LogUp) - -/// Auxiliary trace column indices. -pub mod indices { - /// Block stack table (decoder control flow) - pub const P1_BLOCK_STACK: usize = 0; - /// Block hash table (decoder digest tracking) - pub const P2_BLOCK_HASH: usize = 1; - /// Op group table (decoder operation batching) - pub const P3_OP_GROUP: usize = 2; - /// Stack overflow table (stack p1) - pub const P1_STACK: usize = 3; - /// Range checker bus - pub const B_RANGE: usize = 4; - /// Hash kernel bus: sibling table + ACE memory + log_precompile - pub const B_HASH_KERNEL: usize = 5; - /// Main chiplets bus - pub const B_CHIPLETS: usize = 6; - /// Wiring bus for ACE circuit connections - pub const V_WIRING: usize = 7; -} diff --git a/air/src/constraints/chiplets/bus/chiplets.rs b/air/src/constraints/chiplets/bus/chiplets.rs deleted file mode 100644 index 14d7c9df7b..0000000000 --- a/air/src/constraints/chiplets/bus/chiplets.rs +++ /dev/null @@ -1,1794 +0,0 @@ -//! Chiplets bus constraint (b_chiplets). -//! -//! This module enforces the running product constraint for the main chiplets bus -//! (bus_6_chiplets_bus). The chiplets bus handles communication between the VM components (stack, -//! decoder) and the specialized chiplets (hasher, bitwise, memory, ACE, kernel ROM). -//! -//! ## Running Product Protocol -//! -//! The bus accumulator b_chiplets uses a multiset running product: -//! - Boundary: b_chiplets[0] = 1, b_chiplets[last] = reduced_kernel_digests (via aux_finals) -//! - Transition: b_chiplets' * requests = b_chiplets * responses -//! -//! The bus starts at 1. Kernel ROM INIT_LABEL responses multiply in the kernel procedure hashes, -//! so aux_final[b_chiplets] = reduced_kernel_digests. The verifier checks this against the -//! expected value computed from kernel hashes provided as variable-length public inputs. -//! -//! ## Message Types -//! -//! ### Hasher Chiplet Messages (15 elements) -//! Format: header + state where: -//! - header = alpha + beta^0 * transition_label + beta^1 * addr + beta^2 * node_index -//! - state = sum(beta^(3+i) * hasher_state[i]) for i in 0..12 -//! -//! ### Bitwise Chiplet Messages (5 elements) -//! Format: bus_prefix[CHIPLETS_BUS] + beta^0*label + beta^1*a + beta^2*b + beta^3*z -//! -//! ### Memory Chiplet Messages (6-9 elements) -//! Element format: alpha + beta^0*label + ... + beta^4*element -//! Word format: alpha + beta^0*label + ... + beta^7*word[3] -//! -//! ## References -//! - Processor: processor/src/chiplets/aux_trace/bus/ - -use core::borrow::Borrow; - -use miden_core::{FMP_ADDR, FMP_INIT_VALUE, field::PrimeCharacteristicRing, operations::opcodes}; -use miden_crypto::stark::air::{ExtensionBuilder, WindowAccess}; - -use crate::{ - Felt, MainCols, MidenAirBuilder, - constraints::{ - bus::indices::B_CHIPLETS, - chiplets::{columns::PeriodicCols, selectors::ChipletSelectors}, - op_flags::OpFlags, - }, - trace::{ - Challenges, bus_types, - chiplets::{ - NUM_ACE_SELECTORS, NUM_KERNEL_ROM_SELECTORS, - ace::{ - ACE_INIT_LABEL, CLK_IDX, CTX_IDX, ID_0_IDX, PTR_IDX, READ_NUM_EVAL_IDX, - SELECTOR_START_IDX, - }, - bitwise::{self, BITWISE_AND_LABEL, BITWISE_XOR_LABEL}, - hasher::{ - CONTROLLER_ROWS_PER_PERMUTATION, LINEAR_HASH_LABEL, MP_VERIFY_LABEL, - MR_UPDATE_NEW_LABEL, MR_UPDATE_OLD_LABEL, RETURN_HASH_LABEL, RETURN_STATE_LABEL, - }, - kernel_rom::{KERNEL_PROC_CALL_LABEL, KERNEL_PROC_INIT_LABEL}, - memory::{ - MEMORY_READ_ELEMENT_LABEL, MEMORY_READ_WORD_LABEL, MEMORY_WRITE_ELEMENT_LABEL, - MEMORY_WRITE_WORD_LABEL, - }, - }, - log_precompile::{ - HELPER_ADDR_IDX, HELPER_CAP_PREV_RANGE, STACK_CAP_NEXT_RANGE, STACK_COMM_RANGE, - STACK_R0_RANGE, STACK_R1_RANGE, STACK_TAG_RANGE, - }, - }, -}; - -/// Label offset for input (start) messages on the chiplets bus. -const INPUT_LABEL_OFFSET: u16 = 16; -/// Label offset for output (end) messages on the chiplets bus. -const OUTPUT_LABEL_OFFSET: u16 = 32; - -// ENTRY POINTS -// ================================================================================================ - -/// Enforces the chiplets bus constraint. -/// -/// This is the main constraint for bus_6_chiplets_bus, which handles all communication -/// between VM components and specialized chiplets. -/// -/// The constraint follows the running product protocol: -/// - `b_chiplets' * requests = b_chiplets * responses` -/// -/// Where `requests` are messages inserted by VM operations (stack/decoder) and -/// `responses` are messages removed by chiplet operations. -pub fn enforce_chiplets_bus_constraint( - builder: &mut AB, - local: &MainCols, - next: &MainCols, - op_flags: &OpFlags, - challenges: &Challenges, - selectors: &ChipletSelectors, -) where - AB: MidenAirBuilder, -{ - // Auxiliary trace must be present. - - // Extract auxiliary trace values. - let (b_local_val, b_next_val) = { - let aux = builder.permutation(); - let aux_local = aux.current_slice(); - let aux_next = aux.next_slice(); - (aux_local[B_CHIPLETS], aux_next[B_CHIPLETS]) - }; - - // ========================================================================= - // COMPUTE REQUEST MULTIPLIER - // ========================================================================= - - // --- Hasher request flags --- - let f_hperm: AB::Expr = op_flags.hperm(); - let f_mpverify: AB::Expr = op_flags.mpverify(); - let f_mrupdate: AB::Expr = op_flags.mrupdate(); - - // --- Control block flags --- - let f_join: AB::Expr = op_flags.join(); - let f_split: AB::Expr = op_flags.split(); - let f_loop: AB::Expr = op_flags.loop_op(); - let f_call: AB::Expr = op_flags.call(); - let f_dyn: AB::Expr = op_flags.dyn_op(); - let f_dyncall: AB::Expr = op_flags.dyncall(); - let f_syscall: AB::Expr = op_flags.syscall(); - let f_span: AB::Expr = op_flags.span(); - let f_respan: AB::Expr = op_flags.respan(); - let f_end: AB::Expr = op_flags.end(); - - // --- Memory request flags --- - let f_mload: AB::Expr = op_flags.mload(); - let f_mstore: AB::Expr = op_flags.mstore(); - let f_mloadw: AB::Expr = op_flags.mloadw(); - let f_mstorew: AB::Expr = op_flags.mstorew(); - let f_hornerbase: AB::Expr = op_flags.hornerbase(); - let f_hornerext: AB::Expr = op_flags.hornerext(); - let f_mstream: AB::Expr = op_flags.mstream(); - let f_pipe: AB::Expr = op_flags.pipe(); - let f_cryptostream: AB::Expr = op_flags.cryptostream(); - - // --- Bitwise request flags --- - let f_u32and: AB::Expr = op_flags.u32and(); - let f_u32xor: AB::Expr = op_flags.u32xor(); - - // --- ACE and log_precompile request flags --- - let f_evalcircuit: AB::Expr = op_flags.evalcircuit(); - let f_logprecompile: AB::Expr = op_flags.log_precompile(); - - // --- Hasher request values --- - let v_hperm = compute_hperm_request::(local, next, challenges); - let v_mpverify = compute_mpverify_request::(local, challenges); - let v_mrupdate = compute_mrupdate_request::(local, next, challenges); - - // --- Control block request values --- - let v_join = compute_control_block_request::(local, next, challenges, ControlBlockOp::Join); - let v_split = - compute_control_block_request::(local, next, challenges, ControlBlockOp::Split); - let v_loop = compute_control_block_request::(local, next, challenges, ControlBlockOp::Loop); - let v_call = compute_call_request::(local, next, challenges); - let v_dyn = compute_dyn_request::(local, next, challenges); - let v_dyncall = compute_dyncall_request::(local, next, challenges); - let v_syscall = compute_syscall_request::(local, next, challenges); - let v_span = compute_span_request::(local, next, challenges); - let v_respan = compute_respan_request::(local, next, challenges); - let v_end = compute_end_request::(local, challenges); - - // --- Memory request values --- - let v_mload = compute_memory_element_request::(local, next, challenges, true); // is_read = true - let v_mstore = compute_memory_element_request::(local, next, challenges, false); // is_read = false - let v_mloadw = compute_memory_word_request::(local, next, challenges, true); // is_read = true - let v_mstorew = compute_memory_word_request::(local, next, challenges, false); // is_read = false - let v_hornerbase = compute_hornerbase_request::(local, challenges); - let v_hornerext = compute_hornerext_request::(local, challenges); - let v_mstream = compute_mstream_request::(local, next, challenges); - let v_pipe = compute_pipe_request::(local, next, challenges); - let v_cryptostream = compute_cryptostream_request::(local, next, challenges); - - // --- Bitwise request values --- - let v_u32and = compute_bitwise_request::(local, next, challenges, false); // is_xor = false - let v_u32xor = compute_bitwise_request::(local, next, challenges, true); // is_xor = true - - // --- ACE and log_precompile request values --- - let v_evalcircuit = compute_ace_request::(local, challenges); - let v_logprecompile = compute_log_precompile_request::(local, next, challenges); - - // Sum of request flags (hasher + control blocks + memory + bitwise + ACE + log_precompile) - let request_flag_sum: AB::Expr = f_hperm.clone() - + f_mpverify.clone() - + f_mrupdate.clone() - + f_join.clone() - + f_split.clone() - + f_loop.clone() - + f_call.clone() - + f_dyn.clone() - + f_dyncall.clone() - + f_syscall.clone() - + f_span.clone() - + f_respan.clone() - + f_end.clone() - + f_mload.clone() - + f_mstore.clone() - + f_mloadw.clone() - + f_mstorew.clone() - + f_hornerbase.clone() - + f_hornerext.clone() - + f_mstream.clone() - + f_pipe.clone() - + f_cryptostream.clone() - + f_u32and.clone() - + f_u32xor.clone() - + f_evalcircuit.clone() - + f_logprecompile.clone(); - - let one_ef = AB::ExprEF::ONE; - - // Request multiplier = sum(flag * value) + (1 - sum(flags)) - let requests: AB::ExprEF = v_hperm * f_hperm - + v_mpverify * f_mpverify - + v_mrupdate * f_mrupdate - + v_join * f_join - + v_split * f_split - + v_loop * f_loop - + v_call * f_call - + v_dyn * f_dyn - + v_dyncall * f_dyncall - + v_syscall * f_syscall - + v_span * f_span - + v_respan * f_respan - + v_end * f_end - + v_mload * f_mload - + v_mstore * f_mstore - + v_mloadw * f_mloadw - + v_mstorew * f_mstorew - + v_hornerbase * f_hornerbase - + v_hornerext * f_hornerext - + v_mstream * f_mstream - + v_pipe * f_pipe - + v_cryptostream * f_cryptostream - + v_u32and * f_u32and - + v_u32xor * f_u32xor - + v_evalcircuit * f_evalcircuit - + v_logprecompile * f_logprecompile - + (one_ef - request_flag_sum); - - // ========================================================================= - // COMPUTE RESPONSE MULTIPLIER - // ========================================================================= - // Responses come from chiplet rows. Chiplet selectors are mutually exclusive. - - // --- Get periodic columns for bitwise cycle gating --- - let periodic: &PeriodicCols = builder.periodic_values().borrow(); - let k_transition: AB::Expr = periodic.bitwise.k_transition.into(); - - // --- Chiplet response flags (from precomputed ChipletSelectors) --- - // Bitwise responds only on the last row of its 8-row cycle (k_transition=0). - let is_bitwise_responding: AB::Expr = - selectors.bitwise.is_active.clone() * (AB::Expr::ONE - k_transition); - - let is_memory: AB::Expr = selectors.memory.is_active.clone(); - - // ACE responds only on start rows (ace_start_selector = 1). - let ace_start_selector: AB::Expr = - local.chiplets[NUM_ACE_SELECTORS + SELECTOR_START_IDX].into(); - let is_ace: AB::Expr = selectors.ace.is_active.clone() * ace_start_selector; - - let is_kernel_rom: AB::Expr = selectors.kernel_rom.is_active.clone(); - - // --- Hasher response (complex, depends on cycle position and selectors) --- - let hasher_response = compute_hasher_response::( - local, - next, - challenges, - selectors.controller.is_active.clone(), - ); - - // --- Bitwise response --- - let v_bitwise = compute_bitwise_response::(local, challenges); - - // --- Memory response --- - let v_memory = compute_memory_response::(local, challenges); - - // --- ACE response --- - let v_ace = compute_ace_response::(local, challenges); - - // --- Kernel ROM response --- - let v_kernel_rom = compute_kernel_rom_response::(local, challenges); - - // Convert flags to ExprEF - // Responses: hasher + bitwise + memory + ACE + kernel ROM contributions, others return 1 - let responses: AB::ExprEF = hasher_response.sum - + v_bitwise * is_bitwise_responding.clone() - + v_memory * is_memory.clone() - + v_ace * is_ace.clone() - + v_kernel_rom * is_kernel_rom.clone() - + (AB::ExprEF::ONE - - hasher_response.flag_sum - - is_bitwise_responding - - is_memory - - is_ace - - is_kernel_rom); - - // ========================================================================= - // RUNNING PRODUCT TRANSITION CONSTRAINT - // ========================================================================= - // b_chiplets' * requests = b_chiplets * responses - - let lhs: AB::ExprEF = Into::::into(b_next_val) * requests; - let rhs: AB::ExprEF = Into::::into(b_local_val) * responses; - builder.when_transition().assert_eq_ext(lhs, rhs); -} - -// BITWISE MESSAGE HELPERS -// ================================================================================================ - -/// Computes the bitwise request message value. -/// -/// Format: bus_prefix[CHIPLETS_BUS] + beta^0*label + beta^1*a + beta^2*b + beta^3*z -/// -/// Stack layout for U32AND/U32XOR: [a, b, ...] -> [z, ...] -fn compute_bitwise_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, - is_xor: bool, -) -> AB::ExprEF { - let label: Felt = if is_xor { BITWISE_XOR_LABEL } else { BITWISE_AND_LABEL }; - let label: AB::Expr = AB::Expr::from(label); - - // Stack values - let a: AB::Expr = local.stack.get(0).into(); - let b: AB::Expr = local.stack.get(1).into(); - let z: AB::Expr = next.stack.get(0).into(); - - challenges.encode(bus_types::CHIPLETS_BUS, [label, a, b, z]) -} - -/// Computes the bitwise chiplet response message value. -/// -/// Format: bus_prefix[CHIPLETS_BUS] + beta^0*label + beta^1*a + beta^2*b + beta^3*z -fn compute_bitwise_response( - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - use crate::trace::chiplets::NUM_BITWISE_SELECTORS; - - // Bitwise chiplet columns start at NUM_BITWISE_SELECTORS=2 in local.chiplets - let bw_offset = NUM_BITWISE_SELECTORS; - - // Get bitwise operation selector and compute label - // The AND/XOR selector is at bitwise[0] = local.chiplets[bw_offset] - // label = (1 - sel) * AND_LABEL + sel * XOR_LABEL - let sel: AB::Expr = local.chiplets[bw_offset].into(); - let one_minus_sel = AB::Expr::ONE - sel.clone(); - let label = - one_minus_sel * AB::Expr::from(BITWISE_AND_LABEL) + sel * AB::Expr::from(BITWISE_XOR_LABEL); - - // Bitwise chiplet data columns (offset by bw_offset + bitwise internal indices) - let a: AB::Expr = local.chiplets[bw_offset + bitwise::A_COL_IDX].into(); - let b: AB::Expr = local.chiplets[bw_offset + bitwise::B_COL_IDX].into(); - let z: AB::Expr = local.chiplets[bw_offset + bitwise::OUTPUT_COL_IDX].into(); - - challenges.encode(bus_types::CHIPLETS_BUS, [label, a, b, z]) -} - -// MEMORY MESSAGE HELPERS -// ================================================================================================ - -/// Computes the memory word request message value. -/// -/// Format: bus_prefix[CHIPLETS_BUS] + beta^0*label + beta^1*ctx + beta^2*addr + beta^3*clk + -/// beta^4..beta^7 * word -/// -/// Stack layout for MLOADW: [addr, ...] -> [word[0], word[1], word[2], word[3], ...] -/// Stack layout for MSTOREW: [addr, word[0], word[1], word[2], word[3], ...] -fn compute_memory_word_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, - is_read: bool, -) -> AB::ExprEF { - let label = if is_read { - MEMORY_READ_WORD_LABEL - } else { - MEMORY_WRITE_WORD_LABEL - }; - let label: AB::Expr = AB::Expr::from_u16(label as u16); - - // Context and clock from system columns - let ctx: AB::Expr = local.system.ctx.into(); - let clk: AB::Expr = local.system.clk.into(); - - // Address is at stack[0] - let addr: AB::Expr = local.stack.get(0).into(); - - // Word values depend on read vs write - let (w0, w1, w2, w3) = if is_read { - // MLOADW: word comes from next stack state - ( - next.stack.get(0).into(), - next.stack.get(1).into(), - next.stack.get(2).into(), - next.stack.get(3).into(), - ) - } else { - // MSTOREW: word comes from current stack[1..5] - ( - local.stack.get(1).into(), - local.stack.get(2).into(), - local.stack.get(3).into(), - local.stack.get(4).into(), - ) - }; - - challenges.encode(bus_types::CHIPLETS_BUS, [label, ctx, addr, clk, w0, w1, w2, w3]) -} - -/// Computes the memory element request message value. -/// -/// Format: bus_prefix[CHIPLETS_BUS] + beta^0*label + beta^1*ctx + beta^2*addr + beta^3*clk + -/// beta^4*element -fn compute_memory_element_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, - is_read: bool, -) -> AB::ExprEF { - let label = if is_read { - MEMORY_READ_ELEMENT_LABEL - } else { - MEMORY_WRITE_ELEMENT_LABEL - }; - let label: AB::Expr = AB::Expr::from_u16(label as u16); - - // Context and clock from system columns - let ctx: AB::Expr = local.system.ctx.into(); - let clk: AB::Expr = local.system.clk.into(); - - // Address is at stack[0] - let addr: AB::Expr = local.stack.get(0).into(); - - // Element value - let element = if is_read { - // MLOAD: element comes from next stack[0] - next.stack.get(0).into() - } else { - // MSTORE: element comes from current stack[1] - local.stack.get(1).into() - }; - - challenges.encode(bus_types::CHIPLETS_BUS, [label, ctx, addr, clk, element]) -} - -/// Computes the MSTREAM request message value (two word reads). -fn compute_mstream_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - let label: AB::Expr = AB::Expr::from_u16(MEMORY_READ_WORD_LABEL as u16); - let ctx: AB::Expr = local.system.ctx.into(); - let clk: AB::Expr = local.system.clk.into(); - let addr: AB::Expr = local.stack.get(12).into(); - let four: AB::Expr = AB::Expr::from_u16(4); - - // First word: next.stack[0..4] at addr - let word1 = [ - next.stack.get(0).into(), - next.stack.get(1).into(), - next.stack.get(2).into(), - next.stack.get(3).into(), - ]; - - // Second word: next.stack[4..8] at addr + 4 - let word2 = [ - next.stack.get(4).into(), - next.stack.get(5).into(), - next.stack.get(6).into(), - next.stack.get(7).into(), - ]; - - let msg1 = challenges.encode( - bus_types::CHIPLETS_BUS, - [ - label.clone(), - ctx.clone(), - addr.clone(), - clk.clone(), - word1[0].clone(), - word1[1].clone(), - word1[2].clone(), - word1[3].clone(), - ], - ); - - let msg2 = challenges.encode( - bus_types::CHIPLETS_BUS, - [ - label, - ctx, - addr + four, - clk, - word2[0].clone(), - word2[1].clone(), - word2[2].clone(), - word2[3].clone(), - ], - ); - - msg1 * msg2 -} - -/// Computes the PIPE request message value (two word writes). -fn compute_pipe_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - let label: AB::Expr = AB::Expr::from_u16(MEMORY_WRITE_WORD_LABEL as u16); - let ctx: AB::Expr = local.system.ctx.into(); - let clk: AB::Expr = local.system.clk.into(); - let addr: AB::Expr = local.stack.get(12).into(); - let four: AB::Expr = AB::Expr::from_u16(4); - - // First word to addr: next.stack[0..4] - let word1 = [ - next.stack.get(0).into(), - next.stack.get(1).into(), - next.stack.get(2).into(), - next.stack.get(3).into(), - ]; - - // Second word to addr + 4: next.stack[4..8] - let word2 = [ - next.stack.get(4).into(), - next.stack.get(5).into(), - next.stack.get(6).into(), - next.stack.get(7).into(), - ]; - - let msg1 = challenges.encode( - bus_types::CHIPLETS_BUS, - [ - label.clone(), - ctx.clone(), - addr.clone(), - clk.clone(), - word1[0].clone(), - word1[1].clone(), - word1[2].clone(), - word1[3].clone(), - ], - ); - - let msg2 = challenges.encode( - bus_types::CHIPLETS_BUS, - [ - label, - ctx, - addr + four, - clk, - word2[0].clone(), - word2[1].clone(), - word2[2].clone(), - word2[3].clone(), - ], - ); - - msg1 * msg2 -} - -/// Computes the CRYPTOSTREAM request value (two word reads + two word writes). -fn compute_cryptostream_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - let read_label: AB::Expr = AB::Expr::from_u16(MEMORY_READ_WORD_LABEL as u16); - let write_label: AB::Expr = AB::Expr::from_u16(MEMORY_WRITE_WORD_LABEL as u16); - let ctx: AB::Expr = local.system.ctx.into(); - let clk: AB::Expr = local.system.clk.into(); - let src: AB::Expr = local.stack.get(12).into(); - let dst: AB::Expr = local.stack.get(13).into(); - let four: AB::Expr = AB::Expr::from_u16(4); - - let rate: [AB::Expr; 8] = core::array::from_fn(|i| local.stack.get(i).into()); - let cipher: [AB::Expr; 8] = core::array::from_fn(|i| next.stack.get(i).into()); - let plain: [AB::Expr; 8] = core::array::from_fn(|i| cipher[i].clone() - rate[i].clone()); - - let read_msg1 = challenges.encode( - bus_types::CHIPLETS_BUS, - [ - read_label.clone(), - ctx.clone(), - src.clone(), - clk.clone(), - plain[0].clone(), - plain[1].clone(), - plain[2].clone(), - plain[3].clone(), - ], - ); - - let read_msg2 = challenges.encode( - bus_types::CHIPLETS_BUS, - [ - read_label, - ctx.clone(), - src + four.clone(), - clk.clone(), - plain[4].clone(), - plain[5].clone(), - plain[6].clone(), - plain[7].clone(), - ], - ); - - let write_msg1 = challenges.encode( - bus_types::CHIPLETS_BUS, - [ - write_label.clone(), - ctx.clone(), - dst.clone(), - clk.clone(), - cipher[0].clone(), - cipher[1].clone(), - cipher[2].clone(), - cipher[3].clone(), - ], - ); - - let write_msg2 = challenges.encode( - bus_types::CHIPLETS_BUS, - [ - write_label, - ctx, - dst + four, - clk, - cipher[4].clone(), - cipher[5].clone(), - cipher[6].clone(), - cipher[7].clone(), - ], - ); - - read_msg1 * read_msg2 * write_msg1 * write_msg2 -} - -/// Computes the HORNERBASE request value (two element reads). -fn compute_hornerbase_request( - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - let label: AB::Expr = AB::Expr::from_u16(MEMORY_READ_ELEMENT_LABEL as u16); - let ctx: AB::Expr = local.system.ctx.into(); - let clk: AB::Expr = local.system.clk.into(); - let addr: AB::Expr = local.stack.get(13).into(); - let one: AB::Expr = AB::Expr::ONE; - - // Helper registers hold eval_point_0 and eval_point_1 - let eval0: AB::Expr = local.decoder.hasher_state[2].into(); - let eval1: AB::Expr = local.decoder.hasher_state[3].into(); - - let msg0 = challenges.encode( - bus_types::CHIPLETS_BUS, - [label.clone(), ctx.clone(), addr.clone(), clk.clone(), eval0], - ); - - let msg1 = challenges.encode(bus_types::CHIPLETS_BUS, [label, ctx, addr + one, clk, eval1]); - - msg0 * msg1 -} - -/// Computes the HORNEREXT request value (one word read). -fn compute_hornerext_request( - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - let label: AB::Expr = AB::Expr::from_u16(MEMORY_READ_WORD_LABEL as u16); - let ctx: AB::Expr = local.system.ctx.into(); - let clk: AB::Expr = local.system.clk.into(); - let addr: AB::Expr = local.stack.get(13).into(); - - // Helpers 0..3 hold eval_point_0, eval_point_1, mem_junk_0, mem_junk_1 - let word = [ - local.decoder.hasher_state[2].into(), - local.decoder.hasher_state[3].into(), - local.decoder.hasher_state[4].into(), - local.decoder.hasher_state[5].into(), - ]; - - challenges.encode( - bus_types::CHIPLETS_BUS, - [ - label, - ctx, - addr, - clk, - word[0].clone(), - word[1].clone(), - word[2].clone(), - word[3].clone(), - ], - ) -} - -/// Computes the memory chiplet response message value. -/// -/// The memory chiplet uses different labels for read/write and element/word operations. -/// Address is computed as: word + 2*idx1 + idx0 -/// For element access, the correct element is selected based on idx0, idx1. -fn compute_memory_response( - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - use crate::trace::chiplets::{NUM_MEMORY_SELECTORS, memory}; - - // Memory chiplet columns (offset by NUM_MEMORY_SELECTORS=3 for s0, s1, s2 selectors) - // local.chiplets is relative to CHIPLETS_OFFSET, memory columns start at index 3 - let mem_offset = NUM_MEMORY_SELECTORS; - let is_read: AB::Expr = local.chiplets[mem_offset + memory::IS_READ_COL_IDX].into(); - let is_word: AB::Expr = local.chiplets[mem_offset + memory::IS_WORD_ACCESS_COL_IDX].into(); - let ctx: AB::Expr = local.chiplets[mem_offset + memory::CTX_COL_IDX].into(); - let word: AB::Expr = local.chiplets[mem_offset + memory::WORD_COL_IDX].into(); - let idx0: AB::Expr = local.chiplets[mem_offset + memory::IDX0_COL_IDX].into(); - let idx1: AB::Expr = local.chiplets[mem_offset + memory::IDX1_COL_IDX].into(); - let clk: AB::Expr = local.chiplets[mem_offset + memory::CLK_COL_IDX].into(); - - // Compute address: addr = word + 2*idx1 + idx0 - let addr: AB::Expr = word + idx1.clone() * AB::Expr::from_u16(2) + idx0.clone(); - - // Compute label from flags using the canonical constants. - let one = AB::Expr::ONE; - let write_element_label = AB::Expr::from_u16(MEMORY_WRITE_ELEMENT_LABEL as u16); - let write_word_label = AB::Expr::from_u16(MEMORY_WRITE_WORD_LABEL as u16); - let read_element_label = AB::Expr::from_u16(MEMORY_READ_ELEMENT_LABEL as u16); - let read_word_label = AB::Expr::from_u16(MEMORY_READ_WORD_LABEL as u16); - let write_label = - (one.clone() - is_word.clone()) * write_element_label + is_word.clone() * write_word_label; - let read_label = - (one.clone() - is_word.clone()) * read_element_label + is_word.clone() * read_word_label; - let label = (one.clone() - is_read.clone()) * write_label + is_read * read_label; - - // Get value columns (v0, v1, v2, v3) - let v0: AB::Expr = local.chiplets[mem_offset + memory::V_COL_RANGE.start].into(); - let v1: AB::Expr = local.chiplets[mem_offset + memory::V_COL_RANGE.start + 1].into(); - let v2: AB::Expr = local.chiplets[mem_offset + memory::V_COL_RANGE.start + 2].into(); - let v3: AB::Expr = local.chiplets[mem_offset + memory::V_COL_RANGE.start + 3].into(); - - // For element access, select the correct element based on idx0, idx1: - // - (0,0) -> v0, (1,0) -> v1, (0,1) -> v2, (1,1) -> v3 - // element = v0*(1-idx0)*(1-idx1) + v1*idx0*(1-idx1) + v2*(1-idx0)*idx1 + v3*idx0*idx1 - let element: AB::Expr = - v0.clone() * (one.clone() - idx0.clone()) * (one.clone() - idx1.clone()) - + v1.clone() * idx0.clone() * (one.clone() - idx1.clone()) - + v2.clone() * (one.clone() - idx0.clone()) * idx1.clone() - + v3.clone() * idx0 * idx1; - - // For word access, all v0..v3 are used - let is_element = one - is_word.clone(); - - // Element access: include the selected element in the last slot. - let element_msg = challenges.encode( - bus_types::CHIPLETS_BUS, - [label.clone(), ctx.clone(), addr.clone(), clk.clone(), element], - ); - - // Word access: include all 4 values. - let word_msg = - challenges.encode(bus_types::CHIPLETS_BUS, [label, ctx, addr, clk, v0, v1, v2, v3]); - - // Select based on is_word - element_msg * is_element + word_msg * is_word -} - -// HASHER RESPONSE HELPERS -// ================================================================================================ - -/// Hasher response contribution to the chiplets bus. -struct HasherResponse { - sum: EF, - flag_sum: E, -} - -/// Computes the hasher chiplet response. -/// -/// Only hasher controller rows (dispatch, `s_ctrl = 1`) produce bus responses. -/// Hasher permutation segment rows (compute, `s_perm = 1`) do not contribute. -/// -/// **Controller input rows** (`s0 = 1`): -/// - Sponge start (`is_boundary=1`, `s1=0`, `s2=0`): full 12-element state -/// - Sponge continuation (`is_boundary=0`, `s1=0`, `s2=0`): rate-only 8 elements (RESPAN) -/// - Tree start (`is_boundary=1`, `s1=1` or `s2=1`): leaf word -/// -/// **Controller output rows** (`s0 = 0`, `s1 = 0`): -/// - HOUT (`s2=0`): digest -/// - SOUT (`s2=1`) + `is_boundary=1`: full 12-element state -/// -/// No response on: hasher permutation segment rows, padding rows (`s0=0, s1=1`), -/// tree continuations (`is_boundary=0`), or intermediate SOUT (`is_boundary=0`). -fn compute_hasher_response( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, - controller_flag: AB::Expr, -) -> HasherResponse { - use crate::trace::{ - CHIPLETS_OFFSET, - chiplets::{HASHER_IS_BOUNDARY_COL_IDX, HASHER_NODE_INDEX_COL_IDX, HASHER_STATE_COL_RANGE}, - }; - - let one = AB::Expr::ONE; - - // Hasher internal selectors - let s0: AB::Expr = local.chiplets[1].into(); - let s1: AB::Expr = local.chiplets[2].into(); - let s2: AB::Expr = local.chiplets[3].into(); - - // Lifecycle columns - let is_boundary: AB::Expr = local.chiplets[HASHER_IS_BOUNDARY_COL_IDX - CHIPLETS_OFFSET].into(); - // State and node_index - let state: [AB::Expr; 12] = core::array::from_fn(|i| { - let col_idx = HASHER_STATE_COL_RANGE.start - CHIPLETS_OFFSET + i; - local.chiplets[col_idx].into() - }); - let node_index: AB::Expr = local.chiplets[HASHER_NODE_INDEX_COL_IDX - CHIPLETS_OFFSET].into(); - - // Address - let addr_next: AB::Expr = local.system.clk.into() + one.clone(); - - // --- Response flags (inner, without controller_flag to keep degree low) --- - // - // controller_flag (degree 1, = s_ctrl) is factored out and applied to the entire response - // sum, keeping the max inner flag*message degree at 6 and the final degree at 7. - - // Sponge start: input, s1=0, s2=0, is_boundary=1 - let f_sponge_start = - s0.clone() * (one.clone() - s1.clone()) * (one.clone() - s2.clone()) * is_boundary.clone(); - - // Sponge continuation (RESPAN): input, s1=0, s2=0, is_boundary=0 - let f_sponge_respan = s0.clone() - * (one.clone() - s1.clone()) - * (one.clone() - s2.clone()) - * (one.clone() - is_boundary.clone()); - - // Merkle tree op start inputs (only is_boundary=1 produces response) - let f_mp_start = s0.clone() * (one.clone() - s1.clone()) * s2.clone() * is_boundary.clone(); - let f_mv_start = s0.clone() * s1.clone() * (one.clone() - s2.clone()) * is_boundary.clone(); - let f_mu_start = s0.clone() * s1.clone() * s2.clone() * is_boundary.clone(); - - // HOUT output (always responds) - let f_hout = - (one.clone() - s0.clone()) * (one.clone() - s1.clone()) * (one.clone() - s2.clone()); - - // SOUT output with is_boundary=1 only (HPERM return) - let f_sout_final = (one.clone() - s0) * (one.clone() - s1) * s2 * is_boundary; - - // --- Message values --- - - let label_sponge_start = AB::Expr::from_u16(LINEAR_HASH_LABEL as u16 + INPUT_LABEL_OFFSET); - let label_sponge_respan = AB::Expr::from_u16(LINEAR_HASH_LABEL as u16 + OUTPUT_LABEL_OFFSET); - let label_mp = AB::Expr::from_u16(MP_VERIFY_LABEL as u16 + INPUT_LABEL_OFFSET); - let label_mv = AB::Expr::from_u16(MR_UPDATE_OLD_LABEL as u16 + INPUT_LABEL_OFFSET); - let label_mu = AB::Expr::from_u16(MR_UPDATE_NEW_LABEL as u16 + INPUT_LABEL_OFFSET); - let label_hout = AB::Expr::from_u16(RETURN_HASH_LABEL as u16 + OUTPUT_LABEL_OFFSET); - let label_sout = AB::Expr::from_u16(RETURN_STATE_LABEL as u16 + OUTPUT_LABEL_OFFSET); - - // Sponge start: full 12-element state, node_index=0 (sponge doesn't use index) - let v_sponge_start = compute_hasher_message::( - challenges, - label_sponge_start, - addr_next.clone(), - AB::Expr::ZERO, - &state, - ); - - // Sponge continuation (RESPAN): rate-only 8 elements, addr_next directly - let rate: [AB::Expr; 8] = core::array::from_fn(|i| state[i].clone()); - let v_sponge_respan = compute_hasher_rate_message::( - challenges, - label_sponge_respan, - addr_next.clone(), - AB::Expr::ZERO, - &rate, - ); - - // Merkle tree inputs: leaf word selected by direction bit - let two = AB::Expr::from_u16(2); - let node_index_next: AB::Expr = - next.chiplets[HASHER_NODE_INDEX_COL_IDX - CHIPLETS_OFFSET].into(); - let bit = node_index.clone() - two * node_index_next; - let leaf_word: [AB::Expr; 4] = [ - (one.clone() - bit.clone()) * state[0].clone() + bit.clone() * state[4].clone(), - (one.clone() - bit.clone()) * state[1].clone() + bit.clone() * state[5].clone(), - (one.clone() - bit.clone()) * state[2].clone() + bit.clone() * state[6].clone(), - (one - bit.clone()) * state[3].clone() + bit * state[7].clone(), - ]; - let v_mp = compute_hasher_word_message::( - challenges, - label_mp, - addr_next.clone(), - node_index.clone(), - &leaf_word, - ); - let v_mv = compute_hasher_word_message::( - challenges, - label_mv, - addr_next.clone(), - node_index.clone(), - &leaf_word, - ); - let v_mu = compute_hasher_word_message::( - challenges, - label_mu, - addr_next.clone(), - node_index.clone(), - &leaf_word, - ); - - // HOUT: digest from RATE0 (state[0..4]) - let digest: [AB::Expr; 4] = core::array::from_fn(|i| state[i].clone()); - let v_hout = compute_hasher_word_message::( - challenges, - label_hout, - addr_next.clone(), - node_index, - &digest, - ); - - // SOUT: full 12-element state (HPERM return), node_index=0 - let v_sout = - compute_hasher_message::(challenges, label_sout, addr_next, AB::Expr::ZERO, &state); - - // --- Additive OR combination --- - // - // The inner flag_sum and sum are computed without controller_flag. The controller_flag - // is applied as a multiplicative factor to the entire sum, keeping the degree within - // budget: inner_flag(4) * message(2) = 6, * controller_flag(1) = 7. - - let inner_flag_sum = f_sponge_start.clone() - + f_sponge_respan.clone() - + f_mp_start.clone() - + f_mv_start.clone() - + f_mu_start.clone() - + f_hout.clone() - + f_sout_final.clone(); - - let inner_sum = v_sponge_start * f_sponge_start - + v_sponge_respan * f_sponge_respan - + v_mp * f_mp_start - + v_mv * f_mv_start - + v_mu * f_mu_start - + v_hout * f_hout - + v_sout * f_sout_final; - - // Apply controller_flag to the entire response. On perm segment and non-hasher rows, - // controller_flag=0 so the hasher contributes nothing (identity via the outer 1-flag_sum). - let flag_sum = controller_flag.clone() * inner_flag_sum; - let sum = inner_sum * controller_flag; - - HasherResponse { sum, flag_sum } -} - -// HASHER MESSAGE HELPERS -// ================================================================================================ - -/// Computes the HPERM request message value. -/// -/// HPERM sends two messages to the hasher chiplet: -/// 1. Input message: LINEAR_HASH_LABEL + 16, with input state from stack[0..12] -/// 2. Output message: RETURN_STATE_LABEL + 32, with output state from next stack[0..12] -/// -/// The combined request is the product of these two message values. -/// -/// Stack layout: [s0, s1, ..., s11, ...] -> [s0', s1', ..., s11', ...] -fn compute_hperm_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // Hasher address from helper register 0 - let addr: AB::Expr = local.decoder.hasher_state[2].into(); - - // Input state from current stack[0..12] - let input_state: [AB::Expr; 12] = core::array::from_fn(|i| local.stack.get(i).into()); - - // Output state from next stack[0..12] - let output_state: [AB::Expr; 12] = core::array::from_fn(|i| next.stack.get(i).into()); - - // Input message: transition_label = LINEAR_HASH_LABEL + 16 = 3 + 16 = 19 - let input_label: AB::Expr = AB::Expr::from_u16(LINEAR_HASH_LABEL as u16 + INPUT_LABEL_OFFSET); - let node_index_zero: AB::Expr = AB::Expr::ZERO; - - let input_msg = compute_hasher_message::( - challenges, - input_label, - addr.clone(), - node_index_zero.clone(), - &input_state, - ); - - // Output message: transition_label = RETURN_STATE_LABEL + 32 - // addr_next = addr + (CONTROLLER_ROWS_PER_PERMUTATION - 1) = addr + 1 - let output_label: AB::Expr = - AB::Expr::from_u16(RETURN_STATE_LABEL as u16 + OUTPUT_LABEL_OFFSET); - let addr_offset: AB::Expr = AB::Expr::from_u16((CONTROLLER_ROWS_PER_PERMUTATION - 1) as u16); - let addr_next = addr + addr_offset; - - let output_msg = compute_hasher_message::( - challenges, - output_label, - addr_next, - node_index_zero, - &output_state, - ); - - // Combined request is product of input and output messages - input_msg * output_msg -} - -/// Computes the LOG_PRECOMPILE request message value. -/// -/// LOG_PRECOMPILE absorbs `[COMM, TAG]` with capacity `CAP_PREV` and returns `[R0, R1, CAP_NEXT]`. -/// The request is the product of input (LINEAR_HASH + 16) and output (RETURN_STATE + 32) messages. -fn compute_log_precompile_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // Helper registers (user op helpers start at hasher_state[2]) - let addr: AB::Expr = local.decoder.hasher_state[2 + HELPER_ADDR_IDX].into(); - - // CAP_PREV from helper registers (4 lanes) - let cap_prev: [AB::Expr; 4] = core::array::from_fn(|i| { - local.decoder.hasher_state[2 + HELPER_CAP_PREV_RANGE.start + i].into() - }); - - // COMM and TAG from the current stack - let comm: [AB::Expr; 4] = - core::array::from_fn(|i| local.stack.get(STACK_COMM_RANGE.start + i).into()); - let tag: [AB::Expr; 4] = - core::array::from_fn(|i| local.stack.get(STACK_TAG_RANGE.start + i).into()); - - // Input state [COMM, TAG, CAP_PREV] - let state_input: [AB::Expr; 12] = [ - comm[0].clone(), - comm[1].clone(), - comm[2].clone(), - comm[3].clone(), - tag[0].clone(), - tag[1].clone(), - tag[2].clone(), - tag[3].clone(), - cap_prev[0].clone(), - cap_prev[1].clone(), - cap_prev[2].clone(), - cap_prev[3].clone(), - ]; - - // Output state from next stack [R0, R1, CAP_NEXT] - let r0: [AB::Expr; 4] = - core::array::from_fn(|i| next.stack.get(STACK_R0_RANGE.start + i).into()); - let r1: [AB::Expr; 4] = - core::array::from_fn(|i| next.stack.get(STACK_R1_RANGE.start + i).into()); - let cap_next: [AB::Expr; 4] = - core::array::from_fn(|i| next.stack.get(STACK_CAP_NEXT_RANGE.start + i).into()); - let state_output: [AB::Expr; 12] = [ - r0[0].clone(), - r0[1].clone(), - r0[2].clone(), - r0[3].clone(), - r1[0].clone(), - r1[1].clone(), - r1[2].clone(), - r1[3].clone(), - cap_next[0].clone(), - cap_next[1].clone(), - cap_next[2].clone(), - cap_next[3].clone(), - ]; - - // Input message: LINEAR_HASH_LABEL + 16 - let input_label: AB::Expr = AB::Expr::from_u16(LINEAR_HASH_LABEL as u16 + INPUT_LABEL_OFFSET); - let input_msg = compute_hasher_message::( - challenges, - input_label, - addr.clone(), - AB::Expr::ZERO, - &state_input, - ); - - // Output message: RETURN_STATE_LABEL + 32 with addr offset by CONTROLLER_ROWS_PER_PERMUTATION - - // 1 - let output_label: AB::Expr = - AB::Expr::from_u16(RETURN_STATE_LABEL as u16 + OUTPUT_LABEL_OFFSET); - let addr_offset: AB::Expr = AB::Expr::from_u16((CONTROLLER_ROWS_PER_PERMUTATION - 1) as u16); - let output_msg = compute_hasher_message::( - challenges, - output_label, - addr + addr_offset, - AB::Expr::ZERO, - &state_output, - ); - - input_msg * output_msg -} - -/// Computes a hasher message value. -/// -/// Format: header + state where: -/// - header = alpha + beta^0 * transition_label + beta^1 * addr + beta^2 * node_index -/// - state = sum(beta^(3+i) * hasher_state[i]) for i in 0..12 -fn compute_hasher_message( - challenges: &Challenges, - transition_label: AB::Expr, - addr: AB::Expr, - node_index: AB::Expr, - state: &[AB::Expr; 12], -) -> AB::ExprEF { - challenges.encode( - bus_types::CHIPLETS_BUS, - [ - transition_label, - addr, - node_index, - state[0].clone(), - state[1].clone(), - state[2].clone(), - state[3].clone(), - state[4].clone(), - state[5].clone(), - state[6].clone(), - state[7].clone(), - state[8].clone(), - state[9].clone(), - state[10].clone(), - state[11].clone(), - ], - ) -} - -/// Computes a hasher message for a 4-lane word. -fn compute_hasher_word_message( - challenges: &Challenges, - transition_label: AB::Expr, - addr: AB::Expr, - node_index: AB::Expr, - word: &[AB::Expr; 4], -) -> AB::ExprEF { - challenges.encode( - bus_types::CHIPLETS_BUS, - [ - transition_label, - addr, - node_index, - word[0].clone(), - word[1].clone(), - word[2].clone(), - word[3].clone(), - ], - ) -} - -/// Computes a hasher message for an 8-lane rate. -fn compute_hasher_rate_message( - challenges: &Challenges, - transition_label: AB::Expr, - addr: AB::Expr, - node_index: AB::Expr, - rate: &[AB::Expr; 8], -) -> AB::ExprEF { - challenges.encode( - bus_types::CHIPLETS_BUS, - [ - transition_label, - addr, - node_index, - rate[0].clone(), - rate[1].clone(), - rate[2].clone(), - rate[3].clone(), - rate[4].clone(), - rate[5].clone(), - rate[6].clone(), - rate[7].clone(), - ], - ) -} - -// ACE MESSAGE HELPERS -// ================================================================================================ - -/// Computes the ACE request message value. -/// -/// Format: bus_prefix[CHIPLETS_BUS] + beta^0*label + beta^1*clk + beta^2*ctx + beta^3*ptr -/// + beta^4*num_read_rows + beta^5*num_eval_rows -/// -/// Stack layout for EVALCIRCUIT: [ptr, num_read_rows, num_eval_rows, ...] -fn compute_ace_request( - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // Label is ACE_INIT_LABEL - let label: AB::Expr = AB::Expr::from(ACE_INIT_LABEL); - - // Context and clock from system columns - let ctx: AB::Expr = local.system.ctx.into(); - let clk: AB::Expr = local.system.clk.into(); - - // Stack values - let ptr: AB::Expr = local.stack.get(0).into(); - let num_read_rows: AB::Expr = local.stack.get(1).into(); - let num_eval_rows: AB::Expr = local.stack.get(2).into(); - - challenges.encode(bus_types::CHIPLETS_BUS, [label, clk, ctx, ptr, num_read_rows, num_eval_rows]) -} - -/// Computes the ACE chiplet response message value. -/// -/// Format: bus_prefix[CHIPLETS_BUS] + beta^0*label + beta^1*clk + beta^2*ctx + beta^3*ptr -/// + beta^4*num_read_rows + beta^5*num_eval_rows -/// -/// The chiplet reads from its internal columns: -/// - clk from CLK_IDX -/// - ctx from CTX_IDX -/// - ptr from PTR_IDX -/// - num_eval_rows computed from READ_NUM_EVAL_IDX + 1 -/// - num_read_rows = id_0 + 1 - num_eval_rows -fn compute_ace_response( - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // Label is ACE_INIT_LABEL - let label: AB::Expr = AB::Expr::from(ACE_INIT_LABEL); - - // Read values from ACE chiplet columns (offset by NUM_ACE_SELECTORS) - let clk: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + CLK_IDX].into(); - let ctx: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + CTX_IDX].into(); - let ptr: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + PTR_IDX].into(); - - // num_eval_rows = READ_NUM_EVAL_IDX value + 1 - let read_num_eval: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + READ_NUM_EVAL_IDX].into(); - let num_eval_rows: AB::Expr = read_num_eval + AB::Expr::ONE; - - // id_0 from ID_0_IDX - let id_0: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + ID_0_IDX].into(); - - // num_read_rows = id_0 + 1 - num_eval_rows - let num_read_rows: AB::Expr = id_0 + AB::Expr::ONE - num_eval_rows.clone(); - - challenges.encode(bus_types::CHIPLETS_BUS, [label, clk, ctx, ptr, num_read_rows, num_eval_rows]) -} - -// KERNEL ROM MESSAGE HELPERS -// ================================================================================================ - -/// Computes the kernel ROM chiplet response message value. -/// -/// Format: bus_prefix[CHIPLETS_BUS] + beta^0*label + beta^1*digest[0] + beta^2*digest[1] -/// + beta^3*digest[2] + beta^4*digest[3] -/// -/// The label depends on s_first flag: -/// - s_first=1: KERNEL_PROC_INIT_LABEL (responding to verifier/public input init request) -/// - s_first=0: KERNEL_PROC_CALL_LABEL (responding to decoder SYSCALL request) -fn compute_kernel_rom_response( - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // s_first flag is at CHIPLETS_OFFSET + 5 (after 5 selectors), which is chiplets[5] - let s_first: AB::Expr = local.chiplets[NUM_KERNEL_ROM_SELECTORS].into(); - - // Label depends on s_first: - // label = s_first * INIT_LABEL + (1 - s_first) * CALL_LABEL - let init_label: AB::Expr = AB::Expr::from(KERNEL_PROC_INIT_LABEL); - let call_label: AB::Expr = AB::Expr::from(KERNEL_PROC_CALL_LABEL); - let label: AB::Expr = s_first.clone() * init_label + (AB::Expr::ONE - s_first) * call_label; - - // Kernel procedure digest (root0..root3) at columns 6, 7, 8, 9 relative to chiplets - // These are at NUM_KERNEL_ROM_SELECTORS + 1..5 (after s_first which is at +0) - let root0: AB::Expr = local.chiplets[NUM_KERNEL_ROM_SELECTORS + 1].into(); - let root1: AB::Expr = local.chiplets[NUM_KERNEL_ROM_SELECTORS + 2].into(); - let root2: AB::Expr = local.chiplets[NUM_KERNEL_ROM_SELECTORS + 3].into(); - let root3: AB::Expr = local.chiplets[NUM_KERNEL_ROM_SELECTORS + 4].into(); - - challenges.encode(bus_types::CHIPLETS_BUS, [label, root0, root1, root2, root3]) -} - -// CONTROL BLOCK REQUEST HELPERS -// ================================================================================================ - -/// Control block operation types for request message construction. -#[derive(Clone, Copy)] -enum ControlBlockOp { - Join, - Split, - Loop, - Call, - Syscall, -} - -impl ControlBlockOp { - /// Returns the opcode value for this control block operation. - fn opcode(self) -> u8 { - match self { - ControlBlockOp::Join => opcodes::JOIN, - ControlBlockOp::Split => opcodes::SPLIT, - ControlBlockOp::Loop => opcodes::LOOP, - ControlBlockOp::Call => opcodes::CALL, - ControlBlockOp::Syscall => opcodes::SYSCALL, - } - } -} - -/// Computes the control block request message value for JOIN, SPLIT, LOOP, CALL, and SYSCALL. -/// -/// Format follows ControlBlockRequestMessage from processor: -/// - header = alpha + beta^0 * transition_label + beta^1 * addr_next -/// - state = 12-lane sponge with 8-element decoder hasher state as rate + opcode as domain -/// -/// The message reconstructs: -/// - transition_label = LINEAR_HASH_LABEL + 16 -/// - addr_next = decoder address at next row (from next row's addr column) -/// - hasher_state = rate lanes from decoder hasher columns + opcode in capacity domain position -fn compute_control_block_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, - op: ControlBlockOp, -) -> AB::ExprEF { - // transition_label = LINEAR_HASH_LABEL + - let transition_label: AB::Expr = - AB::Expr::from_u16(LINEAR_HASH_LABEL as u16 + INPUT_LABEL_OFFSET); - - // addr_next = next row's decoder address - let addr_next: AB::Expr = next.decoder.addr.into(); - - // Get decoder hasher state (8 elements) - let hasher_state: [AB::Expr; 8] = - core::array::from_fn(|i| local.decoder.hasher_state[i].into()); - - // op_code as domain in capacity position - let op_code: AB::Expr = AB::Expr::from_u16(op.opcode() as u16); - - // Build 12-lane sponge state: - // [RATE0: h[0..4], RATE1: h[4..8], CAPACITY: [0, domain, 0, 0]] - // LE layout: RATE0 at 0..4, RATE1 at 4..8, CAPACITY at 8..12 - let state: [AB::Expr; 12] = [ - hasher_state[0].clone(), - hasher_state[1].clone(), - hasher_state[2].clone(), - hasher_state[3].clone(), - hasher_state[4].clone(), - hasher_state[5].clone(), - hasher_state[6].clone(), - hasher_state[7].clone(), - AB::Expr::ZERO, - op_code, // domain at CAPACITY_DOMAIN_IDX = 9 - AB::Expr::ZERO, - AB::Expr::ZERO, - ]; - - compute_hasher_message::(challenges, transition_label, addr_next, AB::Expr::ZERO, &state) -} - -/// Computes the CALL request message value. -/// -/// CALL sends: -/// 1. Control block request (with decoder hasher state) -/// 2. FMP initialization write request (to set up new execution context) -fn compute_call_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // Control block request - let control_req = - compute_control_block_request::(local, next, challenges, ControlBlockOp::Call); - - // FMP initialization write request - let fmp_req = compute_fmp_write_request::(local, next, challenges); - - control_req * fmp_req -} - -/// Computes the DYN request message value. -/// -/// DYN sends: -/// 1. Control block request (with zeros for hasher state since callee is dynamic) -/// 2. Memory read request for callee hash from stack[0] -fn compute_dyn_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // Control block request with zeros for hasher state (callee is dynamic) - let control_req = - compute_control_block_request_zeros::(local, next, challenges, opcodes::DYN); - - // Memory read for callee hash (word read from stack[0] address) - let callee_hash_req = compute_dyn_callee_hash_read::(local, challenges); - - control_req * callee_hash_req -} - -/// Computes the DYNCALL request message value. -/// -/// DYNCALL sends: -/// 1. Control block request (with zeros for hasher state since callee is dynamic) -/// 2. Memory read request for callee hash from stack[0] -/// 3. FMP initialization write request -fn compute_dyncall_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // Control block request with zeros for hasher state (callee is dynamic) - let control_req = - compute_control_block_request_zeros::(local, next, challenges, opcodes::DYNCALL); - - // Memory read for callee hash (word read from stack[0] address) - let callee_hash_req = compute_dyn_callee_hash_read::(local, challenges); - - // FMP initialization write request - let fmp_req = compute_fmp_write_request::(local, next, challenges); - - control_req * callee_hash_req * fmp_req -} - -/// Computes the SYSCALL request message value. -/// -/// SYSCALL sends: -/// 1. Control block request (with decoder hasher state) -/// 2. Kernel ROM lookup request (to verify kernel procedure) -fn compute_syscall_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // Control block request - let control_req = - compute_control_block_request::(local, next, challenges, ControlBlockOp::Syscall); - - // Kernel ROM lookup request (digest from first 4 elements of decoder hasher state) - let root0: AB::Expr = local.decoder.hasher_state[0].into(); - let root1: AB::Expr = local.decoder.hasher_state[1].into(); - let root2: AB::Expr = local.decoder.hasher_state[2].into(); - let root3: AB::Expr = local.decoder.hasher_state[3].into(); - - let label: AB::Expr = AB::Expr::from(KERNEL_PROC_CALL_LABEL); - let kernel_req = - challenges.encode(bus_types::CHIPLETS_BUS, [label, root0, root1, root2, root3]); - - control_req * kernel_req -} - -/// Computes the SPAN block request message value. -/// -/// Format: header + full 12-lane sponge state (8 rate lanes + 4 capacity lanes zeroed) -fn compute_span_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // transition_label = LINEAR_HASH_LABEL + 16 - let transition_label: AB::Expr = - AB::Expr::from_u16(LINEAR_HASH_LABEL as u16 + INPUT_LABEL_OFFSET); - - // addr_next = next row's decoder address - let addr_next: AB::Expr = next.decoder.addr.into(); - - // Get decoder hasher state (8 elements) - let hasher_state: [AB::Expr; 8] = - core::array::from_fn(|i| local.decoder.hasher_state[i].into()); - - // Build full 12-lane state with capacity zeroed - let state: [AB::Expr; 12] = [ - hasher_state[0].clone(), - hasher_state[1].clone(), - hasher_state[2].clone(), - hasher_state[3].clone(), - hasher_state[4].clone(), - hasher_state[5].clone(), - hasher_state[6].clone(), - hasher_state[7].clone(), - AB::Expr::ZERO, - AB::Expr::ZERO, - AB::Expr::ZERO, - AB::Expr::ZERO, - ]; - - compute_hasher_message::(challenges, transition_label, addr_next, AB::Expr::ZERO, &state) -} - -/// Computes the RESPAN block request message value. -/// -/// Rate occupies message positions 3..10 (after label/addr/node_index). -fn compute_respan_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // transition_label = LINEAR_HASH_LABEL + 32 - let transition_label: AB::Expr = - AB::Expr::from_u16(LINEAR_HASH_LABEL as u16 + OUTPUT_LABEL_OFFSET); - - // RESPAN message uses addr_next directly (the next row's decoder address). - // In the controller/perm split, addr_next points directly to the continuation - // input row -- no offset needed. - let addr_next: AB::Expr = next.decoder.addr.into(); - let addr_for_msg = addr_next; - - // Get decoder hasher state (8 elements) - let hasher_state: [AB::Expr; 8] = - core::array::from_fn(|i| local.decoder.hasher_state[i].into()); - - compute_hasher_rate_message::( - challenges, - transition_label, - addr_for_msg, - AB::Expr::ZERO, - &hasher_state, - ) -} - -/// Computes the END block request message value. -/// -/// Digest occupies message positions 3..6 (after label/addr/node_index). -fn compute_end_request( - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - // transition_label = RETURN_HASH_LABEL + 32 = 1 + 32 - let transition_label: AB::Expr = - AB::Expr::from_u16(RETURN_HASH_LABEL as u16 + OUTPUT_LABEL_OFFSET); - - // addr = decoder.addr + (CONTROLLER_ROWS_PER_PERMUTATION - 1) = addr + 1 - let addr: AB::Expr = local.decoder.addr.into() - + AB::Expr::from_u16((CONTROLLER_ROWS_PER_PERMUTATION - 1) as u16); - - // Get digest from decoder hasher state (first 4 elements) - let digest: [AB::Expr; 4] = core::array::from_fn(|i| local.decoder.hasher_state[i].into()); - - compute_hasher_word_message::(challenges, transition_label, addr, AB::Expr::ZERO, &digest) -} - -/// Computes control block request with zeros for hasher state (for DYN/DYNCALL). -fn compute_control_block_request_zeros( - _local: &MainCols, - next: &MainCols, - challenges: &Challenges, - opcode: u8, -) -> AB::ExprEF { - // transition_label = LINEAR_HASH_LABEL + 16 - let transition_label: AB::Expr = - AB::Expr::from_u16(LINEAR_HASH_LABEL as u16 + INPUT_LABEL_OFFSET); - - // addr_next = next row's decoder address - let addr_next: AB::Expr = next.decoder.addr.into(); - - // op_code as domain - let op_code: AB::Expr = AB::Expr::from_u16(opcode as u16); - - // State with zeros for rate lanes, opcode in capacity domain - let state: [AB::Expr; 12] = [ - AB::Expr::ZERO, - AB::Expr::ZERO, - AB::Expr::ZERO, - AB::Expr::ZERO, - AB::Expr::ZERO, - AB::Expr::ZERO, - AB::Expr::ZERO, - AB::Expr::ZERO, - AB::Expr::ZERO, - op_code, // domain at CAPACITY_DOMAIN_IDX = 9 - AB::Expr::ZERO, - AB::Expr::ZERO, - ]; - - compute_hasher_message::(challenges, transition_label, addr_next, AB::Expr::ZERO, &state) -} - -/// Computes the FMP initialization write request. -/// -/// This writes FMP_INIT_VALUE to FMP_ADDR in the new context. -fn compute_fmp_write_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - let label: AB::Expr = AB::Expr::from_u16(MEMORY_WRITE_ELEMENT_LABEL as u16); - - // ctx from next row (new execution context) - let ctx: AB::Expr = next.system.ctx.into(); - let clk: AB::Expr = local.system.clk.into(); - let addr: AB::Expr = AB::Expr::from(FMP_ADDR); - let element: AB::Expr = AB::Expr::from(FMP_INIT_VALUE); - - challenges.encode(bus_types::CHIPLETS_BUS, [label, ctx, addr, clk, element]) -} - -/// Computes the callee hash read request for DYN/DYNCALL. -/// -/// Reads a word from the address at stack[0] containing the callee hash. -fn compute_dyn_callee_hash_read( - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - let label: AB::Expr = AB::Expr::from_u16(MEMORY_READ_WORD_LABEL as u16); - - let ctx: AB::Expr = local.system.ctx.into(); - let clk: AB::Expr = local.system.clk.into(); - let addr: AB::Expr = local.stack.get(0).into(); - - // The callee hash is read into decoder hasher state first half - let w0: AB::Expr = local.decoder.hasher_state[0].into(); - let w1: AB::Expr = local.decoder.hasher_state[1].into(); - let w2: AB::Expr = local.decoder.hasher_state[2].into(); - let w3: AB::Expr = local.decoder.hasher_state[3].into(); - - challenges.encode(bus_types::CHIPLETS_BUS, [label, ctx, addr, clk, w0, w1, w2, w3]) -} - -// MPVERIFY/MRUPDATE REQUEST HELPERS -// ================================================================================================ - -/// Computes the MPVERIFY request message value. -/// -/// MPVERIFY sends two messages as a product: -/// 1. Input: node value (stack[0..4]) with node_index -/// 2. Output: root digest (stack[6..10]) at the computed output address -fn compute_mpverify_request( - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - let helper_0: AB::Expr = local.decoder.hasher_state[2].into(); - let rows_per_perm: AB::Expr = AB::Expr::from_u16(CONTROLLER_ROWS_PER_PERMUTATION as u16); - - // Stack layout: [node_value0..3, node_depth, node_index, root0..3, ...] - let node_value: [AB::Expr; 4] = core::array::from_fn(|i| local.stack.get(i).into()); - let node_depth: AB::Expr = local.stack.get(4).into(); - let node_index: AB::Expr = local.stack.get(5).into(); - let root: [AB::Expr; 4] = core::array::from_fn(|i| local.stack.get(6 + i).into()); - - let input_label: AB::Expr = AB::Expr::from_u16(MP_VERIFY_LABEL as u16 + INPUT_LABEL_OFFSET); - let input_msg = compute_hasher_word_message::( - challenges, - input_label, - helper_0.clone(), - node_index, - &node_value, - ); - - // Output address = start + depth * rows_per_perm - 1 (last output row of the path) - let output_addr = helper_0 + node_depth * rows_per_perm - AB::Expr::ONE; - let output_label: AB::Expr = AB::Expr::from_u16(RETURN_HASH_LABEL as u16 + OUTPUT_LABEL_OFFSET); - let output_msg = compute_hasher_word_message::( - challenges, - output_label, - output_addr, - AB::Expr::ZERO, - &root, - ); - - input_msg * output_msg -} - -/// Computes the MRUPDATE request message value. -/// -/// MRUPDATE sends four messages as a product: -/// 1. Input old: old node value (stack[0..4]) with node_index -/// 2. Output old: old root digest (stack[6..10]) at computed output address -/// 3. Input new: new node value (stack[10..14]) with node_index -/// 4. Output new: new root digest (next.stack[0..4]) at computed output address -fn compute_mrupdate_request( - local: &MainCols, - next: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF { - let helper_0: AB::Expr = local.decoder.hasher_state[2].into(); - let rows_per_perm: AB::Expr = AB::Expr::from_u16(CONTROLLER_ROWS_PER_PERMUTATION as u16); - let two_legs_rows: AB::Expr = rows_per_perm.clone() + rows_per_perm.clone(); - - // Stack layout: [old_node0..3, depth, index, old_root0..3, new_node0..3, ...] - let old_node: [AB::Expr; 4] = core::array::from_fn(|i| local.stack.get(i).into()); - let depth: AB::Expr = local.stack.get(4).into(); - let index: AB::Expr = local.stack.get(5).into(); - let old_root: [AB::Expr; 4] = core::array::from_fn(|i| local.stack.get(6 + i).into()); - let new_node: [AB::Expr; 4] = core::array::from_fn(|i| local.stack.get(10 + i).into()); - // New root is at next.stack[0..4] - let new_root: [AB::Expr; 4] = core::array::from_fn(|i| next.stack.get(i).into()); - - let input_old_label: AB::Expr = - AB::Expr::from_u16(MR_UPDATE_OLD_LABEL as u16 + INPUT_LABEL_OFFSET); - let input_old_msg = compute_hasher_word_message::( - challenges, - input_old_label, - helper_0.clone(), - index.clone(), - &old_node, - ); - - let output_old_addr = helper_0.clone() + depth.clone() * rows_per_perm.clone() - AB::Expr::ONE; - let output_old_label: AB::Expr = - AB::Expr::from_u16(RETURN_HASH_LABEL as u16 + OUTPUT_LABEL_OFFSET); - let output_old_msg = compute_hasher_word_message::( - challenges, - output_old_label.clone(), - output_old_addr, - AB::Expr::ZERO, - &old_root, - ); - - let input_new_addr = helper_0.clone() + depth.clone() * rows_per_perm; - let input_new_label: AB::Expr = - AB::Expr::from_u16(MR_UPDATE_NEW_LABEL as u16 + INPUT_LABEL_OFFSET); - let input_new_msg = compute_hasher_word_message::( - challenges, - input_new_label, - input_new_addr, - index, - &new_node, - ); - - let output_new_addr = helper_0 + depth * two_legs_rows - AB::Expr::ONE; - let output_new_msg = compute_hasher_word_message::( - challenges, - output_old_label, - output_new_addr, - AB::Expr::ZERO, - &new_root, - ); - - input_old_msg * output_old_msg * input_new_msg * output_new_msg -} - -// TESTS -// ================================================================================================ - -#[cfg(test)] -mod tests { - use crate::{ - Felt, - trace::chiplets::{ - ace::ACE_INIT_LABEL, - bitwise::{BITWISE_AND_LABEL, BITWISE_XOR_LABEL}, - kernel_rom::{KERNEL_PROC_CALL_LABEL, KERNEL_PROC_INIT_LABEL}, - memory::{ - MEMORY_READ_ELEMENT_LABEL, MEMORY_READ_WORD_LABEL, MEMORY_WRITE_ELEMENT_LABEL, - MEMORY_WRITE_WORD_LABEL, - }, - }, - }; - - #[test] - fn test_operation_labels() { - // Verify operation labels match expected values - assert_eq!(BITWISE_AND_LABEL, Felt::new_unchecked(2)); - assert_eq!(BITWISE_XOR_LABEL, Felt::new_unchecked(6)); - assert_eq!(MEMORY_WRITE_ELEMENT_LABEL, 4); - assert_eq!(MEMORY_READ_ELEMENT_LABEL, 12); - assert_eq!(MEMORY_WRITE_WORD_LABEL, 20); - assert_eq!(MEMORY_READ_WORD_LABEL, 28); - } - - #[test] - fn test_memory_label_formula() { - // Verify: label = 4 + 8*is_read + 16*is_word - fn label(is_read: u64, is_word: u64) -> u64 { - 4 + 8 * is_read + 16 * is_word - } - - assert_eq!(label(0, 0), MEMORY_WRITE_ELEMENT_LABEL as u64); // 4 - assert_eq!(label(1, 0), MEMORY_READ_ELEMENT_LABEL as u64); // 12 - assert_eq!(label(0, 1), MEMORY_WRITE_WORD_LABEL as u64); // 20 - assert_eq!(label(1, 1), MEMORY_READ_WORD_LABEL as u64); // 28 - } - - #[test] - fn test_ace_label() { - // ACE label: selector = [1, 1, 1, 0], reversed = [0, 1, 1, 1] = 7, +1 = 8 - assert_eq!(ACE_INIT_LABEL, Felt::new_unchecked(8)); - } - - #[test] - fn test_kernel_rom_labels() { - // Kernel ROM call label: selector = [1, 1, 1, 1, 0 | 0], reversed = [0, 0, 1, 1, 1, 1] = - // 15, +1 = 16 - assert_eq!(KERNEL_PROC_CALL_LABEL, Felt::new_unchecked(16)); - - // Kernel ROM init label: selector = [1, 1, 1, 1, 0 | 1], reversed = [1, 0, 1, 1, 1, 1] = - // 47, +1 = 48 - assert_eq!(KERNEL_PROC_INIT_LABEL, Felt::new_unchecked(48)); - } -} diff --git a/air/src/constraints/chiplets/bus/hash_kernel.rs b/air/src/constraints/chiplets/bus/hash_kernel.rs deleted file mode 100644 index 257dcb208d..0000000000 --- a/air/src/constraints/chiplets/bus/hash_kernel.rs +++ /dev/null @@ -1,296 +0,0 @@ -//! Hash kernel virtual table bus constraint (`b_hash_kernel`). -//! -//! This module enforces a single running-product column (aux index 5) which aggregates three -//! logically separate tables: -//! -//! 1. **Sibling table** for Merkle root updates (hasher chiplet). -//! 2. **ACE memory reads** (ACE chiplet requests; memory chiplet responses on `b_chiplets`). -//! 3. **Log-precompile transcript** (capacity state transitions for LOGPRECOMPILE). -//! -//! Rows contribute either a request term, a response term, or the identity (when no flag is set). -//! The request/response values use the standard message format: -//! `bus_prefix[bus] + sum_i beta^i * element[i]`. - -use miden_core::field::PrimeCharacteristicRing; -use miden_crypto::stark::air::{ExtensionBuilder, LiftedAirBuilder, WindowAccess}; - -use crate::{ - Felt, MainCols, MidenAirBuilder, - constraints::{ - bus::indices::B_HASH_KERNEL, chiplets::selectors::ChipletSelectors, op_flags::OpFlags, - }, - trace::{ - CHIPLETS_OFFSET, Challenges, LOG_PRECOMPILE_LABEL, bus_types, - chiplets::{ - HASHER_MRUPDATE_ID_COL_IDX, HASHER_NODE_INDEX_COL_IDX, HASHER_STATE_COL_RANGE, - NUM_ACE_SELECTORS, - ace::{ - ACE_INSTRUCTION_ID1_OFFSET, ACE_INSTRUCTION_ID2_OFFSET, CLK_IDX, CTX_IDX, - EVAL_OP_IDX, ID_1_IDX, ID_2_IDX, PTR_IDX, V_0_0_IDX, V_0_1_IDX, V_1_0_IDX, - V_1_1_IDX, - }, - memory::{MEMORY_READ_ELEMENT_LABEL, MEMORY_READ_WORD_LABEL}, - }, - log_precompile::{HELPER_CAP_PREV_RANGE, STACK_CAP_NEXT_RANGE}, - }, -}; - -// CONSTANTS -// ================================================================================================ - -// Column offsets relative to chiplets array. -const H_START: usize = HASHER_STATE_COL_RANGE.start - CHIPLETS_OFFSET; -const IDX_COL: usize = HASHER_NODE_INDEX_COL_IDX - CHIPLETS_OFFSET; -const MRUPDATE_ID_COL: usize = HASHER_MRUPDATE_ID_COL_IDX - CHIPLETS_OFFSET; - -// ENTRY POINTS -// ================================================================================================ - -/// Enforces the hash kernel virtual table (b_hash_kernel) bus constraint. -/// -/// This constraint combines: -/// 1. Sibling table for Merkle root updates -/// 2. ACE memory read requests -/// 3. Log precompile transcript tracking -pub fn enforce_hash_kernel_constraint( - builder: &mut AB, - local: &MainCols, - next: &MainCols, - op_flags: &OpFlags, - challenges: &Challenges, - selectors: &ChipletSelectors, -) where - AB: MidenAirBuilder, -{ - // ========================================================================= - // AUXILIARY TRACE ACCESS - // ========================================================================= - - let (p_local, p_next) = { - let aux = builder.permutation(); - let aux_local = aux.current_slice(); - let aux_next = aux.next_slice(); - (aux_local[B_HASH_KERNEL], aux_next[B_HASH_KERNEL]) - }; - - let one = AB::Expr::ONE; - let one_ef = AB::ExprEF::ONE; - - // ========================================================================= - // COMMON VALUES - // ========================================================================= - - // Controller flag from the precomputed chiplet selectors. - let controller_flag: AB::Expr = selectors.controller.is_active.clone(); - - // Hasher operation selectors (only meaningful on hasher controller rows). - // On controller rows: `s0=1` = input row, `(s0,s1,s2)` encodes the operation. - // On permutation rows these columns hold S-box witnesses — gated out by controller_flag. - let ctrl = local.controller(); - - // Node index and mrupdate_id for sibling table - let node_index: AB::Expr = local.chiplets[IDX_COL].into(); - let node_index_next: AB::Expr = next.chiplets[IDX_COL].into(); - let mrupdate_id: AB::Expr = local.chiplets[MRUPDATE_ID_COL].into(); - - // Hasher state for sibling values - let h: [AB::Expr; 12] = core::array::from_fn(|i| local.chiplets[H_START + i].into()); - - // ========================================================================= - // SIBLING TABLE FLAGS AND VALUES - // ========================================================================= - - // In the controller/perm split, sibling table operations happen on controller input rows - // for MU (new path - requests/removes) and MV (old path - responses/adds). - // All MU/MV input rows participate (not just is_start=1). - // f_mu = s0 * s1 * s2 - let f_mu: AB::Expr = controller_flag.clone() * ctrl.f_mu(); - // f_mv = s0 * s1 * !s2 - let f_mv: AB::Expr = controller_flag * ctrl.f_mv(); - - // Direction bit b = input_node_index - 2 * output_node_index (next row is the paired output). - let b: AB::Expr = node_index.clone() - node_index_next.double(); - let is_b_zero = one.clone() - b.clone(); - let is_b_one = b; - - // Sibling value from the current input row's state, including mrupdate_id for domain - // separation. b selects which half of the rate holds the sibling. - let v_sibling = compute_sibling_b0::(challenges, &mrupdate_id, &node_index, &h) * is_b_zero - + compute_sibling_b1::(challenges, &mrupdate_id, &node_index, &h) * is_b_one; - - // ========================================================================= - // ACE MEMORY FLAGS AND VALUES - // ========================================================================= - - // ACE row flag from the precomputed chiplet selectors. - let is_ace_row: AB::Expr = selectors.ace.is_active.clone(); - let ace = local.ace(); - - let f_ace_read: AB::Expr = is_ace_row.clone() * ace.f_read(); - let f_ace_eval: AB::Expr = is_ace_row * ace.f_eval(); - - // ACE columns for memory messages - let ace_clk: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + CLK_IDX].into(); - let ace_ctx: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + CTX_IDX].into(); - let ace_ptr: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + PTR_IDX].into(); - - // Word read value: label + ctx + ptr + clk + 4-lane value. - let v_ace_word = { - let v0_0: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + V_0_0_IDX].into(); - let v0_1: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + V_0_1_IDX].into(); - let v1_0: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + V_1_0_IDX].into(); - let v1_1: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + V_1_1_IDX].into(); - let label: AB::Expr = AB::Expr::from(Felt::from_u8(MEMORY_READ_WORD_LABEL)); - - challenges.encode( - bus_types::CHIPLETS_BUS, - [label, ace_ctx.clone(), ace_ptr.clone(), ace_clk.clone(), v0_0, v0_1, v1_0, v1_1], - ) - }; - - // Element read value: label + ctx + ptr + clk + element. - let v_ace_element = { - let id_1: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + ID_1_IDX].into(); - let id_2: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + ID_2_IDX].into(); - let eval_op: AB::Expr = local.chiplets[NUM_ACE_SELECTORS + EVAL_OP_IDX].into(); - - let offset1: AB::Expr = AB::Expr::from(ACE_INSTRUCTION_ID1_OFFSET); - let offset2: AB::Expr = AB::Expr::from(ACE_INSTRUCTION_ID2_OFFSET); - let element = id_1 + id_2 * offset1 + (eval_op + one) * offset2; - let label: AB::Expr = AB::Expr::from(Felt::from_u8(MEMORY_READ_ELEMENT_LABEL)); - - challenges.encode(bus_types::CHIPLETS_BUS, [label, ace_ctx, ace_ptr, ace_clk, element]) - }; - - // ========================================================================= - // LOG PRECOMPILE FLAGS AND VALUES - // ========================================================================= - - let f_logprecompile: AB::Expr = op_flags.log_precompile(); - - // CAP_PREV from helper registers (provided and constrained by the decoder logic). - let cap_prev: [AB::Expr; 4] = core::array::from_fn(|i| { - local.decoder.hasher_state[2 + HELPER_CAP_PREV_RANGE.start + i].into() - }); - - // CAP_NEXT from next-row stack. - let cap_next: [AB::Expr; 4] = - core::array::from_fn(|i| next.stack.get(STACK_CAP_NEXT_RANGE.start + i).into()); - - let log_label: AB::Expr = AB::Expr::from(Felt::from_u8(LOG_PRECOMPILE_LABEL)); - - // CAP_PREV value (request - removed). - let v_cap_prev = challenges.encode( - bus_types::LOG_PRECOMPILE_TRANSCRIPT, - [ - log_label.clone(), - cap_prev[0].clone(), - cap_prev[1].clone(), - cap_prev[2].clone(), - cap_prev[3].clone(), - ], - ); - - // CAP_NEXT value (response - inserted). - let v_cap_next = challenges.encode( - bus_types::LOG_PRECOMPILE_TRANSCRIPT, - [ - log_label, - cap_next[0].clone(), - cap_next[1].clone(), - cap_next[2].clone(), - cap_next[3].clone(), - ], - ); - - // ========================================================================= - // RUNNING PRODUCT CONSTRAINT - // ========================================================================= - - // Include the identity term when no request/response flag is set on a row. - // Flags are mutually exclusive by construction (chiplet selectors + op flags). - let request_flag_sum = - f_mu.clone() + f_ace_read.clone() + f_ace_eval.clone() + f_logprecompile.clone(); - let requests: AB::ExprEF = v_sibling.clone() * f_mu - + v_ace_word * f_ace_read - + v_ace_element * f_ace_eval - + v_cap_prev * f_logprecompile.clone() - + (one_ef.clone() - request_flag_sum); - - let response_flag_sum = f_mv.clone() + f_logprecompile.clone(); - let responses: AB::ExprEF = - v_sibling * f_mv + v_cap_next * f_logprecompile + (one_ef - response_flag_sum); - - // Running product constraint: p' * requests = p * responses - let p_local_ef: AB::ExprEF = p_local.into(); - let p_next_ef: AB::ExprEF = p_next.into(); - - builder - .when_transition() - .assert_zero_ext(p_next_ef * requests - p_local_ef * responses); -} - -// INTERNAL HELPERS -// ================================================================================================ - -/// Sibling at h[4..8] (b=0): positions [1, 2, 7, 8, 9, 10]. -/// Position 1 = mrupdate_id, position 2 = node_index, positions 7-10 = sibling (rate1). -const SIBLING_B0_LAYOUT: [usize; 6] = [1, 2, 7, 8, 9, 10]; - -/// Sibling at h[0..4] (b=1): positions [1, 2, 3, 4, 5, 6]. -/// Position 1 = mrupdate_id, position 2 = node_index, positions 3-6 = sibling (rate0). -const SIBLING_B1_LAYOUT: [usize; 6] = [1, 2, 3, 4, 5, 6]; - -/// Compute sibling value when b=0 (sibling at h[4..8], i.e., rate1). -/// -/// Message: `bus_prefix[SIBLING_TABLE] + beta[1]*mrupdate_id + beta[2]*node_index + -/// beta[7..11]*h[4..8]` -fn compute_sibling_b0( - challenges: &Challenges, - mrupdate_id: &AB::Expr, - node_index: &AB::Expr, - h: &[AB::Expr; 12], -) -> AB::ExprEF -where - AB: LiftedAirBuilder, -{ - challenges.encode_sparse( - bus_types::SIBLING_TABLE, - SIBLING_B0_LAYOUT, - [ - mrupdate_id.clone(), - node_index.clone(), - h[4].clone(), - h[5].clone(), - h[6].clone(), - h[7].clone(), - ], - ) -} - -/// Compute sibling value when b=1 (sibling at h[0..4], i.e., rate0). -/// -/// Message: `bus_prefix[SIBLING_TABLE] + beta[1]*mrupdate_id + beta[2]*node_index + -/// beta[3..7]*h[0..4]` -fn compute_sibling_b1( - challenges: &Challenges, - mrupdate_id: &AB::Expr, - node_index: &AB::Expr, - h: &[AB::Expr; 12], -) -> AB::ExprEF -where - AB: LiftedAirBuilder, -{ - challenges.encode_sparse( - bus_types::SIBLING_TABLE, - SIBLING_B1_LAYOUT, - [ - mrupdate_id.clone(), - node_index.clone(), - h[0].clone(), - h[1].clone(), - h[2].clone(), - h[3].clone(), - ], - ) -} diff --git a/air/src/constraints/chiplets/bus/mod.rs b/air/src/constraints/chiplets/bus/mod.rs deleted file mode 100644 index fd57e4d396..0000000000 --- a/air/src/constraints/chiplets/bus/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Chiplets bus constraints. -//! -//! This module groups auxiliary (bus) constraints associated with chiplets. -//! Currently implemented: -//! - b_hash_kernel: hash-kernel virtual table bus -//! - b_chiplets: main chiplets communication bus -//! - b_wiring: ACE wiring bus + memory range checks + hasher perm-link - -pub mod chiplets; -pub mod hash_kernel; -pub mod wiring; - -use super::selectors::ChipletSelectors; -use crate::{MainCols, MidenAirBuilder, constraints::op_flags::OpFlags, trace::Challenges}; - -/// Enforces chiplets bus constraints. -pub fn enforce_bus( - builder: &mut AB, - local: &MainCols, - next: &MainCols, - op_flags: &OpFlags, - challenges: &Challenges, - selectors: &ChipletSelectors, -) where - AB: MidenAirBuilder, -{ - hash_kernel::enforce_hash_kernel_constraint( - builder, local, next, op_flags, challenges, selectors, - ); - chiplets::enforce_chiplets_bus_constraint( - builder, local, next, op_flags, challenges, selectors, - ); - wiring::enforce_wiring_bus_constraint(builder, local, next, challenges, selectors); -} diff --git a/air/src/constraints/chiplets/bus/wiring.rs b/air/src/constraints/chiplets/bus/wiring.rs deleted file mode 100644 index 3be78b13d4..0000000000 --- a/air/src/constraints/chiplets/bus/wiring.rs +++ /dev/null @@ -1,394 +0,0 @@ -//! Wiring bus constraints (v_wiring). -//! -//! This module enforces the running-sum constraints for the shared v_wiring LogUp column. -//! The column carries contributions from three stacked chiplet regions: -//! -//! 1. **ACE wiring**: tracks node definitions and consumptions in the ACE circuit. -//! 2. **Memory range checks**: verifies w0, w1, 4*w1 are 16-bit via LogUp lookups. -//! 3. **Hasher perm-link**: links hasher controller rows to hasher permutation segment. -//! -//! ## Design -//! -//! Since the chiplet regions are stacked (mutually exclusive selectors), three separate -//! additive constraints gate each region's accumulation formula: -//! -//! ```text -//! ace_flag * (delta * D_ace - N_ace) = 0 -//! memory_flag * (delta * D_mem + N_mem) = 0 -//! hasher_flag * (delta * D_perm - N_perm) + idle_flag * delta = 0 -//! ``` -//! -//! The `idle_flag * delta` term is important as on bitwise, kernel-ROM, and padding rows, none of -//! the stacked `v_wiring` contributors are active, but the accumulator must still propagate -//! unchanged so its last-row boundary value remains bound to the earlier accumulation. - -use core::borrow::Borrow; - -use miden_core::field::PrimeCharacteristicRing; -use miden_crypto::stark::air::{ExtensionBuilder, LiftedAirBuilder, WindowAccess}; - -use crate::{ - Felt, MainCols, MidenAirBuilder, - constraints::{ - bus::indices::V_WIRING, - chiplets::{columns::PeriodicCols, selectors::ChipletSelectors}, - }, - trace::{ - CHIPLETS_OFFSET, Challenges, bus_types, - chiplets::{ - HASHER_NODE_INDEX_COL_IDX, HASHER_STATE_COL_RANGE, MEMORY_WORD_ADDR_HI_COL_IDX, - MEMORY_WORD_ADDR_LO_COL_IDX, - ace::{ - CLK_IDX, CTX_IDX, ID_0_IDX, ID_1_IDX, ID_2_IDX, M_0_IDX, M_1_IDX, - SELECTOR_BLOCK_IDX, V_0_0_IDX, V_0_1_IDX, V_1_0_IDX, V_1_1_IDX, V_2_0_IDX, - V_2_1_IDX, - }, - }, - }, -}; - -// CONSTANTS -// ================================================================================================ - -const ACE_OFFSET: usize = 4; - -// ENTRY POINT -// ================================================================================================ - -/// Enforces the wiring bus constraints for all chiplet regions sharing V_WIRING. -/// -/// Three separate additive constraints, one per stacked region: -/// ```text -/// ace_flag * (delta * D_ace - N_ace) = 0 -/// memory_flag * (delta * D_mem + N_mem) = 0 -/// hasher_flag * (delta * D_perm - N_perm) + idle_flag * delta = 0 -/// ``` -/// -/// Each flag selects the correct accumulation formula for its row type. On idle rows -/// (bitwise, kernel-ROM, and padding), all stacked contributors are inactive, so the -/// `idle_flag * delta` term forces the shared accumulator to propagate unchanged. -pub fn enforce_wiring_bus_constraint( - builder: &mut AB, - local: &MainCols, - _next: &MainCols, - challenges: &Challenges, - selectors: &ChipletSelectors, -) where - AB: MidenAirBuilder, -{ - // --- Auxiliary trace access --- - let (v_local, v_next) = { - let aux = builder.permutation(); - let aux_local = aux.current_slice(); - let aux_next = aux.next_slice(); - (aux_local[V_WIRING], aux_next[V_WIRING]) - }; - - let v_local_ef: AB::ExprEF = v_local.into(); - let v_next_ef: AB::ExprEF = v_next.into(); - let delta = v_next_ef - v_local_ef; - - // --- Periodic columns for hasher cycle detection --- - // Row 0 = is_init_ext. Row 15 (boundary) = 1 - selector_sum. - let (p_cycle_row_0, p_cycle_row_boundary) = { - let periodic: &PeriodicCols = builder.periodic_values().borrow(); - let row_0: AB::Expr = periodic.hasher.is_init_ext.into(); - let selector_sum: AB::Expr = Into::::into(periodic.hasher.is_init_ext) - + Into::::into(periodic.hasher.is_ext) - + Into::::into(periodic.hasher.is_packed_int) - + Into::::into(periodic.hasher.is_int_ext); - let row_boundary = AB::Expr::ONE - selector_sum; - (row_0, row_boundary) - }; - - // --- Chiplet region flags (from precomputed ChipletSelectors) --- - // hasher_flag covers both controller and permutation rows (their selector products - // are mutually exclusive, so addition gives the union). ace_flag and memory_flag - // come directly from the precomputed `is_active` selector products. - let ace_flag = selectors.ace.is_active.clone(); - let memory_flag = selectors.memory.is_active.clone(); - let hasher_flag = - selectors.controller.is_active.clone() + selectors.permutation.is_active.clone(); - let idle_flag = AB::Expr::ONE - ace_flag.clone() - memory_flag.clone() - hasher_flag.clone(); - - // --- ACE term --- - let ace_term = compute_ace_term::(&delta, ace_flag, local, challenges); - - // --- Memory term --- - let mem_term = compute_memory_term::(&delta, memory_flag, local, challenges); - - // --- Hasher perm-link + idle propagation term --- - let perm_link_term = compute_hasher_perm_link_term::( - &delta, - hasher_flag, - idle_flag, - selectors.controller.is_active.clone(), - selectors.permutation.is_active.clone(), - local, - challenges, - p_cycle_row_0, - p_cycle_row_boundary, - ); - - // --- Three separate constraints --- - builder.when_transition().assert_zero_ext(ace_term); - builder.when_transition().assert_zero_ext(mem_term); - builder.when_transition().assert_zero_ext(perm_link_term); -} - -// ACE TERM -// ================================================================================================ - -/// Computes the ACE wiring contribution: -/// `ace_flag * (delta * D_ace - N_ace)` -fn compute_ace_term( - delta: &AB::ExprEF, - ace_flag: AB::Expr, - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF -where - AB: MidenAirBuilder, -{ - // Block selector: sblock = 0 for READ, sblock = 1 for EVAL - let sblock: AB::Expr = load_ace_col::(local, SELECTOR_BLOCK_IDX); - let is_read = AB::Expr::ONE - sblock.clone(); - let is_eval = sblock; - - // Load ACE columns - let clk: AB::Expr = load_ace_col::(local, CLK_IDX); - let ctx: AB::Expr = load_ace_col::(local, CTX_IDX); - let wire_0 = encode_wire::( - challenges, - &clk, - &ctx, - &load_ace_wire::(local, ID_0_IDX, V_0_0_IDX, V_0_1_IDX), - ); - let wire_1 = encode_wire::( - challenges, - &clk, - &ctx, - &load_ace_wire::(local, ID_1_IDX, V_1_0_IDX, V_1_1_IDX), - ); - let wire_2 = encode_wire::( - challenges, - &clk, - &ctx, - &load_ace_wire::(local, ID_2_IDX, V_2_0_IDX, V_2_1_IDX), - ); - let m0: AB::Expr = load_ace_col::(local, M_0_IDX); - let m1: AB::Expr = load_ace_col::(local, M_1_IDX); - - // Common denominator - let d_ace = wire_0.clone() * wire_1.clone() * wire_2.clone(); - - // Numerator (not gated by ace_flag -- the outer gate handles it) - // READ: m0 * w1 * w2 + m1 * w0 * w2 - // EVAL: m0 * w1 * w2 - w0 * w2 - w0 * w1 - let read_terms = - wire_1.clone() * wire_2.clone() * m0.clone() + wire_0.clone() * wire_2.clone() * m1; - let eval_terms = - wire_1.clone() * wire_2.clone() * m0 - wire_0.clone() * wire_2 - wire_0 * wire_1; - - let n_ace = read_terms * is_read + eval_terms * is_eval; - - // ace_flag * (delta * D_ace - N_ace) - (delta.clone() * d_ace - n_ace) * ace_flag -} - -// MEMORY TERM -// ================================================================================================ - -/// Computes the memory range check contribution. -/// -/// This is a SEPARATE constraint from the ACE wiring, using its own delta from the -/// V_WIRING aux column. It subtracts 3 LogUp fractions per memory row: -/// 1/(prefix+w0) + 1/(prefix+w1) + 1/(prefix+4w1). -/// -/// Uses `bus_prefix[RANGE_CHECK_BUS]` to match the range checker's encoding. -fn compute_memory_term( - delta: &AB::ExprEF, - memory_flag: AB::Expr, - local: &MainCols, - challenges: &Challenges, -) -> AB::ExprEF -where - AB: MidenAirBuilder, -{ - let prefix = &challenges.bus_prefix[bus_types::RANGE_CHECK_BUS]; - - // Load word-index limbs - let w0: AB::Expr = local.chiplets[MEMORY_WORD_ADDR_LO_COL_IDX - CHIPLETS_OFFSET].into(); - let w1: AB::Expr = local.chiplets[MEMORY_WORD_ADDR_HI_COL_IDX - CHIPLETS_OFFSET].into(); - let w1_mul4: AB::Expr = w1.clone() * AB::Expr::from_u16(4); - - let den0: AB::ExprEF = prefix.clone() + Into::::into(w0); - let den1: AB::ExprEF = prefix.clone() + Into::::into(w1); - let den2: AB::ExprEF = prefix.clone() + Into::::into(w1_mul4); - - // Common denominator and numerator - let common_den = den0.clone() * den1.clone() * den2.clone(); - let rhs = den1.clone() * den2.clone() + den0.clone() * den2 + den0 * den1; - - // memory_flag * (delta * common_den + rhs) = 0 - let memory_flag_ef: AB::ExprEF = memory_flag.into(); - (delta.clone() * common_den + rhs) * memory_flag_ef -} - -// HASHER PERM-LINK TERM -// ================================================================================================ - -/// Computes the hasher perm-link contribution to the wiring bus and enforces idle propagation. -/// -/// This links hasher controller rows (dispatch) to hasher permutation segment (compute): -/// - Hasher controller input (s_perm=0, s0=1): +1/msg_in -/// - Hasher controller output (s_perm=0, s0=0, s1=0): +1/msg_out -/// - Hasher permutation cycle row 0 (`is_init_ext = 1`): -m/msg_in -/// - Hasher permutation boundary row (cycle row 15, i.e. `s_perm=1` and all row-type selectors are -/// 0): -m/msg_out -/// - Idle bitwise / kernel-ROM / padding rows: `delta = 0` -/// -/// Common-denominator form: -/// ```text -/// hasher_flag * (delta * msg_in * msg_out -/// - msg_out * (f_in - f_p_in * m) -/// - msg_in * (f_out - f_p_out * m)) -/// + idle_flag * delta = 0 -/// ``` -fn compute_hasher_perm_link_term( - delta: &AB::ExprEF, - hasher_flag: AB::Expr, - idle_flag: AB::Expr, - ctrl_is_active: AB::Expr, - perm_is_active: AB::Expr, - local: &MainCols, - challenges: &Challenges, - p_cycle_row_0: AB::Expr, - p_cycle_row_boundary: AB::Expr, -) -> AB::ExprEF -where - AB: MidenAirBuilder, -{ - // --- Load hasher-internal sub-selectors (only meaningful on controller rows) --- - // On controller rows: chiplets[1] = s0 (input flag), chiplets[2] = s1. - let s0: AB::Expr = local.chiplets[1].into(); - let s1: AB::Expr = local.chiplets[2].into(); - - // node_index (= multiplicity on perm segment rows) - let m: AB::Expr = local.chiplets[HASHER_NODE_INDEX_COL_IDX - CHIPLETS_OFFSET].into(); - - // --- Flags --- - let one = AB::Expr::ONE; - - // f_in: controller input row (s_ctrl=1, s0=1) - let f_in = ctrl_is_active.clone() * s0.clone(); - - // f_out: controller output row (s_ctrl=1, s0=0, s1=0) - let f_out = ctrl_is_active * (one.clone() - s0) * (one - s1); - - // f_p_in: packed permutation row 0 (s_perm=1 * is_init_ext=1) - let f_p_in = perm_is_active.clone() * p_cycle_row_0; - - // f_p_out: perm boundary row (s_perm=1 * cycle boundary) - let f_p_out = perm_is_active * p_cycle_row_boundary; - - // --- Messages --- - // msg = challenges.encode(HASHER_PERM_LINK, [label, h0, h1, ..., h11]) -- 13 elements - let msg_in = encode_perm_link_message::(local, challenges, AB::Expr::ZERO); - let msg_out = encode_perm_link_message::(local, challenges, AB::Expr::ONE); - - // --- Common-denominator constraint --- - // hasher_flag * (delta * msg_in * msg_out - // - msg_out * (f_in - f_p_in * m) - // - msg_in * (f_out - f_p_out * m)) = 0 - let f_in_ef: AB::ExprEF = f_in.into(); - let f_out_ef: AB::ExprEF = f_out.into(); - let f_p_in_m: AB::ExprEF = (f_p_in * m.clone()).into(); - let f_p_out_m: AB::ExprEF = (f_p_out * m).into(); - - let perm_link_term = delta.clone() * msg_in.clone() * msg_out.clone() - - msg_out * (f_in_ef - f_p_in_m) - - msg_in * (f_out_ef - f_p_out_m); - - let idle_term: AB::ExprEF = delta.clone() * Into::::into(idle_flag); - - perm_link_term * hasher_flag + idle_term -} - -/// Encodes a perm-link message on the dedicated `HASHER_PERM_LINK` bus: `[label, h0, ..., h11]`. -fn encode_perm_link_message( - local: &MainCols, - challenges: &Challenges, - label: AB::Expr, -) -> AB::ExprEF -where - AB: MidenAirBuilder, -{ - let h_start = HASHER_STATE_COL_RANGE.start - CHIPLETS_OFFSET; - challenges.encode( - bus_types::HASHER_PERM_LINK, - [ - label, - local.chiplets[h_start].into(), - local.chiplets[h_start + 1].into(), - local.chiplets[h_start + 2].into(), - local.chiplets[h_start + 3].into(), - local.chiplets[h_start + 4].into(), - local.chiplets[h_start + 5].into(), - local.chiplets[h_start + 6].into(), - local.chiplets[h_start + 7].into(), - local.chiplets[h_start + 8].into(), - local.chiplets[h_start + 9].into(), - local.chiplets[h_start + 10].into(), - local.chiplets[h_start + 11].into(), - ], - ) -} - -// INTERNAL HELPERS -// ================================================================================================ - -struct AceWire { - id: Expr, - v0: Expr, - v1: Expr, -} - -fn load_ace_wire( - row: &MainCols, - id_idx: usize, - v0_idx: usize, - v1_idx: usize, -) -> AceWire -where - AB: LiftedAirBuilder, -{ - AceWire { - id: load_ace_col::(row, id_idx), - v0: load_ace_col::(row, v0_idx), - v1: load_ace_col::(row, v1_idx), - } -} - -fn encode_wire( - challenges: &Challenges, - clk: &AB::Expr, - ctx: &AB::Expr, - wire: &AceWire, -) -> AB::ExprEF -where - AB: LiftedAirBuilder, -{ - challenges.encode( - bus_types::ACE_WIRING_BUS, - [clk.clone(), ctx.clone(), wire.id.clone(), wire.v0.clone(), wire.v1.clone()], - ) -} - -fn load_ace_col(row: &MainCols, ace_col_idx: usize) -> AB::Expr -where - AB: LiftedAirBuilder, -{ - let local_idx = ACE_OFFSET + ace_col_idx; - row.chiplets[local_idx].into() -} diff --git a/air/src/constraints/chiplets/columns.rs b/air/src/constraints/chiplets/columns.rs index 35b11c7b6b..c0e687f73d 100644 --- a/air/src/constraints/chiplets/columns.rs +++ b/air/src/constraints/chiplets/columns.rs @@ -183,7 +183,7 @@ impl ControllerCols { /// Merkle-update new-path flag: `s0 * s1 * s2`. /// /// Active on controller input rows that insert the new Merkle path into the sibling - /// table (request/remove side of the running product). + /// table (request/remove side of the sibling bus). pub fn f_mu(&self) -> E where T: Into, @@ -194,7 +194,7 @@ impl ControllerCols { /// Merkle-verify / old-path flag: `s0 * s1 * (1 - s2)`. /// /// Active on controller input rows that extract the old Merkle path from the sibling - /// table (response/add side of the running product). + /// table (response/add side of the sibling bus). pub fn f_mv(&self) -> E where T: Into, @@ -424,8 +424,8 @@ const _: () = { /// Kernel ROM chiplet columns (5 columns), viewed from `chiplets[5..10]`. #[repr(C)] pub struct KernelRomCols { - /// First-row-of-hash flag. - pub s_first: T, + /// Number of SYSCALLs to this procedure (CALL-label multiplicity). + pub multiplicity: T, /// Kernel procedure root digest. pub root: [T; WORD_SIZE], } diff --git a/air/src/constraints/chiplets/kernel_rom.rs b/air/src/constraints/chiplets/kernel_rom.rs deleted file mode 100644 index 8509b1de7e..0000000000 --- a/air/src/constraints/chiplets/kernel_rom.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! Kernel ROM chiplet constraints. -//! -//! The kernel ROM chiplet exposes the digest table used by kernel (system) calls. -//! This module only enforces shape constraints (binary selectors, digest contiguity, and the -//! start-of-block marker). Validity of syscall selection is enforced by decoder and chiplets' bus. -//! -//! ## Column layout (5 columns within chiplet) -//! -//! | Column | Purpose | -//! |---------|---------------------------------------------------| -//! | sfirst | 1 = first row of digest block, 0 = continuation | -//! | r0-r3 | 4-element kernel procedure digest | -//! -//! ## Operations -//! -//! - **KERNELPROCINIT** (sfirst=1): first row of a digest block (public input digests) -//! - **KERNELPROCCALL** (sfirst=0): continuation rows used by SYSCALL -//! -//! ## Constraints -//! -//! 1. sfirst must be binary -//! 2. Digest contiguity: when sfirst'=0 and s4'=0, digest values stay the same -//! 3. First row: when entering kernel ROM, sfirst' must be 1 - -use miden_crypto::stark::air::AirBuilder; - -use super::selectors::ChipletFlags; -use crate::{MainCols, MidenAirBuilder, constraints::utils::BoolNot}; - -// ENTRY POINTS -// ================================================================================================ - -/// Enforce kernel ROM chiplet constraints. -pub fn enforce_kernel_rom_constraints( - builder: &mut AB, - local: &MainCols, - next: &MainCols, - flags: &ChipletFlags, -) where - AB: MidenAirBuilder, -{ - let krom = local.kernel_rom(); - let krom_next = next.kernel_rom(); - - // ========================================================================== - // SELECTOR CONSTRAINT - // ========================================================================== - - // sfirst must be binary - builder.when(flags.is_active.clone()).assert_bool(krom.s_first); - - // ========================================================================== - // DIGEST CONTIGUITY CONSTRAINTS - // ========================================================================== - - // In all rows but last, ensure that the digest repeats except when the next - // row is the start of a new digest (i.e., s_first' = 0) - { - let gate = flags.is_transition.clone() * krom_next.s_first.into().not(); - let builder = &mut builder.when(gate); - - builder.assert_eq(krom_next.root[0], krom.root[0]); - builder.assert_eq(krom_next.root[1], krom.root[1]); - builder.assert_eq(krom_next.root[2], krom.root[2]); - builder.assert_eq(krom_next.root[3], krom.root[3]); - } - - // ========================================================================== - // FIRST ROW CONSTRAINT - // ========================================================================== - - // First row of kernel ROM must have sfirst' = 1. - // Uses the precomputed next_is_first flag to detect ACE→KernelROM boundary. - builder.when(flags.next_is_first.clone()).assert_one(krom_next.s_first); -} diff --git a/air/src/constraints/chiplets/mod.rs b/air/src/constraints/chiplets/mod.rs index da01faa4f5..37d3eb57a2 100644 --- a/air/src/constraints/chiplets/mod.rs +++ b/air/src/constraints/chiplets/mod.rs @@ -7,16 +7,15 @@ //! - bitwise chiplet main-trace constraints //! - memory chiplet main-trace constraints //! - ACE chiplet main-trace constraints -//! - kernel ROM chiplet main-trace constraints //! -//! Chiplet bus constraints are enforced in the `chiplets::bus` module. +//! Chiplet LogUp lookup-argument constraints are emitted by +//! [`crate::constraints::lookup::chiplet_air::ChipletLookupAir`] and wired through +//! `ProcessorAir`'s `LookupAir` impl from `ProcessorAir::eval`. pub mod ace; pub mod bitwise; -pub mod bus; pub mod columns; pub mod hasher_control; -pub mod kernel_rom; pub mod memory; pub mod permutation; pub mod selectors; @@ -47,5 +46,4 @@ pub fn enforce_main( bitwise::enforce_bitwise_constraints(builder, local, next, &selectors.bitwise); memory::enforce_memory_constraints(builder, local, next, &selectors.memory); ace::enforce_ace_constraints_all_rows(builder, local, next, &selectors.ace); - kernel_rom::enforce_kernel_rom_constraints(builder, local, next, &selectors.kernel_rom); } diff --git a/air/src/constraints/chiplets/selectors.rs b/air/src/constraints/chiplets/selectors.rs index 571cff7104..80c12cc4c2 100644 --- a/air/src/constraints/chiplets/selectors.rs +++ b/air/src/constraints/chiplets/selectors.rs @@ -85,7 +85,6 @@ pub struct ChipletSelectors { pub bitwise: ChipletFlags, pub memory: ChipletFlags, pub ace: ChipletFlags, - pub kernel_rom: ChipletFlags, } // ENTRY POINT @@ -200,7 +199,7 @@ where builder.when(s01.clone()).assert_eq(s1_next.clone(), s1.clone()); builder.when(s012.clone()).assert_eq(s2_next.clone(), s2.clone()); builder.when(s0123.clone()).assert_eq(s3_next.clone(), s3.clone()); - builder.when(s01234.clone()).assert_eq(s4_next.clone(), s4.clone()); + builder.when(s01234).assert_eq(s4_next, s4.clone()); } // ========================================================================= @@ -228,7 +227,6 @@ where let not_s1_next = s1_next.not(); let not_s2_next = s2_next.not(); let not_s3_next = s3_next.not(); - let not_s4_next = s4_next.not(); let is_transition_flag: AB::Expr = builder.is_transition(); @@ -254,20 +252,17 @@ where // --- Remaining chiplet active flags (subtraction trick: prefix - prefix * s_n) --- let is_bitwise = s0.clone() - s01.clone(); let is_memory = s01.clone() - s012.clone(); - let is_ace = s012.clone() - s0123.clone(); - let is_kernel_rom = s0123.clone() - s01234; + let is_ace = s012.clone() - s0123; // --- Remaining chiplet last-row flags: is_active * s_n' --- let is_bitwise_last = is_bitwise.clone() * s1_next; let is_memory_last = is_memory.clone() * s2_next; let is_ace_last = is_ace.clone() * s3_next; - let is_kernel_rom_last = is_kernel_rom.clone() * s4_next; // --- Remaining chiplet next-is-first flags: is_last[n-1] * (1 - s_n') --- let next_is_bitwise_first = perm_is_last.clone() * not_s1_next.clone(); let next_is_memory_first = is_bitwise_last.clone() * not_s2_next.clone(); let next_is_ace_first = is_memory_last.clone() * not_s3_next.clone(); - let next_is_kernel_rom_first = is_ace_last.clone() * not_s4_next.clone(); // --- Remaining chiplet transition flags --- // Each sub-s0 chiplet fires its transition flag when the current row is in that @@ -278,8 +273,7 @@ where // always 1 whenever the prefix is active. let bitwise_transition = is_transition_flag.clone() * s0 * not_s1_next; let memory_transition = is_transition_flag.clone() * s01 * not_s2_next; - let ace_transition = is_transition_flag.clone() * s012 * not_s3_next; - let kernel_rom_transition = is_transition_flag * s0123 * not_s4_next; + let ace_transition = is_transition_flag * s012 * not_s3_next; ChipletSelectors { controller: ChipletFlags { @@ -312,11 +306,5 @@ where is_last: is_ace_last, next_is_first: next_is_ace_first, }, - kernel_rom: ChipletFlags { - is_active: is_kernel_rom, - is_transition: kernel_rom_transition, - is_last: is_kernel_rom_last, - next_is_first: next_is_kernel_rom_first, - }, } } diff --git a/air/src/constraints/columns.rs b/air/src/constraints/columns.rs index 6f158e14cf..b8e5684ebb 100644 --- a/air/src/constraints/columns.rs +++ b/air/src/constraints/columns.rs @@ -20,7 +20,7 @@ use super::{ stack::columns::StackCols, system::columns::SystemCols, }; -use crate::trace::{AUX_TRACE_WIDTH, CHIPLETS_WIDTH, TRACE_WIDTH}; +use crate::trace::{CHIPLETS_WIDTH, TRACE_WIDTH}; // MAIN TRACE COLUMN STRUCT // ================================================================================================ @@ -145,42 +145,14 @@ pub const MAIN_COL_MAP: MainCols = { unsafe { core::mem::transmute(indices_arr::()) } }; -// AUXILIARY TRACE COLUMN STRUCT -// ================================================================================================ - -/// Column layout of the auxiliary execution trace (8 columns). -#[repr(C)] -pub struct AuxCols { - /// Decoder: block stack table running product. - pub p1_block_stack: T, - /// Decoder: block hash table running product. - pub p2_block_hash: T, - /// Decoder: op group table running product. - pub p3_op_group: T, - /// Stack overflow running product. - pub stack_overflow: T, - /// Range checker LogUp sum. - pub range_check: T, - /// Hash-kernel virtual table bus. - pub hash_kernel_vtable: T, - /// Chiplets bus running product. - pub chiplets_bus: T, - /// ACE wiring LogUp sum. - pub ace_wiring: T, -} - -/// Number of columns in the auxiliary trace (8), derived from the struct layout. -pub const NUM_AUX_COLS: usize = size_of::>(); - -/// Compile-time index map for auxiliary columns. -#[allow(dead_code)] -pub const AUX_COL_MAP: AuxCols = { - assert!(NUM_AUX_COLS == AUX_TRACE_WIDTH); - unsafe { core::mem::transmute(indices_arr::()) } -}; - // COLUMN COUNTS // ================================================================================================ +// +// The auxiliary trace is now the LogUp lookup-argument segment built by +// [`crate::ProcessorAir`]'s `AuxBuilder` impl (see `air/src/constraints/lookup/`). +// Its 7-column layout is described entirely by `ProcessorAir::column_shape`; the +// legacy `AuxCols` struct (which mirrored the multiset bus offsets) was removed in +// Milestone B alongside the multiset bus deletion. pub const NUM_SYSTEM_COLS: usize = size_of::>(); pub const NUM_DECODER_COLS: usize = size_of::>(); @@ -194,7 +166,6 @@ pub const NUM_ACE_EVAL_COLS: usize = size_of::>(); pub const NUM_KERNEL_ROM_COLS: usize = size_of::>(); const _: () = assert!(NUM_MAIN_COLS == TRACE_WIDTH); -const _: () = assert!(NUM_AUX_COLS == AUX_TRACE_WIDTH); const _: () = assert!(NUM_SYSTEM_COLS == 6); const _: () = assert!(NUM_DECODER_COLS == 24); const _: () = assert!(NUM_STACK_COLS == 19); @@ -213,9 +184,7 @@ const _: () = assert!(NUM_KERNEL_ROM_COLS == 5); mod tests { use super::*; use crate::trace::{ - ACE_CHIPLET_WIRING_BUS_OFFSET, CHIPLETS_BUS_AUX_TRACE_OFFSET, CHIPLETS_OFFSET, CLK_COL_IDX, - CTX_COL_IDX, DECODER_AUX_TRACE_OFFSET, DECODER_TRACE_OFFSET, FN_HASH_OFFSET, - HASH_KERNEL_VTABLE_AUX_TRACE_OFFSET, RANGE_CHECK_AUX_TRACE_OFFSET, STACK_AUX_TRACE_OFFSET, + CHIPLETS_OFFSET, CLK_COL_IDX, CTX_COL_IDX, DECODER_TRACE_OFFSET, FN_HASH_OFFSET, STACK_TRACE_OFFSET, decoder, range, stack, }; @@ -279,19 +248,4 @@ mod tests { // s_perm is a separate field after chiplets[0..20] assert_eq!(MAIN_COL_MAP.s_perm, CHIPLETS_OFFSET + 20); } - - // --- Auxiliary trace column map vs legacy constants - // ------------------------------------------- - - #[test] - fn aux_col_map() { - assert_eq!(AUX_COL_MAP.p1_block_stack, DECODER_AUX_TRACE_OFFSET); - assert_eq!(AUX_COL_MAP.p2_block_hash, DECODER_AUX_TRACE_OFFSET + 1); - assert_eq!(AUX_COL_MAP.p3_op_group, DECODER_AUX_TRACE_OFFSET + 2); - assert_eq!(AUX_COL_MAP.stack_overflow, STACK_AUX_TRACE_OFFSET); - assert_eq!(AUX_COL_MAP.range_check, RANGE_CHECK_AUX_TRACE_OFFSET); - assert_eq!(AUX_COL_MAP.hash_kernel_vtable, HASH_KERNEL_VTABLE_AUX_TRACE_OFFSET); - assert_eq!(AUX_COL_MAP.chiplets_bus, CHIPLETS_BUS_AUX_TRACE_OFFSET); - assert_eq!(AUX_COL_MAP.ace_wiring, ACE_CHIPLET_WIRING_BUS_OFFSET); - } } diff --git a/air/src/constraints/constants.rs b/air/src/constraints/constants.rs index ab59fbd0f1..c75e5721e1 100644 --- a/air/src/constraints/constants.rs +++ b/air/src/constraints/constants.rs @@ -10,8 +10,6 @@ pub const F_NEG_1: Felt = Felt::NEG_ONE; pub const F_2: Felt = Felt::TWO; pub const F_3: Felt = Felt::new_unchecked(3); pub const F_4: Felt = Felt::new_unchecked(4); -pub const F_5: Felt = Felt::new_unchecked(5); -pub const F_6: Felt = Felt::new_unchecked(6); pub const F_7: Felt = Felt::new_unchecked(7); pub const F_8: Felt = Felt::new_unchecked(8); pub const F_9: Felt = Felt::new_unchecked(9); diff --git a/air/src/constraints/decoder/bus.rs b/air/src/constraints/decoder/bus.rs deleted file mode 100644 index 4b9020c6ad..0000000000 --- a/air/src/constraints/decoder/bus.rs +++ /dev/null @@ -1,798 +0,0 @@ -//! Decoder bus constraints (p1/p2/p3). -//! -//! This module enforces the running‑product relations for the decoder’s three auxiliary tables: -//! - p1: block stack (nesting and call context) -//! - p2: block hash queue (blocks awaiting execution) -//! - p3: op group queue (groups produced by SPAN/RESPAN) -//! -//! ## What is enforced here -//! - The per‑row running‑product equation for each table: `pX' * requests = pX * responses`. -//! - The request/response terms are built from per‑opcode insert/remove messages. -//! -//! ## What is *not* enforced here -//! - Initial/final boundary conditions. The wrapper AIR fixes the first row to the identity element -//! and constrains the last row via `aux_finals`. We intentionally do not duplicate those -//! constraints here. -//! -//! ## Message encoding -//! Each table message is encoded as: -//! `alpha + sum_i beta^i * element[i]` -//! This matches the multiset protocol used by the processor. -//! -//! ## References -//! - Processor tables: `processor/src/decoder/aux_trace/block_stack_table.rs` (p1), -//! `processor/src/decoder/aux_trace/block_hash_table.rs` (p2), -//! `processor/src/decoder/aux_trace/op_group_table.rs` (p3). - -use miden_core::field::PrimeCharacteristicRing; -use miden_crypto::stark::air::{ExtensionBuilder, WindowAccess}; - -use crate::{ - Felt, MainCols, MidenAirBuilder, - constraints::{bus::indices::P1_BLOCK_STACK, constants::*, op_flags::OpFlags, utils::BoolNot}, - trace::{ - Challenges, - bus_types::{BLOCK_HASH_TABLE, BLOCK_STACK_TABLE, OP_GROUP_TABLE}, - }, -}; - -// CONSTANTS -// ================================================================================================ - -/// Weights for opcode bit decoding: b0 + 2*b1 + ... + 64*b6. -const OP_BIT_WEIGHTS: [u16; 7] = [1, 2, 4, 8, 16, 32, 64]; - -/// Encoders for block stack table (p1) messages. -struct BlockStackEncoders<'a, AB: MidenAirBuilder> { - challenges: &'a Challenges, -} - -impl<'a, AB: MidenAirBuilder> BlockStackEncoders<'a, AB> { - fn new(challenges: &'a Challenges) -> Self { - Self { challenges } - } - - /// Encodes `[block_id, parent_id, is_loop]`. - fn simple(&self, block_id: &AB::Expr, parent_id: &AB::Expr, is_loop: &AB::Expr) -> AB::ExprEF { - self.challenges - .encode(BLOCK_STACK_TABLE, [block_id.clone(), parent_id.clone(), is_loop.clone()]) - } - - /// Encodes `[block_id, parent_id, is_loop, ctx, depth, overflow, fn_hash[0..4]]`. - fn full( - &self, - block_id: &AB::Expr, - parent_id: &AB::Expr, - is_loop: &AB::Expr, - ctx: &AB::Expr, - depth: &AB::Expr, - overflow: &AB::Expr, - fh: &[AB::Expr; 4], - ) -> AB::ExprEF { - self.challenges.encode( - BLOCK_STACK_TABLE, - [ - block_id.clone(), - parent_id.clone(), - is_loop.clone(), - ctx.clone(), - depth.clone(), - overflow.clone(), - fh[0].clone(), - fh[1].clone(), - fh[2].clone(), - fh[3].clone(), - ], - ) - } -} - -/// Encoder for block hash table (p2) messages. -struct BlockHashEncoder<'a, AB: MidenAirBuilder> { - challenges: &'a Challenges, -} - -impl<'a, AB: MidenAirBuilder> BlockHashEncoder<'a, AB> { - fn new(challenges: &'a Challenges) -> Self { - Self { challenges } - } - - /// Encodes `[parent_id, hash[0..4], is_first_child, is_loop_body]`. - fn encode( - &self, - parent: &AB::Expr, - hash: [&AB::Expr; 4], - first_child: &AB::Expr, - loop_body: &AB::Expr, - ) -> AB::ExprEF { - self.challenges.encode( - BLOCK_HASH_TABLE, - [ - parent.clone(), - hash[0].clone(), - hash[1].clone(), - hash[2].clone(), - hash[3].clone(), - first_child.clone(), - loop_body.clone(), - ], - ) - } -} - -/// Encoder for op group table (p3) messages. -struct OpGroupEncoder<'a, AB: MidenAirBuilder> { - challenges: &'a Challenges, -} - -impl<'a, AB: MidenAirBuilder> OpGroupEncoder<'a, AB> { - fn new(challenges: &'a Challenges) -> Self { - Self { challenges } - } - - /// Encodes `[block_id, group_count, op_value]`. - fn encode(&self, block_id: &AB::Expr, group_count: &AB::Expr, value: &AB::Expr) -> AB::ExprEF { - self.challenges - .encode(OP_GROUP_TABLE, [block_id.clone(), group_count.clone(), value.clone()]) - } -} - -// ENTRY POINTS -// ================================================================================================ - -/// Enforces all decoder bus constraints (p1, p2, p3). -pub fn enforce_bus( - builder: &mut AB, - local: &MainCols, - next: &MainCols, - op_flags: &OpFlags, - challenges: &Challenges, -) where - AB: MidenAirBuilder, -{ - enforce_block_stack_table_constraint(builder, local, next, op_flags, challenges); - enforce_block_hash_table_constraint(builder, local, next, op_flags, challenges); - enforce_op_group_table_constraint(builder, local, next, op_flags, challenges); -} - -// BLOCK STACK TABLE (p1) -// ================================================================================================ - -/// Enforces the block stack table (p1) bus constraint. -/// -/// The block stack table tracks block nesting state. Entries are added when blocks start -/// and removed when blocks end or transition (RESPAN). -/// -/// Context fields are populated as follows: -/// - JOIN/SPLIT/SPAN/DYN/RESPAN: ctx/depth/overflow/fn_hash are zero and is_loop = 0. -/// - LOOP: is_loop = s0 (other context fields still zero). -/// - CALL/SYSCALL: ctx/system_ctx, depth=stack_b0, overflow=stack_b1, fn_hash[0..3]. -/// - DYNCALL: ctx/system_ctx, depth=h4, overflow=h5, fn_hash[0..3]. -/// -/// ## Constraint Structure -/// -/// ```text -/// p1' * (u_end + u_respan + 1 - (f_end + f_respan)) = -/// p1 * (v_join + v_split + v_loop + v_span + v_respan + v_dyn + v_dyncall + v_call + v_syscall -/// + 1 - (f_join + f_split + f_loop + f_span + f_respan + f_dyn + f_dyncall + f_call + f_syscall)) -/// ``` -/// -/// Where: -/// - `v_xxx = f_xxx * message_xxx` (insertion contribution, degree 7+1=8) -/// - `u_xxx = f_xxx * message_xxx` (removal contribution, degree 7+1=8) -/// - Full constraint degree: 1 + 8 = 9 -/// -/// ## Message Format -/// -/// Messages are linear combinations: `alpha[0]*1 + alpha[1]*block_id + alpha[2]*parent_id + ...` -/// - Simple blocks: 4 elements `[1, block_id, parent_id, is_loop]` -/// - CALL/SYSCALL/DYNCALL: 11 elements with context `[..., ctx, fmp, b0, b1, fn_hash[0..4]]` -pub fn enforce_block_stack_table_constraint( - builder: &mut AB, - local: &MainCols, - next: &MainCols, - op_flags: &OpFlags, - challenges: &Challenges, -) where - AB: MidenAirBuilder, -{ - // Auxiliary trace must be present - - // Extract auxiliary trace values - let (p1_local, p1_next) = { - let aux = builder.permutation(); - let aux_local = aux.current_slice(); - let aux_next = aux.next_slice(); - (aux_local[P1_BLOCK_STACK], aux_next[P1_BLOCK_STACK]) - }; - - // ========================================================================= - // TRACE VALUE EXTRACTION - // ========================================================================= - - // Block addresses - let addr_local: AB::Expr = local.decoder.addr.into(); - let addr_next: AB::Expr = next.decoder.addr.into(); - - // Hasher state element 1 (for RESPAN parent_id) - let h1_next: AB::Expr = next.decoder.hasher_state[1].into(); - - // Stack top (for LOOP is_loop condition) - let s0: AB::Expr = local.stack.get(0).into(); - - // Context info for CALL/SYSCALL/DYNCALL insertions (from current row) - let ctx_local: AB::Expr = local.system.ctx.into(); - let b0_local: AB::Expr = local.stack.b0.into(); - let b1_local: AB::Expr = local.stack.b1.into(); - let fn_hash_local: [AB::Expr; 4] = [ - local.system.fn_hash[0].into(), - local.system.fn_hash[1].into(), - local.system.fn_hash[2].into(), - local.system.fn_hash[3].into(), - ]; - - // Hasher state for DYNCALL (h4, h5 contain post-shift stack state) - let h4_local: AB::Expr = local.decoder.hasher_state[4].into(); - let h5_local: AB::Expr = local.decoder.hasher_state[5].into(); - - // Flags for END context detection - let is_loop_flag: AB::Expr = local.decoder.hasher_state[5].into(); - let is_call_flag: AB::Expr = local.decoder.hasher_state[6].into(); - let is_syscall_flag: AB::Expr = local.decoder.hasher_state[7].into(); - - // Context info for END after CALL/SYSCALL (from next row) - let ctx_next: AB::Expr = next.system.ctx.into(); - let b0_next: AB::Expr = next.stack.b0.into(); - let b1_next: AB::Expr = next.stack.b1.into(); - let fn_hash_next: [AB::Expr; 4] = [ - next.system.fn_hash[0].into(), - next.system.fn_hash[1].into(), - next.system.fn_hash[2].into(), - next.system.fn_hash[3].into(), - ]; - // ========================================================================= - // MESSAGE BUILDERS - // ========================================================================= - - let encoders = BlockStackEncoders::::new(challenges); - - // ========================================================================= - // INSERTION CONTRIBUTIONS (v_xxx = f_xxx * message) - // ========================================================================= - - // Operation flags for control-flow instructions. - let is_join = op_flags.join(); - let is_split = op_flags.split(); - let is_span = op_flags.span(); - let is_dyn = op_flags.dyn_op(); - let is_loop = op_flags.loop_op(); - let is_respan = op_flags.respan(); - let is_call = op_flags.call(); - let is_syscall = op_flags.syscall(); - let is_dyncall = op_flags.dyncall(); - let is_end = op_flags.end(); - - // JOIN/SPLIT/SPAN/DYN: insert(addr', addr, 0, 0, 0, 0, 0, 0, 0, 0) - let msg_simple = encoders.simple(&addr_next, &addr_local, &AB::Expr::ZERO); - let v_join = msg_simple.clone() * is_join.clone(); - let v_split = msg_simple.clone() * is_split.clone(); - let v_span = msg_simple.clone() * is_span.clone(); - let v_dyn = msg_simple * is_dyn.clone(); - - // LOOP: insert(addr', addr, s0, 0, 0, 0, 0, 0, 0, 0) - let msg_loop = encoders.simple(&addr_next, &addr_local, &s0); - let v_loop = msg_loop * is_loop.clone(); - - // RESPAN: insert(addr', h1', 0, 0, 0, 0, 0, 0, 0, 0) - let msg_respan_insert = encoders.simple(&addr_next, &h1_next, &AB::Expr::ZERO); - let v_respan = msg_respan_insert * is_respan.clone(); - - // CALL/SYSCALL: insert(addr', addr, 0, ctx, fmp, b0, b1, fn_hash[0..4]) - let msg_call = encoders.full( - &addr_next, - &addr_local, - &AB::Expr::ZERO, - &ctx_local, - &b0_local, - &b1_local, - &fn_hash_local, - ); - let v_call = msg_call.clone() * is_call.clone(); - let v_syscall = msg_call * is_syscall.clone(); - - // DYNCALL: insert(addr', addr, 0, ctx, h4, h5, fn_hash[0..4]) - let msg_dyncall = encoders.full( - &addr_next, - &addr_local, - &AB::Expr::ZERO, - &ctx_local, - &h4_local, - &h5_local, - &fn_hash_local, - ); - let v_dyncall = msg_dyncall * is_dyncall.clone(); - - // Sum of insertion flags - let insert_flag_sum = is_join - + is_split - + is_span - + is_dyn - + is_loop - + is_respan.clone() - + is_call - + is_syscall - + is_dyncall; - - // Total insertion contribution - let insertion_sum = - v_join + v_split + v_span + v_dyn + v_loop + v_respan + v_call + v_syscall + v_dyncall; - - // Response side: insertion_sum + (1 - insert_flag_sum) - let response = insertion_sum + insert_flag_sum.not(); - - // ========================================================================= - // REMOVAL CONTRIBUTIONS (u_xxx = f_xxx * message) - // ========================================================================= - - // RESPAN removal: remove(addr, h1', 0, 0, 0, 0, 0, 0, 0, 0) - let msg_respan_remove = encoders.simple(&addr_local, &h1_next, &AB::Expr::ZERO); - let u_respan = msg_respan_remove * is_respan.clone(); - - // END for simple blocks: remove(addr, addr', is_loop_flag, 0, 0, 0, 0, 0, 0, 0) - let is_simple_end = AB::Expr::ONE - is_call_flag.clone() - is_syscall_flag.clone(); - let msg_end_simple = encoders.simple(&addr_local, &addr_next, &is_loop_flag); - let end_simple_gate = is_end.clone() * is_simple_end; - let u_end_simple = msg_end_simple * end_simple_gate; - - // END for CALL/SYSCALL: remove(addr, addr', is_loop_flag, ctx', b0', b1', fn_hash'[0..4]) - // Note: The is_loop value is the is_loop_flag from the current row (same as simple END) - // Context values come from the next row's dedicated columns (not hasher state) - let is_call_or_syscall = is_call_flag + is_syscall_flag; - let msg_end_call = encoders.full( - &addr_local, - &addr_next, - &is_loop_flag, - &ctx_next, - &b0_next, - &b1_next, - &fn_hash_next, - ); - let end_call_gate = is_end.clone() * is_call_or_syscall; - let u_end_call = msg_end_call * end_call_gate; - - // Total END contribution - let u_end = u_end_simple + u_end_call; - - // Sum of removal flags - let remove_flag_sum = is_end + is_respan; - - // Total removal contribution - let removal_sum = u_end + u_respan; - - // Request side: removal_sum + (1 - remove_flag_sum) - let request = removal_sum + remove_flag_sum.not(); - - // ========================================================================= - // RUNNING PRODUCT CONSTRAINT - // ========================================================================= - - // p1' * request = p1 * response - let lhs: AB::ExprEF = p1_next.into() * request; - let rhs: AB::ExprEF = p1_local.into() * response; - - builder.when_transition().assert_eq_ext(lhs, rhs); -} - -// BLOCK HASH TABLE (p2) -// ================================================================================================ - -/// Enforces the block hash table (p2) bus constraint. -/// -/// The block hash table tracks blocks awaiting execution. The program hash is added at -/// initialization and removed when the program completes. -/// -/// Message layout: `[parent_id, hash[0..3], is_first_child, is_loop_body]`. -/// - JOIN: inserts two children (left and right halves of the hasher state). -/// - SPLIT: inserts one child selected by s0 (left if s0=1, right if s0=0). -/// - LOOP/REPEAT: inserts loop body hash with is_loop_body = 1. -/// - DYN/DYNCALL/CALL/SYSCALL: insert the single child hash from h0..h3. -/// - END: removes the parent hash from h0..h3 using is_first_child/is_loop_body. -/// -/// ## Operations -/// -/// **Responses (additions)**: JOIN (2x), SPLIT, LOOP (conditional), REPEAT, DYN, DYNCALL, CALL, -/// SYSCALL **Requests (removals)**: END -/// -/// ## Message Format -/// -/// `[1, parent_block_id, hash[0], hash[1], hash[2], hash[3], is_first_child, is_loop_body]` -/// -/// ## Constraint Structure -/// -/// ```text -/// p2' * request = p2 * response -/// -/// response = f_join * (msg_left * msg_right) -/// + f_split * msg_split -/// + f_loop * (s0 * msg_loop + (1 - s0)) -/// + f_repeat * msg_repeat -/// + f_dyn * msg_dyn + f_dyncall * msg_dyncall + f_call * msg_call + f_syscall * msg_syscall -/// + (1 - f_join - f_split - f_loop - f_repeat - f_dyn - f_dyncall - f_call - f_syscall) -/// -/// request = f_end * msg_end + (1 - f_end) -/// ``` -pub fn enforce_block_hash_table_constraint( - builder: &mut AB, - local: &MainCols, - next: &MainCols, - op_flags: &OpFlags, - challenges: &Challenges, -) where - AB: MidenAirBuilder, -{ - // Auxiliary trace must be present - - // Extract auxiliary trace values - let (p2_local, p2_next) = { - let aux = builder.permutation(); - let aux_local = aux.current_slice(); - let aux_next = aux.next_slice(); - ( - aux_local[crate::constraints::bus::indices::P2_BLOCK_HASH], - aux_next[crate::constraints::bus::indices::P2_BLOCK_HASH], - ) - }; - - // ========================================================================= - // TRACE VALUE EXTRACTION - // ========================================================================= - - // Parent block ID (next row's address for all insertions) - let parent_id: AB::Expr = next.decoder.addr.into(); - // Hasher state for child hashes - // First half: h[0..4] - let h0: AB::Expr = local.decoder.hasher_state[0].into(); - let h1: AB::Expr = local.decoder.hasher_state[1].into(); - let h2: AB::Expr = local.decoder.hasher_state[2].into(); - let h3: AB::Expr = local.decoder.hasher_state[3].into(); - // Second half: h[4..8] - let h4: AB::Expr = local.decoder.hasher_state[4].into(); - let h5: AB::Expr = local.decoder.hasher_state[5].into(); - let h6: AB::Expr = local.decoder.hasher_state[6].into(); - let h7: AB::Expr = local.decoder.hasher_state[7].into(); - - // Stack top (for SPLIT and LOOP conditions) - let s0: AB::Expr = local.stack.get(0).into(); - - // For END: block hash comes from current row's hasher state first half - let end_parent_id = parent_id.clone(); - let end_hash_0 = h0.clone(); - let end_hash_1 = h1.clone(); - let end_hash_2 = h2.clone(); - let end_hash_3 = h3.clone(); - - // is_loop_body flag for END (stored at hasher_state[4] = IS_LOOP_BODY_FLAG) - let is_loop_body_flag: AB::Expr = local.decoder.hasher_state[4].into(); - - // is_first_child detection for END: - // A block is first_child if the NEXT row's opcode is NOT (END, REPEAT, or HALT). - // From processor: is_first_child = !(next_op in {END, REPEAT, HALT}) - // We compute op flags from the next row and check these three opcodes. - // - // Note: END (112), REPEAT (116), HALT (124) are all degree-4 operations, - // so is_first_child has degree 4. - let is_end_next = op_flags.end_next(); - let is_repeat_next = op_flags.repeat_next(); - let is_halt_next = op_flags.halt_next(); - - // is_first_child = 1 when next op is NOT end/repeat/halt - let is_not_first_child = is_end_next + is_repeat_next + is_halt_next; - let is_first_child = is_not_first_child.not(); - - // ========================================================================= - // MESSAGE BUILDERS - // ========================================================================= - - let encoder = BlockHashEncoder::::new(challenges); - - // ========================================================================= - // OPERATION FLAGS - // ========================================================================= - - let is_join = op_flags.join(); - let is_split = op_flags.split(); - let is_loop = op_flags.loop_op(); - let is_repeat = op_flags.repeat(); - let is_dyn = op_flags.dyn_op(); - let is_dyncall = op_flags.dyncall(); - let is_call = op_flags.call(); - let is_syscall = op_flags.syscall(); - let is_end = op_flags.end(); - - // ========================================================================= - // RESPONSE CONTRIBUTIONS (insertions) - // ========================================================================= - - // JOIN: Insert both children - // Left child (is_first_child=1): hash from first half - let msg_join_left = - encoder.encode(&parent_id, [&h0, &h1, &h2, &h3], &AB::Expr::ONE, &AB::Expr::ZERO); - // Right child (is_first_child=0): hash from second half - let msg_join_right = - encoder.encode(&parent_id, [&h4, &h5, &h6, &h7], &AB::Expr::ZERO, &AB::Expr::ZERO); - let v_join = (msg_join_left * msg_join_right) * is_join.clone(); - - // SPLIT: Insert selected child based on s0 - // If s0=1: left child (h0-h3), else right child (h4-h7) - let not_s0 = s0.not(); - let split_h0 = s0.clone() * h0.clone() + not_s0.clone() * h4; - let split_h1 = s0.clone() * h1.clone() + not_s0.clone() * h5; - let split_h2 = s0.clone() * h2.clone() + not_s0.clone() * h6; - let split_h3 = s0.clone() * h3.clone() + not_s0 * h7; - let msg_split = encoder.encode( - &parent_id, - [&split_h0, &split_h1, &split_h2, &split_h3], - &AB::Expr::ZERO, - &AB::Expr::ZERO, - ); - let v_split = msg_split * is_split.clone(); - - // LOOP: Conditionally insert body if s0=1 - let msg_loop = - encoder.encode(&parent_id, [&h0, &h1, &h2, &h3], &AB::Expr::ZERO, &AB::Expr::ONE); - // When s0=1: insert msg_loop; when s0=0: multiply by 1 (no insertion) - let v_loop = (msg_loop * s0.clone() + (AB::ExprEF::ONE - s0)) * is_loop.clone(); - - // REPEAT: Insert loop body - let msg_repeat = - encoder.encode(&parent_id, [&h0, &h1, &h2, &h3], &AB::Expr::ZERO, &AB::Expr::ONE); - let v_repeat = msg_repeat * is_repeat.clone(); - - // DYN/DYNCALL/CALL/SYSCALL: Insert child hash from first half - let msg_call_like = - encoder.encode(&parent_id, [&h0, &h1, &h2, &h3], &AB::Expr::ZERO, &AB::Expr::ZERO); - let v_dyn = msg_call_like.clone() * is_dyn.clone(); - let v_dyncall = msg_call_like.clone() * is_dyncall.clone(); - let v_call = msg_call_like.clone() * is_call.clone(); - let v_syscall = msg_call_like * is_syscall.clone(); - - // Sum of insertion flags - let insert_flag_sum = - is_join + is_split + is_loop + is_repeat + is_dyn + is_dyncall + is_call + is_syscall; - - // Response side - let response = v_join - + v_split - + v_loop - + v_repeat - + v_dyn - + v_dyncall - + v_call - + v_syscall - + insert_flag_sum.not(); - - // ========================================================================= - // REQUEST CONTRIBUTIONS (removals) - // ========================================================================= - - // END: Remove the block - // is_first_child is computed above from next row's opcode flags - let msg_end = encoder.encode( - &end_parent_id, - [&end_hash_0, &end_hash_1, &end_hash_2, &end_hash_3], - &is_first_child, - &is_loop_body_flag, - ); - let u_end = msg_end * is_end.clone(); - - // Request side - let request = u_end + is_end.not(); - - // ========================================================================= - // RUNNING PRODUCT CONSTRAINT - // ========================================================================= - - // p2' * request = p2 * response - let lhs: AB::ExprEF = p2_next.into() * request; - let rhs: AB::ExprEF = p2_local.into() * response; - - builder.when_transition().assert_eq_ext(lhs, rhs); -} - -// OP GROUP TABLE (p3) -// ================================================================================================ - -/// Enforces the op group table (p3) bus constraint. -/// -/// The op group table tracks operation groups within span blocks. Groups are added -/// when entering a span and removed as operations are executed. -/// -/// Message layout: `[block_id, group_count, op_value]`. -/// - Inserts happen on SPAN/RESPAN. Batch flags choose how many groups are emitted: g1 emits none, -/// g2 emits h1, g4 emits h1..h3, g8 emits h1..h7. -/// - Removals happen when group_count decrements inside a span (sp=1, gc' < gc). The removed -/// op_value is h0' * 128 + opcode' for non-PUSH, or s0' for PUSH. -/// -/// ## Operations -/// -/// **Responses (additions)**: SPAN, RESPAN (based on batch flags) -/// - 8-group batch: Insert h1-h7 (7 groups) -/// - 4-group batch: Insert h1-h3 (3 groups) -/// - 2-group batch: Insert h1 (1 group) -/// - 1-group batch: Insert nothing -/// -/// **Requests (removals)**: When delta_group_count * is_in_span = 1 -/// -/// ## Message Format -/// -/// `[1, block_id, group_count, op_value]` -/// -/// ## Constraint Structure (from docs/src/design/decoder/constraints.md) -/// -/// ```text -/// p3' * (f_dg * u + 1 - f_dg) = p3 * (f_g1 + f_g2 * v_1 + f_g4 * ∏v_1..3 + f_g8 * ∏v_1..7 + 1 - (f_span + f_respan)) -/// ``` -/// -/// Where: -/// - f_dg = sp * (gc - gc') - flag for group removal -/// - u = removal message -/// - f_g1, f_g2, f_g4, f_g8 = batch size flags -/// - v_i = insertion message for group i -/// -/// ## Degree Analysis -/// -/// - f_g8 * prod_7: degree 1 + 7 = 8 -/// - f_g4 * prod_3: degree 3 + 3 = 6 -/// - f_span: degree 6 -/// - f_dg * u: degree 2 + 7 = 9 (u includes is_push which is degree ~5) -/// - Total constraint: degree 9 -pub fn enforce_op_group_table_constraint( - builder: &mut AB, - local: &MainCols, - next: &MainCols, - op_flags: &OpFlags, - challenges: &Challenges, -) where - AB: MidenAirBuilder, -{ - // Auxiliary trace must be present - - // Extract auxiliary trace values - let (p3_local, p3_next) = { - let aux = builder.permutation(); - let aux_local = aux.current_slice(); - let aux_next = aux.next_slice(); - ( - aux_local[crate::constraints::bus::indices::P3_OP_GROUP], - aux_next[crate::constraints::bus::indices::P3_OP_GROUP], - ) - }; - - // ========================================================================= - // TRACE VALUE EXTRACTION - // ========================================================================= - - // Block ID (next row's address for insertions, current for removals) - let block_id_insert: AB::Expr = next.decoder.addr.into(); - let block_id_remove: AB::Expr = local.decoder.addr.into(); - - // Group count - let gc: AB::Expr = local.decoder.group_count.into(); - let gc_next: AB::Expr = next.decoder.group_count.into(); - - // Hasher state for group values (h1-h7, h0 is decoded immediately) - let h1: AB::Expr = local.decoder.hasher_state[1].into(); - let h2: AB::Expr = local.decoder.hasher_state[2].into(); - let h3: AB::Expr = local.decoder.hasher_state[3].into(); - let h4: AB::Expr = local.decoder.hasher_state[4].into(); - let h5: AB::Expr = local.decoder.hasher_state[5].into(); - let h6: AB::Expr = local.decoder.hasher_state[6].into(); - let h7: AB::Expr = local.decoder.hasher_state[7].into(); - - // Batch flag columns (c0, c1, c2) - let c0: AB::Expr = local.decoder.batch_flags[0].into(); - let c1: AB::Expr = local.decoder.batch_flags[1].into(); - let c2: AB::Expr = local.decoder.batch_flags[2].into(); - - // For removal: h0' and s0' from next row - let h0_next: AB::Expr = next.decoder.hasher_state[0].into(); - let s0_next: AB::Expr = next.stack.get(0).into(); - - // is_in_span flag (sp) - let sp = local.decoder.in_span; - - // ========================================================================= - // MESSAGE BUILDER - // ========================================================================= - - let encoder = OpGroupEncoder::::new(challenges); - - // ========================================================================= - // OPERATION FLAGS - // ========================================================================= - - let is_push = op_flags.push(); - - // ========================================================================= - // BATCH FLAGS - // ========================================================================= - - // Compute batch flags from c0, c1, c2 based on trace constants: - // OP_BATCH_8_GROUPS = [1, 0, 0] -> f_g8 = c0 - // OP_BATCH_4_GROUPS = [0, 1, 0] -> f_g4 = (1-c0) * c1 * (1-c2) - // OP_BATCH_2_GROUPS = [0, 0, 1] -> f_g2 = (1-c0) * (1-c1) * c2 - // OP_BATCH_1_GROUPS = [0, 1, 1] -> f_g1 = (1-c0) * c1 * c2 - let f_g8 = c0.clone(); - let not_c0 = c0.not(); - let f_g4 = not_c0.clone() * c1.clone() * c2.not(); - let f_g2 = not_c0 * c1.not() * c2; - - // ========================================================================= - // RESPONSE (insertions during SPAN/RESPAN) - // ========================================================================= - - // Build messages for each group: v_i = msg(block_id', gc - i, h_i) - let v_1 = encoder.encode(&block_id_insert, &(gc.clone() - F_1), &h1); - let v_2 = encoder.encode(&block_id_insert, &(gc.clone() - F_2), &h2); - let v_3 = encoder.encode(&block_id_insert, &(gc.clone() - F_3), &h3); - let v_4 = encoder.encode(&block_id_insert, &(gc.clone() - F_4), &h4); - let v_5 = encoder.encode(&block_id_insert, &(gc.clone() - F_5), &h5); - let v_6 = encoder.encode(&block_id_insert, &(gc.clone() - F_6), &h6); - let v_7 = encoder.encode(&block_id_insert, &(gc.clone() - F_7), &h7); - - // Compute products for each batch size - let prod_3 = v_1.clone() * v_2.clone() * v_3.clone(); - let prod_7 = v_1.clone() * v_2 * v_3 * v_4 * v_5 * v_6 * v_7; - - // Response formula: - // response = f_g2 * v_1 + f_g4 * ∏(v_1..v_3) + f_g8 * ∏(v_1..v_7) + (1 - (f_g2 + f_g4 + f_g8)) - // - // This omits the explicit f_span/f_respan gating in the rest term; it is safe because - // decoder constraints enforce (1 - f_span_respan) * (c0 + c1 + c2) = 0, so all batch - // flags are zero outside SPAN/RESPAN rows. This keeps the max degree at 9 and matches - // the sum-form bus expansion used in air-script. - let response = (v_1 * f_g2.clone()) - + (prod_3 * f_g4.clone()) - + (prod_7 * f_g8.clone()) - + (AB::ExprEF::ONE - (f_g2 + f_g4 + f_g8)); - - // ========================================================================= - // REQUEST (removals when group count decrements inside span) - // ========================================================================= - - // f_dg = sp * (gc - gc') - flag for decrementing group count - // This is non-zero when inside a span (sp=1) and group count decreased - let delta_gc = gc.clone() - gc_next; - let f_dg = sp * delta_gc; - - // Compute op_code' from next row's opcode bits (b0' + 2*b1' + ... + 64*b6'). - let op_code_next = - OP_BIT_WEIGHTS.iter().enumerate().fold(AB::Expr::ZERO, |acc, (i, weight)| { - let bit = next.decoder.op_bits[i]; - acc + bit * Felt::new_unchecked(*weight as u64) - }); - - // Removal value formula: - // u = (h0' * 128 + op_code') * (1 - is_push) + s0' * is_push - // - // When PUSH: the immediate value is on the stack (s0') - // Otherwise: the group value is h0' * 128 + op_code' - let group_value_non_push = h0_next * F_128 + op_code_next; - let group_value = is_push.clone() * s0_next + is_push.not() * group_value_non_push; - - // Removal message: u = msg(block_id, gc, group_value) - let u = encoder.encode(&block_id_remove, &gc, &group_value); - - // Request formula: f_dg * u + (1 - f_dg) - let request = u * f_dg.clone() + f_dg.not(); - - // ========================================================================= - // RUNNING PRODUCT CONSTRAINT - // ========================================================================= - - // p3' * request = p3 * response - let lhs: AB::ExprEF = p3_next.into() * request; - let rhs: AB::ExprEF = p3_local.into() * response; - - builder.when_transition().assert_eq_ext(lhs, rhs); -} diff --git a/air/src/constraints/decoder/mod.rs b/air/src/constraints/decoder/mod.rs index 35d7f88909..17d0c840db 100644 --- a/air/src/constraints/decoder/mod.rs +++ b/air/src/constraints/decoder/mod.rs @@ -81,8 +81,6 @@ use crate::{ trace::chiplets::hasher::CONTROLLER_ROWS_PER_PERM_FELT, }; -pub mod bus; - // ENTRY POINTS // ================================================================================================ diff --git a/air/src/constraints/lookup/buses/block_hash_and_op_group.rs b/air/src/constraints/lookup/buses/block_hash_and_op_group.rs new file mode 100644 index 0000000000..4dd070a4de --- /dev/null +++ b/air/src/constraints/lookup/buses/block_hash_and_op_group.rs @@ -0,0 +1,286 @@ +//! Merged block-hash + op-group column: block-hash queue (G_block_hash) + op-group table +//! (G_op_group) as one mutually-exclusive group. +//! +//! Combines what were previously two separate columns into a single column by recognizing +//! that G_block_hash and G_op_group are mutually exclusive: +//! +//! - **G_block_hash** (block-hash queue) fires only on control-flow opcodes: JOIN, SPLIT, +//! LOOP/REPEAT, DYN/DYNCALL/CALL/SYSCALL, END. +//! - **G_op_group** (op-group table) fires only on SPAN/RESPAN (insertion side) or in-span decode +//! rows (removal side). +//! +//! Control-flow opcodes are never in-span, and SPAN/RESPAN are not in G_block_hash's +//! variant list, so no row fires both buses. The merged group's +//! `(deg(U_g), deg(V_g))` is the elementwise max of the two individual buses: +//! `(max(8, 8), max(6, 7)) = (8, 7)`, giving a column transition of +//! `max(1 + 8, 7) = 9` — the same saturated cost the two original columns had +//! individually, but using **one** column instead of two, saving one accumulator column in +//! `ProcessorAir::num_columns` (LookupAir impl). +//! +//! The emitter uses the plain `col.group` path (no cached encoding) for both buses; the +//! merged group's degree is unchanged under either mode. The cached-encoding optimization +//! can be reintroduced later if symbolic expression growth becomes a bottleneck. + +use core::array; + +use miden_core::field::PrimeCharacteristicRing; + +use crate::{ + constraints::{ + lookup::{ + main_air::{MainBusContext, MainLookupBuilder}, + messages::{BlockHashMsg, OpGroupMsg}, + }, + utils::{BoolNot, horner_eval_bits}, + }, + lookup::{Deg, LookupBatch, LookupColumn, LookupGroup}, +}; + +/// Upper bound on fractions this emitter pushes into its column per row. +/// +/// G_block_hash (control-flow opcodes): largest branch is JOIN's 2-add batch. +/// G_op_group (SPAN/RESPAN insertions + in-span decode removal): largest branch is g8's +/// 7-add batch. Insertions (batch-setup rows) and the removal (in-span decode rows) are +/// mutually exclusive by construction. +/// The module header establishes G_block_hash and G_op_group are row-disjoint — control-flow +/// opcodes are never in-span, and SPAN/RESPAN aren't control-flow. So the per-row max is +/// `max(2, 7) = 7`. +pub(in crate::constraints::lookup) const MAX_INTERACTIONS_PER_ROW: usize = 7; + +/// Emit the merged block-hash queue (G_block_hash) + op-group table (G_op_group) column as a +/// single mutually-exclusive group. +#[allow(clippy::too_many_lines)] +pub(in crate::constraints::lookup) fn emit_block_hash_and_op_group( + builder: &mut LB, + ctx: &MainBusContext, +) where + LB: MainLookupBuilder, +{ + let local = ctx.local; + let next = ctx.next; + let op_flags = &ctx.op_flags; + + let dec = &local.decoder; + let dec_next = &next.decoder; + let stk = &local.stack; + let stk_next = &next.stack; + + // Raw Vars (Copy — captured directly in closures; converted to `LB::Expr` via a typed + // binding at the top of each message-producing closure). + let addr = dec.addr; + let addr_next = dec_next.addr; + // `dec.hasher_state` is `[Var; 8]` — the rate portion of the sponge, split into two + // halves of 4: `h_0 = h[0..4]` (first child) and `h_1 = h[4..8]` (second child). + let h_0: [LB::Var; 4] = array::from_fn(|i| dec.hasher_state[i]); + let h_1: [LB::Var; 4] = array::from_fn(|i| dec.hasher_state[4 + i]); + let s0 = stk.get(0); + + // G_block_hash per-row-type flags. `f_loop_body` / `f_child` are sums of single-use + // op flags, bound locally since each is consumed once inside its `.add(...)` call. + let f_join = op_flags.join(); + let f_split = op_flags.split(); + let f_loop_body = op_flags.loop_op() * s0 + op_flags.repeat(); + let f_child = op_flags.dyn_op() + op_flags.dyncall() + op_flags.call() + op_flags.syscall(); + let f_end = op_flags.end(); + let f_push = op_flags.push(); + + // G_op_group per-batch-size selectors — `c0` is used in three places (g8 gate, g4's + // `(1 - c0)`, g2's `(1 - c0)`), `c1`/`c2` in two places each. Kept as named `LB::Expr` + // bindings per the style rule (used 2+ times). + let c0: LB::Expr = dec.batch_flags[0].into(); + let c1: LB::Expr = dec.batch_flags[1].into(); + let c2: LB::Expr = dec.batch_flags[2].into(); + + // `group_count` is consumed by the 3 insertion batches AND the removal, so 4+ places. + let gc: LB::Expr = dec.group_count.into(); + + // G_op_group removal flag: `in_span · (gc - gc_next)`. Computed once outside the + // removal closure because it's the `flag` argument of `g.remove`, not part of the + // message construction. + let in_span: LB::Expr = dec.in_span.into(); + let gc_next: LB::Expr = dec_next.group_count.into(); + let f_rem = in_span * (gc.clone() - gc_next); + + builder.next_column( + |col| { + col.group( + "merged_interactions", + |g| { + // =================== G_block_hash BLOCK HASH QUEUE =================== + + // JOIN: two children — `h_0` first, `h_1` second. + g.batch( + "join", + f_join, + |b| { + let parent: LB::Expr = addr_next.into(); + let first_hash = h_0.map(LB::Expr::from); + let second_hash = h_1.map(LB::Expr::from); + b.add( + "join_first_child", + BlockHashMsg::FirstChild { + parent: parent.clone(), + child_hash: first_hash, + }, + Deg { n: 5, d: 6 }, + ); + b.add( + "join_second_child", + BlockHashMsg::Child { parent, child_hash: second_hash }, + Deg { n: 5, d: 6 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + + // SPLIT: `s0`-muxed selection between `h_0` and `h_1`. + g.add( + "split", + f_split, + || { + let parent = addr_next.into(); + let s0: LB::Expr = s0.into(); + let one_minus_s0 = s0.not(); + let child_hash = array::from_fn(|i| { + s0.clone() * h_0[i].into() + one_minus_s0.clone() * h_1[i].into() + }); + BlockHashMsg::Child { parent, child_hash } + }, + Deg { n: 5, d: 7 }, + ); + + // LOOP/REPEAT body: first child is `h_0`. + g.add( + "loop_repeat", + f_loop_body, + || { + let parent = addr_next.into(); + let child_hash = h_0.map(LB::Expr::from); + BlockHashMsg::LoopBody { parent, child_hash } + }, + Deg { n: 6, d: 7 }, + ); + + // DYN/DYNCALL/CALL/SYSCALL: single child at `h_0`. + g.add( + "dyn_dyncall_call_syscall", + f_child, + || { + let parent = addr_next.into(); + let child_hash = h_0.map(LB::Expr::from); + BlockHashMsg::Child { parent, child_hash } + }, + Deg { n: 5, d: 6 }, + ); + + // END: pop the queue entry. `is_first_child` distinguishes the head-of-queue + // case via the next-row control-flow flags, and `is_loop_body` comes from the + // typed END overlay on the current row. + g.remove( + "end", + f_end, + || { + let parent = addr_next.into(); + let child_hash = h_0.map(LB::Expr::from); + let is_first_child = LB::Expr::ONE + - op_flags.end_next() + - op_flags.repeat_next() + - op_flags.halt_next(); + let is_loop_body = dec.end_block_flags().is_loop_body.into(); + BlockHashMsg::End { + parent, + child_hash, + is_first_child, + is_loop_body, + } + }, + Deg { n: 4, d: 8 }, + ); + + // =================== G_op_group OP GROUP TABLE =================== + + // g8: c0 triggers a 7-add batch (groups 1..=7). Groups 1..=3 come from `h_0` + // and groups 4..=7 from `h_1`. + let gc8 = gc.clone(); + g.batch( + "g8_batch", + c0.clone(), + move |b| { + let batch_id: LB::Expr = addr_next.into(); + for i in 1u16..=3 { + let group_value = h_0[i as usize].into(); + b.add( + "g8_group", + OpGroupMsg::new(&batch_id, gc8.clone(), i, group_value), + Deg { n: 1, d: 2 }, + ); + } + for i in 4u16..=7 { + let group_value = h_1[(i - 4) as usize].into(); + b.add( + "g8_group", + OpGroupMsg::new(&batch_id, gc8.clone(), i, group_value), + Deg { n: 1, d: 2 }, + ); + } + }, + Deg { n: 6, d: 7 }, + ); + + // g4: (1 - c0) · c1 · (1 - c2) triggers a 3-add batch (groups 1..=3 from + // `h_0`). + let gc4 = gc.clone(); + g.batch( + "g4_batch", + c0.not() * c1.clone() * c2.not(), + move |b| { + let batch_id: LB::Expr = addr_next.into(); + for i in 1u16..=3 { + let group_value = h_0[i as usize].into(); + b.add( + "g4_group", + OpGroupMsg::new(&batch_id, gc4.clone(), i, group_value), + Deg { n: 3, d: 4 }, + ); + } + }, + Deg { n: 2, d: 3 }, + ); + + // g2: (1 - c0) · (1 - c1) · c2 is a single add for group 1 (from `h_0[1]`). + let gc2 = gc.clone(); + let f_g2 = c0.not() * c1.not() * c2; + g.add( + "g2", + f_g2, + move || { + let batch_id: LB::Expr = addr_next.into(); + let group_value = h_0[1].into(); + OpGroupMsg::new(&batch_id, gc2, 1, group_value) + }, + Deg { n: 3, d: 4 }, + ); + + // Removal: `in_span · (gc - gc_next)`-gated muxed removal whose group_value is + // `is_push · stk_next[0] + (1 - is_push) · (h0_next · 128 + opcode_next)`. + g.remove( + "op_group_removal", + f_rem, + move || { + let opcode_next = horner_eval_bits::<7, _, LB::Expr>(&dec_next.op_bits); + let stk_next_0: LB::Expr = stk_next.get(0).into(); + let h0_next: LB::Expr = dec_next.hasher_state[0].into(); + let group_value = f_push.clone() * stk_next_0 + + f_push.not() * (h0_next * LB::Expr::from_u16(128) + opcode_next); + let batch_id = addr.into(); + OpGroupMsg { batch_id, group_pos: gc, group_value } + }, + Deg { n: 2, d: 8 }, + ); + }, + Deg { n: 7, d: 8 }, + ); + }, + Deg { n: 7, d: 8 }, + ); +} diff --git a/air/src/constraints/lookup/buses/block_stack_and_range_logcap.rs b/air/src/constraints/lookup/buses/block_stack_and_range_logcap.rs new file mode 100644 index 0000000000..5c51710cb2 --- /dev/null +++ b/air/src/constraints/lookup/buses/block_stack_and_range_logcap.rs @@ -0,0 +1,381 @@ +//! Merged main-trace column: block-stack table, u32 range checks, log-precompile capacity, +//! and range-table response (`BusId::BlockStackTable` + `BusId::RangeCheck` + +//! `BusId::LogPrecompileTranscript` on one column). +//! +//! Packs what used to be two separate columns (block-stack and range_logcap) into one, +//! saving an aux column. Soundness of the merge relies on the three buses using distinct +//! `bus_prefix[bus]` bases (so their rationals remain linearly independent in the +//! extension field) and on all opcode-gated interactions being mutually exclusive in the +//! main group. +//! +//! # Structure +//! +//! One [`super::super::LookupBuilder::column`] call with two sibling +//! [`super::super::LookupColumn::group`] calls: +//! +//! - **Main group** (opcode-gated, mutually exclusive by opcode): +//! - Block-stack table: JOIN/SPLIT/SPAN/DYN, LOOP, DYNCALL, CALL/SYSCALL, two END cases, RESPAN +//! batch (7 branches, mutually exclusive via decoder opcode flags). +//! - u32 range-check batch: 4 removes gated by `u32_rc_op`. +//! - Log-precompile capacity batch: 1 remove + 1 add gated by `log_precompile`. +//! - **Sibling group** (always on): +//! - Range-table response: a single insert with runtime multiplicity `range_m`, gated by `ONE` so +//! it fires on every row. Lives in its own group because it overlaps (row-wise) with every +//! opcode-gated interaction above and would break the simple-group mutual-exclusion invariant. +//! +//! # Mutual exclusivity +//! +//! The main group is sound under simple-group accumulation because all its gates are +//! mutually exclusive decoder-opcode flags. The three bus families live in disjoint +//! opcode sets: +//! +//! - Block-stack: {JOIN, SPLIT, SPAN, DYN, LOOP, DYNCALL, CALL, SYSCALL, END, RESPAN} +//! - u32: {U32SPLIT, U32ASSERT2, U32ADD, U32SUB, U32MUL, U32DIV, U32MOD, U32AND, U32XOR, U32ADD3, +//! U32MADD, …} — prefix_100 in the opcode encoding. +//! - LOGPRECOMPILE: {LOGPRECOMPILE} — a single opcode. +//! +//! No row can fire two of these simultaneously. The END-simple / END-call/syscall split +//! inside block-stack is mutually exclusive via the `is_call + is_syscall ≤ 1` end-flag +//! invariant. +//! +//! # Degree budget +//! +//! Main group contribution table: +//! +//! | Interaction | Gate deg | Payload | U contrib | V contrib | +//! |---|---|---|---|---| +//! | JOIN/SPLIT/SPAN/DYN simple add | 5 | Simple, denom 1 | 6 | 5 | +//! | LOOP simple add | 5 | Simple, denom 1 | 6 | 5 | +//! | DYNCALL simple add (Full msg) | 5 | Full, denom 1 | 6 | 5 | +//! | CALL/SYSCALL simple add (Full msg) | 4 | Full, denom 1 | 5 | 4 | +//! | END simple remove | 5 | Simple, denom 1 | 6 | 5 | +//! | END call/syscall remove (Full msg) | 5 | Full, denom 1 | 6 | 5 | +//! | RESPAN batch (k=2, f=respan deg 4) | — | Simple | 6 | 5 | +//! | u32rc batch (k=4, f=u32_rc_op deg 3) | — | Range, denom 1 | **7** | **6** | +//! | logpre batch (k=2, f=log_precompile deg 5) | — | LogCap, denom 1 | **7** | **6** | +//! +//! Main group max: `U_g = 7, V_g = 6`. +//! +//! Sibling range-table group: `g.insert(ONE, range_m, RangeMsg)` — gate deg 0, mult deg 1, +//! denom deg 1. `U_g = 1, V_g = 1`. +//! +//! Column fold (cross-mul rule `U_col = ∏ U_gi`, `V_col = Σᵢ V_gi · ∏_{j≠i} U_gj`): +//! +//! - `deg(U_col) = 7 + 1 = 8` +//! - `deg(V_col) = max(6 + 1, 1 + 7) = 8` +//! - **Transition = `max(1 + 8, 8) = 9`**, 0 headroom. + +use core::array; + +use miden_core::field::PrimeCharacteristicRing; + +use crate::{ + constraints::lookup::{ + main_air::{MainBusContext, MainLookupBuilder}, + messages::{BlockStackMsg, LogCapacityMsg, RangeMsg}, + }, + lookup::{Deg, LookupBatch, LookupColumn, LookupGroup}, + trace::log_precompile::{HELPER_CAP_PREV_RANGE, STACK_CAP_NEXT_RANGE}, +}; + +/// Upper bound on fractions this emitter pushes into its column per row. +/// +/// Main group per-row max is `max(1, 1, 1, 1, 1, 1, 2 (RESPAN), 4 (u32rc), 2 (logpre)) = 4` +/// — the u32rc 4-remove batch is the dominant branch. +/// Sibling range-table group always contributes 1 fraction. +/// Both groups run unconditionally (the main group fires at most one branch per row but +/// the per-column accumulator allocates the worst-case slot budget), so the per-row max is +/// the sum: `4 + 1 = 5`. +pub(in crate::constraints::lookup) const MAX_INTERACTIONS_PER_ROW: usize = 5; + +/// Emit the merged block-stack + u32rc + logpre + range-table column. +#[allow(clippy::too_many_lines)] +pub(in crate::constraints::lookup) fn emit_block_stack_and_range_logcap( + builder: &mut LB, + ctx: &MainBusContext, +) where + LB: MainLookupBuilder, +{ + let local = ctx.local; + let next = ctx.next; + let op_flags = &ctx.op_flags; + + let dec = &local.decoder; + let dec_next = &next.decoder; + let stk = &local.stack; + let stk_next = &next.stack; + + // ---- Block-stack captures (from block_stack.rs) ---- + // + // `dec.hasher_state` holds `[h0..h7]` with `h[4..8]` doubling as the end-block flags + // (see `end_block_flags()`). DYNCALL reads `h[4]`/`h[5]` as `fmp`/`depth`; the END + // variants read `is_loop`/`is_call`/`is_syscall` through the typed `EndBlockFlags` + // overlay. + let addr = dec.addr; + let addr_next = dec_next.addr; + let h4 = dec.hasher_state[4]; + let h5 = dec.hasher_state[5]; + let h1_next = dec_next.hasher_state[1]; + let end_flags = dec.end_block_flags(); + + let s0 = stk.get(0); + let b0 = stk.b0; + let b1 = stk.b1; + let b0_next = stk_next.b0; + let b1_next = stk_next.b1; + + let sys_ctx = local.system.ctx; + let sys_ctx_next = next.system.ctx; + + // `fn_hash` is used twice (DYNCALL, CALL/SYSCALL) and `fn_hash_next` once (END-after- + // CALL/SYSCALL); keep them as `[Var; 4]` and convert inside each closure. + let fn_hash = local.system.fn_hash; + let fn_hash_next = next.system.fn_hash; + + let range_m = local.range.multiplicity; + let range_v = local.range.value; + + // ---- u32rc + logpre captures (from range_logcap.rs) ---- + + let user_helpers = dec.user_op_helpers(); + let f_u32rc = op_flags.u32_rc_op(); + let f_log_precompile = op_flags.log_precompile(); + + // u32rc helpers: first 4 of the 6 user_op_helpers. Kept as `[Var; 4]` (Copy) so the + // batch closure captures them without cloning. + let u32rc_helpers: [LB::Var; 4] = array::from_fn(|i| user_helpers[i]); + + // LOGPRECOMPILE capacity add/remove payloads — also raw `[Var; 4]`. + let cap_prev: [LB::Var; 4] = array::from_fn(|i| user_helpers[HELPER_CAP_PREV_RANGE.start + i]); + let cap_next: [LB::Var; 4] = array::from_fn(|i| stk_next.get(STACK_CAP_NEXT_RANGE.start + i)); + + builder.next_column( + |col| { + // ──────────── Main group: all opcode-gated interactions ──────────── + col.group( + "main_interactions", + |g| { + // ---- Block-stack table (BusId::BlockStackTable) ---- + + // JOIN/SPLIT/SPAN/DYN: simple push with `is_loop = 0`. + let f = + op_flags.join() + op_flags.split() + op_flags.span() + op_flags.dyn_op(); + g.add( + "join_split_span_dyn", + f, + || { + let block_id = addr_next.into(); + let parent_id = addr.into(); + let is_loop = LB::Expr::ZERO; + BlockStackMsg::Simple { block_id, parent_id, is_loop } + }, + Deg { n: 5, d: 6 }, + ); + + // LOOP: push with is_loop = s0. + g.add( + "loop", + op_flags.loop_op(), + || { + let block_id = addr_next.into(); + let parent_id = addr.into(); + let is_loop = s0.into(); + BlockStackMsg::Simple { block_id, parent_id, is_loop } + }, + Deg { n: 5, d: 6 }, + ); + + // DYNCALL: full push with h[4]/h[5] as fmp/depth. + g.add( + "dyncall", + op_flags.dyncall(), + || { + let block_id = addr_next.into(); + let parent_id = addr.into(); + let is_loop = LB::Expr::ZERO; + let ctx = sys_ctx.into(); + let fmp = h4.into(); + let depth = h5.into(); + let fn_hash = fn_hash.map(LB::Expr::from); + BlockStackMsg::Full { + block_id, + parent_id, + is_loop, + ctx, + fmp, + depth, + fn_hash, + } + }, + Deg { n: 5, d: 6 }, + ); + + // CALL/SYSCALL: full push saving the caller context. + let f = op_flags.call() + op_flags.syscall(); + g.add( + "call_syscall", + f, + || { + let block_id = addr_next.into(); + let parent_id = addr.into(); + let is_loop = LB::Expr::ZERO; + let ctx = sys_ctx.into(); + let fmp = b0.into(); + let depth = b1.into(); + let fn_hash = fn_hash.map(LB::Expr::from); + BlockStackMsg::Full { + block_id, + parent_id, + is_loop, + ctx, + fmp, + depth, + fn_hash, + } + }, + Deg { n: 4, d: 5 }, + ); + + // END (simple blocks): pop with the stored is_loop. + let f = op_flags.end() + * (LB::Expr::ONE - end_flags.is_call.into() - end_flags.is_syscall.into()); + g.remove( + "end_simple", + f, + || { + let block_id = addr.into(); + let parent_id = addr_next.into(); + let is_loop = end_flags.is_loop.into(); + BlockStackMsg::Simple { block_id, parent_id, is_loop } + }, + Deg { n: 5, d: 6 }, + ); + + // END (after CALL/SYSCALL): pop with restored caller context. + let f = + op_flags.end() * (end_flags.is_call.into() + end_flags.is_syscall.into()); + g.remove( + "end_call_syscall", + f, + || { + let block_id = addr.into(); + let parent_id = addr_next.into(); + let is_loop = end_flags.is_loop.into(); + let ctx = sys_ctx_next.into(); + let fmp = b0_next.into(); + let depth = b1_next.into(); + let fn_hash = fn_hash_next.map(LB::Expr::from); + BlockStackMsg::Full { + block_id, + parent_id, + is_loop, + ctx, + fmp, + depth, + fn_hash, + } + }, + Deg { n: 5, d: 6 }, + ); + + // RESPAN: simultaneous push + pop — one batch under the RESPAN flag. + g.batch( + "respan", + op_flags.respan(), + |b| { + let block_id_add = addr_next.into(); + let parent_id_add = h1_next.into(); + let is_loop_add = LB::Expr::ZERO; + b.add( + "respan_add", + BlockStackMsg::Simple { + block_id: block_id_add, + parent_id: parent_id_add, + is_loop: is_loop_add, + }, + Deg { n: 4, d: 5 }, + ); + let block_id_rem = addr.into(); + let parent_id_rem = h1_next.into(); + let is_loop_rem = LB::Expr::ZERO; + b.remove( + "respan_remove", + BlockStackMsg::Simple { + block_id: block_id_rem, + parent_id: parent_id_rem, + is_loop: is_loop_rem, + }, + Deg { n: 4, d: 5 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + + // ---- u32 range-check removes (BusId::RangeCheck) ---- + // Four simultaneous range-check removals under the u32rc flag. Mutually + // exclusive with all block-stack branches (u32 ops are disjoint from + // control-flow ops) and with logpre (disjoint from LOGPRECOMPILE). + g.batch( + "u32_range_check", + f_u32rc, + move |b| { + for helper in u32rc_helpers { + let value = helper.into(); + b.remove("u32rc_remove", RangeMsg { value }, Deg { n: 3, d: 4 }); + } + }, + Deg { n: 3, d: 4 }, + ); + + // ---- Log-precompile capacity update (BusId::LogPrecompileTranscript) ---- + // Remove the previous capacity, add the next. Mutually exclusive with all + // block-stack branches and with u32rc. + g.batch( + "log_precompile_capacity", + f_log_precompile, + move |b| { + let capacity_prev = cap_prev.map(LB::Expr::from); + b.remove( + "logpre_cap_remove", + LogCapacityMsg { capacity: capacity_prev }, + Deg { n: 5, d: 6 }, + ); + let capacity_next = cap_next.map(LB::Expr::from); + b.add( + "logpre_cap_add", + LogCapacityMsg { capacity: capacity_next }, + Deg { n: 5, d: 6 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + }, + Deg { n: 6, d: 7 }, + ); + + // ──────────── Sibling group: range-table response (BusId::RangeCheck) ──────────── + // + // Always-active insertion with multiplicity `range_m`. Lives in its own group + // because its gate (`ONE`) makes it fire on every row, overlapping with every + // opcode-gated interaction in the main group — which would break the simple-group + // mutual-exclusion invariant if they shared a group. + col.group( + "range_table", + |g| { + g.insert( + "range_response", + LB::Expr::ONE, + range_m.into(), + || { + let value = range_v.into(); + RangeMsg { value } + }, + Deg { n: 1, d: 1 }, + ); + }, + Deg { n: 1, d: 1 }, + ); + }, + Deg { n: 8, d: 8 }, + ); +} diff --git a/air/src/constraints/lookup/buses/chiplet_requests.rs b/air/src/constraints/lookup/buses/chiplet_requests.rs new file mode 100644 index 0000000000..b152cce451 --- /dev/null +++ b/air/src/constraints/lookup/buses/chiplet_requests.rs @@ -0,0 +1,677 @@ +//! Chiplet requests bus ([`BusId::Chiplets`]). +//! +//! Decoder-side requests into the hasher, bitwise, memory, ACE init, and kernel ROM chiplets. +//! +//! Every interaction is folded into a single [`super::super::LookupColumn::group`] call. +//! The cached-encoding optimization can be reintroduced later if symbolic expression growth +//! becomes a bottleneck. + +use core::array; + +use miden_core::{FMP_ADDR, FMP_INIT_VALUE, field::PrimeCharacteristicRing, operations::opcodes}; + +use crate::{ + constraints::lookup::{ + main_air::{MainBusContext, MainLookupBuilder}, + messages::{AceInitMsg, BitwiseMsg, HasherMsg, KernelRomMsg, MemoryMsg}, + }, + lookup::{Deg, LookupBatch, LookupColumn, LookupGroup}, + trace::{ + chiplets::hasher::CONTROLLER_ROWS_PER_PERMUTATION, + log_precompile::{ + HELPER_ADDR_IDX, HELPER_CAP_PREV_RANGE, STACK_CAP_NEXT_RANGE, STACK_COMM_RANGE, + STACK_R0_RANGE, STACK_R1_RANGE, STACK_TAG_RANGE, + }, + }, +}; + +/// Upper bound on fractions this emitter pushes into its column per row. +/// +/// Every branch here is gated by one mutually exclusive decoder-opcode flag. The heaviest +/// branch is MRUPDATE, whose batch emits 4 removes (merkle_old_init + return_hash + +/// merkle_new_init + return_hash). No other single branch exceeds 4. +pub(in crate::constraints::lookup) const MAX_INTERACTIONS_PER_ROW: usize = 4; + +/// Emit the chiplet requests bus. +#[allow(clippy::too_many_lines)] +pub(in crate::constraints::lookup) fn emit_chiplet_requests( + builder: &mut LB, + main_ctx: &MainBusContext, +) where + LB: MainLookupBuilder, +{ + let local = main_ctx.local; + let next = main_ctx.next; + let op_flags = &main_ctx.op_flags; + + let dec = &local.decoder; + let stk = &local.stack; + let stk_next = &next.stack; + let user_helpers = dec.user_op_helpers(); + + // Raw Vars (Copy — captured by value and converted at point of use). + let addr = dec.addr; + let addr_next = next.decoder.addr; + let h = dec.hasher_state; // [Var; 8] + let helper0 = user_helpers[0]; + let clk = local.system.clk; + let sys_ctx = local.system.ctx; + let sys_ctx_next = next.system.ctx; + let s0 = stk.get(0); + let s1 = stk.get(1); + let stk_next_0 = stk_next.get(0); + let log_addr = user_helpers[HELPER_ADDR_IDX]; + + // Constants reused across HPERM / MPVERIFY / MRUPDATE / END / LOGPRECOMPILE. + // Strides are measured in controller-trace rows (2 per permutation), not physical + // hasher sub-chiplet rows — the address must cancel against `clk + 1` on the hasher + // controller output row. + let last_off: LB::Expr = LB::Expr::from_u16((CONTROLLER_ROWS_PER_PERMUTATION - 1) as u16); + let cycle_len: LB::Expr = LB::Expr::from_u16(CONTROLLER_ROWS_PER_PERMUTATION as u16); + + // Shared (ctx, addr, clk) triple for MLOAD / MSTORE / MLOADW / MSTOREW: all read from + // `s0` with the current system context and clock. + let mem_ctx: LB::Expr = sys_ctx.into(); + let mem_clk: LB::Expr = clk.into(); + let mem_addr: LB::Expr = s0.into(); + + builder.next_column( + |col| { + col.group( + "decoder_requests", + |g| { + // --- Control-block removes (JOIN / SPLIT / LOOP / SPAN / CALL / SYSCALL) --- + // `h` is a `[Var; 8]` captured by copy; each closure shadows it with a fresh + // `[Expr; 8]` via `.map(LB::Expr::from)` at call time. + g.remove( + "join", + op_flags.join(), + move || { + let parent = addr_next.into(); + let h = h.map(LB::Expr::from); + HasherMsg::control_block(parent, &h, opcodes::JOIN) + }, + Deg { n: 5, d: 6 }, + ); + g.remove( + "split", + op_flags.split(), + move || { + let parent = addr_next.into(); + let h = h.map(LB::Expr::from); + HasherMsg::control_block(parent, &h, opcodes::SPLIT) + }, + Deg { n: 5, d: 6 }, + ); + g.remove( + "loop", + op_flags.loop_op(), + move || { + let parent = addr_next.into(); + let h = h.map(LB::Expr::from); + HasherMsg::control_block(parent, &h, opcodes::LOOP) + }, + Deg { n: 5, d: 6 }, + ); + g.remove( + "span", + op_flags.span(), + move || { + // SPAN is encoded with opcode 0 at the `β¹²` slot. + let parent = addr_next.into(); + let h = h.map(LB::Expr::from); + HasherMsg::control_block(parent, &h, 0) + }, + Deg { n: 5, d: 6 }, + ); + + // CALL: control-block remove + FMP write under a fresh header (ctx_next / + // FMP_ADDR / clk). + g.batch( + "call", + op_flags.call(), + move |b| { + let parent = addr_next.into(); + let h = h.map(LB::Expr::from); + b.remove( + "call_ctrl_block", + HasherMsg::control_block(parent, &h, opcodes::CALL), + Deg { n: 4, d: 5 }, + ); + b.remove( + "call_fmp_write", + MemoryMsg::write_element( + sys_ctx_next.into(), + FMP_ADDR.into(), + clk.into(), + FMP_INIT_VALUE.into(), + ), + Deg { n: 4, d: 5 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + + // SYSCALL: control-block remove + kernel-ROM call with the h[0..4] digest. + g.batch( + "syscall", + op_flags.syscall(), + move |b| { + let parent = addr_next.into(); + let digest = array::from_fn(|i| h[i].into()); + let h = h.map(LB::Expr::from); + b.remove( + "syscall_ctrl_block", + HasherMsg::control_block(parent, &h, opcodes::SYSCALL), + Deg { n: 4, d: 5 }, + ); + b.remove( + "syscall_kernel_rom", + KernelRomMsg::call(digest), + Deg { n: 4, d: 5 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + + // --- RESPAN --- + // Uses `addr_next` directly: in the controller/perm split, the next row's + // decoder `addr` already points at the continuation input + // row, so no offset is needed. + g.remove( + "respan", + op_flags.respan(), + move || { + let parent = addr_next.into(); + let h = h.map(LB::Expr::from); + HasherMsg::absorption(parent, h) + }, + Deg { n: 4, d: 5 }, + ); + + // --- END --- + { + let last_off = last_off.clone(); + g.remove( + "end", + op_flags.end(), + move || { + let addr: LB::Expr = addr.into(); + let parent = addr + last_off; + let h = array::from_fn(|i| h[i].into()); + HasherMsg::return_hash(parent, h) + }, + Deg { n: 4, d: 5 }, + ); + } + + // --- DYN --- + { + let mem_ctx = mem_ctx.clone(); + let mem_clk = mem_clk.clone(); + let mem_addr = mem_addr.clone(); + g.batch( + "dyn", + op_flags.dyn_op(), + move |b| { + let parent = addr_next.into(); + let zeros8: [LB::Expr; 8] = array::from_fn(|_| LB::Expr::ZERO); + b.remove( + "dyn_ctrl_block", + HasherMsg::control_block(parent, &zeros8, opcodes::DYN), + Deg { n: 5, d: 6 }, + ); + let word = array::from_fn(|i| h[i].into()); + b.remove( + "dyn_mem_read", + MemoryMsg::read_word(mem_ctx, mem_addr, mem_clk, word), + Deg { n: 5, d: 6 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + } + + // --- DYNCALL --- + { + let mem_ctx = mem_ctx.clone(); + let mem_clk = mem_clk.clone(); + let mem_addr = mem_addr.clone(); + g.batch( + "dyncall", + op_flags.dyncall(), + move |b| { + let parent = addr_next.into(); + let zeros8: [LB::Expr; 8] = array::from_fn(|_| LB::Expr::ZERO); + b.remove( + "dyncall_ctrl_block", + HasherMsg::control_block(parent, &zeros8, opcodes::DYNCALL), + Deg { n: 5, d: 6 }, + ); + let word = array::from_fn(|i| h[i].into()); + b.remove( + "dyncall_mem_read", + MemoryMsg::read_word(mem_ctx, mem_addr, mem_clk.clone(), word), + Deg { n: 5, d: 6 }, + ); + b.remove( + "dyncall_fmp_write", + MemoryMsg::write_element( + sys_ctx_next.into(), + FMP_ADDR.into(), + mem_clk, + FMP_INIT_VALUE.into(), + ), + Deg { n: 5, d: 6 }, + ); + }, + Deg { n: 2, d: 3 }, + ); + } + + // --- HPERM --- + { + let last_off = last_off.clone(); + g.batch( + "hperm", + op_flags.hperm(), + move |b| { + let helper0: LB::Expr = helper0.into(); + let stk_state = array::from_fn(|i| stk.get(i).into()); + let stk_next_state = array::from_fn(|i| stk_next.get(i).into()); + b.remove( + "hperm_init", + HasherMsg::linear_hash_init(helper0.clone(), stk_state), + Deg { n: 5, d: 6 }, + ); + let return_addr = helper0 + last_off; + b.remove( + "hperm_return", + HasherMsg::return_state(return_addr, stk_next_state), + Deg { n: 5, d: 6 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + } + + // --- MPVERIFY --- + { + let cycle_len = cycle_len.clone(); + g.batch( + "mpverify", + op_flags.mpverify(), + move |b| { + let helper0: LB::Expr = helper0.into(); + let mp_index = stk.get(5).into(); + let mp_depth: LB::Expr = stk.get(4).into(); + let stk_word_0 = array::from_fn(|i| stk.get(i).into()); + let old_root = array::from_fn(|i| stk.get(6 + i).into()); + b.remove( + "mpverify_init", + HasherMsg::merkle_verify_init( + helper0.clone(), + mp_index, + stk_word_0, + ), + Deg { n: 5, d: 6 }, + ); + let return_addr = helper0 + mp_depth * cycle_len - LB::Expr::ONE; + b.remove( + "mpverify_return", + HasherMsg::return_hash(return_addr, old_root), + Deg { n: 5, d: 6 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + } + + // --- MRUPDATE --- + { + let cycle_len = cycle_len.clone(); + g.batch( + "mrupdate", + op_flags.mrupdate(), + move |b| { + let helper0: LB::Expr = helper0.into(); + let mr_index: LB::Expr = stk.get(5).into(); + let mr_depth: LB::Expr = stk.get(4).into(); + let stk_word_0 = array::from_fn(|i| stk.get(i).into()); + let stk_next_word_0 = array::from_fn(|i| stk_next.get(i).into()); + let old_root = array::from_fn(|i| stk.get(6 + i).into()); + let new_node = array::from_fn(|i| stk.get(10 + i).into()); + b.remove( + "mrupdate_old_init", + HasherMsg::merkle_old_init( + helper0.clone(), + mr_index.clone(), + stk_word_0, + ), + Deg { n: 4, d: 5 }, + ); + let old_return_addr = helper0.clone() + + mr_depth.clone() * cycle_len.clone() + - LB::Expr::ONE; + b.remove( + "mrupdate_old_return", + HasherMsg::return_hash(old_return_addr, old_root), + Deg { n: 4, d: 5 }, + ); + let new_init_addr = + helper0.clone() + mr_depth.clone() * cycle_len.clone(); + b.remove( + "mrupdate_new_init", + HasherMsg::merkle_new_init(new_init_addr, mr_index, new_node), + Deg { n: 4, d: 5 }, + ); + let new_return_addr = helper0 + + mr_depth * (cycle_len.clone() + cycle_len) + - LB::Expr::ONE; + b.remove( + "mrupdate_new_return", + HasherMsg::return_hash(new_return_addr, stk_next_word_0), + Deg { n: 4, d: 5 }, + ); + }, + Deg { n: 3, d: 4 }, + ); + } + + // --- MLOAD / MSTORE / MLOADW / MSTOREW --- + // Shared (ctx, addr, clk) triple: reads the current system context, s0, + // and clk. + { + let (c, a, k) = (mem_ctx.clone(), mem_addr.clone(), mem_clk.clone()); + g.remove( + "mload", + op_flags.mload(), + move || MemoryMsg::read_element(c, a, k, stk_next_0.into()), + Deg { n: 7, d: 8 }, + ); + } + { + let (c, a, k) = (mem_ctx.clone(), mem_addr.clone(), mem_clk.clone()); + g.remove( + "mstore", + op_flags.mstore(), + move || MemoryMsg::write_element(c, a, k, s1.into()), + Deg { n: 7, d: 8 }, + ); + } + { + let (c, a, k) = (mem_ctx.clone(), mem_addr.clone(), mem_clk.clone()); + g.remove( + "mloadw", + op_flags.mloadw(), + move || { + let word = array::from_fn(|i| stk_next.get(i).into()); + MemoryMsg::read_word(c, a, k, word) + }, + Deg { n: 7, d: 8 }, + ); + } + g.remove( + "mstorew", + op_flags.mstorew(), + move || { + let word = [ + s1.into(), + stk.get(2).into(), + stk.get(3).into(), + stk.get(4).into(), + ]; + MemoryMsg::write_word(mem_ctx, mem_addr, mem_clk, word) + }, + Deg { n: 7, d: 8 }, + ); + + // --- MSTREAM / PIPE --- + // Two-word memory ops. Address `stack[12]` holds the word-addressable target; + // the two words live at `addr` and `addr + 4`. + // + // MSTREAM reads 8 elements from memory into `next.stack[0..8]` + // (MEMORY_READ_WORD). PIPE writes 8 elements from + // `next.stack[0..8]` into memory (MEMORY_WRITE_WORD). + let stream_addr = stk.get(12); + g.batch( + "mstream", + op_flags.mstream(), + move |b| { + let addr0: LB::Expr = stream_addr.into(); + let addr1: LB::Expr = addr0.clone() + LB::Expr::from_u16(4); + let word0: [LB::Expr; 4] = array::from_fn(|i| stk_next.get(i).into()); + let word1: [LB::Expr; 4] = + array::from_fn(|i| stk_next.get(4 + i).into()); + b.remove( + "mstream_word0", + MemoryMsg::read_word(sys_ctx.into(), addr0, clk.into(), word0), + Deg { n: 5, d: 6 }, + ); + b.remove( + "mstream_word1", + MemoryMsg::read_word(sys_ctx.into(), addr1, clk.into(), word1), + Deg { n: 5, d: 6 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + g.batch( + "pipe", + op_flags.pipe(), + move |b| { + let addr0: LB::Expr = stream_addr.into(); + let addr1: LB::Expr = addr0.clone() + LB::Expr::from_u16(4); + let word0: [LB::Expr; 4] = array::from_fn(|i| stk_next.get(i).into()); + let word1: [LB::Expr; 4] = + array::from_fn(|i| stk_next.get(4 + i).into()); + b.remove( + "pipe_word0", + MemoryMsg::write_word(sys_ctx.into(), addr0, clk.into(), word0), + Deg { n: 5, d: 6 }, + ); + b.remove( + "pipe_word1", + MemoryMsg::write_word(sys_ctx.into(), addr1, clk.into(), word1), + Deg { n: 5, d: 6 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + + // --- CRYPTOSTREAM --- + // Two word reads (plaintext from src_ptr, src_ptr + 4) followed by two word + // writes (ciphertext to dst_ptr, dst_ptr + 4). The rate lives on + // `local.stack[0..8]`, and the ciphertext on `next.stack[0..8]`; the + // plaintext is recovered algebraically as `cipher - rate`. + let src_ptr = stk.get(12); + let dst_ptr = stk.get(13); + g.batch( + "cryptostream", + op_flags.cryptostream(), + move |b| { + let src0: LB::Expr = src_ptr.into(); + let src1: LB::Expr = src0.clone() + LB::Expr::from_u16(4); + let dst0: LB::Expr = dst_ptr.into(); + let dst1: LB::Expr = dst0.clone() + LB::Expr::from_u16(4); + let rate: [LB::Expr; 8] = array::from_fn(|i| stk.get(i).into()); + let cipher: [LB::Expr; 8] = array::from_fn(|i| stk_next.get(i).into()); + let plain: [LB::Expr; 8] = + array::from_fn(|i| cipher[i].clone() - rate[i].clone()); + let plain_word0: [LB::Expr; 4] = array::from_fn(|i| plain[i].clone()); + let plain_word1: [LB::Expr; 4] = + array::from_fn(|i| plain[4 + i].clone()); + let cipher_word0: [LB::Expr; 4] = array::from_fn(|i| cipher[i].clone()); + let cipher_word1: [LB::Expr; 4] = + array::from_fn(|i| cipher[4 + i].clone()); + b.remove( + "cryptostream_read0", + MemoryMsg::read_word(sys_ctx.into(), src0, clk.into(), plain_word0), + Deg { n: 4, d: 5 }, + ); + b.remove( + "cryptostream_read1", + MemoryMsg::read_word(sys_ctx.into(), src1, clk.into(), plain_word1), + Deg { n: 4, d: 5 }, + ); + b.remove( + "cryptostream_write0", + MemoryMsg::write_word( + sys_ctx.into(), + dst0, + clk.into(), + cipher_word0, + ), + Deg { n: 4, d: 5 }, + ); + b.remove( + "cryptostream_write1", + MemoryMsg::write_word( + sys_ctx.into(), + dst1, + clk.into(), + cipher_word1, + ), + Deg { n: 4, d: 5 }, + ); + }, + Deg { n: 3, d: 4 }, + ); + + // --- HORNERBASE / HORNEREXT --- + // Both ops read the evaluation point α from memory at `stack[13]`. HORNERBASE + // reads two base-field elements (α₀ at `addr`, α₁ at `addr + 1`); HORNEREXT + // reads a single word `[α₀, α₁, k₀, k₁]` at `addr`. α is held in helpers[0..2] + // for both ops (HORNEREXT additionally parks k₀, k₁ in helpers[2..4]). + let alpha_ptr = stk.get(13); + g.batch( + "hornerbase", + op_flags.hornerbase(), + move |b| { + let addr0: LB::Expr = alpha_ptr.into(); + let addr1: LB::Expr = addr0.clone() + LB::Expr::from_u16(1); + let eval0: LB::Expr = user_helpers[0].into(); + let eval1: LB::Expr = user_helpers[1].into(); + b.remove( + "hornerbase_alpha0", + MemoryMsg::read_element(sys_ctx.into(), addr0, clk.into(), eval0), + Deg { n: 5, d: 6 }, + ); + b.remove( + "hornerbase_alpha1", + MemoryMsg::read_element(sys_ctx.into(), addr1, clk.into(), eval1), + Deg { n: 5, d: 6 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + g.remove( + "hornerext", + op_flags.hornerext(), + move || { + let addr: LB::Expr = alpha_ptr.into(); + let word: [LB::Expr; 4] = [ + user_helpers[0].into(), + user_helpers[1].into(), + user_helpers[2].into(), + user_helpers[3].into(), + ]; + MemoryMsg::read_word(sys_ctx.into(), addr, clk.into(), word) + }, + Deg { n: 5, d: 6 }, + ); + + // --- U32AND / U32XOR --- + g.remove( + "u32and", + op_flags.u32and(), + move || { + let a = s0.into(); + let b = s1.into(); + let c = stk_next_0.into(); + BitwiseMsg::and(a, b, c) + }, + Deg { n: 7, d: 8 }, + ); + g.remove( + "u32xor", + op_flags.u32xor(), + move || { + let a = s0.into(); + let b = s1.into(); + let c = stk_next_0.into(); + BitwiseMsg::xor(a, b, c) + }, + Deg { n: 7, d: 8 }, + ); + + // --- EVALCIRCUIT --- + g.remove( + "evalcircuit", + op_flags.evalcircuit(), + move || { + let clk = clk.into(); + let ctx = sys_ctx.into(); + let ptr = s0.into(); + let num_read = s1.into(); + let num_eval = stk.get(2).into(); + AceInitMsg { clk, ctx, ptr, num_read, num_eval } + }, + Deg { n: 5, d: 6 }, + ); + + // --- LOGPRECOMPILE --- + g.batch( + "logprecompile", + op_flags.log_precompile(), + move |b| { + let log_addr: LB::Expr = log_addr.into(); + let logpre_in: [LB::Expr; 12] = [ + stk.get(STACK_COMM_RANGE.start).into(), + stk.get(STACK_COMM_RANGE.start + 1).into(), + stk.get(STACK_COMM_RANGE.start + 2).into(), + stk.get(STACK_COMM_RANGE.start + 3).into(), + stk.get(STACK_TAG_RANGE.start).into(), + stk.get(STACK_TAG_RANGE.start + 1).into(), + stk.get(STACK_TAG_RANGE.start + 2).into(), + stk.get(STACK_TAG_RANGE.start + 3).into(), + user_helpers[HELPER_CAP_PREV_RANGE.start].into(), + user_helpers[HELPER_CAP_PREV_RANGE.start + 1].into(), + user_helpers[HELPER_CAP_PREV_RANGE.start + 2].into(), + user_helpers[HELPER_CAP_PREV_RANGE.start + 3].into(), + ]; + let logpre_out: [LB::Expr; 12] = [ + stk_next.get(STACK_R0_RANGE.start).into(), + stk_next.get(STACK_R0_RANGE.start + 1).into(), + stk_next.get(STACK_R0_RANGE.start + 2).into(), + stk_next.get(STACK_R0_RANGE.start + 3).into(), + stk_next.get(STACK_R1_RANGE.start).into(), + stk_next.get(STACK_R1_RANGE.start + 1).into(), + stk_next.get(STACK_R1_RANGE.start + 2).into(), + stk_next.get(STACK_R1_RANGE.start + 3).into(), + stk_next.get(STACK_CAP_NEXT_RANGE.start).into(), + stk_next.get(STACK_CAP_NEXT_RANGE.start + 1).into(), + stk_next.get(STACK_CAP_NEXT_RANGE.start + 2).into(), + stk_next.get(STACK_CAP_NEXT_RANGE.start + 3).into(), + ]; + b.remove( + "logprecompile_init", + HasherMsg::linear_hash_init(log_addr.clone(), logpre_in), + Deg { n: 5, d: 6 }, + ); + let return_addr = log_addr + last_off; + b.remove( + "logprecompile_return", + HasherMsg::return_state(return_addr, logpre_out), + Deg { n: 5, d: 6 }, + ); + }, + Deg { n: 1, d: 2 }, + ); + }, + Deg { n: 7, d: 8 }, + ); + }, + Deg { n: 7, d: 8 }, + ); +} diff --git a/air/src/constraints/lookup/buses/chiplet_responses.rs b/air/src/constraints/lookup/buses/chiplet_responses.rs new file mode 100644 index 0000000000..d36800da0b --- /dev/null +++ b/air/src/constraints/lookup/buses/chiplet_responses.rs @@ -0,0 +1,357 @@ +//! Chiplet responses bus ([`BusId::Chiplets`]). +//! +//! Chiplet-side responses from the hasher, bitwise, memory, ACE, and kernel ROM chiplets, +//! all sharing one LogUp column. +//! +//! The 7 hasher response variants are gated on hasher controller rows +//! (`chiplet_active.controller = 1`) via the per-variant `(s0, s1, s2, is_boundary)` +//! combinations. Non-hasher variants (bitwise / memory / ACE init / kernel ROM) are gated +//! by the matching `chiplet_active.{bitwise, memory, ace, kernel_rom}` flag. +//! +//! Memory uses the runtime-muxed [`MemoryResponseMsg`] encoding (label + is_word mux) +//! rather than splitting into 4 per-label variants — this keeps the response-column +//! transition degree at 8 (a per-variant split would bump it to 9). + +use core::{array, borrow::Borrow}; + +use miden_core::field::PrimeCharacteristicRing; + +use crate::{ + constraints::{ + chiplets::columns::PeriodicCols, + lookup::{ + chiplet_air::{ChipletBusContext, ChipletLookupBuilder}, + messages::{ + AceInitMsg, BitwiseMsg, BusId, HasherMsg, HasherPayload, KernelRomMsg, + MemoryResponseMsg, + }, + }, + utils::BoolNot, + }, + lookup::{Deg, LookupBatch, LookupColumn, LookupGroup}, +}; + +/// Upper bound on fractions this emitter pushes into its column per row. +/// +/// All adds gate on per-chiplet `chiplet_active.*` flags which are mutually exclusive (at +/// most one chiplet runs per row). Within the hasher branch, the 7 variants are gated by +/// mutually exclusive `(s0, s1, s2, is_boundary)` combinations. The kernel-ROM branch +/// emits two fractions per active row: an INIT-labeled remove (multiplicity 1) plus a +/// CALL-labeled add with multiplicity equal to the row's `multiplicity` column. Every +/// other chiplet emits exactly one fraction when active. Per-row max: 2. +pub(in crate::constraints::lookup) const MAX_INTERACTIONS_PER_ROW: usize = 2; + +/// Emit the chiplet responses bus. +#[allow(clippy::too_many_lines)] +pub(in crate::constraints::lookup) fn emit_chiplet_responses( + builder: &mut LB, + ctx: &ChipletBusContext, +) where + LB: ChipletLookupBuilder, +{ + let local = ctx.local; + let next = ctx.next; + + // Read the typed periodic column view (used for bitwise k_transition). + let k_transition: LB::Expr = { + let periodic: &PeriodicCols = builder.periodic_values().borrow(); + periodic.bitwise.k_transition.into() + }; + + // Typed chiplet-data overlays. + let ctrl = local.controller(); + let ctrl_next = next.controller(); + let bw = local.bitwise(); + let mem = local.memory(); + let ace = local.ace(); + let krom = local.kernel_rom(); + + // Hasher-internal sub-selectors (valid on controller rows). Used many times below + // via their negated siblings, so kept as named expressions. + let hs0: LB::Expr = ctrl.s0.into(); + let hs1: LB::Expr = ctrl.s1.into(); + let hs2: LB::Expr = ctrl.s2.into(); + let is_boundary: LB::Expr = ctrl.is_boundary.into(); + let not_hs0 = hs0.not(); + let not_hs1 = hs1.not(); + let not_hs2 = hs2.not(); + + // Hasher state split by convention: [rate_0 (4), rate_1 (4), cap (4)]. Kept as Var + // arrays (Copy) so each closure can convert to `LB::Expr` as needed. + let rate_0: [LB::Var; 4] = array::from_fn(|i| ctrl.state[i]); + let rate_1: [LB::Var; 4] = array::from_fn(|i| ctrl.state[4 + i]); + let cap: [LB::Var; 4] = array::from_fn(|i| ctrl.state[8 + i]); + + // --- Hasher response flags --- + // All gated by `chiplet_active.controller`; composed with the per-row-type + // `(s0, s1, s2, is_boundary)` combinations. + let controller_flag = ctx.chiplet_active.controller.clone(); + + // Sponge start: input (hs0=1), hs1=hs2=0, is_boundary=1. Full 12-lane state. + let f_sponge_start: LB::Expr = controller_flag.clone() + * hs0.clone() + * not_hs1.clone() + * not_hs2.clone() + * is_boundary.clone(); + + // Sponge RESPAN: input, hs1=hs2=0, is_boundary=0. Rate-only 8 lanes. + let f_sponge_respan: LB::Expr = controller_flag.clone() + * hs0.clone() + * not_hs1.clone() + * not_hs2.clone() + * is_boundary.not(); + + // Merkle tree input rows (is_boundary=1): + // f_mp = ctrl · hs0 · (1-hs1) · hs2 · is_boundary + // f_mv = ctrl · hs0 · hs1 · (1-hs2) · is_boundary + // f_mu = ctrl · hs0 · hs1 · hs2 · is_boundary + let f_mp: LB::Expr = + controller_flag.clone() * hs0.clone() * not_hs1.clone() * hs2.clone() * is_boundary.clone(); + let f_mv: LB::Expr = + controller_flag.clone() * hs0.clone() * hs1.clone() * not_hs2.clone() * is_boundary.clone(); + let f_mu: LB::Expr = controller_flag.clone() * hs0 * hs1 * hs2.clone() * is_boundary.clone(); + + // HOUT output: hs0=hs1=hs2=0 (always responds on digest). Degree 4 (no is_boundary). + let f_hout: LB::Expr = controller_flag.clone() * not_hs0.clone() * not_hs1.clone() * not_hs2; + + // SOUT output with is_boundary=1 only (HPERM return). + let f_sout: LB::Expr = controller_flag * not_hs0 * not_hs1 * hs2 * is_boundary; + + // --- Non-hasher flags --- + + // Bitwise: responds only on the last row of the 8-row cycle (k_transition = 0). + let is_bitwise_responding: LB::Expr = ctx.chiplet_active.bitwise.clone() * k_transition.not(); + + // ACE init: responds only on ACE start rows. + let is_ace_init: LB::Expr = ctx.chiplet_active.ace.clone() * ace.s_start.into(); + + // --- Emit everything into a single LogUp column --- + + // All hasher response variants encode their row at `clk + 1` (so they cancel against + // the matching request at `clk`). + let clk_plus_one: LB::Expr = local.system.clk.into() + LB::Expr::ONE; + + builder.next_column( + |col| { + col.group( + "chiplet_responses", + |g| { + // Sponge start: full 12-lane state, node_index = 0. + g.add( + "sponge_start", + f_sponge_start, + || { + let addr = clk_plus_one.clone(); + let state: [LB::Expr; 12] = array::from_fn(|i| { + if i < 4 { + rate_0[i].into() + } else if i < 8 { + rate_1[i - 4].into() + } else { + cap[i - 8].into() + } + }); + HasherMsg { + kind: BusId::HasherLinearHashInit, + addr, + node_index: LB::Expr::ZERO, + payload: HasherPayload::State(state), + } + }, + Deg { n: 5, d: 6 }, + ); + + // Sponge RESPAN: rate-only 8 lanes, node_index = 0. + g.add( + "sponge_respan", + f_sponge_respan, + || { + let addr = clk_plus_one.clone(); + let rate: [LB::Expr; 8] = array::from_fn(|i| { + if i < 4 { rate_0[i].into() } else { rate_1[i - 4].into() } + }); + HasherMsg { + kind: BusId::HasherAbsorption, + addr, + node_index: LB::Expr::ZERO, + payload: HasherPayload::Rate(rate), + } + }, + Deg { n: 5, d: 6 }, + ); + + // Merkle leaf-word inputs for MP_VERIFY / MR_UPDATE_OLD / MR_UPDATE_NEW. + // Each fires on its own controller flag; all three encode + // `leaf = (1-bit)·rate_0 + bit·rate_1` with `bit = node_index - + // 2·node_index_next` (the current Merkle direction bit). + for (name, flag, kind) in [ + ("mp_verify_input", f_mp, BusId::HasherMerkleVerifyInit), + ("mr_update_old_input", f_mv, BusId::HasherMerkleOldInit), + ("mr_update_new_input", f_mu, BusId::HasherMerkleNewInit), + ] { + g.add( + name, + flag, + || { + let addr = clk_plus_one.clone(); + let node_index: LB::Expr = ctrl.node_index.into(); + let bit: LB::Expr = + node_index.clone() - ctrl_next.node_index.into().double(); + let one_minus_bit = bit.not(); + let word: [LB::Expr; 4] = array::from_fn(|i| { + one_minus_bit.clone() * rate_0[i].into() + + bit.clone() * rate_1[i].into() + }); + HasherMsg { + kind, + addr, + node_index, + payload: HasherPayload::Word(word), + } + }, + Deg { n: 5, d: 7 }, + ); + } + + // HOUT: digest = rate_0. + g.add( + "hout", + f_hout, + || { + let addr = clk_plus_one.clone(); + let node_index: LB::Expr = ctrl.node_index.into(); + let word: [LB::Expr; 4] = rate_0.map(LB::Expr::from); + HasherMsg { + kind: BusId::HasherReturnHash, + addr, + node_index, + payload: HasherPayload::Word(word), + } + }, + Deg { n: 4, d: 5 }, + ); + + // SOUT: full 12-lane state (HPERM return), node_index = 0. + g.add( + "sout", + f_sout, + || { + let addr = clk_plus_one.clone(); + let state: [LB::Expr; 12] = array::from_fn(|i| { + if i < 4 { + rate_0[i].into() + } else if i < 8 { + rate_1[i - 4].into() + } else { + cap[i - 8].into() + } + }); + HasherMsg { + kind: BusId::HasherReturnState, + addr, + node_index: LB::Expr::ZERO, + payload: HasherPayload::State(state), + } + }, + Deg { n: 5, d: 6 }, + ); + + // Bitwise: runtime op selector bit. + g.add( + "bitwise", + is_bitwise_responding, + || { + let bw_op: LB::Expr = bw.op_flag.into(); + BitwiseMsg { + op: bw_op, + a: bw.a.into(), + b: bw.b.into(), + result: bw.output.into(), + } + }, + Deg { n: 3, d: 4 }, + ); + + // Memory response: runtime (is_read, is_word) mux keeps column transition at 8. + g.add( + "memory", + ctx.chiplet_active.memory.clone(), + || { + let mem_is_read: LB::Expr = mem.is_read.into(); + let is_word: LB::Expr = mem.is_word.into(); + let mem_idx0: LB::Expr = mem.idx0.into(); + let mem_idx1: LB::Expr = mem.idx1.into(); + + let addr = mem.word_addr.into() + + mem_idx1.clone() * LB::Expr::from_u16(2) + + mem_idx0.clone(); + + let word: [LB::Expr; 4] = mem.values.map(LB::Expr::from); + let element = word[0].clone() * mem_idx0.not() * mem_idx1.not() + + word[1].clone() * mem_idx0.clone() * mem_idx1.not() + + word[2].clone() * mem_idx0.not() * mem_idx1.clone() + + word[3].clone() * mem_idx0 * mem_idx1; + + MemoryResponseMsg { + is_read: mem_is_read, + ctx: mem.ctx.into(), + addr, + clk: mem.clk.into(), + is_word, + element, + word, + } + }, + Deg { n: 3, d: 7 }, + ); + + // ACE init. + g.add( + "ace_init", + is_ace_init, + || { + let num_eval = ace.read().num_eval.into() + LB::Expr::ONE; + let num_read = ace.id_0.into() + LB::Expr::ONE - num_eval.clone(); + AceInitMsg { + clk: ace.clk.into(), + ctx: ace.ctx.into(), + ptr: ace.ptr.into(), + num_read, + num_eval, + } + }, + Deg { n: 5, d: 6 }, + ); + + // Kernel ROM: two fractions per active row. + // INIT remove (multiplicity 1) is balanced by the boundary correction. + // CALL add carries the syscall multiplicity. + let kernel_gate = ctx.chiplet_active.kernel_rom.clone(); + g.batch( + "kernel_rom", + kernel_gate, + |b| { + let krom_mult: LB::Expr = krom.multiplicity.into(); + let digest: [LB::Expr; 4] = krom.root.map(LB::Expr::from); + + b.remove( + "kernel_rom_init", + KernelRomMsg::init(digest.clone()), + Deg { n: 5, d: 6 }, + ); + b.insert( + "kernel_rom_call", + krom_mult, + KernelRomMsg::call(digest), + Deg { n: 6, d: 6 }, + ); + }, + Deg { n: 2, d: 2 }, + ); + }, + Deg { n: 7, d: 7 }, + ); + }, + Deg { n: 7, d: 7 }, + ); +} diff --git a/air/src/constraints/lookup/buses/hash_kernel.rs b/air/src/constraints/lookup/buses/hash_kernel.rs new file mode 100644 index 0000000000..162319dee0 --- /dev/null +++ b/air/src/constraints/lookup/buses/hash_kernel.rs @@ -0,0 +1,262 @@ +//! Hash-kernel virtual table bus. Shares one column across +//! `BusId::{SiblingTable, RangeCheck}` plus the shared chiplets column for ACE reads. +//! +//! Combines three tables on a single LogUp column: +//! +//! 1. **Sibling table** (`BusId::SiblingTable`) — Merkle update siblings. On hasher controller +//! input rows with `s0·s1 = 1`, `s2` distinguishes MU (new path, removes siblings) from MV (old +//! path, adds siblings). The direction bit `b = node_index − 2·node_index_next` selects which +//! half of `rate = [rate_0, rate_1]` holds the sibling, giving four gated interactions (two add, +//! two remove). +//! 2. **ACE memory reads** (chiplet-responses column) — on ACE chiplet rows, the block selector +//! distinguishes word reads (`f_ace_read`) from element reads used by EVAL rows (`f_ace_eval`). +//! Both are removed from the chiplets bus. +//! 3. **Memory-side range checks** (`BusId::RangeCheck`) — on memory chiplet rows, a five-remove +//! batch consumes the two delta limbs `d0`/`d1` and the three word-address decomposition values +//! `w0`, `w1`, and `4·w1`. Together these enforce `d0, d1, w0, w1 ∈ [0, 2^16)` plus `w1 ∈ [0, +//! 2^14)` (via the `4·w1` check), which bounds `word_addr = 4·(w0 + 2^16·w1)` to the 32-bit +//! memory address space. +//! +//! Per-chiplet gating flows through [`ChipletBusContext::chiplet_active`]: the controller +//! input gate is `chiplet_active.controller`, the ACE row gate is `chiplet_active.ace`, and +//! the memory row gate is `chiplet_active.memory`. Hasher sub-selectors, hasher state, +//! `node_index`, and `mrupdate_id` come from the typed +//! [`local.controller()`](crate::constraints::columns::MainCols::controller) overlay; +//! memory delta limbs come from [`local.memory()`](crate::constraints::columns::MainCols::memory). +//! `w0` / `w1` are not in the typed `MemoryCols` view (their physical columns live in +//! `chiplets[18..20]`, past the end of the memory overlay, shared with the ACE chiplet +//! column space), so they are read directly from the raw chiplet slice. + +use core::array; + +use miden_core::field::PrimeCharacteristicRing; + +use crate::{ + constraints::{ + lookup::{ + chiplet_air::{ChipletBusContext, ChipletLookupBuilder}, + messages::{MemoryMsg, RangeMsg, SiblingBit, SiblingMsg}, + }, + utils::BoolNot, + }, + lookup::{Deg, LookupBatch, LookupColumn, LookupGroup}, + trace::{ + CHIPLETS_OFFSET, + chiplets::{ + MEMORY_WORD_ADDR_HI_COL_IDX, MEMORY_WORD_ADDR_LO_COL_IDX, + ace::{ACE_INSTRUCTION_ID1_OFFSET, ACE_INSTRUCTION_ID2_OFFSET}, + }, + }, +}; + +/// Upper bound on fractions this emitter pushes into its column per row. +/// +/// Three row-type-disjoint interaction sets, mutually exclusive via the chiplet tri-state: +/// - **Sibling-table** on hasher controller rows (`chiplet_active.controller`): the MV/MU split is +/// mutually exclusive (`s2` vs `1-s2`) and the direction bit cuts within each side, so at most +/// one of the four fires per row → 1 fraction. +/// - **ACE memory reads** on ACE rows (`chiplet_active.ace`): `f_ace_read` / `f_ace_eval` are +/// mutually exclusive via `block_sel` → 1 fraction. +/// - **Memory-side range checks** on memory rows (`chiplet_active.memory`): a 5-remove batch (`d0`, +/// `d1`, `w0`, `w1`, `4·w1`) fires unconditionally when the outer batch flag is active → 5 +/// fractions. +/// +/// Row-type disjointness means only one set fires per row, so the per-row max is +/// `max(1, 1, 5) = 5`. +pub(in crate::constraints::lookup) const MAX_INTERACTIONS_PER_ROW: usize = 5; + +/// Emit the hash-kernel virtual table bus. +#[allow(clippy::too_many_lines)] +pub(in crate::constraints::lookup) fn emit_hash_kernel_table( + builder: &mut LB, + ctx: &ChipletBusContext, +) where + LB: ChipletLookupBuilder, +{ + let local = ctx.local; + let next = ctx.next; + + // --- Sibling-table setup --- + + // Typed hasher-controller overlay: sub-selectors `s0/s1/s2`, state lanes, `node_index`, + // `mrupdate_id`. Next-row `node_index` for the direction-bit computation. + let ctrl = local.controller(); + let ctrl_next = next.controller(); + + let hs0: LB::Expr = ctrl.s0.into(); + let hs1: LB::Expr = ctrl.s1.into(); + let hs2: LB::Expr = ctrl.s2.into(); + + // Sibling flags — on controller rows, `s0·s1 = 1` selects MU/MV input rows. The new + // layout has dropped the old 32-row cycle row filter; all MU/MV input rows participate. + let controller_flag = ctx.chiplet_active.controller.clone(); + let f_mu_all: LB::Expr = controller_flag.clone() * hs0.clone() * hs1.clone() * hs2.clone(); + let f_mv_all: LB::Expr = controller_flag * hs0 * hs1 * hs2.not(); + + // Raw `Var` captures for the sibling payload fields (Copy). Each closure does its own + // `.into()` so the per-closure construction ends as a flat struct literal. Hasher state + // is split by convention into `rate_0 (4), rate_1 (4), cap (4)` — sibling messages only + // use the rate halves. + let rate_0: [LB::Var; 4] = array::from_fn(|i| ctrl.state[i]); + let rate_1: [LB::Var; 4] = array::from_fn(|i| ctrl.state[4 + i]); + let mrupdate_id = ctrl.mrupdate_id; + let node_index = ctrl.node_index; + + // Direction bit `b = node_index − 2·node_index_next`. The bit / one_minus_bit combine + // multiplicatively into the sibling flags below — they're computed once and cloned into + // each `g.add` / `g.remove` flag argument. + let node_index_next: LB::Expr = ctrl_next.node_index.into(); + let bit: LB::Expr = node_index.into() - node_index_next.double(); + let one_minus_bit: LB::Expr = bit.not(); + + // --- ACE memory-read setup --- + + // Typed ACE chiplet overlay. + let ace = local.ace(); + let block_sel: LB::Expr = ace.s_block.into(); + + // ACE row gate comes from the shared `chiplet_active` snapshot; per-mode split by + // `block_sel`. + let is_ace_row = ctx.chiplet_active.ace.clone(); + let f_ace_read: LB::Expr = is_ace_row.clone() * block_sel.not(); + let f_ace_eval: LB::Expr = is_ace_row * block_sel; + + // Raw `Var` captures for ACE payload fields — each producing closure converts them + // via `.into()` inside its body. + let ace_clk = ace.clk; + let ace_ctx = ace.ctx; + let ace_ptr = ace.ptr; + let ace_v0 = ace.v_0; + let ace_v1 = ace.v_1; + let ace_id_1 = ace.id_1; + let ace_id_2 = ace.eval().id_2; + let ace_eval_op = ace.eval_op; + + // --- Memory-side range-check setup --- + + let mem_active = ctx.chiplet_active.memory.clone(); + let mem = local.memory(); + let mem_d0 = mem.d0; + let mem_d1 = mem.d1; + let mem_w0 = local.chiplets[MEMORY_WORD_ADDR_LO_COL_IDX - CHIPLETS_OFFSET]; + let mem_w1 = local.chiplets[MEMORY_WORD_ADDR_HI_COL_IDX - CHIPLETS_OFFSET]; + + builder.next_column( + |col| { + col.group( + "sibling_ace_memory", + |g| { + // --- SIBLING TABLE --- + // MV adds (old path), MU removes (new path); each splits on the Merkle + // direction bit into a BitZero (sibling at rate_1) and BitOne (sibling + // at rate_0) branch. Four mutually exclusive interactions total. + for (op_name, is_add, f_all, bit_tag, bit_gate) in [ + ( + "sibling_mv_b0", + true, + f_mv_all.clone(), + SiblingBit::Zero, + one_minus_bit.clone(), + ), + ("sibling_mv_b1", true, f_mv_all, SiblingBit::One, bit.clone()), + ("sibling_mu_b0", false, f_mu_all.clone(), SiblingBit::Zero, one_minus_bit), + ("sibling_mu_b1", false, f_mu_all, SiblingBit::One, bit), + ] { + let gate = f_all * bit_gate; + let build = move || { + let mrupdate_id: LB::Expr = mrupdate_id.into(); + let node_index: LB::Expr = node_index.into(); + let h = match bit_tag { + SiblingBit::Zero => array::from_fn(|i| rate_1[i].into()), + SiblingBit::One => array::from_fn(|i| rate_0[i].into()), + }; + SiblingMsg { bit: bit_tag, mrupdate_id, node_index, h } + }; + if is_add { + g.add(op_name, gate, build, Deg { n: 5, d: 6 }); + } else { + g.remove(op_name, gate, build, Deg { n: 5, d: 6 }); + } + } + + // --- ACE MEMORY READS (chiplet-responses column) --- + // Word read on READ rows. + g.remove( + "ace_mem_read_word", + f_ace_read, + move || { + let clk = ace_clk.into(); + let ctx = ace_ctx.into(); + let addr = ace_ptr.into(); + let word = [ + ace_v0.0.into(), + ace_v0.1.into(), + ace_v1.0.into(), + ace_v1.1.into(), + ]; + MemoryMsg::read_word(ctx, addr, clk, word) + }, + Deg { n: 5, d: 6 }, + ); + + // Element read on EVAL rows. + g.remove( + "ace_mem_eval_element", + f_ace_eval, + move || { + let clk = ace_clk.into(); + let ctx = ace_ctx.into(); + let addr = ace_ptr.into(); + let id_1: LB::Expr = ace_id_1.into(); + let id_2: LB::Expr = ace_id_2.into(); + let eval_op: LB::Expr = ace_eval_op.into(); + let element = id_1 + + id_2 * LB::Expr::from(ACE_INSTRUCTION_ID1_OFFSET) + + (eval_op + LB::Expr::ONE) + * LB::Expr::from(ACE_INSTRUCTION_ID2_OFFSET); + MemoryMsg::read_element(ctx, addr, clk, element) + }, + Deg { n: 5, d: 6 }, + ); + + // --- MEMORY-SIDE RANGE CHECKS (BusId::RangeCheck) --- + // Five removes per memory-active row: + // - `d0`, `d1` — the two 16-bit delta limbs used by the memory chiplet's + // sorted-access constraints. + // - `w0`, `w1`, `4·w1` — the word-address decomposition limbs. The `4·w1` check + // additionally enforces `w1 ∈ [0, 2^14)`, which bounds `word_addr = 4·(w0 + + // 2^16·w1) < 2^32`. + g.batch( + "memory_range_checks", + mem_active, + move |b| { + b.remove( + "mem_d0", + RangeMsg { value: mem_d0.into() }, + Deg { n: 3, d: 4 }, + ); + b.remove( + "mem_d1", + RangeMsg { value: mem_d1.into() }, + Deg { n: 3, d: 4 }, + ); + let w0: LB::Expr = mem_w0.into(); + let w1: LB::Expr = mem_w1.into(); + let w1_mul4 = w1.clone() * LB::Expr::from_u16(4); + b.remove("mem_w0", RangeMsg { value: w0 }, Deg { n: 3, d: 4 }); + b.remove("mem_w1", RangeMsg { value: w1 }, Deg { n: 3, d: 4 }); + b.remove( + "mem_w1_mul4", + RangeMsg { value: w1_mul4 }, + Deg { n: 3, d: 4 }, + ); + }, + Deg { n: 4, d: 5 }, + ); + }, + Deg { n: 7, d: 8 }, + ); + }, + Deg { n: 7, d: 8 }, + ); +} diff --git a/air/src/constraints/lookup/buses/lookup_op_flags.rs b/air/src/constraints/lookup/buses/lookup_op_flags.rs new file mode 100644 index 0000000000..d24fc43217 --- /dev/null +++ b/air/src/constraints/lookup/buses/lookup_op_flags.rs @@ -0,0 +1,589 @@ +//! Bus-scoped operation flags for the LogUp lookup argument. +//! +//! [`LookupOpFlags`] is a narrower cousin of [`crate::constraints::op_flags::OpFlags`] that +//! carries only the ~32 flags the bus emitters in [`super`] actually read — enough to gate +//! every interaction without materialising the ~150-field surface `OpFlags` exposes to the +//! stack / decoder / chiplet constraint code. +//! +//! The two construction paths live side by side: +//! +//! - [`from_main_cols`](LookupOpFlags::from_main_cols) — polynomial, shared by the constraint-path +//! adapter and the debug builders. Mirrors the relevant parts of +//! [`OpFlags::new`](crate::constraints::op_flags::OpFlags::new) but skips every prefix product +//! that would feed only unused flags. +//! - [`from_boolean_row`](LookupOpFlags::from_boolean_row) — prover-path override that decodes the +//! 7-bit opcode as a `u8` and flips exactly one flag per row. Saves a further factor by +//! sidestepping Felt arithmetic altogether on the discrete flags. +//! +//! The method-accessor shape intentionally mirrors `OpFlags` so the bus emitters read +//! `op_flags.join()` / `op_flags.overflow()` etc. without caring which constructor ran. + +use core::array; + +use miden_core::{ + Felt, + field::{Algebra, PrimeCharacteristicRing}, + operations::opcodes, +}; + +use crate::constraints::{ + decoder::columns::DecoderCols, op_flags::get_op_index, stack::columns::StackCols, +}; + +// LOOKUP OP FLAGS +// ================================================================================================ + +/// Subset of [`OpFlags`](crate::constraints::op_flags::OpFlags) consumed by the LogUp bus +/// emitters. +/// +/// Parameterised by the expression type `E` so the same struct serves the symbolic constraint +/// path and the concrete-row prover path. Only one flag is non-zero on any valid row, same as +/// `OpFlags`. +pub struct LookupOpFlags { + // -- Degree-4 individual ops (current row) -------------------------------------------------- + end: E, + repeat: E, + respan: E, + call: E, + syscall: E, + mrupdate: E, + cryptostream: E, + + // -- Degree-5 individual ops ---------------------------------------------------------------- + join: E, + split: E, + span: E, + loop_op: E, + dyn_op: E, + dyncall: E, + push: E, + hperm: E, + mpverify: E, + mstream: E, + pipe: E, + evalcircuit: E, + log_precompile: E, + hornerbase: E, + hornerext: E, + + // -- Degree-7 individual ops ---------------------------------------------------------------- + mload: E, + mstore: E, + mloadw: E, + mstorew: E, + u32and: E, + u32xor: E, + + // -- Next-row control flow (degree 4) ------------------------------------------------------- + end_next: E, + repeat_next: E, + halt_next: E, + + // -- Composite flags ------------------------------------------------------------------------ + left_shift: E, + right_shift: E, + overflow: E, + u32_rc_op: E, +} + +// CONSTRUCTORS +// ================================================================================================ + +impl LookupOpFlags +where + E: PrimeCharacteristicRing + Clone, +{ + /// Polynomial constructor used by the constraint-path adapter and the debug builders. + /// + /// Mirrors the structure of [`OpFlags::new`](crate::constraints::op_flags::OpFlags::new) + /// but computes only the prefix products needed for the ~32 bus-consumed flags. The + /// shared `b32 / b321 / b3210 / b432` prefix tables are still built once up-front so + /// the per-flag cost is a single multiplication each. + pub fn from_main_cols( + decoder: &DecoderCols, + stack: &StackCols, + decoder_next: &DecoderCols, + ) -> Self + where + V: Copy, + E: Algebra, + { + // -- Bit selectors: bits[k][0] = 1 - b_k, bits[k][1] = b_k -------------------------- + let bits: [[E; 2]; 7] = array::from_fn(|k| { + let val = decoder.op_bits[k]; + [E::ONE - val, val.into()] + }); + + // -- Shared prefix product tables (same shape as OpFlags::new) ---------------------- + let b32: [E; 4] = array::from_fn(|i| bits[3][i >> 1].clone() * bits[2][i & 1].clone()); + let b321: [E; 8] = array::from_fn(|i| b32[i >> 1].clone() * bits[1][i & 1].clone()); + let b3210: [E; 16] = array::from_fn(|i| b321[i >> 1].clone() * bits[0][i & 1].clone()); + let b432: [E; 8] = array::from_fn(|i| bits[4][i >> 2].clone() * b32[i & 3].clone()); + + // -- Degree-7 subset -------------------------------------------------------------- + // deg-7 flag(op) = b654[op >> 4] * b321[(op >> 1) & 7] * bits[0][op & 1]. + // All six bus-consumed deg-7 opcodes have b6=0, so we only need b654[0] (b5=0,b4=0) + // for MLOAD and b654[2] (b5=1,b4=0) for the rest. + let b654_0 = bits[6][0].clone() * bits[5][0].clone() * bits[4][0].clone(); + let b654_2 = bits[6][0].clone() * bits[5][1].clone() * bits[4][0].clone(); + let deg7 = |b654: &E, op: u8| -> E { + let op = op as usize; + b654.clone() * b321[(op >> 1) & 7].clone() * bits[0][op & 1].clone() + }; + let mload = deg7(&b654_0, opcodes::MLOAD); + let u32and = deg7(&b654_2, opcodes::U32AND); + let u32xor = deg7(&b654_2, opcodes::U32XOR); + let mloadw = deg7(&b654_2, opcodes::MLOADW); + let mstore = deg7(&b654_2, opcodes::MSTORE); + let mstorew = deg7(&b654_2, opcodes::MSTOREW); + + // -- Degree-5 subset -------------------------------------------------------------- + let deg5_extra: E = decoder.extra[0].into(); + let deg5 = |op: u8| -> E { deg5_extra.clone() * b3210[get_op_index(op)].clone() }; + let hperm = deg5(opcodes::HPERM); + let mpverify = deg5(opcodes::MPVERIFY); + let pipe = deg5(opcodes::PIPE); + let mstream = deg5(opcodes::MSTREAM); + let split = deg5(opcodes::SPLIT); + let loop_op = deg5(opcodes::LOOP); + let span = deg5(opcodes::SPAN); + let join = deg5(opcodes::JOIN); + let dyn_op = deg5(opcodes::DYN); + let push = deg5(opcodes::PUSH); + let dyncall = deg5(opcodes::DYNCALL); + let evalcircuit = deg5(opcodes::EVALCIRCUIT); + let log_precompile = deg5(opcodes::LOGPRECOMPILE); + let hornerbase = deg5(opcodes::HORNERBASE); + let hornerext = deg5(opcodes::HORNEREXT); + + // -- Degree-4 subset -------------------------------------------------------------- + let deg4_extra: E = decoder.extra[1].into(); + let deg4 = |op: u8| -> E { b432[get_op_index(op)].clone() * deg4_extra.clone() }; + let end = deg4(opcodes::END); + let repeat = deg4(opcodes::REPEAT); + let respan = deg4(opcodes::RESPAN); + let call = deg4(opcodes::CALL); + let syscall = deg4(opcodes::SYSCALL); + let mrupdate = deg4(opcodes::MRUPDATE); + let cryptostream = deg4(opcodes::CRYPTOSTREAM); + + // -- Next-row control flow (END / REPEAT / HALT only) ------------------------------ + // prefix = extra[1]' * b4' = b6'*b5'*b4'. Distinguishes among the four deg-4 ops + // under the `0b0111_xxxx` family via (b3', b2'). + let (end_next, repeat_next, halt_next) = { + let prefix: E = decoder_next.extra[1].into(); + let prefix = prefix * decoder_next.op_bits[4]; + let b3n: E = decoder_next.op_bits[3].into(); + let b2n: E = decoder_next.op_bits[2].into(); + let nb3n = E::ONE - b3n.clone(); + let nb2n = E::ONE - b2n.clone(); + ( + prefix.clone() * nb3n.clone() * nb2n, // END: nb3' * nb2' + prefix.clone() * nb3n * b2n.clone(), // REPEAT: nb3' * b2' + prefix * b3n * b2n, // HALT: b3' * b2' + ) + }; + + // -- Composite flags -------------------------------------------------------------- + // u32_rc_op = prefix_100 = b6*(1-b5)*(1-b4), degree 3. + let u32_rc_op = bits[6][1].clone() * bits[5][0].clone() * bits[4][0].clone(); + + // right_shift_scalar (degree 6): prefix_011 + PUSH + U32SPLIT. + // U32SPLIT is a degree-6 op: u32_rc_op * b321[get_op_index(U32SPLIT)]. + let u32split = u32_rc_op.clone() * b321[get_op_index(opcodes::U32SPLIT)].clone(); + let prefix_01 = bits[6][0].clone() * bits[5][1].clone(); + let prefix_011 = prefix_01.clone() * bits[4][1].clone(); + let right_shift = prefix_011 + push.clone() + u32split; + + // left_shift_scalar (degree 5): + // prefix_010 + u32_add3_madd_group + SPLIT + LOOP + REPEAT + END*is_loop + DYN. + // DYNCALL intentionally excluded (see OpFlags::left_shift doc). + let prefix_010 = prefix_01 * bits[4][0].clone(); + let u32_add3_madd_group = u32_rc_op.clone() * bits[3][1].clone() * bits[2][1].clone(); + let is_loop = decoder.end_block_flags().is_loop; + let end_loop = end.clone() * is_loop; + let left_shift = prefix_010 + + u32_add3_madd_group + + split.clone() + + loop_op.clone() + + repeat.clone() + + end_loop + + dyn_op.clone(); + + // overflow = (b0 - 16) * h0, degree 2 (uses stack columns, not decoder). + let b0: E = stack.b0.into(); + let overflow = (b0 - E::from_u64(16)) * stack.h0; + + Self { + end, + repeat, + respan, + call, + syscall, + mrupdate, + cryptostream, + join, + split, + span, + loop_op, + dyn_op, + dyncall, + push, + hperm, + mpverify, + mstream, + pipe, + evalcircuit, + log_precompile, + hornerbase, + hornerext, + mload, + mstore, + mloadw, + mstorew, + u32and, + u32xor, + end_next, + repeat_next, + halt_next, + left_shift, + right_shift, + overflow, + u32_rc_op, + } + } +} + +// BOOLEAN FAST PATH (PROVER) +// ================================================================================================ + +impl LookupOpFlags { + /// Concrete-row constructor used by the prover-path adapter. + /// + /// Decodes the 7-bit opcode from `decoder.op_bits` as a `u8` and flips exactly one + /// (or no) flag instead of materialising the polynomial products that + /// [`from_main_cols`](LookupOpFlags::from_main_cols) builds. Semantics match + /// `from_main_cols` on any valid trace — op_bits are 0/1 by the decoder's boolean + /// constraint, and the `is_loop` hasher slot that gates `left_shift`'s `end` term is + /// also 0/1 on valid traces. + /// + /// When `debug_assertions` is on, the output is cross-checked field-by-field against + /// `from_main_cols` so divergences surface immediately in tests. + pub fn from_boolean_row( + decoder: &DecoderCols, + stack: &StackCols, + decoder_next: &DecoderCols, + ) -> Self { + let opcode = decode_opcode_u8(&decoder.op_bits); + let opcode_next = decode_opcode_u8(&decoder_next.op_bits); + + let mut f = Self::all_zero(); + + // One match statement per row — branch-free in the hot case (inactive deg-7 rows + // fall through the default arm). + match opcode { + opcodes::JOIN => f.join = Felt::ONE, + opcodes::SPLIT => f.split = Felt::ONE, + opcodes::SPAN => f.span = Felt::ONE, + opcodes::LOOP => f.loop_op = Felt::ONE, + opcodes::DYN => f.dyn_op = Felt::ONE, + opcodes::DYNCALL => f.dyncall = Felt::ONE, + opcodes::PUSH => f.push = Felt::ONE, + opcodes::HPERM => f.hperm = Felt::ONE, + opcodes::MPVERIFY => f.mpverify = Felt::ONE, + opcodes::MSTREAM => f.mstream = Felt::ONE, + opcodes::PIPE => f.pipe = Felt::ONE, + opcodes::EVALCIRCUIT => f.evalcircuit = Felt::ONE, + opcodes::LOGPRECOMPILE => f.log_precompile = Felt::ONE, + opcodes::HORNERBASE => f.hornerbase = Felt::ONE, + opcodes::HORNEREXT => f.hornerext = Felt::ONE, + opcodes::END => f.end = Felt::ONE, + opcodes::REPEAT => f.repeat = Felt::ONE, + opcodes::RESPAN => f.respan = Felt::ONE, + opcodes::CALL => f.call = Felt::ONE, + opcodes::SYSCALL => f.syscall = Felt::ONE, + opcodes::MRUPDATE => f.mrupdate = Felt::ONE, + opcodes::CRYPTOSTREAM => f.cryptostream = Felt::ONE, + opcodes::MLOAD => f.mload = Felt::ONE, + opcodes::MSTORE => f.mstore = Felt::ONE, + opcodes::MLOADW => f.mloadw = Felt::ONE, + opcodes::MSTOREW => f.mstorew = Felt::ONE, + opcodes::U32AND => f.u32and = Felt::ONE, + opcodes::U32XOR => f.u32xor = Felt::ONE, + _ => {}, + } + match opcode_next { + opcodes::END => f.end_next = Felt::ONE, + opcodes::REPEAT => f.repeat_next = Felt::ONE, + opcodes::HALT => f.halt_next = Felt::ONE, + _ => {}, + } + + // -- Composite flags via integer range tests ------------------------------------ + // u32_rc_op: 1 iff opcode is a degree-6 u32 op (opcodes 64..80). + f.u32_rc_op = bool_to_felt((64..80).contains(&opcode)); + // right_shift_scalar: prefix_011 (opcodes 48..64) + PUSH + U32SPLIT. + f.right_shift = bool_to_felt( + (48..64).contains(&opcode) || opcode == opcodes::PUSH || opcode == opcodes::U32SPLIT, + ); + // left_shift_scalar: prefix_010 (opcodes 32..48) + U32ADD3/U32MADD + SPLIT/LOOP/ + // REPEAT/DYN + END*is_loop. DYNCALL intentionally excluded — see OpFlags::left_shift. + let is_end_loop = opcode == opcodes::END && decoder.end_block_flags().is_loop == Felt::ONE; + f.left_shift = bool_to_felt( + (32..48).contains(&opcode) + || matches!( + opcode, + opcodes::U32ADD3 + | opcodes::U32MADD + | opcodes::SPLIT + | opcodes::LOOP + | opcodes::REPEAT + | opcodes::DYN + ) + || is_end_loop, + ); + + // overflow uses non-boolean stack columns, so keep the Felt expression. + f.overflow = (stack.b0 - Felt::from_u64(16)) * stack.h0; + + // -- Debug parity check ----------------------------------------------------------- + // Cross-check against the polynomial path. `from_main_cols` always produces + // identical output on valid traces — mismatches here indicate either (a) a bug in + // this fast path or (b) a non-boolean op_bits row, which the decoder constraint + // would also reject. + #[cfg(debug_assertions)] + f.assert_matches_polynomial(decoder, stack, decoder_next); + + f + } + + fn all_zero() -> Self { + Self { + end: Felt::ZERO, + repeat: Felt::ZERO, + respan: Felt::ZERO, + call: Felt::ZERO, + syscall: Felt::ZERO, + mrupdate: Felt::ZERO, + cryptostream: Felt::ZERO, + join: Felt::ZERO, + split: Felt::ZERO, + span: Felt::ZERO, + loop_op: Felt::ZERO, + dyn_op: Felt::ZERO, + dyncall: Felt::ZERO, + push: Felt::ZERO, + hperm: Felt::ZERO, + mpverify: Felt::ZERO, + mstream: Felt::ZERO, + pipe: Felt::ZERO, + evalcircuit: Felt::ZERO, + log_precompile: Felt::ZERO, + hornerbase: Felt::ZERO, + hornerext: Felt::ZERO, + mload: Felt::ZERO, + mstore: Felt::ZERO, + mloadw: Felt::ZERO, + mstorew: Felt::ZERO, + u32and: Felt::ZERO, + u32xor: Felt::ZERO, + end_next: Felt::ZERO, + repeat_next: Felt::ZERO, + halt_next: Felt::ZERO, + left_shift: Felt::ZERO, + right_shift: Felt::ZERO, + overflow: Felt::ZERO, + u32_rc_op: Felt::ZERO, + } + } + + #[cfg(debug_assertions)] + fn assert_matches_polynomial( + &self, + decoder: &DecoderCols, + stack: &StackCols, + decoder_next: &DecoderCols, + ) { + let p = Self::from_main_cols::(decoder, stack, decoder_next); + // The deg-5/7 row flags can appear in out-of-range rows where op_bits aren't + // strictly boolean (e.g. the padded tail of a trace); only cross-check on + // genuinely boolean rows so this doesn't fire on harmless padding. + if !decoder.op_bits.iter().all(|b| *b == Felt::ZERO || *b == Felt::ONE) { + return; + } + macro_rules! check { + ($($name:ident),* $(,)?) => { + $( + debug_assert_eq!( + self.$name, p.$name, + concat!("LookupOpFlags parity: ", stringify!($name)), + ); + )* + }; + } + check!( + end, + repeat, + respan, + call, + syscall, + mrupdate, + cryptostream, + join, + split, + span, + loop_op, + dyn_op, + dyncall, + push, + hperm, + mpverify, + mstream, + pipe, + evalcircuit, + log_precompile, + hornerbase, + hornerext, + mload, + mstore, + mloadw, + mstorew, + u32and, + u32xor, + end_next, + repeat_next, + halt_next, + left_shift, + right_shift, + overflow, + u32_rc_op, + ); + } +} + +/// Decodes a 7-bit opcode out of boolean op-bit columns. On a valid trace every `op_bits[k]` +/// is in `{0, 1}`; anything else is undefined but also rejected by the decoder's boolean +/// constraints, so this isn't a safety-critical conversion. +#[inline] +fn decode_opcode_u8(op_bits: &[Felt; 7]) -> u8 { + let mut out = 0u8; + for (k, bit) in op_bits.iter().enumerate() { + out |= ((bit.as_canonical_u64() & 1) as u8) << k; + } + out +} + +#[inline] +fn bool_to_felt(b: bool) -> Felt { + if b { Felt::ONE } else { Felt::ZERO } +} + +// STATE ACCESSORS +// ================================================================================================ + +macro_rules! accessors { + ($( $(#[$meta:meta])* $name:ident ),* $(,)?) => { + impl LookupOpFlags { + $( + $(#[$meta])* + #[inline(always)] + pub fn $name(&self) -> E { + self.$name.clone() + } + )* + } + }; +} + +accessors!( + // Degree-4 individual ops + end, + repeat, + respan, + call, + syscall, + mrupdate, + cryptostream, + // Degree-5 individual ops + join, + split, + span, + loop_op, + dyn_op, + dyncall, + push, + hperm, + mpverify, + mstream, + pipe, + evalcircuit, + log_precompile, + hornerbase, + hornerext, + // Degree-7 individual ops + mload, + mstore, + mloadw, + mstorew, + u32and, + u32xor, + // Next-row control flow + end_next, + repeat_next, + halt_next, + // Composite flags + left_shift, + right_shift, + overflow, + u32_rc_op, +); + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_core::{ONE, ZERO, operations::Operation}; + + use super::LookupOpFlags; + use crate::constraints::op_flags::generate_test_row; + + /// Tests `u32_rc_op` flag for u32 operations. + #[test] + fn u32_rc_op_flag() { + // U32 operations that require range checks (degree 6). + let u32_ops = [ + Operation::U32add, + Operation::U32sub, + Operation::U32mul, + Operation::U32div, + Operation::U32split, + Operation::U32assert2(ZERO), + Operation::U32add3, + Operation::U32madd, + ]; + + for op in u32_ops { + let flags = flags_for_opcode(op.op_code().into()); + assert_eq!(flags.u32_rc_op(), ONE, "u32_rc_op should be ONE for {op:?}"); + } + + // Non-u32 operations. + let non_u32_ops = [ + Operation::Add, + Operation::Mul, + Operation::And, // Bitwise AND is degree 7, not u32. + ]; + + for op in non_u32_ops { + let flags = flags_for_opcode(op.op_code().into()); + assert_eq!(flags.u32_rc_op(), ZERO, "u32_rc_op should be ZERO for {op:?}"); + } + } + + fn flags_for_opcode(opcode: usize) -> LookupOpFlags { + let row = generate_test_row(opcode); + let row_next = generate_test_row(0); + LookupOpFlags::from_main_cols(&row.decoder, &row.stack, &row_next.decoder) + } +} diff --git a/air/src/constraints/lookup/buses/mod.rs b/air/src/constraints/lookup/buses/mod.rs new file mode 100644 index 0000000000..f2ad79891c --- /dev/null +++ b/air/src/constraints/lookup/buses/mod.rs @@ -0,0 +1,125 @@ +//! Per-bus emitters for the Miden VM's LogUp argument. +//! +//! The Miden VM's LogUp buses are emitted across 7 columns — most host a single bus, but +//! several host two or more linearly-independent buses sharing one running accumulator via +//! distinct `bus_prefix[bus]` additive bases (see per-bus module docs for the merges). +//! Each emitter is a crate-private `pub(in crate::constraints::lookup) fn emit_*` that +//! opens a single [`super::LookupBuilder::column`] closure and describes the bus's +//! interactions via [`super::LookupColumn::group`] or +//! [`super::LookupColumn::group_with_cached_encoding`]. +//! +//! The emitters are routed through two separate [`super::LookupAir`] implementors: +//! - [`super::main_air::MainLookupAir`] for the main-trace columns. +//! - [`super::chiplet_air::ChipletLookupAir`] for the chiplet-trace columns. +//! +//! [`crate::ProcessorAir`]'s `LookupAir` impl is a thin aggregator that calls both in sequence, +//! preserving the legacy `enforce_main` / `enforce_chiplet` column order for downstream +//! consumers that want the full 7-column picture in a single `eval` call. +//! +//! ## Shared precompute contexts +//! +//! The main-trace and chiplet-trace contexts live next to their respective LookupAirs: +//! - [`super::main_air::MainBusContext`] — two-row window plus the shared +//! [`crate::constraints::op_flags::OpFlags`] instance consumed by the 4 main-trace emitters. +//! - [`super::chiplet_air::ChipletBusContext`] — two-row window plus the shared +//! [`ChipletActiveFlags`] snapshot consumed by the 3 chiplet-trace emitters. +//! +//! Each context is built once per `eval` through an extension-trait hook +//! ([`super::main_air::MainLookupBuilder::build_op_flags`] / +//! [`super::chiplet_air::ChipletLookupBuilder::build_chiplet_active`]), so a future +//! prover-side override can replace the polynomial construction with a cheaper boolean fast +//! path without touching any emitter code. [`ChipletActiveFlags`] itself lives in this +//! module because it's the pure-compute helper both the default chiplet hook and any +//! future override want to reach for; it does not depend on either `MainCols` context type. + +use miden_core::field::{Algebra, PrimeCharacteristicRing}; + +use crate::MainCols; + +pub(in crate::constraints::lookup) mod block_hash_and_op_group; +pub(in crate::constraints::lookup) mod block_stack_and_range_logcap; +pub(in crate::constraints::lookup) mod chiplet_requests; +pub(in crate::constraints::lookup) mod chiplet_responses; +pub(in crate::constraints::lookup) mod hash_kernel; +pub(in crate::constraints::lookup) mod lookup_op_flags; +pub(in crate::constraints::lookup) mod stack_overflow; +pub(in crate::constraints::lookup) mod wiring; + +pub(in crate::constraints::lookup) use lookup_op_flags::LookupOpFlags; + +// CHIPLET ACTIVE FLAGS +// ================================================================================================ + +/// Per-chiplet `is_active` expressions, mirroring the active-flag block of +/// [`build_chiplet_selectors`](super::super::chiplets::selectors::build_chiplet_selectors). +/// +/// These are the only chiplet-flag flavors the LogUp buses consume — +/// `is_transition` / `is_last` / `next_is_first` are used only by the constraint-path +/// chiplet code, not by the LogUp argument — so this type carries no other variants. +/// +/// The constructor is a pure compute function: it builds the same algebra as +/// `build_chiplet_selectors` but does NOT emit any `when` / `assert_*` calls, so it is +/// safe to run in parallel with the constraint-path chiplet selector pass. +pub(crate) struct ChipletActiveFlags { + /// `is_active` for the hasher controller sub-chiplet (= `s_ctrl`). + pub controller: E, + /// `is_active` for the hasher permutation sub-chiplet (= `s_perm`). + pub permutation: E, + /// `is_active` for the bitwise chiplet (= `s0 - s01`). + pub bitwise: E, + /// `is_active` for the memory chiplet (= `s01 - s012`). + pub memory: E, + /// `is_active` for the ACE chiplet (= `s012 - s0123`). + pub ace: E, + /// `is_active` for the kernel ROM chiplet (= `s0123 - s01234`). + pub kernel_rom: E, +} + +impl ChipletActiveFlags +where + E: PrimeCharacteristicRing + Clone, +{ + /// Build the chiplet active-flag snapshot from a `MainCols` borrow. + /// + /// Mirrors the active-flag block of + /// [`build_chiplet_selectors`](super::super::chiplets::selectors::build_chiplet_selectors): + /// - `s_ctrl = chiplets[0]`, `s_perm` + /// - virtual `s0 = 1 - s_ctrl - s_perm` + /// - prefix chain `s01 / s012 / s0123 / s01234` + /// - `is_bitwise = s0 - s01`, `is_memory = s01 - s012`, `is_ace = s012 - s0123`, `is_kernel_rom + /// = s0123 - s01234` + pub fn from_main_cols(local: &MainCols) -> Self + where + V: Copy, + E: Algebra, + { + let s_ctrl: E = local.chiplets[0].into(); + let s_perm: E = local.s_perm.into(); + let s1: E = local.chiplets[1].into(); + let s2: E = local.chiplets[2].into(); + let s3: E = local.chiplets[3].into(); + let s4: E = local.chiplets[4].into(); + + // Virtual non-hasher selector and prefix products. + let s0 = E::ONE - s_ctrl.clone() - s_perm.clone(); + let s01 = s0.clone() * s1; + let s012 = s01.clone() * s2; + let s0123 = s012.clone() * s3; + let s01234 = s0123.clone() * s4; + + // Active flags via the subtraction trick. + let bitwise = s0 - s01.clone(); + let memory = s01 - s012.clone(); + let ace = s012 - s0123.clone(); + let kernel_rom = s0123 - s01234; + + Self { + controller: s_ctrl, + permutation: s_perm, + bitwise, + memory, + ace, + kernel_rom, + } + } +} diff --git a/air/src/constraints/lookup/buses/stack_overflow.rs b/air/src/constraints/lookup/buses/stack_overflow.rs new file mode 100644 index 0000000000..585a216f76 --- /dev/null +++ b/air/src/constraints/lookup/buses/stack_overflow.rs @@ -0,0 +1,101 @@ +//! Stack overflow table bus (`BusId::StackOverflowTable`). +//! +//! Three mutually exclusive interactions: +//! +//! - **Right shift** (add): when an item is pushed past stack[15], record `(clk, s15, b1)` — the +//! cycle, the spilled value, and the link to the previous overflow row. +//! - **Left shift ∧ non-empty overflow** (remove): when an item is popped back from the overflow +//! table, consume the matching `(b1, s15', b1')` row. +//! - **DYNCALL ∧ non-empty overflow** (remove): DYNCALL is excluded from the `left_shift` +//! aggregate; it consumes `(b1, s15', hasher_state[5])` because the new overflow pointer after a +//! DYNCALL is staged in the decoder hasher state, not in `b1'` (which is reset). + +use crate::{ + constraints::lookup::{ + main_air::{MainBusContext, MainLookupBuilder}, + messages::StackOverflowMsg, + }, + lookup::{Deg, LookupColumn, LookupGroup}, +}; + +/// Upper bound on fractions this emitter pushes into its column per row. +/// +/// All three interactions gate on mutually exclusive opcode flags (right_shift, left_shift, +/// dyncall — DYNCALL is excluded from the `left_shift` aggregate by construction), so at +/// most one fires per row. +pub(in crate::constraints::lookup) const MAX_INTERACTIONS_PER_ROW: usize = 1; + +/// Emit the stack overflow table bus. +pub(in crate::constraints::lookup) fn emit_stack_overflow( + builder: &mut LB, + ctx: &MainBusContext, +) where + LB: MainLookupBuilder, +{ + let local = ctx.local; + let next = ctx.next; + let op_flags = &ctx.op_flags; + + let clk = local.system.clk; + let s15 = local.stack.get(15); + let s15_next = next.stack.get(15); + let b1 = local.stack.b1; + let b1_next = next.stack.b1; + let h5 = local.decoder.hasher_state[5]; + + // `op_flags.overflow() = (b0 - 16) * h0`, degree 2. Aliased once so each remove site + // does not re-clone the underlying expression. + let f_overflow = op_flags.overflow(); + let f_left_overflow = op_flags.left_shift() * f_overflow.clone(); + let f_dyncall_overflow = op_flags.dyncall() * f_overflow; + + builder.next_column( + |col| { + col.group( + "overflow_interactions", + |g| { + // Right shift: push `(clk, s15, b1)` onto the overflow table. + g.add( + "right_shift", + op_flags.right_shift(), + || StackOverflowMsg { + clk: clk.into(), + val: s15.into(), + prev: b1.into(), + }, + Deg { n: 6, d: 7 }, + ); + + // Left shift with non-empty overflow: pop `(b1, s15', b1')` off the overflow + // table. + g.remove( + "left_shift", + f_left_overflow, + || StackOverflowMsg { + clk: b1.into(), + val: s15_next.into(), + prev: b1_next.into(), + }, + Deg { n: 7, d: 8 }, + ); + + // DYNCALL with non-empty overflow: pop `(b1, s15', h5)`. The new overflow + // pointer lives in `hasher_state[5]` after a DYNCALL, since + // `b1'` is reset by the call. + g.remove( + "dyncall", + f_dyncall_overflow, + || StackOverflowMsg { + clk: b1.into(), + val: s15_next.into(), + prev: h5.into(), + }, + Deg { n: 7, d: 8 }, + ); + }, + Deg { n: 7, d: 8 }, + ); + }, + Deg { n: 7, d: 8 }, + ); +} diff --git a/air/src/constraints/lookup/buses/wiring.rs b/air/src/constraints/lookup/buses/wiring.rs new file mode 100644 index 0000000000..823269b8be --- /dev/null +++ b/air/src/constraints/lookup/buses/wiring.rs @@ -0,0 +1,280 @@ +//! `v_wiring` shared bus column (`BusId::{AceWiring, HasherPermLinkInput, +//! HasherPermLinkOutput}`). +//! +//! All three buses live inside **one** [`super::super::LookupColumn::group`] call. The chiplet +//! tri-state (`s_ctrl + s_perm + s0_virtual = 1`) makes ACE rows, hasher controller rows, +//! and hasher permutation rows pairwise mutually exclusive, so the simple-group +//! composition `U_g += (d_i − 1)·f_i`, `V_g += m_i·f_i` is sound: at most one of the five +//! interactions fires per row, and the column's running `(U, V)` takes MAX over per- +//! interaction degrees rather than summing them (which a sibling-group split would do). +//! Each bus's denominator uses a distinct `bus_prefix[bus]` additive base, so even +//! though they share the same accumulator their contributions are linearly independent in +//! the extension field and cannot cancel across buses. +//! +//! ## ACE wiring (`BusId::AceWiring`) +//! +//! Two READ/EVAL wire interactions gated by the ACE chiplet selector + its per-row block +//! selector, folded into a single `ace_flag`-gated batch with `sblock`-muxed multiplicities: +//! `wire_0` fires with the same multiplicity `m_0` on both READ and EVAL rows, so it +//! factors out; `wire_1` and `wire_2` get `sblock`-parameterized multiplicities that recover +//! the original rational at every row. This drops the outer selector from degree 5 +//! (`is_read`/`is_eval`) to degree 4 (`ace_flag`), bringing the batch's contribution to +//! `(deg(U_g), deg(V_g)) = (7, 8)`. +//! +//! Algebraic equivalence: +//! +//! ```text +//! is_read · (m_0/wire_0 + m_1/wire_1) +//! + is_eval · (m_0/wire_0 − 1/wire_1 − 1/wire_2) +//! = ace_flag · [ m_0/wire_0 +//! + ((1 − sblock)·m_1 − sblock)/wire_1 +//! + (−sblock)/wire_2 ] +//! ``` +//! +//! The `wire_2` payload reads the physical columns shared with the READ overlay's `m_1` +//! slot — under `sblock = 1` (EVAL) they hold `v_2`, and under `sblock = 0` (READ) the +//! `wire_2` interaction is fully suppressed via the `−sblock` multiplicity, so the +//! interpretation collapses to the READ-mode one. +//! +//! ## Hasher perm-link (`BusId::HasherPermLink{Input,Output}`) +//! +//! Binds hasher controller rows to permutation sub-chiplet rows. Without this bus the +//! permutation segment is structurally independent from the controller, and a malicious +//! prover could pair any controller `(state_in, state_out)` with any perm-cycle execution +//! (or skip the cycle entirely). Four mutually exclusive interactions split across two +//! domain-separated buses: +//! +//! - **Controller input** (`s_ctrl · is_input`, multiplicity `+1`) — controller side of a +//! (state_in, state_out) pair. Routed to `BusId::HasherPermLinkInput`. +//! - **Controller output** (`s_ctrl · is_output`, multiplicity `+1`). Routed to +//! `BusId::HasherPermLinkOutput`. +//! - **Permutation row 0** (`s_perm · is_init_ext`, multiplicity `−m`) — input boundary of a +//! Poseidon2 cycle. `m` is read from `PermutationCols.multiplicity` and is constant within the +//! cycle by [`crate::constraints::chiplets::permutation`]. Routed to +//! `BusId::HasherPermLinkInput`. +//! - **Permutation row 15** (`s_perm · (1 − periodic_sum)`, multiplicity `−m`) — output boundary of +//! the same cycle. Routed to `BusId::HasherPermLinkOutput`. +//! +//! The widest perm-link contribution is `f_ctrl_output` with gate degree 3 — strictly below +//! the ACE batch's `(7, 8)` — so merging into the same group leaves the column's transition +//! at `max(1 + 7, 8) = 8`. + +use core::{array, borrow::Borrow}; + +use miden_core::field::PrimeCharacteristicRing; + +use crate::{ + constraints::{ + chiplets::columns::PeriodicCols, + lookup::{ + chiplet_air::{ChipletBusContext, ChipletLookupBuilder}, + messages::{AceWireMsg, HasherPermLinkMsg}, + }, + utils::BoolNot, + }, + lookup::{Deg, LookupBatch, LookupColumn, LookupGroup}, +}; + +/// Upper bound on fractions this emitter pushes into its column per row. +/// +/// Single group hosts both buses. The chiplet tri-state makes ACE, hasher-controller, and +/// hasher-permutation rows pairwise mutually exclusive, so on any given row only one of: +/// - **ACE wiring batch** on ACE rows: 3 fractions (wire_0 / wire_1 / wire_2 push unconditionally +/// when the outer `ace_flag` fires). +/// - **Perm-link** on hasher controller rows: 1 fraction (one of ctrl_input / ctrl_output, split by +/// `s0`). +/// - **Perm-link** on hasher permutation rows: 1 fraction (one of row 0 / row 15, split by the +/// periodic cycle schedule). +/// +/// Per-row max is therefore `max(3, 1, 1) = 3`. +pub(in crate::constraints::lookup) const MAX_INTERACTIONS_PER_ROW: usize = 3; + +/// Emit the `v_wiring` shared column: ACE wiring + hasher perm-link. +pub(in crate::constraints::lookup) fn emit_v_wiring( + builder: &mut LB, + ctx: &ChipletBusContext, +) where + LB: ChipletLookupBuilder, +{ + let local = ctx.local; + + // ---- ACE wiring captures (Group 1) ---- + let ace_flag = ctx.chiplet_active.ace.clone(); + + // Typed ACE chiplet overlay. `read()` exposes `m_0` / `m_1`, `eval()` exposes `v_2`; + // wiring uses both overlays because its `sblock`-muxed multiplicities combine the + // READ and EVAL row interpretations onto one column. + let ace = local.ace(); + let ace_read = ace.read(); + let ace_eval = ace.eval(); + + // Raw `Var` captures — every field below is Copy and flows directly into a struct + // field inside the batch closure, so we skip the per-field `LB::Expr` conversion up + // front and do it lazily. Prefixed with `ace_` where the shorter name would clash + // with the outer function parameter `ctx`. + let ace_clk = ace.clk; + let ace_ctx = ace.ctx; + let id_0 = ace.id_0; + let id_1 = ace.id_1; + let id_2 = ace_eval.id_2; + let v_0 = ace.v_0; + let v_1 = ace.v_1; + let v_2 = ace_eval.v_2; + let m_0 = ace_read.m_0; + let m_1 = ace_read.m_1; + + // `sblock` mixes into the wire_1 / wire_2 multiplicities; keep it as an `LB::Expr` + // since the `wire_1_mult` expression needs arithmetic against the already-converted + // `m_1`. + let sblock: LB::Expr = ace.s_block.into(); + + // ---- Perm-link captures (Group 2) ---- + + // Periodic Poseidon2 cycle selectors. `is_init_ext` is 1 on cycle row 0 only; the four + // selectors together cover rows 0..14, so `1 - sum` is 1 only on the cycle boundary + // row 15. The `permutation/mod.rs` cycle-alignment constraints pin perm row 0 to cycle + // row 0 and perm row 15 to cycle row 15. + let (perm_row0_select, perm_row15_select): (LB::Expr, LB::Expr) = { + let periodic: &PeriodicCols = builder.periodic_values().borrow(); + let h = periodic.hasher; + let is_init_ext: LB::Expr = h.is_init_ext.into(); + let is_ext: LB::Expr = h.is_ext.into(); + let is_packed_int: LB::Expr = h.is_packed_int.into(); + let is_int_ext: LB::Expr = h.is_int_ext.into(); + let not_cycle_end = is_init_ext.clone() + is_ext + is_packed_int + is_int_ext; + (is_init_ext, LB::Expr::ONE - not_cycle_end) + }; + + // Controller-side row-kind flags. `is_input = s0` (deg 1); `is_output = (1-s0)*(1-s1)` + // (deg 2). Padding rows (`s0=0, s1=1`) are excluded automatically by both expressions. + let ctrl = local.controller(); + let s0c: LB::Expr = ctrl.s0.into(); + let s1c: LB::Expr = ctrl.s1.into(); + let is_input = s0c.clone(); + let is_output = (LB::Expr::ONE - s0c) * (LB::Expr::ONE - s1c); + + let controller_flag = ctx.chiplet_active.controller.clone(); + let permutation_flag = ctx.chiplet_active.permutation.clone(); + + let f_ctrl_input = controller_flag.clone() * is_input; + let f_ctrl_output = controller_flag * is_output; + let f_perm_row0 = permutation_flag.clone() * perm_row0_select; + let f_perm_row15 = permutation_flag * perm_row15_select; + + let ctrl_state: [LB::Var; 12] = array::from_fn(|i| ctrl.state[i]); + let perm = local.permutation(); + let perm_state: [LB::Var; 12] = array::from_fn(|i| perm.state[i]); + let perm_mult = perm.multiplicity; + + builder.next_column( + |col| { + // Single group hosts both buses. ACE rows (`chiplet_active.ace`), controller rows + // (`chiplet_active.controller`), and permutation rows (`chiplet_active.permutation`) + // are pairwise mutually exclusive via the chiplet tri-state, so the simple-group + // composition is sound. Merging into one group takes MAX over per-interaction + // degrees instead of multiplying sibling `(U_g, V_g)` pairs — critical for keeping + // this column's transition inside the degree-9 budget. + col.group( + "ace_perm_link", + |g| { + // ---- ACE wiring (BusId::AceWiring) ---- + // + // Single `ace_flag`-gated batch with `sblock`-muxed multiplicities for wire_1 + // and wire_2. `wire_0`'s `m_0` is invariant across the READ/EVAL split, so it + // lives in the batch as a plain trace-column multiplicity. + g.batch( + "ace_wiring", + ace_flag, + move |b| { + let m_0: LB::Expr = m_0.into(); + let m_1: LB::Expr = m_1.into(); + let wire_1_mult = sblock.not() * m_1 - sblock.clone(); + let wire_2_mult = LB::Expr::ZERO - sblock; + + let wire_0 = AceWireMsg { + clk: ace_clk.into(), + ctx: ace_ctx.into(), + id: id_0.into(), + v0: v_0.0.into(), + v1: v_0.1.into(), + }; + b.insert("wire_0", m_0, wire_0, Deg { n: 5, d: 5 }); + + let wire_1 = AceWireMsg { + clk: ace_clk.into(), + ctx: ace_ctx.into(), + id: id_1.into(), + v0: v_1.0.into(), + v1: v_1.1.into(), + }; + b.insert("wire_1", wire_1_mult, wire_1, Deg { n: 6, d: 5 }); + + let wire_2 = AceWireMsg { + clk: ace_clk.into(), + ctx: ace_ctx.into(), + id: id_2.into(), + v0: v_2.0.into(), + v1: v_2.1.into(), + }; + b.insert("wire_2", wire_2_mult, wire_2, Deg { n: 5, d: 5 }); + }, + Deg { n: 4, d: 3 }, + ); + + // ---- Hasher perm-link (BusId::HasherPermLink{Input,Output}) ---- + + // Controller input: +1 / encode(ctrl.state) on HasherPermLinkInput. + g.add( + "perm_ctrl_input", + f_ctrl_input, + move || { + let state: [LB::Expr; 12] = ctrl_state.map(Into::into); + HasherPermLinkMsg::Input { state } + }, + Deg { n: 2, d: 3 }, + ); + + // Controller output: +1 / encode(ctrl.state) on HasherPermLinkOutput. + g.add( + "perm_ctrl_output", + f_ctrl_output, + move || { + let state: [LB::Expr; 12] = ctrl_state.map(Into::into); + HasherPermLinkMsg::Output { state } + }, + Deg { n: 3, d: 4 }, + ); + + // Perm row 0: -m / encode(perm.state) on HasherPermLinkInput. Multiplicity is + // `0 - m` so the LogUp accumulator subtracts the fraction. + let perm_mult_input: LB::Expr = LB::Expr::ZERO - perm_mult.into(); + g.insert( + "perm_row0", + f_perm_row0, + perm_mult_input, + move || { + let state: [LB::Expr; 12] = perm_state.map(Into::into); + HasherPermLinkMsg::Input { state } + }, + Deg { n: 3, d: 3 }, + ); + + // Perm row 15: -m / encode(perm.state) on HasherPermLinkOutput. + let perm_mult_output: LB::Expr = LB::Expr::ZERO - perm_mult.into(); + g.insert( + "perm_row15", + f_perm_row15, + perm_mult_output, + move || { + let state: [LB::Expr; 12] = perm_state.map(Into::into); + HasherPermLinkMsg::Output { state } + }, + Deg { n: 3, d: 3 }, + ); + }, + Deg { n: 8, d: 7 }, + ); + }, + Deg { n: 8, d: 7 }, + ); +} diff --git a/air/src/constraints/lookup/chiplet_air.rs b/air/src/constraints/lookup/chiplet_air.rs new file mode 100644 index 0000000000..cbca2b0ab6 --- /dev/null +++ b/air/src/constraints/lookup/chiplet_air.rs @@ -0,0 +1,149 @@ +//! Chiplet-trace LogUp lookup AIR. +//! +//! Owns the chiplet-trace side of the Miden VM's LogUp argument: three permutation +//! columns, one per `emit_*` function in [`super::buses`]. This module wires them together +//! via a single [`ChipletBusContext`] that carries the two-row window plus a shared +//! [`ChipletActiveFlags`] snapshot. +//! +//! Columns (in emission order): +//! - chiplet responses (memory / bitwise / hasher replies). +//! - hash-kernel virtual table. +//! - shared wiring column: ACE wiring + hasher perm-link (the legacy `v_wiring`). +//! +//! The [`ChipletLookupBuilder`] extension trait mirrors [`super::main_air::MainLookupBuilder`]: +//! it exposes a single construction hook so the prover path can eventually skip the dead +//! polynomial products in [`ChipletActiveFlags::from_main_cols`]. For now the default body +//! is the polynomial path and every adapter picks it up via an empty `impl` block. + +use core::borrow::Borrow; + +use miden_crypto::stark::air::WindowAccess; + +use super::{ + BusId, + buses::{ + ChipletActiveFlags, + chiplet_responses::{self, emit_chiplet_responses}, + hash_kernel::{self, emit_hash_kernel_table}, + wiring::{self, emit_v_wiring}, + }, +}; +use crate::{ + Felt, MainCols, + lookup::{LookupAir, LookupBuilder}, +}; + +// CHIPLET LOOKUP BUILDER +// ================================================================================================ + +/// Extension trait the chiplet-trace [`LookupAir`] requires from its [`LookupBuilder`]. +/// +/// Carries a single hook, [`build_chiplet_active`](Self::build_chiplet_active), for +/// constructing the shared [`ChipletActiveFlags`] snapshot consumed by the three +/// chiplet-trace bus emitters. Symmetric to [`super::main_air::MainLookupBuilder`]; see its +/// docs for the rationale behind the explicit-impl-no-blanket pattern. +pub(crate) trait ChipletLookupBuilder: LookupBuilder { + /// Build the shared [`ChipletActiveFlags`] snapshot for one `eval` call. + /// + /// Default body calls [`ChipletActiveFlags::from_main_cols`], matching the pre-split + /// behavior of `ChipletTraceContext::new`. Adapters override this when a cheaper + /// construction path is available (e.g. the prover path, where the selector columns + /// are concrete 0/1 and the active-flag subtractions can short-circuit). + fn build_chiplet_active(&self, local: &MainCols) -> ChipletActiveFlags { + ChipletActiveFlags::from_main_cols(local) + } +} + +// CHIPLET BUS CONTEXT +// ================================================================================================ + +/// Shared context for the three chiplet-trace bus emitters. +/// +/// Holds the two-row window plus a single [`ChipletActiveFlags`] snapshot built once per +/// `eval` through [`ChipletLookupBuilder::build_chiplet_active`]. Every emitter reads +/// `ctx.local`, `ctx.next`, and +/// `ctx.chiplet_active.{controller,permutation,bitwise,memory,ace,kernel_rom}` directly — +/// field access, no method indirection. +pub(crate) struct ChipletBusContext<'a, LB> +where + LB: LookupBuilder, +{ + /// Typed view of the current row. + pub local: &'a MainCols, + /// Typed view of the next row. + pub next: &'a MainCols, + /// Per-chiplet `is_active` flags, computed from `local`'s selector columns via the + /// builder-provided hook. + pub chiplet_active: ChipletActiveFlags, +} + +impl<'a, LB> ChipletBusContext<'a, LB> +where + LB: ChipletLookupBuilder, +{ + /// Build the shared chiplet-trace context for one `eval` call. + pub fn new(builder: &LB, local: &'a MainCols, next: &'a MainCols) -> Self { + let chiplet_active = builder.build_chiplet_active(local); + Self { local, next, chiplet_active } + } +} + +// CHIPLET LOOKUP AIR +// ================================================================================================ + +/// LogUp lookup argument over the chiplet trace. +/// +/// Zero-sized. Emits three permutation columns (see module docs for per-column contents), +/// matching the aggregated `LookupAir` impl on [`crate::ProcessorAir`]. The main-trace half +/// of the argument lives in [`super::main_air::MainLookupAir`]. +#[derive(Copy, Clone, Debug, Default)] +pub(crate) struct ChipletLookupAir; + +/// Per-column fraction stride, in emission order (see [`ChipletLookupAir`] docs). +pub(crate) const CHIPLET_COLUMN_SHAPE: [usize; 3] = [ + chiplet_responses::MAX_INTERACTIONS_PER_ROW, + hash_kernel::MAX_INTERACTIONS_PER_ROW, + wiring::MAX_INTERACTIONS_PER_ROW, +]; + +impl LookupAir for ChipletLookupAir +where + LB: ChipletLookupBuilder, +{ + fn num_columns(&self) -> usize { + CHIPLET_COLUMN_SHAPE.len() + } + + fn column_shape(&self) -> &[usize] { + &CHIPLET_COLUMN_SHAPE + } + + fn max_message_width(&self) -> usize { + // Must match `ProcessorAir::max_message_width` since this sub-AIR shares the + // aggregator's bus-prefix table. The widest chiplet-trace payload is + // `HasherMsg::State` on the responses column at 15 slots, but the aggregator's + // `MIDEN_MAX_MESSAGE_WIDTH = 16` is kept for MASM transcript alignment. + super::messages::MIDEN_MAX_MESSAGE_WIDTH + } + + fn num_bus_ids(&self) -> usize { + // Chiplet-trace emitters touch the shared chiplet responses column plus + // `BusId::{SiblingTable, RangeCheck, AceWiring, HasherPermLinkInput, + // HasherPermLinkOutput}`. The adapter's bus-prefix table is shared + // across every LookupAir it runs, so returning `BusId::COUNT` (the total bus-type + // count) is the safe upper bound. + BusId::COUNT + } + + fn eval(&self, builder: &mut LB) { + let main = builder.main(); + let local: &MainCols<_> = main.current_slice().borrow(); + let next: &MainCols<_> = main.next_slice().borrow(); + + let ctx = ChipletBusContext::new(&*builder, local, next); + + emit_chiplet_responses::(builder, &ctx); + emit_hash_kernel_table::(builder, &ctx); + emit_v_wiring::(builder, &ctx); + } +} diff --git a/air/src/constraints/lookup/extension_impls.rs b/air/src/constraints/lookup/extension_impls.rs new file mode 100644 index 0000000000..74f99dbe2a --- /dev/null +++ b/air/src/constraints/lookup/extension_impls.rs @@ -0,0 +1,87 @@ +//! Miden-side extension-trait impls pinning the generic +//! [`ConstraintLookupBuilder`] and [`ProverLookupBuilder`] adapters to the +//! Miden [`MainLookupBuilder`] / [`ChipletLookupBuilder`] traits. +//! +//! Both traits require `LookupBuilder`, so the impls live here +//! (Miden-side) rather than alongside the adapters — the generic adapter +//! code itself is field-polymorphic. +//! +//! The constraint-path adapter and the two debug builders pick up the default +//! polynomial bodies of [`MainLookupBuilder::build_op_flags`] and +//! [`ChipletLookupBuilder::build_chiplet_active`]. The prover-path adapter +//! overrides `build_op_flags` with +//! [`LookupOpFlags::from_boolean_row`](super::buses::LookupOpFlags::from_boolean_row), +//! which decodes the 7-bit opcode as a `u8` and flips exactly one flag per row +//! instead of materialising the polynomial products the symbolic path needs. +//! `build_chiplet_active` stays on the default — the chiplet selectors produce +//! only six outputs via four subtractions, so the boolean shortcut is noise. + +use miden_core::field::ExtensionField; +use miden_crypto::stark::air::LiftedAirBuilder; + +use super::{buses::LookupOpFlags, chiplet_air::ChipletLookupBuilder, main_air::MainLookupBuilder}; +use crate::{ + Felt, MainCols, + lookup::{ConstraintLookupBuilder, ProverLookupBuilder}, +}; + +// CONSTRAINT PATH +// ================================================================================================ + +impl<'ab, AB> MainLookupBuilder for ConstraintLookupBuilder<'ab, AB> where + AB: LiftedAirBuilder +{ +} + +impl<'ab, AB> ChipletLookupBuilder for ConstraintLookupBuilder<'ab, AB> where + AB: LiftedAirBuilder +{ +} + +// PROVER PATH +// ================================================================================================ + +impl<'a, EF> MainLookupBuilder for ProverLookupBuilder<'a, Felt, EF> +where + EF: ExtensionField, +{ + /// Override: use the boolean fast path instead of the default polynomial body. + /// + /// On the prover side `decoder.op_bits` are concrete 0/1 Felt values (enforced by the + /// decoder's boolean constraint), so a `u8` opcode decode + single-field write replaces + /// ~100 Felt multiplications in the shared prefix tree. Semantics match the default body + /// on any valid trace — a `debug_assertions` parity check inside `from_boolean_row` + /// surfaces divergences immediately. + fn build_op_flags( + &self, + local: &MainCols, + next: &MainCols, + ) -> LookupOpFlags { + LookupOpFlags::from_boolean_row(&local.decoder, &local.stack, &next.decoder) + } +} + +impl<'a, EF> ChipletLookupBuilder for ProverLookupBuilder<'a, Felt, EF> where + EF: ExtensionField +{ +} + +// DEBUG BUILDERS +// ================================================================================================ +// +// Empty impls for the Felt/QuadFelt-pinned debug builders. They pick up the default +// polynomial bodies of `build_op_flags` / `build_chiplet_active`; the builders only +// compile when the `debug` module is available (gated on `std`), so there's no need +// for a boolean fast-path override. + +#[cfg(feature = "std")] +mod debug_impls { + use super::{ChipletLookupBuilder, MainLookupBuilder}; + use crate::lookup::debug::{DebugTraceBuilder, ValidationBuilder}; + + impl<'ab, 'r> MainLookupBuilder for ValidationBuilder<'ab, 'r> {} + impl<'ab, 'r> ChipletLookupBuilder for ValidationBuilder<'ab, 'r> {} + + impl<'a> MainLookupBuilder for DebugTraceBuilder<'a> {} + impl<'a> ChipletLookupBuilder for DebugTraceBuilder<'a> {} +} diff --git a/air/src/constraints/lookup/main_air.rs b/air/src/constraints/lookup/main_air.rs new file mode 100644 index 0000000000..fd63fce29c --- /dev/null +++ b/air/src/constraints/lookup/main_air.rs @@ -0,0 +1,173 @@ +//! Main-trace LogUp lookup AIR. +//! +//! Owns the main-trace side of the Miden VM's LogUp argument: four permutation columns, one +//! per `emit_*` function in [`super::buses`]. This module wires them together via a single +//! [`MainBusContext`] that carries the two-row window plus a shared [`OpFlags`] instance. +//! +//! Columns (in emission order): +//! - block-stack table + u32 range checks + log-precompile capacity + range-table response (merged +//! — see [`super::buses::block_stack_and_range_logcap`]). +//! - block-hash queue + op-group table. +//! - chiplet requests from the decoder. +//! - stack overflow table. +//! +//! The [`MainLookupBuilder`] extension trait exists so the `OpFlags` construction can diverge +//! between the constraint path (polynomial, today's default) and the prover path (boolean +//! fast path, planned). For now every adapter picks up the default polynomial body via an +//! empty `impl MainLookupBuilder for …` block — the structural split is the sole purpose of +//! this module today. + +use core::borrow::Borrow; + +use miden_crypto::stark::air::WindowAccess; + +use super::{ + BusId, + buses::{ + LookupOpFlags, + block_hash_and_op_group::{self, emit_block_hash_and_op_group}, + block_stack_and_range_logcap::{self, emit_block_stack_and_range_logcap}, + chiplet_requests::{self, emit_chiplet_requests}, + stack_overflow::{self, emit_stack_overflow}, + }, +}; +use crate::{ + Felt, MainCols, + lookup::{LookupAir, LookupBuilder}, +}; + +// MAIN LOOKUP BUILDER +// ================================================================================================ + +/// Extension trait the main-trace [`LookupAir`] requires from its [`LookupBuilder`]. +/// +/// Carries a single hook, [`build_op_flags`](Self::build_op_flags), for constructing the +/// shared [`LookupOpFlags`] instance that the four main-trace bus emitters consume. The +/// default body uses the polynomial path (today's behavior for both the constraint-path and +/// the prover-path adapters). A future prover-side optimization will override this method on +/// the prover adapter to skip the dead polynomial products that come from decoder bits +/// already being concrete 0/1 values; no other code moves. +/// +/// There is intentionally **no** blanket `impl MainLookupBuilder for LB` +/// — Rust coherence would then forbid the prover adapter from overriding the default body. +/// Each adapter implements this trait explicitly with an empty `impl` block that picks up +/// the default. +pub(crate) trait MainLookupBuilder: LookupBuilder { + /// Build the shared [`LookupOpFlags`] instance for one `eval` call. + /// + /// Default body calls [`LookupOpFlags::from_main_cols`], the polynomial path. Adapters + /// override this when a cheaper construction path is available (e.g. the prover path, + /// where decoder bits are concrete 0/1). + fn build_op_flags( + &self, + local: &MainCols, + next: &MainCols, + ) -> LookupOpFlags { + LookupOpFlags::from_main_cols(&local.decoder, &local.stack, &next.decoder) + } +} + +// MAIN BUS CONTEXT +// ================================================================================================ + +/// Shared context for the four main-trace bus emitters. +/// +/// Holds the two-row window plus a single [`LookupOpFlags`] instance built once per `eval` +/// through [`MainLookupBuilder::build_op_flags`]. Every emitter reads `ctx.local`, +/// `ctx.next`, and `ctx.op_flags.()` directly — no method indirection beyond the +/// single clone each accessor performs. +pub(crate) struct MainBusContext<'a, LB> +where + LB: LookupBuilder, +{ + /// Typed view of the current row. + pub local: &'a MainCols, + /// Typed view of the next row. + pub next: &'a MainCols, + /// Operation flags computed from `(local.decoder, local.stack, next.decoder)` via the + /// builder-provided hook. + pub op_flags: LookupOpFlags, +} + +impl<'a, LB> MainBusContext<'a, LB> +where + LB: MainLookupBuilder, +{ + /// Build the shared main-trace context for one `eval` call. + /// + /// Delegates the `LookupOpFlags` construction to the builder's + /// [`MainLookupBuilder::build_op_flags`] hook so the constraint-path and prover-path + /// adapters can diverge on construction cost without the emitters noticing. + pub fn new(builder: &LB, local: &'a MainCols, next: &'a MainCols) -> Self { + let op_flags = builder.build_op_flags(local, next); + Self { local, next, op_flags } + } +} + +// MAIN LOOKUP AIR +// ================================================================================================ + +/// LogUp lookup argument over the main trace. +/// +/// Zero-sized. Emits four permutation columns: the first packs block-stack + u32 range +/// checks + log-precompile capacity + range-table response; the second unions block-hash +/// queue and op-group table; the third hosts the decoder's chiplet requests; the fourth +/// hosts the stack overflow table. The chiplet-trace half of the argument lives in +/// [`super::chiplet_air::ChipletLookupAir`]. +#[derive(Copy, Clone, Debug, Default)] +pub(crate) struct MainLookupAir; + +/// Per-column fraction stride, in emission order (see [`MainLookupAir`] docs). +pub(crate) const MAIN_COLUMN_SHAPE: [usize; 4] = [ + block_stack_and_range_logcap::MAX_INTERACTIONS_PER_ROW, + block_hash_and_op_group::MAX_INTERACTIONS_PER_ROW, + chiplet_requests::MAX_INTERACTIONS_PER_ROW, + stack_overflow::MAX_INTERACTIONS_PER_ROW, +]; + +impl LookupAir for MainLookupAir +where + LB: MainLookupBuilder, +{ + fn num_columns(&self) -> usize { + MAIN_COLUMN_SHAPE.len() + } + + fn column_shape(&self) -> &[usize] { + &MAIN_COLUMN_SHAPE + } + + fn max_message_width(&self) -> usize { + // Must match `ProcessorAir::max_message_width` since this sub-AIR shares the + // aggregator's bus-prefix table. The widest main-trace payload is + // `HasherMsg::State` (linear_hash_init / return_state) at 15 slots, but the + // aggregator's `MIDEN_MAX_MESSAGE_WIDTH = 16` is kept for MASM transcript alignment. + super::messages::MIDEN_MAX_MESSAGE_WIDTH + } + + fn num_bus_ids(&self) -> usize { + // Main-trace emitters touch `BusId::{BlockStackTable, BlockHashTable, OpGroupTable, + // RangeCheck, LogPrecompileTranscript}` plus the shared chiplet-requests column. + // The adapter's bus-prefix table is shared across every LookupAir it runs, so + // returning `BusId::COUNT` (the total bus-type count) is the safe upper bound. + BusId::COUNT + } + + fn eval(&self, builder: &mut LB) { + // Hold the `MainWindow` as an owned value so its borrow on the underlying builder is + // released by the time we grab the `&mut builder` for the per-column emitters. + let main = builder.main(); + let local: &MainCols<_> = main.current_slice().borrow(); + let next: &MainCols<_> = main.next_slice().borrow(); + + // Build the shared main-trace context once per `eval`. `&*builder` is an immutable + // reborrow of the mutable parameter; it's alive only for the duration of the + // constructor call and is released before the emitters take their own `&mut builder`. + let ctx = MainBusContext::new(&*builder, local, next); + + emit_block_stack_and_range_logcap::(builder, &ctx); + emit_block_hash_and_op_group::(builder, &ctx); + emit_chiplet_requests::(builder, &ctx); + emit_stack_overflow::(builder, &ctx); + } +} diff --git a/air/src/constraints/lookup/messages.rs b/air/src/constraints/lookup/messages.rs new file mode 100644 index 0000000000..84bf7a4220 --- /dev/null +++ b/air/src/constraints/lookup/messages.rs @@ -0,0 +1,943 @@ +//! Message structs for LogUp bus interactions. +//! +//! Each struct represents a reduced denominator encoding: `α + Σ βⁱ · field_i`. +//! Fields are named for readability; the [`super::lookup::LookupMessage`] trait +//! (implemented further down in this file) provides the `encode` method that +//! produces the extension-field value. +//! +//! Chiplet messages are addressed by interaction-specific bus domains (one [`BusId`] +//! variant per semantic message kind). Constructors pick the interaction domain; payloads +//! start directly with the semantic fields (addr, ctx, etc.). +//! +//! All structs are generic over `E` (base-field expression type, typically `AB::Expr`). + +use miden_core::field::{Algebra, PrimeCharacteristicRing}; + +use crate::lookup::Challenges; + +// BUS IDENTIFIERS +// ================================================================================================ + +/// Width of the `beta_powers` table `Challenges` precomputes for Miden's bus +/// messages, i.e. the exponent of `gamma = beta^MIDEN_MAX_MESSAGE_WIDTH` used in +/// `bus_prefix[i] = alpha + (i + 1) * gamma`. +/// +/// Must match the Poseidon2 absorption loop in `crates/lib/core/asm/stark/` which +/// reads the same β-power table during recursive verification. +pub const MIDEN_MAX_MESSAGE_WIDTH: usize = 16; + +/// Domain-separated bus interaction identifier. +/// +/// Each variant identifies a distinct bus interaction type. When encoding a message, +/// the bus is cast to `usize` and indexes into +/// [`Challenges::bus_prefix`](crate::lookup::Challenges) to obtain the additive base +/// `bus_prefix[bus] = alpha + (bus + 1) * gamma`. +#[repr(usize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum BusId { + // --- Out-of-circuit (boundary correction / reduced_aux_values) --- + /// Kernel ROM init: kernel procedure digests from variable-length public inputs. + KernelRomInit = 0, + /// Block hash table (decoder p2): root program hash boundary correction. + BlockHashTable = 1, + /// Log-precompile transcript: initial/final capacity state boundary correction. + LogPrecompileTranscript = 2, + + // --- In-circuit buses --- + KernelRomCall = 3, + HasherLinearHashInit = 4, + HasherReturnState = 5, + HasherAbsorption = 6, + HasherReturnHash = 7, + HasherMerkleVerifyInit = 8, + HasherMerkleOldInit = 9, + HasherMerkleNewInit = 10, + MemoryReadElement = 11, + MemoryWriteElement = 12, + MemoryReadWord = 13, + MemoryWriteWord = 14, + Bitwise = 15, + AceInit = 16, + /// Block stack table (decoder p1): tracks control flow block nesting. + BlockStackTable = 17, + /// Op group table (decoder p3): tracks operation batch consumption. + OpGroupTable = 18, + /// Stack overflow table. + StackOverflowTable = 19, + /// Sibling table: shares Merkle tree sibling nodes between old/new root computations. + SiblingTable = 20, + /// Range checker bus (LogUp). + RangeCheck = 21, + /// ACE wiring bus (LogUp). + AceWiring = 22, + /// Hasher perm-link input bus: pairs controller-input rows with perm-cycle row 0. + HasherPermLinkInput = 23, + /// Hasher perm-link output bus: pairs controller-output rows with perm-cycle row 15. + HasherPermLinkOutput = 24, +} + +impl BusId { + /// Last variant discriminant. Paired with the static assertion below, `COUNT` stays + /// in lockstep with the enum: adding a new variant with a higher discriminant bumps + /// `COUNT` automatically (and the assertion flags a missed update if the new variant's + /// discriminant isn't contiguous). + pub const COUNT: usize = Self::HasherPermLinkOutput as usize + 1; +} + +// Guard against an enum-update that skips a discriminant: any gap would inflate `COUNT` +// relative to the real variant count and silently resize the bus-prefix table. If this +// fires, either fill the gap or extend the check. +const _: () = assert!(BusId::HasherPermLinkOutput as usize == 24); + +// HASHER MESSAGES +// ================================================================================================ + +/// Hasher chiplet message: a [`BusId`] tag plus a variable-width payload. +/// +/// All hasher messages encode as `bus_prefix[kind] + [addr, node_index, ...payload]`; only +/// the payload width differs between variants. +#[derive(Clone, Debug)] +pub struct HasherMsg { + pub kind: BusId, + pub addr: E, + pub node_index: E, + pub payload: HasherPayload, +} + +/// Payload for a [`HasherMsg`]; width varies per interaction kind. +#[derive(Clone, Debug)] +pub enum HasherPayload { + /// 12-lane sponge state. + State([E; 12]), + /// 8-lane rate. + Rate([E; 8]), + /// 4-element word/digest. + Word([E; 4]), +} + +impl HasherMsg { + // --- State messages (14 payload elements: [addr, node_index, state[12]]) --- + + /// Linear hash / control block init: full 12-lane sponge state. + /// + /// Used by: HPERM input, LOGPRECOMPILE input. + pub fn linear_hash_init(addr: E, state: [E; 12]) -> Self { + Self { + kind: BusId::HasherLinearHashInit, + addr, + node_index: E::ZERO, + payload: HasherPayload::State(state), + } + } + + /// Control block init: 8 rate lanes + opcode at `capacity[1]`, zeros elsewhere. + /// + /// Used by: JOIN, SPLIT, LOOP, SPAN, CALL, SYSCALL, DYN, DYNCALL. + pub fn control_block(addr: E, rate: &[E; 8], opcode: u8) -> Self { + let state = [ + rate[0].clone(), + rate[1].clone(), + rate[2].clone(), + rate[3].clone(), + rate[4].clone(), + rate[5].clone(), + rate[6].clone(), + rate[7].clone(), + E::ZERO, + E::from_u16(opcode as u16), + E::ZERO, + E::ZERO, + ]; + Self { + kind: BusId::HasherLinearHashInit, + addr, + node_index: E::ZERO, + payload: HasherPayload::State(state), + } + } + + /// Return full sponge state after permutation. + /// + /// Used by: HPERM output, LOGPRECOMPILE output. + pub fn return_state(addr: E, state: [E; 12]) -> Self { + Self { + kind: BusId::HasherReturnState, + addr, + node_index: E::ZERO, + payload: HasherPayload::State(state), + } + } + + // --- Rate messages (10 payload elements: [addr, node_index, rate[8]]) --- + + /// Absorb new rate into running hash. + /// + /// Used by: RESPAN. + pub fn absorption(addr: E, rate: [E; 8]) -> Self { + Self { + kind: BusId::HasherAbsorption, + addr, + node_index: E::ZERO, + payload: HasherPayload::Rate(rate), + } + } + + // --- Word messages (6 payload elements: [addr, node_index, word[4]]) --- + + /// Return digest only (node_index = 0). + /// + /// Used by: END, MPVERIFY output, MRUPDATE output. + pub fn return_hash(addr: E, word: [E; 4]) -> Self { + Self { + kind: BusId::HasherReturnHash, + addr, + node_index: E::ZERO, + payload: HasherPayload::Word(word), + } + } + + /// Start Merkle path verification (with explicit node_index). + /// + /// Used by: MPVERIFY input. + pub fn merkle_verify_init(addr: E, node_index: E, word: [E; 4]) -> Self { + Self { + kind: BusId::HasherMerkleVerifyInit, + addr, + node_index, + payload: HasherPayload::Word(word), + } + } + + /// Start Merkle update, old path (with explicit node_index). + /// + /// Used by: MRUPDATE old input. + pub fn merkle_old_init(addr: E, node_index: E, word: [E; 4]) -> Self { + Self { + kind: BusId::HasherMerkleOldInit, + addr, + node_index, + payload: HasherPayload::Word(word), + } + } + + /// Start Merkle update, new path (with explicit node_index). + /// + /// Used by: MRUPDATE new input. + pub fn merkle_new_init(addr: E, node_index: E, word: [E; 4]) -> Self { + Self { + kind: BusId::HasherMerkleNewInit, + addr, + node_index, + payload: HasherPayload::Word(word), + } + } +} + +// MEMORY MESSAGES +// ================================================================================================ + +/// Memory chiplet message. Variants differ by payload size. +/// +/// Encodes as `bus_prefix[bus] + [ctx, addr, clk, ...payload]`. Use the [`MemoryMsg`] +/// associated functions (`read_element`, `write_element`, `read_word`, `write_word`) to +/// build messages with the correct interaction kind. +#[derive(Clone, Debug)] +pub enum MemoryMsg { + /// 5-element message: `[ctx, addr, clk, element]`. + Element { + bus: BusId, + ctx: E, + addr: E, + clk: E, + element: E, + }, + /// 8-element message: `[ctx, addr, clk, word[0..4]]`. + Word { + bus: BusId, + ctx: E, + addr: E, + clk: E, + word: [E; 4], + }, +} + +impl MemoryMsg { + /// Read a single element from memory. + pub fn read_element(ctx: E, addr: E, clk: E, element: E) -> Self { + Self::Element { + bus: BusId::MemoryReadElement, + ctx, + addr, + clk, + element, + } + } + + /// Write a single element to memory. + pub fn write_element(ctx: E, addr: E, clk: E, element: E) -> Self { + Self::Element { + bus: BusId::MemoryWriteElement, + ctx, + addr, + clk, + element, + } + } + + /// Read a 4-element word from memory. + pub fn read_word(ctx: E, addr: E, clk: E, word: [E; 4]) -> Self { + Self::Word { + bus: BusId::MemoryReadWord, + ctx, + addr, + clk, + word, + } + } + + /// Write a 4-element word to memory. + pub fn write_word(ctx: E, addr: E, clk: E, word: [E; 4]) -> Self { + Self::Word { + bus: BusId::MemoryWriteWord, + ctx, + addr, + clk, + word, + } + } +} + +// BITWISE MESSAGE +// ================================================================================================ + +/// Bitwise chiplet message (4 elements): `[op, a, b, result]`. +#[derive(Clone, Debug)] +pub struct BitwiseMsg { + pub op: E, + pub a: E, + pub b: E, + pub result: E, +} + +impl BitwiseMsg { + const AND_SELECTOR: u32 = 0; + const XOR_SELECTOR: u32 = 1; + + /// Bitwise AND message (op selector = 0). + pub fn and(a: E, b: E, result: E) -> Self { + Self { + op: E::from_u32(Self::AND_SELECTOR), + a, + b, + result, + } + } + + /// Bitwise XOR message (op selector = 1). + pub fn xor(a: E, b: E, result: E) -> Self { + Self { + op: E::from_u32(Self::XOR_SELECTOR), + a, + b, + result, + } + } +} + +// DECODER MESSAGES +// ================================================================================================ + +/// Block stack message: `[block_id, parent_id, is_loop, ctx, fmp, depth, fn_hash[4]]`. +/// +/// `Simple` — for blocks that don't save context (JOIN/SPLIT/SPAN/DYN/LOOP/RESPAN/END-simple). +/// Context fields are encoded as zeros. +/// +/// `Full` — for blocks that save/restore the caller's execution context +/// (CALL/SYSCALL/DYNCALL/END-call). +#[derive(Clone, Debug)] +pub enum BlockStackMsg { + Simple { + block_id: E, + parent_id: E, + is_loop: E, + }, + Full { + block_id: E, + parent_id: E, + is_loop: E, + ctx: E, + fmp: E, + depth: E, + fn_hash: [E; 4], + }, +} + +/// Block hash queue message (7 elements): +/// `[child_hash[4], parent, is_first_child, is_loop_body]`. +/// +/// `FirstChild` — first child of a JOIN (is_first_child = 1, is_loop_body = 0). +/// `Child` — non-first, non-loop child (is_first_child = 0, is_loop_body = 0). +/// `LoopBody` — loop body entry (is_first_child = 0, is_loop_body = 1). +/// `End` — removal at END; both flags are computed expressions. +#[derive(Clone, Debug)] +pub enum BlockHashMsg { + FirstChild { + parent: E, + child_hash: [E; 4], + }, + Child { + parent: E, + child_hash: [E; 4], + }, + LoopBody { + parent: E, + child_hash: [E; 4], + }, + End { + parent: E, + child_hash: [E; 4], + is_first_child: E, + is_loop_body: E, + }, +} + +/// Op group table message (3 elements): `[batch_id, group_pos, group_value]`. +#[derive(Clone, Debug)] +pub struct OpGroupMsg { + pub batch_id: E, + pub group_pos: E, + pub group_value: E, +} + +impl OpGroupMsg { + /// Create an op group message. Computes `group_pos = group_count - offset`. + pub fn new(batch_id: &E, group_count: V, offset: u16, group_value: E) -> Self + where + V: core::ops::Sub + Clone, + { + Self { + batch_id: batch_id.clone(), + group_pos: group_count - E::from_u16(offset), + group_value, + } + } +} + +// STACK MESSAGE +// ================================================================================================ + +/// Stack overflow table message (3 elements): `[clk, val, prev]`. +/// +/// `clk` is the cycle at which the value spilled past `stack[15]`, `val` is the spilled element, +/// and `prev` links to the previous overflow entry (the prior `b1`). +#[derive(Clone, Debug)] +pub struct StackOverflowMsg { + pub clk: E, + pub val: E, + pub prev: E, +} + +// HASHER PERM-LINK MESSAGE +// ================================================================================================ + +/// Hasher perm-link message (12 elements): `state[0..12]`. +/// +/// Binds hasher controller rows to permutation sub-chiplet rows. The `Input` variant pairs a +/// controller-input row with perm-cycle row 0 on `BusId::HasherPermLinkInput`; the `Output` +/// variant pairs a controller-output row with perm-cycle row 15 on +/// `BusId::HasherPermLinkOutput`. `state` carries all 12 sponge lanes (rate_0, rate_1, capacity). +#[derive(Clone, Debug)] +pub enum HasherPermLinkMsg { + Input { state: [E; 12] }, + Output { state: [E; 12] }, +} + +// KERNEL ROM MESSAGE +// ================================================================================================ + +/// Kernel ROM message (4 elements): `bus_prefix[bus] + [digest[4]]`. +/// +/// Two bus domains: INIT (one remove per declared procedure, balanced by the boundary +/// correction from public inputs) and CALL (one insert per SYSCALL, carrying the +/// multiplicity from kernel ROM column 0; balanced by decoder-emitted SYSCALL removes). +#[derive(Clone, Debug)] +pub struct KernelRomMsg { + bus: BusId, + pub digest: [E; 4], +} + +impl KernelRomMsg { + /// Kernel procedure call message (SYSCALL request side + chiplet CALL response). + pub fn call(digest: [E; 4]) -> Self { + Self { bus: BusId::KernelRomCall, digest } + } + + /// Kernel procedure init message (public-input boundary + chiplet INIT response). + pub fn init(digest: [E; 4]) -> Self { + Self { bus: BusId::KernelRomInit, digest } + } +} + +// ACE MESSAGE +// ================================================================================================ + +/// ACE circuit evaluation init message (5 elements): `[clk, ctx, ptr, num_read, num_eval]`. +#[derive(Clone, Debug)] +pub struct AceInitMsg { + pub clk: E, + pub ctx: E, + pub ptr: E, + pub num_read: E, + pub num_eval: E, +} + +// RANGE CHECK MESSAGE +// ================================================================================================ + +/// Range check message (1 element): `[value]`. +/// +/// The denominator is `α + β⁰ · value`. +#[derive(Clone, Debug)] +pub struct RangeMsg { + pub value: E, +} + +// LOG-PRECOMPILE CAPACITY MESSAGE +// ================================================================================================ + +/// Log-precompile capacity state message (4 elements): `cap[4]`. +#[derive(Clone, Debug)] +pub struct LogCapacityMsg { + pub capacity: [E; 4], +} + +// SIBLING TABLE MESSAGE +// ================================================================================================ + +// Sibling table message for Merkle path operations (sparse encoding). + +// ACE WIRING MESSAGE +// ================================================================================================ + +/// ACE wiring bus message (5 elements): `[clk, ctx, id, v0, v1]`. +/// +/// Encodes a single wire entry for the ACE wiring bus. Each wire carries +/// an identifier and a two-coefficient extension-field value. +#[derive(Clone, Debug)] +pub struct AceWireMsg { + pub clk: E, + pub ctx: E, + pub id: E, + pub v0: E, + pub v1: E, +} + +// CHIPLET RESPONSE MESSAGES +// ================================================================================================ + +/// Memory chiplet response message with conditional element/word encoding. +/// +/// The chiplet-side memory response must select between element access (4 payload +/// elements: `[ctx, addr, clk, element]`) and word access (7 payload elements: +/// `[ctx, addr, clk, word[4]]`) based on `is_word`. The label, address, and element are +/// all pre-computed from the chiplet columns (including the idx0/idx1 element mux). +#[derive(Clone, Debug)] +pub struct MemoryResponseMsg { + pub is_read: E, + pub ctx: E, + pub addr: E, + pub clk: E, + pub is_word: E, + pub element: E, + pub word: [E; 4], +} + +// LOOKUP MESSAGE IMPLEMENTATIONS +// ================================================================================================ + +use crate::lookup::message::LookupMessage; + +// --- HasherMsg (interaction-specific bus ids; payload starts at β⁰) ------------------------------ + +impl LookupMessage for HasherMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + let bp = &challenges.beta_powers; + let mut acc = challenges.bus_prefix[self.kind as usize].clone(); + acc += bp[0].clone() * self.addr.clone(); + acc += bp[1].clone() * self.node_index.clone(); + match &self.payload { + HasherPayload::State(state) => { + for i in 0..12 { + acc += bp[i + 2].clone() * state[i].clone(); + } + }, + HasherPayload::Rate(rate) => { + for i in 0..8 { + acc += bp[i + 2].clone() * rate[i].clone(); + } + }, + HasherPayload::Word(word) => { + for i in 0..4 { + acc += bp[i + 2].clone() * word[i].clone(); + } + }, + } + acc + } +} + +// --- MemoryMsg (interaction-specific bus ids; payload starts at β⁰) ------------------------------ + +impl LookupMessage for MemoryMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + let bp = &challenges.beta_powers; + let bus = match self { + Self::Element { bus, .. } | Self::Word { bus, .. } => *bus as usize, + }; + let mut acc = challenges.bus_prefix[bus].clone(); + match self { + Self::Element { ctx, addr, clk, element, .. } => { + acc += bp[0].clone() * ctx.clone(); + acc += bp[1].clone() * addr.clone(); + acc += bp[2].clone() * clk.clone(); + acc += bp[3].clone() * element.clone(); + }, + Self::Word { ctx, addr, clk, word, .. } => { + acc += bp[0].clone() * ctx.clone(); + acc += bp[1].clone() * addr.clone(); + acc += bp[2].clone() * clk.clone(); + for i in 0..4 { + acc += bp[i + 3].clone() * word[i].clone(); + } + }, + } + acc + } +} + +// --- BitwiseMsg (BusId::Bitwise; op at β⁰) +// ---------------------------------------------------------- + +impl LookupMessage for BitwiseMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + challenges.encode( + BusId::Bitwise as usize, + [self.op.clone(), self.a.clone(), self.b.clone(), self.result.clone()], + ) + } +} + +// --- BlockStackMsg (BusId::BlockStackTable) --------------------------------------------- + +impl LookupMessage for BlockStackMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + let bp = &challenges.beta_powers; + let mut acc = challenges.bus_prefix[BusId::BlockStackTable as usize].clone(); + match self { + // `Simple` zero-pads to 10 slots; slots `3..10` contribute `β^k · 0 = 0` so + // they are elided from the loop. + Self::Simple { block_id, parent_id, is_loop } => { + acc += bp[0].clone() * block_id.clone(); + acc += bp[1].clone() * parent_id.clone(); + acc += bp[2].clone() * is_loop.clone(); + }, + Self::Full { + block_id, + parent_id, + is_loop, + ctx, + fmp, + depth, + fn_hash, + } => { + acc += bp[0].clone() * block_id.clone(); + acc += bp[1].clone() * parent_id.clone(); + acc += bp[2].clone() * is_loop.clone(); + acc += bp[3].clone() * ctx.clone(); + acc += bp[4].clone() * fmp.clone(); + acc += bp[5].clone() * depth.clone(); + acc += bp[6].clone() * fn_hash[0].clone(); + acc += bp[7].clone() * fn_hash[1].clone(); + acc += bp[8].clone() * fn_hash[2].clone(); + acc += bp[9].clone() * fn_hash[3].clone(); + }, + } + acc + } +} + +// --- BlockHashMsg (BusId::BlockHashTable) ----------------------------------------------- + +impl LookupMessage for BlockHashMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + // Per-variant fan-in: produce the (parent, child_hash, is_first_child, is_loop_body) + // tuple, then emit a flat 7-slot payload laid out as + // `[child_hash[4], parent, is_first_child, is_loop_body]`. + let (parent, child_hash, is_first_child, is_loop_body) = match self { + Self::FirstChild { parent, child_hash } => (parent, child_hash, E::ONE, E::ZERO), + Self::Child { parent, child_hash } => (parent, child_hash, E::ZERO, E::ZERO), + Self::LoopBody { parent, child_hash } => (parent, child_hash, E::ZERO, E::ONE), + Self::End { + parent, + child_hash, + is_first_child, + is_loop_body, + } => (parent, child_hash, is_first_child.clone(), is_loop_body.clone()), + }; + challenges.encode( + BusId::BlockHashTable as usize, + [ + child_hash[0].clone(), + child_hash[1].clone(), + child_hash[2].clone(), + child_hash[3].clone(), + parent.clone(), + is_first_child, + is_loop_body, + ], + ) + } +} + +// --- OpGroupMsg (BusId::OpGroupTable) --------------------------------------------------- + +impl LookupMessage for OpGroupMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + challenges.encode( + BusId::OpGroupTable as usize, + [self.batch_id.clone(), self.group_pos.clone(), self.group_value.clone()], + ) + } +} + +// --- StackOverflowMsg (BusId::StackOverflowTable) --------------------------------------- + +impl LookupMessage for StackOverflowMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + challenges.encode( + BusId::StackOverflowTable as usize, + [self.clk.clone(), self.val.clone(), self.prev.clone()], + ) + } +} + +// --- KernelRomMsg (BusId::KernelRomInit / BusId::KernelRomCall) +// ------------------------------------ + +impl LookupMessage for KernelRomMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + challenges.encode(self.bus as usize, self.digest.clone()) + } +} + +// --- AceInitMsg (BusId::AceInit) +// ------------------------------------------------------------------- + +impl LookupMessage for AceInitMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + challenges.encode( + BusId::AceInit as usize, + [ + self.clk.clone(), + self.ctx.clone(), + self.ptr.clone(), + self.num_read.clone(), + self.num_eval.clone(), + ], + ) + } +} + +// --- RangeMsg (BusId::RangeCheck) -------------------------------------------------------- + +impl LookupMessage for RangeMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + challenges.encode(BusId::RangeCheck as usize, [self.value.clone()]) + } +} + +// --- LogCapacityMsg (BusId::LogPrecompileTranscript; capacity at β⁰..β³) ---------------------- + +impl LookupMessage for LogCapacityMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + challenges.encode(BusId::LogPrecompileTranscript as usize, self.capacity.clone()) + } +} + +// --- HasherPermLinkMsg (BusId::HasherPermLinkInput / HasherPermLinkOutput) +// ------------------------------------- + +impl LookupMessage for HasherPermLinkMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + let (bus, state) = match self { + Self::Input { state } => (BusId::HasherPermLinkInput, state), + Self::Output { state } => (BusId::HasherPermLinkOutput, state), + }; + challenges.encode(bus as usize, state.clone()) + } +} + +// --- AceWireMsg (BusId::AceWiring) ------------------------------------------------------- + +impl LookupMessage for AceWireMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + challenges.encode( + BusId::AceWiring as usize, + [ + self.clk.clone(), + self.ctx.clone(), + self.id.clone(), + self.v0.clone(), + self.v1.clone(), + ], + ) + } +} + +// LookupMessage impls for the response + sibling structs +// ================================================================================================ +// +// The `*ResponseMsg` structs below carry `LookupMessage` impls consumed by +// `lookup/buses/chiplet_responses.rs`. The runtime-muxed encoding (bus prefix muxed +// by `is_read`/`is_word` flags) keeps the response-column transition at degree 8. + +impl LookupMessage for MemoryResponseMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + let bp = &challenges.beta_powers; + let is_read = self.is_read.clone(); + let is_write: E = E::ONE - is_read.clone(); + let is_word = self.is_word.clone(); + let is_element: E = E::ONE - is_word.clone(); + + // Mux only the bus prefix; the payload (ctx, addr, clk, ...) is shared. Factored + // as a read/write select per access width so the four (read/write × element/word) + // cases stay audit-visible without blowing the polynomial degree. + let prefix_element = challenges.bus_prefix[BusId::MemoryReadElement as usize].clone() + * is_read.clone() + + challenges.bus_prefix[BusId::MemoryWriteElement as usize].clone() * is_write.clone(); + let prefix_word = challenges.bus_prefix[BusId::MemoryReadWord as usize].clone() * is_read + + challenges.bus_prefix[BusId::MemoryWriteWord as usize].clone() * is_write; + let prefix = prefix_element * is_element.clone() + prefix_word * is_word.clone(); + + let mut acc = prefix; + acc += bp[0].clone() * self.ctx.clone(); + acc += bp[1].clone() * self.addr.clone(); + acc += bp[2].clone() * self.clk.clone(); + + // Element payload (gated by is_element) vs word payload (gated by is_word). + acc += bp[3].clone() * self.element.clone() * is_element; + for i in 0..4 { + acc += bp[i + 3].clone() * self.word[i].clone() * is_word.clone(); + } + acc + } +} + +// SIBLING MESSAGES +// ================================================================================================ +// +// [`SiblingMsg`] carries the relevant hasher half alongside a [`SiblingBit`] tag and +// encodes against sparse β layouts (`[2, 7, 8, 9, 10]` and `[2, 3, 4, 5, 6]`) dictated by +// the responder-side hasher chiplet algebra. The trait is permissive about which β +// positions an `encode` body touches; contiguity is a convention, not a requirement. + +/// Sibling-table message for the Merkle sibling bus. +/// +/// The Merkle direction bit picks which half of the hasher rate block holds the sibling: +/// `bit = 0` → sibling at `h[4..8]`, payload lands in β positions `[1, 2, 7, 8, 9, 10]` +/// (mrupdate_id at β¹, node_index at β², rate1 at β⁷..β¹⁰); `bit = 1` → sibling at +/// `h[0..4]`, payload lands in β positions `[1, 2, 3, 4, 5, 6]`. +#[derive(Clone, Debug)] +pub struct SiblingMsg { + pub bit: SiblingBit, + pub mrupdate_id: E, + pub node_index: E, + pub h: [E; 4], +} + +/// Which half of the hasher rate block holds the sibling word for this row. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum SiblingBit { + /// `bit = 0` — sibling lives in the high rate half (`h[4..8]`). + Zero, + /// `bit = 1` — sibling lives in the low rate half (`h[0..4]`). + One, +} + +impl LookupMessage for SiblingMsg +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + fn encode(&self, challenges: &Challenges) -> EF { + let bp = &challenges.beta_powers; + let mut acc = challenges.bus_prefix[BusId::SiblingTable as usize].clone(); + acc += bp[1].clone() * self.mrupdate_id.clone(); + acc += bp[2].clone() * self.node_index.clone(); + let base = match self.bit { + SiblingBit::Zero => 7, + SiblingBit::One => 3, + }; + acc += bp[base].clone() * self.h[0].clone(); + acc += bp[base + 1].clone() * self.h[1].clone(); + acc += bp[base + 2].clone() * self.h[2].clone(); + acc += bp[base + 3].clone() * self.h[3].clone(); + acc + } +} diff --git a/air/src/constraints/lookup/miden_air.rs b/air/src/constraints/lookup/miden_air.rs new file mode 100644 index 0000000000..bdef51c96e --- /dev/null +++ b/air/src/constraints/lookup/miden_air.rs @@ -0,0 +1,173 @@ +//! Miden-specific LogUp pieces consumed by `ProcessorAir`'s trait impls: the combined +//! 7-column fraction stride, the committed-finals count, and the +//! [`emit_miden_boundary`] helper. +//! +//! The `LookupAir` and `AuxBuilder` trait impls themselves live on [`crate::ProcessorAir`] +//! in `air/src/lib.rs`; this module just supplies the constants and the boundary emitter +//! they share. + +use alloc::vec::Vec; + +use miden_core::{WORD_SIZE, field::PrimeCharacteristicRing}; + +use super::{ + chiplet_air::CHIPLET_COLUMN_SHAPE, + main_air::MAIN_COLUMN_SHAPE, + messages::{BlockHashMsg, KernelRomMsg, LogCapacityMsg}, +}; +use crate::{PV_PROGRAM_HASH, PV_TRANSCRIPT_STATE, lookup::BoundaryBuilder}; + +// COLUMN SHAPE AND COMMITTED-FINALS COUNT +// ================================================================================================ + +/// Full 7-column fraction stride: 4 main + 3 chiplet, in `ProcessorAir::eval` order (main +/// columns first, then chiplet columns — see the per-half docs in +/// [`super::main_air::MainLookupAir`] and [`super::chiplet_air::ChipletLookupAir`]). +pub(crate) const MIDEN_COLUMN_SHAPE: [usize; 7] = [ + MAIN_COLUMN_SHAPE[0], + MAIN_COLUMN_SHAPE[1], + MAIN_COLUMN_SHAPE[2], + MAIN_COLUMN_SHAPE[3], + CHIPLET_COLUMN_SHAPE[0], + CHIPLET_COLUMN_SHAPE[1], + CHIPLET_COLUMN_SHAPE[2], +]; + +/// Number of committed final aux values published with a proof. +/// +/// Only col 0 is a real committed final; slot 1 is a placeholder forced to zero, kept for +/// forward-compatibility with the MASM recursive verifier (which absorbs 2 boundary +/// values). All paths that emit or consume the pair must preserve the zero in slot 1. +/// +/// TODO(#3032): reduce to 1 once trace splitting lands and each sub-trace has its own +/// accumulator. +pub const NUM_LOGUP_COMMITTED_FINALS: usize = 2; + +// BOUNDARY EMITTER +// ================================================================================================ + +/// Emits the three Miden-AIR boundary correction terms (`c_block_hash`, +/// `c_log_precompile`, `c_kernel_rom`) into any [`BoundaryBuilder`]. +/// +/// Single source of truth shared between: +/// - [`crate::ProcessorAir`]'s `LookupAir::eval_boundary` (consumed by the debug walker), and +/// - [`crate::ProcessorAir::reduced_aux_values`] (verifier scalar check; drives the emissions +/// through a reducer that sums `Σ multiplicity / encode(msg)`). +/// +/// See `program_hash_message`, `transcript_messages`, and `kernel_proc_message` in +/// `air/src/lib.rs` for the canonical formulas this mirrors. +pub(crate) fn emit_miden_boundary(boundary: &mut B) { + // Snapshot the needed public-input data up front so the mutable + // `boundary.add/remove` calls below don't conflict with the immutable + // borrows taken by `public_values()` / `var_len_public_inputs()`. + let pv = boundary.public_values(); + let program_hash: [B::F; 4] = [ + pv[PV_PROGRAM_HASH], + pv[PV_PROGRAM_HASH + 1], + pv[PV_PROGRAM_HASH + 2], + pv[PV_PROGRAM_HASH + 3], + ]; + let final_state: [B::F; 4] = [ + pv[PV_TRANSCRIPT_STATE], + pv[PV_TRANSCRIPT_STATE + 1], + pv[PV_TRANSCRIPT_STATE + 2], + pv[PV_TRANSCRIPT_STATE + 3], + ]; + let kernel_digests: Vec<[B::F; 4]> = boundary + .var_len_public_inputs() + .first() + .map(|felts| felts.chunks_exact(WORD_SIZE).map(|d| [d[0], d[1], d[2], d[3]]).collect()) + .unwrap_or_default(); + + // Block-hash seed: +1 / encode(BLOCK_HASH_TABLE, [ph, 0, 0, 0]). + boundary.add( + "block_hash_seed", + BlockHashMsg::Child { + parent: B::F::ZERO, + child_hash: program_hash, + }, + ); + + // Log-precompile transcript terminals: +1 / d_initial − 1 / d_final. + boundary.add("log_precompile_initial", LogCapacityMsg { capacity: [B::F::ZERO; 4] }); + boundary.remove("log_precompile_final", LogCapacityMsg { capacity: final_state }); + + // Kernel ROM init: +Σ 1 / d_kernel_proc_msg_i over VLPI[0]. + for digest in kernel_digests { + boundary.add("kernel_rom_init", KernelRomMsg::init(digest)); + } +} + +// TESTS +// ================================================================================================ + +#[cfg(all(test, feature = "std"))] +mod tests { + extern crate std; + + use std::{vec, vec::Vec}; + + use miden_core::{ + field::{PrimeCharacteristicRing, QuadFelt}, + utils::RowMajorMatrix, + }; + use miden_crypto::stark::air::LiftedAir; + + use super::NUM_LOGUP_COMMITTED_FINALS; + use crate::{ + Felt, NUM_PUBLIC_VALUES, ProcessorAir, + constraints::lookup::{BusId, MIDEN_MAX_MESSAGE_WIDTH}, + lookup::{ + Challenges, + debug::{ValidateLayout, ValidateLookupAir, check_trace_balance}, + }, + trace::{AUX_TRACE_RAND_CHALLENGES, AUX_TRACE_WIDTH, TRACE_WIDTH}, + }; + + fn num_periodic() -> usize { + LiftedAir::::periodic_columns(&ProcessorAir).len() + } + + fn validate_layout() -> ValidateLayout { + ValidateLayout { + trace_width: TRACE_WIDTH, + num_public_values: NUM_PUBLIC_VALUES, + num_periodic_columns: num_periodic(), + permutation_width: AUX_TRACE_WIDTH, + num_permutation_challenges: AUX_TRACE_RAND_CHALLENGES, + num_permutation_values: NUM_LOGUP_COMMITTED_FINALS, + } + } + + /// One self-check that covers num_columns consistency, per-group / per-column + /// declared-vs-observed degree, cached-encoding canonical/encoded equivalence, + /// and simple-group scope (no `insert_encoded` outside cached-encoding groups). + #[test] + fn processor_air_lookup_validates() { + ValidateLookupAir::validate(&ProcessorAir, validate_layout()) + .unwrap_or_else(|err| panic!("ProcessorAir LookupAir validation failed: {err}")); + } + + /// Smoke test: the trace-balance checker runs to completion on a tiny zero-valued trace + /// against `ProcessorAir` without panicking. A zero-valued trace is not a valid program + /// execution so the report is expected to contain unmatched entries; this test only + /// asserts that the checker produces a report (instead of crashing). + #[test] + fn trace_balance_runs_on_zero_trace() { + const NUM_ROWS: usize = 4; + let data = vec![Felt::ZERO; TRACE_WIDTH * NUM_ROWS]; + let main_trace = RowMajorMatrix::new(data, TRACE_WIDTH); + let periodic: Vec> = + (0..num_periodic()).map(|_| vec![Felt::ZERO; NUM_ROWS]).collect(); + let publics: Vec = vec![Felt::ZERO; NUM_PUBLIC_VALUES]; + let challenges = Challenges::::new( + QuadFelt::ONE, + QuadFelt::ONE, + MIDEN_MAX_MESSAGE_WIDTH, + BusId::COUNT, + ); + + let _ = + check_trace_balance(&ProcessorAir, &main_trace, &periodic, &publics, &[], &challenges); + } +} diff --git a/air/src/constraints/lookup/mod.rs b/air/src/constraints/lookup/mod.rs new file mode 100644 index 0000000000..3f21328ae3 --- /dev/null +++ b/air/src/constraints/lookup/mod.rs @@ -0,0 +1,25 @@ +//! Miden-side wiring for the LogUp lookup-argument module. +//! +//! Holds the Miden-specific pieces: the [`MainLookupAir`](main_air::MainLookupAir) and +//! [`ChipletLookupAir`](chiplet_air::ChipletLookupAir) sub-AIRs, the seven bus emitters +//! (plus the [`lookup_op_flags`](buses::lookup_op_flags) helper) in [`buses`], the +//! shared [`emit_miden_boundary`](miden_air::emit_miden_boundary) function, the +//! [`MIDEN_COLUMN_SHAPE`](miden_air::MIDEN_COLUMN_SHAPE) and +//! [`NUM_LOGUP_COMMITTED_FINALS`](miden_air::NUM_LOGUP_COMMITTED_FINALS) constants, and the +//! Miden-side extension-trait impls pinning the generic +//! [`ConstraintLookupBuilder`](crate::lookup::ConstraintLookupBuilder) / +//! [`ProverLookupBuilder`](crate::lookup::ProverLookupBuilder) adapters to the Miden +//! `LookupBuilder` trait. +//! +//! The `LookupAir` and `AuxBuilder` trait impls themselves live on +//! [`crate::ProcessorAir`]; the field-polymorphic core (traits, adapters, accumulators, +//! debug walkers) lives in [`crate::lookup`]. + +pub(crate) mod buses; +pub mod chiplet_air; +mod extension_impls; +pub mod main_air; +pub mod messages; +pub mod miden_air; + +pub use messages::{BusId, MIDEN_MAX_MESSAGE_WIDTH}; diff --git a/air/src/constraints/mod.rs b/air/src/constraints/mod.rs index c46e5b7452..34ddc03dd6 100644 --- a/air/src/constraints/mod.rs +++ b/air/src/constraints/mod.rs @@ -4,32 +4,26 @@ //! //! ## Organization //! -//! Constraints are separated into two categories: +//! - **Main trace constraints** are evaluated by [`enforce_main`] and cover system / range / stack +//! / decoder / chiplets transitions. +//! - **LogUp lookup-argument constraints** are evaluated separately through the closure-based +//! `LookupAir` impl on [`crate::ProcessorAir`], wired in from `ProcessorAir::eval` via +//! [`crate::lookup::ConstraintLookupBuilder`]. //! -//! ### Main Trace Constraints -//! - system: clock, ctx, fn_hash transitions -//! - range: range checker V column transitions -//! - stack: general stack constraints -//! -//! ### Bus Constraints (Auxiliary Trace) -//! - range::bus -//! -//! Bus constraints access the auxiliary trace via `builder.permutation()` and use -//! random challenges from `builder.permutation_randomness()` for multiset/LogUp verification. -//! -//! Additional components (decoder, chiplets) are introduced in later constraint chunks. +//! The legacy multiset bus subtree (`bus.rs`, `decoder/bus.rs`, `stack/bus.rs`, +//! `range/bus.rs`, `chiplets/bus/`) was removed in Milestone B alongside the +//! stateless `ProcessorAir::build_aux_trace` integration. use chiplets::selectors::ChipletSelectors; -use miden_crypto::stark::air::{ExtensionBuilder, WindowAccess}; -use crate::{MainCols, MidenAirBuilder, trace::Challenges}; +use crate::{MainCols, MidenAirBuilder}; -pub mod bus; pub mod chiplets; pub mod columns; pub mod constants; pub mod decoder; pub mod ext_field; +pub mod lookup; pub(crate) mod op_flags; pub mod public_inputs; pub mod range; @@ -56,87 +50,3 @@ pub fn enforce_main( decoder::enforce_main(builder, local, next, op_flags); chiplets::enforce_main(builder, local, next, selectors); } - -/// Enforces all auxiliary (bus) constraints: boundary + transition. -/// -/// Bus soundness relies on three mechanisms: -/// 1. **Boundary** -- aux columns start at identity (1 or 0) -/// 2. **Transition** -- row-to-row update rules determine all subsequent values -/// 3. **`reduced_aux_values`** -- final values satisfy bus identities -pub fn enforce_bus( - builder: &mut AB, - local: &MainCols, - next: &MainCols, - selectors: &ChipletSelectors, - op_flags: &op_flags::OpFlags, -) where - AB: MidenAirBuilder, -{ - let r = builder.permutation_randomness(); - let challenges = Challenges::::new(r[0].into(), r[1].into()); - - // First row: running products = 1, LogUp sums = 0. - enforce_bus_first_row(builder); - - // Last row: bind aux trace to committed finals checked by `reduced_aux_values`, - // preventing a malicious prover from committing arbitrary finals. - enforce_bus_last_row(builder); - - range::bus::enforce_bus(builder, local, op_flags, &challenges, selectors); - stack::bus::enforce_bus(builder, local, next, op_flags, &challenges); - decoder::bus::enforce_bus(builder, local, next, op_flags, &challenges); - chiplets::bus::enforce_bus(builder, local, next, op_flags, &challenges, selectors); -} - -fn enforce_bus_first_row(builder: &mut AB) -where - AB: MidenAirBuilder, -{ - use bus::indices::*; - - let aux = builder.permutation(); - let aux_local = aux.current_slice(); - - let p1 = aux_local[P1_BLOCK_STACK]; - let p2 = aux_local[P2_BLOCK_HASH]; - let p3 = aux_local[P3_OP_GROUP]; - let s_aux = aux_local[P1_STACK]; - let b_hk = aux_local[B_HASH_KERNEL]; - let b_ch = aux_local[B_CHIPLETS]; - let b_rng = aux_local[B_RANGE]; - let v_wir = aux_local[V_WIRING]; - - let mut first = builder.when_first_row(); - first.assert_one_ext(p1); - first.assert_one_ext(p2); - first.assert_one_ext(p3); - first.assert_one_ext(s_aux); - first.assert_one_ext(b_hk); - first.assert_one_ext(b_ch); - first.assert_zero_ext(b_rng); - first.assert_zero_ext(v_wir); -} - -fn enforce_bus_last_row(builder: &mut AB) -where - AB: MidenAirBuilder, -{ - use crate::trace::AUX_TRACE_WIDTH; - - const N: usize = AUX_TRACE_WIDTH; - - let cols: [AB::VarEF; N] = { - let aux = builder.permutation(); - let s = aux.current_slice(); - core::array::from_fn(|i| s[i]) - }; - let finals: [AB::ExprEF; N] = { - let f = builder.permutation_values(); - core::array::from_fn(|i| f[i].clone().into()) - }; - - let mut last = builder.when_last_row(); - for (col, expected) in cols.into_iter().zip(finals) { - last.assert_eq_ext(col, expected); - } -} diff --git a/air/src/constraints/op_flags/mod.rs b/air/src/constraints/op_flags/mod.rs index 12b9a3c4b7..5a4a494864 100644 --- a/air/src/constraints/op_flags/mod.rs +++ b/air/src/constraints/op_flags/mod.rs @@ -106,7 +106,6 @@ pub struct OpFlags { right_shift: E, control_flow: E, overflow: E, - u32_rc_op: E, // Next-row control flow flags (degree 4) end_next: E, @@ -269,7 +268,6 @@ where right_shift: composite.right_shift_scalar, control_flow: composite.control_flow, overflow: composite.overflow, - u32_rc_op: composite.u32_rc_op, end_next, repeat_next, respan_next, @@ -584,7 +582,7 @@ where // DYNCALL is intentionally excluded — it left-shifts the stack but uses // decoder hasher state (h5) for overflow constraints, not the generic path. let prefix_010 = prefix_01 * bits[4][0].clone(); - let u32_add3_madd_group = prefix_100.clone() * bits[3][1].clone() * bits[2][1].clone(); + let u32_add3_madd_group = prefix_100 * bits[3][1].clone() * bits[2][1].clone(); let left_shift_scalar = E::sum_array::<7>(&[ // +prefix_010 — ASSERT, EQ, ADD, MUL, AND, OR, U32AND, U32XOR, DROP, // CSWAP, CSWAPW, MLOADW, MSTORE, MSTOREW, (op46), (op47) @@ -632,9 +630,6 @@ where op4(opcodes::CALL), ]); - // Flag if current operation is a degree-6 u32 operation - let u32_rc_op = prefix_100; - // Flag if overflow table contains values let overflow: E = stack.b0.into(); let overflow = (overflow - E::from_u64(16)) * stack.h0; @@ -646,7 +641,6 @@ where left_shift_scalar, right_shift_scalar, control_flow, - u32_rc_op, overflow, } } @@ -747,12 +741,6 @@ where pub fn halt_next(&self) -> E { self.halt_next.clone() } - - /// Returns the flag when the current operation is a u32 operation requiring range checks. - #[inline(always)] - pub fn u32_rc_op(&self) -> E { - self.u32_rc_op.clone() - } } macro_rules! op_flag_getters { @@ -788,6 +776,7 @@ impl OpFlags { /// Operation Flag of NOT operation. not => opcodes::NOT, /// Operation Flag of MLOAD operation. + #[expect(dead_code)] mload => opcodes::MLOAD, /// Operation Flag of SWAP operation. swap => opcodes::SWAP, @@ -852,8 +841,10 @@ impl OpFlags { /// Operation Flag of OR operation. or => opcodes::OR, /// Operation Flag of U32AND operation. + #[expect(dead_code)] u32and => opcodes::U32AND, /// Operation Flag of U32XOR operation. + #[expect(dead_code)] u32xor => opcodes::U32XOR, /// Operation Flag of FRIE2F4 operation. frie2f4 => opcodes::FRIE2F4, @@ -865,10 +856,13 @@ impl OpFlags { /// Operation Flag of CSWAPW operation. cswapw => opcodes::CSWAPW, /// Operation Flag of MLOADW operation. + #[expect(dead_code)] mloadw => opcodes::MLOADW, /// Operation Flag of MSTORE operation. + #[expect(dead_code)] mstore => opcodes::MSTORE, /// Operation Flag of MSTOREW operation. + #[expect(dead_code)] mstorew => opcodes::MSTOREW, /// Operation Flag of PAD operation. pad => opcodes::PAD, @@ -930,8 +924,10 @@ impl OpFlags { op_flag_getters!(degree5_op_flags, /// Operation Flag of HPERM operation. + #[expect(dead_code)] hperm => opcodes::HPERM, /// Operation Flag of MPVERIFY operation. + #[expect(dead_code)] mpverify => opcodes::MPVERIFY, /// Operation Flag of SPLIT operation. split => opcodes::SPLIT, @@ -940,6 +936,7 @@ impl OpFlags { /// Operation Flag of SPAN operation. span => opcodes::SPAN, /// Operation Flag of JOIN operation. + #[expect(dead_code)] join => opcodes::JOIN, /// Operation Flag of PUSH operation. push => opcodes::PUSH, @@ -948,16 +945,20 @@ impl OpFlags { /// Operation Flag of DYNCALL operation. dyncall => opcodes::DYNCALL, /// Operation Flag of EVALCIRCUIT operation. + #[expect(dead_code)] evalcircuit => opcodes::EVALCIRCUIT, /// Operation Flag of LOG_PRECOMPILE operation. + #[expect(dead_code)] log_precompile => opcodes::LOGPRECOMPILE, /// Operation Flag of HORNERBASE operation. hornerbase => opcodes::HORNERBASE, /// Operation Flag of HORNEREXT operation. hornerext => opcodes::HORNEREXT, /// Operation Flag of MSTREAM operation. + #[expect(dead_code)] mstream => opcodes::MSTREAM, /// Operation Flag of PIPE operation. + #[expect(dead_code)] pipe => opcodes::PIPE, ); @@ -965,6 +966,7 @@ impl OpFlags { op_flag_getters!(degree4_op_flags, /// Operation Flag of MRUPDATE operation. + #[expect(dead_code)] mrupdate => opcodes::MRUPDATE, /// Operation Flag of CALL operation. call => opcodes::CALL, @@ -1007,7 +1009,6 @@ struct CompositeFlags { left_shift_scalar: E, right_shift_scalar: E, control_flow: E, - u32_rc_op: E, overflow: E, } diff --git a/air/src/constraints/op_flags/tests.rs b/air/src/constraints/op_flags/tests.rs index 2ce05f0314..70a95a35b2 100644 --- a/air/src/constraints/op_flags/tests.rs +++ b/air/src/constraints/op_flags/tests.rs @@ -598,36 +598,3 @@ proptest! { } } } - -/// Tests u32_rc_op flag for u32 operations. -#[test] -fn u32_rc_op_flag() { - // U32 operations that require range checks (degree 6) - let u32_ops = [ - Operation::U32add, - Operation::U32sub, - Operation::U32mul, - Operation::U32div, - Operation::U32split, - Operation::U32assert2(ZERO), - Operation::U32add3, - Operation::U32madd, - ]; - - for op in u32_ops { - let op_flags = op_flags_for_opcode(op.op_code().into()); - assert_eq!(op_flags.u32_rc_op, ONE, "u32_rc_op should be ONE for {op:?}"); - } - - // Non-u32 operations - let non_u32_ops = [ - Operation::Add, - Operation::Mul, - Operation::And, // Bitwise AND is degree 7, not u32 - ]; - - for op in non_u32_ops { - let op_flags = op_flags_for_opcode(op.op_code().into()); - assert_eq!(op_flags.u32_rc_op, ZERO, "u32_rc_op should be ZERO for {op:?}"); - } -} diff --git a/air/src/constraints/range/bus.rs b/air/src/constraints/range/bus.rs deleted file mode 100644 index 88b625f2db..0000000000 --- a/air/src/constraints/range/bus.rs +++ /dev/null @@ -1,110 +0,0 @@ -//! Range checker bus constraint. -//! -//! This module enforces the LogUp protocol for the range checker bus (b_range). -//! The range checker validates that values are within [0, 2^16) by tracking requests -//! from stack and memory components against the range table responses. -//! -//! ## LogUp Protocol -//! -//! The bus accumulator b_range uses the LogUp protocol: -//! - Boundary: b_range[0] = 0 and b_range[last] = 0 (enforced by the wrapper AIR) -//! - Transition: b_range' = b_range + responses - requests -//! -//! Where requests come from stack (4 lookups) and memory (2 lookups), and -//! responses come from the range table (V column with multiplicity). - -use miden_crypto::stark::air::{ExtensionBuilder, WindowAccess}; - -use crate::{ - MainCols, MidenAirBuilder, - constraints::{chiplets::selectors::ChipletSelectors, op_flags::OpFlags}, - trace::{Challenges, bus_types::RANGE_CHECK_BUS, range}, -}; - -// ENTRY POINTS -// ================================================================================================ - -/// Enforces the range checker bus constraint for LogUp checks. -/// -/// This constraint tracks range check requests from other components (stack and memory) -/// using the LogUp protocol. The bus accumulator b_range must start and end at 0, -/// and transition according to the LogUp update rule. -/// -/// ## Constraint Degree -/// -/// This is a degree-9 constraint. -/// -/// ## Lookups -/// -/// - Stack lookups (4): decoder helper columns (USER_OP_HELPERS_OFFSET..+4) -/// - Memory lookups (2): memory delta limbs (MEMORY_D0, MEMORY_D1) -/// - Range response: range V column with multiplicity range M column -pub fn enforce_bus( - builder: &mut AB, - local: &MainCols, - op_flags: &OpFlags, - challenges: &Challenges, - selectors: &ChipletSelectors, -) where - AB: MidenAirBuilder, -{ - // In Miden VM, auxiliary trace is always present - - // Extract values needed for constraints - let aux = builder.permutation(); - let aux_local = aux.current_slice(); - let aux_next = aux.next_slice(); - let b_local = aux_local[range::B_RANGE_COL_IDX]; - let b_next = aux_next[range::B_RANGE_COL_IDX]; - - let alpha = &challenges.bus_prefix[RANGE_CHECK_BUS]; - - // Denominators for LogUp - let mem = local.memory(); - let mv0: AB::ExprEF = alpha.clone() + mem.d0.into(); - let mv1: AB::ExprEF = alpha.clone() + mem.d1.into(); - - // Stack lookups: sv0-sv3 = alpha + decoder helper columns - let helpers = local.decoder.user_op_helpers(); - let sv0: AB::ExprEF = alpha.clone() + helpers[0].into(); - let sv1: AB::ExprEF = alpha.clone() + helpers[1].into(); - let sv2: AB::ExprEF = alpha.clone() + helpers[2].into(); - let sv3: AB::ExprEF = alpha.clone() + helpers[3].into(); - - // Range check value: alpha + range V column - let range_check: AB::ExprEF = alpha.clone() + local.range.value.into(); - - // Combined lookup denominators - let memory_lookups = mv0.clone() * mv1.clone(); - let stack_lookups = sv0.clone() * sv1.clone() * sv2.clone() * sv3.clone(); - let lookups = range_check.clone() * stack_lookups.clone() * memory_lookups.clone(); - - // Flags for conditional inclusion - let u32_rc_op = op_flags.u32_rc_op(); - let sflag_rc_mem = range_check.clone() * memory_lookups.clone() * u32_rc_op; - - // chiplets_memory_flag = s0 * s1 * (1 - s2), i.e. memory is active - let chiplets_memory_flag = selectors.memory.is_active.clone(); - let mflag_rc_stack = range_check * stack_lookups.clone() * chiplets_memory_flag; - - // LogUp transition constraint terms - let b_next_term = b_next.into() * lookups.clone(); - let b_term = b_local.into() * lookups; - let rc_term = stack_lookups * memory_lookups * local.range.multiplicity.into(); - - // Stack lookup removal terms - let s0_term = sflag_rc_mem.clone() * sv1.clone() * sv2.clone() * sv3.clone(); - let s1_term = sflag_rc_mem.clone() * sv0.clone() * sv2.clone() * sv3.clone(); - let s2_term = sflag_rc_mem.clone() * sv0.clone() * sv1.clone() * sv3; - let s3_term = sflag_rc_mem * sv0 * sv1 * sv2; - - // Memory lookup removal terms - let m0_term = mflag_rc_stack.clone() * mv1; - let m1_term = mflag_rc_stack * mv0; - - // Main constraint: b_next * lookups = b * lookups + rc_term - s0_term - s1_term - s2_term - - // s3_term - m0_term - m1_term - builder.when_transition().assert_zero_ext( - b_next_term - b_term - rc_term + s0_term + s1_term + s2_term + s3_term + m0_term + m1_term, - ); -} diff --git a/air/src/constraints/range/mod.rs b/air/src/constraints/range/mod.rs index e490e71fd6..696ecc02dc 100644 --- a/air/src/constraints/range/mod.rs +++ b/air/src/constraints/range/mod.rs @@ -12,8 +12,6 @@ use miden_crypto::stark::air::AirBuilder; use crate::{MainCols, MidenAirBuilder, constraints::constants::*}; -pub mod bus; - // ENTRY POINTS // ================================================================================================ diff --git a/air/src/constraints/stack/bus.rs b/air/src/constraints/stack/bus.rs deleted file mode 100644 index 16cf493580..0000000000 --- a/air/src/constraints/stack/bus.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! Stack overflow table bus constraint. -//! -//! This module enforces the running product constraint for the stack overflow table (p1). -//! The stack overflow table tracks values that overflow from the 16-element operand stack. -//! -//! The bus accumulator p1 uses a multiset check: -//! - Boundary: p1[0] = 1 and p1[last] = 1 (enforced by the wrapper AIR) -//! - Transition: p1' * requests = p1 * responses -//! -//! Where: -//! - Responses (adding rows): When right_shift, a row is added with (clk, s15, b1) -//! - Requests (removing rows): When (left_shift OR dyncall) AND non_empty_overflow, a row is -//! removed with (b1, s15', b1') or (b1, s15', hasher_state[5]) for dyncall -//! -//! ## Row Encoding -//! -//! Each row in the overflow table is encoded as: -//! `alpha + beta^0 * clk + beta^1 * val + beta^2 * prev` - -use miden_crypto::stark::air::{ExtensionBuilder, WindowAccess}; - -use crate::{ - MainCols, MidenAirBuilder, - constraints::{bus::indices::P1_STACK, constants::F_16, op_flags::OpFlags, utils::BoolNot}, - trace::{Challenges, bus_types::STACK_OVERFLOW_TABLE}, -}; - -// ENTRY POINTS -// ================================================================================================ - -/// Enforces the stack overflow table bus constraint. -/// -/// This constraint tracks overflow table operations using a running product: -/// - Adding rows when right_shift (element pushed off stack position 15) -/// - Removing rows when (left_shift OR dyncall) AND overflow is non-empty -pub fn enforce_bus( - builder: &mut AB, - local: &MainCols, - next: &MainCols, - op_flags: &OpFlags, - challenges: &Challenges, -) where - AB: MidenAirBuilder, -{ - // Auxiliary trace must be present. - - // Extract auxiliary trace values. - let (p1_local, p1_next) = { - let aux = builder.permutation(); - let aux_local = aux.current_slice(); - let aux_next = aux.next_slice(); - (aux_local[P1_STACK], aux_next[P1_STACK]) - }; - - // ============================================================================================ - // TRANSITION CONSTRAINT - // ============================================================================================ - - // ------------------------------------------------------------------------- - // Stack and bookkeeping column values - // ------------------------------------------------------------------------- - - // Current row values - let clk = local.system.clk; - let s15 = local.stack.get(15); - let b0 = local.stack.b0; - let b1 = local.stack.b1; - let h0 = local.stack.h0; - - // Next row values (needed for removal) - let s15_next = next.stack.get(15); - let b1_next = next.stack.b1; - - // Hasher state element 5, used by DYNCALL to store the new overflow table pointer. - let hasher_state_5 = local.decoder.hasher_state[5]; - - // ------------------------------------------------------------------------- - // Overflow condition: (b0 - 16) * h0 = 1 when overflow is non-empty - // ------------------------------------------------------------------------- - - let is_non_empty_overflow: AB::Expr = (b0 - F_16) * h0; - - // ------------------------------------------------------------------------- - // Operation flags - // ------------------------------------------------------------------------- - - let right_shift = op_flags.right_shift(); - let left_shift = op_flags.left_shift(); - let dyncall = op_flags.dyncall(); - - // ------------------------------------------------------------------------- - // Row value encoding: alpha + beta^0 * clk + beta^1 * val + beta^2 * prev - // ------------------------------------------------------------------------- - - // Response row value (adding to table during right_shift): - let response_row = challenges.encode(STACK_OVERFLOW_TABLE, [clk.into(), s15.into(), b1.into()]); - - // Request row value for left_shift (removing from table): - let request_row_left = - challenges.encode(STACK_OVERFLOW_TABLE, [b1.into(), s15_next.into(), b1_next.into()]); - - // Request row value for dyncall (removing from table): - let request_row_dyncall = challenges - .encode(STACK_OVERFLOW_TABLE, [b1.into(), s15_next.into(), hasher_state_5.into()]); - - // ------------------------------------------------------------------------- - // Compute response and request terms - // ------------------------------------------------------------------------- - - // Response: right_shift * response_row + (1 - right_shift) - let response: AB::ExprEF = response_row * right_shift.clone() + right_shift.not(); - - // Request flags - let left_flag: AB::Expr = left_shift * is_non_empty_overflow.clone(); - let dyncall_flag: AB::Expr = dyncall * is_non_empty_overflow; - let request_flag_sum: AB::Expr = left_flag.clone() + dyncall_flag.clone(); - - // Request: left_flag * left_value + dyncall_flag * dyncall_value + (1 - sum(flags)) - let request: AB::ExprEF = - request_row_left * left_flag + request_row_dyncall * dyncall_flag + request_flag_sum.not(); - - // ------------------------------------------------------------------------- - // Main running product constraint - // ------------------------------------------------------------------------- - - let lhs = p1_next.into() * request; - let rhs = p1_local.into() * response; - - builder.when_transition().assert_eq_ext(lhs, rhs); -} diff --git a/air/src/constraints/stack/mod.rs b/air/src/constraints/stack/mod.rs index a07da51ab0..1584330aea 100644 --- a/air/src/constraints/stack/mod.rs +++ b/air/src/constraints/stack/mod.rs @@ -3,7 +3,6 @@ //! This module exposes the general stack transition, stack ops, stack arith/u32, stack crypto, //! and stack overflow constraints. -pub mod bus; pub mod columns; pub mod crypto; pub mod general; diff --git a/air/src/lib.rs b/air/src/lib.rs index e70a593d86..e0a0cbafac 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -22,10 +22,35 @@ use miden_crypto::stark::air::{ pub mod ace; pub mod config; mod constraints; - +pub mod lookup; pub mod trace; -use constraints::columns::MainCols; -use trace::{AUX_TRACE_WIDTH, TRACE_WIDTH, bus_types}; + +/// Miden VM-specific LogUp lookup argument: bus identifiers and bus message types. +/// +/// The `LookupAir` and `AuxBuilder` trait impls live directly on [`crate::ProcessorAir`]. +/// The generic LogUp framework this builds on lives in [`crate::lookup`] and is free of +/// Miden-specific types so it can be extracted into its own crate. +pub mod logup { + pub use crate::constraints::lookup::{ + BusId, MIDEN_MAX_MESSAGE_WIDTH, messages::*, miden_air::NUM_LOGUP_COMMITTED_FINALS, + }; +} + +use constraints::{ + columns::MainCols, + lookup::{ + chiplet_air::{ChipletLookupAir, ChipletLookupBuilder}, + main_air::{MainLookupAir, MainLookupBuilder}, + miden_air::{MIDEN_COLUMN_SHAPE, emit_miden_boundary}, + }, +}; +use logup::{BusId, MIDEN_MAX_MESSAGE_WIDTH, NUM_LOGUP_COMMITTED_FINALS}; +use lookup::{ + BoundaryBuilder, Challenges, ConstraintLookupBuilder, LookupAir, LookupMessage, + build_logup_aux_trace, +}; +use miden_core::utils::RowMajorMatrix; +use trace::TRACE_WIDTH; // RE-EXPORTS // ================================================================================================ @@ -180,6 +205,9 @@ impl Deserializable for PublicInputs { /// [36..40] precompile transcript state pub const NUM_PUBLIC_VALUES: usize = WORD_SIZE + MIN_STACK_DEPTH + MIN_STACK_DEPTH + WORD_SIZE; +/// LogUp aux trace width: 4 main-trace columns + 3 chiplet-trace columns. +pub const LOGUP_AUX_TRACE_WIDTH: usize = 7; + // Public values layout offsets. const PV_PROGRAM_HASH: usize = 0; const PV_TRANSCRIPT_STATE: usize = NUM_PUBLIC_VALUES - WORD_SIZE; @@ -219,11 +247,14 @@ impl> LiftedAir for ProcessorAir { } fn aux_width(&self) -> usize { - AUX_TRACE_WIDTH + // 4 main-trace + 3 chiplet-trace = 7 LogUp columns. Matches + // `ProcessorAir::num_columns()` (LookupAir impl) and the per-row shape returned by + // `ProcessorAir::build_aux_trace` (AuxBuilder impl). + LOGUP_AUX_TRACE_WIDTH } fn num_aux_values(&self) -> usize { - AUX_TRACE_WIDTH + NUM_LOGUP_COMMITTED_FINALS } /// Returns the number of variable-length public input slices. @@ -246,17 +277,19 @@ impl> LiftedAir for ProcessorAir { where EF: ExtensionField, { - // Extract final aux column values. - let p1 = aux_values[trace::DECODER_AUX_TRACE_OFFSET]; - let p2 = aux_values[trace::DECODER_AUX_TRACE_OFFSET + 1]; - let p3 = aux_values[trace::DECODER_AUX_TRACE_OFFSET + 2]; - let s_aux = aux_values[trace::STACK_AUX_TRACE_OFFSET]; - let b_range = aux_values[trace::RANGE_CHECK_AUX_TRACE_OFFSET]; - let b_hash_kernel = aux_values[trace::HASH_KERNEL_VTABLE_AUX_TRACE_OFFSET]; - let b_chiplets = aux_values[trace::CHIPLETS_BUS_AUX_TRACE_OFFSET]; - let v_wiring = aux_values[trace::ACE_CHIPLET_WIRING_BUS_OFFSET]; - - // Parse fixed-length public values (see `NUM_PUBLIC_VALUES` for layout). + // LogUp boundary identity. The verifier checks `is_identity()` — i.e. + // `prod == ONE && sum == ZERO` — on the accumulated `ReducedAuxValues` + // across every AIR. There are no multiplicative bus checks under LogUp, so + // `prod = ONE`. The boundary equation lives in `sum`: + // + // sum = Σ aux_finals[col] + total_correction + // + // `total_correction` cancels the unmatched-fraction contributions from the + // three open buses (block hash, log precompile, kernel ROM init). Rather + // than spelling those out here, we drive the shared + // `emit_miden_boundary` emitter — the same source `LookupAir::eval_boundary` + // uses for the debug walker — through a reducer that accumulates + // `Σ multiplicity · encode(msg)⁻¹` into an `EF`. if public_values.len() != NUM_PUBLIC_VALUES { return Err(format!( "expected {} public values, got {}", @@ -265,84 +298,57 @@ impl> LiftedAir for ProcessorAir { ) .into()); } - let program_hash: Word = public_values[PV_PROGRAM_HASH..PV_PROGRAM_HASH + WORD_SIZE] - .try_into() - .map_err(|_| -> ReductionError { "invalid program hash slice".into() })?; - let pc_transcript_state: PrecompileTranscriptState = public_values - [PV_TRANSCRIPT_STATE..PV_TRANSCRIPT_STATE + WORD_SIZE] - .try_into() - .map_err(|_| -> ReductionError { "invalid transcript state slice".into() })?; - - // Precompute challenge powers once for all bus message encodings. - let challenges = trace::Challenges::::new(challenges[0], challenges[1]); - - // Compute expected bus messages from public inputs and derived challenges. - let ph_msg = program_hash_message(&challenges, &program_hash); - - let (default_transcript_msg, final_transcript_msg) = - transcript_messages(&challenges, pc_transcript_state); + if var_len_public_inputs.len() != 1 { + return Err(format!( + "expected 1 var-len public input slice, got {}", + var_len_public_inputs.len() + ) + .into()); + } + if !var_len_public_inputs[0].len().is_multiple_of(WORD_SIZE) { + return Err(format!( + "kernel digest felts length {} is not a multiple of {}", + var_len_public_inputs[0].len(), + WORD_SIZE + ) + .into()); + } - let kernel_reduced = kernel_reduced_from_var_len(&challenges, var_len_public_inputs)?; + let challenges = Challenges::::new( + challenges[0], + challenges[1], + MIDEN_MAX_MESSAGE_WIDTH, + BusId::COUNT, + ); + + let mut reducer = ReduceBoundaryBuilder { + challenges: &challenges, + public_values, + var_len_public_inputs, + sum: EF::ZERO, + }; + emit_miden_boundary(&mut reducer); + let total_correction = reducer.finalize(); + + // TODO(#3032): aux_values[1..] are the placeholder slots from + // NUM_LOGUP_COMMITTED_FINALS (see `constraints::lookup::miden_air`); enforce the + // zero invariant until trace splitting lands. The recursive verifier gets the + // matching `AuxBusBoundary(col) = 0` identity via `LogUpBoundaryConfig::zero_columns` + // (see `ace.rs`), so both paths reject a nonzero padding slot. + for unused_aux in aux_values.iter().skip(1) { + if !unused_aux.is_zero() { + return Err("padding aux value is non-zero".into()); + } + } + let aux_sum: EF = aux_values.iter().copied().sum(); - // Combine all multiset column finals with reduced variable length public-inputs. - // - // Running-product columns accumulate `responses / requests` at each row, so - // their final value is product(responses) / product(requests) over the entire trace. - // - // Columns whose requests and responses fully cancel end at 1: - // p1 (block stack table) -- every block pushed is later popped - // p3 (op group table) -- every op group pushed is later consumed - // s_aux (stack overflow) -- every overflow push has a matching pop - // - // Columns with public-input-dependent boundary terms end at non-unity values: - // - // p2 (block hash table): - // The root block's hash is removed from the table at END, but was never - // added (the root has no parent that would add it). This leaves one - // unmatched removal: p2_final = 1 / ph_msg. - // - // b_hash_kernel (chiplets virtual table: sibling table + transcript state): - // The log_precompile transcript tracking chain starts by removing - // default_transcript_msg (initial capacity state) and ends by inserting - // final_transcript_msg (final capacity state). On the other hand, sibling table - // entries cancel out. Net: b_hk_final = final_transcript_msg / default_transcript_msg. - // - // b_chiplets (chiplets bus): - // Each unique kernel procedure produces a KernelRomInitMessage response - // from the kernel ROM chiplet These init messages are matched by the verifier - // via public inputs. Net: b_ch_final = product(kernel_init_msgs) = kernel_reduced. - // - // Multiplying all finals with correction terms: - // prod = (p1 * p3 * s_aux) -- each is 1 - // * (p2 * ph_msg) -- (1/ph_msg) * ph_msg = 1 - // * (b_hk * default_msg / final_msg) -- cancels to 1 - // * (b_ch / kernel_reduced) -- cancels to 1 - // - // Rearranged: prod = all_finals * ph_msg * default_msg / (final_msg * kernel_reduced) (= 1) - let expected_denom = final_transcript_msg * kernel_reduced; - let expected_denom_inv = expected_denom - .try_inverse() - .ok_or_else(|| -> ReductionError { "zero denominator in reduced_aux_values".into() })?; - - let prod = p1 - * p2 - * p3 - * s_aux - * b_hash_kernel - * b_chiplets - * ph_msg - * default_transcript_msg - * expected_denom_inv; - - // LogUp: all columns should end at 0. - let sum = b_range + v_wiring; - - Ok(ReducedAuxValues { prod, sum }) + Ok(ReducedAuxValues { + prod: EF::ONE, + sum: aux_sum + total_correction, + }) } fn eval(&self, builder: &mut AB) { - use crate::constraints; - let main = builder.main(); // Access the two rows: current (local) and next @@ -362,106 +368,137 @@ impl> LiftedAir for ProcessorAir { // Main trace constraints. constraints::enforce_main(builder, local, next, &selectors, &op_flags); - // Auxiliary (bus) constraints. - constraints::enforce_bus(builder, local, next, &selectors, &op_flags); + { + let mut lb = ConstraintLookupBuilder::new(builder, self); + >::eval(self, &mut lb); + } // Public inputs boundary constraints. constraints::public_inputs::enforce_main(builder, local); } + + fn log_quotient_degree(&self) -> usize + where + Self: Sized, + { + // override to avoid recomputing through the SymbolicAir + 3 + } } -// REDUCED AUX VALUES HELPERS -// ================================================================================================ +// --- LookupAir impl (7-column aggregator over main + chiplet sub-AIRs) --- -/// Builds the program-hash bus message for the block-hash table boundary term. -/// -/// Must match `BlockHashTableRow::from_end().collapse()` on the prover side for the -/// root block, which encodes `[parent_id=0, hash[0..4], is_first_child=0, is_loop_body=0]`. -fn program_hash_message>( - challenges: &trace::Challenges, - program_hash: &Word, -) -> EF { - challenges.encode( - bus_types::BLOCK_HASH_TABLE, - [ - Felt::ZERO, // parent_id = 0 (root block) - program_hash[0], - program_hash[1], - program_hash[2], - program_hash[3], - Felt::ZERO, // is_first_child = false - Felt::ZERO, // is_loop_body = false - ], - ) +impl LookupAir for ProcessorAir +where + LB: MainLookupBuilder + ChipletLookupBuilder, +{ + fn num_columns(&self) -> usize { + MIDEN_COLUMN_SHAPE.len() + } + + fn column_shape(&self) -> &[usize] { + &MIDEN_COLUMN_SHAPE + } + + fn max_message_width(&self) -> usize { + // Width of the `beta_powers` table precomputed by `Challenges::new`, also equal + // to the exponent of `gamma = beta^MIDEN_MAX_MESSAGE_WIDTH` used in the per-bus + // prefix. Must match the MASM recursive verifier's Poseidon2 absorption loop. + // `HasherMsg::State` is the widest live payload at 15 slots (label@β⁰, addr@β¹, + // node_index@β², state[0..12]@β³..β¹⁴); the 16th slot is unused slack kept for + // MASM transcript alignment. + MIDEN_MAX_MESSAGE_WIDTH + } + + fn num_bus_ids(&self) -> usize { + BusId::COUNT + } + + fn eval(&self, builder: &mut LB) { + MainLookupAir.eval(builder); + ChipletLookupAir.eval(builder); + } + + fn eval_boundary(&self, boundary: &mut B) + where + B: BoundaryBuilder, + { + emit_miden_boundary(boundary); + } } -/// Returns the pair of (initial, final) log-precompile transcript messages for the -/// virtual-table bus boundary term. -/// -/// The initial message uses the default (zero) capacity state; the final message uses -/// the public-input transcript state. -fn transcript_messages>( - challenges: &trace::Challenges, - final_state: PrecompileTranscriptState, -) -> (EF, EF) { - let encode = |state: PrecompileTranscriptState| { - let cap: &[Felt] = state.as_ref(); - challenges.encode( - bus_types::LOG_PRECOMPILE_TRANSCRIPT, - [Felt::from_u8(trace::LOG_PRECOMPILE_LABEL), cap[0], cap[1], cap[2], cap[3]], - ) - }; - (encode(PrecompileTranscriptState::default()), encode(final_state)) +// --- AuxBuilder impl (stateless LogUp aux-trace construction) --- + +impl AuxBuilder for ProcessorAir +where + EF: ExtensionField, +{ + fn build_aux_trace( + &self, + main: &RowMajorMatrix, + challenges: &[EF], + ) -> (RowMajorMatrix, Vec) { + let (aux_trace, mut committed) = build_logup_aux_trace(self, main, challenges); + // TODO(#3032): pad the placeholder slot — see `NUM_LOGUP_COMMITTED_FINALS`. Remove + // the pad once trace splitting lands. + debug_assert_eq!( + committed.len(), + 1, + "build_logup_aux_trace should return exactly one real committed final" + ); + committed.push(EF::ZERO); + (aux_trace, committed) + } } -/// Builds the kernel procedure init message for the kernel ROM bus. +// REDUCED-AUX BOUNDARY BUILDER +// ================================================================================================ + +/// `BoundaryBuilder` impl that reduces each emitted interaction to its LogUp +/// denominator contribution `multiplicity · encode(msg)⁻¹` and sums them into a +/// running `EF` accumulator. /// -/// Must match `KernelRomInitMessage::value()` on the prover side, which encodes -/// `[KERNEL_PROC_INIT_LABEL, digest[0..4]]`. -fn kernel_proc_message>( - challenges: &trace::Challenges, - digest: &Word, -) -> EF { - challenges.encode( - bus_types::CHIPLETS_BUS, - [ - trace::chiplets::kernel_rom::KERNEL_PROC_INIT_LABEL, - digest[0], - digest[1], - digest[2], - digest[3], - ], - ) +/// Lets `reduced_aux_values` reuse the structured boundary emissions from +/// [`emit_miden_boundary`] — the same source consumed by the debug walker — +/// instead of open-coding the three corrections a second time. +/// +/// Denominators are `α + Σ βⁱ · field_i` with random `α, β`; on any legitimate proof they +/// are non-zero with overwhelming probability, and the outer quotient check already rejects +/// the degenerate case, so `insert` panics rather than threading an error through the +/// reducer. +struct ReduceBoundaryBuilder<'a, EF: ExtensionField> { + challenges: &'a Challenges, + public_values: &'a [Felt], + var_len_public_inputs: VarLenPublicInputs<'a, Felt>, + sum: EF, } -/// Reduces kernel procedure digests from var-len public inputs into a multiset product. -/// -/// Expects exactly one variable-length public input slice containing all kernel digests -/// as concatenated `Felt`s (i.e. `len % WORD_SIZE == 0`). -fn kernel_reduced_from_var_len>( - challenges: &trace::Challenges, - var_len_public_inputs: VarLenPublicInputs<'_, Felt>, -) -> Result { - if var_len_public_inputs.len() != 1 { - return Err(format!( - "expected 1 var-len public input slice, got {}", - var_len_public_inputs.len() - ) - .into()); +impl<'a, EF: ExtensionField> ReduceBoundaryBuilder<'a, EF> { + fn finalize(self) -> EF { + self.sum } - let kernel_felts = var_len_public_inputs[0]; - if !kernel_felts.len().is_multiple_of(WORD_SIZE) { - return Err(format!( - "kernel digest felts length {} is not a multiple of {}", - kernel_felts.len(), - WORD_SIZE - ) - .into()); +} + +impl<'a, EF: ExtensionField> BoundaryBuilder for ReduceBoundaryBuilder<'a, EF> { + type F = Felt; + type EF = EF; + + fn public_values(&self) -> &[Felt] { + self.public_values + } + + fn var_len_public_inputs(&self) -> &[&[Felt]] { + self.var_len_public_inputs } - let mut acc = EF::ONE; - for digest in kernel_felts.chunks_exact(WORD_SIZE) { - let word: Word = [digest[0], digest[1], digest[2], digest[3]].into(); - acc *= kernel_proc_message(challenges, &word); + + fn insert(&mut self, _name: &'static str, multiplicity: Felt, msg: M) + where + M: LookupMessage, + { + let inv = msg + .encode(self.challenges) + .try_inverse() + .expect("LogUp denominator must be non-zero under random challenges"); + self.sum += inv * multiplicity; } - Ok(acc) } diff --git a/air/src/lookup/aux_builder.rs b/air/src/lookup/aux_builder.rs new file mode 100644 index 0000000000..7a077e0903 --- /dev/null +++ b/air/src/lookup/aux_builder.rs @@ -0,0 +1,752 @@ +//! Generic LogUp aux-trace construction: fraction buffer + accumulator + top-level driver. +//! +//! Prover collection writes `(multiplicity, encoded_denominator)` pairs into a +//! [`LookupFractions`] buffer (one flat `Vec<(F, EF)>` plus one flat `Vec` of per-row +//! per-column counts). The fused [`accumulate`] pass batch-inverts denominators and walks +//! rows in order with a running accumulator column, writing out the complete aux trace as a +//! [`RowMajorMatrix`]. [`accumulate_slow`] is the reference oracle that does the same +//! computation naively (one `try_inverse()` per fraction). +//! +//! [`build_logup_aux_trace`] is the top-level driver: it sources challenges / +//! periodic-column shape directly from the AIR's own trait methods, runs +//! [`build_lookup_fractions`] + [`accumulate`], and splits the matrix into an +//! `(aux_trace, committed_final)` pair. Any `LiftedAir + LookupAir` implementor can +//! delegate its `AuxBuilder::build_aux_trace` to it with a one-line body. Callers whose +//! verifier expects a different committed-finals width (e.g. the Miden MASM verifier, +//! which currently absorbs two values) pad the returned `Vec` themselves. +//! +//! ## Aux trace shape +//! +//! [`accumulate`] returns a [`RowMajorMatrix`] with `num_rows + 1` rows: +//! +//! - row 0 is the all-`ZERO` initial accumulator +//! - row `r` (for `1..=num_rows`) holds the running sum **after** row `r − 1`'s fraction +//! contributions have been folded in +//! - row `num_rows` is therefore the global running sum across the entire trace +//! +//! The return value of [`build_logup_aux_trace`] splits that matrix in two: +//! +//! - `aux_trace` is the first `num_rows` rows of the accumulator — it starts at `ZERO` and ends at +//! the running sum **before** the last row's contribution. The last row's fraction contribution +//! does **not** appear in the aux trace. +//! - `committed_finals` is `[acc_final]`: the single accumulator terminal read out of row +//! `num_rows`. Verifiers that want a wider absorbed-values vector (e.g. the Miden MASM verifier, +//! which absorbs two, the second always `ZERO`) pad this themselves. +//! +//! ## Fraction buffer layout +//! +//! - `fractions` holds every `(multiplicity, encoded_denominator)` pair every row pushes, in the +//! exact order the builder produces them. Across one row, column 0's fractions come first, then +//! column 1's, …, then column `num_cols - 1`'s. Across rows, row 0's block comes before row 1's. +//! - `counts` has exactly `num_rows * num_cols` entries, laid out row-major: `counts[r * num_cols + +//! c]` is the number of fractions row `r` pushed into column `c`. Equivalently, +//! `counts.chunks(num_cols).nth(r)` is row `r`'s per-column tally. +//! +//! Both vecs are sized up front from [`LookupAir::column_shape`] so the hot row loop can +//! push into `Vec::with_capacity`-backed storage without re-allocating. + +use alloc::{vec, vec::Vec}; + +use miden_core::{ + field::{ExtensionField, Field}, + utils::{Matrix, RowMajorMatrix}, +}; +use miden_crypto::stark::air::LiftedAir; + +use super::{Challenges, LookupAir, ProverLookupBuilder, prover::build_lookup_fractions}; + +/// Row-chunk granularity for the fused accumulator. Matches +/// [`crate::trace::main_trace::ROW_MAJOR_CHUNK_SIZE`] so we stay consistent with the +/// repo's row-major tuning: ~512 rows × avg shape ~3 ≈ 1.5 K fractions per chunk and +/// ~24 KiB of chunk-local scratch, comfortably L1-resident on any modern x86/arm core. +const ACCUMULATE_ROWS_PER_CHUNK: usize = 512; + +// TOP-LEVEL DRIVER +// ================================================================================================ + +/// Generic `AuxBuilder::build_aux_trace` body for any `LiftedAir + LookupAir` AIR. +/// +/// Sources `α`, `β`, `max_message_width`, `num_bus_ids`, and the periodic columns directly +/// from the AIR's trait methods, runs the collection + accumulation phases, and returns +/// `(aux_trace, vec![acc_final])`. AIRs wire this in with a near one-line body on their +/// `AuxBuilder` impl; verifiers that expect extra absorbed values (e.g. the Miden MASM +/// verifier, which absorbs two) extend the returned `Vec` themselves. +/// +/// The challenges ordering (`challenges[0] = α`, `challenges[1] = β`) mirrors the +/// constraint-path adapter's `ConstraintLookupBuilder::new` so prover- and constraint-path +/// challenges line up. +pub fn build_logup_aux_trace( + air: &A, + main: &RowMajorMatrix, + challenges: &[EF], +) -> (RowMajorMatrix, Vec) +where + F: Field, + EF: ExtensionField, + A: LiftedAir, + for<'a> A: LookupAir>, +{ + let _span = tracing::info_span!("build_aux_trace_logup").entered(); + + let alpha = challenges[0]; + let beta = challenges[1]; + let lookup_challenges = + Challenges::::new(alpha, beta, air.max_message_width(), air.num_bus_ids()); + let periodic = air.periodic_columns(); + + let fractions = build_lookup_fractions(air, main, &periodic, &lookup_challenges); + + let full = accumulate(&fractions); + let num_cols = full.width; + let num_rows = main.height(); + debug_assert_eq!( + full.values.len(), + (num_rows + 1) * num_cols, + "accumulate output buffer is sized for num_rows + 1 rows", + ); + + // `accumulate` emits `num_rows + 1` rows; take the committed final from col 0 of the + // trailing row and truncate in place to avoid allocating a throwaway last-row Vec. + let mut data = full.values; + let committed_final = data[num_rows * num_cols]; + data.truncate(num_rows * num_cols); + + let aux_trace = RowMajorMatrix::new(data, num_cols); + (aux_trace, vec![committed_final]) +} + +// LOOKUP FRACTIONS +// ================================================================================================ + +/// Single flat fraction + counts buffer shared between the prover collection phase and +/// the downstream accumulator. +/// +/// ## Layout +/// +/// ```text +/// fractions (flat, row-major by write order): +/// | row 0, col 0 | row 0, col 1 | ... | row 0, col C-1 || row 1, col 0 | ... | +/// +/// counts (flat, row-major, length = num_rows * num_cols): +/// | r0c0 | r0c1 | ... | r0,C-1 | r1c0 | r1c1 | ... | r1,C-1 | ... | +/// ``` +/// +/// Row `r`'s contribution to column `c` is the slice +/// `fractions[prefix .. prefix + counts[r * num_cols + c]]`, where `prefix` is the running +/// sum of earlier `counts` entries. The accumulator walks rows in order with a single +/// cursor — no separate offset array, no gather. +/// +/// No padding, no fixed stride: a row that contributes zero fractions to a column writes +/// zero entries and records `counts.push(0)`. +pub struct LookupFractions +where + F: Field, + EF: ExtensionField, +{ + /// Flat fraction buffer, packed in builder write order (see the module doc). + /// + /// Exposed to the prover builder as `pub(super)` so it can split-borrow this field + /// disjointly from `counts` inside `LookupBuilder::column`. + pub(super) fractions: Vec<(F, EF)>, + /// Flat count buffer, length `num_rows * num_cols` after a complete collection pass, + /// laid out row-major so `counts[r * num_cols + c]` is the number of fractions row + /// `r` pushed into column `c`. + /// + /// Exposed to the prover builder as `pub(super)` for the same split-borrow reason. + pub(super) counts: Vec, + /// Per-column upper bound on fractions a single row can push. Used as the capacity + /// hint (`num_rows * Σ shape`) when allocating `fractions`, and as the reference for + /// the debug-mode overflow check in the prover builder. + pub(super) shape: Vec, + /// Number of main-trace rows this buffer is sized for. + num_rows: usize, + /// Cached `shape.len()` — the permutation column count. + num_cols: usize, +} + +impl LookupFractions +where + F: Field, + EF: ExtensionField, +{ + /// Allocate a fresh buffer sized to hold every fraction an AIR can emit across + /// `num_rows` rows. The flat fraction capacity is `num_rows * Σ shape`, so the row loop + /// does not re-allocate as long as each row stays within its declared bound. The flat + /// count capacity is `num_rows * shape.len()`. + /// + /// Takes `shape` by value so the caller owns the allocation (typically + /// `air.column_shape().to_vec()`). + pub fn from_shape(shape: Vec, num_rows: usize) -> Self { + let num_cols = shape.len(); + let total_fraction_capacity: usize = num_rows * shape.iter().sum::(); + let fractions = Vec::with_capacity(total_fraction_capacity); + let counts = Vec::with_capacity(num_rows * num_cols); + Self { + fractions, + counts, + shape, + num_rows, + num_cols, + } + } + + /// Number of permutation columns. + pub fn num_columns(&self) -> usize { + self.num_cols + } + + /// Total rows this buffer was sized for. + pub fn num_rows(&self) -> usize { + self.num_rows + } + + /// Per-column upper bound on fractions per row. + pub fn shape(&self) -> &[usize] { + &self.shape + } + + /// Full flat fraction buffer, packed in builder write order. Length equals + /// `Σ counts()` — i.e. the total number of fractions actually pushed. + pub fn fractions(&self) -> &[(F, EF)] { + &self.fractions + } + + /// Full flat count buffer, row-major. Length equals `num_rows * num_cols` after a + /// complete collection pass. Chunk with `counts().chunks(num_cols)` to get per-row + /// slices, or index directly as `counts()[r * num_cols + c]`. + pub fn counts(&self) -> &[usize] { + &self.counts + } +} + +// SLOW ACCUMULATOR (REFERENCE ORACLE) +// ================================================================================================ + +/// Naive per-fraction partial-sum accumulator, used as the correctness oracle for the +/// fused batch-inversion + partial-sum pass. +/// +/// Column 0 is the sole running-sum accumulator; columns 1+ are fraction columns that +/// store per-row values directly. +/// +/// Returns `aux[col]` of length `num_rows + 1`: +/// - `aux[0][0] = ZERO`, `aux[0][r+1] = aux[0][r] + Σ_col per_row_value[col]` +/// - `aux[i>0][r] = per_row_value[i]` for main row `r` +pub fn accumulate_slow(fractions: &LookupFractions) -> Vec> +where + F: Field, + EF: ExtensionField, +{ + let num_cols = fractions.num_columns(); + let num_rows = fractions.num_rows(); + let mut aux: Vec> = (0..num_cols).map(|_| vec![EF::ZERO; num_rows + 1]).collect(); + + let flat_fractions = fractions.fractions(); + let flat_counts = fractions.counts(); + debug_assert_eq!( + flat_counts.len(), + num_rows * num_cols, + "counts length {} != num_rows * num_cols {}", + flat_counts.len(), + num_rows * num_cols, + ); + + let mut per_row_value = vec![EF::ZERO; num_cols]; + let mut running_sum = EF::ZERO; + + let mut cursor = 0usize; + for (row, row_counts) in flat_counts.chunks(num_cols).enumerate() { + for (col, &count) in row_counts.iter().enumerate() { + let mut sum = EF::ZERO; + for &(m, d) in &flat_fractions[cursor..cursor + count] { + let d_inv = d + .try_inverse() + .expect("LogUp denominator must be non-zero (bus_prefix is never zero)"); + sum += d_inv * m; + } + per_row_value[col] = sum; + cursor += count; + } + + // Fraction columns: store per-row value at aux[col][row] (aux_curr convention). + for col in 1..num_cols { + aux[col][row] = per_row_value[col]; + } + + // Accumulator (col 0): running sum of ALL columns' per-row values. + let row_total: EF = per_row_value.iter().copied().sum(); + running_sum += row_total; + aux[0][row + 1] = running_sum; + } + debug_assert_eq!( + cursor, + flat_fractions.len(), + "cursor {cursor} != total fractions {}", + flat_fractions.len(), + ); + + aux +} + +// FUSED ACCUMULATOR (FAST PATH) +// ================================================================================================ + +/// Materialise the LogUp auxiliary trace from collected fractions. +/// +/// Takes the flat `(multiplicity, denominator)` buffer produced by the prover collection +/// phase and returns the complete aux trace as a row-major matrix. Column 0 is the +/// running-sum accumulator; columns 1+ store per-row fraction sums directly. +/// +/// ## Output layout +/// +/// Returns a [`RowMajorMatrix`] with `num_rows + 1` rows and `num_cols` columns. +/// Let `fᵢ(r) = Σⱼ mⱼ · dⱼ⁻¹` be the sum of fractions assigned to column `i` on row `r`: +/// +/// - Fraction columns (i > 0): `output[r][i] = fᵢ(r)` +/// - Accumulator (col 0): `output[0][0] = 0`, `output[r+1][0] = output[r][0] + Σᵢ fᵢ(r)` +/// +/// ## Algorithm +/// +/// **Prepass.** Build `row_frac_offsets` for O(1) row-range lookup into the flat buffer. +/// +/// **Phase 1 (parallel).** Split rows into fixed-size chunks. +/// Each chunk independently: batch-inverts its denominators (Montgomery trick), computes +/// `fᵢ(r)` for every `(row, col)`, writes fraction columns into the output matrix, and +/// records the row total `t(r) = Σᵢ fᵢ(r)` into a side buffer. +/// +/// **Phase 2 (sequential).** Prefix-sum over `t(r)` to fill the accumulator column: +/// `acc(r+1) = acc(r) + t(r)`. This step is inherently sequential (cross-row dependency) +/// but touches only one scalar per row. +pub fn accumulate(fractions: &LookupFractions) -> RowMajorMatrix +where + F: Field, + EF: ExtensionField, +{ + let num_cols = fractions.num_columns(); + let num_rows = fractions.num_rows(); + let out_rows = num_rows + 1; + + let mut output_data = vec![EF::ZERO; out_rows * num_cols]; + + let flat_fractions = fractions.fractions(); + let flat_counts = fractions.counts(); + debug_assert_eq!( + flat_counts.len(), + num_rows * num_cols, + "counts length {} != num_rows * num_cols {}", + flat_counts.len(), + num_rows * num_cols, + ); + + if num_rows == 0 || flat_fractions.is_empty() { + return RowMajorMatrix::new(output_data, num_cols); + } + + // Prepass: fraction-start offset for every row (length num_rows + 1). row_frac_offsets[r] = + // start of row r's fractions in flat_fractions; row_frac_offsets[num_rows] = total count. + let row_frac_offsets = compute_row_frac_offsets(flat_counts, num_rows, num_cols); + debug_assert_eq!(row_frac_offsets.len(), num_rows + 1); + debug_assert_eq!(row_frac_offsets[num_rows], flat_fractions.len()); + + // Phase 1 operates on rows 0..num_rows of the output buffer. It writes fraction + // columns (i > 0) and leaves col 0 untouched (still zero). The side buffer + // row_totals collects t(r) = Σᵢ fᵢ(r) for phase 2's prefix sum. + let frac_region = &mut output_data[..num_rows * num_cols]; + let mut row_totals: Vec = vec![EF::ZERO; num_rows]; + + let rows_per_chunk = ACCUMULATE_ROWS_PER_CHUNK; + + let phase1 = |(chunk_idx, (chunk_out, totals_slice)): (usize, (&mut [EF], &mut [EF]))| { + let row_lo = chunk_idx * rows_per_chunk; + let row_hi = (row_lo + rows_per_chunk).min(num_rows); + let chunk_rows = row_hi - row_lo; + let frac_lo = row_frac_offsets[row_lo]; + let frac_hi = row_frac_offsets[row_hi]; + let chunk_fracs = &flat_fractions[frac_lo..frac_hi]; + let chunk_counts = &flat_counts[row_lo * num_cols..row_hi * num_cols]; + debug_assert_eq!(chunk_out.len(), chunk_rows * num_cols); + debug_assert_eq!(totals_slice.len(), chunk_rows); + + if chunk_fracs.is_empty() { + return; + } + + // Batch-invert and scale: scratch[j] = mⱼ · dⱼ⁻¹ (ready to sum). + // Allocated once per chunk (~1.5 K elements ≈ 24 KiB, L1-resident). + let mut scratch: Vec = vec![EF::ZERO; chunk_fracs.len()]; + invert_and_scale(chunk_fracs, &mut scratch); + + let mut per_row_value: Vec = vec![EF::ZERO; num_cols]; + let mut cursor = 0usize; + for row_in_chunk in 0..chunk_rows { + let row_counts = &chunk_counts[row_in_chunk * num_cols..(row_in_chunk + 1) * num_cols]; + let out_row_base = row_in_chunk * num_cols; + + // fᵢ(r) = Σⱼ scratch[j] (scratch already holds mⱼ · dⱼ⁻¹). + for (col, &count) in row_counts.iter().enumerate() { + let mut sum = EF::ZERO; + for i in 0..count { + sum += scratch[cursor + i]; + } + per_row_value[col] = sum; + cursor += count; + } + + // output[r][i] = fᵢ(r) for fraction columns i > 0. + let out_row = &mut chunk_out[out_row_base..out_row_base + num_cols]; + out_row[1..].copy_from_slice(&per_row_value[1..]); + + // t(r) = Σᵢ fᵢ(r), consumed by phase 2. + totals_slice[row_in_chunk] = per_row_value.iter().copied().sum(); + } + debug_assert_eq!(cursor, chunk_fracs.len()); + }; + + #[cfg(not(feature = "concurrent"))] + { + frac_region + .chunks_mut(rows_per_chunk * num_cols) + .zip(row_totals.chunks_mut(rows_per_chunk)) + .enumerate() + .for_each(phase1); + } + #[cfg(feature = "concurrent")] + { + use miden_crypto::parallel::*; + frac_region + .par_chunks_mut(rows_per_chunk * num_cols) + .zip(row_totals.par_chunks_mut(rows_per_chunk)) + .enumerate() + .for_each(phase1); + } + + // Phase 2: acc(0) = 0, acc(r+1) = acc(r) + t(r). + // Writes col 0 of rows 1..=num_rows; row 0 col 0 stays at zero from the allocation. + let mut acc = EF::ZERO; + for r in 0..num_rows { + acc += row_totals[r]; + output_data[(r + 1) * num_cols] = acc; + } + + RowMajorMatrix::new(output_data, num_cols) +} + +/// Forward scan over the flat `counts` buffer producing per-row fraction-start offsets. +/// +/// Returns a `Vec` of length `num_rows + 1` where `offsets[r]` is the starting index +/// of row `r`'s fractions in the flat `fractions.fractions()` buffer and `offsets[num_rows]` +/// equals the total fraction count. Sequential (O(num_rows · num_cols) `usize` adds). +fn compute_row_frac_offsets(flat_counts: &[usize], num_rows: usize, num_cols: usize) -> Vec { + debug_assert_eq!(flat_counts.len(), num_rows * num_cols); + let mut offsets = Vec::with_capacity(num_rows + 1); + let mut acc = 0usize; + offsets.push(0); + for row_counts in flat_counts.chunks(num_cols) { + for &count in row_counts { + acc += count; + } + offsets.push(acc); + } + offsets +} + +/// Montgomery batch inversion fused with multiplicity scaling: writes `scratch[j] = mⱼ · dⱼ⁻¹` +/// using one field inversion + O(N) multiplications. +/// +/// The backward sweep multiplies each inverse by `mⱼ` (an `EF × F` mul, cheaper than +/// `EF × EF`) so the caller gets ready-to-sum fraction values without a second pass. +/// +/// # Panics +/// +/// Panics if the denominator product is zero (would indicate an upstream bug — individual +/// `dⱼ` are never zero because of the nonzero `bus_prefix[bus]` term). +fn invert_and_scale(chunk_fracs: &[(F, EF)], scratch: &mut [EF]) +where + F: Field, + EF: ExtensionField, +{ + debug_assert_eq!(scratch.len(), chunk_fracs.len()); + debug_assert!(!chunk_fracs.is_empty()); + + // Forward pass: scratch[i] = d₀ · d₁ · … · dᵢ (prefix products of denominators). + let mut acc = chunk_fracs[0].1; + scratch[0] = acc; + for i in 1..chunk_fracs.len() { + acc *= chunk_fracs[i].1; + scratch[i] = acc; + } + + // One field inversion — amortised over the whole chunk. + let mut running_inv = scratch[scratch.len() - 1] + .try_inverse() + .expect("LogUp denominator product must be non-zero (bus_prefix is never zero)"); + + // Backward sweep: scratch[i] = mᵢ · dᵢ⁻¹. + // + // Loop invariant (entering iteration i, for i = n-1 down to 1): + // running_inv = (dᵢ · dᵢ₊₁ · … · dₙ₋₁)⁻¹ + // scratch[i-1] = d₀ · d₁ · … · dᵢ₋₁ (left over from the forward pass) + // + // Then: + // dᵢ⁻¹ = scratch[i-1] · running_inv + // (prefix-product cancels every factor except dᵢ⁻¹ inside running_inv). + // We scale by mᵢ (EF × F, cheaper than EF × EF) to yield the fraction directly, then + // fold dᵢ into running_inv so the invariant holds for iteration i-1. + // After the loop: running_inv = d₀⁻¹, ready for the i = 0 case below. + for i in (1..chunk_fracs.len()).rev() { + let (m_i, d_i) = chunk_fracs[i]; + scratch[i] = scratch[i - 1] * running_inv * m_i; + running_inv *= d_i; + } + // i = 0: running_inv = d₀⁻¹. + scratch[0] = running_inv * chunk_fracs[0].0; +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_core::{ + field::{PrimeCharacteristicRing, QuadFelt}, + utils::Matrix, + }; + + use super::*; + use crate::{ + Felt, + lookup::{LookupAir, LookupBuilder}, + }; + + // Small deterministic LCG — reproducible stream for random-fixture cross-check tests. + // We don't need cryptographic quality, just determinism. + struct Lcg(u64); + impl Lcg { + fn next(&mut self) -> u64 { + self.0 = self.0.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); + self.0 + } + fn felt(&mut self) -> Felt { + // Take the high 32 bits to avoid the near-zero-biasing of low bits. + Felt::new_unchecked(self.next() >> 32) + } + fn quad(&mut self) -> QuadFelt { + QuadFelt::new([self.felt(), self.felt()]) + } + } + + /// Build a `LookupFractions` fixture of the given shape and row count, filled with + /// reproducibly-random non-zero denominators and arbitrary base-field multiplicities. + /// Shared helper for the cross-check tests. + fn random_fixture( + shape: &[usize], + num_rows: usize, + seed: u64, + ) -> LookupFractions { + let mut rng = Lcg(seed); + let mut fx: LookupFractions = LookupFractions { + fractions: Vec::with_capacity(num_rows * shape.iter().sum::()), + counts: Vec::with_capacity(num_rows * shape.len()), + shape: shape.to_vec(), + num_rows, + num_cols: shape.len(), + }; + for _row in 0..num_rows { + for &max_count in shape { + let count = (rng.next() as usize) % (max_count + 1); + for _ in 0..count { + let m = rng.felt(); + // Rejection sample until we get a non-zero denominator. With a 64-bit + // Goldilocks field and random draws, this basically never loops. + let d = loop { + let candidate = rng.quad(); + if candidate != QuadFelt::ZERO { + break candidate; + } + }; + fx.fractions.push((m, d)); + } + fx.counts.push(count); + } + } + fx + } + + /// Assert that `accumulate`'s row-major matrix output matches `accumulate_slow`'s per-column + /// `Vec>` output element-by-element. Shared check used by the single-chunk and + /// multi-chunk regression tests. + fn assert_matrix_matches_slow( + slow: &[Vec], + fast: &RowMajorMatrix, + num_cols: usize, + num_rows: usize, + ) { + assert_eq!(fast.width(), num_cols, "fast.width() mismatch"); + assert_eq!(fast.height(), num_rows + 1, "fast.height() mismatch"); + assert_eq!(slow.len(), num_cols, "slow column count mismatch"); + for (col, slow_col) in slow.iter().enumerate() { + assert_eq!(slow_col.len(), num_rows + 1, "slow col {col} row count mismatch"); + for (row, &s) in slow_col.iter().enumerate() { + let f = fast.values[row * num_cols + col]; + assert_eq!(s, f, "row {row} col {col} differs: slow={s:?} fast={f:?}",); + } + } + } + + /// Minimal `LookupAir` used to drive `LookupFractions::from_shape` without pulling in the + /// real Miden air. Only `num_columns()` and `column_shape()` are exercised; the + /// other methods return sentinel values and `eval` is a no-op. + struct FakeAir { + shape: [usize; 2], + } + + impl LookupAir for FakeAir { + fn num_columns(&self) -> usize { + self.shape.len() + } + fn column_shape(&self) -> &[usize] { + &self.shape + } + fn max_message_width(&self) -> usize { + 0 + } + fn num_bus_ids(&self) -> usize { + 0 + } + fn eval(&self, _builder: &mut LB) {} + } + + fn fixture(shape: [usize; 2], num_rows: usize) -> LookupFractions { + LookupFractions::from_shape(shape.to_vec(), num_rows) + } + + /// `accumulate_slow` returns `num_rows + 1` entries per column. Column 0 is a running + /// sum that also folds in column 1's per-row values. Column 1 is a fraction column + /// storing only per-row values (no cross-row accumulation). + /// + /// Layout fed in: + /// + /// ```text + /// row 0: col 0 pushes 2, col 1 pushes 0 + /// row 1: col 0 pushes 1, col 1 pushes 1 + /// + /// fractions = [(1, d1), (2, d2), (1, d1), (2, d2)] + /// counts = [2, 0, 1, 1] + /// ``` + #[test] + fn accumulate_slow_hand_crafted() { + let one = Felt::new_unchecked(1); + let two = Felt::new_unchecked(2); + let d1 = QuadFelt::new([Felt::new_unchecked(3), Felt::new_unchecked(0)]); + let d2 = QuadFelt::new([Felt::new_unchecked(5), Felt::new_unchecked(0)]); + + let mut fx = fixture([2, 1], 2); + // Row 0 + fx.fractions.push((one, d1)); + fx.fractions.push((two, d2)); + fx.counts.push(2); // col 0 + fx.counts.push(0); // col 1 + // Row 1 + fx.fractions.push((one, d1)); + fx.counts.push(1); // col 0 + fx.fractions.push((two, d2)); + fx.counts.push(1); // col 1 + + let aux = accumulate_slow(&fx); + assert_eq!(aux.len(), 2); + assert_eq!(aux[0].len(), 3); + assert_eq!(aux[1].len(), 3); + + let d1_inv = d1.try_inverse().unwrap(); + let d2_inv = d2.try_inverse().unwrap(); + + // Row 0: col 0 own = 1/d1 + 2/d2, col 1 own = 0 + // Row 1: col 0 own = 1/d1, col 1 own = 2/d2 + let row0_col0 = d1_inv + d2_inv.double(); + let row1_col0 = d1_inv; + let row1_col1 = d2_inv.double(); + + // Column 0 (accumulator): [0, row0_col0+0, prev + row1_col0 + row1_col1] + assert_eq!(aux[0][0], QuadFelt::ZERO); + assert_eq!(aux[0][1], row0_col0); + assert_eq!(aux[0][2], row0_col0 + row1_col0 + row1_col1); + + // Column 1 (fraction, aux_curr): [0, 2/d2, 0] + // Row 0: col 1 has no fractions → aux[1][0] = 0 + // Row 1: col 1 has 2/d2 → aux[1][1] = 2/d2 + // Row 2 (extra row): don't care (committed final) + assert_eq!(aux[1][0], QuadFelt::ZERO); + assert_eq!(aux[1][1], row1_col1); + } + + /// `LookupFractions::from_shape` sizes the flat `fractions` Vec with `num_rows * Σ shape` + /// capacity and the flat `counts` Vec with `num_rows * num_cols` capacity (so neither + /// reallocates in the hot loop). Both start empty. + #[test] + fn new_reserves_capacity() { + let air = FakeAir { shape: [3, 5] }; + let fx: LookupFractions = + LookupFractions::from_shape(air.shape.to_vec(), 10); + + assert_eq!(fx.num_columns(), 2); + assert_eq!(fx.num_rows(), 10); + assert_eq!(fx.shape(), &[3, 5]); + assert!(fx.fractions.capacity() >= 10 * (3 + 5)); + assert!(fx.counts.capacity() >= 10 * 2); + assert!(fx.fractions.is_empty()); + assert!(fx.counts.is_empty()); + } + + /// Single-chunk random cross-check: a tiny fixture (32 rows) fits inside one + /// [`ACCUMULATE_ROWS_PER_CHUNK`] chunk, so phase 2's prefix scan and phase 3's offset + /// add are both trivial (zero offset), and this test only exercises phase 1's fused + /// Montgomery + walk path. + #[test] + fn accumulate_matches_accumulate_slow_random() { + const SHAPE: [usize; 3] = [2, 1, 3]; + const NUM_ROWS: usize = 32; + const _: () = assert!( + NUM_ROWS < ACCUMULATE_ROWS_PER_CHUNK, + "must stay in one chunk to test phase 1", + ); + + let fx = random_fixture(&SHAPE, NUM_ROWS, 0x00c0_ffee_beef_c0de); + let slow = accumulate_slow(&fx); + let fast = accumulate(&fx); + assert_matrix_matches_slow(&slow, &fast, SHAPE.len(), NUM_ROWS); + } + + /// Multi-chunk regression test: a fixture spanning multiple + /// [`ACCUMULATE_ROWS_PER_CHUNK`]-row chunks (with a deliberately short trailing chunk) + /// exercises phase 2's prefix-sum path. The trailing `+ 7` rows ensure the last + /// chunk is smaller than the others and that `num_rows % rows_per_chunk != 0`, catching + /// any off-by-one in the last-chunk bounds. + #[test] + fn accumulate_multi_chunk_matches_accumulate_slow() { + const SHAPE: [usize; 4] = [1, 2, 3, 1]; + const NUM_ROWS: usize = ACCUMULATE_ROWS_PER_CHUNK * 3 + 7; + + let fx = random_fixture(&SHAPE, NUM_ROWS, 0xdead_beef_cafe_babe); + let slow = accumulate_slow(&fx); + let fast = accumulate(&fx); + assert_matrix_matches_slow(&slow, &fast, SHAPE.len(), NUM_ROWS); + } + + /// Empty-trace smoke test: `num_rows = 0` must return a 1-row, `num_cols`-wide zero + /// matrix (the initial condition) without touching the inversion path. + #[test] + fn accumulate_empty_trace() { + let shape = vec![2usize, 3, 1]; + let num_cols = shape.len(); + let fx: LookupFractions = LookupFractions { + fractions: Vec::new(), + counts: Vec::new(), + shape, + num_rows: 0, + num_cols, + }; + let aux = accumulate(&fx); + assert_eq!(aux.width(), num_cols); + assert_eq!(aux.height(), 1); + assert!(aux.values.iter().all(|v| *v == QuadFelt::ZERO)); + } +} diff --git a/air/src/lookup/builder.rs b/air/src/lookup/builder.rs new file mode 100644 index 0000000000..554e6a3c14 --- /dev/null +++ b/air/src/lookup/builder.rs @@ -0,0 +1,522 @@ +//! Closure-based builder traits for the LogUp lookup-argument API. +//! +//! The lookup-air refactor introduces the trait stack that sits on top of +//! `LookupAir`: +//! +//! - [`LookupBuilder`] — the top-level handle mirroring the subset of `LiftedAirBuilder` a lookup +//! author actually needs (trace access plus per-column scoping). It hides `assert_*` / `when_*` / +//! permutation plumbing and does not expose the verifier challenges. +//! - [`LookupColumn`] — per-column handle returned by [`LookupBuilder::next_column`]. It owns the +//! boundary between groups; its only job is to open a group (either the simple path or the +//! cached-encoding dual path). +//! - [`LookupGroup`] — the simple, challenge-free interaction API used by bus authors. Every method +//! here takes a `LookupMessage`; the enclosing adapter is responsible for encoding it under α + Σ +//! βⁱ · payload. +//! - [`LookupBatch`] — a short-lived handle returned inside [`LookupGroup::batch`]. Represents a +//! set of simultaneous interactions that share the outer group's flag. +//! - [`LookupGroup`] also exposes optional encoding primitives (`bus_prefix`, `beta_powers`, +//! `insert_encoded`) for the cached-encoding path. Default implementations panic; only the +//! constraint-path adapter overrides them with real bodies. +//! +//! No adapter impls live in this file; the bounds here are chosen so that both the +//! constraint-path adapter (which forwards to an inner `LiftedAirBuilder`, carrying +//! symbolic `AB::Expr` / `AB::ExprEF` associated types) and the prover-path adapter +//! (instantiated with the concrete `F` / `EF` field types) can satisfy them. + +use miden_core::field::{Algebra, ExtensionField, Field, PrimeCharacteristicRing}; +use miden_crypto::stark::air::WindowAccess; + +use super::message::LookupMessage; + +// DEGREE ANNOTATION +// ================================================================================================ + +/// Expected numerator / denominator degree for one interaction or scope. +/// +/// Every builder method takes a `Deg` as its last argument so authors can +/// declare the expected degrees inline. Production adapters ignore the value +/// (it is `Copy` and dead-code-eliminated after inlining). A debug adapter +/// can compare the declared degrees against the symbolic expression it just +/// accumulated and panic with the interaction's `name` if they disagree. +/// +/// - `n`: degree of the numerator polynomial (the `V` contribution). +/// - `d`: degree of the denominator polynomial (the `U` contribution). +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Deg { + pub n: usize, + pub d: usize, +} + +// LOOKUP BUILDER +// ================================================================================================ + +/// The trace-reading handle handed to a [`super::LookupAir`] implementation. +/// +/// `LookupBuilder` deliberately mirrors the subset of `LiftedAirBuilder`'s +/// associated types needed to read `main` and `periodic_values`. It is +/// **not** a sub-trait of `AirBuilder`: the constraint +/// emission surface (`assert_zero` / `when_first_row` / …) and the +/// permutation column plumbing stay hidden, which keeps the simple lookup +/// path free of challenge access. +/// +/// Implementors must not shortcut the per-column scoping: a [`super::LookupAir`] +/// author that opens `n` columns must issue exactly `n` calls to +/// [`LookupBuilder::next_column`], matching [`super::LookupAir::num_columns`]. +/// +/// ## Associated-type layout +/// +/// The base-field stack (`F`, `Expr`, `Var`) and extension-field stack +/// (`EF`, `ExprEF`, `VarEF`) mirror the upstream `AirBuilder` / +/// `ExtensionBuilder` split one-for-one; `Algebra` on `Expr` lets the +/// lookup author multiply main-trace variables with arbitrary expressions +/// without crossing trait boundaries. `PeriodicVar` / `MainWindow` come +/// from `PeriodicAirBuilder` / `AirBuilder` respectively and are passed +/// through the adapter unchanged. +/// +/// The per-column handle is a generic associated type +/// ([`Self::Column`](Self::Column)) so that each `column(...)` call can +/// borrow from `self` without outliving the closure. Its bound pins the +/// expression and extension-variable types to keep them in sync with the +/// outer builder. +pub trait LookupBuilder: Sized { + // --- base field stack (copied from AirBuilder) --- + + /// Underlying base field. Lookups only pin `Field` here (not the wider + /// `PrimeCharacteristicRing`) because the extension-field associated + /// types below require an `ExtensionField` relationship, and + /// `ExtensionField` itself bounds on `Field`. + type F: Field; + + /// Expression type over base-field elements. Must be an algebra over + /// both `Self::F` (for constants) and `Self::Var` (for trace + /// variables), matching upstream `AirBuilder::Expr`. + type Expr: Algebra + Algebra; + + /// Variable type over base-field trace cells. Held by value, so bound + /// only by `Into + Copy + Send + Sync`; the full arithmetic + /// bound soup from `AirBuilder::Var` is not required here because the + /// `Algebra` bound on `Expr` lets callers convert before + /// composing. + type Var: Into + Copy + Send + Sync; + + // --- extension field stack (copied from ExtensionBuilder) --- + + /// Extension field used by the auxiliary trace and the LogUp + /// accumulators. + type EF: ExtensionField; + + /// Expression type over extension-field elements; must be an algebra + /// over both `Self::Expr` (to lift base expressions) and `Self::EF` + /// (for extension-field constants). + type ExprEF: Algebra + Algebra; + + /// Variable type over extension-field trace cells (permutation + /// columns and the α/β challenges). + type VarEF: Into + Copy + Send + Sync; + + // --- auxiliary trace access types --- + + /// Periodic column value at the current row (copied from + /// `PeriodicAirBuilder::PeriodicVar`). + type PeriodicVar: Into + Copy; + + /// Two-row window over the main trace, returned as-is from the + /// underlying builder. Pinned to [`WindowAccess`] + `Clone` so a + /// lookup author can split it into `current_slice()` / `next_slice()` + /// and pass either to `borrow`-based view types without re-reading + /// the handle. + type MainWindow: WindowAccess + Clone; + + // --- per-column handle (GAT, borrows from self) --- + + /// Per-column handle opened by [`column`](Self::next_column). The GAT lets + /// the adapter stash a mutable borrow of its internal state (running + /// `(U, V)` on the constraint path, fraction collector on the prover + /// path) for the duration of a single column's closure. + type Column<'a>: LookupColumn + where + Self: 'a; + + // ---- trace access ---- + + /// Two-row main trace window. Pass-through to the wrapped builder. + fn main(&self) -> Self::MainWindow; + + /// Periodic column values at the current row. + fn periodic_values(&self) -> &[Self::PeriodicVar]; + + // ---- per-column scoping ---- + + /// Open a fresh permutation column and evaluate `f` inside it. + /// + /// The implementation is responsible for: + /// + /// 1. Wiring the column handle to the adapter's internal state (current `acc` / `acc_next` for + /// the constraint path; the per-column fraction buffer slot for the prover path). + /// 2. Running the closure, which must describe at least one group via [`LookupColumn::group`] + /// or [`LookupColumn::group_with_cached_encoding`]. + /// 3. Finalizing the column on close (emitting boundary + transition constraints, or draining + /// the column's fraction pair). + /// 4. Advancing to the next permutation column index so the next call targets a fresh + /// accumulator. + /// + /// The closure's return value is forwarded unchanged. + fn next_column<'a, R>(&'a mut self, f: impl FnOnce(&mut Self::Column<'a>) -> R, deg: Deg) -> R; +} + +// LOOKUP COLUMN +// ================================================================================================ + +/// Per-column handle returned by [`LookupBuilder::next_column`]. +/// +/// The only decision a column makes is how to open a group: either the +/// simple path via [`group`](Self::group) or the dual cached-encoding path +/// via [`group_with_cached_encoding`](Self::group_with_cached_encoding). +/// +/// Multiple groups may be opened per column; the adapter is responsible +/// for composing them according to the column accumulator algebra +/// (`V ← V·Uᵍ + Vᵍ·U`, `U ← U·Uᵍ`). Groups opened inside the same column +/// are assumed *product-closed*, not mutually exclusive. +pub trait LookupColumn { + /// Expression type over base-field elements. Pinned to + /// [`LookupBuilder::Expr`] through [`LookupBuilder::Column`]. + /// + /// `PrimeCharacteristicRing` is required so that [`LookupMessage`]'s + /// `E: PrimeCharacteristicRing + Clone` bound is transitively + /// satisfied when authors pass messages through `group.add(…)` etc. + type Expr: PrimeCharacteristicRing + Clone; + + /// Expression type over extension-field elements. Pinned to + /// [`LookupBuilder::ExprEF`] through [`LookupBuilder::Column`]. The + /// [`Algebra`] bound lets [`LookupMessage::encode`] + /// multiply an `Expr`-typed payload slot by an `ExprEF`-typed + /// β-power without manually lifting. + type ExprEF: PrimeCharacteristicRing + Clone + Algebra; + + /// Per-group handle used for the simple (challenge-free) path. GAT so + /// the group can borrow from the column for the duration of the + /// closure. + type Group<'a>: LookupGroup + where + Self: 'a; + + /// Open a group using the simple, challenge-free API. + /// + /// Every interaction added inside the closure is folded into this + /// group's `(Uᵍ, Vᵍ)` pair; on close, the column composes the pair + /// into its running accumulator. + /// + /// The `'a` lifetime on the group handle is tied to the `&'a mut + /// self` borrow of the column for the same reason as + /// [`LookupBuilder::next_column`]. + fn group<'a>(&'a mut self, name: &'static str, f: impl FnOnce(&mut Self::Group<'a>), deg: Deg); + + /// Open a group with two sibling descriptions for the same + /// interaction set. + /// + /// - `canonical` runs on the prover path. It sees the simple [`LookupGroup`] surface — no + /// challenges, no `insert_encoded`. Zero-valued flag closures are skipped by the backing + /// fraction collector. + /// - `encoded` runs on the constraint path. It sees the same [`LookupGroup`] surface, plus the + /// encoding primitives `beta_powers()`, `bus_prefix()`, and `insert_encoded()`. Authors use + /// this to precompute shared encoding fragments (e.g. a common `α + β·addr` prefix) and reuse + /// them across mutually-exclusive variants. + /// + /// Both closures must produce mathematically identical `(U, V)` + /// pairs; the split is purely an optimization for expensive + /// extension-field arithmetic on the symbolic path. Adapters are + /// free to drop whichever closure they do not use. + fn group_with_cached_encoding<'a>( + &'a mut self, + name: &'static str, + canonical: impl FnOnce(&mut Self::Group<'a>), + encoded: impl FnOnce(&mut Self::Group<'a>), + deg: Deg, + ); +} + +// LOOKUP GROUP +// ================================================================================================ + +/// Simple, challenge-free interaction API opened inside a +/// [`LookupColumn`]. +/// +/// Authors call `add` / `remove` / `insert` to describe one flag-gated +/// interaction at a time, or `batch` to describe several simultaneous +/// interactions that share a single outer flag. +/// +/// All methods take the message through an `impl FnOnce() -> M` closure +/// so the prover-path adapter can skip the construction (and any +/// expensive derivation) when `flag == 0`. +pub trait LookupGroup { + /// Expression type over base-field elements. Pinned to + /// [`LookupBuilder::Expr`] through the column. The + /// `PrimeCharacteristicRing` bound keeps [`LookupMessage`] happy when + /// authors pass messages through `add` / `remove` / `insert`. + type Expr: PrimeCharacteristicRing + Clone; + + /// Expression type over extension-field elements. Pinned to + /// [`LookupBuilder::ExprEF`] through the column. The + /// [`Algebra`] bound mirrors [`LookupColumn::ExprEF`] + /// and lets [`LookupMessage::encode`] use `ExprEF × Expr` products. + type ExprEF: PrimeCharacteristicRing + Clone + Algebra; + + /// Transient handle returned by [`batch`](Self::batch). GAT so the + /// batch can borrow from `self` (and therefore from the column and + /// the outer builder) for the duration of the closure. + type Batch<'b>: LookupBatch + where + Self: 'b; + + /// Add a single interaction with multiplicity `+1`, gated by `flag`. + /// + /// `msg` is deferred so the adapter can skip both the construction + /// and the encoding when `flag == 0` on the prover path. + /// + /// The default delegates to [`insert`](Self::insert) with multiplicity `ONE`. + /// Adapters may override for optimization (e.g. the constraint path avoids + /// the redundant `flag * ONE` symbolic node). + fn add(&mut self, name: &'static str, flag: Self::Expr, msg: impl FnOnce() -> M, deg: Deg) + where + M: LookupMessage, + { + self.insert(name, flag, Self::Expr::ONE, msg, deg); + } + + /// Add a single interaction with multiplicity `-1`, gated by `flag`. + /// + /// The default delegates to [`insert`](Self::insert) with multiplicity `NEG_ONE`. + fn remove(&mut self, name: &'static str, flag: Self::Expr, msg: impl FnOnce() -> M, deg: Deg) + where + M: LookupMessage, + { + self.insert(name, flag, Self::Expr::NEG_ONE, msg, deg); + } + + /// Add a single interaction with explicit signed multiplicity, gated + /// by `flag`. + /// + /// `multiplicity` is a base-field expression so callers can mix + /// trace columns, constants, and boolean selectors freely. + fn insert( + &mut self, + name: &'static str, + flag: Self::Expr, + multiplicity: Self::Expr, + msg: impl FnOnce() -> M, + deg: Deg, + ) where + M: LookupMessage; + + /// Open a batch of simultaneous interactions that all share the + /// single outer flag `flag`. + /// + /// Inside the closure, messages are passed by value (see + /// [`LookupBatch`]): the flag-zero skip is handled once at the batch + /// level, so per-interaction closures are redundant. + /// + /// Multiple batches inside the same [`LookupGroup`] are **not** + /// checked for mutual exclusion; adapters assume the author upholds + /// this invariant (matching the existing `RationalSet` contract). + fn batch<'a>( + &'a mut self, + name: &'static str, + flag: Self::Expr, + build: impl FnOnce(&mut Self::Batch<'a>), + deg: Deg, + ); + + // ---- encoding primitives (cached-encoding path only) ---- + + /// Precomputed powers `[β⁰, β¹, …, β^(W-1)]`, where + /// `W = max_message_width` from the enclosing + /// [`LookupAir`](super::LookupAir). + /// + /// The slice length is exactly `W` — there is **no** trailing `β^W` + /// entry, because that power is the per-bus step baked into every + /// [`Challenges::bus_prefix`](super::Challenges) entry + /// at builder-construction time. Authors that want to build their + /// own encoded denominator loop should iterate over `beta_powers()` + /// directly and slice to their own message width. + /// + /// Returned as extension-field expressions; the adapter materializes + /// the powers once at construction time (as `AB::ExprEF` on the + /// constraint path) and serves them back by reference. + /// + /// # Panics + /// + /// Default implementation panics — only valid inside the `encoded` + /// closure of [`LookupColumn::group_with_cached_encoding`]. + fn beta_powers(&self) -> &[Self::ExprEF] { + panic!( + "beta_powers() is only available inside the `encoded` closure of group_with_cached_encoding" + ) + } + + /// Look up the precomputed bus prefix + /// `bus_prefix[bus_id] = α + (bus_id + 1) · β^W` for the given + /// coarse bus ID. + /// + /// Returns an owned [`Self::ExprEF`] by cloning the entry — the + /// underlying storage is a `Box<[ExprEF]>` on the adapter and + /// `ExprEF` is typically a ring element, so cloning is cheap. + /// + /// # Panics + /// + /// Default implementation panics — only valid inside the `encoded` + /// closure of [`LookupColumn::group_with_cached_encoding`]. + /// Also panics if `bus_id` is out of bounds of the adapter's + /// `num_bus_ids`. + fn bus_prefix(&self, bus_id: usize) -> Self::ExprEF { + let _ = bus_id; + panic!( + "bus_prefix() is only available inside the `encoded` closure of group_with_cached_encoding" + ) + } + + /// Add a flag-gated interaction whose denominator is already an + /// extension-field expression. + /// + /// - `flag`: base-field selector. Zero flags are skipped by the prover-path adapter + /// (constraint-path evaluates unconditionally). + /// - `multiplicity`: base-field signed multiplicity. + /// - `encoded`: closure producing the final denominator. Run once on the constraint path. On + /// the prover path the adapter may skip the call entirely when `flag == 0`. + /// + /// # Panics + /// + /// Default implementation panics — only valid inside the `encoded` + /// closure of [`LookupColumn::group_with_cached_encoding`]. + fn insert_encoded( + &mut self, + _name: &'static str, + _flag: Self::Expr, + _multiplicity: Self::Expr, + _encoded: impl FnOnce() -> Self::ExprEF, + _deg: Deg, + ) { + panic!( + "insert_encoded() is only available inside the `encoded` closure of group_with_cached_encoding" + ) + } +} + +// LOOKUP BATCH +// ================================================================================================ + +/// Transient handle exposed inside [`LookupGroup::batch`]. +/// +/// A batch groups several simultaneously-active interactions under a +/// single outer flag, emitted by the enclosing group. The flag-zero skip +/// is performed once by the group when the batch opens, so within the +/// batch the message can be built unconditionally and is taken by value +/// (not through a closure). +/// +/// Kept as a separate trait rather than a concrete helper struct because +/// the constraint-path and prover-path adapters need different backing +/// storage (`RationalSet` vs `FractionCollector`) and expressing that +/// split through a GAT on [`LookupGroup::Batch`] is cleaner than bolting +/// a second generic parameter onto a shared struct. +pub trait LookupBatch { + /// Expression type over base-field elements. Must match the + /// enclosing group's `Expr`. `PrimeCharacteristicRing` is required + /// by [`LookupMessage`] (passed by value into the `add` / `remove` / + /// `insert` methods below). + type Expr: PrimeCharacteristicRing + Clone; + + /// Expression type over extension-field elements. Must match the + /// enclosing group's `ExprEF` — [`LookupMessage::encode`] returns an + /// extension-field value and the batch's underlying algebra operates + /// on that type. The [`Algebra`] bound mirrors the + /// enclosing group's `ExprEF` bound. + type ExprEF: PrimeCharacteristicRing + Clone + Algebra; + + /// Absorb an interaction with multiplicity `+1`. + /// + /// The default delegates to [`insert`](Self::insert) with multiplicity `ONE`. + fn add(&mut self, name: &'static str, msg: M, deg: Deg) + where + M: LookupMessage, + { + self.insert(name, Self::Expr::ONE, msg, deg); + } + + /// Absorb an interaction with multiplicity `-1`. + /// + /// The default delegates to [`insert`](Self::insert) with multiplicity `NEG_ONE`. + fn remove(&mut self, name: &'static str, msg: M, deg: Deg) + where + M: LookupMessage, + { + self.insert(name, Self::Expr::NEG_ONE, msg, deg); + } + + /// Absorb an interaction with arbitrary signed multiplicity. + fn insert(&mut self, name: &'static str, multiplicity: Self::Expr, msg: M, deg: Deg) + where + M: LookupMessage; + + /// Absorb an interaction with an already-encoded denominator. + fn insert_encoded( + &mut self, + name: &'static str, + multiplicity: Self::Expr, + encoded: impl FnOnce() -> Self::ExprEF, + deg: Deg, + ); +} + +// BOUNDARY BUILDER +// ================================================================================================ + +/// Handle for emitting **once-per-proof** "outer" interactions — contributions to the +/// LogUp sum that are not tied to any main-trace row. +/// +/// Typical sources are committed-final boundary terminals (kernel ROM init, block hash +/// seed, log-precompile terminals, public-input bus seeds). Each emission contributes +/// one signed fraction to the overall balance; no column / row / group scoping, no +/// flag gating, no `Deg` (boundary terms are plain field elements, not polynomials). +/// +/// Used by [`super::LookupAir::eval_boundary`]. Default implementations on the trait +/// are a no-op, so AIRs with no boundary contributions don't need to override it. +pub trait BoundaryBuilder { + /// Base field for boundary-interaction multiplicities and encoded message slots. + type F: Field; + + /// Extension field used by [`LookupMessage::encode`] — matches the enclosing + /// `LookupAir`'s `LB::EF`. + type EF: ExtensionField; + + /// Public values passed to the proof (the `public_values` slice threaded through + /// `prove_stark`). + fn public_values(&self) -> &[Self::F]; + + /// Variable-length public inputs (e.g. kernel felts). Matches the layout the + /// prover hands to `miden_crypto::stark::prover::prove_single`. + fn var_len_public_inputs(&self) -> &[&[Self::F]]; + + /// Emit a boundary interaction with multiplicity `+1`. + /// + /// The default delegates to [`insert`](Self::insert) with multiplicity `ONE`. + fn add(&mut self, name: &'static str, msg: M) + where + M: LookupMessage, + { + self.insert(name, Self::F::ONE, msg); + } + + /// Emit a boundary interaction with multiplicity `-1`. + /// + /// The default delegates to [`insert`](Self::insert) with multiplicity `NEG_ONE`. + fn remove(&mut self, name: &'static str, msg: M) + where + M: LookupMessage, + { + self.insert(name, Self::F::NEG_ONE, msg); + } + + /// Emit a boundary interaction with an arbitrary signed multiplicity. + fn insert(&mut self, name: &'static str, multiplicity: Self::F, msg: M) + where + M: LookupMessage; +} diff --git a/air/src/trace/challenges.rs b/air/src/lookup/challenges.rs similarity index 52% rename from air/src/trace/challenges.rs rename to air/src/lookup/challenges.rs index 31ffb1b813..c1f692280d 100644 --- a/air/src/trace/challenges.rs +++ b/air/src/lookup/challenges.rs @@ -10,62 +10,85 @@ //! - **Processor aux trace builders** (concrete field elements): `Challenges` //! - **Verifier** (`reduced_aux_values`): `Challenges` //! -//! See [`super::bus_message`] for the standard coefficient index layout. -//! See [`super::bus_types`] for the bus interaction type constants. +//! See [`super::message`] for the standard coefficient index layout. +use alloc::{boxed::Box, vec::Vec}; use core::ops::{AddAssign, Mul}; use miden_core::field::PrimeCharacteristicRing; -use super::{MAX_MESSAGE_WIDTH, bus_types::NUM_BUS_TYPES}; - /// Encodes multiset/LogUp contributions as **bus_prefix\[bus\] + \**. /// /// - `alpha`: randomness base (kept public for direct access by range checker etc.) -/// - `beta_powers`: precomputed powers `[beta^0, beta^1, ..., beta^(MAX_MESSAGE_WIDTH-1)]` +/// - `beta_powers`: precomputed powers `[beta^0, beta^1, ..., beta^(max_message_width-1)]` /// - `bus_prefix`: per-bus domain separation constants `bus_prefix[i] = alpha + (i+1) * -/// beta^MAX_MESSAGE_WIDTH` +/// beta^max_message_width` /// /// The challenges are derived from permutation randomness: /// - `alpha = challenges[0]` /// - `beta = challenges[1]` /// -/// Precomputed once and passed by reference to all bus components. +/// Widths (`beta_powers.len()` and `bus_prefix.len()`) come from the [`LookupAir`]'s +/// `max_message_width()` / `num_bus_ids()` at construction time. The struct is built +/// once and read-only thereafter — `Box<[EF]>` over `Vec` drops the unused +/// capacity word and signals fixed length. +/// +/// [`LookupAir`]: crate::lookup::LookupAir pub struct Challenges { pub alpha: EF, - pub beta_powers: [EF; MAX_MESSAGE_WIDTH], - /// Per-bus prefix: `bus_prefix[i] = alpha + (i+1) * gamma` - /// where `gamma = beta^MAX_MESSAGE_WIDTH` and `(i+1)` is the domain separator. - pub bus_prefix: [EF; NUM_BUS_TYPES], + pub beta_powers: Box<[EF]>, + /// Per-bus domain separation: `bus_prefix[i] = alpha + (i+1) * gamma` + /// where `gamma = beta^max_message_width`. + pub bus_prefix: Box<[EF]>, } impl Challenges { - /// Builds `alpha`, precomputed `beta` powers, and per-bus prefixes. - pub fn new(alpha: EF, beta: EF) -> Self { - let mut beta_powers = core::array::from_fn(|_| EF::ONE); - for i in 1..MAX_MESSAGE_WIDTH { - beta_powers[i] = beta_powers[i - 1].clone() * beta.clone(); + /// Builds `alpha`, precomputed `beta` powers, and per-bus prefixes sized from the + /// [`LookupAir`]'s `max_message_width()` / `num_bus_ids()`. + /// + /// `beta_powers` holds `max_message_width` entries (indices `0..max_message_width`). + /// `bus_prefix` holds `num_bus_ids` entries. + /// `gamma = beta^max_message_width` (one power beyond the highest `beta_powers` index). + /// + /// [`LookupAir`]: crate::lookup::LookupAir + pub fn new(alpha: EF, beta: EF, max_message_width: usize, num_bus_ids: usize) -> Self { + assert!(max_message_width > 0, "max_message_width must be non-zero"); + + let mut beta_powers: Vec = Vec::with_capacity(max_message_width); + beta_powers.push(EF::ONE); + for i in 1..max_message_width { + beta_powers.push(beta_powers[i - 1].clone() * beta.clone()); } - // gamma = beta^MAX_MESSAGE_WIDTH (one power beyond the message range) - let gamma = beta_powers[MAX_MESSAGE_WIDTH - 1].clone() * beta; - let bus_prefix = - core::array::from_fn(|i| alpha.clone() + gamma.clone() * EF::from_u32((i as u32) + 1)); + let beta_powers = beta_powers.into_boxed_slice(); + + // gamma = beta^max_message_width (one power beyond the message range) + let gamma = beta_powers[max_message_width - 1].clone() * beta; + + let bus_prefix: Box<[EF]> = (0..num_bus_ids) + .map(|i| alpha.clone() + gamma.clone() * EF::from_u32((i as u32) + 1)) + .collect(); + Self { alpha, beta_powers, bus_prefix } } /// Encodes as **bus_prefix\[bus\] + sum(beta_powers\[i\] * elem\[i\])** with K consecutive /// elements. /// - /// The `bus` parameter is the bus index used for domain separation. + /// The `bus` parameter selects the bus interaction type for domain separation. #[inline(always)] pub fn encode(&self, bus: usize, elems: [BF; K]) -> EF where EF: Mul + AddAssign, { - const { assert!(K <= MAX_MESSAGE_WIDTH, "Message length exceeds beta_powers capacity") }; debug_assert!( - bus < NUM_BUS_TYPES, - "Bus index {bus} exceeds NUM_BUS_TYPES ({NUM_BUS_TYPES})" + K <= self.beta_powers.len(), + "Message length {K} exceeds beta_powers capacity ({})", + self.beta_powers.len(), + ); + debug_assert!( + bus < self.bus_prefix.len(), + "Bus index {bus} exceeds bus_prefix length ({})", + self.bus_prefix.len(), ); let mut acc = self.bus_prefix[bus].clone(); for (i, elem) in elems.into_iter().enumerate() { @@ -77,7 +100,7 @@ impl Challenges { /// Encodes as **bus_prefix\[bus\] + sum(beta_powers\[layout\[i\]\] * values\[i\])** using /// sparse positions. /// - /// The `bus` parameter is the bus index used for domain separation. + /// The `bus` parameter selects the bus interaction type for domain separation. #[inline(always)] pub fn encode_sparse( &self, @@ -89,8 +112,9 @@ impl Challenges { EF: Mul + AddAssign, { debug_assert!( - bus < NUM_BUS_TYPES, - "Bus index {bus} exceeds NUM_BUS_TYPES ({NUM_BUS_TYPES})" + bus < self.bus_prefix.len(), + "Bus index {bus} exceeds bus_prefix length ({})", + self.bus_prefix.len(), ); let mut acc = self.bus_prefix[bus].clone(); for (idx, value) in layout.into_iter().zip(values) { diff --git a/air/src/lookup/constraint.rs b/air/src/lookup/constraint.rs new file mode 100644 index 0000000000..63c551eff0 --- /dev/null +++ b/air/src/lookup/constraint.rs @@ -0,0 +1,501 @@ +//! Constraint-path adapter for the closure-based lookup API. +//! +//! Implements [`LookupBuilder`] over any `LiftedAirBuilder`. Each column's +//! constraints are emitted inline during [`LookupBuilder::next_column`]. +//! +//! ## LogUp constraint structure +//! +//! The aux trace has one **accumulator** (col 0) and several **fraction columns** (cols 1+). +//! Each bus interaction on row `r` contributes a rational term `mᵢ / dᵢ` to one of the +//! columns. Because the verifier cannot check rational equations directly, each column +//! stores the *numerator–denominator pair* `(Nᵢ, Dᵢ)` — the cross-multiplied sum of all +//! interactions assigned to that column on row `r`. The constraints then check: +//! +//! - **Fraction columns** (i > 0): `Dᵢ · acc[i] - Nᵢ = 0` on transition rows. This asserts that the +//! prover-supplied value `acc[i]` equals `Nᵢ/Dᵢ`, the sum of fractions for column `i` on that +//! row. +//! +//! - **Accumulator** (col 0): the single running sum across the entire trace. +//! - `when_first: acc[0] = 0` — starts at zero. +//! - `when_transition: D₀ · (acc_next[0] - Σᵢ acc[i]) - N₀ = 0` — the next accumulator value +//! equals the current value plus every column's per-row contribution (including col 0's own +//! interactions folded via `N₀/D₀`). +//! - `when_last: acc[0] = committed_final` — binds the final sum to the value committed +//! during the Fiat-Shamir transcript, ensuring the global LogUp sum is correct. +//! +//! ## Algebra location +//! +//! Per Amendment B the per-interaction encoding lives inside each +//! [`LookupMessage::encode`] body, so the adapter carries **no scratch +//! buffer**: every `add` / `remove` / `insert` body is a two-liner that +//! calls `msg.encode(self.challenges)` and absorbs the resulting +//! `AB::ExprEF` denominator into the running `(U, V)` (group) or +//! `(N, D)` (batch) pair. +//! +//! The running-pair updates are also inlined at every call site (no +//! `absorb_single` / `absorb` helpers), which lets the `add` / `remove` +//! paths skip the `flag * E::ONE` / `flag * E::NEG_ONE` multiplication +//! — on the constraint path that shrinks the symbolic tree by one node +//! per single-interaction call. +//! +//! ## Challenge handling +//! +//! The top-level [`ConstraintLookupBuilder`] reads α/β out of +//! `ab.permutation_randomness()[0..2]` exactly once at construction time +//! and stores them in a [`Challenges`], which +//! precomputes both the β-power table (β⁰..β^(W-1)) and the bus-prefix +//! table (`bus_prefix[i] = α + (i + 1) · β^W`) sized from the +//! [`LookupAir`] passed to [`ConstraintLookupBuilder::new`]. The cached +//! challenges flow through the per-column / per-group handles by shared +//! reference, so each interaction sees the same precomputed tables +//! without reconstructing them. +//! +//! The permutation column slices are *not* cached — each +//! [`LookupBuilder::next_column`] call re-queries `ab.permutation()` to pick +//! up the current-row / next-row `VarEF` values. `ab.permutation()` is +//! cheap (it builds a window over references) and not caching keeps the +//! builder small. + +use core::marker::PhantomData; + +use miden_core::field::PrimeCharacteristicRing; +use miden_crypto::stark::air::{ExtensionBuilder, LiftedAirBuilder, WindowAccess}; + +use super::{ + Challenges, Deg, LookupAir, LookupBatch, LookupBuilder, LookupColumn, LookupGroup, + LookupMessage, +}; + +// CONSTRAINT LOOKUP BUILDER +// ================================================================================================ + +/// Constraint-path [`LookupBuilder`] over a wrapped [`LiftedAirBuilder`]. +/// +/// Column 0 is the sole running-sum accumulator; columns 1+ are fraction columns. +/// All constraints are emitted inline during [`LookupBuilder::next_column`]. +pub struct ConstraintLookupBuilder<'ab, AB> +where + AB: LiftedAirBuilder + 'ab, +{ + ab: &'ab mut AB, + challenges: Challenges, + column_idx: usize, +} + +impl<'ab, AB> ConstraintLookupBuilder<'ab, AB> +where + AB: LiftedAirBuilder, +{ + pub fn new(ab: &'ab mut AB, air: &A) -> Self + where + A: LookupAir, + { + let (alpha, beta): (AB::ExprEF, AB::ExprEF) = { + let r = ab.permutation_randomness(); + (r[0].into(), r[1].into()) + }; + let challenges = + Challenges::::new(alpha, beta, air.max_message_width(), air.num_bus_ids()); + + Self { ab, challenges, column_idx: 0 } + } +} + +impl<'ab, AB> LookupBuilder for ConstraintLookupBuilder<'ab, AB> +where + AB: LiftedAirBuilder, +{ + type F = AB::F; + type Expr = AB::Expr; + type Var = AB::Var; + + type EF = AB::EF; + type ExprEF = AB::ExprEF; + type VarEF = AB::VarEF; + + type PeriodicVar = AB::PeriodicVar; + + type MainWindow = AB::MainWindow; + + type Column<'a> + = ConstraintColumn<'a, AB> + where + Self: 'a, + AB: 'a; + + fn main(&self) -> Self::MainWindow { + self.ab.main() + } + + fn periodic_values(&self) -> &[Self::PeriodicVar] { + self.ab.periodic_values() + } + + fn next_column<'a, R>( + &'a mut self, + f: impl FnOnce(&mut Self::Column<'a>) -> R, + _deg: Deg, + ) -> R { + // Open the column with an empty `(U, V) = (1, 0)` accumulator. + // The column only holds a shared borrow of the challenges and + // the two running-pair slots — the `&mut AB` stays on the + // builder and is only reached back into for the constraint + // emission below. + let mut col = ConstraintColumn { + challenges: &self.challenges, + u: AB::ExprEF::ONE, + v: AB::ExprEF::ZERO, + _phantom: PhantomData, + }; + let result = f(&mut col); + let ConstraintColumn { u, v, .. } = col; + + // Pick up permutation column values now that the column borrow is released. + let col_idx = self.column_idx; + self.column_idx += 1; + + if col_idx == 0 { + let (acc, acc_next, committed_final) = { + let mp = self.ab.permutation(); + let acc: AB::ExprEF = mp.current_slice()[0].into(); + let acc_next: AB::ExprEF = mp.next_slice()[0].into(); + let committed_final: AB::ExprEF = self.ab.permutation_values()[0].clone().into(); + (acc, acc_next, committed_final) + }; + + // Σ_i acc[i] across all permutation columns. + let all_curr_sum = { + let mp = self.ab.permutation(); + let current = mp.current_slice(); + let mut sum: AB::ExprEF = current[0].into(); + for &aux_i in ¤t[1..] { + sum += aux_i.into(); + } + sum + }; + + // when_first: acc[0] = 0 + // when_transition: D₀ · (acc_next[0] - Σᵢ acc[i]) - N₀ = 0 + // when_last: acc[0] = committed_final + // + // The natural closing check would fold the last row's interactions into the + // boundary constraint, but `when_last_row`'s selector adds a polynomial factor + // that would push some columns past the degree budget. Our model assumes the + // last row never fires any interactions (U = 1, V = 0), so we use the + // lower-degree form: `acc − committed_final = 0`. The fraction columns below + // enforce this algebraically via `when_last_row acc[i] = 0`. + self.ab.when_first_row().assert_zero_ext(acc.clone()); + self.ab.when_transition().assert_zero_ext(u * (acc_next - all_curr_sum) - v); + self.ab.when_last_row().assert_eq_ext(acc, committed_final); + } else { + // when_transition: Dᵢ · acc[i] - Nᵢ = 0 + // when_last: acc[i] = 0 — no bus may fire on the padding row; this + // is the invariant col 0's closing check + // assumes. + let acc_curr: AB::ExprEF = { + let mp = self.ab.permutation(); + mp.current_slice()[col_idx].into() + }; + self.ab.when_transition().assert_zero_ext(u * acc_curr.clone() - v); + self.ab.when_last_row().assert_zero_ext(acc_curr); + } + + result + } +} + +// CONSTRAINT COLUMN +// ================================================================================================ + +/// Per-column handle returned by [`ConstraintLookupBuilder::next_column`]. +/// +/// Holds only the running `(U, V)` accumulator and a shared borrow of +/// the precomputed [`Challenges`]. The wrapped `&mut AB` and the +/// permutation `acc` / `acc_next` values do **not** live on the column +/// — the enclosing `next_column` method handles finalization +/// directly after the closure returns. +pub struct ConstraintColumn<'a, AB> +where + AB: LiftedAirBuilder + 'a, +{ + challenges: &'a Challenges, + u: AB::ExprEF, + v: AB::ExprEF, + _phantom: PhantomData, +} + +impl<'a, AB> ConstraintColumn<'a, AB> +where + AB: LiftedAirBuilder, +{ + /// Compose an inner-group `(U_g, V_g)` pair into this column's + /// running `(U, V)` using the cross-multiplication rule + /// `V ← V·U_g + V_g·U`, `U ← U·U_g`. + fn fold_group(&mut self, u_g: AB::ExprEF, v_g: AB::ExprEF) { + self.v = self.v.clone() * u_g.clone() + v_g * self.u.clone(); + self.u = self.u.clone() * u_g; + } +} + +impl<'a, AB> LookupColumn for ConstraintColumn<'a, AB> +where + AB: LiftedAirBuilder, +{ + type Expr = AB::Expr; + type ExprEF = AB::ExprEF; + + type Group<'g> + = ConstraintGroup<'g, AB> + where + Self: 'g, + AB: 'g; + + fn group<'g>( + &'g mut self, + _name: &'static str, + f: impl FnOnce(&mut Self::Group<'g>), + _deg: Deg, + ) { + let mut group = ConstraintGroup { + challenges: self.challenges, + u: AB::ExprEF::ONE, + v: AB::ExprEF::ZERO, + _phantom: PhantomData, + }; + f(&mut group); + let ConstraintGroup { u, v, .. } = group; + self.fold_group(u, v); + } + + fn group_with_cached_encoding<'g>( + &'g mut self, + _name: &'static str, + _canonical: impl FnOnce(&mut Self::Group<'g>), + encoded: impl FnOnce(&mut Self::Group<'g>), + _deg: Deg, + ) { + // Constraint path: only the `encoded` closure runs; the + // `canonical` closure is dropped unused. This matches the plan's + // split where `canonical` is the prover-path description. + let mut group = ConstraintGroup { + challenges: self.challenges, + u: AB::ExprEF::ONE, + v: AB::ExprEF::ZERO, + _phantom: PhantomData, + }; + encoded(&mut group); + let ConstraintGroup { u, v, .. } = group; + self.fold_group(u, v); + } +} + +// CONSTRAINT GROUP +// ================================================================================================ + +/// Per-group handle for the constraint path. +/// +/// Implements [`LookupGroup`] with working `beta_powers`, `bus_prefix`, +/// and `insert_encoded` overrides (the constraint path always has the +/// precomputed challenge tables available). +/// +/// Accumulates an internal `(U_g, V_g)` pair as the author calls +/// `add` / `remove` / `insert` / `batch`. The column consumes the pair +/// via `ConstraintColumn::fold_group` once the group closure returns. +/// +/// Each per-interaction `add` / `remove` / `insert` body calls +/// `msg.encode(self.challenges)` directly and folds the resulting +/// denominator into `(U_g, V_g)` inline — no intermediate scratch +/// buffer and no helper method call. +pub struct ConstraintGroup<'a, AB> +where + AB: LiftedAirBuilder + 'a, +{ + challenges: &'a Challenges, + u: AB::ExprEF, + v: AB::ExprEF, + _phantom: PhantomData, +} + +impl<'a, AB> LookupGroup for ConstraintGroup<'a, AB> +where + AB: LiftedAirBuilder, +{ + type Expr = AB::Expr; + type ExprEF = AB::ExprEF; + + type Batch<'b> + = ConstraintBatch<'b, AB> + where + Self: 'b, + AB: 'b; + + fn add(&mut self, _name: &'static str, flag: Self::Expr, msg: impl FnOnce() -> M, _deg: Deg) + where + M: LookupMessage, + { + // `add` = multiplicity +1. `V_g += flag · 1 = flag`, skipping + // the redundant multiplication that a generic `insert` would + // emit. + let v = msg().encode(self.challenges); + self.u += (v - AB::ExprEF::ONE) * flag.clone(); + self.v += flag; + } + + fn remove( + &mut self, + _name: &'static str, + flag: Self::Expr, + msg: impl FnOnce() -> M, + _deg: Deg, + ) where + M: LookupMessage, + { + // `remove` = multiplicity −1. `V_g += flag · (−1) = −flag`. + let v = msg().encode(self.challenges); + self.u += (v - AB::ExprEF::ONE) * flag.clone(); + self.v -= flag; + } + + fn insert( + &mut self, + _name: &'static str, + flag: Self::Expr, + multiplicity: Self::Expr, + msg: impl FnOnce() -> M, + _deg: Deg, + ) where + M: LookupMessage, + { + // General case: `V_g += flag · multiplicity`. + let v = msg().encode(self.challenges); + self.u += (v - AB::ExprEF::ONE) * flag.clone(); + self.v += flag * multiplicity; + } + + fn batch<'b>( + &'b mut self, + _name: &'static str, + flag: Self::Expr, + build: impl FnOnce(&mut Self::Batch<'b>), + _deg: Deg, + ) { + // Batch algebra: start with `(N, D) = (0, 1)`, run `build`, + // then fold the final `(N, D)` into `(U_g, V_g)` via + // `U_g += (D − 1) · flag`, `V_g += N · flag`. + let mut batch = ConstraintBatch { + challenges: self.challenges, + n: AB::ExprEF::ZERO, + d: AB::ExprEF::ONE, + _phantom: PhantomData, + }; + build(&mut batch); + let ConstraintBatch { n, d, .. } = batch; + self.u += (d - AB::ExprEF::ONE) * flag.clone(); + self.v += n * flag; + } + + fn beta_powers(&self) -> &[Self::ExprEF] { + &self.challenges.beta_powers[..] + } + + fn bus_prefix(&self, bus_id: usize) -> Self::ExprEF { + self.challenges.bus_prefix[bus_id].clone() + } + + fn insert_encoded( + &mut self, + _name: &'static str, + flag: Self::Expr, + multiplicity: Self::Expr, + encoded: impl FnOnce() -> Self::ExprEF, + _deg: Deg, + ) { + // Same `(U_g, V_g)` update as `insert`, but the denominator + // comes straight from the user's pre-computed closure instead + // of a `LookupMessage::encode` call. + let v = encoded(); + self.u += (v - AB::ExprEF::ONE) * flag.clone(); + self.v += flag * multiplicity; + } +} + +// CONSTRAINT BATCH +// ================================================================================================ + +/// Batch handle returned by [`LookupGroup::batch`]. +/// +/// Wraps an internal `(N, D)` pair and absorbs each interaction via the +/// cross-multiplication rule `N' = N·v + m·D`, `D' = D·v`. The +/// enclosing [`ConstraintGroup::batch`] folds the final `(N, D)` into +/// the group's `(U_g, V_g)` using the outer flag. +/// +/// Per-interaction encoding lives on the message itself +/// ([`LookupMessage::encode`]), and the `(N, D)` update is inlined at +/// every call site (no `absorb` helper) so the `add` / `remove` paths +/// can skip the `m · D` multiplication when `m = ±1`. +pub struct ConstraintBatch<'a, AB> +where + AB: LiftedAirBuilder + 'a, +{ + challenges: &'a Challenges, + n: AB::ExprEF, + d: AB::ExprEF, + _phantom: PhantomData, +} + +impl<'a, AB> LookupBatch for ConstraintBatch<'a, AB> +where + AB: LiftedAirBuilder, +{ + type Expr = AB::Expr; + type ExprEF = AB::ExprEF; + + fn add(&mut self, _name: &'static str, msg: M, _deg: Deg) + where + M: LookupMessage, + { + // `m = 1`: `(N, D) ← (N·v + D, D·v)`. Skips the `m · D` mul. + let v = msg.encode(self.challenges); + let d_prev = self.d.clone(); + self.n = self.n.clone() * v.clone() + d_prev; + self.d = self.d.clone() * v; + } + + fn remove(&mut self, _name: &'static str, msg: M, _deg: Deg) + where + M: LookupMessage, + { + // `m = −1`: `(N, D) ← (N·v − D, D·v)`. + let v = msg.encode(self.challenges); + let d_prev = self.d.clone(); + self.n = self.n.clone() * v.clone() - d_prev; + self.d = self.d.clone() * v; + } + + fn insert(&mut self, _name: &'static str, multiplicity: Self::Expr, msg: M, _deg: Deg) + where + M: LookupMessage, + { + // General case: `(N, D) ← (N·v + m·D, D·v)`. + let v = msg.encode(self.challenges); + let d_prev = self.d.clone(); + self.n = self.n.clone() * v.clone() + d_prev * multiplicity; + self.d = self.d.clone() * v; + } + + fn insert_encoded( + &mut self, + _name: &'static str, + multiplicity: Self::Expr, + encoded: impl FnOnce() -> Self::ExprEF, + _deg: Deg, + ) { + // Same as `insert`, but the denominator is a user-supplied + // pre-encoded `ExprEF` instead of a `LookupMessage::encode` + // call. + let v = encoded(); + let d_prev = self.d.clone(); + self.n = self.n.clone() * v.clone() + d_prev * multiplicity; + self.d = self.d.clone() * v; + } +} diff --git a/air/src/lookup/debug/mod.rs b/air/src/lookup/debug/mod.rs new file mode 100644 index 0000000000..aa256fff8e --- /dev/null +++ b/air/src/lookup/debug/mod.rs @@ -0,0 +1,19 @@ +//! Generic debug surface for the LogUp lookup-argument API. +//! +//! Split into two regimes: +//! +//! | Module | Regime | +//! |--------|--------| +//! | [`validation`] | AIR self-checks — run against the `LookupAir` itself, no execution trace needed. One entry point, [`validation::validate`] / `.validate()`. | +//! | [`trace`] | Concrete-trace debugging — balance accumulator + per-column `(U, V)` oracle folds + mutex checks over a real main trace. | + +pub mod trace; +pub mod validation; + +pub use trace::{ + BalanceReport, DebugBoundaryEmitter, DebugTraceBuilder, MutualExclusionViolation, Unmatched, + check_trace_balance, collect_column_oracle_folds, +}; +pub use validation::{ + ValidateLayout, ValidateLookupAir, ValidationBuilder, ValidationError, validate, +}; diff --git a/air/src/lookup/debug/trace/builder.rs b/air/src/lookup/debug/trace/builder.rs new file mode 100644 index 0000000000..308b2a2ebc --- /dev/null +++ b/air/src/lookup/debug/trace/builder.rs @@ -0,0 +1,416 @@ +//! `DebugTraceBuilder` — the `LookupBuilder` adapter that updates +//! [`super::DebugTraceState`] per row of a concrete main trace. +//! +//! Pure implementation detail: instantiation happens inside +//! `super::run_trace_walk`. The builder, column, group, and batch handles all collapse +//! their associated types to `Felt` / `QuadFelt`. + +use alloc::{format, string::ToString}; + +use miden_core::field::{PrimeCharacteristicRing, QuadFelt}; +use miden_crypto::stark::air::RowWindow; + +use super::{ + super::super::{ + BoundaryBuilder, Challenges, Deg, LookupBatch, LookupBuilder, LookupColumn, LookupGroup, + LookupMessage, + }, + DebugTraceState, MutualExclusionViolation, PushRecord, +}; +use crate::Felt; + +// BUILDER +// ================================================================================================ + +/// Real-trace `LookupBuilder` that updates [`super::DebugTraceState`] per row. +pub struct DebugTraceBuilder<'a> { + main: RowWindow<'a, Felt>, + periodic_values: &'a [Felt], + challenges: &'a Challenges, + state: &'a mut DebugTraceState, + row_idx: usize, + column_idx: usize, +} + +impl<'a> DebugTraceBuilder<'a> { + pub fn new( + main: RowWindow<'a, Felt>, + periodic_values: &'a [Felt], + challenges: &'a Challenges, + state: &'a mut DebugTraceState, + row_idx: usize, + ) -> Self { + Self { + main, + periodic_values, + challenges, + state, + row_idx, + column_idx: 0, + } + } +} + +impl<'a> LookupBuilder for DebugTraceBuilder<'a> { + type F = Felt; + type Expr = Felt; + type Var = Felt; + + type EF = QuadFelt; + type ExprEF = QuadFelt; + type VarEF = QuadFelt; + + type PeriodicVar = Felt; + + type MainWindow = RowWindow<'a, Felt>; + + type Column<'c> + = DebugTraceColumn<'c> + where + Self: 'c; + + fn main(&self) -> Self::MainWindow { + self.main + } + + fn periodic_values(&self) -> &[Self::PeriodicVar] { + self.periodic_values + } + + fn next_column<'c, R>( + &'c mut self, + f: impl FnOnce(&mut Self::Column<'c>) -> R, + _deg: Deg, + ) -> R { + let mut col = DebugTraceColumn { + challenges: self.challenges, + state: &mut *self.state, + row_idx: self.row_idx, + column_idx: self.column_idx, + next_group_idx: 0, + }; + let result = f(&mut col); + self.column_idx += 1; + result + } +} + +// COLUMN +// ================================================================================================ + +pub struct DebugTraceColumn<'c> { + challenges: &'c Challenges, + state: &'c mut DebugTraceState, + row_idx: usize, + column_idx: usize, + next_group_idx: usize, +} + +impl<'c> DebugTraceColumn<'c> { + /// Common path shared by `group` and `group_with_cached_encoding`. Opens a group, + /// drives the caller's closure, folds the group's `(U_g, V_g)` into the column's + /// running `(U_col, V_col)`, and (for cached-encoding groups) records any mutex + /// violation. + fn open_group<'g>( + &'g mut self, + is_cached_encoding: bool, + f: impl FnOnce(&mut DebugTraceGroup<'g>), + ) { + let group_idx = self.next_group_idx; + let column_idx = self.column_idx; + let row_idx = self.row_idx; + + let mut group = DebugTraceGroup { + challenges: self.challenges, + state: &mut *self.state, + u: QuadFelt::ONE, + v: QuadFelt::ZERO, + row_idx, + column_idx, + group_idx, + check_mutex: is_cached_encoding, + active_flag_count: 0, + }; + f(&mut group); + + if group.check_mutex && group.active_flag_count > 1 { + group.state.mutex_violations.push(MutualExclusionViolation { + row: row_idx, + column_idx, + group_idx, + active_flags: group.active_flag_count, + }); + } + // Fold `(U_g, V_g)` into `(U_col, V_col)`: (U, V) ← (U·U_g, V·U_g + V_g·U) + let (u_col, v_col) = group.state.column_folds[column_idx]; + group.state.column_folds[column_idx] = (u_col * group.u, v_col * group.u + group.v * u_col); + + self.next_group_idx += 1; + } +} + +impl<'c> LookupColumn for DebugTraceColumn<'c> { + type Expr = Felt; + type ExprEF = QuadFelt; + + type Group<'g> + = DebugTraceGroup<'g> + where + Self: 'g; + + fn group<'g>( + &'g mut self, + _name: &'static str, + f: impl FnOnce(&mut Self::Group<'g>), + _deg: Deg, + ) { + self.open_group(false, f); + } + + fn group_with_cached_encoding<'g>( + &'g mut self, + _name: &'static str, + canonical: impl FnOnce(&mut Self::Group<'g>), + _encoded: impl FnOnce(&mut Self::Group<'g>), + _deg: Deg, + ) { + // Run only the canonical closure: both closures must describe the same + // interaction set by contract (`DebugStructureBuilder` verifies their folds + // agree on sampled rows), and running both here would double-count balance + // multiplicities. + self.open_group(true, canonical); + } +} + +// GROUP +// ================================================================================================ + +pub struct DebugTraceGroup<'g> { + challenges: &'g Challenges, + state: &'g mut DebugTraceState, + u: QuadFelt, + v: QuadFelt, + row_idx: usize, + column_idx: usize, + group_idx: usize, + /// `true` for `group_with_cached_encoding` — triggers the mutex check at group close. + check_mutex: bool, + active_flag_count: usize, +} + +impl<'g> DebugTraceGroup<'g> { + /// Count active flags for mutex checks. Only meaningful when `check_mutex == true`, + /// but cheap enough to run unconditionally. + fn track_mutex(&mut self, flag: Felt) { + if self.check_mutex && flag != Felt::ZERO { + self.active_flag_count += 1; + } + } + + /// Push one `(multiplicity, denom)` into both the balance map and the push log, + /// with the group's current `(row, col, group)` source coordinates. + fn record(&mut self, msg_repr: alloc::string::String, denom: QuadFelt, multiplicity: Felt) { + *self.state.balances.entry(denom).or_insert(Felt::ZERO) += multiplicity; + self.state.push_log.push(PushRecord { + row: self.row_idx, + column_idx: self.column_idx, + group_idx: self.group_idx, + msg_repr, + denom, + multiplicity, + }); + } +} + +impl<'g> LookupGroup for DebugTraceGroup<'g> { + type Expr = Felt; + type ExprEF = QuadFelt; + + type Batch<'b> + = DebugTraceBatch<'b> + where + Self: 'b; + + fn insert( + &mut self, + _name: &'static str, + flag: Felt, + multiplicity: Felt, + msg: impl FnOnce() -> M, + _deg: Deg, + ) where + M: LookupMessage, + { + self.track_mutex(flag); + if flag == Felt::ZERO { + return; + } + let built = msg(); + let v_msg = built.encode(self.challenges); + self.record(format!("{built:?}"), v_msg, multiplicity); + self.u += (v_msg - QuadFelt::ONE) * flag; + self.v += flag * multiplicity; + } + + fn batch<'b>( + &'b mut self, + _name: &'static str, + flag: Felt, + build: impl FnOnce(&mut Self::Batch<'b>), + _deg: Deg, + ) { + self.track_mutex(flag); + let active = flag != Felt::ZERO; + let (n, d) = { + let mut batch = DebugTraceBatch { + challenges: self.challenges, + state: &mut *self.state, + active, + n: QuadFelt::ZERO, + d: QuadFelt::ONE, + row_idx: self.row_idx, + column_idx: self.column_idx, + group_idx: self.group_idx, + }; + build(&mut batch); + (batch.n, batch.d) + }; + self.u += (d - QuadFelt::ONE) * flag; + self.v += n * flag; + } + + fn beta_powers(&self) -> &[QuadFelt] { + &self.challenges.beta_powers[..] + } + + fn bus_prefix(&self, bus_id: usize) -> QuadFelt { + self.challenges.bus_prefix[bus_id] + } + + fn insert_encoded( + &mut self, + _name: &'static str, + flag: Felt, + multiplicity: Felt, + encoded: impl FnOnce() -> QuadFelt, + _deg: Deg, + ) { + self.track_mutex(flag); + if flag == Felt::ZERO { + return; + } + let v_msg = encoded(); + self.record("".to_string(), v_msg, multiplicity); + self.u += (v_msg - QuadFelt::ONE) * flag; + self.v += flag * multiplicity; + } +} + +// BATCH +// ================================================================================================ + +pub struct DebugTraceBatch<'b> { + challenges: &'b Challenges, + state: &'b mut DebugTraceState, + /// `false` if the outer group's flag was zero — batch-level short-circuit for balance + /// accumulation. `(N, D)` still tracks normally so the outer group's `(U_g, V_g)` fold + /// stays correct. + active: bool, + n: QuadFelt, + d: QuadFelt, + /// Source coordinates inherited from the enclosing group for push-log records. + row_idx: usize, + column_idx: usize, + group_idx: usize, +} + +impl<'b> DebugTraceBatch<'b> { + fn record(&mut self, msg_repr: alloc::string::String, denom: QuadFelt, multiplicity: Felt) { + *self.state.balances.entry(denom).or_insert(Felt::ZERO) += multiplicity; + self.state.push_log.push(PushRecord { + row: self.row_idx, + column_idx: self.column_idx, + group_idx: self.group_idx, + msg_repr, + denom, + multiplicity, + }); + } +} + +impl<'b> LookupBatch for DebugTraceBatch<'b> { + type Expr = Felt; + type ExprEF = QuadFelt; + + fn insert(&mut self, _name: &'static str, multiplicity: Felt, msg: M, _deg: Deg) + where + M: LookupMessage, + { + let v_msg = msg.encode(self.challenges); + if self.active { + self.record(format!("{msg:?}"), v_msg, multiplicity); + } + let d_prev = self.d; + self.n = self.n * v_msg + d_prev * multiplicity; + self.d *= v_msg; + } + + fn insert_encoded( + &mut self, + _name: &'static str, + multiplicity: Felt, + encoded: impl FnOnce() -> QuadFelt, + _deg: Deg, + ) { + let v_msg = encoded(); + if self.active { + self.record("".to_string(), v_msg, multiplicity); + } + let d_prev = self.d; + self.n = self.n * v_msg + d_prev * multiplicity; + self.d *= v_msg; + } +} + +// BOUNDARY EMITTER +// ================================================================================================ + +/// `BoundaryBuilder` impl that writes once-per-proof emissions into the same +/// [`DebugTraceState`] as the per-row `DebugTraceBuilder`. Emissions are tagged with +/// `row: usize::MAX` and `msg_repr` prefixed `[boundary:]` so they're visible +/// in the report as originating outside the trace. +pub struct DebugBoundaryEmitter<'a> { + pub(super) challenges: &'a Challenges, + pub(super) state: &'a mut DebugTraceState, + pub(super) public_values: &'a [Felt], + pub(super) var_len_public_inputs: &'a [&'a [Felt]], +} + +impl<'a> BoundaryBuilder for DebugBoundaryEmitter<'a> { + type F = Felt; + type EF = QuadFelt; + + fn public_values(&self) -> &[Felt] { + self.public_values + } + + fn var_len_public_inputs(&self) -> &[&[Felt]] { + self.var_len_public_inputs + } + + fn insert(&mut self, name: &'static str, multiplicity: Felt, msg: M) + where + M: LookupMessage, + { + let denom = msg.encode(self.challenges); + *self.state.balances.entry(denom).or_insert(Felt::ZERO) += multiplicity; + self.state.push_log.push(PushRecord { + row: usize::MAX, + column_idx: usize::MAX, + group_idx: usize::MAX, + msg_repr: format!("[boundary:{name}] {msg:?}"), + denom, + multiplicity, + }); + } +} diff --git a/air/src/lookup/debug/trace/mod.rs b/air/src/lookup/debug/trace/mod.rs new file mode 100644 index 0000000000..8dd2d64831 --- /dev/null +++ b/air/src/lookup/debug/trace/mod.rs @@ -0,0 +1,310 @@ +//! Combined real-trace balance + per-column `(U, V)` oracle debug surface. +//! +//! One walk over a concrete main trace, row by row, produces three outputs projected +//! out of the shared `run_trace_walk` driver: +//! +//! - **Balance** — signed multiplicities keyed by encoded denominator. Any residual at the end of +//! the walk is an unmatched interaction. +//! - **Push log** — a [`PushRecord`] per interaction emission, capturing pre-encoding payload, +//! encoded denominator, signed multiplicity, and `(row, column, group)` source coordinates. +//! Joined back against the balance map at finalize time so each unmatched denominator lists the +//! exact pushes that summed to it. +//! - **Column oracle folds** — per-row per-column `(U_col, V_col)` pairs computed via the +//! constraint-path cross-multiplication rule, used by the processor's LogUp cross-check. +//! +//! Layout: +//! +//! - [`builder`] — the `DebugTraceBuilder` (plus column / group / batch handles) that drives each +//! per-row walk. +//! - This file — the report types ([`BalanceReport`], [`Unmatched`], [`PushRecord`], …), the +//! row-by-row `run_trace_walk` driver, and the two public entry points. + +use alloc::{string::String, vec, vec::Vec}; +use core::{borrow::Borrow, fmt}; +use std::collections::HashMap; + +use miden_core::{ + field::{PrimeCharacteristicRing, QuadFelt}, + utils::{Matrix, RowMajorMatrix}, +}; +use miden_crypto::stark::air::RowWindow; + +use super::super::{Challenges, LookupAir}; +use crate::Felt; + +pub mod builder; + +pub use builder::{ + DebugBoundaryEmitter, DebugTraceBatch, DebugTraceBuilder, DebugTraceColumn, DebugTraceGroup, +}; + +// REPORT TYPES +// ================================================================================================ + +/// An unmatched interaction: an encoded denom with non-zero net multiplicity after walking +/// the full trace. +#[derive(Debug, Clone)] +pub struct Unmatched { + pub denom: QuadFelt, + /// Net signed multiplicity modulo the field prime. + pub net_multiplicity: Felt, + /// Every push that landed on this encoded denominator during the walk, in emission + /// order. The caller can bucket these by `msg_repr` / column / row to isolate the + /// specific emit that left the denom unbalanced. + pub contributions: Vec, +} + +/// One interaction emission captured during a trace walk. +/// +/// Populated for every push that passes its flag check, regardless of whether the +/// interaction eventually balances. When a denom lands in [`BalanceReport::unmatched`], +/// the join against the push log shows exactly which emits (row, column, group, +/// payload) summed to the residual multiplicity. +#[derive(Debug, Clone)] +pub struct PushRecord { + pub row: usize, + pub column_idx: usize, + pub group_idx: usize, + /// `format!("{:?}", msg)` of the `LookupMessage` instance. `""` for + /// `insert_encoded` sites, where only the pre-computed denominator is known. + pub msg_repr: String, + pub denom: QuadFelt, + pub multiplicity: Felt, +} + +/// Per-row mutual-exclusion violation inside a cached-encoding group. +#[derive(Debug, Clone)] +pub struct MutualExclusionViolation { + pub row: usize, + pub column_idx: usize, + pub group_idx: usize, + pub active_flags: usize, +} + +/// Full report returned by [`check_trace_balance`]. +#[derive(Debug, Default)] +pub struct BalanceReport { + pub unmatched: Vec, + pub mutex_violations: Vec, +} + +impl BalanceReport { + pub fn is_ok(&self) -> bool { + self.unmatched.is_empty() && self.mutex_violations.is_empty() + } +} + +impl fmt::Display for BalanceReport { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// How many contributing pushes to print per unmatched denom before truncating. + const MAX_CONTRIB_LINES: usize = 4; + + if self.is_ok() { + return writeln!(f, "BalanceReport: OK"); + } + writeln!( + f, + "BalanceReport: {} unmatched, {} mutex violations", + self.unmatched.len(), + self.mutex_violations.len(), + )?; + for u in &self.unmatched { + writeln!(f, " denom {:?} net multiplicity {:?}", u.denom, u.net_multiplicity)?; + for r in u.contributions.iter().take(MAX_CONTRIB_LINES) { + writeln!( + f, + " row={} col={} group={} mult={:?} msg={}", + r.row, r.column_idx, r.group_idx, r.multiplicity, r.msg_repr, + )?; + } + if u.contributions.len() > MAX_CONTRIB_LINES { + writeln!( + f, + " … {} more contributions", + u.contributions.len() - MAX_CONTRIB_LINES, + )?; + } + } + for m in &self.mutex_violations { + writeln!( + f, + " mutex violation at row {} col {} group {}: {} active flags", + m.row, m.column_idx, m.group_idx, m.active_flags, + )?; + } + Ok(()) + } +} + +// STATE +// ================================================================================================ + +/// Scratch state threaded through [`DebugTraceBuilder`] for every row in the walk. The +/// driver creates one instance per walk; it resets `column_folds` at the start of each +/// row and keeps `balances` / `push_log` / `mutex_violations` accumulating across rows. +pub struct DebugTraceState { + /// Signed-multiplicity accumulator keyed by encoded denominator. Sorted at + /// finalize time for deterministic output. + pub(super) balances: HashMap, + /// Per-push record of every interaction emission — the structured equivalent of the + /// legacy `busdbg_log` stderr hook. Joined against `balances` in [`finalize`] so + /// each unmatched denom carries its source pushes. + pub(super) push_log: Vec, + pub(super) mutex_violations: Vec, + /// Per-column `(U_col, V_col)`. Reset to `(ONE, ZERO)` at the start of each row by + /// [`run_trace_walk`]. + pub(super) column_folds: Vec<(QuadFelt, QuadFelt)>, +} + +// ENTRY POINTS +// ================================================================================================ + +/// Walk a complete main trace and return the balance report (unmatched interactions + +/// mutex violations). +/// +/// Includes boundary contributions from [`LookupAir::eval_boundary`], so a fully +/// closed AIR produces `BalanceReport::is_ok() == true`. `var_len_public_inputs` is +/// the same shape the prover hands to `miden_crypto::stark::prover::prove_single` +/// (e.g. `&[&kernel_felts]`); pass `&[]` if the AIR has no variable-length public +/// inputs or no boundary contributions that consume them. +pub fn check_trace_balance( + air: &A, + main_trace: &RowMajorMatrix, + periodic_columns: &[Vec], + public_values: &[Felt], + var_len_public_inputs: &[&[Felt]], + challenges: &Challenges, +) -> BalanceReport +where + for<'a> A: LookupAir>, +{ + run_trace_walk( + air, + main_trace, + periodic_columns, + public_values, + var_len_public_inputs, + challenges, + ) + .balance +} + +/// Walk a complete main trace and return the per-row constraint-path `(U_col, V_col)` +/// folds. `folds[r][col]` is the fold for column `col` at row `r`. +/// +/// Does not incorporate boundary contributions — the folds are a per-row property of +/// the main trace, independent of once-per-proof outer emissions. +pub fn collect_column_oracle_folds( + air: &A, + main_trace: &RowMajorMatrix, + periodic_columns: &[Vec], + public_values: &[Felt], + challenges: &Challenges, +) -> Vec> +where + for<'a> A: LookupAir>, +{ + run_trace_walk(air, main_trace, periodic_columns, public_values, &[], challenges).folds_per_row +} + +// SHARED DRIVER +// ================================================================================================ + +struct TraceWalkOutput { + balance: BalanceReport, + folds_per_row: Vec>, +} + +/// Shared row-by-row driver used by both public entry points. Each row gets a fresh +/// [`DebugTraceBuilder`] with column folds reset to `(ONE, ZERO)`; the balance accumulator +/// persists across rows, the folds snapshot at row end. +fn run_trace_walk( + air: &A, + main_trace: &RowMajorMatrix, + periodic_columns: &[Vec], + public_values: &[Felt], + var_len_public_inputs: &[&[Felt]], + challenges: &Challenges, +) -> TraceWalkOutput +where + for<'a> A: LookupAir>, +{ + let num_rows = main_trace.height(); + let width = main_trace.width(); + let flat: &[Felt] = main_trace.values.borrow(); + let num_cols = air.num_columns(); + + let mut state = DebugTraceState { + balances: HashMap::new(), + push_log: Vec::new(), + mutex_violations: Vec::new(), + column_folds: vec![(QuadFelt::ONE, QuadFelt::ZERO); num_cols], + }; + let mut folds_per_row: Vec> = Vec::with_capacity(num_rows); + let mut periodic_row: Vec = vec![Felt::ZERO; periodic_columns.len()]; + + for r in 0..num_rows { + let curr = &flat[r * width..(r + 1) * width]; + let nxt_idx = (r + 1) % num_rows; + let next = &flat[nxt_idx * width..(nxt_idx + 1) * width]; + let window = RowWindow::from_two_rows(curr, next); + + for (i, col) in periodic_columns.iter().enumerate() { + periodic_row[i] = col[r % col.len()]; + } + + // Reset per-row folds; balances and mutex_violations persist. + for fold in state.column_folds.iter_mut() { + *fold = (QuadFelt::ONE, QuadFelt::ZERO); + } + + { + let mut lb = DebugTraceBuilder::new(window, &periodic_row, challenges, &mut state, r); + air.eval(&mut lb); + } + + folds_per_row.push(state.column_folds.clone()); + } + + // Boundary / outer interactions (once per proof, no row): kernel init, block + // hash, log-precompile terminals, …. Accumulates into the same balance map as + // the per-row trace emissions — a fully closed AIR produces `is_ok() == true`. + { + let mut boundary = DebugBoundaryEmitter { + challenges, + state: &mut state, + public_values, + var_len_public_inputs, + }; + air.eval_boundary(&mut boundary); + } + + TraceWalkOutput { balance: finalize(state), folds_per_row } +} + +fn finalize(state: DebugTraceState) -> BalanceReport { + let DebugTraceState { balances, push_log, mutex_violations, .. } = state; + + // Group every push by its encoded denom so each unmatched denom can pull its + // contributing records in O(1). Preserves emission order within each bucket. + let mut contrib_by_denom: HashMap> = HashMap::new(); + for record in push_log { + contrib_by_denom.entry(record.denom).or_default().push(record); + } + + let mut unmatched = Vec::new(); + for (denom, net) in balances { + if net == Felt::ZERO { + continue; + } + let contributions = contrib_by_denom.remove(&denom).unwrap_or_default(); + unmatched.push(Unmatched { + denom, + net_multiplicity: net, + contributions, + }); + } + // Sort for deterministic output — `HashMap` iteration order is arbitrary. + unmatched.sort_by_key(|u| u.denom); + BalanceReport { unmatched, mutex_violations } +} diff --git a/air/src/lookup/debug/validation.rs b/air/src/lookup/debug/validation.rs new file mode 100644 index 0000000000..f6b8edc5ee --- /dev/null +++ b/air/src/lookup/debug/validation.rs @@ -0,0 +1,685 @@ +//! Single AIR self-validation entry point, backed by one unified walker. +//! +//! Exposes one free function, [`validate`], and one extension trait, +//! [`ValidateLookupAir`], so any qualifying [`LookupAir`] can be checked with +//! `air.validate(layout)`. One short-circuit [`Result<(), ValidationError>`] covers: +//! +//! - `num_columns` declared vs observed (the walker counts `next_column` calls). +//! - Per-group and per-column `Deg { n, d }` declared vs observed (via +//! [`SymbolicExpression::degree_multiple`] on the running `(U, V)`). +//! - Cached-encoding canonical vs encoded `(U, V)` equivalence, checked by evaluating the symbolic +//! difference `U_c·V_e − U_e·V_c` at a random row. +//! - Simple-group scope: no illegal `insert_encoded` outside the `encoded` closure. +//! +//! The global max-degree budget is **not** checked here — the STARK prover's +//! quotient validation already enforces it and duplicating that check muddies +//! this module's purpose. +//! +//! # Why one walker +//! +//! Previously two walkers ran back-to-back over the same `LookupAir::eval` — +//! one symbolic (degree), one concrete (encoding equivalence). They shared the +//! same `(U, V)` fold algebra. Because [`SymbolicExpression`] is a closed +//! `{Leaf, Add, Sub, Neg, Mul}` tree whose leaves can be mapped to concrete +//! field elements given a row valuation, we keep just the symbolic +//! accumulators and evaluate the cached-encoding difference on demand at a +//! random row. Speed is a non-goal here; the `Arc` allocations in the +//! symbolic tree are debug-only. + +use alloc::vec::Vec; +use core::{fmt, marker::PhantomData}; + +use miden_core::field::{PrimeCharacteristicRing, QuadFelt}; +use miden_crypto::{ + rand::random_felt, + stark::air::{ + AirBuilder, PeriodicAirBuilder, PermutationAirBuilder, + symbolic::{ + BaseEntry, BaseLeaf, ExtEntry, ExtLeaf, SymbolicAirBuilder, SymbolicExpr, + SymbolicExpression, SymbolicExpressionExt, SymbolicVariable, SymbolicVariableExt, + }, + }, +}; + +use super::super::{ + Challenges, Deg, LookupAir, LookupBatch, LookupBuilder, LookupColumn, LookupGroup, + LookupMessage, +}; +use crate::Felt; + +type Inner = SymbolicAirBuilder; +type Expr = SymbolicExpression; +type ExprEF = SymbolicExpressionExt; + +// VALIDATION ERROR +// ================================================================================================ + +/// First problem [`validate`] observed. See the module docstring for the per-check +/// semantics; each variant corresponds to one of the checks. +#[derive(Clone, Debug)] +pub enum ValidationError { + /// [`LookupAir::num_columns`] disagreed with the number of `next_column` calls + /// issued by `eval`. + NumColumnsMismatch { declared: usize, observed: usize }, + /// A column's declared `Deg` differs from the observed symbolic degree of + /// its accumulated `(U, V)`. Declared degrees are authoritative and must + /// match exactly — loose upper bounds are rejected. + ColumnDegreeMismatch { + column_idx: usize, + declared: Deg, + observed: Deg, + }, + /// A group's declared `Deg` differs from the observed symbolic degree of + /// the group's `(U, V)` fold. Declared degrees are authoritative and must + /// match exactly — loose upper bounds are rejected. + GroupDegreeMismatch { + column_idx: usize, + group_idx: usize, + name: &'static str, + declared: Deg, + observed: Deg, + }, + /// A cached-encoding group's canonical and encoded closures produced different + /// `(U, V)` pairs: the symbolic difference `U_c·V_e − U_e·V_c` evaluated to a + /// non-zero `QuadFelt` at the sampled random row. + EncodingMismatch { + column_idx: usize, + group_idx: usize, + name: &'static str, + diff: QuadFelt, + }, + /// A simple-mode group called `insert_encoded`, which is only legal inside the + /// `encoded` closure of `group_with_cached_encoding`. + ScopeViolation { + column_idx: usize, + group_idx: usize, + name: &'static str, + }, +} + +impl fmt::Display for ValidationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NumColumnsMismatch { declared, observed } => { + write!(f, "num_columns mismatch: declared {declared}, observed {observed}") + }, + Self::ColumnDegreeMismatch { column_idx, declared, observed } => write!( + f, + "column[{column_idx}] degree mismatch: declared (n={}, d={}), observed (n={}, d={})", + declared.n, declared.d, observed.n, observed.d, + ), + Self::GroupDegreeMismatch { + column_idx, + group_idx, + name, + declared, + observed, + } => write!( + f, + "column[{column_idx}] group[{group_idx}] {name:?} degree mismatch: declared (n={}, d={}), observed (n={}, d={})", + declared.n, declared.d, observed.n, observed.d, + ), + Self::EncodingMismatch { column_idx, group_idx, name, diff } => write!( + f, + "column[{column_idx}] group[{group_idx}] {name:?} cached-encoding mismatch: U_c·V_e − U_e·V_c = {diff:?}", + ), + Self::ScopeViolation { column_idx, group_idx, name } => write!( + f, + "column[{column_idx}] group[{group_idx}] {name:?} simple group called insert_encoded", + ), + } + } +} + +// LAYOUT +// ================================================================================================ + +/// Subset of the full `AirLayout` struct that [`validate`] actually consumes. Kept +/// local so callers don't need to thread prover-only fields (permutation width, +/// committed final count) through just to run the self-check. +#[derive(Clone, Copy, Debug)] +pub struct ValidateLayout { + pub trace_width: usize, + pub num_public_values: usize, + pub num_periodic_columns: usize, + pub permutation_width: usize, + pub num_permutation_challenges: usize, + pub num_permutation_values: usize, +} + +impl ValidateLayout { + fn to_symbolic(self) -> miden_crypto::stark::air::symbolic::AirLayout { + miden_crypto::stark::air::symbolic::AirLayout { + preprocessed_width: 0, + main_width: self.trace_width, + num_public_values: self.num_public_values, + permutation_width: self.permutation_width, + num_permutation_challenges: self.num_permutation_challenges, + num_permutation_values: self.num_permutation_values, + num_periodic_columns: self.num_periodic_columns, + } + } +} + +// VALIDATE +// ================================================================================================ + +/// Run every AIR self-check in one walk. +/// +/// Short-circuits on the first problem. See [`ValidationError`] for the variants. +pub fn validate(air: &A, layout: ValidateLayout) -> Result<(), ValidationError> +where + for<'ab, 'r> A: LookupAir>, +{ + // Sample a single random row valuation shared by the symbolic and concrete + // sides. `alpha`/`beta` are instantiated twice: once as symbolic `Challenge` + // leaves inside `SymbolicAirBuilder::permutation_randomness`, and once as + // concrete `QuadFelt`s in `row_valuation`. The evaluator below maps + // `ExtEntry::Challenge { index: 0/1 }` back to these concrete values. + let current: Vec = (0..layout.trace_width).map(|_| random_felt()).collect(); + let next: Vec = (0..layout.trace_width).map(|_| random_felt()).collect(); + let periodic: Vec = (0..layout.num_periodic_columns).map(|_| random_felt()).collect(); + let alpha = QuadFelt::new([random_felt(), random_felt()]); + let beta = QuadFelt::new([random_felt(), random_felt()]); + + let mut sym = SymbolicAirBuilder::::new(layout.to_symbolic()); + let row_valuation = RowValuation { + current: ¤t, + next: &next, + periodic: &periodic, + alpha, + beta, + }; + let mut builder = ValidationBuilder::new(&mut sym, air, row_valuation); + air.eval(&mut builder); + match builder.take_error() { + Some(err) => Err(err), + None => Ok(()), + } +} + +// EXTENSION TRAIT +// ================================================================================================ + +/// Extension trait that adapts [`validate`] into a method on any qualifying +/// [`LookupAir`]. Call sites write `MyLookupAir.validate(layout)` instead of +/// `validate(&MyLookupAir, layout)`. +pub trait ValidateLookupAir { + fn validate(&self, layout: ValidateLayout) -> Result<(), ValidationError>; +} + +impl ValidateLookupAir for A +where + for<'ab, 'r> A: LookupAir>, +{ + fn validate(&self, layout: ValidateLayout) -> Result<(), ValidationError> { + validate(self, layout) + } +} + +// ROW VALUATION +// ================================================================================================ + +/// Concrete valuation used to evaluate symbolic `(U, V)` trees when the walker +/// needs a numeric answer (cached-encoding equivalence). Borrowed, so construction +/// is cheap and the walker's lifetime tracks the owning `Vec`s in [`validate`]. +#[derive(Clone, Copy)] +struct RowValuation<'r> { + current: &'r [Felt], + next: &'r [Felt], + periodic: &'r [Felt], + /// `Challenge[0]` in any `SymbolicExpressionExt` tree. + alpha: QuadFelt, + /// `Challenge[1]` in any `SymbolicExpressionExt` tree. + beta: QuadFelt, +} + +impl<'r> RowValuation<'r> { + fn eval_base(&self, expr: &Expr) -> Felt { + match expr { + SymbolicExpr::Leaf(leaf) => self.eval_base_leaf(leaf), + SymbolicExpr::Add { x, y, .. } => self.eval_base(x) + self.eval_base(y), + SymbolicExpr::Sub { x, y, .. } => self.eval_base(x) - self.eval_base(y), + SymbolicExpr::Neg { x, .. } => -self.eval_base(x), + SymbolicExpr::Mul { x, y, .. } => self.eval_base(x) * self.eval_base(y), + } + } + + fn eval_base_leaf(&self, leaf: &BaseLeaf) -> Felt { + match leaf { + BaseLeaf::Constant(c) => *c, + BaseLeaf::Variable(SymbolicVariable { entry, index, .. }) => match entry { + BaseEntry::Main { offset: 0 } => self.current[*index], + BaseEntry::Main { offset: 1 } => self.next[*index], + BaseEntry::Periodic => self.periodic[*index], + BaseEntry::Main { offset } => { + panic!("unexpected main offset {offset} in LookupAir::eval") + }, + // LookupBuilder doesn't expose preprocessed or public values, and + // LookupAir::eval can't construct these leaves. + BaseEntry::Preprocessed { .. } | BaseEntry::Public => { + panic!("unexpected {entry:?} leaf in LookupAir::eval") + }, + }, + // Selector leaves are only produced by `AirBuilder::is_first_row` / etc., + // which LookupBuilder does not expose. + BaseLeaf::IsFirstRow | BaseLeaf::IsLastRow | BaseLeaf::IsTransition => { + panic!("selector leaf {leaf:?} unexpected in LookupAir::eval") + }, + } + } + + fn eval_ext(&self, expr: &ExprEF) -> QuadFelt { + match expr { + SymbolicExpr::Leaf(leaf) => self.eval_ext_leaf(leaf), + SymbolicExpr::Add { x, y, .. } => self.eval_ext(x) + self.eval_ext(y), + SymbolicExpr::Sub { x, y, .. } => self.eval_ext(x) - self.eval_ext(y), + SymbolicExpr::Neg { x, .. } => -self.eval_ext(x), + SymbolicExpr::Mul { x, y, .. } => self.eval_ext(x) * self.eval_ext(y), + } + } + + fn eval_ext_leaf(&self, leaf: &ExtLeaf) -> QuadFelt { + match leaf { + ExtLeaf::Base(inner) => self.eval_base(inner).into(), + ExtLeaf::ExtConstant(c) => *c, + ExtLeaf::ExtVariable(SymbolicVariableExt { entry, index, .. }) => match entry { + ExtEntry::Challenge => match *index { + 0 => self.alpha, + 1 => self.beta, + i => panic!("unexpected challenge index {i} in LookupAir::eval"), + }, + // LookupBuilder doesn't expose permutation columns or permutation + // values — the prover-side builder is the only one that touches them. + ExtEntry::Permutation { .. } | ExtEntry::PermutationValue => { + panic!("unexpected {entry:?} leaf in LookupAir::eval") + }, + }, + } + } +} + +// BUILDER +// ================================================================================================ + +/// Unified walker that cross-checks `(U, V)` degrees, cached-encoding equivalence, +/// and simple-group scope in a single pass over [`LookupAir::eval`]. The first +/// error observed is preserved in an internal slot; any later problem is ignored +/// so [`validate`] can short-circuit cleanly once `eval` returns. +pub struct ValidationBuilder<'ab, 'r> { + ab: &'ab mut Inner, + sym_challenges: Challenges, + row_valuation: RowValuation<'r>, + column_idx: usize, + declared_columns: usize, + error: Option, +} + +impl<'ab, 'r> ValidationBuilder<'ab, 'r> { + fn new(ab: &'ab mut Inner, air: &A, row_valuation: RowValuation<'r>) -> Self + where + A: LookupAir, + { + let (alpha, beta): (ExprEF, ExprEF) = { + let r = ab.permutation_randomness(); + (r[0].into(), r[1].into()) + }; + let sym_challenges = + Challenges::::new(alpha, beta, air.max_message_width(), air.num_bus_ids()); + Self { + ab, + sym_challenges, + row_valuation, + column_idx: 0, + declared_columns: air.num_columns(), + error: None, + } + } + + fn take_error(mut self) -> Option { + if self.error.is_none() && self.column_idx != self.declared_columns { + self.error = Some(ValidationError::NumColumnsMismatch { + declared: self.declared_columns, + observed: self.column_idx, + }); + } + self.error + } +} + +impl<'ab, 'r> LookupBuilder for ValidationBuilder<'ab, 'r> { + type F = Felt; + type Expr = Expr; + type Var = SymbolicVariable; + + type EF = QuadFelt; + type ExprEF = ExprEF; + type VarEF = SymbolicVariableExt; + + type PeriodicVar = SymbolicVariable; + + type MainWindow = ::MainWindow; + + type Column<'c> + = ValidationColumn<'c, 'r> + where + Self: 'c; + + fn main(&self) -> Self::MainWindow { + self.ab.main() + } + + fn periodic_values(&self) -> &[Self::PeriodicVar] { + self.ab.periodic_values() + } + + fn next_column<'c, R>(&'c mut self, f: impl FnOnce(&mut Self::Column<'c>) -> R, deg: Deg) -> R { + let column_idx = self.column_idx; + self.column_idx += 1; + + let already_errored = self.error.is_some(); + let mut col = ValidationColumn { + challenges: &self.sym_challenges, + row_valuation: self.row_valuation, + u: ExprEF::ONE, + v: ExprEF::ZERO, + column_idx, + next_group_idx: 0, + error: None, + _phantom: PhantomData, + }; + let result = f(&mut col); + + if !already_errored { + if let Some(err) = col.error.take() { + self.error = Some(err); + } else { + let observed = Deg { + n: col.v.degree_multiple(), + d: col.u.degree_multiple(), + }; + if observed != deg { + self.error = Some(ValidationError::ColumnDegreeMismatch { + column_idx, + declared: deg, + observed, + }); + } + } + } + + result + } +} + +// COLUMN +// ================================================================================================ + +pub struct ValidationColumn<'c, 'r> { + challenges: &'c Challenges, + row_valuation: RowValuation<'r>, + u: ExprEF, + v: ExprEF, + column_idx: usize, + next_group_idx: usize, + /// First group-level error observed while walking this column, drained by + /// [`ValidationBuilder::next_column`] after the closure returns. + error: Option, + _phantom: PhantomData<&'c ()>, +} + +impl<'c, 'r> ValidationColumn<'c, 'r> { + fn fold_group(&mut self, u_g: ExprEF, v_g: ExprEF) { + self.v = self.v.clone() * u_g.clone() + v_g * self.u.clone(); + self.u = self.u.clone() * u_g; + } + + fn check_group_degree( + &mut self, + name: &'static str, + group_idx: usize, + declared: Deg, + u: &ExprEF, + v: &ExprEF, + ) { + if self.error.is_some() { + return; + } + let observed = Deg { + n: v.degree_multiple(), + d: u.degree_multiple(), + }; + if observed != declared { + self.error = Some(ValidationError::GroupDegreeMismatch { + column_idx: self.column_idx, + group_idx, + name, + declared, + observed, + }); + } + } +} + +/// Build a fresh group scoped to `challenges`. Taken as a free function (not a +/// method) so calling it doesn't borrow the containing `ValidationColumn` — +/// the caller can still mutate `self.error` while the group is alive. +fn fresh_group<'g>( + challenges: &'g Challenges, + inside_encoded_closure: bool, +) -> ValidationGroup<'g> { + ValidationGroup { + challenges, + u: ExprEF::ONE, + v: ExprEF::ZERO, + inside_encoded_closure, + used_insert_encoded: false, + } +} + +impl<'c, 'r> LookupColumn for ValidationColumn<'c, 'r> { + type Expr = Expr; + type ExprEF = ExprEF; + + type Group<'g> + = ValidationGroup<'g> + where + Self: 'g; + + fn group<'g>(&'g mut self, name: &'static str, f: impl FnOnce(&mut Self::Group<'g>), deg: Deg) { + let group_idx = self.next_group_idx; + self.next_group_idx += 1; + + let mut group = fresh_group(self.challenges, false); + f(&mut group); + let ValidationGroup { u, v, used_insert_encoded, .. } = group; + + if self.error.is_none() && used_insert_encoded { + self.error = Some(ValidationError::ScopeViolation { + column_idx: self.column_idx, + group_idx, + name, + }); + } + self.check_group_degree(name, group_idx, deg, &u, &v); + self.fold_group(u, v); + } + + fn group_with_cached_encoding<'g>( + &'g mut self, + name: &'static str, + canonical: impl FnOnce(&mut Self::Group<'g>), + encoded: impl FnOnce(&mut Self::Group<'g>), + deg: Deg, + ) { + let group_idx = self.next_group_idx; + self.next_group_idx += 1; + + let mut canon = fresh_group(self.challenges, false); + canonical(&mut canon); + + let mut enc = fresh_group(self.challenges, true); + encoded(&mut enc); + + // Cached-encoding equivalence: the two closures must agree on `(U, V)` + // up to cross-multiplication, i.e. `U_c·V_e − U_e·V_c == 0`. We don't + // rely on symbolic simplification to zero — we evaluate the difference + // at the shared random row. + if self.error.is_none() { + let diff_expr = canon.u.clone() * enc.v.clone() - enc.u.clone() * canon.v; + let diff = self.row_valuation.eval_ext(&diff_expr); + if diff != QuadFelt::ZERO { + self.error = Some(ValidationError::EncodingMismatch { + column_idx: self.column_idx, + group_idx, + name, + diff, + }); + } + } + + // Degree check and column fold use the `encoded` half, consistent with + // the production constraint path which only emits the encoded form. + let ValidationGroup { u, v, .. } = enc; + self.check_group_degree(name, group_idx, deg, &u, &v); + self.fold_group(u, v); + } +} + +// GROUP +// ================================================================================================ + +pub struct ValidationGroup<'g> { + challenges: &'g Challenges, + u: ExprEF, + v: ExprEF, + /// Set when this group was opened via the `encoded` closure of + /// `group_with_cached_encoding`; toggles the legal use of `insert_encoded`. + inside_encoded_closure: bool, + /// `true` if `insert_encoded` was called outside its legal scope. The column + /// inspects this flag at close time and raises `ScopeViolation` if the group + /// was simple. + used_insert_encoded: bool, +} + +impl<'g> LookupGroup for ValidationGroup<'g> { + type Expr = Expr; + type ExprEF = ExprEF; + + type Batch<'b> + = ValidationBatch<'b> + where + Self: 'b; + + fn add(&mut self, _name: &'static str, flag: Expr, msg: impl FnOnce() -> M, _deg: Deg) + where + M: LookupMessage, + { + let v_msg = msg().encode(self.challenges); + self.u += (v_msg - ExprEF::ONE) * flag.clone(); + self.v += flag; + } + + fn remove(&mut self, _name: &'static str, flag: Expr, msg: impl FnOnce() -> M, _deg: Deg) + where + M: LookupMessage, + { + let v_msg = msg().encode(self.challenges); + self.u += (v_msg - ExprEF::ONE) * flag.clone(); + self.v -= flag; + } + + fn insert( + &mut self, + _name: &'static str, + flag: Expr, + multiplicity: Expr, + msg: impl FnOnce() -> M, + _deg: Deg, + ) where + M: LookupMessage, + { + let v_msg = msg().encode(self.challenges); + self.u += (v_msg - ExprEF::ONE) * flag.clone(); + self.v += flag * multiplicity; + } + + fn batch<'b>( + &'b mut self, + _name: &'static str, + flag: Expr, + build: impl FnOnce(&mut Self::Batch<'b>), + _deg: Deg, + ) { + let mut batch = ValidationBatch { + challenges: self.challenges, + n: ExprEF::ZERO, + d: ExprEF::ONE, + }; + build(&mut batch); + let ValidationBatch { n, d, .. } = batch; + self.u += (d - ExprEF::ONE) * flag.clone(); + self.v += n * flag; + } + + fn beta_powers(&self) -> &[ExprEF] { + &self.challenges.beta_powers[..] + } + + fn bus_prefix(&self, bus_id: usize) -> ExprEF { + self.challenges.bus_prefix[bus_id].clone() + } + + fn insert_encoded( + &mut self, + _name: &'static str, + flag: Expr, + multiplicity: Expr, + encoded: impl FnOnce() -> ExprEF, + _deg: Deg, + ) { + if !self.inside_encoded_closure { + self.used_insert_encoded = true; + } + let v_msg = encoded(); + self.u += (v_msg - ExprEF::ONE) * flag.clone(); + self.v += flag * multiplicity; + } +} + +// BATCH +// ================================================================================================ + +pub struct ValidationBatch<'b> { + challenges: &'b Challenges, + n: ExprEF, + d: ExprEF, +} + +impl<'b> LookupBatch for ValidationBatch<'b> { + type Expr = Expr; + type ExprEF = ExprEF; + + fn insert(&mut self, _name: &'static str, multiplicity: Expr, msg: M, _deg: Deg) + where + M: LookupMessage, + { + let v_msg = msg.encode(self.challenges); + let d_prev = self.d.clone(); + self.n = self.n.clone() * v_msg.clone() + d_prev * multiplicity; + self.d = self.d.clone() * v_msg; + } + + fn insert_encoded( + &mut self, + _name: &'static str, + multiplicity: Expr, + encoded: impl FnOnce() -> ExprEF, + _deg: Deg, + ) { + let v_msg = encoded(); + let d_prev = self.d.clone(); + self.n = self.n.clone() * v_msg.clone() + d_prev * multiplicity; + self.d = self.d.clone() * v_msg; + } +} diff --git a/air/src/lookup/message.rs b/air/src/lookup/message.rs new file mode 100644 index 0000000000..de0d385893 --- /dev/null +++ b/air/src/lookup/message.rs @@ -0,0 +1,48 @@ +//! `LookupMessage` trait — the bus-message contract used by the closure-based +//! `LookupAir` / `LookupBuilder` API. +//! +//! Each message owns its full encoding loop against a borrowed +//! [`Challenges`]. +//! +//! ## Encoding contract +//! +//! Given a reference to [`Challenges`](crate::lookup::Challenges), a message produces +//! the denominator +//! +//! ```text +//! bus_prefix[bus] + Σ_{k=0..width} β^k · values[k] +//! ``` +//! +//! where `bus_prefix[i] = α + (i + 1) · β^W` is precomputed at builder construction time +//! and `W = MAX_MESSAGE_WIDTH`. Interaction-specific bus prefixes provide domain separation; +//! payloads then begin directly at `β⁰`. +//! +//! The bus identifier is a `usize` chosen by the caller (typically an enum variant cast +//! to `usize`); it picks out `bus_prefix[bus]` as the additive base. + +use miden_core::field::{Algebra, PrimeCharacteristicRing}; + +use crate::lookup::Challenges; + +// TRAIT +// ================================================================================================ + +/// A bus message: encodes itself as a LogUp denominator against a borrowed +/// [`Challenges`] table. +/// +/// `E` is the base-field expression type (typically `AB::Expr` on the constraint path and +/// `F` on the prover path); `EF` is the matching extension-field expression type +/// (`AB::ExprEF` / `EF` respectively). The [`Algebra`] bound on `EF` lets each message +/// multiply a base-field payload by an `EF`-typed β-power without manually lifting. +/// +/// Implementors pick a bus identifier, start the accumulator from +/// `challenges.bus_prefix[bus]`, and fold each payload value against +/// `challenges.beta_powers[k]` with straight-line arithmetic. +pub trait LookupMessage: core::fmt::Debug +where + E: PrimeCharacteristicRing + Clone, + EF: PrimeCharacteristicRing + Clone + Algebra, +{ + /// Encode this message as a LogUp denominator. See module docs for the encoding contract. + fn encode(&self, challenges: &Challenges) -> EF; +} diff --git a/air/src/lookup/mod.rs b/air/src/lookup/mod.rs new file mode 100644 index 0000000000..59255ecc9d --- /dev/null +++ b/air/src/lookup/mod.rs @@ -0,0 +1,113 @@ +//! Generic `LookupAir` / `LookupBuilder` lookup-argument module. +//! +//! Holds the field-polymorphic core of the closure-based LogUp machinery: the +//! [`LookupAir`] trait, the [`LookupBuilder`] / [`LookupColumn`] / [`LookupGroup`] / +//! [`LookupBatch`] surface, the [`Challenges`] struct, the [`LookupMessage`] encode trait, +//! the two-path adapters ([`ConstraintLookupBuilder`] for symbolic constraint +//! evaluation, [`ProverLookupBuilder`] for concrete-row fraction collection), the +//! [`LookupFractions`] accumulator, and the [`build_logup_aux_trace`] / +//! [`build_lookup_fractions`] drivers. +//! +//! This module is deliberately free of Miden-specific types so it can be extracted into +//! its own crate without further disentangling. The Miden-side wiring (aggregator AIR, +//! bus message structs, aux-trace builder, bus identifiers) is surfaced separately +//! through [`crate::logup`]. + +pub mod aux_builder; +pub mod builder; +pub mod challenges; +pub mod constraint; +#[cfg(feature = "std")] +pub mod debug; +pub mod message; +pub mod prover; + +pub use aux_builder::{LookupFractions, accumulate, accumulate_slow, build_logup_aux_trace}; +pub use builder::{BoundaryBuilder, Deg, LookupBatch, LookupBuilder, LookupColumn, LookupGroup}; +pub use challenges::Challenges; +pub use constraint::ConstraintLookupBuilder; +pub use message::LookupMessage; +pub use prover::{ProverLookupBuilder, build_lookup_fractions}; + +// LOOKUP AIR +// ================================================================================================ + +/// A declarative LogUp lookup argument. +/// +/// Shaped the same way as `p3_air::Air`: generic over the builder +/// the caller picks, and evaluated once per logical "row pair" (the +/// constraint path visits every row symbolically, the prover path visits +/// every concrete row). +/// +/// The trait carries both the static *shape* (column count, payload +/// width bound, bus-id upper bound) and the `eval` method that actually +/// emits the interactions. Adapter constructors take a `&impl +/// LookupAir` and read the shape via the trait — the `LB` type +/// parameter is pinned to the adapter itself, so there is no +/// ambiguity when the blanket `impl LookupAir +/// for MyAir` implementations apply. +/// +/// ## Contract +/// +/// - [`num_columns()`](Self::num_columns) must match the number of `LookupBuilder::next_column` +/// calls issued from [`eval`](Self::eval) — the adapter advances its internal column index each +/// time the closure returns and will panic (or produce undefined constraints) on a mismatch. +/// - [`max_message_width()`](Self::max_message_width) must be ≥ the widest payload any message in +/// the AIR emits. It counts **only** contiguous payload slots — the bus identifier is handled +/// separately through the precomputed bus-prefix table. +/// - [`num_bus_ids()`](Self::num_bus_ids) must be ≥ the largest bus ID any message in the AIR +/// emits, plus one; the adapter precomputes exactly that many bus prefixes and indexes into the +/// table with `bus_id as usize`. +pub trait LookupAir { + /// Number of permutation columns this argument occupies. + fn num_columns(&self) -> usize; + + /// Per-column upper bound on the number of fractions a single row can push. + /// + /// Length must equal [`num_columns()`](Self::num_columns). Each entry is the + /// **mutual-exclusion-aware** max — i.e. the largest active branch count taken across + /// all mutually exclusive groups inside the column, not the sum of every structural + /// `add` / `remove` / `insert` / `batch` push site. + /// + /// The prover-path adapter uses this to size the dense per-column fraction buffer + /// (`Vec::with_capacity`) so the hot row loop never re-allocates. + fn column_shape(&self) -> &[usize]; + + /// Upper bound on the **payload** width of any message emitted by + /// [`eval`](Self::eval), exclusive of the bus identifier slot. + fn max_message_width(&self) -> usize; + + /// Upper bound on any bus ID this AIR emits through + /// [`LookupMessage::encode`], + /// plus one. The adapter pre-computes that many bus prefixes at + /// construction time and indexes into the table with + /// `bus_id as usize`. + fn num_bus_ids(&self) -> usize; + + /// Evaluate the lookup argument, describing its interactions through + /// the builder's closure API. + fn eval(&self, builder: &mut LB); + + /// Emit boundary / "outer" interactions — once-per-proof contributions that don't + /// come from any main-trace row. + /// + /// Typical sources are committed-final terminals and public-input-driven seed + /// emissions (kernel ROM init, block hash seed, log-precompile terminals). + /// These close out buses whose per-row [`eval`](Self::eval) contributions alone + /// don't cancel. + /// + /// Consumed today only by the real-trace debug walker in + /// [`crate::lookup::debug::trace`], which combines per-row and boundary emissions + /// into a single balance check. The constraint and prover paths don't call this + /// method yet — boundary terms still flow through `when_first_row` / `when_last_row` + /// flag selectors inside [`eval`](Self::eval) until they are refactored to read + /// from here too. + /// + /// Default is a no-op so AIRs with no boundary contributions don't need to + /// override it. + fn eval_boundary(&self, _boundary: &mut B) + where + B: BoundaryBuilder, + { + } +} diff --git a/air/src/lookup/prover.rs b/air/src/lookup/prover.rs new file mode 100644 index 0000000000..58195e9e51 --- /dev/null +++ b/air/src/lookup/prover.rs @@ -0,0 +1,663 @@ +//! Prover-path adapter — pushes individual `(m, v)` fractions per +//! interaction into a dense per-column flat [`LookupFractions`] buffer +//! owned by the caller. +//! +//! Implements [`LookupBuilder`] for concrete base-field rows. Where the +//! [constraint-path adapter](super::constraint::ConstraintLookupBuilder) +//! emits symbolic `(U, V)` constraint expressions against a +//! `LiftedAirBuilder`, this adapter consumes two concrete rows of +//! base-field values and **pushes the individual fractions** each +//! interaction contributes — one `(multiplicity, denominator)` entry per +//! active interaction, appended to the current column's flat Vec inside +//! [`LookupFractions`]. +//! +//! ## Runtime shape +//! +//! The caller: +//! +//! 1. Builds one [`Challenges`] once, outside the per-row loop. +//! 2. Allocates one [`LookupFractions`] once via [`LookupFractions::from_shape`], sized from +//! [`LookupAir::column_shape`]. Each column's internal Vec is `Vec::with_capacity(num_rows * +//! shape[col])` so pushes in the row loop never re-allocate. +//! 3. For each row pair, constructs a `ProverLookupBuilder` (cheap — just stores pointers), calls +//! `air.eval(&mut lb)`, then drops the builder. `column(f)` records how many fractions the row +//! pushed into `LookupFractions::counts_per_row[col]`, so the downstream accumulator can later +//! slice each row's contribution out via a running cursor without a separate offsets array. +//! +//! ## Flag-zero skip +//! +//! `add` / `remove` / `insert` short-circuit on `flag == F::ZERO`, +//! avoiding both the `msg.encode()` call and the Vec push when the +//! interaction is inactive. `batch(flag, build)` sets an `active` bit on +//! the child `ProverBatch` so individual pushes inside the batch skip +//! work together when the outer flag is zero. +//! +//! ## Encoded-group collapse +//! +//! Per the plan (§Prover-path adapter) the cached-encoding split is +//! collapsed on the prover side: [`LookupColumn::group`] and +//! [`LookupColumn::group_with_cached_encoding`] both open a +//! [`ProverGroup`] against the same column fraction `Vec`, and the +//! cached-encoding variant runs only the `canonical` closure (the +//! `encoded` closure is dropped unused, since the canonical description +//! is always the cheapest path for concrete rows). + +use alloc::{vec, vec::Vec}; +use core::borrow::Borrow; + +use miden_core::{ + field::{ExtensionField, Field}, + utils::{Matrix, RowMajorMatrix}, +}; +use miden_crypto::stark::air::RowWindow; + +use super::{ + Challenges, Deg, LookupAir, LookupBatch, LookupBuilder, LookupColumn, LookupFractions, + LookupGroup, LookupMessage, +}; + +// PROVER LOOKUP BUILDER +// ================================================================================================ + +/// Concrete-row `LookupBuilder` running on two rows of base-field values. +/// +/// See the module docs for the full runtime shape. Parameterised +/// by the base field `F` and the extension field `EF`; every `Expr` / +/// `Var` / `VarEF` etc. associated type collapses to `F` or `EF` +/// directly — there is no symbolic tree on the prover side. +/// +/// The [`Challenges`] table is **borrowed** from the caller: the +/// caller builds it once outside the row loop and passes a shared +/// reference into every `new` call, so per-row construction is O(1) with +/// no allocations. +pub struct ProverLookupBuilder<'a, F, EF> +where + F: Field, + EF: ExtensionField, +{ + main: RowWindow<'a, F>, + periodic_values: &'a [F], + challenges: &'a Challenges, + /// Dense per-column fraction buffers shared across all rows. Each + /// [`LookupBuilder::next_column`] call appends the current row's fractions to the end of + /// `fractions.fractions[column_idx]` and pushes the row's interaction count into + /// `fractions.counts_per_row[column_idx]`. The outer Vecs never move or re-allocate + /// as long as each row stays within the declared [`LookupAir::column_shape`] bound. + fractions: &'a mut LookupFractions, + column_idx: usize, +} + +impl<'a, F, EF> ProverLookupBuilder<'a, F, EF> +where + F: Field, + EF: ExtensionField, +{ + /// Create a new prover-path adapter for one row pair. + /// + /// - `main`: two-row window over the current and next base-field rows. + /// - `periodic_values`: periodic columns at the current row. + /// - `challenges`: precomputed LogUp challenges (shared across every row — the caller builds + /// this once outside the row loop and passes a shared reference here). + /// - `air`: the lookup shape (used only for a debug assertion that `fractions.num_columns() == + /// air.num_columns()`; the builder never calls `air.eval` itself — that's the caller's job). + /// - `fractions`: dense per-column fraction buffers, sized once via + /// [`LookupFractions::from_shape`] and re-used across every row of the same trace. + /// + /// # Panics + /// + /// Panics in debug builds if `fractions.num_columns() != air.num_columns()`. + pub fn new( + main: RowWindow<'a, F>, + periodic_values: &'a [F], + challenges: &'a Challenges, + air: &A, + fractions: &'a mut LookupFractions, + ) -> Self + where + A: LookupAir, + { + debug_assert_eq!( + fractions.num_columns(), + air.num_columns(), + "fractions buffer must be pre-sized to air.num_columns()", + ); + Self { + main, + periodic_values, + challenges, + fractions, + column_idx: 0, + } + } +} + +// BUILD LOOKUP FRACTIONS DRIVER +// ================================================================================================ + +/// Walk a complete main trace through [`ProverLookupBuilder`] and return the dense +/// [`LookupFractions`] buffer the collection phase produces. +/// +/// Generic over the base field `F` and extension field `EF`. The caller supplies the +/// main trace and periodic columns — this function does row slicing, periodic-column +/// indexing, and fraction collection. Concrete AIRs wrap this with their own +/// periodic-column layout. +/// +/// # Arguments +/// +/// - `air`: the [`LookupAir`] to evaluate. +/// - `main_trace`: row-major main execution trace. Row access is zero-copy via +/// `main_trace.values.borrow()`. +/// - `periodic_columns`: one `Vec` per periodic column, each with its own period. +/// - `challenges`: precomputed LogUp challenges (shared across every row). +/// +/// # Panics +/// +/// Panics in debug builds if any row pushes more fractions into a column than that +/// column's declared [`LookupAir::column_shape`] bound — this indicates the emitter's +/// `MAX_INTERACTIONS_PER_ROW` const is too low and needs to be bumped. +pub fn build_lookup_fractions( + air: &A, + main_trace: &RowMajorMatrix, + periodic_columns: &[Vec], + challenges: &Challenges, +) -> LookupFractions +where + F: Field, + EF: ExtensionField, + for<'a> A: LookupAir>, +{ + let num_rows = main_trace.height(); + let width = main_trace.width(); + let flat: &[F] = main_trace.values.borrow(); + + let shape = air.column_shape().to_vec(); + let mut fractions = LookupFractions::from_shape(shape, num_rows); + + // Per-row periodic slice, filled in place each row — no per-iteration allocation. + let mut periodic_row: Vec = vec![F::ZERO; periodic_columns.len()]; + + for r in 0..num_rows { + let curr = &flat[r * width..(r + 1) * width]; + let nxt_idx = (r + 1) % num_rows; + let next = &flat[nxt_idx * width..(nxt_idx + 1) * width]; + let window = RowWindow::from_two_rows(curr, next); + + for (i, col) in periodic_columns.iter().enumerate() { + periodic_row[i] = col[r % col.len()]; + } + + let mut lb = + ProverLookupBuilder::new(window, &periodic_row, challenges, air, &mut fractions); + air.eval(&mut lb); + } + + debug_assert_eq!( + fractions.counts().len(), + num_rows * fractions.num_columns(), + "counts buffer should have exactly num_rows * num_cols entries after collection", + ); + fractions +} + +impl<'a, F, EF> LookupBuilder for ProverLookupBuilder<'a, F, EF> +where + F: Field, + EF: ExtensionField, +{ + type F = F; + type Expr = F; + type Var = F; + + type EF = EF; + type ExprEF = EF; + type VarEF = EF; + + type PeriodicVar = F; + + type MainWindow = RowWindow<'a, F>; + + type Column<'c> + = ProverColumn<'c, F, EF> + where + Self: 'c; + + fn main(&self) -> Self::MainWindow { + self.main + } + + fn periodic_values(&self) -> &[Self::PeriodicVar] { + self.periodic_values + } + + fn next_column<'c, R>( + &'c mut self, + f: impl FnOnce(&mut Self::Column<'c>) -> R, + _deg: Deg, + ) -> R { + let idx = self.column_idx; + // Split-borrow disjoint fields of `LookupFractions` so the post-closure + // `counts.push(...)` isn't blocked by the GAT-lifetime borrow on `fractions`. + let vec = &mut self.fractions.fractions; + let counts = &mut self.fractions.counts; + let shape_col = self.fractions.shape[idx]; + let start_len = vec.len(); + + // Scope the `ProverColumn` so its borrow on `vec` ends before `counts.push`. + let (result, pushed) = { + let mut col = ProverColumn { + challenges: self.challenges, + fractions: vec, + }; + let result = f(&mut col); + (result, col.fractions.len() - start_len) + }; + debug_assert!( + pushed <= shape_col, + "column {idx} exceeded its shape bound: pushed {pushed}, shape says {shape_col}", + ); + counts.push(pushed); + self.column_idx += 1; + result + } +} + +// PROVER COLUMN +// ================================================================================================ + +/// Per-column handle returned by [`ProverLookupBuilder::next_column`]. +/// +/// Holds a mutable borrow of the column's per-row fraction `Vec`. Each +/// group opened inside the column reborrows the same `Vec` and pushes +/// fractions onto it — no intermediate `(N, D)` state, no +/// cross-denominator clearing. +pub struct ProverColumn<'c, F, EF> +where + F: Field, + EF: ExtensionField, +{ + challenges: &'c Challenges, + fractions: &'c mut Vec<(F, EF)>, +} + +impl<'c, F, EF> LookupColumn for ProverColumn<'c, F, EF> +where + F: Field, + EF: ExtensionField, +{ + type Expr = F; + type ExprEF = EF; + + type Group<'g> + = ProverGroup<'g, F, EF> + where + Self: 'g; + + fn group<'g>( + &'g mut self, + _name: &'static str, + f: impl FnOnce(&mut Self::Group<'g>), + _deg: Deg, + ) { + let mut group = ProverGroup { + challenges: self.challenges, + fractions: &mut *self.fractions, + }; + f(&mut group) + } + + fn group_with_cached_encoding<'g>( + &'g mut self, + name: &'static str, + canonical: impl FnOnce(&mut Self::Group<'g>), + _encoded: impl FnOnce(&mut Self::Group<'g>), + deg: Deg, + ) { + // Prover path runs only the `canonical` closure; both closures must describe + // identical interaction sets, so the `encoded` fast path has no advantage on + // concrete rows and is discarded. + self.group(name, canonical, deg); + } +} + +// PROVER GROUP +// ================================================================================================ + +/// Per-group handle used for both the simple and cached-encoding paths +/// on the prover side. +/// +/// Pushes individual `(multiplicity, denominator)` fractions onto the +/// column's per-row `Vec`. No `(N, D)` state, no cross-denominator +/// clearing — LogUp's aux-trace builder consumes individual fractions +/// downstream. +/// +/// ## Boolean-flag convention +/// +/// The `flag` parameter on `add` / `remove` / `insert` is treated as a +/// 0/1 boolean selector: if `flag == F::ZERO` the interaction is +/// skipped entirely (no encode, no push); otherwise the push happens +/// with the canonical multiplicity (`+1` for `add`, `-1` for `remove`, +/// `multiplicity` for `insert`). This matches the constraint path's +/// `(U_g, V_g)` algebra, which silently assumes flag is 0 or 1 — +/// non-boolean flags produce wrong results on both sides. +/// +/// ## Encoded-group methods +/// +/// The encoding primitives (`beta_powers`, `bus_prefix`, `insert_encoded`) +/// use the default panicking implementations from [`LookupGroup`] — the +/// prover path always runs the `canonical` closure, never the `encoded` +/// one, so these methods should never be reached. +pub struct ProverGroup<'g, F, EF> +where + F: Field, + EF: ExtensionField, +{ + challenges: &'g Challenges, + fractions: &'g mut Vec<(F, EF)>, +} + +impl<'g, F, EF> LookupGroup for ProverGroup<'g, F, EF> +where + F: Field, + EF: ExtensionField, +{ + type Expr = F; + type ExprEF = EF; + + type Batch<'b> + = ProverBatch<'b, F, EF> + where + Self: 'b; + + fn insert( + &mut self, + _name: &'static str, + flag: F, + multiplicity: F, + msg: impl FnOnce() -> M, + _deg: Deg, + ) where + M: LookupMessage, + { + if flag == F::ZERO { + return; + } + let v = msg().encode(self.challenges); + self.fractions.push((multiplicity, v)); + } + + fn batch<'b>( + &'b mut self, + _name: &'static str, + flag: F, + build: impl FnOnce(&mut Self::Batch<'b>), + _deg: Deg, + ) { + // When `active == false` every push inside the batch is a no-op — the + // `msg.encode()` call is skipped too. The `build` closure still runs so + // it can produce its `R` return value without requiring `R: Default`. + let active = flag != F::ZERO; + let mut batch = ProverBatch { + challenges: self.challenges, + fractions: &mut *self.fractions, + active, + }; + build(&mut batch) + } +} + +// PROVER BATCH +// ================================================================================================ + +/// Transient handle returned by [`LookupGroup::batch`] on the prover path. +/// +/// Holds the same mutable borrow of the column's fraction `Vec` as the +/// enclosing [`ProverGroup`], plus an `active` flag copied from the +/// outer `batch(flag, …)` call. When `active == false` every push is a +/// no-op — the `msg.encode()` call is skipped too, so inactive batches +/// do essentially no work. +/// +/// Each push appends one fraction entry when active. There's no `(N, D)` state +/// inside the batch — LogUp's aux-trace builder handles the combination downstream. +pub struct ProverBatch<'b, F, EF> +where + F: Field, + EF: ExtensionField, +{ + challenges: &'b Challenges, + fractions: &'b mut Vec<(F, EF)>, + active: bool, +} + +impl<'b, F, EF> LookupBatch for ProverBatch<'b, F, EF> +where + F: Field, + EF: ExtensionField, +{ + type Expr = F; + type ExprEF = EF; + + fn insert(&mut self, _name: &'static str, multiplicity: F, msg: M, _deg: Deg) + where + M: LookupMessage, + { + if !self.active { + return; + } + let v = msg.encode(self.challenges); + self.fractions.push((multiplicity, v)); + } + + fn insert_encoded( + &mut self, + _name: &'static str, + multiplicity: F, + encoded: impl FnOnce() -> EF, + _deg: Deg, + ) { + if !self.active { + return; + } + let v = encoded(); + self.fractions.push((multiplicity, v)); + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + extern crate std; + + use std::{vec, vec::Vec}; + + use miden_core::field::{PrimeCharacteristicRing, QuadFelt}; + use miden_crypto::stark::air::RowWindow; + + use super::*; + use crate::{ + Felt, + lookup::{Deg, LookupAir, accumulate_slow, message::LookupMessage}, + }; + + /// Minimal `LookupMessage` used by [`SmokeAir`] to drive a `Vec::push` into the + /// prover builder's fraction buffer. Encodes to `bus_prefix[0] + β⁰·value`, which is + /// always non-zero for non-trivial challenges (so `accumulate_slow` can `try_inverse` + /// without blowing up). + #[derive(Clone, Copy, Debug)] + struct SmokeMsg { + value: Felt, + } + + impl LookupMessage for SmokeMsg { + fn encode(&self, challenges: &Challenges) -> QuadFelt { + challenges.bus_prefix[0] + challenges.beta_powers[0] * self.value + } + } + + /// Two-column stand-in for the real Miden lookup AIR, with a handcrafted `eval` body + /// that respects its own shape on **every** row — no mutual-exclusion assumptions, so + /// random (non-trace) input data drives it without tripping the shape debug_assert. + /// + /// - Column 0 always pushes 2 fractions (one `add`, one `remove`) with shape 2. + /// - Column 1 pushes 1 fraction via an inside-batch `insert` with shape 1. + struct SmokeAir; + + const SMOKE_SHAPE: [usize; 2] = [2, 1]; + + impl LookupAir for SmokeAir + where + LB: LookupBuilder, + { + fn num_columns(&self) -> usize { + 2 + } + fn column_shape(&self) -> &[usize] { + &SMOKE_SHAPE + } + fn max_message_width(&self) -> usize { + 1 + } + fn num_bus_ids(&self) -> usize { + 1 + } + fn eval(&self, builder: &mut LB) { + builder.next_column( + |col| { + col.group( + "smoke_grp_0", + |g| { + g.add( + "smoke_add", + Felt::ONE, + || SmokeMsg { value: Felt::ONE }, + Deg { n: 0, d: 0 }, + ); + g.remove( + "smoke_remove", + Felt::ONE, + || SmokeMsg { value: Felt::new_unchecked(2) }, + Deg { n: 0, d: 0 }, + ); + }, + Deg { n: 0, d: 0 }, + ); + }, + Deg { n: 0, d: 0 }, + ); + builder.next_column( + |col| { + col.group( + "smoke_grp_1", + |g| { + g.batch( + "smoke_batch", + Felt::ONE, + |b| { + b.insert( + "smoke_batch_insert", + Felt::ONE, + SmokeMsg { value: Felt::new_unchecked(3) }, + Deg { n: 0, d: 0 }, + ); + }, + Deg { n: 0, d: 0 }, + ); + }, + Deg { n: 0, d: 0 }, + ); + }, + Deg { n: 0, d: 0 }, + ); + } + } + + /// End-to-end collection sanity check: run `SmokeAir::eval` through + /// `ProverLookupBuilder` over several rows and verify the per-column counts match the + /// handcrafted `eval` body, that `accumulate_slow` produces a `num_rows + 1`-long + /// output per column starting at zero, and that the running sum monotonically grows + /// by the expected amount each row. + #[test] + fn prover_lookup_builder_collects_into_fractions() { + const NUM_ROWS: usize = 8; + + let air = SmokeAir; + + // Any reasonable non-zero challenges — SmokeMsg encodes to `bus_prefix[0] + v` + // which is non-zero as long as the challenges are. + let alpha = QuadFelt::new([Felt::new_unchecked(7), Felt::new_unchecked(11)]); + let beta = QuadFelt::new([Felt::new_unchecked(13), Felt::new_unchecked(17)]); + // SmokeAir hard-codes `max_message_width = 1` / `num_bus_ids = 1` in its + // `LookupAir` impl — the trait-method path can't be called directly because + // `LookupAir` is generic over `LB` and disambiguation fails at a value call. + let challenges = Challenges::::new(alpha, beta, 1, 1); + + // `SmokeAir::eval` never touches the main trace, periodic columns, or public + // values — pass dummy zero-length slices. + let empty_row: Vec = vec![]; + let periodic_values: Vec = vec![]; + + let shape = + >>::column_shape(&air) + .to_vec(); + let mut fractions = LookupFractions::::from_shape(shape, NUM_ROWS); + + for _row in 0..NUM_ROWS { + let window = RowWindow::from_two_rows(&empty_row, &empty_row); + let mut lb = ProverLookupBuilder::new( + window, + &periodic_values, + &challenges, + &air, + &mut fractions, + ); + air.eval(&mut lb); + } + + // Two columns, counts buffer has num_rows * num_cols entries. + assert_eq!(fractions.num_columns(), 2); + assert_eq!(fractions.shape(), &SMOKE_SHAPE); + assert_eq!(fractions.counts().len(), NUM_ROWS * 2); + + // Per-row counts: column 0 pushes 2, column 1 pushes 1. Total = 3 per row. + for row_counts in fractions.counts().chunks(2) { + assert_eq!(row_counts, &[2, 1]); + } + assert_eq!(fractions.fractions().len(), 3 * NUM_ROWS); + + // Every pushed fraction has a non-zero denominator and the expected + // multiplicity pattern. Within each row the builder writes col 0 (add 1, + // remove 1) then col 1 (insert 1 with multiplicity 1), so the flat order is + // [(+1, d1), (-1, d2), (+1, d3)] repeated NUM_ROWS times. + for (i, (m, d)) in fractions.fractions().iter().enumerate() { + assert_ne!(*d, QuadFelt::ZERO); + let expected_m = match i % 3 { + 0 => Felt::ONE, + 1 => Felt::NEG_ONE, + _ => Felt::ONE, + }; + assert_eq!(*m, expected_m); + } + + let aux = accumulate_slow(&fractions); + assert_eq!(aux.len(), 2); + for col_aux in &aux { + assert_eq!(col_aux.len(), NUM_ROWS + 1); + } + assert_eq!(aux[0][0], QuadFelt::ZERO, "accumulator initial must be zero"); + + let d1 = SmokeMsg { value: Felt::ONE }.encode(&challenges); + let d2 = SmokeMsg { value: Felt::new_unchecked(2) }.encode(&challenges); + let d3 = SmokeMsg { value: Felt::new_unchecked(3) }.encode(&challenges); + let delta0 = d1.try_inverse().unwrap() - d2.try_inverse().unwrap(); + let delta1 = d3.try_inverse().unwrap(); + // Column 0 (accumulator): each row delta = own fraction + col 1's fraction. + for r in 0..NUM_ROWS { + assert_eq!(aux[0][r + 1] - aux[0][r], delta0 + delta1); + } + // Column 1 (fraction, aux_curr): value at row r is the per-row fraction. + for &entry in aux[1].iter().take(NUM_ROWS) { + assert_eq!(entry, delta1); + } + } +} diff --git a/air/src/snapshots/miden_air__config__tests__relation_digest_matches_current_air.snap b/air/src/snapshots/miden_air__config__tests__relation_digest_matches_current_air.snap index 29767591a9..053b1a8514 100644 --- a/air/src/snapshots/miden_air__config__tests__relation_digest_matches_current_air.snap +++ b/air/src/snapshots/miden_air__config__tests__relation_digest_matches_current_air.snap @@ -1,7 +1,8 @@ --- source: air/src/config.rs +assertion_line: 335 expression: snapshot --- -num_inputs: 568 -num_eval_gates: 5540 -relation_digest: [3886624411320157031, 5903371486919752653, 170319297396068280, 5221005507035467697] +num_inputs: 562 +num_eval_gates: 5580 +relation_digest: [9778406675021743003, 1249738292041492479, 18129162047168657414, 9359130512581117656] diff --git a/air/src/trace/aux_trace.rs b/air/src/trace/aux_trace.rs deleted file mode 100644 index 25fa5d6517..0000000000 --- a/air/src/trace/aux_trace.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Auxiliary trace builder trait for dependency inversion. -//! -//! This trait allows ProcessorAir to build auxiliary traces without depending -//! on the processor crate, avoiding circular dependencies. - -use miden_core::utils::RowMajorMatrix; - -use crate::Felt; - -/// Trait for building auxiliary traces from main trace and challenges. -/// -/// # Why This Trait Exists -/// -/// This trait serves to avoid circular dependencies: -/// - `ProcessorAir` (in this crate) needs to build auxiliary traces during proving -/// - The actual aux building logic lives in the `processor` crate -/// - But `processor` already depends on `air` for trace types and constraints -/// - Direct coupling would create: `air` → `processor` → `air` -/// -/// The trait breaks the cycle: -/// - `air` defines the interface (this trait) -/// - `processor` implements the interface (concrete aux builders) -/// - `prover` injects the implementation: `ProcessorAir::with_aux_builder(impl)` -/// -/// The trait works with row-major matrices (i.e., Plonky3 format). -pub trait AuxTraceBuilder: Send + Sync { - /// Builds auxiliary trace in row-major format from the main trace. - /// - /// Takes the main trace in row-major format (as provided by Plonky3) and - /// returns the auxiliary trace also in row-major format. - fn build_aux_columns( - &self, - main_trace: &RowMajorMatrix, - challenges: &[EF], - ) -> RowMajorMatrix; -} - -/// Dummy implementation for () to support ProcessorAir without aux trace builders (e.g., in -/// verifier). This implementation should never be called since ProcessorAir::build_aux_trace -/// returns None when aux_builder is None. -impl AuxTraceBuilder for () { - fn build_aux_columns( - &self, - _main_trace: &RowMajorMatrix, - _challenges: &[EF], - ) -> RowMajorMatrix { - panic!("No aux trace builder configured - this should never be called") - } -} diff --git a/air/src/trace/chiplets/ace.rs b/air/src/trace/chiplets/ace.rs index 11f61e5868..858ade61ee 100644 --- a/air/src/trace/chiplets/ace.rs +++ b/air/src/trace/chiplets/ace.rs @@ -2,10 +2,6 @@ use crate::trace::chiplets::Felt; // --- CONSTANTS ---------------------------------------------------------------------------------- -/// Unique label ACE operation, computed as the chiplet selector with the bits reversed, plus one. -/// `selector = [1, 1, 1, 0]`, `flag = rev(selector) + 1 = [0, 1, 1, 1] + 1 = 8` -pub const ACE_INIT_LABEL: Felt = Felt::new_unchecked(0b0111 + 1); - /// Total number of columns making up the ACE chiplet. pub const ACE_CHIPLET_NUM_COLS: usize = 16; diff --git a/air/src/trace/chiplets/bitwise.rs b/air/src/trace/chiplets/bitwise.rs index 1abfc50d0f..4dd2dc16c8 100644 --- a/air/src/trace/chiplets/bitwise.rs +++ b/air/src/trace/chiplets/bitwise.rs @@ -16,15 +16,9 @@ pub const OP_CYCLE_LEN: usize = 8; /// Specifies a bitwise AND operation. pub const BITWISE_AND: Felt = ZERO; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// `selector = [1, 0 | 0]`, `flag = rev(selector) + 1 = [0 | 0, 1] + 1 = 2` -pub const BITWISE_AND_LABEL: Felt = Felt::new_unchecked(0b001 + 1); /// Specifies a bitwise XOR operation. pub const BITWISE_XOR: Felt = ONE; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// `selector = [1, 0 | 1]`, `flag = rev(selector) + 1 = [1 | 0, 1] + 1 = 6` -pub const BITWISE_XOR_LABEL: Felt = Felt::new_unchecked(0b101 + 1); // --- INPUT DECOMPOSITION ------------------------------------------------------------------------ diff --git a/air/src/trace/chiplets/hasher.rs b/air/src/trace/chiplets/hasher.rs index fd0101e0a9..e6136603cd 100644 --- a/air/src/trace/chiplets/hasher.rs +++ b/air/src/trace/chiplets/hasher.rs @@ -15,7 +15,7 @@ use core::ops::Range; pub use miden_core::{Word, crypto::hash::Poseidon2 as Hasher}; -use super::{Felt, HASH_KERNEL_VTABLE_AUX_TRACE_OFFSET, ONE, ZERO, create_range}; +use super::{Felt, ONE, ZERO, create_range}; // TYPES ALIASES // ================================================================================================ @@ -131,49 +131,24 @@ pub const CONTROLLER_ROWS_PER_PERM_FELT: Felt = /// executing linear hash computation. These selectors can also be used for a simple 2-to-1 hash /// computation. pub const LINEAR_HASH: Selectors = [ONE, ZERO, ZERO]; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// `selector = [0 | 1, 0, 0]`, `flag = rev(selector) + 1 = [0, 0, 1 | 0] + 1 = 3` -pub const LINEAR_HASH_LABEL: u8 = 0b0010 + 1; - /// Specifies a start of Merkle path verification computation or absorption of a new path node /// into the hasher state. pub const MP_VERIFY: Selectors = [ONE, ZERO, ONE]; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// `selector = [0 | 1, 0, 1]`, `flag = rev(selector) + 1 = [1, 0, 1 | 0] + 1 = 11` -pub const MP_VERIFY_LABEL: u8 = 0b1010 + 1; /// Specifies a start of Merkle path verification or absorption of a new path node into the hasher /// state for the "old" node value during Merkle root update computation. pub const MR_UPDATE_OLD: Selectors = [ONE, ONE, ZERO]; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// `selector = [0 | 1, 1, 0]`, `flag = rev(selector) + 1 = [0, 1, 1 | 0] + 1 = 7` -pub const MR_UPDATE_OLD_LABEL: u8 = 0b0110 + 1; /// Specifies a start of Merkle path verification or absorption of a new path node into the hasher /// state for the "new" node value during Merkle root update computation. pub const MR_UPDATE_NEW: Selectors = [ONE, ONE, ONE]; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// `selector = [0 | 1, 1, 1]`, `flag = rev(selector) + 1 = [1, 1, 1 | 0] + 1 = 15` -pub const MR_UPDATE_NEW_LABEL: u8 = 0b1110 + 1; /// Specifies a completion of a computation such that only the hash result (values in h0, h1, h2 /// h3) is returned. pub const RETURN_HASH: Selectors = [ZERO, ZERO, ZERO]; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// `selector = [0 | 0, 0, 0]`, `flag = rev(selector) + 1 = [0, 0, 0 | 0] + 1 = 1` -#[expect(clippy::identity_op)] -pub const RETURN_HASH_LABEL: u8 = 0b0000 + 1; /// Specifies a completion of a computation such that the entire hasher state (values in h0 through /// h11) is returned. pub const RETURN_STATE: Selectors = [ZERO, ZERO, ONE]; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// `selector = [0 | 0, 0, 1]`, `flag = rev(selector) + 1 = [1, 0, 0 | 0] + 1 = 9` -pub const RETURN_STATE_LABEL: u8 = 0b1000 + 1; // NOTE: Selectors s0/s1/s2 are unconstrained on perm segment rows. - -// --- Column accessors in the auxiliary trace ---------------------------------------------------- - -/// Index of the auxiliary trace column tracking the state of the sibling table. -pub const P1_COL_IDX: usize = HASH_KERNEL_VTABLE_AUX_TRACE_OFFSET; diff --git a/air/src/trace/chiplets/kernel_rom.rs b/air/src/trace/chiplets/kernel_rom.rs deleted file mode 100644 index 4efa74e978..0000000000 --- a/air/src/trace/chiplets/kernel_rom.rs +++ /dev/null @@ -1,32 +0,0 @@ -use super::Felt; - -// CONSTANTS -// ================================================================================================ - -/// Number of columns needed to record an execution trace of the kernel ROM chiplet. -pub const TRACE_WIDTH: usize = 5; - -// --- OPERATION SELECTORS ------------------------------------------------------------------------ - -// All kernel ROM bus labels encode the chiplet selector [1, 1, 1, 1, 0], appended with the internal -// selector `s_first` which indicates whether the chiplet should respond to an `init` or `call` -// request. The value of the flag is derived following the usual convention, i.e., -// adding one to the big-endian representation of the full selector. - -/// Specifies a kernel procedure call operation to access a procedure in the kernel ROM. -/// -/// The label is constructed as follows: -/// - Chiplet selector: [1, 1, 1, 1, 0] -/// - s_first value: 0 -/// - Combined selector: [1, 1, 1, 1, 0 | 0] -/// - Reverse bits and add 1 to get final label value: [0 | 0, 1, 1, 1, 1] + 1 = 16 -pub const KERNEL_PROC_CALL_LABEL: Felt = Felt::new_unchecked(0b001111 + 1); - -/// Specified the label of the kernel ROM initialization request by the verifier. -/// -/// The label is constructed as follows: -/// - Chiplet selector: [1, 1, 1, 1, 0] -/// - s_first value: 1 -/// - Combined selector: [1, 1, 1, 1, 0 | 1] -/// - Reverse bits and add 1 to get final label value: [1 | 0, 1, 1, 1, 1] + 1 = 48 -pub const KERNEL_PROC_INIT_LABEL: Felt = Felt::new_unchecked(0b101111 + 1); diff --git a/air/src/trace/chiplets/memory.rs b/air/src/trace/chiplets/memory.rs index c66ca75190..9bc7330d78 100644 --- a/air/src/trace/chiplets/memory.rs +++ b/air/src/trace/chiplets/memory.rs @@ -19,33 +19,6 @@ pub const MEMORY_ACCESS_ELEMENT: Felt = ZERO; /// Specifies the value of the `ELEMENT_OR_WORD` column when the operation is over a word. pub const MEMORY_ACCESS_WORD: Felt = ONE; -// --- BUS LABELS ------------------------------------------------------------------------ - -// All bus labels encode the chiplet selector (1, 1, 0), as well as the read/write and element/word -// columns. The purpose of the label is to force the chiplet to assign the correct values to the -// read/write and element/word columns. We also include the chiplet selector as a unique identifier -// for memory chiplet labels (to ensure they don't collide with labels from other chiplets). - -/// Unique label when r/w=0 and e/w=0, computed as the full chiplet selector with the bits reversed, -/// plus one. -/// `selector = [1, 1, 0 | 0, 0]`, `flag = rev(selector) + 1 = [0, 0 | 0, 1, 1] + 1 = 4` -pub const MEMORY_WRITE_ELEMENT_LABEL: u8 = 0b00011 + 1; - -/// Unique label when r/w=0 and e/w=1, computed as the full chiplet selector with the bits reversed, -/// plus one. -/// `selector = [1, 1, 0 | 0, 1]`, `flag = rev(selector) + 1 = [1, 0 | 0, 1, 1] + 1 = 20` -pub const MEMORY_WRITE_WORD_LABEL: u8 = 0b10011 + 1; - -/// Unique label when r/w=1 and e/w=0, computed as the full chiplet selector with the bits reversed, -/// plus one. -/// `selector = [1, 1, 0 | 1, 0]`, `flag = rev(selector) + 1 = [0, 1 | 0, 1, 1] + 1 = 12` -pub const MEMORY_READ_ELEMENT_LABEL: u8 = 0b01011 + 1; - -/// Unique label when r/w=1 and e/w=1, computed as the full chiplet selector with the bits reversed, -/// plus one. -/// `selector = [1, 1, 0 | 1, 1]`, `flag = rev(selector) + 1 = [1, 1 | 0, 1, 1] + 1 = 28` -pub const MEMORY_READ_WORD_LABEL: u8 = 0b11011 + 1; - // --- COLUMN ACCESSOR INDICES WITHIN THE CHIPLET ------------------------------------------------- /// Column to hold whether the operation is a read or write. diff --git a/air/src/trace/chiplets/mod.rs b/air/src/trace/chiplets/mod.rs index c25a236d21..4628b8cffc 100644 --- a/air/src/trace/chiplets/mod.rs +++ b/air/src/trace/chiplets/mod.rs @@ -2,12 +2,11 @@ use core::ops::Range; use miden_core::{Felt, ONE, ZERO, utils::range as create_range}; -use super::{CHIPLETS_OFFSET, HASH_KERNEL_VTABLE_AUX_TRACE_OFFSET}; +use super::CHIPLETS_OFFSET; pub mod ace; pub mod bitwise; pub mod hasher; -pub mod kernel_rom; pub mod memory; // CONSTANTS // ================================================================================================ @@ -23,6 +22,9 @@ pub const NUM_ACE_SELECTORS: usize = 4; /// The number of columns in the chiplets which are used as selectors for the kernel ROM chiplet. pub const NUM_KERNEL_ROM_SELECTORS: usize = 5; +/// Number of columns needed to record an execution trace of the kernel ROM chiplet. +pub const KERNEL_ROM_TRACE_WIDTH: usize = 5; + /// The first column of the hash chiplet. pub const HASHER_TRACE_OFFSET: usize = CHIPLETS_OFFSET + NUM_HASHER_SELECTORS; /// The first column of the bitwise chiplet. diff --git a/air/src/trace/decoder/mod.rs b/air/src/trace/decoder/mod.rs index 8ee0d3d54a..863aaa438e 100644 --- a/air/src/trace/decoder/mod.rs +++ b/air/src/trace/decoder/mod.rs @@ -2,8 +2,6 @@ use core::ops::Range; use miden_core::{Felt, ONE, ZERO, operations::Operation, utils::range}; -use super::DECODER_AUX_TRACE_OFFSET; - // CONSTANTS // ================================================================================================ @@ -90,17 +88,6 @@ pub const IS_CALL_FLAG_COL_IDX: usize = HASHER_STATE_RANGE.start + 6; /// Index of a flag column which indicates whether an ending block is a SYSCALL block. pub const IS_SYSCALL_FLAG_COL_IDX: usize = HASHER_STATE_RANGE.start + 7; -// --- Column accessors in the auxiliary columns -------------------------------------------------- - -/// Running product column representing block stack table. -pub const P1_COL_IDX: usize = DECODER_AUX_TRACE_OFFSET; - -/// Running product column representing block hash table -pub const P2_COL_IDX: usize = DECODER_AUX_TRACE_OFFSET + 1; - -/// Running product column representing op group table. -pub const P3_COL_IDX: usize = DECODER_AUX_TRACE_OFFSET + 2; - // --- GLOBALLY-INDEXED DECODER COLUMN ACCESSORS -------------------------------------------------- pub const DECODER_OP_BITS_OFFSET: usize = super::DECODER_TRACE_OFFSET + OP_BITS_OFFSET; pub const DECODER_USER_OP_HELPERS_OFFSET: usize = diff --git a/air/src/trace/mod.rs b/air/src/trace/mod.rs index 9845240e6d..7aa4c37ba7 100644 --- a/air/src/trace/mod.rs +++ b/air/src/trace/mod.rs @@ -3,9 +3,6 @@ use core::ops::Range; use chiplets::hasher::RATE_LEN; use miden_core::utils::range; -mod challenges; -pub use challenges::Challenges; - pub mod chiplets; pub mod decoder; pub mod range; @@ -50,9 +47,6 @@ pub const STACK_TRACE_OFFSET: usize = DECODER_TRACE_RANGE.end; pub const STACK_TRACE_WIDTH: usize = 19; pub const STACK_TRACE_RANGE: Range = range(STACK_TRACE_OFFSET, STACK_TRACE_WIDTH); -/// Label for log_precompile transcript state messages on the virtual table bus. -pub const LOG_PRECOMPILE_LABEL: u8 = miden_core::operations::opcodes::LOGPRECOMPILE; - pub mod log_precompile { use core::ops::Range; @@ -130,66 +124,26 @@ pub const PADDED_TRACE_WIDTH: usize = TRACE_WIDTH.next_multiple_of(RATE_LEN); // AUXILIARY COLUMNS LAYOUT // ------------------------------------------------------------------------------------------------ +// +// The auxiliary trace is the LogUp lookup-argument segment built by +// [`crate::ProcessorAir`]'s `AuxBuilder` impl. It has 7 columns: 4 main-trace LogUp +// columns followed by 3 chiplet-trace LogUp columns. See +// [`crate::constraints::lookup::main_air::MainLookupAir`] and +// [`crate::constraints::lookup::chiplet_air::ChipletLookupAir`] for the per-column +// contents. -// decoder stack range checks chiplets -// (3 columns) (1 column) (1 column) (3 column) -// ├─────────────────────┴──────────────────────┴────────────────────┴───────────────────┤ - -/// Decoder auxiliary columns -pub const DECODER_AUX_TRACE_OFFSET: usize = 0; -pub const DECODER_AUX_TRACE_WIDTH: usize = 3; -pub const DECODER_AUX_TRACE_RANGE: Range = - range(DECODER_AUX_TRACE_OFFSET, DECODER_AUX_TRACE_WIDTH); - -/// Stack auxiliary columns -pub const STACK_AUX_TRACE_OFFSET: usize = DECODER_AUX_TRACE_RANGE.end; -pub const STACK_AUX_TRACE_WIDTH: usize = 1; -pub const STACK_AUX_TRACE_RANGE: Range = - range(STACK_AUX_TRACE_OFFSET, STACK_AUX_TRACE_WIDTH); - -/// Range check auxiliary columns -pub const RANGE_CHECK_AUX_TRACE_OFFSET: usize = STACK_AUX_TRACE_RANGE.end; -pub const RANGE_CHECK_AUX_TRACE_WIDTH: usize = 1; -pub const RANGE_CHECK_AUX_TRACE_RANGE: Range = - range(RANGE_CHECK_AUX_TRACE_OFFSET, RANGE_CHECK_AUX_TRACE_WIDTH); - -/// Chiplets virtual table auxiliary column. -/// -/// This column combines two virtual tables: -/// -/// 1. Hash chiplet's sibling table, -/// 2. Kernel ROM chiplet's kernel procedure table. -pub const HASH_KERNEL_VTABLE_AUX_TRACE_OFFSET: usize = RANGE_CHECK_AUX_TRACE_RANGE.end; -pub const HASHER_AUX_TRACE_WIDTH: usize = 1; -pub const HASHER_AUX_TRACE_RANGE: Range = - range(HASH_KERNEL_VTABLE_AUX_TRACE_OFFSET, HASHER_AUX_TRACE_WIDTH); - -/// Chiplets bus auxiliary columns. -pub const CHIPLETS_BUS_AUX_TRACE_OFFSET: usize = HASHER_AUX_TRACE_RANGE.end; -pub const CHIPLETS_BUS_AUX_TRACE_WIDTH: usize = 1; -pub const CHIPLETS_BUS_AUX_TRACE_RANGE: Range = - range(CHIPLETS_BUS_AUX_TRACE_OFFSET, CHIPLETS_BUS_AUX_TRACE_WIDTH); - -/// ACE chiplet wiring bus. -pub const ACE_CHIPLET_WIRING_BUS_OFFSET: usize = CHIPLETS_BUS_AUX_TRACE_RANGE.end; -pub const ACE_CHIPLET_WIRING_BUS_WIDTH: usize = 1; -pub const ACE_CHIPLET_WIRING_BUS_RANGE: Range = - range(ACE_CHIPLET_WIRING_BUS_OFFSET, ACE_CHIPLET_WIRING_BUS_WIDTH); - -/// Auxiliary trace segment width. -pub const AUX_TRACE_WIDTH: usize = ACE_CHIPLET_WIRING_BUS_RANGE.end; +/// Auxiliary trace segment width — see the LogUp aux trace layout above. +pub const AUX_TRACE_WIDTH: usize = crate::LOGUP_AUX_TRACE_WIDTH; /// Number of random challenges used for auxiliary trace constraints. pub const AUX_TRACE_RAND_CHALLENGES: usize = 2; -/// Maximum number of coefficients used in bus message encodings. -pub const MAX_MESSAGE_WIDTH: usize = 16; - /// Bus message coefficient indices. /// /// These define the standard positions for encoding bus messages using the pattern: /// `bus_prefix[bus] + sum(beta_powers\[i\] * elem\[i\])` where: -/// - `bus_prefix[bus]` is the per-bus domain-separated base (see [`bus_types`]) +/// - `bus_prefix[bus]` is the per-bus domain-separated base (see `BusId` in +/// `constraints::lookup::logup_msg`) /// - `beta_powers\[i\] = beta^i` are the powers of beta /// /// These indices refer to positions in the `beta_powers` array, not including the bus prefix. @@ -232,37 +186,3 @@ pub mod bus_message { /// block messages). pub const CAPACITY_DOMAIN_IDX: usize = CAPACITY_START_IDX + 1; } - -/// Bus interaction type constants for domain separation. -/// -/// Each constant identifies a distinct bus interaction type. When encoding a message, -/// the bus index is passed to [`Challenges::encode`] or [`Challenges::encode_sparse`], -/// which uses `bus_prefix[bus]` as the additive base instead of bare `alpha`. -/// -/// This ensures messages from different buses are always distinct, even if they share -/// the same coefficient layout and labels. This is a prerequisite for a future unified bus. -pub mod bus_types { - /// All chiplet interactions: hasher, bitwise, memory, ACE, kernel ROM. - pub const CHIPLETS_BUS: usize = 0; - /// Block stack table (decoder p1): tracks control flow block nesting. - pub const BLOCK_STACK_TABLE: usize = 1; - /// Block hash table (decoder p2): tracks block digest computation. - pub const BLOCK_HASH_TABLE: usize = 2; - /// Op group table (decoder p3): tracks operation batch consumption. - pub const OP_GROUP_TABLE: usize = 3; - /// Stack overflow table. - pub const STACK_OVERFLOW_TABLE: usize = 4; - /// Sibling table: shares Merkle tree sibling nodes between old/new root computations. - pub const SIBLING_TABLE: usize = 5; - /// Log-precompile transcript: tracks capacity state transitions for LOGPRECOMPILE. - pub const LOG_PRECOMPILE_TRANSCRIPT: usize = 6; - /// Range checker bus (LogUp): verifies values are in the valid range. - pub const RANGE_CHECK_BUS: usize = 7; - /// ACE wiring bus (LogUp): verifies arithmetic circuit wire connections. - pub const ACE_WIRING_BUS: usize = 8; - /// Hasher perm-link bus: links hasher controller rows to permutation segment rows on - /// `v_wiring`. - pub const HASHER_PERM_LINK: usize = 9; - /// Total number of distinct bus interaction types. - pub const NUM_BUS_TYPES: usize = 10; -} diff --git a/air/src/trace/range.rs b/air/src/trace/range.rs index 3081bbe3a8..2181164518 100644 --- a/air/src/trace/range.rs +++ b/air/src/trace/range.rs @@ -1,4 +1,4 @@ -use super::{RANGE_CHECK_AUX_TRACE_OFFSET, RANGE_CHECK_TRACE_OFFSET}; +use super::RANGE_CHECK_TRACE_OFFSET; // CONSTANTS // ================================================================================================ @@ -9,9 +9,3 @@ use super::{RANGE_CHECK_AUX_TRACE_OFFSET, RANGE_CHECK_TRACE_OFFSET}; pub const M_COL_IDX: usize = RANGE_CHECK_TRACE_OFFSET; /// A column to hold the values being range-checked. pub const V_COL_IDX: usize = RANGE_CHECK_TRACE_OFFSET + 1; - -// --- Column accessors in the auxiliary columns -------------------------------------------------- - -/// The running product column used for verifying that the range check lookups performed in the -/// Stack and the Memory chiplet match the values checked in the Range Checker. -pub const B_RANGE_COL_IDX: usize = RANGE_CHECK_AUX_TRACE_OFFSET; diff --git a/crates/ace-codegen/src/layout/plan.rs b/crates/ace-codegen/src/layout/plan.rs index 68e205f9fb..b3db7ff347 100644 --- a/crates/ace-codegen/src/layout/plan.rs +++ b/crates/ace-codegen/src/layout/plan.rs @@ -22,6 +22,8 @@ pub struct InputCounts { pub width: usize, /// Width of the aux trace. pub aux_width: usize, + /// Number of committed boundary values (accumulator column finals). + pub num_aux_boundary: usize, /// Number of public inputs. pub num_public: usize, /// Number of variable-length public input (VLPI) reduction slots (in EF elements). @@ -166,7 +168,7 @@ impl InputLayout { "quotient_next width mismatch" ); assert_eq!( - self.regions.aux_bus_boundary.width, self.counts.aux_width, + self.regions.aux_bus_boundary.width, self.counts.num_aux_boundary, "aux bus boundary width mismatch" ); diff --git a/crates/ace-codegen/src/layout/policy.rs b/crates/ace-codegen/src/layout/policy.rs index c9df275c7c..3f6f3efae2 100644 --- a/crates/ace-codegen/src/layout/policy.rs +++ b/crates/ace-codegen/src/layout/policy.rs @@ -108,7 +108,7 @@ impl InputLayout { let main_next = builder.alloc(counts.width, policy.main); let aux_next = builder.alloc(aux_coord_width, policy.aux); let quotient_next = builder.alloc(counts.num_quotient_chunks * EXT_DEGREE, policy.quotient); - let aux_bus_boundary = builder.alloc(counts.aux_width, policy.aux_bus_boundary); + let aux_bus_boundary = builder.alloc(counts.num_aux_boundary, policy.aux_bus_boundary); let stark_vars = builder.alloc(NUM_STARK_VARS, policy.stark_vars); @@ -189,6 +189,7 @@ mod tests { let counts = InputCounts { width: 1, aux_width: 1, + num_aux_boundary: 1, num_public: 8, // Two logical VLPI groups in MASM occupy four EF slots total: // [group0, pad0, group1, pad1]. @@ -213,6 +214,7 @@ mod tests { let counts = InputCounts { width: 1, aux_width: 1, + num_aux_boundary: 1, num_public: 8, num_vlpi: 2, num_randomness: 2, diff --git a/crates/ace-codegen/src/pipeline.rs b/crates/ace-codegen/src/pipeline.rs index 541c76b89f..4e1f24621a 100644 --- a/crates/ace-codegen/src/pipeline.rs +++ b/crates/ace-codegen/src/pipeline.rs @@ -151,6 +151,7 @@ where InputCounts { width: air.width(), aux_width: air.aux_width(), + num_aux_boundary: air.num_aux_values(), num_public: air.num_public_values(), num_vlpi, num_randomness, diff --git a/crates/ace-codegen/src/tests/layout_masm.rs b/crates/ace-codegen/src/tests/layout_masm.rs index e7d74ff3c4..4fcea57141 100644 --- a/crates/ace-codegen/src/tests/layout_masm.rs +++ b/crates/ace-codegen/src/tests/layout_masm.rs @@ -5,6 +5,9 @@ fn masm_layout_aligns_and_maps_aux_inputs() { let counts = InputCounts { width: 3, aux_width: 2, + // TODO(#3032): 2 boundary values, but only the first is real (col 0 accumulator). + // The second is always zero. Reduce to 1 once trace splitting lands. + num_aux_boundary: 2, num_public: 5, num_vlpi: 0, num_randomness: 16, diff --git a/crates/ace-codegen/src/unit_tests.rs b/crates/ace-codegen/src/unit_tests.rs index 23fa90a89c..f6af0c2a23 100644 --- a/crates/ace-codegen/src/unit_tests.rs +++ b/crates/ace-codegen/src/unit_tests.rs @@ -12,6 +12,7 @@ fn minimal_layout(num_public: usize) -> InputLayout { let counts = InputCounts { width: 0, aux_width: 0, + num_aux_boundary: 0, num_public, num_vlpi: 0, num_randomness: 2, diff --git a/crates/lib/core/asm/stark/constants.masm b/crates/lib/core/asm/stark/constants.masm index 2578d7f207..0c92cac6c8 100644 --- a/crates/lib/core/asm/stark/constants.masm +++ b/crates/lib/core/asm/stark/constants.masm @@ -170,17 +170,18 @@ const AUX_RAND_ELEM_PTR = 3225419776 ### - 16 quotient chunk base columns (8 chunks * extension degree) const OOD_EVALUATIONS_PTR = 3225419780 # AUX_RAND_ELEM_PTR + 4 -### Auxiliary bus boundary values (8 extension field elements = 16 base elements). +### Auxiliary bus boundary values (2 extension field elements = 4 base elements). +### TODO(#3032): The second value is always zero (placeholder for trace splitting). const AUX_BUS_BOUNDARY_PTR = 3225420196 # OOD_EVALUATIONS_PTR + (72 + 16 + 16) * 2 * 2 ### Auxiliary inputs for ACE (stark vars), 10 extension field elements = 20 base elements. -const AUXILIARY_ACE_INPUTS_PTR = 3225420212 # AUX_BUS_BOUNDARY_PTR + 16 +const AUXILIARY_ACE_INPUTS_PTR = 3225420200 # AUX_BUS_BOUNDARY_PTR + 4 ### Address at the start of the memory region holding the arithmetic circuit description (constants + ops). -const ACE_CIRCUIT_STREAM_PTR = 3225420232 # AUXILIARY_ACE_INPUTS_PTR + 20 +const ACE_CIRCUIT_STREAM_PTR = 3225420220 # AUXILIARY_ACE_INPUTS_PTR + 20 ### Address at the start of the evaluation-gates portion of the arithmetic circuit (EVAL section). -const ACE_CIRCUIT_PTR = 3225421380 # ACE_CIRCUIT_STREAM_PTR + num_const_felts +const ACE_CIRCUIT_PTR = 3225420820 # ACE_CIRCUIT_STREAM_PTR + num_const_felts ## FRI ## diff --git a/crates/lib/core/asm/sys/vm/aux_trace.masm b/crates/lib/core/asm/sys/vm/aux_trace.masm index 0e3ad79f2f..f1919ceec7 100644 --- a/crates/lib/core/asm/sys/vm/aux_trace.masm +++ b/crates/lib/core/asm/sys/vm/aux_trace.masm @@ -5,14 +5,14 @@ use miden::core::stark::random_coin #! Observes the auxiliary trace for the Miden VM AIR. #! #! Draws auxiliary randomness, reseeds the transcript with the auxiliary trace commitment, -#! and absorbs the 4 words of auxiliary trace boundary values (8 aux columns, each an -#! extension field element = 16 base field elements = 4 words). +#! and absorbs the boundary values (2 extension field elements = 4 base field elements = 1 word). +#! TODO(#3032): the second value is always zero (placeholder for trace splitting). #! -#! The advice provider must supply exactly 5 words in order: -#! [commitment, W0, W1, W2, W3] +#! The advice provider must supply exactly 2 words in order: +#! [commitment, W0] #! #! The commitment is stored at aux_trace_com_ptr and the boundary values at -#! aux_bus_boundary_ptr (sequentially). +#! aux_bus_boundary_ptr. #! #! Precondition: input_len=0 (guaranteed by the preceding reseed_direct in the generic verifier). #! Postcondition: input_len=0, output_len=8. @@ -31,7 +31,7 @@ pub proc observe_aux_trace # Reseeding clears the output buffer. push.0 exec.constants::random_coin_output_len_ptr mem_store - # --- Permutation 1: Absorb [COM, W0] --- + # --- Single permutation: Absorb [COM, W0] --- padw adv_loadw exec.constants::aux_trace_com_ptr mem_storew_le @@ -47,31 +47,6 @@ pub proc observe_aux_trace exec.poseidon2::permute # => [R1', R2', C', ...] - # --- Permutation 2: Absorb [W1, W2] --- - - # Overwrite R2' with W1, then R1' with W2. - swapw adv_loadw - exec.constants::aux_bus_boundary_ptr add.4 mem_storew_le - # => [W1, R1', C', ...] - - swapw adv_loadw - exec.constants::aux_bus_boundary_ptr add.8 mem_storew_le - # => [W2, W1, C', ...] - - swapw - exec.poseidon2::permute - # => [R1'', R2'', C'', ...] - - # --- Permutation 3: Absorb [W3] into rate slot 1 only --- - - # Overwrite R1'' with W3 from advice, keeping R2'' and C''. - adv_loadw - exec.constants::aux_bus_boundary_ptr add.12 mem_storew_le - # => [W3, R2'', C'', ...] - - exec.poseidon2::permute - # => [R1_final, R2_final, C_final, ...] - # Store final sponge state. exec.random_coin::store_random_coin_state # => [...] diff --git a/crates/lib/core/asm/sys/vm/constraints_eval.masm b/crates/lib/core/asm/sys/vm/constraints_eval.masm index 6726071450..cb3b559143 100644 --- a/crates/lib/core/asm/sys/vm/constraints_eval.masm +++ b/crates/lib/core/asm/sys/vm/constraints_eval.masm @@ -9,10 +9,10 @@ use miden::core::stark::utils # Breakdown: 270 READ slots + 302 constants = 572 # This value must match AceConfig and the layout in `constants.masm`. # If NUM_VAR_LEN_PI_GROUPS changes, this must be regenerated via ace-codegen. -const NUM_INPUTS_CIRCUIT = 568 +const NUM_INPUTS_CIRCUIT = 562 # Number of evaluation gates in the constraint evaluation circuit -const NUM_EVAL_GATES_CIRCUIT = 5540 +const NUM_EVAL_GATES_CIRCUIT = 5580 # Max cycle length for periodic columns const MAX_CYCLE_LEN_LOG = 4 @@ -76,7 +76,7 @@ proc load_ace_circuit_description adv.push_mapval exec.constants::ace_circuit_stream_ptr padw padw padw - repeat.767 + repeat.772 adv_pipe exec.poseidon2::permute end @@ -158,703 +158,708 @@ adv_map CIRCUIT_COMMITMENT = [ 128,0,5,0,6,0,18446744069414584319,0, 9223372034707292161,0,9223372034707292160,0,18446744069414584318,0,18446744069414584317,0, 13835058052060938241,0,4611686017353646080,0,17850970025369572891,0,0,1, - 32,0,64,0,28,0,12,0, - 1073741824,0,1152921504606846976,0,94,0,19,0, - 41,0,33,0,23,0,31,0, - 87,0,84,0,85,0,108,0, - 4294967294,0,2147483648,0,88,0,92,0, - 104,0,35,0,20,0,48,0, - 10,0,0,0,1152927772037879509,2305848956669662925,1152927450989074133,2305848954522179277, - 1152927448841590485,2305848952374695629,1152927446694106837,2305848950227211981,1152927444546623189,2305848948079728333,1152927442399139541,2305848945932244685, - 1152927440251655893,2305848943784761037,1152927438104172245,2305848941637277389,1152927435956688597,2305848939489793741,1152927433809204949,2305848937342310093, - 1152927431661721301,2305848935194826445,1152927429514237653,2305848933047342797,1152927427366754005,2305848930899859149,1152927425219270357,2305848928752375501, - 1152927423071786709,2305848926604891853,1152927754858010325,2305848924457408190,1152927418776819413,2305848922309924543,1152927416629335765,2305848920162440896, - 1152927414481852117,2305848918014957249,1152927412334368469,2305848915867473602,1152927410186884821,2305848913719989955,1152927408039401173,1152927406965659349, - 2305848910498764484,1152927404818175701,2305848908351280837,1152927402670692053,2305848906203797190,1152927400523208405,2305848904056313543,1152927398375724757, - 2305848901908829896,1152927396228241109,2305848899761346249,1152927394080757461,2305848897613862602,1152927391933273813,2305848895466378955,1152927738751882965, - 2305848893318895279,1152927387638306517,2305848891171411632,1152927385490822869,2305848889023927985,1152927383343339221,2305848886876444338,1152927381195855573, - 2305848884728960691,1152927379048371925,2305848882581477044,1152927376900888277,2305848880433993421,1152927374753404629,2305848878286509749,1152927372605920981, - 2305848876139026102,1152927370458437333,2305848873991542455,1152927368310953685,2305848871844058808,1152927366163470037,2305848869696575161,1152927364015986389, - 2305848867549091514,1152927361868502741,2305848865401607867,1152927359721019093,2305848863254124220,1152927729088206549,2305848861106640566,1152927355426051797, - 2305848858959156902,1152927353278568149,2305848856811673272,1152927351131084501,2305848854664189607,1152927348983600853,2305848852516705978,1152927346836117205, - 2305848850369222312,1152927344688633557,2305848848221738665,1152927342541149909,2305848846074255018,1152927340393666261,2305848843926771375,1152927338246182613, - 2305848841779287723,1152927336098698965,2305848839631804081,1152927333951215317,2305848837484320428,1152927331803731669,2305848835336836787,1152927329656248021, - 2305848833189353133,1152927327508764373,2305848831041869517,1152927711908337365,2305848828894385814,1152927323213797077,2305848826746902167,1152927321066313429, - 2305848824599418520,1152927318918829781,2305848822451934873,1152927316771346133,2305848820304451226,1152927314623862485,2305848818156967579,1152927312476378837, - 2305848816009483932,1152927310328895189,2305848813862000285,1152927308181411541,2305848811714516638,1152927306033927893,2305848809567032991,1152927303886444245, - 2305848807419549344,1152927301738960597,2305848805272065697,1152927299591476949,2305848803124582050,1152927297443993301,2305848800977098403,1152927295296509653, - 2305848798829614756,1152927694728468181,2305848796682131078,1152927291001542357,2305848794534647431,1152927288854058709,2305848792387163784,1152927286706575061, - 2305848790239680137,1152927284559091413,2305848788092196490,1152927282411607765,2305848785944712843,1152927280264124117,2305848783797229196,1152927278116640469, - 2305848781649745549,1152927275969156821,2305848779502261902,1152927273821673173,2305848777354778255,1152927271674189525,2305848775207294608,1152927269526705877, - 2305848773059810961,1152927267379222229,2305848770912327314,1152927265231738581,2305848768764843667,1152927263084254933,2305848766617360020,1152927677548598997, - 2305848764469876342,1152927258789287637,2305848762322392695,1152927256641803989,2305848760174909048,1152927254494320341,2305848758027425401,1152927252346836693, - 2305848755879941754,1152927250199353045,2305848753732458107,1152927248051869397,2305848751584974460,1152927245904385749,2305848749437490813,1152927243756902101, - 2305848747290007166,1152927241609418453,2305848745142523519,1152927239461934805,2305848742995039872,1152927237314451157,2305848740847556225,1152927235166967509, - 2305848738700072578,1152927233019483861,2305848736552588931,1152927230872000213,2305848734405105284,1152927660368729813,2305848732257621606,1152927226577032917, - 2305848730110137959,1152927224429549269,2305848727962654312,1152927222282065621,2305848725815170665,1152927220134581973,2305848723667687018,1152927217987098325, - 2305848721520203371,1152927215839614677,2305848719372719724,1152927213692131029,2305848717225236077,1152927211544647381,2305848715077752430,1152927209397163733, - 2305848712930268783,1152927207249680085,2305848710782785136,1152927205102196437,2305848708635301489,1152927202954712789,2305848706487817842,1152927200807229141, - 2305848704340334195,1152927198659745493,2305848702192850548,1152927643188860629,2305848700045366870,1152927194364778197,2305848697897883223,1152927192217294549, - 2305848695750399576,1152927190069810901,2305848693602915929,1152927187922327253,2305848691455432282,1152927185774843605,2305848689307948635,1152927183627359957, - 2305848687160464988,1152927181479876309,2305848685012981341,1152927179332392661,2305848682865497694,1152927177184909013,2305848680718014047,1152927175037425365, - 2305848678570530400,1152927172889941717,2305848676423046753,1152927170742458069,2305848674275563106,1152927168594974421,2305848672128079459,1152927166447490773, - 2305848669980595812,1152927626008991445,2305848667833112134,1152927162152523477,2305848665685628487,1152927160005039829,2305848663538144840,1152927157857556181, - 2305848661390661193,1152927155710072533,2305848659243177546,1152927153562588885,2305848657095693899,1152927151415105237,2305848654948210252,1152927149267621589, - 2305848652800726605,1152927147120137941,2305848650653242958,1152927144972654293,2305848648505759311,1152927142825170645,2305848646358275664,1152927140677686997, - 2305848644210792017,1152927138530203349,2305848642063308370,1152927136382719701,2305848639915824723,1152927134235236053,2305848637768341076,1152927608829122261, - 2305848635620857398,1152927129940268757,2305848633473373751,1152927127792785109,2305848631325890104,1152927125645301461,2305848629178406457,1152927123497817813, - 2305848627030922810,1152927121350334165,2305848624883439163,1152927119202850517,2305848622735955516,1152927117055366869,2305848620588471869,1152927114907883221, - 2305848618440988222,1152927112760399573,2305848616293504575,1152927110612915925,2305848614146020928,1152927108465432277,2305848611998537281,1152927106317948629, - 2305848609851053634,1152927104170464981,2305848607703569987,1152927102022981333,2305848605556086340,1152927591649253077,2305848603408602662,1152927097728014037, - 2305848601261119015,1152927095580530389,2305848599113635368,1152927093433046741,2305848596966151721,1152927091285563093,2305848594818668074,1152927089138079445, - 2305848592671184427,1152927086990595797,2305848590523700780,1152927084843112149,2305848588376217133,1152927082695628501,2305848586228733486,1152927080548144853, - 2305848584081249839,1152927078400661205,2305848581933766192,1152927076253177557,2305848579786282545,1152927074105693909,2305848577638798898,1152927071958210261, - 2305848575491315251,1152927069810726613,2305848573343831604,1152927574469383893,2305848571196347926,1152927065515759317,2305848569048864279,1152927063368275669, - 2305848566901380632,1152927061220792021,2305848564753896985,1152927059073308373,2305848562606413338,1152927056925824725,2305848560458929691,1152927054778341077, - 2305848558311446044,1152927052630857429,2305848556163962397,1152927050483373781,2305848554016478750,1152927048335890133,2305848551868995103,1152927046188406485, - 2305848549721511456,1152927044040922837,2305848547574027809,1152927041893439189,2305848545426544162,1152927039745955541,2305848543279060515,1152927037598471893, - 2305848541131576868,1152927557289514709,2305848538984093190,1152927033303504597,2305848536836609543,1152927031156020949,2305848534689125896,1152927029008537301, - 2305848532541642249,1152927026861053653,2305848530394158602,1152927024713570005,2305848528246674955,1152927022566086357,2305848526099191308,1152927020418602709, - 2305848523951707661,1152927018271119061,2305848521804224014,1152927016123635413,2305848519656740367,1152927013976151765,2305848517509256720,1152927011828668117, - 2305848515361773073,1152927009681184469,2305848513214289426,1152927007533700821,2305848511066805779,1152927005386217173,2305848508919322132,1152927540109645525, - 2305848506771838454,1152927001091249877,2305848504624354807,1152926998943766229,2305848502476871160,1152926996796282581,2305848500329387513,1152926994648798933, - 2305848498181903866,1152926992501315285,2305848496034420219,1152926990353831637,2305848493886936572,1152926988206347989,2305848491739452925,1152926986058864341, - 2305848489591969278,1152926983911380693,2305848487444485631,1152926981763897045,2305848485297001984,1152926979616413397,2305848483149518337,1152926977468929749, - 2305848481002034690,1152926975321446101,2305848478854551043,1152926973173962453,2305848476707067396,1152927522929776341,2305848474559583718,1152926968878995157, - 2305848472412100071,1152926966731511509,2305848470264616424,1152926964584027861,2305848468117132777,1152926962436544213,2305848465969649130,1152926960289060565, - 2305848463822165483,1152926958141576917,2305848461674681836,1152926955994093269,2305848459527198189,1152926953846609621,2305848457379714542,1152926951699125973, - 2305848455232230895,1152926949551642325,2305848453084747248,1152926947404158677,2305848450937263601,1152926945256675029,2305848448789779954,1152926943109191381, - 2305848446642296307,1152926940961707733,2305848444494812660,1152927780627814101,1152927521856033732,2305848441273587172,1152926935592997828,2305848439126103524, - 1152926933445514180,2305848436978619876,1152926931298030532,2305848434831136228,1152926929150546884,2305848432683652580,1152926927003063236,2305848430536168932, - 1152926924855579588,2305848428388685284,1152927514339840964,2305848426241201630,1152926920560612292,2305848424093717983,1152926918413128644,2305848421946234340, - 1152926916265644996,2305848419798750688,1152926914118161348,2305848417651267041,1152926911970677700,2305848415503783394,1152926909823194052,2305848413356299747, - 6008659253116,1152926906601969532,6008659253096,1152926904454485864,1152926905528227543,2305848406913848228,2305849443074709372,6008659252129, - 1152926899085775777,1152926901233260247,2305848401545139103,2305849331405559572,6008659252124,1152926893717067644,1152926892643325650,1152926895864551127, - 2305848395102688153,1152927848273549160,1152926888348358354,1152926889422100183,2305848390807720853,5395552671196,1152926884053391124,1152926882979649234, - 1152926885127132887,2305848385439011728,1152926884053391104,1152926878684681938,1152926879758423767,2305848381144044428,6008659253115,1152926874389714811, - 1152926873315971986,1152926875463456471,2305848375775335303,1152927958868956050,6008659253114,1152926867947263866,1152926866873521028,1152926870094747351, - 2305848369332884353,1152927957795214212,6008659253113,1152926861504812921,1152926860431070078,1152926863652296407,2305848362890433403,1152927956721472382, - 6008659253112,1152926855062361976,1152926853988619128,1152926857209845463,2305848356447982453,6454262109971,1152926849693651844,1152926848619910866, - 1152926850767394519,2305848351079273328,6453188368146,1152926844324942718,1152926843251201746,1152926845398685399,2305848345710564203,6452114626321, - 1152926838956233592,1152926837882492626,1152926840029976279,2305848340341855078,1152927955647730552,6451040884496,1152926832513782627,1152926831440041682, - 1152926834661267159,2305848333899404128,1152927778480330620,1152926828218816215,2305848330678178653,1152927778480330600,1152926824997590743,2305848327456953178, - 1152927778480329609,1152926821776365271,2305848324235727703,1152927778480329603,1152926818555139799,2305848321014502228,1152927778480329597,1152926815333914327, - 2305848317793276753,1152927778480329591,1152926812112688855,2305848314572051278,1152928014703531732,1152926808891463383,2305848311350825803,2305849519310378460, - 5299989649223,1152926803522754258,1152926805670237911,2305848305982116678,6498285524444,1152928005039855525,1152926798154044227,1152927983565017921, - 6501506749916,1152926794932818754,6502580491740,1152926792785335102,1152926791711594387,2305848295244698432,5299989649222,1152926788490367802, - 1152926787416626898,1152926800301528791,2305848289875989303,6500433008092,1152926783121659813,1152926782047916867,1152926780974176146,1152926779900434246, - 1152926778826692306,1152926784195401431,2305848281286054703,2305848284507280192,2305848279138571067,6499359266268,1152926772384240436,1152926771310499748, - 1152926770236757906,2305848273769861931,5263482426844,6509022943046,1152926765941789477,1152926764868048594,1152926775605466839,2305848267327410978, - 6495064299333,1152926760573080378,1152926759499339474,1152926761646823127,2305848261958701853,6493990557508,1152926755204371258,1152926754130630354, - 1152926756278114007,2305848256589992728,6492916815683,1152926749835662138,1152926748761921234,1152926750909404887,2305848251221283603,6491843073858, - 1152926744466953018,1152926743393212114,1152926745540695767,2305848245852574478,2305848273769861946,5234491397596,6507949201221,1152926736950760202, - 1152926735877019346,1152926740171986647,2305848238336381703,6506875459396,1152926731582051082,1152926730508310226,1152926732655793879,2305848232967672578, - 6505801717571,1152926726213341962,1152926725139601106,1152926727287084759,2305848227598963453,6504727975746,1152926720844632842,1152926719770891986, - 1152926721918375639,2305848222230254328,1152927961016440532,1152926716549666519,2305848219009028853,6007585511293,1152926712254699219,1152926713328441047, - 2305848214714061553,6456409593621,6008659251950,1152926706885989102,6006511768302,1152926704738505452,6005438026478,1152926702591021802, - 6004364284654,1152926700443538152,6003290542830,1152926698296054502,6002216801006,1152926696148570852,6001143059182,1152926694001087202, - 6000069317358,1152926691853603552,1152926690779862738,1152926709033473751,2305848193239225053,6497211782620,1152926686484894531,6496138040796, - 1152926684337410777,1152926771310498623,1152926682189927127,1152926681116185405,1152928000744887002,1152926678968701763,1152926782047917991,1152926676821218002, - 2305848180354323156,1152926682189928360,1152926673599993747,1152926772384241574,1152926671452510119,1152926670378767165,1152926669305026451,2305848172838130381, - 2305848171764388559,1152926670378768296,1152926665010059155,1152926686484895652,1152926662862574296,1152928006113596226,1152926660715090627,1152926659641350056, - 2305848163174453957,1152926782047917988,1152926656420124562,1152926798154045348,1152926654272640914,2305848157805744829,2305848156732003007,2305848155658261191, - 6489695589852,1152926648903930663,2305848152437035840,2305848151363293880,1152928001818628931,1152926644608963288,1152926643535221462,1152926642461479741, - 1152926676821218007,1152926683263669054,2305848143847101103,1152926661788832470,1152926771310499751,1152926636019028675,2305848139552133804,2305848138478391981, - 1152926671452508991,1152926631724061379,1152926661788832459,2305848134183424678,1152926661788832465,2305848132035941028,2305848130962199208,1152926625281611688, - 2305848128814715568,1152926782047916863,1152926622060384946,1152926620986644392,1152926643535221566,1152926618839160744,2305848122372264604,2305848121298522783, - 1152926643535221442,1152926614544192317,1152926614544193448,2305848117003555478,1152926631724061501,1152926610249226131,1152926631724062632,1152926608101742483, - 2305848111634846354,2305848110561104532,2305848109487362712,1152927994302436135,1152926622060385085,1152926601659291539,2305848105192395404,2305848104118653755, - 2305848103044911757,2305848101971169972,1152926596290582313,1152927982491275956,1152927981417534087,2305848097676202628,5087388766853,1152926590921873106, - 1152926687558637271,2305848093381235328,1152926681116186536,2305848091233751764,1152926636019028695,1152926584479421245,2305848088012526204,1152926584479422376, - 2305848085865042553,1152926631724061399,1152926579110712125,2305848082643817079,1152926579110713256,2305848080496333428,1152926670378767063,1152926573742003005, - 2305848077275107954,1152926573742004136,2305848075127624303,5175435596397,2305848072980140724,1152926642461480872,2305848070832657072,1152926636019028658, - 1152926564078326589,2305848067611431529,1152926564078327720,2305848065463947878,1152926631724061362,1152926558709617469,2305848062242722404,1152926558709618600, - 2305848060095238753,1152926643535221451,1152926553340908349,2305848056874013279,1152926553340909480,2305848054726529628,5136780890714,2305848052579046023, - 2305848051505304171,1152928001818630052,1152926544750973656,1152926543677231830,1152926542603490109,1152926542603491240,2305848045062853203,1152926543677231787, - 1152926538308522813,2305848041841627729,1152926538308523944,2305848039694144078,1152926543677231783,1152926532939813693,2305848036472918604,1152926532939814824, - 2305848034325434953,1152926543677231819,1152926527571104573,2305848031104209479,1152926527571105704,2305848028956725828,1152926543677231774,1152926522202395453, - 2305848025735500354,1152926522202396584,2305848023588016703,1152926543677231825,1152926516833686333,2305848020366791229,1152926516833687464,2305848018219307578, - 1152926543677231934,1152926511464977213,2305848014998082104,1152926511464978344,2305848012850598453,1152926543677231810,1152926506096268093,2305848009629372979, - 1152926506096269224,2305848007481889328,1152926676821219240,1152926500727560083,2305848004260663854,1152926625281610557,2305848002113180203,2305848001039438423, - 1152926495358850856,1152926567299553168,1152927980343792216,2305847996744471078,1152926496432592785,2305847994596987428,4984309551655,1152926487842658002, - 1152926587700647639,2305847990302020128,1152926622060384983,1152926483547689789,1152926677894959914,2305847986007052828,2305847984933311083,1152926643535221457, - 1152926478178980669,1152926677894959934,2305847980638343703,1152926660715090642,2305847978490860053,1152926640313997224,4967129682451,2305847975269634648, - 2305847974195892761,1152926622060384978,2305847972048409129,2305847970974667279,1152926465294079783,1152926479252723599,1152927979270050320,2305847966679699978, - 1152926466367821712,2305847964532216328,4954244780555,1152926457777886930,1152926484621432535,2305847960237249028,2305847983859569327,1152926639240255400, - 4947802330828,2305847955942281744,2305847954868539905,1152926640313996093,4943507363532,2305847951647314445,2305847950573572605,1152926444892985126, - 1152926453482919822,1152927978196308478,2305847946278605303,1152926445966727055,2305847944131121653,4933843685880,1152926437376792274,1152926454556661463, - 2305847939836154353,1152926660715090647,2305847937688670894,1152926661788832574,1152926622060384963,1152926429860599720,2305847933393703404,2305847932319961581, - 1152926771310498627,1152926425565632402,2305847929098736104,2305847928024994305,1152926618839159613,1152926637092771752,4915590074851,2305847923730027006, - 2305847922656285156,1152926639240254269,4911295108812,2305847919435059706,2305847918361317855,1152926412680730405,1152926422344406925,1152927977122566624, - 2305847914066350553,1152926413754472334,2305847911918866903,4901631431130,1152926405164537554,1152926434155566807,2305847907623899603,2305847926951252652, - 1152926634945288104,4895188981452,2305847903328932320,2305847902255190480,1152926637092770621,4890894014156,2305847899033965020,2305847897960223180, - 1152926392279635748,1152926400869570444,1152927976048824781,2305847893665255878,1152926393353377677,2305847891517772228,4881230336455,1152926384763442898, - 1152926401943312087,2305847887222804928,2305847905476416170,1152926630650320808,4874787886796,2305847882927837645,2305847881854095805,1152926634945286973, - 4870492919500,2305847878632870345,2305847877559128505,1152926371878541091,1152926380468475787,1152927974975082938,2305847873264161203,1152926372952283020, - 2305847871116677553,4860829241780,1152926364362348242,1152926381542217431,2305847866821710253,2305847885075321510,1152926629576578984,4854386792140, - 2305847862526742970,2305847861453001130,1152926630650319677,4850091824844,2305847858231775670,2305847857158033830,1152926351477446434,1152926360067381130, - 1152927973901341095,2305847852863066528,1152926352551188363,2305847850715582878,4840428147105,1152926343961253586,1152926361141122775,2305847846420615578, - 1152926429860598589,2305847844273132197,4926327493014,2305847842125648298,1152926478178981800,1152926627429095336,4829690728851,2305847837830680999, - 2305847836756939156,1152926629576577853,4825395762892,2305847833535713699,2305847832461971855,1152926326781384481,1152926336445061001,1152927972827599248, - 2305847828167004553,1152926327855126410,2305847826019520903,4815732085130,1152926319265191634,1152926340740028119,2305847821724553603,2305847841051906723, - 2305847819577069968,1152926627429094205,4808215893708,2305847816355844492,2305847815282102655,1152926309601515296,1152926314970224520,1152927971753857424, - 2305847810987135353,1152926310675257225,2305847808839651703,4798552215930,1152926302085322450,1152926316043966167,2305847804544684403,1152927860084707707, - 1152927971753857408,1152927970680115600,2305847800249717103,1152926310675257224,2305847798102233453,4787814797680,1152926291347904210,1152926298864096983, - 2305847793807266153,1152927859010965883,1152927970680115584,1152927969606373776,2305847789512298853,1152927971753857404,2305847787364815203,4777077379430, - 1152926280610485970,1152926288126678743,2305847783069847903,1152926682189927229,1152926276315518867,2305847779848622572,1152926430934341544,4768487444826, - 2305847776627396992,2305847775553655184,2305847774479913340,1152926268799325981,1152926270946809733,1152927968532631952,2305847770184946003,1152927970680115580, - 2305847768037462353,4757750026580,1152926261283133138,1152926277389260503,2305847763742495053,1152927856863482197,1152927968532631895,1152927967458890128, - 2305847759447527753,1152927969606373756,2305847757300044103,4747012608330,1152926250545714898,1152926258061907671,2305847753005076803,1152927855789740373, - 1152927967458890071,1152927966385148304,2305847748710109503,1152927968532631932,2305847746562625853,4736275190080,1152926239808296658,1152926247324489431, - 2305847742267658553,2305847775553655164,1152926235513329434,1152927966385148247,1152927967458890108,2305847736898949428,4726611513653,1152926230144620242, - 1152926236587071191,2305847732603982128,5998995576705,1152926225849652948,1152926226923394775,2305847728309014828,1152926225849652947,1152926222628427479, - 2305847725087789353,1152927964237666004,1152926219407202007,2305847721866563878,1152927964237666003,1152926216185976535,2305847718645338403,6460704560921, - 2305848284507280186,4706210420188,2305849496761800603,1152926208669782823,4702989193502,1152926206522298656,1152926684337412003,1152926204374815555, - 1152926677894961061,1152926202227333030,2305847705760436504,2305847704686695055,1152926671452510116,1152926197932365714,2305847701465469580,2305847700391727754, - 2305847699317985556,1152927963163922733,1152926192563654927,2305847696096760090,1152926204374816676,2305847693949276716,2305847692875534862,4682588098828, - 5998995576601,1152926185047462175,2305847688580567304,1152926182899979986,1152926212964751063,2305847685359341828,4687956809180,1152926178605011245, - 1152926179678754519,2305847681064374528,6510096684824,1152926174310043913,1152926173236303570,1152926175383787223,2305847675695665403,1152926193637398298, - 1152926168941334785,1152926167867594450,1152926170015078103,2305847670326956278,1152926541529749289,1152926163572627154,1152926164646368983,2305847666031988978, - 6477884430121,1152926159277658706,1152926158203918034,1152926160351401687,2305847660663279853,6476810688297,1152926153908949583,1152926152835208914, - 1152926154982692567,2305847655294570728,6475736946473,1152926148540240461,1152926147466499794,1152926149613983447,2305847649925861603,6474663204649, - 1152926143171531338,1152926142097790674,1152926144245274327,2305847644557152478,6473589462825,1152926137802822216,1152926136729081554,1152926138876565207, - 2305847639188443353,6472515721001,1152926132434113093,1152926131360372434,1152926133507856087,2305847633819734228,6471441979177,1152926127065403971, - 1152926125991663314,1152926128139146967,2305847628451025103,6470368237353,1152926121696694848,1152926120622954194,1152926122770437847,2305847623082315978, - 6468220753705,1152926116327985726,1152926115254245074,1152926117401728727,2305847617713606853,6466073270057,1152926110959276603,1152926109885535954, - 1152926112033019607,2305847612344897728,6463925786409,1152926105590567481,1152926104516826834,1152926106664310487,2305847606976188603,6461778302761, - 1152926100221858358,1152926099148117714,1152926101295601367,2305847601607479478,6510096684841,1152926094853149231,1152926093779408594,1152926095926892247, - 2305847596238770353,1152926153908949532,1152926089484441298,1152926090558183127,2305847591943803053,6477884430120,1152926085189472796,1152926084115732178, - 1152926086263215831,2305847586575093928,1152926148540240380,1152926079820764882,1152926080894506711,2305847582280126628,1152926143171531230,1152926075525797586, - 1152926076599539415,2305847577985159328,1152926137802822091,1152926071230830290,1152926072304572119,2305847573690192028,1152926132434112952,1152926066935862994, - 1152926068009604823,2305847569395224728,1152926127065403813,1152926062640895698,1152926063714637527,2305847565100257428,1152926121696694670,1152926058345928402, - 1152926059419670231,2305847560805290128,6469294495529,1152926054050959742,1152926052977219282,1152926055124702935,2305847555436581003,6477884430119, - 1152926048682250770,1152926047608510162,1152926049755993815,2305847550067871878,6477884430118,1152926043313541632,1152926042239801042,1152926044387284695, - 2305847544699162753,6477884430117,1152926037944832482,1152926036871091922,1152926039018575575,2305847539330453628,6477884430116,1152926032576123343, - 1152926031502382802,1152926033649866455,2305847533961744503,6477884430115,1152926027207414204,1152926026133673682,1152926028281157335,2305847528593035378, - 6477884430114,1152926021838705065,1152926020764964562,1152926022912448215,2305847523224326253,6477884430113,1152926016469995922,1152926015396255442, - 1152926017543739095,2305847517855617128,1152926137802822039,1152926011101288146,1152926012175029975,2305847513560649828,6472515721000,1152926006806319511, - 1152926005732579026,1152926007880062679,2305847508191940703,6471441979175,1152926001437610391,1152926000363869906,1152926002511353559,2305847502823231578, - 6470368237350,1152925996068901271,1152925994995160786,1152925997142644439,2305847497454522453,1152926037944832407,1152925990700193490,1152925991773935319, - 2305847493159555153,6476810688292,1152925986405224855,1152925985331484370,1152925987478968023,2305847487790846028,6475736946467,1152925981036515735, - 1152925979962775250,1152925982110258903,2305847482422136903,6474663204642,1152925975667806615,1152925974594066130,1152925976741549783,2305847477053427778, - 1152926430934340413,1152925970299097229,1152925969225357010,1152925971372840663,2305847471684718653,6468220753704,1152925964930388031,1152925963856647890, - 1152925966004131543,2305847466316009528,6467147011879,1152925959561678911,1152925958487938770,1152925960635422423,2305847460947300403,6466073270054, - 1152925954192969791,1152925953119229650,1152925955266713303,2305847455578591278,1152925970299097194,1152925948824262354,1152925949898004183,2305847451283623978, - 6476810688288,1152925944529293375,1152925943455553234,1152925945603036887,2305847445914914853,6475736946463,1152925939160584255,1152925938086844114, - 1152925940234327767,2305847440546205728,6474663204638,1152925933791875135,1152925932718134994,1152925934865618647,2305847435177496603,6464999528233, - 1152925928423166297,1152925927349425874,1152925929496909527,2305847429808787478,6463925786408,1152925923054457177,1152925921980716754,1152925924128200407, - 2305847424440078353,6462852044583,1152925917685748057,1152925916612007634,1152925918759491287,2305847419071369228,6461778302758,1152925912317038937, - 1152925911243298514,1152925913390782167,2305847413702660103,6477884430109,1152925906948329817,1152925905874589394,1152925908022073047,2305847408333950978, - 6476810688284,1152925901579620697,1152925900505880274,1152925902653363927,2305847402965241853,6475736946459,1152925896210911577,1152925895137171154, - 1152925897284654807,2305847397596532728,6474663204634,1152925890842202457,1152925889768462034,1152925891915945687,2305847392227823603,1152926659641348925, - 1152925885473493133,1152925884399752914,1152925886547236567,2305847386859114478,1152925885473493050,1152925880104785618,1152925881178527447,2305847382564147178, - 1152925885473493045,1152925875809818322,1152925876883560151,2305847378269179878,1152925885473493040,1152925871514851026,1152925872588592855,2305847373974212578, - 6464999528229,1152925867219881968,1152925866146141906,1152925868293625559,2305847368605503453,6463925786404,1152925861851172848,1152925860777432786, - 1152925862924916439,2305847363236794328,6462852044579,1152925856482463728,1152925855408723666,1152925857556207319,2305847357868085203,6461778302754, - 1152925851113754608,1152925850040014546,1152925852187498199,2305847352499376078,1152925885473493098,1152925845745047250,1152925846818789079,2305847348204408778, - 1152925885473493031,1152925841450079954,1152925842523821783,2305847343909441478,1152925885473493026,1152925837155112658,1152925838228854487,2305847339614474178, - 1152925885473493021,1152925832860145362,1152925833933887191,2305847335319506878,6473589462813,1152925828565176304,1152925827491436242,1152925829638919895, - 2305847329950797753,6472515720988,1152925823196467184,1152925822122727122,1152925824270210775,2305847324582088628,6471441979163,1152925817827758064, - 1152925816754018002,1152925818901501655,2305847319213379503,6470368237338,1152925812459048944,1152925811385308882,1152925813532792535,2305847313844670378, - 6008659253137,1152925807090341777,1152925806016598551,1152925804942857938,1152925808164083415,2305847307402219428,1152927980343793553,6477884429788, - 1152925799574149008,2305847303107252129,4292819818281,1152925796352922135,1152925795279181522,1152925801721632471,2305847297738543003,1152927981417535377, - 1152925799574149007,2305847294517317528,4284229883688,1152925787762987543,1152925786689246930,1152925792057956055,2305847289148608403,1152925806016598419, - 1152925782394279634,1152925783468021463,2305847284853641103,1152927977122568081,2305847282706157471,4272418723625,1152925775951827347,1152925774878086866, - 1152925779173054167,2305847277337448328,1152927976048826257,2305847275189964695,4264902530856,1152925768435634579,1152925767361894098,1152925771656861399, - 2305847269821255553,1152927974975084433,1152925799574149006,2305847266600030078,4256312596263,1152925759845699987,1152925758771959506,1152925764140668631, - 2305847261231320953,1152927973901342609,1152925799574149005,2305847258010095478,4247722661670,1152925751255765395,1152925750182024914,1152925755550734039, - 2305847252641386353,1152925799574149004,2305847250493902744,4240206468901,1152925743739572627,1152925742665832146,1152925746960799447,2305847245125193578, - 1152925799574149003,2305847242977709985,4232690276132,1152925736223379859,1152925735149639378,1152925739444606679,2305847237609000803,1152927979270051729, - 1152925799574149002,2305847234387775328,4224100341539,1152925727633445267,1152925726559704786,1152925731928413911,2305847229019066203,1152927978196309905, - 1152925799574149001,2305847225797840728,4215510406946,1152925719043510675,1152925717969770194,1152925723338479319,2305847220429131603,1152925807090340528, - 1152925713674802898,1152925714748544727,2305847216134164303,1152926483547690920,6507949201193,1152925708306091852,1152925707232351954,1152925710453577431, - 2305847209691713353,6506875459368,1152925702937382732,1152925701863642834,1152925704011126487,2305847204323004228,6505801717543,1152925697568673612, - 1152925696494933714,1152925698642417367,2305847198954295103,6504727975718,1152925692199964492,1152925691126224594,1152925693273708247,2305847193585585978, - 6460704560937,1152925686831256113,1152925685757515474,1152925687904999127,2305847188216876853,1152926671452508995,1152925681462548370,1152925680388806354, - 6469294495521,1152925678241320752,1152925682536290007,2305847180700684078,6468220753696,1152925673946353456,1152925675020097239,2305847176405716778, - 6467147011871,1152925669651386160,1152925670725129943,2305847172110749478,6466073270046,1152925665356418864,1152925666430162647,2305847167815782178, - 2305849007135528837,4156454606621,1152925659987709744,1152925662135195351,2305847162447073053,2305849007135528836,4151085897500,1152925654619000624, - 1152925656766486231,2305847157078363928,6462852044571,1152925650324033328,1152925651397777111,2305847152783396628,6461778302746,1152925646029066032, - 1152925647102809815,2305847148488429328,1152926622060386216,1152925641734100883,1152926159277659858,1152925639586615052,1152925642807842519,2305847142045978378, - 6476810688296,1152925635291649746,1152925634217905932,1152925636365391575,2305847136677269253,6475736946471,1152925629922940626,1152925628849196812, - 1152925630996682455,2305847131308560128,6474663204646,1152925624554231506,1152925623480487692,1152925625627973335,2305847125939851003,6473589462821, - 1152925619185522386,1152925618111778572,1152925620259264215,2305847120571141878,6472515720996,1152925613816813266,1152925612743069452,1152925614890555095, - 2305847115202432753,6471441979171,1152925608448104146,1152925607374360332,1152925609521845975,2305847109833723628,6470368237346,1152925603079395026, - 1152925602005651212,1152925604153136855,2305847104465014503,1152925678241322706,1152925597710683916,1152925598784427735,2305847100170047203,1152925673946355410, - 1152925593415716620,1152925594489460439,2305847095875079903,1152925669651388114,1152925589120749324,1152925590194493143,2305847091580112603,1152925665356420818, - 1152925584825782028,1152925585899525847,2305847087285145303,6464999528221,1152925580530816722,1152925579457072908,1152925581604558551,2305847081916436178, - 6463925786396,1152925575162107602,1152925574088363788,1152925576235849431,2305847076547727053,1152927997523662751,1152927996449920926,1152925568719656401, - 2305847072252759754,1152925566572173187,1152927996449920927,2305847069031534277,1152925563350947714,1152925562277205457,2305847065810308806,1152927982491277215, - 2305847063662825153,2305847062589085584,4052301649819,1152925555834752780,1152925570867140311,2305847058294116028,1152925563350947715,1152925566572173186, - 2305847055072890553,1152927982491277214,2305847052925406903,4042637973402,1152925546171076364,1152925552613529303,2305847048630439603,1152925566572173215, - 1152925563350947742,1152925540802368977,2305847044335472304,1152925538654885787,1152925566572173214,1152925563350947743,2305847040040505003,1152925534359918490, - 1152925533286176209,2305847036819279532,1152925566572173199,2305847034671795878,1152927979270051743,2305847032524312228,2305847031450572685,4021163136925, - 1152925524696239884,1152925542949852887,2305847027155603103,1152925534359918491,1152925538654885786,2305847023934377628,1152925563350947727,2305847021786893978, - 1152927979270051742,2305847019639410328,4009351976860,1152925512885079820,1152925521475016407,2305847015344443028,1152925538654885789,1152925534359918492, - 1152925507516372433,2305847011049475729,1152925566572173196,2305847008901992078,1152927976048826271,2305847006754508428,2305847005680768906,3995393333019, - 1152925498926438098,1152925497852694284,1152925509663856343,2305847000312057478,1152925534359918493,1152925538654885788,2305846997090832003,1152925563350947724, - 2305846994943348353,1152927976048826270,2305846992795864703,3982508431130,1152925486041536210,1152925484967792396,1152925494631470807,2305846987427155578, - 1152926676821218109,1152925480672827283,1152925479599083275,1152925481746568919,2305846982058446453,1152925479599083270,1152925476377859799,2305846978837220978, - 1152925479599083265,1152925473156634327,2305846975615995503,1152925479599083260,1152925469935408855,2305846972394770028,1152925479599083255,1152925466714183383, - 2305846969173544553,1152925479599083250,1152925463492957911,2305846965952319078,1152925479599083245,1152925460271732439,2305846962731093603,1152925479599083240, - 1152925457050506967,2305846959509868128,1152925479599083236,1152925453829281495,2305846956288642653,1152925479599083232,1152925450608056023,2305846953067417178, - 1152925479599083228,1152925447386830551,2305846949846191703,1152925479599083224,1152925444165605079,2305846946624966228,1152925479599083219,1152925440944379607, - 2305846943403740753,1152925479599083214,1152925437723154135,2305846940182515278,1152927981417535390,1152925433428186577,2305846936961289920,2305846935887548097, - 2305846934813808527,3924526372763,1152925428059475574,1152925434501928663,2305846930518838853,1152927981417535391,2305846928371355318,2305846927297613495, - 2305846926223873934,3915936438170,1152925419469540982,1152925424838252247,2305846921928904253,1152925566572173211,1152925563350947738,1152925414100833745, - 2305846917633936954,1152927978196309919,1152927977122568094,1152925409805866449,2305846913338969654,2305846912265227831,2305846911191488395,3900904052507, - 1152925404437157586,1152925403363413622,1152925416248317655,2305846905822776878,1152925563350947739,1152925566572173210,2305846902601551403,1152927977122568095, - 1152927978196309918,2305846899380325928,2305846898306584105,2305846897232844682,3886945408794,1152925390478513874,1152925389404769910,1152925400142190295, - 2305846891864133153,1152926620986643261,6008659253029,1152925384036063013,1152925382962318878,1152925386183546583,2305846885421682203,6008659253028, - 1152925378667353892,1152925377593609758,1152925379741095639,2305846880052973078,6008659253027,1152925373298644771,1152925372224900638,1152925374372386519, - 2305846874684263953,6008659253026,1152925367929935650,1152925366856191518,1152925369003677399,2305846869315554828,2305849370060265253,2305846867168073507, - 2305846866094331682,6008659250695,1152925359339998750,1152925363634968279,2305846861799362053,1152927500381198109,2305849370060265252,2305846858578138915, - 1152927863305934298,2305846856430652928,2305846855356911106,3845069477768,1152925348602580510,1152925356118775511,2305846851061943803,1152927499307456292, - 2305846848914462501,1152927498233714467,2305846846766976503,1152927497159972642,2305846844619492853,3834332059424,1152925337865162270,1152925345381357271, - 2305846840324525553,1152927971753858848,3828963350427,1152925332496453150,1152925334643939031,2305846834955816428,1152927992154953627,6008659250665, - 1152925327127744030,1152925329275229911,2305846829587107303,1152927992154953604,3818225932191,1152925321759034910,1152925323906520791,2305846824218398178, - 1152927992154953603,3812857223070,1152925316390325790,1152925318537811671,2305846818849689053,4061965326237,1152925312095358494,1152925313169102551, - 2305846814554721753,4058744100764,1152925307800391198,1152925308874135255,2305846810259754453,2305849375428974377,2305849484950640529,6475736946577, - 1152925301357942687,6474663204752,1152925299210459038,1152925298136716753,2305846801669819855,2305846800596078033,3790308642258,1152925293841747486, - 1152925304579167959,2305846796301110728,2305849374355232552,2305849483876898704,1152925301357942686,1152925299210459039,2305846790932401603,2305846789858659780, - 3779571224005,1152925283104329246,1152925290620524247,2305846785563692478,2305849373281490727,2305849480655673229,6471441979277,1152927499307456415, - 1152925275588136377,6470368237452,1152927499307456414,1152925272366910902,1152925271293171153,2305846774826274231,2305846773752532410,3763465096635, - 1152925266998201886,1152925279883106007,2305846769457565103,2305849372207748902,2305849479581931404,1152925272366910905,1152925273440652728,2305846764088855978, - 2305846763015114155,3752727678380,1152925256260783646,1152925263776978647,2305846758720146853,2305849361470330652,2305849373281490729,6364067796777, - 1152925249818335133,6362994054952,1152925247670851484,1152925246597109201,2305846750130212255,2305846749056470433,3738769034658,1152925242302139934, - 1152925253039560407,2305846744761503128,2305849360396588827,2305849372207748904,1152925249818335132,1152925247670851485,2305846739392794003,2305846738319052180, - 3728031616405,1152925231564721694,1152925239080916695,2305846734024084878,1152927866527160209,1152927865453418381,2305846730802859403,1152927864379676559, - 2305846728655375753,1152927863305934731,2305846726507892103,3716220458886,1152925219753561630,1152925228343498455,2305846722212924803,1152927866527160208, - 1152927865453418380,2305846718991699328,1152927864379676558,2305846716844215678,1152927863305934730,2305846714696732028,3704409298821,1152925207942401566, - 1152925216532338391,2305846710401764728,1152927971753858951,3699040589601,1152925202573692446,1152925204721178327,2305846705033055603,1152927862232192801, - 3693671880478,1152925197204983326,1152925199352469207,2305846699664346478,2305849007135528834,3688303171359,1152925191836274206,1152925193983760087, - 2305846694295637353,6469294495517,1152925187541306910,1152925188615050967,2305846690000670053,2305849486024382353,3678639494953,1152925182172598887, - 1152925181098858194,1152925184320083671,2305846683558219103,2305849487098124073,1152925176803889786,1152925175730149074,1152925177877632727,2305846678189509978, - 4286377367337,1152925171435180645,1152925170361439954,1152925172508923607,2305846672820800853,1152927982491277097,6008659250514,1152925164992729720, - 1152925163918989010,1152925167140214487,2305846666378349903,2305849487098123740,3655017174825,1152925158550278773,1152925157476538066,1152925160697763543, - 2305846659935898953,1152925806016598643,1152925154255312599,2305846656714673478,6008659250524,1152925149960345298,1152925148886602355,1152925151034087127, - 2305846651345964353,1152925806016598626,1152925145665378007,2305846648124738878,6008659253136,1152925141370410896,1152925140296667746,1152925142444152535, - 2305846642756029753,1152925171435181778,1152925136001700450,1152925137075443415,2305846638461062453,1152925806016598624,1152925132780476119,2305846635239836978, - 1152925140296667744,1152925129559250647,2305846632018611503,4286377364834,3620657436457,1152925124190541522,1152925123116798560,1152925126338025175, - 2305846625576160553,1152926566225811154,6476810688401,1152925117748090665,1152925116674346278,1152925119895574231,2305846619133709603,1152925117748090783, - 3607772534236,3606698792745,1152925110231895334,1152925113453123287,2305846612691258653,1152926586626905810,1152925105936928082,1152925107010672343, - 2305846608396291353,4054449133020,3597035116329,1152925100568218906,1152925102715705047,2305846603027582228,1152926433081825192,1152925096273254098, - 1152927981417535376,3589518923560,1152925093052026128,1152925097346995927,2305846595511389453,1152925141370410793,2305846593363908060,3583076472735, - 1152925086609575184,1152925089830803159,2305846589068938503,1152927980343793567,3577707763495,1152925081240866064,1152925083388352215,2305846583700229378, - 1152927496086230822,2305846581552748329,3571265312654,1152925074798415120,1152925078019643095,2305846577257778428,6008659253033,1152925070503450409, - 1152925069429706000,1152925071577192151,2305846571889069303,1152926428786857682,1152925065134739695,1152925066208483031,2305846567594102003,1152925065134739207, - 1152925061913515735,2305846564372876528,1152927979270051728,1152925057618548177,2305846561151651745,3550864217895,1152925054397320436,1152925058692290263, - 2305846556856683753,2305849483876898703,1152925050102353250,4296041041125,3553011698916,3542274283302,1152925045807385844,1152925051176097495, - 2305846548266749153,1152926631724061394,2305846546119266830,2305846545045525012,1152927493938747292,2305846542898042781,3532610606539,1152925036143712155, - 3530463122908,1152927493938747294,2305846537529333663,1152925031848742103,1152925030775000284,1152925042586162903,2305846533234363603,1152926677894959830, - 2305846531086881294,2305846530013139478,2305846528939396318,2305846527865655828,1152925022185068242,3527241897769,1152925020037582027,1152925027553777367, - 2305846522496945353,3532610606888,1152925015742614731,1152925016816359127,2305846518201978053,1152927497159972764,1152927995376178860,2305846514980752597, - 2305846513907010754,3503619577745,1152925007152681486,1152925012521391831,2305846509612043453,3504693316962,1152925002857712848,1152925003931457239, - 2305846505317076153,2305846687853189007,3504693316790,1152924997489005078,1152924999636489943,2305846499948367028,1152926636019028690,1152924993194038994, - 2305849374355232657,1152927870822127276,3485365963951,3484292224912,1152924987825327280,1152924994267780823,2305846490284690603,1152924992120294648, - 1152924984604104407,2305846487063465128,3527241897768,1152924980309134512,1152924981382878935,2305846482768497828,3503619575704,1152924976014167262, - 1152924977087911639,2305846478473530528,2305847295591061391,3503619574941,1152924970645459476,1152924972792944343,2305846473104821403,1152926670378767058, - 1152924966350493394,1152927869748385681,2305846468809856809,3458522421136,1152924962055523479,1152924967424235223,2305846464514886803,6365141538704, - 3527241895056,1152924956686814359,1152924958834300631,2305846459146177678,6366215280529,2305846541824300508,3446711258251,1152924950244363415, - 1152924953465591511,2305846452703726728,1152926675747477202,3532610606889,1152924944875654277,1152924947023140567,2305846447335017603,1152924945949396133, - 1152924941654431447,2305846444113792128,1152927991081211604,1152924938433205975,2305846440892566653,6008659253145,1152924934138238873,1152924935211980503, - 2305846436597599353,6008659253041,1152924929843270345,1152924928769529554,1152924930917013207,2305846431228890228,1152924929843270333,1152924924474562258, - 1152924925548304087,2305846426933922928,6008659253160,1152924920179595176,1152924921253336791,2305846422638955628,6008659253159,1152924915884627879, - 1152924916958369495,2305846418343988328,6008659253158,1152924911589660582,1152924912663402199,2305846414049021028,6008659253157,1152924907294693285, - 1152924908368434903,2305846409754053728,6008659253156,1152924902999725988,1152924904073467607,2305846405459086428,6008659253155,1152924898704758691, - 1152924899778500311,2305846401164119128,6008659253154,1152924894409791394,1152924895483533015,2305846396869151828,1152926678968702884,3385507977107, - 1152924891188565719,2305846392574184528,1152928000744888227,3381213009810,1152924886893598423,2305846388279217228,6478958172066,6480031910985, - 1152924880451147688,1152924882598631127,2305846382910508103,1152928007187339154,1152924877229922007,2305846379689282628,1152928006113597330,1152924874008696535, - 2305846376468057153,1152925806016598671,1152924870787471063,2305846373246831678,1152927995376177802,1152924867566245591,2305846370025606203,1152926600585549724, - 1152924864345020119,2305846366804380728,1152927993228694154,1152924861123794647,2305846363583155253,1152927992154952330,1152924857902569175,2305846360361929778, - 1152925807090340114,1152924854681343703,2305846357140704303,6008659253149,1152924850386374930,1152924851460118231,2305846352845737003,1152927982491277212, - 1152924846091408167,1152924847165150935,2305846348550769703,1152926769163015890,1152927891223222058,6387690116572,1152927893370702882,1152924838575213603, - 1152924837501471780,6495064299321,1152924835353988127,1152924842870183639,2305846337813351453,6493990557496,1152924831059020831,1152924832132765399, - 2305846333518384153,6492916815671,1152924826764053535,1152924827837798103,2305846329223416853,6491843073846,1152924822469086239,1152924823542830807, - 2305846324928449553,6490769332021,1152924818174118943,1152924819247863511,2305846320633482253,1152927893370705725,1152924813879151651,6008659250185, - 1152924811731669691,1152924810657928914,1152924814952896215,2305846313117289478,1152927991081211602,6373731473304,6008659250178,1152924804215475202, - 1152924803141733379,1152924807436703447,2305846305601096703,4995046970844,1152924798846769057,1152924797773024258,1152924796699282435,1152924799920510679, - 2305846299158645753,2305848159953228489,2305846297011163692,1152924791330573313,1152924790256834258,1152924793478059735,2305846292716194803,6388763858396, - 1152924785961864226,1152924784888122403,1152924785961867069,1152924782740638755,2305846286273743854,1152924780593155074,1152924779519416018,1152924787035608791, - 2305846281978776553,1152927990007468839,1152924776298190551,2305846278757551078,1152927879412062105,3300682372572,1152924770929478627,2305846274462583797, - 1152927492865005369,3263101409185,2305849393682585402,2305849394756324317,2305846269093874652,2305846268020135740,2305846266946391002,2305846265872652093, - 2305846264798907352,2305846263725168446,2305846262651423702,2305849399051291605,2305846260503940052,2305849400125033427,3249142762462,1152924752675867616, - 1152924751602128594,1152924773076965079,2305846254061489103,1152924780593158049,1152924747307161497,1152924746233419474,1152924748380903127,2305846248692779978, - 1152927877264575478,1152924741938452178,1152924743012194007,2305846244397812678,4995046968322,1152924737643484975,1152924736569743257,1152924735496001234, - 1152924738717226711,2305846237955361728,3233036637660,2305849493540574684,3225520445231,1152924729053547453,1152924727979808561,1152924726906066841, - 1152924725832324818,1152924732274775767,2305846228291685303,6008659253143,1152924721537357719,5991479383959,1152924719389871027,6006511769495, - 1152924717242387377,5995774351255,1152924715094903727,5987184416663,1152924712947420077,5986110674839,1152924710799936427,5996848093079, - 1152924708652452777,5997921834903,1152924706504969127,1152924722611099351,2305846208964332453,6008659253142,1152924702210004886,1152924703283746519, - 2305846204669365153,6008659253141,1152924697915037589,1152924698988779223,2305846200374397853,6008659253140,1152924693620070292,1152924694693811927, - 2305846196079430553,6483253138908,1152924689325102997,1152924688251361172,6482179397084,1152924686103874454,1152924685030135700,2305846188563237780, - 6481105655260,1152924681808907157,2305846185342012304,2305846184268273558,3173980834806,1152924690398844631,2305846181047045003,3287797470684, - 2305849491393091478,2305846177825822612,1152924672145230728,1152924675366459095,2305846174604594053,1152924679661426589,1152924668924008151,2305846171383368578, - 1152924679661426588,1152924665702782679,2305846168162143103,1152924679661426587,1152924662481557207,2305846164940917628,1152924679661426586,1152924659260331735, - 2305846161719692153,1152924682882652063,1152924656039106263,2305846158498466678,1152924682882652062,1152924652817880791,2305846155277241203,1152924687177619360, - 1152924649596655319,2305846152056015728,6503654233921,1152924645301688217,1152924644227946194,1152924646375429847,2305846146687306603,2305849512867927500, - 3135326132033,1152924638859236029,1152924637785495250,1152924641006720727,2305846140244855653,1152928008261079739,1152924634564269783,2305846137023630178, - 1152926671452510099,1152927983565019044,2305846133802404703,2305848105192395579,2305846131654921053,2305846130581181228,2305846129507440537,6008659250009, - 1152924631343044311,2305846126286211928,6008659251899,1152924619531884243,1152924620605626071,2305846121991244628,5852966688220,5820754430801, - 1152924614163175272,1152924613089433467,1152924616310658775,2305846115548793678,1152924615236917096,1152924608794466170,1152924609868207831,2305846111253826378, - 1152924608794466169,1152924605573240535,2305846108032600903,1152927938467861894,2305849459180836728,2305849457033353078,2305846103737633603,2305846102663894903, - 2305846101590149955,2305849454885869428,2305849452738385778,2305846098368924478,2305846097295185779,2305846096221440830,2305846095147699007,2305849450590902128, - 2305849448443418478,2305846091926473528,2305846090852734831,2305846089778989880,2305846088705248057,2305846087631506239,2305846086557766927,1152924580877175601, - 1152924579803433777,1152924579803433776,1152924577655950127,2305849458107094902,2305846080115313472,2305849453812127602,2305846077967829819,2305846076894087979, - 2305849449517160302,2305846074746604341,2305846073672862504,2305846072599120683,2305846071525381361,1152924565844790051,1152924564771048227,1152924564771048226, - 1152924562623564577,2305846066156669741,2305846102663894901,2305846064009186114,2305846097295185777,2305846061861702461,2305846060787960604,2305846090852734829, - 2305846058640476983,2305846057566735129,2305846056492993308,2305846055419253971,1152924549738662676,1152924548664920852,1152924548664920851,1152924546517437202, - 2305849460254578552,2305846048976800541,2305849455959611252,2305846046829316891,2305846045755575054,2305849451664643952,2305846043608091416,2305846042534349579, - 2305846041460607758,2305846040386868405,1152924534706277126,1152924533632535302,1152924533632535301,1152924531485051652,2305846035018156816,2305846033944415006, - 2305846032870673183,2305846031796931358,2305846087631506234,2305846029649450135,1152924523968858876,1152924522895117052,1152924522895117051,1152924520747633402, - 2305846072599120681,2305846023206999161,1152924517526407926,1152924516452666102,1152924516452666101,1152924514305182452,2305846017838287608,2305846056492993306, - 2305846015690806363,1152924510010215151,1152924508936473327,1152924508936473326,1152924506788989677,2305846041460607756,2305846009248355389,1152924503567764201, - 1152924502494022377,1152924502494022376,1152924500346538727,2305846003879643883,2305846002805902065,2305846001732160242,2305846000658418417,2305845999584676606, - 2305846087631506228,2305845997437195295,1152924491756604126,1152924490682862302,1152924490682862301,1152924488535378652,2305846072599120678,2305845990994744321, - 1152924485314153176,1152924484240411352,1152924484240411351,1152924482092927702,2305845985626032858,2305846056492993303,2305845983478551523,1152924477797960401, - 1152924476724218577,1152924476724218576,1152924474576734927,2305846041460607753,2305845977036100549,1152924471355509451,1152924470281767627,1152924470281767626, - 1152924468134283977,2305845971667389133,2305845970593647315,2305845969519905492,2305845968446163667,2305845967372421856,2305845966298680062,2956011247376, - 1152924459544349508,1152924602352015063,2305845962003712703,2305846050050542352,2305845959856229119,2305846010322094827,2305845957708745442,2305845956635003579, - 2305845978109840077,2305845954487519940,2305845953413778104,2305845952340036283,2942052603663,1152924445585705796,1152924456323126999,2305845948045068978, - 2305846032870673154,2305845945897585409,2305846001732160229,2305845943750101732,2305845942676359854,2305845969519905479,2305845940528876230,2305845939455134379, - 2305845938381392558,2928093959950,1152924431627062084,1152924442364483287,2305845934086425253,2305846081189055277,2305845931938941615,2305846024280738552, - 2305845929791457965,2305845928717716129,2305845992068483802,2305845926570232490,2305845925496490654,2305845924422748833,2914135316237,1152924417668418372, - 1152924428405839575,2305845920127781528,2305845966298680033,2908766607116,1152924412299709252,1152924414447195863,2305845914759072403,2305845952340036281, - 2903397897995,1152924406931000132,1152924409078486743,2305845909390363278,2305845938381392556,2898029188874,1152924401562291012,1152924403709777623, - 2305845904021654153,2305845924422748831,2892660479753,1152924396193581892,1152924398341068503,2305845898652945028,2305845966298680003,2887291770632, - 1152924390824872772,1152924392972359383,2305845893284235903,2305845952340036278,2881923061511,1152924385456163652,1152924387603650263,2305845887915526778, - 2305845938381392553,2876554352390,1152924380087454532,1152924382234941143,2305845882546817653,2305845924422748828,2871185643269,1152924374718745412, - 1152924376866232023,2305845877178108528,1152927938467861865,2305849460254577935,1152924369350036076,1152924368276294252,1152924368276294251,1152924366128810602, - 2305849459180836081,1152924363981326951,1152924362907585127,1152924362907585126,1152924360760101477,2305845864293206632,2305849458107094227,1152924357538876001, - 1152924356465134177,1152924356465134176,1152924354317650527,2305849457033352373,1152924352170166876,1152924351096425052,1152924351096425051,1152924348948941402, - 2305845852482046557,2305845851408304738,2305845850334562915,2305845849260821090,2305849455959610519,1152924342506490451,1152924341432748627,1152924341432748626, - 1152924339285264977,2305849454885868665,1152924337137781326,1152924336064039502,1152924336064039501,1152924333916555852,2305845837449661007,2305849453812126811, - 1152924330695330376,1152924329621588552,1152924329621588551,1152924327474104902,2305849452738384957,1152924325326621251,1152924324252879427,1152924324252879426, - 1152924322105395777,2305845825638500932,2305845824564759113,2305845823491017290,2305845822417275465,2305845821343533652,2305849451664643103,1152924314589203001, - 1152924313515461177,1152924313515461176,1152924311367977527,2305849450590901249,1152924309220493876,1152924308146752052,1152924308146752051,1152924305999268402, - 2305845809532373557,2305849449517159395,1152924302778042926,1152924301704301102,1152924301704301101,1152924299556817452,2305849448443417541,1152924297409333801, - 1152924296335591977,1152924296335591976,1152924294188108327,2305845797721213482,2305845796647471663,2305845795573729840,2305845794499988015,2305845793426246202, - 2305845792352504404,2782065071888,1152924285598173805,1152924371497522903,2305845788057537053,2305845857850755677,2305845785910053461,2305845831007210052, - 2305845783762569788,2305845782688827929,2305845803089922602,2305845780541344290,2305845779467602454,2305845778393860633,2768106428175,1152924271639530093, - 1152924282376951511,2305845774098893328,2305845850334562904,2305845771951409751,2305845823491017279,2305845769803926078,2305845768730184204,2305845795573729829, - 2305845766582700580,2305845765508958729,2305845764435216908,2754147784462,1152924257680886381,1152924268418307799,2305845760140249603,2305845869661915752, - 2305845757992765965,2305845842818370127,2305845755845282315,2305845754771540479,2305845814901082677,2305845752624056840,2305845751550315004,2305845750476573183, - 2740189140749,1152924243722242669,1152924254459664087,2305845746181605878,2305845792352504379,2734820431628,1152924238353533549,1152924240501020375, - 2305845740812896753,2305845778393860631,2729451722507,1152924232984824429,1152924235132311255,2305845735444187628,2305845764435216906,2724083013386, - 1152924227616115309,1152924229763602135,2305845730075478503,2305845750476573181,2718714304265,1152924222247406189,1152924224394893015,2305845724706769378, - 2305845792352504353,2713345595144,1152924216878697069,1152924219026183895,2305845719338060253,2305845778393860628,2707976886023,1152924211509987949, - 1152924213657474775,2305845713969351128,2305845764435216903,2702608176902,1152924206141278829,1152924208288765655,2305845708600642003,2305845750476573178, - 2697239467781,1152924200772569709,1152924202920056535,2305845703231932878,1152927938467861835,2860448225147,1152924195403860427,1152924197551347415, - 2305845697863223753,1152927489643779963,2305849459180836731,2305845694641998658,2305845693568256828,2305845692494514998,2305845691420772806,2305845690347033841, - 1152924184666442176,1152924183592700352,1152924183592700351,1152924181445216702,2675764631418,1152924179297733067,1152924192182638295,2305845681757096378, - 1152927489643779962,2305845691420776311,2305845678535874426,1152927953500247500,2305845676388387266,1152927488570038133,2305845674240903618,2305845673167161779, - 2305845672093419957,1152927951352763866,2305845669945936322,1152927950279022032,2305845667798452674,2305845666724710829,1152927487496296306,2305845664577227202, - 1152927486422554481,2305845662429743554,2305845661356001704,2305845660282259882,2305845659208518063,1152927485348812656,2305845657061034434,1152927484275070831, - 2305845654913550786,2305845653839808929,1152927483201329006,2305845651692325314,1152927943836571108,2305845649544841666,2305845648471099804,2305845647397357982, - 2305845646323616163,2305845645249874359,2305845644176135379,1152924138495543701,1152924137421801877,1152924137421801876,1152924135274318227,2629593732985, - 1152924133126834635,1152924176076510935,2305845635586197903,1152927489643779961,2305845645249874358,2305845632364975993,1152924170707801548,2305845630217488791, - 1152924168560317893,2305845628070005143,2305845626996263304,2305845625922521482,1152924164265350618,2305845623775037847,1152924162117866960,2305845621627554199, - 2305845620553812354,1152924158896641476,2305845618406328727,1152924156749157827,2305845616258845079,2305845615185103229,2305845614111361407,2305845613037619588, - 1152924151380448706,2305845610890135959,1152924149232965057,2305845608742652311,2305845607668910454,1152924146011739584,2305845605521426839,1152924143864255972, - 2305845603373943191,2305845602300201329,2305845601226459507,2305845600152717688,2305845599078975884,2588791543568,1152924092324645323,1152924129905612503, - 2305845594784008553,2305845599078975883,2583422834447,1152924086955936203,1152924089103423191,2305845589415299428,1152924124536903116,2305845587267815788, - 2576980383502,1152924080513485259,1152924083734714071,2305845582972848478,1152924122389419461,2305845580825364844,2570537932557,1152924074071034315, - 1152924077292263127,2305845576530397528,1152924118094452186,2305845574382913900,2564095481612,1152924067628583371,1152924070849812183,2305845570087946578, - 1152924115946968528,2305845567940462956,2557653030667,1152924061186132427,1152924064407361239,2305845563645495628,1152924112725743044,2305845561498012012, - 2551210579722,1152924054743681483,1152924057964910295,2305845557203044678,1152924110578259395,2305845555055561068,2544768128777,1152924048301230539, - 1152924051522459351,2305845550760593728,1152924105209550274,2305845548613110124,2538325677832,1152924041858779595,1152924045080008407,2305845544318142778, - 1152924103062066625,2305845542170659180,2531883226887,1152924035416328651,1152924038637557463,2305845537875691828,1152924099840841152,2305845535728208236, - 2525440775942,1152924028973877707,1152924032195106519,2305845531433240878,1152924097693357540,2305845529285757292,2518998324997,1152924022531426763, - 1152924025752655575,2305845524990789928,1152927938467861805,2305848986734434168,1152924017162717476,1152924016088975652,1152924016088975651,1152924013941492002, - 2508260906875,1152924011794008357,1152924019310204631,2305845514253371678,2305845690347033871,1152924007499041051,1152924006425299227,1152924006425299226, - 1152924004277815577,2305845678535873777,1152924002130331926,1152924001056590102,1152924001056590101,1152923998909106452,2305845502442211607,2305845675314648275, - 1152923995687880976,1152923994614139152,1152923994614139151,1152923992466655502,2305845673167164597,1152923990319171851,1152923989245430027,1152923989245430026, - 1152923987097946377,2305845490631051532,2305845489557309713,2305845488483567890,2305845487409826065,2305845668872197271,1152923980655495426,1152923979581753602, - 1152923979581753601,1152923977434269952,2305845666724713593,1152923975286786301,1152923974213044477,1152923974213044476,1152923972065560827,2305845475598665982, - 2305845663503488091,1152923968844335351,1152923967770593527,1152923967770593526,1152923965623109877,2305845661356004413,1152923963475626226,1152923962401884402, - 1152923962401884401,1152923960254400752,2305845463787505907,2305845462713764088,2305845461640022265,2305845460566280440,2305845459492538627,2305845655987295263, - 1152923952738207976,1152923951664466152,1152923951664466151,1152923949516982502,2305845653839811585,1152923947369498851,1152923946295757027,1152923946295757026, - 1152923944148273377,2305845447681378532,2305845650618586083,1152923940927047901,1152923939853306077,1152923939853306076,1152923937705822427,2305845648471102405, - 1152923935558338776,1152923934484596952,1152923934484596951,1152923932337113302,2305845435870218457,2305845434796476638,2305845433722734815,2305845432648992990, - 2305845431575251177,2305845430501509379,2420214077200,1152923923747178789,1152924008572786391,2305845426206542028,2305845495999760652,2305845424059058436, - 2305845469156215027,2305845421911574763,2305845420837832904,2305845441238927577,2305845418690349265,2305845417616607429,2305845416542865608,2406255433487, - 1152923909788535077,1152923920525956823,2305845412247898303,2305845488483567879,2305845410100414726,2305845461640022254,2305845407952931053,2305845406879189179, - 2305845433722734804,2305845404731705555,2305845403657963704,2305845402584221883,2392296789774,1152923895829891365,1152923906567313111,2305845398289254578, - 2305845507810920727,2305845396141770940,2305845480967375102,2305845393994287290,2305845392920545454,2305845453050087652,2305845390773061815,2305845389699319979, - 2305845388625578158,2378338146061,1152923881871247653,1152923892608669399,2305845384330610853,2305845430501509354,2372969436940,1152923876502538533, - 1152923878650025687,2305845378961901728,2305845416542865606,2367600727819,1152923871133829413,1152923873281316567,2305845373593192603,2305845402584221881, - 2362232018698,1152923865765120293,1152923867912607447,2305845368224483478,2305845388625578156,2356863309577,1152923860396411173,1152923862543898327, - 2305845362855774353,2305845430501509328,2351494600456,1152923855027702053,1152923857175189207,2305845357487065228,2305845416542865603,2346125891335, - 1152923849658992933,1152923851806480087,2305845352118356103,2305845402584221878,2340757182214,1152923844290283813,1152923846437770967,2305845346749646978, - 2305845388625578153,2335388473093,1152923838921574693,1152923841069061847,2305845341380937853,1152927941689087848,1152923835700352727,2305845338159712378, - 1152927940615346024,1152923832479127255,2305845334938486903,1152927939541604200,1152923829257901783,2305845331717261428,2305848894392636806,2305845329569781067, - 2305845328496039213,6438155982596,1152923821741705327,1152923820667967336,1152923826036676311,2305845323127326828,6343666701788,1152923816373000060, - 1152923815299258112,1152923814225512559,1152923817446741719,2305845316684875878,6322191865308,1152923809930549096,1152923808856803439,1152923811004290775, - 2305845311316166753,1152927958868957052,6008659249246,1152923803488098004,1152923805635581655,2305845305947457628,1152926873315972988,1152923800266872535, - 2305845302726232153,1152926866873522044,1152923797045647063,2305845299505006678,1152926860431071100,1152923793824421591,2305845296283781203,6008659253098, - 1152923789529454442,1152923788455712636,1152923790603196119,2305845290915072078,1152927777406588796,1152923784160745236,6454262109660,6453188367836, - 1152923780939515977,6342592959964,6341519218140,1152923777718290502,1152923776644548679,1152923775570806858,1152923785234486999,2305845278030170178, - 1152923782013261690,1152923778792036114,6008659249214,1152923769128355903,1152923768054614090,1152923772349585111,2305845270513977403,1152923771275843434, - 1152923763759650684,1152923764833392343,2305845266219010103,1152923771275843433,1152923759464683388,1152923760538425047,2305845261924042803,1152923815299258235, - 1152923756243457751,2305845258702817328,1152923779865774160,1152923751948486760,1152923753022232279,2305845254407850028,6008659249220,1152923747653523323, - 1152923746579777610,1152923748727264983,2305845249039140903,1152923780939519867,6452114626012,1152923741211068452,1152923740137330540,1152923739063588732, - 1152923743358555863,2305845241522948128,1152923740137330537,1152923734768621436,1152923735842363095,2305845237227980828,1152923777718294291,6340445476316, - 1152923729399908377,1152923728326166602,6324339348956,1152923726178682902,6442450949896,1152923724031199252,1152923731547395799,2305845226490562578, - 6441377208071,1152923719736231956,1152923720809977559,2305845222195595278,6440303466246,1152923715441264660,1152923716515010263,2305845217900627978, - 6439229724421,1152923711146297364,1152923712220042967,2305845213605660678,2305849461328320378,1152927956721473402,2201170741251,1152923704703850363, - 1152923703630108540,2305849335700526852,2305845206089471849,2195802036076,1152923699335137279,1152923707925075671,2305845201794500603,6008659253097, - 1152923695040173929,1152923693966428159,1152923696113915607,2305845196425791478,1152923702556366704,1152923690745206487,2305845193204566003,1152923702556366703, - 1152923687523981015,2305845189983340528,1152923702556366702,1152923684302755543,2305845186762115053,1152923702556366701,1152923681081530071,2305845183540889578, - 6436008498652,2305849349659170578,1152927845052323602,2170032228326,1152923673565337363,1152923672491591790,1152923671417849831,1152923670344108103, - 1152923669270370172,1152923677860304599,2305845171729729503,1152923779865778044,1152923664975398887,1152923663901657059,6323265607529,1152923661754173402, - 1152923666049144535,2305845164213536728,6339371734796,1152923657459210089,2305845160992315152,2150704879480,1152923654237980634,1152923658532951767, - 2305845156697343953,6338297992971,1152923649943017321,2305845153476122383,2143188686711,1152923646721787866,1152923651016758999,2305845149181151178, - 6337224251146,1152923642426824553,2305845145959929614,2135672493942,1152923639205595098,1152923643500566231,2305845141664958403,6336150509321, - 1152923634910631785,2305845138443736845,2128156301173,1152923631689402330,1152923635984373463,2305845134148765628,1152927846126065427,1152923627394435096, - 1152923626320697090,2305845129853802347,2119566366467,1152923623099467850,1152923628468180695,2305845125558831028,1152923741211068487,1152923618804504428, - 1152923617730762620,1152923619878246103,2305845120190121903,1152923618804504425,1152923613435795324,1152923614509536983,2305845115895154603,1152923779865778041, - 1152927939541604202,1152923608067082152,1152923606993344380,1152923610214569687,2305845109452703653,5364414157714,1152923602698376066,1152923603772118743, - 2305845105157736353,6341519218554,1152923598403408808,1152923597329663906,1152923599477151447,2305845099789027228,6008659253111,1152923593034700663, - 1152923591960954786,1152923594108442327,2305845094420318103,6008659253110,1152923587665991542,1152923586592245666,1152923588739733207,2305845089051608978, - 6008659253109,1152923582297282421,1152923581223536546,1152923583371024087,2305845083682899853,6008659253108,1152923576928573300,1152923575854827426, - 1152923578002314967,2305845078314190728,6008659253107,1152923571559864179,1152923570486118306,1152923572633605847,2305845072945481603,6008659253106, - 1152923566191155058,1152923565117409186,1152923567264896727,2305845067576772478,6008659253105,1152923560822445937,1152923559748700066,1152923561896187607, - 2305845062208063353,6008659253104,1152923555453736816,1152923554379990946,1152923556527478487,2305845056839354228,2305846046829320053,2305845054691870577, - 2305845053618132854,2305845052544386927,2305845051470649207,2041183213433,1152923544716317622,1152923543642572706,1152923551158769367,2305845046101935978, - 2305846043608094577,2305845043954452327,2305845042880714610,2305845041806968677,2305845040733230963,2030445795192,1152923533978899382,1152923532905154466, - 1152923540421351127,2305845035364517728,1152927945984054198,1152923528610187170,1152923529683932887,2305845031069550428,1152927503602423673,2305849344290461452, - 2305845027848328973,2305845026774583127,2305845025700845326,2305845024627099477,2305845023553361679,2305845022479615833,2012192184081,1152923515725288360, - 1152923514651543458,1152923525388965591,2305845017110906703,1152927503602423672,2305849339995494152,2305845013889685257,2305845012815939402,2305845011742201610, - 2305845010668455752,2305845009594717963,2305845008520972108,1998233540368,1152923501766644648,1152923500692899746,1152923511430321879,2305845003152262978, - 6329708058478,1152923496397935528,1152923495324190626,1152923497471678167,2305844997783553853,1152927945984054739,1152927947057796980,2305844994562328377, - 1152927948131538805,2305844992414844728,2305844991341102902,1152927949205280630,2305844989193619253,2305844988119877427,1152927950279022455,2305844985972393778, - 2305844984898651962,2305849451664643956,1984274892590,2305844981677426477,2305849452738385781,2305844992414844727,1969242507051,2305844977382459180, - 2305844976308717352,2305849453812127606,2305844989193619252,1963873797926,2305844972013750055,2305844970940008227,2305849454885869431,2305844985972393777, - 1958505088801,2305844966645040930,1975684957982,1152923459890714490,2305844963423815471,1953136383854,1152923456669484962,1152923492102969047, - 2305844959128848153,5357971706756,1152923452374520700,1152923453448263383,2305844954833880853,1152923452374520694,1152923449153296087,2305844951612655378, - 1152923452374517646,1152923445932070615,2305844948391429903,1152923452374517641,1152923442710845143,2305844945170204428,1152927952426506104,1152923438415873814, - 1152923439489619671,2305844940875237128,1152927951352764280,1152923434120906518,1152923435194652375,2305844936580269828,1152923602698376979,1152923429825939525, - 6339371734492,1152923427678455832,6335076767196,6336150509020,1152923424457230077,1918776645084,1152923422309746430,2305844925842855697, - 1152923420162266890,1152923419088520960,1152923430899685079,2305844921547884278,1152923425530976013,1910186710492,1152923413719811838,2305844917252921105, - 1152923411572332297,1152923410498586368,1152923415867299543,2305844912957949678,1152923424457234188,1901596775900,1152923405129877246,2305844908662986513, - 1152923402982397704,1152923401908651776,1152923407277364951,2305844904368015078,1152927839683614477,1893006841308,1152923396539942654,2305844900073051921, - 1152923394392463111,1152923393318717184,1152923398687430359,2305844895778080478,1152926869021005522,1152923389023750213,6449967142671,1152923386876270340, - 6008659248856,1152923384728782552,1152923383655040730,1152923390097495767,2305844886114404053,1881195681244,1152923379360073433,1152923378286331610, - 1152923380433819351,2305844880745694928,6448893400846,1152923373991368452,6008659248844,1152923371843880652,1152923370770138834,1152923369696397018, - 1152923375065110231,2305844872155760328,1868310779356,1152923365401429709,1152923364327687890,1152923363253946074,1152923366475175639,2305844865713309378, - 1152923365401429714,1854352135939,1152923357885236954,1152923360032724695,2305844860344600253,1152923385802524377,1152923372917622477,6445672175371, - 1152923351442785989,2305844854975891129,1152923349295302354,2305844852828407482,1152927832167421386,2305844850680928006,1840393488052,1152923343926593242, - 1152923354664015575,2305844846385956528,1152923351442790148,1835024782812,2305845234006755363,1152923337484142252,1152923336410404611,1152923335336658650, - 1152923340705371863,2305844837796021928,1152927830019938162,1826434848522,1152923329967949560,1152923328894207706,1152923332115437271,2305844831353570978, - 1152927830019938161,1819992397577,1152923323525498608,1152923322451756762,1152923325672986327,2305844824911120028,1152927830019938160,1813549946632, - 1152923317083047656,1152923316009305818,1152923319230535383,2305844818468669078,1152927830019938159,1807107495687,1152923310640596704,1152923309566854874, - 1152923312788084439,2305844812026218128,1152923452374521618,1152923305271887896,6008659253008,1152923303124403852,1152923306345633495,2305844805583767178, - 5351529255806,1152923298829439862,1152923299903182551,2305844801288799878,1152923298829436824,1152923295608215255,2305844798067574403,1152923298829440785, - 1152923291313248120,1152923292386989783,2305844793772607103,1152926862578554578,1152923287018276888,1152927843978581880,1152923284870792827,1152923288092022487, - 2305844787330156153,1152927954573989752,1152923280575825543,1152923281649571543,2305844783035188853,6338297992668,1152923276280862583,1152923275207116543, - 1152923274133374587,1152923277354604247,2305844776592737903,1152923285944534783,1152923269838407373,1152923270912153303,2305844772297770603,6446745917196, - 1152923265543439980,1152923266617186007,2305844768002803303,6449967142364,1152923261248476624,2305844764781582197,2305844763707840375,1753420404493, - 1152923256953505388,1152923262322218711,2305844759412868703,2305844765855319652,2305844757265389322,2305844756191647607,1745904211826,1152923249437312620, - 1152923253732284119,2305844751896675928,2061584308079,1152923245142345316,1152923244068603527,1152923246216091351,2305844746527966803,1152923276280862468, - 1152927837536130831,2305844743306741328,6438155978318,1152923236552410724,1152923235478668923,1152923240847382231,2305844737938032203,1152923298829440887, - 1152927950279022451,6008659248711,1152923229036222323,1152923227962476104,1152923232257447639,2305844730421839428,1152927941689087859,2305844728274360174, - 1152927941689087854,1152927940615346029,1152923220446287313,2305844723979388479,1713691952704,1152923217225057863,2305844720758163004,1710470731633, - 1152923214003832392,1152923224741254871,2305844716463195703,1152927940615346035,2305844714315716461,1152927940615346030,1152927941689087853,2305844711094486578, - 1700807050803,1152923204340155975,2305844707873261104,1697585829744,1152923201118930504,1152923210782611159,2305844703578293803,1152923285944538896, - 2305844701430810240,1152923195750221412,1152923197897709271,2305844698209584678,1152923195750225777,1152923192529000151,2305844694988359203,1152923195750225776, - 1152923189307774679,2305844691767133728,1152923195750225778,1152923186086549207,2305844688545908253,5328980677496,1152923181791578008,1152923182865323735, - 2305844684250940953,1152926856136103634,1152923177496610559,1152923176422868594,1152923175349126861,1152923178570356439,2305844677808490003,6447819659021, - 1152923171054159380,1152923172127905495,2305844673513522703,1152923175349126760,1152923167832938199,2305844670292297228,1152923175349126840,1152923164611712727, - 2305844667071071753,1152923291313243903,6008659253007,1152923159242999302,1152923161390487255,2305844661702362628,1152927481053845350,2305844659554883431, - 6008659248640,1152923152800552660,1152923156021778135,2305844655259911678,1152927934172894654,2305844653112432485,6008659248634,1152923146358101716, - 1152923149579327191,2305844648817460728,1152927932025411006,2305844646669981539,6008659248628,1152923139915650772,1152923143136876247,2305844642375009778, - 1152927929877927358,2305844640227530593,6008659248622,1152923133473199828,1152923136694425303,2305844635932558828,1152927925582960062,2305844633785079645, - 6008659248616,1152923127030748884,1152923130251974359,2305844629490107878,1152927923435476414,2305844627342628699,6008659248610,1152923120588297940, - 1152923123809523415,2305844623047656928,1152927927730443710,2305844620900177759,1152923115219588820,1152923117367072471,2305844617678947803,1152927921287992766, - 2305844615531468633,1152923109850879700,1152923111998363351,2305844612310238678,6286758381056,1152923105555912403,1152923106629654231,2305844608015271378, - 6285684639226,1152923101260945107,1152923102334686935,2305844603720304078,6284610897396,1152923096965977811,1152923098039719639,2305844599425336778, - 6283537155566,1152923092671010515,1152923093744752343,2305844595130369478,6282463413724,1152923088376043219,1152923089449785047,2305844590835402178, - 6281389671912,1152923084081075923,1152923085154817751,2305844586540434878,6280315930082,1152923079786108627,1152923080859850455,2305844582245467578, - 6279242188247,1152923075491141331,1152923076564883159,2305844577950500278,1152927816061294014,2305844575803021047,1152928016851015601,1152923069048690609, - 1152923067974948785,1152923066901206961,1152923065827465137,1152923064753723313,1152923063679981489,1152923062606239665,1152923061532497841,1152923060458756017, - 1152923059385014193,1152923058311272369,1152923057237530545,1152923056163788721,1152923055090046897,1152923054016304594,2305844557549410224,2305844556475668349, - 2305844556475668383,2305844556475668382,1152923048647591327,2305844556475668381,1152923046500107677,2305844556475668380,1152923044352624027,1152923043278882208, - 2305844556475668334,2305844556475668333,1152923040057656727,1152923038983914904,1152923037910173106,1152923037910173148,1531155842451,1152923038983914905, - 1152927962090177936,1527934616977,1152923038983914912,1152923030393983698,1152923029320238494,1152923028246496668,1152923027172754842,2305844530705859982, - 1152923029320238495,1152923023951529372,1152923022877787546,2305844526410892680,1152923023951529374,1152923019656562074,2305844523189667204,1152923019656562076, - 2305844521042183553,1152923042205140758,1152923014287852950,2305844517820958079,1152923014287852951,2305844515673474428,1152923009992890066,1152923072269915863, - 2305844512452248953,1152927818208777662,2305844510304769785,1152923054016304584,2305844508157286320,2305844507083544448,1152928016851015450,2305844504936056178, - 1152923069048690456,2305844502788572528,1152922997107986701,1152923069048690588,2305844499567347056,1152926192563655483,1152922992813016427,2305844496346121581, - 2305844497419866381,1484984948188,2305844493124896104,1152922987444307317,2305844507083544495,1152928016851015554,2305844488829928803,1152923069048690560, - 2305844486682445153,1152922981001859337,4682588100060,2305844483461219678,1152922977780631022,1472100042084,1152922975633151698,1152923006771664599, - 2305844478092510553,1152927824651228606,2305844475945031423,1152923054016304588,2305844473797547952,2305844472723806121,1152928016851015489,2305844470576317778, - 2305844469502575980,6488621848028,6487548101966,1152922961674507047,1152922960600761679,1152923067974948678,2305844463060125007,1152923066901206809, - 2305844460912641353,1152923065827464984,2305844458765157703,1152923064753723205,2305844456617674053,1152923063679981380,2305844454470190403,1152923062606239555, - 2305844452322706753,1152923061532497730,2305844450175223103,1152922944494637340,2305844448027739467,1152928016851015480,2305844445880255826,1152922940199670461, - 2305844443732772155,2305848159953228583,1432371598812,2305844440511546679,1152922934830957909,2305844472723806017,1152928016851015593,2305844436216579378, - 1152922930535994053,1152922930535994002,2305844432995353903,1152922930535994057,2305844430847870253,1152922930535993994,2305844428700386603,1152923069048690577, - 2305844426552902960,1152922920872317584,2305844424405419305,2305844437290321210,1152922917651092157,2305844421184193829,1152923067974948782,2305844419036710192, - 1152923066901206913,2305844416889226528,1152923065827465088,2305844414741742878,1152923064753723309,2305844412594259228,1152923063679981484,2305844410446775578, - 1152923062606239659,2305844408299291928,1152923061532497834,2305844406151808278,1152922900471223104,2305844404004324642,1152922900471223089,2305844401856840978, - 1152923066901206941,2305844399709357344,1152923065827465116,2305844397561873678,2305844396488131867,2305844395414390041,2305844394340648215,2305844393266906389, - 1152922887586321211,2305844391119422736,2305848113782330053,2305844388971942601,2305844387898200714,2305844386824458896,2305844385750717117,2305844384676975424, - 2305844383603233585,2305844382529491771,1372242056668,2305844380382004486,1152922874701415936,1369020826931,1152922872553936594,1152922972411926231, - 2305844375013295353,1152927822503744958,2305844372865816317,1152923054016304602,2305844370718332848,2305844369644590913,1152928016851015585,2305844367497102578, - 1152923069048690592,2305844365349618928,1152923067974948767,2305844363202135278,1152923066901206942,2305844361054651628,2305846288421227552,2305844358907169801, - 1348619736540,1152922852152837550,2305844355685942506,1152923064753723293,2305844353538458853,1152922847857873703,5264556168668,2305844350317233378, - 1152922844636644597,2305844359980909998,1152928016851015581,2305844346022266098,2305844344948524396,1152923067974948763,2305844342801040603,1152923066901206938, - 2305844340653556953,1152922834972968158,1152922833899229893,1152927982491277217,1152927995376177056,2305844335284847828,1152922829604263857,2305844333137364210, - 1152927982491277216,1152925799574149020,2305844329916138703,1152922824235550129,2305844327768655056,1152925799574149019,2305844325621173952,1152922819940582832, - 2305844323473687755,1152925799574149018,2305844321326206646,1152922815645615535,2305844319178720455,1152922813498135186,2305844317031236821,2305844359980909997, - 1152922810276910993,2305844313810014112,1152922808129426064,2305844311662527681,1152922810276909330,2305844309515044028,1152922855374066314,2305844307367560378, - 1152922855374066491,2305844305220076728,1152922855374066496,2305844303072593078,1152922855374066481,2305844300925109428,2305844388971942544,2305844298777628946, - 2305844297703887498,2305844296630145851,2305844295556404032,2305844294482662193,1284195227100,2305844292335174834,1152922786654586362,1280973997279, - 1152922784507107026,1152922869332711127,2305844286966465703,1152927820356261310,2305844284818986747,1152923054016304592,2305844282671503280,2305844281597761449, - 1152928016851015576,2305844279450272928,1152926499653818153,1152927894444447180,2305844276229052224,1152927893370705360,2305844274081563803,1152927892296963538, - 2305844271934080153,1152927891223221715,2305844269786596503,1152927479980103483,2305844267639112853,1152927478906361658,2305844265491629203,2305844264417889247, - 1152922758737300476,2305844262270403741,1152922756589815217,2305844260122920094,1152924805289219993,1152922753368589452,1248761746908,2305844255827952778, - 1152922750147364003,2305844281597761345,6008659253144,1152922746926143409,2305844250459243654,2305844249385501935,1152922743704914833,5991479383960, - 1152922741557434289,2305844245090534534,1152923069048690591,2305844242943050878,1152922737262462082,6006511769496,1152922735114983345,2305844238648083590, - 1152923069048690590,2305844236500599928,1152922730820011131,1152922729746271118,2305844233279374465,5995774351256,1152922726525048753,2305844230058148998, - 1152923069048690589,2305844227910665328,1152922722230076533,5987184416664,1152922720082597809,2305844223615698054,2305844222541956460,1152922716861367405, - 5986110674840,1152922714713888689,2305844218246988934,1152923069048690587,2305844216099505253,1152922710418916456,5996848093080,1152922708271437745, - 2305844211804537990,1152923069048690586,2305844209657054303,1152922703976465506,1152922702902728598,2305844206435828851,2305846185342012305,2305844204288350102, - 1194000913884,2305844202140861530,1152922696460273140,1190779683975,1152922694312793810,1152922781285881559,2305844196772152403,1152927813913810366, - 2305844194624673525,1152923054016304583,2305844192477190064,1152928016851015531,2305844190329701453,1152923069048690540,2305844188182217803,1152923063679981428, - 2305844186034734153,1152923062606239603,2305844183887250503,1152923061532497778,2305844181739766853,1152923060458755953,2305844179592283203,2196875777900, - 1168231110108,1152922671764210753,1152923067974948728,2305844174223574089,1152923066901206903,2305844172076090428,1152923065827465078,2305844169928606778, - 1152923064753723253,2305844167781123128,1152922662100534336,2305844165633639486,1152927957795215227,1152922658879313785,1152922657805571964,1152922656731825204, - 2305844558623152048,2305848982439461935,1152928016851015542,2305844157043704878,1152923069048690549,2305844154896221228,1152923067974948724,2305844152748737578, - 1152923066901206897,2305844150601253928,1152923065827465072,2305844148453770278,1152923064753723246,2305844146306286628,1152923063679981421,2305844144158802978, - 1152923261248472711,1152922637404472352,2305844140937577520,2305848981365720111,2305844138790093869,2305844137716352043,2305844136642610217,1152927475685136236, - 2305844134495131503,2305849454885869020,1152927474611389462,2305844131273901079,1152922625593312687,2305844129126417433,1152922623445829192,2305844126978933789, - 1152923054016304593,2305844124831455152,2305848978144494606,1152928016851015582,2305844121610224653,2305844120536482927,1152923067974948764,2305844118388999178, - 1152923066901206939,2305844116241515528,1152926660715090749,1152922609487189907,1152922608413443078,2305844111946548240,2305844142011319345,2305844109799065160, - 2305844108725322756,1098437891548,2305844106577839106,1152922600897250383,1152922658879309859,1152922598749771644,1152922597676024884,1152928016851015457, - 2305844100135388173,1152923069048690464,2305844097987904503,1152923067974948639,2305844095840420853,1152923066901206814,2305844093692937203,1152922588012348420, - 2305844091545453561,2305844102282871812,1080184280540,2305844088324228079,1152922582643639784,1076963050492,1152922580496160466,1152922691091568343, - 2305844082955518953,1152927811766326718,2305844080808040179,2305848977070752815,1152928016851015583,2305844077586809828,1152923067974948753,2305844075439326178, - 1152923066901206928,2305844073291842528,1152923065827465103,2305844071144358878,1152923064753723278,2305844068996875228,1152923063679981453,2305844066849391578, - 1152923062606239628,2305844064701907928,1152923061532497803,2305844062554424278,1152923060458755978,2305844060406940628,1152923059385014153,2305844058259456978, - 1152923058311272328,2305844056111973328,1152923057237530503,2305844053964489678,1152923056163788678,2305844051817006028,2305848975997010991,2305849502130509276, - 1152922543988938673,2305844047522038729,1152923067974948649,2305844045374555078,1152923066901206824,2305844043227071428,1152923065827464999,2305844041079587778, - 1152923064753723174,2305844038932104128,1152923063679981349,2305844036784620478,1152923062606239524,2305844034637136828,1152923061532497699,2305844032489653178, - 1152923060458755874,2305844030342169528,1152923059385014049,2305844028194685878,1152923058311272224,2305844026047202228,1152923057237530399,2305844023899718578, - 1152923056163788574,2305844021752234928,1152922516071646154,1152922514997907803,2305844159191193048,2305844017457267683,1152923069048690572,2305844015309783978, - 2305844014236042209,2305844013162300383,2305844012088558557,2305844011014816731,2305848974923269167,1152927496086230925,2305844007793596319,6008659248033, - 1152922501039265713,2305844004572365731,1152923067974948747,2305844002424882078,1152923066901206922,2305844000277398428,1152923065827465097,2305843998129914778, - 1152923064753723272,2305843995982431128,1152922490301842340,1152922489228104397,2305843992761205676,2305848973849527343,2305843990613722083,2305843989539980201, - 2305843988466238433,2305843987392496607,2305843986318754781,2305843985245012955,1152922479564424086,2305848972775785519,1152922502113007537,2305843980950045578, - 2305843979876303785,1152923067974948743,2305843977728820103,1152923066901206918,2305843975581336453,1152923065827465093,2305843973433852803,1152923064753723268, - 2305843971286369153,1152922465605780363,1152927500381198221,2305843968065148831,6008659247996,1152922461310818225,2305843964843918243,2305843963770176453, - 2305843962696434627,2305843961622692801,2305843960548950975,1152922454868362110,1152922453794623974,2305843957327725459,2305844078660552017,1152923067974948769, - 2305843954106499953,1152923066901206944,2305843951959016303,1152923065827465119,2305843949811532653,1152923064753723294,2305843947664049003,1152923063679981469, - 2305843945516565353,1152923062606239644,2305843943369081703,1152923061532497819,2305843941221598053,1152923060458755994,2305843939074114403,1152927467095197095, - 2305843936926630753,1152922431246045893,2305843934779147122,1152927466021455271,2305843932631663457,1152922426951078546,2305843930484179805,1152927464947713447, - 2305843928336696161,1152922422656111248,2305843926189212505,1152927463873971623,2305843924041728865,2305844159191193040,1152928016851015494,2305843920820503378, - 1152927462800229809,2305843918673019728,1152923067974948783,2305843916525536078,1152927461726487983,2305843914378052428,1152922408697463635,1152922407623725888, - 2305843911156826965,1152927460652746151,2305843909009343345,1152928016851015598,2305843906861859886,2305843905788118312,2305843904714376013,1152923066901206945, - 2305843902566892353,1152923065827465120,2305843900419408703,1152923064753723295,2305843898271925053,1152923063679981470,2305843896124441403,1152922390443852613, - 1152922389370114698,2305843892903215943,1152927459579004327,2305843890755732337,1152922385075143481,1152922384001401674,1152922382927663931,2305843886460764982, - 1152927458505262503,2305843884313281377,2305844159191193043,2305843882165798129,2305843881092056303,2305843880018314477,2305843878944572651,1152922373263983406, - 1152922372190245681,2305843875723346736,1152922433393529545,2305843873575863078,2305848962038367279,2305843871428379985,2305843870354637680,2305843869280895854, - 2305843868207154028,2305843867133412202,2305843866059670376,2305843864985928550,2305843863912186724,2305843862838444898,1152922357157860029,2305843860690961188, - 2305849512867927516,1152922353936635825,2305843857469735843,2305843856395993968,2305843855322252142,2305843854248510316,2305843853174768490,1152922347494183719, - 2305843851027284760,2305843906861859868,2305843848879801640,2305843847806059341,1152923066901206825,2305843845658575628,1152922339977990766,2305843843511091983, - 2305843906861859666,2305843841363608872,2305843840289866573,2305843839216124895,1152922333535539866,2305843837068641032,2305843845658575681,1152923065827465000, - 2305843833847415553,1152923064753723175,2305843831699931903,1152923063679981350,2305843829552448253,1152922323871863267,2305843827404964610,2305848960964625455, - 2305843825257481028,2305843824183739688,2305843823109997389,2305843822036255711,2305843820962513885,2305843819888772059,2305843818815030233,1152922313134445206, - 2305843816667546361,1152923069048690564,2305843814520062734,2305843813446320973,1152923066901206943,2305843811298837228,2305849473139480028,1152922304544507313, - 2305843808077611790,2305843807003870029,2305843805930128619,1152922300249539306,1152922299175800588,2305843802708902639,2305843814520062787,2305843800561419085, - 2305843799487677163,1152923065827465118,2305843797340193503,2305843796266452196,1152923063679981468,2305843794118968028,1152922288438382198,2305843791971484386, - 1152923069048690565,2305843789824000835,2305843788750259021,2305843787676517131,2305843786602775296,2305843785529033470,2305843784455291644,2305849004988045189, - 1152922277700961713,2305843781234066243,2305843780160324429,1152923066901206821,2305843778012840653,1152923065827464996,2305843775865357003,1152923064753723171, - 2305843773717873353,1152923063679981346,2305843771570389703,1152922265889800913,1152926636019029928,1152922263742322579,1152922262668575428,2305843766201680600, - 2305843789824000759,2305843764054197069,2305843762980455179,2305843761906713344,2305843760832971518,2305843759759229692,2305843781234066167,2305843757611746125, - 2305843756538004172,2305843755464262346,2305843754390520520,2305843753316778694,1152922247636189882,1152926636019028797,1152922245488711571,1152922244414964403, - 2305843747948069568,1152923066901205231,2305843745800585941,1152923065827462919,2305843743653102253,1152923064753721090,2305843741505618603,1152923063679979261, - 2305843739358134953,1152923066901204728,2305843737210651341,1152923065827462899,2305843735063167653,1152923064753721070,2305843732915684003,1152923063679979241, - 2305843730768200353,1152922225087611559,2305843814520062711,2305843727546975053,2305843726473233163,2305843725399491328,2305843724325749502,2305843723252007676, - 1152922217571418782,2305849004988045188,1152922215423935921,2305843718957040375,2305843717883298637,2305843716809556684,2305843715735814858,2305843714662073032, - 2305843713588331206,1152922207907742359,1152922206834003761,2305843710367105711,2305844159191193036,1152928016851015569,2305843707145880203,1152923069048690576, - 2305843704998396553,2305843703924655045,1152922198244069981,2305843701777171084,2305844159191193031,2305843699629687434,2305843698555945608,2305843697482204101, - 1152922191801619035,2305843695334720132,2305844159191193042,1152928016851015599,2305843692113494653,1152923069048690606,2305843689966011003,2305843688892269537, - 2305843687818527711,2305843686744785885,1152928007187338046,1152922179990460307,1152922178916713078,2305843682449818238,1152923059385014174,2305843680302334930, - 1152923058311272349,2305843678154850928,1152923057237530524,2305843676007367278,1152923056163788699,2305843673859883628,1152922168179295150,1152922167105553412, - 2305843670638658162,2305847779848622797,2305843668491178470,2305843667417436869,2305843666343694994,2305843665269953168,2305843664196211520,2305843663122469514, - 2305843662048727867,2305843660974986033,2305843659901244105,2305843658827502269,2305843657753760551,2305843656680018542,2305843655606276762,2305843654532534755, - 2305843653458793110,2305843652385050380,2305843651311308406,2305843650237563586,2305843649163821745,2305843648090083121,2305843647016342109,2305843645942600283, - 2305843644868854388,2305843643795112964,633507681756,2305843641647628903,1152922135967040485,1152928016851014472,2305843638426403812,2305843637352662077, - 2305843636278920251,2305843635205178425,2305843634131436599,2305843633057694792,2305843631983952966,2305843630910211140,2305843629836469314,1152923059385014128, - 2305843627688985153,1152923058311272303,2305843625541501503,1152923057237530478,2305843623394017853,1152923056163788653,2305843621246534203,1152923740137330538, - 1152922114492203577,2305843638426403619,2305843616951567421,2305843615877825595,2305843614804083769,2305843613730341943,2305843612656600136,2305843611582858310, - 2305843610509116484,2305843609435374658,1152923676786559010,1152922102681043502,2305843606214148663,2305843638426403755,2305843604066665546,1152927831093679564, - 592705492844,591631750620,1152922095164856184,1152922096238598004,2305843597624214053,1152922091943626160,2305843595476730409,1152922095164856183, - 1152922096238598003,2305843592255504928,1152922086574917039,2305843590108021281,1152922095164856182,1152922096238598002,2305843586886795803,1152922081206207918, - 2305843584739312156,1152922095164856181,1152922096238598001,2305843581518086678,1152922075837498797,2305843579370603031,1152923742284814201,1152922072616277866, - 1152922071542530578,2305843575075635755,2305843638426403730,2305843572928152650,2305843571854410274,2305843570780668445,2305843569706926616,2305843568633184787, - 1152922598749771626,1152922061878854152,2305843565411959310,2305843638426403722,2305843563264476234,2305843562190733858,2305843561116992029,2305843560043250200, - 2305843558969508371,1152922657805571946,1152922052215177727,2305843555748282885,2305843638426403747,2305843553600799818,2305843552527057981,2305843551453316155, - 2305843550379574329,2305843549305832503,1152922043625244593,2305843547158348284,2305843638426403785,2305843545010865213,2305843543937123387,2305843542863381561, - 2305843541789639735,2305843540715897928,2305843539642156102,2305843538568414276,2305843537494672450,2305843536420930112,2305843535347188286,2305843534273446460, - 2305843533199704634,1152923609140828010,1152922026445373927,2305843529978479092,1152922024297895804,1152923780939519436,1152927490717521786,2305843525683511778, - 2305843524609770543,1152928016851015545,2305843522462286303,1152923069048690552,2305843520314802653,1152923067974948718,2305843518167319003,5403068863964, - 1152922011412989858,1152922010339246553,2305843513872351715,6451040884188,1152922007118026192,1152927456357783416,2305843509577384403,1152922003896797219, - 1152922007118026170,1152927477832619896,2305843505282417103,1152921999601833849,2305843503134933456,2305843502061192239,1152928016851015543,2305843499913707978, - 1152927951352763852,2305843497766229878,2305843496692488053,1152921991011894705,2305843494544998856,1152923067974948723,2305843492397515203,6447819658716, - 1152921985643190130,6446745916892,1152921983495700927,1152921983495702327,2305843485955064253,1152921985643190128,1152921979200739188,2305843482733838779, - 1152927945984055157,1152921975979513716,2305843479512613304,1152921973832025519,2305843477365129665,1152921971684540884,1152923066901206898,2305843474143904193, - 1152923065827465073,2305843471996420528,1152923064753723248,2305843469848936878,1152923063679981423,2305843467701453228,1152921962020870008,2305843465553969586, - 1152921959873382166,2305843463406485973,1152928016851015540,2305843461259002493,1152923069048690550,2305843459111518628,1152923067974948725,2305843456964034978, - 2305849453812127196,2305849447369676252,444529115551,1152921948062221743,2305843451595325856,1152921949135963566,2305843449447842203,1152923298829440888, - 1152921942693511577,2305843446226616742,1152927455284041591,1152923261248476627,2305843443005391253,2305843441931650095,2305843440857908269,2305843439784166443, - 2305843438710424617,1152923066901206899,2305843436562940303,1152921930882352666,2305843434415456662,2305843607287890488,2305843432267973136,2305843431194231303, - 2305843430120489470,2305843429046749105,2305843427973005798,1152921922292422524,416611833308,505732399491,1947767669122,438086664577, - 1677184729472,2305843420456812939,1152921914776225250,409095635531,1152921912628745938,1152922577274934999,2305843415088103803,1152927809618843070, - 2305843412940625649,1605244027255,1152923054016304601,2305843409719400368,2305843408645658484,2305843407571911725,1152923069048690546,2305843405424427378, - 1152923067974948721,2305843403276943728,1152923066901206896,2305843401129460078,1152923069048690543,2305843398981976434,2305843397908234714,1152923066901206893, - 2305843395760750953,1152921890080162156,2305843406498169930,1152923067974948715,2305843391465783653,1152923066901206890,2305843389318300003,1152921883637711206, - 1152921882563969398,1152921883637711207,1152921880416491369,1152921883637711212,1152921878269007722,2305843381802107229,1152921876121519716,373662155101, - 384399573336,1152921872900298615,2305843376433398105,366145962335,1152921869679068807,1152921868605331154,1152921909407520471,2305843371064688978, - 2305844556475668330,2305844556475668329,1152921863236616527,1152927939541603792,2305843365695980961,1152921860015391053,1152921858941649270,1152921860015391054, - 1152921860015391055,2305843360327270728,2305843359253528909,2305843358179787081,1152921852499199766,1152921851425461970,1152921865384105687,2305843353884819778, - 1152927454210295203,2305843351737341872,1152928016851015544,2305843349589852478,1152923069048690551,2305843347442368828,1152923067974948726,2305843345294885178, - 1152923066901206901,2305843343147401528,1152923065827465076,2305843340999917878,1152923064753723251,2305843338852434228,1152923063679981426,2305843336704950578, - 1152923062606239601,2305843334557466928,1152923061532497776,2305843332409983278,1152923060458755951,2305843330262499628,1152923059385014126,2305843328115015978, - 1152923058311272301,2305843325967532328,1152921820286943606,2305843350663599580,2305843322746306877,2305843321672565051,2305843320598823225,2305843319525081399, - 2305843318451339573,2305843317377597747,2305843316303855921,2305843315230114095,2305843314156372269,2305843313082630443,2305843312008888617,2305843310935146791, - 1152921805254557989,1152927942762826564,298500229214,1152921802033332504,296352743703,1152923782013261692,1152921798812108872,2318208603612, - 1152921796664629096,1152921795590887276,289910292753,1152921793443397926,287762809107,1152921791295919009,1794222593500,1947767669001, - 5395552665864,1152921787000947062,2305843290534052106,1152921784853468882,1152921848204236503,2305843287312826628,6553046357905,1152921780558501588, - 1152921781632243415,2305843283017859328,6551972616080,1152921776263534292,1152921777337276119,2305843278722892028,6550898874255,1152921771968566996, - 1152921773042308823,2305843274427924728,6549825132430,1152921767673599700,1152921768747341527,2305843270132957428,6548751390605,1152921763378632404, - 1152921764452374231,2305843265837990128,6547677648780,1152921759083665108,1152921760157406935,2305843261543022828,6546603906955,1152921754788697812, - 1152921755862439639,2305843257248055528,6545530165130,1152921750493730516,1152921751567472343,2305843252953088228,6544456423305,1152921746198763220, - 1152921747272505047,2305843248658120928,6543382681480,1152921741903795924,1152921742977537751,2305843244363153628,6542308939655,1152921737608828628, - 1152921738682570455,2305843240068186328,6541235197830,1152921733313861332,1152921734387603159,2305843235773219028,6540161456005,1152921729018894036, - 1152921730092635863,2305843231478251728,6539087714180,1152921724723926740,1152921725797668567,2305843227183284428,6538013972355,1152921720428959444, - 1152921721502701271,2305843222888317128,6536940230530,1152921716133992148,1152921717207733975,2305843218593349828,6535866488721,1152921711839024851, - 1152921712912766679,2305843214298382528,6534792746896,1152921707544057555,1152921708617799383,2305843210003415228,6533719005071,1152921703249090259, - 1152921704322832087,2305843205708447928,6532645263246,1152921698954122963,1152921700027864791,2305843201413480628,6531571521421,1152921694659155667, - 1152921695732897495,2305843197118513328,6530497779596,1152921690364188371,1152921691437930199,2305843192823546028,6529424037771,1152921686069221075, - 1152921687142962903,2305843188528578728,6528350295946,1152921681774253779,1152921682847995607,2305843184233611428,6527276554121,1152921677479286483, - 1152921678553028311,2305843179938644128,6526202812296,1152921673184319187,1152921674258061015,2305843175643676828,6525129070471,1152921668889351891, - 1152921669963093719,2305843171348709528,6524055328646,1152921664594384595,1152921665668126423,2305843167053742228,6522981586821,1152921660299417299, - 1152921661373159127,2305843162758774928,6521907844996,1152921656004450003,1152921657078191831,2305843158463807628,6520834103171,1152921651709482707, - 1152921652783224535,2305843154168840328,6519760361346,1152921647414515411,1152921648488257239,2305843149873873028,6268504774358,1152927774185363150, - 1152927775259104975,137438959318,1152921642045806287,1152921640972064463,134217733846,1152921638824580815,1152921637750838991,130996508374, - 1152921635603355343,1152921634529613519,127775282902,1152921632382129871,1152921631308388047,124554057430,1152921629160904399,1152921628087162575, - 121332831958,1152921625939678927,1152921624865937103,118111606486,1152921622718453455,1152921621644711631,1152927919140509118,2305843121956591447, - 1152927916993025470,2305843119809107797,1152927914845541822,2305843117661624147,1152927912698058174,2305843115514140497,1152927910550574526,2305843113366656847, - 1152927908403090878,2305843111219173197,1152927906255607230,2305843109071689547,1152927904108123582,2305843106924205897,1152921636677091454,1152921600169869432, - 1152921599096127605,1152921598022385778,1152921596948643951,1152921595874902124,1152921594801166032,1152921593727418472,1152921636677091457,1152921591579934840, - 1152921590506193013,1152921589432451186,1152921588358709359,1152921587284967532,1152921586211225727,1152921585137483878,2305843088670589010,1152921639898316929, - 1152921581916258424,1152921580842516597,1152921579768774770,1152921578695032943,1152921577621291116,1152921576547549308,1152921575473807460,2305843079006912585, - 1152921581916258427,1152921572252582005,1152921571178840178,1152921570105098351,1152921569031356524,1152921567957614713,1152921566883872866,2305843070416977984, - 1152921572252582008,1152921563662647410,1152921562588905583,1152921561515163756,1152921560441421942,1152921559367680096,2305843062900785208,1152921563662647413, - 1152921556146454639,1152921555072712812,1152921553998970995,1152921552925229150,2305843056458334257,1152921556146454642,1152921549704003692,1152921548630261872, - 1152921547556520028,2305843051089625131,1152921549704003695,1152921544335294573,1152921543261552730,2305843046794657830,6008659252950,1152921540040327202, - 34359738498,1152927790291490527,1152921536819107549,1152921535745365724,1152921534671623898,1152921533597882073,1152928061948172209,2305843036057240819, - 1152928060874425777,2305843033909755928,1152928059800683952,2305843031762272278,1152928058726942127,2305843029614788628,1152921523934199834,1152921522860459021, - 1152928023293466545,2305843025319822349,1152928022219720113,2305843023172337678,1152928021145978288,2305843021024854028,1152928020072236463,2305843018877370378, - 1152928018998493192,7516192784,2305849288455886555,1152927776332846801,1152927776332840966,1152921508901814277,2305843012434919455,2305843010287435778 + 18,0,22,0,19,0,87,0, + 84,0,85,0,108,0,13,0, + 4294967294,0,2147483648,0,104,0,88,0, + 14,0,92,0,10,0,11,0, + 12,0,15,0,17,0,20,0, + 21,0,1073741824,0,1152921504606846976,0,23,0, + 24,0,25,0,1152927814987552509,2305848999619335925,1152927493938747133,2305848997471852277, + 1152927491791263485,2305848995324368629,1152927489643779837,2305848993176884981,1152927487496296189,2305848991029401333,1152927485348812541,2305848988881917685, + 1152927483201328893,2305848986734434037,1152927481053845245,2305848984586950389,1152927478906361597,2305848982439466741,1152927476758877949,2305848980291983093, + 1152927474611394301,2305848978144499445,1152927472463910653,2305848975997015797,1152927470316427005,2305848973849532149,1152927468168943357,2305848971702048501, + 1152927466021459709,2305848969554564853,1152927797807683325,2305848967407081190,1152927461726492413,2305848965259597543,1152927459579008765,2305848963112113896, + 1152927457431525117,2305848960964630249,1152927455284041469,2305848958817146602,1152927453136557821,2305848956669662955,1152927450989074173,1152927449915332349, + 2305848953448437484,1152927447767848701,2305848951300953837,1152927445620365053,2305848949153470190,1152927443472881405,2305848947005986543,1152927441325397757, + 2305848944858502896,1152927439177914109,2305848942711019249,1152927437030430461,2305848940563535602,1152927434882946813,2305848938416051955,1152927781701555965, + 2305848936268568279,1152927430587979517,2305848934121084632,1152927428440495869,2305848931973600985,1152927426293012221,2305848929826117338,1152927424145528573, + 2305848927678633691,1152927421998044925,2305848925531150044,1152927419850561277,2305848923383666421,1152927417703077629,2305848921236182749,1152927415555593981, + 2305848919088699102,1152927413408110333,2305848916941215455,1152927411260626685,2305848914793731808,1152927409113143037,2305848912646248161,1152927406965659389, + 2305848910498764514,1152927404818175741,2305848908351280867,1152927402670692093,2305848906203797220,1152927772037879549,2305848904056313566,1152927398375724797, + 2305848901908829902,1152927396228241149,2305848899761346272,1152927394080757501,2305848897613862607,1152927391933273853,2305848895466378978,1152927389785790205, + 2305848893318895312,1152927387638306557,2305848891171411665,1152927385490822909,2305848889023928018,1152927383343339261,2305848886876444375,1152927381195855613, + 2305848884728960723,1152927379048371965,2305848882581477081,1152927376900888317,2305848880433993428,1152927374753404669,2305848878286509787,1152927372605921021, + 2305848876139026133,1152927370458437373,2305848873991542517,1152927754858010365,2305848871844058814,1152927366163470077,2305848869696575167,1152927364015986429, + 2305848867549091520,1152927361868502781,2305848865401607873,1152927359721019133,2305848863254124226,1152927357573535485,2305848861106640579,1152927355426051837, + 2305848858959156932,1152927353278568189,2305848856811673285,1152927351131084541,2305848854664189638,1152927348983600893,2305848852516705991,1152927346836117245, + 2305848850369222344,1152927344688633597,2305848848221738697,1152927342541149949,2305848846074255050,1152927340393666301,2305848843926771403,1152927338246182653, + 2305848841779287756,1152927737678141181,2305848839631804078,1152927333951215357,2305848837484320431,1152927331803731709,2305848835336836784,1152927329656248061, + 2305848833189353137,1152927327508764413,2305848831041869490,1152927325361280765,2305848828894385843,1152927323213797117,2305848826746902196,1152927321066313469, + 2305848824599418549,1152927318918829821,2305848822451934902,1152927316771346173,2305848820304451255,1152927314623862525,2305848818156967608,1152927312476378877, + 2305848816009483961,1152927310328895229,2305848813862000314,1152927308181411581,2305848811714516667,1152927306033927933,2305848809567033020,1152927720498271997, + 2305848807419549342,1152927301738960637,2305848805272065695,1152927299591476989,2305848803124582048,1152927297443993341,2305848800977098401,1152927295296509693, + 2305848798829614754,1152927293149026045,2305848796682131107,1152927291001542397,2305848794534647460,1152927288854058749,2305848792387163813,1152927286706575101, + 2305848790239680166,1152927284559091453,2305848788092196519,1152927282411607805,2305848785944712872,1152927280264124157,2305848783797229225,1152927278116640509, + 2305848781649745578,1152927275969156861,2305848779502261931,1152927273821673213,2305848777354778284,1152927703318402813,2305848775207294606,1152927269526705917, + 2305848773059810959,1152927267379222269,2305848770912327312,1152927265231738621,2305848768764843665,1152927263084254973,2305848766617360018,1152927260936771325, + 2305848764469876371,1152927258789287677,2305848762322392724,1152927256641804029,2305848760174909077,1152927254494320381,2305848758027425430,1152927252346836733, + 2305848755879941783,1152927250199353085,2305848753732458136,1152927248051869437,2305848751584974489,1152927245904385789,2305848749437490842,1152927243756902141, + 2305848747290007195,1152927241609418493,2305848745142523548,1152927686138533629,2305848742995039870,1152927237314451197,2305848740847556223,1152927235166967549, + 2305848738700072576,1152927233019483901,2305848736552588929,1152927230872000253,2305848734405105282,1152927228724516605,2305848732257621635,1152927226577032957, + 2305848730110137988,1152927224429549309,2305848727962654341,1152927222282065661,2305848725815170694,1152927220134582013,2305848723667687047,1152927217987098365, + 2305848721520203400,1152927215839614717,2305848719372719753,1152927213692131069,2305848717225236106,1152927211544647421,2305848715077752459,1152927209397163773, + 2305848712930268812,1152927668958664445,2305848710782785134,1152927205102196477,2305848708635301487,1152927202954712829,2305848706487817840,1152927200807229181, + 2305848704340334193,1152927198659745533,2305848702192850546,1152927196512261885,2305848700045366899,1152927194364778237,2305848697897883252,1152927192217294589, + 2305848695750399605,1152927190069810941,2305848693602915958,1152927187922327293,2305848691455432311,1152927185774843645,2305848689307948664,1152927183627359997, + 2305848687160465017,1152927181479876349,2305848685012981370,1152927179332392701,2305848682865497723,1152927177184909053,2305848680718014076,1152927651778795261, + 2305848678570530398,1152927172889941757,2305848676423046751,1152927170742458109,2305848674275563104,1152927168594974461,2305848672128079457,1152927166447490813, + 2305848669980595810,1152927164300007165,2305848667833112163,1152927162152523517,2305848665685628516,1152927160005039869,2305848663538144869,1152927157857556221, + 2305848661390661222,1152927155710072573,2305848659243177575,1152927153562588925,2305848657095693928,1152927151415105277,2305848654948210281,1152927149267621629, + 2305848652800726634,1152927147120137981,2305848650653242987,1152927144972654333,2305848648505759340,1152927634598926077,2305848646358275662,1152927140677687037, + 2305848644210792015,1152927138530203389,2305848642063308368,1152927136382719741,2305848639915824721,1152927134235236093,2305848637768341074,1152927132087752445, + 2305848635620857427,1152927129940268797,2305848633473373780,1152927127792785149,2305848631325890133,1152927125645301501,2305848629178406486,1152927123497817853, + 2305848627030922839,1152927121350334205,2305848624883439192,1152927119202850557,2305848622735955545,1152927117055366909,2305848620588471898,1152927114907883261, + 2305848618440988251,1152927112760399613,2305848616293504604,1152927617419056893,2305848614146020926,1152927108465432317,2305848611998537279,1152927106317948669, + 2305848609851053632,1152927104170465021,2305848607703569985,1152927102022981373,2305848605556086338,1152927099875497725,2305848603408602691,1152927097728014077, + 2305848601261119044,1152927095580530429,2305848599113635397,1152927093433046781,2305848596966151750,1152927091285563133,2305848594818668103,1152927089138079485, + 2305848592671184456,1152927086990595837,2305848590523700809,1152927084843112189,2305848588376217162,1152927082695628541,2305848586228733515,1152927080548144893, + 2305848584081249868,1152927600239187709,2305848581933766190,1152927076253177597,2305848579786282543,1152927074105693949,2305848577638798896,1152927071958210301, + 2305848575491315249,1152927069810726653,2305848573343831602,1152927067663243005,2305848571196347955,1152927065515759357,2305848569048864308,1152927063368275709, + 2305848566901380661,1152927061220792061,2305848564753897014,1152927059073308413,2305848562606413367,1152927056925824765,2305848560458929720,1152927054778341117, + 2305848558311446073,1152927052630857469,2305848556163962426,1152927050483373821,2305848554016478779,1152927048335890173,2305848551868995132,1152927583059318525, + 2305848549721511454,1152927044040922877,2305848547574027807,1152927041893439229,2305848545426544160,1152927039745955581,2305848543279060513,1152927037598471933, + 2305848541131576866,1152927035450988285,2305848538984093219,1152927033303504637,2305848536836609572,1152927031156020989,2305848534689125925,1152927029008537341, + 2305848532541642278,1152927026861053693,2305848530394158631,1152927024713570045,2305848528246674984,1152927022566086397,2305848526099191337,1152927020418602749, + 2305848523951707690,1152927018271119101,2305848521804224043,1152927016123635453,2305848519656740396,1152927565879449341,2305848517509256718,1152927011828668157, + 2305848515361773071,1152927009681184509,2305848513214289424,1152927007533700861,2305848511066805777,1152927005386217213,2305848508919322130,1152927003238733565, + 2305848506771838483,1152927001091249917,2305848504624354836,1152926998943766269,2305848502476871189,1152926996796282621,2305848500329387542,1152926994648798973, + 2305848498181903895,1152926992501315325,2305848496034420248,1152926990353831677,2305848493886936601,1152926988206348029,2305848491739452954,1152926986058864381, + 2305848489591969307,1152926983911380733,2305848487444485660,1152927823577487101,1152927564805706732,2305848484223260172,1152926978542670828,2305848482075776524, + 1152926976395187180,2305848479928292876,1152926974247703532,2305848477780809228,1152926972100219884,2305848475633325580,1152926969952736236,2305848473485841932, + 1152926967805252588,2305848471338358284,1152927557289513964,2305848469190874630,1152926963510285292,2305848467043390983,1152926961362801644,2305848464895907340, + 1152926959215317996,2305848462748423688,1152926957067834348,2305848460600940041,1152926954920350700,2305848458453456394,1152926952772867052,2305848456305972747, + 6051608926110,1152926949551642526,6051608926090,1152926947404158858,1152926948477900543,2305848449863521228,2305849479581931422,6051608925129, + 1152926942035448777,1152926944182933247,2305848444494812103,2305849367912781622,6051608925124,1152926936666740638,1152926935592998650,1152926938814224127, + 2305848438052361153,1152927884780771210,1152926931298031354,1152926932371773183,2305848433757393853,5438502344196,1152926927003064118,1152926925929322234, + 1152926928076805887,2305848428388684728,1152926927003064098,1152926921634354938,1152926922708096767,2305848424093717428,6051608926109,1152926917339387805, + 1152926916265644986,1152926918413129471,2305848418725008303,1152927995376178106,6051608926108,1152926910896936860,1152926909823194028,1152926913044420351, + 2305848412282557353,1152927994302436268,6051608926107,1152926904454485915,1152926903380743078,1152926906601969407,2305848405840106403,1152927993228694438, + 6051608926106,1152926898012034970,1152926896938292128,1152926900159518463,2305848399397655453,6490769332021,1152926892643324844,1152926891569583866, + 1152926893717067519,2305848394028946328,6489695590196,1152926887274615718,1152926886200874746,1152926888348358399,2305848388660237203,6488621848371, + 1152926881905906592,1152926880832165626,1152926882979649279,2305848383291528078,1152927992154952608,6487548106546,1152926875463455627,1152926874389714682, + 1152926877610940159,2305848376849077128,1152927821430003614,1152926871168489215,2305848373627851653,1152927821430003594,1152926867947263743,2305848370406626178, + 1152927821430002609,1152926864726038271,2305848367185400703,1152927821430002603,1152926861504812799,2305848363964175228,1152927821430002597,1152926858283587327, + 2305848360742949753,1152927821430002591,1152926855062361855,2305848357521724278,1152928051210753788,1152926851841136383,2305848354300498803,2305849555817600516, + 5342939322217,1152926846472427258,1152926848619910911,2305848348931789678,6534792746500,1152928041547077575,1152926841103717227,1152928020072239977, + 6538013971972,1152926837882491754,6539087713796,1152926835735008102,1152926834661267381,2305848338194371432,5342939322216,1152926831440040802, + 1152926830366299898,1152926843251201791,2305848332825662303,6536940230148,1152926826071332807,1152926824997589867,1152926823923849140,1152926822850107240, + 1152926821776365306,1152926827145074431,2305848324235727703,2305848327456953192,2305848322088244067,6535866488324,1152926815333913436,1152926814260172742, + 1152926813186430900,2305848316719534931,5306432099844,6545530165096,1152926808891462477,1152926807817721594,1152926818555139839,2305848310277083978, + 6531571521383,1152926803522753378,1152926802449012474,1152926804596496127,2305848304908374853,6530497779558,1152926798154044258,1152926797080303354, + 1152926799227787007,2305848299539665728,6529424037733,1152926792785335138,1152926791711594234,1152926793859077887,2305848294170956603,6528350295908, + 1152926787416626018,1152926786342885114,1152926788490368767,2305848288802247478,2305848316719534946,5277441070596,6544456423271,1152926779900433202, + 1152926778826692346,1152926783121659647,2305848281286054703,6543382681446,1152926774531724082,1152926773457983226,1152926775605466879,2305848275917345578, + 6542308939621,1152926769163014962,1152926768089274106,1152926770236757759,2305848270548636453,6541235197796,1152926763794305842,1152926762720564986, + 1152926764868048639,2305848265179927328,1152927997523662588,1152926759499339519,2305848261958701853,6050535184287,1152926755204372219,1152926756278114047, + 2305848257663734553,6492916815671,6051608924950,1152926749835662102,6049461441302,1152926747688178452,6048387699478,1152926745540694802, + 6047313957654,1152926743393211152,6046240215830,1152926741245727502,6045166474006,1152926739098243852,6044092732182,1152926736950760202, + 6043018990358,1152926734803276552,1152926733729535738,1152926751983146751,2305848236188898053,6533719004676,1152926729434567531,6532645262852, + 1152926727287083777,1152926814260171623,1152926725139600127,1152926724065858405,1152928037252109058,1152926721918374763,1152926824997590985,1152926719770891002, + 2305848223303996156,1152926725139601354,1152926716549666741,1152926815333914568,1152926714402183113,1152926713328440165,1152926712254699445,2305848215787803381, + 2305848214714061559,1152926713328441290,1152926707959732149,1152926729434568646,1152926705812247296,1152928042620818282,1152926703664763627,1152926702591023050, + 2305848206124126957,1152926824997590982,1152926699369797556,1152926841103718342,1152926697222313908,2305848200755417829,2305848199681676007,2305848198607934191, + 6526202811908,1152926691853603663,2305848195386708840,2305848194312966880,1152928038325850987,1152926687558636288,1152926686484894462,1152926685411152741, + 1152926719770891007,1152926726213342054,2305848186796774103,1152926704738505470,1152926814260172745,1152926678968701675,2305848182501806804,2305848181428064981, + 1152926714402181991,1152926674673734379,1152926704738505459,2305848177133097678,1152926704738505465,2305848174985614028,2305848173911872208,1152926668231284682, + 2305848171764388568,1152926824997589863,1152926665010057946,1152926663936317386,1152926686484894566,1152926661788833738,2305848165321937604,2305848164248195783, + 1152926686484894442,1152926657493865317,1152926657493866442,2305848159953228478,1152926674673734501,1152926653198899125,1152926674673735626,1152926651051415477, + 2305848154584519354,2305848153510777532,2305848152437035712,1152928030809658191,1152926665010058085,1152926644608964533,2305848148142068404,2305848147068326755, + 2305848145994584757,2305848144920842972,1152926639240255307,1152928018998498012,1152928017924756143,2305848140625875628,5130338439853,1152926633871546106, + 1152926730508310271,2305848136330908328,1152926724065859530,2305848134183424764,1152926678968701695,1152926627429094245,2305848130962199204,1152926627429095370, + 2305848128814715553,1152926674673734399,1152926622060385125,2305848125593490079,1152926622060386250,2305848123446006428,1152926713328440063,1152926616691676005, + 2305848120224780954,1152926616691677130,2305848118077297303,5218385269397,2305848115929813724,1152926685411153866,2305848113782330072,1152926678968701658, + 1152926607027999589,2305848110561104529,1152926607028000714,2305848108413620878,1152926674673734362,1152926601659290469,2305848105192395404,1152926601659291594, + 2305848103044911753,1152926686484894451,1152926596290581349,2305848099823686279,1152926596290582474,2305848097676202628,5179730563714,2305848095528719023, + 2305848094454977171,1152928038325852102,1152926587700646656,1152926586626904830,1152926585553163109,1152926585553164234,2305848088012526203,1152926586626904787, + 1152926581258195813,2305848084791300729,1152926581258196938,2305848082643817078,1152926586626904783,1152926575889486693,2305848079422591604,1152926575889487818, + 2305848077275107953,1152926586626904819,1152926570520777573,2305848074053882479,1152926570520778698,2305848071906398828,1152926586626904774,1152926565152068453, + 2305848068685173354,1152926565152069578,2305848066537689703,1152926586626904825,1152926559783359333,2305848063316464229,1152926559783360458,2305848061168980578, + 1152926586626904934,1152926554414650213,2305848057947755104,1152926554414651338,2305848055800271453,1152926586626904810,1152926549045941093,2305848052579045979, + 1152926549045942218,2305848050431562328,1152926719770892234,1152926543677233077,2305848047210336854,1152926668231283557,2305848045062853203,2305848043989111423, + 1152926538308523850,1152926610249226162,1152928016851014272,2305848039694144078,1152926539382265779,2305848037546660428,5027259224655,1152926530792331002, + 1152926630650320639,2305848033251693128,1152926665010057983,1152926526497362789,1152926720844632914,2305848028956725828,2305848027882984083,1152926686484894457, + 1152926521128653669,1152926720844632934,2305848023588016703,1152926703664763642,2305848021440533053,1152926683263670218,5010079355451,2305848018219307648, + 2305848017145565761,1152926665010057978,2305848014998082129,2305848013924340279,1152926508243752777,1152926522202396593,1152928015777272376,2305848009629372978, + 1152926509317494706,2305848007481889328,4997194453555,1152926500727559930,1152926527571105535,2305848003186922028,2305848026809242327,1152926682189928394, + 4990752003828,2305847998891954744,2305847997818212905,1152926683263669093,4986457036532,2305847994596987445,2305847993523245605,1152926487842658120, + 1152926496432592816,1152928014703530534,2305847989228278303,1152926488916400049,2305847987080794653,4976793358880,1152926480326465274,1152926497506334463, + 2305847982785827353,1152926703664763647,2305847980638343894,1152926704738505574,1152926665010057963,1152926472810272714,2305847976343376404,2305847975269634581, + 1152926814260171627,1152926468515305396,2305847972048409104,2305847970974667305,1152926661788832613,1152926680042444746,4958539747851,2305847966679700006, + 2305847965605958156,1152926682189927269,4954244781812,2305847962384732706,2305847961310990855,1152926455630403399,1152926465294079919,1152928013629788680, + 2305847957016023553,1152926456704145328,2305847954868539903,4944581104130,1152926448114210554,1152926477105239807,2305847950573572603,2305847969900925652, + 1152926677894961098,4938138654452,2305847946278605320,2305847945204863480,1152926680042443621,4933843687156,2305847941983638020,2305847940909896180, + 1152926435229308742,1152926443819243438,1152928012556046837,2305847936614928878,1152926436303050671,2305847934467445228,4924180009455,1152926427713115898, + 1152926444892985087,2305847930172477928,2305847948426089170,1152926673599993802,4917737559796,2305847925877510645,2305847924803768805,1152926677894959973, + 4913442592500,2305847921582543345,2305847920508801505,1152926414828214085,1152926423418148781,1152928011482304994,2305847916213834203,1152926415901956014, + 2305847914066350553,4903778914780,1152926407312021242,1152926424491890431,2305847909771383253,2305847928024994510,1152926672526251978,4897336465140, + 2305847905476415970,2305847904402674130,1152926673599992677,4893041497844,2305847901181448670,2305847900107706830,1152926394427119428,1152926403017054124, + 1152928010408563151,2305847895812739528,1152926395500861357,2305847893665255878,4883377820105,1152926386910926586,1152926404090795775,2305847889370288578, + 1152926472810271589,2305847887222805197,4969277166014,2305847885075321298,1152926521128654794,1152926670378768330,4872640401851,2305847880780353999, + 2305847879706612156,1152926672526250853,4868345435892,2305847876485386699,2305847875411644855,1152926369731057475,1152926379394733995,1152928009334821304, + 2305847871116677553,1152926370804799404,2305847868969193903,4858681758130,1152926362214864634,1152926383689701119,2305847864674226603,2305847884001579723, + 2305847862526742968,1152926670378767205,4851165566708,2305847859305517492,2305847858231775655,1152926352551188290,1152926357919897514,1152928008261079480, + 2305847853936808353,1152926353624930219,2305847851789324703,4841501888930,1152926345034995450,1152926358993639167,2305847847494357403,1152927896591929763, + 1152928008261079464,1152928007187337656,2305847843199390103,1152926353624930218,2305847841051906453,4830764470680,1152926334297577210,1152926341813769983, + 2305847836756939153,1152927895518187939,1152928007187337640,1152928006113595832,2305847832461971853,1152928008261079460,2305847830314488203,4820027052430, + 1152926323560158970,1152926331076351743,2305847826019520903,1152926725139600229,1152926319265191861,2305847822798295572,1152926473884014538,4811437117826, + 2305847819577069992,2305847818503328184,2305847817429586340,1152926311748998975,1152926313896482727,1152928005039854008,2305847813134619003,1152928007187337636, + 2305847810987135353,4800699699580,1152926304232806138,1152926320338933503,2305847806692168053,1152927893370704253,1152928005039853951,1152928003966112184, + 2305847802397200753,1152928006113595812,2305847800249717103,4789962281330,1152926293495387898,1152926301011580671,2305847795954749803,1152927892296962429, + 1152928003966112127,1152928002892370360,2305847791659782503,1152928005039853988,2305847789512298853,4779224863080,1152926282757969658,1152926290274162431, + 2305847785217331553,2305847818503328164,1152926278463002428,1152928002892370303,1152928003966112164,2305847779848622428,4769561186653,1152926273094293242, + 1152926279536744191,2305847775553655128,6041945249699,1152926268799325948,1152926269873067775,2305847771258687828,1152926268799325947,1152926265578100479, + 2305847768037462353,1152928000744888060,1152926262356875007,2305847764816236878,1152928000744888059,1152926259135649535,2305847761595011403,6497211782971, + 2305848327456953186,4749160093188,2305849533269022653,1152926251619455823,4745938866502,1152926249471971656,1152926727287084997,1152926247324488555, + 1152926720844634055,1152926245177006024,2305847748710109504,2305847747636368055,1152926714402183110,1152926240882038708,2305847744415142580,2305847743341400754, + 2305847742267658556,1152927999671144789,1152926235513327927,2305847739046433090,1152926247324489670,2305847736898949716,2305847735825207862,4725537771828, + 6041945249595,1152926227997135175,2305847731530240304,1152926225849652986,1152926255914424063,2305847728309014828,4730906482180,1152926221554684245, + 1152926222628427519,2305847724014047528,6546603906874,1152926217259716913,1152926216185976570,1152926218333460223,2305847718645338403,1152926236587071292, + 1152926211891007785,1152926210817267450,1152926212964751103,2305847713276629278,1152926584479422283,1152926206522300154,1152926207596041983,2305847708981661978, + 6514391652171,1152926202227331706,1152926201153591034,1152926203301074687,2305847703612952853,6513317910347,1152926196858622583,1152926195784881914, + 1152926197932365567,2305847698244243728,6512244168523,1152926191489913461,1152926190416172794,1152926192563656447,2305847692875534603,6511170426699, + 1152926186121204338,1152926185047463674,1152926187194947327,2305847687506825478,6510096684875,1152926180752495216,1152926179678754554,1152926181826238207, + 2305847682138116353,6509022943051,1152926175383786093,1152926174310045434,1152926176457529087,2305847676769407228,6507949201227,1152926170015076971, + 1152926168941336314,1152926171088819967,2305847671400698103,6506875459403,1152926164646367848,1152926163572627194,1152926165720110847,2305847666031988978, + 6504727975755,1152926159277658726,1152926158203918074,1152926160351401727,2305847660663279853,6502580492107,1152926153908949603,1152926152835208954, + 1152926154982692607,2305847655294570728,6500433008459,1152926148540240481,1152926147466499834,1152926149613983487,2305847649925861603,6498285524811, + 1152926143171531358,1152926142097790714,1152926144245274367,2305847644557152478,6546603906891,1152926137802822231,1152926136729081594,1152926138876565247, + 2305847639188443353,1152926196858622532,1152926132434114298,1152926133507856127,2305847634893476053,6514391652170,1152926128139145796,1152926127065405178, + 1152926129212888831,2305847629524766928,1152926191489913380,1152926122770437882,1152926123844179711,2305847625229799628,1152926186121204230,1152926118475470586, + 1152926119549212415,2305847620934832328,1152926180752495091,1152926114180503290,1152926115254245119,2305847616639865028,1152926175383785952,1152926109885535994, + 1152926110959277823,2305847612344897728,1152926170015076813,1152926105590568698,1152926106664310527,2305847608049930428,1152926164646367670,1152926101295601402, + 1152926102369343231,2305847603754963128,6505801717579,1152926097000632742,1152926095926892282,1152926098074375935,2305847598386254003,6514391652169, + 1152926091631923770,1152926090558183162,1152926092705666815,2305847593017544878,6514391652168,1152926086263214632,1152926085189474042,1152926087336957695, + 2305847587648835753,6514391652167,1152926080894505482,1152926079820764922,1152926081968248575,2305847582280126628,6514391652166,1152926075525796343, + 1152926074452055802,1152926076599539455,2305847576911417503,6514391652165,1152926070157087204,1152926069083346682,1152926071230830335,2305847571542708378, + 6514391652164,1152926064788378065,1152926063714637562,1152926065862121215,2305847566173999253,6514391652163,1152926059419668922,1152926058345928442, + 1152926060493412095,2305847560805290128,1152926180752495039,1152926054050961146,1152926055124702975,2305847556510322828,6509022943050,1152926049755992511, + 1152926048682252026,1152926050829735679,2305847551141613703,6507949201225,1152926044387283391,1152926043313542906,1152926045461026559,2305847545772904578, + 6506875459400,1152926039018574271,1152926037944833786,1152926040092317439,2305847540404195453,1152926080894505407,1152926033649866490,1152926034723608319, + 2305847536109228153,6513317910342,1152926029354897855,1152926028281157370,1152926030428641023,2305847530740519028,6512244168517,1152926023986188735, + 1152926022912448250,1152926025059931903,2305847525371809903,6511170426692,1152926018617479615,1152926017543739130,1152926019691222783,2305847520003100778, + 1152926473884013413,1152926013248770229,1152926012175030010,1152926014322513663,2305847514634391653,6504727975754,1152926007880061031,1152926006806320890, + 1152926008953804543,2305847509265682528,6503654233929,1152926002511351911,1152926001437611770,1152926003585095423,2305847503896973403,6502580492104, + 1152925997142642791,1152925996068902650,1152925998216386303,2305847498528264278,1152926013248770194,1152925991773935354,1152925992847677183,2305847494233296978, + 6513317910338,1152925987478966375,1152925986405226234,1152925988552709887,2305847488864587853,6512244168513,1152925982110257255,1152925981036517114, + 1152925983184000767,2305847483495878728,6511170426688,1152925976741548135,1152925975667807994,1152925977815291647,2305847478127169603,6501506750283, + 1152925971372839297,1152925970299098874,1152925972446582527,2305847472758460478,6500433008458,1152925966004130177,1152925964930389754,1152925967077873407, + 2305847467389751353,6499359266633,1152925960635421057,1152925959561680634,1152925961709164287,2305847462021042228,6498285524808,1152925955266711937, + 1152925954192971514,1152925956340455167,2305847456652333103,6514391652159,1152925949898002817,1152925948824262394,1152925950971746047,2305847451283623978, + 6513317910334,1152925944529293697,1152925943455553274,1152925945603036927,2305847445914914853,6512244168509,1152925939160584577,1152925938086844154, + 1152925940234327807,2305847440546205728,6511170426684,1152925933791875457,1152925932718135034,1152925934865618687,2305847435177496603,1152926702591021925, + 1152925928423166133,1152925927349425914,1152925929496909567,2305847429808787478,1152925928423166050,1152925923054458618,1152925924128200447,2305847425513820178, + 1152925928423166045,1152925918759491322,1152925919833233151,2305847421218852878,1152925928423166040,1152925914464524026,1152925915538265855,2305847416923885578, + 6501506750279,1152925910169554968,1152925909095814906,1152925911243298559,2305847411555176453,6500433008454,1152925904800845848,1152925903727105786, + 1152925905874589439,2305847406186467328,6499359266629,1152925899432136728,1152925898358396666,1152925900505880319,2305847400817758203,6498285524804, + 1152925894063427608,1152925892989687546,1152925895137171199,2305847395449049078,1152925928423166098,1152925888694720250,1152925889768462079,2305847391154081778, + 1152925928423166031,1152925884399752954,1152925885473494783,2305847386859114478,1152925928423166026,1152925880104785658,1152925881178527487,2305847382564147178, + 1152925928423166021,1152925875809818362,1152925876883560191,2305847378269179878,6510096684863,1152925871514849304,1152925870441109242,1152925872588592895, + 2305847372900470753,6509022943038,1152925866146140184,1152925865072400122,1152925867219883775,2305847367531761628,6507949201213,1152925860777431064, + 1152925859703691002,1152925861851174655,2305847362163052503,6506875459388,1152925855408721944,1152925854334981882,1152925856482465535,2305847356794343378, + 6051608926131,1152925850040014771,1152925848966271551,1152925847892530938,1152925851113756415,2305847350351892428,1152928016851015603,6514391651844, + 1152925842523822002,2305847346056925129,4335769491275,1152925839302595135,1152925838228854522,1152925844671305471,2305847340688216003,1152928017924757427, + 1152925842523822001,2305847337466990528,4327179556682,1152925830712660543,1152925829638919930,1152925835007629055,2305847332098281403,1152925848966271419, + 1152925825343952634,1152925826417694463,2305847327803314103,1152928013629790131,2305847325655830471,4315368396619,1152925818901500347,1152925817827759866, + 1152925822122727167,2305847320287121328,1152928012556048307,2305847318139637695,4307852203850,1152925811385307579,1152925810311567098,1152925814606534399, + 2305847312770928553,1152928011482306483,1152925842523822000,2305847309549703078,4299262269257,1152925802795372987,1152925801721632506,1152925807090341631, + 2305847304180993953,1152928010408564659,1152925842523821999,2305847300959768478,4290672334664,1152925794205438395,1152925793131697914,1152925798500407039, + 2305847295591059353,1152925842523821998,2305847293443575744,4283156141895,1152925786689245627,1152925785615505146,1152925789910472447,2305847288074866578, + 1152925842523821997,2305847285927382985,4275639949126,1152925779173052859,1152925778099312378,1152925782394279679,2305847280558673803,1152928015777273779, + 1152925842523821996,2305847277337448328,4267050014533,1152925770583118267,1152925769509377786,1152925774878086911,2305847271968739203,1152928014703531955, + 1152925842523821995,2305847268747513728,4258460079940,1152925761993183675,1152925760919443194,1152925766288152319,2305847263378804603,1152925850040013528, + 1152925756624475898,1152925757698217727,2305847259083837303,1152926526497363914,6544456423243,1152925751255764852,1152925750182024954,1152925753403250431, + 2305847252641386353,6543382681418,1152925745887055732,1152925744813315834,1152925746960799487,2305847247272677228,6542308939593,1152925740518346612, + 1152925739444606714,1152925741592090367,2305847241903968103,6541235197768,1152925735149637492,1152925734075897594,1152925736223381247,2305847236535258978, + 6497211782987,1152925729780929113,1152925728707188474,1152925730854672127,2305847231166549853,1152926714402181995,1152925724412221364,1152925723338479354, + 6505801717571,1152925721190993752,1152925725485963007,2305847223650357078,6504727975746,1152925716896026456,1152925717969770239,2305847219355389778, + 6503654233921,1152925712601059160,1152925713674802943,2305847215060422478,6502580492096,1152925708306091864,1152925709379835647,2305847210765455178, + 2305849050085201831,4199404279615,1152925702937382744,1152925705084868351,2305847205396746053,2305849050085201830,4194035570494,1152925697568673624, + 1152925699716159231,2305847200028036928,6499359266621,1152925693273706328,1152925694347450111,2305847195733069628,6498285524796,1152925688978739032, + 1152925690052482815,2305847191438102328,1152926665010059210,1152925684683773877,1152926202227332858,1152925682536288052,1152925685757515519,2305847184995651378, + 6513317910346,1152925678241322746,1152925677167578932,1152925679315064575,2305847179626942253,6512244168521,1152925672872613626,1152925671798869812, + 1152925673946355455,2305847174258233128,6511170426696,1152925667503904506,1152925666430160692,1152925668577646335,2305847168889524003,6510096684871, + 1152925662135195386,1152925661061451572,1152925663208937215,2305847163520814878,6509022943046,1152925656766486266,1152925655692742452,1152925657840228095, + 2305847158152105753,6507949201221,1152925651397777146,1152925650324033332,1152925652471518975,2305847152783396628,6506875459396,1152925646029068026, + 1152925644955324212,1152925647102809855,2305847147414687503,1152925721190995706,1152925640660356916,1152925641734100735,2305847143119720203,1152925716896028410, + 1152925636365389620,1152925637439133439,2305847138824752903,1152925712601061114,1152925632070422324,1152925633144166143,2305847134529785603,1152925708306093818, + 1152925627775455028,1152925628849198847,2305847130234818303,6501506750271,1152925623480489722,1152925622406745908,1152925624554231551,2305847124866109178, + 6500433008446,1152925618111780602,1152925617038036788,1152925619185522431,2305847119497400053,1152928034030884801,1152928032957142976,1152925611669329401, + 2305847115202432754,1152925609521846181,1152928032957142977,2305847111981207277,1152925606300620708,1152925605226878457,2305847108759981806,1152928018998499265, + 2305847106612498153,2305847105538758578,4095251322813,1152925598784425780,1152925613816813311,2305847101243789028,1152925606300620709,1152925609521846180, + 2305847098022563553,1152928018998499264,2305847095875079903,4085587646396,1152925589120749364,1152925595563202303,2305847091580112603,1152925609521846209, + 1152925606300620736,1152925583752041977,2305847087285145304,1152925581604558781,1152925609521846208,1152925606300620737,2305847082990178003,1152925577309591484, + 1152925576235849209,2305847079768952532,1152925609521846193,2305847077621468878,1152928015777273793,2305847075473985228,2305847074400245679,4064112809919, + 1152925567645912884,1152925585899525887,2305847070105276103,1152925577309591485,1152925581604558780,2305847066884050628,1152925606300620721,2305847064736566978, + 1152928015777273792,2305847062589083328,4052301649854,1152925555834752820,1152925564424689407,2305847058294116028,1152925581604558783,1152925577309591486, + 1152925550466045433,2305847053999148729,1152925609521846190,2305847051851665078,1152928012556048321,2305847049704181428,2305847048630441900,4038343006013, + 1152925541876111098,1152925540802367284,1152925552613529343,2305847043261730478,1152925577309591487,1152925581604558782,2305847040040505003,1152925606300620718, + 2305847037893021353,1152928012556048320,2305847035745537703,4025458104124,1152925528991209210,1152925527917465396,1152925537581143807,2305847030376828578, + 1152926719770891109,1152925523622500277,1152925522548756275,1152925524696241919,2305847025008119453,1152925522548756270,1152925519327532799,2305847021786893978, + 1152925522548756265,1152925516106307327,2305847018565668503,1152925522548756260,1152925512885081855,2305847015344443028,1152925522548756255,1152925509663856383, + 2305847012123217553,1152925522548756250,1152925506442630911,2305847008901992078,1152925522548756245,1152925503221405439,2305847005680766603,1152925522548756240, + 1152925500000179967,2305847002459541128,1152925522548756236,1152925496778954495,2305846999238315653,1152925522548756232,1152925493557729023,2305846996017090178, + 1152925522548756228,1152925490336503551,2305846992795864703,1152925522548756224,1152925487115278079,2305846989574639228,1152925522548756219,1152925483894052607, + 2305846986353413753,1152925522548756214,1152925480672827135,2305846983132188278,1152928017924757440,1152925476377859577,2305846979910962920,2305846978837221097, + 2305846977763481521,3967476045757,1152925471009148574,1152925477451601663,2305846973468511853,1152928017924757441,2305846971321028318,2305846970247286495, + 2305846969173546928,3958886111164,1152925462419213982,1152925467787925247,2305846964878577253,1152925609521846205,1152925606300620732,1152925457050506745, + 2305846960583609954,1152928014703531969,1152928013629790144,1152925452755539449,2305846956288642654,2305846955214900831,2305846954141161389,3943853725501, + 1152925447386830586,1152925446313086622,1152925459197990655,2305846948772449878,1152925606300620733,1152925609521846204,2305846945551224403,1152928013629790145, + 1152928014703531968,2305846942329998928,2305846941256257105,2305846940182517676,3929895081788,1152925433428186874,1152925432354442910,1152925443091863295, + 2305846934813806153,1152926663936316261,6051608926023,1152925426985736007,1152925425911991878,1152925429133219583,2305846928371355203,6051608926022, + 1152925421617026886,1152925420543282758,1152925422690768639,2305846923002646078,6051608926021,1152925416248317765,1152925415174573638,1152925417322059519, + 2305846917633936953,6051608926020,1152925410879608644,1152925409805864518,1152925411953350399,2305846912265227828,2305849406567487303,2305846910117746501, + 2305846909044004676,6051608923695,1152925402289671750,1152925406584641279,2305846904749035053,1152927543330871103,2305849406567487302,2305846901527811909, + 1152927899813156354,2305846899380325928,2305846898306584106,3888019150762,1152925391552253510,1152925399068448511,2305846894011616803,1152927542257129286, + 2305846891864135495,1152927541183387461,2305846889716649503,1152927540109645636,2305846887569165853,3877281732418,1152925380814835270,1152925388331030271, + 2305846883274198553,1152928008261080898,3871913023421,1152925375446126150,1152925377593612031,2305846877905489428,1152928028662175677,6051608923665, + 1152925370077417030,1152925372224902911,2305846872536780303,1152928028662175654,3861175605185,1152925364708707910,1152925366856193791,2305846867168071178, + 1152928028662175653,3855806896064,1152925359339998790,1152925361487484671,2305846861799362053,4104914999231,1152925355045031494,1152925356118775551, + 2305846857504394753,4101693773758,1152925350750064198,1152925351823808255,2305846853209427453,2305849411936196427,2305849521457862579,6512244168627, + 1152925344307615681,6511170426802,1152925342160132032,1152925341086389753,2305846844619492855,2305846843545751033,3833258315258,1152925336791420486, + 1152925347528840959,2305846839250783728,2305849410862454602,2305849520384120754,1152925344307615680,1152925342160132033,2305846833882074603,2305846832808332780, + 3822520897005,1152925326054002246,1152925333570197247,2305846828513365478,2305849409788712777,2305849517162895279,6507949201327,1152927542257129409, + 1152925318537809377,6506875459502,1152927542257129408,1152925315316583902,1152925314242844153,2305846817775947231,2305846816702205410,3806414769635, + 1152925309947874886,1152925322832779007,2305846812407238103,2305849408714970952,2305849516089153454,1152925315316583905,1152925316390325728,2305846807038528978, + 2305846805964787155,3795677351380,1152925299210456646,1152925306726651647,2305846801669819853,2305849397977552702,2305849409788712779,6400575018827, + 1152925292768008127,6399501277002,1152925290620524478,1152925289546782201,2305846793079885255,2305846792006143433,3781718707658,1152925285251812934, + 1152925295989233407,2305846787711176128,2305849396903810877,2305849408714970954,1152925292768008126,1152925290620524479,2305846782342467003,2305846781268725180, + 3770981289405,1152925274514394694,1152925282030589695,2305846776973757878,1152927903034382259,1152927901960640431,2305846773752532403,1152927900886898609, + 2305846771605048753,1152927899813156781,2305846769457565103,3759170131880,1152925262703234630,1152925271293171455,2305846765162597803,1152927903034382258, + 1152927901960640430,2305846761941372328,1152927900886898608,2305846759793888678,1152927899813156780,2305846757646405028,3747358971815,1152925250892074566, + 1152925259482011391,2305846753351437728,1152928008261081001,3741990262595,1152925245523365446,1152925247670851327,2305846747982728603,1152927898739414851, + 3736621553472,1152925240154656326,1152925242302142207,2305846742614019478,2305849050085201828,3731252844353,1152925234785947206,1152925236933433087, + 2305846737245310353,6505801717567,1152925230490979910,1152925231564723967,2305846732950343053,2305849522531604403,3721589167947,1152925225122271887, + 1152925224048531194,1152925227269756671,2305846726507892103,2305849523605346123,1152925219753562786,1152925218679822074,1152925220827305727,2305846721139182978, + 4329327040331,1152925214384853645,1152925213311112954,1152925215458596607,2305846715770473853,1152928018998499147,6051608923514,1152925207942402720, + 1152925206868662010,1152925210089887487,2305846709328022903,2305849523605345796,3697966847819,1152925201499951773,1152925200426211066,1152925203647436543, + 2305846702885571953,1152925848966271643,1152925197204985599,2305846699664346478,6051608923524,1152925192910018298,1152925191836275355,1152925193983760127, + 2305846694295637353,1152925848966271626,1152925188615051007,2305846691074411878,6051608926130,1152925184320083890,1152925183246340746,1152925185393825535, + 2305846685705702753,1152925214384854778,1152925178951373450,1152925180025116415,2305846681410735453,1152925848966271624,1152925175730149119,2305846678189509978, + 1152925183246340744,1152925172508923647,2305846674968284503,4329327037834,3663607109451,1152925167140214522,1152925166066471560,1152925169287698175, + 2305846668525833553,1152926609175484154,6513317910451,1152925160697763659,1152925159624019278,1152925162845247231,2305846662083382603,1152925160697763777, + 3650722207236,3649648465739,1152925153181568334,1152925156402796287,2305846655640931653,1152926629576578810,1152925148886601082,1152925149960345343, + 2305846651345964353,4097398806020,3639984789323,1152925143517891906,1152925145665378047,2305846645977255228,1152926476031498186,1152925139222927098, + 1152928017924757426,3632468596554,1152925136001699128,1152925140296668927,2305846638461062453,1152925184320083787,2305846636313581060,3626026145729, + 1152925129559248184,1152925132780476159,2305846632018611503,1152928016851015617,3620657436489,1152925124190539064,1152925126338025215,2305846626649902378, + 1152927539035903816,2305846624502421323,3614214985648,1152925117748088120,1152925120969316095,2305846620207451428,6051608926027,1152925113453123403, + 1152925112379379000,1152925114526865151,2305846614838742303,1152926471736530682,1152925108084412695,1152925109158156031,2305846610543775003,1152925108084412207, + 1152925104863188735,2305846607322549528,1152928015777273778,1152925100568221177,2305846604101324745,3593813890889,1152925097346993436,1152925101641963263, + 2305846599806356753,2305849520384120753,1152925093052026250,4338990714125,3595961371916,3585223956296,1152925088757058844,1152925094125770495, + 2305846591216422153,1152926674673734394,2305846589068939830,2305846587995198012,1152927536888420286,2305846585847715775,3575560279539,1152925079093385149, + 3573412795908,1152927536888420288,2305846580479006657,1152925074798415103,1152925073724673284,1152925085535835903,2305846576184036603,1152926720844632830, + 2305846574036554294,2305846572962812478,2305846571889069318,2305846570815328828,1152925065134741242,3570191570763,1152925062987255027,1152925070503450367, + 2305846565446618353,3575560279882,1152925058692287731,1152925059766032127,2305846561151651053,1152927540109645758,1152928031883400916,2305846557930425597, + 2305846556856683754,3546569250739,1152925050102354486,1152925055471064831,2305846552561716453,3547642989962,1152925045807385848,1152925046881130239, + 2305846548266749153,2305846730802862001,3547642989790,1152925040438678078,1152925042586162943,2305846542898040028,1152926678968701690,1152925036143711994, + 2305849410862454707,1152927907329349332,3528315636951,3527241897906,1152925030775000280,1152925037217453823,2305846533234363603,1152925035069967648, + 1152925027553777407,2305846530013138128,3570191570762,1152925023258807512,1152925024332551935,2305846525718170828,3546569248704,1152925018963840262, + 1152925020037584639,2305846521423203528,2305847338540734385,3546569247941,1152925013595132476,1152925015742617343,2305846516054494403,1152926713328440058, + 1152925009300166394,1152927906255607731,2305846511759529803,3501472094130,1152925005005196479,1152925010373908223,2305846507464559803,6401648760754, + 3570191568056,1152924999636487359,1152925001783973631,2305846502095850678,6402722502579,2305846584773973508,3489660931251,1152924993194036415, + 1152924996415264511,2305846495653399728,1152926718697150202,3575560279883,1152924987825327277,1152924989972813567,2305846490284690603,1152924988899069133, + 1152924984604104447,2305846487063465128,1152928027588433660,1152924981382878975,2305846483842239653,6051608926139,1152924977087911867,1152924978161653503, + 2305846479547272353,6051608926035,1152924972792943345,1152924971719202554,1152924973866686207,2305846474178563228,1152924972792943333,1152924967424235258, + 1152924968497977087,2305846469883595928,6051608926154,1152924963129268170,1152924964203009791,2305846465588628628,6051608926153,1152924958834300873, + 1152924959908042495,2305846461293661328,6051608926152,1152924954539333576,1152924955613075199,2305846456998694028,6051608926151,1152924950244366279, + 1152924951318107903,2305846452703726728,6051608926150,1152924945949398982,1152924947023140607,2305846448408759428,6051608926149,1152924941654431685, + 1152924942728173311,2305846444113792128,6051608926148,1152924937359464388,1152924938433206015,2305846439818824828,1152926721918375878,3428457650101, + 1152924934138238719,2305846435523857528,1152928037252110277,3424162682804,1152924929843271423,2305846431228890228,6515465394116,6516539133041, + 1152924923400820682,1152924925548304127,2305846425860181103,1152928043694561204,1152924920179595007,2305846422638955628,1152928042620819380,1152924916958369535, + 2305846419417730153,1152925848966271671,1152924913737144063,2305846416196504678,1152928031883399858,1152924910515918591,2305846412975279203,1152926643535222718, + 1152924907294693119,2305846409754053728,1152928029735916210,1152924904073467647,2305846406532828253,1152928028662174386,1152924900852242175,2305846403311602778, + 1152925850040013114,1152924897631016703,2305846400090377303,6051608926143,1152924893336047930,1152924894409791231,2305846395795410003,1152928018998499262, + 1152924889041081167,1152924890114823935,2305846391500442703,1152926812112688890,1152927927730444108,6424197338628,1152927929877924938,1152924881524886603, + 1152924880451144780,6531571521371,1152924878303661127,1152924885819856639,2305846380763024453,6530497779546,1152924874008693831,1152924875082438399, + 2305846376468057153,6529424037721,1152924869713726535,1152924870787471103,2305846372173089853,6528350295896,1152924865418759239,1152924866492503807, + 2305846367878122553,6527276554071,1152924861123791943,1152924862197536511,2305846363583155253,1152927929877927775,1152924856828824651,6051608923185, + 1152924854681342691,1152924853607601914,1152924857902569215,2305846356066962478,1152928027588433658,6410238695354,6051608923178,1152924847165148202, + 1152924846091406379,1152924850386376447,2305846348550769703,5037996643844,1152924841796442051,1152924840722697258,1152924839648955435,1152924842870183679, + 2305846342108318753,2305848202902901489,2305846339960836692,1152924834280246313,1152924833206507258,1152924836427732735,2305846335665867803,6425271080452, + 1152924828911537226,1152924827837795403,1152924828911540063,1152924825690311755,2305846329223416854,1152924823542828074,1152924822469089018,1152924829985281791, + 2305846324928449553,1152928026514690895,1152924819247863551,2305846321707224078,1152927915919284155,3343632045572,1152924813879151627,2305846317412256797, + 1152927535814678363,3306051082179,2305849430189807452,2305849431263546373,2305846312043547652,2305846310969808734,2305846309896064002,2305846308822325087, + 2305846307748580352,2305846306674841440,2305846305601096702,2305849435558513661,2305846303453613052,2305849436632255483,3292092435462,1152924795625540616, + 1152924794551801594,1152924816026638079,2305846297011162103,1152924823542831043,1152924790256834491,1152924789183092474,1152924791330576127,2305846291642452978, + 1152927913771797534,1152924784888125178,1152924785961867007,2305846287347485678,5037996641322,1152924780593157969,1152924779519416251,1152924778445674234, + 1152924781666899711,2305846280905034728,3275986310660,2305849530047796740,3268470118225,1152924772003220453,1152924770929481555,1152924769855739835, + 1152924768781997818,1152924775224448767,2305846271241358303,6051608926137,1152924764487030713,6034429056953,1152924762339544027,6049461442489, + 1152924760192060377,6038724024249,1152924758044576727,6030134089657,1152924755897093077,6029060347833,1152924753749609427,6039797766073, + 1152924751602125777,6040871507897,1152924749454642127,1152924765560772351,2305846251914005453,6051608926136,1152924745159677880,1152924746233419519, + 2305846247619038153,6051608926135,1152924740864710583,1152924741938452223,2305846243324070853,6051608926134,1152924736569743286,1152924737643484927, + 2305846239029103553,6519760360964,1152924732274775991,1152924731201034166,6518686619140,1152924729053547454,1152924727979808694,2305846231512910780, + 6517612877316,1152924724758580157,2305846228291685304,2305846227217946552,3216930507806,1152924733348517631,2305846223996718003,3330747143684, + 2305849527900313528,2305846220775495606,1152924715094903728,1152924718316132095,2305846217554267053,1152924722611099583,1152924711873681151,2305846214333041578, + 1152924722611099582,1152924708652455679,2305846211111816103,1152924722611099581,1152924705431230207,2305846207890590628,1152924722611099580,1152924702210004735, + 2305846204669365153,1152924725832325057,1152924698988779263,2305846201448139678,1152924725832325056,1152924695767553791,2305846198226914203,1152924730127292354, + 1152924692546328319,2305846195005688728,6540161455971,1152924688251361211,1152924687177619194,1152924689325102847,2305846189636979603,2305849549375149556, + 3178275805027,1152924681808909029,1152924680735168250,1152924683956393727,2305846183194528653,1152928044768301795,1152924677513942783,2305846179973303178, + 1152926714402183093,1152928020072241094,2305846176752077703,2305848148142068579,2305846174604594053,2305846173530854228,2305846172457113531,6051608923009, + 1152924674292717311,2305846169235884928,6051608924899,1152924662481557243,1152924663555299071,2305846164940917628,5895916361220,5863704103801, + 1152924657112848266,1152924656039106461,1152924659260331775,2305846158498466678,1152924658186590090,1152924651744139164,1152924652817880831,2305846154203499378, + 1152924651744139163,1152924648522913535,2305846150982273903,1152927974975083950,2305849495688058778,2305849493540575128,2305846146687306603,2305846145613567897, + 2305846144539822955,2305849491393091478,2305849489245607828,2305846141318597478,2305846140244858773,2305846139171113830,2305846138097372007,2305849487098124178, + 2305849484950640528,2305846134876146528,2305846133802407825,2305846132728662880,2305846131654921057,2305846130581179239,2305846129507439927,1152924623826848601, + 1152924622753106777,1152924622753106776,1152924620605623127,2305849494614316952,2305846123064986472,2305849490319349652,2305846120917502819,2305846119843760979, + 2305849486024382352,2305846117696277341,2305846116622535504,2305846115548793683,2305846114475054361,1152924608794463051,1152924607720721227,1152924607720721226, + 1152924605573237577,2305846109106342741,2305846145613567895,2305846106958859114,2305846140244858771,2305846104811375461,2305846103737633604,2305846133802407823, + 2305846101590149983,2305846100516408129,2305846099442666308,2305846098368926971,1152924592688335676,1152924591614593852,1152924591614593851,1152924589467110202, + 2305849496761800602,2305846091926473541,2305849492466833302,2305846089778989891,2305846088705248054,2305849488171866002,2305846086557764416,2305846085484022579, + 2305846084410280758,2305846083336541405,1152924577655950126,1152924576582208302,1152924576582208301,1152924574434724652,2305846077967829816,2305846076894088006, + 2305846075820346183,2305846074746604358,2305846130581179234,2305846072599123135,1152924566918531876,1152924565844790052,1152924565844790051,1152924563697306402, + 2305846115548793681,2305846066156672161,1152924560476080926,1152924559402339102,1152924559402339101,1152924557254855452,2305846060787960608,2305846099442666306, + 2305846058640479363,1152924552959888151,1152924551886146327,1152924551886146326,1152924549738662677,2305846084410280756,2305846052198028389,1152924546517437201, + 1152924545443695377,1152924545443695376,1152924543296211727,2305846046829316883,2305846045755575065,2305846044681833242,2305846043608091417,2305846042534349606, + 2305846130581179228,2305846040386868295,1152924534706277126,1152924533632535302,1152924533632535301,1152924531485051652,2305846115548793678,2305846033944417321, + 1152924528263826176,1152924527190084352,1152924527190084351,1152924525042600702,2305846028575705858,2305846099442666303,2305846026428224523,1152924520747633401, + 1152924519673891577,1152924519673891576,1152924517526407927,2305846084410280753,2305846019985773549,1152924514305182451,1152924513231440627,1152924513231440626, + 1152924511083956977,2305846014617062133,2305846013543320315,2305846012469578492,2305846011395836667,2305846010322094856,2305846009248353062,2998960920370, + 1152924502494022508,1152924645301688063,2305846004953385703,2305846093000215352,2305846002805902119,2305846053271767827,2305846000658418442,2305845999584676579, + 2305846021059513077,2305845997437192940,2305845996363451104,2305845995289709283,2985002276657,1152924488535378796,1152924499272799999,2305845990994741978, + 2305846075820346154,2305845988847258409,2305846044681833229,2305845986699774732,2305845985626032854,2305846012469578479,2305845983478549230,2305845982404807379, + 2305845981331065558,2971043632944,1152924474576735084,1152924485314156287,2305845977036098253,2305846124138728277,2305845974888614615,2305846067230411552, + 2305845972741130965,2305845971667389129,2305846035018156802,2305845969519905490,2305845968446163654,2305845967372421833,2957084989231,1152924460618091372, + 1152924471355512575,2305845963077454528,2305846009248353033,2951716280110,1152924455249382252,1152924457396868863,2305845957708745403,2305845995289709281, + 2946347570989,1152924449880673132,1152924452028159743,2305845952340036278,2305845981331065556,2940978861868,1152924444511964012,1152924446659450623, + 2305845946971327153,2305845967372421831,2935610152747,1152924439143254892,1152924441290741503,2305845941602618028,2305846009248353003,2930241443626, + 1152924433774545772,1152924435922032383,2305845936233908903,2305845995289709278,2924872734505,1152924428405836652,1152924430553323263,2305845930865199778, + 2305845981331065553,2919504025384,1152924423037127532,1152924425184614143,2305845925496490653,2305845967372421828,2914135316263,1152924417668418412, + 1152924419815905023,2305845920127781528,1152927974975083921,2305849496761799991,1152924412299709076,1152924411225967252,1152924411225967251,1152924409078483602, + 2305849495688058137,1152924406930999951,1152924405857258127,1152924405857258126,1152924403709774477,2305845907242879632,2305849494614316283,1152924400488549001, + 1152924399414807177,1152924399414807176,1152924397267323527,2305849493540574429,1152924395119839876,1152924394046098052,1152924394046098051,1152924391898614402, + 2305845895431719557,2305845894357977738,2305845893284235915,2305845892210494090,2305849492466832575,1152924385456163451,1152924384382421627,1152924384382421626, + 1152924382234937977,2305849491393090721,1152924380087454326,1152924379013712502,1152924379013712501,1152924376866228852,2305845880399334007,2305849490319348867, + 1152924373645003376,1152924372571261552,1152924372571261551,1152924370423777902,2305849489245607013,1152924368276294251,1152924367202552427,1152924367202552426, + 1152924365055068777,2305845868588173932,2305845867514432113,2305845866440690290,2305845865366948465,2305845864293206652,2305849488171865159,1152924357538876001, + 1152924356465134177,1152924356465134176,1152924354317650527,2305849487098123305,1152924352170166876,1152924351096425052,1152924351096425051,1152924348948941402, + 2305845852482046557,2305849486024381451,1152924345727715926,1152924344653974102,1152924344653974101,1152924342506490452,2305849484950639597,1152924340359006801, + 1152924339285264977,1152924339285264976,1152924337137781327,2305845840670886482,2305845839597144663,2305845838523402840,2305845837449661015,2305845836375919202, + 2305845835302177404,2825014744882,1152924328547846805,1152924414447195903,2305845831007210053,2305845900800428677,2305845828859726461,2305845873956883052, + 2305845826712242788,2305845825638500929,2305845846039595602,2305845823491017290,2305845822417275454,2305845821343533633,2811056101169,1152924314589203093, + 1152924325326624511,2305845817048566328,2305845893284235904,2305845814901082751,2305845866440690279,2305845812753599078,2305845811679857204,2305845838523402829, + 2305845809532373580,2305845808458631729,2305845807384889908,2797097457456,1152924300630559381,1152924311367980799,2305845803089922603,2305845912611588752, + 2305845800942438965,2305845885768043127,2305845798794955315,2305845797721213479,2305845857850755677,2305845795573729840,2305845794499988004,2305845793426246183, + 2783138813743,1152924286671915669,1152924297409337087,2305845789131278878,2305845835302177379,2777770104622,1152924281303206549,1152924283450693375, + 2305845783762569753,2305845821343533631,2772401395501,1152924275934497429,1152924278081984255,2305845778393860628,2305845807384889906,2767032686380, + 1152924270565788309,1152924272713275135,2305845773025151503,2305845793426246181,2761663977259,1152924265197079189,1152924267344566015,2305845767656442378, + 2305845835302177353,2756295268138,1152924259828370069,1152924261975856895,2305845762287733253,2305845821343533628,2750926559017,1152924254459660949, + 1152924256607147775,2305845756919024128,2305845807384889903,2745557849896,1152924249090951829,1152924251238438655,2305845751550315003,2305845793426246178, + 2740189140775,1152924243722242709,1152924245869729535,2305845746181605878,1152927974975083891,2903397898141,1152924238353533427,1152924240501020415, + 2305845740812896753,1152927532593452957,2305849495688058781,2305845737591671658,2305845736517929828,2305845735444187998,2305845734370445806,2305845733296706841, + 1152924227616115176,1152924226542373352,1152924226542373351,1152924224394889702,2718714304412,1152924222247406067,1152924235132311295,2305845724706769378, + 1152927532593452956,2305845734370449305,2305845721485547420,1152927990007469556,2305845719338060266,1152927531519711127,2305845717190576618,2305845716116834779, + 2305845715043092957,1152927987859985922,2305845712895609322,1152927986786244088,2305845710748125674,2305845709674383829,1152927530445969300,2305845707526900202, + 1152927529372227475,2305845705379416554,2305845704305674704,2305845703231932882,2305845702158191063,1152927528298485650,2305845700010707434,1152927527224743825, + 2305845697863223786,2305845696789481929,1152927526151002000,2305845694641998314,1152927980343793164,2305845692494514666,2305845691420772804,2305845690347030982, + 2305845689273289163,2305845688199547359,2305845687125808379,1152924181445216701,1152924180371474877,1152924180371474876,1152924178223991227,2672543405979, + 1152924176076507635,1152924219026183935,2305845678535870903,1152927532593452955,2305845688199547358,2305845675314648987,1152924213657474548,2305845673167161791, + 1152924211509990893,2305845671019678143,2305845669945936304,2305845668872194482,1152924207215023618,2305845666724710847,1152924205067539960,2305845664577227199, + 2305845663503485354,1152924201846314476,2305845661356001727,1152924199698830827,2305845659208518079,2305845658134776229,2305845657061034407,2305845655987292588, + 1152924194330121706,2305845653839808959,1152924192182638057,2305845651692325311,2305845650618583454,1152924188961412584,2305845648471099839,1152924186813928972, + 2305845646323616191,2305845645249874329,2305845644176132507,2305845643102390688,2305845642028648884,2631741216562,1152924135274318323,1152924172855285503, + 2305845637733681553,2305845642028648883,2626372507441,1152924129905609203,1152924132053096191,2305845632364972428,1152924167486576116,2305845630217488788, + 2619930056496,1152924123463158259,1152924126684387071,2305845625922521478,1152924165339092461,2305845623775037844,2613487605551,1152924117020707315, + 1152924120241936127,2305845619480070528,1152924161044125186,2305845617332586900,2607045154606,1152924110578256371,1152924113799485183,2305845613037619578, + 1152924158896641528,2305845610890135956,2600602703661,1152924104135805427,1152924107357034239,2305845606595168628,1152924155675416044,2305845604447685012, + 2594160252716,1152924097693354483,1152924100914583295,2305845600152717678,1152924153527932395,2305845598005234068,2587717801771,1152924091250903539, + 1152924094472132351,2305845593710266728,1152924148159223274,2305845591562783124,2581275350826,1152924084808452595,1152924088029681407,2305845587267815778, + 1152924146011739625,2305845585120332180,2574832899881,1152924078366001651,1152924081587230463,2305845580825364828,1152924142790514152,2305845578677881236, + 2568390448936,1152924071923550707,1152924075144779519,2305845574382913878,1152924140643030540,2305845572235430292,2561947997991,1152924065481099763, + 1152924068702328575,2305845567940462928,1152927974975083861,2305849029684107162,1152924060112390476,1152924059038648652,1152924059038648651,1152924056891165002, + 2551210579869,1152924054743681357,1152924062259877631,2305845557203044678,2305845733296706871,1152924050448714051,1152924049374972227,1152924049374972226, + 1152924047227488577,2305845721485546777,1152924045080004926,1152924044006263102,1152924044006263101,1152924041858779452,2305845545391884607,2305845718264321275, + 1152924038637553976,1152924037563812152,1152924037563812151,1152924035416328502,2305845716116837597,1152924033268844851,1152924032195103027,1152924032195103026, + 1152924030047619377,2305845533580724532,2305845532506982713,2305845531433240890,2305845530359499065,2305845711821870271,1152924023605168426,1152924022531426602, + 1152924022531426601,1152924020383942952,2305845709674386593,1152924018236459301,1152924017162717477,1152924017162717476,1152924015015233827,2305845518548338982, + 2305845706453161091,1152924011794008351,1152924010720266527,1152924010720266526,1152924008572782877,2305845704305677413,1152924006425299226,1152924005351557402, + 1152924005351557401,1152924003204073752,2305845506737178907,2305845505663437088,2305845504589695265,2305845503515953440,2305845502442211627,2305845698936968263, + 1152923995687880976,1152923994614139152,1152923994614139151,1152923992466655502,2305845696789484585,1152923990319171851,1152923989245430027,1152923989245430026, + 1152923987097946377,2305845490631051532,2305845693568259083,1152923983876720901,1152923982802979077,1152923982802979076,1152923980655495427,2305845691420775405, + 1152923978508011776,1152923977434269952,1152923977434269951,1152923975286786302,2305845478819891457,2305845477746149638,2305845476672407815,2305845475598665990, + 2305845474524924177,2305845473451182379,2463163750194,1152923966696851789,1152924051522459391,2305845469156215028,2305845538949433652,2305845467008731436, + 2305845512105888027,2305845464861247763,2305845463787505904,2305845484188600577,2305845461640022265,2305845460566280429,2305845459492538608,2449205106481, + 1152923952738208077,1152923963475629823,2305845455197571303,2305845531433240879,2305845453050087726,2305845504589695254,2305845450902604053,2305845449828862179, + 2305845476672407804,2305845447681378555,2305845446607636704,2305845445533894883,2435246462768,1152923938779564365,1152923949516986111,2305845441238927578, + 2305845550760593727,2305845439091443940,2305845523917048102,2305845436943960290,2305845435870218454,2305845495999760652,2305845433722734815,2305845432648992979, + 2305845431575251158,2421287819055,1152923924820920653,1152923935558342399,2305845427280283853,2305845473451182354,2415919109934,1152923919452211533, + 1152923921599698687,2305845421911574728,2305845459492538606,2410550400813,1152923914083502413,1152923916230989567,2305845416542865603,2305845445533894881, + 2405181691692,1152923908714793293,1152923910862280447,2305845411174156478,2305845431575251156,2399812982571,1152923903346084173,1152923905493571327, + 2305845405805447353,2305845473451182328,2394444273450,1152923897977375053,1152923900124862207,2305845400436738228,2305845459492538603,2389075564329, + 1152923892608665933,1152923894756153087,2305845395068029103,2305845445533894878,2383706855208,1152923887239956813,1152923889387443967,2305845389699319978, + 2305845431575251153,2378338146087,1152923881871247693,1152923884018734847,2305845384330610853,1152927978196309898,1152923878650025727,2305845381109385378, + 1152927977122568074,1152923875428800255,2305845377888159903,1152927976048826250,1152923872207574783,2305845374666934428,2305848937342309806,2305845372519454067, + 2305845371445712213,6474663204646,1152923864691378327,1152923863617640330,1152923868986349311,2305845366076999828,6380173923844,1152923859322673054, + 1152923858248931106,1152923857175185559,1152923860396414719,2305845359634548878,6358699087364,1152923852880222090,1152923851806476439,1152923853953963775, + 2305845354265839753,1152927995376179102,6051608922246,1152923846437771004,1152923848585254655,2305845348897130628,1152926916265645982,1152923843216545535, + 2305845345675905153,1152926909823195038,1152923839995320063,2305845342454679678,1152926903380744094,1152923836774094591,2305845339233454203,6051608926092, + 1152923832479127436,1152923831405385630,1152923833552869119,2305845333864745078,1152927820356261790,1152923827110418230,6490769331716,6489695589892, + 1152923823889188977,6379100182020,6378026440196,1152923820667963502,1152923819594221679,1152923818520479858,1152923828184159999,2305845320979843178, + 1152923824962934684,1152923821741709108,6051608922214,1152923812078028903,1152923811004287090,1152923815299258111,2305845313463650403,1152923814225516428, + 1152923806709323678,1152923807783065343,2305845309168683103,1152923814225516427,1152923802414356382,1152923803488098047,2305845304873715803,1152923858248931229, + 1152923799193130751,2305845301652490328,1152923822815447160,1152923794898159760,1152923795971905279,2305845297357523028,6051608922220,1152923790603196317, + 1152923789529450610,1152923791676937983,2305845291988813903,1152923823889192861,6488621848068,1152923784160741452,1152923783087003534,1152923782013261726, + 1152923786308228863,2305845284472621128,1152923783087003531,1152923777718294430,1152923778792036095,2305845280177653828,1152923820667967285,6376952698372, + 1152923772349581377,1152923771275839602,6360846571012,1152923769128355902,6478958171946,1152923766980872252,1152923774497068799,2305845269440235578, + 6477884430121,1152923762685904956,1152923763759650559,2305845265145268278,6476810688296,1152923758390937660,1152923759464683263,2305845260850300978, + 6475736946471,1152923754095970364,1152923755169715967,2305845256555333678,2305849497835542428,1152927993228695452,2244120414251,1152923747653523357, + 1152923746579781534,2305849372207748902,2305845249039144843,2238751709070,1152923742284810279,1152923750874748671,2305845244744173603,6051608926091, + 1152923737989846923,1152923736916101159,1152923739063588607,2305845239375464478,1152923745506039698,1152923733694879487,2305845236154239003,1152923745506039697, + 1152923730473654015,2305845232933013528,1152923745506039696,1152923727252428543,2305845229711788053,1152923745506039695,1152923724031203071,2305845226490562578, + 6472515720708,2305849386166392628,1152927881559545652,2212981901326,1152923716515010357,1152923715441264790,1152923714367522831,1152923713293781103, + 1152923712220043166,1152923720809977599,2305845214679402503,1152923822815451038,1152923707925071887,1152923706851330059,6359772829579,1152923704703846402, + 1152923708998817535,2305845207163209728,6375878956846,1152923700408883083,2305845203941988146,2193654552474,1152923697187653634,1152923701482624767, + 2305845199647016953,6374805215021,1152923692892690315,2305845196425795377,2186138359705,1152923689671460866,1152923693966431999,2305845192130824178, + 6373731473196,1152923685376497547,2305845188909602608,2178622166936,1152923682155268098,1152923686450239231,2305845184614631403,6372657731371, + 1152923677860304779,2305845181393409839,2171105974167,1152923674639075330,1152923678934046463,2305845177098438628,1152927882633287477,1152923670344108096, + 1152923669270370084,2305845172803475341,2162516039461,1152923666049140850,1152923671417853695,2305845168508504028,1152923784160741487,1152923661754177422, + 1152923660680435614,1152923662827919103,2305845163139794903,1152923661754177419,1152923656385468318,1152923657459209983,2305845158844827603,1152923822815451035, + 1152927976048826252,1152923651016755152,1152923649943017374,1152923653164242687,2305845152402376653,5407363830714,1152923645648049066,1152923646721791743, + 2305845148107409353,6378026440604,1152923641353081808,1152923640279336906,1152923642426824447,2305845142738700228,6051608926105,1152923635984373657, + 1152923634910627786,1152923637058115327,2305845137369991103,6051608926104,1152923630615664536,1152923629541918666,1152923631689406207,2305845132001281978, + 6051608926103,1152923625246955415,1152923624173209546,1152923626320697087,2305845126632572853,6051608926102,1152923619878246294,1152923618804500426, + 1152923620951987967,2305845121263863728,6051608926101,1152923614509537173,1152923613435791306,1152923615583278847,2305845115895154603,6051608926100, + 1152923609140828052,1152923608067082186,1152923610214569727,2305845110526445478,6051608926099,1152923603772118931,1152923602698373066,1152923604845860607, + 2305845105157736353,6051608926098,1152923598403409810,1152923597329663946,1152923599477151487,2305845099789027228,2305846089778993047,2305845097641543577, + 2305845096567805848,2305845095494059927,2305845094420322201,2084132886427,1152923587665990622,1152923586592245706,1152923594108442367,2305845089051608978, + 2305846086557767571,2305845086904125327,2305845085830387604,2305845084756641677,2305845083682903957,2073395468186,1152923576928572382,1152923575854827466, + 1152923583371024127,2305845078314190728,1152927982491276254,1152923571559860170,1152923572633605887,2305845074019223428,1152927546552096667,2305849380797683502, + 2305845070798001967,2305845069724256127,2305845068650518320,2305845067576772477,2305845066503034673,2305845065429288833,2055141857075,1152923558674961360, + 1152923557601216458,1152923568338638591,2305845060060579703,1152927546552096666,2305849376502716202,2305845056839358251,2305845055765612402,2305845054691874604, + 2305845053618128752,2305845052544390957,2305845051470645108,2041183213362,1152923544716317648,1152923543642572746,1152923554379994879,2305845046101935978, + 6366215280528,1152923539347608528,1152923538273863626,1152923540421351167,2305845040733226853,1152927982491276795,1152927983565019030,2305845037512001377, + 1152927984638760855,2305845035364517728,2305845034290775902,1152927985712502680,2305845032143292253,2305845031069550427,1152927986786244505,2305845028922066778, + 2305845027848324962,2305849488171866006,2027224565590,2305845024627099477,2305849489245607831,2305845035364517727,2012192180051,2305845020332132180, + 2305845019258390352,2305849490319349656,2305845032143292252,2006823470926,2305845014963423055,2305845013889681227,2305849491393091481,2305845028922066777, + 2001454761801,2305845009594713930,2018634630982,1152923502840387484,2305845006373488471,1996086056848,1152923499619157962,1152923535052642047, + 2305845002078521153,5400921379756,1152923495324193700,1152923496397936383,2305844997783553853,1152923495324193694,1152923492102969087,2305844994562328378, + 1152923495324190646,1152923488881743615,2305844991341102903,1152923495324190641,1152923485660518143,2305844988119877428,1152927988933728154,1152923481365546814, + 1152923482439292671,2305844983824910128,1152927987859986330,1152923477070579518,1152923478144325375,2305844979529942828,1152923645648049973,1152923472775612525, + 6375878956548,1152923470628128832,6371583989252,6372657731076,1152923467406903077,1961726318084,1152923465259419430,2305844968792528691, + 1152923463111939884,1152923462038193960,1152923473849358079,2305844964497557278,1152923468480649007,1953136383492,1152923456669484838,2305844960202594099, + 1152923454522005291,1152923453448259368,1152923458816972543,2305844955907622678,1152923467406907182,1944546448900,1152923448079550246,2305844951612659507, + 1152923445932070698,1152923444858324776,1152923450227037951,2305844947317688078,1152927876190836527,1935956514308,1152923439489615654,2305844943022724915, + 1152923437342136105,1152923436268390184,1152923441637103359,2305844938727753478,1152926911970678522,1152923431973423213,6486474364721,1152923429825943334, + 6051608921856,1152923427678455552,1152923426604713730,1152923433047168767,2305844929064077053,1924145354244,1152923422309746433,1152923421236004610, + 1152923423383492351,2305844923695367928,6485400622896,1152923416941041446,6051608921844,1152923414793553652,1152923413719811834,1152923412646070018, + 1152923418014783231,2305844915105433328,1911260452356,1152923408351102709,1152923407277360890,1152923406203619074,1152923409424848639,2305844908662982378, + 1152923408351102714,1897301808933,1152923400834909954,1152923402982397695,2305844903294273253,1152923428752197377,1152923415867295477,6482179397421, + 1152923394392458989,2305844897925564129,1152923392244975354,2305844895778080482,1152927868674643442,2305844893630601000,1883343161052,1152923386876266242, + 1152923397613688575,2305844889335629528,1152923394392463142,1877974455812,2305845276956428363,1152923380433815252,1152923379360077605,1152923378286331650, + 1152923383655044863,2305844880745694928,1152927866527160212,1869384521516,1152923372917622560,1152923371843880706,1152923375065110271,2305844874303243978, + 1152927866527160211,1862942070571,1152923366475171608,1152923365401429762,1152923368622659327,2305844867860793028,1152927866527160210,1856499619626, + 1152923360032720656,1152923358958978818,1152923362180208383,2305844861418342078,1152927866527160209,1850057168681,1152923353590269704,1152923352516527874, + 1152923355737757439,2305844854975891128,1152923495324194612,1152923348221560896,6051608926002,1152923346074076852,1152923349295306495,2305844848533440178, + 5394478928806,1152923341779112862,1152923342852855551,2305844844238472878,1152923341779109824,1152923338557888255,2305844841017247403,1152923341779113779, + 1152923334262921114,1152923335336662783,2305844836722280103,1152926905528227578,1152923329967949888,1152927880485803930,1152923327820465827,1152923331041695487, + 2305844830279829153,1152927991081211802,1152923323525498543,1152923324599244543,2305844825984861853,6374805214724,1152923319230535577,1152923318156789543, + 1152923317083047587,1152923320304277247,2305844819542410903,1152923328894207783,1152923312788080373,1152923313861826303,2305844815247443603,6483253139246, + 1152923308493112980,1152923309566859007,2305844810952476303,6486474364420,1152923304198149624,2305844807731255191,2305844806657513369,1796370077487, + 1152923299903178388,1152923305271891711,2305844802362541703,2305844808804992652,2305844800215062316,2305844799141320601,1788853884820,1152923292386985620, + 1152923296681957119,2305844794846348928,2104533981073,1152923288092018316,1152923287018276527,1152923289165764351,2305844789477639803,1152923319230535462, + 1152927874043352881,2305844786256414328,6474663200374,1152923279502083724,1152923278428341923,1152923283797055231,2305844780887705203,1152923341779113881, + 1152927986786244501,6051608921711,1152923271985895317,1152923270912149104,1152923275207120639,2305844773371512428,1152927978196309909,2305844771224033168, + 1152927978196309904,1152927977122568079,1152923263395960313,2305844766929061479,1756641625704,1152923260174730863,2305844763707836004,1753420404627, + 1152923256953505392,1152923267690927871,2305844759412868703,1152927977122568085,2305844757265389455,1152927977122568080,1152927978196309903,2305844754044159578, + 1743756723803,1152923247289828975,2305844750822934104,1740535502738,1152923244068603504,1152923253732284159,2305844746527966803,1152923328894211890, + 2305844744380483240,1152923238699894412,1152923240847382271,2305844741159257678,1152923238699898771,1152923235478673151,2305844737938032203,1152923238699898770, + 1152923232257447679,2305844734716806728,1152923238699898772,1152923229036222207,2305844731495581253,1152927524003518344,2305844729348102025,1152923223667513084, + 1152923225814996735,2305844726126872128,1152928053358237651,1152923219372546003,1152923218298804179,1152923217225062355,1152923216151320531,1152923215077578707, + 1152923214003836883,1152923212930095059,1152923211856353235,1152923210782611411,1152923209708869587,1152923208635127763,1152923207561385939,1152923206487644115, + 1152923205413902291,1152927522929772079,2305844707873265618,2305844706799523683,1152928053358237643,2305844704652035628,6051608921642,2305848156732003053, + 2305844701430813425,2305844700357071538,1152923194676479529,2305844698209588740,1152923219372545971,2305844696062101034,6051608921634,1152923189307773624, + 2305844692840875556,1152923218298804176,2305844690693391914,1152923217225062335,2305844688545908253,1152923216151320510,2305844686398424603,1152923215077578703, + 2305844684250940953,1152923214003836878,2305844682103457303,1152923212930095053,2305844679955973653,1152923211856353228,2305844677808490003,6051608921617, + 1152923171054162787,2305844674587264543,1152923217225062307,2305844672439780893,1152923216151320482,2305844670292297228,2305844669218555416,2305844668144813590, + 2305844667071071764,2305844665997329938,6051608921606,1152923159243002708,2305844662776104462,2305844706799523787,1152928053358237539,2305844659554878978, + 1152923219372545982,2305844657407395328,6051608921598,6525129070084,6524055324156,1152923148505584463,1152923147431839229,2305844650964944387, + 1152923218298804072,2305844648817460734,1152923217225062203,2305844646669977078,1152923216151320378,2305844644522493428,1152923215077578599,2305844642375009778, + 1152923214003836774,2305844640227526128,1152923212930094949,2305844638080042478,1152923211856353124,2305844635932558828,6051608921578,1152923129178231108, + 2305844632711333368,1152928053358237530,2305844630563849772,2305844630563849730,1152923123809519077,6051608921571,1152923121662038757,2305844625195140583, + 1152927521856030255,2305844623047661522,2305844621973919681,2305844621973919680,1152923115219584477,2305844621973919679,1152923113072100827,2305844621973919678, + 1152923110924617177,6051608921559,1152923108777136890,2305844612310238688,1152923204340160002,2305844610162759634,2305844609089017792,1152928053358237631, + 2305844606941529553,2305844605867787775,1152923218298804157,2305844603720304078,2305844609089017667,1152928053358237506,2305844600499078603,1152923219372545857, + 2305844598351594953,1152923218298804032,2305844596204111303,1152923090523522508,6051608921540,1152926703664763749,1152923087302301621,1152923086228555203, + 2305844589761660372,2305844621973919647,6051608921534,2305844586540439044,1152923080859846079,1152927861158450662,2305844583319213857,1152927970680116710, + 2305844581171730311,2305844580097984065,1152927968532633062,2305844577950504837,2305844576876758454,1152927966385149414,2305844574729279363,2305844573655532979, + 1152927964237665766,2305844571508053889,2305844570434307504,1152927962090182118,2305844568286828415,2305844567213082029,1152927959942698470,2305844565065602941, + 2305844563991856554,1553704420793,1152923057237525947,2305844699283329720,2305844559696892771,2305844558623150932,1642824992162,4745938863521, + 1620276413924,1152923049721336549,2305844553254438304,1152923115219588598,1611686479260,1152923045426365914,1609538995610,1152923043278882264, + 1607391511960,1152923041131401978,2305844544664503709,1152923090523526646,2305844542517020108,1152923036836431297,2305844540369536405,1152923034688947644, + 1152927998597399999,2305844537148310928,1526860875173,1152923030393984762,1152923220446287615,2305844532853343628,6323265603137,1152923026099017467, + 1152923027172759295,2305844528558376328,1152923204340159988,2305844526410897362,2305844525337155523,1152928053358237634,2305844523189667203,1152923219372545985, + 2305844521042183553,1152923218298804160,2305844518894699903,1152923217225062243,2305844516747216253,2305844515673474618,2305844525337155519,1152928053358237630, + 2305844512452248953,1152923219372545981,2305844510304765303,1152923218298804156,2305844508157281653,2305844507083539836,1152923001402951034,6051608921457, + 1152922999255470829,2305844502788576772,1152928018998499267,1152928031883399112,2305844499567347053,2305844498493605252,1152928018998499266,1152925842523822014, + 2305844495272379753,1152922989591795667,2305844493124896106,1152925842523822013,2305844490977414888,1152922985296823869,2305844488829928805,1152925842523822012, + 2305844486682447582,1152922981001856572,2305844484534961505,2305844483461219708,6051608921436,1152922976706892474,2305844480239994222,2305844515673474617, + 6051608921432,1152928018998497976,2305844475945029946,1152922970264438103,2305844473797543257,6051608921467,2305846174604596072,2305844470576321369, + 1152922964895728978,2305844468428834131,1152924882598628427,1152922961674505240,1455993918980,1152922961674508128,1453846431051,1152927928804183115, + 1152922956305799008,1450625205577,1152922954158310970,2305844457691415931,1152923215077578687,2305844455543932228,6051608921410,1152922948789605199, + 2305844452322706766,1152927520782288431,2305844450175227858,2305844449101485923,6051608926138,1152922942347155411,2305844445880255804,1152923219372545986, + 2305844443732772153,6034429056954,1152922936978446291,2305844440511546684,2305844439437804928,1152922933757216055,6049461442490,1152922931609737171, + 2305844435142837564,1152923219372545984,2305844432995353903,1152922927314765106,6038724024250,1152922925167286227,2305844428700386620,1152923219372545983, + 2305844426552902953,1152922920872314156,6030134089658,1152922918724835283,2305844422257935676,2305844421184194047,1152922915503605030,6029060347834, + 1152922913356126163,2305844416889226556,2305844415815484790,1152922910134895905,6039797766074,1152922907987417043,2305844411520517436,1152923219372545980, + 2305844409373033753,1152922903692444956,6051608921366,1152922901544966072,2305844405078066495,6051608921388,1152922898323737526,2305844401856840979, + 6051608921399,1152922895102512057,2305844398635615504,2305844449101486027,1152928053358237626,2305844395414390028,1152926542603491147,2305846301306129415, + 1152922887586319396,2305844391119422729,1152922885438834237,2305844388971939082,6051608921348,1152924848238892987,1152922881143866627,2305844384676971789, + 1152922878996383159,2305844506009798010,1152922876848902893,2305844380382007994,2305844379308262741,2305844378234520912,5307505837306,2305844438364063031, + 1152922870406448429,2305844373939553586,1152922868258964775,2305844371792069932,1152922866111481122,2305844369644586278,1152922863963997469,2305844367497102625, + 1152922861816513815,2305844365349618972,1152922859669034936,2305844363202135289,1152922868258966454,2305844361054651628,2305844359980911545,1376537019625, + 1348619732223,1152922852152841978,1152923022877791999,2305844354612200678,1152923075491141371,1152922848931616511,2305844351390975203,1152923204340159984, + 2305844349243496402,2305844348169754467,1152923219372545987,2305844346022266078,1152923218298804162,2305844343874782428,1152923217225062337,2305844341727298778, + 1152923216151320512,2305844339579815128,2305844338506073411,1152923214003836862,2305844336358589653,1152923212930095037,2305844334211106003,1152923211856353212, + 2305844332063622353,1152927519708546612,2305844329916138703,6051608921293,1152922823161811693,2305844326694917636,1152927518634804788,2305844324547429583, + 6051608921288,1152922817793102522,2305844321326204106,1152927517561062964,2305844319178720463,6051608921283,1152922812424393400,2305844315957494981, + 6051608921295,1152922809203167985,2305844312736269504,1152927516487321140,2305844310588785871,1152927515413579311,2305844308441307090,2305844307367565160, + 1152927514339842003,2305844305220076728,1152923219372546001,2305844303072593078,1152927513266095676,2305844300925109428,1152922795244520635,6051608921265, + 1152922793097040744,2305844296630142141,1152927512192353844,2305844294482658511,1152923204340159992,2305844292335179730,2305844291261437891,2305844290187691394, + 2305844289113949568,2305844288040207742,1152922782359618732,6051608921253,1152922780212138841,2305844283745240238,1152923204340159993,2305844281597761490, + 2305844280524019555,2305844279450272989,2305844278376531163,2305844277302789337,2305844276229047511,2305844275155305795,2305844274081563860,2305844273007822034, + 2305844271934080208,6051608921239,1152922765179753189,2305844268712854690,1152923204340159994,2305844266565375954,2305849549375149572,2305844264417887378, + 2305844263344145629,2305844262270403803,2305844261196661977,2305844260122920151,6051608921228,1152922753368593231,2305844256901694612,1152927511118612020, + 2305844254754211038,1152927510044870191,2305844252606732242,2305844251532990416,1152928053358237619,2305844249385501828,2305844248311760053,1152923218298804163, + 2305844246164276353,1152923217225062338,2305844244016792703,1152923216151320513,2305844241869309053,1152923215077578688,2305844239721825403,1152922734041236615, + 6051608921208,1152922731893756594,2305844235426858121,1152927508971128372,2305844233279374558,1152922727598785657,1152922726525043890,6051608921201, + 1152922724377564003,2305844227910665333,2305844348169754561,2305844225763182115,1152923218298804146,2305844223615698028,1152923217225062321,2305844221468214378, + 1152923216151320496,2305844219320730728,1152923215077578671,2305844217173247078,1152923214003836846,2305844215025763428,1152923212930095021,2305844212878279778, + 1152923211856353196,2305844210730796128,1152923210782611371,2305844208583312478,1152923209708869546,2305844206435828828,1152923208635127721,2305844204288345178, + 1152923207561385896,2305844202140861528,1152923204340159983,2305844199993382866,2305849538637731332,2305844197845894228,1152923219372545867,2305844195698410578, + 1152923218298804042,2305844193550926928,1152923217225062217,2305844191403443278,1152923216151320392,2305844189255959628,1152923215077578567,2305844187108475978, + 1152923214003836742,2305844184960992328,1152923212930094917,2305844182813508678,1152923211856353092,2305844180666025028,1152923210782611267,2305844178518541378, + 1152923209708869442,2305844176371057728,1152923208635127617,2305844174223574078,1152923207561385792,2305844172076090428,1152922666395501654,6051608921145, + 1152922664248021379,2305844167781123182,1152923204340160001,2305844165633644498,2305844164559902657,1152928053358237614,2305844162412414003,2305844161338672675, + 2305844160264930411,2305844159191188585,2305844158117446759,1152927539035903919,2305844155969968065,6051608921131,2305844153822479506,1152923219372545965, + 2305844151674995753,1152923218298804140,2305844149527512103,1152923217225062315,2305844147380028453,1152923216151320490,2305844145232544803,1152922639551956013, + 6051608921120,1152922637404476149,2305844140937577526,1152927507897386543,2305844138790098898,2305844137716357057,2305844136642610226,2305844135568868899, + 2305844134495126635,2305844133421384809,2305844132347642983,1152922626667054113,1152927506823644719,2305844129126422482,2305844128052675627,2305844126978933810, + 1152923219372545961,2305844124831450128,1152923218298804136,2305844122683966478,1152923217225062311,2305844120536482828,1152923216151320486,2305844118388999178, + 1152922612708410388,1152927543330871215,2305844115167778753,6051608921093,2305844113020290194,2305844111946548305,2305844110872806479,2305844109799064653, + 2305844108725322827,1152922603044733959,6051608921086,1152922600897253902,2305844104430355485,1152927505749902895,2305844102282876882,2305844101209135056, + 2305844100135388291,2305844099061646517,1152923218298804043,2305844096914162678,6051608921076,1152926727287083778,1152922589086094187,1152922588012352243, + 1152922586938611658,1152922585864864755,2305844089397969915,2305844307367565264,2305844087250486403,2305844086176744629,2305844085103002731,6051608921065, + 1152926246250746726,1152922577274935242,1152922576201188328,2305844079734293485,2305844096914162817,1152923217225062218,2305844076513068003,1152923216151320393, + 2305844074365584353,1152923215077578568,2305844072218100703,6051608921053,1152922577274934117,1152922564390028252,2305844067923133412,1152927504676161071, + 2305844065775654866,2305844064701913040,2305844063628166275,2305844062554424501,2305844061480682603,2305844060406940777,2305844059333198951,2305844058259457125, + 6051608921040,1152926246250746602,1152922550431388517,1152922549357642703,2305844052890747865,1152928053358237607,2305844050743264388,2305844049669522613, + 2305844048595780597,2305844047522038754,2305844046448296928,2305844045374555102,2305849047937718183,1152922538620229587,2305844042153329796,2305844041079588021, + 1152923218298804039,2305844038932104128,1152923217225062214,2305844036784620478,1152923216151320389,2305844034637136828,1152923215077578564,2305844032489653178, + 1152922526809064388,6051608921015,1152926678968702922,1152922523587844021,1152922522514097078,2305844026047202251,2305844050743264214,2305844023899718837, + 2305844022825976821,2305844021752234978,2305844020678493152,2305844019604751326,2305844042153329622,2305844017457267893,2305844016383525823,2305844015309783997, + 2305844014236042171,2305844013162300345,1152922507481711532,6051608920997,1152926678968701797,1152922504260491189,1152922503186744228,2305844006719849394, + 1152923218298802455,2305844004572365768,1152923217225060143,2305844002424882078,1152923216151318314,2305844000277398428,1152923215077576485,2305843998129914778, + 1152923218298801952,2305843995982431168,1152923217225060123,2305843993834947478,1152923216151318294,2305843991687463828,1152923215077576465,2305843989539980178, + 1152922483859391384,1152928053358237606,2305843986318754774,2305843985245013173,2305843984171271157,2305843983097529314,2305843982023787488,2305843980950045662, + 1152922475269456783,2305849047937718182,1152922473121978323,2305843976655078358,2305843975581336757,2305843974507594687,2305843973433852861,2305843972360111035, + 2305843971286369209,1152922465605780359,6051608920958,1152922463458299737,2305843966991401888,2305843986318754808,2305843964843918517,1152923218298804161, + 2305843962696434553,2305849509646702084,1152922455942109139,2305843959475209208,2305843958401467573,2305843957327725950,1152922451647136631,6051608920945, + 1152922449499655988,2305843953032758139,2305843986318754948,2305843950885274805,2305843949811532664,1152923217225062336,2305843947664049003,1152923216151320511, + 2305843945516565353,1152923215077578686,2305843943369081703,6051608920933,1152922436614753950,2305843940147856238,1152923204340159995,2305843938000377810, + 2305843936926631043,1152923219372545970,2305843934779147103,2305843933705405429,6051608920924,1152926246250746611,1152922425877336933,1152922424803591003, + 2305843928336696162,2305843936926635524,2305843926189212803,2305843925115470686,2305843924041729013,6051608920915,1152922425877338058,1152922416213656402, + 2305843919746761559,1152927503602419247,2305843917599283154,2305843916525541329,1152928053358237648,2305843914378052428,2305843913304311331,2305843912230569067, + 2305843911156827241,6051608920903,1152928043694560102,1152922403328759733,1152922402255012678,2305843905788117839,1152923210782611392,2305843903640634462, + 1152923209708869567,2305843901493150528,1152923208635127742,2305843899345666878,1152923207561385917,2305843897198183228,1152922391517594682,6051608920889, + 1152922389370111425,2305843892903215938,1152922387222627764,5202279143156,5147518305076,5145370821427,5206574105394,1152922795244525046, + 1300301349680,1152922379706438504,2305843883239539505,1152922782359623158,1284195222316,1152922375411471193,2305843878944572205,5193689203497, + 5307505836840,1152922734041241078,1244466774822,1152922368969020082,2305843872502121255,1222991938342,1152922365747791026,1221918196513, + 1152922363600311139,2305843867133412131,1152922666395506166,1191853425437,1152922359305343363,2305843862838444830,1152922639551960566,1147830010649, + 1152922355010376437,2305843858543477530,1122060206873,1152922351789147144,1120986465044,1152922349641663487,1107027821330,1152922347494183438, + 2305843851027284758,1081258017551,1071594341134,1059783181069,1044750795532,1152922526809069046,1035087119114,1152922338904245172, + 2305843842437350155,1152922507481716214,1009317315334,1152922334609277858,2305843838142382855,1152922483859396086,987842478850,1152922330314310536, + 978178802432,1152922328166826879,969588867838,1152922326019346265,2305843829552448259,1152922451647141366,952408998650,1152922321724378932, + 2305843825257480955,4017941906167,920196743926,911606809333,897648165620,886910747421,1152922314208183745,2305843817741288179, + 807453852469,1152922310986962682,1152922845710391039,2305843813446320878,1152923072269915899,1152922307765737215,2305843810225095403,1152927502528677423, + 2305843808077617106,2305843807003875281,1152928053358237604,2305843804856386278,1152923219372545954,2305843802708902628,6051608920802,1152922295954575665, + 2305843799487682052,2305843807003875234,1152928053358237500,2305843796266451678,1152923219372545850,2305843794118968028,6051608920794,2305847747636368058, + 2305843790897746616,2305843789824004410,2305843788750262964,2305843787676521138,1152922281995931958,1152922280922186457,2305843784455291615,2305843795192710655, + 6051608920784,1152926235513328483,1152922275553477327,2305843779086582481,1152922273405994417,776315343153,770946630346,765577921227, + 1152922269111031546,1152922304544511743,2305843771570389703,1152923069048690427,1152922265889806079,2305843768349164228,2305844348169753456,1152923219372545946, + 2305843765127938753,1152923218298804121,2305843762980455103,1152923217225062296,2305843760832971453,1152923216151320471,2305843758685487803,1152923215077578646, + 2305843756538004153,1152923214003836821,2305843754390520503,1152923212930094996,2305843752243036853,1152923211856353171,2305843750095553203,1152923210782611346, + 2305843747948069553,1152923209708869521,2305843745800585903,1152923208635127696,2305843743653102253,1152923207561385871,2305843741505618603,6051608920745, + 1152923823889188998,1152922233677547595,1152922232603809676,1152922231530062504,2305843735063172612,2305844280524018544,2305843732915684032,2305843731841942206, + 2305843730768200380,2305843729694458554,2305843728620716728,2305843727546974902,2305843726473233076,2305843725399491250,6051608920730,1152922232603805711, + 1152922217571418777,2305843721104523939,2305844164559901552,1152928053358237582,2305843717883298453,2239825450894,706522125828,1152922210055231386, + 1152922211128973206,2305843712514589328,1152922206834001469,2305843710367105683,1152922210055231385,1152922211128973205,2305843707145880203,1152922201465292348, + 2305843704998396556,1152922210055231384,1152922211128973204,2305843701777171078,1152922196096583227,2305843699629687431,1152922210055231383,1152922211128973203, + 2305843696408461953,1152922190727874106,2305843694260978306,6051608920701,1152922233677551515,1152922186432911244,1152922185359164028,2305843688892269206, + 2305844137716355952,2305843686744785556,2305843685671043725,2305843684597301896,2305843683523560067,2305843682449818238,6051608920690,1152923847511512988, + 1152922174621747275,1152922173548009356,1152922172474262129,2305843676007367288,2305844128052679536,2305843673859883668,2305843672786141837,2305843671712400008, + 2305843670638658179,2305843669564916350,6051608920678,1152922174621751195,1152922161736849292,1152922160663102053,2305843664196207212,2305844265491633008, + 2305843662048723604,2305843660974981824,2305843659901239998,2305843658827498172,2305843657753756346,6051608920667,1152923824962934686,1152922149925685360, + 1152922148851943499,1152922147778200154,2305843651311305313,2305844198919639920,2305843649163821760,2305843648090079934,2305843647016338108,2305843645942596282, + 2305843644868854456,2305843643795112630,2305843642721370804,2305843641647628978,2305843640573887152,2305843639500145326,2305843638426403500,2305843637352661674, + 6051608920648,1152922148851947419,1152922129524594572,1152922128450847303,2305843631983952469,2305843936926635932,1152928053358237595,2305843628762726978, + 2305843627688985280,1152923218298804112,2305843625541501503,6051608920637,6491843073540,6470368231995,1152922116639692701,610959098426, + 5446018536964,1152922113418461752,1152922112344719932,2305843615877825091,1152922596602288027,1152922802760714315,2305843612656599603,6487548106244, + 1152922105902268977,1152922746926143387,1152922560095062091,2305843607287890478,1152922101607307162,2305843605140406831,2305843604066670489,1152927987859985908, + 2305843601919186840,2305843600845445015,1152922095164856275,2305843598697955881,1152923219372545941,2305843596550472228,6484326880772,1152922089796147092, + 6483253138948,1152922087648657952,1152922087648659295,2305843590108021278,1152922089796147090,1152922083353696150,2305843586886795804,1152927982491277207, + 1152922080132470678,2305843583665570329,1152922077984982588,1152922076911239728,2305843580444344866,1152923218298804116,1152922073690019738,2305843577223119379, + 1152923217225062291,1152922070468794266,2305843574001893904,1152923216151320466,1152922067247568794,2305843570780668429,1152923215077578641,1152922064026343322, + 2305843567559442954,6051608920583,1152922115565950876,555124523577,1152922058657628678,2305843562190733876,2305843916525541270,1152928053358237592, + 2305843558969508353,1152923219372545943,2305843556822024703,2305849490319349252,2305849483876898308,544387105276,1152922047920211516,2305843551453315581, + 1152922048993953339,2305843549305831928,6051608920566,1152922059731376027,536870912517,1152922040404023194,1152922039330275829,2305843542863380994, + 2305844708947007442,2305843540715902872,1152928053358237591,2305843538568413678,1152923219372545942,2305843536420930028,1152923218298804117,2305843534273446378, + 2305844291261437848,2305843532125962733,2305843531052220907,2305843529978479081,1152922024297890280,6051608920547,1152922041477765018,516469817844, + 1152922020002922978,2305843523536028144,1152922017855440302,2305843722178265765,2305843520314802810,2305843519241060974,2305843518167319139,2305843517093577303, + 2305843516019835461,2305843514946093622,2305843513872351748,2305843512798609906,1152922024297895414,1152922028592863129,2305843509577384403,1152922003896795616, + 2305843507429900756,497142464989,1152922000675575546,1152922262668580607,2305843503134933453,1152923065827464955,1152921997454350079,2305843499913707978, + 1152927501454935599,2305843497766229970,1152928053358237581,2305843495618740678,1152923219372545934,2305843493471257028,1152923214003836822,2305843491323773378, + 1152923212930094997,2305843489176289728,1152923211856353172,2305843487028806078,1152923210782611347,2305843484881322428,6051608920506,1152922173548003985, + 1152921977053249977,2305843480586360324,1152923218298804122,2305843478438871490,1152923217225062297,2305843476291387828,1152923216151320472,2305843474143904178, + 1152923215077578647,2305843471996420528,6051608920494,1152922173548003986,1152921964168348077,2305843467701453238,1152922161736843921,1152921960947122617, + 2305843464480227754,1152922161736843922,1152921957725897133,2305843461259002279,2305844251532990360,2305843459111518701,2305843458037776875,1152923218298804115, + 2305843455890293153,1152923217225062290,2305843453742809503,1152923216151320464,2305843451595325853,1152923215077578639,2305843449447842203,6051608920473, + 1152922040404018828,1152921941619769752,2305843445152874916,2305844101209135000,2305843443005391341,2305843441931649515,1152927500381198222,2305843439784171409, + 2305849491393091076,1152927499307450767,2305843436562940304,1152921930882352700,2305843434415456658,6051608920459,1152922040404023193,1152921926587384202, + 2305843430120489365,2305844621973919632,2305844621973919631,1152921922292416902,2305844621973919628,1152921920144933252,2305844621973919627,1152921917997449602, + 1152927976048825848,2305843420456814046,1152921914776224128,6051608920445,1152921912628740612,2305843416161845639,1152921910481257899,2305843468775195064, + 456340275576,453119050103,437012922742,421980537205,1152921922292422134,418759311731,1152921901891322243,416611828081, + 1152921899743838593,414464344431,1152921897596354942,412316860781,1152921895448871428,2305843398981976436,388694540665,1152921892227651322, + 1152921994233124607,2305843394687009128,1152923062606239483,1152921889006425855,2305843391465783653,1152927498233710127,2305843389318305746,2305843388244563862, + 2305843387170816512,1152923219372545940,2305843385023332703,2305843383949590944,2305843382875849118,1152923219372545937,2305843380728365407,2305843379654623806, + 1152923217225062287,2305843377507139928,1152921871826551131,2305843386097074627,1152923218298804109,2305843373212172628,1152923217225062284,2305843371064688978, + 1152921865384100181,6051608920399,1152921863236616691,2305843366769726980,1152927497159968303,2305843364622243794,2305843363548501914,1152928053358237593, + 2305843361401012553,1152923219372545944,2305843359253528903,1152923218298804119,2305843357106045253,1152923217225062294,2305843354958561603,1152923216151320469, + 2305843352811077953,1152923215077578644,2305843350663594303,1152923214003836819,2305843348516110653,1152923212930094994,2305843346368627003,1152923211856353169, + 2305843344221143353,1152923210782611344,2305843342073659703,1152923209708869519,2305843339926176053,6051608920371,1152921833171847302,2305843336704950604, + 1152927496086226479,2305843334557472722,2305843333483730842,2305843332409983304,2305843331336241478,2305843330262499652,2305843329188757826,2305843328115016000, + 2305843327041274174,2305843325967532348,2305843324893790522,2305843323820048696,2305843322746306870,2305843321672565044,6051608920354,1152921814918236164, + 2305843318451339568,1152921833171848044,2305843316303855903,2361158276612,1152921809549531018,1152921808475783457,2305843312008888605,1152921806328300968, + 1152921871826556811,1152923304198150028,6486474359062,1152921802033332571,2305843305566437655,1152921799885848912,6486474364660,1152921797738365269, + 2305843301271470354,1152921795590881779,2305843299123988614,2305843298050246660,6474663204596,1152921791295916908,2305843294829019404,1152921791295914267, + 2305843292681535753,282394099992,1152921785927210746,1152921885785200383,2305843288386568453,1152923059385014011,1152921782705985279,2305843285165342978, + 6589553579955,1152921778411017980,1152921779484759807,2305843280870375678,6588479838130,1152921774116050684,1152921775189792511,2305843276575408378, + 6587406096305,1152921769821083388,1152921770894825215,2305843272280441078,6586332354480,1152921765526116092,1152921766599857919,2305843267985473778, + 6585258612655,1152921761231148796,1152921762304890623,2305843263690506478,6584184870830,1152921756936181500,1152921758009923327,2305843259395539178, + 6583111129005,1152921752641214204,1152921753714956031,2305843255100571878,6582037387180,1152921748346246908,1152921749419988735,2305843250805604578, + 6580963645355,1152921744051279612,1152921745125021439,2305843246510637278,6579889903530,1152921739756312316,1152921740830054143,2305843242215669978, + 6578816161705,1152921735461345020,1152921736535086847,2305843237920702678,6577742419880,1152921731166377724,1152921732240119551,2305843233625735378, + 6576668678055,1152921726871410428,1152921727945152255,2305843229330768078,6575594936230,1152921722576443132,1152921723650184959,2305843225035800778, + 6574521194405,1152921718281475836,1152921719355217663,2305843220740833478,6573447452580,1152921713986508540,1152921715060250367,2305843216445866178, + 6572373710771,1152921709691541243,1152921710765283071,2305843212150898878,6571299968946,1152921705396573947,1152921706470315775,2305843207855931578, + 6570226227121,1152921701101606651,1152921702175348479,2305843203560964278,6569152485296,1152921696806639355,1152921697880381183,2305843199265996978, + 6568078743471,1152921692511672059,1152921693585413887,2305843194971029678,6567005001646,1152921688216704763,1152921689290446591,2305843190676062378, + 6565931259821,1152921683921737467,1152921684995479295,2305843186381095078,6564857517996,1152921679626770171,1152921680700511999,2305843182086127778, + 6563783776171,1152921675331802875,1152921676405544703,2305843177791160478,6562710034346,1152921671036835579,1152921672110577407,2305843173496193178, + 6561636292521,1152921666741868283,1152921667815610111,2305843169201225878,6560562550696,1152921662446900987,1152921663520642815,2305843164906258578, + 6559488808871,1152921658151933691,1152921659225675519,2305843160611291278,6558415067046,1152921653856966395,1152921654930708223,2305843156316323978, + 6557341325221,1152921649561999099,1152921650635740927,2305843152021356678,6556267583396,1152921645267031803,1152921646340773631,2305843147726389378, + 6311454447358,1152927817135036150,1152927818208777975,135291475710,1152921639898322679,1152921638824580855,132070250238,1152921636677097207, + 1152921635603355383,128849024766,1152921633455871735,1152921632382129911,125627799294,1152921630234646263,1152921629160904439,122406573822, + 1152921627013420791,1152921625939678967,119185348350,1152921623792195319,1152921622718453495,115964122878,1152921620570969847,1152921619497228023, + 1152927955647731174,2305843119809107833,1152927953500247526,2305843117661624183,1152927951352763878,2305843115514140533,1152927949205280230,2305843113366656883, + 1152927947057796582,2305843111219173233,1152927944910312934,2305843109071689583,1152927942762829286,2305843106924205933,1152927940615345638,2305843104776722283, + 1152921634529607804,1152921598022385782,1152921596948643955,1152921595874902128,1152921594801160301,1152921593727418474,1152921592653682424,1152921591579934822, + 1152921634529607807,1152921589432451190,1152921588358709363,1152921587284967536,1152921586211225709,1152921585137483882,1152921584063742077,1152921582990000228, + 2305843086523105360,1152921637750833279,1152921579768774774,1152921578695032947,1152921577621291120,1152921576547549293,1152921575473807466,1152921574400065658, + 1152921573326323810,2305843076859428935,1152921579768774777,1152921570105098355,1152921569031356528,1152921567957614701,1152921566883872874,1152921565810131063, + 1152921564736389216,2305843068269494334,1152921570105098358,1152921561515163760,1152921560441421933,1152921559367680106,1152921558293938292,1152921557220196446, + 2305843060753301558,1152921561515163763,1152921553998970989,1152921552925229162,1152921551851487345,1152921550777745500,2305843054310850607,1152921553998970992, + 1152921547556520042,1152921546482778222,1152921545409036378,2305843048942141481,1152921547556520045,1152921542187810923,1152921541114069080,2305843044647174180, + 6051608925950,1152921537892843552,32212254848,2305849560112568065,2305849603062236548,1152928097381652435,2305843037130981403,1152928096307906109, + 2305843034983497753,1152928095234164284,2305843032836014103,2305843031762273746,1152921527155426770,2305849564407530962,1152928058726946771,2305843027467304978, + 1152928057653200445,2305843025319821328,1152928056579458620,2305843023172337678,1152921517491748884,1152921525007947254,2305843019951112203,1152921517491748883, + 1152921513196781596,2305843016729886729,1152927819282513926,2305843014582403101,1152927819282519801,1152921507828078336,2305843011361177604,1152921505680588801 ] diff --git a/crates/lib/core/asm/sys/vm/mod.masm b/crates/lib/core/asm/sys/vm/mod.masm index 63e2a95dd3..15e74256af 100644 --- a/crates/lib/core/asm/sys/vm/mod.masm +++ b/crates/lib/core/asm/sys/vm/mod.masm @@ -23,10 +23,10 @@ const ACCEPTABLE_FOLDING_POW_BITS = 4 # protocol choice: hash function, field, blowup factor, FRI folding factor, coset # offset, max remainder degree, etc. It must be bumped when any of these change. # CIRCUIT_COMMITMENT covers the AIR constraints (via the encoded ACE circuit hash). -const RELATION_DIGEST_0 = 3886624411320157031 -const RELATION_DIGEST_1 = 5903371486919752653 -const RELATION_DIGEST_2 = 170319297396068280 -const RELATION_DIGEST_3 = 5221005507035467697 +const RELATION_DIGEST_0 = 9778406675021743003 +const RELATION_DIGEST_1 = 1249738292041492479 +const RELATION_DIGEST_2 = 18129162047168657414 +const RELATION_DIGEST_3 = 9359130512581117656 #! Loads security parameters from the advice stack and stores them in memory. #! diff --git a/crates/lib/core/asm/sys/vm/public_inputs.masm b/crates/lib/core/asm/sys/vm/public_inputs.masm index c090200ccf..459f95e340 100644 --- a/crates/lib/core/asm/sys/vm/public_inputs.masm +++ b/crates/lib/core/asm/sys/vm/public_inputs.masm @@ -17,8 +17,9 @@ use miden::core::crypto::hashes::poseidon2 # anchor --> [ aux_rand: beta0,beta1,alpha0,alpha1 ] (loaded ND here, verified in random_coin) # # The FLPI are the input/output stacks (32), program digest (4), and precompile state (4). -# The VLPI reduction is the product-inverse of (alpha + op_label + beta * digest_i) over -# all kernel procedure digests, enabling the bus interaction check. +# The VLPI reduction is the LogUp kernel ROM boundary correction +# kernel_corr = Σ_i 1 / kernel_proc_message(digest_i) +# (chiplet-side INIT removes; boundary adds). # # Advice stack consumption order: # 1. fixed-length PI (40 base felts) -- load_public_inputs @@ -39,8 +40,6 @@ const NUM_FIXED_LEN_PUBLIC_INPUTS = 40 # The reduced kernel value occupies 1 word (4 base felts) in the ACE READ section. const NUM_VAR_LEN_PI_GROUPS = 1 -# Op label for kernel procedures table messages -const KERNEL_OP_LABEL = 48 # CONSTANTS GETTERS # ================================================================================================= @@ -177,6 +176,14 @@ proc reduce_variable_length_public_inputs # 2) Load kernel digests from advice into memory, absorbing into the FS transcript. # Each digest is padded to 8 elements (2 words), loaded via adv_pipe. + # + # The 4-felt zero-pad is a vestige of the old [label, d0..d3] 5-slot encoding: the + # reversed 8-felt layout made `horner_eval_base` + `β*` produce `d0*β..d3*β^4` so the + # label could sit at β^0. The new encoding is just `bus_prefix + d0..d3` at β^0..β^3 + # — the 4 leading zeros are now Horner no-ops. It's kept because adv_pipe / mem_stream + # / Poseidon2 all operate on 8-felt chunks; a future refactor could pack digests + # sequentially (4 felts each) and pair them per adv_pipe / permute iteration to halve + # VLPI memory. ## a) Flush random coin buffers and load sponge state onto the stack. push.0 exec.constants::random_coin_output_len_ptr mem_store @@ -268,7 +275,8 @@ end #! Outputs: [...] #! #! where `digests_ptr` is a pointer to the kernel procedures digests. The reduced value -#! (kernel_reduced) is stored as [prod0, prod1, 0, 0] at the original `digests_ptr`. +#! `kernel_corr = Σ_i 1 / kernel_proc_message(digest_i)` is stored as +#! [kernel_corr0, kernel_corr1, 0, 0] at the original `digests_ptr`. proc reduce_kernel_digests # Assert that the number of kernel procedures is at most 1023 dup u32lt.1024 assert.err="number of kernel procedures must be less than 1024" @@ -292,8 +300,8 @@ proc reduce_kernel_digests # => [beta0, beta1, alpha0, alpha1, digests_ptr, ...] # Compute bus_prefix = alpha + gamma for per-bus domain separation. - # gamma = beta^16 was precomputed in generate_aux_randomness and stored at BUS_GAMMA_PTR. - # Since CHIPLETS_BUS = 0, bus_prefix = alpha + (0+1)*gamma = alpha + gamma. + # gamma = beta^16 was precomputed and stored at BUS_GAMMA_PTR. + # KERNEL_ROM_INIT = 0, so bus_prefix = alpha + (0+1)*gamma = alpha + gamma. movup.3 movup.3 # => [alpha0, alpha1, beta0, beta1, digests_ptr, ...] exec.constants::bus_gamma_ptr mem_load @@ -305,92 +313,73 @@ proc reduce_kernel_digests ext2add # => [prefix0, prefix1, beta0, beta1, digests_ptr, ...] - # We will keep [prefix0 + op_label, prefix1, beta0, beta1] on the stack so that we can compute - # the final result, where op_label is a unique label to domain separate the interaction with - # the chiplets' bus. - # The final result is then computed as: - # - # bus_prefix + op_label + beta * (r_0 * beta^0 + r_1 * beta^1 + r_2 * beta^2 + r_3 * beta^3) - push.KERNEL_OP_LABEL - add - # => [prefix0 + op_label, prefix1, beta0, beta1, digests_ptr, ...] - # Push the `horner_eval_ext` accumulator push.0.0 - # => [acc0, acc1, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, ...] + # => [acc0, acc1, prefix0, prefix1, beta0, beta1, digests_ptr, ...] # Push the pointer to the evaluation point beta exec.constants::aux_rand_nd_ptr - # => [beta_ptr, acc0, acc1, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, ...] + # => [beta_ptr, acc0, acc1, prefix0, prefix1, beta0, beta1, digests_ptr, ...] # Get the pointer to kernel procedures digests movup.7 - # => [digests_ptr, beta_ptr, acc0, acc1, alpha0 + op_label, alpha1, beta0, beta1, ...] + # => [digests_ptr, beta_ptr, acc0, acc1, prefix0, prefix1, beta0, beta1, ...] # Set up the stack for `mem_stream` + `horner_eval_ext` swapw padw padw - # => [Y, Y, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, acc0, acc1, ...] + # => [Y, Y, prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, acc0, acc1, ...] # where `Y` is a garbage word. # Build a boolean flag for the expression `num_ker_procedures > 0`. - # This will be used to drive the loop reducing the digests. # Use mem_load (not mem_loadw_le) to read only the counter without polluting the stack. exec.constants::tmp1 mem_load - # => [num_ker, Y, Y, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, acc0, acc1, ...] + # => [num_ker, Y, Y, prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, acc0, acc1, ...] push.0 neq while.true - # Compute `acc = sum {0<=i<4} digest_ptr[i] * beta^i` - # - # Since the width of the digests, plus the op_label, is less than 8, we only need - # to loop once + # Compute `acc = sum {0<=i<4} digest[i] * beta^i` via Horner evaluation. + # The stored 8-felt digest is [0,0,0,0, d3,d2,d1,d0]: the 4 leading zeros are + # Horner no-ops (contribute 0*β^4..0*β^7), so the result is just d0 + d1*β + + # d2*β^2 + d3*β^3 — exactly the β^0..β^3 payload we want. One mem_stream pass. repeat.1 mem_stream horner_eval_base end - # => [Y, Y, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, acc0, acc1, ...] + # => [Y, Y, prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, acc0, acc1, ...] swapdw - # => [alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, acc0, acc1, Y, Y, ...] + # => [prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, acc0, acc1, Y, Y, ...] movup.7 movup.7 - # => [acc0, acc1, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, Y, Y, ...] + # => [acc0, acc1, prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, Y, Y, ...] - # Compute `tmp = beta * acc = sum {0<=i<4} digest_ptr[i] * beta^(i+1)` - dup.5 dup.5 - # => [beta0, beta1, acc0, acc1, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, Y, Y, ...] - ext2mul - # => [tmp0, tmp1, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, Y, Y, ...] - - # Compute `term = alpha + op_label + sum{i = 0..4} digest_ptr[i] * beta^(i+1)` + # Compute `term = prefix + acc = bus_prefix + sum{i = 0..4} digest[i] * beta^i` dup.3 dup.3 ext2add - # => [term0, term1, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, Y, Y, ...] + # => [term0, term1, prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, Y, Y, ...] movdn.15 movdn.15 - # => [alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, Y, Y, term0, term1, ...] + # => [prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, Y, Y, term0, term1, ...] push.0 movdn.6 push.0 movdn.6 - # => [alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, 0, 0, Y, Y, term0, term1, ...] + # => [prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, 0, 0, Y, Y, term0, term1, ...] swapdw - # => [Y, Y, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, 0, 0, term0, term1, ...] + # => [Y, Y, prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, 0, 0, term0, term1, ...] - # Subtract 1 from num_ker_digests. - # We decrement TMP1[0] and check > 0. + # Subtract 1 from num_ker_digests and check > 0. exec.constants::tmp1 mem_load sub.1 - # => [counter-1, D, D, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, 0, 0, ...] + # => [counter-1, D, D, prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, 0, 0, ...] dup exec.constants::tmp1 mem_store # => [counter-1, D, D, ...] push.0 neq # => [flag, D, D, ...] - # Note: flag is consumed by while.true, leaving the stack as it was before this block. end - # => [Y, Y, alpha0 + op_label, alpha1, beta0, beta1, digests_ptr, beta_ptr, 0, 0, term0, term1, ...] + # => [Y, Y, prefix0, prefix1, beta0, beta1, digests_ptr, beta_ptr, 0, 0, term0, term1, ...] dropw dropw dropw # => [digests_ptr_advanced, beta_ptr, 0, 0, termN_0, termN_1, ..., term1_0, term1_1, ...] @@ -402,11 +391,11 @@ proc reduce_kernel_digests exec.constants::tmp1 push.3 add mem_load # => [N, termN_0, termN_1, ..., term1_0, term1_1, ...] - # We now need to multiply all of the reduced values. - # We push the multiplicative identity element, the number of elements to multiply, and a boolean + # Accumulate the sum of reciprocals: acc = Σ 1/term_i. + # We push the additive identity element, the number of elements to accumulate, and a boolean # flag to enter or not the loop. push.0 - push.1 + push.0 movup.2 dup push.0 @@ -416,7 +405,11 @@ proc reduce_kernel_digests while.true sub.1 movdn.4 # => [acc0, acc1, term1_0, term1_1, n - 1, ..., termn_0, termn_1, ...] - ext2mul + movup.3 movup.3 + # => [term1_0, term1_1, acc0, acc1, n - 1, ...] + ext2inv + # => [inv_t0, inv_t1, acc0, acc1, n - 1, ...] + ext2add # => [acc0', acc1', n - 1, ..., termn_0, termn_1, ...] movup.2 dup @@ -424,13 +417,13 @@ proc reduce_kernel_digests neq # => [loop, n - 1, acc0', acc1', term1_0, term1_1, ..., termn_0, termn_1, ...] end - # => [0, prod0, prod1, ...] where prod is the resulting product + # => [0, sum0, sum1, ...] where sum = Σ 1/term_i - # The product of all kernel_proc_messages is kernel_reduced. + # kernel_corr = Σ 1/term_i (no negation: chiplet removes, boundary adds). drop - # => [prod0, prod1, ...] + # => [kernel_corr0, kernel_corr1, ...] - # Store the result at `digests_ptr` as `[prod0, prod1, 0, 0]` + # Store the result at `digests_ptr` as `[kernel_corr0, kernel_corr1, 0, 0]` # Note that `digests_ptr` points to the group that we just reduced above and hence it is safe # to overwrite it with the result. push.0.0 diff --git a/crates/lib/core/docs/sys/vm/aux_trace.md b/crates/lib/core/docs/sys/vm/aux_trace.md index 3d118ac562..88ac6da04f 100644 --- a/crates/lib/core/docs/sys/vm/aux_trace.md +++ b/crates/lib/core/docs/sys/vm/aux_trace.md @@ -2,4 +2,4 @@ ## miden::core::sys::vm::aux_trace | Procedure | Description | | ----------- | ------------- | -| observe_aux_trace | Observes the auxiliary trace for the Miden VM AIR.

Draws auxiliary randomness, reseeds the transcript with the auxiliary trace commitment,
and absorbs the 4 words of auxiliary trace boundary values (8 aux columns, each an
extension field element = 16 base field elements = 4 words).

The advice provider must supply exactly 5 words in order:
[commitment, W0, W1, W2, W3]

The commitment is stored at aux_trace_com_ptr and the boundary values at
aux_bus_boundary_ptr (sequentially).

Precondition: input_len=0 (guaranteed by the preceding reseed_direct in the generic verifier).
Postcondition: input_len=0, output_len=8.

Input: [...]
Output: [...]
| +| observe_aux_trace | Observes the auxiliary trace for the Miden VM AIR.

Draws auxiliary randomness, reseeds the transcript with the auxiliary trace commitment,
and absorbs the boundary values (2 extension field elements = 4 base field elements = 1 word).
TODO(#3032): the second value is always zero (placeholder for trace splitting).

The advice provider must supply exactly 2 words in order:
[commitment, W0]

The commitment is stored at aux_trace_com_ptr and the boundary values at
aux_bus_boundary_ptr.

Precondition: input_len=0 (guaranteed by the preceding reseed_direct in the generic verifier).
Postcondition: input_len=0, output_len=8.

Input: [...]
Output: [...]
| diff --git a/crates/lib/core/src/constraints_regen.rs b/crates/lib/core/src/constraints_regen.rs index 178a3646d9..448ffec03a 100644 --- a/crates/lib/core/src/constraints_regen.rs +++ b/crates/lib/core/src/constraints_regen.rs @@ -31,7 +31,7 @@ const RELATION_DIGEST_PATH: &str = RELATION_DIGEST_PATHS.0; /// Builds the batched ACE circuit used by the Miden VM recursive verifier. pub fn build_batched_circuit(config: AceConfig) -> AceCircuit { let air = ProcessorAir; - let batch_config = miden_air::ace::reduced_aux_batch_config(); + let batch_config = miden_air::ace::logup_boundary_config(); miden_air::ace::build_batched_ace_circuit::<_, QuadFelt>(&air, config, &batch_config).unwrap() } diff --git a/crates/lib/core/tests/crypto/circuit_evaluation.rs b/crates/lib/core/tests/crypto/circuit_evaluation.rs index 7395b37eda..24b41a1054 100644 --- a/crates/lib/core/tests/crypto/circuit_evaluation.rs +++ b/crates/lib/core/tests/crypto/circuit_evaluation.rs @@ -9,7 +9,7 @@ use miden_utils_testing::rand::rand_quad_felt; /// Build the batched ACE circuit for the Miden VM ProcessorAir. fn build_batched_circuit(config: AceConfig) -> AceCircuit { let air = miden_air::ProcessorAir; - let batch_config = miden_air::ace::reduced_aux_batch_config(); + let batch_config = miden_air::ace::logup_boundary_config(); miden_air::ace::build_batched_ace_circuit::<_, QuadFelt>(&air, config, &batch_config).unwrap() } diff --git a/crates/lib/core/tests/stark/ace_read_check.rs b/crates/lib/core/tests/stark/ace_read_check.rs index 3e9a06543a..947dcc2f58 100644 --- a/crates/lib/core/tests/stark/ace_read_check.rs +++ b/crates/lib/core/tests/stark/ace_read_check.rs @@ -102,7 +102,7 @@ pub fn cross_check_ace_circuit(output: &ExecutionOutput) { layout: LayoutKind::Masm, }; - let batch_config = miden_air::ace::reduced_aux_batch_config(); + let batch_config = miden_air::ace::logup_boundary_config(); let circuit = build_batched_ace_circuit::<_, QuadFelt>(&ProcessorAir, config, &batch_config) .expect("ace circuit"); let layout = circuit.layout(); diff --git a/crates/lib/core/tests/stark/mod.rs b/crates/lib/core/tests/stark/mod.rs index 07a134c6d4..5ebaea17d4 100644 --- a/crates/lib/core/tests/stark/mod.rs +++ b/crates/lib/core/tests/stark/mod.rs @@ -4,7 +4,7 @@ use miden_air::PublicInputs; use miden_assembly::Assembler; use miden_core::{ Felt, WORD_SIZE, - field::{BasedVectorSpace, PrimeCharacteristicRing, QuadFelt}, + field::{BasedVectorSpace, Field, PrimeCharacteristicRing, QuadFelt}, precompile::PrecompileTranscriptState, proof::HashFunction, }; @@ -312,24 +312,25 @@ fn reduce_kernel_procedures_digests( alpha: QuadFelt, beta: QuadFelt, ) -> QuadFelt { + // kernel_corr = Σ_i 1 / term_i (MASM: chiplet removes, boundary adds — no negation). kernel_procedures_digests .chunks(2 * WORD_SIZE) .map(|digest| reduce_digest(digest, alpha, beta)) - .fold(QuadFelt::ONE, |acc, term| acc * term) + .fold(QuadFelt::ZERO, |acc, term| { + acc + term.try_inverse().expect("zero kernel ROM denominator") + }) } fn reduce_digest(digest: &[u64], alpha: QuadFelt, beta: QuadFelt) -> QuadFelt { - const KERNEL_OP_LABEL: Felt = Felt::new_unchecked(48); // gamma = beta^MAX_MESSAGE_WIDTH = beta^16 let gamma = (0..16).fold(QuadFelt::ONE, |acc, _| acc * beta); - // CHIPLETS_BUS = 0, so bus_prefix = alpha + (0+1) * gamma = alpha + gamma + // KERNEL_ROM_INIT = 0, so bus_prefix = alpha + (0+1) * gamma = alpha + gamma let bus_prefix = alpha + gamma; + // Horner evaluation matches MASM `horner_eval_base` over the 8-element reversed digest. bus_prefix - + QuadFelt::from(KERNEL_OP_LABEL) - + beta - * digest.iter().fold(QuadFelt::ZERO, |acc, coef| { - acc * beta + QuadFelt::from(Felt::new_unchecked(*coef)) - }) + + digest.iter().fold(QuadFelt::ZERO, |acc, coef| { + acc * beta + QuadFelt::from(Felt::new_unchecked(*coef)) + }) } // CONSTANTS diff --git a/docs/src/design/chiplets/kernel_rom.md b/docs/src/design/chiplets/kernel_rom.md index 4ff902ffac..d3b8cc2b44 100644 --- a/docs/src/design/chiplets/kernel_rom.md +++ b/docs/src/design/chiplets/kernel_rom.md @@ -12,108 +12,50 @@ More background about Miden VM execution contexts can be found [here](../../user ## Kernel ROM trace -The kernel ROM table consists of five columns. -The following example table shows the execution trace of the kernel ROM with procedure digests $a, b, c$, which were called 1, 2, and 0 times, respectively. -Each digest is included once to respond to the initialization request by the public inputs, and then repeated for each call made by the decoder. +The kernel ROM table consists of five columns, with exactly one row per declared kernel procedure. +The following example table shows the execution trace for three procedures with digests $a, b, c$, called 1, 2, and 0 times respectively. -| $s_{first}$ | $r_0$ | $r_1$ | $r_2$ | $r_3$ | -|-------------|-------|-------|-------|-------| -| 1 | $a_0$ | $a_1$ | $a_2$ | $a_3$ | -| 0 | $a_0$ | $a_1$ | $a_2$ | $a_3$ | -| 1 | $b_0$ | $b_1$ | $b_2$ | $b_3$ | -| 0 | $b_0$ | $b_1$ | $b_2$ | $b_3$ | -| 0 | $b_0$ | $b_1$ | $b_2$ | $b_3$ | -| 1 | $c_0$ | $c_1$ | $c_2$ | $c_3$ | +| $m$ | $r_0$ | $r_1$ | $r_2$ | $r_3$ | +|-----|-------|-------|-------|-------| +| 1 | $a_0$ | $a_1$ | $a_2$ | $a_3$ | +| 2 | $b_0$ | $b_1$ | $b_2$ | $b_3$ | +| 0 | $c_0$ | $c_1$ | $c_2$ | $c_3$ | -The meaning of columns in the above is as follows: +Column meanings: -- Column $s_{first}$ specifies the start of a block of rows with identical kernel procedure digests. -- $r_0, ..., r_3$ contain the digests of the kernel procedures. The values in these columns can change only when $s_{first}$ is set to 1 in the next row. Otherwise, the values in the $r$ columns remain the same. +- $m$ is the CALL-label multiplicity — the number of times the procedure was invoked by a `SYSCALL`. It may be zero for procedures declared in the kernel but never called. +- $r_0, \ldots, r_3$ contain the digest of the kernel procedure. -## Constraints +## Main-trace constraints -We first define the selector flag $f_{krom}$ that is active in all rows of the kernel ROM chiplet. +The kernel ROM chiplet has **no main-trace shape constraints** under the all-LogUp layout. +Earlier designs carried a binary "first-row-of-block" selector, a digest-contiguity rule, and an entry-row anchor to shape the trace for a permutation argument. +LogUp replaces those with multiset equality under a random challenge $\alpha$, so any prover assignment to $(m, r_0, \ldots, r_3)$ that balances the chiplets bus is sound; no extra shape constraints are required. ->$$ -> f_{krom} = s_0 \cdot s_1 \cdot s_2 \cdot s_3 \cdot (1 - s_4) \text{ | degree} = 5 ->$$ +## Chiplets bus constraints -where $s_i$ are the chiplet selector flags. Refer to the [chiplets order](./index.md#chiplets-order) for more information. - -The following constraints are required to enforce the correctness of the kernel ROM trace. - -The $s_{first}$ column is a selector indicating the start of a new digest included in the kernel ROM chiplet trace. -In this row, the chiplet responds to a bus request made by the verifier to ensure consistency with the set of kernel procedure digests given as public inputs. - -As $s_{first}$ is a selector, it must be binary. - -> $$ -> f_{krom} \cdot (s_{first}^2 - s_{first}) = 0 \text{ | degree} = 7 -> $$ - - -The flag $s_{first}$ must be set to be 1 in the first row of the kernel ROM chiplet. -Otherwise, the digest in this row would not be matched with one of the input procedure roots. -This constraint is enforced in the last row of the previous trace, using selector columns from the [chiplets](index.md) module. -More precisely, we use the virtual $f_{ACE}$ flag, which is active in all rows of the ACE chiplet (which comes right before this chiplet), -along with the selector $s_3$ which transitions from 0 to 1 in the last row, allowing us to target the first row of the kernel ROM trace. - -> $$ -> f_{ACE} \cdot s_{3}' \cdot (1 - s_{4}') \cdot (s_{first}' - 1) = 0 \text{ | degree} = 7 -> $$ - -The contiguity of the digests in a block is ensured by enforcing equality between digests across two consecutive rows, whenever the next row is not the start of a new block. -That is, when $s_{first}' = 0$, it must hold that $r_i = r_i'$. -We disable this constraint in the last row of the kernel ROM chiplet trace by using the kernel ROM chiplet selector $s_4'$, since the latter transitions from 0 to 1 in the first row of the next chiplet. - -For $i \in \{0,1,2,3\}$, - -> $$ -> f_{krom} \cdot (1 - s_4') \cdot (1 - s_{first}') \cdot (r_i' - r_i) = 0 \text{ | degree} = 8 -> $$ - -### Chiplets bus constraints - -The kernel ROM chiplet must ensure that all kernel procedure digests requested by the decoder correspond to one of the digests provided by the verifier through public inputs. -This is achieved by making use of the chiplet bus $b_{bus}$, responding to requests made by the decoder and by the verifier through public inputs. - -In the first row of each new block of hashes in the kernel ROM chiplet trace (i.e., when $s_{first} = 1$), the chiplet responds to a message $v_{init}$ requested by the verifier. -Since these initialization messages must match, the set of digests across all blocks must be equal to the set of procedure digests provided by the verifier (though not necessarily in the same order). - -Whenever a digest is requested by the decoder during program block hashing of the [`SYSCALL` operation](../decoder/constraints.md#block-hash-computation-constraints), a new row is added to the trace after the first row which is used to respond to one of the initialization requests made by the verifier using public inputs. -The chiplet responds to the request with a message $v_{call}$. - -In other words, the selector $s_{first}$ indicates whether the chiplet should respond to the decoder or the verifier initialization requests. -If a digest is requested $n$ times by the decoder, the same digest appears in a single block of length $n+1$. - -The variables $v_{init}$ and $v_{call}$ representing the bus messages contain reduced bus messages containing a kernel procedure digest. -Denoting the random values received from the verifier as $\alpha_0, \alpha_1$, etc., this can be defined as +The kernel ROM chiplet emits two fractions on the chiplets bus $b_{chip}$ per active row, gated by the selector flag $f_{krom}$. +Let $$ \begin{aligned} -\tilde{r} &= \sum_{i=0}^3 (\alpha_{i + 2} \cdot r_i) -v_{init} &= \alpha_0 + \alpha_1 \cdot \textsf{KERNEL\_PROC\_INIT} + \tilde{r} +\tilde{r} &= \sum_{i=0}^{3} \alpha_{i+2} \cdot r_i \\ +v_{init} &= \alpha_0 + \alpha_1 \cdot \textsf{KERNEL\_PROC\_INIT} + \tilde{r} \\ v_{call} &= \alpha_0 + \alpha_1 \cdot \textsf{KERNEL\_PROC\_CALL} + \tilde{r} \end{aligned} $$ -Here, $\textsf{KERNEL\_PROC\_INIT}$ and $\textsf{KERNEL\_PROC\_CALL}$ are the unique [operation labels](./index.md#operation-labels) for the kernel ROM bus message. +denote the two encoded bus messages for a row's digest. Here $\textsf{KERNEL\_PROC\_INIT}$ and $\textsf{KERNEL\_PROC\_CALL}$ are the unique [operation labels](./index.md#operation-labels), and $\alpha_i$ are challenges received from the verifier. -Each row of the kernel ROM chiplet trace responds to either a procedure digest initialization or decoder call request. -Since the $s_{first}$ column defines which type of response is sent to the bus, it is used to combine both requests into a single constraint given by +The chiplet contributes to $b_{chip}$ via > $$ -> b'_{chip} = b_{chip} \cdot (s_{first} \cdot v_{init} + (1 - s_{first}) \cdot v_{call}) \text{ | degree} = 3. +> f_{krom} \cdot \left( -\frac{1}{v_{init}} + \frac{m}{v_{call}} \right) > $$ -The above simplifies to - -- $s_{first} = 1$: $b'_{chip} = b_{chip} \cdot v_{init}$, when responding to a $\textsf{KERNEL\_PROC\_INIT}$ request. -- $s_{first} = 0$: $b'_{chip} = b_{chip} \cdot v_{call}$, when responding to a $\textsf{KERNEL\_PROC\_CALL}$ request. - -The kernel procedure digests initialization requests are implemented by imposing a boundary constraint in the first row of the $b_{chip}$ column. -This is described in the [chiplets bus constraints](../chiplets/index.md#chiplets-bus-constraints). +- The **INIT term** removes exactly one fraction per declared procedure. It is balanced by the public-input boundary term the verifier injects on $b_{chip}$ (one add per kernel procedure digest read from public inputs). This anchors every chiplet row to a declared procedure: a forged row would leave an unmatched INIT remove. +- The **CALL term** contributes $m$ fractions. Each `SYSCALL` in the decoder emits one matching remove on $b_{chip}$. Bus balance forces $m$ to equal the true syscall count for that procedure. -By using the bus to initialize the kernel ROM procedure digest in this way, the verifier only learns which procedures can be invoked but doesn't learn how often they were called, if at all. +The full set of constraints applied to $b_{chip}$ (including the public-input boundary term for INIT) is described in the [chiplets bus constraints](../chiplets/index.md#chiplets-bus-constraints). -The full set of constraints applied to the $b_{chip}$ are described as part of the [chiplets bus constraints](../chiplets/index.md#chiplets-bus-constraints). +By using the bus this way, the verifier only learns which procedures can be invoked, not how often they were called — the multiplicity $m$ is a private witness that only reaches the verifier through the bus balance. diff --git a/miden-vm/tests/integration/prove_verify.rs b/miden-vm/tests/integration/prove_verify.rs index a1eca04b11..e95cbe350d 100644 --- a/miden-vm/tests/integration/prove_verify.rs +++ b/miden-vm/tests/integration/prove_verify.rs @@ -501,18 +501,11 @@ mod fast_parallel { let (public_values, kernel_felts) = trace.public_inputs().to_air_inputs(); let var_len_public_inputs: &[&[Felt]] = &[&kernel_felts]; - let aux_builder = trace.aux_trace_builders(); - // Generate proof using Blake3_256 let blake3_config = config::blake3_256_config(config::pcs_params()); - let proof_bytes = prove_stark( - &blake3_config, - &trace_matrix, - &public_values, - var_len_public_inputs, - &aux_builder, - ) - .expect("Proving failed"); + let proof_bytes = + prove_stark(&blake3_config, &trace_matrix, &public_values, var_len_public_inputs) + .expect("Proving failed"); let precompile_requests = trace.precompile_requests().to_vec(); diff --git a/processor/Cargo.toml b/processor/Cargo.toml index c6ccb46b6c..d3e54a753d 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -28,8 +28,10 @@ std = [ "thiserror/std", ] testing = ["miden-air/testing"] -# Like `testing`, but slows down the processor speed to make it easier to debug. -bus-debugger = ["testing", "miden-air/testing"] +# Pulls in the LogUp debug surface from miden-air (under `#[cfg(feature = "std")]`). +# NOTE: the real-trace bus debugger is not yet wired into the prover/processor paths, +# so today this feature only compiles in the LookupAir shape-validation walker. +bus-debugger = ["miden-air/std"] [dependencies] # Miden dependencies diff --git a/processor/src/debug.rs b/processor/src/debug.rs deleted file mode 100644 index 8f4c399ee0..0000000000 --- a/processor/src/debug.rs +++ /dev/null @@ -1,120 +0,0 @@ -use alloc::{boxed::Box, string::String, vec::Vec}; -use core::fmt; - -use miden_air::trace::Challenges; -use miden_core::field::ExtensionField; - -use crate::Felt; - -/// A message that can be sent on a bus. -pub(crate) trait BusMessage>: fmt::Display { - /// The concrete value that this message evaluates to. - fn value(&self, challenges: &Challenges) -> E; - - /// The source of this message (e.g. "mload" or "memory chiplet"). - fn source(&self) -> &str; -} - -/// A debugger for a bus that can be used to track outstanding requests and responses. -/// -/// Note: we use `Vec` internally instead of a `BTreeMap`, since messages can have collisions (i.e. -/// 2 messages sent with the same key), which results in relatively complex insertion/deletion -/// logic. Since this is only used in debug/test code, the performance hit is acceptable. -pub(crate) struct BusDebugger> { - pub bus_name: String, - pub outstanding_requests: Vec<(E, Box>)>, - pub outstanding_responses: Vec<(E, Box>)>, -} - -impl BusDebugger -where - E: ExtensionField, -{ - pub fn new(bus_name: String) -> Self { - Self { - bus_name, - outstanding_requests: Vec::new(), - outstanding_responses: Vec::new(), - } - } -} - -impl BusDebugger -where - E: ExtensionField, -{ - /// Attempts to match the request with an existing response. If a match is found, the response - /// is removed from the list of outstanding responses. Otherwise, the request is added to the - /// list of outstanding requests. - #[cfg(any(test, feature = "bus-debugger"))] - pub fn add_request(&mut self, request_msg: Box>, challenges: &Challenges) { - let msg_value = request_msg.value(challenges); - - if let Some(pos) = - self.outstanding_responses.iter().position(|(value, _)| *value == msg_value) - { - self.outstanding_responses.swap_remove(pos); - } else { - self.outstanding_requests.push((msg_value, request_msg)); - } - } - - /// Attempts to match the response with an existing request. If a match is found, the request is - /// removed from the list of outstanding requests. Otherwise, the response is added to the list - /// of outstanding responses. - #[cfg(any(test, feature = "bus-debugger"))] - pub fn add_response( - &mut self, - response_msg: Box>, - challenges: &Challenges, - ) { - let msg_value = response_msg.value(challenges); - - if let Some(pos) = - self.outstanding_requests.iter().position(|(value, _)| *value == msg_value) - { - self.outstanding_requests.swap_remove(pos); - } else { - self.outstanding_responses.push((msg_value, response_msg)); - } - } - - /// Returns true if there are no outstanding requests or responses. - /// - /// This is meant to be called at the end of filling the bus. If there are any outstanding - /// requests or responses, it means that there is a mismatch between the requests and responses, - /// and the test should fail. The `Debug` implementation for `BusDebugger` will print out the - /// outstanding requests and responses. - pub fn is_empty(&self) -> bool { - self.outstanding_requests.is_empty() && self.outstanding_responses.is_empty() - } -} - -impl fmt::Display for BusDebugger -where - E: ExtensionField, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_empty() { - writeln!(f, "Bus '{}' is empty.", self.bus_name)?; - } else { - writeln!(f, "Bus '{}' construction failed.", self.bus_name)?; - - if !self.outstanding_requests.is_empty() { - writeln!(f, "The following requests are still outstanding:")?; - for (_value, msg) in &self.outstanding_requests { - writeln!(f, "- {}: {}", msg.source(), msg)?; - } - } - - if !self.outstanding_responses.is_empty() { - writeln!(f, "\nThe following responses are still outstanding:")?; - for (_value, msg) in &self.outstanding_responses { - writeln!(f, "- {}: {}", msg.source(), msg)?; - } - } - } - - Ok(()) - } -} diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 687833e169..bbd9427af1 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -16,7 +16,6 @@ use core::{ }; mod continuation_stack; -mod debug; mod errors; mod execution; mod execution_options; diff --git a/processor/src/trace/decoder/block_stack.rs b/processor/src/trace/block_stack.rs similarity index 100% rename from processor/src/trace/decoder/block_stack.rs rename to processor/src/trace/block_stack.rs diff --git a/processor/src/trace/chiplets/ace/mod.rs b/processor/src/trace/chiplets/ace/mod.rs index 523c173d54..d73fe96a65 100644 --- a/processor/src/trace/chiplets/ace/mod.rs +++ b/processor/src/trace/chiplets/ace/mod.rs @@ -1,14 +1,12 @@ use alloc::{collections::BTreeMap, vec::Vec}; -use miden_air::trace::{ - Challenges, MainTrace, RowIndex, bus_types::ACE_WIRING_BUS, chiplets::ace::ACE_CHIPLET_NUM_COLS, -}; -use miden_core::{Felt, ZERO, field::ExtensionField}; +use miden_air::trace::{RowIndex, chiplets::ace::ACE_CHIPLET_NUM_COLS}; +use miden_core::{Felt, ZERO}; use crate::trace::TraceFragment; mod trace; -pub use trace::{CircuitEvaluation, NUM_ACE_LOGUP_FRACTIONS_EVAL, NUM_ACE_LOGUP_FRACTIONS_READ}; +pub use trace::CircuitEvaluation; mod instruction; #[cfg(test)] @@ -37,10 +35,7 @@ impl Ace { } /// Fills the portion of the main trace allocated to the ACE chiplet. - /// - /// This also returns helper data needed for generating the part of the auxiliary trace - /// associated with the ACE chiplet. - pub(crate) fn fill_trace(self, trace: &mut TraceFragment) -> Vec { + pub(crate) fn fill_trace(self, trace: &mut TraceFragment) { // make sure fragment dimensions are consistent with the dimensions of this trace debug_assert_eq!(self.trace_len(), trace.len(), "inconsistent trace lengths"); debug_assert_eq!(ACE_CHIPLET_NUM_COLS, trace.width(), "inconsistent trace widths"); @@ -51,21 +46,15 @@ impl Ace { .try_into() .expect("failed to convert vector to array"); - let mut sections_info = Vec::with_capacity(self.circuit_evaluations.keys().count()); - let mut offset = 0; for eval_ctx in self.circuit_evaluations.into_values() { eval_ctx.fill(offset, &mut gen_trace); offset += eval_ctx.num_rows(); - let section = EvaluatedCircuitsMetadata::from_evaluation_context(&eval_ctx); - sections_info.push(section); } for (out_column, column) in trace.columns().zip(gen_trace) { out_column.copy_from_slice(&column); } - - sections_info } /// Adds an entry resulting from a call to the ACE chiplet. @@ -77,183 +66,3 @@ impl Ace { self.circuit_evaluations.insert(clk, circuit_eval); } } - -/// Stores metadata associated to an evaluated circuit needed for building the portion of the -/// auxiliary trace segment relevant for the ACE chiplet. -#[derive(Debug, Default, Clone)] -pub struct EvaluatedCircuitsMetadata { - ctx: u32, - clk: u32, - num_vars: u32, - num_evals: u32, -} - -impl EvaluatedCircuitsMetadata { - pub fn clk(&self) -> u32 { - self.clk - } - - pub fn ctx(&self) -> u32 { - self.ctx - } - - pub fn num_vars(&self) -> u32 { - self.num_vars - } - - pub fn num_evals(&self) -> u32 { - self.num_evals - } - - fn from_evaluation_context(eval_ctx: &CircuitEvaluation) -> EvaluatedCircuitsMetadata { - EvaluatedCircuitsMetadata { - ctx: eval_ctx.ctx(), - clk: eval_ctx.clk(), - num_vars: eval_ctx.num_read_rows(), - num_evals: eval_ctx.num_eval_rows(), - } - } -} - -/// Stores metadata for the ACE chiplet useful when building the portion of the auxiliary -/// trace segment relevant for the ACE chiplet. -/// -/// This data is already present in the main trace but collecting it here allows us to simplify -/// the logic for building the auxiliary segment portion for the ACE chiplet. -/// For example, we know that `clk` and `ctx` are constant throughout each circuit evaluation -/// and we also know the exact number of ACE chiplet rows per circuit evaluation and the exact -/// number of rows per `READ` and `EVAL` portions, which allows us to avoid the need to compute -/// selectors as part of the logic of auxiliary trace generation. -#[derive(Clone, Debug, Default)] -pub struct AceHints { - offset_chiplet_trace: usize, - pub sections: Vec, -} - -impl AceHints { - pub fn new(offset_chiplet_trace: usize, sections: Vec) -> Self { - Self { offset_chiplet_trace, sections } - } - - pub(crate) fn offset(&self) -> usize { - self.offset_chiplet_trace - } - - /// Encodes an ACE wire value into a bus message. - /// - /// Layout: `alpha + beta^0*clk + beta^1*ctx + beta^2*wire[0] + beta^3*wire[1] + beta^4*wire[2]` - #[inline(always)] - fn encode_ace_wire_value>( - challenges: &Challenges, - clk: u32, - ctx: u32, - wire: [Felt; 3], - ) -> E { - challenges.encode( - ACE_WIRING_BUS, - [Felt::from_u32(clk), Felt::from_u32(ctx), wire[0], wire[1], wire[2]], - ) - } - - pub(crate) fn build_divisors>( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - ) -> Vec { - let num_fractions = self.num_fractions(); - let mut total_values = vec![E::ZERO; num_fractions]; - let mut total_inv_values = vec![E::ZERO; num_fractions]; - - let mut chiplet_offset = self.offset_chiplet_trace; - let mut values_offset = 0; - let mut acc = E::ONE; - for section in self.sections.iter() { - let clk = section.clk(); - let ctx = section.ctx(); - - let values = &mut total_values[values_offset - ..values_offset + NUM_ACE_LOGUP_FRACTIONS_READ * section.num_vars() as usize]; - let inv_values = &mut total_inv_values[values_offset - ..values_offset + NUM_ACE_LOGUP_FRACTIONS_READ * section.num_vars() as usize]; - - // read section - for (i, (value, inv_value)) in values - .chunks_mut(NUM_ACE_LOGUP_FRACTIONS_READ) - .zip(inv_values.chunks_mut(NUM_ACE_LOGUP_FRACTIONS_READ)) - .enumerate() - { - let trace_row = i + chiplet_offset; - - let wire_0 = main_trace.chiplet_ace_wire_0(trace_row.into()); - let wire_1 = main_trace.chiplet_ace_wire_1(trace_row.into()); - - let value_0 = Self::encode_ace_wire_value(challenges, clk, ctx, wire_0); - let value_1 = Self::encode_ace_wire_value(challenges, clk, ctx, wire_1); - - value[0] = value_0; - value[1] = value_1; - inv_value[0] = acc; - acc *= value_0; - inv_value[1] = acc; - acc *= value_1; - } - - chiplet_offset += section.num_vars() as usize; - values_offset += NUM_ACE_LOGUP_FRACTIONS_READ * section.num_vars() as usize; - - // eval section - let values = &mut total_values[values_offset - ..values_offset + NUM_ACE_LOGUP_FRACTIONS_EVAL * section.num_evals() as usize]; - let inv_values = &mut total_inv_values[values_offset - ..values_offset + NUM_ACE_LOGUP_FRACTIONS_EVAL * section.num_evals() as usize]; - for (i, (value, inv_value)) in values - .chunks_mut(NUM_ACE_LOGUP_FRACTIONS_EVAL) - .zip(inv_values.chunks_mut(NUM_ACE_LOGUP_FRACTIONS_EVAL)) - .enumerate() - { - let trace_row = i + chiplet_offset; - - let wire_0 = main_trace.chiplet_ace_wire_0(trace_row.into()); - let wire_1 = main_trace.chiplet_ace_wire_1(trace_row.into()); - let wire_2 = main_trace.chiplet_ace_wire_2(trace_row.into()); - - let value_0 = Self::encode_ace_wire_value(challenges, clk, ctx, wire_0); - let value_1 = Self::encode_ace_wire_value(challenges, clk, ctx, wire_1); - let value_2 = Self::encode_ace_wire_value(challenges, clk, ctx, wire_2); - - value[0] = value_0; - value[1] = value_1; - value[2] = value_2; - inv_value[0] = acc; - acc *= value_0; - inv_value[1] = acc; - acc *= value_1; - inv_value[2] = acc; - acc *= value_2; - } - - chiplet_offset += section.num_evals() as usize; - values_offset += NUM_ACE_LOGUP_FRACTIONS_EVAL * section.num_evals() as usize; - } - - // invert the accumulated product - acc = acc.inverse(); - - for i in (0..total_values.len()).rev() { - total_inv_values[i] *= acc; - acc *= total_values[i]; - } - - total_inv_values - } - - fn num_fractions(&self) -> usize { - self.sections - .iter() - .map(|section| { - NUM_ACE_LOGUP_FRACTIONS_READ * (section.num_vars as usize) - + NUM_ACE_LOGUP_FRACTIONS_EVAL * (section.num_evals as usize) - }) - .sum() - } -} diff --git a/processor/src/trace/chiplets/ace/trace.rs b/processor/src/trace/chiplets/ace/trace.rs index d5948a8df3..95241c8d94 100644 --- a/processor/src/trace/chiplets/ace/trace.rs +++ b/processor/src/trace/chiplets/ace/trace.rs @@ -20,11 +20,6 @@ use super::{ }; use crate::{ContextId, errors::AceError}; -/// Number of LogUp fractions in the wiring bus for rows in the `READ` section. -pub const NUM_ACE_LOGUP_FRACTIONS_READ: usize = 2; -/// Number of LogUp fractions in the wiring bus for rows in the `EVAL` section. -pub const NUM_ACE_LOGUP_FRACTIONS_EVAL: usize = 3; - /// Contains the variable and evaluation nodes resulting from the evaluation of a circuit. /// The output value is checked to be equal to 0. /// diff --git a/processor/src/trace/chiplets/aux_trace/bus/ace.rs b/processor/src/trace/chiplets/aux_trace/bus/ace.rs deleted file mode 100644 index c1fffd8ffe..0000000000 --- a/processor/src/trace/chiplets/aux_trace/bus/ace.rs +++ /dev/null @@ -1,131 +0,0 @@ -use core::fmt::{Display, Formatter, Result as FmtResult}; - -use miden_air::trace::{ - Challenges, MainTrace, RowIndex, bus_types::CHIPLETS_BUS, chiplets::ace::ACE_INIT_LABEL, -}; -use miden_core::{Felt, ONE, field::ExtensionField}; - -use crate::debug::{BusDebugger, BusMessage}; - -// REQUESTS -// ============================================================================================== - -/// Builds requests made to the arithmetic circuit evaluation chiplet. -pub fn build_ace_chiplet_requests>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let clk = main_trace.clk(row); - let ctx = main_trace.ctx(row); - let ptr = main_trace.stack_element(0, row); - let num_read_rows = main_trace.stack_element(1, row); - let num_eval_rows = main_trace.stack_element(2, row); - - let ace_request_message = AceMessage { - op_label: ACE_INIT_LABEL, - clk, - ctx, - ptr, - num_read_rows, - num_eval_rows, - source: "ace request", - }; - - let value = ace_request_message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(ace_request_message), challenges); - - value -} - -// RESPONSES -// ============================================================================================== - -/// Builds the response from the ace chiplet at `row`. -pub fn build_ace_chiplet_responses( - main_trace: &MainTrace, - row: RowIndex, - challenges: &Challenges, - _debugger: &mut BusDebugger, -) -> E -where - E: ExtensionField, -{ - let start_selector = main_trace.chiplet_ace_start_selector(row); - if start_selector == ONE { - let clk = main_trace.chiplet_ace_clk(row); - let ctx = main_trace.chiplet_ace_ctx(row); - let ptr = main_trace.chiplet_ace_ptr(row); - let num_eval_rows = main_trace.chiplet_ace_num_eval_rows(row) + ONE; - let id_0 = main_trace.chiplet_ace_id_0(row); - let num_read_rows = id_0 + ONE - num_eval_rows; - - let ace_message = AceMessage { - op_label: ACE_INIT_LABEL, - clk, - ctx, - ptr, - num_read_rows, - num_eval_rows, - source: "ace response", - }; - let value = ace_message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_response(alloc::boxed::Box::new(ace_message), challenges); - - value - } else { - E::ONE - } -} - -// MESSAGE -// =============================================================================================== - -#[derive(Debug)] -pub struct AceMessage { - pub op_label: Felt, - pub clk: Felt, - pub ctx: Felt, - pub ptr: Felt, - pub num_read_rows: Felt, - pub num_eval_rows: Felt, - pub source: &'static str, -} - -impl BusMessage for AceMessage -where - E: ExtensionField, -{ - fn value(&self, challenges: &Challenges) -> E { - challenges.encode( - CHIPLETS_BUS, - [ - self.op_label, - self.clk, - self.ctx, - self.ptr, - self.num_read_rows, - self.num_eval_rows, - ], - ) - } - - fn source(&self) -> &str { - self.source - } -} - -impl Display for AceMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{{ op_label: {}, clk: {}, ctx: {}, ptr: {}, num_read_rows: {}, num_eval_rows: {} }}", - self.op_label, self.clk, self.ctx, self.ptr, self.num_read_rows, self.num_eval_rows - ) - } -} diff --git a/processor/src/trace/chiplets/aux_trace/bus/bitwise.rs b/processor/src/trace/chiplets/aux_trace/bus/bitwise.rs deleted file mode 100644 index de9783b282..0000000000 --- a/processor/src/trace/chiplets/aux_trace/bus/bitwise.rs +++ /dev/null @@ -1,106 +0,0 @@ -use core::fmt::{Display, Formatter, Result as FmtResult}; - -use miden_air::trace::{ - Challenges, MainTrace, RowIndex, bus_types::CHIPLETS_BUS, - chiplets::bitwise::OP_CYCLE_LEN as BITWISE_OP_CYCLE_LEN, -}; -use miden_core::{Felt, ONE, ZERO, field::ExtensionField}; - -use super::get_op_label; -use crate::debug::{BusDebugger, BusMessage}; - -// REQUESTS -// ============================================================================================== - -/// Builds requests made to the bitwise chiplet. This can be either a request for the computation -/// of a `XOR` or an `AND` operation. -pub(super) fn build_bitwise_request>( - main_trace: &MainTrace, - is_xor: Felt, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let bitwise_request_message = BitwiseMessage { - op_label: get_op_label(ONE, ZERO, is_xor, ZERO), - a: main_trace.stack_element(0, row), - b: main_trace.stack_element(1, row), - z: main_trace.stack_element(0, row + 1), - source: if is_xor == ONE { "u32xor" } else { "u32and" }, - }; - - let value = bitwise_request_message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(bitwise_request_message), challenges); - - value -} - -// RESPONSES -// ============================================================================================== - -/// Builds the response from the bitwise chiplet at `row`. -pub(super) fn build_bitwise_chiplet_responses( - main_trace: &MainTrace, - row: RowIndex, - challenges: &Challenges, - _debugger: &mut BusDebugger, -) -> E -where - E: ExtensionField, -{ - let is_xor = main_trace.chiplet_selector_2(row); - if row.as_usize() % BITWISE_OP_CYCLE_LEN == BITWISE_OP_CYCLE_LEN - 1 { - let bitwise_message = BitwiseMessage { - op_label: get_op_label(ONE, ZERO, is_xor, ZERO), - a: main_trace.chiplet_bitwise_a(row), - b: main_trace.chiplet_bitwise_b(row), - z: main_trace.chiplet_bitwise_z(row), - source: "bitwise chiplet", - }; - - let value = bitwise_message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_response(alloc::boxed::Box::new(bitwise_message), challenges); - - value - } else { - E::ONE - } -} - -// MESSAGE -// =============================================================================================== - -pub struct BitwiseMessage { - pub op_label: Felt, - pub a: Felt, - pub b: Felt, - pub z: Felt, - pub source: &'static str, -} - -impl BusMessage for BitwiseMessage -where - E: ExtensionField, -{ - fn value(&self, challenges: &Challenges) -> E { - challenges.encode(CHIPLETS_BUS, [self.op_label, self.a, self.b, self.z]) - } - - fn source(&self) -> &str { - self.source - } -} - -impl Display for BitwiseMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{{ op_label: {}, a: {}, b: {}, z: {} }}", - self.op_label, self.a, self.b, self.z - ) - } -} diff --git a/processor/src/trace/chiplets/aux_trace/bus/hasher.rs b/processor/src/trace/chiplets/aux_trace/bus/hasher.rs deleted file mode 100644 index 602bbef563..0000000000 --- a/processor/src/trace/chiplets/aux_trace/bus/hasher.rs +++ /dev/null @@ -1,887 +0,0 @@ -use core::fmt::{Display, Formatter, Result as FmtResult}; - -use miden_air::trace::{ - Challenges, MainTrace, RowIndex, bus_message, - bus_types::CHIPLETS_BUS, - chiplets::{ - hasher, - hasher::{ - CONTROLLER_ROWS_PER_PERM_FELT, LINEAR_HASH_LABEL, MP_VERIFY_LABEL, MR_UPDATE_NEW_LABEL, - MR_UPDATE_OLD_LABEL, RETURN_HASH_LABEL, RETURN_STATE_LABEL, - }, - }, - log_precompile::{ - HELPER_ADDR_IDX, HELPER_CAP_PREV_RANGE, STACK_CAP_NEXT_RANGE, STACK_COMM_RANGE, - STACK_R0_RANGE, STACK_R1_RANGE, STACK_TAG_RANGE, - }, -}; -use miden_core::{Felt, ONE, WORD_SIZE, ZERO, field::ExtensionField, operations::opcodes}; - -use super::get_op_label; -use crate::{ - Word, - debug::{BusDebugger, BusMessage}, -}; - -// HASHER MESSAGE ENCODING LAYOUT -// ================================================================================================ -// -// All hasher chiplet bus messages use a common encoding structure: -// -// challenges.bus_prefix[CHIPLETS_BUS] = alpha + 1*gamma (domain-separated base for this bus) -// challenges.beta_powers[0] = beta^0 (label: transition type) -// challenges.beta_powers[1] = beta^1 (addr: hasher chiplet address) -// challenges.beta_powers[2] = beta^2 (node_index: Merkle path position, 0 for -// non-Merkle ops) -// challenges.beta_powers[3..10] = beta^3..beta^10 (state[0..7]: RATE0 || RATE1) -// challenges.beta_powers[11..14] = beta^11..beta^14 (capacity[0..3]) -// -// Message encoding: bus_prefix[CHIPLETS_BUS] -// + beta^0*label + beta^1*addr + beta^2*node_index -// + beta^3*state[0] + ... + beta^10*state[7] -// + beta^11*capacity[0] + ... + beta^14*capacity[3] -// -// Different message types use different subsets of this layout: -// - Full state messages (HPERM, LOG_PRECOMPILE): all 12 state elements (rate + capacity) -// - Rate-only messages (SPAN, RESPAN): skip node_index and capacity, use label + addr + state[0..7] -// - Digest messages (END block): label + addr + RATE0 digest (state[0..3]) -// - Control block messages: rate + one capacity element (beta_powers[12]) for op_code -// - Tree operation messages (MPVERIFY, MRUPDATE): include node_index - -// HASHER MESSAGE CONSTANTS AND HELPERS -// ================================================================================================ - -const LABEL_OFFSET_START: Felt = Felt::new_unchecked(16); -const LABEL_OFFSET_END: Felt = Felt::new_unchecked(32); -const LINEAR_HASH_LABEL_START: Felt = Felt::new_unchecked((LINEAR_HASH_LABEL + 16) as u64); -const LINEAR_HASH_LABEL_RESPAN: Felt = Felt::new_unchecked((LINEAR_HASH_LABEL + 32) as u64); -const RETURN_HASH_LABEL_END: Felt = Felt::new_unchecked((RETURN_HASH_LABEL + 32) as u64); -const RETURN_STATE_LABEL_END: Felt = Felt::new_unchecked((RETURN_STATE_LABEL + 32) as u64); -const MP_VERIFY_LABEL_START: Felt = Felt::new_unchecked((MP_VERIFY_LABEL + 16) as u64); -const MR_UPDATE_OLD_LABEL_START: Felt = Felt::new_unchecked((MR_UPDATE_OLD_LABEL + 16) as u64); -const MR_UPDATE_NEW_LABEL_START: Felt = Felt::new_unchecked((MR_UPDATE_NEW_LABEL + 16) as u64); - -/// Creates a full hasher state with a word in the first 4 elements and zeros elsewhere. -/// Used by the bus debugger to construct HasherMessage structs for Merkle operations -/// where only the digest word (4 elements) is meaningful. -#[cfg(any(test, feature = "bus-debugger"))] -fn word_to_hasher_state(word: &[Felt; WORD_SIZE]) -> [Felt; hasher::STATE_WIDTH] { - let mut state = [ZERO; hasher::STATE_WIDTH]; - state[..WORD_SIZE].copy_from_slice(word); - state -} - -/// Encodes hasher message as **bus_prefix[CHIPLETS_BUS] + **. -/// -/// Used for tree operations (MPVERIFY, MRUPDATE) and generic hasher messages with node_index. -#[inline(always)] -fn hasher_message_value( - challenges: &Challenges, - transition_label: Felt, - addr_next: Felt, - node_index: Felt, - state: [Felt; N], -) -> E -where - E: ExtensionField, -{ - let mut acc = challenges.bus_prefix[CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] * transition_label - + challenges.beta_powers[bus_message::ADDR_IDX] * addr_next - + challenges.beta_powers[bus_message::NODE_INDEX_IDX] * node_index; - for (i, &elem) in state.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc -} - -/// Encodes hasher message as **bus_prefix[CHIPLETS_BUS] + ** -/// (skips node_index). -#[inline(always)] -fn header_rate_value( - challenges: &Challenges, - transition_label: Felt, - addr: Felt, - state: [Felt; hasher::RATE_LEN], -) -> E -where - E: ExtensionField, -{ - let mut acc = challenges.bus_prefix[CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] * transition_label - + challenges.beta_powers[bus_message::ADDR_IDX] * addr; - for (i, &elem) in state.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc -} - -/// Encodes hasher message as **bus_prefix[CHIPLETS_BUS] + ** -/// (skips node_index, digest is RATE0 only). -#[inline(always)] -fn header_digest_value( - challenges: &Challenges, - transition_label: Felt, - addr: Felt, - digest: [Felt; WORD_SIZE], -) -> E -where - E: ExtensionField, -{ - let mut acc = challenges.bus_prefix[CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] * transition_label - + challenges.beta_powers[bus_message::ADDR_IDX] * addr; - for (i, &elem) in digest.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc -} - -// REQUESTS -// ============================================================================================== - -/// Builds requests made to the hasher chiplet at the start of a control block. -pub(super) fn build_control_block_request>( - main_trace: &MainTrace, - decoder_hasher_state: [Felt; 8], - op_code_felt: Felt, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let message = ControlBlockRequestMessage { - transition_label: LINEAR_HASH_LABEL_START, - addr_next: main_trace.addr(row + 1), - op_code: op_code_felt, - decoder_hasher_state, - }; - - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(message), challenges); - - value -} - -/// Builds requests made to the hasher chiplet at the start of a span block. -pub(super) fn build_span_block_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let span_block_message = SpanBlockMessage { - transition_label: LINEAR_HASH_LABEL_START, - addr_next: main_trace.addr(row + 1), - state: main_trace.decoder_hasher_state(row), - }; - - let value = span_block_message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(span_block_message), challenges); - - value -} - -/// Builds requests made to the hasher chiplet at the start of a respan block. -pub(super) fn build_respan_block_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let respan_block_message = RespanBlockMessage { - transition_label: LINEAR_HASH_LABEL_RESPAN, - addr_next: main_trace.addr(row + 1), - state: main_trace.decoder_hasher_state(row), - }; - - let value = respan_block_message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(respan_block_message), challenges); - - value -} - -/// Builds requests made to the hasher chiplet at the end of a block. -pub(super) fn build_end_block_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let end_block_message = EndBlockMessage { - // The output row's hasher address is the input row's address + 1, - // since each controller pair occupies 2 consecutive rows. - addr: main_trace.addr(row) + ONE, - transition_label: RETURN_HASH_LABEL_END, - digest: main_trace.decoder_hasher_state(row)[..4] - .try_into() - .expect("decoder_hasher_state[0..4] must be 4 field elements"), - }; - - let value = end_block_message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(end_block_message), challenges); - - value -} - -/// Builds `HPERM` requests made to the hash chiplet. -pub(super) fn build_hperm_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let helper_0 = main_trace.helper_register(0, row); - let state: [Felt; 12] = core::array::from_fn(|i| main_trace.stack_element(i, row)); - let state_nxt: [Felt; 12] = core::array::from_fn(|i| main_trace.stack_element(i, row + 1)); - - let input_req = HasherMessage { - transition_label: LINEAR_HASH_LABEL_START, - addr_next: helper_0, - node_index: ZERO, - // Internal Poseidon2 state for HPERM is taken directly from the top 12 - // stack elements in order: [RATE0, RATE1, CAPACITY] = [s0..s11]. - hasher_state: state, - source: "hperm input", - }; - let output_req = HasherMessage { - transition_label: RETURN_STATE_LABEL_END, - // Output row is 1 row after input in controller pair - addr_next: helper_0 + ONE, - node_index: ZERO, - hasher_state: state_nxt, - source: "hperm output", - }; - - let combined_value = input_req.value(challenges) * output_req.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - { - _debugger.add_request(alloc::boxed::Box::new(input_req), challenges); - _debugger.add_request(alloc::boxed::Box::new(output_req), challenges); - } - - combined_value -} - -/// Builds `LOG_PRECOMPILE` requests made to the hash chiplet. -/// -/// The operation absorbs `[TAG, COMM]` into the transcript via a Poseidon2 permutation with -/// capacity `CAP_PREV`, producing output `[R0, R1, CAP_NEXT]`. -/// -/// Stack layout (current row), structural (LSB-first) per word: -/// - `s0..s3`: `COMM[0..3]` -/// - `s4..s7`: `TAG[0..3]` -/// -/// Helper registers (current row): -/// - `h0`: hasher address -/// - `h1..h4`: `CAP_PREV[0..3]` -/// -/// Stack layout (next row): -/// - `s0..s3`: `R0[0..3]` -/// - `s4..s7`: `R1[0..3]` -/// - `s8..s11`: `CAP_NEXT[0..3]` -pub(super) fn build_log_precompile_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - // Read helper registers - let addr = main_trace.helper_register(HELPER_ADDR_IDX, row); - - // Input state [COMM, TAG, CAP_PREV] in sponge order [RATE0, RATE1, CAP] - // Helper registers store capacity in sequential order [e0, e1, e2, e3] - let cap_prev = Word::from([ - main_trace.helper_register(HELPER_CAP_PREV_RANGE.start, row), - main_trace.helper_register(HELPER_CAP_PREV_RANGE.start + 1, row), - main_trace.helper_register(HELPER_CAP_PREV_RANGE.start + 2, row), - main_trace.helper_register(HELPER_CAP_PREV_RANGE.start + 3, row), - ]); - - // Stack stores words for log_precompile in structural (LSB-first) layout, - // so we read them directly as [w0, w1, w2, w3]. - let comm = main_trace.stack_word(STACK_COMM_RANGE.start, row); - let tag = main_trace.stack_word(STACK_TAG_RANGE.start, row); - // Internal Poseidon2 state is [RATE0, RATE1, CAPACITY] = [COMM, TAG, CAP_PREV] - let state_input = [comm, tag, cap_prev]; - - // Output state [R0, R1, CAP_NEXT] in sponge order - let r0 = main_trace.stack_word(STACK_R0_RANGE.start, row + 1); - let r1 = main_trace.stack_word(STACK_R1_RANGE.start, row + 1); - let cap_next = main_trace.stack_word(STACK_CAP_NEXT_RANGE.start, row + 1); - let state_output = [r0, r1, cap_next]; - - let input_req = HasherMessage { - transition_label: LINEAR_HASH_LABEL_START, - addr_next: addr, - node_index: ZERO, - hasher_state: Word::words_as_elements(&state_input) - .try_into() - .expect("log_precompile input state must be 12 field elements (3 words)"), - source: "log_precompile input", - }; - - let output_req = HasherMessage { - transition_label: RETURN_STATE_LABEL_END, - // Output row is 1 row after input in controller pair - addr_next: addr + ONE, - node_index: ZERO, - hasher_state: Word::words_as_elements(&state_output) - .try_into() - .expect("log_precompile output state must be 12 field elements (3 words)"), - source: "log_precompile output", - }; - - let combined_value = input_req.value(challenges) * output_req.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - { - _debugger.add_request(alloc::boxed::Box::new(input_req), challenges); - _debugger.add_request(alloc::boxed::Box::new(output_req), challenges); - } - - combined_value -} - -/// Builds `MPVERIFY` requests made to the hash chiplet. -pub(super) fn build_mpverify_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - // helper register holds (clk + 1) - let helper_0 = main_trace.helper_register(0, row); - let rows_per_perm = CONTROLLER_ROWS_PER_PERM_FELT; - - let node_value = main_trace.stack_word(0, row); - let node_depth = main_trace.stack_element(4, row); - let node_index = main_trace.stack_element(5, row); - let merkle_tree_root = main_trace.stack_word(6, row); - - let node_word: [Felt; WORD_SIZE] = - node_value.as_elements().try_into().expect("word must be 4 field elements"); - let root_word: [Felt; WORD_SIZE] = merkle_tree_root - .as_elements() - .try_into() - .expect("word must be 4 field elements"); - - let input_value = - hasher_message_value(challenges, MP_VERIFY_LABEL_START, helper_0, node_index, node_word); - // Output addr: depth pairs * 2 rows/pair - 1 (last output row) - let output_value = hasher_message_value( - challenges, - RETURN_HASH_LABEL_END, - helper_0 + node_depth * rows_per_perm - ONE, - ZERO, - root_word, - ); - - let combined_value = input_value * output_value; - - #[cfg(any(test, feature = "bus-debugger"))] - { - let input = HasherMessage { - transition_label: MP_VERIFY_LABEL_START, - addr_next: helper_0, - node_index, - hasher_state: word_to_hasher_state(&node_word), - source: "mpverify input", - }; - - let output = HasherMessage { - transition_label: RETURN_HASH_LABEL_END, - addr_next: helper_0 + node_depth * rows_per_perm - ONE, - node_index: ZERO, - hasher_state: word_to_hasher_state(&root_word), - source: "mpverify output", - }; - - _debugger.add_request(alloc::boxed::Box::new(input), challenges); - _debugger.add_request(alloc::boxed::Box::new(output), challenges); - } - - combined_value -} - -/// Builds `MRUPDATE` requests made to the hash chiplet. -pub(super) fn build_mrupdate_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - // helper register holds (clk + 1) - let helper_0 = main_trace.helper_register(0, row); - let rows_per_perm = CONTROLLER_ROWS_PER_PERM_FELT; - let two_legs_rows = rows_per_perm + rows_per_perm; - - let old_node_value = main_trace.stack_word(0, row); - let merkle_path_depth = main_trace.stack_element(4, row); - let node_index = main_trace.stack_element(5, row); - let old_root = main_trace.stack_word(6, row); - let new_node_value = main_trace.stack_word(10, row); - let new_root = main_trace.stack_word(0, row + 1); - - let old_node_word: [Felt; WORD_SIZE] = - old_node_value.as_elements().try_into().expect("word must be 4 field elements"); - let old_root_word: [Felt; WORD_SIZE] = - old_root.as_elements().try_into().expect("word must be 4 field elements"); - let new_node_word: [Felt; WORD_SIZE] = - new_node_value.as_elements().try_into().expect("word must be 4 field elements"); - let new_root_word: [Felt; WORD_SIZE] = - new_root.as_elements().try_into().expect("word must be 4 field elements"); - - let input_old_value = hasher_message_value( - challenges, - MR_UPDATE_OLD_LABEL_START, - helper_0, - node_index, - old_node_word, - ); - // Old path output: depth pairs * 2 rows/pair - 1 - let output_old_value = hasher_message_value( - challenges, - RETURN_HASH_LABEL_END, - helper_0 + merkle_path_depth * rows_per_perm - ONE, - ZERO, - old_root_word, - ); - // New path input: starts right after old path output - let input_new_value = hasher_message_value( - challenges, - MR_UPDATE_NEW_LABEL_START, - helper_0 + merkle_path_depth * rows_per_perm, - node_index, - new_node_word, - ); - // New path output: depth pairs * 2 rows/pair * 2 legs - 1 - let output_new_value = hasher_message_value( - challenges, - RETURN_HASH_LABEL_END, - helper_0 + merkle_path_depth * two_legs_rows - ONE, - ZERO, - new_root_word, - ); - - let combined_value = input_old_value * output_old_value * input_new_value * output_new_value; - - #[cfg(any(test, feature = "bus-debugger"))] - { - let input_old = HasherMessage { - transition_label: MR_UPDATE_OLD_LABEL_START, - addr_next: helper_0, - node_index, - hasher_state: word_to_hasher_state(&old_node_word), - source: "mrupdate input_old", - }; - - let output_old = HasherMessage { - transition_label: RETURN_HASH_LABEL_END, - addr_next: helper_0 + merkle_path_depth * rows_per_perm - ONE, - node_index: ZERO, - hasher_state: word_to_hasher_state(&old_root_word), - source: "mrupdate output_old", - }; - - let input_new = HasherMessage { - transition_label: MR_UPDATE_NEW_LABEL_START, - addr_next: helper_0 + merkle_path_depth * rows_per_perm, - node_index, - hasher_state: word_to_hasher_state(&new_node_word), - source: "mrupdate input_new", - }; - - let output_new = HasherMessage { - transition_label: RETURN_HASH_LABEL_END, - addr_next: helper_0 + merkle_path_depth * two_legs_rows - ONE, - node_index: ZERO, - hasher_state: word_to_hasher_state(&new_root_word), - source: "mrupdate output_new", - }; - - _debugger.add_request(alloc::boxed::Box::new(input_old), challenges); - _debugger.add_request(alloc::boxed::Box::new(output_old), challenges); - _debugger.add_request(alloc::boxed::Box::new(input_new), challenges); - _debugger.add_request(alloc::boxed::Box::new(output_new), challenges); - } - - combined_value -} - -// RESPONSES -// ============================================================================================== - -/// Builds the response from the hasher chiplet at `row`. -/// -/// Only controller rows of the hasher chiplet are able to produce bus responses. -/// -/// **Input rows that produce responses:** -/// - Sponge start (is_boundary=1, LINEAR_HASH): full state -> matches SPAN/control block request -/// - Sponge continuation (is_boundary=0, LINEAR_HASH): rate-only -> matches RESPAN request -/// - Tree start (is_boundary=1, MP/MV/MU): leaf word -> matches MPVERIFY/MRUPDATE input -/// -/// **Input rows that do NOT produce responses:** -/// - Tree continuation (is_boundary=0, MP/MV/MU): no matching request from decoder -/// -/// **Output rows that produce responses:** -/// - HOUT (s2=0): digest -> matches END / MPVERIFY output / MRUPDATE output -/// - SOUT with is_boundary=1 (s2=1): full state -> matches HPERM output -/// -/// **Output rows that do NOT produce responses:** -/// - SOUT with is_boundary=0: intermediate output, no matching request -/// -/// **Perm segment rows:** never produce responses. -pub(super) fn build_hasher_chiplet_responses( - main_trace: &MainTrace, - row: RowIndex, - challenges: &Challenges, - _debugger: &mut BusDebugger, -) -> E -where - E: ExtensionField, -{ - // Permutation segment rows never produce chiplets bus responses. - if main_trace.chiplet_s_perm(row) == ONE { - return E::ONE; - } - - // --- Precompute common values ----------------------------------------------- - - let selector1 = main_trace.chiplet_selector_1(row); - let selector2 = main_trace.chiplet_selector_2(row); - let selector3 = main_trace.chiplet_selector_3(row); - // Hasher labels are computed with s0=0 (the old chiplet-level selector for hasher). - // chiplet_selector_0 is now s_ctrl (1 on controller rows), but labels encode - // [0, s0, s1, s2] to match the label constants defined in hasher.rs. - let op_label = get_op_label(ZERO, selector1, selector2, selector3); - let addr_next = Felt::from(row + 1); - let state = main_trace.chiplet_hasher_state(row); - let node_index = main_trace.chiplet_node_index(row); - - // Hasher-internal selectors (not chiplet-level selectors). - // chiplet selector1 = hasher s0, selector2 = hasher s1, selector3 = hasher s2. - let s0 = selector1; - let s1 = selector2; - let s2 = selector3; - - let is_boundary = main_trace.chiplet_is_boundary(row); - - // Precompute commonly needed slices. - let digest: [Felt; WORD_SIZE] = - state[..WORD_SIZE].try_into().expect("state[0..4] must be 4 field elements"); - let rate: [Felt; hasher::RATE_LEN] = state[..hasher::RATE_LEN] - .try_into() - .expect("state[0..8] must be 8 field elements"); - - // --- Classify row and compute response -------------------------------------- - // - // The branches below are mutually exclusive. Each either returns a non-identity - // response or falls through to return E::ONE (identity = no response). - - if s0 == ONE && s1 == ZERO && s2 == ZERO && is_boundary == ONE { - // Sponge start (LINEAR_HASH, is_boundary=1): full 12-element state. - // Matches SPAN / control block start request. - let label = op_label + LABEL_OFFSET_START; - let msg = HasherMessage { - transition_label: label, - addr_next, - node_index, - hasher_state: state, - source: "hasher sponge_start", - }; - let value = msg.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_response(alloc::boxed::Box::new(msg), challenges); - - value - } else if s0 == ONE && s1 == ZERO && s2 == ZERO { - // Sponge continuation (LINEAR_HASH, is_boundary=0): rate-only message. - // Label uses OUTPUT_LABEL_OFFSET because the decoder's RESPAN request uses - // LINEAR_HASH_LABEL + 32. - let label = op_label + LABEL_OFFSET_END; - let value = header_rate_value(challenges, label, addr_next, rate); - - #[cfg(any(test, feature = "bus-debugger"))] - { - let msg = HasherMessage { - transition_label: label, - addr_next, - node_index: ZERO, - hasher_state: word_to_hasher_state(&digest), // rate-only, capacity zeroed - source: "hasher sponge_respan", - }; - _debugger.add_response(alloc::boxed::Box::new(msg), challenges); - } - - value - } else if s0 == ONE && (s1 == ONE || s2 == ONE) && is_boundary == ONE { - // Tree start (MP_VERIFY / MR_UPDATE_OLD / MR_UPDATE_NEW, is_boundary=1): leaf word - // selected by direction bit. Matches MPVERIFY / MRUPDATE first-input request. - // Tree continuation inputs (is_boundary=0) produce no response. - let label = op_label + LABEL_OFFSET_START; - let bit = node_index.as_canonical_u64() & 1; - let leaf_word: [Felt; WORD_SIZE] = if bit == 0 { - digest - } else { - state[WORD_SIZE..hasher::RATE_LEN] - .try_into() - .expect("state[4..8] must be 4 field elements") - }; - - let value = hasher_message_value(challenges, label, addr_next, node_index, leaf_word); - - #[cfg(any(test, feature = "bus-debugger"))] - { - let msg = HasherMessage { - transition_label: label, - addr_next, - node_index, - hasher_state: word_to_hasher_state(&leaf_word), - source: "hasher tree_start", - }; - _debugger.add_response(alloc::boxed::Box::new(msg), challenges); - } - - value - } else if s0 == ZERO && s1 == ZERO && s2 == ZERO { - // HOUT -- RETURN_HASH (0,0,0): digest-only response. - // Matches END / MPVERIFY output / MRUPDATE output. - let label = op_label + LABEL_OFFSET_END; - let value = hasher_message_value(challenges, label, addr_next, node_index, digest); - - #[cfg(any(test, feature = "bus-debugger"))] - { - let msg = HasherMessage { - transition_label: label, - addr_next, - node_index, - hasher_state: word_to_hasher_state(&digest), - source: "hasher hout", - }; - _debugger.add_response(alloc::boxed::Box::new(msg), challenges); - } - - value - } else if s0 == ZERO && s1 == ZERO && s2 == ONE && is_boundary == ONE { - // SOUT final -- RETURN_STATE (0,0,1) with is_boundary=1: full 12-element state. - // Matches HPERM output request. Intermediate SOUT (is_boundary=0) produces no response. - let label = op_label + LABEL_OFFSET_END; - let msg = HasherMessage { - transition_label: label, - addr_next, - node_index, - hasher_state: state, - source: "hasher sout_final", - }; - let value = msg.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_response(alloc::boxed::Box::new(msg), challenges); - - value - } else { - // No response: padding rows (s0=0, s1=1), tree continuations (is_boundary=0), - // intermediate SOUT (is_boundary=0), or any other non-responding row. - E::ONE - } -} - -// CONTROL BLOCK REQUEST MESSAGE -// =============================================================================================== -pub struct ControlBlockRequestMessage { - pub transition_label: Felt, - pub addr_next: Felt, - pub op_code: Felt, - pub decoder_hasher_state: [Felt; 8], -} - -impl BusMessage for ControlBlockRequestMessage -where - E: ExtensionField, -{ - /// Encodes as **bus_prefix[CHIPLETS_BUS] + ** (skips node_index). - fn value(&self, challenges: &Challenges) -> E { - // Header + rate portion + capacity domain element for op_code - let mut acc = header_rate_value( - challenges, - self.transition_label, - self.addr_next, - self.decoder_hasher_state, - ); - acc += challenges.beta_powers[bus_message::CAPACITY_DOMAIN_IDX] * self.op_code; - acc - } - - fn source(&self) -> &str { - let op_code = self.op_code.as_canonical_u64() as u8; - match op_code { - opcodes::JOIN => "join", - opcodes::SPLIT => "split", - opcodes::LOOP => "loop", - opcodes::CALL => "call", - opcodes::DYN => "dyn", - opcodes::DYNCALL => "dyncall", - opcodes::SYSCALL => "syscall", - _ => panic!("unexpected opcode: {op_code}"), - } - } -} - -impl Display for ControlBlockRequestMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{{ transition_label: {}, addr_next: {}, op_code: {}, decoder_hasher_state: {:?} }}", - self.transition_label, self.addr_next, self.op_code, self.decoder_hasher_state - ) - } -} - -// GENERIC HASHER MESSAGE -// =============================================================================================== - -pub struct HasherMessage { - pub transition_label: Felt, - pub addr_next: Felt, - pub node_index: Felt, - pub hasher_state: [Felt; hasher::STATE_WIDTH], - pub source: &'static str, -} - -impl BusMessage for HasherMessage -where - E: ExtensionField, -{ - fn value(&self, challenges: &Challenges) -> E { - hasher_message_value( - challenges, - self.transition_label, - self.addr_next, - self.node_index, - self.hasher_state, - ) - } - - fn source(&self) -> &str { - self.source - } -} - -impl Display for HasherMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{{ transition_label: {}, addr_next: {}, node_index: {}, hasher_state: {:?} }}", - self.transition_label, self.addr_next, self.node_index, self.hasher_state - ) - } -} - -// SPAN BLOCK MESSAGE -// =============================================================================================== - -pub struct SpanBlockMessage { - pub transition_label: Felt, - pub addr_next: Felt, - pub state: [Felt; 8], -} - -impl BusMessage for SpanBlockMessage -where - E: ExtensionField, -{ - fn value(&self, challenges: &Challenges) -> E { - header_rate_value(challenges, self.transition_label, self.addr_next, self.state) - } - - fn source(&self) -> &str { - "span" - } -} - -impl Display for SpanBlockMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{{ transition_label: {}, addr_next: {}, state: {:?} }}", - self.transition_label, self.addr_next, self.state - ) - } -} - -// RESPAN BLOCK MESSAGE -// =============================================================================================== - -pub struct RespanBlockMessage { - pub transition_label: Felt, - pub addr_next: Felt, - pub state: [Felt; 8], -} - -impl BusMessage for RespanBlockMessage -where - E: ExtensionField, -{ - fn value(&self, challenges: &Challenges) -> E { - // In the controller/perm split, addr_next is used directly (no subtraction). - header_rate_value(challenges, self.transition_label, self.addr_next, self.state) - } - - fn source(&self) -> &str { - "respan" - } -} - -impl Display for RespanBlockMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{{ transition_label: {}, addr_next: {}, state: {:?} }}", - self.transition_label, self.addr_next, self.state - ) - } -} - -// END BLOCK MESSAGE -// =============================================================================================== - -pub struct EndBlockMessage { - pub addr: Felt, - pub transition_label: Felt, - pub digest: [Felt; 4], -} - -impl BusMessage for EndBlockMessage -where - E: ExtensionField, -{ - fn value(&self, challenges: &Challenges) -> E { - header_digest_value(challenges, self.transition_label, self.addr, self.digest) - } - - fn source(&self) -> &str { - "end" - } -} - -impl Display for EndBlockMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{{ addr: {}, transition_label: {}, digest: {:?} }}", - self.addr, self.transition_label, self.digest - ) - } -} diff --git a/processor/src/trace/chiplets/aux_trace/bus/kernel.rs b/processor/src/trace/chiplets/aux_trace/bus/kernel.rs deleted file mode 100644 index a366c612b6..0000000000 --- a/processor/src/trace/chiplets/aux_trace/bus/kernel.rs +++ /dev/null @@ -1,134 +0,0 @@ -use core::fmt::{Display, Formatter, Result as FmtResult}; - -use miden_air::trace::{ - Challenges, MainTrace, RowIndex, - bus_types::CHIPLETS_BUS, - chiplets::kernel_rom::{KERNEL_PROC_CALL_LABEL, KERNEL_PROC_INIT_LABEL}, -}; -use miden_core::{Felt, field::ExtensionField}; - -use crate::debug::{BusDebugger, BusMessage}; - -// RESPONSES -// ================================================================================================ - -/// Builds the response from the kernel chiplet at `row`. -/// -/// # Details -/// Each kernel procedure digest appears `n+1` times in the trace when requested `n` times by -/// the decoder (via SYSCALL). The first row for each unique digest produces a -/// `KernelRomInitMessage` response; the remaining `n` rows produce `KernelRomMessage` responses -/// matching decoder requests. -pub(super) fn build_kernel_chiplet_responses( - main_trace: &MainTrace, - row: RowIndex, - challenges: &Challenges, - _debugger: &mut BusDebugger, -) -> E -where - E: ExtensionField, -{ - let root0 = main_trace.chiplet_kernel_root_0(row); - let root1 = main_trace.chiplet_kernel_root_1(row); - let root2 = main_trace.chiplet_kernel_root_2(row); - let root3 = main_trace.chiplet_kernel_root_3(row); - - // The caller ensures this row is a kernel ROM row, so we just need to check if this is - // the first row for a unique procedure digest. - if main_trace.chiplet_kernel_is_first_hash_row(row) { - // Respond to the requests performed by the verifier when they initialize the bus - // column with the unique proc hashes. - let message = KernelRomInitMessage { - kernel_proc_digest: [root0, root1, root2, root3], - }; - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_response(alloc::boxed::Box::new(message), challenges); - - value - } else { - // Respond to decoder messages. - let message = KernelRomMessage { - kernel_proc_digest: [root0, root1, root2, root3], - }; - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_response(alloc::boxed::Box::new(message), challenges); - value - } -} - -// MESSAGES -// =============================================================================================== - -/// A message between the decoder and the kernel ROM to ensure a SYSCALL can only call procedures -///in the kernel as specified through public inputs. -pub struct KernelRomMessage { - pub kernel_proc_digest: [Felt; 4], -} - -impl BusMessage for KernelRomMessage -where - E: ExtensionField, -{ - #[inline(always)] - fn value(&self, challenges: &Challenges) -> E { - challenges.encode( - CHIPLETS_BUS, - [ - KERNEL_PROC_CALL_LABEL, - self.kernel_proc_digest[0], - self.kernel_proc_digest[1], - self.kernel_proc_digest[2], - self.kernel_proc_digest[3], - ], - ) - } - - fn source(&self) -> &str { - "kernel rom" - } -} - -impl Display for KernelRomMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!(f, "{{ proc digest: {:?} }}", self.kernel_proc_digest) - } -} - -/// A message linking unique kernel procedure hashes provided by public inputs, with hashes -/// contained in the kernel ROM chiplet trace. -pub struct KernelRomInitMessage { - pub kernel_proc_digest: [Felt; 4], -} - -impl BusMessage for KernelRomInitMessage -where - E: ExtensionField, -{ - #[inline(always)] - fn value(&self, challenges: &Challenges) -> E { - challenges.encode( - CHIPLETS_BUS, - [ - KERNEL_PROC_INIT_LABEL, - self.kernel_proc_digest[0], - self.kernel_proc_digest[1], - self.kernel_proc_digest[2], - self.kernel_proc_digest[3], - ], - ) - } - - fn source(&self) -> &str { - "kernel rom init" - } -} - -impl Display for KernelRomInitMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!(f, "{{ proc digest init: {:?} }}", self.kernel_proc_digest) - } -} diff --git a/processor/src/trace/chiplets/aux_trace/bus/memory.rs b/processor/src/trace/chiplets/aux_trace/bus/memory.rs deleted file mode 100644 index e22a6078e0..0000000000 --- a/processor/src/trace/chiplets/aux_trace/bus/memory.rs +++ /dev/null @@ -1,665 +0,0 @@ -use core::fmt::{Display, Formatter, Result as FmtResult}; - -use miden_air::trace::{ - Challenges, MainTrace, RowIndex, - bus_types::CHIPLETS_BUS, - chiplets::{ - ace::{ACE_INSTRUCTION_ID1_OFFSET, ACE_INSTRUCTION_ID2_OFFSET}, - memory::{ - MEMORY_ACCESS_ELEMENT, MEMORY_ACCESS_WORD, MEMORY_READ_ELEMENT_LABEL, - MEMORY_READ_WORD_LABEL, MEMORY_WRITE_ELEMENT_LABEL, MEMORY_WRITE_WORD_LABEL, - }, - }, -}; -use miden_core::{ - FMP_ADDR, FMP_INIT_VALUE, Felt, ONE, ZERO, field::ExtensionField, operations::opcodes, -}; - -use crate::debug::{BusDebugger, BusMessage}; - -// CONSTANTS -// ================================================================================================ - -const FOUR: Felt = Felt::new_unchecked(4); - -// REQUESTS -// ================================================================================================ - -/// Builds ACE chiplet read requests as part of the `READ` section made to the memory chiplet. -pub fn build_ace_memory_read_word_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let word = [ - main_trace.chiplet_ace_v_0_0(row), - main_trace.chiplet_ace_v_0_1(row), - main_trace.chiplet_ace_v_1_0(row), - main_trace.chiplet_ace_v_1_1(row), - ]; - let op_label = MEMORY_READ_WORD_LABEL; - let clk = main_trace.chiplet_ace_clk(row); - let ctx = main_trace.chiplet_ace_ctx(row); - let addr = main_trace.chiplet_ace_ptr(row); - - let message = MemoryWordMessage { - op_label: Felt::from_u8(op_label), - ctx, - addr, - clk, - word, - source: "read word ACE", - }; - - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(message), challenges); - - value -} - -/// Builds ACE chiplet read requests as part of the `EVAL` section made to the memory chiplet. -pub fn build_ace_memory_read_element_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let element = main_trace.chiplet_ace_eval_op(row); - - let id_0 = main_trace.chiplet_ace_id_1(row); - let id_1 = main_trace.chiplet_ace_id_2(row); - let element = - id_0 + id_1 * ACE_INSTRUCTION_ID1_OFFSET + (element + ONE) * ACE_INSTRUCTION_ID2_OFFSET; - let op_label = MEMORY_READ_ELEMENT_LABEL; - let clk = main_trace.chiplet_ace_clk(row); - let ctx = main_trace.chiplet_ace_ctx(row); - let addr = main_trace.chiplet_ace_ptr(row); - - let message = MemoryElementMessage { - op_label: Felt::from_u8(op_label), - ctx, - addr, - clk, - element, - }; - - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(message), challenges); - - value -} - -/// Builds `DYN` and `DYNCALL` read request made to the memory chiplet for the callee hash. -pub(super) fn build_dyn_dyncall_callee_hash_read_request>( - main_trace: &MainTrace, - op_code_felt: Felt, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let memory_req = MemoryWordMessage { - op_label: Felt::from_u8(MEMORY_READ_WORD_LABEL), - ctx: main_trace.ctx(row), - addr: main_trace.stack_element(0, row), - clk: main_trace.clk(row), - word: main_trace.decoder_hasher_state_first_half(row).into(), - source: if op_code_felt == Felt::from_u8(opcodes::DYNCALL) { - "dyncall" - } else { - "dyn" - }, - }; - - let value = memory_req.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(memory_req), challenges); - - value -} - -/// Builds a write request to initialize the frame pointer in memory when entering a new execution -/// context. -/// -/// Currently, this is done with `CALL` and `DYNCALL`. -pub(super) fn build_fmp_initialization_write_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - // Note that `ctx` is taken from the next row, as the goal of this request is to write the - // initial FMP value to memory at the start of a new execution context, which happens - // immediately after the current row. - let memory_req = MemoryElementMessage { - op_label: Felt::from_u8(MEMORY_WRITE_ELEMENT_LABEL), - ctx: main_trace.ctx(row + 1), - addr: FMP_ADDR, - clk: main_trace.clk(row), - element: FMP_INIT_VALUE, - }; - - let value = memory_req.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(memory_req), challenges); - - value -} - -/// Builds `MLOADW` and `MSTOREW` requests made to the memory chiplet. -pub(super) fn build_mem_mloadw_mstorew_request>( - main_trace: &MainTrace, - op_label: u8, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - // word[i] maps directly to stack position i. - let word = [ - main_trace.stack_element(0, row + 1), - main_trace.stack_element(1, row + 1), - main_trace.stack_element(2, row + 1), - main_trace.stack_element(3, row + 1), - ]; - let addr = main_trace.stack_element(0, row); - - debug_assert!(op_label == MEMORY_READ_WORD_LABEL || op_label == MEMORY_WRITE_WORD_LABEL); - let ctx = main_trace.ctx(row); - let clk = main_trace.clk(row); - - let message = MemoryWordMessage { - op_label: Felt::from_u8(op_label), - ctx, - addr, - clk, - word, - source: if op_label == MEMORY_READ_WORD_LABEL { - "mloadw" - } else { - "mstorew" - }, - }; - - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(message), challenges); - - value -} - -/// Builds `MLOAD` and `MSTORE` requests made to the memory chiplet. -pub(super) fn build_mem_mload_mstore_request>( - main_trace: &MainTrace, - op_label: u8, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let element = main_trace.stack_element(0, row + 1); - let addr = main_trace.stack_element(0, row); - - debug_assert!(op_label == MEMORY_READ_ELEMENT_LABEL || op_label == MEMORY_WRITE_ELEMENT_LABEL); - - let ctx = main_trace.ctx(row); - let clk = main_trace.clk(row); - - let message = MemoryElementMessage { - op_label: Felt::from_u8(op_label), - ctx, - addr, - clk, - element, - }; - - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(message), challenges); - - value -} - -/// Builds `MSTREAM` requests made to the memory chiplet. -pub(super) fn build_mstream_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let op_label = Felt::from_u8(MEMORY_READ_WORD_LABEL); - let addr = main_trace.stack_element(12, row); - let ctx = main_trace.ctx(row); - let clk = main_trace.clk(row); - - // word[0] is at stack position 0 (top). - // MSTREAM loads two words: first word (from addr) to s0-s3, second word (from addr+4) to s4-s7. - let mem_req_1 = MemoryWordMessage { - op_label, - ctx, - addr, - clk, - word: [ - main_trace.stack_element(0, row + 1), - main_trace.stack_element(1, row + 1), - main_trace.stack_element(2, row + 1), - main_trace.stack_element(3, row + 1), - ], - source: "mstream req 1", - }; - let mem_req_2 = MemoryWordMessage { - op_label, - ctx, - addr: addr + FOUR, - clk, - word: [ - main_trace.stack_element(4, row + 1), - main_trace.stack_element(5, row + 1), - main_trace.stack_element(6, row + 1), - main_trace.stack_element(7, row + 1), - ], - source: "mstream req 2", - }; - - let combined_value = mem_req_1.value(challenges) * mem_req_2.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - { - _debugger.add_request(alloc::boxed::Box::new(mem_req_1), challenges); - _debugger.add_request(alloc::boxed::Box::new(mem_req_2), challenges); - } - - combined_value -} - -/// Builds `PIPE` requests made to the memory chiplet. -pub(super) fn build_pipe_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let op_label = Felt::from_u8(MEMORY_WRITE_WORD_LABEL); - let addr = main_trace.stack_element(12, row); - let ctx = main_trace.ctx(row); - let clk = main_trace.clk(row); - - // word[0] is at stack position 0 (top). - // PIPE writes two words: first word (from s0-s3) to addr, second word (from s4-s7) to addr+4. - let mem_req_1 = MemoryWordMessage { - op_label, - ctx, - addr, - clk, - word: [ - main_trace.stack_element(0, row + 1), - main_trace.stack_element(1, row + 1), - main_trace.stack_element(2, row + 1), - main_trace.stack_element(3, row + 1), - ], - source: "pipe req 1", - }; - let mem_req_2 = MemoryWordMessage { - op_label, - ctx, - addr: addr + FOUR, - clk, - word: [ - main_trace.stack_element(4, row + 1), - main_trace.stack_element(5, row + 1), - main_trace.stack_element(6, row + 1), - main_trace.stack_element(7, row + 1), - ], - source: "pipe req 2", - }; - - let combined_value = mem_req_1.value(challenges) * mem_req_2.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - { - _debugger.add_request(alloc::boxed::Box::new(mem_req_1), challenges); - _debugger.add_request(alloc::boxed::Box::new(mem_req_2), challenges); - } - - combined_value -} - -/// Builds `CRYPTOSTREAM` requests made to the memory chiplet. -/// -/// CryptoStream reads two words from `src_ptr` and `src_ptr + 4`, and writes two words (the -/// ciphertext) to `dst_ptr` and `dst_ptr + 4`. The ciphertext is `plaintext + rate`, where -/// rate is the stack top before the operation. We derive the read (plaintext) values as -/// `ciphertext - rate`. -pub(super) fn build_crypto_stream_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let ctx = main_trace.ctx(row); - let clk = main_trace.clk(row); - let src_addr = main_trace.stack_element(12, row); - let dst_addr = main_trace.stack_element(13, row); - - // Ciphertext is on the stack after the operation (row + 1), positions 0..7. - let ciphertext: [Felt; 8] = core::array::from_fn(|i| main_trace.stack_element(i, row + 1)); - - // Rate is on the stack before the operation (row), positions 0..7. - let rate: [Felt; 8] = core::array::from_fn(|i| main_trace.stack_element(i, row)); - - // Plaintext = ciphertext - rate. - let plaintext: [Felt; 8] = core::array::from_fn(|i| ciphertext[i] - rate[i]); - - // Two read-word requests at src_addr and src_addr + 4. - let read_label = Felt::from_u8(MEMORY_READ_WORD_LABEL); - let read_req_1 = MemoryWordMessage { - op_label: read_label, - ctx, - addr: src_addr, - clk, - word: [plaintext[0], plaintext[1], plaintext[2], plaintext[3]], - source: "crypto_stream read 1", - }; - let read_req_2 = MemoryWordMessage { - op_label: read_label, - ctx, - addr: src_addr + FOUR, - clk, - word: [plaintext[4], plaintext[5], plaintext[6], plaintext[7]], - source: "crypto_stream read 2", - }; - - // Two write-word requests at dst_addr and dst_addr + 4. - let write_label = Felt::from_u8(MEMORY_WRITE_WORD_LABEL); - let write_req_1 = MemoryWordMessage { - op_label: write_label, - ctx, - addr: dst_addr, - clk, - word: [ciphertext[0], ciphertext[1], ciphertext[2], ciphertext[3]], - source: "crypto_stream write 1", - }; - let write_req_2 = MemoryWordMessage { - op_label: write_label, - ctx, - addr: dst_addr + FOUR, - clk, - word: [ciphertext[4], ciphertext[5], ciphertext[6], ciphertext[7]], - source: "crypto_stream write 2", - }; - - let combined_value = read_req_1.value(challenges) - * read_req_2.value(challenges) - * write_req_1.value(challenges) - * write_req_2.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - { - _debugger.add_request(alloc::boxed::Box::new(read_req_1), challenges); - _debugger.add_request(alloc::boxed::Box::new(read_req_2), challenges); - _debugger.add_request(alloc::boxed::Box::new(write_req_1), challenges); - _debugger.add_request(alloc::boxed::Box::new(write_req_2), challenges); - } - - combined_value -} - -/// Builds `HORNERBASE` requests made to the memory chiplet. -pub(super) fn build_hornerbase_eval_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let eval_point_0 = main_trace.helper_register(0, row); - let eval_point_1 = main_trace.helper_register(1, row); - let eval_point_ptr = main_trace.stack_element(13, row); - let op_label = Felt::from_u8(MEMORY_READ_ELEMENT_LABEL); - - let ctx = main_trace.ctx(row); - let clk = main_trace.clk(row); - - let mem_req_0 = MemoryElementMessage { - op_label, - ctx, - addr: eval_point_ptr, - clk, - element: eval_point_0, - }; - let mem_req_1 = MemoryElementMessage { - op_label, - ctx, - addr: eval_point_ptr + ONE, - clk, - element: eval_point_1, - }; - - let value = mem_req_0.value(challenges) * mem_req_1.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - { - _debugger.add_request(alloc::boxed::Box::new(mem_req_0), challenges); - _debugger.add_request(alloc::boxed::Box::new(mem_req_1), challenges); - } - - value -} - -/// Builds `HORNEREXT` requests made to the memory chiplet. -pub(super) fn build_hornerext_eval_request>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E { - let eval_point_0 = main_trace.helper_register(0, row); - let eval_point_1 = main_trace.helper_register(1, row); - let mem_junk_0 = main_trace.helper_register(2, row); - let mem_junk_1 = main_trace.helper_register(3, row); - let eval_point_ptr = main_trace.stack_element(13, row); - let op_label = Felt::from_u8(MEMORY_READ_WORD_LABEL); - - let ctx = main_trace.ctx(row); - let clk = main_trace.clk(row); - - let mem_req = MemoryWordMessage { - op_label, - ctx, - addr: eval_point_ptr, - clk, - word: [eval_point_0, eval_point_1, mem_junk_0, mem_junk_1], - source: "hornerext_eval_* req", - }; - - let value = mem_req.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - { - _debugger.add_request(alloc::boxed::Box::new(mem_req), challenges); - } - - value -} - -// RESPONSES -// ================================================================================================ - -/// Builds the response from the memory chiplet at `row`. -pub(super) fn build_memory_chiplet_responses( - main_trace: &MainTrace, - row: RowIndex, - challenges: &Challenges, - _debugger: &mut BusDebugger, -) -> E -where - E: ExtensionField, -{ - let access_type = main_trace.chiplet_selector_4(row); - let op_label = { - let is_read = main_trace.chiplet_selector_3(row); - get_memory_op_label(is_read, access_type) - }; - let ctx = main_trace.chiplet_memory_ctx(row); - let clk = main_trace.chiplet_memory_clk(row); - let addr = { - let word = main_trace.chiplet_memory_word(row); - let idx0 = main_trace.chiplet_memory_idx0(row); - let idx1 = main_trace.chiplet_memory_idx1(row); - - word + idx1.double() + idx0 - }; - - if access_type == MEMORY_ACCESS_ELEMENT { - let idx0 = main_trace.chiplet_memory_idx0(row); - let idx1 = main_trace.chiplet_memory_idx1(row); - - let element = if idx1 == ZERO && idx0 == ZERO { - main_trace.chiplet_memory_value_0(row) - } else if idx1 == ZERO && idx0 == ONE { - main_trace.chiplet_memory_value_1(row) - } else if idx1 == ONE && idx0 == ZERO { - main_trace.chiplet_memory_value_2(row) - } else if idx1 == ONE && idx0 == ONE { - main_trace.chiplet_memory_value_3(row) - } else { - panic!("Invalid word indices. idx0: {idx0}, idx1: {idx1}"); - }; - - let message = MemoryElementMessage { op_label, ctx, addr, clk, element }; - - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_response(alloc::boxed::Box::new(message), challenges); - - value - } else if access_type == MEMORY_ACCESS_WORD { - let value0 = main_trace.chiplet_memory_value_0(row); - let value1 = main_trace.chiplet_memory_value_1(row); - let value2 = main_trace.chiplet_memory_value_2(row); - let value3 = main_trace.chiplet_memory_value_3(row); - - let message = MemoryWordMessage { - op_label, - ctx, - addr, - clk, - word: [value0, value1, value2, value3], - source: "memory chiplet", - }; - - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_response(alloc::boxed::Box::new(message), challenges); - - value - } else { - panic!("Invalid memory element/word column value: {access_type}"); - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Returns the operation unique label for memory operations. -/// -/// The memory selector flags are `[1, 1, 0, is_read, is_word_access]`. -/// The flag is derived as the big-endian representation of these flags, plus one. -/// They are also defined in [`chiplets::memory`](miden_air::trace::chiplets::memory). -fn get_memory_op_label(is_read: Felt, is_word_access: Felt) -> Felt { - let is_read = (is_read == ONE) as u8; - let is_word_access = (is_word_access == ONE) as u8; - - const MEMORY_SELECTOR_FLAG_BASE: u8 = 0b011 + 1; - const OP_FLAG_SHIFT: u8 = 3; - - let op_flag = is_read + 2 * is_word_access; - - Felt::from_u8(MEMORY_SELECTOR_FLAG_BASE + (op_flag << OP_FLAG_SHIFT)) -} - -// MESSAGES -// =============================================================================================== - -pub struct MemoryWordMessage { - pub op_label: Felt, - pub ctx: Felt, - pub addr: Felt, - pub clk: Felt, - pub word: [Felt; 4], - pub source: &'static str, -} - -impl BusMessage for MemoryWordMessage -where - E: ExtensionField, -{ - fn value(&self, challenges: &Challenges) -> E { - challenges.encode( - CHIPLETS_BUS, - [ - self.op_label, - self.ctx, - self.addr, - self.clk, - self.word[0], - self.word[1], - self.word[2], - self.word[3], - ], - ) - } - - fn source(&self) -> &str { - self.source - } -} - -impl Display for MemoryWordMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{{ op_label: {}, ctx: {}, addr: {}, clk: {}, word: {:?} }}", - self.op_label, self.ctx, self.addr, self.clk, self.word - ) - } -} - -pub struct MemoryElementMessage { - pub op_label: Felt, - pub ctx: Felt, - pub addr: Felt, - pub clk: Felt, - pub element: Felt, -} - -impl BusMessage for MemoryElementMessage -where - E: ExtensionField, -{ - fn value(&self, challenges: &Challenges) -> E { - challenges - .encode(CHIPLETS_BUS, [self.op_label, self.ctx, self.addr, self.clk, self.element]) - } - - fn source(&self) -> &str { - "memory element" - } -} - -impl Display for MemoryElementMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!( - f, - "{{ op_label: {}, ctx: {}, addr: {}, clk: {}, element: {} }}", - self.op_label, self.ctx, self.addr, self.clk, self.element - ) - } -} diff --git a/processor/src/trace/chiplets/aux_trace/bus/mod.rs b/processor/src/trace/chiplets/aux_trace/bus/mod.rs deleted file mode 100644 index ced7ba4977..0000000000 --- a/processor/src/trace/chiplets/aux_trace/bus/mod.rs +++ /dev/null @@ -1,336 +0,0 @@ -use ace::{build_ace_chiplet_requests, build_ace_chiplet_responses}; -use bitwise::{build_bitwise_chiplet_responses, build_bitwise_request}; -use hasher::{ - ControlBlockRequestMessage, build_control_block_request, build_end_block_request, - build_hasher_chiplet_responses, build_hperm_request, build_log_precompile_request, - build_mpverify_request, build_mrupdate_request, build_respan_block_request, - build_span_block_request, -}; -use kernel::{KernelRomMessage, build_kernel_chiplet_responses}; -use memory::{ - build_crypto_stream_request, build_dyn_dyncall_callee_hash_read_request, - build_fmp_initialization_write_request, build_hornerbase_eval_request, - build_hornerext_eval_request, build_mem_mload_mstore_request, build_mem_mloadw_mstorew_request, - build_memory_chiplet_responses, build_mstream_request, build_pipe_request, -}; -use miden_air::trace::{ - Challenges, MainTrace, RowIndex, - bus_types::CHIPLETS_BUS, - chiplets::{ - hasher::LINEAR_HASH_LABEL, - memory::{ - MEMORY_READ_ELEMENT_LABEL, MEMORY_READ_WORD_LABEL, MEMORY_WRITE_ELEMENT_LABEL, - MEMORY_WRITE_WORD_LABEL, - }, - }, -}; -use miden_core::{ONE, ZERO, field::ExtensionField, operations::opcodes}; - -use super::Felt; -use crate::{ - debug::{BusDebugger, BusMessage}, - trace::AuxColumnBuilder, -}; - -mod ace; -mod bitwise; -mod hasher; -mod kernel; -mod memory; - -pub use memory::{build_ace_memory_read_element_request, build_ace_memory_read_word_request}; - -// BUS COLUMN BUILDER -// ================================================================================================ - -/// Describes how to construct the execution trace of the chiplets bus auxiliary trace column. -pub struct BusColumnBuilder; - -impl AuxColumnBuilder for BusColumnBuilder -where - E: ExtensionField, -{ - /// Constructs the requests made by the VM-components to the chiplets at `row`. - fn get_requests_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - debugger: &mut BusDebugger, - ) -> E { - let op_code_felt = main_trace.get_op_code(row); - let op_code = op_code_felt.as_canonical_u64() as u8; - - match op_code { - opcodes::JOIN | opcodes::SPLIT | opcodes::LOOP => build_control_block_request( - main_trace, - main_trace.decoder_hasher_state(row), - op_code_felt, - challenges, - row, - debugger, - ), - opcodes::CALL => { - build_call_request(main_trace, op_code_felt, challenges, row, debugger) - }, - opcodes::DYN => build_dyn_request(main_trace, op_code_felt, challenges, row, debugger), - opcodes::DYNCALL => { - build_dyncall_request(main_trace, op_code_felt, challenges, row, debugger) - }, - opcodes::SYSCALL => { - build_syscall_block_request(main_trace, op_code_felt, challenges, row, debugger) - }, - opcodes::SPAN => build_span_block_request(main_trace, challenges, row, debugger), - opcodes::RESPAN => build_respan_block_request(main_trace, challenges, row, debugger), - opcodes::END => build_end_block_request(main_trace, challenges, row, debugger), - opcodes::U32AND => build_bitwise_request(main_trace, ZERO, challenges, row, debugger), - opcodes::U32XOR => build_bitwise_request(main_trace, ONE, challenges, row, debugger), - opcodes::MLOADW => build_mem_mloadw_mstorew_request( - main_trace, - MEMORY_READ_WORD_LABEL, - challenges, - row, - debugger, - ), - opcodes::MSTOREW => build_mem_mloadw_mstorew_request( - main_trace, - MEMORY_WRITE_WORD_LABEL, - challenges, - row, - debugger, - ), - opcodes::MLOAD => build_mem_mload_mstore_request( - main_trace, - MEMORY_READ_ELEMENT_LABEL, - challenges, - row, - debugger, - ), - opcodes::MSTORE => build_mem_mload_mstore_request( - main_trace, - MEMORY_WRITE_ELEMENT_LABEL, - challenges, - row, - debugger, - ), - opcodes::HORNERBASE => { - build_hornerbase_eval_request(main_trace, challenges, row, debugger) - }, - opcodes::HORNEREXT => { - build_hornerext_eval_request(main_trace, challenges, row, debugger) - }, - opcodes::MSTREAM => build_mstream_request(main_trace, challenges, row, debugger), - opcodes::CRYPTOSTREAM => { - build_crypto_stream_request(main_trace, challenges, row, debugger) - }, - opcodes::HPERM => build_hperm_request(main_trace, challenges, row, debugger), - opcodes::LOGPRECOMPILE => { - build_log_precompile_request(main_trace, challenges, row, debugger) - }, - opcodes::MPVERIFY => build_mpverify_request(main_trace, challenges, row, debugger), - opcodes::MRUPDATE => build_mrupdate_request(main_trace, challenges, row, debugger), - opcodes::PIPE => build_pipe_request(main_trace, challenges, row, debugger), - opcodes::EVALCIRCUIT => { - build_ace_chiplet_requests(main_trace, challenges, row, debugger) - }, - _ => E::ONE, - } - } - - /// Constructs the responses from the chiplets to the other VM-components at `row`. - fn get_responses_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - debugger: &mut BusDebugger, - ) -> E { - if main_trace.is_hash_row(row) { - build_hasher_chiplet_responses(main_trace, row, challenges, debugger) - } else if main_trace.is_bitwise_row(row) { - build_bitwise_chiplet_responses(main_trace, row, challenges, debugger) - } else if main_trace.is_memory_row(row) { - build_memory_chiplet_responses(main_trace, row, challenges, debugger) - } else if main_trace.is_ace_row(row) { - build_ace_chiplet_responses(main_trace, row, challenges, debugger) - } else if main_trace.is_kernel_row(row) { - build_kernel_chiplet_responses(main_trace, row, challenges, debugger) - } else { - E::ONE - } - } - - #[cfg(any(test, feature = "bus-debugger"))] - fn enforce_bus_balance(&self) -> bool { - // The chiplets bus final value encodes kernel procedure digest boundary terms, - // which are checked via reduced_aux_values. It does not balance to identity. - false - } -} - -// CHIPLETS REQUESTS TO MORE THAN ONE CHIPLET -// ================================================================================================ - -/// Encodes a control block request without hasher state (optimized for DYN/DYNCALL). -/// -/// Standard control block encoding includes state[0..7] which are always zero for DYN/DYNCALL. -/// This optimization skips those 8 multiplications. -/// -/// Encoding: `alpha + beta^0*label + beta^1*addr + beta^12*op_code` -/// where beta^12 is the capacity domain element at coeffs[13]. -#[inline(always)] -fn encode_control_block_without_state(challenges: &Challenges, addr: Felt, op_code: Felt) -> E -where - E: ExtensionField, -{ - use miden_air::trace::bus_message; - - challenges.bus_prefix[CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] * Felt::from_u8(LINEAR_HASH_LABEL + 16) - + challenges.beta_powers[bus_message::ADDR_IDX] * addr - + challenges.beta_powers[bus_message::CAPACITY_DOMAIN_IDX] * op_code -} - -/// Builds requests made on a `DYN` operation. -fn build_dyn_request( - main_trace: &MainTrace, - op_code_felt: Felt, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E -where - E: ExtensionField, -{ - let control_block_req_value = - encode_control_block_without_state(challenges, main_trace.addr(row + 1), op_code_felt); - - #[cfg(any(test, feature = "bus-debugger"))] - { - let control_block_req = ControlBlockRequestMessage { - transition_label: Felt::from_u8(LINEAR_HASH_LABEL + 16), - addr_next: main_trace.addr(row + 1), - op_code: op_code_felt, - // DYN encodes without state; keep it zeroed to match the request encoding. - decoder_hasher_state: [ZERO; 8], - }; - _debugger.add_request(alloc::boxed::Box::new(control_block_req), challenges); - } - - let callee_hash_read_req_value = build_dyn_dyncall_callee_hash_read_request( - main_trace, - op_code_felt, - challenges, - row, - _debugger, - ); - - control_block_req_value * callee_hash_read_req_value -} - -/// Builds requests made on a `DYNCALL` operation. -fn build_dyncall_request( - main_trace: &MainTrace, - op_code_felt: Felt, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E -where - E: ExtensionField, -{ - let control_block_req_value = - encode_control_block_without_state(challenges, main_trace.addr(row + 1), op_code_felt); - - #[cfg(any(test, feature = "bus-debugger"))] - { - let control_block_req = ControlBlockRequestMessage { - transition_label: Felt::from_u8(LINEAR_HASH_LABEL + 16), - addr_next: main_trace.addr(row + 1), - op_code: op_code_felt, - // DYNCALL encodes without state; keep it zeroed to match the request encoding. - decoder_hasher_state: [ZERO; 8], - }; - _debugger.add_request(alloc::boxed::Box::new(control_block_req), challenges); - } - - let callee_hash_read_req_value = build_dyn_dyncall_callee_hash_read_request( - main_trace, - op_code_felt, - challenges, - row, - _debugger, - ); - - let fmp_write_req_value = - build_fmp_initialization_write_request(main_trace, challenges, row, _debugger); - - control_block_req_value * callee_hash_read_req_value * fmp_write_req_value -} - -fn build_call_request( - main_trace: &MainTrace, - op_code_felt: Felt, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E -where - E: ExtensionField, -{ - let control_block_req_value = build_control_block_request( - main_trace, - main_trace.decoder_hasher_state(row), - op_code_felt, - challenges, - row, - _debugger, - ); - - let fmp_write_req_value = - build_fmp_initialization_write_request(main_trace, challenges, row, _debugger); - - control_block_req_value * fmp_write_req_value -} - -/// Builds requests made to kernel ROM chiplet when initializing a syscall block. -fn build_syscall_block_request( - main_trace: &MainTrace, - op_code_felt: Felt, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, -) -> E -where - E: ExtensionField, -{ - let control_block_req = ControlBlockRequestMessage { - transition_label: Felt::from_u8(LINEAR_HASH_LABEL + 16), - addr_next: main_trace.addr(row + 1), - op_code: op_code_felt, - decoder_hasher_state: main_trace.decoder_hasher_state(row), - }; - - let kernel_rom_req = KernelRomMessage { - kernel_proc_digest: main_trace.decoder_hasher_state(row)[0..4].try_into().unwrap(), - }; - - let combined_value = control_block_req.value(challenges) * kernel_rom_req.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - { - _debugger.add_request(alloc::boxed::Box::new(control_block_req), challenges); - _debugger.add_request(alloc::boxed::Box::new(kernel_rom_req), challenges); - } - - combined_value -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Returns the operation unique label. -#[inline(always)] -fn get_op_label(s0: Felt, s1: Felt, s2: Felt, s3: Felt) -> Felt { - s3 * Felt::from_u16(1 << 3) + s2 * Felt::from_u16(1 << 2) + s1 * Felt::from_u16(2) + s0 + ONE -} diff --git a/processor/src/trace/chiplets/aux_trace/hasher_perm.rs b/processor/src/trace/chiplets/aux_trace/hasher_perm.rs deleted file mode 100644 index 44f87de089..0000000000 --- a/processor/src/trace/chiplets/aux_trace/hasher_perm.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! Hasher perm-link bus builder. -//! -//! Builds the LogUp running-sum that links hasher controller rows (dispatch) to hasher -//! permutation segment rows (compute). Each controller input/output row adds +1/msg, -//! and each permutation cycle boundary subtracts -m/msg (where m = multiplicity from -//! memoization). -//! -//! The running sum is merged into the shared v_wiring auxiliary column. - -use alloc::vec::Vec; - -use miden_air::trace::{ - Challenges, MainTrace, RowIndex, bus_types::HASHER_PERM_LINK, chiplets::hasher::HASH_CYCLE_LEN, -}; -use miden_core::{Felt, field::ExtensionField}; - -/// Labels that distinguish input vs output perm-link messages within the `HASHER_PERM_LINK` bus. -const LABEL_IN: Felt = Felt::ZERO; -const LABEL_OUT: Felt = Felt::ONE; - -/// Builds the hasher perm-link running sum as a prefix array. -/// -/// The result is a vector of the same length as the main trace, where each entry -/// is the cumulative LogUp contribution from hasher perm-link messages up to that row. -pub fn build_perm_link_running_sum>( - main_trace: &MainTrace, - challenges: &Challenges, -) -> Vec { - let num_rows = main_trace.num_rows(); - let mut running_sum = vec![E::ZERO; num_rows]; - - // The hasher is always the first chiplet, so its trace starts at row 0. - // This invariant is required for the cycle_pos calculation below. - assert!( - main_trace.is_hash_row(RowIndex::from(0u32)), - "hasher chiplet must start at row 0" - ); - - // TODO: batch inversion - for row_idx in 0..(num_rows - 1) { - let row: RowIndex = (row_idx as u32).into(); - - if !main_trace.is_hash_row(row) { - running_sum[row_idx + 1] = running_sum[row_idx]; - continue; - } - - let s_perm = main_trace.chiplet_s_perm(row); - // Hasher-internal sub-selectors (only meaningful on controller rows): - // s0 = chiplets[1] (input flag), s1 = chiplets[2]. - let s0 = main_trace.chiplet_selector_1(row); - let s1 = main_trace.chiplet_selector_2(row); - - if s_perm == Felt::ZERO { - // Controller region - if s0 == Felt::ONE { - // Controller input row: +1/msg_in - let msg_in = encode_perm_link_message(main_trace, row, challenges, LABEL_IN); - running_sum[row_idx + 1] = running_sum[row_idx] + msg_in.inverse(); - } else if s0 == Felt::ZERO && s1 == Felt::ZERO { - // Controller output row (RETURN_HASH or RETURN_STATE with s0=0, s1=0): +1/msg_out - let msg_out = encode_perm_link_message(main_trace, row, challenges, LABEL_OUT); - running_sum[row_idx + 1] = running_sum[row_idx] + msg_out.inverse(); - } else { - running_sum[row_idx + 1] = running_sum[row_idx]; - } - } else { - // Permutation segment. - // This works because the hasher is always the first chiplet (rows start at 0) - // and the controller region is padded to a HASH_CYCLE_LEN boundary, so perm - // cycles are aligned to global row indices. - let cycle_pos = row_idx % HASH_CYCLE_LEN; - - if cycle_pos == 0 { - // Perm row 0: -m/msg_in - let m: E = main_trace.chiplet_node_index(row).into(); - let msg_in = encode_perm_link_message(main_trace, row, challenges, LABEL_IN); - running_sum[row_idx + 1] = running_sum[row_idx] - m * msg_in.inverse(); - } else if cycle_pos == HASH_CYCLE_LEN - 1 { - // Perm boundary row (row 15 in the packed 16-row cycle): -m/msg_out - let m: E = main_trace.chiplet_node_index(row).into(); - let msg_out = encode_perm_link_message(main_trace, row, challenges, LABEL_OUT); - running_sum[row_idx + 1] = running_sum[row_idx] - m * msg_out.inverse(); - } else { - running_sum[row_idx + 1] = running_sum[row_idx]; - } - } - } - - // The running sum should balance to zero (all requests matched by responses). - assert!( - running_sum[num_rows - 1] == E::ZERO, - "hasher perm-link running sum did not balance to zero: {:?}", - running_sum[num_rows - 1] - ); - - running_sum -} - -/// Encodes a perm-link message on the dedicated `HASHER_PERM_LINK` bus: `[label, h0, ..., h11]`. -fn encode_perm_link_message>( - main_trace: &MainTrace, - row: RowIndex, - challenges: &Challenges, - label: Felt, -) -> E { - let state = main_trace.chiplet_hasher_state(row); - challenges.encode( - HASHER_PERM_LINK, - [ - label, state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7], - state[8], state[9], state[10], state[11], - ], - ) -} diff --git a/processor/src/trace/chiplets/aux_trace/mod.rs b/processor/src/trace/chiplets/aux_trace/mod.rs deleted file mode 100644 index 6bc0421011..0000000000 --- a/processor/src/trace/chiplets/aux_trace/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -use alloc::vec::Vec; - -use miden_air::trace::{Challenges, MainTrace}; -use miden_core::field::ExtensionField; -use wiring_bus::WiringBusBuilder; - -use super::{Felt, ace::AceHints}; -use crate::trace::AuxColumnBuilder; - -mod bus; -pub use bus::{ - BusColumnBuilder, build_ace_memory_read_element_request, build_ace_memory_read_word_request, -}; - -mod virtual_table; -pub use virtual_table::ChipletsVTableColBuilder; - -mod hasher_perm; -mod wiring_bus; - -/// Constructs the execution trace for chiplets-related auxiliary columns (used in multiset checks). -#[derive(Debug, Clone)] -pub struct AuxTraceBuilder { - ace_hints: AceHints, -} - -impl AuxTraceBuilder { - // CONSTRUCTORS - // -------------------------------------------------------------------------------------------- - - pub fn new(ace_hints: AceHints) -> Self { - Self { ace_hints } - } - - // COLUMN TRACE CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - - /// Builds and returns the Chiplets's auxiliary trace columns. This consists of: - /// - /// 1. A bus column `b_chip` describing requests made by the stack and decoder and responses - /// received from the chiplets in the Chiplets module. It also responds to requests made by - /// the verifier with kernel procedure hashes included in the public inputs of the program. - /// 2. A column acting as - /// - a virtual table for the sibling table used by the hasher chiplet, - /// - a bus between the memory chiplet and the ACE chiplet. - /// 3. A column used as a bus to wire the gates of the ACE chiplet. It also carries the hasher - /// perm-link (linking hasher controller rows to hasher permutation segment). - pub(crate) fn build_aux_columns>( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - ) -> [Vec; 3] { - let v_table_col_builder = ChipletsVTableColBuilder; - let bus_col_builder = BusColumnBuilder; - let wiring_bus_builder = WiringBusBuilder::new(&self.ace_hints); - let t_chip = v_table_col_builder.build_aux_column(main_trace, challenges); - let b_chip = bus_col_builder.build_aux_column(main_trace, challenges); - let wiring_bus = wiring_bus_builder.build_aux_column(main_trace, challenges); - - // The wiring bus (v_wiring) carries three stacked LogUp contributions: - // 1. ACE wiring (node definitions and consumptions) - // 2. Memory range checks (3 fractions per memory row) - // 3. Hasher perm-link (linking controller rows to permutation segment) - // The final value is non-zero due to memory range check residual; - // the verifier checks b_range + v_wiring = 0 in reduced_aux_values. - - [t_chip, b_chip, wiring_bus] - } -} diff --git a/processor/src/trace/chiplets/aux_trace/virtual_table.rs b/processor/src/trace/chiplets/aux_trace/virtual_table.rs deleted file mode 100644 index b6a9d87767..0000000000 --- a/processor/src/trace/chiplets/aux_trace/virtual_table.rs +++ /dev/null @@ -1,255 +0,0 @@ -use miden_air::trace::{ - Challenges, LOG_PRECOMPILE_LABEL, MainTrace, RowIndex, - bus_types::{LOG_PRECOMPILE_TRANSCRIPT, SIBLING_TABLE}, - chiplets::hasher::DIGEST_LEN, - log_precompile::{HELPER_CAP_PREV_RANGE, STACK_CAP_NEXT_RANGE}, -}; -use miden_core::{ - Felt, field::ExtensionField, operations::opcodes, precompile::PrecompileTranscriptState, -}; - -use super::{build_ace_memory_read_element_request, build_ace_memory_read_word_request}; -use crate::{ - debug::{BusDebugger, BusMessage}, - trace::AuxColumnBuilder, -}; - -// CHIPLETS VIRTUAL TABLE -// ================================================================================================ - -/// Describes how to construct the execution trace of the chiplets virtual table auxiliary trace -/// column. This column enables communication between the different chiplets, in particular: -/// - Ensuring sharing of sibling nodes in a Merkle tree when one of its leaves is updated by the -/// hasher chiplet. -/// - Allowing memory access for the ACE chiplet. -/// -/// # Detail: -/// The hasher chiplet requires the bus to be empty whenever a Merkle tree update is requested. -/// This implies that the bus is also empty at the end of the trace containing the hasher rows. -/// On the other hand, communication between the ACE and memory chiplets requires the bus to be -/// contiguous, since messages are shared between these rows. -/// -/// Since the hasher chip is in the first position, the other chiplets can treat it as a shared bus. -/// However, this prevents any bus initialization via public inputs using boundary constraints -/// in the first row. If such constraints are required, they must be enforced via -/// `reduced_aux_values` in the last row of the trace. -/// -/// If public inputs are required for other chiplets, it is also possible to use the chiplet bus, -/// as is done for the kernel ROM chiplet. -pub struct ChipletsVTableColBuilder; - -impl AuxColumnBuilder for ChipletsVTableColBuilder -where - E: ExtensionField, -{ - fn get_requests_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, - ) -> E { - let op_code = main_trace.get_op_code(row).as_canonical_u64() as u8; - let log_pc_request = if op_code == opcodes::LOGPRECOMPILE { - build_log_precompile_capacity_remove(main_trace, row, challenges, _debugger) - } else { - E::ONE - }; - - let request_ace = if main_trace.chiplet_ace_is_read_row(row) { - build_ace_memory_read_word_request(main_trace, challenges, row, _debugger) - } else if main_trace.chiplet_ace_is_eval_row(row) { - build_ace_memory_read_element_request(main_trace, challenges, row, _debugger) - } else { - E::ONE - }; - - chiplets_vtable_remove_sibling(main_trace, challenges, row) * request_ace * log_pc_request - } - - fn get_responses_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, - ) -> E { - let op_code = main_trace.get_op_code(row).as_canonical_u64() as u8; - let log_pc_response = if op_code == opcodes::LOGPRECOMPILE { - build_log_precompile_capacity_insert(main_trace, row, challenges, _debugger) - } else { - E::ONE - }; - - chiplets_vtable_add_sibling(main_trace, challenges, row) * log_pc_response - } - - #[cfg(any(test, feature = "bus-debugger"))] - fn enforce_bus_balance(&self) -> bool { - // The chiplets vtable final value encodes transcript state boundary terms, - // which are checked via reduced_aux_values. It does not balance to identity. - false - } -} - -// VIRTUAL TABLE REQUESTS -// ================================================================================================ - -/// Range for RATE0 (first rate word) in sponge state. -const RATE0_RANGE: core::ops::Range = 0..DIGEST_LEN; -/// Range for RATE1 (second rate word) in sponge state. -const RATE1_RANGE: core::ops::Range = DIGEST_LEN..(2 * DIGEST_LEN); - -/// Node is left child (lsb=0), sibling is right child at RATE1. -/// Layout: [mrupdate_id, node_index, h[4], h[5], h[6], h[7]] -const SIBLING_RATE1_LAYOUT: [usize; 6] = [1, 2, 7, 8, 9, 10]; -/// Node is right child (lsb=1), sibling is left child at RATE0. -/// Layout: [mrupdate_id, node_index, h[0], h[1], h[2], h[3]] -const SIBLING_RATE0_LAYOUT: [usize; 6] = [1, 2, 3, 4, 5, 6]; - -/// Extracts the node index, mrupdate_id, and sibling word from a controller input row -/// and encodes a sibling table entry. -/// -/// In the controller/perm split, the sibling is always in the current row's state -/// (no need to look at the next row). -#[inline(always)] -fn encode_sibling_from_trace>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, -) -> E { - let index = main_trace.chiplet_node_index(row); - let mrupdate_id = main_trace.chiplet_mrupdate_id(row); - let lsb = index.as_canonical_u64() & 1; - let state = main_trace.chiplet_hasher_state(row); - let (layout, sibling) = if lsb == 0 { - // Node is left child, sibling is right child at RATE1 - (SIBLING_RATE1_LAYOUT, &state[RATE1_RANGE]) - } else { - // Node is right child, sibling is left child at RATE0 - (SIBLING_RATE0_LAYOUT, &state[RATE0_RANGE]) - }; - challenges.encode_sparse( - SIBLING_TABLE, - layout, - [mrupdate_id, index, sibling[0], sibling[1], sibling[2], sibling[3]], - ) -} - -/// Constructs the removals from the table for MU (new path) controller input rows. -/// -/// In the controller/perm split, all MU input rows participate (not just init rows). -fn chiplets_vtable_remove_sibling>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, -) -> E { - if main_trace.f_mu(row) { - encode_sibling_from_trace(main_trace, challenges, row) - } else { - E::ONE - } -} - -// VIRTUAL TABLE RESPONSES -// ================================================================================================ - -/// Constructs the inclusions to the table for MV (old path) controller input rows. -fn chiplets_vtable_add_sibling>( - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, -) -> E { - if main_trace.f_mv(row) { - encode_sibling_from_trace(main_trace, challenges, row) - } else { - E::ONE - } -} - -// LOG PRECOMPILE MESSAGES -// ================================================================================================ - -/// Message for log_precompile transcript-state tracking on the virtual table bus. -struct LogPrecompileMessage { - state: PrecompileTranscriptState, -} - -impl BusMessage for LogPrecompileMessage -where - E: ExtensionField, -{ - fn value(&self, challenges: &Challenges) -> E { - let state_elements: [Felt; 4] = self.state.into(); - challenges.encode( - LOG_PRECOMPILE_TRANSCRIPT, - [ - Felt::from_u8(LOG_PRECOMPILE_LABEL), - state_elements[0], - state_elements[1], - state_elements[2], - state_elements[3], - ], - ) - } - - fn source(&self) -> &str { - "log_precompile" - } -} - -impl core::fmt::Display for LogPrecompileMessage { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{{ state: {:?} }}", self.state) - } -} - -/// Removes the previous transcript state (`CAP_PREV`) from the virtual table bus. -/// -/// Helper register layout for `log_precompile` is codified as: -/// - `h0` = hasher address, `h1..h4` = `CAP_PREV[0..3]`. -fn build_log_precompile_capacity_remove>( - main_trace: &MainTrace, - row: RowIndex, - challenges: &Challenges, - _debugger: &mut BusDebugger, -) -> E { - let state = PrecompileTranscriptState::from([ - main_trace.helper_register(HELPER_CAP_PREV_RANGE.start, row), - main_trace.helper_register(HELPER_CAP_PREV_RANGE.start + 1, row), - main_trace.helper_register(HELPER_CAP_PREV_RANGE.start + 2, row), - main_trace.helper_register(HELPER_CAP_PREV_RANGE.start + 3, row), - ]); - - let message = LogPrecompileMessage { state }; - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_request(alloc::boxed::Box::new(message), challenges); - - value -} - -/// Inserts the next transcript state (`CAP_NEXT`) into the virtual table bus. -fn build_log_precompile_capacity_insert>( - main_trace: &MainTrace, - row: RowIndex, - challenges: &Challenges, - _debugger: &mut BusDebugger, -) -> E { - let state: PrecompileTranscriptState = [ - main_trace.stack_element(STACK_CAP_NEXT_RANGE.start, row + 1), - main_trace.stack_element(STACK_CAP_NEXT_RANGE.start + 1, row + 1), - main_trace.stack_element(STACK_CAP_NEXT_RANGE.start + 2, row + 1), - main_trace.stack_element(STACK_CAP_NEXT_RANGE.start + 3, row + 1), - ] - .into(); - - let message = LogPrecompileMessage { state }; - let value = message.value(challenges); - - #[cfg(any(test, feature = "bus-debugger"))] - _debugger.add_response(alloc::boxed::Box::new(message), challenges); - - value -} diff --git a/processor/src/trace/chiplets/aux_trace/wiring_bus.rs b/processor/src/trace/chiplets/aux_trace/wiring_bus.rs deleted file mode 100644 index 11493c3602..0000000000 --- a/processor/src/trace/chiplets/aux_trace/wiring_bus.rs +++ /dev/null @@ -1,112 +0,0 @@ -use alloc::vec::Vec; - -use miden_air::trace::{Challenges, MainTrace, RowIndex}; -use miden_core::{Felt, field::ExtensionField}; - -use super::{ - super::ace::{AceHints, NUM_ACE_LOGUP_FRACTIONS_EVAL, NUM_ACE_LOGUP_FRACTIONS_READ}, - hasher_perm, -}; - -/// Describes how to construct the execution trace of the wiring bus column (v_wiring). -/// This column carries three stacked LogUp contributions: -/// 1. ACE wiring (node definitions and consumptions) -/// 2. Memory range checks (w0, w1, 4*w1 16-bit lookups) -/// 3. Hasher perm-link (controller-to-permutation segment linking) -pub struct WiringBusBuilder<'a> { - ace_hints: &'a AceHints, -} -impl<'a> WiringBusBuilder<'a> { - pub(crate) fn new(ace_hints: &'a AceHints) -> Self { - Self { ace_hints } - } - - /// Builds the ACE chiplet wiring bus auxiliary trace column. - pub fn build_aux_column>( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - ) -> Vec { - let mut wiring_bus = vec![E::ZERO; main_trace.num_rows()]; - - // compute divisors - let total_divisors = self.ace_hints.build_divisors(main_trace, challenges); - - // fill only the portion relevant to ACE chiplet - let mut trace_offset = self.ace_hints.offset(); - let mut divisors_offset = 0; - for section in self.ace_hints.sections.iter() { - let divisors = &total_divisors[divisors_offset - ..divisors_offset + NUM_ACE_LOGUP_FRACTIONS_READ * section.num_vars() as usize]; - - // read section - for (i, divisor_tuple) in divisors.chunks(NUM_ACE_LOGUP_FRACTIONS_READ).enumerate() { - let trace_row = i + trace_offset; - - let m_0 = main_trace.chiplet_ace_m_0(trace_row.into()); - let m_1 = main_trace.chiplet_ace_m_1(trace_row.into()); - let value = divisor_tuple[0] * m_0 + divisor_tuple[1] * m_1; - - wiring_bus[trace_row + 1] = wiring_bus[trace_row] + value; - } - - trace_offset += section.num_vars() as usize; - divisors_offset += NUM_ACE_LOGUP_FRACTIONS_READ * section.num_vars() as usize; - - // eval section - let divisors = &total_divisors[divisors_offset - ..divisors_offset + NUM_ACE_LOGUP_FRACTIONS_EVAL * section.num_evals() as usize]; - for (i, divisor_tuple) in divisors.chunks(NUM_ACE_LOGUP_FRACTIONS_EVAL).enumerate() { - let trace_row = i + trace_offset; - - let m_0 = main_trace.chiplet_ace_m_0(trace_row.into()); - let value = divisor_tuple[0] * m_0 - (divisor_tuple[1] + divisor_tuple[2]); - - wiring_bus[trace_row + 1] = wiring_bus[trace_row] + value; - } - - trace_offset += section.num_evals() as usize; - divisors_offset += NUM_ACE_LOGUP_FRACTIONS_EVAL * section.num_evals() as usize; - } - - assert_eq!(wiring_bus[trace_offset], E::ZERO); - - // Build memory range check LogUp requests as a running sum, then merge into wiring_bus. - // For each memory row, subtract 1/(alpha_rc+w0) + 1/(alpha_rc+w1) + 1/(alpha_rc+4*w1). - // Must use bus_prefix[RANGE_CHECK_BUS] to match the range checker's encoding. - use miden_air::trace::bus_types::RANGE_CHECK_BUS; - let alpha = challenges.bus_prefix[RANGE_CHECK_BUS]; - let mut mem_prefix = vec![E::ZERO; main_trace.num_rows()]; - for row_idx in 0..(main_trace.num_rows() - 1) { - let row: RowIndex = (row_idx as u32).into(); - if !main_trace.is_memory_row(row) { - mem_prefix[row_idx + 1] = mem_prefix[row_idx]; - continue; - } - - let w0: E = main_trace.chiplet_memory_word_addr_lo(row).into(); - let w1: E = main_trace.chiplet_memory_word_addr_hi(row).into(); - let w1_mul4: E = - (main_trace.chiplet_memory_word_addr_hi(row) * Felt::from_u8(4)).into(); - - let den0 = alpha + w0; - let den1 = alpha + w1; - let den2 = alpha + w1_mul4; - - let delta = -(den0.inverse() + den1.inverse() + den2.inverse()); - mem_prefix[row_idx + 1] = mem_prefix[row_idx] + delta; - } - - for (dst, mem) in wiring_bus.iter_mut().zip(mem_prefix.iter()) { - *dst += *mem; - } - - // Build hasher perm-link LogUp running sum and merge into wiring_bus. - let perm_prefix = hasher_perm::build_perm_link_running_sum(main_trace, challenges); - for (dst, perm) in wiring_bus.iter_mut().zip(perm_prefix.iter()) { - *dst += *perm; - } - - wiring_bus - } -} diff --git a/processor/src/trace/chiplets/kernel_rom/mod.rs b/processor/src/trace/chiplets/kernel_rom/mod.rs index 7a9e30bebc..184ca8ece9 100644 --- a/processor/src/trace/chiplets/kernel_rom/mod.rs +++ b/processor/src/trace/chiplets/kernel_rom/mod.rs @@ -1,6 +1,6 @@ use alloc::collections::BTreeMap; -use miden_air::trace::{RowIndex, chiplets::kernel_rom::TRACE_WIDTH}; +use miden_air::trace::{RowIndex, chiplets::KERNEL_ROM_TRACE_WIDTH}; use miden_core::field::PrimeCharacteristicRing; use super::{Felt, Kernel, TraceFragment, Word as Digest}; @@ -19,27 +19,21 @@ type ProcHashBytes = [u8; 32]; /// Kernel ROM chiplet for the VM. /// -/// This component is responsible for validating that kernel calls requested by the executing -/// program are made against procedures which are contained within the specified kernel. It also -/// tacks all calls to kernel procedures and this info is used to construct an execution trace of -/// all kernel accesses. +/// Validates that every SYSCALL targets a procedure declared in the kernel, and produces +/// exactly one trace row per declared procedure carrying the row's CALL-side multiplicity. /// /// # Execution trace -/// The layout of the execution trace of kernel procedure accesses is shown below: /// -/// s_first h0 h1 h2 h3 -/// ├─────────┴────┴────┴────┴────┤ +/// m h0 h1 h2 h3 +/// ├────┴────┴────┴────┴────┤ /// -/// In the above, the meaning of columns is as follows: -/// - `s_first` indicates that this is the first occurrence of a new block of kernel procedure -/// hashes. It also acts as a flag within the block indicating whether the hash should be sent to -/// the virtual table or the bus. -/// - `h0` - `h3` columns contain roots of procedures in a given kernel. +/// - `m` is the number of SYSCALLs to this procedure (0 if declared but never called). It gates the +/// chiplet-side CALL add; the INIT add is always emitted with multiplicity 1. +/// - `h0..h3` is the procedure root digest. #[derive(Debug)] pub struct KernelRom { access_map: BTreeMap, kernel: Kernel, - trace_len: usize, } impl KernelRom { @@ -50,21 +44,23 @@ impl KernelRom { /// The kernel ROM is populated with all procedures from the provided kernel. For each /// procedure the access count is set to 0. pub fn new(kernel: Kernel) -> Self { - let trace_len = kernel.proc_hashes().len(); let mut access_map = BTreeMap::new(); for &proc_hash in kernel.proc_hashes() { access_map.insert(proc_hash.into(), ProcAccessInfo::new(proc_hash)); } - Self { access_map, kernel, trace_len } + Self { access_map, kernel } } // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- /// Returns length of execution trace required to describe kernel ROM. - pub const fn trace_len(&self) -> usize { - self.trace_len + /// + /// Under the all-LogUp layout this is exactly the number of declared kernel procedures + /// (one row per proc, regardless of access count). + pub fn trace_len(&self) -> usize { + self.access_map.len() } // STATE MUTATORS @@ -81,7 +77,6 @@ impl KernelRom { .get_mut(&proc_hash_bytes) .ok_or(OperationError::SyscallTargetNotInKernel { proc_root })?; - self.trace_len += 1; access_info.num_accesses += 1; Ok(()) } @@ -90,22 +85,24 @@ impl KernelRom { // -------------------------------------------------------------------------------------------- /// Populates the provided execution trace fragment with execution trace of this kernel ROM. + /// + /// Emits one row per declared kernel procedure: column 0 is the CALL-label multiplicity + /// (= number of SYSCALLs to this proc), columns 1..5 are the procedure digest. pub fn fill_trace(self, trace: &mut TraceFragment) { - debug_assert_eq!(TRACE_WIDTH, trace.width(), "inconsistent trace fragment width"); + debug_assert_eq!( + KERNEL_ROM_TRACE_WIDTH, + trace.width(), + "inconsistent trace fragment width" + ); let mut row = RowIndex::from(0); for access_info in self.access_map.values() { - // Always write an entry for this procedure hash responding to the requests in the - // requests in the virtual table. The verifier makes those requests by initializing - // the bus with the set of procedure hashes included in the public inputs. - access_info.write_into_trace(trace, row, true); + let multiplicity = Felt::from_u64(access_info.num_accesses as u64); + trace.set(row, 0, multiplicity); + trace.set(row, 1, access_info.proc_hash[0]); + trace.set(row, 2, access_info.proc_hash[1]); + trace.set(row, 3, access_info.proc_hash[2]); + trace.set(row, 4, access_info.proc_hash[3]); row += 1_u32; - - // For every access made by the decoder/trace, include an entry in the chiplet bus - // responding to those requests. - for _ in 0..access_info.num_accesses { - access_info.write_into_trace(trace, row, false); - row += 1_u32; - } } } @@ -133,14 +130,4 @@ impl ProcAccessInfo { pub fn new(proc_hash: Digest) -> Self { Self { proc_hash, num_accesses: 0 } } - - /// Writes a single row into the provided trace fragment for this procedure access entry. - pub fn write_into_trace(&self, trace: &mut TraceFragment, row: RowIndex, is_first: bool) { - let s_first = Felt::from_bool(is_first); - trace.set(row, 0, s_first); - trace.set(row, 1, self.proc_hash[0]); - trace.set(row, 2, self.proc_hash[1]); - trace.set(row, 3, self.proc_hash[2]); - trace.set(row, 4, self.proc_hash[3]); - } } diff --git a/processor/src/trace/chiplets/kernel_rom/tests.rs b/processor/src/trace/chiplets/kernel_rom/tests.rs index 8bae80c235..00a77c8d9e 100644 --- a/processor/src/trace/chiplets/kernel_rom/tests.rs +++ b/processor/src/trace/chiplets/kernel_rom/tests.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use miden_core::{WORD_SIZE, field::PrimeCharacteristicRing}; -use super::{Felt, Kernel, KernelRom, TRACE_WIDTH, TraceFragment}; +use super::{Felt, KERNEL_ROM_TRACE_WIDTH, Kernel, KernelRom, TraceFragment}; use crate::{ONE, ZERO}; // CONSTANTS @@ -28,108 +28,57 @@ fn kernel_rom_invalid_access() { #[test] fn kernel_rom_no_access() { + // Each declared procedure gets one row with multiplicity 0 when never called; the INIT + // side of the chiplets bus still matches the public-input-injected remove. let kernel = build_kernel(); let rom = KernelRom::new(kernel); let expected_trace_len = 2; assert_eq!(expected_trace_len, rom.trace_len()); - // generate trace let trace = build_trace(rom, expected_trace_len); - // the first row of the trace should correspond to the first procedure - let row = 0; - - assert_eq!(trace[0][row], ONE); // s0 - assert_eq!(trace[1][row], PROC1_HASH[0]); - assert_eq!(trace[2][row], PROC1_HASH[1]); - assert_eq!(trace[3][row], PROC1_HASH[2]); - assert_eq!(trace[4][row], PROC1_HASH[3]); - - // the second row of the trace should correspond to the second procedure - let row = 1; - - assert_eq!(trace[0][row], ONE); // s0 - assert_eq!(trace[1][row], PROC2_HASH[0]); - assert_eq!(trace[2][row], PROC2_HASH[1]); - assert_eq!(trace[3][row], PROC2_HASH[2]); - assert_eq!(trace[4][row], PROC2_HASH[3]); + assert_row(&trace, 0, ZERO, PROC1_HASH); + assert_row(&trace, 1, ZERO, PROC2_HASH); } #[test] fn kernel_rom_with_access() { + // 5 accesses: 3 for proc1, 2 for proc2 -> multiplicities (3, 2). let kernel = build_kernel(); let mut rom = KernelRom::new(kernel); - // generate 5 access: 3 for proc1 and 2 for proc2 rom.access_proc(PROC1_HASH.into()).unwrap(); rom.access_proc(PROC2_HASH.into()).unwrap(); rom.access_proc(PROC1_HASH.into()).unwrap(); rom.access_proc(PROC1_HASH.into()).unwrap(); rom.access_proc(PROC2_HASH.into()).unwrap(); - let expected_trace_len = 7; + let expected_trace_len = 2; assert_eq!(expected_trace_len, rom.trace_len()); - // generate trace let trace = build_trace(rom, expected_trace_len); - // the first 5 rows of the trace should correspond to the first procedure - for row in 0..4 { - let s_first = row == 0; - - assert_eq!(trace[0][row], Felt::from_bool(s_first)); // s_first - assert_eq!(trace[1][row], PROC1_HASH[0]); - assert_eq!(trace[2][row], PROC1_HASH[1]); - assert_eq!(trace[3][row], PROC1_HASH[2]); - assert_eq!(trace[4][row], PROC1_HASH[3]); - } - - // the remaining 2 rows of the trace should correspond to the second procedure - for row in 4..7 { - let s_first = row == 4; - - assert_eq!(trace[0][row], Felt::from_bool(s_first)); // s_first - assert_eq!(trace[1][row], PROC2_HASH[0]); - assert_eq!(trace[2][row], PROC2_HASH[1]); - assert_eq!(trace[3][row], PROC2_HASH[2]); - assert_eq!(trace[4][row], PROC2_HASH[3]); - } + assert_row(&trace, 0, Felt::from_u64(3), PROC1_HASH); + assert_row(&trace, 1, Felt::from_u64(2), PROC2_HASH); } #[test] fn kernel_rom_with_single_access() { + // Mixed: proc1 accessed twice, proc2 never -> multiplicities (2, 0). let kernel = build_kernel(); let mut rom = KernelRom::new(kernel); - // generate 2 access for proc1 rom.access_proc(PROC1_HASH.into()).unwrap(); rom.access_proc(PROC1_HASH.into()).unwrap(); - let expected_trace_len = 4; + let expected_trace_len = 2; assert_eq!(expected_trace_len, rom.trace_len()); - // generate trace let trace = build_trace(rom, expected_trace_len); - // the first 3 rows of the trace should correspond to the first procedure - for row in 0..3 { - let s_first = row == 0; - - assert_eq!(trace[0][row], Felt::from_bool(s_first)); // s_first - assert_eq!(trace[1][row], PROC1_HASH[0]); - assert_eq!(trace[2][row], PROC1_HASH[1]); - assert_eq!(trace[3][row], PROC1_HASH[2]); - assert_eq!(trace[4][row], PROC1_HASH[3]); - } - - // the last row of the trace should correspond to the second procedure - let row = 3; - assert_eq!(trace[0][row], Felt::from_bool(true)); // s_first - assert_eq!(trace[1][row], PROC2_HASH[0]); - assert_eq!(trace[2][row], PROC2_HASH[1]); - assert_eq!(trace[3][row], PROC2_HASH[2]); - assert_eq!(trace[4][row], PROC2_HASH[3]); + assert_row(&trace, 0, Felt::from_u64(2), PROC1_HASH); + assert_row(&trace, 1, ZERO, PROC2_HASH); } // HELPER FUNCTIONS @@ -143,9 +92,18 @@ fn build_kernel() -> Kernel { /// Builds a trace of the specified length and fills it with data from the provided KernelRom /// instance. fn build_trace(kernel_rom: KernelRom, num_rows: usize) -> Vec> { - let mut trace = (0..TRACE_WIDTH).map(|_| vec![ZERO; num_rows]).collect::>(); + let mut trace = (0..KERNEL_ROM_TRACE_WIDTH).map(|_| vec![ZERO; num_rows]).collect::>(); let mut fragment = TraceFragment::trace_to_fragment(&mut trace); kernel_rom.fill_trace(&mut fragment); trace } + +/// Asserts that row `row` carries the given multiplicity and procedure digest. +fn assert_row(trace: &[Vec], row: usize, multiplicity: Felt, digest: [Felt; WORD_SIZE]) { + assert_eq!(trace[0][row], multiplicity, "multiplicity mismatch at row {row}"); + assert_eq!(trace[1][row], digest[0], "digest[0] mismatch at row {row}"); + assert_eq!(trace[2][row], digest[1], "digest[1] mismatch at row {row}"); + assert_eq!(trace[3][row], digest[2], "digest[2] mismatch at row {row}"); + assert_eq!(trace[4][row], digest[3], "digest[3] mismatch at row {row}"); +} diff --git a/processor/src/trace/chiplets/mod.rs b/processor/src/trace/chiplets/mod.rs index f854ef94c5..33218cb788 100644 --- a/processor/src/trace/chiplets/mod.rs +++ b/processor/src/trace/chiplets/mod.rs @@ -19,16 +19,11 @@ mod memory; use memory::Memory; mod ace; -use ace::AceHints; pub use ace::{Ace, CircuitEvaluation, MAX_NUM_ACE_WIRES, PTR_OFFSET_ELEM, PTR_OFFSET_WORD}; mod kernel_rom; use kernel_rom::KernelRom; -mod aux_trace; - -pub use aux_trace::AuxTraceBuilder; - #[cfg(test)] #[allow(clippy::needless_range_loop)] mod tests; @@ -38,7 +33,6 @@ mod tests; pub struct ChipletsTrace { pub(crate) trace: Vec, - pub(crate) aux_builder: AuxTraceBuilder, } // CHIPLETS MODULE OF HASHER, BITWISE, MEMORY, ACE, AND KERNEL ROM CHIPLETS @@ -214,17 +208,14 @@ impl Chiplets { .collect::>() .try_into() .expect("failed to convert vector to array"); - let ace_hint = self.fill_trace(&mut trace); + self.fill_trace(&mut trace); let mut row_flat = Vec::with_capacity(CHIPLETS_WIDTH * trace_len); for row_idx in 0..trace_len { row_flat.extend(trace.iter().map(|column| column[row_idx])); } - ChipletsTrace { - trace: row_flat, - aux_builder: AuxTraceBuilder::new(ace_hint), - } + ChipletsTrace { trace: row_flat } } // HELPER METHODS @@ -234,7 +225,7 @@ impl Chiplets { /// Hasher, Bitwise, Memory, ACE, and kernel ROM chiplets along with selector columns /// to identify each individual chiplet trace in addition to padding to fill the rest of /// the trace. - fn fill_trace(self, trace: &mut [Vec; CHIPLETS_WIDTH]) -> AceHints { + fn fill_trace(self, trace: &mut [Vec; CHIPLETS_WIDTH]) { // s_ctrl (trace[0]) is 1 on the hasher's controller rows and 0 elsewhere. // The controller region is the padded prefix of the hasher region; `region_lengths` // returns the same padded length that `finalize_trace` will materialize later. @@ -325,8 +316,7 @@ impl Chiplets { } } - // Fill independent chiplets in parallel: hasher, bitwise, memory, kernel_rom. - // ACE must be processed separately since it returns a value. + // Fill all chiplets in parallel. rayon::scope(|s| { s.spawn(move |_| { hasher.fill_trace(&mut hasher_fragment); @@ -340,10 +330,10 @@ impl Chiplets { s.spawn(move |_| { kernel_rom.fill_trace(&mut kernel_rom_fragment); }); + s.spawn(move |_| { + ace.fill_trace(&mut ace_fragment); + }); }); - - let ace_sections = ace.fill_trace(&mut ace_fragment); - AceHints::new(ace_start, ace_sections) } } } diff --git a/processor/src/trace/chiplets/tests.rs b/processor/src/trace/chiplets/tests.rs index 5c91a7ed5c..78cb131c69 100644 --- a/processor/src/trace/chiplets/tests.rs +++ b/processor/src/trace/chiplets/tests.rs @@ -3,10 +3,11 @@ use alloc::vec::Vec; use miden_air::trace::{ CHIPLETS_RANGE, CHIPLETS_WIDTH, chiplets::{ - NUM_BITWISE_SELECTORS, NUM_KERNEL_ROM_SELECTORS, NUM_MEMORY_SELECTORS, + KERNEL_ROM_TRACE_WIDTH, NUM_BITWISE_SELECTORS, NUM_KERNEL_ROM_SELECTORS, + NUM_MEMORY_SELECTORS, bitwise::{self, BITWISE_XOR, OP_CYCLE_LEN}, hasher::{CONTROLLER_ROWS_PER_PERMUTATION, HASH_CYCLE_LEN, LINEAR_HASH, S_PERM_COL_IDX}, - kernel_rom, memory, + memory, }, }; use miden_core::{ @@ -375,8 +376,8 @@ fn validate_memory_trace(trace: &ChipletsTrace, start: usize, end: usize) { /// - Columns beyond kernel ROM trace width + selectors are zero fn validate_kernel_rom_trace(trace: &ChipletsTrace, start: usize, end: usize) { // Kernel ROM uses NUM_KERNEL_ROM_SELECTORS (5) chiplet selector columns + - // kernel_rom::TRACE_WIDTH (5) internal columns = 10 columns total. - let kernel_rom_used_cols = NUM_KERNEL_ROM_SELECTORS + kernel_rom::TRACE_WIDTH; + // KERNEL_ROM_TRACE_WIDTH (5) internal columns = 10 columns total. + let kernel_rom_used_cols = NUM_KERNEL_ROM_SELECTORS + KERNEL_ROM_TRACE_WIDTH; for row in start..end { // Chiplet selectors: s_ctrl=0, s1=1, s2=1, s3=1, s4=0 diff --git a/processor/src/trace/decoder/aux_trace/block_hash_table.rs b/processor/src/trace/decoder/aux_trace/block_hash_table.rs deleted file mode 100644 index 88fc15782c..0000000000 --- a/processor/src/trace/decoder/aux_trace/block_hash_table.rs +++ /dev/null @@ -1,271 +0,0 @@ -use miden_air::trace::{Challenges, RowIndex, bus_types::BLOCK_HASH_TABLE}; -use miden_core::{Word, ZERO, field::ExtensionField, operations::opcodes}; - -use super::{AuxColumnBuilder, Felt, MainTrace, ONE}; -use crate::debug::BusDebugger; - -// BLOCK HASH TABLE COLUMN BUILDER -// ================================================================================================ - -/// Builds the execution trace of the decoder's `p2` column which describes the state of the block -/// hash table via multiset checks. -/// -/// At any point in time, the block hash table contains the hashes of the blocks whose parents have -/// been visited, and that remain to be executed. For example, when we encounter the beginning of a -/// JOIN block, we add both children to the table, since both will be executed at some point in the -/// future. However, when we encounter the beginning of a SPLIT block, we only push the left or the -/// right child, depending on the current value on the stack (since only one child gets executed in -/// a SPLIT block). When we encounter an `END` operation, we remove the block from the table that -/// corresponds to the block that just ended. The root block's hash is checked via -/// `reduced_aux_values`, since it doesn't have a parent and would never be added to the table -/// otherwise. -#[derive(Default)] -pub struct BlockHashTableColumnBuilder {} - -impl> AuxColumnBuilder for BlockHashTableColumnBuilder { - /// Removes a row from the block hash table. - fn get_requests_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, - ) -> E { - let op_code = main_trace.get_op_code(row).as_canonical_u64() as u8; - - match op_code { - opcodes::END => BlockHashTableRow::from_end(main_trace, row).collapse(challenges), - _ => E::ONE, - } - } - - /// Adds a row to the block hash table. - fn get_responses_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - _debugger: &mut BusDebugger, - ) -> E { - let op_code = main_trace.get_op_code(row).as_canonical_u64() as u8; - - match op_code { - opcodes::JOIN => { - let left_child_row = BlockHashTableRow::from_join(main_trace, row, true); - let right_child_row = BlockHashTableRow::from_join(main_trace, row, false); - - left_child_row.collapse(challenges) * right_child_row.collapse(challenges) - }, - opcodes::SPLIT => BlockHashTableRow::from_split(main_trace, row).collapse(challenges), - opcodes::LOOP => BlockHashTableRow::from_loop(main_trace, row) - .map(|row| row.collapse(challenges)) - .unwrap_or(E::ONE), - opcodes::REPEAT => BlockHashTableRow::from_repeat(main_trace, row).collapse(challenges), - opcodes::DYN | opcodes::DYNCALL | opcodes::CALL | opcodes::SYSCALL => { - BlockHashTableRow::from_dyn_dyncall_call_syscall(main_trace, row) - .collapse(challenges) - }, - _ => E::ONE, - } - } - - #[cfg(any(test, feature = "bus-debugger"))] - fn enforce_bus_balance(&self) -> bool { - // The block hash table's final value encodes the program hash boundary term, - // which is checked via reduced_aux_values. It does not balance to identity. - false - } -} - -// BLOCK HASH TABLE ROW -// ================================================================================================ - -/// Describes a single entry in the block hash table. An entry in the block hash table is a tuple -/// (parent_id, block_hash, is_first_child, is_loop_body), where each column is defined as follows: -/// - parent_block_id: contains the ID of the current block. Note: the current block's ID is the -/// parent block's ID from the perspective of the block being added to the table. -/// - block_hash: these 4 columns hold the hash of the current block's child which will be executed -/// at some point in time in the future. -/// - is_first_child: set to true if the table row being added represents the first child of the -/// current block. If the current block has only one child, set to false. -/// - is_loop_body: Set to true when the current block block is a LOOP code block (and hence, the -/// current block's child being added to the table is the body of a loop). -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BlockHashTableRow { - parent_block_id: Felt, - child_block_hash: Word, - is_first_child: bool, - is_loop_body: bool, -} - -impl BlockHashTableRow { - // CONSTRUCTORS - // ---------------------------------------------------------------------------------------------- - - /// Computes the row to be removed from the block hash table when encountering an `END` - /// operation. - pub fn from_end(main_trace: &MainTrace, row: RowIndex) -> Self { - let op_code_next = main_trace.get_op_code(row + 1).as_canonical_u64() as u8; - let parent_block_id = main_trace.addr(row + 1); - let child_block_hash = main_trace.decoder_hasher_state_first_half(row); - - // A block can only be a first child of a JOIN block; every other control block only - // executes one child. Hence, it is easier to identify the conditions that only a - // "second child" (i.e. a JOIN block's second child, as well as all other control - // block's child) can find itself in. That is, when the opcode of the next row is: - // - END: this marks the end of the parent block, which only a second child can be in - // - REPEAT: this means that the current block is the child of a LOOP block, and hence a - // "second child" - // - HALT: The end of the program, which a first child can't find itself in (since the - // second child needs to execute first) - let is_first_child = op_code_next != opcodes::END - && op_code_next != opcodes::REPEAT - && op_code_next != opcodes::HALT; - - let is_loop_body = match main_trace.is_loop_body_flag(row).as_canonical_u64() { - 0 => false, - 1 => true, - other => panic!("expected loop body flag to be 0 or 1, got {other}"), - }; - - Self { - parent_block_id, - child_block_hash, - is_first_child, - is_loop_body, - } - } - - /// Computes the row corresponding to the left or right child to add to the block hash table - /// when encountering a `JOIN` operation. - pub fn from_join(main_trace: &MainTrace, row: RowIndex, is_first_child: bool) -> Self { - let child_block_hash = if is_first_child { - main_trace.decoder_hasher_state_first_half(row) - } else { - main_trace.decoder_hasher_state_second_half(row) - }; - - Self { - parent_block_id: main_trace.addr(row + 1), - child_block_hash, - is_first_child, - is_loop_body: false, - } - } - - /// Computes the row to add to the block hash table when encountering a `SPLIT` operation. - pub fn from_split(main_trace: &MainTrace, row: RowIndex) -> Self { - let stack_top = main_trace.stack_element(0, row); - let parent_block_id = main_trace.addr(row + 1); - // Note: only one child of a split block is executed. Hence, `is_first_child` is always - // false. - let is_first_child = false; - let is_loop_body = false; - - if stack_top == ONE { - let left_child_block_hash = main_trace.decoder_hasher_state_first_half(row); - Self { - parent_block_id, - child_block_hash: left_child_block_hash, - is_first_child, - is_loop_body, - } - } else { - let right_child_block_hash = main_trace.decoder_hasher_state_second_half(row); - - Self { - parent_block_id, - child_block_hash: right_child_block_hash, - is_first_child, - is_loop_body, - } - } - } - - /// Computes the row (optionally) to add to the block hash table when encountering a `LOOP` - /// operation. That is, a loop will have a child to execute when the top of the stack is 1. - pub fn from_loop(main_trace: &MainTrace, row: RowIndex) -> Option { - let stack_top = main_trace.stack_element(0, row); - - if stack_top == ONE { - Some(Self { - parent_block_id: main_trace.addr(row + 1), - child_block_hash: main_trace.decoder_hasher_state_first_half(row), - is_first_child: false, - is_loop_body: true, - }) - } else { - None - } - } - - /// Computes the row to add to the block hash table when encountering a `REPEAT` operation. A - /// `REPEAT` marks the start of a new loop iteration, and hence the loop's child block needs to - /// be added to the block hash table once again (since it was removed in the previous `END` - /// instruction). - pub fn from_repeat(main_trace: &MainTrace, row: RowIndex) -> Self { - Self { - parent_block_id: main_trace.addr(row + 1), - child_block_hash: main_trace.decoder_hasher_state_first_half(row), - is_first_child: false, - is_loop_body: true, - } - } - - /// Computes the row to add to the block hash table when encountering a `DYN`, `DYNCALL`, `CALL` - /// or `SYSCALL` operation. - /// - /// The hash of the child node being called is expected to be in the first half of the decoder - /// hasher state. - pub fn from_dyn_dyncall_call_syscall(main_trace: &MainTrace, row: RowIndex) -> Self { - Self { - parent_block_id: main_trace.addr(row + 1), - child_block_hash: main_trace.decoder_hasher_state_first_half(row), - is_first_child: false, - is_loop_body: false, - } - } - - // COLLAPSE - // ---------------------------------------------------------------------------------------------- - - /// Collapses this row to a single field element in the field specified by E by taking a random - /// linear combination of all the columns. This requires 8 alpha values, which are assumed to - /// have been drawn randomly. - pub fn collapse>(&self, challenges: &Challenges) -> E { - let is_first_child = if self.is_first_child { ONE } else { ZERO }; - let is_loop_body = if self.is_loop_body { ONE } else { ZERO }; - challenges.encode( - BLOCK_HASH_TABLE, - [ - self.parent_block_id, - self.child_block_hash[0], - self.child_block_hash[1], - self.child_block_hash[2], - self.child_block_hash[3], - is_first_child, - is_loop_body, - ], - ) - } - - // TEST - // ---------------------------------------------------------------------------------------------- - - /// Returns a new [BlockHashTableRow] instantiated with the specified parameters. This is - /// used for test purpose only. - #[cfg(test)] - pub fn new_test( - parent_id: Felt, - block_hash: Word, - is_first_child: bool, - is_loop_body: bool, - ) -> Self { - Self { - parent_block_id: parent_id, - child_block_hash: block_hash, - is_first_child, - is_loop_body, - } - } -} diff --git a/processor/src/trace/decoder/aux_trace/block_stack_table.rs b/processor/src/trace/decoder/aux_trace/block_stack_table.rs deleted file mode 100644 index 38984142b0..0000000000 --- a/processor/src/trace/decoder/aux_trace/block_stack_table.rs +++ /dev/null @@ -1,183 +0,0 @@ -use miden_air::trace::{Challenges, RowIndex, bus_types::BLOCK_STACK_TABLE}; -use miden_core::{field::ExtensionField, operations::opcodes}; - -use super::{AuxColumnBuilder, Felt, MainTrace, ONE, ZERO}; -use crate::debug::BusDebugger; - -// BLOCK STACK TABLE COLUMN BUILDER -// ================================================================================================ - -/// Builds the execution trace of the decoder's `p1` column which describes the state of the block -/// stack table via multiset checks. -#[derive(Default)] -pub struct BlockStackColumnBuilder {} - -impl> AuxColumnBuilder for BlockStackColumnBuilder { - /// Removes a row from the block stack table. - fn get_requests_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - i: RowIndex, - _debugger: &mut BusDebugger, - ) -> E { - let op_code_felt = main_trace.get_op_code(i); - let op_code = op_code_felt.as_canonical_u64() as u8; - - match op_code { - opcodes::RESPAN => get_block_stack_table_respan_multiplicand(main_trace, i, challenges), - opcodes::END => get_block_stack_table_end_multiplicand(main_trace, i, challenges), - _ => E::ONE, - } - } - - /// Adds a row to the block stack table. - fn get_responses_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - i: RowIndex, - _debugger: &mut BusDebugger, - ) -> E { - let op_code_felt = main_trace.get_op_code(i); - let op_code = op_code_felt.as_canonical_u64() as u8; - - match op_code { - opcodes::JOIN - | opcodes::SPLIT - | opcodes::SPAN - | opcodes::DYN - | opcodes::DYNCALL - | opcodes::LOOP - | opcodes::RESPAN - | opcodes::CALL - | opcodes::SYSCALL => { - get_block_stack_table_inclusion_multiplicand(main_trace, i, challenges, op_code) - }, - _ => E::ONE, - } - } - - #[cfg(any(test, feature = "bus-debugger"))] - fn enforce_bus_balance(&self) -> bool { - true - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Computes the multiplicand representing the removal of a row from the block stack table when -/// encountering a RESPAN operation. -fn get_block_stack_table_respan_multiplicand>( - main_trace: &MainTrace, - i: RowIndex, - challenges: &Challenges, -) -> E { - let block_id = main_trace.addr(i); - let parent_id = main_trace.decoder_hasher_state_element(1, i + 1); - let is_loop = ZERO; - - challenges.encode(BLOCK_STACK_TABLE, [block_id, parent_id, is_loop]) -} - -/// Computes the multiplicand representing the removal of a row from the block stack table when -/// encountering an END operation. -fn get_block_stack_table_end_multiplicand>( - main_trace: &MainTrace, - i: RowIndex, - challenges: &Challenges, -) -> E { - let block_id = main_trace.addr(i); - let parent_id = main_trace.addr(i + 1); - let is_loop = main_trace.is_loop_flag(i); - - if main_trace.is_call_flag(i) == ONE || main_trace.is_syscall_flag(i) == ONE { - let parent_ctx = main_trace.ctx(i + 1); - let parent_stack_depth = main_trace.stack_depth(i + 1); - let parent_next_overflow_addr = main_trace.parent_overflow_address(i + 1); - let parent_fn_hash = main_trace.fn_hash(i + 1); - - challenges.encode( - BLOCK_STACK_TABLE, - [ - block_id, - parent_id, - is_loop, - parent_ctx, - parent_stack_depth, - parent_next_overflow_addr, - parent_fn_hash[0], - parent_fn_hash[1], - parent_fn_hash[2], - parent_fn_hash[3], - ], - ) - } else { - challenges.encode(BLOCK_STACK_TABLE, [block_id, parent_id, is_loop]) - } -} - -/// Computes the multiplicand representing the inclusion of a new row to the block stack table. -fn get_block_stack_table_inclusion_multiplicand>( - main_trace: &MainTrace, - i: RowIndex, - challenges: &Challenges, - op_code: u8, -) -> E { - let block_id = main_trace.addr(i + 1); - let parent_id = if op_code == opcodes::RESPAN { - main_trace.decoder_hasher_state_element(1, i + 1) - } else { - main_trace.addr(i) - }; - let is_loop = if op_code == opcodes::LOOP { - main_trace.stack_element(0, i) - } else { - ZERO - }; - - if op_code == opcodes::CALL || op_code == opcodes::SYSCALL { - let parent_ctx = main_trace.ctx(i); - let parent_stack_depth = main_trace.stack_depth(i); - let parent_next_overflow_addr = main_trace.parent_overflow_address(i); - let parent_fn_hash = main_trace.fn_hash(i); - challenges.encode( - BLOCK_STACK_TABLE, - [ - block_id, - parent_id, - is_loop, - parent_ctx, - parent_stack_depth, - parent_next_overflow_addr, - parent_fn_hash[0], - parent_fn_hash[1], - parent_fn_hash[2], - parent_fn_hash[3], - ], - ) - } else if op_code == opcodes::DYNCALL { - let parent_ctx = main_trace.ctx(i); - let parent_stack_depth = main_trace.decoder_hasher_state_element(4, i); - let parent_next_overflow_addr = main_trace.decoder_hasher_state_element(5, i); - let parent_fn_hash = main_trace.fn_hash(i); - challenges.encode( - BLOCK_STACK_TABLE, - [ - block_id, - parent_id, - is_loop, - parent_ctx, - parent_stack_depth, - parent_next_overflow_addr, - parent_fn_hash[0], - parent_fn_hash[1], - parent_fn_hash[2], - parent_fn_hash[3], - ], - ) - } else { - challenges.encode(BLOCK_STACK_TABLE, [block_id, parent_id, is_loop]) - } -} diff --git a/processor/src/trace/decoder/aux_trace/mod.rs b/processor/src/trace/decoder/aux_trace/mod.rs deleted file mode 100644 index b7c9fd36fe..0000000000 --- a/processor/src/trace/decoder/aux_trace/mod.rs +++ /dev/null @@ -1,59 +0,0 @@ -use alloc::vec::Vec; - -use miden_air::trace::{Challenges, MainTrace}; -use miden_core::field::ExtensionField; - -use crate::{Felt, ONE, ZERO, trace::AuxColumnBuilder}; - -mod block_hash_table; -use block_hash_table::BlockHashTableColumnBuilder; -#[cfg(test)] -pub use block_hash_table::BlockHashTableRow; - -mod block_stack_table; -use block_stack_table::BlockStackColumnBuilder; - -mod op_group_table; -use op_group_table::OpGroupTableColumnBuilder; - -// AUXILIARY TRACE BUILDER -// ================================================================================================ - -/// Constructs the execution traces of stack-related auxiliary trace segment columns -/// (used in multiset checks). -#[derive(Debug, Default, Clone, Copy)] -pub struct AuxTraceBuilder {} - -impl AuxTraceBuilder { - /// Builds and returns decoder auxiliary trace columns p1, p2, and p3 describing states of block - /// stack, block hash, and op group tables respectively. - pub fn build_aux_columns>( - self, - main_trace: &MainTrace, - challenges: &Challenges, - ) -> Vec> { - let block_stack_column_builder = BlockStackColumnBuilder::default(); - let block_hash_column_builder = BlockHashTableColumnBuilder::default(); - let op_group_table_column_builder = OpGroupTableColumnBuilder::default(); - - let p1 = block_stack_column_builder.build_aux_column(main_trace, challenges); - let p2 = block_hash_column_builder.build_aux_column(main_trace, challenges); - let p3 = op_group_table_column_builder.build_aux_column(main_trace, challenges); - - debug_assert_eq!( - *p1.last().unwrap(), - E::ONE, - "block stack table is not empty at the end of program execution" - ); - // p2 (block hash table) does NOT end at 1: the program hash boundary response - // is unmatched, so p2 ends at 1/program_hash_msg. This is verified by the - // verifier in reduced_aux_values. - debug_assert_eq!( - *p3.last().unwrap(), - E::ONE, - "op group table is not empty at the end of program execution" - ); - - vec![p1, p2, p3] - } -} diff --git a/processor/src/trace/decoder/aux_trace/op_group_table.rs b/processor/src/trace/decoder/aux_trace/op_group_table.rs deleted file mode 100644 index 5520e1dad4..0000000000 --- a/processor/src/trace/decoder/aux_trace/op_group_table.rs +++ /dev/null @@ -1,117 +0,0 @@ -use miden_air::trace::{ - Challenges, RowIndex, - bus_types::OP_GROUP_TABLE, - decoder::{OP_BATCH_2_GROUPS, OP_BATCH_4_GROUPS, OP_BATCH_8_GROUPS}, -}; -use miden_core::{field::ExtensionField, operations::opcodes}; - -use super::{AuxColumnBuilder, Felt, MainTrace, ONE}; -use crate::debug::BusDebugger; - -// OP GROUP TABLE COLUMN -// ================================================================================================ - -/// Builds the execution trace of the decoder's `p3` column which describes the state of the op -/// group table via multiset checks. -#[derive(Default)] -pub struct OpGroupTableColumnBuilder {} - -impl> AuxColumnBuilder for OpGroupTableColumnBuilder { - /// Removes a row from the block hash table. - fn get_requests_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - i: RowIndex, - _debugger: &mut BusDebugger, - ) -> E { - let delete_group_flag = main_trace.delta_group_count(i) * main_trace.is_in_span(i); - - if delete_group_flag == ONE { - get_op_group_table_removal_multiplicand(main_trace, i, challenges) - } else { - E::ONE - } - } - - /// Adds a row to the block hash table. - fn get_responses_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - i: RowIndex, - _debugger: &mut BusDebugger, - ) -> E { - let op_code_felt = main_trace.get_op_code(i); - let op_code = op_code_felt.as_canonical_u64() as u8; - - match op_code { - opcodes::SPAN | opcodes::RESPAN => { - get_op_group_table_inclusion_multiplicand(main_trace, i, challenges) - }, - _ => E::ONE, - } - } - - #[cfg(any(test, feature = "bus-debugger"))] - fn enforce_bus_balance(&self) -> bool { - true - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Computes the multiplicand representing the inclusion of a new row to the op group table. -fn get_op_group_table_inclusion_multiplicand>( - main_trace: &MainTrace, - i: RowIndex, - challenges: &Challenges, -) -> E { - let block_id = main_trace.addr(i + 1); - let group_count = main_trace.group_count(i); - let op_batch_flag = main_trace.op_batch_flag(i); - - if op_batch_flag == OP_BATCH_8_GROUPS { - let h = main_trace.decoder_hasher_state(i); - (1..8_u8).fold(E::ONE, |acc, k| { - acc * challenges - .encode(OP_GROUP_TABLE, [block_id, group_count - Felt::from_u8(k), h[k as usize]]) - }) - } else if op_batch_flag == OP_BATCH_4_GROUPS { - let h = main_trace.decoder_hasher_state_first_half(i); - (1..4_u8).fold(E::ONE, |acc, k| { - acc * challenges - .encode(OP_GROUP_TABLE, [block_id, group_count - Felt::from_u8(k), h[k as usize]]) - }) - } else if op_batch_flag == OP_BATCH_2_GROUPS { - let h = main_trace.decoder_hasher_state_first_half(i); - challenges.encode(OP_GROUP_TABLE, [block_id, group_count - ONE, h[1]]) - } else { - E::ONE - } -} - -/// Computes the multiplicand representing the removal of a row from the op group table. -fn get_op_group_table_removal_multiplicand>( - main_trace: &MainTrace, - i: RowIndex, - challenges: &Challenges, -) -> E { - let group_count = main_trace.group_count(i); - let block_id = main_trace.addr(i); - let group_value = { - let op_code = main_trace.get_op_code(i); - - if op_code == Felt::from_u8(opcodes::PUSH) { - main_trace.stack_element(0, i + 1) - } else { - let h0 = main_trace.decoder_hasher_state_first_half(i + 1)[0]; - - let op_prime = main_trace.get_op_code(i + 1); - h0 * Felt::from_u16(1 << 7) + op_prime - } - }; - - challenges.encode(OP_GROUP_TABLE, [block_id, group_count, group_value]) -} diff --git a/processor/src/trace/decoder/mod.rs b/processor/src/trace/decoder/mod.rs deleted file mode 100644 index 0983f60a65..0000000000 --- a/processor/src/trace/decoder/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[cfg(test)] -use miden_core::{Felt, mast::OP_GROUP_SIZE, operations::Operation}; - -mod aux_trace; -pub use aux_trace::AuxTraceBuilder; -#[cfg(test)] -pub use aux_trace::BlockHashTableRow; - -pub mod block_stack; - -// TEST HELPERS -// ================================================================================================ - -/// Build an operation group from the specified list of operations. -#[cfg(test)] -pub fn build_op_group(ops: &[Operation]) -> Felt { - let mut group = 0u64; - let mut i = 0; - for op in ops.iter() { - group |= (op.op_code() as u64) << (Operation::OP_BITS * i); - i += 1; - } - assert!(i <= OP_GROUP_SIZE, "too many ops"); - Felt::new_unchecked(group) -} diff --git a/processor/src/trace/execution_tracer.rs b/processor/src/trace/execution_tracer.rs index 330f6ba9b9..1c6d4d60fc 100644 --- a/processor/src/trace/execution_tracer.rs +++ b/processor/src/trace/execution_tracer.rs @@ -6,7 +6,7 @@ use miden_air::trace::chiplets::hasher::{ use miden_core::{FMP_ADDR, FMP_INIT_VALUE, operations::Operation}; use super::{ - decoder::block_stack::{BlockInfo, BlockStack, ExecutionContextInfo}, + block_stack::{BlockInfo, BlockStack, ExecutionContextInfo}, stack::OverflowTable, trace_state::{ AceReplay, AdviceReplay, BitwiseReplay, BlockAddressReplay, BlockStackReplay, diff --git a/processor/src/trace/mod.rs b/processor/src/trace/mod.rs index 5b8bbcd8d8..8dd884b249 100644 --- a/processor/src/trace/mod.rs +++ b/processor/src/trace/mod.rs @@ -3,7 +3,7 @@ use alloc::vec::Vec; use core::ops::Range; use miden_air::{ - AirWitness, AuxBuilder, ProcessorAir, PublicInputs, debug, + AirWitness, ProcessorAir, PublicInputs, debug, trace::{ DECODER_TRACE_OFFSET, MainTrace, PADDED_TRACE_WIDTH, TRACE_WIDTH, decoder::{NUM_USER_OP_HELPERS, USER_OP_HELPERS_OFFSET}, @@ -14,19 +14,18 @@ use miden_core::{crypto::hash::Blake3_256, serde::Serializable}; use crate::{ Felt, MIN_STACK_DEPTH, Program, ProgramInfo, StackInputs, StackOutputs, Word, ZERO, fast::ExecutionOutput, - field::{ExtensionField, QuadFelt}, + field::QuadFelt, precompile::{PrecompileRequest, PrecompileTranscript, PrecompileTranscriptDigest}, - utils::{ColMatrix, Matrix, RowMajorMatrix}, + utils::RowMajorMatrix, }; pub(crate) mod utils; -use miden_air::trace::Challenges; -use utils::{AuxColumnBuilder, TraceFragment}; +use utils::TraceFragment; pub mod chiplets; pub(crate) mod execution_tracer; -mod decoder; +mod block_stack; mod parallel; mod range; mod stack; @@ -171,7 +170,6 @@ impl TraceBuildInputs { /// /// The trace consists of the following components: /// - Main traces of System, Decoder, Operand Stack, Range Checker, and Chiplets. -/// - Auxiliary trace builders. /// - Information about the program (program hash and the kernel). /// - Information about execution outputs (stack state, deferred precompile requests, and the final /// precompile transcript). @@ -179,7 +177,6 @@ impl TraceBuildInputs { #[derive(Debug)] pub struct ExecutionTrace { main_trace: MainTrace, - aux_trace_builders: AuxTraceBuilders, program_info: ProgramInfo, stack_outputs: StackOutputs, precompile_requests: Vec, @@ -195,7 +192,6 @@ impl ExecutionTrace { program_info: ProgramInfo, trace_output: TraceBuildOutput, main_trace: MainTrace, - aux_trace_builders: AuxTraceBuilders, trace_len_summary: TraceLenSummary, ) -> Self { let TraceBuildOutput { @@ -207,7 +203,6 @@ impl ExecutionTrace { Self { main_trace, - aux_trace_builders, program_info, stack_outputs, precompile_requests, @@ -249,11 +244,6 @@ impl ExecutionTrace { self.public_inputs().to_elements() } - /// Returns a clone of the auxiliary trace builders. - pub fn aux_trace_builders(&self) -> AuxTraceBuilders { - self.aux_trace_builders.clone() - } - /// Returns a reference to the main trace. pub fn main_trace(&self) -> &MainTrace { &self.main_trace @@ -349,8 +339,6 @@ impl ExecutionTrace { let (public_values, kernel_felts) = public_inputs.to_air_inputs(); let var_len_public_inputs: &[&[Felt]] = &[&kernel_felts]; - let aux_builder = self.aux_trace_builders(); - // Derive deterministic challenges by hashing public values with Poseidon2. // The 4-element digest maps directly to 2 QuadFelt challenges. let digest = crate::crypto::hash::Poseidon2::hash_elements(&public_values); @@ -358,7 +346,7 @@ impl ExecutionTrace { [QuadFelt::new([digest[0], digest[1]]), QuadFelt::new([digest[2], digest[3]])]; let witness = AirWitness::new(&trace_matrix, &public_values, var_len_public_inputs); - debug::check_constraints(&ProcessorAir, witness, &aux_builder, &challenges); + debug::check_constraints(&ProcessorAir, witness, &ProcessorAir, &challenges); } /// Returns the main trace as a row-major matrix for proving. @@ -405,135 +393,4 @@ impl ExecutionTrace { pub fn get_column_range(&self, range: Range) -> Vec> { self.main_trace.get_column_range(range) } - - pub fn build_aux_trace(&self, rand_elements: &[E]) -> Option> - where - E: ExtensionField, - { - let aux_columns = - self.aux_trace_builders.build_aux_columns(&self.main_trace, rand_elements); - - Some(ColMatrix::new(aux_columns)) - } -} - -// AUX TRACE BUILDERS -// ================================================================================================ - -#[derive(Debug, Clone)] -pub struct AuxTraceBuilders { - pub(crate) decoder: decoder::AuxTraceBuilder, - pub(crate) stack: stack::AuxTraceBuilder, - pub(crate) range: range::AuxTraceBuilder, - pub(crate) chiplets: chiplets::AuxTraceBuilder, -} - -impl AuxTraceBuilders { - /// Builds auxiliary columns for all trace segments given the main trace and challenges. - /// - /// This is the internal column-major version used by the processor. - pub fn build_aux_columns(&self, main_trace: &MainTrace, challenges: &[E]) -> Vec> - where - E: ExtensionField, - { - // Expand raw challenges (alpha, beta) into coefficient array once, then pass - // the expanded challenges to all sub-builders. - let challenges = Challenges::::new(challenges[0], challenges[1]); - - let (decoder_cols, stack_cols, range_cols, chiplets_cols) = { - let ((decoder_cols, stack_cols), (range_cols, chiplets_cols)) = rayon::join( - || { - rayon::join( - || self.decoder.build_aux_columns(main_trace, &challenges), - || self.stack.build_aux_columns(main_trace, &challenges), - ) - }, - || { - rayon::join( - || self.range.build_aux_columns(main_trace, &challenges), - || { - let [a, b, c] = - self.chiplets.build_aux_columns(main_trace, &challenges); - vec![a, b, c] - }, - ) - }, - ); - (decoder_cols, stack_cols, range_cols, chiplets_cols) - }; - - decoder_cols - .into_iter() - .chain(stack_cols) - .chain(range_cols) - .chain(chiplets_cols) - .collect() - } -} - -// PLONKY3 AUX TRACE BUILDER -// ================================================================================================ - -impl> AuxBuilder for AuxTraceBuilders { - fn build_aux_trace( - &self, - main: &RowMajorMatrix, - challenges: &[EF], - ) -> (RowMajorMatrix, Vec) { - let _span = tracing::info_span!("build_aux_trace").entered(); - - // Transpose the row-major main trace into column-major `MainTrace` needed by the - // auxiliary trace builders. The last program row is the point where the clock - // (column 0) stops incrementing. - let main_for_aux = { - let num_rows = main.height(); - // Find the last program row by binary search on the clock column. - let clk0 = main.get(0, 0).expect("valid indices"); - let last_program_row = if num_rows <= 1 { - 0 - } else if main.get(num_rows - 1, 0).expect("valid indices") - == clk0 + Felt::new_unchecked((num_rows - 1) as u64) - { - num_rows - 1 - } else { - let mut lo = 1usize; - let mut hi = num_rows - 1; - while lo < hi { - let mid = lo + (hi - lo) / 2; - let expected = clk0 + Felt::new_unchecked(mid as u64); - if main.get(mid, 0).expect("valid indices") == expected { - lo = mid + 1; - } else { - hi = mid; - } - } - lo - 1 - }; - let transposed = main.transpose(); - MainTrace::from_transposed(transposed, RowIndex::from(last_program_row)) - }; - - let aux_columns = self.build_aux_columns(&main_for_aux, challenges); - assert!(!aux_columns.is_empty(), "aux columns should not be empty"); - - let trace_len = main.height(); - let num_ef_cols = aux_columns.len(); - for col in &aux_columns { - debug_assert_eq!(col.len(), trace_len, "aux column length must match main height"); - } - - let mut flat = Vec::with_capacity(trace_len * num_ef_cols); - for col in &aux_columns { - flat.extend_from_slice(col); - } - let aux_trace = RowMajorMatrix::new(flat, trace_len).transpose(); - - // Extract the last row from the row-major aux trace for Fiat-Shamir. - let last_row = aux_trace - .row_slice(trace_len - 1) - .expect("aux trace has at least one row") - .to_vec(); - - (aux_trace, last_row) - } } diff --git a/processor/src/trace/parallel/mod.rs b/processor/src/trace/parallel/mod.rs index b9784db170..11fc907731 100644 --- a/processor/src/trace/parallel/mod.rs +++ b/processor/src/trace/parallel/mod.rs @@ -28,7 +28,7 @@ use crate::{ continuation_stack::ContinuationStack, errors::MapExecErrNoCtx, trace::{ - AuxTraceBuilders, ChipletsLengths, ExecutionTrace, TraceBuildInputs, TraceLenSummary, + ChipletsLengths, ExecutionTrace, TraceBuildInputs, TraceLenSummary, parallel::{processor::ReplayProcessor, tracer::CoreTraceGenerationTracer}, range::RangeChecker, utils::RowMajorTraceWriter, @@ -50,9 +50,7 @@ mod tracer; use super::{ chiplets::Chiplets, - decoder::AuxTraceBuilder as DecoderAuxTraceBuilder, execution_tracer::TraceGenerationContext, - stack::AuxTraceBuilder as StackAuxTraceBuilder, trace_state::{ AceReplay, BitwiseOp, BitwiseReplay, CoreTraceFragmentContext, CoreTraceState, ExecutionReplay, HasherOp, HasherRequestReplay, KernelReplay, MemoryWritesReplay, @@ -193,19 +191,10 @@ pub fn build_trace_with_max_len( ) }; - // Create aux trace builders - let aux_trace_builders = AuxTraceBuilders { - decoder: DecoderAuxTraceBuilder::default(), - range: range_checker_trace.aux_builder, - chiplets: chiplets_trace.aux_builder, - stack: StackAuxTraceBuilder, - }; - Ok(ExecutionTrace::new_from_parts( program_info, trace_output, main_trace, - aux_trace_builders, trace_len_summary, )) } diff --git a/processor/src/trace/parallel/snapshots/miden_processor__trace__parallel__tests__trace__parallel__tests__test_trace_generation_at_fragment_boundaries__case_13.snap b/processor/src/trace/parallel/snapshots/miden_processor__trace__parallel__tests__trace__parallel__tests__test_trace_generation_at_fragment_boundaries__case_13.snap index 15f4cfacb1..11ed9c2d2c 100644 --- a/processor/src/trace/parallel/snapshots/miden_processor__trace__parallel__tests__trace__parallel__tests__test_trace_generation_at_fragment_boundaries__case_13.snap +++ b/processor/src/trace/parallel/snapshots/miden_processor__trace__parallel__tests__trace__parallel__tests__test_trace_generation_at_fragment_boundaries__case_13.snap @@ -2,4 +2,4 @@ source: processor/src/trace/parallel/tests.rs expression: DeterministicTrace(&trace_from_fragments) --- -ExecutionTrace { main_trace: MainTrace { storage: Parts { core_rm: [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 0, 0, 0, 0, 0, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 2, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 5, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 6, 0, 0, 0, 0, 0, 5, 0, 1, 1, 0, 1, 0, 1, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 7, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 8, 5, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 8, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 9, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 1, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 10, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 1, 1, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 11, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 59, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 62, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 74, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 75, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 79, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 83, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 84, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 85, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 89, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 93, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 97, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 103, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 105, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 109, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 115, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 121, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 122, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 124, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 126, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0], chiplets_rm: [1, 1, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 87, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 18295472245208082500, 5861891236755868697, 15427470125368014171, 5623455816162216176, 11335643519415282023, 11551270937114235273, 11863835819310119404, 14266241634618616211, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 0, 0, 1, 0, 0, 1, 1, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 104, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 3302920651539558582, 2392789792202575031, 11898680931385242334, 3118702462093730513, 6675709269000188192, 6602703777719207165, 6265915402349571564, 2890093557727619615, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 15913981378790993016, 12701179462806902902, 9645342555817022514, 6889850077248808033, 678249829196625442, 8726676798253716750, 14136118088409135949, 1027279235140821190, 8048729929177993405, 2542525775606472849, 3283471062508934848, 18058280687405074483, 2, 0, 0, 0, 1, 0, 0, 0, 0, 11899090779839289087, 1701090513954162998, 6953639183117323837, 11460660778637092812, 8345945186928076892, 11308243255833047975, 5734666540714547293, 1546649737555419076, 14643837757854608147, 7477230882792828789, 2359592855656894189, 12732146754685591216, 2, 0, 0, 0, 1, 0, 0, 0, 0, 16283412161296764998, 17261112905912533997, 1498213616014501531, 7707889409925905689, 16090493533591377617, 525010687572874692, 5228788497345464933, 13051466527897639054, 3560924095387897187, 12551242480725370445, 4038828260160651477, 6132317990703151787, 2, 0, 0, 0, 1, 0, 14261694278902826987, 16097544111075686229, 16632169854204459773, 14265791174051766329, 14732671264513481238, 15642599276989292777, 7593738854072541043, 11440582168151819564, 18258483622390056257, 6851601084855956341, 10463349537167637967, 10032876585525342603, 825669147467031185, 16271217766495541082, 9021089524754424392, 2, 0, 0, 0, 1, 0, 16780109665356203593, 16025948514351396737, 4256182047405196535, 4919013996388780384, 6974015548720770018, 10189882052640604940, 12288632186238773648, 7055558833039165669, 9820743264545515107, 7353106531603230851, 5612808106478211181, 362487531847990854, 10259224130335842891, 2374318293393776907, 16208179153232019183, 2, 0, 0, 0, 1, 0, 9496969110446094198, 10424961628901078909, 7587726188775237019, 13423955604028434301, 12288543613568774022, 4068762405627465026, 7288480650179820504, 1035430088689333582, 2651098721515326755, 4524440782000877024, 3013038852551457145, 12536534023666437466, 6560028330562582937, 9090521726813489969, 17634755409309861791, 2, 0, 0, 0, 1, 0, 15761194215082968049, 15796684185970405493, 1294179585337070936, 5701860167148115542, 17707247215386403887, 18419960639673587986, 14570347312565937582, 8634142387233894354, 11938075017385927203, 8060480374868324599, 16699922976999064541, 14234489074154948378, 17210389744085299852, 17331527780947314617, 16356838976302290254, 2, 0, 0, 0, 1, 0, 6184336662619539433, 7106147576739595100, 18237994300618329820, 9768155666925548219, 226714884885574576, 11169479382209254075, 5756676929838789145, 29803495366482069, 13128905441940840949, 18304516631519674746, 3177693875357946498, 10624850715031233718, 16956738608020324589, 10558540052020438814, 7826502712354673600, 2, 0, 0, 0, 1, 0, 1980308537464408089, 6810023246846442283, 9400257804338494201, 11246932855318952856, 7504545710540897051, 5823677062954428984, 9151299935574592196, 15905322272715446259, 18174748553199601666, 9877077233183640995, 2431998410719122545, 3541292002384422941, 5416588529930333172, 15788517205879313917, 2490575598934209003, 2, 0, 0, 0, 1, 0, 17950140321155419078, 9014768943295123367, 10707122777228105884, 10701204914974985643, 15060806908759160804, 7011191650405807117, 13598159658926423040, 3947314552577424057, 12985682730462769834, 18264715466581442035, 7667189540353817157, 7685483800933651249, 16085167604591833455, 16105855167414075111, 16215395602017884846, 2, 0, 0, 0, 1, 0, 8676227747932036802, 0, 0, 11504729673400082663, 3659553513673892306, 10374352082945691939, 15819529278889203623, 18107940902385670152, 8889798095480337831, 12737345777792032790, 456604824069734238, 2859376131275373721, 8758251935142908528, 2364536875264708997, 2435873391009301907, 2, 0, 0, 0, 1, 0, 0, 0, 0, 16797630026773069829, 16954579754978210547, 4393671449525183699, 6245909065255485436, 8311309349844601638, 3305827896314343556, 1615477551160071258, 3897903986825909618, 13015230208471866351, 13839070994009764281, 2415598337053791919, 5526300544248852335, 2, 0, 0, 0, 1, 0, 0, 0, 0, 12827873667465929582, 15892701250663610383, 549489834728826775, 9425334953627649153, 15402634395731091512, 11814416271399500934, 14597718118926923662, 4726679541432826422, 342915941842538663, 11221492717904369823, 17188959961041335276, 13559932473868727168, 2, 0, 0, 0, 1, 0, 0, 0, 0, 8689096602925893779, 4096531352684607500, 8424481237826564505, 15238165026175259240, 10985176737527635898, 2672540837788784952, 6649439643634522762, 7581835251244605715, 17671970641549389116, 18069913154458602790, 749417941228438762, 6150863900366154379, 2, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 2, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 104, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8045445736281483033, 11250386210932698181, 8447328201766628152, 527413860513422548, 2685530738566346168, 6831208533457089257, 18437518609042158966, 9278656209339918204, 4985219875437159245, 11102677601257469434, 16507477945842790035, 1824439754798293960, 1, 0, 0, 0, 1, 0, 0, 0, 0, 13956548861570575417, 9763011176116608502, 4674794667694672124, 15480945315513057131, 1720127490715987209, 8831144276914012491, 250532683106813113, 18120264276823504303, 7075953940225467814, 5209117550659913714, 16837262421580091230, 11776861544437613654, 1, 0, 0, 0, 1, 0, 0, 0, 0, 11512288827914447463, 17480311708360681553, 1580648391454313983, 3096307188042936924, 1465975368055000312, 1362735000009130378, 9465263839861379226, 14989718516974665241, 6566304943303044120, 2566111340353544025, 11325114382049196546, 15312957863351292707, 1, 0, 0, 0, 1, 0, 14277010279095994320, 16421135900115366688, 8672081456162485113, 3315051758368515759, 16767795201975506841, 15745773069630130456, 10428682341827527559, 1609230777320982945, 8538451532688083939, 3309529438878958210, 2752224770668803053, 4030118788308868253, 9432517950422109187, 16655292287441213846, 3022134227219891882, 1, 0, 0, 0, 1, 0, 12527609013750224641, 4064424364292504784, 12759359572746242099, 17498386834554682438, 9899399543530128857, 13823203783008011183, 4789373231313821943, 2275698290172824909, 14631083886626574849, 6083693282888760833, 17878128073269252636, 9098606451540172932, 4868784239831205121, 2796426330015583824, 10989039359288278884, 1, 0, 0, 0, 1, 0, 11227322858093773958, 1256671723465513282, 4658091495748016755, 14665531030768273966, 5299487590341247604, 3822486110676485203, 2504794437525571246, 16499336243305873535, 10440692312118465582, 8016916182618053860, 11635644660770389253, 4003332628141212645, 8214607993079575395, 7207010966340578848, 10940185796204185824, 1, 0, 0, 0, 1, 0, 12668058855470446370, 5255019430616758101, 14462744774902056824, 4113277495358115867, 1662113282866907292, 6585130578479449998, 17149676088405916342, 10457761600032190139, 3327323833077679847, 8197592365883421679, 7425424839480263678, 13938948244809494455, 16228892676090661713, 1504971017217144480, 8182776272628891435, 1, 0, 0, 0, 1, 0, 6914860967029724068, 10806403396787496173, 3848579298848325229, 13805102062857951563, 16229844626955596217, 13935582109846251306, 8572389593169145834, 17576620671980411587, 10572618363726686694, 4850773447466262928, 15638373093776703034, 7910188832488865399, 15531097057290072206, 4889338479662679107, 10949924992689136363, 1, 0, 0, 0, 1, 0, 4330865582496855032, 8473297999555512458, 7242240654409420037, 10299128364456298062, 14941042471610818695, 7565852759213474271, 3702504147651721524, 207598518842942194, 6753564273898291164, 11919043583188527726, 2096230895461827816, 9814026405907874850, 17772509140662880385, 15771358593260243554, 6654163437055992392, 1, 0, 0, 0, 1, 0, 14424762448455122149, 9431772018868236879, 584341294112809445, 4918781353216964245, 2337856752823889422, 16060522829040602978, 1539091624536270714, 3206058370812795090, 10870942617842401040, 8606083879706978586, 4344150548445731150, 380060561426657383, 2730766720879432427, 14498706802584030890, 7769509436274319865, 1, 0, 0, 0, 1, 0, 4147902545389306045, 0, 0, 16093847529947137599, 5589254378675598674, 14560311910293184412, 2592280228933147027, 10554718058272358421, 1080024418599256897, 646685994271806030, 5548578222066968465, 13466005348915411989, 1280314003050779809, 11336903746061502338, 9170512654829817344, 1, 0, 0, 0, 1, 0, 0, 0, 0, 4018352536943676596, 1718970205214835807, 16103902027304323769, 7153843611717905198, 7564037379049998274, 6261812460083581507, 17412746410051240602, 11497751499239916990, 9826997558611816650, 2818155236871917143, 1303564367886020260, 8520201627654591972, 1, 0, 0, 0, 1, 0, 0, 0, 0, 7399548230295413142, 15817399793431362137, 11111223990289672490, 14277270972042777124, 9762507146447503800, 5775448381623517643, 9705617515238056177, 10499143968321217911, 13713380413483444727, 2803285551461817904, 3117235683594903662, 8848529162601814389, 1, 0, 0, 0, 1, 0, 0, 0, 0, 41605777382338142, 13705243190620087435, 8813156438606599548, 3668196738148877897, 8870883218370515482, 1311045307375281156, 891925000095841393, 18305520102948210690, 3095238878624340898, 8925726914230878692, 11158112996086314015, 16463229027226752316, 1, 0, 0, 0, 1, 0, 0, 0, 0, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 3302920651539558582, 2392789792202575031, 11898680931385242334, 3118702462093730513, 6675709269000188192, 6602703777719207165, 6265915402349571564, 2890093557727619615, 1, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 87, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 3856634821061399044, 17111039960895529319, 4566799539362193550, 16844085186824886612, 3355565787613486613, 17176810829821093448, 1352137470786356802, 17998789203926124900, 16786513982140394745, 18361321143200586840, 13282415620666686464, 8228901733828007087, 1, 0, 0, 0, 1, 0, 0, 0, 0, 7778838127124972351, 10159506934852280087, 12722539778584946047, 2132149890053205639, 18102156639692354752, 8649394742436479221, 4276590013765363445, 12997989410109585404, 9529430557140348691, 7606828505864121076, 17598459768503654580, 7777709650181082785, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8194007358744550661, 306611979519524457, 10528705374691960700, 319508112022086105, 10482736583082303606, 10309893049243492470, 12917037695721108312, 6248886070590142047, 6486447845183860200, 17251231915071761348, 16404214944354933870, 13204576128129345952, 1, 0, 0, 0, 1, 0, 8699431654038775343, 37936723307283666, 8146588049311051327, 4530941775236136548, 16738435301601374688, 11359785745622903572, 15791382447019722900, 13379247305879358010, 13936169968399350974, 3508629571016445401, 5950565638198879243, 3988632325321614173, 14773776822077474877, 3784342517219646416, 16429780078047820450, 1, 0, 0, 0, 1, 0, 3424975013671552621, 14815277144979047633, 8209627855274846837, 8295342049297215958, 15269992241592835077, 6438591265885629379, 13730717627697535913, 7787232003492715877, 9621206971434531853, 11223875126467095457, 6436756480486800604, 5377264485189635000, 5482319143373695788, 12350937431324290559, 6389531569788570962, 1, 0, 0, 0, 1, 0, 4990332689225356878, 13089030850077380376, 2471524608045276896, 17139358263655017093, 18165060065290469256, 9446121688110358951, 977359788342611310, 16707128933418187061, 16621781274459601592, 7539458736435553900, 13565999334907596759, 1167963651906558177, 801268771425181312, 6228026786881094671, 1048304545096961231, 1, 0, 0, 0, 1, 0, 9535941747246118180, 6758632961600419030, 13740260214376027515, 1405920164415583051, 14920069431652436384, 17439962485710135044, 417887867370226026, 16088228384974311599, 3710261815002798848, 137273432282796342, 5851059442771612226, 15136621875351529272, 15702932139541150313, 8779333934582463234, 9538098266673672038, 1, 0, 0, 0, 1, 0, 8185802697182936648, 17682692702261395783, 2219736463483295590, 10757593865239447517, 1468754948270208383, 13746393160610271679, 6189648269704333551, 6527095477330083457, 289679669419955019, 9046656083049426920, 3573808134852581123, 16538711805104705524, 17122129810962376462, 2926585958971102942, 10107422984112301769, 1, 0, 0, 0, 1, 0, 12705492336505225259, 715424026399539381, 2549298692279483128, 1219968445867773701, 12911638424059178129, 5048199682718381540, 1535097268869899419, 17260972721034176516, 15413356179839910737, 13476986835056197648, 7470895530577539713, 8398414302064166980, 16518486341402545799, 70008875869363926, 10307933594111103714, 1, 0, 0, 0, 1, 0, 450024681039430413, 17110346123676092707, 503623504778112596, 1520822994009218024, 3997469258016952350, 18365130032920199069, 7964473389350786015, 9071675461601628413, 4719657860691726363, 2082610983791917873, 13643709933077212296, 16696777778525571691, 5937099920759461106, 17798517164851075633, 7519473231185213307, 1, 0, 0, 0, 1, 0, 17931731304193244959, 0, 0, 11156145431451224746, 9448001553779471302, 11663814486624427477, 8105139713546092506, 996052311483623897, 18356245393792640289, 11128269591471854109, 3650549423833220560, 10102060878284432657, 6999065210900455588, 9632395034475899386, 10920626017129534091, 1, 0, 0, 0, 1, 0, 0, 0, 0, 17063798001278894185, 2357038714604006367, 10530018908095235062, 8857952782846291740, 17150424826448967994, 8501298665346388356, 15857100073185553560, 2606143849991065668, 1709380969765926542, 7242804148613591485, 10415598312293556776, 3315485625242432418, 1, 0, 0, 0, 1, 0, 0, 0, 0, 6980956723582544769, 17736580895885199098, 10473837116748539797, 5495612785159148096, 8023377746099375924, 15245498074053387423, 15841461147440760970, 14724882291190243897, 13636145514567347966, 11793730383025693858, 9279036588745121360, 3956126734745245173, 1, 0, 0, 0, 1, 0, 0, 0, 0, 14149948942544104911, 13474931984659215558, 7610362789910399421, 10137886774563078569, 110488320743199774, 6631727638469492070, 9820426758070632911, 11785623090416681374, 2883529595690350478, 8282658300559339579, 1247473541961741200, 12265308465311135280, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 18295472245208082500, 5861891236755868697, 15427470125368014171, 5623455816162216176, 11335643519415282023, 11551270937114235273, 11863835819310119404, 14266241634618616211, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], range_checker_cols: [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2187, 4374, 6561, 8748, 10935, 13122, 15309, 17496, 19683, 21870, 24057, 26244, 28431, 30618, 32805, 34992, 37179, 39366, 41553, 43740, 45927, 48114, 50301, 52488, 54675, 56862, 59049, 61236, 63423, 64152, 64881, 65124, 65367, 65448, 65529, 65532, 65535, 65535]], num_rows: 128 }, last_program_row: RowIndex(12) }, program_info: ProgramInfo { program_hash: Word([8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090]), kernel: Kernel([Word([13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008])]) }, stack_outputs: StackOutputs { elements: [1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, precompile_requests: [], final_precompile_transcript: PrecompileTranscript { state: Word([0, 0, 0, 0]) }, trace_len_summary: TraceLenSummary { main_trace_len: 13, range_trace_len: 39, chiplets_trace_len: ChipletsLengths { hash_chiplet_len: 64, bitwise_chiplet_len: 0, memory_chiplet_len: 0, ace_chiplet_len: 0, kernel_rom_len: 2 } } } +ExecutionTrace { main_trace: MainTrace { storage: Parts { core_rm: [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 0, 0, 0, 0, 0, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 2, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 5, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 6, 0, 0, 0, 0, 0, 5, 0, 1, 1, 0, 1, 0, 1, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 7, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 8, 5, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 8, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 9, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 1, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 10, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 1, 1, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 11, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 59, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 62, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 74, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 75, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 79, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 83, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 84, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 85, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 89, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 93, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 97, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 103, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 105, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 109, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 115, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 121, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 122, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 124, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 126, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0], chiplets_rm: [1, 1, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 87, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 18295472245208082500, 5861891236755868697, 15427470125368014171, 5623455816162216176, 11335643519415282023, 11551270937114235273, 11863835819310119404, 14266241634618616211, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 0, 0, 1, 0, 0, 1, 1, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 104, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 3302920651539558582, 2392789792202575031, 11898680931385242334, 3118702462093730513, 6675709269000188192, 6602703777719207165, 6265915402349571564, 2890093557727619615, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 15913981378790993016, 12701179462806902902, 9645342555817022514, 6889850077248808033, 678249829196625442, 8726676798253716750, 14136118088409135949, 1027279235140821190, 8048729929177993405, 2542525775606472849, 3283471062508934848, 18058280687405074483, 2, 0, 0, 0, 1, 0, 0, 0, 0, 11899090779839289087, 1701090513954162998, 6953639183117323837, 11460660778637092812, 8345945186928076892, 11308243255833047975, 5734666540714547293, 1546649737555419076, 14643837757854608147, 7477230882792828789, 2359592855656894189, 12732146754685591216, 2, 0, 0, 0, 1, 0, 0, 0, 0, 16283412161296764998, 17261112905912533997, 1498213616014501531, 7707889409925905689, 16090493533591377617, 525010687572874692, 5228788497345464933, 13051466527897639054, 3560924095387897187, 12551242480725370445, 4038828260160651477, 6132317990703151787, 2, 0, 0, 0, 1, 0, 14261694278902826987, 16097544111075686229, 16632169854204459773, 14265791174051766329, 14732671264513481238, 15642599276989292777, 7593738854072541043, 11440582168151819564, 18258483622390056257, 6851601084855956341, 10463349537167637967, 10032876585525342603, 825669147467031185, 16271217766495541082, 9021089524754424392, 2, 0, 0, 0, 1, 0, 16780109665356203593, 16025948514351396737, 4256182047405196535, 4919013996388780384, 6974015548720770018, 10189882052640604940, 12288632186238773648, 7055558833039165669, 9820743264545515107, 7353106531603230851, 5612808106478211181, 362487531847990854, 10259224130335842891, 2374318293393776907, 16208179153232019183, 2, 0, 0, 0, 1, 0, 9496969110446094198, 10424961628901078909, 7587726188775237019, 13423955604028434301, 12288543613568774022, 4068762405627465026, 7288480650179820504, 1035430088689333582, 2651098721515326755, 4524440782000877024, 3013038852551457145, 12536534023666437466, 6560028330562582937, 9090521726813489969, 17634755409309861791, 2, 0, 0, 0, 1, 0, 15761194215082968049, 15796684185970405493, 1294179585337070936, 5701860167148115542, 17707247215386403887, 18419960639673587986, 14570347312565937582, 8634142387233894354, 11938075017385927203, 8060480374868324599, 16699922976999064541, 14234489074154948378, 17210389744085299852, 17331527780947314617, 16356838976302290254, 2, 0, 0, 0, 1, 0, 6184336662619539433, 7106147576739595100, 18237994300618329820, 9768155666925548219, 226714884885574576, 11169479382209254075, 5756676929838789145, 29803495366482069, 13128905441940840949, 18304516631519674746, 3177693875357946498, 10624850715031233718, 16956738608020324589, 10558540052020438814, 7826502712354673600, 2, 0, 0, 0, 1, 0, 1980308537464408089, 6810023246846442283, 9400257804338494201, 11246932855318952856, 7504545710540897051, 5823677062954428984, 9151299935574592196, 15905322272715446259, 18174748553199601666, 9877077233183640995, 2431998410719122545, 3541292002384422941, 5416588529930333172, 15788517205879313917, 2490575598934209003, 2, 0, 0, 0, 1, 0, 17950140321155419078, 9014768943295123367, 10707122777228105884, 10701204914974985643, 15060806908759160804, 7011191650405807117, 13598159658926423040, 3947314552577424057, 12985682730462769834, 18264715466581442035, 7667189540353817157, 7685483800933651249, 16085167604591833455, 16105855167414075111, 16215395602017884846, 2, 0, 0, 0, 1, 0, 8676227747932036802, 0, 0, 11504729673400082663, 3659553513673892306, 10374352082945691939, 15819529278889203623, 18107940902385670152, 8889798095480337831, 12737345777792032790, 456604824069734238, 2859376131275373721, 8758251935142908528, 2364536875264708997, 2435873391009301907, 2, 0, 0, 0, 1, 0, 0, 0, 0, 16797630026773069829, 16954579754978210547, 4393671449525183699, 6245909065255485436, 8311309349844601638, 3305827896314343556, 1615477551160071258, 3897903986825909618, 13015230208471866351, 13839070994009764281, 2415598337053791919, 5526300544248852335, 2, 0, 0, 0, 1, 0, 0, 0, 0, 12827873667465929582, 15892701250663610383, 549489834728826775, 9425334953627649153, 15402634395731091512, 11814416271399500934, 14597718118926923662, 4726679541432826422, 342915941842538663, 11221492717904369823, 17188959961041335276, 13559932473868727168, 2, 0, 0, 0, 1, 0, 0, 0, 0, 8689096602925893779, 4096531352684607500, 8424481237826564505, 15238165026175259240, 10985176737527635898, 2672540837788784952, 6649439643634522762, 7581835251244605715, 17671970641549389116, 18069913154458602790, 749417941228438762, 6150863900366154379, 2, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 2, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 104, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8045445736281483033, 11250386210932698181, 8447328201766628152, 527413860513422548, 2685530738566346168, 6831208533457089257, 18437518609042158966, 9278656209339918204, 4985219875437159245, 11102677601257469434, 16507477945842790035, 1824439754798293960, 1, 0, 0, 0, 1, 0, 0, 0, 0, 13956548861570575417, 9763011176116608502, 4674794667694672124, 15480945315513057131, 1720127490715987209, 8831144276914012491, 250532683106813113, 18120264276823504303, 7075953940225467814, 5209117550659913714, 16837262421580091230, 11776861544437613654, 1, 0, 0, 0, 1, 0, 0, 0, 0, 11512288827914447463, 17480311708360681553, 1580648391454313983, 3096307188042936924, 1465975368055000312, 1362735000009130378, 9465263839861379226, 14989718516974665241, 6566304943303044120, 2566111340353544025, 11325114382049196546, 15312957863351292707, 1, 0, 0, 0, 1, 0, 14277010279095994320, 16421135900115366688, 8672081456162485113, 3315051758368515759, 16767795201975506841, 15745773069630130456, 10428682341827527559, 1609230777320982945, 8538451532688083939, 3309529438878958210, 2752224770668803053, 4030118788308868253, 9432517950422109187, 16655292287441213846, 3022134227219891882, 1, 0, 0, 0, 1, 0, 12527609013750224641, 4064424364292504784, 12759359572746242099, 17498386834554682438, 9899399543530128857, 13823203783008011183, 4789373231313821943, 2275698290172824909, 14631083886626574849, 6083693282888760833, 17878128073269252636, 9098606451540172932, 4868784239831205121, 2796426330015583824, 10989039359288278884, 1, 0, 0, 0, 1, 0, 11227322858093773958, 1256671723465513282, 4658091495748016755, 14665531030768273966, 5299487590341247604, 3822486110676485203, 2504794437525571246, 16499336243305873535, 10440692312118465582, 8016916182618053860, 11635644660770389253, 4003332628141212645, 8214607993079575395, 7207010966340578848, 10940185796204185824, 1, 0, 0, 0, 1, 0, 12668058855470446370, 5255019430616758101, 14462744774902056824, 4113277495358115867, 1662113282866907292, 6585130578479449998, 17149676088405916342, 10457761600032190139, 3327323833077679847, 8197592365883421679, 7425424839480263678, 13938948244809494455, 16228892676090661713, 1504971017217144480, 8182776272628891435, 1, 0, 0, 0, 1, 0, 6914860967029724068, 10806403396787496173, 3848579298848325229, 13805102062857951563, 16229844626955596217, 13935582109846251306, 8572389593169145834, 17576620671980411587, 10572618363726686694, 4850773447466262928, 15638373093776703034, 7910188832488865399, 15531097057290072206, 4889338479662679107, 10949924992689136363, 1, 0, 0, 0, 1, 0, 4330865582496855032, 8473297999555512458, 7242240654409420037, 10299128364456298062, 14941042471610818695, 7565852759213474271, 3702504147651721524, 207598518842942194, 6753564273898291164, 11919043583188527726, 2096230895461827816, 9814026405907874850, 17772509140662880385, 15771358593260243554, 6654163437055992392, 1, 0, 0, 0, 1, 0, 14424762448455122149, 9431772018868236879, 584341294112809445, 4918781353216964245, 2337856752823889422, 16060522829040602978, 1539091624536270714, 3206058370812795090, 10870942617842401040, 8606083879706978586, 4344150548445731150, 380060561426657383, 2730766720879432427, 14498706802584030890, 7769509436274319865, 1, 0, 0, 0, 1, 0, 4147902545389306045, 0, 0, 16093847529947137599, 5589254378675598674, 14560311910293184412, 2592280228933147027, 10554718058272358421, 1080024418599256897, 646685994271806030, 5548578222066968465, 13466005348915411989, 1280314003050779809, 11336903746061502338, 9170512654829817344, 1, 0, 0, 0, 1, 0, 0, 0, 0, 4018352536943676596, 1718970205214835807, 16103902027304323769, 7153843611717905198, 7564037379049998274, 6261812460083581507, 17412746410051240602, 11497751499239916990, 9826997558611816650, 2818155236871917143, 1303564367886020260, 8520201627654591972, 1, 0, 0, 0, 1, 0, 0, 0, 0, 7399548230295413142, 15817399793431362137, 11111223990289672490, 14277270972042777124, 9762507146447503800, 5775448381623517643, 9705617515238056177, 10499143968321217911, 13713380413483444727, 2803285551461817904, 3117235683594903662, 8848529162601814389, 1, 0, 0, 0, 1, 0, 0, 0, 0, 41605777382338142, 13705243190620087435, 8813156438606599548, 3668196738148877897, 8870883218370515482, 1311045307375281156, 891925000095841393, 18305520102948210690, 3095238878624340898, 8925726914230878692, 11158112996086314015, 16463229027226752316, 1, 0, 0, 0, 1, 0, 0, 0, 0, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 3302920651539558582, 2392789792202575031, 11898680931385242334, 3118702462093730513, 6675709269000188192, 6602703777719207165, 6265915402349571564, 2890093557727619615, 1, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 87, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 3856634821061399044, 17111039960895529319, 4566799539362193550, 16844085186824886612, 3355565787613486613, 17176810829821093448, 1352137470786356802, 17998789203926124900, 16786513982140394745, 18361321143200586840, 13282415620666686464, 8228901733828007087, 1, 0, 0, 0, 1, 0, 0, 0, 0, 7778838127124972351, 10159506934852280087, 12722539778584946047, 2132149890053205639, 18102156639692354752, 8649394742436479221, 4276590013765363445, 12997989410109585404, 9529430557140348691, 7606828505864121076, 17598459768503654580, 7777709650181082785, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8194007358744550661, 306611979519524457, 10528705374691960700, 319508112022086105, 10482736583082303606, 10309893049243492470, 12917037695721108312, 6248886070590142047, 6486447845183860200, 17251231915071761348, 16404214944354933870, 13204576128129345952, 1, 0, 0, 0, 1, 0, 8699431654038775343, 37936723307283666, 8146588049311051327, 4530941775236136548, 16738435301601374688, 11359785745622903572, 15791382447019722900, 13379247305879358010, 13936169968399350974, 3508629571016445401, 5950565638198879243, 3988632325321614173, 14773776822077474877, 3784342517219646416, 16429780078047820450, 1, 0, 0, 0, 1, 0, 3424975013671552621, 14815277144979047633, 8209627855274846837, 8295342049297215958, 15269992241592835077, 6438591265885629379, 13730717627697535913, 7787232003492715877, 9621206971434531853, 11223875126467095457, 6436756480486800604, 5377264485189635000, 5482319143373695788, 12350937431324290559, 6389531569788570962, 1, 0, 0, 0, 1, 0, 4990332689225356878, 13089030850077380376, 2471524608045276896, 17139358263655017093, 18165060065290469256, 9446121688110358951, 977359788342611310, 16707128933418187061, 16621781274459601592, 7539458736435553900, 13565999334907596759, 1167963651906558177, 801268771425181312, 6228026786881094671, 1048304545096961231, 1, 0, 0, 0, 1, 0, 9535941747246118180, 6758632961600419030, 13740260214376027515, 1405920164415583051, 14920069431652436384, 17439962485710135044, 417887867370226026, 16088228384974311599, 3710261815002798848, 137273432282796342, 5851059442771612226, 15136621875351529272, 15702932139541150313, 8779333934582463234, 9538098266673672038, 1, 0, 0, 0, 1, 0, 8185802697182936648, 17682692702261395783, 2219736463483295590, 10757593865239447517, 1468754948270208383, 13746393160610271679, 6189648269704333551, 6527095477330083457, 289679669419955019, 9046656083049426920, 3573808134852581123, 16538711805104705524, 17122129810962376462, 2926585958971102942, 10107422984112301769, 1, 0, 0, 0, 1, 0, 12705492336505225259, 715424026399539381, 2549298692279483128, 1219968445867773701, 12911638424059178129, 5048199682718381540, 1535097268869899419, 17260972721034176516, 15413356179839910737, 13476986835056197648, 7470895530577539713, 8398414302064166980, 16518486341402545799, 70008875869363926, 10307933594111103714, 1, 0, 0, 0, 1, 0, 450024681039430413, 17110346123676092707, 503623504778112596, 1520822994009218024, 3997469258016952350, 18365130032920199069, 7964473389350786015, 9071675461601628413, 4719657860691726363, 2082610983791917873, 13643709933077212296, 16696777778525571691, 5937099920759461106, 17798517164851075633, 7519473231185213307, 1, 0, 0, 0, 1, 0, 17931731304193244959, 0, 0, 11156145431451224746, 9448001553779471302, 11663814486624427477, 8105139713546092506, 996052311483623897, 18356245393792640289, 11128269591471854109, 3650549423833220560, 10102060878284432657, 6999065210900455588, 9632395034475899386, 10920626017129534091, 1, 0, 0, 0, 1, 0, 0, 0, 0, 17063798001278894185, 2357038714604006367, 10530018908095235062, 8857952782846291740, 17150424826448967994, 8501298665346388356, 15857100073185553560, 2606143849991065668, 1709380969765926542, 7242804148613591485, 10415598312293556776, 3315485625242432418, 1, 0, 0, 0, 1, 0, 0, 0, 0, 6980956723582544769, 17736580895885199098, 10473837116748539797, 5495612785159148096, 8023377746099375924, 15245498074053387423, 15841461147440760970, 14724882291190243897, 13636145514567347966, 11793730383025693858, 9279036588745121360, 3956126734745245173, 1, 0, 0, 0, 1, 0, 0, 0, 0, 14149948942544104911, 13474931984659215558, 7610362789910399421, 10137886774563078569, 110488320743199774, 6631727638469492070, 9820426758070632911, 11785623090416681374, 2883529595690350478, 8282658300559339579, 1247473541961741200, 12265308465311135280, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 18295472245208082500, 5861891236755868697, 15427470125368014171, 5623455816162216176, 11335643519415282023, 11551270937114235273, 11863835819310119404, 14266241634618616211, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], range_checker_cols: [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2187, 4374, 6561, 8748, 10935, 13122, 15309, 17496, 19683, 21870, 24057, 26244, 28431, 30618, 32805, 34992, 37179, 39366, 41553, 43740, 45927, 48114, 50301, 52488, 54675, 56862, 59049, 61236, 63423, 64152, 64881, 65124, 65367, 65448, 65529, 65532, 65535, 65535]], num_rows: 128 }, last_program_row: RowIndex(12) }, program_info: ProgramInfo { program_hash: Word([8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090]), kernel: Kernel([Word([13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008])]) }, stack_outputs: StackOutputs { elements: [1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, precompile_requests: [], final_precompile_transcript: PrecompileTranscript { state: Word([0, 0, 0, 0]) }, trace_len_summary: TraceLenSummary { main_trace_len: 13, range_trace_len: 39, chiplets_trace_len: ChipletsLengths { hash_chiplet_len: 64, bitwise_chiplet_len: 0, memory_chiplet_len: 0, ace_chiplet_len: 0, kernel_rom_len: 1 } } } diff --git a/processor/src/trace/parallel/snapshots/miden_processor__trace__parallel__tests__trace__parallel__tests__test_trace_generation_at_fragment_boundaries__case_14.snap b/processor/src/trace/parallel/snapshots/miden_processor__trace__parallel__tests__trace__parallel__tests__test_trace_generation_at_fragment_boundaries__case_14.snap index 15f4cfacb1..11ed9c2d2c 100644 --- a/processor/src/trace/parallel/snapshots/miden_processor__trace__parallel__tests__trace__parallel__tests__test_trace_generation_at_fragment_boundaries__case_14.snap +++ b/processor/src/trace/parallel/snapshots/miden_processor__trace__parallel__tests__trace__parallel__tests__test_trace_generation_at_fragment_boundaries__case_14.snap @@ -2,4 +2,4 @@ source: processor/src/trace/parallel/tests.rs expression: DeterministicTrace(&trace_from_fragments) --- -ExecutionTrace { main_trace: MainTrace { storage: Parts { core_rm: [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 0, 0, 0, 0, 0, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 2, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 5, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 6, 0, 0, 0, 0, 0, 5, 0, 1, 1, 0, 1, 0, 1, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 7, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 8, 5, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 8, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 9, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 1, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 10, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 1, 1, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 11, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 59, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 62, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 74, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 75, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 79, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 83, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 84, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 85, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 89, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 93, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 97, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 103, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 105, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 109, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 115, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 121, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 122, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 124, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 126, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0], chiplets_rm: [1, 1, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 87, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 18295472245208082500, 5861891236755868697, 15427470125368014171, 5623455816162216176, 11335643519415282023, 11551270937114235273, 11863835819310119404, 14266241634618616211, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 0, 0, 1, 0, 0, 1, 1, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 104, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 3302920651539558582, 2392789792202575031, 11898680931385242334, 3118702462093730513, 6675709269000188192, 6602703777719207165, 6265915402349571564, 2890093557727619615, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 15913981378790993016, 12701179462806902902, 9645342555817022514, 6889850077248808033, 678249829196625442, 8726676798253716750, 14136118088409135949, 1027279235140821190, 8048729929177993405, 2542525775606472849, 3283471062508934848, 18058280687405074483, 2, 0, 0, 0, 1, 0, 0, 0, 0, 11899090779839289087, 1701090513954162998, 6953639183117323837, 11460660778637092812, 8345945186928076892, 11308243255833047975, 5734666540714547293, 1546649737555419076, 14643837757854608147, 7477230882792828789, 2359592855656894189, 12732146754685591216, 2, 0, 0, 0, 1, 0, 0, 0, 0, 16283412161296764998, 17261112905912533997, 1498213616014501531, 7707889409925905689, 16090493533591377617, 525010687572874692, 5228788497345464933, 13051466527897639054, 3560924095387897187, 12551242480725370445, 4038828260160651477, 6132317990703151787, 2, 0, 0, 0, 1, 0, 14261694278902826987, 16097544111075686229, 16632169854204459773, 14265791174051766329, 14732671264513481238, 15642599276989292777, 7593738854072541043, 11440582168151819564, 18258483622390056257, 6851601084855956341, 10463349537167637967, 10032876585525342603, 825669147467031185, 16271217766495541082, 9021089524754424392, 2, 0, 0, 0, 1, 0, 16780109665356203593, 16025948514351396737, 4256182047405196535, 4919013996388780384, 6974015548720770018, 10189882052640604940, 12288632186238773648, 7055558833039165669, 9820743264545515107, 7353106531603230851, 5612808106478211181, 362487531847990854, 10259224130335842891, 2374318293393776907, 16208179153232019183, 2, 0, 0, 0, 1, 0, 9496969110446094198, 10424961628901078909, 7587726188775237019, 13423955604028434301, 12288543613568774022, 4068762405627465026, 7288480650179820504, 1035430088689333582, 2651098721515326755, 4524440782000877024, 3013038852551457145, 12536534023666437466, 6560028330562582937, 9090521726813489969, 17634755409309861791, 2, 0, 0, 0, 1, 0, 15761194215082968049, 15796684185970405493, 1294179585337070936, 5701860167148115542, 17707247215386403887, 18419960639673587986, 14570347312565937582, 8634142387233894354, 11938075017385927203, 8060480374868324599, 16699922976999064541, 14234489074154948378, 17210389744085299852, 17331527780947314617, 16356838976302290254, 2, 0, 0, 0, 1, 0, 6184336662619539433, 7106147576739595100, 18237994300618329820, 9768155666925548219, 226714884885574576, 11169479382209254075, 5756676929838789145, 29803495366482069, 13128905441940840949, 18304516631519674746, 3177693875357946498, 10624850715031233718, 16956738608020324589, 10558540052020438814, 7826502712354673600, 2, 0, 0, 0, 1, 0, 1980308537464408089, 6810023246846442283, 9400257804338494201, 11246932855318952856, 7504545710540897051, 5823677062954428984, 9151299935574592196, 15905322272715446259, 18174748553199601666, 9877077233183640995, 2431998410719122545, 3541292002384422941, 5416588529930333172, 15788517205879313917, 2490575598934209003, 2, 0, 0, 0, 1, 0, 17950140321155419078, 9014768943295123367, 10707122777228105884, 10701204914974985643, 15060806908759160804, 7011191650405807117, 13598159658926423040, 3947314552577424057, 12985682730462769834, 18264715466581442035, 7667189540353817157, 7685483800933651249, 16085167604591833455, 16105855167414075111, 16215395602017884846, 2, 0, 0, 0, 1, 0, 8676227747932036802, 0, 0, 11504729673400082663, 3659553513673892306, 10374352082945691939, 15819529278889203623, 18107940902385670152, 8889798095480337831, 12737345777792032790, 456604824069734238, 2859376131275373721, 8758251935142908528, 2364536875264708997, 2435873391009301907, 2, 0, 0, 0, 1, 0, 0, 0, 0, 16797630026773069829, 16954579754978210547, 4393671449525183699, 6245909065255485436, 8311309349844601638, 3305827896314343556, 1615477551160071258, 3897903986825909618, 13015230208471866351, 13839070994009764281, 2415598337053791919, 5526300544248852335, 2, 0, 0, 0, 1, 0, 0, 0, 0, 12827873667465929582, 15892701250663610383, 549489834728826775, 9425334953627649153, 15402634395731091512, 11814416271399500934, 14597718118926923662, 4726679541432826422, 342915941842538663, 11221492717904369823, 17188959961041335276, 13559932473868727168, 2, 0, 0, 0, 1, 0, 0, 0, 0, 8689096602925893779, 4096531352684607500, 8424481237826564505, 15238165026175259240, 10985176737527635898, 2672540837788784952, 6649439643634522762, 7581835251244605715, 17671970641549389116, 18069913154458602790, 749417941228438762, 6150863900366154379, 2, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 2, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 104, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8045445736281483033, 11250386210932698181, 8447328201766628152, 527413860513422548, 2685530738566346168, 6831208533457089257, 18437518609042158966, 9278656209339918204, 4985219875437159245, 11102677601257469434, 16507477945842790035, 1824439754798293960, 1, 0, 0, 0, 1, 0, 0, 0, 0, 13956548861570575417, 9763011176116608502, 4674794667694672124, 15480945315513057131, 1720127490715987209, 8831144276914012491, 250532683106813113, 18120264276823504303, 7075953940225467814, 5209117550659913714, 16837262421580091230, 11776861544437613654, 1, 0, 0, 0, 1, 0, 0, 0, 0, 11512288827914447463, 17480311708360681553, 1580648391454313983, 3096307188042936924, 1465975368055000312, 1362735000009130378, 9465263839861379226, 14989718516974665241, 6566304943303044120, 2566111340353544025, 11325114382049196546, 15312957863351292707, 1, 0, 0, 0, 1, 0, 14277010279095994320, 16421135900115366688, 8672081456162485113, 3315051758368515759, 16767795201975506841, 15745773069630130456, 10428682341827527559, 1609230777320982945, 8538451532688083939, 3309529438878958210, 2752224770668803053, 4030118788308868253, 9432517950422109187, 16655292287441213846, 3022134227219891882, 1, 0, 0, 0, 1, 0, 12527609013750224641, 4064424364292504784, 12759359572746242099, 17498386834554682438, 9899399543530128857, 13823203783008011183, 4789373231313821943, 2275698290172824909, 14631083886626574849, 6083693282888760833, 17878128073269252636, 9098606451540172932, 4868784239831205121, 2796426330015583824, 10989039359288278884, 1, 0, 0, 0, 1, 0, 11227322858093773958, 1256671723465513282, 4658091495748016755, 14665531030768273966, 5299487590341247604, 3822486110676485203, 2504794437525571246, 16499336243305873535, 10440692312118465582, 8016916182618053860, 11635644660770389253, 4003332628141212645, 8214607993079575395, 7207010966340578848, 10940185796204185824, 1, 0, 0, 0, 1, 0, 12668058855470446370, 5255019430616758101, 14462744774902056824, 4113277495358115867, 1662113282866907292, 6585130578479449998, 17149676088405916342, 10457761600032190139, 3327323833077679847, 8197592365883421679, 7425424839480263678, 13938948244809494455, 16228892676090661713, 1504971017217144480, 8182776272628891435, 1, 0, 0, 0, 1, 0, 6914860967029724068, 10806403396787496173, 3848579298848325229, 13805102062857951563, 16229844626955596217, 13935582109846251306, 8572389593169145834, 17576620671980411587, 10572618363726686694, 4850773447466262928, 15638373093776703034, 7910188832488865399, 15531097057290072206, 4889338479662679107, 10949924992689136363, 1, 0, 0, 0, 1, 0, 4330865582496855032, 8473297999555512458, 7242240654409420037, 10299128364456298062, 14941042471610818695, 7565852759213474271, 3702504147651721524, 207598518842942194, 6753564273898291164, 11919043583188527726, 2096230895461827816, 9814026405907874850, 17772509140662880385, 15771358593260243554, 6654163437055992392, 1, 0, 0, 0, 1, 0, 14424762448455122149, 9431772018868236879, 584341294112809445, 4918781353216964245, 2337856752823889422, 16060522829040602978, 1539091624536270714, 3206058370812795090, 10870942617842401040, 8606083879706978586, 4344150548445731150, 380060561426657383, 2730766720879432427, 14498706802584030890, 7769509436274319865, 1, 0, 0, 0, 1, 0, 4147902545389306045, 0, 0, 16093847529947137599, 5589254378675598674, 14560311910293184412, 2592280228933147027, 10554718058272358421, 1080024418599256897, 646685994271806030, 5548578222066968465, 13466005348915411989, 1280314003050779809, 11336903746061502338, 9170512654829817344, 1, 0, 0, 0, 1, 0, 0, 0, 0, 4018352536943676596, 1718970205214835807, 16103902027304323769, 7153843611717905198, 7564037379049998274, 6261812460083581507, 17412746410051240602, 11497751499239916990, 9826997558611816650, 2818155236871917143, 1303564367886020260, 8520201627654591972, 1, 0, 0, 0, 1, 0, 0, 0, 0, 7399548230295413142, 15817399793431362137, 11111223990289672490, 14277270972042777124, 9762507146447503800, 5775448381623517643, 9705617515238056177, 10499143968321217911, 13713380413483444727, 2803285551461817904, 3117235683594903662, 8848529162601814389, 1, 0, 0, 0, 1, 0, 0, 0, 0, 41605777382338142, 13705243190620087435, 8813156438606599548, 3668196738148877897, 8870883218370515482, 1311045307375281156, 891925000095841393, 18305520102948210690, 3095238878624340898, 8925726914230878692, 11158112996086314015, 16463229027226752316, 1, 0, 0, 0, 1, 0, 0, 0, 0, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 3302920651539558582, 2392789792202575031, 11898680931385242334, 3118702462093730513, 6675709269000188192, 6602703777719207165, 6265915402349571564, 2890093557727619615, 1, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 87, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 3856634821061399044, 17111039960895529319, 4566799539362193550, 16844085186824886612, 3355565787613486613, 17176810829821093448, 1352137470786356802, 17998789203926124900, 16786513982140394745, 18361321143200586840, 13282415620666686464, 8228901733828007087, 1, 0, 0, 0, 1, 0, 0, 0, 0, 7778838127124972351, 10159506934852280087, 12722539778584946047, 2132149890053205639, 18102156639692354752, 8649394742436479221, 4276590013765363445, 12997989410109585404, 9529430557140348691, 7606828505864121076, 17598459768503654580, 7777709650181082785, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8194007358744550661, 306611979519524457, 10528705374691960700, 319508112022086105, 10482736583082303606, 10309893049243492470, 12917037695721108312, 6248886070590142047, 6486447845183860200, 17251231915071761348, 16404214944354933870, 13204576128129345952, 1, 0, 0, 0, 1, 0, 8699431654038775343, 37936723307283666, 8146588049311051327, 4530941775236136548, 16738435301601374688, 11359785745622903572, 15791382447019722900, 13379247305879358010, 13936169968399350974, 3508629571016445401, 5950565638198879243, 3988632325321614173, 14773776822077474877, 3784342517219646416, 16429780078047820450, 1, 0, 0, 0, 1, 0, 3424975013671552621, 14815277144979047633, 8209627855274846837, 8295342049297215958, 15269992241592835077, 6438591265885629379, 13730717627697535913, 7787232003492715877, 9621206971434531853, 11223875126467095457, 6436756480486800604, 5377264485189635000, 5482319143373695788, 12350937431324290559, 6389531569788570962, 1, 0, 0, 0, 1, 0, 4990332689225356878, 13089030850077380376, 2471524608045276896, 17139358263655017093, 18165060065290469256, 9446121688110358951, 977359788342611310, 16707128933418187061, 16621781274459601592, 7539458736435553900, 13565999334907596759, 1167963651906558177, 801268771425181312, 6228026786881094671, 1048304545096961231, 1, 0, 0, 0, 1, 0, 9535941747246118180, 6758632961600419030, 13740260214376027515, 1405920164415583051, 14920069431652436384, 17439962485710135044, 417887867370226026, 16088228384974311599, 3710261815002798848, 137273432282796342, 5851059442771612226, 15136621875351529272, 15702932139541150313, 8779333934582463234, 9538098266673672038, 1, 0, 0, 0, 1, 0, 8185802697182936648, 17682692702261395783, 2219736463483295590, 10757593865239447517, 1468754948270208383, 13746393160610271679, 6189648269704333551, 6527095477330083457, 289679669419955019, 9046656083049426920, 3573808134852581123, 16538711805104705524, 17122129810962376462, 2926585958971102942, 10107422984112301769, 1, 0, 0, 0, 1, 0, 12705492336505225259, 715424026399539381, 2549298692279483128, 1219968445867773701, 12911638424059178129, 5048199682718381540, 1535097268869899419, 17260972721034176516, 15413356179839910737, 13476986835056197648, 7470895530577539713, 8398414302064166980, 16518486341402545799, 70008875869363926, 10307933594111103714, 1, 0, 0, 0, 1, 0, 450024681039430413, 17110346123676092707, 503623504778112596, 1520822994009218024, 3997469258016952350, 18365130032920199069, 7964473389350786015, 9071675461601628413, 4719657860691726363, 2082610983791917873, 13643709933077212296, 16696777778525571691, 5937099920759461106, 17798517164851075633, 7519473231185213307, 1, 0, 0, 0, 1, 0, 17931731304193244959, 0, 0, 11156145431451224746, 9448001553779471302, 11663814486624427477, 8105139713546092506, 996052311483623897, 18356245393792640289, 11128269591471854109, 3650549423833220560, 10102060878284432657, 6999065210900455588, 9632395034475899386, 10920626017129534091, 1, 0, 0, 0, 1, 0, 0, 0, 0, 17063798001278894185, 2357038714604006367, 10530018908095235062, 8857952782846291740, 17150424826448967994, 8501298665346388356, 15857100073185553560, 2606143849991065668, 1709380969765926542, 7242804148613591485, 10415598312293556776, 3315485625242432418, 1, 0, 0, 0, 1, 0, 0, 0, 0, 6980956723582544769, 17736580895885199098, 10473837116748539797, 5495612785159148096, 8023377746099375924, 15245498074053387423, 15841461147440760970, 14724882291190243897, 13636145514567347966, 11793730383025693858, 9279036588745121360, 3956126734745245173, 1, 0, 0, 0, 1, 0, 0, 0, 0, 14149948942544104911, 13474931984659215558, 7610362789910399421, 10137886774563078569, 110488320743199774, 6631727638469492070, 9820426758070632911, 11785623090416681374, 2883529595690350478, 8282658300559339579, 1247473541961741200, 12265308465311135280, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 18295472245208082500, 5861891236755868697, 15427470125368014171, 5623455816162216176, 11335643519415282023, 11551270937114235273, 11863835819310119404, 14266241634618616211, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], range_checker_cols: [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2187, 4374, 6561, 8748, 10935, 13122, 15309, 17496, 19683, 21870, 24057, 26244, 28431, 30618, 32805, 34992, 37179, 39366, 41553, 43740, 45927, 48114, 50301, 52488, 54675, 56862, 59049, 61236, 63423, 64152, 64881, 65124, 65367, 65448, 65529, 65532, 65535, 65535]], num_rows: 128 }, last_program_row: RowIndex(12) }, program_info: ProgramInfo { program_hash: Word([8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090]), kernel: Kernel([Word([13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008])]) }, stack_outputs: StackOutputs { elements: [1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, precompile_requests: [], final_precompile_transcript: PrecompileTranscript { state: Word([0, 0, 0, 0]) }, trace_len_summary: TraceLenSummary { main_trace_len: 13, range_trace_len: 39, chiplets_trace_len: ChipletsLengths { hash_chiplet_len: 64, bitwise_chiplet_len: 0, memory_chiplet_len: 0, ace_chiplet_len: 0, kernel_rom_len: 2 } } } +ExecutionTrace { main_trace: MainTrace { storage: Parts { core_rm: [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 0, 0, 0, 0, 0, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 2, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 5, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 6, 0, 0, 0, 0, 0, 5, 0, 1, 1, 0, 1, 0, 1, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 7, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 8, 5, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 8, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 9, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 1, 1, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 10, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 1, 1, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 11, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 59, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 62, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 74, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 75, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 79, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 83, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 84, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 85, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 89, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 93, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 95, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 97, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 99, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 103, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 105, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 109, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 111, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 115, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 121, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 122, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 124, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 126, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0], chiplets_rm: [1, 1, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 87, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 18295472245208082500, 5861891236755868697, 15427470125368014171, 5623455816162216176, 11335643519415282023, 11551270937114235273, 11863835819310119404, 14266241634618616211, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 0, 0, 1, 0, 0, 1, 1, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 104, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 3302920651539558582, 2392789792202575031, 11898680931385242334, 3118702462093730513, 6675709269000188192, 6602703777719207165, 6265915402349571564, 2890093557727619615, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 15913981378790993016, 12701179462806902902, 9645342555817022514, 6889850077248808033, 678249829196625442, 8726676798253716750, 14136118088409135949, 1027279235140821190, 8048729929177993405, 2542525775606472849, 3283471062508934848, 18058280687405074483, 2, 0, 0, 0, 1, 0, 0, 0, 0, 11899090779839289087, 1701090513954162998, 6953639183117323837, 11460660778637092812, 8345945186928076892, 11308243255833047975, 5734666540714547293, 1546649737555419076, 14643837757854608147, 7477230882792828789, 2359592855656894189, 12732146754685591216, 2, 0, 0, 0, 1, 0, 0, 0, 0, 16283412161296764998, 17261112905912533997, 1498213616014501531, 7707889409925905689, 16090493533591377617, 525010687572874692, 5228788497345464933, 13051466527897639054, 3560924095387897187, 12551242480725370445, 4038828260160651477, 6132317990703151787, 2, 0, 0, 0, 1, 0, 14261694278902826987, 16097544111075686229, 16632169854204459773, 14265791174051766329, 14732671264513481238, 15642599276989292777, 7593738854072541043, 11440582168151819564, 18258483622390056257, 6851601084855956341, 10463349537167637967, 10032876585525342603, 825669147467031185, 16271217766495541082, 9021089524754424392, 2, 0, 0, 0, 1, 0, 16780109665356203593, 16025948514351396737, 4256182047405196535, 4919013996388780384, 6974015548720770018, 10189882052640604940, 12288632186238773648, 7055558833039165669, 9820743264545515107, 7353106531603230851, 5612808106478211181, 362487531847990854, 10259224130335842891, 2374318293393776907, 16208179153232019183, 2, 0, 0, 0, 1, 0, 9496969110446094198, 10424961628901078909, 7587726188775237019, 13423955604028434301, 12288543613568774022, 4068762405627465026, 7288480650179820504, 1035430088689333582, 2651098721515326755, 4524440782000877024, 3013038852551457145, 12536534023666437466, 6560028330562582937, 9090521726813489969, 17634755409309861791, 2, 0, 0, 0, 1, 0, 15761194215082968049, 15796684185970405493, 1294179585337070936, 5701860167148115542, 17707247215386403887, 18419960639673587986, 14570347312565937582, 8634142387233894354, 11938075017385927203, 8060480374868324599, 16699922976999064541, 14234489074154948378, 17210389744085299852, 17331527780947314617, 16356838976302290254, 2, 0, 0, 0, 1, 0, 6184336662619539433, 7106147576739595100, 18237994300618329820, 9768155666925548219, 226714884885574576, 11169479382209254075, 5756676929838789145, 29803495366482069, 13128905441940840949, 18304516631519674746, 3177693875357946498, 10624850715031233718, 16956738608020324589, 10558540052020438814, 7826502712354673600, 2, 0, 0, 0, 1, 0, 1980308537464408089, 6810023246846442283, 9400257804338494201, 11246932855318952856, 7504545710540897051, 5823677062954428984, 9151299935574592196, 15905322272715446259, 18174748553199601666, 9877077233183640995, 2431998410719122545, 3541292002384422941, 5416588529930333172, 15788517205879313917, 2490575598934209003, 2, 0, 0, 0, 1, 0, 17950140321155419078, 9014768943295123367, 10707122777228105884, 10701204914974985643, 15060806908759160804, 7011191650405807117, 13598159658926423040, 3947314552577424057, 12985682730462769834, 18264715466581442035, 7667189540353817157, 7685483800933651249, 16085167604591833455, 16105855167414075111, 16215395602017884846, 2, 0, 0, 0, 1, 0, 8676227747932036802, 0, 0, 11504729673400082663, 3659553513673892306, 10374352082945691939, 15819529278889203623, 18107940902385670152, 8889798095480337831, 12737345777792032790, 456604824069734238, 2859376131275373721, 8758251935142908528, 2364536875264708997, 2435873391009301907, 2, 0, 0, 0, 1, 0, 0, 0, 0, 16797630026773069829, 16954579754978210547, 4393671449525183699, 6245909065255485436, 8311309349844601638, 3305827896314343556, 1615477551160071258, 3897903986825909618, 13015230208471866351, 13839070994009764281, 2415598337053791919, 5526300544248852335, 2, 0, 0, 0, 1, 0, 0, 0, 0, 12827873667465929582, 15892701250663610383, 549489834728826775, 9425334953627649153, 15402634395731091512, 11814416271399500934, 14597718118926923662, 4726679541432826422, 342915941842538663, 11221492717904369823, 17188959961041335276, 13559932473868727168, 2, 0, 0, 0, 1, 0, 0, 0, 0, 8689096602925893779, 4096531352684607500, 8424481237826564505, 15238165026175259240, 10985176737527635898, 2672540837788784952, 6649439643634522762, 7581835251244605715, 17671970641549389116, 18069913154458602790, 749417941228438762, 6150863900366154379, 2, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 11116382020814057190, 14732796941305378371, 13074361791711182490, 6715193688317415490, 1571558009365433839, 13285275197432594374, 15905141483335727027, 2507840277166973476, 2, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 104, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8045445736281483033, 11250386210932698181, 8447328201766628152, 527413860513422548, 2685530738566346168, 6831208533457089257, 18437518609042158966, 9278656209339918204, 4985219875437159245, 11102677601257469434, 16507477945842790035, 1824439754798293960, 1, 0, 0, 0, 1, 0, 0, 0, 0, 13956548861570575417, 9763011176116608502, 4674794667694672124, 15480945315513057131, 1720127490715987209, 8831144276914012491, 250532683106813113, 18120264276823504303, 7075953940225467814, 5209117550659913714, 16837262421580091230, 11776861544437613654, 1, 0, 0, 0, 1, 0, 0, 0, 0, 11512288827914447463, 17480311708360681553, 1580648391454313983, 3096307188042936924, 1465975368055000312, 1362735000009130378, 9465263839861379226, 14989718516974665241, 6566304943303044120, 2566111340353544025, 11325114382049196546, 15312957863351292707, 1, 0, 0, 0, 1, 0, 14277010279095994320, 16421135900115366688, 8672081456162485113, 3315051758368515759, 16767795201975506841, 15745773069630130456, 10428682341827527559, 1609230777320982945, 8538451532688083939, 3309529438878958210, 2752224770668803053, 4030118788308868253, 9432517950422109187, 16655292287441213846, 3022134227219891882, 1, 0, 0, 0, 1, 0, 12527609013750224641, 4064424364292504784, 12759359572746242099, 17498386834554682438, 9899399543530128857, 13823203783008011183, 4789373231313821943, 2275698290172824909, 14631083886626574849, 6083693282888760833, 17878128073269252636, 9098606451540172932, 4868784239831205121, 2796426330015583824, 10989039359288278884, 1, 0, 0, 0, 1, 0, 11227322858093773958, 1256671723465513282, 4658091495748016755, 14665531030768273966, 5299487590341247604, 3822486110676485203, 2504794437525571246, 16499336243305873535, 10440692312118465582, 8016916182618053860, 11635644660770389253, 4003332628141212645, 8214607993079575395, 7207010966340578848, 10940185796204185824, 1, 0, 0, 0, 1, 0, 12668058855470446370, 5255019430616758101, 14462744774902056824, 4113277495358115867, 1662113282866907292, 6585130578479449998, 17149676088405916342, 10457761600032190139, 3327323833077679847, 8197592365883421679, 7425424839480263678, 13938948244809494455, 16228892676090661713, 1504971017217144480, 8182776272628891435, 1, 0, 0, 0, 1, 0, 6914860967029724068, 10806403396787496173, 3848579298848325229, 13805102062857951563, 16229844626955596217, 13935582109846251306, 8572389593169145834, 17576620671980411587, 10572618363726686694, 4850773447466262928, 15638373093776703034, 7910188832488865399, 15531097057290072206, 4889338479662679107, 10949924992689136363, 1, 0, 0, 0, 1, 0, 4330865582496855032, 8473297999555512458, 7242240654409420037, 10299128364456298062, 14941042471610818695, 7565852759213474271, 3702504147651721524, 207598518842942194, 6753564273898291164, 11919043583188527726, 2096230895461827816, 9814026405907874850, 17772509140662880385, 15771358593260243554, 6654163437055992392, 1, 0, 0, 0, 1, 0, 14424762448455122149, 9431772018868236879, 584341294112809445, 4918781353216964245, 2337856752823889422, 16060522829040602978, 1539091624536270714, 3206058370812795090, 10870942617842401040, 8606083879706978586, 4344150548445731150, 380060561426657383, 2730766720879432427, 14498706802584030890, 7769509436274319865, 1, 0, 0, 0, 1, 0, 4147902545389306045, 0, 0, 16093847529947137599, 5589254378675598674, 14560311910293184412, 2592280228933147027, 10554718058272358421, 1080024418599256897, 646685994271806030, 5548578222066968465, 13466005348915411989, 1280314003050779809, 11336903746061502338, 9170512654829817344, 1, 0, 0, 0, 1, 0, 0, 0, 0, 4018352536943676596, 1718970205214835807, 16103902027304323769, 7153843611717905198, 7564037379049998274, 6261812460083581507, 17412746410051240602, 11497751499239916990, 9826997558611816650, 2818155236871917143, 1303564367886020260, 8520201627654591972, 1, 0, 0, 0, 1, 0, 0, 0, 0, 7399548230295413142, 15817399793431362137, 11111223990289672490, 14277270972042777124, 9762507146447503800, 5775448381623517643, 9705617515238056177, 10499143968321217911, 13713380413483444727, 2803285551461817904, 3117235683594903662, 8848529162601814389, 1, 0, 0, 0, 1, 0, 0, 0, 0, 41605777382338142, 13705243190620087435, 8813156438606599548, 3668196738148877897, 8870883218370515482, 1311045307375281156, 891925000095841393, 18305520102948210690, 3095238878624340898, 8925726914230878692, 11158112996086314015, 16463229027226752316, 1, 0, 0, 0, 1, 0, 0, 0, 0, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 3302920651539558582, 2392789792202575031, 11898680931385242334, 3118702462093730513, 6675709269000188192, 6602703777719207165, 6265915402349571564, 2890093557727619615, 1, 0, 0, 0, 1, 0, 0, 0, 0, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 2899453830169817055, 16816598283634567027, 3068995836555945716, 12171400876639430472, 0, 87, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 3856634821061399044, 17111039960895529319, 4566799539362193550, 16844085186824886612, 3355565787613486613, 17176810829821093448, 1352137470786356802, 17998789203926124900, 16786513982140394745, 18361321143200586840, 13282415620666686464, 8228901733828007087, 1, 0, 0, 0, 1, 0, 0, 0, 0, 7778838127124972351, 10159506934852280087, 12722539778584946047, 2132149890053205639, 18102156639692354752, 8649394742436479221, 4276590013765363445, 12997989410109585404, 9529430557140348691, 7606828505864121076, 17598459768503654580, 7777709650181082785, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8194007358744550661, 306611979519524457, 10528705374691960700, 319508112022086105, 10482736583082303606, 10309893049243492470, 12917037695721108312, 6248886070590142047, 6486447845183860200, 17251231915071761348, 16404214944354933870, 13204576128129345952, 1, 0, 0, 0, 1, 0, 8699431654038775343, 37936723307283666, 8146588049311051327, 4530941775236136548, 16738435301601374688, 11359785745622903572, 15791382447019722900, 13379247305879358010, 13936169968399350974, 3508629571016445401, 5950565638198879243, 3988632325321614173, 14773776822077474877, 3784342517219646416, 16429780078047820450, 1, 0, 0, 0, 1, 0, 3424975013671552621, 14815277144979047633, 8209627855274846837, 8295342049297215958, 15269992241592835077, 6438591265885629379, 13730717627697535913, 7787232003492715877, 9621206971434531853, 11223875126467095457, 6436756480486800604, 5377264485189635000, 5482319143373695788, 12350937431324290559, 6389531569788570962, 1, 0, 0, 0, 1, 0, 4990332689225356878, 13089030850077380376, 2471524608045276896, 17139358263655017093, 18165060065290469256, 9446121688110358951, 977359788342611310, 16707128933418187061, 16621781274459601592, 7539458736435553900, 13565999334907596759, 1167963651906558177, 801268771425181312, 6228026786881094671, 1048304545096961231, 1, 0, 0, 0, 1, 0, 9535941747246118180, 6758632961600419030, 13740260214376027515, 1405920164415583051, 14920069431652436384, 17439962485710135044, 417887867370226026, 16088228384974311599, 3710261815002798848, 137273432282796342, 5851059442771612226, 15136621875351529272, 15702932139541150313, 8779333934582463234, 9538098266673672038, 1, 0, 0, 0, 1, 0, 8185802697182936648, 17682692702261395783, 2219736463483295590, 10757593865239447517, 1468754948270208383, 13746393160610271679, 6189648269704333551, 6527095477330083457, 289679669419955019, 9046656083049426920, 3573808134852581123, 16538711805104705524, 17122129810962376462, 2926585958971102942, 10107422984112301769, 1, 0, 0, 0, 1, 0, 12705492336505225259, 715424026399539381, 2549298692279483128, 1219968445867773701, 12911638424059178129, 5048199682718381540, 1535097268869899419, 17260972721034176516, 15413356179839910737, 13476986835056197648, 7470895530577539713, 8398414302064166980, 16518486341402545799, 70008875869363926, 10307933594111103714, 1, 0, 0, 0, 1, 0, 450024681039430413, 17110346123676092707, 503623504778112596, 1520822994009218024, 3997469258016952350, 18365130032920199069, 7964473389350786015, 9071675461601628413, 4719657860691726363, 2082610983791917873, 13643709933077212296, 16696777778525571691, 5937099920759461106, 17798517164851075633, 7519473231185213307, 1, 0, 0, 0, 1, 0, 17931731304193244959, 0, 0, 11156145431451224746, 9448001553779471302, 11663814486624427477, 8105139713546092506, 996052311483623897, 18356245393792640289, 11128269591471854109, 3650549423833220560, 10102060878284432657, 6999065210900455588, 9632395034475899386, 10920626017129534091, 1, 0, 0, 0, 1, 0, 0, 0, 0, 17063798001278894185, 2357038714604006367, 10530018908095235062, 8857952782846291740, 17150424826448967994, 8501298665346388356, 15857100073185553560, 2606143849991065668, 1709380969765926542, 7242804148613591485, 10415598312293556776, 3315485625242432418, 1, 0, 0, 0, 1, 0, 0, 0, 0, 6980956723582544769, 17736580895885199098, 10473837116748539797, 5495612785159148096, 8023377746099375924, 15245498074053387423, 15841461147440760970, 14724882291190243897, 13636145514567347966, 11793730383025693858, 9279036588745121360, 3956126734745245173, 1, 0, 0, 0, 1, 0, 0, 0, 0, 14149948942544104911, 13474931984659215558, 7610362789910399421, 10137886774563078569, 110488320743199774, 6631727638469492070, 9820426758070632911, 11785623090416681374, 2883529595690350478, 8282658300559339579, 1247473541961741200, 12265308465311135280, 1, 0, 0, 0, 1, 0, 0, 0, 0, 8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090, 18295472245208082500, 5861891236755868697, 15427470125368014171, 5623455816162216176, 11335643519415282023, 11551270937114235273, 11863835819310119404, 14266241634618616211, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], range_checker_cols: [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2187, 4374, 6561, 8748, 10935, 13122, 15309, 17496, 19683, 21870, 24057, 26244, 28431, 30618, 32805, 34992, 37179, 39366, 41553, 43740, 45927, 48114, 50301, 52488, 54675, 56862, 59049, 61236, 63423, 64152, 64881, 65124, 65367, 65448, 65529, 65532, 65535, 65535]], num_rows: 128 }, last_program_row: RowIndex(12) }, program_info: ProgramInfo { program_hash: Word([8390006645619712562, 1693808462939001829, 16098741705486284700, 15555841994343850090]), kernel: Kernel([Word([13219816384148928727, 13736132481315974302, 7221098028825620478, 15161595578319487008])]) }, stack_outputs: StackOutputs { elements: [1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, precompile_requests: [], final_precompile_transcript: PrecompileTranscript { state: Word([0, 0, 0, 0]) }, trace_len_summary: TraceLenSummary { main_trace_len: 13, range_trace_len: 39, chiplets_trace_len: ChipletsLengths { hash_chiplet_len: 64, bitwise_chiplet_len: 0, memory_chiplet_len: 0, ace_chiplet_len: 0, kernel_rom_len: 1 } } } diff --git a/processor/src/trace/parallel/tests.rs b/processor/src/trace/parallel/tests.rs index 800d848203..6de9817704 100644 --- a/processor/src/trace/parallel/tests.rs +++ b/processor/src/trace/parallel/tests.rs @@ -1,13 +1,18 @@ use alloc::{string::String, sync::Arc}; -use miden_air::trace::{ - AUX_TRACE_RAND_CHALLENGES, DECODER_TRACE_OFFSET, - chiplets::hasher::HASH_CYCLE_LEN, - decoder::{HASHER_STATE_OFFSET, NUM_OP_BITS, OP_BITS_OFFSET}, +use miden_air::{ + ProcessorAir, + lookup::build_logup_aux_trace, + trace::{ + DECODER_TRACE_OFFSET, + chiplets::hasher::HASH_CYCLE_LEN, + decoder::{HASHER_STATE_OFFSET, NUM_OP_BITS, OP_BITS_OFFSET}, + }, }; use miden_core::{ Felt, Word, events::EventId, + field::QuadFelt, mast::{ BasicBlockNodeBuilder, CallNodeBuilder, DynNodeBuilder, ExternalNodeBuilder, JoinNodeBuilder, LoopNodeBuilder, MastForest, MastForestContributor, MastNodeExt, @@ -409,21 +414,6 @@ fn test_trace_generation_at_fragment_boundaries( trace_from_single_fragment.final_precompile_transcript, ); - // Verify aux trace columns match. - let rand_elements = rand_array::(); - let aux_from_fragments = trace_from_fragments.build_aux_trace(&rand_elements).unwrap(); - let aux_from_single_fragment = - trace_from_single_fragment.build_aux_trace(&rand_elements).unwrap(); - let aux_from_fragments = aux_from_fragments - .columns() - .map(<[miden_core::Felt]>::to_vec) - .collect::>(); - let aux_from_single_fragment = aux_from_single_fragment - .columns() - .map(<[miden_core::Felt]>::to_vec) - .collect::>(); - assert_eq!(aux_from_fragments, aux_from_single_fragment,); - // Compare deterministic traces as a compact sanity check and to keep the snapshot stable. assert_eq!( format!("{:?}", DeterministicTrace(&trace_from_fragments)), @@ -431,6 +421,26 @@ fn test_trace_generation_at_fragment_boundaries( "Deterministic trace mismatch between fragments and single fragment" ); + // Build the LogUp aux trace from each main trace under identical random challenges and + // verify every column matches row-for-row. Catches fragment-boundary nondeterminism in + // lookup collection that `DeterministicTrace` (main-trace only) would miss. + let raw = rand_array::(); + let challenges = [QuadFelt::new([raw[0], raw[1]]), QuadFelt::new([raw[2], raw[3]])]; + let main_from_fragments = trace_from_fragments.main_trace().to_row_major(); + let main_from_single = trace_from_single_fragment.main_trace().to_row_major(); + let (aux_from_fragments, committed_from_fragments) = + build_logup_aux_trace(&ProcessorAir, &main_from_fragments, &challenges); + let (aux_from_single, committed_from_single) = + build_logup_aux_trace(&ProcessorAir, &main_from_single, &challenges); + assert_eq!( + aux_from_fragments.values, aux_from_single.values, + "LogUp aux trace mismatch between fragments and single fragment" + ); + assert_eq!( + committed_from_fragments, committed_from_single, + "LogUp committed finals mismatch between fragments and single fragment" + ); + // Snapshot testing to ensure that future changes don't unexpectedly change the trace. // We use DeterministicTrace to produce stable Debug output, since ExecutionTrace contains // a MerkleStore backed by HashMap whose iteration order is non-deterministic. diff --git a/processor/src/trace/parallel/tracer/mod.rs b/processor/src/trace/parallel/tracer/mod.rs index a69f496a6a..e58a415f0a 100644 --- a/processor/src/trace/parallel/tracer/mod.rs +++ b/processor/src/trace/parallel/tracer/mod.rs @@ -5,7 +5,7 @@ use miden_core::program::MIN_STACK_DEPTH; use super::{ super::{ - decoder::block_stack::ExecutionContextInfo, + block_stack::ExecutionContextInfo, trace_state::{ BlockAddressReplay, BlockStackReplay, DecoderState, StackState, SystemState, }, diff --git a/processor/src/trace/range/aux_trace.rs b/processor/src/trace/range/aux_trace.rs deleted file mode 100644 index 7f76797ff8..0000000000 --- a/processor/src/trace/range/aux_trace.rs +++ /dev/null @@ -1,197 +0,0 @@ -use alloc::{collections::BTreeMap, vec::Vec}; -use core::mem::MaybeUninit; - -use miden_air::trace::{ - Challenges, MainTrace, RowIndex, - bus_types::RANGE_CHECK_BUS, - range::{M_COL_IDX, V_COL_IDX}, -}; - -use crate::{ - Felt, ZERO, - field::ExtensionField, - utils::{assume_init_vec, uninit_vector}, -}; - -// AUXILIARY TRACE BUILDER -// ================================================================================================ - -/// Describes how to construct the execution trace of columns related to the range checker in the -/// auxiliary segment of the trace. These are used in multiset checks. -#[derive(Debug, Clone)] -pub struct AuxTraceBuilder { - /// A list of the unique values for which range checks are performed. - lookup_values: Vec, - /// Range check lookups performed by all user operations, grouped and sorted by the clock cycle - /// at which they are requested. - cycle_lookups: BTreeMap>, - // The index of the first row of Range Checker's trace when the padded rows end and values to - // be range checked start. - values_start: usize, -} - -impl AuxTraceBuilder { - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - pub fn new( - lookup_values: Vec, - cycle_lookups: BTreeMap>, - values_start: usize, - ) -> Self { - Self { - lookup_values, - cycle_lookups, - values_start, - } - } - - // AUX COLUMN BUILDERS - // -------------------------------------------------------------------------------------------- - - /// Builds and returns range checker auxiliary trace columns. Currently this consists of one - /// column: - /// - `b_range`: ensures that the range checks performed by the Range Checker match those - /// requested by the Stack and Memory processors. - pub fn build_aux_columns>( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - ) -> Vec> { - let b_range = self.build_aux_col_b_range(main_trace, challenges); - vec![b_range] - } - - /// Builds the execution trace of the range check `b_range` column which ensure that the range - /// check lookups performed by user operations match those executed by the Range Checker. - fn build_aux_col_b_range>( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - ) -> Vec { - // run batch inversion on the lookup values - let divisors = get_divisors(&self.lookup_values, challenges.bus_prefix[RANGE_CHECK_BUS]); - - // allocate memory for the running sum column and set the initial value to ZERO - let mut b_range: Vec> = uninit_vector(main_trace.num_rows()); - b_range[0].write(E::ZERO); - - // keep track of the last updated row in the `b_range` running sum column. `b_range` is - // filled with result values that are added to the next row after the operation's execution. - let mut b_range_idx = 0_usize; - // track the current running sum value to avoid reading from MaybeUninit - let mut current_value = E::ZERO; - - // the first half of the trace only includes values from the operations. - for (clk, range_checks) in - self.cycle_lookups.range(RowIndex::from(0)..RowIndex::from(self.values_start)) - { - let clk: usize = (*clk).into(); - - // if we skipped some cycles since the last update was processed, values in the last - // updated row should be copied over until the current cycle. - if b_range_idx < clk { - b_range[(b_range_idx + 1)..=clk].fill(MaybeUninit::new(current_value)); - } - - // move the column pointer to the next row. - b_range_idx = clk + 1; - - let mut new_value = current_value; - // include the operation lookups - for lookup in range_checks.iter() { - new_value -= divisors[*lookup as usize]; - } - b_range[b_range_idx].write(new_value); - current_value = new_value; - } - - // if we skipped some cycles since the last update was processed, values in the last - // updated row should by copied over until the current cycle. - if b_range_idx < self.values_start { - b_range[(b_range_idx + 1)..=self.values_start].fill(MaybeUninit::new(current_value)); - } - - // after the padded section of the range checker table, include the lookup value specified - // by the range checker into the running sum at each step, and remove lookups from user ops - // at any step where user ops were executed. - // - // Note: we take `num_rows - 1` because the loop writes to `b_range[row_idx + 1]`, so we - // need to stop one row early to avoid writing past the end of the array. - for (row_idx, (multiplicity, lookup)) in main_trace - .get_column(M_COL_IDX) - .iter() - .zip(main_trace.get_column(V_COL_IDX).iter()) - .enumerate() - .take(main_trace.num_rows() - 1) - .skip(self.values_start) - { - b_range_idx = row_idx + 1; - - let mut new_value = current_value; - if *multiplicity != ZERO { - // add the value in the range checker: multiplicity / (alpha + lookup) - new_value = - current_value + divisors[lookup.as_canonical_u64() as usize] * *multiplicity; - } - - // subtract the range checks requested by operations - if let Some(range_checks) = self.cycle_lookups.get(&(row_idx as u32).into()) { - for lookup in range_checks.iter() { - new_value -= divisors[*lookup as usize]; - } - } - - b_range[b_range_idx].write(new_value); - current_value = new_value; - } - - // At this point, b_range accounts for all cycle-based range check lookups (deltas from - // memory and stack) matched against the range checker table. The range table also contains - // entries for memory address decomposition (w0, w1, 4*w1) whose LogUp requests are on the - // wiring bus, not b_range. So b_range may have a non-zero residual equal to the sum of - // w0/w1/4*w1 LogUp fractions. The combined balance (b_range + v_wiring) is checked via - // reduced_aux_values. - - if b_range_idx < b_range.len() - 1 { - b_range[(b_range_idx + 1)..].fill(MaybeUninit::new(current_value)); - } - - // all elements are now initialized - unsafe { assume_init_vec(b_range) } - } -} - -/// Runs batch inversion on all range check lookup values and returns a map which maps each value -/// to the divisor used for including it in the LogUp lookup. In other words, the map contains -/// mappings of x to 1/(alpha + x). -fn get_divisors>(lookup_values: &[u16], alpha: E) -> Vec { - // run batch inversion on the lookup values - let mut values: Vec> = uninit_vector(lookup_values.len()); - let mut inv_values: Vec> = uninit_vector(lookup_values.len()); - - let mut acc = E::ONE; - for (i, (value, inv_value)) in values.iter_mut().zip(inv_values.iter_mut()).enumerate() { - inv_value.write(acc); - let v = alpha + E::from_u16(lookup_values[i]); - value.write(v); - acc *= v; - } - - // all elements are now initialized - let values = unsafe { assume_init_vec(values) }; - let mut inv_values = unsafe { assume_init_vec(inv_values) }; - - // invert the accumulated product - acc = acc.inverse(); - - // multiply the accumulated product by the original values to compute the inverses, then - // build a map of inverses for the lookup values - let mut log_values = vec![E::ZERO; 1 << 16]; - for i in (0..lookup_values.len()).rev() { - inv_values[i] *= acc; - acc *= values[i]; - log_values[lookup_values[i] as usize] = inv_values[i]; - } - - log_values -} diff --git a/processor/src/trace/range/mod.rs b/processor/src/trace/range/mod.rs index b9c19f9992..8f9bf06a5d 100644 --- a/processor/src/trace/range/mod.rs +++ b/processor/src/trace/range/mod.rs @@ -9,9 +9,6 @@ use crate::{ utils::{assume_init_vec, uninit_vector}, }; -mod aux_trace; -pub use aux_trace::AuxTraceBuilder; - #[cfg(test)] mod tests; @@ -20,7 +17,6 @@ mod tests; pub struct RangeCheckTrace { pub(crate) trace: [Vec; RANGE_CHECK_TRACE_WIDTH], - pub(crate) aux_builder: AuxTraceBuilder, } // RANGE CHECKER @@ -153,14 +149,7 @@ impl RangeChecker { let [t0, t1] = trace; let trace = unsafe { [assume_init_vec(t0), assume_init_vec(t1)] }; - RangeCheckTrace { - trace, - aux_builder: AuxTraceBuilder::new( - self.lookups.keys().cloned().collect(), - self.cycle_lookups, - num_padding_rows, - ), - } + RangeCheckTrace { trace } } // PUBLIC ACCESSORS diff --git a/processor/src/trace/range/tests.rs b/processor/src/trace/range/tests.rs index e90b8c91b6..36d054f236 100644 --- a/processor/src/trace/range/tests.rs +++ b/processor/src/trace/range/tests.rs @@ -19,7 +19,7 @@ fn range_checks() { checker.add_value(value.as_canonical_u64() as u16); } - let RangeCheckTrace { trace, aux_builder: _ } = checker.into_trace(64); + let RangeCheckTrace { trace } = checker.into_trace(64); validate_trace(&trace, &values); // skip the padded rows @@ -61,7 +61,7 @@ fn range_checks_rand() { } let trace_len = checker.trace_len().next_power_of_two(); - let RangeCheckTrace { trace, aux_builder: _ } = checker.into_trace(trace_len); + let RangeCheckTrace { trace } = checker.into_trace(trace_len); validate_trace(&trace, &values); } diff --git a/processor/src/trace/stack/aux_trace.rs b/processor/src/trace/stack/aux_trace.rs deleted file mode 100644 index c8fb40f813..0000000000 --- a/processor/src/trace/stack/aux_trace.rs +++ /dev/null @@ -1,117 +0,0 @@ -use alloc::vec::Vec; - -use miden_air::trace::{Challenges, MainTrace, RowIndex, bus_types::STACK_OVERFLOW_TABLE}; -use miden_core::{field::ExtensionField, operations::opcodes}; - -use super::Felt; -use crate::{debug::BusDebugger, trace::AuxColumnBuilder}; - -// AUXILIARY TRACE BUILDER -// ================================================================================================ - -/// Describes how to construct execution traces of stack-related auxiliary trace segment columns -/// (used in multiset checks). -#[derive(Debug, Clone)] -pub struct AuxTraceBuilder; - -impl AuxTraceBuilder { - /// Builds and returns stack auxiliary trace columns. Currently this consists of a single - /// column p1 describing states of the stack overflow table. - pub fn build_aux_columns>( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - ) -> Vec> { - let p1 = self.build_aux_column(main_trace, challenges); - - debug_assert_eq!(*p1.last().unwrap(), E::ONE); - vec![p1] - } -} - -impl> AuxColumnBuilder for AuxTraceBuilder { - /// Removes a row from the stack overflow table. - fn get_requests_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - i: RowIndex, - _debugger: &mut BusDebugger, - ) -> E { - let is_left_shift = main_trace.is_left_shift(i); - let is_dyncall = main_trace.get_op_code(i) == Felt::from_u8(opcodes::DYNCALL); - let is_non_empty_overflow = main_trace.is_non_empty_overflow(i); - - if is_left_shift && is_non_empty_overflow { - let b1 = main_trace.parent_overflow_address(i); - let s15_prime = main_trace.stack_element(15, i + 1); - let b1_prime = main_trace.parent_overflow_address(i + 1); - - OverflowTableRow::new(b1, s15_prime, b1_prime).to_value(challenges) - } else if is_dyncall && is_non_empty_overflow { - let b1 = main_trace.parent_overflow_address(i); - let s15_prime = main_trace.stack_element(15, i + 1); - let b1_prime = main_trace.decoder_hasher_state_element(5, i); - - OverflowTableRow::new(b1, s15_prime, b1_prime).to_value(challenges) - } else { - E::ONE - } - } - - /// Adds a row to the stack overflow table. - fn get_responses_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - i: RowIndex, - _debugger: &mut BusDebugger, - ) -> E { - let is_right_shift = main_trace.is_right_shift(i); - - if is_right_shift { - let k0 = main_trace.clk(i); - let s15 = main_trace.stack_element(15, i); - let b1 = main_trace.parent_overflow_address(i); - - let row = OverflowTableRow::new(k0, s15, b1); - row.to_value(challenges) - } else { - E::ONE - } - } - - #[cfg(any(test, feature = "bus-debugger"))] - fn enforce_bus_balance(&self) -> bool { - true - } -} - -// OVERFLOW STACK ROW -// ================================================================================================ - -/// A single row in the stack overflow table. Each row contains the following values: -/// - The value of the stack item pushed into the overflow stack. -/// - The clock cycle at which the stack item was pushed into the overflow stack. -/// - The clock cycle of the value which was at the top of the overflow stack when this value was -/// pushed onto it. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OverflowTableRow { - val: Felt, - clk: Felt, - prev: Felt, -} - -impl OverflowTableRow { - pub fn new(clk: Felt, val: Felt, prev: Felt) -> Self { - Self { val, clk, prev } - } -} - -impl OverflowTableRow { - /// Reduces this row to a single field element in the field specified by E. This requires - /// at least 4 alpha values. - pub fn to_value>(&self, challenges: &Challenges) -> E { - challenges.encode(STACK_OVERFLOW_TABLE, [self.clk, self.val, self.prev]) - } -} diff --git a/processor/src/trace/stack/mod.rs b/processor/src/trace/stack/mod.rs index ef76a243d7..87ce4a96a4 100644 --- a/processor/src/trace/stack/mod.rs +++ b/processor/src/trace/stack/mod.rs @@ -2,8 +2,3 @@ use super::{Felt, ZERO}; mod overflow; pub(crate) use overflow::OverflowTable; - -mod aux_trace; -pub use aux_trace::AuxTraceBuilder; -#[cfg(test)] -pub(crate) use aux_trace::OverflowTableRow; diff --git a/processor/src/trace/tests/chiplets/bitwise.rs b/processor/src/trace/tests/chiplets/bitwise.rs index ce411b19b7..ec87f4b769 100644 --- a/processor/src/trace/tests/chiplets/bitwise.rs +++ b/processor/src/trace/tests/chiplets/bitwise.rs @@ -1,211 +1,135 @@ -use miden_air::trace::{ - Challenges, RowIndex, - chiplets::{ - BITWISE_A_COL_IDX, BITWISE_B_COL_IDX, BITWISE_OUTPUT_COL_IDX, BITWISE_TRACE_OFFSET, - bitwise::{BITWISE_AND, BITWISE_AND_LABEL, BITWISE_XOR, BITWISE_XOR_LABEL, OP_CYCLE_LEN}, - }, +//! Bitwise chiplet bus test. +//! +//! Runs a program with `U32and` + `U32xor` operations and verifies that every bitwise request +//! row emits the expected `BitwiseMsg` on the chiplet-requests side AND that every bitwise +//! chiplet cycle-end row emits the matching response on the chiplet-responses side. +//! +//! Column-blind by design: the subset matcher in `lookup_harness` compares `(mult, denom)` +//! pairs regardless of which aux column the framework routes them onto. + +use alloc::vec::Vec; + +use miden_air::{logup::BitwiseMsg, trace::chiplets::BITWISE_SELECTOR_COL_IDX}; +use miden_core::{ + Felt, + operations::{Operation, opcodes}, }; -use miden_core::field::Field; -use super::{ - AUX_TRACE_RAND_CHALLENGES, CHIPLETS_BUS_AUX_TRACE_OFFSET, ExecutionTrace, Felt, HASH_CYCLE_LEN, - ONE, Operation, build_trace_from_ops, rand_array, rand_value, +use super::super::{ + build_trace_from_ops, + lookup_harness::{Expectations, InteractionLog}, }; +use crate::RowIndex; + +/// Period of the bitwise chiplet cycle. The response fires on the last row of each cycle. +const BITWISE_CYCLE_LEN: usize = 8; -/// Tests the generation of the `b_chip` bus column when only bitwise lookups are included. It -/// ensures that trace generation is correct when all of the following are true. -/// -/// - All possible bitwise operations are called by the stack. -/// - Some requests from the Stack and responses from the Bitwise chiplet occur at the same cycle. -/// -/// Note: Communication with the Hash chiplet is also required, due to the span block decoding, but -/// for this test we set those values explicitly, enforcing only that the same initial and final -/// values are requested & provided. #[test] -fn b_chip_trace_bitwise() { - let a = rand_value::(); - let b = rand_value::(); - let stack = [a as u64, b as u64]; - let operations = vec![ - Operation::U32and, - Operation::Push(Felt::from_u32(a)), - Operation::Push(Felt::from_u32(b)), +fn bitwise_chiplet_bus_emits_per_request_row() { + // Two distinct operand pairs for the two AND requests so per-row denominators differ: + // a copy-paste bug attaching an expectation to the wrong row can't pass the subset check. + let a1: u32 = 0x1111_2222; + let b1: u32 = 0x3333_4444; + let a2: u32 = 0x5555_6666; + let b2: u32 = 0x7777_8888; + let a3: u32 = 0xdead_beef; + let b3: u32 = 0x1234_5678; + + // `Drop` between ops isn't strictly required under subset semantics (extra pushes are + // ignored), but it keeps the stack overflow table from growing and matches sibling tests. + let ops = vec![ + Operation::Push(Felt::from_u32(a1)), + Operation::Push(Felt::from_u32(b1)), Operation::U32and, - // Add 8 padding operations so that U32xor is requested by the stack in the same cycle when - // U32and is provided by the Bitwise chiplet. - Operation::Pad, - Operation::Pad, - Operation::Pad, - Operation::Pad, - Operation::Drop, - Operation::Drop, Operation::Drop, + Operation::Push(Felt::from_u32(a2)), + Operation::Push(Felt::from_u32(b2)), + Operation::U32and, Operation::Drop, - Operation::Push(Felt::from_u32(a)), - Operation::Push(Felt::from_u32(b)), + Operation::Push(Felt::from_u32(a3)), + Operation::Push(Felt::from_u32(b3)), Operation::U32xor, - // Drop 4 values to empty the stack's overflow table. - Operation::Drop, - Operation::Drop, - Operation::Drop, Operation::Drop, ]; - let trace = build_trace_from_ops(operations, &stack); - - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let b_chip = aux_columns.get_column(CHIPLETS_BUS_AUX_TRACE_OFFSET); - let challenges = Challenges::::new(challenges[0], challenges[1]); - - assert_eq!(trace.length(), b_chip.len()); - assert_eq!(ONE, b_chip[0]); - - // At cycle 0 the span hash initialization is requested from the decoder and provided by the - // hasher (both at main trace row 0), so they cancel and b_chip stays ONE. - assert_eq!(ONE, b_chip[1]); - - // At row 1, two things happen simultaneously: - // - The hasher provides the HOUT response (span hash result) at controller output row 1 - // - The decoder sends the first U32and bitwise request (user op at cycle 1) - // We treat the HOUT response as a black box and extract it from the bus column. - let bitwise_1_value = build_expected_bitwise( - &challenges, - BITWISE_AND_LABEL, - Felt::from_u32(a), - Felt::from_u32(b), - Felt::from_u32(a & b), - ); - // b_chip[2] = ONE * hout_response * bitwise_1.inverse() - // so hout_response = b_chip[2] * bitwise_1 - let hout_response = b_chip[2] * bitwise_1_value; - let mut expected = hout_response * bitwise_1_value.inverse(); - assert_eq!(expected, b_chip[2]); - - // Nothing changes during user operations with no requests to the Chiplets. - for row in 3..5 { - assert_eq!(expected, b_chip[row]); + let trace = build_trace_from_ops(ops, &[]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut exp = Expectations::new(&log); + + // ---- Request side: decoder emits a `-1` push of `BitwiseMsg` at each U32AND/U32XOR row. + // + // Operands are hardcoded (not read back from the trace) so a bug that swaps `s0`/`s1` in + // the request emitter would produce a message with the `a`/`b` fields flipped and fail + // the subset check. The stack pushes `a` then `b`, so `b` ends up on top (`s0`) and `a` + // sits at slot 1 (`s1`); `BitwiseMsg::and(s0, s1, c)` = `BitwiseMsg::and(b, a, a & b)`. + let and_expected = [(a1, b1, a1 & b1), (a2, b2, a2 & b2)]; + let xor_expected = [(a3, b3, a3 ^ b3)]; + + let mut and_rows: Vec = Vec::new(); + let mut xor_rows: Vec = Vec::new(); + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let op = main.get_op_code(idx).as_canonical_u64(); + if op == opcodes::U32AND as u64 { + and_rows.push(idx); + } else if op == opcodes::U32XOR as u64 { + xor_rows.push(idx); + } } - - // The second bitwise request from the stack is sent when the `U32and` operation is executed at - // cycle 4, so the request is included in the next row. - let value = build_expected_bitwise( - &challenges, - BITWISE_AND_LABEL, - Felt::from_u32(b), - Felt::from_u32(a), - Felt::from_u32(a & b), + assert_eq!( + and_rows.len(), + and_expected.len(), + "request cardinality guardrail: expected {} U32AND rows, found {}", + and_expected.len(), + and_rows.len(), ); - expected *= value.inverse(); - assert_eq!(expected, b_chip[5]); - - // Nothing changes during user operations with no requests to the Chiplets. - for row in 6..16 { - assert_eq!(expected, b_chip[row]); - } - - // The third bitwise request from the stack is sent when the `U32xor` operation is executed at - // cycle 15, so the request is included in the next row. - let value = build_expected_bitwise( - &challenges, - BITWISE_XOR_LABEL, - Felt::from_u32(b), - Felt::from_u32(a), - Felt::from_u32(a ^ b), + assert_eq!( + xor_rows.len(), + xor_expected.len(), + "request cardinality guardrail: expected {} U32XOR rows, found {}", + xor_expected.len(), + xor_rows.len(), ); - expected *= value.inverse(); - assert_eq!(expected, b_chip[16]); - // Nothing changes until the decoder requests the result of the `SPAN` hash at cycle 21. - for row in 17..22 { - assert_eq!(expected, b_chip[row]); + for (&row, &(a, b, c)) in and_rows.iter().zip(and_expected.iter()) { + let msg = BitwiseMsg::and(Felt::from_u32(b), Felt::from_u32(a), Felt::from_u32(c)); + exp.remove(usize::from(row), &msg); } - - // At cycle 21 the decoder requests the span hash result (END). This should cancel with the - // HOUT response that was provided earlier at row 1. - assert_ne!(expected, b_chip[22]); - let span_end_request = b_chip[22] * b_chip[21].inverse(); - expected *= span_end_request; - assert_eq!(expected, b_chip[22]); - // Verify the HOUT response and END request cancel. - assert_eq!(hout_response * span_end_request, ONE); - - // The hasher trace in the dispatch/compute split has: - // - Hasher controller rows 0-1 (already processed above) - // - Hasher padding rows 2-31 (selectors [0,1,0] = neither input nor output, no bus activity) - // - Hasher permutation segment rows 32-63 (no bus activity on chiplets bus) - // So nothing changes until the bitwise segment. - let hasher_trace_len = 2 * HASH_CYCLE_LEN; // controller(32 padded) + perm(32) - for row in 23..hasher_trace_len { - assert_eq!(expected, b_chip[row]); - } - - // Bitwise responses are provided during the bitwise segment, which starts after the hasher. - let response_1_row = hasher_trace_len + OP_CYCLE_LEN; - let response_2_row = response_1_row + OP_CYCLE_LEN; - let response_3_row = response_2_row + OP_CYCLE_LEN; - - // Nothing changes until the Bitwise chiplet responds. - for row in hasher_trace_len..response_1_row { - assert_eq!(expected, b_chip[row]); - } - - // At the end of the first bitwise cycle, the response for `U32and` is provided. - expected *= build_expected_bitwise_from_trace(&trace, &challenges, (response_1_row - 1).into()); - assert_eq!(expected, b_chip[response_1_row]); - - // At the end of the next bitwise cycle, the response for `U32and` is provided. - for row in (response_1_row + 1)..response_2_row { - assert_eq!(expected, b_chip[row]); - } - expected *= build_expected_bitwise_from_trace(&trace, &challenges, (response_2_row - 1).into()); - assert_eq!(expected, b_chip[response_2_row]); - - // Nothing changes until the next time the Bitwise chiplet responds. - for row in (response_2_row + 1)..response_3_row { - assert_eq!(expected, b_chip[row]); + for (&row, &(a, b, c)) in xor_rows.iter().zip(xor_expected.iter()) { + let msg = BitwiseMsg::xor(Felt::from_u32(b), Felt::from_u32(a), Felt::from_u32(c)); + exp.remove(usize::from(row), &msg); } - // At the end of the next bitwise cycle, the response for `U32xor` is provided. - expected *= build_expected_bitwise_from_trace(&trace, &challenges, (response_3_row - 1).into()); - assert_eq!(expected, b_chip[response_3_row]); - - // The value in b_chip should be ONE now and for the rest of the trace. - for row in response_3_row..trace.length() { - assert_eq!(ONE, b_chip[row]); + // ---- Response side: each bitwise-chiplet cycle-end row emits `+1 × BitwiseMsg`. + // + // Cycle-end = `row % BITWISE_CYCLE_LEN == BITWISE_CYCLE_LEN - 1` (the periodic `k_transition` + // column is `0` on the last row of every 8-row cycle, starting from trace row 0). The bitwise + // chiplet segment starts at a multiple of `HASH_CYCLE_LEN = 16`, which is a multiple of 8, + // so this alignment condition holds across the whole trace. + let mut response_rows_seen = 0usize; + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + if !main.is_bitwise_row(idx) { + continue; + } + if row % BITWISE_CYCLE_LEN != BITWISE_CYCLE_LEN - 1 { + continue; + } + response_rows_seen += 1; + + let op = main.get(idx, BITWISE_SELECTOR_COL_IDX); + let a = main.chiplet_bitwise_a(idx); + let b = main.chiplet_bitwise_b(idx); + let z = main.chiplet_bitwise_z(idx); + exp.add(row, &BitwiseMsg { op, a, b, result: z }); } -} - -// TEST HELPERS -// ================================================================================================ - -fn build_expected_bitwise( - challenges: &Challenges, - label: Felt, - s0: Felt, - s1: Felt, - result: Felt, -) -> Felt { - challenges.encode(miden_air::trace::bus_types::CHIPLETS_BUS, [label, s0, s1, result]) -} - -fn build_expected_bitwise_from_trace( - trace: &ExecutionTrace, - challenges: &Challenges, - row: RowIndex, -) -> Felt { - let selector = trace.main_trace.get_column(BITWISE_TRACE_OFFSET)[row]; - - let op_id = if selector == BITWISE_AND { - BITWISE_AND_LABEL - } else if selector == BITWISE_XOR { - BITWISE_XOR_LABEL - } else { - panic!("Execution trace contains an invalid bitwise operation.") - }; - - let a = trace.main_trace.get_column(BITWISE_A_COL_IDX)[row]; - let b = trace.main_trace.get_column(BITWISE_B_COL_IDX)[row]; - let output = trace.main_trace.get_column(BITWISE_OUTPUT_COL_IDX)[row]; + let expected_responses = and_expected.len() + xor_expected.len(); + assert_eq!( + response_rows_seen, expected_responses, + "response cardinality guardrail: expected {expected_responses} cycle-end bitwise rows, \ + found {response_rows_seen}", + ); - build_expected_bitwise(challenges, op_id, a, b, output) + log.assert_contains(&exp); } diff --git a/processor/src/trace/tests/chiplets/hasher.rs b/processor/src/trace/tests/chiplets/hasher.rs index 5984a7d151..84760ee4ec 100644 --- a/processor/src/trace/tests/chiplets/hasher.rs +++ b/processor/src/trace/tests/chiplets/hasher.rs @@ -1,204 +1,416 @@ +//! Hasher-chiplet bus tests. +//! +//! For each of the main hasher scenarios (SPAN/END control block, RESPAN, SPLIT merge, HPERM, +//! LOGPRECOMPILE, MPVERIFY, MRUPDATE) the test registers the decoder-side `remove` requests and +//! the chiplet-side `add` responses it expects to see, then lets +//! [`InteractionLog::assert_contains`] confirm every one of them fires somewhere in the trace. +//! +//! Because request and response messages share a `bus_prefix` and the same payload shape, +//! an add at a controller row and a remove at the matching decoder row produce the same +//! encoded denominator with opposite multiplicities — which is what makes the bus balance. +//! We don't need to pin the running-product walk row-by-row the way the legacy +//! `verify_b_chip_step_by_step` did; the subset matcher verifies each claimed interaction +//! lands, and their pairing is an algebraic consequence. +//! +//! Each test pairs the `assert_contains` call with explicit request/response-count guardrails +//! so a silent-pass bug (e.g. the subset matcher ignoring a whole category of expectations +//! because nothing was registered) is caught structurally, not just by shape. + use alloc::vec::Vec; -use miden_air::trace::{ - Challenges, RowIndex, bus_message, bus_types, - chiplets::hasher::{ - CONTROLLER_ROWS_PER_PERM_FELT, DIGEST_RANGE, HasherState, LINEAR_HASH_LABEL, - MP_VERIFY_LABEL, MR_UPDATE_NEW_LABEL, MR_UPDATE_OLD_LABEL, RATE_LEN, RETURN_HASH_LABEL, - RETURN_STATE_LABEL, - }, - log_precompile::{ - HELPER_ADDR_IDX, HELPER_CAP_PREV_RANGE, STACK_CAP_NEXT_RANGE, STACK_COMM_RANGE, - STACK_R0_RANGE, STACK_R1_RANGE, STACK_TAG_RANGE, +use miden_air::{ + logup::{HasherMsg, SiblingBit, SiblingMsg}, + trace::{ + MainTrace, + chiplets::hasher::CONTROLLER_ROWS_PER_PERM_FELT, + log_precompile::{ + HELPER_ADDR_IDX, HELPER_CAP_PREV_RANGE, STACK_CAP_NEXT_RANGE, STACK_COMM_RANGE, + STACK_R0_RANGE, STACK_R1_RANGE, STACK_TAG_RANGE, + }, }, }; use miden_core::{ - Word, - crypto::merkle::{MerkleStore, MerkleTree}, - field::Field, + Felt, ONE, Word, ZERO, + crypto::merkle::{MerkleStore, MerkleTree, NodeIndex}, mast::{BasicBlockNodeBuilder, MastForest, MastForestContributor, SplitNodeBuilder}, - operations::opcodes, + operations::{Operation, opcodes}, program::Program, }; use miden_utils_testing::stack; +use rstest::rstest; -use super::{ - AUX_TRACE_RAND_CHALLENGES, AdviceInputs, CHIPLETS_BUS_AUX_TRACE_OFFSET, ExecutionTrace, Felt, - ONE, Operation, ZERO, build_span_with_respan_ops, build_trace_from_ops_with_inputs, - build_trace_from_program, rand_array, +use super::super::{ + build_trace_from_ops_with_inputs, build_trace_from_program, + lookup_harness::{Expectations, InteractionLog}, }; -use crate::StackInputs; +use crate::{AdviceInputs, RowIndex, StackInputs, trace::utils::build_span_with_respan_ops}; -// TESTS +// RESPONSE-SIDE DISPATCH // ================================================================================================ -/// Tests the generation of the `b_chip` bus column when the hasher only performs a single `SPAN` -/// with one operation batch. +/// Hasher controller response kinds, keyed on the emitter's `(hs0, hs1, hs2, is_boundary)` mux. /// -/// Verifies step-by-step that each decoder request (SPAN, END) and each hasher response -/// (sponge start, digest return) correctly update the bus running product. -#[test] -pub fn b_chip_span() { - let program = { - let mut mast_forest = MastForest::new(); - - let basic_block_id = - BasicBlockNodeBuilder::new(vec![Operation::Add, Operation::Mul], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - mast_forest.make_root(basic_block_id); +/// Shared across every test so each can `match` on the semantic kind instead of re-deriving +/// the selector combinations (`ctrl · hs0 · not_hs1 · not_hs2 · is_boundary`, etc.) by hand. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum HasherResponseKind { + SpongeStart, + SpongeRespan, + MpInput, + MvOldInput, + MuNewInput, + Hout, + Sout, +} - Program::new(mast_forest.into(), basic_block_id) - }; +/// Walk every hasher controller row in `main` and yield a [`HasherResponseKind`] for each row +/// that matches one of the 7 emitter patterns (see `chiplet_responses.rs::emit_chiplet_responses` +/// and `docs/src/design/chiplets/hasher.md`). +/// +/// Controller rows where no response fires (e.g. Merkle tree continuation rows where +/// `is_boundary = 0`) are skipped. +fn hasher_response_rows( + main: &MainTrace, +) -> impl Iterator + '_ { + (0..main.num_rows()).filter_map(move |row| { + let idx = RowIndex::from(row); + if !is_hasher_controller_row(main, idx) { + return None; + } + let hs0 = main.chiplet_selector_1(idx); + let hs1 = main.chiplet_selector_2(idx); + let hs2 = main.chiplet_selector_3(idx); + let is_boundary = main.chiplet_is_boundary(idx); + let kind = if hs0 == ONE && hs1 == ZERO && hs2 == ZERO && is_boundary == ONE { + HasherResponseKind::SpongeStart + } else if hs0 == ONE && hs1 == ZERO && hs2 == ZERO && is_boundary == ZERO { + HasherResponseKind::SpongeRespan + } else if hs0 == ONE && hs1 == ZERO && hs2 == ONE && is_boundary == ONE { + HasherResponseKind::MpInput + } else if hs0 == ONE && hs1 == ONE && hs2 == ZERO && is_boundary == ONE { + HasherResponseKind::MvOldInput + } else if hs0 == ONE && hs1 == ONE && hs2 == ONE && is_boundary == ONE { + HasherResponseKind::MuNewInput + } else if hs0 == ZERO && hs1 == ZERO && hs2 == ZERO { + HasherResponseKind::Hout + } else if hs0 == ZERO && hs1 == ZERO && hs2 == ONE && is_boundary == ONE { + HasherResponseKind::Sout + } else { + return None; + }; + Some((idx, kind)) + }) +} - let trace = build_trace_from_program(&program, &[]); +// TESTS +// ================================================================================================ - let rand_challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&rand_challenges).unwrap(); - let b_chip = aux_columns.get_column(CHIPLETS_BUS_AUX_TRACE_OFFSET); - let challenges = Challenges::new(rand_challenges[0], rand_challenges[1]); +#[test] +fn span_end_hasher_bus() { + let program = single_block_program(vec![Operation::Add, Operation::Mul]); - assert_eq!(trace.length(), b_chip.len()); - assert_eq!(ONE, b_chip[0]); + let trace = build_trace_from_program(&program, &[]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut exp = Expectations::new(&log); + let mut request_count = 0usize; + + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let op = main.get_op_code(idx).as_canonical_u64(); + + if op == opcodes::SPAN as u64 { + let addr_next = main.addr(RowIndex::from(row + 1)); + let rate = rate_from_hasher_state(main, idx); + exp.remove(row, &HasherMsg::control_block(addr_next, &rate, 0)); + request_count += 1; + } else if op == opcodes::END as u64 { + let parent = main.addr(idx) + CONTROLLER_ROWS_PER_PERM_FELT - ONE; + let h = rate_from_hasher_state(main, idx); + let digest: [Felt; 4] = [h[0], h[1], h[2], h[3]]; + exp.remove(row, &HasherMsg::return_hash(parent, digest)); + request_count += 1; + } + } - // Verify the bus running product step-by-step at every row. - verify_b_chip_step_by_step(&trace, &challenges, b_chip); + let mut response_count = 0usize; + for (idx, kind) in hasher_response_rows(main) { + let addr = main.clk(idx) + ONE; + let state = main.chiplet_hasher_state(idx); + match kind { + HasherResponseKind::SpongeStart => { + exp.add(usize::from(idx), &HasherMsg::linear_hash_init(addr, state)); + response_count += 1; + }, + HasherResponseKind::Hout => { + let digest: [Felt; 4] = [state[0], state[1], state[2], state[3]]; + exp.add(usize::from(idx), &HasherMsg::return_hash(addr, digest)); + response_count += 1; + }, + _ => {}, + } + } - assert_bus_balanced(b_chip); + assert_eq!(request_count, 2, "SPAN+END: expected 2 removes (SPAN + END)"); + assert_eq!(response_count, 2, "SPAN+END: expected 2 adds (sponge_start + HOUT)"); + log.assert_contains(&exp); } -/// Tests the generation of the `b_chip` bus column when the hasher only performs a `SPAN` but it -/// includes multiple batches (RESPAN). -/// -/// Verifies step-by-step that SPAN, RESPAN, and END requests are each matched by hasher responses. #[test] -pub fn b_chip_span_with_respan() { - let program = { - let mut mast_forest = MastForest::new(); +fn respan_hasher_bus() { + let (ops, _iv) = build_span_with_respan_ops(); + let program = single_block_program(ops); - let (ops, _) = build_span_with_respan_ops(); - let basic_block_id = BasicBlockNodeBuilder::new(ops, Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - mast_forest.make_root(basic_block_id); - - Program::new(mast_forest.into(), basic_block_id) - }; let trace = build_trace_from_program(&program, &[]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); - let rand_challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&rand_challenges).unwrap(); - let b_chip = aux_columns.get_column(CHIPLETS_BUS_AUX_TRACE_OFFSET); - let challenges = Challenges::new(rand_challenges[0], rand_challenges[1]); + let mut exp = Expectations::new(&log); + let mut respan_request_count = 0usize; - assert_eq!(ONE, b_chip[0]); + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let op = main.get_op_code(idx).as_canonical_u64(); + if op != opcodes::RESPAN as u64 { + continue; + } + let addr_next = main.addr(RowIndex::from(row + 1)); + let rate = rate_from_hasher_state(main, idx); + exp.remove(row, &HasherMsg::absorption(addr_next, rate)); + respan_request_count += 1; + } - verify_b_chip_step_by_step(&trace, &challenges, b_chip); + let mut sponge_respan_count = 0usize; + for (idx, kind) in hasher_response_rows(main) { + if kind != HasherResponseKind::SpongeRespan { + continue; + } + let addr = main.clk(idx) + ONE; + let state = main.chiplet_hasher_state(idx); + let rate: [Felt; 8] = + [state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7]]; + exp.add(usize::from(idx), &HasherMsg::absorption(addr, rate)); + sponge_respan_count += 1; + } - assert_bus_balanced(b_chip); + assert!(respan_request_count > 0, "multi-batch span should emit at least one RESPAN"); + assert_eq!( + respan_request_count, sponge_respan_count, + "each RESPAN request must be paired with a sponge_respan response", + ); + log.assert_contains(&exp); } -/// Tests the generation of the `b_chip` bus column when the hasher performs a merge of two code -/// blocks requested by the decoder (SPLIT). This also requires inner SPAN blocks. -/// -/// Verifies step-by-step that SPLIT, SPAN, and END requests are each matched by hasher responses. #[test] -pub fn b_chip_merge() { +fn merge_hasher_bus() { let program = { let mut mast_forest = MastForest::new(); - - let t_branch_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new()) + let t_branch = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new()) .add_to_forest(&mut mast_forest) .unwrap(); - let f_branch_id = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new()) + let f_branch = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new()) .add_to_forest(&mut mast_forest) .unwrap(); - let split_id = SplitNodeBuilder::new([t_branch_id, f_branch_id]) + let split_id = SplitNodeBuilder::new([t_branch, f_branch]) .add_to_forest(&mut mast_forest) .unwrap(); mast_forest.make_root(split_id); - Program::new(mast_forest.into(), split_id) }; let trace = build_trace_from_program(&program, &[]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); - let rand_challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&rand_challenges).unwrap(); - let b_chip = aux_columns.get_column(CHIPLETS_BUS_AUX_TRACE_OFFSET); - let challenges = Challenges::new(rand_challenges[0], rand_challenges[1]); + let mut exp = Expectations::new(&log); + let mut split_request_count = 0usize; - assert_eq!(ONE, b_chip[0]); + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let op = main.get_op_code(idx).as_canonical_u64(); + if op != opcodes::SPLIT as u64 { + continue; + } + let addr_next = main.addr(RowIndex::from(row + 1)); + let rate = rate_from_hasher_state(main, idx); + exp.remove(row, &HasherMsg::control_block(addr_next, &rate, opcodes::SPLIT)); + split_request_count += 1; + } - verify_b_chip_step_by_step(&trace, &challenges, b_chip); + let mut split_response_count = 0usize; + for (idx, kind) in hasher_response_rows(main) { + if kind != HasherResponseKind::SpongeStart { + continue; + } + let addr = main.clk(idx) + ONE; + let state = main.chiplet_hasher_state(idx); + // SPLIT's own hasher response carries opcode `SPLIT` at capacity[1] (position 9 of the + // 12-lane state); sibling SPAN sponge_start rows carry opcode 0. + if state[9] == Felt::from(opcodes::SPLIT) { + exp.add(usize::from(idx), &HasherMsg::linear_hash_init(addr, state)); + split_response_count += 1; + } + } - assert_bus_balanced(b_chip); + assert_eq!(split_request_count, 1, "single SPLIT program should emit one SPLIT remove"); + assert_eq!( + split_response_count, 1, + "single SPLIT program should emit one SPLIT-capacity sponge_start", + ); + log.assert_contains(&exp); } -/// Tests the generation of the `b_chip` bus column when the hasher performs a permutation -/// requested by the `HPerm` user operation. #[test] -pub fn b_chip_permutation() { - let program = { - let mut mast_forest = MastForest::new(); - - let basic_block_id = BasicBlockNodeBuilder::new(vec![Operation::HPerm], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - mast_forest.make_root(basic_block_id); - - Program::new(mast_forest.into(), basic_block_id) - }; +fn hperm_hasher_bus() { + let program = single_block_program(vec![Operation::HPerm]); let stack = vec![8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 8]; let trace = build_trace_from_program(&program, &stack); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut exp = Expectations::new(&log); + let mut request_count = 0usize; + let mut hperm_helper0: Option = None; + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let op = main.get_op_code(idx).as_canonical_u64(); + if op != opcodes::HPERM as u64 { + continue; + } - let rand_challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&rand_challenges).unwrap(); - let b_chip = aux_columns.get_column(CHIPLETS_BUS_AUX_TRACE_OFFSET); - let challenges = Challenges::new(rand_challenges[0], rand_challenges[1]); - - assert_eq!(ONE, b_chip[0]); - - verify_b_chip_step_by_step(&trace, &challenges, b_chip); + let helper0 = main.helper_register(0, idx); + hperm_helper0 = Some(helper0); + let next = RowIndex::from(row + 1); + let stk_state: [Felt; 12] = core::array::from_fn(|i| main.stack_element(i, idx)); + let stk_next_state: [Felt; 12] = core::array::from_fn(|i| main.stack_element(i, next)); + exp.remove(row, &HasherMsg::linear_hash_init(helper0, stk_state)); + exp.remove( + row, + &HasherMsg::return_state(helper0 + CONTROLLER_ROWS_PER_PERM_FELT - ONE, stk_next_state), + ); + request_count += 2; + } + let hperm_helper0 = hperm_helper0.expect("program should contain an HPERM row"); + let hperm_return_addr = hperm_helper0 + CONTROLLER_ROWS_PER_PERM_FELT - ONE; + + let mut sponge_start_count = 0usize; + let mut sout_count = 0usize; + for (idx, kind) in hasher_response_rows(main) { + let addr = main.clk(idx) + ONE; + let state = main.chiplet_hasher_state(idx); + match kind { + HasherResponseKind::SpongeStart => { + exp.add(usize::from(idx), &HasherMsg::linear_hash_init(addr, state)); + // Only the HPERM-paired sponge_start matches `hperm_helper0`; the outer + // SPAN/END controller rows live on their own `addr` track. + if addr == hperm_helper0 { + sponge_start_count += 1; + } + }, + HasherResponseKind::Sout => { + exp.add(usize::from(idx), &HasherMsg::return_state(addr, state)); + if addr == hperm_return_addr { + sout_count += 1; + } + }, + _ => {}, + } + } - assert_bus_balanced(b_chip); + assert_eq!(request_count, 2, "HPERM: expected 2 removes (init + return)"); + assert_eq!(sponge_start_count, 1, "HPERM: expected 1 HPERM-paired sponge_start"); + assert_eq!(sout_count, 1, "HPERM: expected 1 HPERM-paired SOUT"); + log.assert_contains(&exp); } -/// Tests the generation of the `b_chip` bus column when the hasher performs a log_precompile -/// operation requested by the stack. The operation absorbs TAG and COMM into a Poseidon2 -/// sponge with capacity CAP_PREV, producing (CAP_NEXT, R0, R1). #[test] -pub fn b_chip_log_precompile() { - let program = { - let mut mast_forest = MastForest::new(); - - let basic_block_id = BasicBlockNodeBuilder::new(vec![Operation::LogPrecompile], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - mast_forest.make_root(basic_block_id); - - Program::new(mast_forest.into(), basic_block_id) - }; - // stack! takes elements in runtime order (first = top) and handles reversal +fn logprecompile_hasher_bus() { + let program = single_block_program(vec![Operation::LogPrecompile]); let stack_inputs = stack![5, 6, 7, 8, 1, 2, 3, 4]; let trace = build_trace_from_program(&program, &stack_inputs); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut exp = Expectations::new(&log); + let mut request_count = 0usize; + let mut logprecompile_addr: Option = None; + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let op = main.get_op_code(idx).as_canonical_u64(); + if op != opcodes::LOGPRECOMPILE as u64 { + continue; + } - let rand_challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&rand_challenges).unwrap(); - let b_chip = aux_columns.get_column(CHIPLETS_BUS_AUX_TRACE_OFFSET); - let challenges = Challenges::new(rand_challenges[0], rand_challenges[1]); + let next = RowIndex::from(row + 1); + let log_addr = main.helper_register(HELPER_ADDR_IDX, idx); + logprecompile_addr = Some(log_addr); - assert_eq!(ONE, b_chip[0]); + // Input: [COMM, TAG, CAP_PREV] — 8 stack lanes + 4 helper registers. + let input_state: [Felt; 12] = core::array::from_fn(|i| { + if i < 4 { + main.stack_element(STACK_COMM_RANGE.start + i, idx) + } else if i < 8 { + main.stack_element(STACK_TAG_RANGE.start + (i - 4), idx) + } else { + main.helper_register(HELPER_CAP_PREV_RANGE.start + (i - 8), idx) + } + }); + + // Output (next row): [R0, R1, CAP_NEXT] — all 12 lanes from stack. + let output_state: [Felt; 12] = core::array::from_fn(|i| { + if i < 4 { + main.stack_element(STACK_R0_RANGE.start + i, next) + } else if i < 8 { + main.stack_element(STACK_R1_RANGE.start + (i - 4), next) + } else { + main.stack_element(STACK_CAP_NEXT_RANGE.start + (i - 8), next) + } + }); - verify_b_chip_step_by_step(&trace, &challenges, b_chip); + exp.remove(row, &HasherMsg::linear_hash_init(log_addr, input_state)); + exp.remove( + row, + &HasherMsg::return_state(log_addr + CONTROLLER_ROWS_PER_PERM_FELT - ONE, output_state), + ); + request_count += 2; + } + let log_addr = logprecompile_addr.expect("program should contain a LOGPRECOMPILE row"); + let log_return_addr = log_addr + CONTROLLER_ROWS_PER_PERM_FELT - ONE; + + let mut sponge_start_count = 0usize; + let mut sout_count = 0usize; + for (idx, kind) in hasher_response_rows(main) { + let addr = main.clk(idx) + ONE; + let state = main.chiplet_hasher_state(idx); + match kind { + HasherResponseKind::SpongeStart => { + exp.add(usize::from(idx), &HasherMsg::linear_hash_init(addr, state)); + if addr == log_addr { + sponge_start_count += 1; + } + }, + HasherResponseKind::Sout => { + exp.add(usize::from(idx), &HasherMsg::return_state(addr, state)); + if addr == log_return_addr { + sout_count += 1; + } + }, + _ => {}, + } + } - assert_bus_balanced(b_chip); + assert_eq!(request_count, 2, "LOGPRECOMPILE: expected 2 removes (init + return)"); + assert_eq!( + sponge_start_count, 1, + "LOGPRECOMPILE: expected 1 LOGPRECOMPILE-paired sponge_start" + ); + assert_eq!(sout_count, 1, "LOGPRECOMPILE: expected 1 LOGPRECOMPILE-paired SOUT"); + log.assert_contains(&exp); } -/// Tests the generation of the `b_chip` bus column when the hasher performs a Merkle path -/// verification requested by the `MpVerify` user operation. #[test] -fn b_chip_mpverify() { +fn mpverify_hasher_bus() { let index = 5usize; let leaves = init_leaves(&[1, 2, 3, 4, 5, 6, 7, 8]); let tree = MerkleTree::new(&leaves).unwrap(); @@ -217,36 +429,78 @@ fn b_chip_mpverify() { stack_inputs, advice_inputs, ); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); - let rand_challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&rand_challenges).unwrap(); - let b_chip = aux_columns.get_column(CHIPLETS_BUS_AUX_TRACE_OFFSET); - let challenges = Challenges::new(rand_challenges[0], rand_challenges[1]); + let mut exp = Expectations::new(&log); + let mut request_count = 0usize; - assert_eq!(ONE, b_chip[0]); + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let op = main.get_op_code(idx).as_canonical_u64(); + if op != opcodes::MPVERIFY as u64 { + continue; + } + let helper0 = main.helper_register(0, idx); + let mp_depth = main.stack_element(4, idx); + let mp_index = main.stack_element(5, idx); + let leaf_word: [Felt; 4] = core::array::from_fn(|i| main.stack_element(i, idx)); + let old_root: [Felt; 4] = core::array::from_fn(|i| main.stack_element(6 + i, idx)); + + let return_addr = helper0 + mp_depth * CONTROLLER_ROWS_PER_PERM_FELT - ONE; + exp.remove(row, &HasherMsg::merkle_verify_init(helper0, mp_index, leaf_word)); + exp.remove(row, &HasherMsg::return_hash(return_addr, old_root)); + request_count += 2; + } - verify_b_chip_step_by_step(&trace, &challenges, b_chip); + let mut mp_input_count = 0usize; + let mut hout_count = 0usize; + for (idx, kind) in hasher_response_rows(main) { + let addr = main.clk(idx) + ONE; + let state = main.chiplet_hasher_state(idx); + let rate_0: [Felt; 4] = [state[0], state[1], state[2], state[3]]; + let rate_1: [Felt; 4] = [state[4], state[5], state[6], state[7]]; + match kind { + HasherResponseKind::MpInput => { + let node_index = main.chiplet_node_index(idx); + // Match the emitter's own `bit = node_index - 2·node_index_next` formula rather + // than reading `chiplet_direction_bit`: keeps this assertion independent of the + // column whose constraints are under test. + let bit = merkle_direction_bit(main, idx); + let word: [Felt; 4] = if bit == ZERO { rate_0 } else { rate_1 }; + exp.add(usize::from(idx), &HasherMsg::merkle_verify_init(addr, node_index, word)); + mp_input_count += 1; + }, + HasherResponseKind::Hout => { + // `chiplet_node_index(idx)` is `ZERO` at MPVERIFY's final HOUT row (Merkle walk + // terminates with node_index halved to 0). Using `return_hash` keeps the test + // aligned with the decoder-side `HasherMsg::return_hash(...)` shape. + exp.add(usize::from(idx), &HasherMsg::return_hash(addr, rate_0)); + hout_count += 1; + }, + _ => {}, + } + } - assert_bus_balanced(b_chip); + assert_eq!(request_count, 2, "MPVERIFY: expected 2 removes (init + return)"); + assert_eq!(mp_input_count, 1, "MPVERIFY: expected 1 mp_verify_input add"); + // Depth-3 MPVERIFY emits HOUT on every merkle-verify sub-cycle return (one per level). + assert_eq!(hout_count, 2, "MPVERIFY: expected exactly 2 HOUT adds"); + log.assert_contains(&exp); } -/// Tests the generation of the `b_chip` bus column when the hasher performs a Merkle root update -/// requested by the `MrUpdate` user operation. #[test] -fn b_chip_mrupdate() { +fn mrupdate_hasher_bus() { let index = 5usize; let leaves = init_leaves(&[1, 2, 3, 4, 5, 6, 7, 8]); let tree = MerkleTree::new(&leaves).unwrap(); - - let old_root = tree.root(); - let old_leaf_value = leaves[index]; let new_leaf_value = leaves[0]; let mut runtime_stack = Vec::new(); - runtime_stack.extend_from_slice(&word_to_ints(old_leaf_value)); + runtime_stack.extend_from_slice(&word_to_ints(leaves[index])); runtime_stack.push(tree.depth() as u64); runtime_stack.push(index as u64); - runtime_stack.extend_from_slice(&word_to_ints(old_root)); + runtime_stack.extend_from_slice(&word_to_ints(tree.root())); runtime_stack.extend_from_slice(&word_to_ints(new_leaf_value)); let stack_inputs = StackInputs::try_from_ints(runtime_stack).unwrap(); let store = MerkleStore::from(&tree); @@ -254,504 +508,233 @@ fn b_chip_mrupdate() { let trace = build_trace_from_ops_with_inputs(vec![Operation::MrUpdate], stack_inputs, advice_inputs); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); - let rand_challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&rand_challenges).unwrap(); - let b_chip = aux_columns.get_column(CHIPLETS_BUS_AUX_TRACE_OFFSET); - let challenges = Challenges::new(rand_challenges[0], rand_challenges[1]); + let mut exp = Expectations::new(&log); + let mut request_count = 0usize; - assert_eq!(ONE, b_chip[0]); + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let op = main.get_op_code(idx).as_canonical_u64(); + if op != opcodes::MRUPDATE as u64 { + continue; + } + let helper0 = main.helper_register(0, idx); + let next = RowIndex::from(row + 1); + let mr_depth = main.stack_element(4, idx); + let mr_index = main.stack_element(5, idx); + let old_leaf: [Felt; 4] = core::array::from_fn(|i| main.stack_element(i, idx)); + let old_root: [Felt; 4] = core::array::from_fn(|i| main.stack_element(6 + i, idx)); + let new_leaf: [Felt; 4] = core::array::from_fn(|i| main.stack_element(10 + i, idx)); + let new_root: [Felt; 4] = core::array::from_fn(|i| main.stack_element(i, next)); + + let old_return = helper0 + mr_depth * CONTROLLER_ROWS_PER_PERM_FELT - ONE; + let new_init = helper0 + mr_depth * CONTROLLER_ROWS_PER_PERM_FELT; + let new_return = helper0 + + mr_depth * (CONTROLLER_ROWS_PER_PERM_FELT + CONTROLLER_ROWS_PER_PERM_FELT) + - ONE; + + exp.remove(row, &HasherMsg::merkle_old_init(helper0, mr_index, old_leaf)); + exp.remove(row, &HasherMsg::return_hash(old_return, old_root)); + exp.remove(row, &HasherMsg::merkle_new_init(new_init, mr_index, new_leaf)); + exp.remove(row, &HasherMsg::return_hash(new_return, new_root)); + request_count += 4; + } - verify_b_chip_step_by_step(&trace, &challenges, b_chip); + let mut mv_count = 0usize; + let mut mu_count = 0usize; + let mut hout_count = 0usize; + for (idx, kind) in hasher_response_rows(main) { + let addr = main.clk(idx) + ONE; + let state = main.chiplet_hasher_state(idx); + let rate_0: [Felt; 4] = [state[0], state[1], state[2], state[3]]; + let rate_1: [Felt; 4] = [state[4], state[5], state[6], state[7]]; + let node_index = main.chiplet_node_index(idx); + let bit = merkle_direction_bit(main, idx); + let word: [Felt; 4] = if bit == ZERO { rate_0 } else { rate_1 }; + + match kind { + HasherResponseKind::MvOldInput => { + exp.add(usize::from(idx), &HasherMsg::merkle_old_init(addr, node_index, word)); + mv_count += 1; + }, + HasherResponseKind::MuNewInput => { + exp.add(usize::from(idx), &HasherMsg::merkle_new_init(addr, node_index, word)); + mu_count += 1; + }, + HasherResponseKind::Hout => { + exp.add(usize::from(idx), &HasherMsg::return_hash(addr, rate_0)); + hout_count += 1; + }, + _ => {}, + } + } - assert_bus_balanced(b_chip); + assert_eq!( + request_count, 4, + "MRUPDATE: expected 4 removes (old_init + old_return + new_init + new_return)", + ); + assert_eq!(mv_count, 1, "MRUPDATE: expected 1 mr_update_old_input add"); + assert_eq!(mu_count, 1, "MRUPDATE: expected 1 mr_update_new_input add"); + // Depth-3 MRUPDATE emits HOUT on each old-path and new-path sub-cycle return. + assert_eq!(hout_count, 3, "MRUPDATE: expected exactly 3 HOUT adds"); + log.assert_contains(&exp); } -// TEST HELPERS -- MESSAGE BUILDERS +// HELPERS // ================================================================================================ -// -// These helpers build expected bus message values for each of the 5 message types. -// The label encoding and selector mapping are encapsulated here so tests can -// speak in terms of operations, not selector bits. -// -// Label convention: input messages use label + 16, output messages use label + 32. - -const LABEL_OFFSET_INPUT: u8 = 16; -const LABEL_OFFSET_OUTPUT: u8 = 32; - -/// Sponge start message: full 12-element state (matches SPAN / control block request). -fn sponge_start_msg(challenges: &Challenges, addr: Felt, state: &HasherState) -> Felt { - let header = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[0] * Felt::from_u8(LINEAR_HASH_LABEL + LABEL_OFFSET_INPUT) - + challenges.beta_powers[1] * addr; - header + build_value(&challenges.beta_powers[3..15], state) -} -/// Sponge continuation message: rate-only 8 elements (matches RESPAN request). -/// Both the RESPAN request and the hasher continuation response use LABEL_OFFSET_OUTPUT (= 32). -fn sponge_continuation_msg(challenges: &Challenges, addr: Felt, rate: &[Felt]) -> Felt { - assert_eq!(rate.len(), RATE_LEN); - let header = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[0] * Felt::from_u8(LINEAR_HASH_LABEL + LABEL_OFFSET_OUTPUT) - + challenges.beta_powers[1] * addr; - header + build_value(&challenges.beta_powers[3..11], rate) +fn single_block_program(ops: Vec) -> Program { + let mut mast_forest = MastForest::new(); + let id = BasicBlockNodeBuilder::new(ops, Vec::new()) + .add_to_forest(&mut mast_forest) + .unwrap(); + mast_forest.make_root(id); + Program::new(mast_forest.into(), id) } -/// Digest return message: 4-element digest (matches END / MPVERIFY output / MRUPDATE output). -fn digest_return_msg(challenges: &Challenges, addr: Felt, digest: &[Felt]) -> Felt { - assert_eq!(digest.len(), 4); - let header = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[0] * Felt::from_u8(RETURN_HASH_LABEL + LABEL_OFFSET_OUTPUT) - + challenges.beta_powers[1] * addr; - header + build_value(&challenges.beta_powers[3..7], digest) +fn rate_from_hasher_state(main: &MainTrace, row: RowIndex) -> [Felt; 8] { + let first = main.decoder_hasher_state_first_half(row); + let second = main.decoder_hasher_state_second_half(row); + [ + first[0], first[1], first[2], first[3], second[0], second[1], second[2], second[3], + ] } -/// Full state return message: 12-element state (matches HPERM output). -fn full_state_return_msg(challenges: &Challenges, addr: Felt, state: &HasherState) -> Felt { - let header = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[0] * Felt::from_u8(RETURN_STATE_LABEL + LABEL_OFFSET_OUTPUT) - + challenges.beta_powers[1] * addr; - header + build_value(&challenges.beta_powers[3..15], state) +fn is_hasher_controller_row(main: &MainTrace, row: RowIndex) -> bool { + main.chiplet_selector_0(row) == ONE && main.chiplet_s_perm(row) == ZERO } -/// Tree input message: leaf word selected by direction bit (matches MPVERIFY/MRUPDATE input). -fn tree_input_msg( - challenges: &Challenges, - label: u8, - addr: Felt, - index: Felt, - leaf_word: &[Felt; 4], -) -> Felt { - let header = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[0] * Felt::from_u8(label + LABEL_OFFSET_INPUT) - + challenges.beta_powers[1] * addr - + challenges.beta_powers[2] * index; - header + build_value(&challenges.beta_powers[3..7], leaf_word) +/// Recompute the Merkle direction bit the emitter uses: `bit = node_index - 2·node_index_next` +/// (see `chiplet_responses.rs::mp_verify_input`). Independent of the `chiplet_direction_bit` +/// column, so bugs in that column don't make the assertion vacuously pass. +fn merkle_direction_bit(main: &MainTrace, row: RowIndex) -> Felt { + let next = RowIndex::from(usize::from(row) + 1); + main.chiplet_node_index(row) - main.chiplet_node_index(next).double() } -/// Reads the hasher chiplet response at the given trace row. -/// -/// This mirrors the response builder logic: reads the trace and computes the bus message. -/// Returns ONE (identity) if the row doesn't produce a response. -fn hasher_response_at( - trace: &ExecutionTrace, - challenges: &Challenges, - row: RowIndex, -) -> Felt { - let s_perm = trace.main_trace.chiplet_s_perm(row); - if s_perm == ONE { - return ONE; // perm segment: no response - } - - // Hasher internal selectors (chiplet columns 1, 2, 3 = hasher s0, s1, s2) - let hasher_s0 = trace.main_trace.chiplet_selector_1(row); - let hasher_s1 = trace.main_trace.chiplet_selector_2(row); - let hasher_s2 = trace.main_trace.chiplet_selector_3(row); - - let addr = Felt::from(u32::from(row) + 1); - let state = trace.main_trace.chiplet_hasher_state(row); - let node_index = trace.main_trace.chiplet_node_index(row); - - // Input rows (hasher s0=1) - if hasher_s0 == ONE { - let is_start = trace.main_trace.chiplet_is_boundary(row); +// SIBLING TABLE BUS (MRUPDATE add/remove pairing) +// ================================================================================================ +// +// MRUPDATE verifies the old Merkle root (MV leg, adds to sibling table) and then recomputes +// the new root (MU leg, removes from sibling table). Each of the 3 levels of a depth-3 tree +// emits one add on the MV leg and one remove on the MU leg, matched by `(mrupdate_id, +// node_index, sibling_word)`. +// +// The test iterates every hasher controller row, picks out the MV/MU sibling-emitting rows +// via the `(s0, s1, s2)` sub-selectors, and attaches a `SiblingMsg` expectation tagged with +// the direction bit. Column-blind — the subset matcher finds each message regardless of +// where the M4/C2 packing puts it. + +/// Drive a depth-3 Merkle MRUPDATE and assert the sibling-table bus fires one add per MV +/// controller row and one remove per MU controller row (3 levels → 3 adds + 3 removes). +#[rstest] +#[case(5_u64)] +#[case(4_u64)] +fn mrupdate_emits_sibling_add_and_remove_per_level(#[case] index: u64) { + let (tree, _) = build_merkle_tree(); + let old_node = tree.get_node(NodeIndex::new(3, index).unwrap()).unwrap(); + let new_node = init_leaf(11); + + // Build the program inputs the way the legacy test did. + let mut init_stack = Vec::new(); + init_stack.extend_from_slice(&word_to_ints(old_node)); + init_stack.extend_from_slice(&[3, index]); + init_stack.extend_from_slice(&word_to_ints(tree.root())); + init_stack.extend_from_slice(&word_to_ints(new_node)); + let stack_inputs = StackInputs::try_from_ints(init_stack).unwrap(); + let store = MerkleStore::from(&tree); + let advice_inputs = AdviceInputs::default().with_merkle_store(store); - // Sponge mode (s1=0, s2=0) - if hasher_s1 == ZERO && hasher_s2 == ZERO { - if is_start == ONE { - return sponge_start_msg(challenges, addr, &state); - } else { - return sponge_continuation_msg(challenges, addr, &state[..RATE_LEN]); - } + let ops = vec![Operation::MrUpdate]; + let trace = build_trace_from_ops_with_inputs(ops, stack_inputs, advice_inputs); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + // Collect MV / MU controller rows. A row is a sibling-table add/remove site when + // `chiplet_active.controller = 1` (s_ctrl column) AND the hasher internal + // `(s0, s1, s2)` sub-selectors pick out the MV-all (`s0·s1·(1-s2)`) or MU-all + // (`s0·s1·s2`) pattern. See `air/src/constraints/lookup/buses/hash_kernel.rs`. + let mut mv_rows: Vec = Vec::new(); + let mut mu_rows: Vec = Vec::new(); + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + if main.chiplet_selector_0(idx) != ONE || main.chiplet_s_perm(idx) != ZERO { + continue; } - - // Tree mode (s1=1 or s2=1) -- only start rows produce responses - if is_start == ONE { - let label = if hasher_s1 == ZERO { - MP_VERIFY_LABEL - } else if hasher_s2 == ZERO { - MR_UPDATE_OLD_LABEL - } else { - MR_UPDATE_NEW_LABEL - }; - let bit = (node_index.as_canonical_u64() & 1) as usize; - let leaf_word: [Felt; 4] = if bit == 0 { - state[..4].try_into().unwrap() - } else { - state[4..8].try_into().unwrap() - }; - return tree_input_msg(challenges, label, addr, node_index, &leaf_word); + let hs0 = main.chiplet_selector_1(idx); + let hs1 = main.chiplet_selector_2(idx); + let hs2 = main.chiplet_selector_3(idx); + if hs0 == ONE && hs1 == ONE && hs2 == ZERO { + mv_rows.push(idx); + } else if hs0 == ONE && hs1 == ONE && hs2 == ONE { + mu_rows.push(idx); } - - return ONE; // tree continuation: no response } + assert_eq!(mv_rows.len(), 3, "depth-3 MRUPDATE should emit 3 MV sibling adds"); + assert_eq!(mu_rows.len(), 3, "depth-3 MRUPDATE should emit 3 MU sibling removes"); - // Output rows (hasher s0=0, s1=0) - if hasher_s0 == ZERO && hasher_s1 == ZERO { - // HOUT (s2=0): always produces response - if hasher_s2 == ZERO { - return digest_return_msg(challenges, addr, &state[DIGEST_RANGE]); - } - - // SOUT (s2=1): only with is_final=1 - let is_final = trace.main_trace.chiplet_is_boundary(row); - if is_final == ONE { - return full_state_return_msg(challenges, addr, &state); - } + let mut exp = Expectations::new(&log); + for &row in &mv_rows { + push_sibling(&mut exp, row, main, SiblingSide::Add); } - - ONE // no response -} - -// TEST HELPERS -- STATE BUILDERS -// ================================================================================================ - -/// Builds a value from coefficients and elements of matching lengths. -fn build_value(coeffs: &[Felt], elements: &[Felt]) -> Felt { - let mut value = ZERO; - for (&coeff, &element) in coeffs.iter().zip(elements.iter()) { - value += coeff * element; + for &row in &mu_rows { + push_sibling(&mut exp, row, main, SiblingSide::Remove); } - value -} - -// STEP-BY-STEP BUS VERIFICATION -// ================================================================================================ -/// Computes the decoder's request to the chiplets bus at the given row. -/// -/// Only handles hasher-related opcodes (SPAN, END, RESPAN, SPLIT, JOIN, LOOP). -/// Returns ONE (identity) for all other opcodes. -fn decoder_request_at( - trace: &ExecutionTrace, - challenges: &Challenges, - row: RowIndex, -) -> Felt { - let op_code = trace.main_trace.get_op_code(row).as_canonical_u64() as u8; - - match op_code { - opcodes::SPAN => { - // SPAN request: rate-only message (LINEAR_HASH_LABEL + 16) at addr(row+1). - let addr_next = trace.main_trace.addr(row + 1); - let state = trace.main_trace.decoder_hasher_state(row); - let header = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(LINEAR_HASH_LABEL + LABEL_OFFSET_INPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * addr_next; - let mut value = header; - for (i, &elem) in state.iter().enumerate() { - value += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - value - }, - opcodes::RESPAN => { - // RESPAN request: rate-only message (LINEAR_HASH_LABEL + 32) at addr(row+1). - let addr_next = trace.main_trace.addr(row + 1); - let state = trace.main_trace.decoder_hasher_state(row); - let header = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(LINEAR_HASH_LABEL + LABEL_OFFSET_OUTPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * addr_next; - let mut value = header; - for (i, &elem) in state.iter().enumerate() { - value += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - value - }, - opcodes::END => { - // END request: digest message (RETURN_HASH_LABEL + 32) at addr(row) + 1. - let addr = trace.main_trace.addr(row) + ONE; - let state = trace.main_trace.decoder_hasher_state(row); - let digest = &state[..4]; - let header = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(RETURN_HASH_LABEL + LABEL_OFFSET_OUTPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * addr; - let mut value = header; - for (i, &elem) in digest.iter().enumerate() { - value += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - value - }, - opcodes::SPLIT | opcodes::JOIN | opcodes::LOOP => { - // Control block request: rate + capacity domain element for op_code. - let addr_next = trace.main_trace.addr(row + 1); - let state = trace.main_trace.decoder_hasher_state(row); - let op_code_felt = trace.main_trace.get_op_code(row); - let header = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(LINEAR_HASH_LABEL + LABEL_OFFSET_INPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * addr_next; - let mut value = header; - for (i, &elem) in state.iter().enumerate() { - value += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - value += challenges.beta_powers[bus_message::CAPACITY_DOMAIN_IDX] * op_code_felt; - value - }, - opcodes::HPERM => { - // HPERM sends two messages: input (full state from stack) and output (full state - // from next row). Combined as a product. - let helper_0 = trace.main_trace.helper_register(0, row); - let state: [Felt; 12] = - core::array::from_fn(|i| trace.main_trace.stack_element(i, row)); - let state_nxt: [Felt; 12] = - core::array::from_fn(|i| trace.main_trace.stack_element(i, row + 1)); - - let input_value = { - let mut acc = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(LINEAR_HASH_LABEL + LABEL_OFFSET_INPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * helper_0; - for (i, &elem) in state.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc - }; - let output_value = { - let mut acc = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(RETURN_STATE_LABEL + LABEL_OFFSET_OUTPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * (helper_0 + ONE); - for (i, &elem) in state_nxt.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc - }; - input_value * output_value - }, - opcodes::LOGPRECOMPILE => { - // LOG_PRECOMPILE sends two messages: input [COMM, TAG, CAP_PREV] and - // output [R0, R1, CAP_NEXT]. Combined as a product. - let addr = trace.main_trace.helper_register(HELPER_ADDR_IDX, row); - let cap_prev: [Felt; 4] = core::array::from_fn(|i| { - trace.main_trace.helper_register(HELPER_CAP_PREV_RANGE.start + i, row) - }); - let comm = trace.main_trace.stack_word(STACK_COMM_RANGE.start, row); - let tag = trace.main_trace.stack_word(STACK_TAG_RANGE.start, row); - let input_state: Vec = comm - .as_elements() - .iter() - .chain(tag.as_elements().iter()) - .chain(cap_prev.iter()) - .copied() - .collect(); - - let r0 = trace.main_trace.stack_word(STACK_R0_RANGE.start, row + 1); - let r1 = trace.main_trace.stack_word(STACK_R1_RANGE.start, row + 1); - let cap_next = trace.main_trace.stack_word(STACK_CAP_NEXT_RANGE.start, row + 1); - let output_state: Vec = r0 - .as_elements() - .iter() - .chain(r1.as_elements().iter()) - .chain(cap_next.as_elements().iter()) - .copied() - .collect(); - - let input_value = { - let mut acc = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(LINEAR_HASH_LABEL + LABEL_OFFSET_INPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * addr; - for (i, &elem) in input_state.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc - }; - let output_value = { - let mut acc = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(RETURN_STATE_LABEL + LABEL_OFFSET_OUTPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * (addr + ONE); - for (i, &elem) in output_state.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc - }; - input_value * output_value - }, - opcodes::MPVERIFY => { - // MPVERIFY sends two messages: input (leaf word + node_index) and output (root - // digest). Combined as a product. - let helper_0 = trace.main_trace.helper_register(0, row); - let rows_per_perm = CONTROLLER_ROWS_PER_PERM_FELT; - - let node_value = trace.main_trace.stack_word(0, row); - let node_depth = trace.main_trace.stack_element(4, row); - let node_index = trace.main_trace.stack_element(5, row); - let merkle_root = trace.main_trace.stack_word(6, row); - - let node_word: [Felt; 4] = - node_value.as_elements().try_into().expect("word must be 4 field elements"); - let root_word: [Felt; 4] = - merkle_root.as_elements().try_into().expect("word must be 4 field elements"); - - let input_value = { - let mut acc = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(MP_VERIFY_LABEL + LABEL_OFFSET_INPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * helper_0 - + challenges.beta_powers[bus_message::NODE_INDEX_IDX] * node_index; - for (i, &elem) in node_word.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc - }; - let output_addr = helper_0 + node_depth * rows_per_perm - ONE; - let output_value = { - let mut acc = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(RETURN_HASH_LABEL + LABEL_OFFSET_OUTPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * output_addr; - for (i, &elem) in root_word.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc - }; - input_value * output_value - }, - opcodes::MRUPDATE => { - // MRUPDATE sends four messages: old input, old output, new input, new output. - // Combined as a product. - let helper_0 = trace.main_trace.helper_register(0, row); - let rows_per_perm = CONTROLLER_ROWS_PER_PERM_FELT; - let two_legs_rows = rows_per_perm + rows_per_perm; - - let old_node_value = trace.main_trace.stack_word(0, row); - let merkle_path_depth = trace.main_trace.stack_element(4, row); - let node_index = trace.main_trace.stack_element(5, row); - let old_root = trace.main_trace.stack_word(6, row); - let new_node_value = trace.main_trace.stack_word(10, row); - let new_root = trace.main_trace.stack_word(0, row + 1); - - let old_node_word: [Felt; 4] = - old_node_value.as_elements().try_into().expect("word must be 4 field elements"); - let old_root_word: [Felt; 4] = - old_root.as_elements().try_into().expect("word must be 4 field elements"); - let new_node_word: [Felt; 4] = - new_node_value.as_elements().try_into().expect("word must be 4 field elements"); - let new_root_word: [Felt; 4] = - new_root.as_elements().try_into().expect("word must be 4 field elements"); - - let input_old = { - let mut acc = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(MR_UPDATE_OLD_LABEL + LABEL_OFFSET_INPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * helper_0 - + challenges.beta_powers[bus_message::NODE_INDEX_IDX] * node_index; - for (i, &elem) in old_node_word.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc - }; - let output_old = { - let output_addr = helper_0 + merkle_path_depth * rows_per_perm - ONE; - let mut acc = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(RETURN_HASH_LABEL + LABEL_OFFSET_OUTPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * output_addr; - for (i, &elem) in old_root_word.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc - }; - let input_new = { - let new_input_addr = helper_0 + merkle_path_depth * rows_per_perm; - let mut acc = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(MR_UPDATE_NEW_LABEL + LABEL_OFFSET_INPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * new_input_addr - + challenges.beta_powers[bus_message::NODE_INDEX_IDX] * node_index; - for (i, &elem) in new_node_word.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc - }; - let output_new = { - let new_output_addr = helper_0 + merkle_path_depth * two_legs_rows - ONE; - let mut acc = challenges.bus_prefix[bus_types::CHIPLETS_BUS] - + challenges.beta_powers[bus_message::LABEL_IDX] - * Felt::from_u8(RETURN_HASH_LABEL + LABEL_OFFSET_OUTPUT) - + challenges.beta_powers[bus_message::ADDR_IDX] * new_output_addr; - for (i, &elem) in new_root_word.iter().enumerate() { - acc += challenges.beta_powers[bus_message::STATE_START_IDX + i] * elem; - } - acc - }; - input_old * output_old * input_new * output_new - }, - _ => ONE, - } + log.assert_contains(&exp); } -/// Verifies b_chip step-by-step by recomputing the running product at every row. -/// -/// At each row `r`: -/// request = decoder_request_at(r) -/// response = hasher_response_at(r) -/// expected[r+1] = expected[r] * response / request -/// -/// Asserts that the recomputed value matches `b_chip[r+1]` at every row. -fn verify_b_chip_step_by_step( - trace: &ExecutionTrace, - challenges: &Challenges, - b_chip: &[Felt], -) { - let mut expected = ONE; - let trace_len = b_chip.len(); - - for row_idx in 0..trace_len - 1 { - let row = RowIndex::from(row_idx); - let request = decoder_request_at(trace, challenges, row); - let response = hasher_response_at(trace, challenges, row); - - expected *= response * request.try_inverse().expect("request must be invertible"); - - assert_eq!( - expected, - b_chip[row_idx + 1], - "b_chip mismatch at row {} (after processing row {}): \ - expected={}, actual={}, request={}, response={}", - row_idx + 1, - row_idx, - expected, - b_chip[row_idx + 1], - request, - response, - ); - } +enum SiblingSide { + Add, + Remove, } -// BUS BALANCE ASSERTION -// ================================================================================================ +fn push_sibling(exp: &mut Expectations<'_>, row: RowIndex, main: &MainTrace, side: SiblingSide) { + let mrupdate_id = main.chiplet_mrupdate_id(row); + let node_index = main.chiplet_node_index(row); + let state = main.chiplet_hasher_state(row); + let rate_0: [Felt; 4] = [state[0], state[1], state[2], state[3]]; + let rate_1: [Felt; 4] = [state[4], state[5], state[6], state[7]]; + + // Direction bit drives which rate half the sibling lives in. The trace's + // `chiplet_direction_bit` column carries the extracted bit on Merkle controller rows. + let bit = main.chiplet_direction_bit(row); + let row_usize = usize::from(row); + let (bit_tag, h) = if bit == ZERO { + (SiblingBit::Zero, rate_1) + } else { + (SiblingBit::One, rate_0) + }; + let msg = SiblingMsg { bit: bit_tag, mrupdate_id, node_index, h }; + match side { + SiblingSide::Add => exp.add(row_usize, &msg), + SiblingSide::Remove => exp.remove(row_usize, &msg), + }; +} -/// Asserts that the b_chip bus column eventually settles to ONE (balanced) and stays there. -fn assert_bus_balanced(b_chip: &[Felt]) { - // The bus should reach ONE at some point after the initial requests/responses - // and stay there for the rest of the trace. - let last = *b_chip.last().expect("b_chip should not be empty"); - assert_eq!(last, ONE, "b_chip final value should be ONE (bus balanced), got {last}"); +fn build_merkle_tree() -> (MerkleTree, Vec) { + let leaves = init_leaves(&[1, 2, 3, 4, 5, 6, 7, 8]); + (MerkleTree::new(leaves.clone()).unwrap(), leaves) } -// MERKLE TREE HELPERS +// MERKLE TEST HELPERS // ================================================================================================ -/// Initializes Merkle tree leaves with the specified values. fn init_leaves(values: &[u64]) -> Vec { values.iter().map(|&v| init_leaf(v)).collect() } -/// Initializes a Merkle tree leaf with the specified value. fn init_leaf(value: u64) -> Word { [Felt::new_unchecked(value), ZERO, ZERO, ZERO].into() } -/// Converts a Word to stack input values (u64 array) in element order. -fn word_to_ints(w: Word) -> [u64; 4] { +fn word_to_ints(word: Word) -> [u64; 4] { [ - w[0].as_canonical_u64(), - w[1].as_canonical_u64(), - w[2].as_canonical_u64(), - w[3].as_canonical_u64(), + word[0].as_canonical_u64(), + word[1].as_canonical_u64(), + word[2].as_canonical_u64(), + word[3].as_canonical_u64(), ] } diff --git a/processor/src/trace/tests/chiplets/memory.rs b/processor/src/trace/tests/chiplets/memory.rs index 3826c37429..d04e375a48 100644 --- a/processor/src/trace/tests/chiplets/memory.rs +++ b/processor/src/trace/tests/chiplets/memory.rs @@ -1,372 +1,292 @@ -use miden_air::trace::{ - Challenges, RowIndex, - chiplets::{ - MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, MEMORY_IDX0_COL_IDX, MEMORY_IDX1_COL_IDX, - MEMORY_IS_READ_COL_IDX, MEMORY_IS_WORD_ACCESS_COL_IDX, MEMORY_V_COL_RANGE, - MEMORY_WORD_COL_IDX, - memory::{ - MEMORY_ACCESS_ELEMENT, MEMORY_ACCESS_WORD, MEMORY_READ, MEMORY_READ_ELEMENT_LABEL, - MEMORY_READ_WORD_LABEL, MEMORY_WRITE, MEMORY_WRITE_ELEMENT_LABEL, - MEMORY_WRITE_WORD_LABEL, - }, +//! Memory-chiplet bus tests. +//! +//! Exercises stack-issued memory opcodes (`MStoreW`, `MLoadW`, `MLoad`, `MStore`, `MStream`) +//! plus the `CryptoStream` double-word read+write pair, and verifies the chiplet-requests / +//! chiplet-responses bus pair. +//! +//! For each stack-level memory op the test registers an expected `-1` push of a +//! [`MemoryMsg`] (the "request" side). For each memory chiplet row the test registers an +//! expected `+1` push of a [`MemoryResponseMsg`] (the "response" side). The subset matcher in +//! `lookup_harness` is column-blind, so a `(mult, denom)` pair on the response side pairs up +//! with a matching `(-mult, denom)` on the request side regardless of which aux column the +//! framework routes them onto. +//! +//! # Scope +//! +//! Coverage is limited to the stack-only memory ops above plus `CryptoStream`. The DYN, +//! DYNCALL, CALL-FMP-write, and PIPE memory-request paths in +//! `air/src/constraints/lookup/buses/chiplet_requests.rs` are deferred to integration tests — +//! each is heavyweight to set up and small in algebraic surface. A bug in those paths would +//! escape this module. +//! +//! The programs run at ctx = 0 throughout (no CALL/SYSCALL), so a request/response bug that +//! mismatches stack-side `ctx` vs chiplet-side `mem_ctx` is not caught here. + +use miden_air::{ + logup::{MemoryMsg, MemoryResponseMsg}, + trace::{ + MainTrace, + chiplets::{MEMORY_IS_READ_COL_IDX, MEMORY_IS_WORD_ACCESS_COL_IDX}, }, }; -use miden_core::{WORD_SIZE, field::Field}; +use miden_core::{ + Felt, ONE, ZERO, + operations::{Operation, opcodes}, +}; -use super::{ - AUX_TRACE_RAND_CHALLENGES, CHIPLETS_BUS_AUX_TRACE_OFFSET, ExecutionTrace, Felt, HASH_CYCLE_LEN, - ONE, Operation, Word, ZERO, build_trace_from_ops, rand_array, +use super::super::{ + build_trace_from_ops, + lookup_harness::{Expectations, InteractionLog}, }; +use crate::RowIndex; -/// Tests the generation of the `b_chip` bus column when only memory lookups are included. It -/// ensures that trace generation is correct when all of the following are true. -/// -/// - All possible memory operations are called by the stack. -/// - Some requests from the Stack and responses from Memory occur at the same cycle. -/// - Multiple memory addresses are used. -/// -/// Note: Communication with the Hash chiplet is also required, due to the span block decoding, but -/// for this test we set those values explicitly, enforcing only that the same initial and final -/// values are requested & provided. -#[test] -fn b_chip_trace_mem() { - const FOUR: Felt = Felt::new_unchecked(4); +const FOUR: Felt = Felt::new_unchecked(4); +/// Covers `MStoreW`, `MLoad`, `MLoadW`, `MStore`, `MStream` — every memory opcode issuable +/// directly from the stack — asserting the chiplet-bus request/response pair fires at every +/// memory row. +#[test] +fn memory_chiplet_bus_request_response_pairs() { let stack = [0, 1, 2, 3, 4]; - let word = [ONE, Felt::new_unchecked(2), Felt::new_unchecked(3), Felt::new_unchecked(4)]; let operations = vec![ - Operation::MStoreW, // store [1, 2, 3, 4] - Operation::Drop, // clear the stack + Operation::MStoreW, // store [1, 2, 3, 4] at addr 0 + Operation::Drop, Operation::Drop, Operation::Drop, Operation::Drop, - Operation::MLoad, // read the first value of the word - Operation::MovDn5, // put address 0 and space for a full word at top of stack - Operation::MLoadW, // load word from address 0 to stack - Operation::Push(ONE), // push a new value onto the stack - Operation::Push(FOUR), // push a new address on to the stack - Operation::MStore, // store 1 at address 4 - Operation::Drop, // ensure the stack overflow table is empty - Operation::MStream, // read 2 words starting at address 0 + Operation::MLoad, // read first element at addr 0 + Operation::MovDn5, // reshape stack + Operation::MLoadW, // load word from addr 0 + Operation::Push(ONE), // value = 1 + Operation::Push(FOUR), // addr = 4 + Operation::MStore, // store 1 at addr 4 + Operation::Drop, + Operation::MStream, // two-word read starting at stack[12] ]; let trace = build_trace_from_ops(operations, &stack); - - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let b_chip = aux_columns.get_column(CHIPLETS_BUS_AUX_TRACE_OFFSET); - let challenges = Challenges::::new(challenges[0], challenges[1]); - assert_eq!(trace.length(), b_chip.len()); - assert_eq!(ONE, b_chip[0]); - - // At cycle 0 the span hash initialization is requested from the decoder and provided by the - // hash chiplet, so the trace should still equal one. - assert_eq!(ONE, b_chip[1]); - - // At row 1, two things happen simultaneously: - // - The hasher provides the HOUT response (span hash result) at controller output row 1 - // - The stack sends the MStoreW memory write request (user op at cycle 1) - // Extract the HOUT response as a black box. - let mstore_value = build_expected_bus_word_msg( - &challenges, - MEMORY_WRITE_WORD_LABEL, - ZERO, - ZERO, - ONE, - word.into(), - ); - // b_chip[2] = ONE * hout_response * mstore_value.inverse() - let hout_response = b_chip[2] * mstore_value; - let mut expected = hout_response * mstore_value.inverse(); - assert_eq!(expected, b_chip[2]); - - // Nothing changes after user operations that don't make requests to the Chiplets. - for row in 3..7 { - assert_eq!(expected, b_chip[row]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut exp = Expectations::new(&log); + + // ---- Request side: stack rows emit `-1 × MemoryMsg` when their opcode is a memory op. + // + // We also count the number of `remove` calls and assert it matches the expected total at + // the end. Without this, a bug that stopped the emitter entirely would pass vacuously: + // the request-opcode loop iterates the trace, so no memory rows → no expectations → + // trivial subset match. + let mut request_exps_added = 0usize; + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let ctx = main.ctx(idx); + let clk = main.clk(idx); + let next = RowIndex::from(row + 1); + let op = main.get_op_code(idx).as_canonical_u64(); + if op == opcodes::MLOAD as u64 { + let addr = main.stack_element(0, idx); + let value = main.stack_element(0, next); + exp.remove(row, &MemoryMsg::read_element(ctx, addr, clk, value)); + request_exps_added += 1; + } else if op == opcodes::MSTORE as u64 { + let addr = main.stack_element(0, idx); + let value = main.stack_element(1, idx); + exp.remove(row, &MemoryMsg::write_element(ctx, addr, clk, value)); + request_exps_added += 1; + } else if op == opcodes::MLOADW as u64 { + let addr = main.stack_element(0, idx); + let word = next_word(main, next, 0); + exp.remove(row, &MemoryMsg::read_word(ctx, addr, clk, word)); + request_exps_added += 1; + } else if op == opcodes::MSTOREW as u64 { + let addr = main.stack_element(0, idx); + let word = [ + main.stack_element(1, idx), + main.stack_element(2, idx), + main.stack_element(3, idx), + main.stack_element(4, idx), + ]; + exp.remove(row, &MemoryMsg::write_word(ctx, addr, clk, word)); + request_exps_added += 1; + } else if op == opcodes::MSTREAM as u64 { + let base = main.stack_element(12, idx); + let word0 = next_word(main, next, 0); + let word1 = next_word(main, next, 4); + exp.remove(row, &MemoryMsg::read_word(ctx, base, clk, word0)); + exp.remove(row, &MemoryMsg::read_word(ctx, base + FOUR, clk, word1)); + request_exps_added += 2; + } } - - // The next memory request from the stack is sent when `MLoad` is executed at cycle 6 and - // included at row 7 - let value = build_expected_bus_element_msg( - &challenges, - MEMORY_READ_ELEMENT_LABEL, - ZERO, - ZERO, - Felt::new_unchecked(6), - word[0], - ); - expected *= value.inverse(); - assert_eq!(expected, b_chip[7]); - - // Nothing changes until the next memory request from the stack: `MLoadW` executed at cycle 8 - // and included at row 9. - let value = build_expected_bus_word_msg( - &challenges, - MEMORY_READ_WORD_LABEL, - ZERO, - ZERO, - Felt::new_unchecked(8), - word.into(), - ); - expected *= value.inverse(); - assert_eq!(expected, b_chip[9]); - - // Nothing changes until the next memory request from the stack. - assert_eq!(expected, b_chip[10]); - - // At cycle 11, `MStore` is requested by the stack and included at row 12. - let value = build_expected_bus_element_msg( - &challenges, - MEMORY_WRITE_ELEMENT_LABEL, - ZERO, - FOUR, - Felt::new_unchecked(11), - ONE, - ); - expected *= value.inverse(); - assert_eq!(expected, b_chip[12]); - - // Nothing changes until the next memory request from the stack. - assert_eq!(expected, b_chip[13]); - - // At cycle 13, `MStream` is requested by the stack, and the second read of `MStream` is - // requested for inclusion at row 14. - let value1 = build_expected_bus_word_msg( - &challenges, - MEMORY_READ_WORD_LABEL, - ZERO, - ZERO, - Felt::new_unchecked(13), - word.into(), - ); - let value2 = build_expected_bus_word_msg( - &challenges, - MEMORY_READ_WORD_LABEL, - ZERO, - Felt::new_unchecked(4), - Felt::new_unchecked(13), - [ONE, ZERO, ZERO, ZERO].into(), - ); - expected *= (value1 * value2).inverse(); - assert_eq!(expected, b_chip[14]); - - // At cycle 14 the decoder requests the span hash result (END). In the controller/perm split, - // the hasher already provided the HOUT response at row 1. The END request should cancel with - // it. We capture the multiplicand as a black box. - assert_ne!(expected, b_chip[15]); - let span_end_mult = b_chip[15] * expected.inverse(); - expected = b_chip[15]; - - // Verify the HOUT response and END request cancel. - assert_eq!(hout_response * span_end_mult, ONE); - - // Nothing changes until the memory segment starts. The hasher contributes 32 total rows: - // 16 rows for the padded controller region (2 controller rows + 14 padding) and 16 rows for - // the packed permutation segment. No bitwise ops, so memory starts at row 32. - let memory_start = 2 * HASH_CYCLE_LEN; // controller(16 padded) + perm(16) - for row in 16..memory_start { - assert_eq!(expected, b_chip[row]); + // 5 stack opcodes (MStoreW, MLoad, MLoadW, MStore, MStream) + 1 extra for MStream's 2nd read. + assert_eq!(request_exps_added, 6, "expected 6 memory request expectations"); + + // ---- Response side: every memory chiplet row emits `+1 × MemoryResponseMsg`. + let mut mem_rows_seen = 0usize; + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + if !main.is_memory_row(idx) { + continue; + } + mem_rows_seen += 1; + + let is_read = main.get(idx, MEMORY_IS_READ_COL_IDX); + let is_word = main.get(idx, MEMORY_IS_WORD_ACCESS_COL_IDX); + let mem_ctx = main.chiplet_memory_ctx(idx); + let word_addr = main.chiplet_memory_word(idx); + let idx0 = main.chiplet_memory_idx0(idx); + let idx1 = main.chiplet_memory_idx1(idx); + let addr = word_addr + idx1.double() + idx0; + let mem_clk = main.chiplet_memory_clk(idx); + let word = [ + main.chiplet_memory_value_0(idx), + main.chiplet_memory_value_1(idx), + main.chiplet_memory_value_2(idx), + main.chiplet_memory_value_3(idx), + ]; + // `element` is ignored by `MemoryResponseMsg::encode` when `is_word = 1`, so on + // word-access rows the fallback `ZERO` is harmless. `element_idx` uses `u64` + // arithmetic (native `usize` indexing) while `addr` above uses felt arithmetic — + // same math, different domain required by the consumer. + let element = if is_word == ZERO { + let element_idx = (idx1.as_canonical_u64() * 2 + idx0.as_canonical_u64()) as usize; + word[element_idx] + } else { + ZERO + }; + + exp.add( + row, + &MemoryResponseMsg { + is_read, + ctx: mem_ctx, + addr, + clk: mem_clk, + is_word, + element, + word, + }, + ); } + // 6 memory operations: MStoreW, MLoad, MLoadW, MStore, MStream (2 rows). + assert_eq!(mem_rows_seen, 6, "expected 6 memory chiplet rows"); - // Memory responses are provided during the memory segment after the hash cycle. There will be 6 - // rows, corresponding to the 5 memory operations (MStream requires 2 rows). - - // At cycle 8 `MLoadW` was requested by the stack; `MStoreW` is provided by memory here. - expected *= build_expected_bus_msg_from_trace(&trace, &challenges, memory_start.into()); - assert_eq!(expected, b_chip[memory_start + 1]); - - // At cycle 9, `MLoad` is provided by memory. - expected *= build_expected_bus_msg_from_trace(&trace, &challenges, (memory_start + 1).into()); - assert_eq!(expected, b_chip[memory_start + 2]); - - // At cycle 10, `MLoadW` is provided by memory. - expected *= build_expected_bus_msg_from_trace(&trace, &challenges, (memory_start + 2).into()); - assert_eq!(expected, b_chip[memory_start + 3]); - - // At cycle 11, `MStore` is provided by the memory. - expected *= build_expected_bus_msg_from_trace(&trace, &challenges, (memory_start + 3).into()); - assert_eq!(expected, b_chip[memory_start + 4]); - - // At cycle 12, the first read of `MStream` is provided by the memory. - expected *= build_expected_bus_msg_from_trace(&trace, &challenges, (memory_start + 4).into()); - assert_eq!(expected, b_chip[memory_start + 5]); - - // At cycle 13, the second read of `MStream` is provided by the memory. - expected *= build_expected_bus_msg_from_trace(&trace, &challenges, (memory_start + 5).into()); - assert_eq!(expected, b_chip[memory_start + 6]); - - // The value in b_chip should be ONE now and for the rest of the trace. - for row in (memory_start + 6)..trace.length() { - assert_eq!(ONE, b_chip[row]); - } + log.assert_contains(&exp); } +/// Regression test for a production bug where `CryptoStream`'s four memory requests weren't +/// being emitted onto the chiplet-requests bus. Verifies the exact read+read+write+write +/// pattern using hand-coded expected values (ciphertext = plaintext + rate), not values +/// read back from the trace — a missing emission, a wrong opcode label, or a swapped +/// addr/clk would all fail the subset match. #[test] -fn crypto_stream_missing_chiplets_bus_requests() { - // `crypto_stream` stack layout: [rate(8), cap(4), src_ptr, dst_ptr, ...] +fn cryptostream_emits_four_memory_requests() { + // `crypto_stream` stack layout: [rate(8), cap(4), src_ptr, dst_ptr, pad, pad] let stack = [ 1, 2, 3, 4, 5, 6, 7, 8, // rate(8) 0, 0, 0, 0, // cap(4) 0, // src_ptr 8, // dst_ptr - 0, 0, // unused + 0, 0, // pad ]; let trace = build_trace_from_ops(vec![Operation::CryptoStream], &stack); - let rand_challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&rand_challenges).unwrap(); - let b_chip = aux_columns.get_column(CHIPLETS_BUS_AUX_TRACE_OFFSET); - let challenges = Challenges::::new(rand_challenges[0], rand_challenges[1]); - - // --- Assert exact bus requests for the four CryptoStream memory operations. --- - - // CryptoStream with src_ptr=0, dst_ptr=8, rate=[1..8], and uninitialized (zero) memory: - // - reads word at addr 0: plaintext = [0, 0, 0, 0] - // - reads word at addr 4: plaintext = [0, 0, 0, 0] - // - writes word at addr 8: ciphertext = plaintext + rate = [1, 2, 3, 4] - // - writes word at addr 12: ciphertext = [5, 6, 7, 8] - let ctx = ZERO; - let clk = ONE; // CryptoStream executes at cycle 1 (cycle 0 is SPAN) - - let read1 = build_expected_bus_word_msg( - &challenges, - MEMORY_READ_WORD_LABEL, - ctx, - ZERO, // src_ptr = 0 - clk, - [ZERO, ZERO, ZERO, ZERO].into(), - ); - let read2 = build_expected_bus_word_msg( - &challenges, - MEMORY_READ_WORD_LABEL, - ctx, - Felt::new_unchecked(4), // src_ptr + 4 - clk, - [ZERO, ZERO, ZERO, ZERO].into(), - ); - let write1 = build_expected_bus_word_msg( - &challenges, - MEMORY_WRITE_WORD_LABEL, - ctx, - Felt::new_unchecked(8), // dst_ptr = 8 - clk, - [ONE, Felt::new_unchecked(2), Felt::new_unchecked(3), Felt::new_unchecked(4)].into(), - ); - let write2 = build_expected_bus_word_msg( - &challenges, - MEMORY_WRITE_WORD_LABEL, - ctx, - Felt::new_unchecked(12), // dst_ptr + 4 - clk, - [ - Felt::new_unchecked(5), - Felt::new_unchecked(6), - Felt::new_unchecked(7), - Felt::new_unchecked(8), - ] - .into(), - ); - - // All four requests are emitted at the same cycle, so they multiply together. - let combined_request = (read1 * read2 * write1 * write2).inverse(); - - // b_chip[0] should be ONE. At cycle 0, span hash init request and response cancel. - assert_eq!(ONE, b_chip[0]); - assert_eq!(ONE, b_chip[1]); - - // At row 1, the hasher's HOUT response and the CryptoStream's 4 memory requests all occur. - // Extract the HOUT response as a black box. - let hout_response = b_chip[2] * combined_request.inverse(); - assert_eq!(hout_response * combined_request, b_chip[2]); + let log = InteractionLog::new(&trace); + + let mut exp = Expectations::new(&log); + + // CryptoStream runs at cycle 1 (cycle 0 is SPAN), ctx = 0, uninitialized source memory + // (reads return zeros). Ciphertext = plaintext + rate = rate in this case. + const ROW: usize = 1; + let zero_word = [ZERO, ZERO, ZERO, ZERO]; + let cipher1 = [ + Felt::new_unchecked(1), + Felt::new_unchecked(2), + Felt::new_unchecked(3), + Felt::new_unchecked(4), + ]; + let cipher2 = [ + Felt::new_unchecked(5), + Felt::new_unchecked(6), + Felt::new_unchecked(7), + Felt::new_unchecked(8), + ]; - // The chiplets bus should be balanced: final value must be ONE. - assert_eq!(*b_chip.last().unwrap(), ONE); + let mut request_exps_added = 0usize; + // read src_ptr, read src_ptr + 4 + exp.remove(ROW, &MemoryMsg::read_word(ZERO, ZERO, ONE, zero_word)); + request_exps_added += 1; + exp.remove(ROW, &MemoryMsg::read_word(ZERO, FOUR, ONE, zero_word)); + request_exps_added += 1; + // write dst_ptr, write dst_ptr + 4 + exp.remove(ROW, &MemoryMsg::write_word(ZERO, Felt::new_unchecked(8), ONE, cipher1)); + request_exps_added += 1; + exp.remove(ROW, &MemoryMsg::write_word(ZERO, Felt::new_unchecked(12), ONE, cipher2)); + request_exps_added += 1; + + assert_eq!(request_exps_added, 4, "expected 4 CryptoStream request expectations"); + + log.assert_contains(&exp); } -// TEST HELPERS -// ================================================================================================ +/// Regression test for a previously unwired path where `HornerBase`'s two element-reads +/// weren't emitted onto the chiplet-requests bus. HornerBase reads α = (α₀, α₁) from +/// memory at (`alpha_ptr`, `alpha_ptr + 1`) — uninitialized memory returns zeros, and +/// the trace stores those same zeros into helpers[0..2], so a missing request or a +/// swapped addr/clk would fail the subset match. +#[test] +fn hornerbase_emits_two_memory_requests() { + // HornerBase stack layout: [c0..c7, _, _, _, _, _, alpha_ptr, acc_low, acc_high] + let stack = [ + 1, 2, 3, 4, 5, 6, 7, 8, // coeffs[0..8] + 0, 0, 0, 0, 0, // pad (5 slots) + 0, // alpha_ptr (stack[13]) + 0, 0, // acc_low, acc_high + ]; -fn build_expected_bus_element_msg( - challenges: &Challenges, - op_label: u8, - ctx: Felt, - addr: Felt, - clk: Felt, - value: Felt, -) -> Felt { - assert!(op_label == MEMORY_READ_ELEMENT_LABEL || op_label == MEMORY_WRITE_ELEMENT_LABEL); + let trace = build_trace_from_ops(vec![Operation::HornerBase], &stack); + let log = InteractionLog::new(&trace); - challenges.encode( - miden_air::trace::bus_types::CHIPLETS_BUS, - [Felt::from_u8(op_label), ctx, addr, clk, value], - ) -} + let mut exp = Expectations::new(&log); -fn build_expected_bus_word_msg( - challenges: &Challenges, - op_label: u8, - ctx: Felt, - addr: Felt, - clk: Felt, - word: Word, -) -> Felt { - assert!(op_label == MEMORY_READ_WORD_LABEL || op_label == MEMORY_WRITE_WORD_LABEL); + // HornerBase runs at cycle 1 (cycle 0 is SPAN), ctx = 0, uninitialized memory returns zeros. + const ROW: usize = 1; + exp.remove(ROW, &MemoryMsg::read_element(ZERO, ZERO, ONE, ZERO)); + exp.remove(ROW, &MemoryMsg::read_element(ZERO, ONE, ONE, ZERO)); - challenges.encode( - miden_air::trace::bus_types::CHIPLETS_BUS, - [Felt::from_u8(op_label), ctx, addr, clk, word[0], word[1], word[2], word[3]], - ) + log.assert_contains(&exp); } -fn build_expected_bus_msg_from_trace( - trace: &ExecutionTrace, - challenges: &Challenges, - row: RowIndex, -) -> Felt { - // get the memory access operation - let read_write = trace.main_trace.get_column(MEMORY_IS_READ_COL_IDX)[row]; - let element_or_word = trace.main_trace.get_column(MEMORY_IS_WORD_ACCESS_COL_IDX)[row]; - let op_label = if read_write == MEMORY_WRITE { - if element_or_word == MEMORY_ACCESS_ELEMENT { - MEMORY_WRITE_ELEMENT_LABEL - } else { - MEMORY_WRITE_WORD_LABEL - } - } else if read_write == MEMORY_READ { - if element_or_word == MEMORY_ACCESS_ELEMENT { - MEMORY_READ_ELEMENT_LABEL - } else { - MEMORY_READ_WORD_LABEL - } - } else { - panic!("invalid read_write value: {read_write}"); - }; +/// Regression test for a previously unwired path where `HornerExt`'s single word-read +/// wasn't emitted onto the chiplet-requests bus. HornerExt reads `[α₀, α₁, k₀, k₁]` as a +/// single word from `alpha_ptr`; uninitialized memory returns the zero word, which the +/// trace parks in helpers[0..4]. +#[test] +fn hornerext_emits_one_memory_request() { + // HornerExt stack layout: [s0_lo, s0_hi, ..., s3_lo, s3_hi, _, _, _, _, _, alpha_ptr, + // acc_low, acc_high] + let stack = [ + 1, 2, 3, 4, 5, 6, 7, 8, // 4 extension coeffs (s0..s3) + 0, 0, 0, 0, 0, // pad (5 slots) + 0, // alpha_ptr (stack[13]) + 0, 0, // acc_low, acc_high + ]; - // get the memory access data - let ctx = trace.main_trace.get_column(MEMORY_CTX_COL_IDX)[row]; - let addr = { - let word = trace.main_trace.get_column(MEMORY_WORD_COL_IDX)[row]; - let idx1 = trace.main_trace.get_column(MEMORY_IDX1_COL_IDX)[row]; - let idx0 = trace.main_trace.get_column(MEMORY_IDX0_COL_IDX)[row]; + let trace = build_trace_from_ops(vec![Operation::HornerExt], &stack); + let log = InteractionLog::new(&trace); - word + idx1.double() + idx0 - }; - let clk = trace.main_trace.get_column(MEMORY_CLK_COL_IDX)[row]; + let mut exp = Expectations::new(&log); - // get the memory value - let mut word = [ZERO; WORD_SIZE]; - for (i, element) in word.iter_mut().enumerate() { - *element = trace.main_trace.get_column(MEMORY_V_COL_RANGE.start + i)[row]; - } + const ROW: usize = 1; + let zero_word = [ZERO, ZERO, ZERO, ZERO]; + exp.remove(ROW, &MemoryMsg::read_word(ZERO, ZERO, ONE, zero_word)); - if element_or_word == MEMORY_ACCESS_ELEMENT { - let idx1 = trace.main_trace.get_column(MEMORY_IDX1_COL_IDX)[row].as_canonical_u64(); - let idx0 = trace.main_trace.get_column(MEMORY_IDX0_COL_IDX)[row].as_canonical_u64(); - let idx = idx1 * 2 + idx0; + log.assert_contains(&exp); +} - build_expected_bus_element_msg(challenges, op_label, ctx, addr, clk, word[idx as usize]) - } else if element_or_word == MEMORY_ACCESS_WORD { - build_expected_bus_word_msg(challenges, op_label, ctx, addr, clk, word.into()) - } else { - panic!("invalid element_or_word value: {element_or_word}"); - } +fn next_word(main: &MainTrace, next: RowIndex, start: usize) -> [Felt; 4] { + [ + main.stack_element(start, next), + main.stack_element(start + 1, next), + main.stack_element(start + 2, next), + main.stack_element(start + 3, next), + ] } diff --git a/processor/src/trace/tests/chiplets/mod.rs b/processor/src/trace/tests/chiplets/mod.rs index 4f29a1f165..10283dc1d4 100644 --- a/processor/src/trace/tests/chiplets/mod.rs +++ b/processor/src/trace/tests/chiplets/mod.rs @@ -1,13 +1,14 @@ -use miden_air::trace::{ - AUX_TRACE_RAND_CHALLENGES, CHIPLETS_BUS_AUX_TRACE_OFFSET, chiplets::hasher::HASH_CYCLE_LEN, -}; -use miden_core::{ONE, Word, ZERO, operations::Operation}; -use miden_utils_testing::rand::rand_value; - -use super::{ - super::utils::build_span_with_respan_ops, AdviceInputs, ExecutionTrace, Felt, - build_trace_from_ops, build_trace_from_ops_with_inputs, build_trace_from_program, rand_array, -}; +//! Chiplet-bus tests (bitwise, hasher, memory). +//! +//! Each submodule drives a small program against the relevant chiplet and asserts that every +//! chiplet-request (`-1` push) the constraints expect, and every chiplet-response (`+1` push) +//! the chiplet emits, shows up in the prover's `(mult, denom)` bag at the right row. The +//! subset matcher in [`super::lookup_harness`] is column-blind, so a test passes regardless +//! of which aux column the framework routes a given bus message onto. +//! +//! Tests pair the subset match with explicit cardinality guardrails (e.g. "exactly 2 HOUT +//! adds") so a silent-pass bug — extra emissions, missing emissions, or the matcher ignoring +//! a whole category — fails structurally rather than just by shape. mod bitwise; mod hasher; diff --git a/processor/src/trace/tests/decoder.rs b/processor/src/trace/tests/decoder.rs index 9643b73596..a6b72ffd4b 100644 --- a/processor/src/trace/tests/decoder.rs +++ b/processor/src/trace/tests/decoder.rs @@ -1,927 +1,784 @@ -use alloc::vec::Vec; +//! Decoder virtual-table bus tests. +//! +//! The legacy names P1/P2/P3 refer to the block-stack, block-hash queue, and op-group tables +//! respectively. Those dedicated running-product columns no longer exist: block-stack rides M1 +//! (merged with u32 range checks and the log-precompile capacity bus), and block-hash + op-group +//! share M_2+5. The names are kept as historical shorthand for the interaction families. +//! +//! Under the LogUp framework the interactions still look like "+1 / encode(Msg)" on push rows +//! and "-1 / encode(Msg)" on pop rows. Each test runs a tiny program that exercises one +//! push/pop pair (or a small batch) and checks both halves land via the subset matcher. +//! +//! Coverage is targeted rather than exhaustive: the tests below hit the control-flow variants +//! that have historically harbored off-by-one or selector-muxing bugs (JOIN, LOOP+REPEAT, CALL, +//! SPAN/RESPAN op-group batching). Broader end-to-end soundness comes from +//! `build_lookup_fractions_matches_constraint_path_oracle` in `tests/lookup.rs`. -use miden_air::trace::{ - AUX_TRACE_RAND_CHALLENGES, Challenges, - chiplets::hasher::CONTROLLER_ROWS_PER_PERM_FELT, - decoder::{P1_COL_IDX, P2_COL_IDX, P3_COL_IDX}, -}; -use miden_utils_testing::rand::rand_array; +use alloc::vec::Vec; -use super::super::{ - decoder::{BlockHashTableRow, build_op_group}, - tests::{build_trace_from_ops, build_trace_from_program}, - utils::build_span_with_respan_ops, -}; -use crate::{ - ContextId, Felt, ONE, Program, Word, ZERO, - field::{ExtensionField, Field}, +use miden_air::logup::{BlockHashMsg, BlockStackMsg, OpGroupMsg}; +use miden_core::{ + Felt, ONE, ZERO, mast::{ - BasicBlockNodeBuilder, JoinNodeBuilder, LoopNodeBuilder, MastForest, MastForestContributor, - MastNodeExt, SplitNodeBuilder, + BasicBlockNodeBuilder, CallNodeBuilder, JoinNodeBuilder, LoopNodeBuilder, MastForest, + MastForestContributor, SplitNodeBuilder, }, - operation::Operation, + operations::{Operation, opcodes}, + program::Program, }; -// BLOCK STACK TABLE TESTS +use super::{ + ExecutionTrace, build_trace_from_ops, build_trace_from_program, + lookup_harness::{Expectations, InteractionLog}, +}; +use crate::RowIndex; + +// HELPERS // ================================================================================================ -#[test] -fn decoder_p1_span_with_respan() { - let (ops, _) = build_span_with_respan_ops(); - let trace = build_trace_from_ops(ops, &[]); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p1 = aux_columns.get_column(P1_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - let row_values = [ - BlockStackTableRow::new(ONE, ZERO, false).to_value(&challenges), - BlockStackTableRow::new(ONE + CONTROLLER_ROWS_PER_PERM_FELT, ZERO, false) - .to_value(&challenges), - ]; - - // make sure the first entry is ONE - assert_eq!(ONE, p1[0]); - - // when SPAN operation is executed, entry for span block is added to the table - let expected_value = row_values[0]; - assert_eq!(expected_value, p1[1]); - - // for the next 8 cycles (as we execute user ops), the table is not affected - for i in 2..10 { - assert_eq!(expected_value, p1[i]); +/// Calls `f(row, opcode)` for every row except the last. +/// +/// Most decoder tests need `row + 1` lookups (next-row flags, addr_next, etc.) so stopping one +/// short of the end avoids per-test bounds checks. +fn for_each_op(trace: &ExecutionTrace, mut f: F) +where + F: FnMut(usize, Felt), +{ + let main = trace.main_trace(); + let num_rows = main.num_rows(); + for row in 0..num_rows - 1 { + let idx = RowIndex::from(row); + f(row, main.get_op_code(idx)); } +} - // when RESPAN is executed, the first entry is replaced with a new entry - let expected_value = expected_value * row_values[0].inverse() * row_values[1]; - assert_eq!(expected_value, p1[10]); +// BLOCK STACK TABLE (M1) TESTS +// ================================================================================================ - // for the next 11 cycles (as we execute user ops), the table is not affected - for i in 11..22 { - assert_eq!(expected_value, p1[i]); - } +/// A lone SPAN pushes one simple entry and the matching END pops it. +#[test] +fn block_stack_span_push_pop() { + let ops = vec![Operation::Add, Operation::Mul]; + let trace = build_trace_from_ops(ops, &[]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + let idx = RowIndex::from(row); + let addr = main.addr(idx); + let addr_next = main.addr(RowIndex::from(row + 1)); + + if op == Felt::from_u8(opcodes::SPAN) { + exp.add( + row, + &BlockStackMsg::Simple { + block_id: addr_next, + parent_id: addr, + is_loop: ZERO, + }, + ); + } else if op == Felt::from_u8(opcodes::END) { + exp.remove( + row, + &BlockStackMsg::Simple { + block_id: addr, + parent_id: addr_next, + is_loop: ZERO, + }, + ); + } + }); - // at cycle 22, the END operation is executed and the table is cleared - let expected_value = expected_value * row_values[1].inverse(); - assert_eq!(expected_value, ONE); - for i in 22..(p1.len()) { - assert_eq!(ONE, p1[i]); - } + assert_eq!(exp.count_adds(), 1, "expected exactly one SPAN push"); + assert_eq!(exp.count_removes(), 1, "expected exactly one matching END pop"); + log.assert_contains(&exp); } +/// CALL pushes a `Full` entry saving caller context (ctx, fmp, depth, fn_hash); the matching +/// END pops a `Full` entry reading the *next* row's system/stack state (the restored caller +/// context). #[test] -fn decoder_p1_join() { +fn block_stack_call_full_push_pop() { let program = { - let mut mast_forest = MastForest::new(); - - let basic_block_1_id = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new()) - .add_to_forest(&mut mast_forest) + let mut forest = MastForest::new(); + let callee = BasicBlockNodeBuilder::new(vec![Operation::Noop], Vec::new()) + .add_to_forest(&mut forest) .unwrap(); - let basic_block_2_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - let join_id = JoinNodeBuilder::new([basic_block_1_id, basic_block_2_id]) - .add_to_forest(&mut mast_forest) - .unwrap(); - mast_forest.make_root(join_id); - - Program::new(mast_forest.into(), join_id) + let call_id = CallNodeBuilder::new(callee).add_to_forest(&mut forest).unwrap(); + forest.make_root(call_id); + Program::new(forest.into(), call_id) }; - let trace = build_trace_from_program(&program, &[]); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p1 = aux_columns.get_column(P1_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - let a_3 = ONE + CONTROLLER_ROWS_PER_PERM_FELT; - let a_5 = a_3 + CONTROLLER_ROWS_PER_PERM_FELT; - let row_values = [ - BlockStackTableRow::new(ONE, ZERO, false).to_value(&challenges), - BlockStackTableRow::new(a_3, ONE, false).to_value(&challenges), - BlockStackTableRow::new(a_5, ONE, false).to_value(&challenges), - ]; - - // make sure the first entry is ONE - assert_eq!(ONE, p1[0]); - - // when JOIN operation is executed, entry for the JOIN block is added to the table - let mut expected_value = row_values[0]; - assert_eq!(expected_value, p1[1]); - - // when the first SPAN is executed, its entry is added to the table - expected_value *= row_values[1]; - assert_eq!(expected_value, p1[2]); - - // when the user op is executed, the table is not affected - assert_eq!(expected_value, p1[3]); - - // when the first SPAN block ends, its entry is removed from the table - expected_value *= row_values[1].inverse(); - assert_eq!(expected_value, p1[4]); - - // when the second SPAN is executed, its entry is added to the table - expected_value *= row_values[2]; - assert_eq!(expected_value, p1[5]); - - // when the user op is executed, the table is not affected - assert_eq!(expected_value, p1[6]); - - // when the second SPAN block ends, its entry is removed from the table - expected_value *= row_values[2].inverse(); - assert_eq!(expected_value, p1[7]); - - // when the JOIN block ends, its entry is removed from the table - expected_value *= row_values[0].inverse(); - assert_eq!(expected_value, p1[8]); - - // at this point the table should be empty, and thus, all subsequent values must be ONE - assert_eq!(expected_value, ONE); - for i in 9..(p1.len()) { - assert_eq!(ONE, p1[i]); - } + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + let idx = RowIndex::from(row); + let next = RowIndex::from(row + 1); + + if op == Felt::from_u8(opcodes::CALL) { + exp.add( + row, + &BlockStackMsg::Full { + block_id: main.addr(next), + parent_id: main.addr(idx), + is_loop: ZERO, + ctx: main.ctx(idx), + fmp: main.stack_depth(idx), + depth: main.parent_overflow_address(idx), + fn_hash: main.fn_hash(idx), + }, + ); + } + + // END-of-CALL branch: `is_call_flag` at the END row selects the Full variant. Caller + // context is restored on the *next* row, so the emitter reads ctx/b0/b1/fn_hash from + // row+1. + if op == Felt::from_u8(opcodes::END) && main.is_call_flag(idx) == ONE { + exp.remove( + row, + &BlockStackMsg::Full { + block_id: main.addr(idx), + parent_id: main.addr(next), + is_loop: ZERO, + ctx: main.ctx(next), + fmp: main.stack_depth(next), + depth: main.parent_overflow_address(next), + fn_hash: main.fn_hash(next), + }, + ); + } + }); + + assert_eq!(exp.count_adds(), 1, "expected exactly one CALL push"); + assert_eq!(exp.count_removes(), 1, "expected exactly one matching END pop"); + log.assert_contains(&exp); } -#[test] -fn decoder_p1_split() { +/// SPLIT pushes a `Simple { is_loop: 0 }` entry (parent = current block, block = addr_next) and +/// the matching END pops it. Runs twice — once with `s0 = 1` (TRUE branch), once with `s0 = 0` +/// (FALSE branch) — since the block-stack emission is identical either way but the END reached +/// for the matching pop differs between branches. +#[rstest::rstest] +#[case::taken(1)] +#[case::not_taken(0)] +fn block_stack_split_push_pop(#[case] cond: u64) { let program = { - let mut mast_forest = MastForest::new(); - - let basic_block_1_id = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - let basic_block_2_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new()) - .add_to_forest(&mut mast_forest) + let mut f = MastForest::new(); + let t = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new()) + .add_to_forest(&mut f) .unwrap(); - let split_id = SplitNodeBuilder::new([basic_block_1_id, basic_block_2_id]) - .add_to_forest(&mut mast_forest) + let e = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new()) + .add_to_forest(&mut f) .unwrap(); - mast_forest.make_root(split_id); - - Program::new(mast_forest.into(), split_id) + let s = SplitNodeBuilder::new([t, e]).add_to_forest(&mut f).unwrap(); + f.make_root(s); + Program::new(f.into(), s) }; + let trace = build_trace_from_program(&program, &[cond]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut split_adds = 0usize; + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + let idx = RowIndex::from(row); + let addr = main.addr(idx); + let addr_next = main.addr(RowIndex::from(row + 1)); + + if op == Felt::from_u8(opcodes::SPLIT) { + exp.add( + row, + &BlockStackMsg::Simple { + block_id: addr_next, + parent_id: addr, + is_loop: ZERO, + }, + ); + split_adds += 1; + } else if op == Felt::from_u8(opcodes::END) && main.is_call_flag(idx) == ZERO { + // is_loop on the END overlay comes from the typed END flags; for non-loop ENDs it + // is zero. We can read it back from the trace to stay agnostic about which END row + // matches which push. + let is_loop = main.is_loop_flag(idx); + exp.remove( + row, + &BlockStackMsg::Simple { + block_id: addr, + parent_id: addr_next, + is_loop, + }, + ); + } + }); - let trace = build_trace_from_program(&program, &[1]); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p1 = aux_columns.get_column(P1_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - let a_3 = ONE + CONTROLLER_ROWS_PER_PERM_FELT; - let row_values = [ - BlockStackTableRow::new(ONE, ZERO, false).to_value(&challenges), - BlockStackTableRow::new(a_3, ONE, false).to_value(&challenges), - ]; - - // make sure the first entry is ONE - assert_eq!(ONE, p1[0]); - - // when SPLIT operation is executed, entry for the SPLIT block is added to the table - let mut expected_value = row_values[0]; - assert_eq!(expected_value, p1[1]); - - // when the true branch SPAN is executed, its entry is added to the table - expected_value *= row_values[1]; - assert_eq!(expected_value, p1[2]); - - // when the user op is executed, the table is not affected - assert_eq!(expected_value, p1[3]); - - // when the SPAN block ends, its entry is removed from the table - expected_value *= row_values[1].inverse(); - assert_eq!(expected_value, p1[4]); + assert_eq!(split_adds, 1, "expected exactly one SPLIT push"); + // One END for the taken inner branch, one for the SPLIT itself (parent). Both pop Simple. + assert_eq!(exp.count_removes(), 2, "expected two Simple pops (child END + SPLIT END)"); + log.assert_contains(&exp); +} - // when the SPLIT block ends, its entry is removed from the table - expected_value *= row_values[0].inverse(); - assert_eq!(expected_value, p1[5]); +/// LOOP with `s0 = 1` pushes a `Simple { is_loop: 1 }` entry and the matching END pops it. +/// LOOP with `s0 = 0` also pushes (emitter is unconditional on LOOP), but with `is_loop = 0` +/// and no body executes — the END on the very next row pops immediately. This test runs both +/// variants via `rstest`. +#[rstest::rstest] +#[case::enters(1, ONE)] +#[case::skips(0, ZERO)] +fn block_stack_loop_is_loop_flag(#[case] cond: u64, #[case] expected_is_loop: Felt) { + let program = { + let mut f = MastForest::new(); + let body = BasicBlockNodeBuilder::new(vec![Operation::Pad, Operation::Drop], Vec::new()) + .add_to_forest(&mut f) + .unwrap(); + let loop_id = LoopNodeBuilder::new(body).add_to_forest(&mut f).unwrap(); + f.make_root(loop_id); + Program::new(f.into(), loop_id) + }; + // Stack is laid out top-first: `cond` on top (drives LOOP), trailing zero drives the + // single REPEAT/exit read when `cond == 1`. + let trace = build_trace_from_program(&program, &[cond, 0]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut loop_pushes = 0usize; + let mut loop_pops = 0usize; + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + let idx = RowIndex::from(row); + let addr = main.addr(idx); + let addr_next = main.addr(RowIndex::from(row + 1)); + + if op == Felt::from_u8(opcodes::LOOP) { + let is_loop = main.stack_element(0, idx); + assert_eq!(is_loop, expected_is_loop, "s0 sanity at LOOP row"); + exp.add( + row, + &BlockStackMsg::Simple { + block_id: addr_next, + parent_id: addr, + is_loop, + }, + ); + loop_pushes += 1; + } else if op == Felt::from_u8(opcodes::END) + && main.is_call_flag(idx) == ZERO + && main.is_loop_flag(idx) == expected_is_loop + && main.is_loop_body_flag(idx) == ZERO + { + exp.remove( + row, + &BlockStackMsg::Simple { + block_id: addr, + parent_id: addr_next, + is_loop: expected_is_loop, + }, + ); + loop_pops += 1; + } + }); + + assert_eq!(loop_pushes, 1, "expected one LOOP push"); + assert_eq!( + loop_pops, 1, + "expected one matching LOOP END pop (is_loop={expected_is_loop:?})" + ); + log.assert_contains(&exp); +} - // at this point the table should be empty, and thus, all subsequent values must be ONE - assert_eq!(expected_value, ONE); - for i in 6..(p1.len()) { - assert_eq!(ONE, p1[i]); - } +/// RESPAN fires a simultaneous push + pop on the block-stack bus (batch addition is recorded +/// as an Add, and the prior batch's entry is simultaneously Removed). Uses a SPAN long enough +/// to require two batches so at least one RESPAN row exists. +#[test] +fn block_stack_respan_add_and_remove() { + // 80 Noops require two batches (each batch holds up to 72 ops), so the SPAN decomposes + // into SPAN + 64 ops + RESPAN + remaining ops + END. + let ops: Vec = (0..80).map(|_| Operation::Noop).collect(); + let trace = build_trace_from_ops(ops, &[]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut respan_rows = 0usize; + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + if op != Felt::from_u8(opcodes::RESPAN) { + return; + } + let idx = RowIndex::from(row); + let next = RowIndex::from(row + 1); + let addr = main.addr(idx); + let addr_next = main.addr(next); + // The RESPAN emitter uses `h1_next` as the parent link for both the add and remove. + let parent = main.decoder_hasher_state_element(1, next); + + exp.add( + row, + &BlockStackMsg::Simple { + block_id: addr_next, + parent_id: parent, + is_loop: ZERO, + }, + ); + exp.remove( + row, + &BlockStackMsg::Simple { + block_id: addr, + parent_id: parent, + is_loop: ZERO, + }, + ); + respan_rows += 1; + }); + + assert!(respan_rows >= 1, "program did not produce a RESPAN row"); + assert_eq!(exp.count_adds(), respan_rows); + assert_eq!(exp.count_removes(), respan_rows); + log.assert_contains(&exp); } +// BLOCK HASH QUEUE (M_2+5) TESTS +// ================================================================================================ + +/// A JOIN enqueues two children (first + subsequent) and the two child ENDs dequeue them. #[test] -fn decoder_p1_loop_with_repeat() { +fn block_hash_join_enqueue_dequeue() { let program = { let mut mast_forest = MastForest::new(); - - let basic_block_1_id = BasicBlockNodeBuilder::new(vec![Operation::Pad], Vec::new()) + let bb1 = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new()) .add_to_forest(&mut mast_forest) .unwrap(); - let basic_block_2_id = BasicBlockNodeBuilder::new(vec![Operation::Drop], Vec::new()) + let bb2 = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new()) .add_to_forest(&mut mast_forest) .unwrap(); - let join_id = JoinNodeBuilder::new([basic_block_1_id, basic_block_2_id]) - .add_to_forest(&mut mast_forest) - .unwrap(); - let loop_node_id = LoopNodeBuilder::new(join_id).add_to_forest(&mut mast_forest).unwrap(); - mast_forest.make_root(loop_node_id); - - Program::new(mast_forest.into(), loop_node_id) + let join_id = JoinNodeBuilder::new([bb1, bb2]).add_to_forest(&mut mast_forest).unwrap(); + mast_forest.make_root(join_id); + Program::new(mast_forest.into(), join_id) }; + let trace = build_trace_from_program(&program, &[]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + let idx = RowIndex::from(row); + let next = RowIndex::from(row + 1); + let addr_next = main.addr(next); + let first = main.decoder_hasher_state_first_half(idx); + let h0: [Felt; 4] = [first[0], first[1], first[2], first[3]]; + let second = main.decoder_hasher_state_second_half(idx); + let h1: [Felt; 4] = [second[0], second[1], second[2], second[3]]; + + if op == Felt::from_u8(opcodes::JOIN) { + exp.add(row, &BlockHashMsg::FirstChild { parent: addr_next, child_hash: h0 }); + exp.add(row, &BlockHashMsg::Child { parent: addr_next, child_hash: h1 }); + } - // Input [1, 1, 0]: position 0 (top) = 1 (1st iteration enters) - // After Pad+Drop: position 0 = 1 (2nd iteration enters) - // After Pad+Drop: position 0 = 0 (loop exits) - let trace = build_trace_from_program(&program, &[1, 1, 0]); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p1 = aux_columns.get_column(P1_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - // The loop node consumes the first hasher cycle; join/span addresses follow sequentially. - let a_3 = ONE + CONTROLLER_ROWS_PER_PERM_FELT; // address of the JOIN block in the first iteration - let a_5 = a_3 + CONTROLLER_ROWS_PER_PERM_FELT; // address of the first SPAN block in the first iteration - let a_7 = a_5 + CONTROLLER_ROWS_PER_PERM_FELT; // address of the second SPAN block in the first iteration - let a_9 = a_7 + CONTROLLER_ROWS_PER_PERM_FELT; // address of the JOIN block in the second iteration - let a_11 = a_9 + CONTROLLER_ROWS_PER_PERM_FELT; // address of the first SPAN block in the second iteration - let a_13 = a_11 + CONTROLLER_ROWS_PER_PERM_FELT; // address of the second SPAN block in the second iteration - let row_values = [ - BlockStackTableRow::new(ONE, ZERO, true).to_value(&challenges), - BlockStackTableRow::new(a_3, ONE, false).to_value(&challenges), - BlockStackTableRow::new(a_5, a_3, false).to_value(&challenges), - BlockStackTableRow::new(a_7, a_3, false).to_value(&challenges), - BlockStackTableRow::new(a_9, ONE, false).to_value(&challenges), - BlockStackTableRow::new(a_11, a_9, false).to_value(&challenges), - BlockStackTableRow::new(a_13, a_9, false).to_value(&challenges), - ]; - - // make sure the first entry is ONE - assert_eq!(ONE, p1[0]); - - // --- first iteration ---------------------------------------------------- - - // when LOOP operation is executed, entry for the LOOP block is added to the table - let mut expected_value = row_values[0]; - assert_eq!(expected_value, p1[1]); - - // when JOIN operation is executed, entry for the JOIN block is added to the table - expected_value *= row_values[1]; - assert_eq!(expected_value, p1[2]); - - // when the first SPAN is executed, its entry is added to the table - expected_value *= row_values[2]; - assert_eq!(expected_value, p1[3]); - - // when the user op is executed, the table is not affected - assert_eq!(expected_value, p1[4]); - - // when the first SPAN block ends, its entry is removed from the table - expected_value *= row_values[2].inverse(); - assert_eq!(expected_value, p1[5]); - - // when the second SPAN is executed, its entry is added to the table - expected_value *= row_values[3]; - assert_eq!(expected_value, p1[6]); - - // when the user op is executed, the table is not affected - assert_eq!(expected_value, p1[7]); - - // when the second SPAN block ends, its entry is removed from the table - expected_value *= row_values[3].inverse(); - assert_eq!(expected_value, p1[8]); - - // when the JOIN block ends, its entry is removed from the table - expected_value *= row_values[1].inverse(); - assert_eq!(expected_value, p1[9]); - - // --- second iteration --------------------------------------------------- - - // when REPEAT operation is executed, the table is not affected - assert_eq!(expected_value, p1[10]); - - // when JOIN operation is executed, entry for the JOIN block is added to the table - expected_value *= row_values[4]; - assert_eq!(expected_value, p1[11]); - - // when the first SPAN is executed, its entry is added to the table - expected_value *= row_values[5]; - assert_eq!(expected_value, p1[12]); - - // when the user op is executed, the table is not affected - assert_eq!(expected_value, p1[13]); - - // when the first SPAN block ends, its entry is removed from the table - expected_value *= row_values[5].inverse(); - assert_eq!(expected_value, p1[14]); - - // when the second SPAN is executed, its entry is added to the table - expected_value *= row_values[6]; - assert_eq!(expected_value, p1[15]); - - // when the user op is executed, the table is not affected - assert_eq!(expected_value, p1[16]); - - // when the second SPAN block ends, its entry is removed from the table - expected_value *= row_values[6].inverse(); - assert_eq!(expected_value, p1[17]); - - // when the JOIN block ends, its entry is removed from the table - expected_value *= row_values[4].inverse(); - assert_eq!(expected_value, p1[18]); - - // when the LOOP block ends, its entry is removed from the table - expected_value *= row_values[0].inverse(); - assert_eq!(expected_value, p1[19]); - - // at this point the table should be empty, and thus, all subsequent values must be ONE - assert_eq!(expected_value, ONE); - for i in 20..(p1.len()) { - assert_eq!(ONE, p1[i]); - } -} + // Emitter uses `is_first_child = 1 - end_next - repeat_next - halt_next` (arithmetic). + // Since END/REPEAT/HALT are distinct 7-bit opcodes, at most one flag is non-zero per + // row, so the arithmetic form collapses to the trace-level OR. We still encode it + // arithmetically to mirror the constraint expression exactly. + if op == Felt::from_u8(opcodes::END) { + let next_end = if main.get_op_code(next) == Felt::from_u8(opcodes::END) { + ONE + } else { + ZERO + }; + let next_repeat = if main.get_op_code(next) == Felt::from_u8(opcodes::REPEAT) { + ONE + } else { + ZERO + }; + let next_halt = if main.get_op_code(next) == Felt::from_u8(opcodes::HALT) { + ONE + } else { + ZERO + }; + let is_first_child = ONE - next_end - next_repeat - next_halt; + let is_loop_body = main.is_loop_body_flag(idx); + exp.remove( + row, + &BlockHashMsg::End { + parent: addr_next, + child_hash: h0, + is_first_child, + is_loop_body, + }, + ); + } + }); -// BLOCK HASH TABLE TESTS -// ================================================================================================ + // JOIN enqueues 2 children; ENDs fire for bb1, bb2, and the JOIN itself (3 total). + assert_eq!(exp.count_adds(), 2, "expected JOIN to enqueue FirstChild + Child"); + assert_eq!(exp.count_removes(), 3, "expected an END dequeue for bb1, bb2, and JOIN"); + log.assert_contains(&exp); +} +/// LOOP (when `s0 = 1`) and REPEAT both enqueue a `LoopBody` entry for the body, and the END +/// at the end of each body dequeues it with `is_loop_body = 1`. Runs two iterations +/// (advice/inputs `[1, 1, 0]`) so both the LOOP and REPEAT branches fire. #[test] -fn decoder_p2_span_with_respan() { +fn block_hash_loop_body_with_repeat() { let program = { let mut mast_forest = MastForest::new(); - - let (ops, _) = build_span_with_respan_ops(); - let basic_block_id = BasicBlockNodeBuilder::new(ops, Vec::new()) + let bb1 = BasicBlockNodeBuilder::new(vec![Operation::Pad], Vec::new()) .add_to_forest(&mut mast_forest) .unwrap(); - mast_forest.make_root(basic_block_id); - - Program::new(mast_forest.into(), basic_block_id) + let bb2 = BasicBlockNodeBuilder::new(vec![Operation::Drop], Vec::new()) + .add_to_forest(&mut mast_forest) + .unwrap(); + let join_id = JoinNodeBuilder::new([bb1, bb2]).add_to_forest(&mut mast_forest).unwrap(); + let loop_id = LoopNodeBuilder::new(join_id).add_to_forest(&mut mast_forest).unwrap(); + mast_forest.make_root(loop_id); + Program::new(mast_forest.into(), loop_id) }; - let trace = build_trace_from_program(&program, &[]); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p2 = aux_columns.get_column(P2_COL_IDX); - let challenges = Challenges::::new(challenges[0], challenges[1]); - let program_hash_msg = - BlockHashTableRow::new_test(ZERO, program.hash(), false, false).collapse(&challenges); - - // p2 starts at identity (1) - let mut expected_value = ONE; - assert_eq!(expected_value, p2[0]); - - // as operations inside the span execute (including RESPAN), the table is not affected - for i in 1..22 { - assert_eq!(expected_value, p2[i]); - } + // Stack `[1, 1, 0]` drives two iterations: enter, repeat, exit. + let trace = build_trace_from_program(&program, &[1, 1, 0]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut fired_loop_body = 0usize; + let mut fired_loop_body_end = 0usize; + + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + let idx = RowIndex::from(row); + let next = RowIndex::from(row + 1); + let first = main.decoder_hasher_state_first_half(idx); + let h0: [Felt; 4] = [first[0], first[1], first[2], first[3]]; + let addr_next = main.addr(next); + + // `f_loop_body = loop_op * s0 + repeat`. LOOP with `s0 = 0` does not enter the body, so + // the body is only enqueued when `s0 = 1` (first iteration entry) or on REPEAT. + let is_loop_entering = + op == Felt::from_u8(opcodes::LOOP) && main.stack_element(0, idx) == ONE; + let is_repeat = op == Felt::from_u8(opcodes::REPEAT); + if is_loop_entering || is_repeat { + exp.add(row, &BlockHashMsg::LoopBody { parent: addr_next, child_hash: h0 }); + fired_loop_body += 1; + } - // at cycle 22, the END operation removes the root block hash (unmatched by any add) - expected_value *= program_hash_msg.inverse(); - for i in 22..(p2.len()) { - assert_eq!(expected_value, p2[i]); - } -} + // END of the loop body: `is_loop_body` bit is set on the END overlay. + if op == Felt::from_u8(opcodes::END) && main.is_loop_body_flag(idx) == ONE { + let next_end = if main.get_op_code(next) == Felt::from_u8(opcodes::END) { + ONE + } else { + ZERO + }; + let next_repeat = if main.get_op_code(next) == Felt::from_u8(opcodes::REPEAT) { + ONE + } else { + ZERO + }; + let next_halt = if main.get_op_code(next) == Felt::from_u8(opcodes::HALT) { + ONE + } else { + ZERO + }; + let is_first_child = ONE - next_end - next_repeat - next_halt; + exp.remove( + row, + &BlockHashMsg::End { + parent: addr_next, + child_hash: h0, + is_first_child, + is_loop_body: ONE, + }, + ); + fired_loop_body_end += 1; + } + }); -#[test] -fn decoder_p2_join() { - let mut mast_forest = MastForest::new(); - - let basic_block_1_id = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - let basic_block_2_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - - let join_id = JoinNodeBuilder::new([basic_block_1_id, basic_block_2_id]) - .add_to_forest(&mut mast_forest) - .unwrap(); - let basic_block_1 = mast_forest[basic_block_1_id].clone(); - let basic_block_2 = mast_forest[basic_block_2_id].clone(); - let join = mast_forest[join_id].clone(); - mast_forest.make_root(join_id); - - let program = Program::new(mast_forest.into(), join_id); + // Sanity: each iteration fires one LoopBody enqueue and one body-ending END remove. + assert_eq!(fired_loop_body, 2, "expected LOOP + REPEAT to each fire a LoopBody enqueue"); + assert_eq!(fired_loop_body_end, 2, "expected one END-of-loop-body remove per iteration"); - let trace = build_trace_from_program(&program, &[]); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p2 = aux_columns.get_column(P2_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - let program_hash_msg = - BlockHashTableRow::new_test(ZERO, join.digest(), false, false).collapse(&challenges); - let child1_msg = - BlockHashTableRow::new_test(ONE, basic_block_1.digest(), true, false).collapse(&challenges); - let child2_msg = BlockHashTableRow::new_test(ONE, basic_block_2.digest(), false, false) - .collapse(&challenges); - - // p2 starts at identity (1) - let mut expected_value = ONE; - assert_eq!(expected_value, p2[0]); - - // when JOIN operation is executed, entries for both children are added to the table - expected_value *= child1_msg * child2_msg; - assert_eq!(expected_value, p2[1]); - - // for the next 2 cycles, the table is not affected - assert_eq!(expected_value, p2[2]); - assert_eq!(expected_value, p2[3]); - - // when the first SPAN block ends, its entry is removed from the table - expected_value *= child1_msg.inverse(); - assert_eq!(expected_value, p2[4]); - - // for the next 2 cycles, the table is not affected - assert_eq!(expected_value, p2[5]); - assert_eq!(expected_value, p2[6]); - - // when the second SPAN block ends, its entry is removed from the table - expected_value *= child2_msg.inverse(); - assert_eq!(expected_value, p2[7]); - - // when the JOIN block ends, its entry (the root hash) is removed (unmatched by any add) - expected_value *= program_hash_msg.inverse(); - assert_eq!(expected_value, p2[8]); - - // the final value is 1/program_hash_msg - for i in 9..(p2.len()) { - assert_eq!(expected_value, p2[i]); - } + log.assert_contains(&exp); } -#[test] -fn decoder_p2_split_true() { - // build program - let mut mast_forest = MastForest::new(); - - let basic_block_1_id = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - let basic_block_2_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - let basic_block_1 = mast_forest[basic_block_1_id].clone(); - let _basic_block_2 = mast_forest[basic_block_2_id].clone(); - let split_id = SplitNodeBuilder::new([basic_block_1_id, basic_block_2_id]) - .add_to_forest(&mut mast_forest) - .unwrap(); - mast_forest.make_root(split_id); - - let program = Program::new(mast_forest.into(), split_id); - - // build trace from program - let trace = build_trace_from_program(&program, &[1]); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p2 = aux_columns.get_column(P2_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - let program_hash_msg = - BlockHashTableRow::new_test(ZERO, program.hash(), false, false).collapse(&challenges); - let child_msg = BlockHashTableRow::new_test(ONE, basic_block_1.digest(), false, false) - .collapse(&challenges); - - // p2 starts at identity (1) - let mut expected_value = ONE; - assert_eq!(expected_value, p2[0]); - - // when SPLIT operation is executed, entry for the true branch is added to the table - expected_value *= child_msg; - assert_eq!(expected_value, p2[1]); - - // for the next 2 cycles, the table is not affected - assert_eq!(expected_value, p2[2]); - assert_eq!(expected_value, p2[3]); - - // when the SPAN block ends, its entry is removed from the table - expected_value *= child_msg.inverse(); - assert_eq!(expected_value, p2[4]); - - // when the SPLIT block ends, its entry (the root hash) is removed (unmatched by any add) - expected_value *= program_hash_msg.inverse(); - assert_eq!(expected_value, p2[5]); - - // the final value is 1/program_hash_msg - for i in 6..(p2.len()) { - assert_eq!(expected_value, p2[i]); - } -} +/// SPLIT enqueues exactly one `Child` entry carrying the `s0`-muxed child hash +/// (`s0 * h_0 + (1 - s0) * h_1`); the matching END on the taken branch dequeues it. +#[rstest::rstest] +#[case::taken(1)] +#[case::not_taken(0)] +fn block_hash_split_enqueue_dequeue(#[case] cond: u64) { + let program = { + let mut f = MastForest::new(); + let t = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new()) + .add_to_forest(&mut f) + .unwrap(); + let e = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new()) + .add_to_forest(&mut f) + .unwrap(); + let s = SplitNodeBuilder::new([t, e]).add_to_forest(&mut f).unwrap(); + f.make_root(s); + Program::new(f.into(), s) + }; + let trace = build_trace_from_program(&program, &[cond]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut split_rows = 0usize; + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + let idx = RowIndex::from(row); + let next = RowIndex::from(row + 1); + let addr_next = main.addr(next); + let first = main.decoder_hasher_state_first_half(idx); + let second = main.decoder_hasher_state_second_half(idx); + + if op == Felt::from_u8(opcodes::SPLIT) { + let s0 = main.stack_element(0, idx); + let one_minus_s0 = ONE - s0; + let child_hash: [Felt; 4] = + std::array::from_fn(|i| s0 * first[i] + one_minus_s0 * second[i]); + exp.add(row, &BlockHashMsg::Child { parent: addr_next, child_hash }); + split_rows += 1; + } -#[test] -fn decoder_p2_split_false() { - // build program - let mut mast_forest = MastForest::new(); - - let basic_block_1_id = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - let basic_block_2_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - let _basic_block_1 = mast_forest[basic_block_1_id].clone(); - let basic_block_2 = mast_forest[basic_block_2_id].clone(); - - let split_id = SplitNodeBuilder::new([basic_block_1_id, basic_block_2_id]) - .add_to_forest(&mut mast_forest) - .unwrap(); - mast_forest.make_root(split_id); - - let program = Program::new(mast_forest.into(), split_id); - - // build trace from program - let trace = build_trace_from_program(&program, &[0]); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p2 = aux_columns.get_column(P2_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - let program_hash_msg = - BlockHashTableRow::new_test(ZERO, program.hash(), false, false).collapse(&challenges); - let child_msg = BlockHashTableRow::new_test(ONE, basic_block_2.digest(), false, false) - .collapse(&challenges); - - // p2 starts at identity (1) - let mut expected_value = ONE; - assert_eq!(expected_value, p2[0]); - - // when SPLIT operation is executed, entry for the false branch is added to the table - expected_value *= child_msg; - assert_eq!(expected_value, p2[1]); - - // for the next 2 cycles, the table is not affected - assert_eq!(expected_value, p2[2]); - assert_eq!(expected_value, p2[3]); - - // when the SPAN block ends, its entry is removed from the table - expected_value *= child_msg.inverse(); - assert_eq!(expected_value, p2[4]); - - // when the SPLIT block ends, its entry (the root hash) is removed (unmatched by any add) - expected_value *= program_hash_msg.inverse(); - assert_eq!(expected_value, p2[5]); - - // the final value is 1/program_hash_msg - for i in 6..(p2.len()) { - assert_eq!(expected_value, p2[i]); - } -} + if op == Felt::from_u8(opcodes::END) { + let is_loop_body = main.is_loop_body_flag(idx); + let h0: [Felt; 4] = [first[0], first[1], first[2], first[3]]; + let op_next = main.get_op_code(next); + let next_end = if op_next == Felt::from_u8(opcodes::END) { + ONE + } else { + ZERO + }; + let next_repeat = if op_next == Felt::from_u8(opcodes::REPEAT) { + ONE + } else { + ZERO + }; + let next_halt = if op_next == Felt::from_u8(opcodes::HALT) { + ONE + } else { + ZERO + }; + let is_first_child = ONE - next_end - next_repeat - next_halt; + exp.remove( + row, + &BlockHashMsg::End { + parent: addr_next, + child_hash: h0, + is_first_child, + is_loop_body, + }, + ); + } + }); -#[test] -fn decoder_p2_loop_with_repeat() { - // build program - let mut mast_forest = MastForest::new(); - - let basic_block_1_id = BasicBlockNodeBuilder::new(vec![Operation::Pad], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - let basic_block_2_id = BasicBlockNodeBuilder::new(vec![Operation::Drop], Vec::new()) - .add_to_forest(&mut mast_forest) - .unwrap(); - - let join_id = JoinNodeBuilder::new([basic_block_1_id, basic_block_2_id]) - .add_to_forest(&mut mast_forest) - .unwrap(); - let basic_block_1 = mast_forest[basic_block_1_id].clone(); - let basic_block_2 = mast_forest[basic_block_2_id].clone(); - let join = mast_forest[join_id].clone(); - - let loop_node_id = LoopNodeBuilder::new(join_id).add_to_forest(&mut mast_forest).unwrap(); - mast_forest.make_root(loop_node_id); - - let program = Program::new(mast_forest.into(), loop_node_id); - - // Input [1, 1, 0]: position 0 (top) = 1 (1st iteration enters) - // After Pad+Drop: position 0 = 1 (2nd iteration enters) - // After Pad+Drop: position 0 = 0 (loop exits) - let trace = build_trace_from_program(&program, &[1, 1, 0]); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p2 = aux_columns.get_column(P2_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - // The loop node consumes the first hasher cycle; join/span addresses follow sequentially. - let a_3 = ONE + CONTROLLER_ROWS_PER_PERM_FELT; // address of the JOIN block in the first iteration - let a_9 = a_3 + CONTROLLER_ROWS_PER_PERM_FELT * Felt::new_unchecked(3); // address of the JOIN block in the second iteration - let program_hash_msg = - BlockHashTableRow::new_test(ZERO, program.hash(), false, false).collapse(&challenges); - let loop_body_msg = - BlockHashTableRow::new_test(ONE, join.digest(), false, true).collapse(&challenges); - let child1_iter1 = - BlockHashTableRow::new_test(a_3, basic_block_1.digest(), true, false).collapse(&challenges); - let child2_iter1 = BlockHashTableRow::new_test(a_3, basic_block_2.digest(), false, false) - .collapse(&challenges); - let child1_iter2 = - BlockHashTableRow::new_test(a_9, basic_block_1.digest(), true, false).collapse(&challenges); - let child2_iter2 = BlockHashTableRow::new_test(a_9, basic_block_2.digest(), false, false) - .collapse(&challenges); - - // p2 starts at identity (1) - let mut expected_value = ONE; - assert_eq!(expected_value, p2[0]); - - // --- first iteration ---------------------------------------------------- - - // when LOOP operation is executed, entry for loop body is added to the table - expected_value *= loop_body_msg; - assert_eq!(expected_value, p2[1]); - - // when JOIN operation is executed, entries for both children are added to the table - expected_value *= child1_iter1 * child2_iter1; - assert_eq!(expected_value, p2[2]); - - // for the next 2 cycles, the table is not affected - assert_eq!(expected_value, p2[3]); - assert_eq!(expected_value, p2[4]); - - // when the first SPAN block ends, its entry is removed from the table - expected_value *= child1_iter1.inverse(); - assert_eq!(expected_value, p2[5]); - - // for the next 2 cycles, the table is not affected - assert_eq!(expected_value, p2[6]); - assert_eq!(expected_value, p2[7]); - - // when the second SPAN block ends, its entry is removed from the table - expected_value *= child2_iter1.inverse(); - assert_eq!(expected_value, p2[8]); - - // when the JOIN block ends, its entry is removed from the table - expected_value *= loop_body_msg.inverse(); - assert_eq!(expected_value, p2[9]); - - // --- second iteration --------------------------------------------------- - - // when REPEAT operation is executed, entry for loop body is again added to the table - expected_value *= loop_body_msg; - assert_eq!(expected_value, p2[10]); - - // when JOIN operation is executed, entries for both children are added to the table - expected_value *= child1_iter2 * child2_iter2; - assert_eq!(expected_value, p2[11]); - - // for the next 2 cycles, the table is not affected - assert_eq!(expected_value, p2[12]); - assert_eq!(expected_value, p2[13]); - - // when the first SPAN block ends, its entry is removed from the table - expected_value *= child1_iter2.inverse(); - assert_eq!(expected_value, p2[14]); - - // for the next 2 cycles, the table is not affected - assert_eq!(expected_value, p2[15]); - assert_eq!(expected_value, p2[16]); - - // when the second SPAN block ends, its entry is removed from the table - expected_value *= child2_iter2.inverse(); - assert_eq!(expected_value, p2[17]); - - // when the JOIN block ends, its entry is removed from the table - expected_value *= loop_body_msg.inverse(); - assert_eq!(expected_value, p2[18]); - - // when the LOOP block ends, its entry (the root hash) is removed (unmatched by any add) - expected_value *= program_hash_msg.inverse(); - assert_eq!(expected_value, p2[19]); - - // the final value is 1/program_hash_msg - for i in 20..(p2.len()) { - assert_eq!(expected_value, p2[i]); - } + assert_eq!(split_rows, 1, "expected exactly one SPLIT enqueue"); + // END fires for: taken branch's child, the SPLIT itself. Two removes total. + assert_eq!(exp.count_removes(), 2, "expected END pops for child + SPLIT: cond={cond}"); + log.assert_contains(&exp); } -// OP GROUP TABLE TESTS +// OP GROUP TABLE (M_2+5) TESTS // ================================================================================================ +/// A SPAN whose batch holds 8 op groups triggers the g8 insert batch (7 adds for positions 1..=7; +/// position 0 is consumed inline by the SPAN decode row and not inserted). Each in-span decode +/// row where `group_count` decrements emits a matching remove — covered in +/// [`op_group_span_removal_covers_decode_rows`]. +/// +/// A batch of 64 Noops was picked because each op group packs 9 seven-bit opcodes into a 63-bit +/// group value, so 8 groups hold 72 ops max; 64 Noops reliably fills the batch up to the g8 +/// threshold (`c0 == 1`) without spilling into a second batch. #[test] -fn decoder_p3_trace_empty_table() { - let stack = [1, 2]; - let operations = vec![Operation::Add]; - let trace = build_trace_from_ops(operations, &stack); - - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - - // no rows should have been added or removed from the op group table, and thus, all values - // in the column must be ONE - let p3 = aux_columns.get_column(P3_COL_IDX); - for &value in p3.iter().take(p3.len()) { - assert_eq!(ONE, value); - } -} - -#[test] -fn decoder_p3_trace_one_batch() { - let stack = [1, 2, 3, 4, 5, 6, 7, 8]; - let ops = vec![ - Operation::Add, - Operation::Mul, - Operation::Add, - Operation::Push(ONE), - Operation::Add, - Operation::Mul, - Operation::Add, - Operation::Push(Felt::new_unchecked(2)), - Operation::Add, - Operation::Swap, - Operation::Mul, - Operation::Add, - ]; - let trace = build_trace_from_ops(ops.clone(), &stack); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p3 = aux_columns.get_column(P3_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - - // make sure the first entry is ONE - assert_eq!(ONE, p3[0]); - - // make sure 3 groups were inserted at clock cycle 1; these entries are for the two immediate - // values and the second operation group consisting of [SWAP, MUL, ADD] - let g1_value = OpGroupTableRow::new(ONE, Felt::new_unchecked(3), ONE).to_value(&challenges); - let g2_value = OpGroupTableRow::new(ONE, Felt::new_unchecked(2), Felt::new_unchecked(2)) - .to_value(&challenges); - let g3_value = OpGroupTableRow::new(ONE, ONE, build_op_group(&ops[9..])).to_value(&challenges); - let expected_value = g1_value * g2_value * g3_value; - assert_eq!(expected_value, p3[1]); - - // for the next 3 cycles (2, 3, 4), op group table doesn't change - for i in 2..5 { - assert_eq!(expected_value, p3[i]); - } - - // at cycle 5, when PUSH(1) is executed, the entry for the first group is removed from the - // table - let expected_value = expected_value / g1_value; - assert_eq!(expected_value, p3[5]); - - // for the next 3 cycles (6, 7, 8), op group table doesn't change - for i in 6..9 { - assert_eq!(expected_value, p3[i]); - } - - // at cycle 9, when PUSH(2) is executed, the entry for the second group is removed from the - // table - let expected_value = expected_value / g2_value; - assert_eq!(expected_value, p3[9]); +fn op_group_span_8_groups_inserts() { + let ops: Vec = (0..64).map(|_| Operation::Noop).collect(); + let trace = build_trace_from_ops(ops, &[]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut g8_rows_seen = 0usize; + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + let idx = RowIndex::from(row); + if op != Felt::from_u8(opcodes::SPAN) && op != Felt::from_u8(opcodes::RESPAN) { + return; + } + let batch_flags = main.op_batch_flag(idx); + if batch_flags[0] != ONE { + return; + } + g8_rows_seen += 1; + + let addr_next = main.addr(RowIndex::from(row + 1)); + let gc = main.group_count(idx); + let first = main.decoder_hasher_state_first_half(idx); + let second = main.decoder_hasher_state_second_half(idx); + for i in 1u16..=3 { + let group_value = first[i as usize]; + exp.add(row, &OpGroupMsg::new(&addr_next, gc, i, group_value)); + } + for i in 4u16..=7 { + let group_value = second[(i - 4) as usize]; + exp.add(row, &OpGroupMsg::new(&addr_next, gc, i, group_value)); + } + }); - // at cycle 10, op group 0 is completed, and the entry for the next op group is removed from - // the table - let expected_value = expected_value / g3_value; - assert_eq!(expected_value, p3[10]); + assert!(g8_rows_seen > 0, "program did not produce a g8 SPAN/RESPAN batch"); + assert_eq!( + exp.count_adds(), + 7 * g8_rows_seen, + "expected 7 g8 inserts per SPAN/RESPAN row (positions 1..=7)" + ); + assert_eq!(exp.count_removes(), 0, "op_group_span_8_groups_inserts only checks inserts"); - // at this point, the table should be empty and thus, running product should be ONE - assert_eq!(expected_value, ONE); - for i in 11..(p3.len()) { - assert_eq!(ONE, p3[i]); - } + log.assert_contains(&exp); } +/// Every in-span decode row where `group_count` strictly decrements removes one entry from the +/// op-group table. The removal's `group_value` is muxed by `is_push`: +/// +/// - PUSH rows: pull the immediate from `stk_next[0]` (pushed value is at stack top next cycle). +/// - Non-PUSH rows: `group_value = h0_next · 128 + opcode_next` — the residual group value after +/// the current op is "peeled off" the low 7 bits. +/// +/// Includes at least one PUSH to exercise both mux branches and enough in-group ops to force a +/// boundary decrement where the emitter could otherwise off-by-one. #[test] -fn decoder_p3_trace_two_batches() { - let (ops, iv) = build_span_with_respan_ops(); +fn op_group_span_removal_covers_decode_rows() { + // Two full groups (9 Noops) + PUSH(immediate) + a handful more. The 9th Noop closes the + // first op group (non-PUSH decrement, exercising the `h0_next * 128 + opcode_next` branch) + // and the PUSH pulls its immediate from a dedicated group (exercising the `stk_next[0]` + // branch). + let mut ops: Vec = (0..9).map(|_| Operation::Noop).collect(); + ops.push(Operation::Push(Felt::new_unchecked(42))); + ops.extend(vec![Operation::Add, Operation::Mul, Operation::Drop]); let trace = build_trace_from_ops(ops, &[]); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p3 = aux_columns.get_column(P3_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - - // make sure the first entry is ONE - assert_eq!(ONE, p3[0]); - - // --- first batch ---------------------------------------------------------------------------- - // make sure entries for 7 groups were inserted at clock cycle 1 - let b0_values = [ - OpGroupTableRow::new(ONE, Felt::new_unchecked(11), iv[0]).to_value(&challenges), - OpGroupTableRow::new(ONE, Felt::new_unchecked(10), iv[1]).to_value(&challenges), - OpGroupTableRow::new(ONE, Felt::new_unchecked(9), iv[2]).to_value(&challenges), - OpGroupTableRow::new(ONE, Felt::new_unchecked(8), iv[3]).to_value(&challenges), - OpGroupTableRow::new(ONE, Felt::new_unchecked(7), iv[4]).to_value(&challenges), - OpGroupTableRow::new(ONE, Felt::new_unchecked(6), iv[5]).to_value(&challenges), - OpGroupTableRow::new(ONE, Felt::new_unchecked(5), iv[6]).to_value(&challenges), - ]; - let mut expected_value: Felt = b0_values.iter().fold(ONE, |acc, &val| acc * val); - assert_eq!(expected_value, p3[1]); - - // for the next 7 cycles (2, 3, 4, 5, 6, 7, 8), an entry for an op group is removed from the - // table - for (i, clk) in (2..9).enumerate() { - expected_value /= b0_values[i]; - assert_eq!(expected_value, p3[clk]); - } - - // at cycle 9, when we execute a NOOP to finish the first batch, op group table doesn't change; - // also, at this point op group table must be empty - assert_eq!(expected_value, p3[9]); - assert_eq!(expected_value, ONE); - - // --- second batch --------------------------------------------------------------------------- - // make sure entries for 3 group are inserted at clock cycle 10 (when RESPAN is executed) - // group 3 consists of two DROP operations which do not fit into group 0 - let batch1_addr = ONE + CONTROLLER_ROWS_PER_PERM_FELT; - let op_group3 = build_op_group(&[Operation::Drop; 2]); - let b1_values = [ - OpGroupTableRow::new(batch1_addr, Felt::new_unchecked(3), iv[7]).to_value(&challenges), - OpGroupTableRow::new(batch1_addr, Felt::new_unchecked(2), iv[8]).to_value(&challenges), - OpGroupTableRow::new(batch1_addr, ONE, op_group3).to_value(&challenges), - ]; - let mut expected_value: Felt = b1_values.iter().fold(ONE, |acc, &val| acc * val); - assert_eq!(expected_value, p3[10]); - - // for the next 2 cycles (11, 12), an entry for an op group is removed from the table - for (i, clk) in (11..13).enumerate() { - expected_value *= b1_values[i].inverse(); - assert_eq!(expected_value, p3[clk]); - } - - // then, as we executed ADD and DROP operations for group 0, op group table doesn't change - for i in 13..19 { - assert_eq!(expected_value, p3[i]); - } - - // at cycle 19 we start executing group 3 - so, the entry for the last op group is removed - // from the table - expected_value *= b1_values[2].inverse(); - assert_eq!(expected_value, p3[19]); - - // at this point, the table should be empty and thus, running product should be ONE - assert_eq!(expected_value, ONE); - for i in 20..(p3.len()) { - assert_eq!(ONE, p3[i]); - } -} - -// HELPER STRUCTS AND METHODS -// ================================================================================================ - -/// Describes a single entry in the block stack table. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BlockStackTableRow { - block_id: Felt, - parent_id: Felt, - is_loop: bool, - parent_ctx: ContextId, - parent_fn_hash: Word, - parent_fmp: Felt, - parent_stack_depth: u32, - parent_next_overflow_addr: Felt, -} - -impl BlockStackTableRow { - /// Returns a new [BlockStackTableRow] instantiated with the specified parameters. This is - /// used for test purpose only. - #[cfg(test)] - pub fn new(block_id: Felt, parent_id: Felt, is_loop: bool) -> Self { - Self { - block_id, - parent_id, - is_loop, - parent_ctx: ContextId::root(), - parent_fn_hash: miden_core::EMPTY_WORD, - parent_fmp: ZERO, - parent_stack_depth: 0, - parent_next_overflow_addr: ZERO, + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut fired_push_branch = false; + let mut fired_nonpush_branch = false; + + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + let idx = RowIndex::from(row); + let next = RowIndex::from(row + 1); + if main.is_in_span(idx) != ONE { + return; + } + let gc = main.group_count(idx); + let gc_next = main.group_count(next); + if gc == gc_next { + return; } - } -} -impl BlockStackTableRow { - /// Reduces this row to a single field element in the field specified by E. This requires - /// at least 12 coefficients. - pub fn to_value>(&self, challenges: &Challenges) -> E { - let is_loop = if self.is_loop { ONE } else { ZERO }; - challenges.bus_prefix[miden_air::trace::bus_types::BLOCK_STACK_TABLE] - + challenges.beta_powers[0] * self.block_id - + challenges.beta_powers[1] * self.parent_id - + challenges.beta_powers[2] * is_loop - + challenges.beta_powers[3] * Felt::from_u32(u32::from(self.parent_ctx)) - + challenges.beta_powers[4] * self.parent_fmp - + challenges.beta_powers[5] * Felt::from_u32(self.parent_stack_depth) - + challenges.beta_powers[6] * self.parent_next_overflow_addr - + challenges.beta_powers[7] * self.parent_fn_hash[0] - + challenges.beta_powers[8] * self.parent_fn_hash[1] - + challenges.beta_powers[9] * self.parent_fn_hash[2] - + challenges.beta_powers[10] * self.parent_fn_hash[3] - } + let addr = main.addr(idx); + let group_value = if op == Felt::from_u8(opcodes::PUSH) { + fired_push_branch = true; + main.stack_element(0, next) + } else { + fired_nonpush_branch = true; + let h0_next = main.decoder_hasher_state_element(0, next); + let opcode_next = main.get_op_code(next); + h0_next * Felt::from_u16(128) + opcode_next + }; + exp.remove( + row, + &OpGroupMsg { + batch_id: addr, + group_pos: gc, + group_value, + }, + ); + }); + + assert!( + fired_push_branch, + "test did not cover the PUSH-mux branch of the op-group remove" + ); + assert!( + fired_nonpush_branch, + "test did not cover the non-PUSH branch of the op-group remove" + ); + + log.assert_contains(&exp); } -/// Describes a single entry in the op group table. An entry in the op group table is a tuple -/// (batch_id, group_pos, group_value). -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OpGroupTableRow { - batch_id: Felt, - group_pos: Felt, - group_value: Felt, -} +/// A SPAN that spans two batches exercises the RESPAN-boundary op-group dispatch. Runs with +/// op counts that force each non-g8 batch variant in the second batch to catch off-by-one / +/// batch-flag muxing bugs at the transition: +/// +/// - 80 Noops: first batch g8 (7 adds) + RESPAN + g1 second batch (0 adds — single group consumed +/// inline; emitter has no branch for `(c0, c1, c2) = (0, 1, 1)`). +/// - 100 Noops: first batch g8 + RESPAN + g4 second batch (3 adds for positions 1..=3). +/// +/// The batch-flag dispatch below mirrors the emitter exactly: `c0` is the g8 selector, +/// `(1-c0)·c1·(1-c2)` is g4, `(1-c0)·(1-c1)·c2` is g2, and `(1-c0)·c1·c2` is g1. +#[rstest::rstest] +#[case::g8_plus_g1(80, 1, 0, 0, 1)] +#[case::g8_plus_g4(100, 1, 1, 0, 0)] +fn op_group_span_two_batch_transition_inserts( + #[case] noop_count: usize, + #[case] expected_g8_rows: usize, + #[case] expected_g4_rows: usize, + #[case] expected_g2_rows: usize, + #[case] expected_g1_rows: usize, +) { + let ops: Vec = (0..noop_count).map(|_| Operation::Noop).collect(); + let trace = build_trace_from_ops(ops, &[]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut g8_rows = 0usize; + let mut g4_rows = 0usize; + let mut g2_rows = 0usize; + let mut g1_rows = 0usize; + let mut respan_observed = false; + let mut exp = Expectations::new(&log); + for_each_op(&trace, |row, op| { + let idx = RowIndex::from(row); + if op != Felt::from_u8(opcodes::SPAN) && op != Felt::from_u8(opcodes::RESPAN) { + return; + } + if op == Felt::from_u8(opcodes::RESPAN) { + respan_observed = true; + } + let batch_flags = main.op_batch_flag(idx); + let (c0, c1, c2) = (batch_flags[0], batch_flags[1], batch_flags[2]); + let addr_next = main.addr(RowIndex::from(row + 1)); + let gc = main.group_count(idx); + let first = main.decoder_hasher_state_first_half(idx); + let second = main.decoder_hasher_state_second_half(idx); + + if c0 == ONE && c1 == ZERO && c2 == ZERO { + g8_rows += 1; + for i in 1u16..=3 { + exp.add(row, &OpGroupMsg::new(&addr_next, gc, i, first[i as usize])); + } + for i in 4u16..=7 { + exp.add(row, &OpGroupMsg::new(&addr_next, gc, i, second[(i - 4) as usize])); + } + } else if c0 == ZERO && c1 == ONE && c2 == ZERO { + g4_rows += 1; + for i in 1u16..=3 { + exp.add(row, &OpGroupMsg::new(&addr_next, gc, i, first[i as usize])); + } + } else if c0 == ZERO && c1 == ZERO && c2 == ONE { + g2_rows += 1; + exp.add(row, &OpGroupMsg::new(&addr_next, gc, 1, first[1])); + } else if c0 == ZERO && c1 == ONE && c2 == ONE { + // g1 batch: single group consumed inline by the RESPAN decode row; no inserts. + g1_rows += 1; + } else { + panic!("unexpected batch_flags on SPAN/RESPAN row: ({c0:?}, {c1:?}, {c2:?})"); + } + }); -impl OpGroupTableRow { - /// Returns a new [OpGroupTableRow] instantiated with the specified parameters. - pub fn new(batch_id: Felt, group_pos: Felt, group_value: Felt) -> Self { - Self { batch_id, group_pos, group_value } - } -} + assert!(respan_observed, "program did not produce a RESPAN row"); + assert_eq!(g8_rows, expected_g8_rows); + assert_eq!(g4_rows, expected_g4_rows); + assert_eq!(g2_rows, expected_g2_rows); + assert_eq!(g1_rows, expected_g1_rows); + assert_eq!(exp.count_adds(), 7 * g8_rows + 3 * g4_rows + g2_rows); + assert_eq!(exp.count_removes(), 0); -impl OpGroupTableRow { - /// Reduces this row to a single field element in the field specified by E. This requires - /// at least 4 coefficients. - pub fn to_value>(&self, challenges: &Challenges) -> E { - challenges.bus_prefix[miden_air::trace::bus_types::OP_GROUP_TABLE] - + challenges.beta_powers[0] * self.batch_id - + challenges.beta_powers[1] * self.group_pos - + challenges.beta_powers[2] * self.group_value - } + log.assert_contains(&exp); } diff --git a/processor/src/trace/tests/hasher.rs b/processor/src/trace/tests/hasher.rs deleted file mode 100644 index 6b1d71a2aa..0000000000 --- a/processor/src/trace/tests/hasher.rs +++ /dev/null @@ -1,238 +0,0 @@ -use alloc::vec::Vec; - -use miden_air::trace::{ - AUX_TRACE_RAND_CHALLENGES, Challenges, MainTrace, bus_types, chiplets::hasher::P1_COL_IDX, -}; -use miden_core::{ - ONE, Word, ZERO, - crypto::merkle::{MerkleStore, MerkleTree, NodeIndex}, - field::{ExtensionField, Field}, - operations::Operation, -}; -use rstest::rstest; - -use super::{Felt, build_trace_from_ops_with_inputs, rand_array}; -use crate::{AdviceInputs, StackInputs}; - -// SIBLING TABLE TESTS -// ================================================================================================ - -#[rstest] -#[case(5_u64)] -#[case(4_u64)] -fn hasher_p1_mp_verify(#[case] index: u64) { - let (tree, _) = build_merkle_tree(); - let store = MerkleStore::from(&tree); - let depth = 3; - let node = tree.get_node(NodeIndex::new(depth as u8, index).unwrap()).unwrap(); - - // build program inputs - let mut init_stack = vec![]; - append_word(&mut init_stack, node); - init_stack.extend_from_slice(&[depth, index]); - append_word(&mut init_stack, tree.root()); - let stack_inputs = StackInputs::try_from_ints(init_stack).unwrap(); - let advice_inputs = AdviceInputs::default().with_merkle_store(store); - - // build execution trace and extract the sibling table column from it - let ops = vec![Operation::MpVerify(ZERO)]; - let trace = build_trace_from_ops_with_inputs(ops, stack_inputs, advice_inputs); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p1 = aux_columns.get_column(P1_COL_IDX); - - // executing MPVERIFY does not affect the sibling table - so, all values in the column must be - // ONE - for value in p1.iter() { - assert_eq!(ONE, *value); - } -} - -#[rstest] -#[case(5_u64)] -#[case(4_u64)] -fn hasher_p1_mr_update(#[case] index: u64) { - let (tree, _) = build_merkle_tree(); - let old_node = tree.get_node(NodeIndex::new(3, index).unwrap()).unwrap(); - let new_node = init_leaf(11); - let path = tree.get_path(NodeIndex::new(3, index).unwrap()).unwrap(); - - // build program inputs - let mut init_stack = vec![]; - append_word(&mut init_stack, old_node); - init_stack.extend_from_slice(&[3, index]); - append_word(&mut init_stack, tree.root()); - append_word(&mut init_stack, new_node); - let stack_inputs = StackInputs::try_from_ints(init_stack).unwrap(); - let store = MerkleStore::from(&tree); - let advice_inputs = AdviceInputs::default().with_merkle_store(store); - - // build execution trace and extract the sibling table column from it - let ops = vec![Operation::MrUpdate]; - let trace = build_trace_from_ops_with_inputs(ops, stack_inputs, advice_inputs); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p1 = aux_columns.get_column(P1_COL_IDX); - - let challenges = Challenges::::new(challenges[0], challenges[1]); - // mrupdate_id = 1 for the first (and only) MR_UPDATE operation. - let mrupdate_id = ONE; - let row_values = [ - SiblingTableRow::new(Felt::new_unchecked(index), path[0], mrupdate_id) - .to_value(&trace.main_trace, &challenges), - SiblingTableRow::new(Felt::new_unchecked(index >> 1), path[1], mrupdate_id) - .to_value(&trace.main_trace, &challenges), - SiblingTableRow::new(Felt::new_unchecked(index >> 2), path[2], mrupdate_id) - .to_value(&trace.main_trace, &challenges), - ]; - - // Make sure the first entry is ONE. - let mut expected_value = ONE; - assert_eq!(expected_value, p1[0]); - - // The running product does not change while the hasher computes the hash of the SPAN block. - // In the controller/perm split, the span uses 2 controller rows (0-1). The MR_UPDATE starts - // at controller row 2. Each Merkle level is a 2-row controller pair. - // - // MV leg (old path, depth 3): input rows at 2, 4, 6. Siblings added at rows 3, 5, 7. - // MU leg (new path, depth 3): input rows at 8, 10, 12. Siblings removed at rows 9, 11, 13. - let row_add_1 = 3; - for value in p1.iter().take(row_add_1).skip(1) { - assert_eq!(expected_value, *value); - } - - // First sibling is added (MV level 0). - expected_value *= row_values[0]; - assert_eq!(expected_value, p1[row_add_1]); - - // The value remains the same until the next sibling is added. - let row_add_2 = 5; - for value in p1.iter().take(row_add_2).skip(row_add_1 + 1) { - assert_eq!(expected_value, *value); - } - - // Second sibling is added (MV level 1). - expected_value *= row_values[1]; - assert_eq!(expected_value, p1[row_add_2]); - - // The value remains the same until the last sibling is added. - let row_add_3 = 7; - for value in p1.iter().take(row_add_3).skip(row_add_2 + 1) { - assert_eq!(expected_value, *value); - } - - // Last sibling is added (MV level 2). - expected_value *= row_values[2]; - assert_eq!(expected_value, p1[row_add_3]); - - // The value remains the same until computation of the "new Merkle root" is started. - // MU leg starts at controller row 8, first sibling removed at row 9. - let row_remove_1 = 9; - for value in p1.iter().take(row_remove_1).skip(row_add_3 + 1) { - assert_eq!(expected_value, *value); - } - - // First sibling is removed from the table in the following row. - expected_value *= row_values[0].inverse(); - assert_eq!(expected_value, p1[row_remove_1]); - - // The value remains the same until the next sibling is removed (MU level 1, row 11). - let row_remove_2 = 11; - for value in p1.iter().take(row_remove_2).skip(row_remove_1 + 1) { - assert_eq!(expected_value, *value); - } - - // Second sibling is removed (MU level 1). - expected_value *= row_values[1].inverse(); - assert_eq!(expected_value, p1[row_remove_2]); - - // The value remains the same until the last sibling is removed (MU level 2, row 13). - let row_remove_3 = 13; - for value in p1.iter().take(row_remove_3).skip(row_remove_2 + 1) { - assert_eq!(expected_value, *value); - } - - // Last sibling is removed (MU level 2). - expected_value *= row_values[2].inverse(); - assert_eq!(expected_value, p1[row_remove_3]); - - // at this point the table should be empty again, and it should stay empty until the end - assert_eq!(expected_value, ONE); - for value in p1.iter().skip(row_remove_3 + 1) { - assert_eq!(ONE, *value); - } -} - -// HELPER STRUCTS, METHODS AND FUNCTIONS -// ================================================================================================ - -fn build_merkle_tree() -> (MerkleTree, Vec) { - // build a Merkle tree - let leaves = init_leaves(&[1, 2, 3, 4, 5, 6, 7, 8]); - (MerkleTree::new(leaves.clone()).unwrap(), leaves) -} - -fn init_leaves(values: &[u64]) -> Vec { - values.iter().map(|&v| init_leaf(v)).collect() -} - -fn init_leaf(value: u64) -> Word { - [Felt::new_unchecked(value), ZERO, ZERO, ZERO].into() -} - -fn append_word(target: &mut Vec, word: Word) { - word.iter().for_each(|v| target.push(v.as_canonical_u64())); -} - -/// Describes a single entry in the sibling table which consists of a tuple `(index, node)` where -/// index is the index of the node at its depth. For example, assume a leaf has index n. For the -/// leaf's parent the index will be n << 1. For the parent of the parent, the index will be -/// n << 2 etc. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SiblingTableRow { - index: Felt, - sibling: Word, - mrupdate_id: Felt, -} - -impl SiblingTableRow { - pub fn new(index: Felt, sibling: Word, mrupdate_id: Felt) -> Self { - Self { index, sibling, mrupdate_id } - } - - /// Reduces this row to a single field element in the field specified by E. - /// - /// The encoding includes: - /// - `mrupdate_id` at position 1: prevents cross-operation sibling reuse by binding each - /// sibling table entry to a specific MRUPDATE operation. Without this, a prover could swap - /// siblings between the old path of one update and the new path of another. - /// - `node_index` at position 2: the Merkle tree index at this path level. - /// - sibling word at positions 3-6 or 7-10: which rate half holds the sibling depends on the - /// direction bit (LSB of node_index). - pub fn to_value>( - &self, - _main_trace: &MainTrace, - challenges: &Challenges, - ) -> E { - let lsb = self.index.as_canonical_u64() & 1; - if lsb == 0 { - // Sibling at rate1 (positions 7-10) - challenges.bus_prefix[bus_types::SIBLING_TABLE] - + challenges.beta_powers[1] * self.mrupdate_id - + challenges.beta_powers[2] * self.index - + challenges.beta_powers[7] * self.sibling[0] - + challenges.beta_powers[8] * self.sibling[1] - + challenges.beta_powers[9] * self.sibling[2] - + challenges.beta_powers[10] * self.sibling[3] - } else { - // Sibling at rate0 (positions 3-6) - challenges.bus_prefix[bus_types::SIBLING_TABLE] - + challenges.beta_powers[1] * self.mrupdate_id - + challenges.beta_powers[2] * self.index - + challenges.beta_powers[3] * self.sibling[0] - + challenges.beta_powers[4] * self.sibling[1] - + challenges.beta_powers[5] * self.sibling[2] - + challenges.beta_powers[6] * self.sibling[3] - } - } -} diff --git a/processor/src/trace/tests/lookup.rs b/processor/src/trace/tests/lookup.rs new file mode 100644 index 0000000000..b2f982ec9c --- /dev/null +++ b/processor/src/trace/tests/lookup.rs @@ -0,0 +1,140 @@ +//! End-to-end collection-phase smoke test for the prover-side LogUp pipeline. +//! +//! Runs a tiny MASM basic block through `build_trace_from_ops`, materialises the resulting +//! main trace as a [`RowMajorMatrix`], and pipes it through [`build_lookup_fractions`] +//! + [`accumulate`]. The test validates: +//! +//! 1. **Shape-const drift**: every bus emitter's declared `MAX_INTERACTIONS_PER_ROW` is large +//! enough to accommodate real trace data (the `debug_assert!` inside +//! `ProverLookupBuilder::column` panics on overflow). +//! 2. **Zero-denominator bugs**: every encoded `LookupMessage` evaluates to a non-zero +//! extension-field element, so per-fraction `try_inverse` inside the accumulator does not panic. +//! 3. **Pipeline plumbing**: row slicing with wraparound, per-row periodic composition, `RowWindow` +//! construction over a real matrix, and the dense `LookupFractions` buffer all line up. +//! 4. **Prover/constraint agreement**: the fused `accumulate` prover path must agree with the +//! constraint-path `(U_col, V_col)` oracle bit-exactly on every `(row, col)` delta. If any pair +//! disagrees, either the prover path or the oracle has a bug. +//! +//! The oracle cross-check in (4) subsumes the "does it run to completion?" shape of a +//! separate plumbing test, so both live in one function below. + +use alloc::vec::Vec; + +use miden_air::{ + LOGUP_AUX_TRACE_WIDTH, LiftedAir, ProcessorAir, + logup::{BusId, MIDEN_MAX_MESSAGE_WIDTH}, + lookup::{Challenges, accumulate, build_lookup_fractions, debug::collect_column_oracle_folds}, +}; +use miden_core::{ + field::{Field, QuadFelt}, + utils::Matrix, +}; + +use super::{Felt, build_trace_from_ops, rand_array}; +use crate::operation::Operation; + +/// Pad/Add/Mul/Drop inside a span — same flavour of ops the decoder/stack tests use, with +/// enough variety to exercise decoder, stack, and range-check bus emitters. +fn tiny_span() -> Vec { + vec![ + Operation::Pad, + Operation::Pad, + Operation::Add, + Operation::Pad, + Operation::Mul, + Operation::Drop, + ] +} + +/// Cross-check: the fused `accumulate` prover path must agree with the constraint-path +/// `(U_col, V_col)` oracle bit-exactly on every `(row, col)` delta. +/// +/// - **Prover path**: collect `(m_i, d_i)` fractions via `ProverLookupBuilder` on each row, then +/// `accumulate` runs batched Montgomery inversion + per-column partial sums to produce +/// `aux[col][r+1] - aux[col][r] = Σ m_i · d_i^{-1}`. +/// - **Constraint path**: `ColumnOracleBuilder` evaluates `ProcessorAir` row-by-row using the same +/// `(U_g, V_g)` algebra the constraint system uses, folded per column via cross-multiplication, +/// producing `expected_delta = V_col · U_col^{-1}`. +/// +/// A divergence means either the prover path or the oracle has a bug — fix the root cause. +#[test] +fn build_lookup_fractions_matches_constraint_path_oracle() { + let trace = build_trace_from_ops(tiny_span(), &[]); + + let main_trace = trace.main_trace().to_row_major(); + let public_vals = trace.to_public_values(); + let periodic = LiftedAir::::periodic_columns(&ProcessorAir); + + // QuadFelt challenges for LogUp, built from 4 random Felts (QuadFelt itself doesn't + // implement Randomizable, so we draw base-field elements and pair them). + let raw = rand_array::(); + let alpha = QuadFelt::new([raw[0], raw[1]]); + let beta = QuadFelt::new([raw[2], raw[3]]); + let air = ProcessorAir; + let challenges = + Challenges::::new(alpha, beta, MIDEN_MAX_MESSAGE_WIDTH, BusId::COUNT); + + // --- Prover path: collect fractions and run the fused accumulator. --- + let fractions = build_lookup_fractions(&air, &main_trace, &periodic, &challenges); + + // Shape / non-degenerate smoke checks — if these trip, the oracle check below is moot. + let num_rows = trace.main_trace().num_rows(); + assert_eq!(fractions.num_rows(), num_rows); + assert_eq!(fractions.num_columns(), LOGUP_AUX_TRACE_WIDTH); + assert_eq!(fractions.counts().len(), num_rows * LOGUP_AUX_TRACE_WIDTH); + assert!( + !fractions.fractions().is_empty(), + "no fractions collected — trace is degenerate or emitters are broken", + ); + + // `accumulate` returns a row-major matrix with `num_rows + 1` rows and `num_cols` + // columns. Col 0 is the running-sum accumulator; cols 1+ hold per-row fraction values. + let aux = accumulate(&fractions); + let aux_width = aux.width(); + let aux_values = &aux.values; + assert_eq!(aux_width, LOGUP_AUX_TRACE_WIDTH); + assert_eq!(aux.height(), num_rows + 1); + + // --- Constraint path: walk the trace through the oracle to collect per-row folded + // `(U_col, V_col)` pairs. --- + let oracle_folds = + collect_column_oracle_folds(&air, &main_trace, &periodic, &public_vals, &challenges); + assert_eq!(oracle_folds.len(), num_rows); + + // --- Per-(row, col) value check. --- + // Col 0 (accumulator): aux[r+1][0] - aux[r][0] == Σ_col per_row_value[col]. + // Cols 1+ (fraction): aux[r][col] == V/U per-row value. + for (r, row_folds) in oracle_folds.iter().enumerate() { + assert_eq!(row_folds.len(), LOGUP_AUX_TRACE_WIDTH); + let per_row_values: Vec = row_folds + .iter() + .enumerate() + .map(|(col, &(u_col, v_col))| { + let u_inv = u_col.try_inverse().unwrap_or_else(|| { + panic!( + "row {r} col {col}: oracle U_col is zero — bus has a zero-denominator \ + product, indicating a bug in the emitter or message encoding", + ) + }); + v_col * u_inv + }) + .collect(); + + // Accumulator (col 0): delta == sum of all columns' per-row values. + let expected_delta: QuadFelt = per_row_values.iter().copied().sum(); + let actual_delta = aux_values[(r + 1) * aux_width] - aux_values[r * aux_width]; + assert_eq!( + actual_delta, expected_delta, + "row {r} col 0 (accumulator): prover vs constraint path mismatch", + ); + + // Fraction columns (cols 1+): aux[r][col] == per-row V/U. + for col in 1..LOGUP_AUX_TRACE_WIDTH { + let actual_value = aux_values[r * aux_width + col]; + assert_eq!( + actual_value, per_row_values[col], + "row {r} col {col} (fraction): prover vs constraint path mismatch", + ); + } + } +} diff --git a/processor/src/trace/tests/lookup_harness.rs b/processor/src/trace/tests/lookup_harness.rs new file mode 100644 index 0000000000..8876e6e1d6 --- /dev/null +++ b/processor/src/trace/tests/lookup_harness.rs @@ -0,0 +1,170 @@ +//! Column-agnostic, subset-based LogUp interaction harness. +//! +//! The prover path pushes one `(multiplicity, encoded_denominator)` pair per interaction into a +//! flat buffer. [`InteractionLog`] slices that buffer into per-row bags (across all columns) and +//! exposes one assertion, [`InteractionLog::assert_contains`]: for each row, the bag of expected +//! `(mult, msg)` pairs the test describes must be a multiset-subset of the bag of actual pushes. +//! +//! Tests describe expected interactions via [`Expectations`], which accepts raw +//! [`LookupMessage`] instances and encodes them against the log's challenges. `Expectations` is +//! column-blind by construction — two messages routed onto different aux columns still compare +//! equal if they share both multiplicity and encoded denominator. +//! +//! No scalar sums, no per-column deltas, no terminals: those views invite missing + spurious +//! interactions to cancel silently. Subset semantics over raw `(mult, denom)` tuples keeps every +//! expected interaction independently observable. + +use alloc::vec::Vec; + +use miden_air::{ + LiftedAir, ProcessorAir, + logup::{BusId, MIDEN_MAX_MESSAGE_WIDTH}, + lookup::{Challenges, LookupFractions, LookupMessage, build_lookup_fractions}, +}; +use miden_core::field::QuadFelt; +use miden_utils_testing::rand::rand_array; + +use super::{ExecutionTrace, Felt}; + +// INTERACTION LOG +// ================================================================================================ + +/// Per-row record of every `(multiplicity, denominator)` pair the prover path emitted for a +/// given execution trace, aggregated across all aux columns. +/// +/// Column identity is intentionally discarded: a row's contribution to the LogUp closure +/// identity is the union of its per-column pushes, and processor-side tests should stay +/// invariant under any AIR-side column repack. +pub(super) struct InteractionLog { + /// Random challenges used to encode messages. Exposed so `Expectations` can encode + /// hand-built [`LookupMessage`] instances against the same challenges used by the prover + /// path above. + pub challenges: Challenges, + /// `rows[r]` = multiset of `(mult, denom)` pushes the prover produced at main-trace row + /// `r`, across all columns, in builder order. + rows: Vec>, +} + +impl InteractionLog { + /// Drive the prover-path pipeline on `trace` with fresh random challenges and slice the + /// resulting [`LookupFractions`] buffer into per-row bags. + pub fn new(trace: &ExecutionTrace) -> Self { + let main_trace = trace.main_trace().to_row_major(); + let periodic = LiftedAir::::periodic_columns(&ProcessorAir); + + // `QuadFelt` itself isn't `Randomizable`, so draw 4 base-field elements and pair them. + let raw = rand_array::(); + let alpha = QuadFelt::new([raw[0], raw[1]]); + let beta = QuadFelt::new([raw[2], raw[3]]); + let challenges = + Challenges::::new(alpha, beta, MIDEN_MAX_MESSAGE_WIDTH, BusId::COUNT); + + let fractions = build_lookup_fractions(&ProcessorAir, &main_trace, &periodic, &challenges); + + Self { challenges, rows: split_rows(&fractions) } + } + + /// Verify that each row's expected bag is a multiset-subset of that row's actual bag of + /// prover pushes. + /// + /// For every `(row, mult, denom)` in `expected`, there must be at least as many matching + /// pushes at that row. Unclaimed actual pushes are ignored — this is the whole point of + /// subset semantics, so partial tests can focus on one bus or one instruction without + /// enumerating every other interaction that happens to fire. + pub fn assert_contains(&self, expected: &Expectations) { + for &entry in &expected.entries { + let (row, mult, denom) = entry; + let want = expected.entries.iter().filter(|&&e| e == entry).count(); + let have = self.rows[row].iter().filter(|&&(m, d)| m == mult && d == denom).count(); + assert!( + have >= want, + "row {row}: expected at least {want}× (mult={mult:?}, denom={denom:?}), saw {have}.\n\ + actual row bag: {:?}", + self.rows[row], + ); + } + } +} + +// EXPECTATIONS +// ================================================================================================ + +/// Hand-assembled list of `(row, multiplicity, encoded_denominator)` triples representing the +/// interactions a test expects to see somewhere on that row. +/// +/// [`Expectations`] is column-blind by construction: `add` / `remove` / `signed` encode a +/// [`LookupMessage`] against the owning [`InteractionLog`]'s challenges and store only the +/// resulting denominator plus the signed multiplicity, so the subsequent subset check never +/// consults column identity. +pub(super) struct Expectations<'a> { + challenges: &'a Challenges, + entries: Vec<(usize, Felt, QuadFelt)>, +} + +impl<'a> Expectations<'a> { + /// Start an empty expectation set tied to `log`'s challenges. + pub fn new(log: &'a InteractionLog) -> Self { + Self { + challenges: &log.challenges, + entries: Vec::new(), + } + } + + /// Add an expected `+1 · 1 / encode(msg)` interaction at `row`. + pub fn add(&mut self, row: usize, msg: &M) -> &mut Self + where + M: LookupMessage, + { + self.push(row, Felt::ONE, msg) + } + + /// Add an expected `-1 · 1 / encode(msg)` interaction at `row`. + pub fn remove(&mut self, row: usize, msg: &M) -> &mut Self + where + M: LookupMessage, + { + self.push(row, -Felt::ONE, msg) + } + + /// Add an expected `mult · 1 / encode(msg)` interaction at `row` with arbitrary + /// multiplicity. Use for range-check table lookups and kernel-ROM table multiplicities. + pub fn push(&mut self, row: usize, mult: Felt, msg: &M) -> &mut Self + where + M: LookupMessage, + { + let denom = msg.encode(self.challenges); + self.entries.push((row, mult, denom)); + self + } + + /// Number of expected entries registered via `add` (multiplicity = +1). + pub fn count_adds(&self) -> usize { + self.entries.iter().filter(|(_, m, _)| *m == Felt::ONE).count() + } + + /// Number of expected entries registered via `remove` (multiplicity = -1). + pub fn count_removes(&self) -> usize { + self.entries.iter().filter(|(_, m, _)| *m == -Felt::ONE).count() + } +} + +// HELPERS +// ================================================================================================ + +/// Slice the flat `LookupFractions` buffer into per-row bags using the row-major `counts` +/// layout (see `air/src/lookup/fractions.rs` for the ordering spec). +fn split_rows(fractions: &LookupFractions) -> Vec> { + let num_cols = fractions.num_columns(); + let counts = fractions.counts(); + let flat = fractions.fractions(); + + let num_rows = counts.len() / num_cols; + let mut rows = Vec::with_capacity(num_rows); + let mut cursor = 0usize; + for per_row in counts.chunks(num_cols) { + let total: usize = per_row.iter().sum(); + rows.push(flat[cursor..cursor + total].to_vec()); + cursor += total; + } + rows +} diff --git a/processor/src/trace/tests/mod.rs b/processor/src/trace/tests/mod.rs index 7b2bd43115..3a298b7d27 100644 --- a/processor/src/trace/tests/mod.rs +++ b/processor/src/trace/tests/mod.rs @@ -14,7 +14,8 @@ use crate::{ mod chiplets; mod decoder; -mod hasher; +mod lookup; +mod lookup_harness; mod range; mod stack; @@ -56,9 +57,10 @@ pub fn build_trace_from_ops(operations: Vec, stack: &[u64]) -> Execut build_trace_from_program(&program, stack) } -/// Builds a sample trace by executing a span block containing the specified operations. Unlike the -/// function above, this function accepts the full [AdviceInputs] object, which means it can run -/// the programs with initialized advice provider. +/// Builds a sample trace by executing a span block containing the specified operations. Unlike +/// [`build_trace_from_ops`], this variant accepts the full [`AdviceInputs`] object, so the +/// program can run against an initialised advice provider (e.g. to seed a Merkle tree for the +/// sibling-table tests). pub fn build_trace_from_ops_with_inputs( operations: Vec, stack_inputs: StackInputs, diff --git a/processor/src/trace/tests/range.rs b/processor/src/trace/tests/range.rs index 80f830de12..62132f004a 100644 --- a/processor/src/trace/tests/range.rs +++ b/processor/src/trace/tests/range.rs @@ -1,109 +1,73 @@ -use miden_air::trace::{ - ACE_CHIPLET_WIRING_BUS_OFFSET, AUX_TRACE_RAND_CHALLENGES, Challenges, bus_types, - chiplets::hasher::HASH_CYCLE_LEN, range::B_RANGE_COL_IDX, +//! Range-check bus test. +//! +//! Verifies that every expected `RangeMsg` interaction fires at the right row, whether it +//! comes from a u32 stack op (decoder-side `user_op_helpers`) or a memory chiplet row (delta +//! limbs + word-address decomposition). The test is column-blind — since both call sites are +//! currently packed into different aux columns (M1 for u32rc, C2 for memory range checks), +//! the subset-based [`InteractionLog`] happens to pick up both without the test having to +//! know where each one landed. +//! +//! Covers the ground the pre-deletion `b_range_trace_stack` + `b_range_trace_mem` tests did, +//! minus the `b_range + v_wiring = 0` closure identity (no longer meaningful under the new +//! bus packing — cross-column closure is an AIR-side invariant, not something a processor +//! test should describe). + +use alloc::vec::Vec; + +use miden_air::{ + logup::RangeMsg, + trace::{ + MainTrace, RANGE_CHECK_TRACE_OFFSET, + chiplets::{MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX}, + }, }; -use miden_core::{ONE, ZERO, field::Field, operations::Operation}; -use miden_utils_testing::{rand::rand_array, stack}; +use miden_core::{Felt, operations::Operation}; +use miden_utils_testing::stack; -use super::{Felt, build_trace_from_ops}; +use super::{ + build_trace_from_ops, + lookup_harness::{Expectations, InteractionLog}, +}; +use crate::RowIndex; -/// This test checks that range check lookups from stack operations are balanced by the range checks -/// processed in the Range Checker. -/// -/// The `U32add` operation results in 4 16-bit range checks of 256, 0, 0, 0. +/// `U32add` range-checks its four decoder helper columns: for `1 + 255 = 256`, the four +/// values are `{0, 256, 0, 0}`, so we expect exactly three removes of `RangeMsg { value: 0 }` +/// and one remove of `RangeMsg { value: 256 }` at the U32add row. #[test] -fn b_range_trace_stack() { +fn u32_stack_op_emits_range_check_removes() { let stack = [1, 255]; let operations = vec![Operation::U32add]; let trace = build_trace_from_ops(operations, &stack); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let u32add_row = find_op_row(main, miden_core::operations::opcodes::U32ADD); - let rand_elements = rand_array::(); - let challenges = Challenges::new(rand_elements[0], rand_elements[1]); - let alpha = challenges.bus_prefix[bus_types::RANGE_CHECK_BUS]; - let aux_columns = trace.build_aux_trace(&rand_elements).unwrap(); - let b_range = aux_columns.get_column(B_RANGE_COL_IDX); - - assert_eq!(trace.length(), b_range.len()); - - // --- Check the stack processor's range check lookups. --------------------------------------- - - // Before any range checks are executed, the value in b_range should be zero. - assert_eq!(ZERO, b_range[0]); - assert_eq!(ZERO, b_range[1]); - - // The first range check lookup from the stack will happen when the add operation is executed, - // at cycle 1. (The trace begins by executing `span`). It must be subtracted out of `b_range`. - // The range-checked values are 0, 256, 0, 0, so the values to subtract are 3/(alpha + 0) and - // 1/(alpha + 256). - let lookups = alpha.inverse() * Felt::from_u8(3) + (alpha + Felt::from_u16(256)).inverse(); - let mut expected = b_range[1] - lookups; - assert_eq!(expected, b_range[2]); - - // --- Check the range checker's lookups. ----------------------------------------------------- - - // 44 rows are needed for 0, 243, 252, 255, 256, ... 38 additional bridge rows of powers of - // 3 ..., 65535. (0 and 256 are range-checked. 65535 is the max, and the rest are "bridge" - // values.) An extra row is added to pad the u16::MAX value. - let len_16bit = 44 + 1; - // The start of the values in the range checker table. - let values_start = trace.length() - len_16bit; - - // After the padded rows, the first value will be unchanged. - assert_eq!(expected, b_range[values_start]); - // We include 3 lookups of 0. - expected += alpha.inverse() * Felt::from_u8(3); - assert_eq!(expected, b_range[values_start + 1]); - // Then we have 3 bridge rows between 0 and 255 where the value does not change - assert_eq!(expected, b_range[values_start + 2]); - assert_eq!(expected, b_range[values_start + 3]); - assert_eq!(expected, b_range[values_start + 4]); - // Then we include 1 lookup of 256, so it should be multiplied by alpha + 256. - expected += (alpha + Felt::from_u16(256)).inverse(); - assert_eq!(expected, b_range[values_start + 5]); - - // --- Check the last value of the b_range column is zero -------------------------------------- - - let last_row = b_range.len() - 1; - assert_eq!(ZERO, b_range[last_row]); + let mut exp = Expectations::new(&log); + for i in 0..4 { + let value = main.helper_register(i, u32add_row); + exp.remove(usize::from(u32add_row), &RangeMsg { value }); + } + + assert_eq!(exp.count_removes(), 4, "expected 4 helper-register range-check removes"); + assert_eq!(exp.count_adds(), 0); + log.assert_contains(&exp); } -/// Tests that range check lookups from memory operations are balanced across `b_range` and -/// `v_wiring`. -/// -/// ## Background -/// -/// Each memory access produces two kinds of 16-bit range checks: -/// -/// 1. **Delta checks** (`d0`, `d1`): the difference between consecutive memory rows is decomposed -/// into two 16-bit limbs. These are subtracted from `b_range` at the memory row and added back -/// by the range checker table. -/// -/// 2. **Address decomposition checks** (`w0`, `w1`, `4*w1`): the word-aligned address is decomposed -/// as `word_index = word_addr / 4`, then `w0 = word_index & 0xFFFF` and `w1 = word_index >> 16`. -/// The three values `w0`, `w1`, `4*w1` are subtracted from the **wiring bus** (`v_wiring`) at -/// the memory row, and added back by the range checker table into `b_range`. -/// -/// Because the address range checks are subtracted on `v_wiring` but their multiplicities are -/// added to `b_range`, neither column balances to zero on its own. The verifier checks the -/// combined identity: `b_range[last] + v_wiring[last] = 0`. -/// -/// ## Test setup +/// Two memory ops (`MStoreW` + `MLoadW`) on the same word address emit 5 `RangeMsg` removes +/// per memory chiplet row: `d0`, `d1` (the 16-bit delta limbs used for sorted-access +/// constraints) and `w0`, `w1`, `4·w1` (the word-address decomposition). /// -/// We use `addr = 262148 = 4 * 65537` to ensure non-trivial address decomposition: -/// - `word_index = 65537 = 0x10001` -/// - `w0 = 1`, `w1 = 1`, `4*w1 = 4` -/// -/// Two memory operations (MStoreW + MLoadW) at the same address produce 2 memory rows, -/// each contributing delta checks to `b_range` and address checks to `v_wiring`. +/// The address `262148 = 4 · 65537` is word-aligned with `word_index = 65537 = 0x10001`, so +/// `w0 = 1`, `w1 = 1`, `4·w1 = 4` — a non-trivial decomposition that exercises the full +/// five-way range-check batch. #[test] -fn b_range_trace_mem() { - // ===================================================================== - // 1. BUILD THE TRACE - // ===================================================================== - +fn memory_chiplet_row_emits_range_check_removes() { let addr: u64 = 262148; let stack_input = stack![addr, 1, 2, 3, 4, addr]; + // MStoreW + 4×Drop + MLoadW, then 60 Noops so the memory chiplet segment does not overlap + // with the range checker's table segment at the end of the chiplet trace. let mut operations = vec![ Operation::MStoreW, Operation::Drop, @@ -112,126 +76,85 @@ fn b_range_trace_mem() { Operation::Drop, Operation::MLoadW, ]; - // Pad with Noops so that the memory chiplet and range checker table sections don't overlap - // in the trace, making it easier to reason about which rows contribute to which column. operations.resize(operations.len() + 60, Operation::Noop); let trace = build_trace_from_ops(operations, &stack_input); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + // Collect every memory chiplet row — we expect exactly two for the two memory ops. + let mut mem_rows: Vec = Vec::new(); + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + if main.is_memory_row(idx) { + mem_rows.push(idx); + } + } + assert_eq!(mem_rows.len(), 2, "expected exactly two memory chiplet rows"); + + let mut exp = Expectations::new(&log); + for mem_row in &mem_rows { + let row = usize::from(*mem_row); + let d0 = main.get(*mem_row, MEMORY_D0_COL_IDX); + let d1 = main.get(*mem_row, MEMORY_D1_COL_IDX); + let w0 = main.chiplet_memory_word_addr_lo(*mem_row); + let w1 = main.chiplet_memory_word_addr_hi(*mem_row); + let four_w1 = w1 * Felt::from_u8(4); + + for value in [d0, d1, w0, w1, four_w1] { + exp.remove(row, &RangeMsg { value }); + } + } + + assert_eq!(exp.count_removes(), 5 * mem_rows.len(), "expected 5 RC removes per memory row"); + assert_eq!(exp.count_adds(), 0); + log.assert_contains(&exp); +} + +/// Every trace row carries the range-checker table's response: a `RangeMsg { value: v }` add +/// with runtime multiplicity `m`. This test verifies the per-row add side of the bus using +/// hardcoded request demand: a `U32add` requests 4 values (helper columns) and each chiplet +/// row with a range-check demand adds its `m` copies of that value to the bus. +/// +/// Catches regressions where the range-checker add-back emitter misreads the multiplicity +/// column, the value column, or drops the always-active gate — bugs that the per-request +/// removes-only tests above cannot detect. +#[test] +fn range_checker_table_emits_per_row_adds() { + // U32add issues 4 range-check requests for values {0, 256, 0, 0} on 1 + 255 = 256. The + // range-checker chiplet will then add back four multiplicities of those values distributed + // across its trace rows. We don't need to predict where — subset semantics lets us verify + // that *every* row's add matches its `(m, v)` columns. + let stack = [1, 255]; + let operations = vec![Operation::U32add]; + let trace = build_trace_from_ops(operations, &stack); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + const M_COL_IDX: usize = RANGE_CHECK_TRACE_OFFSET; + const V_COL_IDX: usize = RANGE_CHECK_TRACE_OFFSET + 1; + + let mut nonzero_mult_rows = 0usize; + let mut exp = Expectations::new(&log); + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let m = main.get(idx, M_COL_IDX); + let v = main.get(idx, V_COL_IDX); + exp.push(row, m, &RangeMsg { value: v }); + if m != Felt::from_u8(0) { + nonzero_mult_rows += 1; + } + } + + assert!(nonzero_mult_rows > 0, "range checker table is empty — test is vacuous"); + log.assert_contains(&exp); +} - let rand_elements = rand_array::(); - let challenges = Challenges::new(rand_elements[0], rand_elements[1]); - let alpha = challenges.bus_prefix[bus_types::RANGE_CHECK_BUS]; - let aux_columns = trace.build_aux_trace(&rand_elements).unwrap(); - let b_range = aux_columns.get_column(B_RANGE_COL_IDX); - let v_wiring = aux_columns.get_column(ACE_CHIPLET_WIRING_BUS_OFFSET); - - assert_eq!(trace.length(), b_range.len()); - - // ===================================================================== - // 2. ADDRESS DECOMPOSITION - // ===================================================================== - // - // addr = 262148 is word-aligned (262148 % 4 == 0), so word_addr = 262148. - // word_index = word_addr / 4 = 65537 = 0x10001. - // - // w0 = word_index & 0xFFFF = 0x0001 = 1 - // w1 = word_index >> 16 = 0x0001 = 1 - // 4*w1 = 4 * 1 = 4 - // - // These are the three values whose range checks go through v_wiring. - let w0 = ONE; - let w1 = ONE; - let four_w1 = Felt::from_u8(4); - - // ===================================================================== - // 3. DELTA VALUES - // ===================================================================== - // - // The memory trace has 2 rows (one per memory op), sorted by (ctx, word_addr, clk). - // Both operations access the same (ctx=0, word_addr=262148), so the delta between - // consecutive rows is just the clock cycle difference. - // - // Each delta is decomposed into two 16-bit limbs: d0 (low) and d1 (high). - // - // Memory row 0 (MStoreW, clk=1): - // The memory module initializes the virtual previous row to (same_ctx, same_addr, - // clk - 1), so the first row's delta is always 1 regardless of the actual address. - // delta = 1, d0 = 1, d1 = 0. - let d0_store = ONE; - let d1_store = ZERO; - - // Memory row 1 (MLoadW, clk=6): - // Delta from previous row: clk_load - clk_store = 6 - 1 = 5. - // d0 = 5, d1 = 0. - let d0_load = Felt::from_u8(5); - let d1_load = ZERO; - - // ===================================================================== - // 4. CHECK b_range: DELTA SUBTRACTIONS ON MEMORY ROWS - // ===================================================================== - // - // The hasher trace occupies the first 32 rows in total: - // 16 rows for the padded controller region (2 controller rows + 14 padding) and - // 16 rows for the packed permutation segment. The memory chiplet starts at row 32. - let memory_start = 2 * HASH_CYCLE_LEN; - - // b_range starts at zero and stays zero until the first memory row. - assert_eq!(ZERO, b_range[0]); - - // At memory row 0 (MStoreW): b_range subtracts the two delta LogUp fractions. - // b_range[mem+1] = b_range[mem] - 1/(alpha + d0_store) - 1/(alpha + d1_store) - let store_delta_sub = (alpha + d0_store).inverse() + (alpha + d1_store).inverse(); - let mut expected_b_range = ZERO - store_delta_sub; - assert_eq!(expected_b_range, b_range[memory_start + 1]); - - // At memory row 1 (MLoadW): b_range subtracts two more delta LogUp fractions. - // b_range[mem+2] = b_range[mem+1] - 1/(alpha + d0_load) - 1/(alpha + d1_load) - let load_delta_sub = (alpha + d0_load).inverse() + (alpha + d1_load).inverse(); - expected_b_range -= load_delta_sub; - assert_eq!(expected_b_range, b_range[memory_start + 2]); - - // ===================================================================== - // 5. CHECK END-TO-END BALANCE: b_range + v_wiring = 0 - // ===================================================================== - // - // After the range checker table processes all rows, b_range has added back the - // multiplicities for ALL range-checked values -- including the address decomposition - // values (w0, w1, 4*w1) whose subtractions live on v_wiring, not b_range. - - let last_row = b_range.len() - 1; - let b_range_final = b_range[last_row]; - let v_wiring_final = v_wiring[last_row]; - - // Both columns should have non-zero residuals individually, since the address range - // check contributions are split across them. - assert_ne!( - b_range_final, ZERO, - "b_range should have a non-zero residual: the range table added w0/w1/4*w1 \ - multiplicities that are not cancelled by delta subtractions" - ); - assert_ne!( - v_wiring_final, ZERO, - "v_wiring should have a non-zero residual: it subtracted w0/w1/4*w1 LogUp \ - fractions that are not added back on v_wiring itself" - ); - - // Verify the wiring bus residual matches the expected value. - // The wiring bus uses bus_prefix[RANGE_CHECK_BUS] for memory address range checks - // (same as b_range) so they balance to zero. - let alpha_w = challenges.bus_prefix[bus_types::RANGE_CHECK_BUS]; - let num_memory_rows = Felt::from_u8(2); - let w0_contribution = (alpha_w + w0).inverse() * num_memory_rows; - let w1_contribution = (alpha_w + w1).inverse() * num_memory_rows; - let four_w1_contribution = (alpha_w + four_w1).inverse() * num_memory_rows; - let expected_wiring_residual = -(w0_contribution + w1_contribution + four_w1_contribution); - assert_eq!( - v_wiring_final, expected_wiring_residual, - "v_wiring residual should equal -(2/(alpha_w+w0) + 2/(alpha_w+w1) + 2/(alpha_w+4*w1))" - ); - - // Verify the end-to-end balance: b_range + v_wiring = 0. - assert_eq!( - b_range_final + v_wiring_final, - ZERO, - "b_range + v_wiring must balance to zero at the last row" - ); +fn find_op_row(main: &MainTrace, opcode: u8) -> RowIndex { + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + if main.get_op_code(idx) == Felt::from_u8(opcode) { + return idx; + } + } + panic!("no row with opcode 0x{opcode:02x} in trace"); } diff --git a/processor/src/trace/tests/stack.rs b/processor/src/trace/tests/stack.rs index 11f4284d72..56d7b40f9b 100644 --- a/processor/src/trace/tests/stack.rs +++ b/processor/src/trace/tests/stack.rs @@ -1,96 +1,86 @@ -use alloc::vec::Vec; - -use miden_air::trace::{AUX_TRACE_RAND_CHALLENGES, Challenges, STACK_AUX_TRACE_OFFSET}; -use miden_core::{ONE, ZERO, field::Field, operations::Operation}; - -use super::{super::stack::OverflowTableRow, Felt, build_trace_from_ops, rand_array}; +//! Stack overflow table bus test. +//! +//! Runs a PAD/DROP sequence and verifies that every interaction the stack-overflow bus emitter +//! fires at a right-shift (`PAD`) or left-shift-with-non-empty-overflow (`DROP`) row appears +//! somewhere in that row's bag of prover pushes. The test is column-blind — see +//! `processor/src/trace/tests/lookup_harness.rs` for the subset matcher. +//! +//! The DYNCALL branch of the bus is intentionally not exercised here; constructing a DYNCALL +//! with a non-empty overflow requires a full program around a host and fits better in an +//! integration test. -// CONSTANTS -// ================================================================================================ +use alloc::vec::Vec; -const P1_COL_IDX: usize = STACK_AUX_TRACE_OFFSET; -const TWO: Felt = Felt::new_unchecked(2); +use miden_air::logup::StackOverflowMsg; +use miden_core::{ + Felt, + operations::{Operation, opcodes}, +}; -// OVERFLOW TABLE TESTS -// ================================================================================================ +use super::{ + build_trace_from_ops, + lookup_harness::{Expectations, InteractionLog}, +}; +use crate::RowIndex; #[test] -fn p1_trace() { +fn stack_overflow_bus_emits_per_interaction_row() { + // Mix of right-shifts (PAD) and left-shifts (DROP) around a couple of U32add no-shift ops, + // ending with the overflow table empty. + // Overflow depth before each op (determines whether DROP emits): + // U32add:0 Pad:0→1 Pad:1→2 U32add:2 Drop:2→1 Pad:1→2 Drop:2→1 Drop:1→0 Drop:0 Pad:0→1 + // Drop:1→0 Four of the five DROPs run against a non-empty table and emit; the fourth DROP + // sees overflow=0 and is a no-op. All four PADs emit. let ops = vec![ - Operation::U32add, // no shift, clk 1 - Operation::Pad, // right shift, clk 2 - Operation::Pad, // right shift, clk 3 - Operation::U32add, // no shift, clk 4 - Operation::Drop, // left shift, clk 5 - Operation::Pad, // right shift, clk 6 - Operation::Drop, // left shift, clk 7 - Operation::Drop, // left shift, clk 8 - Operation::Drop, // left shift, clk 9 - Operation::Pad, // right shift, clk 10 - Operation::Drop, // left shift, clk 11 + Operation::U32add, // no shift + Operation::Pad, // right shift + Operation::Pad, // right shift + Operation::U32add, // no shift + Operation::Drop, // left shift (overflow=2 → remove) + Operation::Pad, // right shift + Operation::Drop, // left shift (overflow=2 → remove) + Operation::Drop, // left shift (overflow=1 → remove) + Operation::Drop, // left shift (overflow=0 → no interaction) + Operation::Pad, // right shift + Operation::Drop, // left shift (overflow=1 → remove) ]; - let init_stack = (1..17).rev().collect::>(); + let init_stack = (1..17).rev().collect::>(); let trace = build_trace_from_ops(ops, &init_stack); - let challenges = rand_array::(); - let aux_columns = trace.build_aux_trace(&challenges).unwrap(); - let p1 = aux_columns.get_column(P1_COL_IDX); - - let challenges: Challenges = Challenges::::new(challenges[0], challenges[1]); - let row_values = [ - OverflowTableRow::new(Felt::new_unchecked(2), ONE, ZERO).to_value(&challenges), - OverflowTableRow::new(Felt::new_unchecked(3), TWO, TWO).to_value(&challenges), - OverflowTableRow::new(Felt::new_unchecked(6), TWO, TWO).to_value(&challenges), - OverflowTableRow::new(Felt::new_unchecked(10), ZERO, ZERO).to_value(&challenges), - ]; - - // make sure the first entry is ONE - let mut expected_value = ONE; - assert_eq!(expected_value, p1[0]); - - // SPAN and U32ADD do not affect the overflow table - assert_eq!(expected_value, p1[1]); - assert_eq!(expected_value, p1[2]); - - // two PADs push values 1 and 2 onto the overflow table - expected_value *= row_values[0]; - assert_eq!(expected_value, p1[3]); - expected_value *= row_values[1]; - assert_eq!(expected_value, p1[4]); - - // U32ADD does not affect the overflow table - assert_eq!(expected_value, p1[5]); - - // DROP removes a row from the overflow table - expected_value *= row_values[1].inverse(); - assert_eq!(expected_value, p1[6]); - - // PAD pushes the value onto the overflow table again - expected_value *= row_values[2]; - assert_eq!(expected_value, p1[7]); - - // two DROPs remove both values from the overflow table - expected_value *= row_values[2].inverse(); - assert_eq!(expected_value, p1[8]); - expected_value *= row_values[0].inverse(); - assert_eq!(expected_value, p1[9]); - - // at this point the table should be empty - assert_eq!(expected_value, ONE); - - // the 3rd DROP should not affect the overflow table as it is already empty - assert_eq!(expected_value, p1[10]); - - // PAD pushes the value (ZERO) onto the overflow table again - expected_value *= row_values[3]; - assert_eq!(expected_value, p1[11]); - - // and then the last DROP removes it from the overflow table - expected_value *= row_values[3].inverse(); - assert_eq!(expected_value, p1[12]); - - // at this point the table should be empty again, and it should stay empty until the end - assert_eq!(expected_value, ONE); - for i in 13..(p1.len()) { - assert_eq!(ONE, p1[i]); + let log = InteractionLog::new(&trace); + let main = trace.main_trace(); + + let mut exp = Expectations::new(&log); + for row in 0..main.num_rows() { + let idx = RowIndex::from(row); + let next = RowIndex::from(row + 1); + let op = main.get_op_code(idx); + + if op == Felt::from_u8(opcodes::PAD) { + // Right shift: `add (clk, s15, b1)`. + exp.add( + row, + &StackOverflowMsg { + clk: main.clk(idx), + val: main.stack_element(15, idx), + prev: main.parent_overflow_address(idx), + }, + ); + } else if op == Felt::from_u8(opcodes::DROP) && main.is_non_empty_overflow(idx) { + // Left shift with non-empty overflow: `remove (b1, s15', b1')`. + exp.remove( + row, + &StackOverflowMsg { + clk: main.parent_overflow_address(idx), + val: main.stack_element(15, next), + prev: main.parent_overflow_address(next), + }, + ); + } } + + // 4 PADs and 4 DROPs-with-non-empty-overflow (only the lone DROP on an empty overflow + // table emits nothing). + assert_eq!(exp.count_adds(), 4, "expected one add per PAD"); + assert_eq!(exp.count_removes(), 4, "expected one remove per DROP with non-empty overflow"); + log.assert_contains(&exp); } diff --git a/processor/src/trace/utils.rs b/processor/src/trace/utils.rs index ae12ecb89b..984ab7837e 100644 --- a/processor/src/trace/utils.rs +++ b/processor/src/trace/utils.rs @@ -1,15 +1,10 @@ -use alloc::{string::ToString, vec::Vec}; -use core::{mem::MaybeUninit, slice}; +use alloc::vec::Vec; +use core::slice; -use miden_air::trace::{Challenges, MIN_TRACE_LEN, MainTrace}; +use miden_air::trace::MIN_TRACE_LEN; use super::chiplets::Chiplets; -use crate::{ - Felt, RowIndex, - debug::BusDebugger, - field::ExtensionField, - utils::{assume_init_vec, uninit_vector}, -}; +use crate::{Felt, RowIndex}; #[cfg(test)] use crate::{operation::Operation, utils::ToElements}; @@ -252,94 +247,6 @@ impl ChipletsLengths { } } -// AUXILIARY COLUMN BUILDER -// ================================================================================================ - -/// Defines a builder responsible for building a single auxiliary bus column in the execution -/// trace. -/// -/// Columns are initialized to the multiset identity. Public-input-dependent boundary -/// terms are used in the check by the verifier in `reduced_aux_values` for the final values of -/// the auxiliary columns. -pub(crate) trait AuxColumnBuilder> { - // REQUIRED METHODS - // -------------------------------------------------------------------------------------------- - - fn get_requests_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - debugger: &mut BusDebugger, - ) -> E; - - fn get_responses_at( - &self, - main_trace: &MainTrace, - challenges: &Challenges, - row: RowIndex, - debugger: &mut BusDebugger, - ) -> E; - - /// Whether to assert that all requests/responses balance in debug mode. - /// - /// Buses whose final value encodes a public-input-dependent boundary term (checked - /// via `reduced_aux_values`) will NOT balance to identity and should return `false`. - #[cfg(any(test, feature = "bus-debugger"))] - fn enforce_bus_balance(&self) -> bool; - - // PROVIDED METHODS - // -------------------------------------------------------------------------------------------- - - /// Builds an auxiliary bus trace column as a running product of responses over requests. - /// - /// The column is initialized to 1; boundary terms are checked via `reduced_aux_values` - /// in the verifier. - fn build_aux_column(&self, main_trace: &MainTrace, challenges: &Challenges) -> Vec { - let mut bus_debugger = BusDebugger::new("aux bus".to_string()); - - let mut requests: Vec> = uninit_vector(main_trace.num_rows()); - requests[0].write(E::ONE); - - let mut responses_prod: Vec> = uninit_vector(main_trace.num_rows()); - responses_prod[0].write(E::ONE); - - let mut requests_running_prod = E::ONE; - let mut prev_prod = E::ONE; - - // Product of all requests to be inverted, used to compute inverses of requests. - for row_idx in 0..main_trace.num_rows() - 1 { - let row = row_idx.into(); - - let response = self.get_responses_at(main_trace, challenges, row, &mut bus_debugger); - prev_prod *= response; - responses_prod[row_idx + 1].write(prev_prod); - - let request = self.get_requests_at(main_trace, challenges, row, &mut bus_debugger); - requests[row_idx + 1].write(request); - requests_running_prod *= request; - } - - // all elements are now initialized - let requests = unsafe { assume_init_vec(requests) }; - let mut result_aux_column = unsafe { assume_init_vec(responses_prod) }; - - // Use batch-inversion method to compute running product of `response[i]/request[i]`. - let mut requests_running_divisor = requests_running_prod.inverse(); - for i in (0..main_trace.num_rows()).rev() { - result_aux_column[i] *= requests_running_divisor; - requests_running_divisor *= requests[i]; - } - - #[cfg(any(test, feature = "bus-debugger"))] - if self.enforce_bus_balance() { - assert!(bus_debugger.is_empty(), "{bus_debugger}"); - } - - result_aux_column - } -} - // U32 HELPERS // ================================================================================================ @@ -367,10 +274,13 @@ pub(crate) fn split_u32_into_u16(value: u64) -> (u16, u16) { // TEST HELPERS // ================================================================================================ +/// Builds a 17-op basic block payload that straddles a RESPAN batch boundary, plus the initial +/// values its `Push` ops emit. Consumed by decoder / hasher tests that exercise multi-batch +/// SPAN execution. #[cfg(test)] pub fn build_span_with_respan_ops() -> (Vec, Vec) { let iv = [1, 3, 5, 7, 9, 11, 13, 15, 17].to_elements(); - let ops = vec![ + let ops = alloc::vec![ Operation::Push(iv[0]), Operation::Push(iv[1]), Operation::Push(iv[2]), diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 02859873e0..52df4a9cbe 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -14,7 +14,7 @@ use miden_crypto::stark::{ }; use miden_processor::{ FastProcessor, Program, - trace::{AuxTraceBuilders, ExecutionTrace, build_trace}, + trace::{ExecutionTrace, build_trace}, }; use tracing::instrument; @@ -134,29 +134,28 @@ fn prove_execution_trace( let (public_values, kernel_felts) = trace.public_inputs().to_air_inputs(); let var_len_public_inputs: &[&[Felt]] = &[&kernel_felts]; - let aux_builder = trace.aux_trace_builders(); let params = config::pcs_params(); let proof_bytes = match hash_fn { HashFunction::Blake3_256 => { let config = config::blake3_256_config(params); - prove_stark(&config, &trace_matrix, &public_values, var_len_public_inputs, &aux_builder) + prove_stark(&config, &trace_matrix, &public_values, var_len_public_inputs) }, HashFunction::Keccak => { let config = config::keccak_config(params); - prove_stark(&config, &trace_matrix, &public_values, var_len_public_inputs, &aux_builder) + prove_stark(&config, &trace_matrix, &public_values, var_len_public_inputs) }, HashFunction::Rpo256 => { let config = config::rpo_config(params); - prove_stark(&config, &trace_matrix, &public_values, var_len_public_inputs, &aux_builder) + prove_stark(&config, &trace_matrix, &public_values, var_len_public_inputs) }, HashFunction::Poseidon2 => { let config = config::poseidon2_config(params); - prove_stark(&config, &trace_matrix, &public_values, var_len_public_inputs, &aux_builder) + prove_stark(&config, &trace_matrix, &public_values, var_len_public_inputs) }, HashFunction::Rpx256 => { let config = config::rpx_config(params); - prove_stark(&config, &trace_matrix, &public_values, var_len_public_inputs, &aux_builder) + prove_stark(&config, &trace_matrix, &public_values, var_len_public_inputs) }, }?; @@ -176,7 +175,6 @@ pub fn prove_stark( trace: &RowMajorMatrix, public_values: &[Felt], var_len_public_inputs: VarLenPublicInputs<'_, Felt>, - aux_builder: &AuxTraceBuilders, ) -> Result, ExecutionError> where SC: StarkConfig, @@ -192,7 +190,7 @@ where trace, public_values, var_len_public_inputs, - aux_builder, + &ProcessorAir, challenger, ) .map_err(|e| ExecutionError::ProvingError(e.to_string()))?;