From f20b2a9ef5dd7328080c7da27043f3b25317e788 Mon Sep 17 00:00:00 2001 From: Vasilev Dmitrii Date: Sat, 8 Aug 2026 10:41:00 +0700 Subject: [PATCH] fix(gen-zig): discard unused parameters and declare test/bench block bindings Two Zig-backend output-validity fixes, measured against tri-net's 68 legacy specs with zig ast-check: (1) parameters the body never reads get a '_ = param;' discard (Zig errors on unused fn parameters); (2) the first assignment to a plain identifier in a test/bench block lowers to 'const name = expr;' -- bindings parse as StmtAssign, not StmtLocal, so the emitted assignment referenced an undeclared name (the Zig twin of the Verilog testbench defect #1894). Invalid gens drop 66/68 -> 38/68; the remaining three mechanical classes (tuple destructuring LHS, un-translated [T;N] array types, unused local consts) are tracked in #1910. Unit suite 1537/1537; gen-rust output byte-identical. FROZEN_HASH resealed. Closes #1910 Co-Authored-By: Claude Fable 5 --- NOW.md | 7 +++++ bootstrap/src/compiler.rs | 57 ++++++++++++++++++++++++++++++++++-- bootstrap/stage0/FROZEN_HASH | 2 +- docs/NOW.md | 9 +++++- 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/NOW.md b/NOW.md index bd4a66913c..a42c81520f 100644 --- a/NOW.md +++ b/NOW.md @@ -2,6 +2,13 @@ Last updated: 2026-08-08 +## fix(gen-zig): discard unused params + declare test/bench bindings (Closes #1910) + +- Zig gen emitted unused fn parameters (zig errors on them) and test-block bindings without declarations (the Zig twin of #1894) -- 66/68 of tri-net's legacy spec gens failed zig ast-check +- Now: `_ = param;` discards for parameters the body never reads; the FIRST assignment to a plain identifier in a test/bench block lowers to `const name = expr;` +- 38/68 remain invalid in three mechanical classes (tuple destructuring LHS, un-translated `[T;N]` array types, unused local consts) -- tracked in #1910 for full zig-leg restoration +- Unit suite 1537/1537; gen-rust output byte-identical (Zig-only change); FROZEN_HASH resealed + ## fix(gen-verilog-sim) -- test-block reg decls + 64-bit __mul_noop (this PR, Closes #1894, Closes #1886) - StmtAssign test-block bindings ('h = f(...);') now get hoisted reg declarations (width-inferred, 64-bit fallback) -- iverilog could not bind them before; unlocks 11 tri-net ring specs diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index 3a7afe30ad..eece7e990f 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -3500,6 +3500,26 @@ impl Codegen { self.indent(); + // Zig errors on unused function parameters; a spec is free to keep one + // for interface symmetry (e.g. a lane the body ignores). Discard any + // parameter the body never reads so the generated Zig compiles. + fn param_referenced(nodes: &[Node], name: &str) -> bool { + nodes.iter().any(|n| { + (n.name == name + && !matches!( + n.kind, + NodeKind::FnDecl | NodeKind::TestBlock | NodeKind::BenchBlock + )) + || param_referenced(&n.children, name) + }) + } + for (pname, _) in &node.params { + if pname != "self" && !param_referenced(&node.children, pname) { + self.write_indent(); + self.write_line(&format!("_ = {}; // unused by the spec body", pname)); + } + } + if node.children.is_empty() { self.write_indent(); self.write_line("@compileError(\"not yet implemented\");"); @@ -3519,8 +3539,26 @@ impl Codegen { self.indent(); + // Test-block bindings (`b0 = f(...);`) parse as StmtAssign, not + // StmtLocal, so a verbatim assignment referenced an undeclared name in + // Zig (the same defect the Verilog testbench had, gHashTag/t27#1894). + // Emit the FIRST assignment to each plain identifier as a `const` + // binding; later statements go through the normal path. + let mut bound: std::collections::HashSet = std::collections::HashSet::new(); for stmt in &node.children { - self.gen_stmt(stmt); + let fresh_binding = stmt.kind == NodeKind::StmtAssign + && stmt.children.len() >= 2 + && stmt.children[0].kind == NodeKind::ExprIdentifier + && !stmt.children[0].name.is_empty() + && bound.insert(stmt.children[0].name.clone()); + if fresh_binding { + self.write_indent(); + self.write(&format!("const {} = ", stmt.children[0].name)); + self.gen_expr(&stmt.children[1]); + self.write_line(";"); + } else { + self.gen_stmt(stmt); + } } self.dedent(); @@ -3565,8 +3603,23 @@ impl Codegen { self.write_indent(); self.write_line(&format!("// bench: {}", node.name)); + // Same first-assignment-as-const lowering as gen_test_block: bench + // bindings parse as StmtAssign and would reference undeclared names. + let mut bound: std::collections::HashSet = std::collections::HashSet::new(); for stmt in &node.children { - self.gen_stmt(stmt); + let fresh_binding = stmt.kind == NodeKind::StmtAssign + && stmt.children.len() >= 2 + && stmt.children[0].kind == NodeKind::ExprIdentifier + && !stmt.children[0].name.is_empty() + && bound.insert(stmt.children[0].name.clone()); + if fresh_binding { + self.write_indent(); + self.write(&format!("const {} = ", stmt.children[0].name)); + self.gen_expr(&stmt.children[1]); + self.write_line(";"); + } else { + self.gen_stmt(stmt); + } } if node.children.is_empty() { diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index e5afb64c8f..baa44b92a5 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -2bd87d0d139e26dabe74fed28c121a5941e402d5cab4be71e652ef1fb18da0c6 +7fff8f7dd495f8b61ded4480c3a52bbf82ce2765b0db77bda9500641a5b734f6 diff --git a/docs/NOW.md b/docs/NOW.md index e890dc908d..3e11201b8b 100644 --- a/docs/NOW.md +++ b/docs/NOW.md @@ -1,7 +1,14 @@ -# NOW — CI now gates that the trainer LEARNS, not just that it is bit-exact (2026-08-08) +# NOW — fix(gen-zig): unused params + test bindings (2026-08-08) Last updated: 2026-08-08 +## fix(gen-zig): discard unused params + declare test/bench bindings (Closes #1910) + +- Zig gen emitted unused fn parameters (zig errors on them) and test-block bindings without declarations (the Zig twin of #1894) -- 66/68 of tri-net's legacy spec gens failed zig ast-check +- Now: `_ = param;` discards for parameters the body never reads; the FIRST assignment to a plain identifier in a test/bench block lowers to `const name = expr;` +- 38/68 remain invalid in three mechanical classes (tuple destructuring LHS, un-translated `[T;N]` array types, unused local consts) -- tracked in #1910 for full zig-leg restoration +- Unit suite 1537/1537; gen-rust output byte-identical (Zig-only change); FROZEN_HASH resealed + ## test(ci): gate that the generated trainer LEARNS -- XOR 4/4 + nonlinear held-out >=90%, incl. deep 3-layer (Refs #1764) - The bit-exact gates prove the generated RTL == model, but nothing CI-enforced that the algorithm actually LEARNS non-trivial tasks. The generator's self-tests already do (XOR 4/4; (2,4,1) noisy nonlinear held-out 58/60; (2,4,2) multi-output argmax 56/60; deep [2,4,3,1] 3-layer 59/60 -- all >=90%), but they only ran when the file was executed by hand