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}