Skip to main content

miden_ace_codegen/
pipeline.rs

1//! High-level ACE codegen pipeline helpers.
2//!
3//! This module ties together the major layers:
4//! - build a verifier-style DAG from AIR constraints,
5//! - choose a READ layout for inputs,
6//! - emit a circuit that mirrors verifier evaluation.
7
8use miden_crypto::{
9    field::{Algebra, ExtensionField, Field, TwoAdicField},
10    stark::air::{
11        LiftedAir,
12        symbolic::{AirLayout, SymbolicAirBuilder, SymbolicExpressionExt},
13    },
14};
15
16use crate::{
17    AceError,
18    circuit::{AceCircuit, emit_circuit},
19    dag::{AceDag, PeriodicColumnData, build_verifier_dag},
20    layout::{InputCounts, InputLayout},
21};
22
23/// Layout strategy for arranging ACE inputs.
24///
25/// - `Native`: minimal, no alignment/padding; useful for native (off-VM) evaluation.
26/// - `Masm`: matches the recursive verifier READ layout (alignment, padding, and randomness
27///   ordering).
28#[derive(Debug, Clone, Copy)]
29pub enum LayoutKind {
30    /// Minimal layout used for off-VM evaluation.
31    Native,
32    /// MASM-aligned layout used by the recursive verifier.
33    Masm,
34}
35
36/// Configuration for building an ACE DAG and its input layout.
37#[derive(Debug, Clone, Copy)]
38pub struct AceConfig {
39    /// Number of quotient chunks used by the AIR.
40    pub num_quotient_chunks: usize,
41    /// Number of variable-length public input groups.
42    /// Each group produces one reduced extension field element.
43    /// The layout policy handles alignment (e.g., MASM word-aligns each group to
44    /// 2 EF slots; Native uses 1 EF slot per group).
45    pub num_vlpi_groups: usize,
46    /// Layout policy (Native vs Masm).
47    pub layout: LayoutKind,
48    /// Whether this circuit is a multi-AIR combined circuit. When `true`, the
49    /// stark-vars region reserves additional EF slots for the multi-AIR β
50    /// coefficients (one per AIR) and per-AIR lifted selector triples (one per
51    /// AIR). Default: `false` (single-AIR layout).
52    pub is_multi_air: bool,
53}
54
55/// Output of the ACE codegen pipeline (layout + DAG).
56#[derive(Debug)]
57pub struct AceArtifacts<EF> {
58    /// Input layout describing the READ section order.
59    pub layout: InputLayout,
60    /// DAG that mirrors the verifier evaluation.
61    pub dag: AceDag<EF>,
62}
63
64/// Build a verifier-equivalent ACE circuit for the provided AIR.
65///
66/// This builds the constraint-evaluation DAG, validates layout invariants, and
67/// emits the off-VM circuit representation. The circuit performs the constraint
68/// evaluation check at the out-of-domain point z.
69pub fn build_ace_circuit_for_air<A, F, EF>(
70    air: &A,
71    config: AceConfig,
72) -> Result<AceCircuit<EF>, AceError>
73where
74    A: LiftedAir<F, EF>,
75    F: TwoAdicField,
76    EF: ExtensionField<F>,
77    SymbolicExpressionExt<F, EF>: Algebra<EF>,
78{
79    let artifacts = build_ace_dag_for_air::<A, F, EF>(air, config)?;
80    emit_circuit(&artifacts.dag, artifacts.layout)
81}
82
83/// Build a verifier-equivalent DAG and layout for the provided AIR.
84pub fn build_ace_dag_for_air<A, F, EF>(
85    air: &A,
86    config: AceConfig,
87) -> Result<AceArtifacts<EF>, AceError>
88where
89    A: LiftedAir<F, EF>,
90    F: TwoAdicField,
91    EF: ExtensionField<F>,
92    SymbolicExpressionExt<F, EF>: Algebra<EF>,
93{
94    let periodic_columns = air.periodic_columns();
95    let counts = input_counts_for_air::<A, F, EF>(air, config, periodic_columns.len());
96    let layout = match (config.layout, config.is_multi_air) {
97        (LayoutKind::Native, false) => InputLayout::new(counts),
98        (LayoutKind::Masm, false) => InputLayout::new_masm(counts),
99        (LayoutKind::Native, true) => InputLayout::new_multi_air(counts),
100        (LayoutKind::Masm, true) => InputLayout::new_masm_multi_air(counts),
101    };
102    layout.validate();
103
104    let air_layout = AirLayout {
105        preprocessed_width: 0,
106        main_width: counts.width,
107        num_public_values: counts.num_public,
108        permutation_width: counts.aux_width,
109        num_permutation_challenges: counts.num_randomness,
110        num_permutation_values: air.num_aux_values(),
111        num_periodic_columns: counts.num_periodic,
112    };
113    let mut builder = SymbolicAirBuilder::<F, EF>::new(air_layout);
114    air.eval(&mut builder);
115    let constraint_layout = builder.constraint_layout();
116    let base_constraints = builder.base_constraints();
117    let ext_constraints = builder.extension_constraints();
118
119    let periodic_data = (!periodic_columns.is_empty())
120        .then(|| PeriodicColumnData::from_periodic_columns::<F>(periodic_columns.to_vec()));
121    let dag = build_verifier_dag::<F, EF>(
122        &base_constraints,
123        &ext_constraints,
124        &constraint_layout,
125        &layout,
126        periodic_data.as_ref(),
127    );
128
129    Ok(AceArtifacts { layout, dag })
130}
131
132fn input_counts_for_air<A, F, EF>(air: &A, config: AceConfig, num_periodic: usize) -> InputCounts
133where
134    A: LiftedAir<F, EF>,
135    F: Field,
136    EF: ExtensionField<F>,
137{
138    assert!(config.num_quotient_chunks > 0, "num_quotient_chunks must be > 0");
139    assert!(
140        air.preprocessed_trace().is_none(),
141        "preprocessed trace inputs are not supported"
142    );
143
144    let num_randomness = air.num_randomness();
145    assert!(
146        num_randomness == 2,
147        "AIR must declare exactly 2 randomness challenges (alpha, beta), got {num_randomness}"
148    );
149
150    // Convert logical VLPI groups to EF slots based on layout policy.
151    // MASM word-aligns each group (4 base felts = 2 EF slots per group).
152    // Native uses 1 EF slot per group (no padding).
153    let num_vlpi = match config.layout {
154        LayoutKind::Masm => config.num_vlpi_groups * 2,
155        LayoutKind::Native => config.num_vlpi_groups,
156    };
157
158    InputCounts {
159        width: air.width(),
160        aux_width: air.aux_width(),
161        num_aux_boundary: air.num_aux_values(),
162        num_public: air.num_public_values(),
163        num_vlpi,
164        num_randomness,
165        num_periodic,
166        num_quotient_chunks: config.num_quotient_chunks,
167    }
168}