diff --git a/NOW.md b/NOW.md index 2e5dca212a..e71b104fb5 100644 --- a/NOW.md +++ b/NOW.md @@ -2,6 +2,14 @@ Last updated: 2026-08-08 +## fix(gen-c): [T;N] params, test-block bindings, t27_assert macro (Refs #1919) + +- Rust-style `[T; N]` parameter types now lower to `T*` (previously emitted a bare `* name` -- no base type) +- Test-block bindings are declared on first assignment (`uint64_t b0 = ...;` -- the C twin of #1894); tuple targets bind the struct return once via GNU `__auto_type` and peel `.f0/.f1` +- Two-argument `assert(cond, "msg")` lowers to a self-contained `t27_assert` macro with the message RE-QUOTED (bare unquoted words previously hit C's one-arg assert and leaked non-ASCII into identifiers) +- tri-net gcc -fsyntax-only sweep: 64/68 -> 8/68 invalid; the eight distinct long-tails are enumerated in #1919 +- Unit suite 1537/1537; other backends untouched; FROZEN_HASH resealed + ## fix(gen-zig): array-literal text lowering, CSE hoist, undefined init, shadow-aware dead-local scan (Closes #1910) - ExprArrayLiteral carries its ELEMENT TEXT in extra_size with no children; Zig now emits `.{ e1, e2 }` / `.{ v } ** n` (paren-aware top-level comma split), which coerce to the typed array target diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index 0d9896b959..6f1cee6cea 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -9565,6 +9565,11 @@ impl CCodegen { let has_tests = ast.children.iter().any(|d| d.kind == NodeKind::TestBlock); if has_tests { self.write_line("#include "); + // t27's two-argument assert(cond, "msg") is not C's assert; lower it + // to a self-contained macro (message kept for readability, unused). + self.write_line( + "#define t27_assert(c, m) do { if (!(c)) { __builtin_trap(); } } while (0)", + ); } self.write_line(""); @@ -9950,8 +9955,55 @@ impl CCodegen { self.write_line(&format!("void {}(void) {{", fn_name)); self.indent(); + // Test-block bindings ('b0 = f(...);') parse as StmtAssign, not + // StmtLocal -- the same defect fixed for Verilog (#1894) and Zig. + // Declare the FIRST assignment to each plain identifier (uint64_t holds + // every t27 scalar); a tuple target binds the struct return once via + // GNU __auto_type (gcc and clang) and peels .f0/.f1/... + let mut bound: std::collections::HashSet = std::collections::HashSet::new(); + let mut tuple_ctr = 0u32; for stmt in &node.children { - self.gen_c_stmt(stmt); + let fresh = 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()); + let tuple = stmt.kind == NodeKind::StmtAssign + && stmt.children.len() >= 2 + && stmt.children[0].kind == NodeKind::ExprTuple + && stmt.children[0] + .children + .iter() + .all(|e| e.kind == NodeKind::ExprIdentifier && !e.name.is_empty()); + if fresh { + self.write_indent(); + self.write(&format!("uint64_t {} = ", stmt.children[0].name)); + self.gen_c_expr(&stmt.children[1]); + self.write(";"); + self.write_line(""); + self.write_indent(); + self.write(&format!("(void){};", stmt.children[0].name)); + self.write_line(""); + } else if tuple { + tuple_ctr += 1; + let tmp = format!("__t27_tup{}", tuple_ctr); + self.write_indent(); + self.write(&format!("__auto_type {} = ", tmp)); + self.gen_c_expr(&stmt.children[1]); + self.write(";"); + self.write_line(""); + for (fi, e) in stmt.children[0].children.iter().enumerate() { + bound.insert(e.name.clone()); + self.write_indent(); + self.write(&format!("__auto_type {} = {}.f{};", e.name, tmp, fi)); + self.write_line(""); + self.write_indent(); + self.write(&format!("(void){};", e.name)); + self.write_line(""); + } + } else { + self.gen_c_stmt(stmt); + } } if node.children.is_empty() { @@ -10310,6 +10362,21 @@ impl CCodegen { }; return format!("{}*", c_inner); } + // Rust-style array types: [T; N] → T* (pointer in param position). + if ty.starts_with('[') && ty.contains(';') { + if let Some(bracket_end) = ty.rfind(']') { + let inner = &ty[1..bracket_end]; + if let Some(semi) = inner.find(';') { + let elem = inner[..semi].trim(); + let c_elem = if Self::is_primitive(elem) { + Self::type_to_c(elem).to_string() + } else { + elem.to_string() + }; + return format!("{}*", c_elem); + } + } + } // Array types: [SIZE]Type → Type* (pointer in param position) if ty.starts_with('[') { if let Some(bracket_end) = ty.find(']') { @@ -10366,7 +10433,21 @@ impl CCodegen { } NodeKind::ExprCall => { let fname = &node.name; - if fname == "@compileAssert" { + if fname == "assert" && node.children.len() == 2 { + // t27 assert(cond, "msg"): C's assert macro takes ONE argument + // and the message literal must be re-quoted (the parser stores + // string contents unquoted). + self.write("t27_assert("); + self.gen_c_expr(&node.children[0]); + let msg = node.children[1] + .value + .trim_matches('"') + .replace('\\', "\\\\") + .replace('"', "\\\""); + self.write(", \""); + self.write(&msg); + self.write("\")"); + } else if fname == "@compileAssert" { self.write("_Static_assert("); if !node.children.is_empty() { self.gen_c_expr(&node.children[0]); diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index 04f9546364..8d524157af 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -ed9e8b98b73d75673c2416383daffc74aeea0c3f5c8b97156aa5825464857323 +409ab06144271d348b7ffdb0332a032c13928984f21c8fc0f838df5ecd6caa28 diff --git a/docs/NOW.md b/docs/NOW.md index 3252526b3a..c7fec87592 100644 --- a/docs/NOW.md +++ b/docs/NOW.md @@ -1,7 +1,15 @@ -# NOW — fix(gen-zig): campaign complete, 68/68 valid (2026-08-08) +# NOW — fix(gen-c): params, test bindings, assert macro (2026-08-08) Last updated: 2026-08-08 +## fix(gen-c): [T;N] params, test-block bindings, t27_assert macro (Refs #1919) + +- Rust-style `[T; N]` parameter types now lower to `T*` (previously emitted a bare `* name` -- no base type) +- Test-block bindings are declared on first assignment (`uint64_t b0 = ...;` -- the C twin of #1894); tuple targets bind the struct return once via GNU `__auto_type` and peel `.f0/.f1` +- Two-argument `assert(cond, "msg")` lowers to a self-contained `t27_assert` macro with the message RE-QUOTED (bare unquoted words previously hit C's one-arg assert and leaked non-ASCII into identifiers) +- tri-net gcc -fsyntax-only sweep: 64/68 -> 8/68 invalid; the eight distinct long-tails are enumerated in #1919 +- Unit suite 1537/1537; other backends untouched; FROZEN_HASH resealed + ## fix(gen-zig): array-literal text lowering, CSE hoist, undefined init, shadow-aware dead-local scan (Closes #1910) - ExprArrayLiteral carries its ELEMENT TEXT in extra_size with no children; Zig now emits `.{ e1, e2 }` / `.{ v } ** n` (paren-aware top-level comma split), which coerce to the typed array target