Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 55 additions & 2 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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\");");
Expand All @@ -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<String> = 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();
Expand Down Expand Up @@ -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<String> = 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() {
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2bd87d0d139e26dabe74fed28c121a5941e402d5cab4be71e652ef1fb18da0c6
7fff8f7dd495f8b61ded4480c3a52bbf82ce2765b0db77bda9500641a5b734f6
9 changes: 8 additions & 1 deletion docs/NOW.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading