Skip to main content

logicaffeine_compile/codegen_sva/
fabric_tile.rs

1//! fabric_tile — the SINGLE SOURCE OF TRUTH for "which ISA ops the spatial fabric realizes".
2//!
3//! The soft-core interpreter runs *every* op (it is the universal fallback). The spatial fabric — the
4//! ASIC-perf datapath — realizes a growing SUBSET: the campaign lowers the ISA onto tiles category by
5//! category ("the interpreter itself becomes the fabric"). [`fabric_tile`] classifies each [`Op`] as the
6//! tile it lowers to, or `None` when it still falls back to the interpreter. This is the fabric analog of
7//! `isa::silicon_realized`: the coverage ratchet (`fabric_coverage_lock.rs`) keys off it, and — crucially —
8//! every op it marks realized must ship an `eval == VM` equivalence witness, so a coverage claim can never
9//! outrun its proof.
10//!
11//! Tile kinds grow as coverage grows (bitwise/shift, the shared state plane for collections, floats, CRDT,
12//! concurrency, calls). Today the realized set is the arithmetic/comparison datapath plus the plumbing
13//! (Move/LoadConst) and control (Return) the fabric framework wires around it.
14
15use super::fabric_rtl::{AluOp, CmpKind, FuncUnit};
16use super::fabric_target::{Capability, FabricTarget};
17use crate::vm::Op;
18
19/// The fabric tile an [`Op`] lowers to. A *compute* tile (`Alu`/`Cmp`/`Func`) has independently-fuzzable
20/// datapath semantics and MUST carry an `eval == VM` witness in the coverage lock; a *structural* tile
21/// (`Wire`/`Control`) is realized by the fabric framework itself (a route, a const, a controller edge) and
22/// is exercised by every fabric, so it carries no standalone fuzz witness.
23#[derive(Clone, Copy, Debug)]
24pub enum FabricTile {
25    /// Combinational arithmetic — an `AluOp` tile (`alu.v`).
26    Alu(AluOp),
27    /// Combinational comparison — folds into a `Select` predicate tile.
28    Cmp(CmpKind),
29    /// Sequential functional unit — the `shared_divider` (division/remainder), start/done handshake.
30    Func(FuncUnit),
31    /// Memory read — a scratchpad/heap read tile (the induction counter drives the address bus). Today:
32    /// `IterNext`'s element read `xs[iv]` in a sequence loop (proven by `fabric_mem_lock`).
33    Mem,
34    /// Plumbing — `Move`/`LoadConst`: realized as a wire / a baked constant, no compute tile.
35    Wire,
36    /// Control — `Return`/`ReturnNothing`: realized by the CFG controller (the fabric's result edge).
37    Control,
38}
39
40impl FabricTile {
41    /// A compute tile has independently-fuzzable datapath semantics (so it needs a witness); structural
42    /// tiles (`Wire`/`Control`) are framework plumbing (exercised by every fabric, no standalone witness).
43    pub fn is_compute(&self) -> bool {
44        matches!(
45            self,
46            FabricTile::Alu(_) | FabricTile::Cmp(_) | FabricTile::Func(_) | FabricTile::Mem
47        )
48    }
49
50    /// The capability a described target must provide to HOST this tile: the ALU op, the functional unit,
51    /// the predicated mux a comparison folds into, the state plane for a memory tile. A structural tile
52    /// (`Wire`/`Control`) is the fabric framework's own and needs nothing from the descriptor.
53    pub fn capability(&self) -> Option<Capability> {
54        match self {
55            FabricTile::Alu(op) => Some(Capability::Alu(*op)),
56            FabricTile::Func(unit) => Some(Capability::Func(*unit)),
57            FabricTile::Cmp(_) => Some(Capability::Select),
58            FabricTile::Mem => Some(Capability::Memory),
59            FabricTile::Wire | FabricTile::Control => None,
60        }
61    }
62}
63
64/// THE authority: the fabric tile this op lowers to, or `None` when it still falls back to the interpreter.
65/// As the campaign lowers each op category, its arm moves from `None` into a `Some(tile)` here — and the
66/// coverage lock then demands its equivalence witness before the floor can rise.
67pub fn fabric_tile(op: &Op) -> Option<FabricTile> {
68    Some(match op {
69        // ── combinational arithmetic (alu.v) ──
70        Op::Add { .. } => FabricTile::Alu(AluOp::Add),
71        Op::AddAssign { .. } => FabricTile::Alu(AluOp::Add), // `x += y` — the same adder, in place
72        // Reversible ALU (`rev_alu_core` ISA — no source emits them, so hand-bytecode witnessed against the
73        // VM oracle): the same combinational tiles as their irreversible twins, updating `place` in place.
74        Op::RevXor { .. } => FabricTile::Alu(AluOp::Xor),
75        Op::RevAdd { .. } => FabricTile::Alu(AluOp::Add),
76        Op::RevSub { .. } => FabricTile::Alu(AluOp::Sub),
77        Op::RevNeg { .. } => FabricTile::Alu(AluOp::Sub), // `p = -p` = `0 - p`
78        Op::Sub { .. } => FabricTile::Alu(AluOp::Sub),
79        Op::Mul { .. } => FabricTile::Alu(AluOp::Mul),
80        // ── bitwise / shift (combinational, alu.v) ──
81        Op::BitAnd { .. } => FabricTile::Alu(AluOp::And),
82        Op::BitOr { .. } => FabricTile::Alu(AluOp::Or),
83        Op::BitXor { .. } => FabricTile::Alu(AluOp::Xor),
84        Op::Shl { .. } => FabricTile::Alu(AluOp::Shl),
85        Op::Shr { .. } => FabricTile::Alu(AluOp::Shr),
86        // ── sequential division (shared_divider), b==0 → 0 ──
87        Op::Div { .. } => FabricTile::Func(FuncUnit::DivS),
88        Op::Mod { .. } => FabricTile::Func(FuncUnit::ModS),
89        Op::FloorDiv { .. } => FabricTile::Func(FuncUnit::FloorDivS),
90        // `ExactDiv` (exact-operand contract) and `DivPow2` (`lhs / 2^k`) both reduce to the same signed
91        // truncating divider — the Oracle only emits them where that equals the intended result. No source
92        // emits them on the no-oracle fabric path, so they are hand-bytecode witnessed.
93        Op::ExactDiv { .. } | Op::DivPow2 { .. } => FabricTile::Func(FuncUnit::DivS),
94        // `MagicDivU` — division/modulo by a constant via the magic reciprocal; the characteristic unit is
95        // the unsigned high-half multiply (`MulHiU`), reduced by shifts/add. Hand-bytecode witnessed.
96        Op::MagicDivU { .. } => FabricTile::Func(FuncUnit::MulHiU),
97        // ── exponentiation: the sequential power unit (a `b`-step multiply FSM) ──
98        Op::Pow { .. } => FabricTile::Func(FuncUnit::Pow),
99        // `is approximately` — the numeric approximate-equality functional unit (f64 tolerance). Float
100        // operands are refused at the Float load, so only the numeric-int case is realized (witnessed).
101        Op::ApproxEq { .. } => FabricTile::Func(FuncUnit::ApproxEqF),
102        // `BigIntWide` — the 256-bit BigInt lane. FromInt/Add/Mul/Low are realized with an overflow-tracked
103        // `big` flag (characteristic unit = the signed-overflow test); Sub/Cmp/Div are refused. Witnessed.
104        Op::BigIntWide { .. } => FabricTile::Func(FuncUnit::MulOvfS),
105        // ── combinational comparison → predicate/select ──
106        Op::Lt { .. } => FabricTile::Cmp(CmpKind::Lt),
107        Op::Gt { .. } => FabricTile::Cmp(CmpKind::Gt),
108        Op::LtEq { .. } => FabricTile::Cmp(CmpKind::Le),
109        Op::GtEq { .. } => FabricTile::Cmp(CmpKind::Ge),
110        Op::Eq { .. } => FabricTile::Cmp(CmpKind::Eq),
111        Op::NotEq { .. } => FabricTile::Cmp(CmpKind::Ne),
112        // logical not `(src == 0) ? 1 : 0` — a Select driven by an equality-to-zero comparison
113        Op::Not { .. } => FabricTile::Cmp(CmpKind::Eq),
114        // ── sequence iteration (loop fabric): the element read + the loop-setup scaffolding ──
115        Op::IterNext { .. } => FabricTile::Mem,
116        // `IterPop` — pop the iterator at a loop's end; the explicit-loop-index model makes it a vacuous
117        // structural no-op (the loop-fabric extractor already treats it so).
118        Op::NewRange { .. } | Op::IterPrepare { .. } | Op::IterPop => FabricTile::Control,
119        // ── state-plane READS (the shared heap): collection length + 1-based element index ──
120        // `IndexUnchecked` (the Oracle-proven-in-bounds form) shares the read tile — no source emits it on
121        // the no-oracle fabric compile path, so it is hand-bytecode witnessed (identical to `Index` in range).
122        Op::Length { .. } | Op::Index { .. } | Op::IndexUnchecked { .. } => FabricTile::Mem,
123        // ── state-plane ALLOC + WRITE: build a fresh list, set an element in place, grow via push ──
124        // `SetIndexUnchecked` shares the in-place write tile (hand-bytecode witnessed, like `IndexUnchecked`).
125        Op::NewList { .. }
126        | Op::SetIndex { .. }
127        | Op::SetIndexUnchecked { .. }
128        | Op::ListPush { .. } => FabricTile::Mem,
129        // `NewEmptyListI32` (the narrowing-proven half-width empty `Seq of Int`) shares the length-0 heap
130        // region — no source emits it on the no-oracle fabric path, so it is hand-bytecode witnessed.
131        Op::NewEmptyList { .. }
132        | Op::NewEmptyListWithCapacity { .. }
133        | Op::NewEmptyListFixed { .. }
134        | Op::NewEmptyListI32 { .. }
135        | Op::ListPop { .. } => FabricTile::Mem,
136        // an empty set / map — the same length-0 heap region (structure only matters once elements are added)
137        Op::NewEmptySet { .. } | Op::NewEmptyMap { .. } | Op::NewEmptyMapWithCapacity { .. } => {
138            FabricTile::Mem
139        }
140        // `Add v to s` — dedup append: a runtime-count-guarded membership scan then a conditional write
141        Op::SetAdd { .. } => FabricTile::Mem,
142        // `Remove v from s` — compacting removal: a running `past` flag shifts every later slot down by one
143        Op::RemoveFrom { .. } => FabricTile::Mem,
144        // `s union t` — a fresh set: copy lhs, then dedup-append each rhs element not already present
145        Op::UnionOp { .. } => FabricTile::Mem,
146        // `s intersection t` — a fresh set: lhs elements filtered by membership in rhs (nested scan)
147        Op::IntersectOp { .. } => FabricTile::Mem,
148        // `Concat` (string interpolation) — render each operand to a char-list (int→decimal) and concatenate
149        Op::Concat { .. } => FabricTile::Mem,
150        // `FormatValue` — render a value to TEXT (default case: int→decimal char-list, or a Text identity).
151        // Format-specs are a follow-on; hand-bytecode witnessed on the default rendering.
152        Op::FormatValue { .. } => FabricTile::Mem,
153        // channel DATA ops — a heap FIFO buffer (create / enqueue / dequeue) for sequential (spawn-free) use;
154        // ChanTrySend always succeeds on the depth-bounded FIFO (enqueue + true)
155        Op::ChanNew { .. }
156        | Op::ChanSend { .. }
157        | Op::ChanRecv { .. }
158        | Op::ChanTrySend { .. }
159        | Op::ChanTryRecv { .. } => FabricTile::Mem,
160        // `ChanClose` — output-transparent on the deterministic scheduler (buffered values stay receivable),
161        // so a structural no-op on the combinational fabric. Hand-bytecode differential vs the RevCoop oracle.
162        Op::ChanClose { .. } => FabricTile::Control,
163        // `select` — the arm-registration ops (`SelectArmRecv`/`SelectArmTimeout`) record pending state (no
164        // cell → Control); `SelectWait` is the PRIORITY resolver — a bounded fan of channel-readiness reads +
165        // a guarded dequeue of the winner (a memory tile), proven by a `select` corpus program ≡ the VM.
166        Op::SelectArmRecv { .. } | Op::SelectArmTimeout { .. } => FabricTile::Control,
167        Op::SelectWait { .. } => FabricTile::Mem,
168        // `contains` — a bounded, unrolled OR-scan over the collection's static length (heap reads + equality)
169        Op::Contains { .. } => FabricTile::Mem,
170        // `copy of xs` — a deep clone: unrolled element copy into a fresh heap region (value semantics)
171        Op::DeepClone { .. } => FabricTile::Mem,
172        // `EnsureOwned` — the call-site COW barrier: clone a possibly-aliased collection before a mutable-
173        // borrow callee writes it (always-clone = observably identical to the VM's clone-iff-shared). Mem.
174        Op::EnsureOwned { .. } => FabricTile::Mem,
175        // `xs followed by ys` — sequence concatenation: two unrolled copies into a fresh region
176        Op::SeqConcat { .. } => FabricTile::Mem,
177        // `items s through e of xs` — a slice: unrolled subrange copy (const bounds) into a fresh region
178        Op::SliceOp { .. } => FabricTile::Mem,
179        // `(a, b, c)` — a tuple shares the list heap shape `[count, e0..]`; indexed by `item i of t`
180        Op::NewTuple { .. } => FabricTile::Mem,
181        // `Let (x, y, ..) be t` — read each tuple component `heap[ptr+1+j]` into a register (a fan of reads).
182        // Source emits it only in a `Repeat for (x,y) in ..` loop, so it is hand-bytecode witnessed.
183        Op::DestructureTuple { .. } => FabricTile::Mem,
184        // `Args` — the command-line list, EMPTY on a spatial circuit (no CLI); a length-0 heap region.
185        Op::Args { .. } => FabricTile::Mem,
186        // ── structs on the shared state plane `[n, f0..f_{n-1}]` — alloc + field read/write. `GetField`/
187        // `StructInsert`'s field NAME resolves to a slot via register TYPE-TRACKING (REG_TYPE set at
188        // NewStruct → STRUCT_FIELD_POS), the sound mechanism that also unlocks floats.
189        Op::NewStruct { .. } | Op::GetField { .. } | Op::StructInsert { .. } => FabricTile::Mem,
190        // `Increase/Decrease s's field by n` — a CRDT counter bump: a struct-field read-add-write
191        Op::CrdtBump { .. } => FabricTile::Mem,
192        // `Resolve s's field to v` — resolve a plain field: a struct-field overwrite (value semantics)
193        Op::CrdtResolve { .. } => FabricTile::Mem,
194        // `Append v to xs` — for a list seq, exactly a heap push (grow the FIFO/list in place)
195        Op::CrdtAppend { .. } => FabricTile::Mem,
196        // `Merge source into target` — per-field add over a plain-Int `Shared` struct's fields
197        Op::CrdtMerge { .. } => FabricTile::Mem,
198        // `CrdtJoin` — per-field MAX (the idempotent G-Counter lattice join). No source emits it, so it is
199        // hand-bytecode witnessed: the same struct heap walk as CrdtMerge, with a `max` Select per field.
200        Op::CrdtJoin { .. } => FabricTile::Mem,
201        // `new counter` / `new growing set` — create a fresh CRDT value. The fabric models the deterministic
202        // initial state (a counter is 0; the drawn replica id is unobservable single-replica). Witnessed by a
203        // hand-bytecode counter/PN-counter Show ≡ VM; the collection kinds (set/seq/register) are a follow-on.
204        Op::NewCrdt { .. } => FabricTile::Mem,
205        // `Push v to s's field` — read the field's list pointer, then push (nested list in a struct)
206        Op::ListPushField { .. } => FabricTile::Mem,
207        // ── enums / pattern matching on the same heap plane `[tag, arg0..]`. NewInductive/BindArm are heap
208        // alloc + payload read; TestArm is a tag comparison that folds into the match's `Select` predicate.
209        Op::NewInductive { .. } | Op::BindArm { .. } => FabricTile::Mem,
210        Op::TestArm { .. } => FabricTile::Cmp(CmpKind::Eq),
211        // A promoted GLOBAL (a module-level `Let` used inside a function) is a pure dataflow WIRE in the
212        // straight-line fabric: `GlobalSet` remembers the definition's wire, `GlobalGet` re-wires it into the
213        // reader. No cell — set-before-use makes it plumbing, exactly like `Move` (proven by a whole-program
214        // differential that reads a global from an inlined callee ≡ the VM).
215        Op::GlobalGet { .. } | Op::GlobalSet { .. } => FabricTile::Wire,
216        // ── plumbing / control the fabric framework realizes ──
217        Op::Move { .. } | Op::LoadConst { .. } => FabricTile::Wire,
218        // `now` / `today` — a clock value-load: the fabric reads the injected fixed clock and bakes the
219        // instant as a Const (structurally a value-load, like LoadConst). Proven by a differential where a
220        // date-component extractor (`the hour of now`, `the weekday of today`) renders a plain Int == the VM.
221        Op::LoadNow { .. } | Op::LoadToday { .. } => FabricTile::Wire,
222        // `Swap a and b` — a reversible register exchange, realized as pure rewiring (no cell)
223        Op::RevSwap { .. } => FabricTile::Wire,
224        Op::Return { .. } | Op::ReturnNothing => FabricTile::Control,
225        // `Show v` — the whole-program output edge: the fabric framework appends the value to the output
226        // tape (proven by the whole-program differential, `eval_whole_program == VM stdout`).
227        Op::Show { .. } => FabricTile::Control,
228        // `Sleep n` — a temporal delay with no data effect. The combinational fabric is clockless, so the
229        // deterministic execution model makes it a vacuous no-op (proven by a differential vs the concurrent
230        // VM, whose output is identical with or without the sleep). A structural control edge.
231        Op::Sleep { .. } => FabricTile::Control,
232        // `FailWith` — raise an error; the VM shows nothing after, so the fabric HALTS its output tape at
233        // that point (a control edge). Proven by a differential vs the VM's output up to the failure.
234        Op::FailWith { .. } => FabricTile::Control,
235        // `CheckPolicy` — a SECURITY guard; a granted check is transparent, a denied one makes the VM return
236        // `Err` (no output after). The fabric compiles the condition to a per-`Check` GRANT predicate and
237        // CONDITIONALLY halts the output tape (a control edge, not a data tile). A no-op here would be a
238        // security hole, so it is proven by a grant-AND-deny differential vs the VM's output-up-to-the-error.
239        Op::CheckPolicy { .. } => FabricTile::Control,
240        // `Obs` — an observability event feeding a separate trace sink (off by default), never stdout; it has
241        // no effect on the program's output, so a structural no-op. Differential vs the VM (output unchanged).
242        Op::Obs { .. } => FabricTile::Control,
243        // `RegionBoundsGuard` — an Oracle bounds-check assertion with no data effect (VM: `pc += 1`), so a
244        // structural control edge. No source emits it on the fabric path; exercised by a hand-bytecode test.
245        Op::RegionBoundsGuard { .. } => FabricTile::Control,
246        // OFFLINE networking (single node, no relay) — the deterministic model the AOT proves (tw==VM==AOT):
247        // Connect/Listen/Send/Stream have no observable effect and `NetSync` is offline-identity, so all are
248        // structural control no-ops; a `vm_outcome_net` differential exercises them (never "realized by
249        // ignoring"). `NetMakePeer` wires an opaque handle from its address — structural plumbing.
250        // `NetListen`/`NetSend`/`NetAwait` form the offline loopback (Listen establishes a FIFO inbox, Send
251        // publishes, Await drains) — the deterministic single-node round-trip; Connect/Stream/Sync are pure
252        // no-ops/offline-identity. All structural, proven together by the `vm_outcome_net` differential.
253        Op::NetConnect { .. }
254        | Op::NetListen { .. }
255        | Op::NetSend { .. }
256        | Op::NetStream { .. }
257        | Op::NetSync { .. }
258        | Op::NetAwait { .. } => FabricTile::Control,
259        Op::NetMakePeer { .. } => FabricTile::Wire,
260        // `Halt` — the program-end edge: every `Main` terminates on it, and the whole-program builder ends
261        // the fabric there (exercised by every whole-program differential).
262        Op::Halt => FabricTile::Control,
263        // A direct call is realized by INLINING the callee's decision fabric into the caller (Futamura) —
264        // the call site is a structural composition edge; the callee's own ops are the compute tiles.
265        Op::Call { .. } => FabricTile::Control,
266        // `CallBuiltin` — the numeric builtins: the float lane (`Sqrt`/`Floor` on `f64` bit patterns) plus
267        // integer `Abs`/`Min`/`Max` selects. A compute tile (characteristic unit = the float floor bridge);
268        // its witness exercises both the float path and the int selects. IO/DOM/format builtins are refused.
269        Op::CallBuiltin { .. } => FabricTile::Func(FuncUnit::FFloorF),
270        // CLOSURES: `MakeClosure` captures its free variables into a tracked closure value (dataflow, no
271        // cell → Wire); `CallValue` resolves the closure's STATIC body and inlines it (a composition edge
272        // like `Call` → Control). Dynamic dispatch realized as a Futamura projection; hand-bytecode witnessed.
273        Op::MakeClosure { .. } => FabricTile::Wire,
274        Op::CallValue { .. } => FabricTile::Control,
275        // The SCHEDULER (spatial coroutine): `Spawn`/`SpawnHandle` inline a producer child's side effects at
276        // the spawn point (the child's ops are the compute tiles); `TaskAwait` joins instantly (child already
277        // ran) and `TaskAbort` is a no-op on a complete child — all structural composition edges, witnessed
278        // through the cooperative scheduler oracle (the full scheduler for `TaskAbort`, RevCoop for the rest).
279        Op::Spawn { .. } | Op::SpawnHandle { .. } | Op::TaskAwait { .. } | Op::TaskAbort { .. } => {
280            FabricTile::Control
281        }
282        // Branch control: the decision fabric if-CONVERTS `JumpIfFalse` (its predicate → a Select) and
283        // consumes the merge `Jump`; both are exercised by every comparison witness's `If`. `JumpIfTrue` is
284        // the short-circuit `or`/`and` mirror — it materialises a Bool temp the same way, if-converted too.
285        Op::Jump { .. } | Op::JumpIfFalse { .. } | Op::JumpIfTrue { .. } => FabricTile::Control,
286        // ── the interpreter-fallback frontier (grows smaller each campaign phase) ──
287        _ => return None,
288    })
289}
290
291/// The mnemonics of every op the fabric realizes today — the ground-truth "covered" set for the ratchet.
292pub fn fabric_realized_ops() -> Vec<&'static str> {
293    Op::all_variants()
294        .iter()
295        .filter(|o| fabric_tile(o).is_some())
296        .map(|o| o.name())
297        .collect()
298}
299
300/// The realized ops a described target's envelope HOSTS: every op whose tile's capability the descriptor
301/// provides (a structural tile is hosted everywhere). One descriptor's column of the coverage ratchet —
302/// `rdu_op_coverage_lock` pins it for the SN50 exactly, so a capability lost on the descriptor is as red
303/// as a tile lost on the fabric.
304pub fn hosted_ops(target: &FabricTarget) -> Vec<&'static str> {
305    Op::all_variants()
306        .iter()
307        .filter(|o| {
308            fabric_tile(o).is_some_and(|t| t.capability().map_or(true, |cap| target.provides(cap)))
309        })
310        .map(|o| o.name())
311        .collect()
312}
313
314/// Why the fabric has no realization of an op on the interpreter-fallback frontier — `None` for every
315/// realized op, and `None` for a frontier op nobody has explained, which the coverage locks refuse: an op
316/// is tiled or it states its reason, there is no third state. A lane that meets one of these ops refuses
317/// the program with this sentence, never a silently dropped op.
318pub fn frontier_reason(op: &Op) -> Option<&'static str> {
319    if fabric_tile(op).is_some() {
320        return None;
321    }
322    match op {
323        Op::Tcp { .. } => Some(
324            "Tcp: real host sockets (the tcp.lg client natives) have no spatial-fabric realization and no offline model yet — graduating it needs a sealed-world tcp model, never a silent no-op tile",
325        ),
326        Op::Serve { .. } => Some("Serve: the HTTP accept loop is host IO (bind/accept/read/write) with no spatial-fabric realization"),
327        _ => None,
328    }
329}
330
331/// The fraction of the ISA the fabric datapath realizes (the rest runs on the interpreter fallback).
332/// Ratchets toward 1.0 as the campaign lowers each op category.
333pub fn fabric_coverage() -> f64 {
334    let total = Op::all_variants().len();
335    fabric_realized_ops().len() as f64 / total as f64
336}