diff --git a/.trinity/seals/GF16.json b/.trinity/seals/GF16.json index 6dcaa0e63..66c14c636 100644 --- a/.trinity/seals/GF16.json +++ b/.trinity/seals/GF16.json @@ -1,12 +1,12 @@ { "conformance_hash": null, - "gen_hash_c": "sha256:55d2b44f985fbd1be8ec09860670dac3cdd063a1820f051bfbc3bb06f22c0e74", + "gen_hash_c": "sha256:54ee54e614f675e28b0f377dfac3bc7c43d4cb721b1f5f5ed19da303d4e6b546", "gen_hash_rust": "sha256:1cf53ef31359cc59054b4d9901fb855b6803d61129e35f021f6411d320817f92", "gen_hash_verilog": "sha256:b375909814c58781db08e9113bf93da4b331d45e8955f04af92309e134436b64", "gen_hash_zig": "sha256:fa7ce3a6674834a2e6c9a6c86297b05393a66eed9c4709b0252f0daffa43bf53", "module": "GF16", "ring": 26, - "sealed_at": "2026-09-08T01:03:29Z", + "sealed_at": "2026-09-08T02:02:02Z", "spec_hash": "sha256:1b204a1ef4013b32da4d2c0053a89ef417770d751fb61d89e391517e4fff9974", "spec_path": "specs/numeric/gf16.t27", "tests": { diff --git a/.trinity/seals/HSLM.json b/.trinity/seals/HSLM.json index e3143210b..46cf76e01 100644 --- a/.trinity/seals/HSLM.json +++ b/.trinity/seals/HSLM.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:700c64e5be00db51ac30b9da47466b94a31dc394f14918954a94df1ed91be8f6", + "gen_hash_c": "sha256:0273f119a74cb3b77d972925aa64e6340a2f84f6b1605b13bd9ab8cba31b3782", "gen_hash_rust": "sha256:77c0564db10be29afcc6dda0eab695afdb56acf295b08700ee82bbf58b457772", "gen_hash_verilog": "sha256:f8a956019cea253548cd3f3eee38d2405cea113b8ee5e66e3d387be41810c697", "gen_hash_zig": "sha256:51d88b507c315ec746ec177be352377540d8989ae03666f57d9db787201c57b9", "module": "HSLM", "ring": 12, - "sealed_at": "2026-09-08T01:34:16Z", + "sealed_at": "2026-09-08T02:02:02Z", "spec_hash": "sha256:8ea0766984db7775c474eab25dfb14965fa835ce0a56b33173df319fad342182", "spec_path": "specs/nn/hslm.t27" } \ No newline at end of file diff --git a/.trinity/seals/PropertyTestTemplate.json b/.trinity/seals/PropertyTestTemplate.json index e84139764..eb5ab2f36 100644 --- a/.trinity/seals/PropertyTestTemplate.json +++ b/.trinity/seals/PropertyTestTemplate.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:d530fca4a0eb3f9abeba48d030aae73ee647956eabede18d744ef1eaef0de4f9", + "gen_hash_c": "sha256:884ce0da6d12cb6805e570f1a99dbeeffada5f92ace29e969646e38bf97fa25f", "gen_hash_rust": "sha256:33aaed851aeafeb8f31da838c2e072debd64328d1c465ac7c0ec62e459632174", "gen_hash_verilog": "sha256:d0d9df245c4a74c12cdb34fc00370554fe93cc899817b67eebfb8fdc4f3c86a3", "gen_hash_zig": "sha256:8d4f14e78a932b8b762312d2c683ae7fade682f7b9e08b6cbdc12e33d7789ba8", "module": "PropertyTestTemplate", "ring": 12, - "sealed_at": "2026-08-28T21:20:51Z", + "sealed_at": "2026-09-08T02:02:02Z", "spec_hash": "sha256:2dae7f68a5651636623154005f92a6e43432b46d72b3bdd45c164df2a38b961c", "spec_path": "specs/math/property_test_template.t27" -} +} \ No newline at end of file diff --git a/.trinity/seals/cli_gen_commands.json b/.trinity/seals/cli_gen_commands.json index 65edd6e3e..096d03ce1 100644 --- a/.trinity/seals/cli_gen_commands.json +++ b/.trinity/seals/cli_gen_commands.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:dd4db6dff0df04da9fbb36636078a2fc5083b33f34c6e1f1a7ec14fb574b8c41", + "gen_hash_c": "sha256:5f95b4c07011f9ca9ea2707084160966f10f52a3d645f83cc3cffb36f3245165", "gen_hash_rust": "sha256:4d096f082adf390017c25b642874563896b645747042e098301d213212539f58", "gen_hash_verilog": "sha256:9438160852d64b29290d043b8901105bae4848dd9275bd4e34f558e06d70c985", "gen_hash_zig": "sha256:7b6328077fae13ac3d8cdd17bc883c31b8e9ba99cd56daf86957f330338db7f2", "module": "gen_commands", "ring": 12, - "sealed_at": "2026-09-07T21:07:03Z", + "sealed_at": "2026-09-08T02:02:02Z", "sealed_by": "t27c-bootstrap@0.2.0", "spec_hash": "sha256:4dd1bf05316f1a0ed952457e8fbbe7e03c51d51b2891c1dfe603db0425b4f2a3", "spec_path": "compiler/cli/gen.t27" diff --git a/.trinity/seals/gen_commands.json b/.trinity/seals/gen_commands.json index 93db02687..1a06e3a73 100644 --- a/.trinity/seals/gen_commands.json +++ b/.trinity/seals/gen_commands.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:dd4db6dff0df04da9fbb36636078a2fc5083b33f34c6e1f1a7ec14fb574b8c41", + "gen_hash_c": "sha256:5f95b4c07011f9ca9ea2707084160966f10f52a3d645f83cc3cffb36f3245165", "gen_hash_rust": "sha256:4d096f082adf390017c25b642874563896b645747042e098301d213212539f58", "gen_hash_verilog": "sha256:9438160852d64b29290d043b8901105bae4848dd9275bd4e34f558e06d70c985", "gen_hash_zig": "sha256:7b6328077fae13ac3d8cdd17bc883c31b8e9ba99cd56daf86957f330338db7f2", "module": "gen_commands", "ring": 12, - "sealed_at": "2026-08-28T20:32:32Z", + "sealed_at": "2026-09-08T02:02:02Z", "spec_hash": "sha256:4dd1bf05316f1a0ed952457e8fbbe7e03c51d51b2891c1dfe603db0425b4f2a3", "spec_path": "compiler/cli/gen.t27" -} +} \ No newline at end of file diff --git a/.trinity/seals/math_PropertyTestTemplate.json b/.trinity/seals/math_PropertyTestTemplate.json index cd9d3d82c..2b785e6f8 100644 --- a/.trinity/seals/math_PropertyTestTemplate.json +++ b/.trinity/seals/math_PropertyTestTemplate.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:d530fca4a0eb3f9abeba48d030aae73ee647956eabede18d744ef1eaef0de4f9", + "gen_hash_c": "sha256:884ce0da6d12cb6805e570f1a99dbeeffada5f92ace29e969646e38bf97fa25f", "gen_hash_rust": "sha256:33aaed851aeafeb8f31da838c2e072debd64328d1c465ac7c0ec62e459632174", "gen_hash_verilog": "sha256:d0d9df245c4a74c12cdb34fc00370554fe93cc899817b67eebfb8fdc4f3c86a3", "gen_hash_zig": "sha256:8d4f14e78a932b8b762312d2c683ae7fade682f7b9e08b6cbdc12e33d7789ba8", "module": "PropertyTestTemplate", "ring": 12, - "sealed_at": "2026-09-05T10:40:48Z", + "sealed_at": "2026-09-08T02:02:02Z", "sealed_by": "t27c-bootstrap@0.2.0", "spec_hash": "sha256:2dae7f68a5651636623154005f92a6e43432b46d72b3bdd45c164df2a38b961c", "spec_path": "specs/math/property_test_template.t27" diff --git a/.trinity/seals/nn_HSLM.json b/.trinity/seals/nn_HSLM.json index 5aca051d2..234a11bbf 100644 --- a/.trinity/seals/nn_HSLM.json +++ b/.trinity/seals/nn_HSLM.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:700c64e5be00db51ac30b9da47466b94a31dc394f14918954a94df1ed91be8f6", + "gen_hash_c": "sha256:0273f119a74cb3b77d972925aa64e6340a2f84f6b1605b13bd9ab8cba31b3782", "gen_hash_rust": "sha256:77c0564db10be29afcc6dda0eab695afdb56acf295b08700ee82bbf58b457772", "gen_hash_verilog": "sha256:f8a956019cea253548cd3f3eee38d2405cea113b8ee5e66e3d387be41810c697", "gen_hash_zig": "sha256:51d88b507c315ec746ec177be352377540d8989ae03666f57d9db787201c57b9", "module": "HSLM", "ring": 12, - "sealed_at": "2026-09-08T01:34:16Z", + "sealed_at": "2026-09-08T02:02:02Z", "sealed_by": "t27c-bootstrap@0.2.0", "spec_hash": "sha256:8ea0766984db7775c474eab25dfb14965fa835ce0a56b33173df319fad342182", "spec_path": "specs/nn/hslm.t27" diff --git a/.trinity/seals/numeric_triformat-gf16.json b/.trinity/seals/numeric_triformat-gf16.json index e71b7be43..a3b9b1905 100644 --- a/.trinity/seals/numeric_triformat-gf16.json +++ b/.trinity/seals/numeric_triformat-gf16.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:55d2b44f985fbd1be8ec09860670dac3cdd063a1820f051bfbc3bb06f22c0e74", + "gen_hash_c": "sha256:54ee54e614f675e28b0f377dfac3bc7c43d4cb721b1f5f5ed19da303d4e6b546", "gen_hash_rust": "sha256:1cf53ef31359cc59054b4d9901fb855b6803d61129e35f021f6411d320817f92", "gen_hash_verilog": "sha256:b375909814c58781db08e9113bf93da4b331d45e8955f04af92309e134436b64", "gen_hash_zig": "sha256:fa7ce3a6674834a2e6c9a6c86297b05393a66eed9c4709b0252f0daffa43bf53", "module": "triformat-gf16", "ring": 12, - "sealed_at": "2026-09-08T01:03:29Z", + "sealed_at": "2026-09-08T02:02:02Z", "sealed_by": "t27c-bootstrap@0.2.0", "spec_hash": "sha256:1b204a1ef4013b32da4d2c0053a89ef417770d751fb61d89e391517e4fff9974", "spec_path": "specs/numeric/gf16.t27" diff --git a/.trinity/seals/property_test_template.json b/.trinity/seals/property_test_template.json index cb9c107af..484670011 100644 --- a/.trinity/seals/property_test_template.json +++ b/.trinity/seals/property_test_template.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:d530fca4a0eb3f9abeba48d030aae73ee647956eabede18d744ef1eaef0de4f9", + "gen_hash_c": "sha256:884ce0da6d12cb6805e570f1a99dbeeffada5f92ace29e969646e38bf97fa25f", "gen_hash_rust": "sha256:33aaed851aeafeb8f31da838c2e072debd64328d1c465ac7c0ec62e459632174", "gen_hash_verilog": "sha256:d0d9df245c4a74c12cdb34fc00370554fe93cc899817b67eebfb8fdc4f3c86a3", "gen_hash_zig": "sha256:8d4f14e78a932b8b762312d2c683ae7fade682f7b9e08b6cbdc12e33d7789ba8", "module": "property_test_template", "ring": 12, - "sealed_at": "2026-08-28T21:20:51Z", + "sealed_at": "2026-09-08T02:02:02Z", "spec_hash": "sha256:2dae7f68a5651636623154005f92a6e43432b46d72b3bdd45c164df2a38b961c", "spec_path": "specs/math/property_test_template.t27" -} +} \ No newline at end of file diff --git a/.trinity/seals/triformat-gf16.json b/.trinity/seals/triformat-gf16.json index 731a1f580..b85276405 100644 --- a/.trinity/seals/triformat-gf16.json +++ b/.trinity/seals/triformat-gf16.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:55d2b44f985fbd1be8ec09860670dac3cdd063a1820f051bfbc3bb06f22c0e74", + "gen_hash_c": "sha256:54ee54e614f675e28b0f377dfac3bc7c43d4cb721b1f5f5ed19da303d4e6b546", "gen_hash_rust": "sha256:1cf53ef31359cc59054b4d9901fb855b6803d61129e35f021f6411d320817f92", "gen_hash_verilog": "sha256:b375909814c58781db08e9113bf93da4b331d45e8955f04af92309e134436b64", "gen_hash_zig": "sha256:fa7ce3a6674834a2e6c9a6c86297b05393a66eed9c4709b0252f0daffa43bf53", "module": "triformat-gf16", "ring": 12, - "sealed_at": "2026-09-08T01:03:29Z", + "sealed_at": "2026-09-08T02:02:02Z", "spec_hash": "sha256:1b204a1ef4013b32da4d2c0053a89ef417770d751fb61d89e391517e4fff9974", "spec_path": "specs/numeric/gf16.t27" } \ No newline at end of file diff --git a/.trinity/seals/verilog_codegen.json b/.trinity/seals/verilog_codegen.json index ab23bd5c6..0957608df 100644 --- a/.trinity/seals/verilog_codegen.json +++ b/.trinity/seals/verilog_codegen.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:bf1aa7bf56c4135885e1c34df0496f040b85354d3203c0c6fa037092530851e2", + "gen_hash_c": "sha256:9bdf9414115dc5e8fd0cb611c2ca21db13acbf41170e6f61e5f34381afa428cd", "gen_hash_rust": "sha256:493dfdbbe8fb6c20833938752f1d38e6269276295091fa4cdfc1081b179690e5", "gen_hash_verilog": "sha256:6f8f90892c3ec1ff69ee038babea3258d40eb05f102c10ef280a70e37aa29199", "gen_hash_zig": "sha256:3ca02254ff52c1f2af6b72737baa0668ac3b0b9ba09e423b3ede762d77006153", "module": "verilog_codegen", "ring": 12, - "sealed_at": "2026-09-07T22:05:57Z", + "sealed_at": "2026-09-08T02:02:02Z", "spec_hash": "sha256:7454245beb5d71c1fff9e450cc1f8a5883c47243aaaac38bac1c7a894287e8db", "spec_path": "compiler/codegen/verilog/codegen.t27" } \ No newline at end of file diff --git a/.trinity/seals/verilog_verilog_codegen.json b/.trinity/seals/verilog_verilog_codegen.json index 5356551ff..50e5ad658 100644 --- a/.trinity/seals/verilog_verilog_codegen.json +++ b/.trinity/seals/verilog_verilog_codegen.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:bf1aa7bf56c4135885e1c34df0496f040b85354d3203c0c6fa037092530851e2", + "gen_hash_c": "sha256:9bdf9414115dc5e8fd0cb611c2ca21db13acbf41170e6f61e5f34381afa428cd", "gen_hash_rust": "sha256:493dfdbbe8fb6c20833938752f1d38e6269276295091fa4cdfc1081b179690e5", "gen_hash_verilog": "sha256:6f8f90892c3ec1ff69ee038babea3258d40eb05f102c10ef280a70e37aa29199", "gen_hash_zig": "sha256:3ca02254ff52c1f2af6b72737baa0668ac3b0b9ba09e423b3ede762d77006153", "module": "verilog_codegen", "ring": 12, - "sealed_at": "2026-09-07T22:05:57Z", + "sealed_at": "2026-09-08T02:02:02Z", "sealed_by": "t27c-bootstrap@0.2.0", "spec_hash": "sha256:7454245beb5d71c1fff9e450cc1f8a5883c47243aaaac38bac1c7a894287e8db", "spec_path": "compiler/codegen/verilog/codegen.t27" diff --git a/.trinity/seals/zig_codegen.json b/.trinity/seals/zig_codegen.json index 0107d1a40..d68921e1b 100644 --- a/.trinity/seals/zig_codegen.json +++ b/.trinity/seals/zig_codegen.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:be1cb9a8eb5bb8c80f63aa8db98b7ed69273a0c4e3e09a62d0fc7efec4bd11b5", + "gen_hash_c": "sha256:b0c5a061c88d43df6dca22b0fd0033b58680ebeed138925f7c7158e0f393a341", "gen_hash_rust": "sha256:2d422d58b346eb22364a64839b0c48fdef43c9f1a57abd9931e324fe06efdfc2", "gen_hash_verilog": "sha256:281e5bf4b4bf860712ca54c2b0d173ba6ee0fc6f96043e8c243e9f3e1291a8f1", "gen_hash_zig": "sha256:e0de552c5b497d45d640bb9ac9664b1ba515a9d44a2bb280921ffdab5b9fd0f6", "module": "zig_codegen", "ring": 12, - "sealed_at": "2026-08-28T20:32:32Z", + "sealed_at": "2026-09-08T02:02:02Z", "spec_hash": "sha256:face91b2c4dda278c97006286cdbd18e00e2a2d01e9288eeb0d6520aa47f7fce", "spec_path": "compiler/codegen/zig/codegen.t27" -} +} \ No newline at end of file diff --git a/.trinity/seals/zig_zig_codegen.json b/.trinity/seals/zig_zig_codegen.json index bf6ca529b..c17ee15e2 100644 --- a/.trinity/seals/zig_zig_codegen.json +++ b/.trinity/seals/zig_zig_codegen.json @@ -1,11 +1,11 @@ { - "gen_hash_c": "sha256:be1cb9a8eb5bb8c80f63aa8db98b7ed69273a0c4e3e09a62d0fc7efec4bd11b5", + "gen_hash_c": "sha256:b0c5a061c88d43df6dca22b0fd0033b58680ebeed138925f7c7158e0f393a341", "gen_hash_rust": "sha256:2d422d58b346eb22364a64839b0c48fdef43c9f1a57abd9931e324fe06efdfc2", "gen_hash_verilog": "sha256:281e5bf4b4bf860712ca54c2b0d173ba6ee0fc6f96043e8c243e9f3e1291a8f1", "gen_hash_zig": "sha256:e0de552c5b497d45d640bb9ac9664b1ba515a9d44a2bb280921ffdab5b9fd0f6", "module": "zig_codegen", "ring": 12, - "sealed_at": "2026-09-06T07:17:01Z", + "sealed_at": "2026-09-08T02:02:02Z", "sealed_by": "t27c-bootstrap@0.2.0", "spec_hash": "sha256:face91b2c4dda278c97006286cdbd18e00e2a2d01e9288eeb0d6520aa47f7fce", "spec_path": "compiler/codegen/zig/codegen.t27" diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index 551061d39..834dc3f4e 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -19810,12 +19810,51 @@ impl CCodegen { if let Some(bracket_end) = raw_type.find(']') { let size = &raw_type[1..bracket_end]; let elem = &raw_type[bracket_end + 1..]; - let c_elem = if Self::is_primitive(elem) { - Self::type_to_c(elem).to_string() - } else { - elem.to_string() + // W583, again and in the other position. That note is + // 200 lines below, on `param_type_to_c`: the gate used + // to be `is_primitive`, "which lists only the integer + // scalars -- so `f32`, `f64`, `str`, `string` and + // `gf16` took the pass-through arm and reached C + // unmapped even after `type_to_c` learned them." The + // repair did not travel here, so a LOCAL array kept the + // pass-through and C received + // + // GF16 x[4]; error: use of undeclared identifier 'GF16' + // + // while the same element is `uint16_t` in both a + // parameter (`uint16_t a[static 4]`) and a struct field + // (`uint16_t f[4];`). `type_to_c` passes a genuinely + // custom type through unchanged, so the gate only ever + // suppressed correct mappings -- the same sentence that + // retired it downstairs. + // `[]const u8` carries the qualifier INSIDE the + // element, so the element text is the literal + // "const u8" and C received `const u8* x` -- + // "unknown type name 'u8'". `param_type_to_c` strips + // it for a slice parameter; the same strip is needed + // here. + let elem = elem.trim(); + let (qual, elem) = match elem.strip_prefix("const ") { + Some(rest) => ("const ", rest.trim()), + None => ("", elem), }; - self.write(&format!("{} {}[{}]", c_elem, node.name, size)); + let c_elem = format!("{}{}", qual, Self::type_to_c(elem)); + if size.is_empty() && node.children.is_empty() { + // A slice `[]T` has no compile-time length, and + // `uint8_t x[];` is not a definition -- clang says + // "definition of variable with array type needs an + // explicit size or an initializer". + // + // ONLY when there is no initialiser. `T x[] = {…}` + // is legal C and takes its size from the list, and + // rewriting that to `T* x = {…}` made the one + // corpus file carrying the shape WORSE -- measured, + // +5 errors and nothing better, which is how this + // condition got here. + self.write(&format!("{}* {}", c_elem, node.name)); + } else { + self.write(&format!("{} {}[{}]", c_elem, node.name, size)); + } } else { self.write(&format!("int {}", node.name)); } diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index 5eb10ffc1..512a5ec1c 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -7ca9bc868124840670ac04c1173056ef70c5a6f4850fae5c533039c31534e974 bootstrap/src/compiler.rs +911a664330ef073a4a89924d1d8c2838f400ce978c735258c3a1dfbb91b1fede bootstrap/src/compiler.rs diff --git a/bootstrap/tests/c_local_array_types.rs b/bootstrap/tests/c_local_array_types.rs new file mode 100644 index 000000000..3590684cb --- /dev/null +++ b/bootstrap/tests/c_local_array_types.rs @@ -0,0 +1,142 @@ +//! The element type of a LOCAL array must be lowered, not passed through. +//! +//! `var x : [4]GF16;` reached C as `GF16 x[4];` -- `use of undeclared +//! identifier 'GF16'` -- while the SAME element is `uint16_t` in a parameter +//! (`uint16_t a[static 4]`) and in a struct field (`uint16_t f[4];`). +//! +//! The gate was `is_primitive`, and this is W583 a second time. That note sits +//! on `param_type_to_c` two hundred lines away and says it exactly: the gate +//! "lists only the integer scalars -- so `f32`, `f64`, `str`, `string` and +//! `gf16` took the pass-through arm and reached C unmapped even after +//! `type_to_c` learned them". The repair never travelled to the local path. +//! +//! Every assertion here hands the header to `cc`, because the text `GF16 x[4]` +//! and `uint16_t x[4]` are equally plausible-looking and only a compiler +//! separates them. + +use std::process::Command; +use std::sync::atomic::{AtomicUsize, Ordering}; + +static N: AtomicUsize = AtomicUsize::new(0); + +fn cc_present() -> bool { + Command::new("cc").arg("--version").output().map(|o| o.status.success()).unwrap_or(false) +} + +fn local_decl(ty: &str, init: Option<&str>) -> (String, String) { + let d = std::env::temp_dir().join(format!( + "t27c-clocal-{}-{}", + std::process::id(), + N.fetch_add(1, Ordering::Relaxed) + )); + let _ = std::fs::remove_dir_all(&d); + std::fs::create_dir_all(&d).expect("create temp dir"); + let body = match init { + Some(i) => format!("var x : {ty} = {i};"), + None => format!("var x : {ty};"), + }; + let p = d.join("in.t27"); + std::fs::write( + &p, + format!("module P {{\n struct Pair {{ a : i32, b : i32, }}\n fn probe(v: i32) -> i32 {{ {body} return 0; }}\n}}\n"), + ) + .expect("write spec"); + let out = Command::new(env!("CARGO_BIN_EXE_t27c")) + .arg("gen-c") + .arg(&p) + .output() + .expect("run t27c"); + assert!(out.status.success(), "gen-c failed: {}", String::from_utf8_lossy(&out.stderr)); + let h = String::from_utf8_lossy(&out.stdout).to_string(); + let line = h + .lines() + .find(|l| l.trim_start().starts_with(|c: char| c.is_alphabetic()) && l.contains(" x") && l.trim_end().ends_with(';')) + .unwrap_or_else(|| panic!("no local declaration for `{ty}` in:\n{h}")) + .trim() + .to_string(); + (line, h) +} + +fn errors(header: &str, tag: &str) -> String { + let d = std::env::temp_dir().join(format!("t27c-clocal-cc-{tag}-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&d); + std::fs::create_dir_all(&d).expect("dir"); + let p = d.join("h.h"); + std::fs::write(&p, header).expect("write"); + let out = Command::new("cc") + .args(["-std=c11", "-Wall", "-Wextra", "-Wno-unused-parameter", "-fsyntax-only", "-x", "c"]) + .arg(&p) + .output() + .expect("cc"); + String::from_utf8_lossy(&out.stderr).to_string() +} + +#[test] +fn a_gf16_array_local_uses_the_c_element_type() { + let (line, h) = local_decl("[4]GF16", None); + assert_eq!(line, "uint16_t x[4];", "got `{line}`"); + if cc_present() { + let e = errors(&h, "gf16"); + assert!(!e.contains("error"), "and it must compile:\n{e}"); + } +} + +#[test] +fn a_float_array_local_uses_the_c_element_type() { + // `f32`/`f64` were the types the W583 note named first, and they were still + // passing through here. + for (t, want) in [("[4]f64", "double x[4];"), ("[4]f32", "float x[4];")] { + let (line, h) = local_decl(t, None); + assert_eq!(line, want, "for `{t}` got `{line}`"); + if cc_present() { + assert!(!errors(&h, "float").contains("error"), "`{t}` must compile"); + } + } +} + +#[test] +fn a_declared_struct_element_still_passes_through() { + // The negative half: `type_to_c` passes a genuinely custom type through + // unchanged, and it must -- `Pair` IS the C spelling, via the typedef. + // A mapping that rewrote everything would break this. + let (line, h) = local_decl("[4]Pair", None); + assert_eq!(line, "Pair x[4];", "got `{line}`"); + if cc_present() { + assert!(!errors(&h, "pair").contains("error"), "and it must compile"); + } +} + +#[test] +fn a_slice_local_without_an_initialiser_is_a_pointer() { + // `uint8_t x[];` is not a definition: "definition of variable with array + // type needs an explicit size or an initializer". + let (line, h) = local_decl("[]u8", None); + assert_eq!(line, "uint8_t* x;", "got `{line}`"); + if cc_present() { + assert!(!errors(&h, "slice").contains("error"), "and it must compile"); + } +} + +#[test] +fn a_slice_local_WITH_an_initialiser_keeps_the_array_form() { + // `T x[] = { ... }` is legal C and takes its size from the list. Rewriting + // THAT to a pointer made the one corpus file carrying the shape worse -- + // +5 errors, nothing better. This test is that measurement, kept. + let (line, _h) = local_decl("[]i32", Some("[1, 2, 3]")); + assert!( + line.starts_with("int32_t x[]"), + "an initialised slice local keeps the sized-by-initialiser array form; got `{line}`" + ); +} + +#[test] +fn a_const_qualified_slice_element_is_lowered() { + // `[]const u8` carries the qualifier inside the element, so the element + // text is the literal "const u8" and C received `const u8* x` -- + // "unknown type name 'u8'". + let (line, h) = local_decl("[]const u8", None); + assert_eq!(line, "const uint8_t* x;", "got `{line}`"); + if cc_present() { + assert!(!errors(&h, "constslice").contains("error"), "and it must compile"); + } +} diff --git a/docs/now/2026-09-08-the-limit-was-censoring-every-total.md b/docs/now/2026-09-08-the-limit-was-censoring-every-total.md new file mode 100644 index 000000000..a4abc93d0 --- /dev/null +++ b/docs/now/2026-09-08-the-limit-was-censoring-every-total.md @@ -0,0 +1,9 @@ +# NOW -- The limit was censoring every total (2026-09-08) + +## The limit was censoring every total (Closes #3448) + +- **Every error count I published this session was a floor.** Clang stops at `-ferror-limit=20` per file by default, and **141 files reach it**. The corpus emits **15133** errors, not the 3849 I have been reporting. The per-file splits were sound because they were counted per file; the totals were not. Re-measured without the limit, the previous pass's change moved **15188 → 15133 (−55)**, not the −24 published. The number was never wrong about direction and was wrong about size, and I only found it because a fix that removed real errors showed a **zero** delta -- three files that each sat exactly at 20 before and after. +- The fix itself is W583 in a position its own repair never reached. `var x : [4]GF16;` emitted `GF16 x[4];` -- `use of undeclared identifier` -- while the same element is `uint16_t` in a parameter and in a struct field. The local path gated on `is_primitive`, and the note retiring that gate sits **two hundred lines below in the same file**, naming `f32`, `f64`, `str`, `string` and `gf16` as the types it swallowed. +- Two more in the same eight lines: `var x : []u8;` emitted `uint8_t x[];`, which is not a definition, and `var x : []const u8;` emitted `const u8* x` because the qualifier lives inside the element text. Measured: **15133 → 15126**, three files better, **zero worse**. +- **The first version of the slice repair was wrong and the corpus said so.** Rewriting every `[]T` local to a pointer broke `T x[] = {…}`, which is legal C and takes its size from the initialiser list: +5 errors and nothing better, in the one file carrying the shape. The condition `size.is_empty() && node.children.is_empty()` is that measurement, kept as code. +- The table's fourth position measured the wrong thing first, too. `var x : T;` with no initialiser: C and Rust declare it, Zig and Verilog emit **nothing** -- so the probe was reading their dead-code removal rather than their lowering. Initialising from a parameter of the same type fixed it, and the self-check now covers all four positions. diff --git a/tools/backend_parity_table.py b/tools/backend_parity_table.py index eff6796a5..1293a8506 100755 --- a/tools/backend_parity_table.py +++ b/tools/backend_parity_table.py @@ -44,7 +44,7 @@ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -POSITIONS = ("param", "return", "field") +POSITIONS = ("param", "return", "field", "local") # One row per declaration form. Kept deliberately small and hand-chosen: the # point is a page a person reads, not exhaustive coverage of the type surface. @@ -94,6 +94,13 @@ def t27c() -> str: "return": " fn probe(v: i32) -> {t} {{ return 0; }}\n", "field": (" struct Holder {{ f : {t}, g : i32, }}\n" " fn probe(h: Holder) -> i32 {{ return h.g; }}\n"), + # The local is initialised FROM A PARAMETER of the same type, which keeps + # one probe for every form -- a per-type literal would be a second thing + # that can be wrong. It also has to be initialised at all: `var x : T;` + # parses, and C and Rust declare it, but Zig and Verilog emit NOTHING for + # an uninitialised local, so that probe measured their dead-code removal + # rather than their lowering. + "local": " fn probe(p: {t}) -> i32 {{ var x : {t} = p; return 0; }}\n", } # One pattern per (position, backend). Three positions, because the same @@ -110,6 +117,14 @@ def t27c() -> str: "Zig": r"^(?:pub )?fn probe\([^)]*\)\s*([^{\n]+)", "Verilog": r"function\s+([^;\n]*?)\s*\bprobe;", }, + "local": { + # The declarator only, up to `=` -- the initialiser is the same in + # every row and would just push the type out of the column. + "C": r"^\s{4}([^\n;=]*\bx(?:\[[^\]]*\])*)\s*(?:=[^\n;]*)?;", + "Rust": r"^\s{4}(let (?:mut )?x:[^=;\n]*)", + "Zig": r"^\s{4}((?:var|const) x:[^=;\n]*)", + "Verilog": r"^\s*(reg[^\n;]*\bx\b[^\n;]*);", + }, "field": { "C": r"struct Holder \{[^}]*?\n\s*([^\n;]*\bf(?:\[[^\]]*\])*)\s*;", "Rust": r"pub struct Holder \{[^}]*?\n\s*(pub f:[^,\n]*)",