1pub const W: u32 = 64;
24
25#[derive(
28 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
29)]
30pub enum AluOp {
31 Add,
32 Sub,
33 Mul,
34 And,
37 Or,
38 Xor,
39 Shl,
41 Shr,
43 UShr,
48}
49
50impl AluOp {
51 pub fn verilog(&self, a: &str, b: &str) -> String {
52 match self {
53 AluOp::Add => format!("$signed({a}) + $signed({b})"),
54 AluOp::Sub => format!("$signed({a}) - $signed({b})"),
55 AluOp::Mul => format!("$signed({a}) * $signed({b})"),
56 AluOp::And => format!("({a}) & ({b})"),
57 AluOp::Or => format!("({a}) | ({b})"),
58 AluOp::Xor => format!("({a}) ^ ({b})"),
59 AluOp::Shl => format!("(({a}) << (({b}) & 64'd63))"),
61 AluOp::Shr => format!("($signed({a}) >>> (({b}) & 64'd63))"),
62 AluOp::UShr => format!("($unsigned({a}) >> (({b}) & 64'd63))"),
64 }
65 }
66 pub fn eval(&self, a: i64, b: i64) -> i64 {
67 match self {
68 AluOp::Add => a.wrapping_add(b),
69 AluOp::Sub => a.wrapping_sub(b),
70 AluOp::Mul => a.wrapping_mul(b),
71 AluOp::And => a & b,
72 AluOp::Or => a | b,
73 AluOp::Xor => a ^ b,
74 AluOp::Shl => a.wrapping_shl(b as u32),
75 AluOp::Shr => a.wrapping_shr(b as u32),
76 AluOp::UShr => (a as u64).wrapping_shr(b as u32) as i64,
77 }
78 }
79}
80
81#[derive(
88 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
89)]
90pub enum FuncUnit {
91 DivS,
93 ModS,
95 FloorDivS,
97 Pow,
101 MulHiU,
105 ApproxEqF,
110 FAddF,
115 FSubF,
116 FMulF,
117 FDivF,
118 FSqrtF,
119 FFloorF,
120 MulOvfS,
124 IntToF,
130 FLtF,
135 FGtF,
136 FLeF,
137 FGeF,
138 FEqF,
139 FNeF,
140 PopCount,
144}
145
146impl FuncUnit {
147 pub fn eval(&self, a: i64, b: i64) -> i64 {
148 if let FuncUnit::Pow = self {
151 return a.wrapping_pow(b.max(0) as u32);
152 }
153 if let FuncUnit::MulHiU = self {
156 return (((a as u64 as u128) * (b as u64 as u128)) >> 64) as i64;
157 }
158 if let FuncUnit::ApproxEqF = self {
161 return i64::from(logicaffeine_data::ops::logos_approx_eq(a as f64, b as f64));
162 }
163 match self {
166 FuncUnit::FAddF => {
167 return (f64::from_bits(a as u64) + f64::from_bits(b as u64)).to_bits() as i64
168 }
169 FuncUnit::FSubF => {
170 return (f64::from_bits(a as u64) - f64::from_bits(b as u64)).to_bits() as i64
171 }
172 FuncUnit::FMulF => {
173 return (f64::from_bits(a as u64) * f64::from_bits(b as u64)).to_bits() as i64
174 }
175 FuncUnit::FDivF => {
176 return (f64::from_bits(a as u64) / f64::from_bits(b as u64)).to_bits() as i64
177 }
178 FuncUnit::FSqrtF => return f64::from_bits(a as u64).sqrt().to_bits() as i64,
179 FuncUnit::FFloorF => return f64::from_bits(a as u64).floor() as i64,
180 FuncUnit::MulOvfS => {
181 let p = (a as i128) * (b as i128);
182 return i64::from(p < i64::MIN as i128 || p > i64::MAX as i128);
183 }
184 FuncUnit::IntToF => return (a as f64).to_bits() as i64,
187 FuncUnit::FLtF => {
189 return i64::from(f64::from_bits(a as u64) < f64::from_bits(b as u64))
190 }
191 FuncUnit::FGtF => {
192 return i64::from(f64::from_bits(a as u64) > f64::from_bits(b as u64))
193 }
194 FuncUnit::FLeF => {
195 return i64::from(f64::from_bits(a as u64) <= f64::from_bits(b as u64))
196 }
197 FuncUnit::FGeF => {
198 return i64::from(f64::from_bits(a as u64) >= f64::from_bits(b as u64))
199 }
200 FuncUnit::FEqF => {
201 return i64::from(f64::from_bits(a as u64) == f64::from_bits(b as u64))
202 }
203 FuncUnit::FNeF => {
204 return i64::from(f64::from_bits(a as u64) != f64::from_bits(b as u64))
205 }
206 FuncUnit::PopCount => return (a as u64).count_ones() as i64,
208 _ => {}
209 }
210 if b == 0 {
211 return 0; }
213 match self {
214 FuncUnit::DivS => a.wrapping_div(b),
215 FuncUnit::ModS => a.wrapping_rem(b),
216 FuncUnit::FloorDivS => {
217 let q = a.wrapping_div(b);
218 if a.wrapping_rem(b) != 0 && ((a < 0) != (b < 0)) {
219 q - 1
220 } else {
221 q
222 }
223 }
224 FuncUnit::Pow => unreachable!("Pow handled above"),
225 FuncUnit::MulHiU => unreachable!("MulHiU handled above"),
226 FuncUnit::ApproxEqF
227 | FuncUnit::FAddF
228 | FuncUnit::FSubF
229 | FuncUnit::FMulF
230 | FuncUnit::FDivF
231 | FuncUnit::FSqrtF
232 | FuncUnit::FFloorF
233 | FuncUnit::MulOvfS
234 | FuncUnit::IntToF
235 | FuncUnit::FLtF
236 | FuncUnit::FGtF
237 | FuncUnit::FLeF
238 | FuncUnit::FGeF
239 | FuncUnit::FEqF
240 | FuncUnit::FNeF
241 | FuncUnit::PopCount => unreachable!("float/approx/overflow/popcount handled above"),
242 }
243 }
244 pub fn verilog(&self, a: &str, b: &str) -> String {
248 match self {
249 FuncUnit::DivS => format!("(($signed({b}) == 0) ? 64'sd0 : $signed({a}) / $signed({b}))"),
250 FuncUnit::ModS => format!("(($signed({b}) == 0) ? 64'sd0 : $signed({a}) % $signed({b}))"),
251 FuncUnit::FloorDivS => format!(
252 "(($signed({b}) == 0) ? 64'sd0 : (($signed({a}) % $signed({b}) != 0 && \
253 (($signed({a}) < 0) != ($signed({b}) < 0))) ? ($signed({a}) / $signed({b})) - 64'sd1 \
254 : $signed({a}) / $signed({b})))"
255 ),
256 FuncUnit::Pow => panic!(
257 "Pow RTL is a sequential-multiplier functional unit (a `b`-step multiply FSM), not a \
258 combinational wire — it is emitted by `emit_pow_datapath_verilog` (the sequenced power \
259 lane) and iverilog-exercised by fabric_pow_lock; the combinational tape does not emit it"
260 ),
261 FuncUnit::MulHiU => format!("(({{64'h0, {a}}} * {{64'h0, {b}}}) >> 64)"),
264 FuncUnit::MulOvfS => format!(
268 "((($signed({{{{64{{{a}[63]}}}}, {a}}}) * $signed({{{{64{{{b}[63]}}}}, {b}}})) \
269 <= 128'sh0000_0000_0000_0000_7FFF_FFFF_FFFF_FFFF && \
270 ($signed({{{{64{{{a}[63]}}}}, {a}}}) * $signed({{{{64{{{b}[63]}}}}, {b}}})) \
271 >= -128'sh0000_0000_0000_0000_8000_0000_0000_0000) ? 64'sd0 : 64'sd1)"
272 ),
273 FuncUnit::ApproxEqF => format!(
279 "((($signed({a}) == $signed({b})) || \
280 ($signed(((($signed({a}) - $signed({b})) < 0) ? -($signed({a}) - $signed({b})) \
281 : ($signed({a}) - $signed({b}))) * 128'sd1000000000) <= \
282 $signed((((($signed({a}) < 0) ? -$signed({a}) : $signed({a})) > \
283 (($signed({b}) < 0) ? -$signed({b}) : $signed({b}))) ? \
284 ((($signed({a}) < 0) ? -$signed({a}) : $signed({a}))) : \
285 ((($signed({b}) < 0) ? -$signed({b}) : $signed({b}))))))) ? 64'sd1 : 64'sd0)"
286 ),
287 FuncUnit::FFloorF => ffloor_verilog(a),
295 FuncUnit::PopCount => popcount_verilog(a),
298 FuncUnit::FAddF
299 | FuncUnit::FSubF
300 | FuncUnit::FMulF
301 | FuncUnit::FDivF
302 | FuncUnit::FSqrtF
303 | FuncUnit::IntToF
304 | FuncUnit::FLtF
305 | FuncUnit::FGtF
306 | FuncUnit::FLeF
307 | FuncUnit::FGeF
308 | FuncUnit::FEqF
309 | FuncUnit::FNeF => panic!(
310 "the float-lane RTL (an FPU functional unit binding fpu.v) is emitted by \
311 `emit_float_datapath_verilog` (the sequenced float lane), not the combinational tape — its \
312 semantics are pinned by the eval + differential + iverilog (float_soc_lock)"
313 ),
314 }
315 }
316}
317
318#[derive(
323 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
324)]
325pub enum Divergence {
326 IntOverflow,
328 DivideByZero,
330 NegativeExponent,
332 ExponentTooLarge,
334 FloatDivideByZero,
336}
337
338impl Divergence {
339 pub const ALL: [Divergence; 5] = [
340 Divergence::IntOverflow,
341 Divergence::DivideByZero,
342 Divergence::NegativeExponent,
343 Divergence::ExponentTooLarge,
344 Divergence::FloatDivideByZero,
345 ];
346
347 pub fn code(self) -> u32 {
349 match self {
350 Divergence::IntOverflow => 1,
351 Divergence::DivideByZero => 2,
352 Divergence::NegativeExponent => 3,
353 Divergence::ExponentTooLarge => 4,
354 Divergence::FloatDivideByZero => 5,
355 }
356 }
357
358 pub fn from_code(code: u32) -> Option<Divergence> {
360 Divergence::ALL.into_iter().find(|d| d.code() == code)
361 }
362
363 pub fn name(self) -> &'static str {
365 match self {
366 Divergence::IntOverflow => "int-overflow",
367 Divergence::DivideByZero => "divide-by-zero",
368 Divergence::NegativeExponent => "negative-exponent",
369 Divergence::ExponentTooLarge => "exponent-too-large",
370 Divergence::FloatDivideByZero => "float-divide-by-zero",
371 }
372 }
373}
374
375impl std::fmt::Display for Divergence {
376 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377 f.write_str(self.name())
378 }
379}
380
381impl AluOp {
382 pub fn eval_checked(&self, a: i64, b: i64) -> Result<i64, Divergence> {
385 match self {
386 AluOp::Add => a.checked_add(b).ok_or(Divergence::IntOverflow),
387 AluOp::Sub => a.checked_sub(b).ok_or(Divergence::IntOverflow),
388 AluOp::Mul => a.checked_mul(b).ok_or(Divergence::IntOverflow),
389 AluOp::And | AluOp::Or | AluOp::Xor | AluOp::Shl | AluOp::Shr | AluOp::UShr => {
390 Ok(self.eval(a, b))
391 }
392 }
393 }
394}
395
396impl FuncUnit {
397 pub fn eval_checked(&self, a: i64, b: i64) -> Result<i64, Divergence> {
400 match self {
401 FuncUnit::DivS => {
402 if b == 0 {
403 Err(Divergence::DivideByZero)
404 } else {
405 a.checked_div(b).ok_or(Divergence::IntOverflow)
406 }
407 }
408 FuncUnit::ModS => {
409 if b == 0 {
410 Err(Divergence::DivideByZero)
411 } else {
412 Ok(a.wrapping_rem(b))
413 }
414 }
415 FuncUnit::FloorDivS => {
416 if b == 0 {
417 Err(Divergence::DivideByZero)
418 } else if a == i64::MIN && b == -1 {
419 Err(Divergence::IntOverflow)
420 } else {
421 Ok(self.eval(a, b))
422 }
423 }
424 FuncUnit::Pow => {
425 if b < 0 {
426 Err(Divergence::NegativeExponent)
427 } else {
428 let e = u32::try_from(b).map_err(|_| Divergence::ExponentTooLarge)?;
429 a.checked_pow(e).ok_or(Divergence::IntOverflow)
430 }
431 }
432 FuncUnit::FDivF => {
433 if f64::from_bits(b as u64) == 0.0 {
434 Err(Divergence::FloatDivideByZero)
435 } else {
436 Ok(self.eval(a, b))
437 }
438 }
439 FuncUnit::MulHiU
440 | FuncUnit::ApproxEqF
441 | FuncUnit::FAddF
442 | FuncUnit::FSubF
443 | FuncUnit::FMulF
444 | FuncUnit::FSqrtF
445 | FuncUnit::FFloorF
446 | FuncUnit::MulOvfS
447 | FuncUnit::IntToF
448 | FuncUnit::FLtF
449 | FuncUnit::FGtF
450 | FuncUnit::FLeF
451 | FuncUnit::FGeF
452 | FuncUnit::FEqF
453 | FuncUnit::FNeF
454 | FuncUnit::PopCount => Ok(self.eval(a, b)),
455 }
456 }
457}
458
459fn ffloor_verilog(a: &str) -> String {
464 let exp = format!("($signed({{2'b0, {a}[62:52]}}) - 13'sd1023)"); let mant = format!("({a}[62:52]==11'd0 ? {{1'b0,{a}[51:0]}} : {{1'b1,{a}[51:0]}})"); let tmag = format!(
469 "({a}[62:52] == 11'd2047 ? 64'h7FFFFFFFFFFFFFFF : \
470 ({exp} < 0 ? 64'd0 : \
471 ({exp} >= 63 ? 64'hFFFFFFFFFFFFFFFF : \
472 ({exp} >= 52 ? ({{11'd0, {mant}}} << ({exp} - 52)) \
473 : ({{11'd0, {mant}}} >> (52 - {exp}))))))"
474 );
475 let had_frac = format!(
477 "({a}[62:52] == 11'd2047 ? 1'b0 : \
478 ({exp} < 0 ? ({mant} != 53'd0) : \
479 ({exp} >= 52 ? 1'b0 : \
480 ((({{11'd0, {mant}}}) & ((64'd1 << (52 - {exp})) - 64'd1)) != 64'd0))))"
481 );
482 format!("({a}[63] ? (-$signed({tmag}) - ({had_frac} ? 64'sd1 : 64'sd0)) : $signed({tmag}))")
484}
485
486fn popcount_verilog(a: &str) -> String {
491 let terms = (0..64)
492 .map(|i| format!("{{63'd0, {a}[{i}]}}"))
493 .collect::<Vec<_>>()
494 .join(" + ");
495 format!("({terms})")
496}
497
498#[derive(Clone, Copy, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize, Eq)]
501pub enum Src {
502 In(usize),
503 Val(usize),
504 Const(i64),
505}
506
507impl Src {
508 fn verilog(&self) -> String {
509 match self {
510 Src::In(k) => format!("in{k}"),
511 Src::Val(k) => format!("v{k}"),
512 Src::Const(c) => {
513 if *c < 0 {
514 format!("-64'sd{}", (*c as i128).unsigned_abs())
515 } else {
516 format!("64'sd{c}")
517 }
518 }
519 }
520 }
521 pub fn eval(&self, inputs: &[i64], vals: &[i64]) -> i64 {
522 match self {
523 Src::In(k) => inputs[*k],
524 Src::Val(k) => vals[*k],
525 Src::Const(c) => *c,
526 }
527 }
528}
529
530#[derive(Clone, Copy, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize, Eq)]
532pub struct FabricOp {
533 pub op: AluOp,
534 pub a: Src,
535 pub b: Src,
536}
537
538pub fn emit_fabric_verilog(name: &str, n_in: usize, ops: &[FabricOp], result: usize) -> String {
542 let inputs = (0..n_in)
543 .map(|k| format!("input [63:0] in{k}"))
544 .collect::<Vec<_>>()
545 .join(", ");
546 let mut body = String::new();
547 for (i, op) in ops.iter().enumerate() {
548 body.push_str(&format!(
549 " wire [63:0] v{i} = {};\n",
550 op.op.verilog(&op.a.verilog(), &op.b.verilog())
551 ));
552 }
553 format!(
554 "// fabric_{name} — a compiled dispatch-free residual as a fixed dataflow circuit.\n\
555 // No program counter, no fetch, no opcode dispatch: the residual's ops are WIRED\n\
556 // (SSA), not interpreted. Futamura P1: the specialized residual IS the circuit.\n\
557 // Lowered from the REAL compiler's residual; 64-bit so it wraps identically to the\n\
558 // VM's Int; proven == the running VM by iverilog.\n\
559 module fabric_{name}({inputs}, output [63:0] result);\n\
560 {body} assign result = v{result};\n\
561 endmodule\n"
562 )
563}
564
565pub fn eval_fabric(ops: &[FabricOp], inputs: &[i64], result: usize) -> i64 {
568 let mut vals: Vec<i64> = Vec::with_capacity(ops.len());
569 for op in ops {
570 let a = op.a.eval(inputs, &vals);
571 let b = op.b.eval(inputs, &vals);
572 vals.push(op.op.eval(a, b));
573 }
574 vals[result]
575}
576
577pub fn residual_from_source(src: &str, fn_name: &str) -> (Vec<FabricOp>, usize, usize) {
584 use crate::vm::{Compiler, Constant, Op};
585 use std::collections::HashMap;
586
587 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
588 let (stmts, _types, _pol) = parsed.expect("parse failed");
589 let program = Compiler::compile(stmts, interner).expect("compile failed");
590 let f = program
591 .functions
592 .iter()
593 .find(|f| interner.resolve(f.name) == fn_name)
594 .unwrap_or_else(|| panic!("function `{fn_name}` not found in the compiled program"));
595
596 let mut body: Vec<Op> = Vec::new();
598 for op in &program.code[f.entry_pc..] {
599 body.push(*op);
600 if matches!(op, Op::Return { .. } | Op::ReturnNothing) {
601 break;
602 }
603 }
604
605 let mut reg: HashMap<u16, Src> = HashMap::new();
607 for p in 0..f.param_count {
608 reg.insert(p, Src::In(p as usize));
609 }
610 let mut ops: Vec<FabricOp> = Vec::new();
611 let mut result: Option<Src> = None;
612 let get = |reg: &HashMap<u16, Src>, r: &u16| -> Src {
613 *reg.get(r)
614 .unwrap_or_else(|| panic!("residual reads unwritten register R{r}"))
615 };
616 for op in &body {
617 let binop = |aop: AluOp,
618 dst: &u16,
619 lhs: &u16,
620 rhs: &u16,
621 reg: &mut HashMap<u16, Src>,
622 ops: &mut Vec<FabricOp>| {
623 let k = ops.len();
624 ops.push(FabricOp {
625 op: aop,
626 a: get(reg, lhs),
627 b: get(reg, rhs),
628 });
629 reg.insert(*dst, Src::Val(k));
630 };
631 match op {
632 Op::LoadConst { dst, idx } => {
633 let v = match program.constants[*idx as usize] {
634 Constant::Int(n) => n,
635 ref c => panic!("fabric residual: non-integer constant {c:?}"),
636 };
637 reg.insert(*dst, Src::Const(v));
638 }
639 Op::Move { dst, src } => {
640 let s = get(®, src);
641 reg.insert(*dst, s);
642 }
643 Op::Add { dst, lhs, rhs } => binop(AluOp::Add, dst, lhs, rhs, &mut reg, &mut ops),
644 Op::Sub { dst, lhs, rhs } => binop(AluOp::Sub, dst, lhs, rhs, &mut reg, &mut ops),
645 Op::Mul { dst, lhs, rhs } => binop(AluOp::Mul, dst, lhs, rhs, &mut reg, &mut ops),
646 Op::Shr { dst, lhs, rhs } => binop(AluOp::Shr, dst, lhs, rhs, &mut reg, &mut ops),
650 Op::Shl { dst, lhs, rhs } => binop(AluOp::Shl, dst, lhs, rhs, &mut reg, &mut ops),
651 Op::BitXor { dst, lhs, rhs } => {
654 binop(AluOp::Xor, dst, lhs, rhs, &mut reg, &mut ops)
655 }
656 Op::BitAnd { dst, lhs, rhs } => {
657 binop(AluOp::And, dst, lhs, rhs, &mut reg, &mut ops)
658 }
659 Op::BitOr { dst, lhs, rhs } => binop(AluOp::Or, dst, lhs, rhs, &mut reg, &mut ops),
660 Op::Return { src } => result = Some(get(®, src)),
661 Op::ReturnNothing => {}
662 other => panic!(
663 "fabric residual: `{}` is not a straight-line arithmetic op (control flow / \
664 unsupported — loops map to a CGRA, roadmap)",
665 other.name()
666 ),
667 }
668 }
669 let result = match result {
670 Some(Src::Val(k)) => k,
671 other => panic!("fabric residual result is not a computed value: {other:?}"),
672 };
673 (ops, result, f.param_count as usize)
674 })
675}
676
677pub fn fabric_to_bounded(
680 ops: &[FabricOp],
681 result: usize,
682 width: u32,
683) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
684 use crate::codegen_sva::sva_to_verify::{BitVecBoundedOp, BoundedExpr};
685 fn src_expr(s: Src, vals: &[BoundedExpr], width: u32) -> BoundedExpr {
686 match s {
687 Src::In(k) => BoundedExpr::BitVecVar(format!("in{k}"), width),
688 Src::Val(k) => vals[k].clone(),
689 Src::Const(c) => BoundedExpr::BitVecConst {
690 width,
691 value: c as u64,
692 },
693 }
694 }
695 let mut vals: Vec<BoundedExpr> = Vec::with_capacity(ops.len());
696 for op in ops {
697 let a = src_expr(op.a, &vals, width);
698 let b = src_expr(op.b, &vals, width);
699 let bop = match op.op {
700 AluOp::Add => BitVecBoundedOp::Add,
701 AluOp::Sub => BitVecBoundedOp::Sub,
702 AluOp::Mul => BitVecBoundedOp::Mul,
703 AluOp::And => BitVecBoundedOp::And,
704 AluOp::Or => BitVecBoundedOp::Or,
705 AluOp::Xor => BitVecBoundedOp::Xor,
706 AluOp::Shl => BitVecBoundedOp::Shl,
707 AluOp::Shr => BitVecBoundedOp::AShr,
708 AluOp::UShr => BitVecBoundedOp::Shr,
709 };
710 vals.push(BoundedExpr::BitVecBinary {
711 op: bop,
712 left: Box::new(a),
713 right: Box::new(b),
714 });
715 }
716 vals[result].clone()
717}
718
719pub const POLY_BODY: &str = "(((2 * x + 5) * x - 3) * x + 100)";
722
723pub fn poly_program(main: &str) -> String {
725 format!("## To poly (x: Int) -> Int:\n Return {POLY_BODY}.\n## Main\n {main}")
726}
727
728pub fn emit_fabric_testbench(name: &str, n: usize) -> String {
731 format!(
732 r#"// tb_fabric_{name} — the dataflow circuit vs the RUNNING VM's output for the program.
733`timescale 1ns/1ps
734module tb_fabric_{name};
735 reg [63:0] in0;
736 reg [63:0] oin [0:{last}];
737 reg [63:0] oout [0:{last}];
738 wire [63:0] result;
739 integer i, errors = 0;
740 fabric_{name} dut(.in0(in0), .result(result));
741 initial begin
742 $readmemh("fabric_in.hex", oin);
743 $readmemh("fabric_out.hex", oout);
744 for (i = 0; i < {n}; i = i + 1) begin
745 in0 = oin[i]; #1;
746 if (result !== oout[i]) begin errors = errors + 1; $display("FAIL eval %0d", i); end
747 end
748 if (errors == 0) $display("PASS: {n} circuit evaluations match the VM (residual = circuit, no interpreter)");
749 else $display("FAIL: %0d fabric mismatches", errors);
750 $finish;
751 end
752endmodule
753"#,
754 name = name,
755 last = n - 1,
756 n = n,
757 )
758}
759
760#[derive(Clone, Copy, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize, Eq)]
779pub enum CmpKind {
780 Lt,
781 Gt,
782 Le,
783 Ge,
784 Eq,
785 Ne,
786}
787
788impl CmpKind {
789 pub fn eval(&self, a: i64, b: i64) -> bool {
790 match self {
791 CmpKind::Lt => a < b,
792 CmpKind::Gt => a > b,
793 CmpKind::Le => a <= b,
794 CmpKind::Ge => a >= b,
795 CmpKind::Eq => a == b,
796 CmpKind::Ne => a != b,
797 }
798 }
799 pub fn verilog(&self) -> &'static str {
801 match self {
802 CmpKind::Lt => "<",
803 CmpKind::Gt => ">",
804 CmpKind::Le => "<=",
805 CmpKind::Ge => ">=",
806 CmpKind::Eq => "==",
807 CmpKind::Ne => "!=",
808 }
809 }
810}
811
812#[derive(Clone, Copy, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize, Eq)]
816pub enum LoopCell {
817 Arith { op: AluOp, a: Src, b: Src },
819 Select {
821 cmp: CmpKind,
822 ca: Src,
823 cb: Src,
824 t: Src,
825 e: Src,
826 },
827 Func { unit: FuncUnit, a: Src, b: Src },
831 MemRead { addr: Src },
835 HeapRead { addr: Src },
840 HeapWrite { addr: Src, val: Src },
842 HeapAlloc { size: Src },
845 VectorArith {
849 op: super::simd::VecOp,
850 ty: super::simd::LaneTy,
851 a: Src,
852 b: Src,
853 },
854}
855
856#[derive(Clone, Debug)]
861pub struct LoopFabric {
862 pub n_in: usize,
864 pub n_state: usize,
866 pub init: Vec<Src>,
868 pub lo: Src,
870 pub hi: Src,
871 pub invariants: Vec<Src>,
873 pub body: Vec<LoopCell>,
876 pub next: Vec<Src>,
879 pub result_state: usize,
881 pub seq_input: Option<usize>,
887 pub epilogue: Vec<LoopCell>,
893 pub epilogue_result: Src,
895 pub prologue_heap: Vec<LoopCell>,
899 pub seq_heap_ptr: Option<Src>,
905}
906
907fn src_named(
910 s: &Src,
911 name_in: &dyn Fn(usize) -> String,
912 name_val: &dyn Fn(usize) -> String,
913) -> String {
914 match s {
915 Src::In(k) => name_in(*k),
916 Src::Val(k) => name_val(*k),
917 Src::Const(c) => {
918 if *c < 0 {
919 format!("-64'sd{}", (*c as i128).unsigned_abs())
920 } else {
921 format!("64'sd{c}")
922 }
923 }
924 }
925}
926
927#[derive(Clone, Copy)]
932struct GSlot {
933 src: Src,
934 len: Option<i64>,
935 text: bool,
936 ty: Option<u32>,
937}
938
939#[derive(Clone, Debug)]
943enum PolicyGrant {
944 IntEq { slot: usize, expected: i64 },
947 TextEq { slot: usize, expected: Vec<u8> },
950 AlwaysDeny,
955}
956
957#[derive(Clone, Copy, PartialEq, Eq, Debug)]
960enum TimeKind {
961 Moment,
962 Date,
963}
964
965thread_local! {
966 static STRUCT_FIELDS: std::cell::RefCell<std::collections::HashMap<u32, usize>> =
972 std::cell::RefCell::new(std::collections::HashMap::new());
973
974 static STRUCT_FIELD_POS: std::cell::RefCell<std::collections::HashMap<(u32, u32), usize>> =
978 std::cell::RefCell::new(std::collections::HashMap::new());
979
980 static REG_TYPE: std::cell::RefCell<std::collections::HashMap<u16, u32>> =
984 std::cell::RefCell::new(std::collections::HashMap::new());
985
986 static INLINE_CODE: std::cell::RefCell<Vec<crate::vm::Op>> = std::cell::RefCell::new(Vec::new());
991 static INLINE_FNS: std::cell::RefCell<Vec<(usize, u16)>> = std::cell::RefCell::new(Vec::new());
992 static INLINE_DEPTH: std::cell::RefCell<usize> = std::cell::RefCell::new(0);
993
994 static COLL_LEN: std::cell::RefCell<std::collections::HashMap<u16, i64>> =
999 std::cell::RefCell::new(std::collections::HashMap::new());
1000
1001 static SHOW_OUTPUTS: std::cell::RefCell<Vec<(Src, bool, Option<Src>)>> =
1005 std::cell::RefCell::new(Vec::new());
1006
1007 static TEXT_REGS: std::cell::RefCell<std::collections::HashSet<u16>> =
1011 std::cell::RefCell::new(std::collections::HashSet::new());
1012
1013 static NAME_REGS: std::cell::RefCell<std::collections::HashMap<u16, u32>> =
1016 std::cell::RefCell::new(std::collections::HashMap::new());
1017
1018 static FLOAT_REGS: std::cell::RefCell<std::collections::HashSet<u16>> =
1023 std::cell::RefCell::new(std::collections::HashSet::new());
1024
1025 static W32_REGS: std::cell::RefCell<std::collections::HashSet<u16>> =
1030 std::cell::RefCell::new(std::collections::HashSet::new());
1031
1032 static FLOAT_COLLS: std::cell::RefCell<std::collections::HashSet<u16>> =
1037 std::cell::RefCell::new(std::collections::HashSet::new());
1038
1039 static BIGINT_BIG: std::cell::RefCell<std::collections::HashMap<u16, Src>> =
1044 std::cell::RefCell::new(std::collections::HashMap::new());
1045
1046 static GLOBALS: std::cell::RefCell<std::collections::HashMap<u16, GSlot>> =
1051 std::cell::RefCell::new(std::collections::HashMap::new());
1052
1053 static NET_INBOX: std::cell::RefCell<Option<Src>> = std::cell::RefCell::new(None);
1058
1059 static SELECT_ARMS: std::cell::RefCell<Vec<Option<(Src, u16)>>> =
1063 std::cell::RefCell::new(Vec::new());
1064
1065 static HALTED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1069
1070 static REG_TIMEKIND: std::cell::RefCell<std::collections::HashMap<u16, TimeKind>> =
1075 std::cell::RefCell::new(std::collections::HashMap::new());
1076
1077 #[allow(clippy::type_complexity)]
1082 static CLOSURES: std::cell::RefCell<std::collections::HashMap<u16, (usize, Vec<(Src, bool)>)>> =
1083 std::cell::RefCell::new(std::collections::HashMap::new());
1084
1085 static CLOSURE_CAPTURES: std::cell::RefCell<Vec<Vec<Option<u16>>>> =
1089 std::cell::RefCell::new(Vec::new());
1090
1091 static POLICY_CONDITIONS: std::cell::RefCell<std::collections::HashMap<(u32, u32, bool), PolicyGrant>> =
1100 std::cell::RefCell::new(std::collections::HashMap::new());
1101
1102 static POLICY_GUARD: std::cell::RefCell<Option<Src>> = std::cell::RefCell::new(None);
1108}
1109
1110fn struct_field_slot(obj_reg: u16, field_const: u32) -> usize {
1114 let tn = REG_TYPE.with(|m| m.borrow().get(&obj_reg).copied()).expect(
1115 "struct field access on a register with no tracked struct type \
1116 (param-passed structs need param-type seeding — not yet wired)",
1117 );
1118 STRUCT_FIELD_POS
1119 .with(|m| m.borrow().get(&(tn, field_const)).copied())
1120 .expect("struct field slot not found for (type, field) — STRUCT_FIELD_POS not populated for this type")
1121}
1122
1123fn struct_field_slot_named(obj_reg: u16, name: u32, consts: &[crate::vm::Constant]) -> usize {
1126 let tn = REG_TYPE
1127 .with(|m| m.borrow().get(&obj_reg).copied())
1128 .expect("struct field write on a register with no tracked struct type");
1129 let text = |i: u32| match consts.get(i as usize) {
1130 Some(crate::vm::Constant::Text(s)) => Some(s.as_str()),
1131 _ => None,
1132 };
1133 let wanted = text(name).expect("a struct field is keyed by a Text constant");
1134 STRUCT_FIELD_POS
1135 .with(|m| {
1136 m.borrow()
1137 .iter()
1138 .find(|((t, f), _)| *t == tn && text(*f) == Some(wanted))
1139 .map(|(_, &slot)| slot)
1140 })
1141 .unwrap_or_else(|| {
1142 panic!("struct field write: no field named {wanted:?} on the object's type")
1143 })
1144}
1145
1146fn canonical_text_tag(consts: &[crate::vm::Constant], idx: u32) -> i64 {
1152 let name = match &consts[idx as usize] {
1153 crate::vm::Constant::Text(s) => s.as_str(),
1154 other => panic!("enum tag: constant {idx} is not Text: {other:?}"),
1155 };
1156 consts
1157 .iter()
1158 .position(|c| matches!(c, crate::vm::Constant::Text(s) if s == name))
1159 .expect("enum tag: name not found in constants") as i64
1160}
1161
1162fn push_int_to_decimal(cells: &mut Vec<LoopCell>, v: Src) -> Src {
1167 let ksign = cells.len();
1168 cells.push(LoopCell::Select {
1169 cmp: CmpKind::Lt,
1170 ca: v,
1171 cb: Src::Const(0),
1172 t: Src::Const(1),
1173 e: Src::Const(0),
1174 });
1175 let sign = Src::Val(ksign);
1176 let kneg = cells.len();
1177 cells.push(LoopCell::Arith {
1178 op: AluOp::Sub,
1179 a: Src::Const(0),
1180 b: v,
1181 });
1182 let km = cells.len();
1183 cells.push(LoopCell::Select {
1184 cmp: CmpKind::Lt,
1185 ca: v,
1186 cb: Src::Const(0),
1187 t: Src::Val(kneg),
1188 e: v,
1189 });
1190 let m = Src::Val(km);
1191 let ka = cells.len();
1192 cells.push(LoopCell::HeapAlloc {
1193 size: Src::Const(21),
1194 }); let ptr = Src::Val(ka);
1196 let kdaddr = cells.len(); cells.push(LoopCell::Arith {
1198 op: AluOp::Add,
1199 a: ptr,
1200 b: Src::Const(1),
1201 });
1202 cells.push(LoopCell::HeapWrite {
1203 addr: Src::Val(kdaddr),
1204 val: Src::Const(45),
1205 });
1206 let kwp = cells.len(); cells.push(LoopCell::Select {
1208 cmp: CmpKind::Ne,
1209 ca: sign,
1210 cb: Src::Const(0),
1211 t: Src::Const(2),
1212 e: Src::Const(1),
1213 });
1214 let mut write_pos = Src::Val(kwp);
1215 let mut p10: i64 = 1;
1216 for _ in 0..18 {
1217 p10 *= 10; }
1219 for i in (0..19).rev() {
1220 let kq = cells.len();
1221 cells.push(LoopCell::Func {
1222 unit: FuncUnit::DivS,
1223 a: m,
1224 b: Src::Const(p10),
1225 });
1226 let kr = cells.len();
1227 cells.push(LoopCell::Func {
1228 unit: FuncUnit::ModS,
1229 a: Src::Val(kq),
1230 b: Src::Const(10),
1231 });
1232 let kchar = cells.len();
1233 cells.push(LoopCell::Arith {
1234 op: AluOp::Add,
1235 a: Src::Val(kr),
1236 b: Src::Const(48),
1237 }); let output = if i == 0 {
1240 Src::Const(1)
1241 } else {
1242 let klt = cells.len();
1243 cells.push(LoopCell::Select {
1244 cmp: CmpKind::Lt,
1245 ca: m,
1246 cb: Src::Const(p10),
1247 t: Src::Const(1),
1248 e: Src::Const(0),
1249 });
1250 let kge = cells.len();
1251 cells.push(LoopCell::Select {
1252 cmp: CmpKind::Eq,
1253 ca: Src::Val(klt),
1254 cb: Src::Const(0),
1255 t: Src::Const(1),
1256 e: Src::Const(0),
1257 });
1258 Src::Val(kge)
1259 };
1260 let kwaddr = cells.len(); cells.push(LoopCell::Arith {
1262 op: AluOp::Add,
1263 a: ptr,
1264 b: write_pos,
1265 });
1266 cells.push(LoopCell::HeapWrite {
1267 addr: Src::Val(kwaddr),
1268 val: Src::Val(kchar),
1269 });
1270 let kwp1 = cells.len();
1271 cells.push(LoopCell::Arith {
1272 op: AluOp::Add,
1273 a: write_pos,
1274 b: Src::Const(1),
1275 });
1276 let kwpn = cells.len(); cells.push(LoopCell::Select {
1278 cmp: CmpKind::Ne,
1279 ca: output,
1280 cb: Src::Const(0),
1281 t: Src::Val(kwp1),
1282 e: write_pos,
1283 });
1284 write_pos = Src::Val(kwpn);
1285 p10 /= 10;
1286 }
1287 let klen = cells.len(); cells.push(LoopCell::Arith {
1289 op: AluOp::Sub,
1290 a: write_pos,
1291 b: Src::Const(1),
1292 });
1293 cells.push(LoopCell::HeapWrite {
1294 addr: ptr,
1295 val: Src::Val(klen),
1296 });
1297 ptr
1298}
1299
1300fn carried_regs(body: &[(usize, crate::vm::Op)]) -> Vec<u16> {
1303 use crate::vm::Op;
1304 let mut carried: Vec<u16> = Vec::new();
1305 for (_, op) in body {
1306 let dst = match op {
1307 Op::LoadConst { dst, .. }
1308 | Op::Move { dst, .. }
1309 | Op::Add { dst, .. }
1310 | Op::Sub { dst, .. }
1311 | Op::Mul { dst, .. }
1312 | Op::Pow { dst, .. }
1313 | Op::BitAnd { dst, .. }
1314 | Op::BitOr { dst, .. }
1315 | Op::BitXor { dst, .. }
1316 | Op::Shl { dst, .. }
1317 | Op::Shr { dst, .. }
1318 | Op::Length { dst, .. }
1319 | Op::Index { dst, .. }
1320 | Op::IndexUnchecked { dst, .. }
1321 | Op::NewList { dst, .. }
1322 | Op::NewTuple { dst, .. }
1323 | Op::NewEmptyList { dst, .. }
1324 | Op::NewEmptyListWithCapacity { dst, .. }
1325 | Op::NewEmptyListFixed { dst, .. }
1326 | Op::NewEmptyListI32 { dst, .. }
1327 | Op::NewEmptySet { dst, .. }
1328 | Op::NewEmptyMap { dst, .. }
1329 | Op::NewEmptyMapWithCapacity { dst, .. }
1330 | Op::ListPop { dst, .. }
1331 | Op::NewStruct { dst, .. }
1332 | Op::GetField { dst, .. }
1333 | Op::NewInductive { dst, .. }
1334 | Op::BindArm { dst, .. }
1335 | Op::Not { dst, .. }
1336 | Op::Call { dst, .. }
1337 | Op::ChanNew { dst, .. }
1338 | Op::ChanRecv { dst, .. }
1339 | Op::ChanTrySend { dst, .. }
1340 | Op::Contains { dst, .. }
1341 | Op::DeepClone { dst, .. }
1342 | Op::SeqConcat { dst, .. }
1343 | Op::SliceOp { dst, .. }
1344 | Op::UnionOp { dst, .. }
1345 | Op::IntersectOp { dst, .. }
1346 | Op::Concat { dst, .. }
1347 | Op::AddAssign { dst, .. } => Some(*dst),
1348 _ => None,
1349 };
1350 if let Some(d) = dst {
1351 if !carried.contains(&d) {
1352 carried.push(d);
1353 }
1354 }
1355 }
1356 carried
1357}
1358
1359fn inline_callee(
1365 func_idx: usize,
1366 arg_srcs: &[Src],
1367 cells: &mut Vec<LoopCell>,
1368 consts: &[crate::vm::Constant],
1369) -> Src {
1370 use crate::vm::Op;
1371 let (entry_pc, param_count) = INLINE_FNS.with(|f| {
1372 *f.borrow()
1373 .get(func_idx)
1374 .unwrap_or_else(|| panic!("fabric inline: no function #{func_idx}"))
1375 });
1376 let depth = INLINE_DEPTH.with(|d| {
1377 let mut d = d.borrow_mut();
1378 *d += 1;
1379 *d
1380 });
1381 assert!(depth < 64, "fabric inline: call graph too deep or recursive — a spatial call STACK is needed, not inlining");
1382
1383 let code = INLINE_CODE.with(|c| c.borrow().clone());
1384 let mut body: Vec<(usize, Op)> = Vec::new();
1385 let mut ret_src: Option<u16> = None;
1386 for (off, op) in code[entry_pc..].iter().enumerate() {
1387 if let Op::Return { src } = op {
1388 ret_src = Some(*src);
1389 break;
1390 }
1391 if matches!(op, Op::ReturnNothing) {
1392 break;
1393 }
1394 body.push((entry_pc + off, *op));
1395 }
1396 let ret_src = ret_src.expect("fabric inline: callee does not Return a value");
1397 let carried = carried_regs(&body);
1398
1399 let mut reg: std::collections::HashMap<u16, Src> = std::collections::HashMap::new();
1400 for p in 0..param_count {
1401 reg.insert(p, arg_srcs[p as usize]); }
1403 let mut preds: std::collections::HashMap<u16, (CmpKind, Src, Src)> =
1404 std::collections::HashMap::new();
1405 dec_convert(&body, &mut reg, cells, &mut preds, &carried, consts);
1406 let result = *reg
1407 .get(&ret_src)
1408 .unwrap_or_else(|| panic!("fabric inline: callee return register R{ret_src} is unwritten"));
1409
1410 INLINE_DEPTH.with(|d| *d.borrow_mut() -= 1);
1411 result
1412}
1413
1414fn inline_spawn(
1421 func_idx: usize,
1422 arg_srcs: &[Src],
1423 cells: &mut Vec<LoopCell>,
1424 consts: &[crate::vm::Constant],
1425) {
1426 use crate::vm::Op;
1427 let (entry_pc, param_count) = INLINE_FNS.with(|f| {
1428 *f.borrow()
1429 .get(func_idx)
1430 .unwrap_or_else(|| panic!("fabric spawn: no function #{func_idx}"))
1431 });
1432 let depth = INLINE_DEPTH.with(|d| {
1433 let mut d = d.borrow_mut();
1434 *d += 1;
1435 *d
1436 });
1437 assert!(depth < 64, "fabric spawn: call graph too deep or recursive");
1438
1439 let code = INLINE_CODE.with(|c| c.borrow().clone());
1440 let mut body: Vec<(usize, Op)> = Vec::new();
1441 for (off, op) in code[entry_pc..].iter().enumerate() {
1442 if matches!(op, Op::Return { .. } | Op::ReturnNothing) {
1443 break;
1444 }
1445 body.push((entry_pc + off, *op));
1446 }
1447 let carried = carried_regs(&body);
1448 let mut reg: std::collections::HashMap<u16, Src> = std::collections::HashMap::new();
1449 for p in 0..param_count {
1450 reg.insert(p, arg_srcs[p as usize]); }
1452 let mut preds: std::collections::HashMap<u16, (CmpKind, Src, Src)> =
1453 std::collections::HashMap::new();
1454 dec_convert(&body, &mut reg, cells, &mut preds, &carried, consts);
1455 INLINE_DEPTH.with(|d| *d.borrow_mut() -= 1);
1456}
1457
1458fn inline_closure(
1463 func_idx: usize,
1464 arg_srcs: &[Src],
1465 captures: &[(Src, bool)],
1466 cells: &mut Vec<LoopCell>,
1467 consts: &[crate::vm::Constant],
1468) -> Src {
1469 use crate::vm::Op;
1470 let (entry_pc, param_count) = INLINE_FNS.with(|f| {
1471 *f.borrow()
1472 .get(func_idx)
1473 .unwrap_or_else(|| panic!("fabric closure: no function #{func_idx}"))
1474 });
1475 let depth = INLINE_DEPTH.with(|d| {
1476 let mut d = d.borrow_mut();
1477 *d += 1;
1478 *d
1479 });
1480 assert!(
1481 depth < 64,
1482 "fabric closure: call graph too deep or recursive"
1483 );
1484
1485 let code = INLINE_CODE.with(|c| c.borrow().clone());
1486 let mut body: Vec<(usize, Op)> = Vec::new();
1487 let mut ret_src: Option<u16> = None;
1488 for (off, op) in code[entry_pc..].iter().enumerate() {
1489 if let Op::Return { src } = op {
1490 ret_src = Some(*src);
1491 break;
1492 }
1493 if matches!(op, Op::ReturnNothing) {
1494 break;
1495 }
1496 body.push((entry_pc + off, *op));
1497 }
1498 let ret_src = ret_src.expect("fabric closure: body does not Return a value");
1499 let carried = carried_regs(&body);
1500
1501 let mut reg: std::collections::HashMap<u16, Src> = std::collections::HashMap::new();
1502 for p in 0..param_count {
1503 reg.insert(p, arg_srcs[p as usize]); }
1505 let cap_count = captures.len() as u16;
1506 for (k, (val, present)) in captures.iter().enumerate() {
1507 let k = k as u16;
1508 reg.insert(param_count + k, *val); reg.insert(param_count + cap_count + k, Src::Const(i64::from(*present)));
1510 }
1512 let mut preds: std::collections::HashMap<u16, (CmpKind, Src, Src)> =
1513 std::collections::HashMap::new();
1514 dec_convert(&body, &mut reg, cells, &mut preds, &carried, consts);
1515 let result = *reg
1516 .get(&ret_src)
1517 .unwrap_or_else(|| panic!("fabric closure: return register R{ret_src} is unwritten"));
1518 INLINE_DEPTH.with(|d| *d.borrow_mut() -= 1);
1519 result
1520}
1521
1522fn setup_program_thread_locals(program: &crate::vm::CompiledProgram) {
1528 use std::collections::HashMap;
1529 STRUCT_FIELDS.with(|m| {
1531 let mut m = m.borrow_mut();
1532 m.clear();
1533 for (i, c) in program.constants.iter().enumerate() {
1534 if let crate::vm::Constant::Text(name) = c {
1535 if let Some(sd) = program.struct_types.iter().find(|s| &s.name == name) {
1536 m.insert(i as u32, sd.fields.len());
1537 }
1538 }
1539 }
1540 });
1541 STRUCT_FIELD_POS.with(|m| {
1543 let mut m = m.borrow_mut();
1544 m.clear();
1545 for sd in &program.struct_types {
1546 let name_pos: HashMap<&str, usize> = sd
1547 .fields
1548 .iter()
1549 .enumerate()
1550 .map(|(pos, (fname, _))| (fname.as_str(), pos))
1551 .collect();
1552 for (t, ct) in program.constants.iter().enumerate() {
1553 if !matches!(ct, crate::vm::Constant::Text(n) if *n == sd.name) {
1554 continue;
1555 }
1556 for (f, cf) in program.constants.iter().enumerate() {
1557 if let crate::vm::Constant::Text(fname) = cf {
1558 if let Some(&pos) = name_pos.get(fname.as_str()) {
1559 m.insert((t as u32, f as u32), pos);
1560 }
1561 }
1562 }
1563 }
1564 }
1565 if std::env::var("FABRIC_DUMP").is_ok() {
1566 let st: Vec<(String, Vec<String>)> = program
1567 .struct_types
1568 .iter()
1569 .map(|s| {
1570 (
1571 s.name.clone(),
1572 s.fields.iter().map(|(f, _)| f.clone()).collect(),
1573 )
1574 })
1575 .collect();
1576 let tc: Vec<(usize, String)> = program
1577 .constants
1578 .iter()
1579 .enumerate()
1580 .filter_map(|(i, c)| match c {
1581 crate::vm::Constant::Text(t) => Some((i, t.clone())),
1582 _ => None,
1583 })
1584 .collect();
1585 eprintln!("STRUCTDUMP struct_types={st:?}");
1586 eprintln!("STRUCTDUMP text_consts={tc:?}");
1587 eprintln!("STRUCTDUMP field_pos={:?}", m.iter().collect::<Vec<_>>());
1588 }
1589 });
1590 REG_TYPE.with(|m| m.borrow_mut().clear());
1591 INLINE_CODE.with(|c| *c.borrow_mut() = program.code.clone());
1592 INLINE_FNS.with(|m| {
1593 *m.borrow_mut() = program
1594 .functions
1595 .iter()
1596 .map(|fun| (fun.entry_pc, fun.param_count))
1597 .collect();
1598 });
1599 CLOSURE_CAPTURES.with(|m| {
1600 *m.borrow_mut() = program
1601 .functions
1602 .iter()
1603 .map(|fun| fun.captures.iter().map(|(_, gidx)| *gidx).collect())
1604 .collect();
1605 });
1606 CLOSURES.with(|c| c.borrow_mut().clear());
1607 INLINE_DEPTH.with(|d| *d.borrow_mut() = 0);
1608 COLL_LEN.with(|m| m.borrow_mut().clear());
1609 SHOW_OUTPUTS.with(|o| o.borrow_mut().clear());
1610 TEXT_REGS.with(|t| t.borrow_mut().clear());
1611 NAME_REGS.with(|t| t.borrow_mut().clear());
1612 FLOAT_REGS.with(|t| t.borrow_mut().clear());
1613 W32_REGS.with(|t| t.borrow_mut().clear());
1614 BIGINT_BIG.with(|m| m.borrow_mut().clear());
1615 GLOBALS.with(|g| g.borrow_mut().clear());
1616 NET_INBOX.with(|n| *n.borrow_mut() = None);
1617 SELECT_ARMS.with(|a| a.borrow_mut().clear());
1618 HALTED.with(|h| h.set(false));
1619 POLICY_CONDITIONS.with(|m| m.borrow_mut().clear());
1622 POLICY_GUARD.with(|g| *g.borrow_mut() = None);
1623 REG_TIMEKIND.with(|m| m.borrow_mut().clear());
1624}
1625
1626fn logical_shr_const(cells: &mut Vec<LoopCell>, src: Src, s: i64) -> Src {
1631 if s == 0 {
1632 return src;
1633 }
1634 let kshr = cells.len();
1635 cells.push(LoopCell::Arith {
1636 op: AluOp::Shr,
1637 a: src,
1638 b: Src::Const(s),
1639 });
1640 let mask = (((1u128 << (64 - s)) - 1) as u64) as i64; let kand = cells.len();
1642 cells.push(LoopCell::Arith {
1643 op: AluOp::And,
1644 a: Src::Val(kshr),
1645 b: Src::Const(mask),
1646 });
1647 Src::Val(kand)
1648}
1649
1650fn t_arith(cells: &mut Vec<LoopCell>, op: AluOp, a: Src, b: Src) -> Src {
1652 let k = cells.len();
1653 cells.push(LoopCell::Arith { op, a, b });
1654 Src::Val(k)
1655}
1656fn t_func(cells: &mut Vec<LoopCell>, unit: FuncUnit, a: Src, b: Src) -> Src {
1658 let k = cells.len();
1659 cells.push(LoopCell::Func { unit, a, b });
1660 Src::Val(k)
1661}
1662fn t_select(cells: &mut Vec<LoopCell>, cmp: CmpKind, ca: Src, cb: Src, t: Src, e: Src) -> Src {
1664 let k = cells.len();
1665 cells.push(LoopCell::Select { cmp, ca, cb, t, e });
1666 Src::Val(k)
1667}
1668
1669fn civil_from_days_tiles(cells: &mut Vec<LoopCell>, days: Src) -> (Src, Src, Src) {
1674 let c = Src::Const;
1675 let z = t_arith(cells, AluOp::Add, days, c(719468));
1676 let z_m = t_arith(cells, AluOp::Sub, z, c(146096));
1677 let era_num = t_select(cells, CmpKind::Ge, z, c(0), z, z_m); let era = t_func(cells, FuncUnit::DivS, era_num, c(146097));
1679 let era146097 = t_arith(cells, AluOp::Mul, era, c(146097));
1680 let doe = t_arith(cells, AluOp::Sub, z, era146097); let doe1460 = t_func(cells, FuncUnit::DivS, doe, c(1460));
1682 let doe36524 = t_func(cells, FuncUnit::DivS, doe, c(36524));
1683 let doe146096 = t_func(cells, FuncUnit::DivS, doe, c(146096));
1684 let yn1 = t_arith(cells, AluOp::Sub, doe, doe1460);
1685 let yn2 = t_arith(cells, AluOp::Add, yn1, doe36524);
1686 let yn3 = t_arith(cells, AluOp::Sub, yn2, doe146096);
1687 let yoe = t_func(cells, FuncUnit::DivS, yn3, c(365)); let era400 = t_arith(cells, AluOp::Mul, era, c(400));
1689 let y = t_arith(cells, AluOp::Add, yoe, era400);
1690 let yoe4 = t_func(cells, FuncUnit::DivS, yoe, c(4));
1691 let yoe100 = t_func(cells, FuncUnit::DivS, yoe, c(100));
1692 let t365 = t_arith(cells, AluOp::Mul, yoe, c(365));
1693 let t2 = t_arith(cells, AluOp::Add, t365, yoe4);
1694 let t3 = t_arith(cells, AluOp::Sub, t2, yoe100);
1695 let doy = t_arith(cells, AluOp::Sub, doe, t3); let mpn1 = t_arith(cells, AluOp::Mul, doy, c(5));
1697 let mpn2 = t_arith(cells, AluOp::Add, mpn1, c(2));
1698 let mp = t_func(cells, FuncUnit::DivS, mpn2, c(153)); let d153 = t_arith(cells, AluOp::Mul, mp, c(153));
1700 let d1532 = t_arith(cells, AluOp::Add, d153, c(2));
1701 let d5 = t_func(cells, FuncUnit::DivS, d1532, c(5));
1702 let dd = t_arith(cells, AluOp::Sub, doy, d5);
1703 let day = t_arith(cells, AluOp::Add, dd, c(1)); let mp3 = t_arith(cells, AluOp::Add, mp, c(3));
1705 let mp9 = t_arith(cells, AluOp::Sub, mp, c(9));
1706 let month = t_select(cells, CmpKind::Lt, mp, c(10), mp3, mp9); let y1 = t_arith(cells, AluOp::Add, y, c(1));
1708 let year = t_select(cells, CmpKind::Le, month, c(2), y1, y); (year, month, day)
1710}
1711
1712fn days_from_civil_tiles(cells: &mut Vec<LoopCell>, year: Src, month: Src, day: Src) -> Src {
1715 let c = Src::Const;
1716 let ym1 = t_arith(cells, AluOp::Sub, year, c(1));
1717 let y = t_select(cells, CmpKind::Le, month, c(2), ym1, year); let ym399 = t_arith(cells, AluOp::Sub, y, c(399));
1719 let era_num = t_select(cells, CmpKind::Ge, y, c(0), y, ym399); let era = t_func(cells, FuncUnit::DivS, era_num, c(400));
1721 let era400 = t_arith(cells, AluOp::Mul, era, c(400));
1722 let yoe = t_arith(cells, AluOp::Sub, y, era400); let mm3 = t_arith(cells, AluOp::Sub, month, c(3));
1724 let mp9 = t_arith(cells, AluOp::Add, month, c(9));
1725 let mp = t_select(cells, CmpKind::Gt, month, c(2), mm3, mp9); let d153 = t_arith(cells, AluOp::Mul, mp, c(153));
1727 let d1532 = t_arith(cells, AluOp::Add, d153, c(2));
1728 let d5 = t_func(cells, FuncUnit::DivS, d1532, c(5));
1729 let dd = t_arith(cells, AluOp::Add, d5, day);
1730 let doy = t_arith(cells, AluOp::Sub, dd, c(1)); let yoe365 = t_arith(cells, AluOp::Mul, yoe, c(365));
1732 let yoe4 = t_func(cells, FuncUnit::DivS, yoe, c(4));
1733 let yoe100 = t_func(cells, FuncUnit::DivS, yoe, c(100));
1734 let doe1 = t_arith(cells, AluOp::Add, yoe365, yoe4);
1735 let doe2 = t_arith(cells, AluOp::Sub, doe1, yoe100);
1736 let doe = t_arith(cells, AluOp::Add, doe2, doy); let e146097 = t_arith(cells, AluOp::Mul, era, c(146097));
1738 let d1 = t_arith(cells, AluOp::Add, e146097, doe);
1739 t_arith(cells, AluOp::Sub, d1, c(719468))
1740}
1741
1742fn iso_weekday_from_days_tiles(cells: &mut Vec<LoopCell>, z: Src) -> Src {
1746 let z3 = t_arith(cells, AluOp::Add, z, Src::Const(3));
1747 let m = t_func(cells, FuncUnit::ModS, z3, Src::Const(7));
1748 t_arith(cells, AluOp::Add, m, Src::Const(1))
1749}
1750
1751fn forget_reg(r: u16) {
1754 TEXT_REGS.with(|t| t.borrow_mut().remove(&r));
1755 NAME_REGS.with(|t| t.borrow_mut().remove(&r));
1756 FLOAT_REGS.with(|t| t.borrow_mut().remove(&r));
1757 FLOAT_COLLS.with(|t| t.borrow_mut().remove(&r));
1758 COLL_LEN.with(|t| t.borrow_mut().remove(&r));
1759 BIGINT_BIG.with(|t| t.borrow_mut().remove(&r));
1760 REG_TYPE.with(|t| t.borrow_mut().remove(&r));
1761 REG_TIMEKIND.with(|t| t.borrow_mut().remove(&r));
1762 CLOSURES.with(|t| t.borrow_mut().remove(&r));
1763}
1764
1765fn forget_overwritten(op: &crate::vm::Op, captures: impl Fn(u16) -> usize) {
1770 let mut read = Vec::new();
1771 op.for_each_read_reg(captures, |r| read.push(r));
1772 op.for_each_written_reg(|r| {
1773 if !read.contains(&r) {
1774 forget_reg(r);
1775 }
1776 });
1777}
1778
1779fn heap_text_literal(cells: &mut Vec<LoopCell>, bytes: &[u8], extra: i64) -> Src {
1782 let n = bytes.len();
1783 let ka = cells.len();
1784 cells.push(LoopCell::HeapAlloc {
1785 size: Src::Const(n as i64 + 1 + extra),
1786 });
1787 let ptr = Src::Val(ka);
1788 cells.push(LoopCell::HeapWrite {
1789 addr: ptr,
1790 val: Src::Const(n as i64),
1791 });
1792 for (i, b) in bytes.iter().enumerate() {
1793 let kadd = cells.len();
1794 cells.push(LoopCell::Arith {
1795 op: AluOp::Add,
1796 a: ptr,
1797 b: Src::Const(i as i64 + 1),
1798 });
1799 cells.push(LoopCell::HeapWrite {
1800 addr: Src::Val(kadd),
1801 val: Src::Const(*b as i64),
1802 });
1803 }
1804 ptr
1805}
1806
1807fn heap_argv(cells: &mut Vec<LoopCell>, extra: i64) -> Src {
1811 let name = heap_text_literal(cells, crate::semantics::PROGRAM_NAME.as_bytes(), 0);
1812 let ka = cells.len();
1813 cells.push(LoopCell::HeapAlloc {
1814 size: Src::Const(2 + extra),
1815 });
1816 let ptr = Src::Val(ka);
1817 cells.push(LoopCell::HeapWrite {
1818 addr: ptr,
1819 val: Src::Const(1),
1820 });
1821 let kadd = cells.len();
1822 cells.push(LoopCell::Arith {
1823 op: AluOp::Add,
1824 a: ptr,
1825 b: Src::Const(1),
1826 });
1827 cells.push(LoopCell::HeapWrite {
1828 addr: Src::Val(kadd),
1829 val: name,
1830 });
1831 ptr
1832}
1833
1834fn exec_ops(
1838 ops: &[crate::vm::Op],
1839 reg: &mut std::collections::HashMap<u16, Src>,
1840 cells: &mut Vec<LoopCell>,
1841 preds: &mut std::collections::HashMap<u16, (CmpKind, Src, Src)>,
1842 consts: &[crate::vm::Constant],
1843) {
1844 use crate::vm::{Constant, Op};
1845 let getb = |reg: &std::collections::HashMap<u16, Src>, r: u16| -> Src {
1846 *reg.get(&r)
1847 .unwrap_or_else(|| panic!("loop body reads unwritten register R{r}"))
1848 };
1849 for op in ops {
1850 forget_overwritten(op, |_| usize::MAX);
1851 let cmp = match op {
1852 Op::Lt { .. } => Some(CmpKind::Lt),
1853 Op::Gt { .. } => Some(CmpKind::Gt),
1854 Op::LtEq { .. } => Some(CmpKind::Le),
1855 Op::GtEq { .. } => Some(CmpKind::Ge),
1856 Op::Eq { .. } => Some(CmpKind::Eq),
1857 Op::NotEq { .. } => Some(CmpKind::Ne),
1858 _ => None,
1859 };
1860 if let Some(kind) = cmp {
1861 if let Op::Lt { dst, lhs, rhs }
1862 | Op::Gt { dst, lhs, rhs }
1863 | Op::LtEq { dst, lhs, rhs }
1864 | Op::GtEq { dst, lhs, rhs }
1865 | Op::Eq { dst, lhs, rhs }
1866 | Op::NotEq { dst, lhs, rhs } = op
1867 {
1868 preds.insert(*dst, (kind, getb(reg, *lhs), getb(reg, *rhs)));
1869 }
1870 continue;
1871 }
1872 match op {
1873 Op::LoadConst { dst, idx: ci } => match &consts[*ci as usize] {
1874 Constant::Int(n) => {
1875 reg.insert(*dst, Src::Const(*n));
1876 }
1877 Constant::Bool(b) => {
1878 reg.insert(*dst, Src::Const(i64::from(*b))); }
1880 Constant::Text(s) => {
1881 let ptr = heap_text_literal(cells, s.as_bytes(), 0);
1884 reg.insert(*dst, ptr);
1885 TEXT_REGS.with(|t| {
1886 t.borrow_mut().insert(*dst);
1887 });
1888 NAME_REGS.with(|t| {
1889 t.borrow_mut().insert(*dst, *ci);
1890 });
1891 COLL_LEN.with(|m| {
1892 m.borrow_mut().insert(*dst, s.len() as i64);
1893 });
1894 }
1895 Constant::Duration(n) => {
1898 reg.insert(*dst, Src::Const(*n));
1899 }
1900 Constant::Float(f) => {
1903 reg.insert(*dst, Src::Const(f.to_bits() as i64));
1904 FLOAT_REGS.with(|t| {
1905 t.borrow_mut().insert(*dst);
1906 });
1907 }
1908 Constant::Nothing => {
1911 reg.insert(*dst, Src::Const(0));
1912 }
1913 c => panic!("loop body: unsupported constant {c:?}"),
1914 },
1915 Op::LoadNow { dst } => {
1922 let (_, nanos) = crate::semantics::temporal::fixed_clock().expect(
1923 "fabric LoadNow: the combinational fabric cannot read the wall clock — inject a fixed \
1924 clock (temporal::set_fixed_clock) so the VM and the fabric read the same instant",
1925 );
1926 reg.insert(*dst, Src::Const(nanos));
1927 REG_TIMEKIND.with(|m| m.borrow_mut().insert(*dst, TimeKind::Moment));
1928 }
1929 Op::LoadToday { dst } => {
1930 let (days, _) = crate::semantics::temporal::fixed_clock().expect(
1931 "fabric LoadToday: the combinational fabric cannot read the wall clock — inject a fixed \
1932 clock (temporal::set_fixed_clock) so the VM and the fabric read the same instant",
1933 );
1934 reg.insert(*dst, Src::Const(i64::from(days)));
1935 REG_TIMEKIND.with(|m| m.borrow_mut().insert(*dst, TimeKind::Date));
1936 }
1937 Op::NewCrdt { dst, kind } => match kind {
1944 4 | 5 => {
1946 reg.insert(*dst, Src::Const(0));
1947 }
1948 _ => {
1952 let ka = cells.len();
1953 cells.push(LoopCell::HeapAlloc {
1954 size: Src::Const(1),
1955 });
1956 let ptr = Src::Val(ka);
1957 cells.push(LoopCell::HeapWrite {
1958 addr: ptr,
1959 val: Src::Const(0),
1960 });
1961 reg.insert(*dst, ptr);
1962 }
1963 },
1964 Op::Move { dst, src } => {
1965 let s = getb(reg, *src);
1966 reg.insert(*dst, s);
1967 NAME_REGS.with(|m| {
1968 let mut m = m.borrow_mut();
1969 if let Some(n) = m.get(src).copied() {
1970 m.insert(*dst, n);
1971 }
1972 });
1973 REG_TYPE.with(|m| {
1975 let mut m = m.borrow_mut();
1976 if let Some(t) = m.get(src).copied() {
1977 m.insert(*dst, t);
1978 }
1979 });
1980 COLL_LEN.with(|m| {
1981 let mut m = m.borrow_mut();
1982 if let Some(l) = m.get(src).copied() {
1983 m.insert(*dst, l);
1984 }
1985 });
1986 TEXT_REGS.with(|t| {
1987 let mut t = t.borrow_mut();
1988 if t.contains(src) {
1989 t.insert(*dst);
1990 }
1991 });
1992 FLOAT_REGS.with(|t| {
1993 let mut t = t.borrow_mut();
1994 if t.contains(src) {
1995 t.insert(*dst);
1996 }
1997 });
1998 W32_REGS.with(|t| {
2001 let mut t = t.borrow_mut();
2002 if t.contains(src) {
2003 t.insert(*dst);
2004 } else {
2005 t.remove(dst);
2006 }
2007 });
2008 REG_TIMEKIND.with(|m| {
2009 let mut m = m.borrow_mut();
2010 if let Some(k) = m.get(src).copied() {
2011 m.insert(*dst, k);
2012 }
2013 });
2014 }
2015 Op::RevSwap { a, b } => {
2018 let sa = getb(reg, *a);
2019 let sb = getb(reg, *b);
2020 reg.insert(*a, sb);
2021 reg.insert(*b, sa);
2022 COLL_LEN.with(|m| {
2023 let mut m = m.borrow_mut();
2024 let (va, vb) = (m.remove(a), m.remove(b));
2025 if let Some(v) = vb {
2026 m.insert(*a, v);
2027 }
2028 if let Some(v) = va {
2029 m.insert(*b, v);
2030 }
2031 });
2032 REG_TYPE.with(|m| {
2033 let mut m = m.borrow_mut();
2034 let (va, vb) = (m.remove(a), m.remove(b));
2035 if let Some(v) = vb {
2036 m.insert(*a, v);
2037 }
2038 if let Some(v) = va {
2039 m.insert(*b, v);
2040 }
2041 });
2042 TEXT_REGS.with(|t| {
2043 let mut t = t.borrow_mut();
2044 let (ha, hb) = (t.remove(a), t.remove(b));
2045 if hb {
2046 t.insert(*a);
2047 }
2048 if ha {
2049 t.insert(*b);
2050 }
2051 });
2052 }
2053 Op::RevXor { place, delta } => {
2058 let k = cells.len();
2059 cells.push(LoopCell::Arith {
2060 op: AluOp::Xor,
2061 a: getb(reg, *place),
2062 b: getb(reg, *delta),
2063 });
2064 reg.insert(*place, Src::Val(k));
2065 }
2066 Op::RevAdd { place, delta } => {
2067 let k = cells.len();
2068 cells.push(LoopCell::Arith {
2069 op: AluOp::Add,
2070 a: getb(reg, *place),
2071 b: getb(reg, *delta),
2072 });
2073 reg.insert(*place, Src::Val(k));
2074 }
2075 Op::RevSub { place, delta } => {
2076 let k = cells.len();
2077 cells.push(LoopCell::Arith {
2078 op: AluOp::Sub,
2079 a: getb(reg, *place),
2080 b: getb(reg, *delta),
2081 });
2082 reg.insert(*place, Src::Val(k));
2083 }
2084 Op::RevNeg { place } => {
2085 let k = cells.len();
2086 cells.push(LoopCell::Arith {
2087 op: AluOp::Sub,
2088 a: Src::Const(0),
2089 b: getb(reg, *place),
2090 });
2091 reg.insert(*place, Src::Val(k));
2092 }
2093 Op::Add { dst, lhs, rhs } => {
2094 let float =
2095 FLOAT_REGS.with(|t| t.borrow().contains(lhs) || t.borrow().contains(rhs));
2096 let w32 = W32_REGS.with(|t| t.borrow().contains(lhs) || t.borrow().contains(rhs));
2097 let (a, b) = (getb(reg, *lhs), getb(reg, *rhs));
2098 let k = cells.len();
2099 if float {
2100 cells.push(LoopCell::Func {
2101 unit: FuncUnit::FAddF,
2102 a,
2103 b,
2104 });
2105 FLOAT_REGS.with(|t| {
2106 t.borrow_mut().insert(*dst);
2107 });
2108 W32_REGS.with(|t| {
2109 t.borrow_mut().remove(dst);
2110 });
2111 reg.insert(*dst, Src::Val(k));
2112 } else if w32 {
2113 cells.push(LoopCell::Arith {
2115 op: AluOp::Add,
2116 a,
2117 b,
2118 });
2119 let km = cells.len();
2120 cells.push(LoopCell::Arith {
2121 op: AluOp::And,
2122 a: Src::Val(k),
2123 b: Src::Const(0xFFFF_FFFF),
2124 });
2125 reg.insert(*dst, Src::Val(km));
2126 W32_REGS.with(|t| {
2127 t.borrow_mut().insert(*dst);
2128 });
2129 } else {
2130 cells.push(LoopCell::Arith {
2131 op: AluOp::Add,
2132 a,
2133 b,
2134 });
2135 reg.insert(*dst, Src::Val(k));
2136 W32_REGS.with(|t| {
2137 t.borrow_mut().remove(dst);
2138 });
2139 FLOAT_REGS.with(|t| {
2140 t.borrow_mut().remove(dst);
2141 });
2142 }
2143 }
2144 Op::AddAssign { dst, src } => {
2149 let float =
2150 FLOAT_REGS.with(|t| t.borrow().contains(dst) || t.borrow().contains(src));
2151 let (a, b) = (getb(reg, *dst), getb(reg, *src));
2152 let k = cells.len();
2153 if float {
2154 cells.push(LoopCell::Func {
2155 unit: FuncUnit::FAddF,
2156 a,
2157 b,
2158 });
2159 FLOAT_REGS.with(|t| {
2160 t.borrow_mut().insert(*dst);
2161 });
2162 } else {
2163 cells.push(LoopCell::Arith {
2164 op: AluOp::Add,
2165 a,
2166 b,
2167 });
2168 }
2169 reg.insert(*dst, Src::Val(k));
2170 }
2171 Op::Not { dst, src } => {
2175 let k = cells.len();
2176 cells.push(LoopCell::Select {
2177 cmp: CmpKind::Eq,
2178 ca: getb(reg, *src),
2179 cb: Src::Const(0),
2180 t: Src::Const(1),
2181 e: Src::Const(0),
2182 });
2183 reg.insert(*dst, Src::Val(k));
2184 }
2185 Op::Sub { dst, lhs, rhs } => {
2186 let float =
2187 FLOAT_REGS.with(|t| t.borrow().contains(lhs) || t.borrow().contains(rhs));
2188 let w32 = W32_REGS.with(|t| t.borrow().contains(lhs) || t.borrow().contains(rhs));
2189 let (a, b) = (getb(reg, *lhs), getb(reg, *rhs));
2190 let k = cells.len();
2191 if float {
2192 cells.push(LoopCell::Func {
2193 unit: FuncUnit::FSubF,
2194 a,
2195 b,
2196 });
2197 FLOAT_REGS.with(|t| {
2198 t.borrow_mut().insert(*dst);
2199 });
2200 W32_REGS.with(|t| {
2201 t.borrow_mut().remove(dst);
2202 });
2203 reg.insert(*dst, Src::Val(k));
2204 } else if w32 {
2205 cells.push(LoopCell::Arith {
2206 op: AluOp::Sub,
2207 a,
2208 b,
2209 });
2210 let km = cells.len();
2211 cells.push(LoopCell::Arith {
2212 op: AluOp::And,
2213 a: Src::Val(k),
2214 b: Src::Const(0xFFFF_FFFF),
2215 });
2216 reg.insert(*dst, Src::Val(km));
2217 W32_REGS.with(|t| {
2218 t.borrow_mut().insert(*dst);
2219 });
2220 } else {
2221 cells.push(LoopCell::Arith {
2222 op: AluOp::Sub,
2223 a,
2224 b,
2225 });
2226 reg.insert(*dst, Src::Val(k));
2227 W32_REGS.with(|t| {
2228 t.borrow_mut().remove(dst);
2229 });
2230 FLOAT_REGS.with(|t| {
2231 t.borrow_mut().remove(dst);
2232 });
2233 }
2234 }
2235 Op::Mul { dst, lhs, rhs } => {
2236 let float =
2237 FLOAT_REGS.with(|t| t.borrow().contains(lhs) || t.borrow().contains(rhs));
2238 let w32 = W32_REGS.with(|t| t.borrow().contains(lhs) || t.borrow().contains(rhs));
2239 let (a, b) = (getb(reg, *lhs), getb(reg, *rhs));
2240 let k = cells.len();
2241 if float {
2242 cells.push(LoopCell::Func {
2243 unit: FuncUnit::FMulF,
2244 a,
2245 b,
2246 });
2247 FLOAT_REGS.with(|t| {
2248 t.borrow_mut().insert(*dst);
2249 });
2250 W32_REGS.with(|t| {
2251 t.borrow_mut().remove(dst);
2252 });
2253 reg.insert(*dst, Src::Val(k));
2254 } else if w32 {
2255 cells.push(LoopCell::Arith {
2256 op: AluOp::Mul,
2257 a,
2258 b,
2259 });
2260 let km = cells.len();
2261 cells.push(LoopCell::Arith {
2262 op: AluOp::And,
2263 a: Src::Val(k),
2264 b: Src::Const(0xFFFF_FFFF),
2265 });
2266 reg.insert(*dst, Src::Val(km));
2267 W32_REGS.with(|t| {
2268 t.borrow_mut().insert(*dst);
2269 });
2270 } else {
2271 cells.push(LoopCell::Arith {
2272 op: AluOp::Mul,
2273 a,
2274 b,
2275 });
2276 reg.insert(*dst, Src::Val(k));
2277 W32_REGS.with(|t| {
2278 t.borrow_mut().remove(dst);
2279 });
2280 FLOAT_REGS.with(|t| {
2281 t.borrow_mut().remove(dst);
2282 });
2283 }
2284 }
2285 Op::BitAnd { dst, lhs, rhs } => {
2287 let k = cells.len();
2288 cells.push(LoopCell::Arith {
2289 op: AluOp::And,
2290 a: getb(reg, *lhs),
2291 b: getb(reg, *rhs),
2292 });
2293 reg.insert(*dst, Src::Val(k));
2294 }
2295 Op::BitOr { dst, lhs, rhs } => {
2296 let k = cells.len();
2297 cells.push(LoopCell::Arith {
2298 op: AluOp::Or,
2299 a: getb(reg, *lhs),
2300 b: getb(reg, *rhs),
2301 });
2302 reg.insert(*dst, Src::Val(k));
2303 }
2304 Op::BitXor { dst, lhs, rhs } => {
2305 let k = cells.len();
2306 cells.push(LoopCell::Arith {
2307 op: AluOp::Xor,
2308 a: getb(reg, *lhs),
2309 b: getb(reg, *rhs),
2310 });
2311 reg.insert(*dst, Src::Val(k));
2312 }
2313 Op::Shl { dst, lhs, rhs } => {
2314 let k = cells.len();
2315 cells.push(LoopCell::Arith {
2316 op: AluOp::Shl,
2317 a: getb(reg, *lhs),
2318 b: getb(reg, *rhs),
2319 });
2320 reg.insert(*dst, Src::Val(k));
2321 }
2322 Op::Shr { dst, lhs, rhs } => {
2323 let k = cells.len();
2324 cells.push(LoopCell::Arith {
2325 op: AluOp::Shr,
2326 a: getb(reg, *lhs),
2327 b: getb(reg, *rhs),
2328 });
2329 reg.insert(*dst, Src::Val(k));
2330 }
2331 Op::Div { dst, lhs, rhs } => {
2337 let float =
2338 FLOAT_REGS.with(|t| t.borrow().contains(lhs) || t.borrow().contains(rhs));
2339 let (a, b) = (getb(reg, *lhs), getb(reg, *rhs));
2340 let k = cells.len();
2341 if float {
2342 cells.push(LoopCell::Func {
2343 unit: FuncUnit::FDivF,
2344 a,
2345 b,
2346 });
2347 FLOAT_REGS.with(|t| {
2348 t.borrow_mut().insert(*dst);
2349 });
2350 } else {
2351 cells.push(LoopCell::Func {
2352 unit: FuncUnit::DivS,
2353 a,
2354 b,
2355 });
2356 FLOAT_REGS.with(|t| {
2359 t.borrow_mut().remove(dst);
2360 });
2361 }
2362 reg.insert(*dst, Src::Val(k));
2363 }
2364 Op::Mod { dst, lhs, rhs } => {
2365 let k = cells.len();
2366 cells.push(LoopCell::Func {
2367 unit: FuncUnit::ModS,
2368 a: getb(reg, *lhs),
2369 b: getb(reg, *rhs),
2370 });
2371 reg.insert(*dst, Src::Val(k));
2372 FLOAT_REGS.with(|t| {
2373 t.borrow_mut().remove(dst);
2374 }); }
2376 Op::FloorDiv { dst, lhs, rhs } => {
2377 let k = cells.len();
2378 cells.push(LoopCell::Func {
2379 unit: FuncUnit::FloorDivS,
2380 a: getb(reg, *lhs),
2381 b: getb(reg, *rhs),
2382 });
2383 reg.insert(*dst, Src::Val(k));
2384 FLOAT_REGS.with(|t| {
2385 t.borrow_mut().remove(dst);
2386 }); }
2388 Op::Pow { dst, lhs, rhs } => {
2390 let k = cells.len();
2391 cells.push(LoopCell::Func {
2392 unit: FuncUnit::Pow,
2393 a: getb(reg, *lhs),
2394 b: getb(reg, *rhs),
2395 });
2396 reg.insert(*dst, Src::Val(k));
2397 }
2398 Op::CallBuiltin {
2403 dst,
2404 builtin,
2405 args_start,
2406 arg_count: _,
2407 } => {
2408 use crate::semantics::builtins::BuiltinId;
2409 let a0 = getb(reg, *args_start);
2410 let is_f = FLOAT_REGS.with(|t| t.borrow().contains(args_start));
2411 match builtin {
2412 BuiltinId::Sqrt if is_f => {
2413 let k = cells.len();
2414 cells.push(LoopCell::Func { unit: FuncUnit::FSqrtF, a: a0, b: Src::Const(0) });
2415 reg.insert(*dst, Src::Val(k));
2416 FLOAT_REGS.with(|t| { t.borrow_mut().insert(*dst); });
2417 }
2418 BuiltinId::Floor if is_f => {
2419 let k = cells.len();
2420 cells.push(LoopCell::Func { unit: FuncUnit::FFloorF, a: a0, b: Src::Const(0) });
2421 reg.insert(*dst, Src::Val(k));
2422 FLOAT_REGS.with(|t| { t.borrow_mut().remove(dst); }); }
2424 BuiltinId::Floor => {
2425 reg.insert(*dst, a0); FLOAT_REGS.with(|t| { t.borrow_mut().remove(dst); });
2427 }
2428 BuiltinId::Ceil if is_f => {
2430 let kneg = cells.len();
2431 cells.push(LoopCell::Func { unit: FuncUnit::FSubF, a: Src::Const(0), b: a0 }); let kfl = cells.len();
2433 cells.push(LoopCell::Func { unit: FuncUnit::FFloorF, a: Src::Val(kneg), b: Src::Const(0) });
2434 let k = cells.len();
2435 cells.push(LoopCell::Arith { op: AluOp::Sub, a: Src::Const(0), b: Src::Val(kfl) }); reg.insert(*dst, Src::Val(k));
2437 FLOAT_REGS.with(|t| { t.borrow_mut().remove(dst); }); }
2439 BuiltinId::Ceil => {
2440 reg.insert(*dst, a0); FLOAT_REGS.with(|t| { t.borrow_mut().remove(dst); });
2442 }
2443 BuiltinId::Round if is_f => {
2448 let kabs = cells.len();
2449 cells.push(LoopCell::Arith { op: AluOp::And, a: a0, b: Src::Const(0x7FFF_FFFF_FFFF_FFFF) }); let ksum = cells.len();
2451 cells.push(LoopCell::Func { unit: FuncUnit::FAddF, a: Src::Val(kabs), b: Src::Const(0.5f64.to_bits() as i64) });
2452 let kfl = cells.len();
2453 cells.push(LoopCell::Func { unit: FuncUnit::FFloorF, a: Src::Val(ksum), b: Src::Const(0) }); let kneg = cells.len();
2455 cells.push(LoopCell::Arith { op: AluOp::Sub, a: Src::Const(0), b: Src::Val(kfl) }); let kpred = cells.len();
2457 cells.push(LoopCell::Func { unit: FuncUnit::FLtF, a: a0, b: Src::Const(0) }); let ksel = cells.len();
2459 cells.push(LoopCell::Select {
2460 cmp: CmpKind::Ne,
2461 ca: Src::Val(kpred),
2462 cb: Src::Const(0),
2463 t: Src::Val(kneg),
2464 e: Src::Val(kfl),
2465 });
2466 reg.insert(*dst, Src::Val(ksel));
2467 FLOAT_REGS.with(|t| { t.borrow_mut().remove(dst); }); }
2469 BuiltinId::Round => {
2470 reg.insert(*dst, a0); FLOAT_REGS.with(|t| { t.borrow_mut().remove(dst); });
2472 }
2473 BuiltinId::Abs if is_f => {
2474 let k = cells.len();
2475 cells.push(LoopCell::Arith { op: AluOp::And, a: a0, b: Src::Const(0x7FFF_FFFF_FFFF_FFFF) });
2476 reg.insert(*dst, Src::Val(k));
2477 FLOAT_REGS.with(|t| { t.borrow_mut().insert(*dst); });
2478 }
2479 BuiltinId::Abs => {
2480 let kneg = cells.len();
2481 cells.push(LoopCell::Arith { op: AluOp::Sub, a: Src::Const(0), b: a0 }); let ksel = cells.len();
2483 cells.push(LoopCell::Select { cmp: CmpKind::Lt, ca: a0, cb: Src::Const(0), t: Src::Val(kneg), e: a0 });
2484 reg.insert(*dst, Src::Val(ksel));
2485 FLOAT_REGS.with(|t| { t.borrow_mut().remove(dst); });
2486 }
2487 BuiltinId::Min | BuiltinId::Max => {
2488 let a1 = getb(reg, *args_start + 1);
2489 let b_f = FLOAT_REGS.with(|t| t.borrow().contains(&(*args_start + 1)));
2490 let is_min = matches!(builtin, BuiltinId::Min);
2491 if is_f || b_f {
2492 let unit = if is_min { FuncUnit::FLtF } else { FuncUnit::FGtF };
2497 let kpred = cells.len();
2498 cells.push(LoopCell::Func { unit, a: a0, b: a1 });
2499 let ksel = cells.len();
2500 cells.push(LoopCell::Select {
2501 cmp: CmpKind::Ne,
2502 ca: Src::Val(kpred),
2503 cb: Src::Const(0),
2504 t: a0,
2505 e: a1,
2506 });
2507 reg.insert(*dst, Src::Val(ksel));
2508 FLOAT_REGS.with(|t| { t.borrow_mut().insert(*dst); }); } else {
2510 let cmp = if is_min { CmpKind::Lt } else { CmpKind::Gt };
2511 let k = cells.len();
2512 cells.push(LoopCell::Select { cmp, ca: a0, cb: a1, t: a0, e: a1 });
2513 reg.insert(*dst, Src::Val(k));
2514 FLOAT_REGS.with(|t| { t.borrow_mut().remove(dst); });
2515 }
2516 }
2517 BuiltinId::Word32 => {
2520 let k = cells.len();
2521 cells.push(LoopCell::Arith { op: AluOp::And, a: a0, b: Src::Const(0xFFFF_FFFF) });
2522 reg.insert(*dst, Src::Val(k));
2523 }
2524 BuiltinId::IntOfWord32 => {
2525 reg.insert(*dst, a0); }
2527 BuiltinId::Wand | BuiltinId::Wor => {
2528 let a1 = getb(reg, *args_start + 1);
2529 let op = if matches!(builtin, BuiltinId::Wand) { AluOp::And } else { AluOp::Or };
2530 let k = cells.len();
2531 cells.push(LoopCell::Arith { op, a: a0, b: a1 });
2532 reg.insert(*dst, Src::Val(k));
2533 }
2534 BuiltinId::Wnot => {
2535 let kx = cells.len();
2536 cells.push(LoopCell::Arith { op: AluOp::Xor, a: a0, b: Src::Const(-1) }); let km = cells.len();
2538 cells.push(LoopCell::Arith { op: AluOp::And, a: Src::Val(kx), b: Src::Const(0xFFFF_FFFF) });
2539 reg.insert(*dst, Src::Val(km));
2540 }
2541 BuiltinId::Word32Shr => {
2542 let n = getb(reg, *args_start + 1);
2543 let k = cells.len();
2544 cells.push(LoopCell::Arith { op: AluOp::Shr, a: a0, b: n });
2545 reg.insert(*dst, Src::Val(k));
2546 }
2547 BuiltinId::Rotr | BuiltinId::Rotl => {
2548 let n = getb(reg, *args_start + 1);
2549 let (dir_a, dir_b) = if matches!(builtin, BuiltinId::Rotr) {
2551 (AluOp::Shr, AluOp::Shl)
2552 } else {
2553 (AluOp::Shl, AluOp::Shr)
2554 };
2555 let k1 = cells.len();
2556 cells.push(LoopCell::Arith { op: dir_a, a: a0, b: n });
2557 let kcomp = cells.len();
2558 cells.push(LoopCell::Arith { op: AluOp::Sub, a: Src::Const(32), b: n }); let k2 = cells.len();
2560 cells.push(LoopCell::Arith { op: dir_b, a: a0, b: Src::Val(kcomp) });
2561 let kor = cells.len();
2562 cells.push(LoopCell::Arith { op: AluOp::Or, a: Src::Val(k1), b: Src::Val(k2) });
2563 let km = cells.len();
2564 cells.push(LoopCell::Arith { op: AluOp::And, a: Src::Val(kor), b: Src::Const(0xFFFF_FFFF) });
2565 reg.insert(*dst, Src::Val(km));
2566 }
2567 BuiltinId::Word64 | BuiltinId::IntOfWord64 => {
2573 reg.insert(*dst, a0); }
2575 BuiltinId::Word64And => {
2576 let a1 = getb(reg, *args_start + 1);
2577 let k = cells.len();
2578 cells.push(LoopCell::Arith { op: AluOp::And, a: a0, b: a1 });
2579 reg.insert(*dst, Src::Val(k));
2580 }
2581 BuiltinId::Word64Shl => {
2582 let n = getb(reg, *args_start + 1);
2583 let k = cells.len();
2584 cells.push(LoopCell::Arith { op: AluOp::Shl, a: a0, b: n });
2585 reg.insert(*dst, Src::Val(k));
2586 }
2587 BuiltinId::Word64Shr => {
2588 let n = getb(reg, *args_start + 1);
2589 let k = cells.len();
2590 cells.push(LoopCell::Arith { op: AluOp::UShr, a: a0, b: n }); reg.insert(*dst, Src::Val(k));
2592 }
2593 BuiltinId::CountOnes => {
2595 let k = cells.len();
2596 cells.push(LoopCell::Func { unit: FuncUnit::PopCount, a: a0, b: Src::Const(0) });
2597 reg.insert(*dst, Src::Val(k));
2598 FLOAT_REGS.with(|t| { t.borrow_mut().remove(dst); }); }
2600 BuiltinId::YearOf
2607 | BuiltinId::MonthOf
2608 | BuiltinId::DayOf
2609 | BuiltinId::QuarterOf
2610 | BuiltinId::WeekOf
2611 | BuiltinId::HourOf
2612 | BuiltinId::MinuteOf
2613 | BuiltinId::SecondOf
2614 | BuiltinId::WeekdayOf => {
2615 use logicaffeine_base::temporal::{NANOS_PER_DAY, NANOS_PER_SECOND};
2616 let kind = REG_TIMEKIND
2617 .with(|m| m.borrow().get(args_start).copied())
2618 .unwrap_or_else(|| {
2619 panic!(
2620 "fabric CallBuiltin {builtin:?}: register {args_start} is not a tracked clock \
2621 value — a date-component extractor needs a `now`/`today` value"
2622 )
2623 });
2624 let days = match kind {
2626 TimeKind::Moment => t_func(cells, FuncUnit::DivS, a0, Src::Const(NANOS_PER_DAY)),
2627 TimeKind::Date => a0,
2628 };
2629 let result = match builtin {
2630 BuiltinId::WeekdayOf => iso_weekday_from_days_tiles(cells, days),
2631 BuiltinId::YearOf => civil_from_days_tiles(cells, days).0,
2632 BuiltinId::MonthOf => civil_from_days_tiles(cells, days).1,
2633 BuiltinId::DayOf => civil_from_days_tiles(cells, days).2,
2634 BuiltinId::QuarterOf => {
2635 let month = civil_from_days_tiles(cells, days).1;
2637 let m1 = t_arith(cells, AluOp::Sub, month, Src::Const(1));
2638 let q = t_func(cells, FuncUnit::DivS, m1, Src::Const(3));
2639 t_arith(cells, AluOp::Add, q, Src::Const(1))
2640 }
2641 BuiltinId::WeekOf => {
2642 let z = days;
2645 let iso_dow = iso_weekday_from_days_tiles(cells, z);
2646 let four_minus = t_arith(cells, AluOp::Sub, Src::Const(4), iso_dow);
2647 let thursday = t_arith(cells, AluOp::Add, z, four_minus);
2648 let iso_year = civil_from_days_tiles(cells, thursday).0;
2649 let jan4 = days_from_civil_tiles(cells, iso_year, Src::Const(1), Src::Const(4));
2650 let dow_jan4 = iso_weekday_from_days_tiles(cells, jan4);
2651 let dm1 = t_arith(cells, AluOp::Sub, dow_jan4, Src::Const(1));
2652 let week1_monday = t_arith(cells, AluOp::Sub, jan4, dm1);
2653 let diff = t_arith(cells, AluOp::Sub, z, week1_monday);
2654 let wk = t_func(cells, FuncUnit::DivS, diff, Src::Const(7));
2655 t_arith(cells, AluOp::Add, wk, Src::Const(1))
2656 }
2657 BuiltinId::HourOf | BuiltinId::MinuteOf | BuiltinId::SecondOf => {
2658 assert!(
2659 kind == TimeKind::Moment,
2660 "fabric CallBuiltin {builtin:?}: a Date has no time-of-day (the VM errors) — \
2661 use a Moment"
2662 );
2663 let rem = t_func(cells, FuncUnit::ModS, a0, Src::Const(NANOS_PER_DAY));
2665 let secs = t_func(cells, FuncUnit::DivS, rem, Src::Const(NANOS_PER_SECOND));
2666 match builtin {
2667 BuiltinId::HourOf => t_func(cells, FuncUnit::DivS, secs, Src::Const(3600)),
2668 BuiltinId::MinuteOf => {
2669 let m = t_func(cells, FuncUnit::ModS, secs, Src::Const(3600));
2670 t_func(cells, FuncUnit::DivS, m, Src::Const(60))
2671 }
2672 BuiltinId::SecondOf => t_func(cells, FuncUnit::ModS, secs, Src::Const(60)),
2673 _ => unreachable!(),
2674 }
2675 }
2676 _ => unreachable!(),
2677 };
2678 reg.insert(*dst, result);
2679 FLOAT_REGS.with(|t| { t.borrow_mut().remove(dst); }); }
2681 other => panic!(
2682 "loop body: builtin `{other:?}` unsupported (sqrt/floor/abs/min/max/ceil/round/count_ones \
2683 + word32 lane + date-component extractors)"
2684 ),
2685 }
2686 match builtin {
2689 BuiltinId::Word32
2690 | BuiltinId::Wand
2691 | BuiltinId::Wor
2692 | BuiltinId::Wnot
2693 | BuiltinId::Rotr
2694 | BuiltinId::Rotl
2695 | BuiltinId::Word32Shr => {
2696 W32_REGS.with(|t| {
2697 t.borrow_mut().insert(*dst);
2698 });
2699 }
2700 _ => {
2701 W32_REGS.with(|t| {
2702 t.borrow_mut().remove(dst);
2703 });
2704 }
2705 }
2706 }
2707 Op::ExactDiv { dst, lhs, rhs } => {
2712 let k = cells.len();
2713 cells.push(LoopCell::Func {
2714 unit: FuncUnit::DivS,
2715 a: getb(reg, *lhs),
2716 b: getb(reg, *rhs),
2717 });
2718 reg.insert(*dst, Src::Val(k));
2719 }
2720 Op::DivPow2 { dst, lhs, k: kexp } => {
2723 let k = cells.len();
2724 cells.push(LoopCell::Func {
2725 unit: FuncUnit::DivS,
2726 a: getb(reg, *lhs),
2727 b: Src::Const(1i64 << kexp),
2728 });
2729 reg.insert(*dst, Src::Val(k));
2730 }
2731 Op::BigIntWide { kind, dst, a, b } => {
2736 let big_of = |r: &u16| {
2737 BIGINT_BIG
2738 .with(|m| m.borrow().get(r).copied())
2739 .unwrap_or(Src::Const(0))
2740 };
2741 match kind {
2742 0 => {
2743 let v = getb(reg, *a);
2745 reg.insert(*dst, v);
2746 BIGINT_BIG.with(|m| { m.borrow_mut().insert(*dst, Src::Const(0)); });
2747 }
2748 1 => {
2749 let (av, bv) = (getb(reg, *a), getb(reg, *b));
2751 let ksum = cells.len();
2752 cells.push(LoopCell::Arith { op: AluOp::Add, a: av, b: bv });
2753 let sum = Src::Val(ksum);
2754 let kxa = cells.len(); cells.push(LoopCell::Arith { op: AluOp::Xor, a: av, b: sum });
2756 let kxb = cells.len(); cells.push(LoopCell::Arith { op: AluOp::Xor, a: bv, b: sum });
2757 let kand = cells.len(); cells.push(LoopCell::Arith { op: AluOp::And, a: Src::Val(kxa), b: Src::Val(kxb) });
2758 let kovf = cells.len(); cells.push(LoopCell::Select { cmp: CmpKind::Lt, ca: Src::Val(kand), cb: Src::Const(0), t: Src::Const(1), e: Src::Const(0) });
2759 let ko1 = cells.len(); cells.push(LoopCell::Arith { op: AluOp::Or, a: big_of(a), b: big_of(b) });
2760 let ko2 = cells.len(); cells.push(LoopCell::Arith { op: AluOp::Or, a: Src::Val(ko1), b: Src::Val(kovf) });
2761 reg.insert(*dst, sum);
2762 BIGINT_BIG.with(|m| { m.borrow_mut().insert(*dst, Src::Val(ko2)); });
2763 }
2764 6 => {
2765 let (av, bv) = (getb(reg, *a), getb(reg, *b));
2767 let kmul = cells.len(); cells.push(LoopCell::Arith { op: AluOp::Mul, a: av, b: bv });
2768 let kovf = cells.len(); cells.push(LoopCell::Func { unit: FuncUnit::MulOvfS, a: av, b: bv });
2769 let ko1 = cells.len(); cells.push(LoopCell::Arith { op: AluOp::Or, a: big_of(a), b: big_of(b) });
2770 let ko2 = cells.len(); cells.push(LoopCell::Arith { op: AluOp::Or, a: Src::Val(ko1), b: Src::Val(kovf) });
2771 reg.insert(*dst, Src::Val(kmul));
2772 BIGINT_BIG.with(|m| { m.borrow_mut().insert(*dst, Src::Val(ko2)); });
2773 }
2774 4 => {
2775 let av = getb(reg, *a);
2777 let kmask = cells.len(); cells.push(LoopCell::Arith { op: AluOp::And, a: av, b: Src::Const(0xFFFF_FFFF) });
2778 let klow = cells.len(); cells.push(LoopCell::Select { cmp: CmpKind::Ne, ca: big_of(a), cb: Src::Const(0), t: Src::Const(0), e: Src::Val(kmask) });
2779 reg.insert(*dst, Src::Val(klow));
2780 BIGINT_BIG.with(|m| { m.borrow_mut().remove(dst); }); }
2782 _ => panic!(
2783 "fabric BigIntWide: kind {kind} (Sub/Cmp/Div) is a follow-on — Sub can UN-promote a \
2784 BigInt to Int (needs the wide value), Cmp/Div need the full magnitude; \
2785 FromInt/Add/Mul/Low are realized"
2786 ),
2787 }
2788 }
2789 Op::ApproxEq { dst, lhs, rhs } => {
2793 let k = cells.len();
2794 cells.push(LoopCell::Func {
2795 unit: FuncUnit::ApproxEqF,
2796 a: getb(reg, *lhs),
2797 b: getb(reg, *rhs),
2798 });
2799 reg.insert(*dst, Src::Val(k));
2800 }
2801 Op::MagicDivU {
2807 dst,
2808 lhs,
2809 magic,
2810 more,
2811 mul_back,
2812 divisor: _,
2813 wrap: _,
2814 } => {
2815 const SHIFT_MASK: u8 = 0x3F;
2816 const ADD_MARKER: u8 = 0x40;
2817 const SHIFT_PATH: u8 = 0x80;
2818 let n = getb(reg, *lhs);
2819 let shift = (*more & SHIFT_MASK) as i64;
2820 let q = if *more & SHIFT_PATH != 0 {
2821 logical_shr_const(cells, n, shift) } else {
2823 let khi = cells.len(); cells.push(LoopCell::Func {
2825 unit: FuncUnit::MulHiU,
2826 a: Src::Const(*magic as i64),
2827 b: n,
2828 });
2829 let hi = Src::Val(khi);
2830 if *more & ADD_MARKER != 0 {
2831 let ksub = cells.len();
2833 cells.push(LoopCell::Arith {
2834 op: AluOp::Sub,
2835 a: n,
2836 b: hi,
2837 });
2838 let half = logical_shr_const(cells, Src::Val(ksub), 1);
2839 let kadd = cells.len();
2840 cells.push(LoopCell::Arith {
2841 op: AluOp::Add,
2842 a: half,
2843 b: hi,
2844 });
2845 logical_shr_const(cells, Src::Val(kadd), shift)
2846 } else {
2847 logical_shr_const(cells, hi, shift) }
2849 };
2850 let result = if *mul_back == 0 {
2851 q } else {
2853 let kmul = cells.len();
2855 cells.push(LoopCell::Arith {
2856 op: AluOp::Mul,
2857 a: q,
2858 b: Src::Const(*mul_back),
2859 });
2860 let kres = cells.len();
2861 cells.push(LoopCell::Arith {
2862 op: AluOp::Sub,
2863 a: n,
2864 b: Src::Val(kmul),
2865 });
2866 Src::Val(kres)
2867 };
2868 reg.insert(*dst, result);
2869 }
2870 Op::Length { dst, collection } => {
2873 let ptr = getb(reg, *collection);
2874 let k = cells.len();
2875 cells.push(LoopCell::HeapRead { addr: ptr });
2876 reg.insert(*dst, Src::Val(k));
2877 }
2878 Op::Index {
2879 dst,
2880 collection,
2881 index,
2882 }
2883 | Op::IndexUnchecked {
2884 dst,
2885 collection,
2886 index,
2887 } => {
2888 let ptr = getb(reg, *collection);
2889 let idx = getb(reg, *index);
2890 let ka = cells.len(); cells.push(LoopCell::Arith {
2892 op: AluOp::Add,
2893 a: ptr,
2894 b: idx,
2895 });
2896 let kr = cells.len();
2897 cells.push(LoopCell::HeapRead { addr: Src::Val(ka) });
2898 reg.insert(*dst, Src::Val(kr));
2899 }
2900 Op::NewList { dst, start, count } => {
2903 let n = *count;
2904 let ka = cells.len();
2905 cells.push(LoopCell::HeapAlloc {
2906 size: Src::Const(n as i64 + 1),
2907 });
2908 let ptr = Src::Val(ka);
2909 cells.push(LoopCell::HeapWrite {
2910 addr: ptr,
2911 val: Src::Const(n as i64),
2912 }); for j in 0..n {
2914 let elem = getb(reg, *start + j);
2915 let kadd = cells.len(); cells.push(LoopCell::Arith {
2917 op: AluOp::Add,
2918 a: ptr,
2919 b: Src::Const(j as i64 + 1),
2920 });
2921 cells.push(LoopCell::HeapWrite {
2922 addr: Src::Val(kadd),
2923 val: elem,
2924 });
2925 }
2926 reg.insert(*dst, ptr);
2927 COLL_LEN.with(|m| m.borrow_mut().insert(*dst, n as i64));
2928 }
2929 Op::Args { dst } => {
2930 let ptr = heap_argv(cells, 0);
2931 reg.insert(*dst, ptr);
2932 TEXT_REGS.with(|t| {
2933 t.borrow_mut().remove(dst);
2934 });
2935 COLL_LEN.with(|m| m.borrow_mut().insert(*dst, 1));
2936 }
2937 Op::NewEmptyListFixed { dst, capacity } => {
2940 let ka = cells.len();
2941 cells.push(LoopCell::HeapAlloc {
2942 size: Src::Const(i64::from(*capacity) + 1),
2943 });
2944 let ptr = Src::Val(ka);
2945 cells.push(LoopCell::HeapWrite {
2946 addr: ptr,
2947 val: Src::Const(0),
2948 });
2949 reg.insert(*dst, ptr);
2950 COLL_LEN.with(|m| m.borrow_mut().insert(*dst, 0));
2951 }
2952 Op::NewEmptyList { dst }
2953 | Op::NewEmptyListWithCapacity { dst, .. }
2954 | Op::NewEmptyListI32 { dst }
2955 | Op::NewEmptySet { dst }
2956 | Op::NewEmptyMap { dst }
2957 | Op::NewEmptyMapWithCapacity { dst, .. } => {
2958 let ka = cells.len();
2959 cells.push(LoopCell::HeapAlloc {
2960 size: Src::Const(1),
2961 });
2962 let ptr = Src::Val(ka);
2963 cells.push(LoopCell::HeapWrite {
2964 addr: ptr,
2965 val: Src::Const(0),
2966 }); reg.insert(*dst, ptr);
2968 COLL_LEN.with(|m| m.borrow_mut().insert(*dst, 0));
2969 }
2970 Op::NewTuple { dst, start, count } => {
2973 let n = *count;
2974 let ka = cells.len();
2975 cells.push(LoopCell::HeapAlloc {
2976 size: Src::Const(n as i64 + 1),
2977 });
2978 let ptr = Src::Val(ka);
2979 cells.push(LoopCell::HeapWrite {
2980 addr: ptr,
2981 val: Src::Const(n as i64),
2982 });
2983 for j in 0..n {
2984 let elem = getb(reg, *start + j);
2985 let kadd = cells.len();
2986 cells.push(LoopCell::Arith {
2987 op: AluOp::Add,
2988 a: ptr,
2989 b: Src::Const(j as i64 + 1),
2990 });
2991 cells.push(LoopCell::HeapWrite {
2992 addr: Src::Val(kadd),
2993 val: elem,
2994 });
2995 }
2996 reg.insert(*dst, ptr);
2997 COLL_LEN.with(|m| m.borrow_mut().insert(*dst, n as i64));
2998 }
2999 Op::DestructureTuple { src, start, count } => {
3001 let ptr = getb(reg, *src);
3002 for j in 0..*count {
3003 let kadd = cells.len();
3004 cells.push(LoopCell::Arith {
3005 op: AluOp::Add,
3006 a: ptr,
3007 b: Src::Const(j as i64 + 1),
3008 });
3009 let kr = cells.len();
3010 cells.push(LoopCell::HeapRead {
3011 addr: Src::Val(kadd),
3012 });
3013 reg.insert(*start + j, Src::Val(kr));
3014 }
3015 }
3016 Op::SetIndex {
3019 collection,
3020 index,
3021 value,
3022 }
3023 | Op::SetIndexUnchecked {
3024 collection,
3025 index,
3026 value,
3027 } if REG_TYPE.with(|m| m.borrow().contains_key(collection)) => {
3028 let ptr = getb(reg, *collection);
3029 let val = getb(reg, *value);
3030 let name = NAME_REGS
3031 .with(|m| m.borrow().get(index).copied())
3032 .unwrap_or_else(|| {
3033 panic!("struct SetIndex keyed by R{index}, which holds no constant name")
3034 });
3035 let slot = struct_field_slot_named(*collection, name, consts);
3036 let kadd = cells.len();
3037 cells.push(LoopCell::Arith {
3038 op: AluOp::Add,
3039 a: ptr,
3040 b: Src::Const(slot as i64 + 1),
3041 });
3042 cells.push(LoopCell::HeapWrite {
3043 addr: Src::Val(kadd),
3044 val,
3045 });
3046 }
3047 Op::SetIndex {
3049 collection,
3050 index,
3051 value,
3052 }
3053 | Op::SetIndexUnchecked {
3054 collection,
3055 index,
3056 value,
3057 } => {
3058 let ptr = getb(reg, *collection);
3059 let idx = getb(reg, *index);
3060 let val = getb(reg, *value);
3061 let kadd = cells.len(); cells.push(LoopCell::Arith {
3063 op: AluOp::Add,
3064 a: ptr,
3065 b: idx,
3066 });
3067 cells.push(LoopCell::HeapWrite {
3068 addr: Src::Val(kadd),
3069 val,
3070 });
3071 }
3072 Op::ListPop { list, dst } => {
3074 let ptr = getb(reg, *list);
3075 let klen = cells.len();
3076 cells.push(LoopCell::HeapRead { addr: ptr }); let len = Src::Val(klen);
3078 let kaddr = cells.len(); cells.push(LoopCell::Arith {
3080 op: AluOp::Add,
3081 a: ptr,
3082 b: len,
3083 });
3084 let kr = cells.len();
3085 cells.push(LoopCell::HeapRead {
3086 addr: Src::Val(kaddr),
3087 }); let kdec = cells.len();
3089 cells.push(LoopCell::Arith {
3090 op: AluOp::Sub,
3091 a: len,
3092 b: Src::Const(1),
3093 });
3094 cells.push(LoopCell::HeapWrite {
3095 addr: ptr,
3096 val: Src::Val(kdec),
3097 }); reg.insert(*dst, Src::Val(kr));
3099 COLL_LEN.with(|m| {
3100 let mut m = m.borrow_mut();
3101 if let Some(l) = m.get(list).copied() {
3102 m.insert(*list, l - 1);
3103 }
3104 });
3105 }
3106 Op::ListPush { list, value } => {
3108 let ptr = getb(reg, *list);
3109 let val = getb(reg, *value);
3110 let klen = cells.len();
3111 cells.push(LoopCell::HeapRead { addr: ptr }); let len = Src::Val(klen);
3113 let kp1 = cells.len();
3114 cells.push(LoopCell::Arith {
3115 op: AluOp::Add,
3116 a: ptr,
3117 b: Src::Const(1),
3118 });
3119 let kaddr = cells.len(); cells.push(LoopCell::Arith {
3121 op: AluOp::Add,
3122 a: Src::Val(kp1),
3123 b: len,
3124 });
3125 cells.push(LoopCell::HeapWrite {
3126 addr: Src::Val(kaddr),
3127 val,
3128 });
3129 let knl = cells.len();
3130 cells.push(LoopCell::Arith {
3131 op: AluOp::Add,
3132 a: len,
3133 b: Src::Const(1),
3134 });
3135 cells.push(LoopCell::HeapWrite {
3136 addr: ptr,
3137 val: Src::Val(knl),
3138 }); COLL_LEN.with(|m| {
3140 let mut m = m.borrow_mut();
3141 if let Some(l) = m.get(list).copied() {
3142 m.insert(*list, l + 1);
3143 }
3144 });
3145 }
3146 Op::CrdtAppend { seq, value } => {
3149 let ptr = getb(reg, *seq);
3150 let val = getb(reg, *value);
3151 let klen = cells.len();
3152 cells.push(LoopCell::HeapRead { addr: ptr });
3153 let len = Src::Val(klen);
3154 let kp1 = cells.len();
3155 cells.push(LoopCell::Arith {
3156 op: AluOp::Add,
3157 a: ptr,
3158 b: Src::Const(1),
3159 });
3160 let kaddr = cells.len();
3161 cells.push(LoopCell::Arith {
3162 op: AluOp::Add,
3163 a: Src::Val(kp1),
3164 b: len,
3165 });
3166 cells.push(LoopCell::HeapWrite {
3167 addr: Src::Val(kaddr),
3168 val,
3169 });
3170 let knl = cells.len();
3171 cells.push(LoopCell::Arith {
3172 op: AluOp::Add,
3173 a: len,
3174 b: Src::Const(1),
3175 });
3176 cells.push(LoopCell::HeapWrite {
3177 addr: ptr,
3178 val: Src::Val(knl),
3179 });
3180 COLL_LEN.with(|m| {
3181 let mut m = m.borrow_mut();
3182 if let Some(l) = m.get(seq).copied() {
3183 m.insert(*seq, l + 1);
3184 }
3185 });
3186 }
3187 Op::ListPushField { obj, field, src } => {
3190 let optr = getb(reg, *obj);
3191 let slot = struct_field_slot(*obj, *field);
3192 let val = getb(reg, *src);
3193 let kfa = cells.len(); cells.push(LoopCell::Arith {
3195 op: AluOp::Add,
3196 a: optr,
3197 b: Src::Const(slot as i64 + 1),
3198 });
3199 let klp = cells.len();
3200 cells.push(LoopCell::HeapRead {
3201 addr: Src::Val(kfa),
3202 }); let lptr = Src::Val(klp);
3204 let klen = cells.len();
3205 cells.push(LoopCell::HeapRead { addr: lptr }); let len = Src::Val(klen);
3207 let kp1 = cells.len();
3208 cells.push(LoopCell::Arith {
3209 op: AluOp::Add,
3210 a: lptr,
3211 b: Src::Const(1),
3212 });
3213 let kaddr = cells.len(); cells.push(LoopCell::Arith {
3215 op: AluOp::Add,
3216 a: Src::Val(kp1),
3217 b: len,
3218 });
3219 cells.push(LoopCell::HeapWrite {
3220 addr: Src::Val(kaddr),
3221 val,
3222 });
3223 let knl = cells.len();
3224 cells.push(LoopCell::Arith {
3225 op: AluOp::Add,
3226 a: len,
3227 b: Src::Const(1),
3228 });
3229 cells.push(LoopCell::HeapWrite {
3230 addr: lptr,
3231 val: Src::Val(knl),
3232 }); }
3234 Op::Contains {
3239 dst,
3240 collection,
3241 value,
3242 } => {
3243 let ptr = getb(reg, *collection);
3244 let val = getb(reg, *value);
3245 let max = COLL_LEN
3246 .with(|m| m.borrow().get(collection).copied())
3247 .expect("fabric Contains: collection length not statically known (a runtime-length scan needs a sequential unit)");
3248 let klen = cells.len();
3252 cells.push(LoopCell::HeapRead { addr: ptr });
3253 let rlen = Src::Val(klen);
3254 let mut acc = Src::Const(0);
3255 for i in 0..max {
3256 let kaddr = cells.len(); cells.push(LoopCell::Arith {
3258 op: AluOp::Add,
3259 a: ptr,
3260 b: Src::Const(i + 1),
3261 });
3262 let kelem = cells.len();
3263 cells.push(LoopCell::HeapRead {
3264 addr: Src::Val(kaddr),
3265 });
3266 let kg = cells.len(); cells.push(LoopCell::Select {
3268 cmp: CmpKind::Lt,
3269 ca: Src::Const(i),
3270 cb: rlen,
3271 t: Src::Const(1),
3272 e: Src::Const(0),
3273 });
3274 let keq = cells.len(); cells.push(LoopCell::Select {
3276 cmp: CmpKind::Eq,
3277 ca: Src::Val(kelem),
3278 cb: val,
3279 t: Src::Const(1),
3280 e: Src::Const(0),
3281 });
3282 let kpres = cells.len(); cells.push(LoopCell::Arith {
3284 op: AluOp::And,
3285 a: Src::Val(kg),
3286 b: Src::Val(keq),
3287 });
3288 let kor = cells.len();
3289 cells.push(LoopCell::Arith {
3290 op: AluOp::Or,
3291 a: acc,
3292 b: Src::Val(kpres),
3293 });
3294 acc = Src::Val(kor);
3295 }
3296 reg.insert(*dst, acc);
3297 }
3298 Op::SetAdd { set, value } => {
3304 let ptr = getb(reg, *set);
3305 let val = getb(reg, *value);
3306 let max = COLL_LEN
3307 .with(|m| m.borrow().get(set).copied())
3308 .expect("fabric SetAdd: set max-length not tracked (build the set from a literal/empty)");
3309 let klen = cells.len();
3310 cells.push(LoopCell::HeapRead { addr: ptr }); let rlen = Src::Val(klen);
3312 let mut found = Src::Const(0);
3313 for i in 0..max {
3314 let kaddr = cells.len();
3315 cells.push(LoopCell::Arith {
3316 op: AluOp::Add,
3317 a: ptr,
3318 b: Src::Const(i + 1),
3319 });
3320 let kelem = cells.len();
3321 cells.push(LoopCell::HeapRead {
3322 addr: Src::Val(kaddr),
3323 });
3324 let kguard = cells.len(); cells.push(LoopCell::Select {
3326 cmp: CmpKind::Lt,
3327 ca: Src::Const(i),
3328 cb: rlen,
3329 t: Src::Const(1),
3330 e: Src::Const(0),
3331 });
3332 let keq = cells.len(); cells.push(LoopCell::Select {
3334 cmp: CmpKind::Eq,
3335 ca: Src::Val(kelem),
3336 cb: val,
3337 t: Src::Const(1),
3338 e: Src::Const(0),
3339 });
3340 let kpres = cells.len(); cells.push(LoopCell::Arith {
3342 op: AluOp::And,
3343 a: Src::Val(kguard),
3344 b: Src::Val(keq),
3345 });
3346 let kor = cells.len();
3347 cells.push(LoopCell::Arith {
3348 op: AluOp::Or,
3349 a: found,
3350 b: Src::Val(kpres),
3351 });
3352 found = Src::Val(kor);
3353 }
3354 let kp1 = cells.len();
3356 cells.push(LoopCell::Arith {
3357 op: AluOp::Add,
3358 a: ptr,
3359 b: Src::Const(1),
3360 });
3361 let kwaddr = cells.len();
3362 cells.push(LoopCell::Arith {
3363 op: AluOp::Add,
3364 a: Src::Val(kp1),
3365 b: rlen,
3366 });
3367 cells.push(LoopCell::HeapWrite {
3368 addr: Src::Val(kwaddr),
3369 val,
3370 });
3371 let krlen1 = cells.len();
3373 cells.push(LoopCell::Arith {
3374 op: AluOp::Add,
3375 a: rlen,
3376 b: Src::Const(1),
3377 });
3378 let knew = cells.len();
3379 cells.push(LoopCell::Select {
3380 cmp: CmpKind::Eq,
3381 ca: found,
3382 cb: Src::Const(0),
3383 t: Src::Val(krlen1),
3384 e: rlen,
3385 });
3386 cells.push(LoopCell::HeapWrite {
3387 addr: ptr,
3388 val: Src::Val(knew),
3389 });
3390 COLL_LEN.with(|m| {
3391 let mut m = m.borrow_mut();
3392 m.insert(*set, max + 1); });
3394 }
3395 Op::RemoveFrom { collection, value } => {
3400 let ptr = getb(reg, *collection);
3401 let val = getb(reg, *value);
3402 let max = COLL_LEN
3403 .with(|m| m.borrow().get(collection).copied())
3404 .expect("fabric RemoveFrom: collection max-length not tracked");
3405 let klen = cells.len();
3406 cells.push(LoopCell::HeapRead { addr: ptr }); let rlen = Src::Val(klen);
3408 let mut past = Src::Const(0);
3409 for i in 0..max {
3410 let kaddr = cells.len(); cells.push(LoopCell::Arith {
3412 op: AluOp::Add,
3413 a: ptr,
3414 b: Src::Const(i + 1),
3415 });
3416 let kelem = cells.len();
3417 cells.push(LoopCell::HeapRead {
3418 addr: Src::Val(kaddr),
3419 });
3420 let kg = cells.len(); cells.push(LoopCell::Select {
3422 cmp: CmpKind::Lt,
3423 ca: Src::Const(i),
3424 cb: rlen,
3425 t: Src::Const(1),
3426 e: Src::Const(0),
3427 });
3428 let ke = cells.len(); cells.push(LoopCell::Select {
3430 cmp: CmpKind::Eq,
3431 ca: Src::Val(kelem),
3432 cb: val,
3433 t: Src::Const(1),
3434 e: Src::Const(0),
3435 });
3436 let km = cells.len(); cells.push(LoopCell::Arith {
3438 op: AluOp::And,
3439 a: Src::Val(kg),
3440 b: Src::Val(ke),
3441 });
3442 let kpast = cells.len(); cells.push(LoopCell::Arith {
3444 op: AluOp::Or,
3445 a: past,
3446 b: Src::Val(km),
3447 });
3448 past = Src::Val(kpast);
3449 let kaddr2 = cells.len(); cells.push(LoopCell::Arith {
3451 op: AluOp::Add,
3452 a: ptr,
3453 b: Src::Const(i + 2),
3454 });
3455 let knext = cells.len();
3456 cells.push(LoopCell::HeapRead {
3457 addr: Src::Val(kaddr2),
3458 });
3459 let knew = cells.len(); cells.push(LoopCell::Select {
3461 cmp: CmpKind::Ne,
3462 ca: past,
3463 cb: Src::Const(0),
3464 t: Src::Val(knext),
3465 e: Src::Val(kelem),
3466 });
3467 cells.push(LoopCell::HeapWrite {
3468 addr: Src::Val(kaddr),
3469 val: Src::Val(knew),
3470 });
3471 }
3472 let krlen1 = cells.len(); cells.push(LoopCell::Arith {
3474 op: AluOp::Sub,
3475 a: rlen,
3476 b: Src::Const(1),
3477 });
3478 let knewc = cells.len(); cells.push(LoopCell::Select {
3480 cmp: CmpKind::Ne,
3481 ca: past,
3482 cb: Src::Const(0),
3483 t: Src::Val(krlen1),
3484 e: rlen,
3485 });
3486 cells.push(LoopCell::HeapWrite {
3487 addr: ptr,
3488 val: Src::Val(knewc),
3489 });
3490 }
3491 Op::UnionOp { dst, lhs, rhs } => {
3495 let lptr = getb(reg, *lhs);
3496 let rptr = getb(reg, *rhs);
3497 let lmax = COLL_LEN
3498 .with(|m| m.borrow().get(lhs).copied())
3499 .expect("fabric UnionOp: lhs max-length not tracked");
3500 let rmax = COLL_LEN
3501 .with(|m| m.borrow().get(rhs).copied())
3502 .expect("fabric UnionOp: rhs max-length not tracked");
3503 let klc = cells.len();
3504 cells.push(LoopCell::HeapRead { addr: lptr });
3505 let lcount = Src::Val(klc);
3506 let krc = cells.len();
3507 cells.push(LoopCell::HeapRead { addr: rptr });
3508 let rcount = Src::Val(krc);
3509 let ka = cells.len();
3510 cells.push(LoopCell::HeapAlloc {
3511 size: Src::Const(1 + lmax + rmax),
3512 });
3513 let nptr = Src::Val(ka);
3514 for i in 0..lmax {
3516 let ksa = cells.len();
3517 cells.push(LoopCell::Arith {
3518 op: AluOp::Add,
3519 a: lptr,
3520 b: Src::Const(i + 1),
3521 });
3522 let ks = cells.len();
3523 cells.push(LoopCell::HeapRead {
3524 addr: Src::Val(ksa),
3525 });
3526 let kda = cells.len();
3527 cells.push(LoopCell::Arith {
3528 op: AluOp::Add,
3529 a: nptr,
3530 b: Src::Const(i + 1),
3531 });
3532 cells.push(LoopCell::HeapWrite {
3533 addr: Src::Val(kda),
3534 val: Src::Val(ks),
3535 });
3536 }
3537 let mut new_count = lcount; for j in 0..rmax {
3539 let kra = cells.len();
3540 cells.push(LoopCell::Arith {
3541 op: AluOp::Add,
3542 a: rptr,
3543 b: Src::Const(j + 1),
3544 });
3545 let kre = cells.len();
3546 cells.push(LoopCell::HeapRead {
3547 addr: Src::Val(kra),
3548 });
3549 let rhs_elem = Src::Val(kre);
3550 let mut in_lhs = Src::Const(0);
3552 for i in 0..lmax {
3553 let ksa = cells.len();
3554 cells.push(LoopCell::Arith {
3555 op: AluOp::Add,
3556 a: lptr,
3557 b: Src::Const(i + 1),
3558 });
3559 let ks = cells.len();
3560 cells.push(LoopCell::HeapRead {
3561 addr: Src::Val(ksa),
3562 });
3563 let kg = cells.len();
3564 cells.push(LoopCell::Select {
3565 cmp: CmpKind::Lt,
3566 ca: Src::Const(i),
3567 cb: lcount,
3568 t: Src::Const(1),
3569 e: Src::Const(0),
3570 });
3571 let keq = cells.len();
3572 cells.push(LoopCell::Select {
3573 cmp: CmpKind::Eq,
3574 ca: Src::Val(ks),
3575 cb: rhs_elem,
3576 t: Src::Const(1),
3577 e: Src::Const(0),
3578 });
3579 let kp = cells.len();
3580 cells.push(LoopCell::Arith {
3581 op: AluOp::And,
3582 a: Src::Val(kg),
3583 b: Src::Val(keq),
3584 });
3585 let kor = cells.len();
3586 cells.push(LoopCell::Arith {
3587 op: AluOp::Or,
3588 a: in_lhs,
3589 b: Src::Val(kp),
3590 });
3591 in_lhs = Src::Val(kor);
3592 }
3593 let kgj = cells.len();
3594 cells.push(LoopCell::Select {
3595 cmp: CmpKind::Lt,
3596 ca: Src::Const(j),
3597 cb: rcount,
3598 t: Src::Const(1),
3599 e: Src::Const(0),
3600 });
3601 let knil = cells.len();
3602 cells.push(LoopCell::Select {
3603 cmp: CmpKind::Eq,
3604 ca: in_lhs,
3605 cb: Src::Const(0),
3606 t: Src::Const(1),
3607 e: Src::Const(0),
3608 });
3609 let kap = cells.len();
3610 cells.push(LoopCell::Arith {
3611 op: AluOp::And,
3612 a: Src::Val(kgj),
3613 b: Src::Val(knil),
3614 });
3615 let append = Src::Val(kap);
3616 let kwa1 = cells.len();
3618 cells.push(LoopCell::Arith {
3619 op: AluOp::Add,
3620 a: nptr,
3621 b: Src::Const(1),
3622 });
3623 let kwa2 = cells.len();
3624 cells.push(LoopCell::Arith {
3625 op: AluOp::Add,
3626 a: Src::Val(kwa1),
3627 b: new_count,
3628 });
3629 cells.push(LoopCell::HeapWrite {
3630 addr: Src::Val(kwa2),
3631 val: rhs_elem,
3632 });
3633 let knc1 = cells.len();
3634 cells.push(LoopCell::Arith {
3635 op: AluOp::Add,
3636 a: new_count,
3637 b: Src::Const(1),
3638 });
3639 let knc = cells.len();
3640 cells.push(LoopCell::Select {
3641 cmp: CmpKind::Ne,
3642 ca: append,
3643 cb: Src::Const(0),
3644 t: Src::Val(knc1),
3645 e: new_count,
3646 });
3647 new_count = Src::Val(knc);
3648 }
3649 cells.push(LoopCell::HeapWrite {
3650 addr: nptr,
3651 val: new_count,
3652 }); reg.insert(*dst, nptr);
3654 COLL_LEN.with(|m| m.borrow_mut().insert(*dst, lmax + rmax));
3655 }
3656 Op::IntersectOp { dst, lhs, rhs } => {
3659 let lptr = getb(reg, *lhs);
3660 let rptr = getb(reg, *rhs);
3661 let lmax = COLL_LEN
3662 .with(|m| m.borrow().get(lhs).copied())
3663 .expect("fabric IntersectOp: lhs max-length not tracked");
3664 let rmax = COLL_LEN
3665 .with(|m| m.borrow().get(rhs).copied())
3666 .expect("fabric IntersectOp: rhs max-length not tracked");
3667 let klc = cells.len();
3668 cells.push(LoopCell::HeapRead { addr: lptr });
3669 let lcount = Src::Val(klc);
3670 let krc = cells.len();
3671 cells.push(LoopCell::HeapRead { addr: rptr });
3672 let rcount = Src::Val(krc);
3673 let ka = cells.len();
3674 cells.push(LoopCell::HeapAlloc {
3675 size: Src::Const(1 + lmax),
3676 }); let nptr = Src::Val(ka);
3678 let mut new_count = Src::Const(0);
3679 for i in 0..lmax {
3680 let ksa = cells.len();
3681 cells.push(LoopCell::Arith {
3682 op: AluOp::Add,
3683 a: lptr,
3684 b: Src::Const(i + 1),
3685 });
3686 let ks = cells.len();
3687 cells.push(LoopCell::HeapRead {
3688 addr: Src::Val(ksa),
3689 });
3690 let lhs_elem = Src::Val(ks);
3691 let mut in_rhs = Src::Const(0);
3693 for j in 0..rmax {
3694 let kra = cells.len();
3695 cells.push(LoopCell::Arith {
3696 op: AluOp::Add,
3697 a: rptr,
3698 b: Src::Const(j + 1),
3699 });
3700 let kr = cells.len();
3701 cells.push(LoopCell::HeapRead {
3702 addr: Src::Val(kra),
3703 });
3704 let kg = cells.len();
3705 cells.push(LoopCell::Select {
3706 cmp: CmpKind::Lt,
3707 ca: Src::Const(j),
3708 cb: rcount,
3709 t: Src::Const(1),
3710 e: Src::Const(0),
3711 });
3712 let keq = cells.len();
3713 cells.push(LoopCell::Select {
3714 cmp: CmpKind::Eq,
3715 ca: Src::Val(kr),
3716 cb: lhs_elem,
3717 t: Src::Const(1),
3718 e: Src::Const(0),
3719 });
3720 let kp = cells.len();
3721 cells.push(LoopCell::Arith {
3722 op: AluOp::And,
3723 a: Src::Val(kg),
3724 b: Src::Val(keq),
3725 });
3726 let kor = cells.len();
3727 cells.push(LoopCell::Arith {
3728 op: AluOp::Or,
3729 a: in_rhs,
3730 b: Src::Val(kp),
3731 });
3732 in_rhs = Src::Val(kor);
3733 }
3734 let kgi = cells.len();
3735 cells.push(LoopCell::Select {
3736 cmp: CmpKind::Lt,
3737 ca: Src::Const(i),
3738 cb: lcount,
3739 t: Src::Const(1),
3740 e: Src::Const(0),
3741 });
3742 let kkeep = cells.len();
3743 cells.push(LoopCell::Arith {
3744 op: AluOp::And,
3745 a: Src::Val(kgi),
3746 b: in_rhs,
3747 });
3748 let keep = Src::Val(kkeep);
3749 let kwa1 = cells.len();
3751 cells.push(LoopCell::Arith {
3752 op: AluOp::Add,
3753 a: nptr,
3754 b: Src::Const(1),
3755 });
3756 let kwa2 = cells.len();
3757 cells.push(LoopCell::Arith {
3758 op: AluOp::Add,
3759 a: Src::Val(kwa1),
3760 b: new_count,
3761 });
3762 cells.push(LoopCell::HeapWrite {
3763 addr: Src::Val(kwa2),
3764 val: lhs_elem,
3765 });
3766 let knc1 = cells.len();
3767 cells.push(LoopCell::Arith {
3768 op: AluOp::Add,
3769 a: new_count,
3770 b: Src::Const(1),
3771 });
3772 let knc = cells.len();
3773 cells.push(LoopCell::Select {
3774 cmp: CmpKind::Ne,
3775 ca: keep,
3776 cb: Src::Const(0),
3777 t: Src::Val(knc1),
3778 e: new_count,
3779 });
3780 new_count = Src::Val(knc);
3781 }
3782 cells.push(LoopCell::HeapWrite {
3783 addr: nptr,
3784 val: new_count,
3785 }); reg.insert(*dst, nptr);
3787 COLL_LEN.with(|m| m.borrow_mut().insert(*dst, lmax));
3788 }
3789 Op::Concat { dst, lhs, rhs } => {
3793 let (lptr, lmax) = {
3794 let is_text = TEXT_REGS.with(|t| t.borrow().contains(lhs));
3795 if is_text {
3796 (
3797 getb(reg, *lhs),
3798 COLL_LEN.with(|m| m.borrow().get(lhs).copied().unwrap_or(0)),
3799 )
3800 } else {
3801 (push_int_to_decimal(cells, getb(reg, *lhs)), 20)
3802 }
3803 };
3804 let (rptr, rmax) = {
3805 let is_text = TEXT_REGS.with(|t| t.borrow().contains(rhs));
3806 if is_text {
3807 (
3808 getb(reg, *rhs),
3809 COLL_LEN.with(|m| m.borrow().get(rhs).copied().unwrap_or(0)),
3810 )
3811 } else {
3812 (push_int_to_decimal(cells, getb(reg, *rhs)), 20)
3813 }
3814 };
3815 let kll = cells.len();
3816 cells.push(LoopCell::HeapRead { addr: lptr });
3817 let llen = Src::Val(kll);
3818 let krl = cells.len();
3819 cells.push(LoopCell::HeapRead { addr: rptr });
3820 let rlen = Src::Val(krl);
3821 let ka = cells.len();
3822 cells.push(LoopCell::HeapAlloc {
3823 size: Src::Const(1 + lmax + rmax),
3824 });
3825 let nptr = Src::Val(ka);
3826 for i in 0..lmax {
3827 let ksa = cells.len();
3828 cells.push(LoopCell::Arith {
3829 op: AluOp::Add,
3830 a: lptr,
3831 b: Src::Const(i + 1),
3832 });
3833 let ks = cells.len();
3834 cells.push(LoopCell::HeapRead {
3835 addr: Src::Val(ksa),
3836 });
3837 let kda = cells.len();
3838 cells.push(LoopCell::Arith {
3839 op: AluOp::Add,
3840 a: nptr,
3841 b: Src::Const(i + 1),
3842 });
3843 cells.push(LoopCell::HeapWrite {
3844 addr: Src::Val(kda),
3845 val: Src::Val(ks),
3846 });
3847 }
3848 for j in 0..rmax {
3849 let ksa = cells.len();
3850 cells.push(LoopCell::Arith {
3851 op: AluOp::Add,
3852 a: rptr,
3853 b: Src::Const(j + 1),
3854 });
3855 let ks = cells.len();
3856 cells.push(LoopCell::HeapRead {
3857 addr: Src::Val(ksa),
3858 });
3859 let kd1 = cells.len();
3860 cells.push(LoopCell::Arith {
3861 op: AluOp::Add,
3862 a: nptr,
3863 b: Src::Const(j + 1),
3864 });
3865 let kd2 = cells.len();
3866 cells.push(LoopCell::Arith {
3867 op: AluOp::Add,
3868 a: Src::Val(kd1),
3869 b: llen,
3870 });
3871 cells.push(LoopCell::HeapWrite {
3872 addr: Src::Val(kd2),
3873 val: Src::Val(ks),
3874 });
3875 }
3876 let knl = cells.len();
3877 cells.push(LoopCell::Arith {
3878 op: AluOp::Add,
3879 a: llen,
3880 b: rlen,
3881 });
3882 cells.push(LoopCell::HeapWrite {
3883 addr: nptr,
3884 val: Src::Val(knl),
3885 });
3886 reg.insert(*dst, nptr);
3887 TEXT_REGS.with(|t| {
3888 t.borrow_mut().insert(*dst);
3889 });
3890 COLL_LEN.with(|m| {
3891 m.borrow_mut().insert(*dst, lmax + rmax);
3892 });
3893 }
3894 Op::FormatValue {
3899 dst,
3900 src,
3901 spec,
3902 debug_prefix,
3903 } => {
3904 assert!(
3905 *spec == u32::MAX && *debug_prefix == u32::MAX,
3906 "fabric FormatValue: format-specs / debug prefixes are a follow-on (default rendering only)"
3907 );
3908 if TEXT_REGS.with(|t| t.borrow().contains(src)) {
3909 let s = getb(reg, *src); reg.insert(*dst, s);
3911 TEXT_REGS.with(|t| {
3912 t.borrow_mut().insert(*dst);
3913 });
3914 COLL_LEN.with(|m| {
3915 let mut m = m.borrow_mut();
3916 if let Some(l) = m.get(src).copied() {
3917 m.insert(*dst, l);
3918 }
3919 });
3920 } else {
3921 let ptr = push_int_to_decimal(cells, getb(reg, *src)); reg.insert(*dst, ptr);
3923 TEXT_REGS.with(|t| {
3924 t.borrow_mut().insert(*dst);
3925 });
3926 }
3927 }
3928 Op::ChanNew { dst, .. } => {
3933 let ka = cells.len();
3934 cells.push(LoopCell::HeapAlloc {
3935 size: Src::Const(2 + 64),
3936 }); let ptr = Src::Val(ka);
3938 cells.push(LoopCell::HeapWrite {
3939 addr: ptr,
3940 val: Src::Const(0),
3941 }); let kh1 = cells.len();
3943 cells.push(LoopCell::Arith {
3944 op: AluOp::Add,
3945 a: ptr,
3946 b: Src::Const(1),
3947 });
3948 cells.push(LoopCell::HeapWrite {
3949 addr: Src::Val(kh1),
3950 val: Src::Const(0),
3951 }); reg.insert(*dst, ptr);
3953 }
3954 Op::ChanSend { chan, val } => {
3956 let ptr = getb(reg, *chan);
3957 let v = getb(reg, *val);
3958 let kca = cells.len(); cells.push(LoopCell::Arith {
3960 op: AluOp::Add,
3961 a: ptr,
3962 b: Src::Const(1),
3963 });
3964 let kc = cells.len();
3965 cells.push(LoopCell::HeapRead {
3966 addr: Src::Val(kca),
3967 });
3968 let count = Src::Val(kc);
3969 let kp2 = cells.len(); cells.push(LoopCell::Arith {
3971 op: AluOp::Add,
3972 a: ptr,
3973 b: Src::Const(2),
3974 });
3975 let kaddr = cells.len();
3976 cells.push(LoopCell::Arith {
3977 op: AluOp::Add,
3978 a: Src::Val(kp2),
3979 b: count,
3980 });
3981 cells.push(LoopCell::HeapWrite {
3982 addr: Src::Val(kaddr),
3983 val: v,
3984 });
3985 let knc = cells.len();
3986 cells.push(LoopCell::Arith {
3987 op: AluOp::Add,
3988 a: count,
3989 b: Src::Const(1),
3990 });
3991 cells.push(LoopCell::HeapWrite {
3992 addr: Src::Val(kca),
3993 val: Src::Val(knc),
3994 }); }
3996 Op::ChanRecv { dst, chan } | Op::ChanTryRecv { dst, chan } => {
4001 let ptr = getb(reg, *chan);
4002 let krc = cells.len();
4003 cells.push(LoopCell::HeapRead { addr: ptr }); let rc = Src::Val(krc);
4005 let kp2 = cells.len(); cells.push(LoopCell::Arith {
4007 op: AluOp::Add,
4008 a: ptr,
4009 b: Src::Const(2),
4010 });
4011 let kaddr = cells.len();
4012 cells.push(LoopCell::Arith {
4013 op: AluOp::Add,
4014 a: Src::Val(kp2),
4015 b: rc,
4016 });
4017 let kv = cells.len();
4018 cells.push(LoopCell::HeapRead {
4019 addr: Src::Val(kaddr),
4020 });
4021 let knc = cells.len();
4022 cells.push(LoopCell::Arith {
4023 op: AluOp::Add,
4024 a: rc,
4025 b: Src::Const(1),
4026 });
4027 cells.push(LoopCell::HeapWrite {
4028 addr: ptr,
4029 val: Src::Val(knc),
4030 }); reg.insert(*dst, Src::Val(kv));
4032 }
4033 Op::ChanTrySend { dst, chan, val } => {
4036 let ptr = getb(reg, *chan);
4037 let v = getb(reg, *val);
4038 let kca = cells.len();
4039 cells.push(LoopCell::Arith {
4040 op: AluOp::Add,
4041 a: ptr,
4042 b: Src::Const(1),
4043 });
4044 let kc = cells.len();
4045 cells.push(LoopCell::HeapRead {
4046 addr: Src::Val(kca),
4047 });
4048 let count = Src::Val(kc);
4049 let kp2 = cells.len();
4050 cells.push(LoopCell::Arith {
4051 op: AluOp::Add,
4052 a: ptr,
4053 b: Src::Const(2),
4054 });
4055 let kaddr = cells.len();
4056 cells.push(LoopCell::Arith {
4057 op: AluOp::Add,
4058 a: Src::Val(kp2),
4059 b: count,
4060 });
4061 cells.push(LoopCell::HeapWrite {
4062 addr: Src::Val(kaddr),
4063 val: v,
4064 });
4065 let knc = cells.len();
4066 cells.push(LoopCell::Arith {
4067 op: AluOp::Add,
4068 a: count,
4069 b: Src::Const(1),
4070 });
4071 cells.push(LoopCell::HeapWrite {
4072 addr: Src::Val(kca),
4073 val: Src::Val(knc),
4074 });
4075 reg.insert(*dst, Src::Const(1)); }
4077 Op::ChanClose { .. } => {}
4082 Op::NetConnect { .. } | Op::NetStream { .. } | Op::NetSync { .. } => {}
4086 Op::NetListen { .. } => {
4088 if NET_INBOX.with(|n| n.borrow().is_none()) {
4089 let ka = cells.len();
4090 cells.push(LoopCell::HeapAlloc {
4091 size: Src::Const(2 + 64),
4092 });
4093 let ptr = Src::Val(ka);
4094 cells.push(LoopCell::HeapWrite {
4095 addr: ptr,
4096 val: Src::Const(0),
4097 }); let kh1 = cells.len();
4099 cells.push(LoopCell::Arith {
4100 op: AluOp::Add,
4101 a: ptr,
4102 b: Src::Const(1),
4103 });
4104 cells.push(LoopCell::HeapWrite {
4105 addr: Src::Val(kh1),
4106 val: Src::Const(0),
4107 }); NET_INBOX.with(|n| *n.borrow_mut() = Some(ptr));
4109 }
4110 }
4111 Op::NetSend { msg, .. } => {
4114 if let Some(ptr) = NET_INBOX.with(|n| *n.borrow()) {
4115 let v = getb(reg, *msg);
4116 let kca = cells.len(); cells.push(LoopCell::Arith {
4118 op: AluOp::Add,
4119 a: ptr,
4120 b: Src::Const(1),
4121 });
4122 let kc = cells.len();
4123 cells.push(LoopCell::HeapRead {
4124 addr: Src::Val(kca),
4125 });
4126 let count = Src::Val(kc);
4127 let kp2 = cells.len(); cells.push(LoopCell::Arith {
4129 op: AluOp::Add,
4130 a: ptr,
4131 b: Src::Const(2),
4132 });
4133 let kaddr = cells.len();
4134 cells.push(LoopCell::Arith {
4135 op: AluOp::Add,
4136 a: Src::Val(kp2),
4137 b: count,
4138 });
4139 cells.push(LoopCell::HeapWrite {
4140 addr: Src::Val(kaddr),
4141 val: v,
4142 });
4143 let knc = cells.len();
4144 cells.push(LoopCell::Arith {
4145 op: AluOp::Add,
4146 a: count,
4147 b: Src::Const(1),
4148 });
4149 cells.push(LoopCell::HeapWrite {
4150 addr: Src::Val(kca),
4151 val: Src::Val(knc),
4152 }); }
4154 }
4155 Op::NetAwait { dst, .. } => {
4157 let ptr = NET_INBOX
4158 .with(|n| *n.borrow())
4159 .expect("fabric NetAwait: no inbox — an Await needs a prior Listen");
4160 let krc = cells.len();
4161 cells.push(LoopCell::HeapRead { addr: ptr }); let rc = Src::Val(krc);
4163 let kp2 = cells.len(); cells.push(LoopCell::Arith {
4165 op: AluOp::Add,
4166 a: ptr,
4167 b: Src::Const(2),
4168 });
4169 let kaddr = cells.len();
4170 cells.push(LoopCell::Arith {
4171 op: AluOp::Add,
4172 a: Src::Val(kp2),
4173 b: rc,
4174 });
4175 let kv = cells.len();
4176 cells.push(LoopCell::HeapRead {
4177 addr: Src::Val(kaddr),
4178 });
4179 let knc = cells.len();
4180 cells.push(LoopCell::Arith {
4181 op: AluOp::Add,
4182 a: rc,
4183 b: Src::Const(1),
4184 });
4185 cells.push(LoopCell::HeapWrite {
4186 addr: ptr,
4187 val: Src::Val(knc),
4188 }); reg.insert(*dst, Src::Val(kv));
4190 }
4191 Op::NetMakePeer { dst, addr } => {
4194 let a = getb(reg, *addr);
4195 reg.insert(*dst, a);
4196 }
4197 Op::SelectArmRecv { chan, var } => {
4200 let ch = getb(reg, *chan);
4201 SELECT_ARMS.with(|a| a.borrow_mut().push(Some((ch, *var))));
4202 }
4203 Op::SelectArmTimeout { .. } => {
4204 SELECT_ARMS.with(|a| a.borrow_mut().push(None));
4205 }
4206 Op::SelectWait { dst_arm } => {
4211 let arms = SELECT_ARMS.with(|a| std::mem::take(&mut *a.borrow_mut()));
4212 let mut info: Vec<Option<(Src, Src, Src, Src, u16)>> = Vec::new();
4214 for arm in &arms {
4215 match arm {
4216 Some((ptr, var)) => {
4217 let ptr = *ptr;
4218 let krc = cells.len();
4219 cells.push(LoopCell::HeapRead { addr: ptr }); let rc = Src::Val(krc);
4221 let kca = cells.len();
4222 cells.push(LoopCell::Arith {
4223 op: AluOp::Add,
4224 a: ptr,
4225 b: Src::Const(1),
4226 });
4227 let kc = cells.len();
4228 cells.push(LoopCell::HeapRead {
4229 addr: Src::Val(kca),
4230 }); let krdy = cells.len(); cells.push(LoopCell::Select {
4233 cmp: CmpKind::Gt,
4234 ca: Src::Val(kc),
4235 cb: rc,
4236 t: Src::Const(1),
4237 e: Src::Const(0),
4238 });
4239 let kp2 = cells.len();
4240 cells.push(LoopCell::Arith {
4241 op: AluOp::Add,
4242 a: ptr,
4243 b: Src::Const(2),
4244 });
4245 let kaddr = cells.len();
4246 cells.push(LoopCell::Arith {
4247 op: AluOp::Add,
4248 a: Src::Val(kp2),
4249 b: rc,
4250 });
4251 let kval = cells.len();
4252 cells.push(LoopCell::HeapRead {
4253 addr: Src::Val(kaddr),
4254 }); info.push(Some((ptr, rc, Src::Val(krdy), Src::Val(kval), *var)));
4256 }
4257 None => info.push(None),
4258 }
4259 }
4260 let timeout_idx = arms.iter().position(Option::is_none).map(|i| i as i64);
4263 let mut winner = Src::Const(timeout_idx.unwrap_or(0));
4264 for (i, arm) in info.iter().enumerate().rev() {
4265 if let Some((_, _, ready, _, _)) = arm {
4266 let kw = cells.len();
4267 cells.push(LoopCell::Select {
4268 cmp: CmpKind::Ne,
4269 ca: *ready,
4270 cb: Src::Const(0),
4271 t: Src::Const(i as i64),
4272 e: winner,
4273 });
4274 winner = Src::Val(kw);
4275 }
4276 }
4277 reg.insert(*dst_arm, winner);
4278 for (i, arm) in info.iter().enumerate() {
4280 if let Some((ptr, rc, _, val, var)) = arm {
4281 reg.insert(*var, *val);
4282 let kwon = cells.len(); cells.push(LoopCell::Select {
4284 cmp: CmpKind::Eq,
4285 ca: winner,
4286 cb: Src::Const(i as i64),
4287 t: Src::Const(1),
4288 e: Src::Const(0),
4289 });
4290 let krc1 = cells.len();
4291 cells.push(LoopCell::Arith {
4292 op: AluOp::Add,
4293 a: *rc,
4294 b: Src::Const(1),
4295 });
4296 let knew = cells.len(); cells.push(LoopCell::Select {
4298 cmp: CmpKind::Ne,
4299 ca: Src::Val(kwon),
4300 cb: Src::Const(0),
4301 t: Src::Val(krc1),
4302 e: *rc,
4303 });
4304 cells.push(LoopCell::HeapWrite {
4305 addr: *ptr,
4306 val: Src::Val(knew),
4307 });
4308 }
4309 }
4310 }
4311 Op::EnsureOwned { reg: r } => {
4317 if let Some(len) = COLL_LEN.with(|m| m.borrow().get(r).copied()) {
4318 let sptr = getb(reg, *r);
4319 let ka = cells.len();
4320 cells.push(LoopCell::HeapAlloc {
4321 size: Src::Const(len + 1),
4322 });
4323 let dptr = Src::Val(ka);
4324 cells.push(LoopCell::HeapWrite {
4325 addr: dptr,
4326 val: Src::Const(len),
4327 }); for i in 0..len {
4329 let ksa = cells.len();
4330 cells.push(LoopCell::Arith {
4331 op: AluOp::Add,
4332 a: sptr,
4333 b: Src::Const(i + 1),
4334 });
4335 let ks = cells.len();
4336 cells.push(LoopCell::HeapRead {
4337 addr: Src::Val(ksa),
4338 });
4339 let kda = cells.len();
4340 cells.push(LoopCell::Arith {
4341 op: AluOp::Add,
4342 a: dptr,
4343 b: Src::Const(i + 1),
4344 });
4345 cells.push(LoopCell::HeapWrite {
4346 addr: Src::Val(kda),
4347 val: Src::Val(ks),
4348 });
4349 }
4350 reg.insert(*r, dptr); }
4352 }
4353 Op::DeepClone { dst, src } => {
4356 let sptr = getb(reg, *src);
4357 let len = COLL_LEN
4358 .with(|m| m.borrow().get(src).copied())
4359 .expect("fabric DeepClone: source length not statically known");
4360 let ka = cells.len();
4361 cells.push(LoopCell::HeapAlloc {
4362 size: Src::Const(len + 1),
4363 });
4364 let dptr = Src::Val(ka);
4365 cells.push(LoopCell::HeapWrite {
4366 addr: dptr,
4367 val: Src::Const(len),
4368 }); for i in 0..len {
4370 let ksa = cells.len(); cells.push(LoopCell::Arith {
4372 op: AluOp::Add,
4373 a: sptr,
4374 b: Src::Const(i + 1),
4375 });
4376 let ks = cells.len();
4377 cells.push(LoopCell::HeapRead {
4378 addr: Src::Val(ksa),
4379 });
4380 let kda = cells.len(); cells.push(LoopCell::Arith {
4382 op: AluOp::Add,
4383 a: dptr,
4384 b: Src::Const(i + 1),
4385 });
4386 cells.push(LoopCell::HeapWrite {
4387 addr: Src::Val(kda),
4388 val: Src::Val(ks),
4389 });
4390 }
4391 reg.insert(*dst, dptr);
4392 COLL_LEN.with(|m| m.borrow_mut().insert(*dst, len));
4393 }
4394 Op::SeqConcat { dst, lhs, rhs } => {
4397 let lptr = getb(reg, *lhs);
4398 let rptr = getb(reg, *rhs);
4399 let ll = COLL_LEN
4400 .with(|m| m.borrow().get(lhs).copied())
4401 .expect("fabric SeqConcat: left length not statically known");
4402 let lr = COLL_LEN
4403 .with(|m| m.borrow().get(rhs).copied())
4404 .expect("fabric SeqConcat: right length not statically known");
4405 let total = ll + lr;
4406 let ka = cells.len();
4407 cells.push(LoopCell::HeapAlloc {
4408 size: Src::Const(total + 1),
4409 });
4410 let dptr = Src::Val(ka);
4411 cells.push(LoopCell::HeapWrite {
4412 addr: dptr,
4413 val: Src::Const(total),
4414 });
4415 for (base_ptr, off, count) in [(lptr, 0i64, ll), (rptr, ll, lr)] {
4417 for i in 0..count {
4418 let ksa = cells.len();
4419 cells.push(LoopCell::Arith {
4420 op: AluOp::Add,
4421 a: base_ptr,
4422 b: Src::Const(i + 1),
4423 });
4424 let ks = cells.len();
4425 cells.push(LoopCell::HeapRead {
4426 addr: Src::Val(ksa),
4427 });
4428 let kda = cells.len();
4429 cells.push(LoopCell::Arith {
4430 op: AluOp::Add,
4431 a: dptr,
4432 b: Src::Const(off + i + 1),
4433 });
4434 cells.push(LoopCell::HeapWrite {
4435 addr: Src::Val(kda),
4436 val: Src::Val(ks),
4437 });
4438 }
4439 }
4440 reg.insert(*dst, dptr);
4441 COLL_LEN.with(|m| m.borrow_mut().insert(*dst, total));
4442 }
4443 Op::SliceOp {
4447 dst,
4448 collection,
4449 start,
4450 end,
4451 } => {
4452 let cptr = getb(reg, *collection);
4453 let len = COLL_LEN
4454 .with(|m| m.borrow().get(collection).copied())
4455 .expect("fabric SliceOp: collection length not statically known");
4456 let s = match getb(reg, *start) {
4457 Src::Const(v) => v,
4458 _ => panic!("fabric SliceOp: non-constant start bound (a runtime bound needs a sequential unit)"),
4459 };
4460 let e = match getb(reg, *end) {
4461 Src::Const(v) => v,
4462 _ => panic!("fabric SliceOp: non-constant end bound (a runtime bound needs a sequential unit)"),
4463 };
4464 let start0 = (s - 1).max(0);
4465 let count = if start0 < e && e <= len {
4466 e - start0
4467 } else {
4468 0
4469 };
4470 let ka = cells.len();
4471 cells.push(LoopCell::HeapAlloc {
4472 size: Src::Const(count + 1),
4473 });
4474 let dptr = Src::Val(ka);
4475 cells.push(LoopCell::HeapWrite {
4476 addr: dptr,
4477 val: Src::Const(count),
4478 });
4479 for j in 0..count {
4480 let ksa = cells.len(); cells.push(LoopCell::Arith {
4482 op: AluOp::Add,
4483 a: cptr,
4484 b: Src::Const(start0 + j + 1),
4485 });
4486 let ks = cells.len();
4487 cells.push(LoopCell::HeapRead {
4488 addr: Src::Val(ksa),
4489 });
4490 let kda = cells.len();
4491 cells.push(LoopCell::Arith {
4492 op: AluOp::Add,
4493 a: dptr,
4494 b: Src::Const(j + 1),
4495 });
4496 cells.push(LoopCell::HeapWrite {
4497 addr: Src::Val(kda),
4498 val: Src::Val(ks),
4499 });
4500 }
4501 reg.insert(*dst, dptr);
4502 COLL_LEN.with(|m| m.borrow_mut().insert(*dst, count));
4503 }
4504 Op::NewStruct { dst, type_name } => {
4508 let n = STRUCT_FIELDS.with(|m| m.borrow().get(type_name).copied().unwrap_or(0));
4509 let ka = cells.len();
4510 cells.push(LoopCell::HeapAlloc {
4511 size: Src::Const(n as i64 + 1),
4512 });
4513 let ptr = Src::Val(ka);
4514 cells.push(LoopCell::HeapWrite {
4515 addr: ptr,
4516 val: Src::Const(n as i64),
4517 }); reg.insert(*dst, ptr);
4519 REG_TYPE.with(|m| m.borrow_mut().insert(*dst, *type_name)); }
4521 Op::GetField { dst, obj, field } => {
4525 let ptr = getb(reg, *obj);
4526 let slot = struct_field_slot(*obj, *field);
4527 let kadd = cells.len();
4528 cells.push(LoopCell::Arith {
4529 op: AluOp::Add,
4530 a: ptr,
4531 b: Src::Const(slot as i64 + 1),
4532 });
4533 let kr = cells.len();
4534 cells.push(LoopCell::HeapRead {
4535 addr: Src::Val(kadd),
4536 });
4537 reg.insert(*dst, Src::Val(kr));
4538 }
4539 Op::StructInsert { obj, field, value } => {
4540 let ptr = getb(reg, *obj);
4541 let val = getb(reg, *value);
4542 let slot = struct_field_slot(*obj, *field);
4543 let kadd = cells.len();
4544 cells.push(LoopCell::Arith {
4545 op: AluOp::Add,
4546 a: ptr,
4547 b: Src::Const(slot as i64 + 1),
4548 });
4549 cells.push(LoopCell::HeapWrite {
4550 addr: Src::Val(kadd),
4551 val,
4552 });
4553 }
4554 Op::CrdtBump {
4558 obj,
4559 field,
4560 amount,
4561 negate,
4562 } => {
4563 let ptr = getb(reg, *obj);
4564 let slot = struct_field_slot(*obj, *field);
4565 let amt = getb(reg, *amount);
4566 let kaddr = cells.len();
4567 cells.push(LoopCell::Arith {
4568 op: AluOp::Add,
4569 a: ptr,
4570 b: Src::Const(slot as i64 + 1),
4571 });
4572 let kcur = cells.len();
4573 cells.push(LoopCell::HeapRead {
4574 addr: Src::Val(kaddr),
4575 });
4576 let knew = cells.len();
4577 cells.push(LoopCell::Arith {
4578 op: if *negate { AluOp::Sub } else { AluOp::Add },
4579 a: Src::Val(kcur),
4580 b: amt,
4581 });
4582 cells.push(LoopCell::HeapWrite {
4583 addr: Src::Val(kaddr),
4584 val: Src::Val(knew),
4585 });
4586 }
4587 Op::CrdtResolve { obj, field, value } => {
4590 let ptr = getb(reg, *obj);
4591 let slot = struct_field_slot(*obj, *field);
4592 let val = getb(reg, *value);
4593 let kadd = cells.len();
4594 cells.push(LoopCell::Arith {
4595 op: AluOp::Add,
4596 a: ptr,
4597 b: Src::Const(slot as i64 + 1),
4598 });
4599 cells.push(LoopCell::HeapWrite {
4600 addr: Src::Val(kadd),
4601 val,
4602 });
4603 }
4604 Op::CrdtMerge { target, source } => {
4608 let tptr = getb(reg, *target);
4609 let sptr = getb(reg, *source);
4610 let tn = REG_TYPE
4611 .with(|m| m.borrow().get(target).copied())
4612 .expect("fabric CrdtMerge: target struct type not tracked");
4613 let fcount = STRUCT_FIELDS
4614 .with(|m| m.borrow().get(&tn).copied())
4615 .expect("fabric CrdtMerge: struct field count not known");
4616 for i in 0..fcount {
4617 let kta = cells.len(); cells.push(LoopCell::Arith {
4619 op: AluOp::Add,
4620 a: tptr,
4621 b: Src::Const(i as i64 + 1),
4622 });
4623 let ktc = cells.len();
4624 cells.push(LoopCell::HeapRead {
4625 addr: Src::Val(kta),
4626 });
4627 let ksa = cells.len(); cells.push(LoopCell::Arith {
4629 op: AluOp::Add,
4630 a: sptr,
4631 b: Src::Const(i as i64 + 1),
4632 });
4633 let ksv = cells.len();
4634 cells.push(LoopCell::HeapRead {
4635 addr: Src::Val(ksa),
4636 });
4637 let ksum = cells.len();
4638 cells.push(LoopCell::Arith {
4639 op: AluOp::Add,
4640 a: Src::Val(ktc),
4641 b: Src::Val(ksv),
4642 });
4643 cells.push(LoopCell::HeapWrite {
4644 addr: Src::Val(kta),
4645 val: Src::Val(ksum),
4646 });
4647 }
4648 }
4649 Op::CrdtJoin { target, source } => {
4653 let tptr = getb(reg, *target);
4654 let sptr = getb(reg, *source);
4655 let tn = REG_TYPE
4656 .with(|m| m.borrow().get(target).copied())
4657 .expect("fabric CrdtJoin: target struct type not tracked");
4658 let fcount = STRUCT_FIELDS
4659 .with(|m| m.borrow().get(&tn).copied())
4660 .expect("fabric CrdtJoin: struct field count not known");
4661 for i in 0..fcount {
4662 let kta = cells.len(); cells.push(LoopCell::Arith {
4664 op: AluOp::Add,
4665 a: tptr,
4666 b: Src::Const(i as i64 + 1),
4667 });
4668 let ktc = cells.len();
4669 cells.push(LoopCell::HeapRead {
4670 addr: Src::Val(kta),
4671 });
4672 let ksa = cells.len(); cells.push(LoopCell::Arith {
4674 op: AluOp::Add,
4675 a: sptr,
4676 b: Src::Const(i as i64 + 1),
4677 });
4678 let ksv = cells.len();
4679 cells.push(LoopCell::HeapRead {
4680 addr: Src::Val(ksa),
4681 });
4682 let kmax = cells.len(); cells.push(LoopCell::Select {
4684 cmp: CmpKind::Gt,
4685 ca: Src::Val(ktc),
4686 cb: Src::Val(ksv),
4687 t: Src::Val(ktc),
4688 e: Src::Val(ksv),
4689 });
4690 cells.push(LoopCell::HeapWrite {
4691 addr: Src::Val(kta),
4692 val: Src::Val(kmax),
4693 });
4694 }
4695 }
4696 Op::NewInductive {
4700 dst,
4701 type_name: _,
4702 ctor,
4703 args_start,
4704 count,
4705 } => {
4706 let ka = cells.len();
4707 cells.push(LoopCell::HeapAlloc {
4708 size: Src::Const(*count as i64 + 1),
4709 });
4710 let ptr = Src::Val(ka);
4711 let tag = canonical_text_tag(consts, *ctor);
4712 cells.push(LoopCell::HeapWrite {
4713 addr: ptr,
4714 val: Src::Const(tag),
4715 }); for k in 0..*count {
4717 let av = getb(reg, *args_start + k);
4718 let kadd = cells.len();
4719 cells.push(LoopCell::Arith {
4720 op: AluOp::Add,
4721 a: ptr,
4722 b: Src::Const(k as i64 + 1),
4723 });
4724 cells.push(LoopCell::HeapWrite {
4725 addr: Src::Val(kadd),
4726 val: av,
4727 }); }
4729 reg.insert(*dst, ptr);
4730 }
4731 Op::TestArm {
4734 dst,
4735 target,
4736 variant,
4737 } => {
4738 let ptr = getb(reg, *target);
4739 let ktag = cells.len();
4740 cells.push(LoopCell::HeapRead { addr: ptr }); let vtag = canonical_text_tag(consts, *variant);
4742 preds.insert(*dst, (CmpKind::Eq, Src::Val(ktag), Src::Const(vtag)));
4743 }
4744 Op::BindArm {
4747 dst,
4748 target,
4749 field,
4750 index,
4751 } => {
4752 let ptr = getb(reg, *target);
4753 let is_struct = REG_TYPE.with(|m| m.borrow().contains_key(target));
4754 let slot = if is_struct {
4755 struct_field_slot(*target, *field)
4756 } else {
4757 *index as usize
4758 };
4759 let kadd = cells.len();
4760 cells.push(LoopCell::Arith {
4761 op: AluOp::Add,
4762 a: ptr,
4763 b: Src::Const(slot as i64 + 1),
4764 });
4765 let kr = cells.len();
4766 cells.push(LoopCell::HeapRead {
4767 addr: Src::Val(kadd),
4768 });
4769 reg.insert(*dst, Src::Val(kr));
4770 }
4771 Op::Call {
4774 dst,
4775 func,
4776 args_start,
4777 arg_count,
4778 } => {
4779 let arg_srcs: Vec<Src> = (0..*arg_count)
4780 .map(|k| getb(reg, *args_start + k))
4781 .collect();
4782 let result = inline_callee(*func as usize, &arg_srcs, cells, consts);
4783 reg.insert(*dst, result);
4784 }
4785 Op::MakeClosure {
4790 dst,
4791 func,
4792 locals_start,
4793 } => {
4794 let layout = CLOSURE_CAPTURES
4795 .with(|m| m.borrow().get(*func as usize).cloned().unwrap_or_default());
4796 let mut captures: Vec<(Src, bool)> = Vec::with_capacity(layout.len());
4797 let mut local_k: u16 = 0;
4798 for cap in &layout {
4799 match cap {
4800 None => {
4801 captures.push((getb(reg, *locals_start + local_k), true)); local_k += 1;
4803 }
4804 Some(gidx) => match GLOBALS.with(|g| g.borrow().get(gidx).copied()) {
4805 Some(slot) => captures.push((slot.src, true)),
4806 None => captures.push((Src::Const(0), false)), },
4808 }
4809 }
4810 CLOSURES.with(|c| c.borrow_mut().insert(*dst, (*func as usize, captures)));
4811 }
4812 Op::CallValue {
4815 dst,
4816 callee,
4817 args_start,
4818 arg_count,
4819 ..
4820 } => {
4821 let (func, captures) = CLOSURES
4822 .with(|c| c.borrow().get(callee).cloned())
4823 .expect("fabric CallValue: callee is not a tracked closure (dynamic-from-data dispatch is unsupported)");
4824 let arg_srcs: Vec<Src> = (0..*arg_count)
4825 .map(|k| getb(reg, *args_start + k))
4826 .collect();
4827 let result = inline_closure(func, &arg_srcs, &captures, cells, consts);
4828 reg.insert(*dst, result);
4829 }
4830 Op::Spawn {
4835 func,
4836 args_start,
4837 arg_count,
4838 } => {
4839 let args: Vec<Src> = (0..*arg_count)
4840 .map(|k| getb(reg, *args_start + k))
4841 .collect();
4842 inline_spawn(*func as usize, &args, cells, consts);
4843 }
4844 Op::SpawnHandle {
4845 dst,
4846 func,
4847 args_start,
4848 arg_count,
4849 } => {
4850 let args: Vec<Src> = (0..*arg_count)
4851 .map(|k| getb(reg, *args_start + k))
4852 .collect();
4853 inline_spawn(*func as usize, &args, cells, consts);
4854 reg.insert(*dst, Src::Const(0)); }
4856 Op::TaskAwait { dst, .. } => {
4857 reg.insert(*dst, Src::Const(0));
4860 }
4861 Op::TaskAbort { .. } => {}
4862 Op::GlobalSet { idx, src } => {
4866 let s = getb(reg, *src);
4867 let len = COLL_LEN.with(|m| m.borrow().get(src).copied());
4868 let text = TEXT_REGS.with(|t| t.borrow().contains(src));
4869 let ty = REG_TYPE.with(|m| m.borrow().get(src).copied());
4870 GLOBALS.with(|g| {
4871 g.borrow_mut().insert(
4872 *idx,
4873 GSlot {
4874 src: s,
4875 len,
4876 text,
4877 ty,
4878 },
4879 )
4880 });
4881 }
4882 Op::GlobalGet { dst, idx } => {
4884 let slot = GLOBALS
4885 .with(|g| g.borrow().get(idx).copied())
4886 .unwrap_or_else(|| {
4887 panic!("fabric GlobalGet: global #{idx} read before it was set")
4888 });
4889 reg.insert(*dst, slot.src);
4890 if let Some(l) = slot.len {
4891 COLL_LEN.with(|m| {
4892 m.borrow_mut().insert(*dst, l);
4893 });
4894 }
4895 if slot.text {
4896 TEXT_REGS.with(|t| {
4897 t.borrow_mut().insert(*dst);
4898 });
4899 }
4900 if let Some(t) = slot.ty {
4901 REG_TYPE.with(|m| {
4902 m.borrow_mut().insert(*dst, t);
4903 });
4904 }
4905 }
4906 Op::Sleep { .. } => {}
4910 Op::RegionBoundsGuard { .. } => {}
4914 Op::IterPop => {}
4918 Op::Obs { .. } => {}
4922 Op::FailWith { .. } => {
4926 HALTED.with(|h| h.set(true));
4927 }
4928 Op::Show { src } => {
4931 assert!(
4932 !FLOAT_REGS.with(|t| t.borrow().contains(src)),
4933 "fabric Show: rendering a FLOAT to a string (IEEE float→decimal) is a follow-on — the \
4934 float lane is witnessed through int-returning results (floor/compare), not Show-of-float"
4935 );
4936 assert!(
4937 !BIGINT_BIG.with(|m| m.borrow().contains_key(src)),
4938 "fabric Show: rendering a BigInt to a string is a follow-on — the BigInt lane is witnessed \
4939 through `Low` (an Int), not Show-of-BigInt"
4940 );
4941 if !HALTED.with(std::cell::Cell::get) {
4942 let s = getb(reg, *src);
4943 let is_text = TEXT_REGS.with(|t| t.borrow().contains(src));
4944 let guard = POLICY_GUARD.with(|g| g.borrow().clone());
4948 SHOW_OUTPUTS.with(|o| o.borrow_mut().push((s, is_text, guard)));
4949 }
4950 }
4951 Op::CheckPolicy {
4960 subject,
4961 predicate,
4962 is_capability,
4963 object,
4964 source_text,
4965 } => {
4966 let _ = source_text; assert!(
4968 *object == crate::vm::Reg::MAX,
4969 "fabric CheckPolicy: an object-bearing (cross-field) capability is a follow-on — the \
4970 modeled conditions are single-field FieldBool/FieldEquals only; refusing rather than \
4971 silently granting"
4972 );
4973 let tn = REG_TYPE
4974 .with(|m| m.borrow().get(subject).copied())
4975 .expect("fabric CheckPolicy: subject register has no tracked struct type");
4976 let key = (tn, predicate.index() as u32, *is_capability);
4977 let spec = POLICY_CONDITIONS
4978 .with(|m| m.borrow().get(&key).cloned())
4979 .unwrap_or_else(|| {
4980 panic!(
4981 "fabric CheckPolicy: no MODELED condition for (type-const {tn}, predicate \
4982 {predicate:?}, capability {is_capability}) — a compound (And/Or) / object-cross- \
4983 field / predicate-indirection condition is a follow-on; refusing rather than \
4984 silently granting (a wrong grant is a security hole)"
4985 )
4986 });
4987 let field_val = |cells: &mut Vec<LoopCell>, slot: usize| -> Src {
4989 let ptr = getb(reg, *subject);
4990 let kaddr = cells.len();
4991 cells.push(LoopCell::Arith {
4992 op: AluOp::Add,
4993 a: ptr,
4994 b: Src::Const(slot as i64 + 1),
4995 });
4996 let kfield = cells.len();
4997 cells.push(LoopCell::HeapRead {
4998 addr: Src::Val(kaddr),
4999 });
5000 Src::Val(kfield)
5001 };
5002 let grant = match spec {
5003 PolicyGrant::AlwaysDeny => Src::Const(0),
5005 PolicyGrant::IntEq { slot, expected } => {
5007 let fv = field_val(cells, slot);
5008 let kg = cells.len();
5009 cells.push(LoopCell::Select {
5010 cmp: CmpKind::Eq,
5011 ca: fv,
5012 cb: Src::Const(expected),
5013 t: Src::Const(1),
5014 e: Src::Const(0),
5015 });
5016 Src::Val(kg)
5017 }
5018 PolicyGrant::TextEq { slot, expected } => {
5022 let strptr = field_val(cells, slot);
5023 let klen = cells.len();
5024 cells.push(LoopCell::HeapRead { addr: strptr }); let klenchk = cells.len();
5026 cells.push(LoopCell::Select {
5027 cmp: CmpKind::Eq,
5028 ca: Src::Val(klen),
5029 cb: Src::Const(expected.len() as i64),
5030 t: Src::Const(1),
5031 e: Src::Const(0),
5032 });
5033 let mut acc = Src::Val(klenchk);
5034 for (i, b) in expected.iter().enumerate() {
5035 let kba = cells.len();
5036 cells.push(LoopCell::Arith {
5037 op: AluOp::Add,
5038 a: strptr,
5039 b: Src::Const(i as i64 + 1),
5040 });
5041 let kbv = cells.len();
5042 cells.push(LoopCell::HeapRead {
5043 addr: Src::Val(kba),
5044 });
5045 let kbc = cells.len();
5046 cells.push(LoopCell::Select {
5047 cmp: CmpKind::Eq,
5048 ca: Src::Val(kbv),
5049 cb: Src::Const(i64::from(*b)),
5050 t: Src::Const(1),
5051 e: Src::Const(0),
5052 });
5053 let kand = cells.len();
5054 cells.push(LoopCell::Arith {
5055 op: AluOp::Mul,
5056 a: acc,
5057 b: Src::Val(kbc),
5058 });
5059 acc = Src::Val(kand);
5060 }
5061 acc
5062 }
5063 };
5064 POLICY_GUARD.with(|g| {
5065 let mut g = g.borrow_mut();
5066 *g = Some(match g.take() {
5067 None => grant,
5068 Some(prev) => {
5070 let kand = cells.len();
5071 cells.push(LoopCell::Arith {
5072 op: AluOp::Mul,
5073 a: prev,
5074 b: grant,
5075 });
5076 Src::Val(kand)
5077 }
5078 });
5079 });
5080 }
5081 other => panic!(
5082 "loop body arm: `{}` unsupported (nested control flow is future work)",
5083 other.name()
5084 ),
5085 }
5086 }
5087}
5088
5089fn if_convert_body(
5094 body: &[(usize, crate::vm::Op)],
5095 reg: &mut std::collections::HashMap<u16, Src>,
5096 carried: &[u16],
5097 consts: &[crate::vm::Constant],
5098 seed_cells: Vec<LoopCell>,
5101) -> Vec<LoopCell> {
5102 use crate::vm::Op;
5103 let ops_of = |seg: &[(usize, Op)]| -> Vec<Op> { seg.iter().map(|(_, o)| *o).collect() };
5104 let idx_of_pc = |target: usize| {
5105 body.iter()
5106 .position(|(p, _)| *p == target)
5107 .unwrap_or(body.len())
5108 };
5109
5110 let mut cells: Vec<LoopCell> = seed_cells;
5111 let mut preds: std::collections::HashMap<u16, (CmpKind, Src, Src)> =
5112 std::collections::HashMap::new();
5113 let mut i = 0;
5114 while i < body.len() {
5115 if let (_pc, Op::JumpIfFalse { cond, target }) = body[i] {
5116 let (cmp, ca, cb) = *preds
5117 .get(&cond)
5118 .expect("loop fabric: JumpIfFalse condition is not a comparison");
5119 let target_idx = idx_of_pc(target);
5123 let (then_seg, else_seg, merge_idx) =
5124 if target_idx > i + 1 && matches!(body[target_idx - 1].1, Op::Jump { .. }) {
5125 let m = match body[target_idx - 1].1 {
5126 Op::Jump { target } => target,
5127 _ => unreachable!(),
5128 };
5129 (
5130 &body[i + 1..target_idx - 1],
5131 &body[target_idx..idx_of_pc(m)],
5132 idx_of_pc(m),
5133 )
5134 } else {
5135 (&body[i + 1..target_idx], &body[0..0], target_idx)
5136 };
5137 let entry = reg.clone();
5138 let mut then_reg = entry.clone();
5139 exec_ops(
5140 &ops_of(then_seg),
5141 &mut then_reg,
5142 &mut cells,
5143 &mut preds,
5144 consts,
5145 );
5146 let mut else_reg = entry;
5147 exec_ops(
5148 &ops_of(else_seg),
5149 &mut else_reg,
5150 &mut cells,
5151 &mut preds,
5152 consts,
5153 );
5154 for &r in carried {
5155 let t = then_reg[&r];
5156 let e = else_reg[&r];
5157 if t != e {
5158 let k = cells.len();
5159 cells.push(LoopCell::Select { cmp, ca, cb, t, e });
5160 reg.insert(r, Src::Val(k));
5161 } else {
5162 reg.insert(r, t);
5163 }
5164 }
5165 i = merge_idx;
5166 } else {
5167 exec_ops(&[body[i].1], reg, &mut cells, &mut preds, consts);
5168 i += 1;
5169 }
5170 }
5171 cells
5172}
5173
5174#[derive(Clone, Debug)]
5191pub struct DecisionFabric {
5192 pub n_in: usize,
5193 pub cells: Vec<LoopCell>,
5194 pub result: Src,
5195 pub escaped_first_return: bool,
5201}
5202
5203fn dec_convert(
5207 body: &[(usize, crate::vm::Op)],
5208 reg: &mut std::collections::HashMap<u16, Src>,
5209 cells: &mut Vec<LoopCell>,
5210 preds: &mut std::collections::HashMap<u16, (CmpKind, Src, Src)>,
5211 carried: &[u16],
5212 consts: &[crate::vm::Constant],
5213) {
5214 use crate::vm::Op;
5215 let idx_of_pc = |t: usize| body.iter().position(|(p, _)| *p == t).unwrap_or(body.len());
5216 let mut i = 0;
5217 while i < body.len() {
5218 match body[i].1 {
5219 Op::JumpIfFalse { cond, target } => {
5220 let (cmp, ca, cb) = preds.get(&cond).copied().unwrap_or_else(|| {
5224 (
5225 CmpKind::Ne,
5226 reg.get(&cond).copied().unwrap_or(Src::In(cond as usize)),
5227 Src::Const(0),
5228 )
5229 });
5230 let target_idx = idx_of_pc(target);
5231 let (then_seg, else_seg, merge_idx) =
5232 if target_idx > i + 1 && matches!(body[target_idx - 1].1, Op::Jump { .. }) {
5233 let m = match body[target_idx - 1].1 {
5234 Op::Jump { target } => target,
5235 _ => unreachable!(),
5236 };
5237 (
5238 &body[i + 1..target_idx - 1],
5239 &body[target_idx..idx_of_pc(m)],
5240 idx_of_pc(m),
5241 )
5242 } else {
5243 (&body[i + 1..target_idx], &body[0..0], target_idx)
5244 };
5245 let entry = reg.clone();
5246 let mut then_reg = entry.clone();
5247 dec_convert(then_seg, &mut then_reg, cells, preds, carried, consts);
5248 let mut else_reg = entry.clone();
5249 dec_convert(else_seg, &mut else_reg, cells, preds, carried, consts);
5250 for &r in carried {
5251 match (then_reg.get(&r).copied(), else_reg.get(&r).copied()) {
5257 (Some(t), Some(e)) if t != e => {
5258 let k = cells.len();
5259 cells.push(LoopCell::Select { cmp, ca, cb, t, e });
5260 reg.insert(r, Src::Val(k));
5261 }
5262 (Some(v), Some(_)) => {
5263 reg.insert(r, v);
5264 }
5265 (Some(v), None) | (None, Some(v)) => {
5266 reg.insert(r, v);
5267 }
5268 (None, None) => {}
5269 }
5270 }
5271 i = merge_idx;
5272 }
5273 Op::JumpIfTrue { cond, target } => {
5279 let (cmp, ca, cb) = preds.get(&cond).copied().unwrap_or_else(|| {
5280 (
5281 CmpKind::Ne,
5282 reg.get(&cond).copied().unwrap_or(Src::In(cond as usize)),
5283 Src::Const(0),
5284 )
5285 });
5286 let target_idx = idx_of_pc(target);
5287 let (false_seg, true_seg, merge_idx) =
5291 if target_idx > i + 1 && matches!(body[target_idx - 1].1, Op::Jump { .. }) {
5292 let m = match body[target_idx - 1].1 {
5293 Op::Jump { target } => target,
5294 _ => unreachable!(),
5295 };
5296 (
5297 &body[i + 1..target_idx - 1],
5298 &body[target_idx..idx_of_pc(m)],
5299 idx_of_pc(m),
5300 )
5301 } else {
5302 (&body[i + 1..target_idx], &body[0..0], target_idx)
5303 };
5304 let entry = reg.clone();
5305 let mut true_reg = entry.clone();
5306 dec_convert(true_seg, &mut true_reg, cells, preds, carried, consts);
5307 let mut false_reg = entry.clone();
5308 dec_convert(false_seg, &mut false_reg, cells, preds, carried, consts);
5309 for &r in carried {
5310 match (true_reg.get(&r).copied(), false_reg.get(&r).copied()) {
5313 (Some(t), Some(e)) if t != e => {
5314 let k = cells.len();
5315 cells.push(LoopCell::Select { cmp, ca, cb, t, e });
5316 reg.insert(r, Src::Val(k));
5317 }
5318 (Some(v), Some(_)) => {
5319 reg.insert(r, v);
5320 }
5321 (Some(v), None) | (None, Some(v)) => {
5322 reg.insert(r, v);
5323 }
5324 (None, None) => {}
5325 }
5326 }
5327 i = merge_idx;
5328 }
5329 Op::Jump { .. } | Op::Return { .. } | Op::ReturnNothing => {
5330 i += 1;
5331 }
5332 _ => {
5333 let start = i;
5334 while i < body.len()
5335 && !matches!(
5336 body[i].1,
5337 Op::JumpIfFalse { .. }
5338 | Op::JumpIfTrue { .. }
5339 | Op::Jump { .. }
5340 | Op::Return { .. }
5341 | Op::ReturnNothing
5342 )
5343 {
5344 i += 1;
5345 }
5346 let run: Vec<Op> = body[start..i].iter().map(|(_, o)| *o).collect();
5347 exec_ops(&run, reg, cells, preds, consts);
5348 }
5349 }
5350 }
5351}
5352
5353pub fn function_op_names(src: &str, fn_name: &str) -> Vec<&'static str> {
5358 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
5359 let (stmts, types, _pol) = parsed.expect("parse failed");
5360 let program = crate::vm::Compiler::compile_with_types(stmts, interner, Some(types))
5363 .expect("compile failed");
5364 let f = program
5365 .functions
5366 .iter()
5367 .find(|f| interner.resolve(f.name) == fn_name)
5368 .unwrap_or_else(|| panic!("function `{fn_name}` not found"));
5369 let mut names = Vec::new();
5370 for op in &program.code[f.entry_pc..] {
5371 names.push(op.name());
5372 if matches!(
5373 op,
5374 crate::vm::Op::Return { .. } | crate::vm::Op::ReturnNothing
5375 ) {
5376 break;
5377 }
5378 }
5379 names
5380 })
5381}
5382
5383pub fn whole_program_op_names(src: &str) -> Vec<&'static str> {
5388 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
5389 let (stmts, types, _pol) = parsed.expect("parse failed");
5390 let program = crate::vm::Compiler::compile_with_types(stmts, interner, Some(types))
5393 .expect("compile failed");
5394 program.code.iter().map(|op| op.name()).collect()
5395 })
5396}
5397
5398pub fn decision_fabric_from_source(src: &str, fn_name: &str) -> DecisionFabric {
5404 use crate::vm::Op;
5405 use std::collections::HashMap;
5406
5407 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
5408 let (stmts, types, _pol) = parsed.expect("parse failed");
5409 let program = crate::vm::Compiler::compile_with_types(stmts, interner, Some(types))
5412 .expect("compile failed");
5413 setup_program_thread_locals(&program);
5416 let f = program
5417 .functions
5418 .iter()
5419 .find(|f| interner.resolve(f.name) == fn_name)
5420 .unwrap_or_else(|| panic!("function `{fn_name}` not found"));
5421
5422 let mut body: Vec<(usize, Op)> = Vec::new();
5424 let mut ret_src: Option<u16> = None;
5425 for (off, op) in program.code[f.entry_pc..].iter().enumerate() {
5426 if let Op::Return { src } = op {
5427 ret_src = Some(*src);
5428 break;
5429 }
5430 if matches!(op, Op::ReturnNothing) {
5431 break;
5432 }
5433 body.push((f.entry_pc + off, *op));
5434 }
5435 let ret_src = ret_src.expect("decision fabric: function does not Return a value");
5436
5437 let body_end = f.entry_pc + body.len();
5442 let escaped_first_return = body.iter().any(|(_, op)| {
5443 let target = match op {
5444 Op::Jump { target } => Some(*target as usize),
5445 Op::JumpIfFalse { target, .. } | Op::JumpIfTrue { target, .. } => {
5446 Some(*target as usize)
5447 }
5448 _ => None,
5449 };
5450 target.is_some_and(|t| t > body_end)
5451 });
5452
5453 let carried = carried_regs(&body);
5454
5455 let mut reg: HashMap<u16, Src> = HashMap::new();
5456 for p in 0..f.param_count {
5457 reg.insert(p, Src::In(p as usize));
5458 if matches!(
5462 f.param_kinds.get(p as usize),
5463 Some(Some(crate::vm::ParamKind::Scalar(
5464 crate::vm::SlotKind::Float
5465 )))
5466 ) {
5467 FLOAT_REGS.with(|t| {
5468 t.borrow_mut().insert(p);
5469 });
5470 }
5471 }
5472 let mut cells: Vec<LoopCell> = Vec::new();
5473 let mut preds: HashMap<u16, (CmpKind, Src, Src)> = HashMap::new();
5474 dec_convert(
5475 &body,
5476 &mut reg,
5477 &mut cells,
5478 &mut preds,
5479 &carried,
5480 &program.constants,
5481 );
5482
5483 let result = *reg.get(&ret_src).unwrap_or_else(|| {
5484 panic!("decision fabric: returned register R{ret_src} is unwritten")
5485 });
5486 let (cells, result) = prune_dead_cells(&cells, result);
5487 DecisionFabric {
5488 n_in: f.param_count as usize,
5489 cells,
5490 result,
5491 escaped_first_return,
5492 }
5493 })
5494}
5495
5496fn prune_dead_cells(cells: &[LoopCell], result: Src) -> (Vec<LoopCell>, Src) {
5500 fn mark(s: Src, stack: &mut Vec<usize>) {
5501 if let Src::Val(k) = s {
5502 stack.push(k);
5503 }
5504 }
5505 let mut live = vec![false; cells.len()];
5506 let mut stack = Vec::new();
5507 mark(result, &mut stack);
5508 for (k, c) in cells.iter().enumerate() {
5512 if matches!(c, LoopCell::HeapWrite { .. } | LoopCell::HeapAlloc { .. }) {
5513 stack.push(k);
5514 }
5515 }
5516 while let Some(k) = stack.pop() {
5517 if live[k] {
5518 continue;
5519 }
5520 live[k] = true;
5521 match cells[k] {
5522 LoopCell::Arith { a, b, .. } | LoopCell::VectorArith { a, b, .. } => {
5523 mark(a, &mut stack);
5524 mark(b, &mut stack);
5525 }
5526 LoopCell::Func { a, b, .. } => {
5527 mark(a, &mut stack);
5528 mark(b, &mut stack);
5529 }
5530 LoopCell::MemRead { addr, .. } | LoopCell::HeapRead { addr, .. } => {
5531 mark(addr, &mut stack);
5532 }
5533 LoopCell::HeapWrite { addr, val } => {
5534 mark(addr, &mut stack);
5535 mark(val, &mut stack);
5536 }
5537 LoopCell::HeapAlloc { size } => {
5538 mark(size, &mut stack);
5539 }
5540 LoopCell::Select { ca, cb, t, e, .. } => {
5541 mark(ca, &mut stack);
5542 mark(cb, &mut stack);
5543 mark(t, &mut stack);
5544 mark(e, &mut stack);
5545 }
5546 }
5547 }
5548 let mut remap = vec![usize::MAX; cells.len()];
5549 let mut kept: Vec<LoopCell> = Vec::new();
5550 for (k, c) in cells.iter().enumerate() {
5551 if live[k] {
5552 remap[k] = kept.len();
5553 kept.push(*c);
5554 }
5555 }
5556 fn relabel(s: Src, remap: &[usize]) -> Src {
5557 match s {
5558 Src::Val(k) => Src::Val(remap[k]),
5559 other => other,
5560 }
5561 }
5562 for c in kept.iter_mut() {
5563 match c {
5564 LoopCell::Arith { a, b, .. } | LoopCell::VectorArith { a, b, .. } => {
5565 *a = relabel(*a, &remap);
5566 *b = relabel(*b, &remap);
5567 }
5568 LoopCell::Func { a, b, .. } => {
5569 *a = relabel(*a, &remap);
5570 *b = relabel(*b, &remap);
5571 }
5572 LoopCell::MemRead { addr, .. } | LoopCell::HeapRead { addr, .. } => {
5573 *addr = relabel(*addr, &remap);
5574 }
5575 LoopCell::HeapWrite { addr, val } => {
5576 *addr = relabel(*addr, &remap);
5577 *val = relabel(*val, &remap);
5578 }
5579 LoopCell::HeapAlloc { size } => {
5580 *size = relabel(*size, &remap);
5581 }
5582 LoopCell::Select { ca, cb, t, e, .. } => {
5583 *ca = relabel(*ca, &remap);
5584 *cb = relabel(*cb, &remap);
5585 *t = relabel(*t, &remap);
5586 *e = relabel(*e, &remap);
5587 }
5588 }
5589 }
5590 (kept, relabel(result, &remap))
5591}
5592
5593pub fn eval_decision_fabric(df: &DecisionFabric, inputs: &[i64]) -> i64 {
5596 let mut vals: Vec<i64> = Vec::with_capacity(df.cells.len());
5597 for c in &df.cells {
5598 let v = match c {
5599 LoopCell::Arith { op, a, b } => op.eval(a.eval(inputs, &vals), b.eval(inputs, &vals)),
5600 LoopCell::VectorArith { op, ty, a, b } => op.eval(
5601 *ty,
5602 a.eval(inputs, &vals) as u64,
5603 b.eval(inputs, &vals) as u64,
5604 ) as i64,
5605 LoopCell::Func { unit, a, b } => {
5606 unit.eval(a.eval(inputs, &vals), b.eval(inputs, &vals))
5607 }
5608 LoopCell::MemRead { .. }
5609 | LoopCell::HeapRead { .. }
5610 | LoopCell::HeapWrite { .. }
5611 | LoopCell::HeapAlloc { .. } => panic!(
5612 "MemRead reached an evaluator that does not thread the iterated sequence — \
5613 a sequence loop must be evaluated via eval_loop_fabric_seq"
5614 ),
5615 LoopCell::Select { cmp, ca, cb, t, e } => {
5616 if cmp.eval(ca.eval(inputs, &vals), cb.eval(inputs, &vals)) {
5617 t.eval(inputs, &vals)
5618 } else {
5619 e.eval(inputs, &vals)
5620 }
5621 }
5622 };
5623 vals.push(v);
5624 }
5625 df.result.eval(inputs, &vals)
5626}
5627
5628pub fn eval_decision_fabric_heap(df: &DecisionFabric, inputs: &[i64], heap: &mut Vec<i64>) -> i64 {
5634 let vals = eval_cells(&df.cells, inputs, heap);
5635 df.result.eval(inputs, &vals)
5636}
5637
5638fn eval_cells(cells: &[LoopCell], inputs: &[i64], heap: &mut Vec<i64>) -> Vec<i64> {
5642 let mut vals: Vec<i64> = Vec::with_capacity(cells.len());
5643 for c in cells {
5644 let v = match c {
5645 LoopCell::Arith { op, a, b } => op.eval(a.eval(inputs, &vals), b.eval(inputs, &vals)),
5646 LoopCell::VectorArith { op, ty, a, b } => op.eval(
5647 *ty,
5648 a.eval(inputs, &vals) as u64,
5649 b.eval(inputs, &vals) as u64,
5650 ) as i64,
5651 LoopCell::Func { unit, a, b } => {
5652 unit.eval(a.eval(inputs, &vals), b.eval(inputs, &vals))
5653 }
5654 LoopCell::HeapRead { addr } => {
5655 let a = addr.eval(inputs, &vals);
5656 heap.get(a as usize).copied().unwrap_or(0)
5657 }
5658 LoopCell::HeapWrite { addr, val } => {
5659 let a = addr.eval(inputs, &vals) as usize;
5660 let v = val.eval(inputs, &vals);
5661 if a >= heap.len() {
5662 heap.resize(a + 1, 0);
5663 }
5664 heap[a] = v;
5665 v
5666 }
5667 LoopCell::HeapAlloc { size } => {
5668 let s = size.eval(inputs, &vals).max(0) as usize;
5669 let ptr = heap.len() as i64;
5670 heap.resize(heap.len() + s, 0);
5671 ptr
5672 }
5673 LoopCell::MemRead { .. } => panic!(
5674 "MemRead (loop-sequence read) has no meaning in a straight-line heap eval — \
5675 sequence loops evaluate via eval_loop_fabric_seq"
5676 ),
5677 LoopCell::Select { cmp, ca, cb, t, e } => {
5678 if cmp.eval(ca.eval(inputs, &vals), cb.eval(inputs, &vals)) {
5679 t.eval(inputs, &vals)
5680 } else {
5681 e.eval(inputs, &vals)
5682 }
5683 }
5684 };
5685 vals.push(v);
5686 }
5687 vals
5688}
5689
5690pub fn eval_seq_program(pf: &ProgramCfgFabric, params: &[i64]) -> i64 {
5699 use std::collections::HashMap;
5700 let mut file: HashMap<u16, i64> = (0..pf.n_params)
5701 .map(|p| (p as u16, params.get(p).copied().unwrap_or(0)))
5702 .collect();
5703 let mut heap: Vec<i64> = Vec::new();
5704 let mut blk = 0usize;
5705 for _ in 0..10_000_000 {
5706 let b = &pf.blocks[blk];
5707 let inputs: Vec<i64> = b
5708 .inputs
5709 .iter()
5710 .map(|r| *file.get(r).unwrap_or(&0))
5711 .collect();
5712 let vals = eval_cells(&b.body, &inputs, &mut heap);
5713 for (r, s) in &b.writes {
5714 file.insert(*r, s.eval(&inputs, &vals));
5715 }
5716 match &b.term {
5717 Term::Jump(t) => blk = *t,
5718 Term::Branch {
5719 cond,
5720 then_blk,
5721 else_blk,
5722 } => {
5723 blk = if cond.eval(&inputs, &vals) != 0 {
5724 *then_blk
5725 } else {
5726 *else_blk
5727 }
5728 }
5729 Term::Return(s) => return s.eval(&inputs, &vals),
5730 }
5731 }
5732 panic!("eval_seq_program: block walk did not return within the step budget (a back-edge slipped through?)")
5733}
5734
5735#[derive(Clone, Debug)]
5739pub struct WholeProgramFabric {
5740 pub cells: Vec<LoopCell>,
5741 pub outputs: Vec<(Src, bool, Option<Src>)>,
5745}
5746
5747pub fn whole_program_fabric_from_source(src: &str) -> WholeProgramFabric {
5751 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
5752 let (stmts, types, pol) = parsed.expect("parse failed");
5753 let mut program = crate::vm::Compiler::compile_with_types(stmts, interner, Some(types))
5754 .expect("compile failed");
5755 let policies = extract_policy_conditions(&pol, interner, &program);
5759 crate::vm::peephole::peephole(&mut program);
5764 whole_program_fabric_from_program_with_policies(&program, &policies)
5765 })
5766}
5767
5768fn extract_policy_conditions(
5776 registry: &crate::analysis::PolicyRegistry,
5777 interner: &crate::intern::Interner,
5778 program: &crate::vm::CompiledProgram,
5779) -> Vec<((u32, u32, bool), PolicyGrant)> {
5780 let mut out = Vec::new();
5781 for (subj_type, preds) in registry.iter_predicates() {
5782 for pred in preds {
5783 push_policy_condition(
5784 &mut out,
5785 interner,
5786 program,
5787 *subj_type,
5788 pred.predicate_name,
5789 false,
5790 &pred.condition,
5791 );
5792 }
5793 }
5794 for (subj_type, caps) in registry.iter_capabilities() {
5795 for cap in caps {
5796 push_policy_condition(
5797 &mut out,
5798 interner,
5799 program,
5800 *subj_type,
5801 cap.action,
5802 true,
5803 &cap.condition,
5804 );
5805 }
5806 }
5807 out
5808}
5809
5810#[allow(clippy::too_many_arguments)]
5816fn push_policy_condition(
5817 out: &mut Vec<((u32, u32, bool), PolicyGrant)>,
5818 interner: &crate::intern::Interner,
5819 program: &crate::vm::CompiledProgram,
5820 subj_type: crate::intern::Symbol,
5821 predicate: crate::intern::Symbol,
5822 is_capability: bool,
5823 cond: &crate::analysis::PolicyCondition,
5824) {
5825 use crate::analysis::PolicyCondition;
5826 use crate::vm::BoundaryType;
5827 let tname = interner.resolve(subj_type);
5828 let sd = match program.struct_types.iter().find(|s| s.name == tname) {
5829 Some(sd) => sd,
5830 None => return,
5831 };
5832 let slot_of = |field: crate::intern::Symbol| -> Option<usize> {
5834 let fname = interner.resolve(field);
5835 sd.fields.iter().position(|(n, _)| n == fname)
5836 };
5837 let grant = match cond {
5838 PolicyCondition::FieldBool { field, value } => match slot_of(*field) {
5841 Some(slot) => PolicyGrant::IntEq {
5842 slot,
5843 expected: i64::from(*value),
5844 },
5845 None => return,
5846 },
5847 PolicyCondition::FieldEquals {
5848 field,
5849 value,
5850 is_string_literal,
5851 } => {
5852 let slot = match slot_of(*field) {
5853 Some(s) => s,
5854 None => return,
5855 };
5856 let expected = interner.resolve(*value);
5857 match &sd.fields[slot].1 {
5858 BoundaryType::Bool => {
5861 if *is_string_literal {
5862 PolicyGrant::AlwaysDeny
5863 } else {
5864 match expected.parse::<bool>() {
5865 Ok(b) => PolicyGrant::IntEq {
5866 slot,
5867 expected: i64::from(b),
5868 },
5869 Err(_) => PolicyGrant::AlwaysDeny,
5870 }
5871 }
5872 }
5873 BoundaryType::Int => {
5874 if *is_string_literal {
5875 PolicyGrant::AlwaysDeny
5876 } else {
5877 match expected.parse::<i64>() {
5878 Ok(n) => PolicyGrant::IntEq { slot, expected: n },
5879 Err(_) => PolicyGrant::AlwaysDeny,
5880 }
5881 }
5882 }
5883 BoundaryType::Text => PolicyGrant::TextEq {
5886 slot,
5887 expected: expected.as_bytes().to_vec(),
5888 },
5889 _ => return, }
5891 }
5892 _ => return, };
5894 for (i, c) in program.constants.iter().enumerate() {
5895 if matches!(c, crate::vm::Constant::Text(n) if n == tname) {
5896 out.push((
5897 (i as u32, predicate.index() as u32, is_capability),
5898 grant.clone(),
5899 ));
5900 }
5901 }
5902}
5903
5904pub fn whole_program_fabric_from_program(
5912 program: &crate::vm::CompiledProgram,
5913) -> WholeProgramFabric {
5914 whole_program_fabric_from_program_with_policies(program, &[])
5915}
5916
5917fn whole_program_fabric_from_program_with_policies(
5921 program: &crate::vm::CompiledProgram,
5922 policies: &[((u32, u32, bool), PolicyGrant)],
5923) -> WholeProgramFabric {
5924 use crate::vm::Op;
5925 use std::collections::HashMap;
5926 setup_program_thread_locals(program);
5927 POLICY_CONDITIONS.with(|m| {
5928 let mut m = m.borrow_mut();
5929 for (k, v) in policies {
5930 m.insert(*k, v.clone());
5931 }
5932 });
5933
5934 let mut body: Vec<Op> = Vec::new();
5935 for op in &program.code {
5936 match op {
5937 Op::Halt | Op::ReturnNothing => break,
5938 Op::Jump { .. } | Op::JumpIfFalse { .. } | Op::JumpIfTrue { .. } => panic!(
5939 "whole-program fabric: Main has a branch — a conditional Show cannot ride the \
5940 combinational output tape (straight-line Main only)"
5941 ),
5942 _ => body.push(*op),
5943 }
5944 }
5945 let mut reg: HashMap<u16, Src> = HashMap::new();
5946 let mut cells: Vec<LoopCell> = Vec::new();
5947 let mut preds: HashMap<u16, (CmpKind, Src, Src)> = HashMap::new();
5948 exec_ops(&body, &mut reg, &mut cells, &mut preds, &program.constants);
5949 let outputs = SHOW_OUTPUTS.with(|o| o.borrow().clone());
5950 WholeProgramFabric { cells, outputs }
5951}
5952
5953pub fn eval_whole_program(wpf: &WholeProgramFabric) -> Vec<String> {
5956 let mut heap: Vec<i64> = Vec::new();
5957 let vals = eval_cells(&wpf.cells, &[], &mut heap);
5958 let mut out = Vec::with_capacity(wpf.outputs.len());
5959 for (s, is_text, guard) in &wpf.outputs {
5960 if let Some(g) = guard {
5963 if g.eval(&[], &vals) == 0 {
5964 break;
5965 }
5966 }
5967 let v = s.eval(&[], &vals);
5968 out.push(if *is_text {
5969 let ptr = v.max(0) as usize;
5970 let len = heap.get(ptr).copied().unwrap_or(0).max(0) as usize;
5971 let bytes: Vec<u8> = (0..len)
5972 .map(|i| heap.get(ptr + 1 + i).copied().unwrap_or(0) as u8)
5973 .collect();
5974 String::from_utf8_lossy(&bytes).into_owned()
5975 } else {
5976 format!("{v}")
5977 });
5978 }
5979 out
5980}
5981
5982pub fn emit_decision_fabric_verilog(name: &str, df: &DecisionFabric) -> String {
5985 let name_in = |k: usize| format!("in{k}");
5986 let name_val = |k: usize| format!("v{k}");
5987 let inputs = (0..df.n_in)
5988 .map(|k| format!("input [63:0] in{k}"))
5989 .collect::<Vec<_>>()
5990 .join(", ");
5991 let mut body = String::new();
5992 for (i, c) in df.cells.iter().enumerate() {
5993 let rhs = match c {
5994 LoopCell::Arith { op, a, b } => {
5995 op.verilog(&src_named(a, &name_in, &name_val), &src_named(b, &name_in, &name_val))
5996 }
5997 LoopCell::VectorArith { op, ty, a, b } => {
5998 op.verilog(*ty, &src_named(a, &name_in, &name_val), &src_named(b, &name_in, &name_val))
5999 }
6000 LoopCell::Func { unit, a, b } => unit.verilog(&src_named(a, &name_in, &name_val), &src_named(b, &name_in, &name_val)),
6001 LoopCell::MemRead { .. } | LoopCell::HeapRead { .. } | LoopCell::HeapWrite { .. } | LoopCell::HeapAlloc { .. } => panic!(
6002 "MemRead RTL emission (a scratchpad-read tile driven by the induction counter) is the \
6003 P2 scratchpad follow-on; the sequence-loop LOWERING is proven via eval_loop_fabric_seq"
6004 ),
6005 LoopCell::Select { cmp, ca, cb, t, e } => format!(
6006 "($signed({}) {} $signed({})) ? {} : {}",
6007 src_named(ca, &name_in, &name_val),
6008 cmp.verilog(),
6009 src_named(cb, &name_in, &name_val),
6010 src_named(t, &name_in, &name_val),
6011 src_named(e, &name_in, &name_val),
6012 ),
6013 };
6014 body.push_str(&format!(" wire [63:0] v{i} = {rhs};\n"));
6015 }
6016 format!(
6017 "// decision_{name} — the HFT decision if-CONVERTED to a combinational select-DAG.\n\
6018 // Every `If` is a predicated Select (mux) tile; the answer settles in ONE clock — no PC,\n\
6019 // no fetch, no dispatch, no loop counter. Futamura P1: the decision IS the circuit.\n\
6020 // Lowered from the REAL compiler; 64-bit, so it wraps identically to the VM's Int; proven\n\
6021 // == the running VM by iverilog.\n\
6022 module decision_{name}({inputs}, output [63:0] result);\n\
6023 {body} assign result = {res};\n\
6024 endmodule\n",
6025 res = src_named(&df.result, &name_in, &name_val),
6026 )
6027}
6028
6029pub fn emit_decision_fabric_testbench_dummy(name: &str, n_in: usize, n: usize) -> String {
6034 let decls = (0..n_in)
6035 .map(|k| format!(" reg [63:0] in{k};\n"))
6036 .collect::<String>();
6037 let ports = (0..n_in)
6038 .map(|k| format!(".in{k}(in{k}), "))
6039 .collect::<String>();
6040 let drive = (0..n_in)
6041 .map(|k| format!(" in{k} = oin[i * {n_in} + {k}];\n"))
6042 .collect::<String>();
6043 format!(
6044 r#"// tb_dec_{name} — the combinational decision fabric vs the RUNNING VM's per-vector output.
6045`timescale 1ns/1ps
6046module tb_dec_{name};
6047{decls} reg [63:0] oin [0:{in_last}];
6048 reg [63:0] oout [0:{last}];
6049 wire [63:0] result;
6050 integer i, errors = 0;
6051 decision_{name} dut({ports}.result(result));
6052 initial begin
6053 $readmemh("dec_{name}_in.hex", oin);
6054 $readmemh("dec_{name}_out.hex", oout);
6055 for (i = 0; i < {n}; i = i + 1) begin
6056{drive} #1;
6057 if (result !== oout[i]) begin errors = errors + 1; $display("FAIL vec %0d: got %0d", i, $signed(result)); end
6058 end
6059 if (errors == 0) $display("PASS: {n} decision-fabric evaluations match the VM (the tile IS the circuit)");
6060 else $display("FAIL: %0d decision mismatches", errors);
6061 $finish;
6062 end
6063endmodule
6064"#,
6065 in_last = n_in * n - 1,
6066 last = n - 1,
6067 )
6068}
6069
6070pub fn emit_seq_datapath_verilog(name: &str, df: &DecisionFabric) -> String {
6078 let name_in = |k: usize| format!("in{k}");
6079 let name_val = |k: usize| format!("R[{k}]");
6080 let src = |s: &Src| src_named(s, &name_in, &name_val);
6081 let m = df.cells.len();
6082 assert!(m > 0, "a sequenced datapath needs ≥1 cell");
6083 let inputs = (0..df.n_in)
6084 .map(|k| format!("input [63:0] in{k}"))
6085 .collect::<Vec<_>>()
6086 .join(", ");
6087 let inputs = if inputs.is_empty() {
6088 String::new()
6089 } else {
6090 format!("{inputs}, ")
6091 };
6092
6093 let mut arms = String::new();
6094 for (i, c) in df.cells.iter().enumerate() {
6095 arms.push_str(&format!(" {i}: begin {} end\n", seq_cell_arm(c, &src)));
6096 }
6097
6098 format!(
6099 r#"// seq_{name} — a general SEQUENCED datapath over the decision fabric's cells (LATENCY-INSENSITIVE).
6100// Combinational Arith/Select/IntToF/FFloor settle in one step; a Func DIVISION cell issues the multi-cycle
6101// shared_divider, and a FLOAT arithmetic cell issues the shared fpu.v unit (fp_addsub/mul/div/sqrt) — each a
6102// variable-latency TILE that stalls the datapath until its `done`. Proven == eval_decision_fabric == the VM by
6103// iverilog (fpu.v + shared_divider.v are the real synthesizable units).
6104module seq_{name}(input clk, input rst, {inputs}output [63:0] result, output done);
6105 localparam M = {m};
6106 (* ram_style="registers" *) reg [63:0] R [0:M-1];
6107 reg [31:0] step;
6108 reg running, issued;
6109 reg is_comb; reg [63:0] comb_y; reg [63:0] ua, ub; reg [3:0] usel;
6110{converters} always @(*) begin
6111 is_comb=1'b1; comb_y=64'd0; ua=64'd0; ub=64'd0; usel=4'd0;
6112 case (step)
6113{arms} default: ;
6114 endcase
6115 end
6116{units} always @(posedge clk) begin
6117 if (rst) begin step<=32'd0; running<=(M>0); issued<=1'b0; end
6118 else if (running) begin
6119 if (is_comb) begin
6120 R[step] <= comb_y;
6121 if (step==M-1) running<=1'b0; else step<=step+32'd1;
6122 end else if (!issued) begin
6123 issued <= 1'b1; // the start pulse fired this cycle
6124 end else if (mc_done) begin
6125 R[step] <= mc_y; issued<=1'b0;
6126 if (step==M-1) running<=1'b0; else step<=step+32'd1;
6127 end
6128 end
6129 end
6130 assign done = !running;
6131 assign result = {res};
6132endmodule
6133"#,
6134 res = src(&df.result),
6135 converters = I64_F64_CONVERTERS,
6136 units = seq_units_for(&df.cells),
6137 )
6138}
6139
6140fn seq_cell_arm(c: &LoopCell, src: &dyn Fn(&Src) -> String) -> String {
6144 match c {
6145 LoopCell::Arith { op, a, b } => {
6146 format!("is_comb=1'b1; comb_y={};", op.verilog(&src(a), &src(b)))
6147 }
6148 LoopCell::Select { cmp, ca, cb, t, e } => format!(
6149 "is_comb=1'b1; comb_y=($signed({}) {} $signed({})) ? {} : {};",
6150 src(ca),
6151 cmp.verilog(),
6152 src(cb),
6153 src(t),
6154 src(e)
6155 ),
6156 LoopCell::Func { unit, a, b } => match unit {
6157 FuncUnit::DivS => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd1;", src(a), src(b)),
6158 FuncUnit::ModS => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd2;", src(a), src(b)),
6159 FuncUnit::FloorDivS => {
6160 format!("is_comb=1'b0; ua={}; ub={}; usel=4'd3;", src(a), src(b))
6161 }
6162 FuncUnit::FAddF => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd5;", src(a), src(b)),
6163 FuncUnit::FSubF => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd6;", src(a), src(b)),
6164 FuncUnit::FMulF => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd7;", src(a), src(b)),
6165 FuncUnit::FDivF => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd8;", src(a), src(b)),
6166 FuncUnit::FSqrtF => format!("is_comb=1'b0; ua={}; ub=64'd0; usel=4'd9;", src(a)),
6167 FuncUnit::IntToF => format!("is_comb=1'b1; comb_y=i64_to_f64({});", src(a)),
6168 FuncUnit::FFloorF => format!("is_comb=1'b1; comb_y=f64_floor_i64({});", src(a)),
6169 FuncUnit::FLtF => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd10;", src(a), src(b)),
6172 FuncUnit::FGtF => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd11;", src(a), src(b)),
6173 FuncUnit::FLeF => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd12;", src(a), src(b)),
6174 FuncUnit::FGeF => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd13;", src(a), src(b)),
6175 FuncUnit::FEqF => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd14;", src(a), src(b)),
6176 FuncUnit::FNeF => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd15;", src(a), src(b)),
6177 FuncUnit::Pow => format!("is_comb=1'b0; ua={}; ub={}; usel=4'd4;", src(a), src(b)),
6180 FuncUnit::PopCount | FuncUnit::MulHiU | FuncUnit::MulOvfS | FuncUnit::ApproxEqF => {
6184 format!("is_comb=1'b1; comb_y={};", unit.verilog(&src(a), &src(b)))
6185 }
6186 },
6187 other => panic!("seq datapath: {other:?} (Mem/Heap) is a state-plane follow-on"),
6188 }
6189}
6190
6191fn seq_units_for(cells: &[LoopCell]) -> String {
6197 let (mut div, mut addsub, mut mul, mut fdiv, mut sqrt, mut cmp, mut pow) =
6198 (false, false, false, false, false, false, false);
6199 for c in cells {
6200 if let LoopCell::Func { unit, .. } = c {
6201 match unit {
6202 FuncUnit::DivS | FuncUnit::ModS | FuncUnit::FloorDivS => div = true,
6203 FuncUnit::Pow => pow = true,
6204 FuncUnit::FAddF | FuncUnit::FSubF => addsub = true,
6205 FuncUnit::FMulF => mul = true,
6206 FuncUnit::FDivF => fdiv = true,
6207 FuncUnit::FSqrtF => sqrt = true,
6208 FuncUnit::FLtF
6209 | FuncUnit::FGtF
6210 | FuncUnit::FLeF
6211 | FuncUnit::FGeF
6212 | FuncUnit::FEqF
6213 | FuncUnit::FNeF => cmp = true,
6214 _ => {}
6215 }
6216 }
6217 }
6218 let mut s = String::new();
6219 s.push_str(" wire is_div = (usel>=4'd1 && usel<=4'd3);\n");
6220 s.push_str(" wire is_mod = (usel==4'd2);\n wire is_floor = (usel==4'd3);\n");
6221 s.push_str(" wire is_pow = (usel==4'd4);\n");
6222 s.push_str(" wire is_fadd = (usel==4'd5)||(usel==4'd6);\n wire is_fsub = (usel==4'd6);\n");
6223 s.push_str(" wire is_fmul = (usel==4'd7);\n wire is_fdiv = (usel==4'd8);\n wire is_fsqrt = (usel==4'd9);\n");
6224 s.push_str(" wire is_cmp = (usel>=4'd10);\n");
6225 s.push_str(" wire pulse = running && !issued && !is_comb;\n");
6226 if div {
6227 s.push_str(" wire d_done; wire [63:0] d_q, d_r;\n");
6228 s.push_str(" shared_divider #(.W(64)) u_div(.clk(clk), .rst(rst), .start(pulse && is_div && (ub!=64'd0)), .a(ua), .b(ub), .q(d_q), .r(d_r), .done(d_done));\n");
6229 s.push_str(" wire signed [63:0] d_floor_v = $signed(d_q) - (((d_r!=64'd0) && (ua[63]^ub[63])) ? 64'sd1 : 64'sd0);\n");
6230 s.push_str(" wire [63:0] div_y = (ub==64'd0) ? 64'd0 : is_mod ? d_r : is_floor ? d_floor_v : d_q;\n");
6231 s.push_str(" wire div_done_g = (ub==64'd0) ? 1'b1 : d_done;\n");
6232 } else {
6233 s.push_str(" wire [63:0] div_y = 64'd0; wire div_done_g = 1'b1;\n");
6234 }
6235 if addsub {
6236 s.push_str(" wire fa_done; wire [63:0] fa_y;\n fp_addsub u_fa(.clk(clk),.rst(rst),.start(pulse && is_fadd),.a(ua),.b_in(ub),.sub(is_fsub),.y(fa_y),.done(fa_done));\n");
6237 } else {
6238 s.push_str(" wire [63:0] fa_y = 64'd0; wire fa_done = 1'b1;\n");
6239 }
6240 if mul {
6241 s.push_str(" wire fm_done; wire [63:0] fm_y;\n fp_mul u_fm(.clk(clk),.rst(rst),.start(pulse && is_fmul),.a(ua),.b(ub),.y(fm_y),.done(fm_done));\n");
6242 } else {
6243 s.push_str(" wire [63:0] fm_y = 64'd0; wire fm_done = 1'b1;\n");
6244 }
6245 if fdiv {
6246 s.push_str(" wire fd_done; wire [63:0] fd_y;\n fp_div u_fd(.clk(clk),.rst(rst),.start(pulse && is_fdiv),.a(ua),.b(ub),.y(fd_y),.done(fd_done));\n");
6247 } else {
6248 s.push_str(" wire [63:0] fd_y = 64'd0; wire fd_done = 1'b1;\n");
6249 }
6250 if sqrt {
6251 s.push_str(" wire fs_done; wire [63:0] fs_y;\n fp_sqrt u_fs(.clk(clk),.rst(rst),.start(pulse && is_fsqrt),.a(ua),.y(fs_y),.done(fs_done));\n");
6252 } else {
6253 s.push_str(" wire [63:0] fs_y = 64'd0; wire fs_done = 1'b1;\n");
6254 }
6255 if cmp {
6256 s.push_str(" wire cmp_swap = (usel==4'd11)||(usel==4'd13);\n");
6257 s.push_str(
6258 " wire [63:0] fcm_a = cmp_swap ? ub : ua; wire [63:0] fcm_b = cmp_swap ? ua : ub;\n",
6259 );
6260 s.push_str(
6261 " wire fp_lt, fp_eq;\n fp_cmp u_fcm(.a(fcm_a), .b(fcm_b), .lt(fp_lt), .eq(fp_eq));\n",
6262 );
6263 s.push_str(" wire [63:0] cmp_y = (usel==4'd10||usel==4'd11) ? {63'd0, fp_lt} : (usel==4'd12||usel==4'd13) ? {63'd0, fp_lt|fp_eq} : (usel==4'd14) ? {63'd0, fp_eq} : (usel==4'd15) ? {63'd0, ~fp_eq} : 64'd0;\n");
6264 } else {
6265 s.push_str(" wire [63:0] cmp_y = 64'd0;\n");
6266 }
6267 if pow {
6268 s.push_str(" reg [63:0] pw_acc, pw_base, pw_cnt; reg pw_busy, pw_done;\n");
6273 s.push_str(" always @(posedge clk) begin\n");
6274 s.push_str(" if (rst) begin pw_busy <= 1'b0; pw_done <= 1'b0; end\n");
6275 s.push_str(" else if (pulse && is_pow) begin\n");
6276 s.push_str(" pw_acc <= 64'd1; pw_base <= ua;\n");
6277 s.push_str(" pw_cnt <= ($signed(ub) <= 0) ? 64'd0 : ub;\n");
6278 s.push_str(" pw_busy <= ($signed(ub) > 0); pw_done <= ($signed(ub) <= 0);\n");
6279 s.push_str(" end else if (pw_busy) begin\n");
6280 s.push_str(" pw_acc <= pw_acc * pw_base;\n");
6281 s.push_str(" if (pw_cnt <= 64'd1) begin pw_busy <= 1'b0; pw_done <= 1'b1; end else pw_cnt <= pw_cnt - 64'd1;\n");
6282 s.push_str(" end\n end\n");
6283 } else {
6284 s.push_str(" wire [63:0] pw_acc = 64'd0; wire pw_done = 1'b1;\n");
6285 }
6286 s.push_str(" wire mc_done = is_div ? div_done_g : is_pow ? pw_done : is_cmp ? 1'b1 : is_fadd ? fa_done : is_fmul ? fm_done : is_fdiv ? fd_done : is_fsqrt ? fs_done : 1'b1;\n");
6288 s.push_str(" wire [63:0] mc_y = is_div ? div_y : is_pow ? pw_acc : is_cmp ? cmp_y : is_fadd ? fa_y : is_fmul ? fm_y : is_fdiv ? fd_y : is_fsqrt ? fs_y : 64'd0;\n");
6289 s
6290}
6291
6292pub fn emit_seq_block_verilog(name: &str, block: &CfgBlock) -> String {
6298 emit_seq_block_verilog_sp(name, block, false)
6299}
6300
6301fn cell_is_heap(c: &LoopCell) -> bool {
6303 matches!(
6304 c,
6305 LoopCell::HeapRead { .. }
6306 | LoopCell::HeapWrite { .. }
6307 | LoopCell::HeapAlloc { .. }
6308 | LoopCell::MemRead { .. }
6309 )
6310}
6311
6312fn emit_seq_block_verilog_sp(name: &str, block: &CfgBlock, with_heap: bool) -> String {
6320 let name_in = |k: usize| format!("in{k}");
6321 let name_val = |k: usize| format!("R[{k}]");
6322 let src = |s: &Src| src_named(s, &name_in, &name_val);
6323 let m = block.body.len();
6324 let n_in = block.inputs.len();
6325 let mut needed: Vec<Src> = block.writes.iter().map(|(_, s)| *s).collect();
6326 match &block.term {
6327 Term::Jump(_) => {}
6328 Term::Branch { cond, .. } => needed.push(*cond),
6329 Term::Return(s) => needed.push(*s),
6330 }
6331 let inports = (0..n_in)
6332 .map(|k| format!("input [63:0] in{k}"))
6333 .collect::<Vec<_>>()
6334 .join(", ");
6335 let inports = if inports.is_empty() {
6336 String::new()
6337 } else {
6338 format!("{inports}, ")
6339 };
6340 let outports = (0..needed.len())
6341 .map(|j| format!("output [63:0] o{j}"))
6342 .collect::<Vec<_>>()
6343 .join(", ");
6344 let outports = if outports.is_empty() {
6345 String::new()
6346 } else {
6347 format!("{outports}, ")
6348 };
6349 let outassign = needed
6350 .iter()
6351 .enumerate()
6352 .map(|(j, s)| format!(" assign o{j} = {};\n", src(s)))
6353 .collect::<String>();
6354 let heap_ports = if with_heap {
6356 "output [63:0] heap_raddr, input [63:0] heap_rdata, output heap_we, output [63:0] heap_waddr, \
6357 output [63:0] heap_wdata, output alloc_req, output [63:0] alloc_size, input [63:0] hp_in, "
6358 } else {
6359 ""
6360 };
6361 let (heap_regs, heap_defaults, heap_assigns) = if with_heap {
6362 (
6363 " reg [63:0] heap_raddr_r, heap_waddr_r, heap_wdata_r, alloc_size_r; reg heap_we_r, alloc_req_r;\n",
6364 " heap_raddr_r=64'd0; heap_we_r=1'b0; heap_waddr_r=64'd0; heap_wdata_r=64'd0; alloc_req_r=1'b0; alloc_size_r=64'd0;\n",
6365 " assign heap_raddr=heap_raddr_r; assign heap_we=running && heap_we_r; assign heap_waddr=heap_waddr_r;\n\
6366 \x20 assign heap_wdata=heap_wdata_r; assign alloc_req=running && alloc_req_r; assign alloc_size=alloc_size_r;\n",
6367 )
6368 } else {
6369 ("", "", "")
6370 };
6371 let mut arms = String::new();
6372 for (i, c) in block.body.iter().enumerate() {
6373 let arm = match c {
6375 LoopCell::HeapRead { addr } => {
6376 format!(
6377 "is_comb=1'b1; comb_y=heap_rdata; heap_raddr_r={};",
6378 src(addr)
6379 )
6380 }
6381 LoopCell::HeapWrite { addr, val } => format!(
6382 "is_comb=1'b1; comb_y={v}; heap_we_r=1'b1; heap_waddr_r={a}; heap_wdata_r={v};",
6383 a = src(addr),
6384 v = src(val)
6385 ),
6386 LoopCell::HeapAlloc { size } => {
6387 format!(
6388 "is_comb=1'b1; comb_y=hp_in; alloc_req_r=1'b1; alloc_size_r={};",
6389 src(size)
6390 )
6391 }
6392 _ => seq_cell_arm(c, &src),
6393 };
6394 arms.push_str(&format!(" {i}: begin {arm} end\n"));
6395 }
6396 let mdecl = format!("localparam M = {m};");
6399 let rdecl = format!(
6400 " (* ram_style=\"registers\" *) reg [63:0] R [0:{}];\n",
6401 m.max(1) - 1
6402 );
6403 format!(
6404 r#"// seqblk_{name} — a per-block SEQUENCED datapath (LATENCY-INSENSITIVE): start pulse -> walk the cells
6405// (combinational in one step, division/float on the shared multi-cycle units, heap on the shared state plane) ->
6406// done + the block's outputs.
6407module seqblk_{name}(input clk, input rst, input start, {inports}{outports}{heap_ports}output done);
6408 {mdecl}
6409{rdecl} reg [31:0] step;
6410 reg running, issued;
6411 reg is_comb; reg [63:0] comb_y; reg [63:0] ua, ub; reg [3:0] usel;
6412{heap_regs}{converters} always @(*) begin
6413 is_comb=1'b1; comb_y=64'd0; ua=64'd0; ub=64'd0; usel=4'd0;
6414{heap_defaults} case (step)
6415{arms} default: ;
6416 endcase
6417 end
6418{units} always @(posedge clk) begin
6419 if (rst) begin running<=1'b0; step<=32'd0; issued<=1'b0; end
6420 else if (start) begin step<=32'd0; running<=(M>0); issued<=1'b0; end
6421 else if (running) begin
6422 if (is_comb) begin
6423 R[step] <= comb_y;
6424 if (step==M-1) running<=1'b0; else step<=step+32'd1;
6425 end else if (!issued) begin
6426 issued <= 1'b1;
6427 end else if (mc_done) begin
6428 R[step] <= mc_y; issued<=1'b0;
6429 if (step==M-1) running<=1'b0; else step<=step+32'd1;
6430 end
6431 end
6432 end
6433 assign done = !running;
6434{heap_assigns}{outassign}endmodule
6435"#,
6436 converters = I64_F64_CONVERTERS,
6437 units = seq_units_for(&block.body),
6438 )
6439}
6440
6441pub fn emit_seq_program_verilog(name: &str, pf: &ProgramCfgFabric) -> String {
6447 let nb = pf.blocks.len();
6448 assert!(nb >= 1, "seq program: need ≥1 block");
6449 let has_heap = pf.blocks.iter().any(|b| b.body.iter().any(cell_is_heap));
6452 const HEAP_WORDS: usize = 64;
6457 let aw = (usize::BITS - (HEAP_WORDS - 1).leading_zeros()).max(1) as usize;
6458 let nreg = {
6459 let mut max_reg = 0u16;
6460 for b in &pf.blocks {
6461 for r in &b.inputs {
6462 max_reg = max_reg.max(*r);
6463 }
6464 for (r, _) in &b.writes {
6465 max_reg = max_reg.max(*r);
6466 }
6467 }
6468 (max_reg as usize + 1).max(pf.n_params).max(1)
6469 };
6470 let pcw = {
6471 let mut b = 1;
6472 while (1usize << b) < nb {
6473 b += 1;
6474 }
6475 b
6476 };
6477
6478 let mut modules = String::new();
6479 let mut needs: Vec<(Vec<Src>, usize)> = Vec::with_capacity(nb);
6480 for (k, b) in pf.blocks.iter().enumerate() {
6481 modules.push_str(&emit_seq_block_verilog_sp(
6482 &format!("{name}_b{k}"),
6483 b,
6484 has_heap,
6485 ));
6486 modules.push('\n');
6487 let mut needed: Vec<Src> = b.writes.iter().map(|(_, s)| *s).collect();
6488 let term_off = needed.len();
6489 match &b.term {
6490 Term::Jump(_) => {}
6491 Term::Branch { cond, .. } => needed.push(*cond),
6492 Term::Return(s) => needed.push(*s),
6493 }
6494 needs.push((needed, term_off));
6495 }
6496
6497 let mut ports: Vec<String> = vec!["input clk".into(), "input rst".into()];
6498 for p in 0..pf.n_params {
6499 ports.push(format!("input [63:0] p{p}"));
6500 }
6501 ports.push("output [63:0] result".into());
6502 ports.push("output done".into());
6503
6504 let mut body = String::new();
6505 body.push_str(&format!(" reg [63:0] file [0:{}];\n", nreg - 1));
6506 body.push_str(&format!(" reg [{}:0] pc;\n", pcw - 1));
6507 body.push_str(" reg done_r; reg [63:0] result_r; reg started;\n");
6508 body.push_str(" assign done = done_r; assign result = result_r;\n");
6509 for r in 0..nreg {
6510 body.push_str(&format!(" wire [63:0] fr_{r} = file[{r}];\n"));
6511 }
6512 if has_heap {
6516 body.push_str(&format!(
6517 " (* ram_style=\"registers\" *) reg [63:0] heap [0:{}];\n",
6518 HEAP_WORDS - 1
6519 ));
6520 body.push_str(" reg [63:0] hp;\n");
6521 for k in 0..nb {
6522 body.push_str(&format!(
6523 " wire [63:0] b{k}_heap_raddr, b{k}_heap_waddr, b{k}_heap_wdata, b{k}_alloc_size; wire b{k}_heap_we, b{k}_alloc_req;\n"
6524 ));
6525 }
6526 body.push_str(
6527 " reg [63:0] act_raddr, act_waddr, act_wdata, act_size; reg act_we, act_alloc;\n",
6528 );
6529 body.push_str(" always @(*) begin\n act_raddr=64'd0; act_waddr=64'd0; act_wdata=64'd0; act_size=64'd0; act_we=1'b0; act_alloc=1'b0;\n case (pc)\n");
6530 for k in 0..nb {
6531 body.push_str(&format!(
6532 " {pcw}'d{k}: begin act_raddr=b{k}_heap_raddr; act_we=b{k}_heap_we; act_waddr=b{k}_heap_waddr; act_wdata=b{k}_heap_wdata; act_alloc=b{k}_alloc_req; act_size=b{k}_alloc_size; end\n"
6533 ));
6534 }
6535 body.push_str(" default: ;\n endcase\n end\n");
6536 body.push_str(&format!(
6537 " wire [63:0] heap_rdata_bus = heap[act_raddr[{}:0]];\n",
6538 aw - 1
6539 ));
6540 body.push_str(" integer hj;\n always @(posedge clk) begin\n");
6541 body.push_str(&format!(" if (rst) begin hp<=64'd0; for (hj=0; hj<{HEAP_WORDS}; hj=hj+1) heap[hj]<=64'd0; end\n"));
6542 body.push_str(" else begin\n");
6543 body.push_str(&format!(
6544 " if (act_we) heap[act_waddr[{}:0]] <= act_wdata;\n",
6545 aw - 1
6546 ));
6547 body.push_str(" if (act_alloc) hp <= hp + act_size;\n");
6548 body.push_str(" else if (act_we && ($signed(act_waddr) + 64'sd1 > $signed(hp))) hp <= act_waddr + 64'sd1;\n");
6549 body.push_str(" end\n end\n");
6550 }
6551 for (k, b) in pf.blocks.iter().enumerate() {
6553 let (needed, _) = &needs[k];
6554 for j in 0..needed.len() {
6555 body.push_str(&format!(" wire [63:0] b{k}_o{j};\n"));
6556 }
6557 body.push_str(&format!(" wire b{k}_done;\n"));
6558 let mut conns: Vec<String> = vec![
6559 ".clk(clk)".into(),
6560 ".rst(rst)".into(),
6561 format!(".start((pc=={pcw}'d{k}) && !started && !done_r)"),
6562 ];
6563 for (j, r) in b.inputs.iter().enumerate() {
6564 conns.push(format!(".in{j}(fr_{r})"));
6565 }
6566 for j in 0..needed.len() {
6567 conns.push(format!(".o{j}(b{k}_o{j})"));
6568 }
6569 if has_heap {
6570 conns.push(format!(".heap_raddr(b{k}_heap_raddr)"));
6571 conns.push(".heap_rdata(heap_rdata_bus)".into());
6572 conns.push(format!(".heap_we(b{k}_heap_we)"));
6573 conns.push(format!(".heap_waddr(b{k}_heap_waddr)"));
6574 conns.push(format!(".heap_wdata(b{k}_heap_wdata)"));
6575 conns.push(format!(".alloc_req(b{k}_alloc_req)"));
6576 conns.push(format!(".alloc_size(b{k}_alloc_size)"));
6577 conns.push(".hp_in(hp)".into());
6578 }
6579 conns.push(format!(".done(b{k}_done)"));
6580 body.push_str(&format!(
6581 " seqblk_{name}_b{k} u_b{k}({});\n",
6582 conns.join(", ")
6583 ));
6584 }
6585 body.push_str(" reg cur_done;\n always @(*) begin cur_done = 1'b1; case (pc)\n");
6587 for k in 0..nb {
6588 body.push_str(&format!(" {pcw}'d{k}: cur_done = b{k}_done;\n"));
6589 }
6590 body.push_str(" default: cur_done = 1'b1;\n endcase end\n");
6591
6592 body.push_str(" always @(posedge clk) begin\n if (rst) begin\n");
6594 body.push_str(&format!(
6595 " pc<={pcw}'d0; done_r<=1'b0; result_r<=64'd0; started<=1'b0;\n"
6596 ));
6597 for r in 0..nreg {
6598 if r < pf.n_params {
6599 body.push_str(&format!(" file[{r}]<=p{r};\n"));
6600 } else {
6601 body.push_str(&format!(" file[{r}]<=64'd0;\n"));
6602 }
6603 }
6604 body.push_str(" end else if (!done_r) begin\n");
6605 body.push_str(" if (!started) started<=1'b1;\n");
6606 body.push_str(" else if (cur_done) begin\n started<=1'b0;\n case (pc)\n");
6607 for (k, b) in pf.blocks.iter().enumerate() {
6608 let (_, term_off) = &needs[k];
6609 body.push_str(&format!(" {pcw}'d{k}: begin\n"));
6610 for (i, (r, _)) in b.writes.iter().enumerate() {
6611 body.push_str(&format!(" file[{r}] <= b{k}_o{i};\n"));
6612 }
6613 match &b.term {
6614 Term::Jump(t) => body.push_str(&format!(" pc <= {pcw}'d{t};\n")),
6615 Term::Branch { then_blk, else_blk, .. } => body.push_str(&format!(
6616 " pc <= (b{k}_o{term_off} != 64'd0) ? {pcw}'d{then_blk} : {pcw}'d{else_blk};\n"
6617 )),
6618 Term::Return(_) => {
6619 body.push_str(&format!(" result_r <= b{k}_o{term_off}; done_r <= 1'b1;\n"))
6620 }
6621 }
6622 body.push_str(" end\n");
6623 }
6624 body.push_str(" default: done_r<=1'b1;\n endcase\n end\n end\n end\n");
6625
6626 format!(
6627 "// seqprog_{name} — the whole-program SEQUENCED fabric (LATENCY-INSENSITIVE): per-block datapaths with\n\
6628 // multi-cycle division/float TILES + a block-PC FSM that starts each block, waits its done, latches the\n\
6629 // register writes, and takes the terminator. == run_program_cfg_on_grid == the VM.\n\
6630 {modules}\
6631 module seqprog_{name}(\n {ports}\n);\n{body}endmodule\n",
6632 ports = ports.join(",\n "),
6633 )
6634}
6635
6636fn seq_datapath_cells_supported(cells: &[LoopCell]) -> Result<(), LoweringError> {
6640 for c in cells {
6641 if cell_is_heap(c) {
6642 return Err(LoweringError::StatePlaneUnlowered(format!("{c:?}")));
6643 }
6644 }
6645 Ok(())
6646}
6647
6648fn seq_program_cells_supported(cells: &[LoopCell]) -> Result<(), LoweringError> {
6652 for c in cells {
6653 if matches!(c, LoopCell::MemRead { .. }) {
6654 return Err(LoweringError::StatePlaneUnlowered(format!("{c:?}")));
6655 }
6656 }
6657 Ok(())
6658}
6659
6660pub fn try_emit_seq_program_verilog(
6664 name: &str,
6665 pf: &ProgramCfgFabric,
6666) -> Result<String, LoweringError> {
6667 for b in &pf.blocks {
6668 seq_program_cells_supported(&b.body)?;
6669 }
6670 Ok(emit_seq_program_verilog(name, pf))
6671}
6672
6673pub fn try_emit_seq_datapath_verilog(
6677 name: &str,
6678 df: &DecisionFabric,
6679) -> Result<String, LoweringError> {
6680 seq_datapath_cells_supported(&df.cells)?;
6681 Ok(emit_seq_datapath_verilog(name, df))
6682}
6683
6684pub fn emit_seqprog_soc_verilog(name: &str, pf: &ProgramCfgFabric, vectors: &[Vec<i64>]) -> String {
6690 let np = pf.n_params;
6691 let nv = vectors.len();
6692 assert!(
6693 nv >= 1 && vectors.iter().all(|v| v.len() == np),
6694 "each vector must have n_params={np} entries"
6695 );
6696 let core = emit_seq_program_verilog(name, pf);
6697
6698 let mut rom = String::new();
6700 for (i, v) in vectors.iter().enumerate() {
6701 let sets = (0..np)
6702 .map(|k| {
6703 let x = v[k];
6704 if x < 0 {
6705 format!("p{k}=-64'sd{};", -(x as i128))
6706 } else {
6707 format!("p{k}=64'sd{x};")
6708 }
6709 })
6710 .collect::<Vec<_>>()
6711 .join(" ");
6712 rom.push_str(&format!(" {i}: begin {sets} end\n"));
6713 }
6714 let pdecls = (0..np)
6715 .map(|k| format!(" reg signed [63:0] p{k};\n"))
6716 .collect::<String>();
6717 let pconn = (0..np)
6718 .map(|k| format!(".p{k}(p{k}), "))
6719 .collect::<String>();
6720 let pdefault = (0..np)
6721 .map(|k| format!("p{k}=64'sd0;"))
6722 .collect::<Vec<_>>()
6723 .join(" ");
6724 let vecw = std::cmp::max(1, (usize::BITS - (nv.max(2) - 1).leading_zeros()) as usize);
6725
6726 format!(
6727 r#"{core}
6728// nexys_seqprog_{name} — the whole-program SEQUENCED fabric (multi-cycle division/float tiles) on a Nexys A7.
6729// A baked vector ROM runs seqprog_{name} for each input (drive params -> pulse fab_rst -> wait done -> capture),
6730// streaming {{0xA5, result[63:0], run_cycles[15:0], 0x5A}} out the UART per vector. 781.25 kHz core clock.
6731module nexys_seqprog_{name}(input clk, input rst, output reg uart_tx, output [15:0] led);
6732 localparam CLKS_PER_BIT = 81; // 781.25e3 / 9600
6733 localparam NVEC = {nv};
6734 // /128 core clock on a global buffer.
6735 reg [6:0] divcnt = 7'd0;
6736 always @(posedge clk) divcnt <= divcnt + 1'b1;
6737 wire coreclk;
6738 BUFG bufg_core(.I(divcnt[6]), .O(coreclk));
6739 reg [3:0] por = 4'd0;
6740 always @(posedge coreclk) if (!por[3]) por <= por + 1'b1;
6741 wire soc_rst = rst | ~por[3];
6742
6743 reg [{vhi}:0] vec;
6744{pdecls} always @(*) begin
6745 {pdefault}
6746 case (vec)
6747{rom} default: ;
6748 endcase
6749 end
6750 reg fab_rst;
6751 wire [63:0] fab_result; wire fab_done;
6752 reg [63:0] result_l; reg [15:0] cyc_l; reg [15:0] runcyc;
6753 seqprog_{name} u_fab(.clk(coreclk), .rst(fab_rst), {pconn}.result(fab_result), .done(fab_done));
6754
6755 localparam R_POR=3'd0, R_RUN=3'd1, R_WAIT=3'd2, R_LOAD=3'd3, R_START=3'd4, R_DATA=3'd5, R_STOP=3'd6;
6756 reg [2:0] rstate;
6757 reg [15:0] clkc; reg [2:0] bidx; reg [3:0] pos; reg [3:0] porc; reg [1:0] rstc;
6758 reg [7:0] txb;
6759 assign led = {{rstate, vec[4:0], 8'd0}};
6760 wire [3:0] rsel = (pos >= 4'd1 && pos <= 4'd8) ? (4'd8 - pos) : 4'd0;
6761 wire [7:0] res_byte = result_l[rsel * 8 +: 8];
6762 always @(posedge coreclk) begin
6763 if (soc_rst) begin
6764 rstate<=R_POR; uart_tx<=1'b1; clkc<=16'd0; bidx<=3'd0; vec<={vw}'d0; pos<=4'd0; porc<=4'd0;
6765 fab_rst<=1'b1; rstc<=2'd0; runcyc<=16'd0; result_l<=64'd0; cyc_l<=16'd0;
6766 end else case (rstate)
6767 R_POR: if (porc==4'd8) begin rstate<=R_RUN; fab_rst<=1'b1; rstc<=2'd0; end else porc<=porc+4'd1;
6768 R_RUN: begin
6769 // hold fab_rst 2 coreclks (load params + reset the fabric), then release to run.
6770 if (rstc==2'd2) begin fab_rst<=1'b0; runcyc<=16'd0; rstate<=R_WAIT; end else rstc<=rstc+2'd1;
6771 end
6772 R_WAIT: begin
6773 runcyc <= runcyc + 16'd1;
6774 if (fab_done) begin result_l<=fab_result; cyc_l<=runcyc; pos<=4'd0; rstate<=R_LOAD; end
6775 end
6776 R_LOAD: begin
6777 case (pos)
6778 4'd0: txb<=8'hA5;
6779 4'd9: txb<=cyc_l[15:8];
6780 4'd10: txb<=cyc_l[7:0];
6781 4'd11: txb<=8'h5A;
6782 default: txb<=res_byte; // pos 1..8 = result[63:0] MSB-first
6783 endcase
6784 clkc<=16'd0; bidx<=3'd0; rstate<=R_START;
6785 end
6786 R_START: begin uart_tx<=1'b0; if (clkc==CLKS_PER_BIT-1) begin clkc<=16'd0; rstate<=R_DATA; end else clkc<=clkc+16'd1; end
6787 R_DATA: begin uart_tx<=txb[bidx];
6788 if (clkc==CLKS_PER_BIT-1) begin clkc<=16'd0; if (bidx==3'd7) rstate<=R_STOP; else bidx<=bidx+3'd1; end else clkc<=clkc+16'd1;
6789 end
6790 R_STOP: begin uart_tx<=1'b1;
6791 if (clkc==CLKS_PER_BIT-1) begin clkc<=16'd0;
6792 if (pos==4'd11) begin
6793 pos<=4'd0;
6794 if (vec==NVEC-1) vec<={vw}'d0; else vec<=vec+{vw}'d1;
6795 fab_rst<=1'b1; rstc<=2'd0; rstate<=R_RUN;
6796 end else begin pos<=pos+4'd1; rstate<=R_LOAD; end
6797 end else clkc<=clkc+16'd1;
6798 end
6799 default: rstate<=R_POR;
6800 endcase
6801 end
6802endmodule
6803"#,
6804 vhi = vecw - 1,
6805 vw = vecw,
6806 )
6807}
6808
6809pub fn emit_seq_program_testbench(name: &str, n_params: usize, n: usize) -> String {
6812 let decls = (0..n_params)
6813 .map(|k| format!(" reg [63:0] p{k};\n"))
6814 .collect::<String>();
6815 let ports = (0..n_params)
6816 .map(|k| format!(".p{k}(p{k}), "))
6817 .collect::<String>();
6818 let drive = (0..n_params)
6819 .map(|k| format!(" p{k} = oin[i*{n_params}+{k}];\n"))
6820 .collect::<String>();
6821 format!(
6822 r#"// tb_seqprog_{name} — the whole-program sequenced fabric vs the RUNNING VM, over {n} vectors.
6823`timescale 1ns/1ps
6824module tb_seqprog_{name};
6825 reg clk=0, rst=1;
6826{decls} reg [63:0] oin [0:{in_last}];
6827 reg [63:0] oout [0:{last}];
6828 wire [63:0] result; wire done;
6829 integer i, errors=0, timeout;
6830 seqprog_{name} dut(.clk(clk), .rst(rst), {ports}.result(result), .done(done));
6831 always #5 clk = ~clk;
6832 initial begin
6833 $readmemh("seqprog_{name}_in.hex", oin);
6834 $readmemh("seqprog_{name}_out.hex", oout);
6835 for (i=0; i<{n}; i=i+1) begin
6836{drive} rst=1'b1; @(posedge clk); #1; rst=1'b0;
6837 timeout=0;
6838 while (!done && timeout<2000000) begin @(posedge clk); timeout=timeout+1; end
6839 #1;
6840 if (result !== oout[i]) begin errors=errors+1; $display("FAIL vec %0d: got %0d exp %0d", i, $signed(result), $signed(oout[i])); end
6841 end
6842 if (errors==0) $display("PASS: {n} whole-program sequenced-fabric runs match the VM");
6843 else $display("FAIL: %0d whole-program mismatches", errors);
6844 $finish;
6845 end
6846endmodule
6847"#,
6848 in_last = if n_params == 0 { 0 } else { n_params * n - 1 },
6849 last = n - 1,
6850 )
6851}
6852
6853const I64_F64_CONVERTERS: &str = r#" function [63:0] i64_to_f64(input signed [63:0] n);
6857 reg s; reg [63:0] m; integer lead, i, sh; reg [10:0] be; reg [51:0] frac;
6858 reg [63:0] shifted, dropped; reg [53:0] sig; reg round_up;
6859 begin
6860 if (n == 64'sd0) i64_to_f64 = 64'd0;
6861 else begin
6862 s = n[63];
6863 m = s ? (~n + 64'sd1) : n;
6864 lead = 0;
6865 for (i = 63; i >= 0; i = i - 1) if (m[i] && lead == 0) lead = i;
6866 be = 11'd1023 + lead[10:0];
6867 if (lead <= 52) begin
6868 shifted = m << (52 - lead);
6869 i64_to_f64 = {s, be, shifted[51:0]};
6870 end else begin
6871 sh = lead - 52;
6872 sig = m >> sh;
6873 dropped = m << (64 - sh);
6874 round_up = dropped[63] && (|dropped[62:0] || sig[0]);
6875 if (round_up) sig = sig + 54'd1;
6876 if (sig[53]) begin be = be + 11'd1; frac = 52'd0; end
6877 else frac = sig[51:0];
6878 i64_to_f64 = {s, be, frac};
6879 end
6880 end
6881 end
6882 endfunction
6883 function signed [63:0] f64_floor_i64(input [63:0] x);
6884 reg s; reg [10:0] e; reg [52:0] sig; integer ue; reg [63:0] mag; reg hasfrac;
6885 begin
6886 s = x[63]; e = x[62:52];
6887 if (e < 11'd1023)
6888 f64_floor_i64 = (s && !(e == 11'd0 && x[51:0] == 52'd0)) ? -64'sd1 : 64'sd0;
6889 else begin
6890 sig = {1'b1, x[51:0]};
6891 ue = e - 11'd1023;
6892 if (ue >= 52) begin mag = {11'd0, sig} << (ue - 52); hasfrac = 1'b0; end
6893 else begin
6894 mag = {11'd0, sig} >> (52 - ue);
6895 hasfrac = (({11'd0, sig} << (ue + 12)) != 64'd0);
6896 end
6897 if (!s) f64_floor_i64 = mag;
6898 else f64_floor_i64 = hasfrac ? -(mag + 64'd1) : -mag;
6899 end
6900 end
6901 endfunction
6902"#;
6903
6904pub fn emit_seq_datapath_testbench(name: &str, n_in: usize, n: usize) -> String {
6908 let decls = (0..n_in)
6909 .map(|k| format!(" reg [63:0] in{k};\n"))
6910 .collect::<String>();
6911 let ports = (0..n_in)
6912 .map(|k| format!(".in{k}(in{k}), "))
6913 .collect::<String>();
6914 let drive = (0..n_in)
6915 .map(|k| format!(" in{k} = oin[i*{n_in}+{k}];\n"))
6916 .collect::<String>();
6917 format!(
6918 r#"// tb_seq_{name} — the sequenced datapath vs the RUNNING VM, over the {n} vectors.
6919`timescale 1ns/1ps
6920module tb_seq_{name};
6921 reg clk=0, rst=1;
6922{decls} reg [63:0] oin [0:{in_last}];
6923 reg [63:0] oout [0:{last}];
6924 wire [63:0] result; wire done;
6925 integer i, errors=0, timeout;
6926 seq_{name} dut(.clk(clk), .rst(rst), {ports}.result(result), .done(done));
6927 always #5 clk = ~clk;
6928 initial begin
6929 $readmemh("seq_{name}_in.hex", oin);
6930 $readmemh("seq_{name}_out.hex", oout);
6931 for (i=0; i<{n}; i=i+1) begin
6932{drive} rst=1'b1; @(posedge clk); #1; rst=1'b0;
6933 timeout=0;
6934 while (!done && timeout<200000) begin @(posedge clk); timeout=timeout+1; end
6935 #1;
6936 if (result !== oout[i]) begin errors=errors+1; $display("FAIL vec %0d: got %0d exp %0d", i, $signed(result), $signed(oout[i])); end
6937 end
6938 if (errors==0) $display("PASS: {n} sequenced-datapath evaluations match the VM (multi-cycle tiles ≡VM)");
6939 else $display("FAIL: %0d sequenced-datapath mismatches", errors);
6940 $finish;
6941 end
6942endmodule
6943"#,
6944 in_last = n_in * n - 1,
6945 last = n - 1,
6946 )
6947}
6948
6949pub fn decision_fabric_is_float_arith(df: &DecisionFabric) -> bool {
6962 !df.cells.is_empty()
6963 && df.cells.iter().all(|c| {
6964 matches!(
6965 c,
6966 LoopCell::Func {
6967 unit: FuncUnit::FAddF
6968 | FuncUnit::FSubF
6969 | FuncUnit::FMulF
6970 | FuncUnit::FDivF
6971 | FuncUnit::FSqrtF,
6972 ..
6973 }
6974 )
6975 })
6976}
6977
6978pub fn emit_float_datapath_verilog(name: &str, df: &DecisionFabric) -> String {
6982 let name_in = |k: usize| format!("in{k}");
6983 let name_val = |k: usize| format!("R[{k}]");
6984 let src = |s: &Src| src_named(s, &name_in, &name_val);
6985 let m = df.cells.len();
6986 assert!(m > 0, "a float datapath must have at least one cell");
6987
6988 let inputs = (0..df.n_in)
6989 .map(|k| format!("input [63:0] in{k}"))
6990 .collect::<Vec<_>>()
6991 .join(", ");
6992 let inputs = if inputs.is_empty() {
6993 String::new()
6994 } else {
6995 format!("{inputs}, ")
6996 };
6997
6998 let mut arms = String::new();
7000 for (i, c) in df.cells.iter().enumerate() {
7001 let (usel, a, b) = match c {
7002 LoopCell::Func { unit, a, b } => match unit {
7003 FuncUnit::FAddF => (0, src(a), src(b)),
7004 FuncUnit::FSubF => (1, src(a), src(b)),
7005 FuncUnit::FMulF => (2, src(a), src(b)),
7006 FuncUnit::FDivF => (3, src(a), src(b)),
7007 FuncUnit::FSqrtF => (4, src(a), "64'd0".to_string()),
7008 other => panic!(
7009 "float datapath: {other:?} is not a bound fpu.v unit (add/sub/mul/div/sqrt)"
7010 ),
7011 },
7012 other => panic!("float datapath: cell {other:?} is not a float functional unit"),
7013 };
7014 arms.push_str(&format!(
7015 " {i}: begin opa = {a}; opb = {b}; usel = 3'd{usel}; end\n"
7016 ));
7017 }
7018
7019 format!(
7020 "// float_{name} — a pure-float-arithmetic datapath as a SEQUENCED controller binding fpu.v. One of each\n\
7021 // multi-cycle IEEE-754 unit (fp_addsub/fp_mul/fp_div/fp_sqrt) is SHARED across cells; a micro-sequencer\n\
7022 // muxes each cell's operands to the selected unit, pulses start, waits done, latches the result. The\n\
7023 // streamed `result` is the f64 BIT-PATTERN of the final float. Proven == the model (eval_grid over f64\n\
7024 // bits) == Rust f64 by iverilog; fpu.v is bit-exact to the VM.\n\
7025 module float_{name}(input clk, input rst, {inputs}output [63:0] result, output done);\n\
7026 \x20 localparam M = {m};\n\
7027 \x20 (* ram_style = \"registers\" *) reg [63:0] R [0:M-1];\n\
7028 \x20 reg [31:0] step;\n\
7029 \x20 reg running, issued;\n\
7030 \x20 reg [63:0] opa, opb; reg [2:0] usel;\n\
7031 \x20 always @(*) begin\n\
7032 \x20\x20 opa = 64'd0; opb = 64'd0; usel = 3'd0;\n\
7033 \x20\x20 case (step)\n\
7034 {arms}\
7035 \x20\x20\x20 default: ;\n\
7036 \x20\x20 endcase\n\
7037 \x20 end\n\
7038 \x20 wire is_addsub = (usel == 3'd0) || (usel == 3'd1);\n\
7039 \x20 wire is_sub = (usel == 3'd1);\n\
7040 \x20 wire is_mul = (usel == 3'd2);\n\
7041 \x20 wire is_div = (usel == 3'd3);\n\
7042 \x20 wire is_sqrt = (usel == 3'd4);\n\
7043 \x20 // the start pulse fires for exactly the selected unit in the cycle a step is issued.\n\
7044 \x20 wire pulse = running && !issued;\n\
7045 \x20 wire add_done, mul_done, div_done, sqrt_done;\n\
7046 \x20 wire [63:0] add_y, mul_y, div_y, sqrt_y;\n\
7047 \x20 fp_addsub u_add(.clk(clk), .rst(rst), .start(pulse && is_addsub), .a(opa), .b_in(opb), .sub(is_sub), .y(add_y), .done(add_done));\n\
7048 \x20 fp_mul u_mul(.clk(clk), .rst(rst), .start(pulse && is_mul), .a(opa), .b(opb), .y(mul_y), .done(mul_done));\n\
7049 \x20 fp_div u_div(.clk(clk), .rst(rst), .start(pulse && is_div), .a(opa), .b(opb), .y(div_y), .done(div_done));\n\
7050 \x20 fp_sqrt u_sqrt(.clk(clk), .rst(rst), .start(pulse && is_sqrt), .a(opa), .y(sqrt_y), .done(sqrt_done));\n\
7051 \x20 wire sel_done = is_addsub ? add_done : is_mul ? mul_done : is_div ? div_done : sqrt_done;\n\
7052 \x20 wire [63:0] sel_y = is_addsub ? add_y : is_mul ? mul_y : is_div ? div_y : sqrt_y;\n\
7053 \x20 always @(posedge clk) begin\n\
7054 \x20\x20 if (rst) begin step <= 32'd0; running <= (M > 0); issued <= 1'b0; end\n\
7055 \x20\x20 else if (running) begin\n\
7056 \x20\x20\x20 if (!issued) issued <= 1'b1; // the start pulse fired this cycle\n\
7057 \x20\x20\x20 else if (sel_done) begin\n\
7058 \x20\x20\x20\x20 R[step] <= sel_y; issued <= 1'b0;\n\
7059 \x20\x20\x20\x20 if (step == M - 1) running <= 1'b0; else step <= step + 32'd1;\n\
7060 \x20\x20\x20 end\n\
7061 \x20\x20 end\n\
7062 \x20 end\n\
7063 \x20 assign done = !running;\n\
7064 \x20 assign result = {res};\n\
7065 endmodule\n",
7066 res = src(&df.result),
7067 )
7068}
7069
7070pub fn decision_fabric_is_pow(df: &DecisionFabric) -> bool {
7083 !df.cells.is_empty()
7084 && df.cells.iter().all(|c| {
7085 matches!(
7086 c,
7087 LoopCell::Func {
7088 unit: FuncUnit::Pow,
7089 ..
7090 }
7091 )
7092 })
7093}
7094
7095pub fn emit_pow_datapath_verilog(name: &str, df: &DecisionFabric) -> String {
7099 let name_in = |k: usize| format!("in{k}");
7100 let name_val = |k: usize| format!("R[{k}]");
7101 let src = |s: &Src| src_named(s, &name_in, &name_val);
7102 let m = df.cells.len();
7103 assert!(m > 0, "a power datapath must have at least one cell");
7104
7105 let inputs = (0..df.n_in)
7106 .map(|k| format!("input signed [63:0] in{k}"))
7107 .collect::<Vec<_>>()
7108 .join(", ");
7109 let inputs = if inputs.is_empty() {
7110 String::new()
7111 } else {
7112 format!("{inputs}, ")
7113 };
7114
7115 let mut arms = String::new();
7117 for (i, c) in df.cells.iter().enumerate() {
7118 let (a, b) = match c {
7119 LoopCell::Func {
7120 unit: FuncUnit::Pow,
7121 a,
7122 b,
7123 } => (src(a), src(b)),
7124 other => panic!("power datapath: cell {other:?} is not a `Pow` functional unit"),
7125 };
7126 arms.push_str(&format!(" {i}: begin base = {a}; expo = {b}; end\n"));
7127 }
7128
7129 format!(
7130 "// pow_{name} — a pure-exponentiation datapath as a SEQUENCED controller. One shared multiply-accumulate\n\
7131 // loop is time-multiplexed across the cells: per cell, latch (base, exponent), then multiply the\n\
7132 // accumulator `exponent` times (a negative exponent clamps to 0 → result 1), latch the power, advance.\n\
7133 // 64-bit signed wrapping, so bit-identical to the VM's `a ** b` (`wrapping_pow`). Proven == the model\n\
7134 // (eval) == VM by iverilog (fabric_pow_lock); NOT SAT (an unbounded multiply loop is outside QF_BV).\n\
7135 module pow_{name}(input clk, input rst, {inputs}output signed [63:0] result, output done);\n\
7136 \x20 localparam M = {m};\n\
7137 \x20 (* ram_style = \"registers\" *) reg signed [63:0] R [0:M-1];\n\
7138 \x20 reg [31:0] step;\n\
7139 \x20 reg running, loading;\n\
7140 \x20 reg signed [63:0] base, expo;\n\
7141 \x20 reg signed [63:0] acc; // running product for the current cell\n\
7142 \x20 reg [63:0] rem; // remaining multiplies (exponent, clamped ≥ 0)\n\
7143 \x20 always @(*) begin\n\
7144 \x20\x20 base = 64'sd0; expo = 64'sd0;\n\
7145 \x20\x20 case (step)\n\
7146 {arms}\
7147 \x20\x20\x20 default: ;\n\
7148 \x20\x20 endcase\n\
7149 \x20 end\n\
7150 \x20 always @(posedge clk) begin\n\
7151 \x20\x20 if (rst) begin step <= 32'd0; running <= (M > 0); loading <= 1'b1; acc <= 64'sd1; rem <= 64'd0; end\n\
7152 \x20\x20 else if (running) begin\n\
7153 \x20\x20\x20 if (loading) begin\n\
7154 \x20\x20\x20\x20 acc <= 64'sd1; rem <= (expo[63] ? 64'd0 : expo); loading <= 1'b0;\n\
7155 \x20\x20\x20 end else if (rem == 64'd0) begin\n\
7156 \x20\x20\x20\x20 R[step] <= acc; loading <= 1'b1;\n\
7157 \x20\x20\x20\x20 if (step == M - 1) running <= 1'b0; else step <= step + 32'd1;\n\
7158 \x20\x20\x20 end else begin\n\
7159 \x20\x20\x20\x20 acc <= acc * base; rem <= rem - 64'd1;\n\
7160 \x20\x20\x20 end\n\
7161 \x20\x20 end\n\
7162 \x20 end\n\
7163 \x20 assign done = !running;\n\
7164 \x20 assign result = {res};\n\
7165 endmodule\n",
7166 res = src(&df.result),
7167 )
7168}
7169
7170pub fn emit_pow_datapath_testbench(name: &str, n_in: usize, n: usize, max_cycles: usize) -> String {
7174 let drive = (0..n_in)
7175 .map(|k| format!(" in{k} = oin[i * {n_in} + {k}];\n"))
7176 .collect::<String>();
7177 let ports = (0..n_in)
7178 .map(|k| format!(".in{k}(in{k}), "))
7179 .collect::<String>();
7180 let decls = (0..n_in)
7181 .map(|k| format!(" reg signed [63:0] in{k};\n"))
7182 .collect::<String>();
7183 format!(
7184 r#"// tb_pow_{name} — the sequenced power datapath vs the RUNNING VM over the exponentiation battery.
7185`timescale 1ns/1ps
7186module tb_pow_{name};
7187 reg clk = 0, rst;
7188{decls} reg signed [63:0] oin [0:{in_last}];
7189 reg signed [63:0] oout [0:{last}];
7190 wire signed [63:0] result;
7191 wire done;
7192 integer i, c, errors = 0;
7193 pow_{name} dut(.clk(clk), .rst(rst), {ports}.result(result), .done(done));
7194 always #5 clk = ~clk;
7195 initial begin
7196 $readmemh("pow_{name}_in.hex", oin);
7197 $readmemh("pow_{name}_out.hex", oout);
7198 for (i = 0; i < {n}; i = i + 1) begin
7199 @(negedge clk);
7200{drive} rst = 1; @(negedge clk); rst = 0;
7201 c = 0; while (!done && c < {max_cycles}) begin @(negedge clk); c = c + 1; end
7202 if (result !== oout[i]) begin errors = errors + 1; $display("FAIL vec %0d: got %0d", i, result); end
7203 end
7204 if (errors == 0) $display("PASS: {n} power evaluations match the VM (a`b = the sequenced MAC lane)");
7205 else $display("FAIL: %0d pow mismatches", errors);
7206 $finish;
7207 end
7208endmodule
7209"#,
7210 in_last = n_in * n - 1,
7211 last = n - 1,
7212 max_cycles = max_cycles,
7213 )
7214}
7215
7216pub const STATE_PLANE_HEAP_WORDS: usize = 64;
7231
7232pub fn decision_fabric_has_state(df: &DecisionFabric) -> bool {
7235 df.cells.iter().any(|c| {
7236 matches!(
7237 c,
7238 LoopCell::HeapRead { .. } | LoopCell::HeapWrite { .. } | LoopCell::HeapAlloc { .. }
7239 )
7240 })
7241}
7242
7243pub fn emit_state_plane_verilog(name: &str, df: &DecisionFabric, heap_words: usize) -> String {
7247 let name_in = |k: usize| format!("in{k}");
7248 let name_val = |k: usize| format!("R[{k}]");
7250 let src = |s: &Src| src_named(s, &name_in, &name_val);
7251 let m = df.cells.len();
7252 assert!(m > 0, "a state-plane fabric must have at least one cell");
7253 let aw = (usize::BITS - (heap_words - 1).leading_zeros()).max(1) as usize; let inputs = (0..df.n_in)
7256 .map(|k| format!("input [63:0] in{k}"))
7257 .collect::<Vec<_>>()
7258 .join(", ");
7259 let inputs = if inputs.is_empty() {
7260 String::new()
7261 } else {
7262 format!("{inputs}, ")
7263 };
7264
7265 let mut wires = String::new();
7268 let mut arms = String::new();
7270 for (i, c) in df.cells.iter().enumerate() {
7271 match c {
7272 LoopCell::Arith { op, a, b } => {
7273 wires.push_str(&format!(" wire [63:0] v{i} = {};\n", op.verilog(&src(a), &src(b))));
7274 arms.push_str(&format!(" {i}: R[{i}] <= v{i};\n"));
7275 }
7276 LoopCell::VectorArith { op, ty, a, b } => {
7277 wires.push_str(&format!(" wire [63:0] v{i} = {};\n", op.verilog(*ty, &src(a), &src(b))));
7278 arms.push_str(&format!(" {i}: R[{i}] <= v{i};\n"));
7279 }
7280 LoopCell::Func { unit, a, b } => {
7281 wires.push_str(&format!(" wire [63:0] v{i} = {};\n", unit.verilog(&src(a), &src(b))));
7282 arms.push_str(&format!(" {i}: R[{i}] <= v{i};\n"));
7283 }
7284 LoopCell::Select { cmp, ca, cb, t, e } => {
7285 wires.push_str(&format!(
7286 " wire [63:0] v{i} = ($signed({}) {} $signed({})) ? {} : {};\n",
7287 src(ca), cmp.verilog(), src(cb), src(t), src(e)
7288 ));
7289 arms.push_str(&format!(" {i}: R[{i}] <= v{i};\n"));
7290 }
7291 LoopCell::HeapRead { addr } => {
7292 wires.push_str(&format!(" wire [63:0] addr{i} = {};\n", src(addr)));
7293 arms.push_str(&format!(" {i}: R[{i}] <= heap[addr{i}[{hi}:0]];\n", hi = aw - 1));
7295 }
7296 LoopCell::HeapWrite { addr, val } => {
7297 wires.push_str(&format!(" wire [63:0] addr{i} = {};\n", src(addr)));
7298 wires.push_str(&format!(" wire [63:0] wval{i} = {};\n", src(val)));
7299 arms.push_str(&format!(
7302 " {i}: begin heap[addr{i}[{hi}:0]] <= wval{i}; R[{i}] <= wval{i}; \
7303 if ($signed(addr{i}) + 64'sd1 > $signed(hp)) hp <= addr{i} + 64'sd1; end\n",
7304 hi = aw - 1
7305 ));
7306 }
7307 LoopCell::HeapAlloc { size } => {
7308 wires.push_str(&format!(" wire [63:0] size{i} = {};\n", src(size)));
7309 arms.push_str(&format!(
7311 " {i}: begin R[{i}] <= hp; hp <= hp + size{i}; end\n"
7312 ));
7313 }
7314 LoopCell::MemRead { .. } => panic!(
7315 "state plane: a loop-sequence MemRead is the induction-counter scratchpad (P2), distinct \
7316 from the shared-heap state plane"
7317 ),
7318 }
7319 }
7320
7321 format!(
7322 "// state_{name} — a COLLECTION/STRUCT program as a SEQUENCED state-plane controller: a hardwired\n\
7323 // micro-sequencer (no fetch, no decode — the cell sequence IS the program) over ONE shared heap\n\
7324 // (LUTRAM: async read, sync write) with a bump-pointer allocator. Each cell is one step; pure-compute\n\
7325 // cells latch a combinational result, heap cells read/write the heap in program order. This is the\n\
7326 // Futamura projection for STATEFUL code: the residual dataflow-with-memory IS the circuit; proven ==\n\
7327 // the running VM by iverilog (mutable heap is outside QF_BV — eval+iverilog, not SAT). 64-bit wraps.\n\
7328 module state_{name}(input clk, input rst, {inputs}output [63:0] result, output done);\n\
7329 \x20 localparam M = {m};\n\
7330 \x20 localparam HEAP = {heap_words};\n\
7331 \x20 // ram_style=registers: the open FOSS flow (yosys+nextpnr-xilinx) drops write-ported LUT-RAM, so\n\
7332 \x20 // the shared heap + cell registers are forced to real FFs (a small register file) for silicon.\n\
7333 \x20 (* ram_style = \"registers\" *) reg [63:0] R [0:M-1];\n\
7334 \x20 (* ram_style = \"registers\" *) reg [63:0] heap [0:HEAP-1];\n\
7335 \x20 reg [63:0] hp;\n\
7336 \x20 reg [31:0] step;\n\
7337 \x20 reg running;\n\
7338 \x20 integer j;\n\
7339 {wires}\
7340 \x20 always @(posedge clk) begin\n\
7341 \x20 if (rst) begin\n\
7342 \x20 step <= 0; hp <= 0; running <= (M > 0);\n\
7343 \x20 for (j = 0; j < HEAP; j = j + 1) heap[j] <= 64'd0;\n\
7344 \x20 end else if (running) begin\n\
7345 \x20 case (step)\n\
7346 {arms}\
7347 \x20 default: ;\n\
7348 \x20 endcase\n\
7349 \x20 if (step == M - 1) running <= 1'b0;\n\
7350 \x20 else step <= step + 1;\n\
7351 \x20 end\n\
7352 \x20 end\n\
7353 \x20 assign done = !running;\n\
7354 \x20 assign result = {res};\n\
7355 endmodule\n",
7356 res = src(&df.result),
7357 )
7358}
7359
7360pub fn emit_state_plane_testbench(name: &str, n_in: usize, n: usize, max_cycles: usize) -> String {
7364 let drive = (0..n_in)
7365 .map(|k| format!("in{k} = oin[i*{n_in}+{k}];"))
7366 .collect::<Vec<_>>()
7367 .join(" ");
7368 let ports = (0..n_in)
7369 .map(|k| format!(".in{k}(in{k})"))
7370 .collect::<Vec<_>>()
7371 .join(", ");
7372 let ports = if ports.is_empty() {
7373 String::new()
7374 } else {
7375 format!("{ports}, ")
7376 };
7377 let decls = (0..n_in)
7378 .map(|k| format!(" reg [63:0] in{k};"))
7379 .collect::<Vec<_>>()
7380 .join("\n");
7381 format!(
7382 r#"// tb_state_{name} — the sequenced state-plane controller vs the RUNNING VM over the collection program.
7383`timescale 1ns/1ps
7384module tb_state_{name};
7385 reg clk = 0, rst;
7386{decls}
7387 reg [63:0] oin [0:{ilast}];
7388 reg [63:0] oout [0:{olast}];
7389 wire [63:0] result;
7390 wire done;
7391 integer i, c, errors = 0;
7392 state_{name} dut(.clk(clk), .rst(rst), {ports}.result(result), .done(done));
7393 always #5 clk = ~clk;
7394 initial begin
7395 $readmemh("state_{name}_in.hex", oin);
7396 $readmemh("state_{name}_out.hex", oout);
7397 for (i = 0; i < {n}; i = i + 1) begin
7398 @(negedge clk); {drive} rst = 1;
7399 @(negedge clk); rst = 0;
7400 c = 0;
7401 while (!done && c < {maxc}) begin @(negedge clk); c = c + 1; end
7402 if (result !== oout[i]) begin errors = errors + 1; $display("FAIL case %0d: got %0d exp %0d", i, result, oout[i]); end
7403 end
7404 if (errors == 0) $display("PASS: {n} state-plane runs match the VM (collections on the heap, sequenced)");
7405 else $display("FAIL: %0d state-plane mismatches", errors);
7406 $finish;
7407 end
7408endmodule
7409"#,
7410 ilast = (n * n_in).saturating_sub(1),
7411 olast = n.saturating_sub(1),
7412 maxc = max_cycles,
7413 )
7414}
7415
7416#[derive(Clone, Debug)]
7430pub enum Term {
7431 Jump(usize),
7433 Branch {
7435 cond: Src,
7436 then_blk: usize,
7437 else_blk: usize,
7438 },
7439 Return(Src),
7441}
7442
7443#[derive(Clone, Debug)]
7446pub struct CfgBlock {
7447 pub inputs: Vec<u16>,
7449 pub body: Vec<LoopCell>,
7451 pub writes: Vec<(u16, Src)>,
7453 pub shows: Vec<Src>,
7461 pub term: Term,
7463}
7464
7465#[derive(Clone, Debug)]
7467pub struct ProgramCfgFabric {
7468 pub n_params: usize,
7469 pub blocks: Vec<CfgBlock>,
7470}
7471
7472pub(crate) const SEQ_RESERVE: i64 = 2048;
7483
7484thread_local! {
7485 static SEQ_RESERVE_OVERRIDE: std::cell::RefCell<Option<i64>> = const { std::cell::RefCell::new(None) };
7492}
7493
7494pub(crate) fn seq_reserve() -> i64 {
7496 SEQ_RESERVE_OVERRIDE.with(|c| c.borrow().unwrap_or(SEQ_RESERVE))
7497}
7498
7499pub fn with_seq_reserve<T>(n: i64, f: impl FnOnce() -> T) -> T {
7502 let prev = SEQ_RESERVE_OVERRIDE.with(|c| c.replace(Some(n)));
7503 let out = f();
7504 SEQ_RESERVE_OVERRIDE.with(|c| *c.borrow_mut() = prev);
7505 out
7506}
7507
7508fn promote_float_operand(cells: &mut Vec<LoopCell>, s: Src, is_float: bool) -> Src {
7514 if is_float {
7515 return s;
7516 }
7517 let k = cells.len();
7518 cells.push(LoopCell::Func {
7519 unit: FuncUnit::IntToF,
7520 a: s,
7521 b: Src::Const(0),
7522 });
7523 Src::Val(k)
7524}
7525
7526fn mark_int_reg(dst: u16) {
7531 FLOAT_REGS.with(|t| {
7532 t.borrow_mut().remove(&dst);
7533 });
7534 FLOAT_COLLS.with(|t| {
7535 t.borrow_mut().remove(&dst);
7536 });
7537}
7538
7539#[derive(Debug, Clone, PartialEq, Eq)]
7544pub enum LoweringError {
7545 UnsupportedOp(String),
7547 UnsupportedBuiltin(String),
7549 UnsupportedLoopBody(String),
7551 StatePlaneUnlowered(String),
7557}
7558
7559impl std::fmt::Display for LoweringError {
7560 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7561 match self {
7562 LoweringError::UnsupportedOp(op) => write!(
7563 f,
7564 "cfg block: op `{op}` unsupported (straight-line integer + collection body only)"
7565 ),
7566 LoweringError::UnsupportedBuiltin(b) => write!(
7567 f,
7568 "cfg block: builtin `{b}` unsupported on the whole-program fabric \
7569 (only floor/sqrt over the float lane are wired)"
7570 ),
7571 LoweringError::UnsupportedLoopBody(op) => {
7572 write!(f, "loop fabric: body op `{op}` unsupported")
7573 }
7574 LoweringError::StatePlaneUnlowered(cell) => write!(
7575 f,
7576 "seq datapath: {cell} (Mem/Heap) is a state-plane follow-on"
7577 ),
7578 }
7579 }
7580}
7581impl std::error::Error for LoweringError {}
7582
7583pub fn program_cfg_fabric(src: &str, fn_name: &str) -> ProgramCfgFabric {
7592 try_program_cfg_fabric(src, fn_name).unwrap_or_else(|e| panic!("{e}"))
7593}
7594
7595pub fn try_program_cfg_fabric(src: &str, fn_name: &str) -> Result<ProgramCfgFabric, LoweringError> {
7596 try_cfg_fabric_at(src, Some(fn_name))
7597}
7598
7599pub fn try_main_cfg_fabric(src: &str) -> Result<ProgramCfgFabric, LoweringError> {
7621 try_cfg_fabric_at(src, None)
7622}
7623
7624fn try_cfg_fabric_at(src: &str, fn_name: Option<&str>) -> Result<ProgramCfgFabric, LoweringError> {
7625 use crate::vm::wasm::cfg::Blocks;
7626 use crate::vm::{Compiler, Constant, Op};
7627 use std::collections::HashMap;
7628
7629 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
7630 let (stmts, types, _p) = match parsed {
7637 Ok(v) => v,
7638 Err(e) => {
7639 return Err(LoweringError::UnsupportedOp(format!(
7640 "the program does not parse: {e:?}"
7641 )))
7642 }
7643 };
7644 let program = match Compiler::compile_with_types(stmts, interner, Some(types)) {
7645 Ok(p) => p,
7646 Err(e) => {
7647 return Err(LoweringError::UnsupportedOp(format!(
7648 "the program does not compile: {e:?}"
7649 )))
7650 }
7651 };
7652 let (entry_pc, n_params) = match fn_name {
7659 None => (0usize, 0u16),
7660 Some(want) => match program
7661 .functions
7662 .iter()
7663 .find(|f| interner.resolve(f.name) == want)
7664 {
7665 Some(f) => (f.entry_pc, f.param_count),
7666 None => {
7667 let mut known: Vec<&str> = program
7668 .functions
7669 .iter()
7670 .map(|f| interner.resolve(f.name))
7671 .collect();
7672 known.sort_unstable();
7673 return Err(LoweringError::UnsupportedOp(format!(
7674 "no function named `{want}` in this program (it defines: {}) — the top-level `## Main` is lowered by `try_main_cfg_fabric`, not by name",
7675 if known.is_empty() { "nothing".to_string() } else { known.join(", ") }
7676 )));
7677 }
7678 },
7679 };
7680 FLOAT_REGS.with(|t| t.borrow_mut().clear());
7683 FLOAT_COLLS.with(|t| t.borrow_mut().clear());
7684 TEXT_REGS.with(|t| t.borrow_mut().clear());
7685 NAME_REGS.with(|t| t.borrow_mut().clear());
7686 COLL_LEN.with(|t| t.borrow_mut().clear());
7687 let entry = entry_pc;
7688 let end = program
7689 .functions
7690 .iter()
7691 .map(|g| g.entry_pc)
7692 .filter(|&e| e > entry)
7693 .min()
7694 .unwrap_or(program.code.len());
7695 let region: Vec<Op> = (entry..end)
7697 .map(|pc| {
7698 let mut op = program.code[pc];
7699 match &mut op {
7700 Op::Jump { target }
7701 | Op::JumpIfFalse { target, .. }
7702 | Op::JumpIfTrue { target, .. } => *target -= entry,
7703 _ => {}
7704 }
7705 op
7706 })
7707 .collect();
7708 let blocks =
7715 Blocks::new(®ion).expect("cfg fabric: function region is not self-contained");
7716 let nb = blocks.num_blocks();
7717
7718 let get = |reg: &mut HashMap<u16, Src>, inputs: &mut Vec<u16>, r: u16| -> Src {
7721 if let Some(s) = reg.get(&r) {
7722 return *s;
7723 }
7724 let k = inputs.len();
7725 inputs.push(r);
7726 let s = Src::In(k);
7727 reg.insert(r, s);
7728 s
7729 };
7730 let cmp_of = |op: &Op| -> Option<CmpKind> {
7731 Some(match op {
7732 Op::Lt { .. } => CmpKind::Lt,
7733 Op::Gt { .. } => CmpKind::Gt,
7734 Op::LtEq { .. } => CmpKind::Le,
7735 Op::GtEq { .. } => CmpKind::Ge,
7736 Op::Eq { .. } => CmpKind::Eq,
7737 Op::NotEq { .. } => CmpKind::Ne,
7738 _ => return None,
7739 })
7740 };
7741
7742 let mut out_blocks: Vec<CfgBlock> = Vec::with_capacity(nb);
7743 for k in 0..nb {
7744 let bstart = blocks.start(k);
7745 let bend = blocks.end(k);
7746 let ops = ®ion[bstart..bend];
7747 let (body_ops, term_op): (&[Op], Option<Op>) = match ops.last() {
7748 Some(
7753 t @ (Op::Jump { .. }
7754 | Op::JumpIfFalse { .. }
7755 | Op::JumpIfTrue { .. }
7756 | Op::Return { .. }
7757 | Op::ReturnNothing
7758 | Op::Halt),
7759 ) => (&ops[..ops.len() - 1], Some(*t)),
7760 _ => (ops, None), };
7762
7763 let mut reg: HashMap<u16, Src> = HashMap::new();
7764 let mut inputs: Vec<u16> = Vec::new();
7765 let mut cells: Vec<LoopCell> = Vec::new();
7766 let mut shows: Vec<Src> = Vec::new();
7767 let mut write_order: Vec<u16> = Vec::new();
7768 let record = |write_order: &mut Vec<u16>, dst: u16| {
7769 if !write_order.contains(&dst) {
7770 write_order.push(dst);
7771 }
7772 };
7773 for op in body_ops {
7774 forget_overwritten(op, |func| {
7775 program
7776 .functions
7777 .get(func as usize)
7778 .map_or(0, |f| f.captures.len())
7779 });
7780 if let Some(kind) = cmp_of(op) {
7781 if let Op::Lt { dst, lhs, rhs }
7782 | Op::Gt { dst, lhs, rhs }
7783 | Op::LtEq { dst, lhs, rhs }
7784 | Op::GtEq { dst, lhs, rhs }
7785 | Op::Eq { dst, lhs, rhs }
7786 | Op::NotEq { dst, lhs, rhs } = op
7787 {
7788 let ca = get(&mut reg, &mut inputs, *lhs);
7789 let cb = get(&mut reg, &mut inputs, *rhs);
7790 let byte_of = |cells: &mut Vec<LoopCell>, r: u16, s: Src| -> Src {
7795 let one_char = TEXT_REGS.with(|t| t.borrow().contains(&r))
7796 && COLL_LEN.with(|t| t.borrow().get(&r).copied() == Some(1));
7797 if one_char {
7798 let kadd = cells.len();
7799 cells.push(LoopCell::Arith {
7800 op: AluOp::Add,
7801 a: s,
7802 b: Src::Const(1),
7803 });
7804 let kread = cells.len();
7805 cells.push(LoopCell::HeapRead {
7806 addr: Src::Val(kadd),
7807 });
7808 Src::Val(kread)
7809 } else {
7810 s
7811 }
7812 };
7813 let ca = byte_of(&mut cells, *lhs, ca);
7814 let cb = byte_of(&mut cells, *rhs, cb);
7815 let (lhs_f, rhs_f) = FLOAT_REGS
7816 .with(|t| (t.borrow().contains(lhs), t.borrow().contains(rhs)));
7817 if lhs_f || rhs_f {
7818 let a2 = promote_float_operand(&mut cells, ca, lhs_f);
7822 let b2 = promote_float_operand(&mut cells, cb, rhs_f);
7823 let unit = match kind {
7824 CmpKind::Lt => FuncUnit::FLtF,
7825 CmpKind::Gt => FuncUnit::FGtF,
7826 CmpKind::Le => FuncUnit::FLeF,
7827 CmpKind::Ge => FuncUnit::FGeF,
7828 CmpKind::Eq => FuncUnit::FEqF,
7829 CmpKind::Ne => FuncUnit::FNeF,
7830 };
7831 let kk = cells.len();
7832 cells.push(LoopCell::Func { unit, a: a2, b: b2 });
7833 reg.insert(*dst, Src::Val(kk));
7834 FLOAT_REGS.with(|t| {
7835 t.borrow_mut().remove(dst); });
7837 } else {
7838 let kk = cells.len();
7839 cells.push(LoopCell::Select {
7841 cmp: kind,
7842 ca,
7843 cb,
7844 t: Src::Const(1),
7845 e: Src::Const(0),
7846 });
7847 reg.insert(*dst, Src::Val(kk));
7848 }
7849 mark_int_reg(*dst); record(&mut write_order, *dst);
7851 }
7852 continue;
7853 }
7854 match op {
7855 Op::LoadConst { dst, idx } => match &program.constants[*idx as usize] {
7856 Constant::Int(n) => {
7857 reg.insert(*dst, Src::Const(*n));
7858 mark_int_reg(*dst);
7859 record(&mut write_order, *dst);
7860 }
7861 Constant::Text(s) => {
7865 let ptr = heap_text_literal(&mut cells, s.as_bytes(), seq_reserve());
7869 reg.insert(*dst, ptr);
7870 TEXT_REGS.with(|t| {
7871 t.borrow_mut().insert(*dst);
7872 });
7873 COLL_LEN.with(|t| {
7874 t.borrow_mut().insert(*dst, s.len() as i64);
7875 });
7876 record(&mut write_order, *dst);
7877 }
7878 Constant::Float(f) => {
7882 reg.insert(*dst, Src::Const(f.to_bits() as i64));
7883 FLOAT_REGS.with(|t| {
7884 t.borrow_mut().insert(*dst);
7885 });
7886 record(&mut write_order, *dst);
7887 }
7888 Constant::Bool(b) => {
7891 reg.insert(*dst, Src::Const(i64::from(*b)));
7892 record(&mut write_order, *dst);
7893 }
7894 c => panic!("cfg block: unsupported constant {c:?}"),
7895 },
7896 Op::Move { dst, src } => {
7897 let s = get(&mut reg, &mut inputs, *src);
7898 reg.insert(*dst, s);
7899 let src_f = FLOAT_REGS.with(|t| t.borrow().contains(src));
7902 FLOAT_REGS.with(|t| {
7903 let mut s = t.borrow_mut();
7904 if src_f {
7905 s.insert(*dst);
7906 } else {
7907 s.remove(dst);
7908 }
7909 });
7910 let src_fc = FLOAT_COLLS.with(|t| t.borrow().contains(src));
7912 FLOAT_COLLS.with(|t| {
7913 let mut s = t.borrow_mut();
7914 if src_fc {
7915 s.insert(*dst);
7916 } else {
7917 s.remove(dst);
7918 }
7919 });
7920 let src_txt = TEXT_REGS.with(|t| t.borrow().contains(src));
7922 TEXT_REGS.with(|t| {
7923 let mut s = t.borrow_mut();
7924 if src_txt {
7925 s.insert(*dst);
7926 } else {
7927 s.remove(dst);
7928 }
7929 });
7930 let src_len = COLL_LEN.with(|t| t.borrow().get(src).copied());
7931 COLL_LEN.with(|t| {
7932 let mut m = t.borrow_mut();
7933 match src_len {
7934 Some(l) => {
7935 m.insert(*dst, l);
7936 }
7937 None => {
7938 m.remove(dst);
7939 }
7940 }
7941 });
7942 record(&mut write_order, *dst);
7943 }
7944 Op::Add { dst, lhs, rhs }
7945 | Op::Sub { dst, lhs, rhs }
7946 | Op::Mul { dst, lhs, rhs } => {
7947 let a = get(&mut reg, &mut inputs, *lhs);
7948 let b = get(&mut reg, &mut inputs, *rhs);
7949 let (lhs_f, rhs_f) = FLOAT_REGS
7950 .with(|t| (t.borrow().contains(lhs), t.borrow().contains(rhs)));
7951 if lhs_f || rhs_f {
7952 let a2 = promote_float_operand(&mut cells, a, lhs_f);
7956 let b2 = promote_float_operand(&mut cells, b, rhs_f);
7957 let unit = match op {
7958 Op::Add { .. } => FuncUnit::FAddF,
7959 Op::Sub { .. } => FuncUnit::FSubF,
7960 _ => FuncUnit::FMulF,
7961 };
7962 let kk = cells.len();
7963 cells.push(LoopCell::Func { unit, a: a2, b: b2 });
7964 reg.insert(*dst, Src::Val(kk));
7965 FLOAT_REGS.with(|t| {
7966 t.borrow_mut().insert(*dst);
7967 });
7968 } else {
7969 let alu = match op {
7970 Op::Add { .. } => AluOp::Add,
7971 Op::Sub { .. } => AluOp::Sub,
7972 _ => AluOp::Mul,
7973 };
7974 let kk = cells.len();
7975 cells.push(LoopCell::Arith { op: alu, a, b });
7976 reg.insert(*dst, Src::Val(kk));
7977 mark_int_reg(*dst);
7978 }
7979 record(&mut write_order, *dst);
7980 }
7981 Op::Div { dst, lhs, rhs }
7982 | Op::Mod { dst, lhs, rhs }
7983 | Op::FloorDiv { dst, lhs, rhs } => {
7984 let a = get(&mut reg, &mut inputs, *lhs);
7985 let b = get(&mut reg, &mut inputs, *rhs);
7986 let (lhs_f, rhs_f) = FLOAT_REGS
7987 .with(|t| (t.borrow().contains(lhs), t.borrow().contains(rhs)));
7988 if lhs_f || rhs_f {
7989 let a2 = promote_float_operand(&mut cells, a, lhs_f);
7992 let b2 = promote_float_operand(&mut cells, b, rhs_f);
7993 let kk = cells.len();
7994 cells.push(LoopCell::Func {
7995 unit: FuncUnit::FDivF,
7996 a: a2,
7997 b: b2,
7998 });
7999 reg.insert(*dst, Src::Val(kk));
8000 FLOAT_REGS.with(|t| {
8001 t.borrow_mut().insert(*dst);
8002 });
8003 } else {
8004 let unit = match op {
8005 Op::Mod { .. } => FuncUnit::ModS,
8006 Op::FloorDiv { .. } => FuncUnit::FloorDivS,
8007 _ => FuncUnit::DivS,
8008 };
8009 let kk = cells.len();
8010 cells.push(LoopCell::Func { unit, a, b });
8011 reg.insert(*dst, Src::Val(kk));
8012 mark_int_reg(*dst);
8013 }
8014 record(&mut write_order, *dst);
8015 }
8016 Op::BitAnd { dst, lhs, rhs }
8019 | Op::BitOr { dst, lhs, rhs }
8020 | Op::BitXor { dst, lhs, rhs }
8021 | Op::Shl { dst, lhs, rhs }
8022 | Op::Shr { dst, lhs, rhs } => {
8023 let a = get(&mut reg, &mut inputs, *lhs);
8024 let b = get(&mut reg, &mut inputs, *rhs);
8025 let alu = match op {
8026 Op::BitAnd { .. } => AluOp::And,
8027 Op::BitOr { .. } => AluOp::Or,
8028 Op::BitXor { .. } => AluOp::Xor,
8029 Op::Shl { .. } => AluOp::Shl,
8030 _ => AluOp::Shr,
8031 };
8032 let kk = cells.len();
8033 cells.push(LoopCell::Arith { op: alu, a, b });
8034 reg.insert(*dst, Src::Val(kk));
8035 mark_int_reg(*dst);
8036 record(&mut write_order, *dst);
8037 }
8038 Op::Pow { dst, lhs, rhs } => {
8040 let a = get(&mut reg, &mut inputs, *lhs);
8041 let b = get(&mut reg, &mut inputs, *rhs);
8042 let kk = cells.len();
8043 cells.push(LoopCell::Func {
8044 unit: FuncUnit::Pow,
8045 a,
8046 b,
8047 });
8048 reg.insert(*dst, Src::Val(kk));
8049 mark_int_reg(*dst);
8050 record(&mut write_order, *dst);
8051 }
8052 Op::AddAssign { dst, src } => {
8053 let a = get(&mut reg, &mut inputs, *dst);
8054 let b = get(&mut reg, &mut inputs, *src);
8055 let (dst_f, src_f) = FLOAT_REGS
8056 .with(|t| (t.borrow().contains(dst), t.borrow().contains(src)));
8057 let dst_txt = TEXT_REGS.with(|t| t.borrow().contains(dst));
8058 if dst_txt {
8059 let klen = cells.len();
8061 cells.push(LoopCell::HeapRead { addr: a }); let len = Src::Val(klen);
8063 if let Some(l) = COLL_LEN.with(|t| t.borrow().get(src).copied()) {
8064 for j in 0..l {
8067 let ksrc = cells.len();
8068 cells.push(LoopCell::Arith {
8069 op: AluOp::Add,
8070 a: b,
8071 b: Src::Const(j + 1),
8072 });
8073 let kbyte = cells.len();
8074 cells.push(LoopCell::HeapRead {
8075 addr: Src::Val(ksrc),
8076 }); let kd1 = cells.len();
8078 cells.push(LoopCell::Arith {
8079 op: AluOp::Add,
8080 a,
8081 b: Src::Const(j + 1),
8082 });
8083 let kd2 = cells.len();
8084 cells.push(LoopCell::Arith {
8085 op: AluOp::Add,
8086 a: Src::Val(kd1),
8087 b: len,
8088 });
8089 cells.push(LoopCell::HeapWrite {
8090 addr: Src::Val(kd2),
8091 val: Src::Val(kbyte),
8092 });
8093 }
8094 let knl = cells.len();
8095 cells.push(LoopCell::Arith {
8096 op: AluOp::Add,
8097 a: len,
8098 b: Src::Const(l),
8099 });
8100 cells.push(LoopCell::HeapWrite {
8101 addr: a,
8102 val: Src::Val(knl),
8103 });
8104 } else {
8105 const MAXD: i64 = 19; let mut d = Src::Const(1);
8112 let mut p10: i64 = 10;
8113 for _ in 1..MAXD {
8114 let kge = cells.len();
8115 cells.push(LoopCell::Select {
8116 cmp: CmpKind::Ge,
8117 ca: b,
8118 cb: Src::Const(p10),
8119 t: Src::Const(1),
8120 e: Src::Const(0),
8121 });
8122 let kadd = cells.len();
8123 cells.push(LoopCell::Arith {
8124 op: AluOp::Add,
8125 a: d,
8126 b: Src::Val(kge),
8127 });
8128 d = Src::Val(kadd);
8129 p10 = p10.saturating_mul(10);
8130 }
8131 let klpd = cells.len();
8132 cells.push(LoopCell::Arith {
8133 op: AluOp::Add,
8134 a: len,
8135 b: d,
8136 }); let lpd = Src::Val(klpd);
8138 let mut p10k: i64 = 1;
8139 for k in 0..MAXD {
8140 let kdiv = cells.len();
8141 cells.push(LoopCell::Func {
8142 unit: FuncUnit::DivS,
8143 a: b,
8144 b: Src::Const(p10k),
8145 });
8146 let kmod = cells.len();
8147 cells.push(LoopCell::Func {
8148 unit: FuncUnit::ModS,
8149 a: Src::Val(kdiv),
8150 b: Src::Const(10),
8151 });
8152 let kchar = cells.len();
8153 cells.push(LoopCell::Arith {
8154 op: AluOp::Add,
8155 a: Src::Val(kmod),
8156 b: Src::Const(48),
8157 });
8158 let ksub = cells.len();
8162 cells.push(LoopCell::Arith {
8163 op: AluOp::Sub,
8164 a: lpd,
8165 b: Src::Const(k),
8166 });
8167 let kpos = cells.len();
8168 cells.push(LoopCell::Arith {
8169 op: AluOp::Add,
8170 a,
8171 b: Src::Val(ksub),
8172 });
8173 let pos = Src::Val(kpos);
8174 let kvalid = cells.len();
8175 cells.push(LoopCell::Select {
8176 cmp: CmpKind::Lt,
8177 ca: Src::Const(k),
8178 cb: d,
8179 t: Src::Const(1),
8180 e: Src::Const(0),
8181 });
8182 let kexist = cells.len();
8183 cells.push(LoopCell::HeapRead { addr: pos });
8184 let kwval = cells.len();
8185 cells.push(LoopCell::Select {
8186 cmp: CmpKind::Ne,
8187 ca: Src::Val(kvalid),
8188 cb: Src::Const(0),
8189 t: Src::Val(kchar),
8190 e: Src::Val(kexist),
8191 });
8192 cells.push(LoopCell::HeapWrite {
8193 addr: pos,
8194 val: Src::Val(kwval),
8195 });
8196 p10k = p10k.saturating_mul(10);
8197 }
8198 cells.push(LoopCell::HeapWrite { addr: a, val: lpd });
8199 }
8201 reg.insert(*dst, a); COLL_LEN.with(|t| {
8203 t.borrow_mut().remove(dst); });
8205 } else if dst_f || src_f {
8206 let a2 = promote_float_operand(&mut cells, a, dst_f);
8209 let b2 = promote_float_operand(&mut cells, b, src_f);
8210 let kk = cells.len();
8211 cells.push(LoopCell::Func {
8212 unit: FuncUnit::FAddF,
8213 a: a2,
8214 b: b2,
8215 });
8216 reg.insert(*dst, Src::Val(kk));
8217 FLOAT_REGS.with(|t| {
8218 t.borrow_mut().insert(*dst);
8219 });
8220 } else {
8221 let kk = cells.len();
8222 cells.push(LoopCell::Arith {
8223 op: AluOp::Add,
8224 a,
8225 b,
8226 });
8227 reg.insert(*dst, Src::Val(kk));
8228 mark_int_reg(*dst);
8229 }
8230 record(&mut write_order, *dst);
8231 }
8232 Op::Length { dst, collection } => {
8237 let ptr = get(&mut reg, &mut inputs, *collection);
8238 let k = cells.len();
8239 cells.push(LoopCell::HeapRead { addr: ptr });
8240 reg.insert(*dst, Src::Val(k));
8241 mark_int_reg(*dst); record(&mut write_order, *dst);
8243 }
8244 Op::Index {
8245 dst,
8246 collection,
8247 index,
8248 }
8249 | Op::IndexUnchecked {
8250 dst,
8251 collection,
8252 index,
8253 } => {
8254 let ptr = get(&mut reg, &mut inputs, *collection);
8255 let idx = get(&mut reg, &mut inputs, *index);
8256 let ka = cells.len();
8257 cells.push(LoopCell::Arith {
8258 op: AluOp::Add,
8259 a: ptr,
8260 b: idx,
8261 });
8262 let kr = cells.len();
8263 cells.push(LoopCell::HeapRead { addr: Src::Val(ka) });
8264 reg.insert(*dst, Src::Val(kr));
8265 let coll_f = FLOAT_COLLS.with(|t| t.borrow().contains(collection));
8268 FLOAT_REGS.with(|t| {
8269 let mut s = t.borrow_mut();
8270 if coll_f {
8271 s.insert(*dst);
8272 } else {
8273 s.remove(dst);
8274 }
8275 });
8276 record(&mut write_order, *dst);
8277 }
8278 Op::NewList { dst, start, count } | Op::NewTuple { dst, start, count } => {
8279 let n = *count;
8280 if (0..n).any(|j| FLOAT_REGS.with(|t| t.borrow().contains(&(*start + j)))) {
8282 FLOAT_COLLS.with(|t| {
8283 t.borrow_mut().insert(*dst);
8284 });
8285 }
8286 let ka = cells.len();
8287 cells.push(LoopCell::HeapAlloc {
8288 size: Src::Const(n as i64 + 1),
8289 });
8290 let ptr = Src::Val(ka);
8291 cells.push(LoopCell::HeapWrite {
8292 addr: ptr,
8293 val: Src::Const(n as i64),
8294 });
8295 for j in 0..n {
8296 let elem = get(&mut reg, &mut inputs, *start + j);
8297 let kadd = cells.len();
8298 cells.push(LoopCell::Arith {
8299 op: AluOp::Add,
8300 a: ptr,
8301 b: Src::Const(j as i64 + 1),
8302 });
8303 cells.push(LoopCell::HeapWrite {
8304 addr: Src::Val(kadd),
8305 val: elem,
8306 });
8307 }
8308 reg.insert(*dst, ptr);
8309 record(&mut write_order, *dst);
8310 }
8311 Op::Args { dst } => {
8312 let ptr = heap_argv(&mut cells, seq_reserve());
8313 reg.insert(*dst, ptr);
8314 TEXT_REGS.with(|t| {
8315 t.borrow_mut().remove(dst);
8316 });
8317 record(&mut write_order, *dst);
8318 }
8319 Op::NewEmptyListFixed { dst, capacity } => {
8320 let ka = cells.len();
8323 cells.push(LoopCell::HeapAlloc {
8324 size: Src::Const(i64::from(*capacity) + 1),
8325 });
8326 let ptr = Src::Val(ka);
8327 cells.push(LoopCell::HeapWrite {
8328 addr: ptr,
8329 val: Src::Const(0),
8330 });
8331 reg.insert(*dst, ptr);
8332 record(&mut write_order, *dst);
8333 }
8334 Op::NewEmptyList { dst }
8335 | Op::NewEmptyListWithCapacity { dst, .. }
8336 | Op::NewEmptyListI32 { dst }
8337 | Op::NewEmptySet { dst }
8338 | Op::NewEmptyMap { dst }
8339 | Op::NewEmptyMapWithCapacity { dst, .. } => {
8340 let ka = cells.len();
8343 cells.push(LoopCell::HeapAlloc {
8344 size: Src::Const(1 + seq_reserve()),
8345 });
8346 let ptr = Src::Val(ka);
8347 cells.push(LoopCell::HeapWrite {
8348 addr: ptr,
8349 val: Src::Const(0),
8350 });
8351 reg.insert(*dst, ptr);
8352 record(&mut write_order, *dst);
8353 }
8354 Op::DestructureTuple { src, start, count } => {
8355 let ptr = get(&mut reg, &mut inputs, *src);
8356 for j in 0..*count {
8357 let kadd = cells.len();
8358 cells.push(LoopCell::Arith {
8359 op: AluOp::Add,
8360 a: ptr,
8361 b: Src::Const(j as i64 + 1),
8362 });
8363 let kr = cells.len();
8364 cells.push(LoopCell::HeapRead {
8365 addr: Src::Val(kadd),
8366 });
8367 reg.insert(*start + j, Src::Val(kr));
8368 record(&mut write_order, *start + j);
8369 }
8370 }
8371 Op::SetIndex {
8372 collection,
8373 index,
8374 value,
8375 }
8376 | Op::SetIndexUnchecked {
8377 collection,
8378 index,
8379 value,
8380 } => {
8381 if FLOAT_REGS.with(|t| t.borrow().contains(value)) {
8383 FLOAT_COLLS.with(|t| {
8384 t.borrow_mut().insert(*collection);
8385 });
8386 }
8387 let ptr = get(&mut reg, &mut inputs, *collection);
8388 let idx = get(&mut reg, &mut inputs, *index);
8389 let val = get(&mut reg, &mut inputs, *value);
8390 let kadd = cells.len();
8391 cells.push(LoopCell::Arith {
8392 op: AluOp::Add,
8393 a: ptr,
8394 b: idx,
8395 });
8396 cells.push(LoopCell::HeapWrite {
8397 addr: Src::Val(kadd),
8398 val,
8399 });
8400 }
8401 Op::Contains {
8406 dst,
8407 collection,
8408 value,
8409 } => {
8410 let ptr = get(&mut reg, &mut inputs, *collection);
8411 let key = get(&mut reg, &mut inputs, *value);
8412 let kaddr = cells.len();
8413 cells.push(LoopCell::Arith {
8414 op: AluOp::Add,
8415 a: ptr,
8416 b: key,
8417 });
8418 let kread = cells.len();
8419 cells.push(LoopCell::HeapRead {
8420 addr: Src::Val(kaddr),
8421 });
8422 let ksel = cells.len();
8423 cells.push(LoopCell::Select {
8424 cmp: CmpKind::Ne,
8425 ca: Src::Val(kread),
8426 cb: Src::Const(0),
8427 t: Src::Const(1),
8428 e: Src::Const(0),
8429 });
8430 reg.insert(*dst, Src::Val(ksel));
8431 mark_int_reg(*dst); record(&mut write_order, *dst);
8433 }
8434 Op::ListPush { list, value } => {
8435 if FLOAT_REGS.with(|t| t.borrow().contains(value)) {
8437 FLOAT_COLLS.with(|t| {
8438 t.borrow_mut().insert(*list);
8439 });
8440 }
8441 let ptr = get(&mut reg, &mut inputs, *list);
8442 let val = get(&mut reg, &mut inputs, *value);
8443 let klen = cells.len();
8444 cells.push(LoopCell::HeapRead { addr: ptr });
8445 let len = Src::Val(klen);
8446 let kp1 = cells.len();
8447 cells.push(LoopCell::Arith {
8448 op: AluOp::Add,
8449 a: ptr,
8450 b: Src::Const(1),
8451 });
8452 let kaddr = cells.len();
8453 cells.push(LoopCell::Arith {
8454 op: AluOp::Add,
8455 a: Src::Val(kp1),
8456 b: len,
8457 });
8458 cells.push(LoopCell::HeapWrite {
8459 addr: Src::Val(kaddr),
8460 val,
8461 });
8462 let knl = cells.len();
8463 cells.push(LoopCell::Arith {
8464 op: AluOp::Add,
8465 a: len,
8466 b: Src::Const(1),
8467 });
8468 cells.push(LoopCell::HeapWrite {
8469 addr: ptr,
8470 val: Src::Val(knl),
8471 });
8472 }
8473 Op::ListPop { list, dst } => {
8474 let ptr = get(&mut reg, &mut inputs, *list);
8475 let klen = cells.len();
8476 cells.push(LoopCell::HeapRead { addr: ptr });
8477 let len = Src::Val(klen);
8478 let kaddr = cells.len();
8479 cells.push(LoopCell::Arith {
8480 op: AluOp::Add,
8481 a: ptr,
8482 b: len,
8483 });
8484 let kr = cells.len();
8485 cells.push(LoopCell::HeapRead {
8486 addr: Src::Val(kaddr),
8487 });
8488 let kdec = cells.len();
8489 cells.push(LoopCell::Arith {
8490 op: AluOp::Sub,
8491 a: len,
8492 b: Src::Const(1),
8493 });
8494 cells.push(LoopCell::HeapWrite {
8495 addr: ptr,
8496 val: Src::Val(kdec),
8497 });
8498 reg.insert(*dst, Src::Val(kr));
8499 let coll_f = FLOAT_COLLS.with(|t| t.borrow().contains(list));
8501 FLOAT_REGS.with(|t| {
8502 let mut s = t.borrow_mut();
8503 if coll_f {
8504 s.insert(*dst);
8505 } else {
8506 s.remove(dst);
8507 }
8508 });
8509 record(&mut write_order, *dst);
8510 }
8511 Op::CallBuiltin {
8517 dst,
8518 builtin,
8519 args_start,
8520 ..
8521 } => {
8522 use crate::semantics::builtins::BuiltinId;
8523 let a0 = get(&mut reg, &mut inputs, *args_start);
8524 let is_f = FLOAT_REGS.with(|t| t.borrow().contains(args_start));
8525 match builtin {
8526 BuiltinId::Sqrt if is_f => {
8527 let k = cells.len();
8528 cells.push(LoopCell::Func {
8529 unit: FuncUnit::FSqrtF,
8530 a: a0,
8531 b: Src::Const(0),
8532 });
8533 reg.insert(*dst, Src::Val(k));
8534 FLOAT_REGS.with(|t| {
8535 t.borrow_mut().insert(*dst);
8536 });
8537 }
8538 BuiltinId::Floor if is_f => {
8539 let k = cells.len();
8540 cells.push(LoopCell::Func {
8541 unit: FuncUnit::FFloorF,
8542 a: a0,
8543 b: Src::Const(0),
8544 });
8545 reg.insert(*dst, Src::Val(k));
8546 mark_int_reg(*dst); }
8548 BuiltinId::Floor => {
8549 reg.insert(*dst, a0);
8550 mark_int_reg(*dst); }
8552 BuiltinId::Abs if !is_f => {
8555 let kneg = cells.len();
8556 cells.push(LoopCell::Arith {
8557 op: AluOp::Sub,
8558 a: Src::Const(0),
8559 b: a0,
8560 });
8561 let ksel = cells.len();
8562 cells.push(LoopCell::Select {
8563 cmp: CmpKind::Lt,
8564 ca: a0,
8565 cb: Src::Const(0),
8566 t: Src::Val(kneg),
8567 e: a0,
8568 });
8569 reg.insert(*dst, Src::Val(ksel));
8570 mark_int_reg(*dst);
8571 }
8572 BuiltinId::Min if !is_f => {
8573 let a1 = get(&mut reg, &mut inputs, *args_start + 1);
8574 let k = cells.len();
8575 cells.push(LoopCell::Select {
8576 cmp: CmpKind::Lt,
8577 ca: a0,
8578 cb: a1,
8579 t: a0,
8580 e: a1,
8581 });
8582 reg.insert(*dst, Src::Val(k));
8583 mark_int_reg(*dst);
8584 }
8585 BuiltinId::Max if !is_f => {
8586 let a1 = get(&mut reg, &mut inputs, *args_start + 1);
8587 let k = cells.len();
8588 cells.push(LoopCell::Select {
8589 cmp: CmpKind::Gt,
8590 ca: a0,
8591 cb: a1,
8592 t: a0,
8593 e: a1,
8594 });
8595 reg.insert(*dst, Src::Val(k));
8596 mark_int_reg(*dst);
8597 }
8598 BuiltinId::Abs => {
8602 let kneg = cells.len();
8603 cells.push(LoopCell::Func {
8604 unit: FuncUnit::FSubF,
8605 a: Src::Const(0),
8606 b: a0,
8607 });
8608 let kcmp = cells.len();
8609 cells.push(LoopCell::Func {
8610 unit: FuncUnit::FLtF,
8611 a: a0,
8612 b: Src::Const(0),
8613 });
8614 let ksel = cells.len();
8615 cells.push(LoopCell::Select {
8616 cmp: CmpKind::Ne,
8617 ca: Src::Val(kcmp),
8618 cb: Src::Const(0),
8619 t: Src::Val(kneg),
8620 e: a0,
8621 });
8622 reg.insert(*dst, Src::Val(ksel));
8623 FLOAT_REGS.with(|t| {
8624 t.borrow_mut().insert(*dst);
8625 });
8626 }
8627 BuiltinId::Min => {
8628 let a1 = get(&mut reg, &mut inputs, *args_start + 1);
8629 let kcmp = cells.len();
8630 cells.push(LoopCell::Func {
8631 unit: FuncUnit::FLtF,
8632 a: a0,
8633 b: a1,
8634 });
8635 let ksel = cells.len();
8636 cells.push(LoopCell::Select {
8637 cmp: CmpKind::Ne,
8638 ca: Src::Val(kcmp),
8639 cb: Src::Const(0),
8640 t: a0,
8641 e: a1,
8642 });
8643 reg.insert(*dst, Src::Val(ksel));
8644 FLOAT_REGS.with(|t| {
8645 t.borrow_mut().insert(*dst);
8646 });
8647 }
8648 BuiltinId::Max => {
8649 let a1 = get(&mut reg, &mut inputs, *args_start + 1);
8650 let kcmp = cells.len();
8651 cells.push(LoopCell::Func {
8652 unit: FuncUnit::FGtF,
8653 a: a0,
8654 b: a1,
8655 });
8656 let ksel = cells.len();
8657 cells.push(LoopCell::Select {
8658 cmp: CmpKind::Ne,
8659 ca: Src::Val(kcmp),
8660 cb: Src::Const(0),
8661 t: a0,
8662 e: a1,
8663 });
8664 reg.insert(*dst, Src::Val(ksel));
8665 FLOAT_REGS.with(|t| {
8666 t.borrow_mut().insert(*dst);
8667 });
8668 }
8669 other => {
8670 return Err(LoweringError::UnsupportedBuiltin(format!("{other:?}")))
8671 }
8672 }
8673 record(&mut write_order, *dst);
8674 }
8675 Op::Show { src } => {
8686 if FLOAT_REGS.with(|t| t.borrow().contains(src)) {
8687 return Err(LoweringError::UnsupportedOp(
8688 "Show of a float (rendering IEEE→decimal is a separate lane)"
8689 .into(),
8690 ));
8691 }
8692 if TEXT_REGS.with(|t| t.borrow().contains(src)) {
8693 return Err(LoweringError::UnsupportedOp(
8694 "Show of text (rendering a heap string is a separate lane)".into(),
8695 ));
8696 }
8697 let v = get(&mut reg, &mut inputs, *src);
8698 shows.push(v);
8699 }
8700 other => return Err(LoweringError::UnsupportedOp(other.name().to_string())),
8701 }
8702 }
8703
8704 let term = match term_op {
8705 Some(Op::Return { src }) => Term::Return(get(&mut reg, &mut inputs, src)),
8706 Some(Op::ReturnNothing) | Some(Op::Halt) => Term::Return(Src::Const(0)),
8708 Some(Op::Jump { target }) => Term::Jump(blocks.block_of(target)),
8709 Some(Op::JumpIfFalse { cond, target }) => {
8710 let c = get(&mut reg, &mut inputs, cond);
8712 Term::Branch {
8713 cond: c,
8714 then_blk: blocks.block_of(bend),
8715 else_blk: blocks.block_of(target),
8716 }
8717 }
8718 Some(Op::JumpIfTrue { cond, target }) => {
8719 let c = get(&mut reg, &mut inputs, cond);
8720 Term::Branch {
8721 cond: c,
8722 then_blk: blocks.block_of(target),
8723 else_blk: blocks.block_of(bend),
8724 }
8725 }
8726 None => Term::Jump(blocks.block_of(bend)), _ => unreachable!(),
8728 };
8729
8730 let writes: Vec<(u16, Src)> = write_order.iter().map(|r| (*r, reg[r])).collect();
8731 out_blocks.push(CfgBlock {
8732 inputs,
8733 body: cells,
8734 writes,
8735 shows,
8736 term,
8737 });
8738 }
8739
8740 Ok(ProgramCfgFabric {
8741 n_params: n_params as usize,
8742 blocks: out_blocks,
8743 })
8744 })
8745}
8746
8747pub fn eval_program_cfg_fabric(pf: &ProgramCfgFabric, params: &[i64]) -> i64 {
8752 eval_program_cfg_fabric_traced(pf, params).0
8753}
8754
8755pub fn eval_program_cfg_fabric_traced(pf: &ProgramCfgFabric, params: &[i64]) -> (i64, Vec<i64>) {
8763 use std::collections::HashMap;
8764 let mut file: HashMap<u16, i64> = (0..pf.n_params).map(|p| (p as u16, params[p])).collect();
8765 let mut heap: Vec<i64> = Vec::new();
8768 let mut shown: Vec<i64> = Vec::new();
8769 let mut blk = 0usize;
8770 for _ in 0..10_000_000 {
8771 let b = &pf.blocks[blk];
8772 let inputs: Vec<i64> = b
8773 .inputs
8774 .iter()
8775 .map(|r| *file.get(r).unwrap_or(&0))
8776 .collect();
8777 let mut vals: Vec<i64> = Vec::with_capacity(b.body.len());
8778 for cell in &b.body {
8779 let v = match cell {
8780 LoopCell::Arith { op, a, b } => op.eval(a.eval(&inputs, &vals), b.eval(&inputs, &vals)),
8781 LoopCell::VectorArith { op, ty, a, b } => {
8782 op.eval(*ty, a.eval(&inputs, &vals) as u64, b.eval(&inputs, &vals) as u64) as i64
8783 }
8784 LoopCell::Func { unit, a, b } => unit.eval(a.eval(&inputs, &vals), b.eval(&inputs, &vals)),
8785 LoopCell::Select { cmp, ca, cb, t, e } => {
8786 if cmp.eval(ca.eval(&inputs, &vals), cb.eval(&inputs, &vals)) {
8787 t.eval(&inputs, &vals)
8788 } else {
8789 e.eval(&inputs, &vals)
8790 }
8791 }
8792 LoopCell::HeapRead { addr } => {
8794 let a = addr.eval(&inputs, &vals);
8795 heap.get(a.max(0) as usize).copied().unwrap_or(0)
8796 }
8797 LoopCell::HeapWrite { addr, val } => {
8798 let a = addr.eval(&inputs, &vals);
8799 let bv = val.eval(&inputs, &vals);
8800 let ai = a.max(0) as usize;
8801 if ai >= heap.len() {
8802 heap.resize(ai + 1, 0);
8803 }
8804 heap[ai] = bv;
8805 bv
8806 }
8807 LoopCell::HeapAlloc { size } => {
8808 let a = size.eval(&inputs, &vals);
8809 let ptr = heap.len() as i64;
8810 heap.resize(heap.len() + a.max(0) as usize, 0);
8811 ptr
8812 }
8813 LoopCell::MemRead { .. } => panic!(
8814 "cfg block body cannot contain a loop-sequence MemRead (the induction-counter scratchpad \
8815 is the loop fabric's mechanism, distinct from the CFG shared-heap state plane)"
8816 ),
8817 };
8818 vals.push(v);
8819 }
8820 for src in &b.shows {
8822 shown.push(src.eval(&inputs, &vals));
8823 }
8824 for (r, src) in &b.writes {
8825 file.insert(*r, src.eval(&inputs, &vals));
8826 }
8827 match &b.term {
8828 Term::Jump(t) => blk = *t,
8829 Term::Branch {
8830 cond,
8831 then_blk,
8832 else_blk,
8833 } => {
8834 blk = if cond.eval(&inputs, &vals) != 0 {
8835 *then_blk
8836 } else {
8837 *else_blk
8838 }
8839 }
8840 Term::Return(src) => return (src.eval(&inputs, &vals), shown),
8841 }
8842 }
8843 panic!("cfg fabric: controller did not return within the step budget (a back-edge slipped through?)")
8844}
8845
8846fn counted_header(ops: &[(usize, crate::vm::Op)], i: usize) -> Option<(u16, u16, usize)> {
8850 use crate::vm::Op;
8851 match (ops.get(i).map(|(_, o)| *o), ops.get(i + 1).map(|(_, o)| *o)) {
8852 (Some(Op::LtEq { dst, lhs, rhs }), Some(Op::JumpIfFalse { cond, target }))
8853 if cond == dst =>
8854 {
8855 Some((lhs, rhs, target))
8856 }
8857 _ => None,
8858 }
8859}
8860
8861fn counted_step(op: &crate::vm::Op, counter: u16, is_one: impl Fn(u16) -> bool) -> bool {
8863 use crate::vm::Op;
8864 match *op {
8865 Op::Add { dst, lhs, rhs } => {
8866 dst == counter && ((lhs == counter && is_one(rhs)) || (rhs == counter && is_one(lhs)))
8867 }
8868 Op::AddAssign { dst, src } => dst == counter && is_one(src),
8869 _ => false,
8870 }
8871}
8872
8873fn counted_latch(
8879 body: &[(usize, crate::vm::Op)],
8880 counter: u16,
8881 bound: u16,
8882 exit: usize,
8883 is_one: impl Fn(u16) -> bool,
8884) -> usize {
8885 use crate::vm::Op;
8886 let n = body.len();
8887 if n == 0 || !counted_step(&body[n - 1].1, counter, &is_one) {
8888 return 0;
8889 }
8890 if n >= 3 {
8891 if let (Op::Lt { dst, lhs, rhs }, Op::JumpIfFalse { cond, target }) =
8892 (body[n - 3].1, body[n - 2].1)
8893 {
8894 if lhs == counter && rhs == bound && cond == dst && target == exit {
8895 return 3;
8896 }
8897 }
8898 }
8899 1
8900}
8901
8902pub fn loop_fabric_from_source(src: &str, fn_name: &str) -> LoopFabric {
8909 use crate::vm::{Compiler, Constant, Op};
8910 use std::collections::{BTreeSet, HashMap};
8911
8912 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
8913 let (stmts, _types, _pol) = parsed.expect("parse failed");
8914 let program = Compiler::compile(stmts, interner).expect("compile failed");
8915 let f = program
8916 .functions
8917 .iter()
8918 .find(|f| interner.resolve(f.name) == fn_name)
8919 .unwrap_or_else(|| panic!("function `{fn_name}` not found"));
8920
8921 setup_program_thread_locals(&program);
8926 for (p, pt) in f.param_types.iter().enumerate() {
8927 if matches!(pt, Some(crate::vm::BoundaryType::Float)) {
8928 FLOAT_REGS.with(|t| {
8929 t.borrow_mut().insert(p as u16);
8930 });
8931 }
8932 }
8933
8934 let mut ops: Vec<(usize, Op)> = Vec::new();
8936 for (i, op) in program.code.iter().enumerate().skip(f.entry_pc) {
8937 ops.push((i, *op));
8938 if matches!(op, Op::Return { .. } | Op::ReturnNothing) {
8939 break;
8940 }
8941 }
8942
8943 let mut pro: HashMap<u16, Src> = HashMap::new();
8945 for p in 0..f.param_count {
8946 pro.insert(p, Src::In(p as usize));
8947 }
8948 let getp = |pro: &HashMap<u16, Src>, r: u16| -> Src {
8949 *pro.get(&r)
8950 .unwrap_or_else(|| panic!("loop prologue reads unwritten register R{r}"))
8951 };
8952
8953 let mut lo = None;
8956 let mut hi = None;
8957 let mut iv_reg = None;
8958 let mut header_pc = None;
8959 let mut seq_reg: Option<u16> = None;
8962 let mut prologue_heap: Vec<LoopCell> = Vec::new();
8965 let mut ppreds: HashMap<u16, (CmpKind, Src, Src)> = HashMap::new();
8966 let mut counter: Option<u16> = None;
8968 let mut latch_bound: Option<(u16, usize)> = None;
8970 let mut lengths: HashMap<u16, u16> = HashMap::new();
8973 let mut by_element = false;
8974 let mut idx = 0usize;
8975 while idx < ops.len() {
8976 let (_pc, op) = ops[idx];
8977 if let Some((c, bound, exit)) = counted_header(&ops, idx) {
8978 latch_bound = Some((bound, exit));
8979 let element = match (lengths.get(&bound), ops.get(idx + 2).map(|(_, o)| *o)) {
8980 (
8981 Some(&s),
8982 Some(
8983 Op::Index {
8984 dst,
8985 collection,
8986 index,
8987 }
8988 | Op::IndexUnchecked {
8989 dst,
8990 collection,
8991 index,
8992 },
8993 ),
8994 ) if collection == s
8995 && index == c
8996 && matches!(pro.get(&c), Some(Src::Const(1))) =>
8997 {
8998 Some((s, dst))
8999 }
9000 _ => None,
9001 };
9002 if let Some((s, x)) = element {
9003 by_element = true;
9004 seq_reg = Some(s);
9005 iv_reg = Some(x);
9006 header_pc = Some(ops[idx].0);
9007 counter = Some(c);
9008 idx += 3;
9009 break;
9010 }
9011 lo = Some(getp(&pro, c));
9012 hi = Some(getp(&pro, bound));
9013 iv_reg = Some(c);
9014 header_pc = Some(ops[idx].0);
9015 counter = Some(c);
9016 idx += 2;
9017 break;
9018 }
9019 match op {
9020 Op::LoadConst { dst, idx: ci } => match program.constants[ci as usize] {
9021 Constant::Int(n) => {
9022 pro.insert(dst, Src::Const(n));
9023 }
9024 Constant::Float(fv) => {
9027 pro.insert(dst, Src::Const(fv.to_bits() as i64));
9028 FLOAT_REGS.with(|t| {
9029 t.borrow_mut().insert(dst);
9030 });
9031 }
9032 ref c => panic!(
9033 "loop fabric: non-integer constant {c:?} (only Int / Float supported)"
9034 ),
9035 },
9036 Op::Move { dst, src } => {
9037 let s = getp(&pro, src);
9038 pro.insert(dst, s);
9039 if FLOAT_REGS.with(|t| t.borrow().contains(&src)) {
9041 FLOAT_REGS.with(|t| {
9042 t.borrow_mut().insert(dst);
9043 });
9044 }
9045 }
9046 Op::NewRange { start, end, .. } => {
9047 lo = Some(getp(&pro, start));
9048 hi = Some(getp(&pro, end));
9049 }
9050 Op::IterPrepare { iterable } => {
9051 seq_reg = Some(iterable);
9052 }
9053 Op::Length { dst, collection } => {
9054 lengths.insert(dst, collection);
9055 }
9056 Op::IterNext { dst, .. } => {
9057 iv_reg = Some(dst);
9058 header_pc = Some(ops[idx].0); idx += 1;
9060 break;
9061 }
9062 other => {
9066 exec_ops(
9067 &[other],
9068 &mut pro,
9069 &mut prologue_heap,
9070 &mut ppreds,
9071 &program.constants,
9072 );
9073 }
9074 }
9075 idx += 1;
9076 }
9077 let (seq_input, seq_heap_ptr): (Option<usize>, Option<Src>) = if lo.is_none() {
9081 let sreg = seq_reg.expect("loop fabric: neither NewRange nor IterPrepare (not a loop)");
9082 match getp(&pro, sreg) {
9083 Src::In(k) => (Some(k), None),
9084 s @ Src::Val(_) => (None, Some(s)), other => panic!("loop fabric: the iterated sequence is not a parameter or a local list ({other:?})"),
9086 }
9087 } else {
9088 (None, None)
9089 };
9090 let lo = lo.unwrap_or(Src::Const(0));
9091 let hi = hi.unwrap_or(Src::Const(0));
9092 let iv_reg = iv_reg.expect("loop fabric: no IterNext (not a loop)");
9093 let header_pc = header_pc.expect("loop fabric: no loop header");
9094 if let Some(k) = seq_input {
9100 if matches!(
9101 f.param_types.get(k),
9102 Some(Some(crate::vm::BoundaryType::Seq(elem)))
9103 if matches!(**elem, crate::vm::BoundaryType::Float)
9104 ) {
9105 FLOAT_REGS.with(|t| {
9106 t.borrow_mut().insert(iv_reg);
9107 });
9108 }
9109 }
9110
9111 let mut body_ops: Vec<(usize, Op)> = Vec::new();
9115 let mut result_reg = None;
9116 while idx < ops.len() {
9117 let (pc, op) = ops[idx];
9118 if matches!(op, Op::Jump { target } if target == header_pc) {
9119 idx += 1;
9120 break;
9121 }
9122 body_ops.push((pc, op));
9123 idx += 1;
9124 }
9125 if let Some(c) = counter {
9127 let is_one = |r: u16| matches!(pro.get(&r), Some(Src::Const(1)));
9128 let (bound, exit) = latch_bound.expect("a counted loop has a header");
9129 match counted_latch(&body_ops, c, bound, exit, is_one) {
9130 0 => panic!("loop fabric: a counted loop's body does not end in its unit step"),
9131 n => body_ops.truncate(body_ops.len() - n),
9132 }
9133 if by_element {
9135 for (_, op) in &body_ops {
9136 op.for_each_read_reg(
9137 |_| 0,
9138 |r| {
9139 assert!(
9140 r != c,
9141 "loop fabric: a sequence loop's body reads its index"
9142 );
9143 },
9144 );
9145 }
9146 }
9147 }
9148 let mut epilogue_ops: Vec<Op> = Vec::new();
9151 for (_pc, op) in &ops[idx..] {
9152 match op {
9153 Op::IterPop | Op::ReturnNothing => {}
9154 Op::Return { src } => result_reg = Some(*src),
9155 Op::LoadConst { .. }
9156 | Op::Move { .. }
9157 | Op::Add { .. }
9158 | Op::Sub { .. }
9159 | Op::Mul { .. }
9160 | Op::Div { .. }
9161 | Op::Mod { .. }
9162 | Op::FloorDiv { .. }
9163 | Op::AddAssign { .. } => epilogue_ops.push(*op),
9164 other => panic!(
9165 "loop fabric: unexpected post-loop op `{}` (only a straight-line epilogue transform \
9166 over the loop result + Return is supported)",
9167 other.name()
9168 ),
9169 }
9170 }
9171 let result_reg = result_reg.expect("loop fabric: loop has no Return");
9172
9173 let reg_of = |op: &Op| -> (Option<u16>, Vec<u16>) {
9177 match op {
9178 Op::LoadConst { dst, .. } => (Some(*dst), vec![]),
9179 Op::LoadNow { dst } | Op::LoadToday { dst } => (Some(*dst), vec![]),
9181 Op::Move { dst, src } => (Some(*dst), vec![*src]),
9182 Op::Not { dst, src } => (Some(*dst), vec![*src]),
9184 Op::CallBuiltin { dst, args_start, arg_count, .. } => {
9187 (Some(*dst), (0..*arg_count as u16).map(|k| *args_start + k).collect())
9188 }
9189 Op::Add { dst, lhs, rhs }
9190 | Op::Sub { dst, lhs, rhs }
9191 | Op::Mul { dst, lhs, rhs }
9192 | Op::Div { dst, lhs, rhs }
9197 | Op::Mod { dst, lhs, rhs }
9198 | Op::FloorDiv { dst, lhs, rhs }
9199 | Op::BitAnd { dst, lhs, rhs }
9202 | Op::BitOr { dst, lhs, rhs }
9203 | Op::BitXor { dst, lhs, rhs }
9204 | Op::Shl { dst, lhs, rhs }
9205 | Op::Shr { dst, lhs, rhs }
9206 | Op::Pow { dst, lhs, rhs }
9208 | Op::Lt { dst, lhs, rhs }
9209 | Op::Gt { dst, lhs, rhs }
9210 | Op::LtEq { dst, lhs, rhs }
9211 | Op::GtEq { dst, lhs, rhs }
9212 | Op::Eq { dst, lhs, rhs }
9213 | Op::NotEq { dst, lhs, rhs } => (Some(*dst), vec![*lhs, *rhs]),
9214 Op::AddAssign { dst, src } => (Some(*dst), vec![*dst, *src]),
9215 Op::Jump { .. } | Op::JumpIfFalse { .. } => (None, vec![]),
9218 other => panic!("loop fabric: body op `{}` unsupported", other.name()),
9219 }
9220 };
9221 let mut written: BTreeSet<u16> = BTreeSet::new();
9222 let mut upward: BTreeSet<u16> = BTreeSet::new();
9223 for (_pc, op) in &body_ops {
9224 if let Op::DestructureTuple { src, start, count } = op {
9227 if *src != iv_reg && !written.contains(src) {
9228 upward.insert(*src);
9229 }
9230 for j in 0..*count {
9231 written.insert(*start + j);
9232 }
9233 continue;
9234 }
9235 let (dst, reads) = reg_of(op);
9236 for r in reads {
9237 if r != iv_reg && !written.contains(&r) {
9238 upward.insert(r);
9239 }
9240 }
9241 if let Some(d) = dst {
9242 written.insert(d);
9243 }
9244 }
9245 let mut carried: Vec<u16> = upward.intersection(&written).copied().collect();
9247 if written.contains(&result_reg) && !carried.contains(&result_reg) {
9248 carried.push(result_reg);
9249 }
9250 carried.sort_unstable();
9251 let invariants_regs: Vec<u16> = upward.difference(&written).copied().collect();
9252
9253 let n_state = carried.len();
9254 let state_slot: HashMap<u16, usize> =
9255 carried.iter().enumerate().map(|(i, r)| (*r, i)).collect();
9256 let inv_slot: HashMap<u16, usize> = invariants_regs
9257 .iter()
9258 .enumerate()
9259 .map(|(i, r)| (*r, i))
9260 .collect();
9261
9262 let mut reg: HashMap<u16, Src> = HashMap::new();
9265 for (r, s) in &state_slot {
9266 reg.insert(*r, Src::In(*s));
9267 }
9268 for (r, s) in &inv_slot {
9269 reg.insert(*r, Src::In(n_state + 1 + *s));
9270 }
9271 let init_cells: Vec<LoopCell> = if seq_input.is_some() {
9275 reg.insert(iv_reg, Src::Val(0));
9276 vec![LoopCell::MemRead {
9277 addr: Src::In(n_state),
9278 }]
9279 } else if seq_heap_ptr.is_some() {
9280 let list_ptr_slot = n_state + 1 + invariants_regs.len();
9283 reg.insert(iv_reg, Src::Val(2));
9284 vec![
9285 LoopCell::Arith {
9286 op: AluOp::Add,
9287 a: Src::In(list_ptr_slot),
9288 b: Src::Const(1),
9289 }, LoopCell::Arith {
9291 op: AluOp::Add,
9292 a: Src::Val(0),
9293 b: Src::In(n_state),
9294 }, LoopCell::HeapRead { addr: Src::Val(1) }, ]
9297 } else {
9298 reg.insert(iv_reg, Src::In(n_state));
9299 Vec::new()
9300 };
9301
9302 let body = if_convert_body(
9305 &body_ops,
9306 &mut reg,
9307 &carried,
9308 &program.constants,
9309 init_cells,
9310 );
9311
9312 let init: Vec<Src> = carried.iter().map(|r| getp(&pro, *r)).collect();
9313 let next: Vec<Src> = carried.iter().map(|&r| *reg.get(&r).unwrap()).collect();
9314 let mut invariants: Vec<Src> = invariants_regs.iter().map(|r| getp(&pro, *r)).collect();
9315 if let Some(ptr) = seq_heap_ptr {
9319 invariants.push(ptr);
9320 }
9321 let (epilogue, epilogue_result, result_state) = if epilogue_ops.is_empty() {
9326 let rs = *state_slot
9327 .get(&result_reg)
9328 .expect("loop fabric: the returned register is not loop-carried");
9329 (Vec::new(), Src::Const(0), rs)
9330 } else {
9331 let mut ereg: HashMap<u16, Src> = HashMap::new();
9332 for (r, s) in &state_slot {
9333 ereg.insert(*r, Src::In(*s));
9334 }
9335 let mut ecells: Vec<LoopCell> = Vec::new();
9336 let mut epreds: HashMap<u16, (CmpKind, Src, Src)> = HashMap::new();
9337 exec_ops(
9338 &epilogue_ops,
9339 &mut ereg,
9340 &mut ecells,
9341 &mut epreds,
9342 &program.constants,
9343 );
9344 let eres = *ereg
9345 .get(&result_reg)
9346 .expect("loop fabric: the epilogue does not define the returned register");
9347 (ecells, eres, 0usize)
9348 };
9349
9350 LoopFabric {
9351 n_in: f.param_count as usize,
9352 n_state,
9353 init,
9354 lo,
9355 hi,
9356 invariants,
9357 body,
9358 next,
9359 result_state,
9360 seq_input,
9361 epilogue,
9362 epilogue_result,
9363 prologue_heap,
9364 seq_heap_ptr,
9365 }
9366 })
9367}
9368
9369pub fn eval_loop_fabric(lf: &LoopFabric, params: &[i64]) -> i64 {
9374 if lf.seq_heap_ptr.is_some() {
9375 eval_loop_fabric_heap(lf, params)
9376 } else {
9377 eval_loop_fabric_seq(lf, params, &[])
9378 }
9379}
9380
9381pub fn eval_loop_fabric_heap(lf: &LoopFabric, params: &[i64]) -> i64 {
9386 let mut heap: Vec<i64> = Vec::new();
9387 let pro_vals = eval_cells(&lf.prologue_heap, params, &mut heap);
9388 let list_ptr = lf
9389 .seq_heap_ptr
9390 .as_ref()
9391 .expect("eval_loop_fabric_heap: not a heap-list loop")
9392 .eval(params, &pro_vals);
9393 let len = heap.get(list_ptr as usize).copied().unwrap_or(0);
9394 let mut state: Vec<i64> = lf.init.iter().map(|s| s.eval(params, &pro_vals)).collect();
9397 let invs: Vec<i64> = lf
9398 .invariants
9399 .iter()
9400 .map(|s| s.eval(params, &pro_vals))
9401 .collect();
9402 for iv in 0..len {
9403 let mut bin: Vec<i64> = Vec::with_capacity(lf.n_state + 1 + invs.len());
9404 bin.extend_from_slice(&state);
9405 bin.push(iv);
9406 bin.extend_from_slice(&invs);
9407 let vals = eval_cells(&lf.body, &bin, &mut heap); state = lf.next.iter().map(|s| s.eval(&bin, &vals)).collect();
9409 }
9410 if lf.epilogue.is_empty() {
9411 state[lf.result_state]
9412 } else {
9413 let evals = eval_cells(&lf.epilogue, &state, &mut heap);
9414 lf.epilogue_result.eval(&state, &evals)
9415 }
9416}
9417
9418pub fn eval_loop_body(lf: &LoopFabric, bin: &[i64], seq: &[i64]) -> Vec<i64> {
9422 let mut vals: Vec<i64> = Vec::with_capacity(lf.body.len());
9423 for cell in &lf.body {
9424 let v = match cell {
9425 LoopCell::Arith { op, a, b } => op.eval(a.eval(bin, &vals), b.eval(bin, &vals)),
9426 LoopCell::VectorArith { op, ty, a, b } => {
9427 op.eval(*ty, a.eval(bin, &vals) as u64, b.eval(bin, &vals) as u64) as i64
9428 }
9429 LoopCell::Func { unit, a, b } => unit.eval(a.eval(bin, &vals), b.eval(bin, &vals)),
9430 LoopCell::MemRead { addr } => {
9431 let a = addr.eval(bin, &vals);
9432 seq.get(a as usize).copied().unwrap_or(0)
9433 }
9434 LoopCell::HeapRead { .. } | LoopCell::HeapWrite { .. } | LoopCell::HeapAlloc { .. } => panic!(
9435 "heap tile in a loop body is a state-plane follow-on (collection ops inside a loop); \
9436 straight-line collection ops evaluate via eval_decision_fabric_heap"
9437 ),
9438 LoopCell::Select { cmp, ca, cb, t, e } => {
9439 if cmp.eval(ca.eval(bin, &vals), cb.eval(bin, &vals)) {
9440 t.eval(bin, &vals)
9441 } else {
9442 e.eval(bin, &vals)
9443 }
9444 }
9445 };
9446 vals.push(v);
9447 }
9448 vals
9449}
9450
9451pub fn eval_loop_fabric_seq(lf: &LoopFabric, params: &[i64], seq: &[i64]) -> i64 {
9456 let empty: [i64; 0] = [];
9457 let mut state: Vec<i64> = lf.init.iter().map(|s| s.eval(params, &empty)).collect();
9458 let (lo, hi) = if lf.seq_input.is_some() {
9459 (0i64, seq.len() as i64 - 1) } else {
9461 (lf.lo.eval(params, &empty), lf.hi.eval(params, &empty))
9462 };
9463 let invs: Vec<i64> = lf
9464 .invariants
9465 .iter()
9466 .map(|s| s.eval(params, &empty))
9467 .collect();
9468 let mut iv = lo;
9469 while iv <= hi {
9470 let mut bin: Vec<i64> = Vec::with_capacity(lf.n_state + 1 + invs.len());
9471 bin.extend_from_slice(&state);
9472 bin.push(iv);
9473 bin.extend_from_slice(&invs);
9474 let vals = eval_loop_body(lf, &bin, seq);
9475 state = lf.next.iter().map(|s| s.eval(&bin, &vals)).collect();
9476 if iv == hi {
9477 break; }
9479 iv = iv.wrapping_add(1);
9480 }
9481 if lf.epilogue.is_empty() {
9484 return state[lf.result_state];
9485 }
9486 let mut evals: Vec<i64> = Vec::with_capacity(lf.epilogue.len());
9487 for cell in &lf.epilogue {
9488 let v = match cell {
9489 LoopCell::Arith { op, a, b } => op.eval(a.eval(&state, &evals), b.eval(&state, &evals)),
9490 LoopCell::VectorArith { op, ty, a, b } => op.eval(
9491 *ty,
9492 a.eval(&state, &evals) as u64,
9493 b.eval(&state, &evals) as u64,
9494 ) as i64,
9495 LoopCell::Func { unit, a, b } => {
9496 unit.eval(a.eval(&state, &evals), b.eval(&state, &evals))
9497 }
9498 LoopCell::Select { cmp, ca, cb, t, e } => {
9499 if cmp.eval(ca.eval(&state, &evals), cb.eval(&state, &evals)) {
9500 t.eval(&state, &evals)
9501 } else {
9502 e.eval(&state, &evals)
9503 }
9504 }
9505 LoopCell::MemRead { .. }
9506 | LoopCell::HeapRead { .. }
9507 | LoopCell::HeapWrite { .. }
9508 | LoopCell::HeapAlloc { .. } => {
9509 panic!("epilogue tile cannot be a memory/heap tile — no sequence iteration after the loop")
9510 }
9511 };
9512 evals.push(v);
9513 }
9514 lf.epilogue_result.eval(&state, &evals)
9515}
9516
9517pub fn emit_loop_fabric_verilog(name: &str, lf: &LoopFabric) -> String {
9523 emit_loop_fabric_verilog_dp(name, lf, None)
9524}
9525
9526pub fn emit_loop_fabric_verilog_int8(name: &str, lf: &LoopFabric, mul_bits: u32) -> String {
9533 assert!(
9534 (2..=32).contains(&mul_bits),
9535 "int8 datapath mul_bits must be 2..=32 (got {mul_bits})"
9536 );
9537 emit_loop_fabric_verilog_dp(name, lf, Some(mul_bits))
9538}
9539
9540fn loop_body_wire(
9543 i: usize,
9544 cell: &LoopCell,
9545 mul_bits: Option<u32>,
9546 name_in: &dyn Fn(usize) -> String,
9547 name_val: &dyn Fn(usize) -> String,
9548 mem_addrw: usize,
9549) -> String {
9550 let mut out = String::new();
9551 if let (
9556 Some(w),
9557 LoopCell::Arith {
9558 op: AluOp::Mul,
9559 a,
9560 b,
9561 },
9562 ) = (mul_bits, cell)
9563 {
9564 let a_s = src_named(a, name_in, name_val);
9565 let b_s = src_named(b, name_in, name_val);
9566 out.push_str(&format!(
9567 " wire signed [{h}:0] ma{i} = {a_s};\n wire signed [{h}:0] mb{i} = {b_s};\n \
9568 wire signed [{ph}:0] mp{i} = ma{i} * mb{i};\n wire [63:0] b{i} = mp{i};\n",
9569 h = w - 1,
9570 ph = 2 * w - 1,
9571 ));
9572 return out;
9573 }
9574 if let LoopCell::MemRead { addr } = cell {
9577 let a_s = src_named(addr, name_in, name_val);
9578 out.push_str(&format!(
9579 " wire [63:0] amr{i} = {a_s};\n wire [63:0] b{i} = mem[amr{i}[{aw}:0]];\n",
9580 aw = mem_addrw - 1
9581 ));
9582 return out;
9583 }
9584 let rhs = match cell {
9585 LoopCell::Arith { op, a, b } => op.verilog(
9586 &src_named(a, name_in, name_val),
9587 &src_named(b, name_in, name_val),
9588 ),
9589 LoopCell::VectorArith { op, ty, a, b } => op.verilog(
9590 *ty,
9591 &src_named(a, name_in, name_val),
9592 &src_named(b, name_in, name_val),
9593 ),
9594 LoopCell::Func { unit, a, b } => unit.verilog(&src_named(a, name_in, name_val), &src_named(b, name_in, name_val)),
9595 LoopCell::MemRead { .. } => unreachable!("MemRead is emitted before the match"),
9596 LoopCell::HeapRead { .. } | LoopCell::HeapWrite { .. } | LoopCell::HeapAlloc { .. } => panic!(
9597 "Heap-tile RTL emission (a shared-heap read/write inside a loop) is the state-plane \
9598 follow-on; a sequence loop's operand read lowers via LoopCell::MemRead"
9599 ),
9600 LoopCell::Select { cmp, ca, cb, t, e } => format!(
9601 "($signed({}) {} $signed({})) ? {} : {}",
9602 src_named(ca, name_in, name_val),
9603 cmp.verilog(),
9604 src_named(cb, name_in, name_val),
9605 src_named(t, name_in, name_val),
9606 src_named(e, name_in, name_val),
9607 ),
9608 };
9609 out.push_str(&format!(" wire [63:0] b{i} = {rhs};\n"));
9610 out
9611}
9612
9613fn emit_loop_fabric_verilog_dp(name: &str, lf: &LoopFabric, mul_bits: Option<u32>) -> String {
9614 let name_in = |k: usize| -> String {
9615 if k < lf.n_state {
9616 format!("s{k}")
9617 } else if k == lf.n_state {
9618 "iv".to_string()
9619 } else {
9620 format!("inv{}", k - lf.n_state - 1)
9621 }
9622 };
9623 let name_val = |k: usize| format!("b{k}");
9624 let circ = |s: &Src| src_named(s, &|k| format!("in{k}"), &|k| format!("v{k}"));
9626
9627 let seq_k = lf.seq_input;
9631 const MEM_DEPTH: usize = 512;
9632 const MEM_ADDRW: usize = 9; let inputs = (0..lf.n_in)
9634 .filter(|k| Some(*k) != seq_k)
9635 .map(|k| format!("input [63:0] in{k}"))
9636 .collect::<Vec<_>>()
9637 .join(", ");
9638 let inputs = if inputs.is_empty() {
9639 String::new()
9640 } else {
9641 format!("{inputs}, ")
9642 };
9643 let (mem_ports, mem_decl, lo_str, hi_str) = if seq_k.is_some() {
9644 (
9645 format!(
9646 "input [63:0] seq_len, input mem_we, input [{a}:0] mem_waddr, input signed [63:0] mem_wdata, ",
9647 a = MEM_ADDRW - 1
9648 ),
9649 format!(
9650 " reg signed [63:0] mem [0:{d}];\n \
9651 always @(posedge clk) if (mem_we) mem[mem_waddr] <= mem_wdata;\n",
9652 d = MEM_DEPTH - 1
9653 ),
9654 "64'sd0".to_string(),
9655 "$signed(seq_len) - 64'sd1".to_string(),
9656 )
9657 } else {
9658 (String::new(), String::new(), circ(&lf.lo), circ(&lf.hi))
9659 };
9660
9661 let mut invs = String::new();
9662 for (j, s) in lf.invariants.iter().enumerate() {
9663 invs.push_str(&format!(" wire [63:0] inv{j} = {};\n", circ(s)));
9664 }
9665 let mut bodyw = String::new();
9666 for (i, cell) in lf.body.iter().enumerate() {
9667 bodyw.push_str(&loop_body_wire(i, cell, mul_bits, &name_in, &name_val, MEM_ADDRW));
9668 }
9669 let mut nexts = String::new();
9670 for (j, s) in lf.next.iter().enumerate() {
9671 nexts.push_str(&format!(
9672 " wire [63:0] ns{j} = {};\n",
9673 src_named(s, &name_in, &name_val)
9674 ));
9675 }
9676 let mut reset = String::new();
9677 let mut step = String::new();
9678 for (j, s) in lf.init.iter().enumerate() {
9679 reset.push_str(&format!(" s{j} <= {};\n", circ(s)));
9680 step.push_str(&format!(" s{j} <= ns{j};\n"));
9681 }
9682 let state_decl = (0..lf.n_state)
9683 .map(|j| format!("s{j}"))
9684 .collect::<Vec<_>>()
9685 .join(", ");
9686
9687 format!(
9688 "// cgra_{name} — a compiled COUNTED LOOP as a CGRA: a fixed spatial body datapath\n\
9689 // (wired SSA, no fetch/dispatch) streamed by a sequential controller once per induction\n\
9690 // value in lo..=hi. Futamura P1 for control flow: the body IS the circuit; the loop is a\n\
9691 // counter. 64-bit, so it wraps identically to the VM's Int; the body's one-step next-state\n\
9692 // is certified == the VM's per-iteration transition by our own SAT (the induction step).\n\
9693 module cgra_{name}(input clk, input rst, {inputs}{mem_ports}output [63:0] result, output done);\n\
9694 \x20 reg [63:0] {state_decl};\n\
9695 \x20 reg [63:0] iv;\n\
9696 \x20 reg running;\n\
9697 {mem_decl}\
9698 \x20 wire [63:0] lo = {lo};\n\
9699 \x20 wire [63:0] hi = {hi};\n\
9700 {invs}{bodyw}{nexts}\
9701 \x20 always @(posedge clk) begin\n\
9702 \x20 if (rst) begin\n\
9703 {reset}\
9704 \x20 iv <= lo;\n\
9705 \x20 running <= ($signed(lo) <= $signed(hi));\n\
9706 \x20 end else if (running) begin\n\
9707 {step}\
9708 \x20 if ($signed(iv) < $signed(hi)) iv <= iv + 64'sd1;\n\
9709 \x20 else running <= 1'b0;\n\
9710 \x20 end\n\
9711 \x20 end\n\
9712 \x20 assign done = !running;\n\
9713 \x20 assign result = s{result_state};\n\
9714 endmodule\n",
9715 lo = lo_str,
9716 hi = hi_str,
9717 result_state = lf.result_state,
9718 )
9719}
9720
9721fn accumulation_terms(lf: &LoopFabric, j: usize, uses: &[usize]) -> Option<(Vec<(Src, bool)>, Vec<usize>)> {
9724 fn walk(
9725 lf: &LoopFabric,
9726 s: Src,
9727 negated: bool,
9728 uses: &[usize],
9729 terms: &mut Vec<(Src, bool)>,
9730 absorbed: &mut Vec<usize>,
9731 ) {
9732 if let Src::Val(k) = s {
9733 if let LoopCell::Arith { op: op @ (AluOp::Add | AluOp::Sub), a, b } = lf.body[k] {
9734 if uses[k] == 1 {
9735 absorbed.push(k);
9736 walk(lf, a, negated, uses, terms, absorbed);
9737 walk(lf, b, negated != (op == AluOp::Sub), uses, terms, absorbed);
9738 return;
9739 }
9740 }
9741 }
9742 terms.push((s, negated));
9743 }
9744 let mut terms = Vec::new();
9745 let mut absorbed = Vec::new();
9746 walk(lf, lf.next[j], false, uses, &mut terms, &mut absorbed);
9747 let own: Vec<usize> = (0..terms.len()).filter(|&t| terms[t].0 == Src::In(j)).collect();
9748 if own.len() != 1 || terms[own[0]].1 {
9749 return None;
9750 }
9751 terms.remove(own[0]);
9752 Some((terms, absorbed))
9753}
9754
9755#[derive(Clone, Debug)]
9757pub struct PipelinedLoop {
9758 pub verilog: String,
9759 pub term_stages: usize,
9762}
9763
9764pub fn loop_cell_delay(cell: &LoopCell, mul_bits: Option<u32>) -> f64 {
9766 match cell {
9767 LoopCell::Arith { op: AluOp::Mul, .. } => if mul_bits.is_some() { 2.0 } else { 4.0 },
9768 LoopCell::Arith { op: AluOp::Add | AluOp::Sub, .. } => 1.0,
9769 LoopCell::Arith { op: AluOp::Shl | AluOp::Shr | AluOp::UShr, b: Src::Const(_), .. } => 0.0,
9770 LoopCell::Arith { op: AluOp::Shl | AluOp::Shr | AluOp::UShr, .. } => 1.0,
9771 LoopCell::Arith { op: AluOp::And | AluOp::Or | AluOp::Xor, .. } => 0.3,
9772 _ => 1.0,
9773 }
9774}
9775
9776pub fn emit_loop_fabric_verilog_pipelined(
9792 name: &str,
9793 lf: &LoopFabric,
9794 mul_bits: Option<u32>,
9795 term_budget: f64,
9796) -> Result<PipelinedLoop, String> {
9797 assert!(term_budget > 0.0, "a term stage budget is positive");
9798 if lf.seq_input.is_some() || lf.seq_heap_ptr.is_some() {
9799 return Err("a sequence loop streams its operands from memory; the pipelined form takes counted loops".into());
9800 }
9801 if !lf.prologue_heap.is_empty() || !lf.epilogue.is_empty() {
9802 return Err("a loop with a heap prologue or an epilogue island is not a bare accumulation".into());
9803 }
9804 if let Some(w) = mul_bits {
9805 assert!((2..=32).contains(&w), "int8 datapath mul_bits must be 2..=32 (got {w})");
9806 }
9807 for (i, c) in lf.body.iter().enumerate() {
9808 if !matches!(c, LoopCell::Arith { .. } | LoopCell::Select { .. } | LoopCell::VectorArith { .. }) {
9809 return Err(format!("body tile b{i} ({c:?}) is not a combinational tile"));
9810 }
9811 }
9812 let ns = lf.n_state;
9813 let mut uses = vec![0usize; lf.body.len()];
9814 let srcs = |c: &LoopCell| -> Vec<Src> {
9815 match *c {
9816 LoopCell::Arith { a, b, .. } | LoopCell::VectorArith { a, b, .. } => vec![a, b],
9817 LoopCell::Select { ca, cb, t, e, .. } => vec![ca, cb, t, e],
9818 _ => Vec::new(),
9819 }
9820 };
9821 for c in &lf.body {
9822 for s in srcs(c) {
9823 if let Src::Val(k) = s {
9824 uses[k] += 1;
9825 }
9826 }
9827 }
9828 for s in &lf.next {
9829 if let Src::Val(k) = s {
9830 uses[*k] += 1;
9831 }
9832 }
9833 let mut accumulations = Vec::with_capacity(ns);
9834 let mut absorbed = vec![false; lf.body.len()];
9835 for j in 0..ns {
9836 let (terms, cells) = accumulation_terms(lf, j, &uses)
9837 .ok_or_else(|| format!("state s{j} is not an accumulator s{j} + Σ terms"))?;
9838 for k in cells {
9839 absorbed[k] = true;
9840 }
9841 accumulations.push(terms);
9842 }
9843 let mut reads_state = vec![false; lf.body.len()];
9845 for (i, c) in lf.body.iter().enumerate() {
9846 if absorbed[i] {
9847 continue;
9848 }
9849 reads_state[i] = srcs(c).iter().any(|s| match s {
9850 Src::In(k) => *k < ns,
9851 Src::Val(k) => reads_state[*k],
9852 Src::Const(_) => false,
9853 });
9854 }
9855 for (j, terms) in accumulations.iter().enumerate() {
9856 for (t, _) in terms {
9857 let dep = match t {
9858 Src::In(k) => *k < ns,
9859 Src::Val(k) => absorbed[*k] || reads_state[*k],
9860 Src::Const(_) => false,
9861 };
9862 if dep {
9863 return Err(format!("a term of s{j} reads a state register; it cannot run a stage ahead"));
9864 }
9865 }
9866 }
9867
9868 let mut stage = vec![0usize; lf.body.len()];
9871 let mut arrival = vec![0.0f64; lf.body.len()];
9872 for (i, c) in lf.body.iter().enumerate() {
9873 if absorbed[i] {
9874 continue;
9875 }
9876 let ops = srcs(c);
9877 let latest = ops
9878 .iter()
9879 .filter_map(|s| match s {
9880 Src::Val(k) if !absorbed[*k] => Some(stage[*k]),
9881 _ => None,
9882 })
9883 .max()
9884 .unwrap_or(0);
9885 let start = ops
9886 .iter()
9887 .filter_map(|s| match s {
9888 Src::Val(k) if !absorbed[*k] && stage[*k] == latest => Some(arrival[*k]),
9889 _ => None,
9890 })
9891 .fold(0.0, f64::max);
9892 let d = loop_cell_delay(c, mul_bits);
9893 if start > 0.0 && start + d > term_budget {
9894 stage[i] = latest + 1;
9895 arrival[i] = d;
9896 } else {
9897 stage[i] = latest;
9898 arrival[i] = start + d;
9899 }
9900 }
9901 let last = accumulations
9902 .iter()
9903 .flatten()
9904 .filter_map(|(t, _)| match t {
9905 Src::Val(k) => Some(stage[*k]),
9906 _ => None,
9907 })
9908 .max()
9909 .unwrap_or(0);
9910 let term_stages = last + 1;
9911
9912 let mut reach = vec![0usize; lf.body.len()];
9914 let mut iv_reach = 0usize;
9915 let note = |s: Src, at: usize, reach: &mut Vec<usize>, iv_reach: &mut usize| match s {
9916 Src::Val(k) => reach[k] = reach[k].max(at - stage[k]),
9917 Src::In(k) if k == ns => *iv_reach = (*iv_reach).max(at),
9918 _ => {}
9919 };
9920 for (i, c) in lf.body.iter().enumerate() {
9921 if !absorbed[i] {
9922 for s in srcs(c) {
9923 note(s, stage[i], &mut reach, &mut iv_reach);
9924 }
9925 }
9926 }
9927 for (t, _) in accumulations.iter().flatten() {
9928 note(*t, last, &mut reach, &mut iv_reach);
9929 }
9930 let delayed = |base: String, d: usize| if d == 0 { base } else { format!("{base}_d{d}") };
9931
9932 let circ = |s: &Src| src_named(s, &|k| format!("in{k}"), &|k| format!("v{k}"));
9933 let inputs = (0..lf.n_in)
9934 .map(|k| format!("input [63:0] in{k}, "))
9935 .collect::<String>();
9936 let mut invs = String::new();
9937 for (j, s) in lf.invariants.iter().enumerate() {
9938 invs.push_str(&format!(" wire [63:0] inv{j} = {};\n", circ(s)));
9939 }
9940 let mut bodyw = String::new();
9941 for (i, cell) in lf.body.iter().enumerate() {
9942 if absorbed[i] {
9943 continue;
9944 }
9945 let at = stage[i];
9946 let name_in = |k: usize| -> String {
9947 if k < ns {
9948 format!("s{k}")
9949 } else if k == ns {
9950 delayed("iv".into(), at)
9951 } else {
9952 format!("inv{}", k - ns - 1)
9953 }
9954 };
9955 let name_val = |k: usize| delayed(format!("b{k}"), at - stage[k]);
9956 bodyw.push_str(&loop_body_wire(i, cell, mul_bits, &name_in, &name_val, 9));
9957 }
9958 let mut pipe_regs = String::new();
9959 let mut pipe_shift = String::new();
9960 for d in 1..=iv_reach {
9961 pipe_regs.push_str(&format!(" reg [63:0] iv_d{d};\n"));
9962 pipe_shift.push_str(&format!(" iv_d{d} <= {};\n", delayed("iv".into(), d - 1)));
9963 }
9964 for (k, &r) in reach.iter().enumerate() {
9965 for d in 1..=r {
9966 pipe_regs.push_str(&format!(" reg [63:0] b{k}_d{d};\n"));
9967 pipe_shift.push_str(&format!(" b{k}_d{d} <= {};\n", delayed(format!("b{k}"), d - 1)));
9968 }
9969 }
9970 let name_in = |k: usize| -> String {
9971 if k < ns {
9972 format!("s{k}")
9973 } else if k == ns {
9974 delayed("iv".into(), last)
9975 } else {
9976 format!("inv{}", k - ns - 1)
9977 }
9978 };
9979 let name_val = |k: usize| delayed(format!("b{k}"), last - stage[k]);
9980
9981 let mut regs = String::new();
9984 let mut latch = String::new();
9985 let mut trees = String::new();
9986 let mut accumulate = String::new();
9987 let mut reset = String::new();
9988 for (j, terms) in accumulations.iter().enumerate() {
9989 regs.push_str(&format!(" reg [63:0] s{j}_sum, s{j}_carry;\n"));
9990 reset.push_str(&format!(" s{j}_sum <= {}; s{j}_carry <= 64'd0;\n", circ(&lf.init[j])));
9991 let mut words = vec![format!("s{j}_sum"), format!("s{j}_carry")];
9992 let mut constant: i64 = 0;
9993 for (n, (t, negated)) in terms.iter().enumerate() {
9994 match t {
9995 Src::Const(c) => {
9996 constant = if *negated { constant.wrapping_sub(*c) } else { constant.wrapping_add(*c) };
9997 }
9998 _ => {
9999 regs.push_str(&format!(" reg [63:0] t{j}_{n};\n"));
10000 latch.push_str(&format!(" t{j}_{n} <= {};\n", src_named(t, &name_in, &name_val)));
10001 if *negated {
10002 words.push(format!("~t{j}_{n}"));
10003 constant = constant.wrapping_add(1);
10004 } else {
10005 words.push(format!("t{j}_{n}"));
10006 }
10007 }
10008 }
10009 }
10010 if constant != 0 {
10011 words.push(format!("64'h{:016x}", constant as u64));
10012 }
10013 let mut level = 0usize;
10014 while words.len() > 2 {
10015 let mut next = Vec::with_capacity(words.len());
10016 for (g, t) in words.chunks(3).enumerate() {
10017 if t.len() < 3 {
10018 next.extend(t.iter().cloned());
10019 continue;
10020 }
10021 let tag = format!("s{j}_l{level}_{g}");
10022 trees.push_str(&format!(
10023 " wire [63:0] {tag}_x = {a} ^ {b};\n wire [63:0] {tag}_s = {tag}_x ^ {c};\n \
10024 wire [63:0] {tag}_c = (({a} & {b}) | ({c} & {tag}_x)) << 1;\n",
10025 a = t[0],
10026 b = t[1],
10027 c = t[2],
10028 ));
10029 next.push(format!("{tag}_s"));
10030 next.push(format!("{tag}_c"));
10031 }
10032 words = next;
10033 level += 1;
10034 }
10035 trees.push_str(&format!(
10036 " wire [63:0] s{j}_sum_next = {};\n wire [63:0] s{j}_carry_next = {};\n",
10037 words[0],
10038 words.get(1).cloned().unwrap_or_else(|| "64'd0".into())
10039 ));
10040 accumulate.push_str(&format!(" s{j}_sum <= s{j}_sum_next; s{j}_carry <= s{j}_carry_next;\n"));
10041 }
10042 let mut prefix = String::from(" wire [63:0] ivp0 = iv;\n");
10043 let mut level = 0;
10044 let mut span = 1;
10045 while span < 64 {
10046 prefix.push_str(&format!(
10047 " wire [63:0] ivp{n} = ivp{level} & {{ivp{level}[{hi}:0], {span}'b{ones}}};\n",
10048 n = level + 1,
10049 hi = 63 - span,
10050 ones = "1".repeat(span),
10051 ));
10052 level += 1;
10053 span *= 2;
10054 }
10055 prefix.push_str(&format!(" wire [63:0] iv_next = iv ^ {{ivp{level}[62:0], 1'b1}};\n"));
10056 let state_reads = (0..ns)
10057 .map(|j| format!(" wire [63:0] s{j} = s{j}_sum + s{j}_carry;\n"))
10058 .collect::<String>();
10059 Ok(format!(
10060 "// cgra_{name} — a compiled COUNTED LOOP as a CGRA, pipelined: the body's terms compute from the induction\n\
10061 // counter and the invariants through {term_stages} register stage(s); each accumulator holds sum and carry words\n\
10062 // that absorb them through 3:2 compressors, so neither recurrence carries a carry chain. 64-bit, so it wraps\n\
10063 // identically to the VM's Int; a loop whose body runs finishes {term_stages} clock(s) after the counter does.\n\
10064 module cgra_{name}(input clk, input rst, {inputs}output [63:0] result, output done);\n\
10065 {regs}\
10066 \x20 reg [63:0] iv;\n\
10067 \x20 reg running;\n\
10068 \x20 reg [{live_top}:0] live;\n\
10069 {pipe_regs}\
10070 \x20 wire [63:0] lo = {lo};\n\
10071 \x20 wire [63:0] hi = {hi};\n\
10072 {invs}{bodyw}{trees}{prefix}{state_reads}\
10073 \x20 always @(posedge clk) begin\n\
10074 \x20 if (rst) begin\n\
10075 {reset}\
10076 \x20 iv <= lo;\n\
10077 \x20 running <= ($signed(lo) <= $signed(hi));\n\
10078 \x20 live <= {{{live_width}{{1'b0}}}};\n\
10079 \x20 end else begin\n\
10080 \x20 live <= {live_next};\n\
10081 {pipe_shift}{latch}\
10082 \x20 if (live[{live_top}]) begin\n\
10083 {accumulate}\
10084 \x20 end\n\
10085 \x20 if (running) begin\n\
10086 \x20 if ($signed(iv) < $signed(hi)) iv <= iv_next;\n\
10087 \x20 else running <= 1'b0;\n\
10088 \x20 end\n\
10089 \x20 end\n\
10090 \x20 end\n\
10091 \x20 assign done = !running && !(|live);\n\
10092 \x20 assign result = s{result_state};\n\
10093 endmodule\n",
10094 lo = circ(&lf.lo),
10095 hi = circ(&lf.hi),
10096 result_state = lf.result_state,
10097 live_top = term_stages - 1,
10098 live_width = term_stages,
10099 live_next = if term_stages == 1 {
10100 "running".to_string()
10101 } else {
10102 format!("{{live[{}:0], running}}", term_stages - 2)
10103 },
10104 ))
10105 .map(|verilog| PipelinedLoop { verilog, term_stages })
10106}
10107
10108pub fn loop_next_to_bounded(
10112 lf: &LoopFabric,
10113 which: usize,
10114 width: u32,
10115) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
10116 use crate::codegen_sva::sva_to_verify::{BitVecBoundedOp, BoundedExpr};
10117 let name_in = |k: usize| -> String {
10118 if k < lf.n_state {
10119 format!("s{k}")
10120 } else if k == lf.n_state {
10121 "iv".to_string()
10122 } else {
10123 format!("inv{}", k - lf.n_state - 1)
10124 }
10125 };
10126 fn src_expr(
10127 s: &Src,
10128 vals: &[crate::codegen_sva::sva_to_verify::BoundedExpr],
10129 width: u32,
10130 name_in: &dyn Fn(usize) -> String,
10131 ) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
10132 use crate::codegen_sva::sva_to_verify::BoundedExpr;
10133 match s {
10134 Src::In(k) => BoundedExpr::BitVecVar(name_in(*k), width),
10135 Src::Val(k) => vals[*k].clone(),
10136 Src::Const(c) => BoundedExpr::BitVecConst {
10137 width,
10138 value: *c as u64,
10139 },
10140 }
10141 }
10142 let slt = |l: BoundedExpr, r: BoundedExpr| BoundedExpr::BitVecBinary {
10143 op: BitVecBoundedOp::SLt,
10144 left: Box::new(l),
10145 right: Box::new(r),
10146 };
10147 let eq = |l: BoundedExpr, r: BoundedExpr| BoundedExpr::BitVecBinary {
10148 op: BitVecBoundedOp::Eq,
10149 left: Box::new(l),
10150 right: Box::new(r),
10151 };
10152 let mut vals: Vec<BoundedExpr> = Vec::with_capacity(lf.body.len());
10153 for cell in &lf.body {
10154 let expr = match cell {
10155 LoopCell::Arith { op, a, b } => {
10156 let a = src_expr(a, &vals, width, &name_in);
10157 let b = src_expr(b, &vals, width, &name_in);
10158 let bop = match op {
10159 AluOp::Add => BitVecBoundedOp::Add,
10160 AluOp::Sub => BitVecBoundedOp::Sub,
10161 AluOp::Mul => BitVecBoundedOp::Mul,
10162 AluOp::And => BitVecBoundedOp::And,
10163 AluOp::Or => BitVecBoundedOp::Or,
10164 AluOp::Xor => BitVecBoundedOp::Xor,
10165 AluOp::Shl => BitVecBoundedOp::Shl,
10166 AluOp::Shr => BitVecBoundedOp::AShr,
10167 AluOp::UShr => BitVecBoundedOp::Shr,
10168 };
10169 BoundedExpr::BitVecBinary { op: bop, left: Box::new(a), right: Box::new(b) }
10170 }
10171 LoopCell::VectorArith { .. } => panic!(
10172 "a packed-SIMD (Simd) tile in a loop SAT bound is not SAT-lowered here — a Simd-bearing fabric \
10173 is proven by eval ≡ VM + iverilog (simd_tile_lock + simd_rtl_lock), like a Func tile"
10174 ),
10175 LoopCell::Func { .. } => panic!(
10176 "division in a counted-loop body is not yet SAT-lowered — the shared_divider's \
10177 own certified proof (rtl.rs::the_signed_divider_is_proven_correct_by_our_sat) covers \
10178 the arithmetic; loop-carried division SAT is future work"
10179 ),
10180 LoopCell::MemRead { .. } | LoopCell::HeapRead { .. } | LoopCell::HeapWrite { .. } | LoopCell::HeapAlloc { .. } => panic!(
10181 "MemRead in a loop-carried SAT bound is future work (the memory-loop induction proof)"
10182 ),
10183 LoopCell::Select { cmp, ca, cb, t, e } => {
10184 let ca = src_expr(ca, &vals, width, &name_in);
10185 let cb = src_expr(cb, &vals, width, &name_in);
10186 let t = src_expr(t, &vals, width, &name_in);
10187 let e = src_expr(e, &vals, width, &name_in);
10188 let (cond, tt, ee) = match cmp {
10191 CmpKind::Lt => (slt(ca, cb), t, e),
10192 CmpKind::Gt => (slt(cb, ca), t, e),
10193 CmpKind::Ge => (slt(ca, cb), e, t),
10194 CmpKind::Le => (slt(cb, ca), e, t),
10195 CmpKind::Eq => (eq(ca, cb), t, e),
10196 CmpKind::Ne => (eq(ca, cb), e, t),
10197 };
10198 BoundedExpr::BitVecIte {
10199 cond: Box::new(cond),
10200 then: Box::new(tt),
10201 else_: Box::new(ee),
10202 }
10203 }
10204 };
10205 vals.push(expr);
10206 }
10207 src_expr(&lf.next[which], &vals, width, &name_in)
10208}
10209
10210#[derive(Clone, Debug)]
10221pub struct WhileFabric {
10222 pub n_in: usize,
10223 pub n_state: usize,
10224 pub init: Vec<Src>,
10225 pub invariants: Vec<Src>,
10226 pub body: Vec<LoopCell>,
10228 pub next: Vec<Src>,
10229 pub pred: (CmpKind, Src, Src),
10231 pub result_state: usize,
10232}
10233
10234pub fn while_fabric_from_source(src: &str, fn_name: &str) -> WhileFabric {
10237 use crate::vm::{Compiler, Constant, Op};
10238 use std::collections::{BTreeSet, HashMap};
10239
10240 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
10241 let (stmts, _t, _p) = parsed.expect("parse failed");
10242 let program = Compiler::compile(stmts, interner).expect("compile failed");
10243 let f = program
10244 .functions
10245 .iter()
10246 .find(|f| interner.resolve(f.name) == fn_name)
10247 .unwrap_or_else(|| panic!("function `{fn_name}` not found"));
10248
10249 setup_program_thread_locals(&program);
10253 for (p, pt) in f.param_types.iter().enumerate() {
10254 if matches!(pt, Some(crate::vm::BoundaryType::Float)) {
10255 FLOAT_REGS.with(|t| {
10256 t.borrow_mut().insert(p as u16);
10257 });
10258 }
10259 }
10260
10261 let mut ops: Vec<(usize, Op)> = Vec::new();
10262 for (i, op) in program.code.iter().enumerate().skip(f.entry_pc) {
10263 ops.push((i, *op));
10264 if matches!(op, Op::Return { .. } | Op::ReturnNothing) {
10265 break;
10266 }
10267 }
10268 let (backedge_idx, header_pc) = ops
10270 .iter()
10271 .enumerate()
10272 .find_map(|(i, (pc, op))| match op {
10273 Op::Jump { target } if *target < *pc => Some((i, *target)),
10274 _ => None,
10275 })
10276 .expect("while fabric: no back-edge (not a while loop)");
10277
10278 let mut pro: HashMap<u16, Src> = HashMap::new();
10279 for p in 0..f.param_count {
10280 pro.insert(p, Src::In(p as usize));
10281 }
10282 let getp = |pro: &HashMap<u16, Src>, r: u16| -> Src {
10283 *pro.get(&r)
10284 .unwrap_or_else(|| panic!("while prologue reads unwritten R{r}"))
10285 };
10286 let mut header_start = 0;
10288 for (i, (pc, op)) in ops.iter().enumerate() {
10289 if *pc == header_pc {
10290 header_start = i;
10291 break;
10292 }
10293 match op {
10294 Op::LoadConst { dst, idx: ci } => match program.constants[*ci as usize] {
10295 Constant::Int(n) => {
10296 pro.insert(*dst, Src::Const(n));
10297 }
10298 Constant::Float(fv) => {
10299 pro.insert(*dst, Src::Const(fv.to_bits() as i64));
10300 FLOAT_REGS.with(|t| {
10301 t.borrow_mut().insert(*dst);
10302 });
10303 }
10304 ref c => panic!(
10305 "while prologue: non-integer constant {c:?} (only Int / Float supported)"
10306 ),
10307 },
10308 Op::Move { dst, src } => {
10309 let s = getp(&pro, *src);
10310 if FLOAT_REGS.with(|t| t.borrow().contains(src)) {
10311 FLOAT_REGS.with(|t| {
10312 t.borrow_mut().insert(*dst);
10313 });
10314 }
10315 pro.insert(*dst, s);
10316 }
10317 other => panic!("while prologue: `{}` unsupported", other.name()),
10318 }
10319 }
10320 let jif_idx = ops[header_start..backedge_idx]
10322 .iter()
10323 .position(|(_, o)| matches!(o, Op::JumpIfFalse { .. }))
10324 .map(|x| x + header_start)
10325 .expect("while fabric: no JumpIfFalse header test");
10326 let (cond_reg, exit_target) = match ops[jif_idx].1 {
10327 Op::JumpIfFalse { cond, target } => (cond, target),
10328 _ => unreachable!(),
10329 };
10330 let header_ops: Vec<Op> = ops[header_start..jif_idx].iter().map(|(_, o)| *o).collect();
10331 let body_ops: Vec<(usize, Op)> = ops[jif_idx + 1..backedge_idx].to_vec();
10332 let result_reg = ops[backedge_idx + 1..]
10333 .iter()
10334 .find_map(|(pc, o)| match o {
10335 Op::Return { src } if *pc == exit_target => Some(*src),
10336 _ => None,
10337 })
10338 .or_else(|| {
10339 ops.iter().find_map(|(_, o)| match o {
10340 Op::Return { src } => Some(*src),
10341 _ => None,
10342 })
10343 })
10344 .expect("while fabric: no Return");
10345
10346 let reg_of = |op: &Op| -> (Option<u16>, Vec<u16>) {
10347 match op {
10348 Op::LoadConst { dst, .. } => (Some(*dst), vec![]),
10349 Op::LoadNow { dst } | Op::LoadToday { dst } => (Some(*dst), vec![]),
10351 Op::Move { dst, src } => (Some(*dst), vec![*src]),
10352 Op::Not { dst, src } => (Some(*dst), vec![*src]),
10354 Op::CallBuiltin { dst, args_start, arg_count, .. } => {
10357 (Some(*dst), (0..*arg_count as u16).map(|k| *args_start + k).collect())
10358 }
10359 Op::Add { dst, lhs, rhs }
10360 | Op::Sub { dst, lhs, rhs }
10361 | Op::Mul { dst, lhs, rhs }
10362 | Op::Div { dst, lhs, rhs }
10365 | Op::Mod { dst, lhs, rhs }
10366 | Op::FloorDiv { dst, lhs, rhs }
10367 | Op::BitAnd { dst, lhs, rhs }
10370 | Op::BitOr { dst, lhs, rhs }
10371 | Op::BitXor { dst, lhs, rhs }
10372 | Op::Shl { dst, lhs, rhs }
10373 | Op::Shr { dst, lhs, rhs }
10374 | Op::Pow { dst, lhs, rhs }
10376 | Op::Lt { dst, lhs, rhs }
10377 | Op::Gt { dst, lhs, rhs }
10378 | Op::LtEq { dst, lhs, rhs }
10379 | Op::GtEq { dst, lhs, rhs }
10380 | Op::Eq { dst, lhs, rhs }
10381 | Op::NotEq { dst, lhs, rhs } => (Some(*dst), vec![*lhs, *rhs]),
10382 Op::AddAssign { dst, src } => (Some(*dst), vec![*dst, *src]),
10383 Op::Jump { .. } | Op::JumpIfFalse { .. } => (None, vec![]),
10384 other => panic!("while loop: body op `{}` unsupported", other.name()),
10385 }
10386 };
10387 let mut written: BTreeSet<u16> = BTreeSet::new();
10388 let mut upward: BTreeSet<u16> = BTreeSet::new();
10389 for op in header_ops.iter().chain(body_ops.iter().map(|(_, o)| o)) {
10390 let (dst, reads) = reg_of(op);
10391 for r in reads {
10392 if !written.contains(&r) {
10393 upward.insert(r);
10394 }
10395 }
10396 if let Some(d) = dst {
10397 written.insert(d);
10398 }
10399 }
10400 let mut carried: Vec<u16> = upward.intersection(&written).copied().collect();
10401 if written.contains(&result_reg) && !carried.contains(&result_reg) {
10402 carried.push(result_reg);
10403 }
10404 carried.sort_unstable();
10405 let invariants_regs: Vec<u16> = upward.difference(&written).copied().collect();
10406
10407 let n_state = carried.len();
10408 let state_slot: HashMap<u16, usize> =
10409 carried.iter().enumerate().map(|(i, r)| (*r, i)).collect();
10410 let inv_slot: HashMap<u16, usize> = invariants_regs
10411 .iter()
10412 .enumerate()
10413 .map(|(i, r)| (*r, i))
10414 .collect();
10415
10416 let mut reg: HashMap<u16, Src> = HashMap::new();
10418 for (r, s) in &state_slot {
10419 reg.insert(*r, Src::In(*s));
10420 }
10421 for (r, s) in &inv_slot {
10422 reg.insert(*r, Src::In(n_state + *s));
10423 }
10424
10425 let mut hdr_cells: Vec<LoopCell> = Vec::new();
10428 let mut preds: HashMap<u16, (CmpKind, Src, Src)> = HashMap::new();
10429 let mut hdr_reg = reg.clone();
10430 exec_ops(
10431 &header_ops,
10432 &mut hdr_reg,
10433 &mut hdr_cells,
10434 &mut preds,
10435 &program.constants,
10436 );
10437 assert!(
10438 hdr_cells.is_empty(),
10439 "while fabric: arithmetic in the loop predicate is unsupported (G3 scope)"
10440 );
10441 let pred = *preds
10442 .get(&cond_reg)
10443 .expect("while fabric: the header test is not a comparison");
10444
10445 let body = if_convert_body(
10446 &body_ops,
10447 &mut reg,
10448 &carried,
10449 &program.constants,
10450 Vec::new(),
10451 );
10452 let init: Vec<Src> = carried.iter().map(|r| getp(&pro, *r)).collect();
10453 let next: Vec<Src> = carried.iter().map(|&r| *reg.get(&r).unwrap()).collect();
10454 let invariants: Vec<Src> = invariants_regs.iter().map(|r| getp(&pro, *r)).collect();
10455 let result_state = *state_slot
10456 .get(&result_reg)
10457 .expect("while fabric: the returned register is not loop-carried");
10458
10459 WhileFabric {
10460 n_in: f.param_count as usize,
10461 n_state,
10462 init,
10463 invariants,
10464 body,
10465 next,
10466 pred,
10467 result_state,
10468 }
10469 })
10470}
10471
10472pub fn eval_while_fabric(wf: &WhileFabric, params: &[i64]) -> i64 {
10475 let empty: [i64; 0] = [];
10476 let mut state: Vec<i64> = wf.init.iter().map(|s| s.eval(params, &empty)).collect();
10477 let invs: Vec<i64> = wf
10478 .invariants
10479 .iter()
10480 .map(|s| s.eval(params, &empty))
10481 .collect();
10482 let holds = |state: &[i64]| -> bool {
10483 let mut bin: Vec<i64> = state.to_vec();
10484 bin.extend_from_slice(&invs);
10485 let (cmp, ca, cb) = &wf.pred;
10486 cmp.eval(ca.eval(&bin, &empty), cb.eval(&bin, &empty))
10487 };
10488 let mut guard: u64 = 0;
10489 while holds(&state) {
10490 let mut bin: Vec<i64> = state.clone();
10491 bin.extend_from_slice(&invs);
10492 let mut vals: Vec<i64> = Vec::with_capacity(wf.body.len());
10493 for cell in &wf.body {
10494 let v = match cell {
10495 LoopCell::Arith { op, a, b } => op.eval(a.eval(&bin, &vals), b.eval(&bin, &vals)),
10496 LoopCell::VectorArith { op, ty, a, b } => {
10497 op.eval(*ty, a.eval(&bin, &vals) as u64, b.eval(&bin, &vals) as u64) as i64
10498 }
10499 LoopCell::Func { unit, a, b } => {
10500 unit.eval(a.eval(&bin, &vals), b.eval(&bin, &vals))
10501 }
10502 LoopCell::MemRead { .. }
10503 | LoopCell::HeapRead { .. }
10504 | LoopCell::HeapWrite { .. }
10505 | LoopCell::HeapAlloc { .. } => panic!(
10506 "MemRead reached an evaluator that does not thread the iterated sequence — \
10507 a sequence loop must be evaluated via eval_loop_fabric_seq"
10508 ),
10509 LoopCell::Select { cmp, ca, cb, t, e } => {
10510 if cmp.eval(ca.eval(&bin, &vals), cb.eval(&bin, &vals)) {
10511 t.eval(&bin, &vals)
10512 } else {
10513 e.eval(&bin, &vals)
10514 }
10515 }
10516 };
10517 vals.push(v);
10518 }
10519 state = wf.next.iter().map(|s| s.eval(&bin, &vals)).collect();
10520 guard += 1;
10521 assert!(
10522 guard < 100_000_000,
10523 "while fabric did not terminate within the guard"
10524 );
10525 }
10526 state[wf.result_state]
10527}
10528
10529pub fn emit_while_fabric_verilog(name: &str, wf: &WhileFabric) -> String {
10533 let ns = wf.n_state;
10534 let name_in = |k: usize| -> String {
10535 if k < ns {
10536 format!("s{k}")
10537 } else {
10538 format!("inv{}", k - ns)
10539 }
10540 };
10541 let name_val = |k: usize| format!("b{k}");
10542 let circ = |s: &Src| src_named(s, &|k| format!("in{k}"), &|k| format!("v{k}"));
10543
10544 let pred_with = |state_name: &dyn Fn(usize) -> String| -> String {
10546 let (cmp, ca, cb) = &wf.pred;
10547 let r = |s: &Src| -> String {
10548 match s {
10549 Src::In(k) if *k < ns => state_name(*k),
10550 Src::In(k) => format!("inv{}", k - ns),
10551 Src::Const(c) => {
10552 if *c < 0 {
10553 format!("-64'sd{}", (*c as i128).unsigned_abs())
10554 } else {
10555 format!("64'sd{c}")
10556 }
10557 }
10558 Src::Val(_) => unreachable!("while predicate has no datapath vals"),
10559 }
10560 };
10561 format!("($signed({}) {} $signed({}))", r(ca), cmp.verilog(), r(cb))
10562 };
10563
10564 let inputs = (0..wf.n_in)
10565 .map(|k| format!("input [63:0] in{k}"))
10566 .collect::<Vec<_>>()
10567 .join(", ");
10568 let inputs = if inputs.is_empty() {
10569 String::new()
10570 } else {
10571 format!("{inputs}, ")
10572 };
10573 let mut invs = String::new();
10574 for (j, s) in wf.invariants.iter().enumerate() {
10575 invs.push_str(&format!(" wire [63:0] inv{j} = {};\n", circ(s)));
10576 }
10577 let mut bodyw = String::new();
10578 for (i, cell) in wf.body.iter().enumerate() {
10579 let rhs = match cell {
10580 LoopCell::Arith { op, a, b } => {
10581 op.verilog(&src_named(a, &name_in, &name_val), &src_named(b, &name_in, &name_val))
10582 }
10583 LoopCell::VectorArith { op, ty, a, b } => {
10584 op.verilog(*ty, &src_named(a, &name_in, &name_val), &src_named(b, &name_in, &name_val))
10585 }
10586 LoopCell::Func { unit, a, b } => unit.verilog(&src_named(a, &name_in, &name_val), &src_named(b, &name_in, &name_val)),
10587 LoopCell::MemRead { .. } | LoopCell::HeapRead { .. } | LoopCell::HeapWrite { .. } | LoopCell::HeapAlloc { .. } => panic!(
10588 "MemRead RTL emission (a scratchpad-read tile driven by the induction counter) is the \
10589 P2 scratchpad follow-on; the sequence-loop LOWERING is proven via eval_loop_fabric_seq"
10590 ),
10591 LoopCell::Select { cmp, ca, cb, t, e } => format!(
10592 "($signed({}) {} $signed({})) ? {} : {}",
10593 src_named(ca, &name_in, &name_val),
10594 cmp.verilog(),
10595 src_named(cb, &name_in, &name_val),
10596 src_named(t, &name_in, &name_val),
10597 src_named(e, &name_in, &name_val),
10598 ),
10599 };
10600 bodyw.push_str(&format!(" wire [63:0] b{i} = {rhs};\n"));
10601 }
10602 let mut nexts = String::new();
10603 for (j, s) in wf.next.iter().enumerate() {
10604 nexts.push_str(&format!(
10605 " wire [63:0] ns{j} = {};\n",
10606 src_named(s, &name_in, &name_val)
10607 ));
10608 }
10609 let mut reset = String::new();
10610 let mut step = String::new();
10611 for (j, s) in wf.init.iter().enumerate() {
10612 reset.push_str(&format!(" s{j} <= {};\n", circ(s)));
10613 step.push_str(&format!(" s{j} <= ns{j};\n"));
10614 }
10615 let state_decl = (0..ns)
10616 .map(|j| format!("s{j}"))
10617 .collect::<Vec<_>>()
10618 .join(", ");
10619 let pred_init = pred_with(&|k| circ(&wf.init[k]));
10621 let pred_nxt = pred_with(&|k| format!("ns{k}"));
10622
10623 format!(
10624 "// while_{name} — a compiled WHILE loop as a CGRA: a fixed body datapath streamed by a\n\
10625 // controller whose CONTINUE condition is FABRIC-COMPUTED (a predicate over the state), not\n\
10626 // a counter — the data-dependent trip count is decided by the datapath. Futamura P1 for a\n\
10627 // data-dependent loop; 64-bit so it wraps like the VM's Int; body-step and predicate are\n\
10628 // each certified == the VM by our own SAT.\n\
10629 module while_{name}(input clk, input rst, {inputs}output [63:0] result, output done);\n\
10630 \x20 reg [63:0] {state_decl};\n\
10631 \x20 reg running;\n\
10632 {invs}{bodyw}{nexts}\
10633 \x20 always @(posedge clk) begin\n\
10634 \x20 if (rst) begin\n\
10635 {reset}\
10636 \x20 running <= {pred_init};\n\
10637 \x20 end else if (running) begin\n\
10638 {step}\
10639 \x20 running <= {pred_nxt};\n\
10640 \x20 end\n\
10641 \x20 end\n\
10642 \x20 assign done = !running;\n\
10643 \x20 assign result = s{result_state};\n\
10644 endmodule\n",
10645 result_state = wf.result_state,
10646 )
10647}
10648
10649pub fn while_next_to_bounded(
10652 wf: &WhileFabric,
10653 which: usize,
10654 width: u32,
10655) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
10656 use crate::codegen_sva::sva_to_verify::{BitVecBoundedOp, BoundedExpr};
10657 let ns = wf.n_state;
10658 let name_in = move |k: usize| -> String {
10659 if k < ns {
10660 format!("s{k}")
10661 } else {
10662 format!("inv{}", k - ns)
10663 }
10664 };
10665 fn src_expr(
10666 s: &Src,
10667 vals: &[crate::codegen_sva::sva_to_verify::BoundedExpr],
10668 width: u32,
10669 name_in: &dyn Fn(usize) -> String,
10670 ) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
10671 use crate::codegen_sva::sva_to_verify::BoundedExpr;
10672 match s {
10673 Src::In(k) => BoundedExpr::BitVecVar(name_in(*k), width),
10674 Src::Val(k) => vals[*k].clone(),
10675 Src::Const(c) => BoundedExpr::BitVecConst {
10676 width,
10677 value: *c as u64,
10678 },
10679 }
10680 }
10681 let slt = |l: BoundedExpr, r: BoundedExpr| BoundedExpr::BitVecBinary {
10682 op: BitVecBoundedOp::SLt,
10683 left: Box::new(l),
10684 right: Box::new(r),
10685 };
10686 let eq = |l: BoundedExpr, r: BoundedExpr| BoundedExpr::BitVecBinary {
10687 op: BitVecBoundedOp::Eq,
10688 left: Box::new(l),
10689 right: Box::new(r),
10690 };
10691 let mut vals: Vec<BoundedExpr> = Vec::with_capacity(wf.body.len());
10692 for cell in &wf.body {
10693 let expr = match cell {
10694 LoopCell::Arith { op, a, b } => {
10695 let a = src_expr(a, &vals, width, &name_in);
10696 let b = src_expr(b, &vals, width, &name_in);
10697 let bop = match op {
10698 AluOp::Add => BitVecBoundedOp::Add,
10699 AluOp::Sub => BitVecBoundedOp::Sub,
10700 AluOp::Mul => BitVecBoundedOp::Mul,
10701 AluOp::And => BitVecBoundedOp::And,
10702 AluOp::Or => BitVecBoundedOp::Or,
10703 AluOp::Xor => BitVecBoundedOp::Xor,
10704 AluOp::Shl => BitVecBoundedOp::Shl,
10705 AluOp::Shr => BitVecBoundedOp::AShr,
10706 AluOp::UShr => BitVecBoundedOp::Shr,
10707 };
10708 BoundedExpr::BitVecBinary { op: bop, left: Box::new(a), right: Box::new(b) }
10709 }
10710 LoopCell::VectorArith { .. } => panic!(
10711 "a packed-SIMD (Simd) tile in a loop SAT bound is not SAT-lowered here — a Simd-bearing fabric \
10712 is proven by eval ≡ VM + iverilog (simd_tile_lock + simd_rtl_lock), like a Func tile"
10713 ),
10714 LoopCell::Func { .. } => panic!(
10715 "division in a counted-loop body is not yet SAT-lowered — the shared_divider's \
10716 own certified proof (rtl.rs::the_signed_divider_is_proven_correct_by_our_sat) covers \
10717 the arithmetic; loop-carried division SAT is future work"
10718 ),
10719 LoopCell::MemRead { .. } | LoopCell::HeapRead { .. } | LoopCell::HeapWrite { .. } | LoopCell::HeapAlloc { .. } => panic!(
10720 "MemRead in a loop-carried SAT bound is future work (the memory-loop induction proof)"
10721 ),
10722 LoopCell::Select { cmp, ca, cb, t, e } => {
10723 let ca = src_expr(ca, &vals, width, &name_in);
10724 let cb = src_expr(cb, &vals, width, &name_in);
10725 let t = src_expr(t, &vals, width, &name_in);
10726 let e = src_expr(e, &vals, width, &name_in);
10727 let (cond, tt, ee) = match cmp {
10728 CmpKind::Lt => (slt(ca, cb), t, e),
10729 CmpKind::Gt => (slt(cb, ca), t, e),
10730 CmpKind::Ge => (slt(ca, cb), e, t),
10731 CmpKind::Le => (slt(cb, ca), e, t),
10732 CmpKind::Eq => (eq(ca, cb), t, e),
10733 CmpKind::Ne => (eq(ca, cb), e, t),
10734 };
10735 BoundedExpr::BitVecIte { cond: Box::new(cond), then: Box::new(tt), else_: Box::new(ee) }
10736 }
10737 };
10738 vals.push(expr);
10739 }
10740 src_expr(&wf.next[which], &vals, width, &name_in)
10741}
10742
10743pub fn emit_while_fabric_testbench(
10746 name: &str,
10747 wf: &WhileFabric,
10748 n: usize,
10749 max_cycles: usize,
10750) -> String {
10751 let drive = (0..wf.n_in)
10752 .map(|k| format!("in{k} = oin[i*{ni}+{k}];", ni = wf.n_in, k = k))
10753 .collect::<Vec<_>>()
10754 .join(" ");
10755 let ports = (0..wf.n_in)
10756 .map(|k| format!(".in{k}(in{k})"))
10757 .collect::<Vec<_>>()
10758 .join(", ");
10759 let ports = if ports.is_empty() {
10760 String::new()
10761 } else {
10762 format!("{ports}, ")
10763 };
10764 let decls = (0..wf.n_in)
10765 .map(|k| format!(" reg [63:0] in{k};"))
10766 .collect::<Vec<_>>()
10767 .join("\n");
10768 format!(
10769 r#"// tb_while_{name} — the while-CGRA (fabric-predicate controller) vs the RUNNING VM.
10770`timescale 1ns/1ps
10771module tb_while_{name};
10772 reg clk = 0, rst;
10773{decls}
10774 reg [63:0] oin [0:{ilast}];
10775 reg [63:0] oout [0:{olast}];
10776 wire [63:0] result;
10777 wire done;
10778 integer i, c, errors = 0;
10779 while_{name} dut(.clk(clk), .rst(rst), {ports}.result(result), .done(done));
10780 always #5 clk = ~clk;
10781 initial begin
10782 $readmemh("while_{name}_in.hex", oin);
10783 $readmemh("while_{name}_out.hex", oout);
10784 for (i = 0; i < {n}; i = i + 1) begin
10785 @(negedge clk); {drive} rst = 1;
10786 @(negedge clk); rst = 0;
10787 c = 0;
10788 while (!done && c < {maxc}) begin @(negedge clk); c = c + 1; end
10789 if (result !== oout[i]) begin errors = errors + 1; $display("FAIL case %0d: got %0d exp %0d", i, result, oout[i]); end
10790 end
10791 if (errors == 0) $display("PASS: {n} while-CGRA runs match the VM (fabric predicate = trip count)");
10792 else $display("FAIL: %0d while-CGRA mismatches", errors);
10793 $finish;
10794 end
10795endmodule
10796"#,
10797 name = name,
10798 n = n,
10799 maxc = max_cycles,
10800 ilast = (n * wf.n_in).saturating_sub(1),
10801 olast = n - 1,
10802 )
10803}
10804
10805#[derive(Clone, Copy, Debug, PartialEq)]
10817pub enum NestSrc {
10818 Param(usize),
10819 Const(i64),
10820 OuterIv,
10821}
10822
10823impl NestSrc {
10824 fn eval(&self, params: &[i64], outer_iv: i64) -> i64 {
10825 match self {
10826 NestSrc::Param(p) => params[*p],
10827 NestSrc::Const(c) => *c,
10828 NestSrc::OuterIv => outer_iv,
10829 }
10830 }
10831 fn verilog(&self) -> String {
10833 match self {
10834 NestSrc::Param(p) => format!("in{p}"),
10835 NestSrc::Const(c) => {
10836 if *c < 0 {
10837 format!("-64'sd{}", (*c as i128).unsigned_abs())
10838 } else {
10839 format!("64'sd{c}")
10840 }
10841 }
10842 NestSrc::OuterIv => "oi".to_string(),
10843 }
10844 }
10845}
10846
10847#[derive(Clone, Debug)]
10851pub struct NestedFabric {
10852 pub n_in: usize,
10853 pub n_state: usize,
10854 pub init: Vec<Src>,
10855 pub outer_lo: Src,
10856 pub outer_hi: Src,
10857 pub inner_lo: NestSrc,
10858 pub inner_hi: NestSrc,
10859 pub inner_body: Vec<LoopCell>,
10862 pub inner_next: Vec<Src>,
10863 pub invariants: Vec<Src>,
10864 pub result_state: usize,
10865}
10866
10867pub fn nested_fabric_from_source(src: &str, fn_name: &str) -> NestedFabric {
10869 use crate::vm::{Compiler, Constant, Op};
10870 use std::collections::{BTreeSet, HashMap};
10871
10872 crate::ui_bridge::with_parsed_program(src, |parsed, interner| {
10873 let (stmts, _t, _p) = parsed.expect("parse failed");
10874 let program = Compiler::compile(stmts, interner).expect("compile failed");
10875 let f = program
10876 .functions
10877 .iter()
10878 .find(|f| interner.resolve(f.name) == fn_name)
10879 .unwrap_or_else(|| panic!("function `{fn_name}` not found"));
10880
10881 setup_program_thread_locals(&program);
10886 for (p, pt) in f.param_types.iter().enumerate() {
10887 if matches!(pt, Some(crate::vm::BoundaryType::Float)) {
10888 FLOAT_REGS.with(|t| {
10889 t.borrow_mut().insert(p as u16);
10890 });
10891 }
10892 }
10893
10894 let mut ops: Vec<(usize, Op)> = Vec::new();
10895 for (i, op) in program.code.iter().enumerate().skip(f.entry_pc) {
10896 ops.push((i, *op));
10897 if matches!(op, Op::Return { .. } | Op::ReturnNothing) {
10898 break;
10899 }
10900 }
10901 let int_const = |ci: u32| -> i64 {
10902 match program.constants[ci as usize] {
10903 Constant::Int(n) => n,
10904 ref c => panic!("nested fabric: non-integer constant {c:?}"),
10905 }
10906 };
10907
10908 let mut pro: HashMap<u16, Src> = HashMap::new();
10910 for p in 0..f.param_count {
10911 pro.insert(p, Src::In(p as usize));
10912 }
10913 let getp = |pro: &HashMap<u16, Src>, r: u16| -> Src {
10914 *pro.get(&r)
10915 .unwrap_or_else(|| panic!("nested outer prologue reads R{r}"))
10916 };
10917 let (mut outer_lo, mut outer_hi, mut outer_iv_reg, mut outer_header_pc) =
10918 (None, None, None, None);
10919 let mut outer_counter: Option<u16> = None;
10920 let mut outer_latch: Option<(u16, usize)> = None;
10921 let mut idx = 0;
10922 while idx < ops.len() {
10923 let (pc, op) = ops[idx];
10924 if let Some((c, bound, exit)) = counted_header(&ops, idx) {
10925 outer_latch = Some((bound, exit));
10926 outer_lo = Some(getp(&pro, c));
10927 outer_hi = Some(getp(&pro, bound));
10928 outer_iv_reg = Some(c);
10929 outer_header_pc = Some(pc);
10930 outer_counter = Some(c);
10931 idx += 2;
10932 break;
10933 }
10934 match op {
10935 Op::LoadConst { dst, idx: ci } => match program.constants[ci as usize] {
10936 Constant::Float(fv) => {
10937 pro.insert(dst, Src::Const(fv.to_bits() as i64));
10938 FLOAT_REGS.with(|t| {
10939 t.borrow_mut().insert(dst);
10940 });
10941 }
10942 _ => {
10943 pro.insert(dst, Src::Const(int_const(ci)));
10944 }
10945 },
10946 Op::Move { dst, src } => {
10947 let s = getp(&pro, src);
10948 if FLOAT_REGS.with(|t| t.borrow().contains(&src)) {
10949 FLOAT_REGS.with(|t| {
10950 t.borrow_mut().insert(dst);
10951 });
10952 }
10953 pro.insert(dst, s);
10954 }
10955 Op::NewRange { start, end, .. } => {
10956 outer_lo = Some(getp(&pro, start));
10957 outer_hi = Some(getp(&pro, end));
10958 }
10959 Op::IterPrepare { .. } => {}
10960 Op::IterNext { dst, .. } => {
10961 outer_iv_reg = Some(dst);
10962 outer_header_pc = Some(pc);
10963 idx += 1;
10964 break;
10965 }
10966 other => panic!("nested outer prologue: `{}` unsupported", other.name()),
10967 }
10968 idx += 1;
10969 }
10970 let outer_lo = outer_lo.expect("nested fabric: no outer NewRange");
10971 let outer_hi = outer_hi.expect("nested fabric: no outer range end");
10972 let outer_iv_reg = outer_iv_reg.expect("nested fabric: no outer IterNext");
10973 let outer_header_pc = outer_header_pc.expect("nested fabric: no outer header");
10974
10975 let outer_be = ops[idx..]
10976 .iter()
10977 .position(|(_pc, o)| matches!(o, Op::Jump { target } if *target == outer_header_pc))
10978 .map(|x| x + idx)
10979 .expect("nested fabric: no outer back-edge");
10980 let result_reg = ops
10981 .iter()
10982 .find_map(|(_, o)| match o {
10983 Op::Return { src } => Some(*src),
10984 _ => None,
10985 })
10986 .expect("nested fabric: no Return");
10987
10988 let mut pn: HashMap<u16, NestSrc> = HashMap::new();
10990 for p in 0..f.param_count {
10991 pn.insert(p, NestSrc::Param(p as usize));
10992 }
10993 pn.insert(outer_iv_reg, NestSrc::OuterIv);
10994 let getn = |pn: &HashMap<u16, NestSrc>, r: u16| -> NestSrc {
10995 *pn.get(&r)
10996 .unwrap_or_else(|| panic!("nested inner prologue reads R{r}"))
10997 };
10998 let (mut inner_lo, mut inner_hi, mut inner_iv_reg, mut inner_header_pc) =
10999 (None, None, None, None);
11000 let mut inner_counter: Option<u16> = None;
11001 let mut inner_latch: Option<(u16, usize)> = None;
11002 let mut k = idx;
11003 while k < outer_be {
11004 let (pc, op) = ops[k];
11005 if let Some((c, bound, exit)) = counted_header(&ops, k) {
11006 inner_latch = Some((bound, exit));
11007 inner_lo = Some(getn(&pn, c));
11008 inner_hi = Some(getn(&pn, bound));
11009 inner_iv_reg = Some(c);
11010 inner_header_pc = Some(pc);
11011 inner_counter = Some(c);
11012 k += 2;
11013 break;
11014 }
11015 match op {
11016 Op::LoadConst { dst, idx: ci } => {
11017 pn.insert(dst, NestSrc::Const(int_const(ci)));
11018 }
11019 Op::Move { dst, src } => {
11020 let s = getn(&pn, src);
11021 pn.insert(dst, s);
11022 }
11023 Op::NewRange { start, end, .. } => {
11024 inner_lo = Some(getn(&pn, start));
11025 inner_hi = Some(getn(&pn, end));
11026 }
11027 Op::IterPrepare { .. } => {}
11028 Op::IterNext { dst, .. } => {
11029 inner_iv_reg = Some(dst);
11030 inner_header_pc = Some(pc);
11031 k += 1;
11032 break;
11033 }
11034 other => panic!(
11035 "nested fabric: the outer body must be a single inner loop; got `{}` \
11036 (deeper nesting / mixed bodies are future work)",
11037 other.name()
11038 ),
11039 }
11040 k += 1;
11041 }
11042 let inner_lo = inner_lo.expect("nested fabric: no inner NewRange (not perfectly nested)");
11043 let inner_hi = inner_hi.expect("nested fabric: no inner range end");
11044 let inner_iv_reg = inner_iv_reg.expect("nested fabric: no inner IterNext");
11045 let inner_header_pc = inner_header_pc.expect("nested fabric: no inner header");
11046
11047 let inner_be = ops[k..outer_be]
11048 .iter()
11049 .position(|(_pc, o)| matches!(o, Op::Jump { target } if *target == inner_header_pc))
11050 .map(|x| x + k)
11051 .expect("nested fabric: no inner back-edge");
11052 let mut inner_body_pcops: Vec<(usize, Op)> = ops[k..inner_be].to_vec();
11053 if let Some(c) = inner_counter {
11055 let is_one = |r: u16| matches!(pn.get(&r), Some(NestSrc::Const(1)));
11056 let (bound, exit) = inner_latch.expect("a counted inner loop has a header");
11057 match counted_latch(&inner_body_pcops, c, bound, exit, is_one) {
11058 0 => panic!(
11059 "nested fabric: a counted inner loop's body does not end in its unit step"
11060 ),
11061 n => inner_body_pcops.truncate(inner_body_pcops.len() - n),
11062 }
11063 }
11064 let mut between: Vec<(usize, Op)> = ops[inner_be + 1..outer_be].to_vec();
11067 if let (Some(c), Some((bound, exit))) = (outer_counter, outer_latch) {
11068 let outer_is_one = |r: u16| matches!(pro.get(&r), Some(Src::Const(1)));
11069 let n = counted_latch(&between, c, bound, exit, outer_is_one);
11070 between.truncate(between.len() - n);
11071 }
11072 for (_, o) in &between {
11073 if !matches!(o, Op::IterPop) {
11074 panic!(
11075 "nested fabric: the outer body has ops beyond the inner loop: `{}`",
11076 o.name()
11077 );
11078 }
11079 }
11080
11081 let reg_of = |op: &Op| -> (Option<u16>, Vec<u16>) {
11082 match op {
11083 Op::LoadConst { dst, .. } => (Some(*dst), vec![]),
11084 Op::LoadNow { dst } | Op::LoadToday { dst } => (Some(*dst), vec![]),
11086 Op::Move { dst, src } => (Some(*dst), vec![*src]),
11087 Op::Not { dst, src } => (Some(*dst), vec![*src]),
11089 Op::CallBuiltin { dst, args_start, arg_count, .. } => {
11092 (Some(*dst), (0..*arg_count as u16).map(|k| *args_start + k).collect())
11093 }
11094 Op::Add { dst, lhs, rhs }
11095 | Op::Sub { dst, lhs, rhs }
11096 | Op::Mul { dst, lhs, rhs }
11097 | Op::Div { dst, lhs, rhs }
11100 | Op::Mod { dst, lhs, rhs }
11101 | Op::FloorDiv { dst, lhs, rhs }
11102 | Op::BitAnd { dst, lhs, rhs }
11105 | Op::BitOr { dst, lhs, rhs }
11106 | Op::BitXor { dst, lhs, rhs }
11107 | Op::Shl { dst, lhs, rhs }
11108 | Op::Shr { dst, lhs, rhs }
11109 | Op::Pow { dst, lhs, rhs }
11111 | Op::Lt { dst, lhs, rhs }
11112 | Op::Gt { dst, lhs, rhs }
11113 | Op::LtEq { dst, lhs, rhs }
11114 | Op::GtEq { dst, lhs, rhs }
11115 | Op::Eq { dst, lhs, rhs }
11116 | Op::NotEq { dst, lhs, rhs } => (Some(*dst), vec![*lhs, *rhs]),
11117 Op::AddAssign { dst, src } => (Some(*dst), vec![*dst, *src]),
11118 Op::Jump { .. } | Op::JumpIfFalse { .. } => (None, vec![]),
11119 other => panic!("nested inner body: `{}` unsupported", other.name()),
11120 }
11121 };
11122 let outer_iv_regs: BTreeSet<u16> = pn
11125 .iter()
11126 .filter(|(_, s)| matches!(s, NestSrc::OuterIv))
11127 .map(|(r, _)| *r)
11128 .collect();
11129 let mut written: BTreeSet<u16> = BTreeSet::new();
11130 let mut upward: BTreeSet<u16> = BTreeSet::new();
11131 for (_, op) in &inner_body_pcops {
11132 let (dst, reads) = reg_of(op);
11133 for r in reads {
11134 if r != inner_iv_reg && !outer_iv_regs.contains(&r) && !written.contains(&r) {
11135 upward.insert(r);
11136 }
11137 }
11138 if let Some(d) = dst {
11139 written.insert(d);
11140 }
11141 }
11142 let mut carried: Vec<u16> = upward.intersection(&written).copied().collect();
11143 if written.contains(&result_reg) && !carried.contains(&result_reg) {
11144 carried.push(result_reg);
11145 }
11146 carried.sort_unstable();
11147 let invariants_regs: Vec<u16> = upward.difference(&written).copied().collect();
11148 let n_state = carried.len();
11149 let state_slot: HashMap<u16, usize> =
11150 carried.iter().enumerate().map(|(i, r)| (*r, i)).collect();
11151 let inv_slot: HashMap<u16, usize> = invariants_regs
11152 .iter()
11153 .enumerate()
11154 .map(|(i, r)| (*r, i))
11155 .collect();
11156
11157 let mut reg: HashMap<u16, Src> = HashMap::new();
11159 for (r, s) in &state_slot {
11160 reg.insert(*r, Src::In(*s));
11161 }
11162 reg.insert(inner_iv_reg, Src::In(n_state));
11163 for r in &outer_iv_regs {
11164 reg.insert(*r, Src::In(n_state + 1));
11165 }
11166 for (r, s) in &inv_slot {
11167 reg.insert(*r, Src::In(n_state + 2 + *s));
11168 }
11169 let inner_body = if_convert_body(
11170 &inner_body_pcops,
11171 &mut reg,
11172 &carried,
11173 &program.constants,
11174 Vec::new(),
11175 );
11176 let init: Vec<Src> = carried.iter().map(|r| getp(&pro, *r)).collect();
11177 let inner_next: Vec<Src> = carried.iter().map(|&r| *reg.get(&r).unwrap()).collect();
11178 let invariants: Vec<Src> = invariants_regs.iter().map(|r| getp(&pro, *r)).collect();
11179 let result_state = *state_slot
11180 .get(&result_reg)
11181 .expect("nested fabric: the returned register is not loop-carried");
11182
11183 NestedFabric {
11184 n_in: f.param_count as usize,
11185 n_state,
11186 init,
11187 outer_lo,
11188 outer_hi,
11189 inner_lo,
11190 inner_hi,
11191 inner_body,
11192 inner_next,
11193 invariants,
11194 result_state,
11195 }
11196 })
11197}
11198
11199pub fn eval_nested_fabric(nf: &NestedFabric, params: &[i64]) -> i64 {
11202 let empty: [i64; 0] = [];
11203 let mut state: Vec<i64> = nf.init.iter().map(|s| s.eval(params, &empty)).collect();
11204 let invs: Vec<i64> = nf
11205 .invariants
11206 .iter()
11207 .map(|s| s.eval(params, &empty))
11208 .collect();
11209 let olo = nf.outer_lo.eval(params, &empty);
11210 let ohi = nf.outer_hi.eval(params, &empty);
11211 let mut i = olo;
11212 while i <= ohi {
11213 let ilo = nf.inner_lo.eval(params, i);
11214 let ihi = nf.inner_hi.eval(params, i);
11215 let mut j = ilo;
11216 while j <= ihi {
11217 let mut bin: Vec<i64> = state.clone();
11218 bin.push(j);
11219 bin.push(i);
11220 bin.extend_from_slice(&invs);
11221 let mut vals: Vec<i64> = Vec::with_capacity(nf.inner_body.len());
11222 for cell in &nf.inner_body {
11223 let v = match cell {
11224 LoopCell::Arith { op, a, b } => {
11225 op.eval(a.eval(&bin, &vals), b.eval(&bin, &vals))
11226 }
11227 LoopCell::VectorArith { op, ty, a, b } => {
11228 op.eval(*ty, a.eval(&bin, &vals) as u64, b.eval(&bin, &vals) as u64) as i64
11229 }
11230 LoopCell::Func { unit, a, b } => {
11231 unit.eval(a.eval(&bin, &vals), b.eval(&bin, &vals))
11232 }
11233 LoopCell::MemRead { .. }
11234 | LoopCell::HeapRead { .. }
11235 | LoopCell::HeapWrite { .. }
11236 | LoopCell::HeapAlloc { .. } => panic!(
11237 "MemRead reached an evaluator that does not thread the iterated sequence — \
11238 a sequence loop must be evaluated via eval_loop_fabric_seq"
11239 ),
11240 LoopCell::Select { cmp, ca, cb, t, e } => {
11241 if cmp.eval(ca.eval(&bin, &vals), cb.eval(&bin, &vals)) {
11242 t.eval(&bin, &vals)
11243 } else {
11244 e.eval(&bin, &vals)
11245 }
11246 }
11247 };
11248 vals.push(v);
11249 }
11250 state = nf.inner_next.iter().map(|s| s.eval(&bin, &vals)).collect();
11251 if j == ihi {
11252 break;
11253 }
11254 j = j.wrapping_add(1);
11255 }
11256 if i == ohi {
11257 break;
11258 }
11259 i = i.wrapping_add(1);
11260 }
11261 state[nf.result_state]
11262}
11263
11264pub fn emit_nested_fabric_verilog(name: &str, nf: &NestedFabric) -> String {
11269 let ns = nf.n_state;
11270 let name_in = |k: usize| -> String {
11271 if k < ns {
11272 format!("s{k}")
11273 } else if k == ns {
11274 "ij".to_string()
11275 } else if k == ns + 1 {
11276 "oi".to_string()
11277 } else {
11278 format!("inv{}", k - ns - 2)
11279 }
11280 };
11281 let name_val = |k: usize| format!("b{k}");
11282 let circ = |s: &Src| src_named(s, &|k| format!("in{k}"), &|k| format!("v{k}"));
11283
11284 let inputs = (0..nf.n_in)
11285 .map(|k| format!("input [63:0] in{k}"))
11286 .collect::<Vec<_>>()
11287 .join(", ");
11288 let inputs = if inputs.is_empty() {
11289 String::new()
11290 } else {
11291 format!("{inputs}, ")
11292 };
11293 let mut invs = String::new();
11294 for (j, s) in nf.invariants.iter().enumerate() {
11295 invs.push_str(&format!(" wire [63:0] inv{j} = {};\n", circ(s)));
11296 }
11297 let mut bodyw = String::new();
11298 for (i, cell) in nf.inner_body.iter().enumerate() {
11299 let rhs = match cell {
11300 LoopCell::Arith { op, a, b } => {
11301 op.verilog(&src_named(a, &name_in, &name_val), &src_named(b, &name_in, &name_val))
11302 }
11303 LoopCell::VectorArith { op, ty, a, b } => {
11304 op.verilog(*ty, &src_named(a, &name_in, &name_val), &src_named(b, &name_in, &name_val))
11305 }
11306 LoopCell::Func { unit, a, b } => unit.verilog(&src_named(a, &name_in, &name_val), &src_named(b, &name_in, &name_val)),
11307 LoopCell::MemRead { .. } | LoopCell::HeapRead { .. } | LoopCell::HeapWrite { .. } | LoopCell::HeapAlloc { .. } => panic!(
11308 "MemRead RTL emission (a scratchpad-read tile driven by the induction counter) is the \
11309 P2 scratchpad follow-on; the sequence-loop LOWERING is proven via eval_loop_fabric_seq"
11310 ),
11311 LoopCell::Select { cmp, ca, cb, t, e } => format!(
11312 "($signed({}) {} $signed({})) ? {} : {}",
11313 src_named(ca, &name_in, &name_val),
11314 cmp.verilog(),
11315 src_named(cb, &name_in, &name_val),
11316 src_named(t, &name_in, &name_val),
11317 src_named(e, &name_in, &name_val),
11318 ),
11319 };
11320 bodyw.push_str(&format!(" wire [63:0] b{i} = {rhs};\n"));
11321 }
11322 let mut nexts = String::new();
11323 for (j, s) in nf.inner_next.iter().enumerate() {
11324 nexts.push_str(&format!(
11325 " wire [63:0] ns{j} = {};\n",
11326 src_named(s, &name_in, &name_val)
11327 ));
11328 }
11329 let mut reset = String::new();
11330 let mut step = String::new();
11331 for (j, s) in nf.init.iter().enumerate() {
11332 reset.push_str(&format!(" s{j} <= {};\n", circ(s)));
11333 step.push_str(&format!(" s{j} <= ns{j};\n"));
11334 }
11335 let state_decl = (0..ns)
11336 .map(|j| format!("s{j}"))
11337 .collect::<Vec<_>>()
11338 .join(", ");
11339
11340 format!(
11341 "// nested_{name} — a compiled NESTED LOOP as a HIERARCHICAL CGRA: an outer counter that\n\
11342 // runs the INNER loop (its own body datapath + counter) to completion each iteration; the\n\
11343 // inner range may depend on the outer induction variable. Futamura P1 for nested control\n\
11344 // flow; 64-bit so it wraps like the VM's Int; the inner body-step is certified == the VM by\n\
11345 // our own SAT, and the nested loop is sound by double induction.\n\
11346 module nested_{name}(input clk, input rst, {inputs}output [63:0] result, output done);\n\
11347 \x20 localparam SETUP = 2'd0, RUN = 2'd1, DONE = 2'd2;\n\
11348 \x20 reg [1:0] st;\n\
11349 \x20 reg [63:0] {state_decl};\n\
11350 \x20 reg [63:0] oi, ij;\n\
11351 \x20 wire [63:0] olo = {olo};\n\
11352 \x20 wire [63:0] ohi = {ohi};\n\
11353 \x20 wire [63:0] ilo = {ilo};\n\
11354 \x20 wire [63:0] ihi = {ihi};\n\
11355 {invs}{bodyw}{nexts}\
11356 \x20 always @(posedge clk) begin\n\
11357 \x20 if (rst) begin\n\
11358 {reset}\
11359 \x20 oi <= olo;\n\
11360 \x20 st <= ($signed(olo) <= $signed(ohi)) ? SETUP : DONE;\n\
11361 \x20 end else if (st == SETUP) begin\n\
11362 \x20 ij <= ilo;\n\
11363 \x20 if ($signed(ilo) <= $signed(ihi)) st <= RUN;\n\
11364 \x20 else if ($signed(oi) < $signed(ohi)) oi <= oi + 64'sd1;\n\
11365 \x20 else st <= DONE;\n\
11366 \x20 end else if (st == RUN) begin\n\
11367 {step}\
11368 \x20 if ($signed(ij) < $signed(ihi)) ij <= ij + 64'sd1;\n\
11369 \x20 else if ($signed(oi) < $signed(ohi)) begin oi <= oi + 64'sd1; st <= SETUP; end\n\
11370 \x20 else st <= DONE;\n\
11371 \x20 end\n\
11372 \x20 end\n\
11373 \x20 assign done = (st == DONE);\n\
11374 \x20 assign result = s{result_state};\n\
11375 endmodule\n",
11376 olo = circ(&nf.outer_lo),
11377 ohi = circ(&nf.outer_hi),
11378 ilo = nf.inner_lo.verilog(),
11379 ihi = nf.inner_hi.verilog(),
11380 result_state = nf.result_state,
11381 )
11382}
11383
11384pub fn nested_inner_next_to_bounded(
11387 nf: &NestedFabric,
11388 which: usize,
11389 width: u32,
11390) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
11391 use crate::codegen_sva::sva_to_verify::{BitVecBoundedOp, BoundedExpr};
11392 let ns = nf.n_state;
11393 let name_in = move |k: usize| -> String {
11394 if k < ns {
11395 format!("s{k}")
11396 } else if k == ns {
11397 "ij".to_string()
11398 } else if k == ns + 1 {
11399 "oi".to_string()
11400 } else {
11401 format!("inv{}", k - ns - 2)
11402 }
11403 };
11404 fn src_expr(
11405 s: &Src,
11406 vals: &[crate::codegen_sva::sva_to_verify::BoundedExpr],
11407 width: u32,
11408 name_in: &dyn Fn(usize) -> String,
11409 ) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
11410 use crate::codegen_sva::sva_to_verify::BoundedExpr;
11411 match s {
11412 Src::In(k) => BoundedExpr::BitVecVar(name_in(*k), width),
11413 Src::Val(k) => vals[*k].clone(),
11414 Src::Const(c) => BoundedExpr::BitVecConst {
11415 width,
11416 value: *c as u64,
11417 },
11418 }
11419 }
11420 let slt = |l: BoundedExpr, r: BoundedExpr| BoundedExpr::BitVecBinary {
11421 op: BitVecBoundedOp::SLt,
11422 left: Box::new(l),
11423 right: Box::new(r),
11424 };
11425 let eq = |l: BoundedExpr, r: BoundedExpr| BoundedExpr::BitVecBinary {
11426 op: BitVecBoundedOp::Eq,
11427 left: Box::new(l),
11428 right: Box::new(r),
11429 };
11430 let mut vals: Vec<BoundedExpr> = Vec::with_capacity(nf.inner_body.len());
11431 for cell in &nf.inner_body {
11432 let expr = match cell {
11433 LoopCell::Arith { op, a, b } => {
11434 let a = src_expr(a, &vals, width, &name_in);
11435 let b = src_expr(b, &vals, width, &name_in);
11436 let bop = match op {
11437 AluOp::Add => BitVecBoundedOp::Add,
11438 AluOp::Sub => BitVecBoundedOp::Sub,
11439 AluOp::Mul => BitVecBoundedOp::Mul,
11440 AluOp::And => BitVecBoundedOp::And,
11441 AluOp::Or => BitVecBoundedOp::Or,
11442 AluOp::Xor => BitVecBoundedOp::Xor,
11443 AluOp::Shl => BitVecBoundedOp::Shl,
11444 AluOp::Shr => BitVecBoundedOp::AShr,
11445 AluOp::UShr => BitVecBoundedOp::Shr,
11446 };
11447 BoundedExpr::BitVecBinary { op: bop, left: Box::new(a), right: Box::new(b) }
11448 }
11449 LoopCell::VectorArith { .. } => panic!(
11450 "a packed-SIMD (Simd) tile in a loop SAT bound is not SAT-lowered here — a Simd-bearing fabric \
11451 is proven by eval ≡ VM + iverilog (simd_tile_lock + simd_rtl_lock), like a Func tile"
11452 ),
11453 LoopCell::Func { .. } => panic!(
11454 "division in a counted-loop body is not yet SAT-lowered — the shared_divider's \
11455 own certified proof (rtl.rs::the_signed_divider_is_proven_correct_by_our_sat) covers \
11456 the arithmetic; loop-carried division SAT is future work"
11457 ),
11458 LoopCell::MemRead { .. } | LoopCell::HeapRead { .. } | LoopCell::HeapWrite { .. } | LoopCell::HeapAlloc { .. } => panic!(
11459 "MemRead in a loop-carried SAT bound is future work (the memory-loop induction proof)"
11460 ),
11461 LoopCell::Select { cmp, ca, cb, t, e } => {
11462 let ca = src_expr(ca, &vals, width, &name_in);
11463 let cb = src_expr(cb, &vals, width, &name_in);
11464 let t = src_expr(t, &vals, width, &name_in);
11465 let e = src_expr(e, &vals, width, &name_in);
11466 let (cond, tt, ee) = match cmp {
11467 CmpKind::Lt => (slt(ca, cb), t, e),
11468 CmpKind::Gt => (slt(cb, ca), t, e),
11469 CmpKind::Ge => (slt(ca, cb), e, t),
11470 CmpKind::Le => (slt(cb, ca), e, t),
11471 CmpKind::Eq => (eq(ca, cb), t, e),
11472 CmpKind::Ne => (eq(ca, cb), e, t),
11473 };
11474 BoundedExpr::BitVecIte { cond: Box::new(cond), then: Box::new(tt), else_: Box::new(ee) }
11475 }
11476 };
11477 vals.push(expr);
11478 }
11479 src_expr(&nf.inner_next[which], &vals, width, &name_in)
11480}
11481
11482pub fn emit_nested_fabric_testbench(
11484 name: &str,
11485 nf: &NestedFabric,
11486 n: usize,
11487 max_cycles: usize,
11488) -> String {
11489 let drive = (0..nf.n_in)
11490 .map(|k| format!("in{k} = oin[i*{ni}+{k}];", ni = nf.n_in, k = k))
11491 .collect::<Vec<_>>()
11492 .join(" ");
11493 let ports = (0..nf.n_in)
11494 .map(|k| format!(".in{k}(in{k})"))
11495 .collect::<Vec<_>>()
11496 .join(", ");
11497 let ports = if ports.is_empty() {
11498 String::new()
11499 } else {
11500 format!("{ports}, ")
11501 };
11502 let decls = (0..nf.n_in)
11503 .map(|k| format!(" reg [63:0] in{k};"))
11504 .collect::<Vec<_>>()
11505 .join("\n");
11506 format!(
11507 r#"// tb_nested_{name} — the hierarchical (outer+inner counter) CGRA vs the RUNNING VM.
11508`timescale 1ns/1ps
11509module tb_nested_{name};
11510 reg clk = 0, rst;
11511{decls}
11512 reg [63:0] oin [0:{ilast}];
11513 reg [63:0] oout [0:{olast}];
11514 wire [63:0] result;
11515 wire done;
11516 integer i, c, errors = 0;
11517 nested_{name} dut(.clk(clk), .rst(rst), {ports}.result(result), .done(done));
11518 always #5 clk = ~clk;
11519 initial begin
11520 $readmemh("nested_{name}_in.hex", oin);
11521 $readmemh("nested_{name}_out.hex", oout);
11522 for (i = 0; i < {n}; i = i + 1) begin
11523 @(negedge clk); {drive} rst = 1;
11524 @(negedge clk); rst = 0;
11525 c = 0;
11526 while (!done && c < {maxc}) begin @(negedge clk); c = c + 1; end
11527 if (result !== oout[i]) begin errors = errors + 1; $display("FAIL case %0d: got %0d exp %0d", i, result, oout[i]); end
11528 end
11529 if (errors == 0) $display("PASS: {n} nested-CGRA runs match the VM (outer counter streams the inner loop)");
11530 else $display("FAIL: %0d nested-CGRA mismatches", errors);
11531 $finish;
11532 end
11533endmodule
11534"#,
11535 name = name,
11536 n = n,
11537 maxc = max_cycles,
11538 ilast = (n * nf.n_in).saturating_sub(1),
11539 olast = n - 1,
11540 )
11541}
11542
11543pub fn emit_loop_fabric_testbench(
11546 name: &str,
11547 lf: &LoopFabric,
11548 n: usize,
11549 max_cycles: usize,
11550) -> String {
11551 let drive = (0..lf.n_in)
11552 .map(|k| format!("in{k} = oin[i*{ni}+{k}];", ni = lf.n_in, k = k))
11553 .collect::<Vec<_>>()
11554 .join(" ");
11555 let ports = (0..lf.n_in)
11556 .map(|k| format!(".in{k}(in{k})"))
11557 .collect::<Vec<_>>()
11558 .join(", ");
11559 let ports = if ports.is_empty() {
11560 String::new()
11561 } else {
11562 format!("{ports}, ")
11563 };
11564 let decls = (0..lf.n_in)
11565 .map(|k| format!(" reg [63:0] in{k};"))
11566 .collect::<Vec<_>>()
11567 .join("\n");
11568 format!(
11569 r#"// tb_cgra_{name} — the CGRA (clocked body datapath) vs the RUNNING VM over the loop program.
11570`timescale 1ns/1ps
11571module tb_cgra_{name};
11572 reg clk = 0, rst;
11573{decls}
11574 reg [63:0] oin [0:{ilast}];
11575 reg [63:0] oout [0:{olast}];
11576 wire [63:0] result;
11577 wire done;
11578 integer i, c, errors = 0;
11579 cgra_{name} dut(.clk(clk), .rst(rst), {ports}.result(result), .done(done));
11580 always #5 clk = ~clk;
11581 initial begin
11582 $readmemh("cgra_{name}_in.hex", oin);
11583 $readmemh("cgra_{name}_out.hex", oout);
11584 for (i = 0; i < {n}; i = i + 1) begin
11585 @(negedge clk); {drive} rst = 1;
11586 @(negedge clk); rst = 0;
11587 c = 0;
11588 while (!done && c < {maxc}) begin @(negedge clk); c = c + 1; end
11589 if (result !== oout[i]) begin errors = errors + 1; $display("FAIL case %0d: got %0d exp %0d", i, result, oout[i]); end
11590 end
11591 if (errors == 0) $display("PASS: {n} CGRA loop runs match the VM (body = circuit, loop = counter)");
11592 else $display("FAIL: %0d CGRA mismatches", errors);
11593 $finish;
11594 end
11595endmodule
11596"#,
11597 name = name,
11598 n = n,
11599 maxc = max_cycles,
11600 ilast = (n * lf.n_in).saturating_sub(1),
11601 olast = n - 1,
11602 )
11603}
11604
11605#[cfg(test)]
11606#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/fabric_rtl/tests.rs"]
11607mod tests;