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 auxiliary inputs reserved in the stark-vars block.
42    pub num_aux_inputs: usize,
43    /// Layout policy (Native vs Masm).
44    pub layout: LayoutKind,
45}
46
47/// Output of the ACE codegen pipeline (layout + DAG).
48#[derive(Debug)]
49pub(crate) struct AceArtifacts<EF> {
50    /// Input layout describing the READ section order.
51    pub layout: InputLayout,
52    /// DAG that mirrors the verifier evaluation.
53    pub dag: AceDag<EF>,
54}
55
56/// Build a verifier-equivalent ACE circuit for the provided AIR.
57///
58/// This is the highest-level entry point: it builds the DAG, validates layout
59/// invariants, and emits the off-VM circuit representation.
60pub fn build_ace_circuit_for_air<A, F, EF>(
61    air: &A,
62    config: AceConfig,
63) -> Result<AceCircuit<EF>, AceError>
64where
65    A: LiftedAir<F, EF>,
66    F: TwoAdicField,
67    EF: ExtensionField<F>,
68    SymbolicExpressionExt<F, EF>: Algebra<EF>,
69{
70    let artifacts = build_ace_dag_for_air::<A, F, EF>(air, config)?;
71    emit_circuit(&artifacts.dag, artifacts.layout)
72}
73
74/// Build the input layout for the provided AIR.
75///
76/// The returned `InputLayout` is validated and ready for input assembly.
77pub fn build_layout_for_air<A, F, EF>(air: &A, config: AceConfig) -> InputLayout
78where
79    A: LiftedAir<F, EF>,
80    F: Field,
81    EF: ExtensionField<F>,
82    SymbolicExpressionExt<F, EF>: Algebra<EF>,
83{
84    let num_periodic = air.periodic_columns().len();
85    let counts = input_counts_for_air::<A, F, EF>(air, config, num_periodic);
86    let layout = match config.layout {
87        LayoutKind::Native => InputLayout::new(counts),
88        LayoutKind::Masm => InputLayout::new_masm(counts),
89    };
90    layout.validate();
91    layout
92}
93
94/// Build a verifier-equivalent DAG and layout for the provided AIR.
95///
96/// This is useful when you need the DAG for off-VM checks and want to
97/// assemble inputs separately.
98pub(crate) fn build_ace_dag_for_air<A, F, EF>(
99    air: &A,
100    config: AceConfig,
101) -> Result<AceArtifacts<EF>, AceError>
102where
103    A: LiftedAir<F, EF>,
104    F: TwoAdicField,
105    EF: ExtensionField<F>,
106    SymbolicExpressionExt<F, EF>: Algebra<EF>,
107{
108    let periodic_columns = air.periodic_columns();
109    let counts = input_counts_for_air::<A, F, EF>(air, config, periodic_columns.len());
110    let layout = match config.layout {
111        LayoutKind::Native => InputLayout::new(counts),
112        LayoutKind::Masm => InputLayout::new_masm(counts),
113    };
114    layout.validate();
115
116    let air_layout = AirLayout {
117        preprocessed_width: 0,
118        main_width: counts.width,
119        num_public_values: counts.num_public,
120        permutation_width: counts.aux_width,
121        num_permutation_challenges: counts.num_randomness,
122        num_permutation_values: air.num_aux_values(),
123        num_periodic_columns: counts.num_periodic,
124    };
125    let mut builder = SymbolicAirBuilder::<F, EF>::new(air_layout);
126    air.eval(&mut builder);
127    let constraint_layout = builder.constraint_layout();
128    let base_constraints = builder.base_constraints();
129    let ext_constraints = builder.extension_constraints();
130
131    let periodic_data = (!periodic_columns.is_empty())
132        .then(|| PeriodicColumnData::from_periodic_columns::<F>(periodic_columns.to_vec()));
133    let dag = build_verifier_dag::<F, EF>(
134        &base_constraints,
135        &ext_constraints,
136        &constraint_layout,
137        &layout,
138        periodic_data.as_ref(),
139    );
140
141    Ok(AceArtifacts { layout, dag })
142}
143
144fn input_counts_for_air<A, F, EF>(air: &A, config: AceConfig, num_periodic: usize) -> InputCounts
145where
146    A: LiftedAir<F, EF>,
147    F: Field,
148    EF: ExtensionField<F>,
149{
150    assert!(config.num_quotient_chunks > 0, "num_quotient_chunks must be > 0");
151    assert!(
152        air.preprocessed_trace().is_none(),
153        "preprocessed trace inputs are not supported"
154    );
155
156    let num_randomness = air.num_randomness();
157    assert!(num_randomness > 0, "AIR must declare at least one randomness challenge");
158
159    InputCounts {
160        width: air.width(),
161        aux_width: air.aux_width(),
162        num_public: air.num_public_values(),
163        num_randomness,
164        num_periodic,
165        num_aux_inputs: config.num_aux_inputs,
166        num_quotient_chunks: config.num_quotient_chunks,
167    }
168}