From 87fd30f80018c80bc59a5f495eab1db4f455343c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 20:42:02 +0700 Subject: [PATCH] t27b: a bench's `_ = e;` is a discard, and ternary_gates' benches type-check once referenced (Closes #8546) c7570433e (see #8195) changed the bench bodies of two specs. After that, t27b refused both with `type mismatch`. - arty_a7_integration.t27: bench_integration_config discards a u32, a u32, then a bool at its top level. discard_plan.t27 sent every top-level `_ = e;` in a bench to t27b's test-binding path. That path bound `_` as a variable of the first value's type, so it refused the bool. t27c's gen_bench_block prints each one as Zig's `_ = e;`, and Zig compiles the bench once a test calls it (checked on the lab). The plan's `plan` now takes `in_bench`, and a bench's discards are EVALUATE. Two new tests cover this; the glue changes one line. - ternary_gates.t27: t27b was right to refuse it. Once a test calls its benches, zig 0.16 refuses them three ways: `(result + 1) % 3` is a signed `%`, an i32 into a u32, and `vals[idx]` indexes a tuple at run time. The reference passed only because nothing calls a bench (see #7908 for the same case in a fn). The benches now use a `[3]i32` and `@intCast(result + 1)`. With a test that calls all four benches, the generated Zig passes 13/13. Ledger: both rows are back to pass, and max_not_pass goes from 27 to 25. Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/TernaryGates.json | 8 +-- .trinity/seals/isa_TernaryGates.json | 10 ++-- .trinity/seals/t27b_T27bDiscardPlan.json | 18 +++---- cli/t27b/src/lower.rs | 2 +- ...ench-discards-and-ternary-gates-benches.md | 7 +++ docs/reports/t27b_expectations.json | 6 +-- gen/rust/tri/t27b/discard_plan.rs | 6 +-- specs/isa/ternary_gates.t27 | 14 +++--- specs/tri/t27b/discard_plan.t27 | 50 +++++++++++++------ 9 files changed, 74 insertions(+), 47 deletions(-) create mode 100644 docs/now/2026-10-10-t27b-bench-discards-and-ternary-gates-benches.md diff --git a/.trinity/seals/TernaryGates.json b/.trinity/seals/TernaryGates.json index 53c231abdf..859993c184 100644 --- a/.trinity/seals/TernaryGates.json +++ b/.trinity/seals/TernaryGates.json @@ -1,12 +1,12 @@ { - "gen_hash_c": "sha256:608e9f52b077faf20ba1e274f02a386bb48f27767c878ec5683567bd5b6a755d", + "gen_hash_c": "sha256:87ac584ac4d46a1011b6ebcdc8bda48bd7b578e7f34a6f8df1942c0b12a5a247", "gen_hash_rust": "sha256:d50d3454f51312df8ae97f8885d1ef89eccf26835aab641ec4a7d131a94556a4", "gen_hash_verilog": "sha256:a0ecc024bbf108bf40c663321efaadc401dc9e22f8d45323eae4913dc08738bc", - "gen_hash_zig": "sha256:50a48d04ba20fe714abc08eeeba6821801b1db40bce026966670030291dc4819", + "gen_hash_zig": "sha256:d9602a0e9c3228201cb27c2a83f47a149871059fbfb3c7fa1b73ec47eda8cbcf", "module": "TernaryGates", "ring": 12, - "sealed_at": "2026-10-10T12:32:59Z", - "spec_hash": "sha256:48513ed8f9c995547a1b374a3c09c7ac428c9a11b40834a78e75028b956903ca", + "sealed_at": "2026-10-10T13:36:51Z", + "spec_hash": "sha256:94fc7637fe4d830a624d700ad231075159fdaed1a79b0d605a5bdacb38083b0c", "spec_path": "specs/isa/ternary_gates.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/isa_TernaryGates.json b/.trinity/seals/isa_TernaryGates.json index a6a754cd6b..e60635840b 100644 --- a/.trinity/seals/isa_TernaryGates.json +++ b/.trinity/seals/isa_TernaryGates.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+c494cc5", + "built_by": "t27c-bootstrap@0.5.2+3c0ae4d49", "closure": [ { "spec": "specs/base/types.t27", @@ -7,16 +7,16 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:608e9f52b077faf20ba1e274f02a386bb48f27767c878ec5683567bd5b6a755d", + "gen_hash_c": "sha256:87ac584ac4d46a1011b6ebcdc8bda48bd7b578e7f34a6f8df1942c0b12a5a247", "gen_hash_rust": "sha256:d50d3454f51312df8ae97f8885d1ef89eccf26835aab641ec4a7d131a94556a4", "gen_hash_verilog": "sha256:a0ecc024bbf108bf40c663321efaadc401dc9e22f8d45323eae4913dc08738bc", - "gen_hash_zig": "sha256:50a48d04ba20fe714abc08eeeba6821801b1db40bce026966670030291dc4819", + "gen_hash_zig": "sha256:d9602a0e9c3228201cb27c2a83f47a149871059fbfb3c7fa1b73ec47eda8cbcf", "module": "TernaryGates", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-10T12:32:59Z", + "sealed_at": "2026-10-10T13:36:51Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:48513ed8f9c995547a1b374a3c09c7ac428c9a11b40834a78e75028b956903ca", + "spec_hash": "sha256:94fc7637fe4d830a624d700ad231075159fdaed1a79b0d605a5bdacb38083b0c", "spec_path": "specs/isa/ternary_gates.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/t27b_T27bDiscardPlan.json b/.trinity/seals/t27b_T27bDiscardPlan.json index c94e37b109..f2f833ea6b 100644 --- a/.trinity/seals/t27b_T27bDiscardPlan.json +++ b/.trinity/seals/t27b_T27bDiscardPlan.json @@ -1,24 +1,24 @@ { - "built_by": "t27c-bootstrap@0.5.2+5d5d2cdef", + "built_by": "t27c-bootstrap@0.5.2+3c0ae4d49", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:95bdef51d7d83ec98fd1df0adbb7dd26ba707b33e2bc13dcadbd7c75857ed19f", - "gen_hash_rust": "sha256:1a068c18c77ef1bb3a34f116501b6c794ded4b50086c1d867f1c0e7f72ebf869", - "gen_hash_verilog": "sha256:48ffbb7235fbdb556a2d06f9f9d920c88daa31b1087c956a4d933eeecbc7df97", - "gen_hash_zig": "sha256:59c5b80da21ff3c327b46ee14bce77bb445604eae90e2362695b8058dc001a6a", + "gen_hash_c": "sha256:c9fdbc89627e959585ccc9a35b15953f241aabe076af0bfe307b3c5e3d87bc43", + "gen_hash_rust": "sha256:4651655a9576deecffd83b5cbb135703587e3d711d707a24a99ea79b51a5dc52", + "gen_hash_verilog": "sha256:c9fc6a3d44087796ad3f96e53227faf9755babf5a3cb8dce76fc688ba8944140", + "gen_hash_zig": "sha256:e984cd161839534c3a1fe6159e0d6b298b89a4d151f1134e523daae68c3f8e2a", "module": "T27bDiscardPlan", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-09T14:58:35Z", + "sealed_at": "2026-10-10T13:36:52Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:f255459b0b4e1feb33373c09a9e1fb1c7368796cc261177db4dc6613b5c3881d", + "spec_hash": "sha256:f136521731ed02abfe8e7d04578f3407479efcca16e3054998728880d3778491", "spec_path": "specs/tri/t27b/discard_plan.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 4, - "total": 4 + "passed": 6, + "total": 6 }, "toolchain": { "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", diff --git a/cli/t27b/src/lower.rs b/cli/t27b/src/lower.rs index 57dcebfba6..9d4c8dbf49 100644 --- a/cli/t27b/src/lower.rs +++ b/cli/t27b/src/lower.rs @@ -2523,7 +2523,7 @@ impl<'a> Lower<'a> { if name == "_" && (op.is_empty() || op == "=") { let (mut calls, top) = (Vec::new(), self.scopes.len() == 1); calls_in(std::slice::from_ref(rhs), &mut calls); - match dp::plan(rhs.kind == NodeKind::ExprIdentifier, self.in_test && top, !self.in_test && top, !calls.is_empty()) { + match dp::plan(rhs.kind == NodeKind::ExprIdentifier, self.in_test && top, self.in_bench, !self.in_test && top, !calls.is_empty()) { dp::DELETED => return Ok(()), dp::EVALUATE => return self.discard_value(rhs, out), _ => {} diff --git a/docs/now/2026-10-10-t27b-bench-discards-and-ternary-gates-benches.md b/docs/now/2026-10-10-t27b-bench-discards-and-ternary-gates-benches.md new file mode 100644 index 0000000000..68f8955003 --- /dev/null +++ b/docs/now/2026-10-10-t27b-bench-discards-and-ternary-gates-benches.md @@ -0,0 +1,7 @@ +# NOW -- t27b: a bench's `_ = e;` is a discard; ternary_gates' benches type-check once referenced (2026-10-10) + +## t27b blocker after c7570433e (Closes #8546; see #8195) + +- arty_a7_integration.t27: its bench discards a u32, a u32, then a bool at the top level. t27b's test-binding path bound `_` as a u32 variable and refused the bool (`type mismatch`). t27c prints each one as Zig's `_ = e;`. specs/tri/t27b/discard_plan.t27 now evaluates and drops every `_ = e;` at the top of a bench. The glue changes one line. +- ternary_gates.t27: t27b was right. Once a test calls the benches, zig 0.16 refuses them three ways: signed `%`, an i32 into a u32, and a run-time index into a tuple. The spec now uses a `[3]i32` array and `@intCast(result + 1)`. With a test that calls all four benches, the reference passes 13/13. +- t27b `test --check` passes both specs: 12/12 with 122 asserts and 6/6 with 13. Both ledger rows are back to pass, and max_not_pass goes from 27 to 25. diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 1e6f0b34f8..c0166dfff4 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -12,7 +12,7 @@ "reference-bug": "the reference passes it for the wrong reason; t27b is right not to", "n/a": "outside what t27b is for (say why in the PR that sets it)" }, - "max_not_pass": 27, + "max_not_pass": 25, "entries": [ {"path": "specs/a/b_c.t27", "t27b": "pass"}, {"path": "specs/a_b/c.t27", "t27b": "pass"}, @@ -263,7 +263,7 @@ {"path": "specs/docs/system.t27", "t27b": "pass"}, {"path": "specs/fpga/adapter.t27", "t27b": "pass"}, {"path": "specs/fpga/assembler.t27", "t27b": "pass"}, - {"path": "specs/fpga/boards/arty_a7_integration.t27", "t27b": "blocked", "blocker": "type mismatch", "reason": "unimplemented"}, + {"path": "specs/fpga/boards/arty_a7_integration.t27", "t27b": "pass"}, {"path": "specs/fpga/boards/qmtech_a100t_integration.t27", "t27b": "pass"}, {"path": "specs/fpga/bootrom.t27", "t27b": "pass"}, {"path": "specs/fpga/bpsk.t27", "t27b": "pass"}, @@ -355,7 +355,7 @@ {"path": "specs/isa/t27a.t27", "t27b": "pass"}, {"path": "specs/isa/t27a_mnemonics.t27", "t27b": "pass"}, {"path": "specs/isa/ternary_encoding.t27", "t27b": "pass"}, - {"path": "specs/isa/ternary_gates.t27", "t27b": "blocked", "blocker": "type mismatch", "reason": "unimplemented"}, + {"path": "specs/isa/ternary_gates.t27", "t27b": "pass"}, {"path": "specs/isa/ternary_graph.t27", "t27b": "pass"}, {"path": "specs/isa/tri27_bytecode.t27", "t27b": "pass"}, {"path": "specs/isa/tri27_machine.t27", "t27b": "pass"}, diff --git a/gen/rust/tri/t27b/discard_plan.rs b/gen/rust/tri/t27b/discard_plan.rs index f56f0cf5ff..14254c34a7 100644 --- a/gen/rust/tri/t27b/discard_plan.rs +++ b/gen/rust/tri/t27b/discard_plan.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 4 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 6 test(s), 0 invariant(s), 0 function(s) with no body. // This backend emits declarations only. The spec's checks live in // the Zig and Verilog outputs; do not read this file as verified. @@ -10,11 +10,11 @@ pub const DELETED: u8 = 1; pub const EVALUATE: u8 = 2; -pub fn plan(name: bool, test_top: bool, fn_top: bool, call: bool) -> u8 { +pub fn plan(name: bool, test_top: bool, in_bench: bool, fn_top: bool, call: bool) -> u8 { if name { return NOT_MINE; } - if test_top { + if (test_top && !(in_bench)) { return NOT_MINE; } if (fn_top && !(call)) { diff --git a/specs/isa/ternary_gates.t27 b/specs/isa/ternary_gates.t27 index 892faf7642..0e4bb668d1 100644 --- a/specs/isa/ternary_gates.t27 +++ b/specs/isa/ternary_gates.t27 @@ -372,12 +372,12 @@ module TernaryGates { bench ternary_min_performance // Measure: cycles to compute 1000 MIN operations // Target: < 1000 cycles - const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + const vals : [3]i32 = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; var result : i32 = 0; var i : u32 = 0; while i < 1000 { - var idx : u32 = (result + 1) % 3; - result = ternary_min(result, vals[idx]); + const idx : u32 = @intCast(result + 1); + result = ternary_min(result, vals[idx % 3]); i = i + 1; } _ = result; @@ -385,12 +385,12 @@ module TernaryGates { bench ternary_max_performance // Measure: cycles to compute 1000 MAX operations // Target: < 1000 cycles - const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + const vals : [3]i32 = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; var result : i32 = 0; var i : u32 = 0; while i < 1000 { - var idx : u32 = (result + 1) % 3; - result = ternary_max(result, vals[idx]); + const idx : u32 = @intCast(result + 1); + result = ternary_max(result, vals[idx % 3]); i = i + 1; } _ = result; @@ -398,7 +398,7 @@ module TernaryGates { bench ternary_consensus_performance // Measure: cycles to compute 1000 CONSENSUS operations // Target: < 2000 cycles - const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + const vals : [3]i32 = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; var result : i32 = 0; var idx : u32 = 0; var i : u32 = 0; diff --git a/specs/tri/t27b/discard_plan.t27 b/specs/tri/t27b/discard_plan.t27 index 69d3f46eb7..1346c73942 100644 --- a/specs/tri/t27b/discard_plan.t27 +++ b/specs/tri/t27b/discard_plan.t27 @@ -16,14 +16,19 @@ ; `if` traps), and drops the value, void or not. ; - At the top level of a test, t27c's test-binding form prints `const _ = e;`, and Zig refuses it ("'_' used ; as an identifier without @"_" syntax"). +; - At the top level of a bench, t27c's gen_bench_block prints every `_ = e;` as written: the first through +; its W608 discard spelling, each later one through gen_stmt. Each is Zig's discard, whatever e's type, in +; an uncalled `fn bench_() void` Zig never analyzes. specs/fpga/boards/arty_a7_integration.t27 +; discards a u32, a u32, then a bool there (see #8195, #8546). ; - specs/port/bootstrap/src/compiler/collect_type_decls.t27 writes `_ = process(node);` (a void fn) at the top ; of a body, `_ = "literal";` in the arms of an `if`, and a trailing `_ = "literal";` that is deleted. ; -- ; What t27b does. DELETED: at the top level of a fn body, an e with no call is not lowered at all, as the ; reference deletes it. EVALUATE: anywhere else, e is lowered and evaluated for its effects and its traps, and ; its value is dropped. NOT_MINE: a bare name (`_ = p;`, t27b's parameter-discard path, which models Zig's -; pointless-discard rule), and any `_ = e;` at the top level of a test, invariant or bench (t27b's test-binding -; path, left as it was). +; pointless-discard rule), and any `_ = e;` at the top level of a test or invariant (t27b's test-binding path, +; left as it was). At the top level of a bench that path bound `_` as a variable of the first e's type and +; refused a later e of another type (`type mismatch`); there a discard is EVALUATE too, compiled, never run. ; ASCII only (L3) ; phi^2 + 1/phi^2 = 3 | TRINITY @@ -35,11 +40,11 @@ pub const DELETED : u8 = 1; pub const EVALUATE : u8 = 2; /// The decision for one `_ = e;`. name: e is a bare name. test_top: the statement is at the top level of a test, -/// invariant or bench. fn_top: it is at the top level of a fn body, where the reference's dead-store pass runs. -/// call: e holds a call anywhere in it. -pub fn plan(name: bool, test_top: bool, fn_top: bool, call: bool) -> u8 { +/// invariant or bench. in_bench: it is in a bench body. fn_top: it is at the top level of a fn body, where the +/// reference's dead-store pass runs. call: e holds a call anywhere in it. +pub fn plan(name: bool, test_top: bool, in_bench: bool, fn_top: bool, call: bool) -> u8 { if (name) { return NOT_MINE; } - if (test_top) { return NOT_MINE; } + if (test_top and !in_bench) { return NOT_MINE; } if (fn_top and !call) { return DELETED; } return EVALUATE; } @@ -55,28 +60,43 @@ pub fn runs(act: u8) -> bool { test a_call_discarded_at_the_top_of_a_fn_runs { // collect_type_decls.t27's `_ = process_node_for_types(node);`: kept by keep_call_discards. - assert(plan(false, false, true, true) == EVALUATE); + assert(plan(false, false, false, true, true) == EVALUATE); assert(runs(EVALUATE)); } test a_discard_with_no_call_at_the_top_of_a_fn_is_deleted { // collect_type_decls.t27's trailing `_ = "children_processed";`, and `_ = a + b;`, which then cannot trap. - assert(plan(false, false, true, false) == DELETED); + assert(plan(false, false, false, true, false) == DELETED); assert(!runs(DELETED)); } test a_nested_discard_runs_with_or_without_a_call { // collect_type_decls.t27's `_ = "struct_fields_collected";` in an arm of an `if`; a nested // `_ = a + b;` traps on overflow, as the reference's does. - assert(plan(false, false, false, false) == EVALUATE); - assert(plan(false, false, false, true) == EVALUATE); + assert(plan(false, false, false, false, false) == EVALUATE); + assert(plan(false, false, false, false, true) == EVALUATE); } test a_bare_name_and_a_test_binding_are_left_alone { - assert(plan(true, false, true, false) == NOT_MINE); - assert(plan(true, false, false, true) == NOT_MINE); - assert(plan(true, true, false, true) == NOT_MINE); - assert(plan(false, true, false, true) == NOT_MINE); - assert(plan(false, true, false, false) == NOT_MINE); + assert(plan(true, false, false, true, false) == NOT_MINE); + assert(plan(true, false, false, false, true) == NOT_MINE); + assert(plan(true, true, false, false, true) == NOT_MINE); + assert(plan(false, true, false, false, true) == NOT_MINE); + assert(plan(false, true, false, false, false) == NOT_MINE); assert(!runs(NOT_MINE)); } + +test every_discard_at_the_top_of_a_bench_runs { + // arty_a7_integration.t27's bench_integration_config: `_ = calc_baud_divisor(..);` (a u32), then + // `_ = verify_pin_config(..);` (a bool), each its own discard. + assert(plan(false, true, true, false, true) == EVALUATE); + assert(plan(false, true, true, false, false) == EVALUATE); + assert(runs(plan(false, true, true, false, true))); +} + +test a_nested_discard_in_a_bench_runs_and_a_bare_name_there_is_left_alone { + assert(plan(false, false, true, false, true) == EVALUATE); + assert(plan(false, false, true, false, false) == EVALUATE); + // ternary_gates.t27's `_ = result;` closing each bench. + assert(plan(true, true, true, false, false) == NOT_MINE); +}