Skip to main content

logicaffeine_compile/codegen_mx/
mod.rs

1//! LOGOS → **mixed-signal XPU** — the unification of the digital whole-program engine
2//! ([`crate::codegen_sva`]'s wprog engine, exact ≡VM) and the analog plane
3//! ([`crate::codegen_ana`], ε-certified floats / ½-grid-certified-exact integers).
4//!
5//! ## The dispatch covenant (settled 2026-08-30)
6//! The analog plane is an ACCELERATOR, never a correctness dependency. A region function
7//! dispatches to the plane iff its certificate ADMITTED it (the float budget or the integer
8//! exactness gate, calibrated); the DIGITAL lowering of every region remains available in the
9//! same artifact, so stripping every binding leaves a program the engine runs ≡VM unchanged.
10//! SoC correctness is therefore the digital story; analog changes only energy and latency —
11//! inside the compiler's own ε, or exactly, per binding.
12//!
13//! This module owns the PARTITION (which functions bind to which lane, with ledgered refusals
14//! for the rest) and, in later phases, the composed artifact (engine config + region configs +
15//! the `MixedCertificate`) and the port models. Region compilation itself is entirely
16//! [`crate::codegen_ana::try_region`] — panic-safe, typed refusals, never a crash on arbitrary
17//! program shapes.
18
19use crate::codegen_ana::{try_region, AnalogArtifact, AnalogTarget, RegionLane};
20use logicaffeine_language::ast::stmt::{Expr, Stmt};
21
22/// One region function bound to the analog plane, certificate-admitted.
23#[derive(Debug, Clone)]
24pub struct RegionBinding {
25    pub fn_name: String,
26    pub lane: RegionLane,
27    pub artifact: AnalogArtifact,
28}
29
30/// One region function that stays digital, with the NAMED reason — the ledger the partition
31/// lock pins (shrink-toward-binding is the win condition).
32#[derive(Debug, Clone, PartialEq)]
33pub struct RegionRefusal {
34    pub fn_name: String,
35    pub reason: String,
36}
37
38/// The program's analog/digital split. `bindings` dispatch; `refusals` stay on the engine;
39/// nothing is silently skipped — every function appears in exactly one list.
40#[derive(Debug, Clone)]
41pub struct MixedPartition {
42    pub bindings: Vec<RegionBinding>,
43    pub refusals: Vec<RegionRefusal>,
44}
45
46impl MixedPartition {
47    /// Every function name, in declaration order — the totality witness.
48    pub fn all_fns(&self) -> Vec<&str> {
49        let mut v: Vec<(&str, bool)> = self
50            .bindings
51            .iter()
52            .map(|b| (b.fn_name.as_str(), true))
53            .chain(self.refusals.iter().map(|r| (r.fn_name.as_str(), false)))
54            .collect();
55        v.sort_by_key(|(n, _)| n.to_string());
56        v.into_iter().map(|(n, _)| n).collect()
57    }
58}
59
60/// Partition every function of `source` against `target`: certificate-admitted regions bind
61/// (int-exact or float-ε per [`try_region`]'s lane decision); everything else is ledgered with
62/// its typed reason. Deterministic: same source + target ⇒ the same partition, always.
63pub fn partition(source: &str, target: &AnalogTarget) -> MixedPartition {
64    let fn_names: Vec<String> =
65        crate::ui_bridge::with_parsed_program(source, |parsed, interner| match parsed {
66            Ok((stmts, _types, _pol)) => stmts
67                .iter()
68                .filter_map(|s| match s {
69                    Stmt::FunctionDef { name, .. } => Some(interner.resolve(*name).to_string()),
70                    _ => None,
71                })
72                .collect(),
73            Err(_) => Vec::new(),
74        });
75    let mut bindings = Vec::new();
76    let mut refusals = Vec::new();
77    for fn_name in fn_names {
78        match try_region(source, &fn_name, target) {
79            Ok((lane, artifact)) => bindings.push(RegionBinding {
80                fn_name,
81                lane,
82                artifact,
83            }),
84            Err(e) => refusals.push(RegionRefusal {
85                fn_name,
86                reason: e.to_string(),
87            }),
88        }
89    }
90    MixedPartition { bindings, refusals }
91}
92
93/// One binding PLACED on the stack, with its certified thermal map — the S6 composition.
94#[derive(Debug, Clone)]
95pub struct StackBinding {
96    pub binding: RegionBinding,
97    pub placement: crate::codegen_ana::stack_place::StackPlacement,
98    /// The certified steady-state map for THIS binding's placement (judged by
99    /// `logicaffeine_proof::thermal_replay::check_thermal_claim` — the lock re-judges it).
100    pub thermal: crate::codegen_ana::thermal::ThermalMap,
101}
102
103/// The stacked partition: every certificate-admitted region placed on the stack's analog
104/// tiers with a certified temperature map that MEETS the declared cooling budget; a region
105/// that placed but cannot be cooled is a LEDGERED refusal, not a hot surprise. The
106/// accelerator covenant is unchanged — stripping every binding leaves the digital program.
107#[derive(Debug, Clone)]
108pub struct StackPartition {
109    pub bindings: Vec<StackBinding>,
110    pub refusals: Vec<RegionRefusal>,
111}
112
113/// Partition `source` onto a STACK: region compilation against the stack's analog tier,
114/// 3D placement, the exact-ℚ thermal solve, and the cooling gate — in that order, each
115/// failure a named ledger entry. Deterministic end to end.
116pub fn partition_on_stack(
117    source: &str,
118    stack: &crate::codegen_ana::stack::StackTarget,
119    hop_cost_pitches: f64,
120) -> StackPartition {
121    use crate::codegen_ana::stack_place::place_stack;
122    use crate::codegen_ana::thermal::{check_cooling, thermal_default_opts, thermal_map};
123
124    let Some((_, target)) = stack.analog_tier() else {
125        return StackPartition {
126            bindings: Vec::new(),
127            refusals: vec![RegionRefusal {
128                fn_name: "*".to_string(),
129                reason: "stack has no analog tier".to_string(),
130            }],
131        };
132    };
133    let flat = partition(source, &target.clone());
134    let mut bindings = Vec::new();
135    let mut refusals = flat.refusals;
136    for b in flat.bindings {
137        let placed = match place_stack(&b.artifact.schedule.quantized, stack, hop_cost_pitches) {
138            Ok(p) => p,
139            Err(e) => {
140                refusals.push(RegionRefusal {
141                    fn_name: b.fn_name,
142                    reason: e.to_string(),
143                });
144                continue;
145            }
146        };
147        let map = match thermal_map(
148            stack,
149            &b.artifact.schedule.quantized,
150            &placed,
151            &thermal_default_opts(),
152        ) {
153            Ok(m) => m,
154            Err(e) => {
155                refusals.push(RegionRefusal {
156                    fn_name: b.fn_name,
157                    reason: e.to_string(),
158                });
159                continue;
160            }
161        };
162        if let Err(e) = check_cooling(&map, stack) {
163            refusals.push(RegionRefusal {
164                fn_name: b.fn_name,
165                reason: e.to_string(),
166            });
167            continue;
168        }
169        bindings.push(StackBinding {
170            binding: b,
171            placement: placed,
172            thermal: map,
173        });
174    }
175    StackPartition { bindings, refusals }
176}
177
178/// The four faces of the machine (R4): which laws of physics serve a region.
179#[derive(Clone, Copy, Debug, PartialEq, Eq)]
180pub enum Fabric {
181    /// `y = f(x)` — the ε-certified analog dataflow lanes.
182    Dataflow,
183    /// `ẋ = f(x)` — equation solving by settling (`Solve … for x.`).
184    Dynamics,
185    /// `minimize H` — soft-spin / gradient-flow relaxation (`Minimize … over x.`).
186    Relaxation,
187    /// Exact bits on the engine — everything else, refusing nothing.
188    Digital,
189}
190
191/// Classify every function's fabric AFFINITY: a certificate-admitted region is `Dataflow`;
192/// a body reaching the `Solve` desugar (`solveLinear`) is `Dynamics`; one reaching the
193/// `Minimize` desugar (`minimizeQuadratic`) is `Relaxation`; everything else is `Digital` —
194/// the face that refuses nothing. Deterministic, total.
195pub fn fabrics(source: &str, target: &AnalogTarget) -> Vec<(String, Fabric)> {
196    fn expr_calls(e: &Expr<'_>, needle: &str, interner: &crate::intern::Interner) -> bool {
197        match e {
198            Expr::Call { function, args } => {
199                interner.resolve(*function) == needle
200                    || args.iter().any(|a| expr_calls(a, needle, interner))
201            }
202            Expr::BinaryOp { left, right, .. } => {
203                expr_calls(left, needle, interner) || expr_calls(right, needle, interner)
204            }
205            _ => false,
206        }
207    }
208    fn body_calls(stmts: &[Stmt<'_>], needle: &str, interner: &crate::intern::Interner) -> bool {
209        stmts.iter().any(|s| match s {
210            Stmt::Let { value, .. } | Stmt::Set { value, .. } => {
211                expr_calls(value, needle, interner)
212            }
213            Stmt::Return { value: Some(v) } => expr_calls(v, needle, interner),
214            Stmt::If {
215                then_block,
216                else_block,
217                ..
218            } => {
219                body_calls(then_block, needle, interner)
220                    || else_block
221                        .map(|e| body_calls(e, needle, interner))
222                        .unwrap_or(false)
223            }
224            Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
225                body_calls(body, needle, interner)
226            }
227            _ => false,
228        })
229    }
230    let names_and_shapes: Vec<(String, bool, bool)> =
231        crate::ui_bridge::with_parsed_program(source, |parsed, interner| match parsed {
232            Ok((stmts, _t, _p)) => stmts
233                .iter()
234                .filter_map(|s| match s {
235                    Stmt::FunctionDef { name, body, .. } => Some((
236                        interner.resolve(*name).to_string(),
237                        body_calls(body, "solveLinear", interner),
238                        body_calls(body, "minimizeQuadratic", interner),
239                    )),
240                    _ => None,
241                })
242                .collect(),
243            Err(_) => Vec::new(),
244        });
245    names_and_shapes
246        .into_iter()
247        .map(|(name, solves, minimizes)| {
248            let fabric = if minimizes {
249                Fabric::Relaxation
250            } else if solves {
251                Fabric::Dynamics
252            } else if try_region(source, &name, target).is_ok() {
253                Fabric::Dataflow
254            } else {
255                Fabric::Digital
256            };
257            (name, fabric)
258        })
259        .collect()
260}