From 14d7fb77dd0779e6bfe7cce3f2497758c25dbd55 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 18:26:22 +0700 Subject: [PATCH 1/2] WIP t27b: a narrow signed @mod widens to the i64 routine (see #10423) Co-Authored-By: Claude Opus 5.5 --- cli/t27b/src/lower.rs | 13 ++-- specs/tri/t27b/conformance/rem_int.t27 | 86 ++++++++++++++++++++++++- specs/tri/t27b/libm_plan.t27 | 89 ++++++++++++++++++++++---- 3 files changed, 166 insertions(+), 22 deletions(-) diff --git a/cli/t27b/src/lower.rs b/cli/t27b/src/lower.rs index 598be641c6..43ca6b3fef 100644 --- a/cli/t27b/src/lower.rs +++ b/cli/t27b/src/lower.rs @@ -3807,7 +3807,7 @@ impl<'a> Lower<'a> { } } - /// A builtin of libm_plan.t27 (`@exp`, `@log`, an i64 `@mod`; an integer `@rem` is t27b's `%`): a call of its routine from libm.t27, compiler_rt's + /// A builtin of libm_plan.t27 (`@exp`, `@log`, a signed `@mod`; an integer `@rem` is t27b's `%`): a call of its routine from libm.t27, compiler_rt's /// algorithm, so the bits are the reference's, folded or not. What is refused instead is the plan's. fn libm_call(&mut self, n: &Node) -> R { self.see(n); @@ -3829,12 +3829,11 @@ impl<'a> Lower<'a> { match (v, self.sigs.get(xp::routine(b, a)).filter(|s| !s.poisoned).map(|s| (s.id, s.ret.clone()))) { (v, _) if a == xp::OPERATOR => { let r = self.expr(&n.children[1])?; self.binary(xp::OPERATOR_TEXT, v, r) } (v, Some((func, Some(LTy::S(ty))))) if t || matches!(v, Val::E(_) | Val::Cf(..)) => { - let mut args = if t { vec![] } else { vec![self.coerce(v, ty0)?] }; // `@log(10.0)`: the literal, one rounding to f64 - for c in &n.children[at + 1..] { - let r = self.expr_as(c, <y::S(ty0))?; // `@rem(x, 2.0)`, `@mod(i, 2147483648)`: the first one's type - args.push(self.reg(r)?); - } - Ok(Val::E(Expr { ty, kind: ExprKind::Call { func, args } })) + let w = |e: Expr| if xp::widens(a) { Expr { ty, kind: ExprKind::Widen(Box::new(e)) } } else { e }; // an i32 `@mod`: to the i64 routine's type + let mut args = if t { vec![] } else { vec![w(self.coerce(v, ty0)?)] }; // `@log(10.0)`: the literal, one rounding to f64 + for c in &n.children[at + 1..] { let r = self.expr_as(c, <y::S(ty0))?; args.push(w(self.reg(r)?)); } // `@rem(x, 2.0)`, `@mod(i, 2147483648)`: the first one's type + let call = Expr { ty, kind: ExprKind::Call { func, args } }; // and back to the operands' type, which holds 0 <= r < b + Ok(Val::E(if xp::widens(a) { Expr { ty: ty0, kind: ExprKind::Cast { arg: Box::new(call), site: 0 } } } else { call })) } _ => self.reject(xp::what(b), format!("`{}`: {}", n.name, xp::why(a))), } diff --git a/specs/tri/t27b/conformance/rem_int.t27 b/specs/tri/t27b/conformance/rem_int.t27 index e106ed442a..324360d227 100644 --- a/specs/tri/t27b/conformance/rem_int.t27 +++ b/specs/tri/t27b/conformance/rem_int.t27 @@ -6,7 +6,9 @@ ; of signed and unsigned operands, with a literal beside a typed integer taking that integer's type. ; - `@mod(a, b)` is the floored remainder (#10405): the sign of the divisor, so 0 <= @mod(a, b) < b, of an ; i64 (a call of specs/tri/t27b/libm.t27's `t27b_libm_mod`) and of an unsigned integer (`%`), with -; specs/t27f/reel_particles.t27's `lcg` as written. t27b ports a signed `@mod` of i64 only, so no i32 one here. +; specs/t27f/reel_particles.t27's `lcg` as written. A narrower signed `@mod` (#10423: i32, i16, i8, i7) widens to +; that i64 routine and narrows back, as specs/tri/t27b/libm_plan.t27 says; its extremes are here, and +; reel_particles_mux's `millis` and arena_heap's power-of-two test as written. ; Not here (it fails the test, so no passing spec can hold it): a run-time zero divisor, or `@mod`'s negative one, ; which is a safety panic in Zig and a DivZero trap (a panic for `@mod`) in t27b ; (specs/tri/t27b/libm_plan.t27 lowers `@rem` as t27b's `%`). @@ -114,3 +116,85 @@ test mod_of_unsigned_and_the_lcg { i = i + 1; } } + +fn ri_mod32(a: i32, b: i32) -> i32 { + return @mod(a, b); +} + +fn ri_mod16(a: i16, b: i16) -> i16 { + return @mod(a, b); +} + +fn ri_mod8(a: i8, b: i8) -> i8 { + return @mod(a, b); +} + +fn ri_mod7(a: i7, b: i7) -> i7 { + return @mod(a, b); +} + +// specs/t27f/reel_particles_mux.t27's `millis`, as written. +fn ri_millis(ms: i32) -> u32 { + return @as(u32, @intCast(@mod(ms, 1000))); +} + +// specs/ml/arena_heap.t27's `is_pow2`, as written. +fn ri_is_pow2(n: i32) -> bool { + if (n < 1) { + return false; + } + var m : i32 = n; + while (m > 1) { + if (@mod(m, 2) != 0) { + return false; + } + m = @divTrunc(m, 2); + } + return true; +} + +test a_narrow_signed_mod_takes_the_sign_of_the_divisor { + assert ri_mod32(7, 3) == 1; + assert ri_mod32(-7, 3) == 2; + assert ri_mod32(-6, 3) == 0; + assert ri_mod32(0, 7) == 0; + assert ri_mod32(-1, 2147483647) == 2147483646; + assert ri_mod32(-2147483648, 2147483647) == 2147483646; + assert ri_mod32(-2147483648, 3) == 1; + assert ri_mod32(2147483647, 2) == 1; + assert ri_mod32(2147483647, 2147483647) == 0; + assert ri_mod16(-32768, 7) == 6; + assert ri_mod16(-5, 32767) == 32762; + assert ri_mod16(32767, 10) == 7; + assert ri_mod8(-128, 3) == 1; + assert ri_mod8(-128, 127) == 126; + assert ri_mod8(127, 10) == 7; + assert ri_mod8(-1, 127) == 126; + assert ri_mod7(-64, 5) == 1; + assert ri_mod7(-1, 63) == 62; + assert ri_mod7(63, 2) == 1; + var a : i32 = -23; + while (a <= 23) { + const m : i32 = ri_mod32(a, 6); + assert m >= 0; + assert m < 6; + assert @rem(a - m, 6) == 0; + assert ri_mod32(a, 6) == @as(i32, @intCast(ri_mod(a, 6))); + a = a + 1; + } +} + +test the_i32_mods_of_the_corpus_as_written { + assert ri_millis(1234) == 234; + assert ri_millis(-1) == 999; + assert ri_millis(-1000) == 0; + assert ri_millis(0) == 0; + assert ri_millis(2147483647) == 647; + assert ri_is_pow2(1); + assert ri_is_pow2(64); + assert ri_is_pow2(1073741824); + assert !ri_is_pow2(0); + assert !ri_is_pow2(-8); + assert !ri_is_pow2(96); + assert !ri_is_pow2(2147483647); +} diff --git a/specs/tri/t27b/libm_plan.t27 b/specs/tri/t27b/libm_plan.t27 index a80763ad13..a50d0bf332 100644 --- a/specs/tri/t27b/libm_plan.t27 +++ b/specs/tri/t27b/libm_plan.t27 @@ -29,7 +29,13 @@ ; `@mod` of two integers (#10405, specs/t27f/reel_particles.t27's `lcg`) is Zig's floored remainder, the sign ; of the divisor. Of an unsigned pair it equals `@rem`, so it is OPERATOR too. Of an i64 pair it calls libm.t27's ; `mod`: `@rem`, moved up by the divisor when negative, and a panic on a negative divisor, as Zig 0.16 Debug panics. -; A signed `@mod` of another width is refused (REFUSE_WIDTH), and so is a float `@mod`. Pinned by rem_int.t27 too. +; A signed `@mod` narrower than i64 (#10423: the i32 ones of specs/ml/arena_heap.t27, specs/ml/file_io_contract.t27 +; and specs/t27f/reel_particles_mux.t27) widens: both operands, of the first one's type, go to i64 losslessly, the +; same `mod` runs, and its result narrows back to that type (CALL_WIDE). The narrowing loses nothing: a result is +; 0 <= r < b and b fits the type, and a zero or negative divisor traps or panics in the i64 routine as it would in +; the narrow one. The other way, a `mod` per width, would be one more routine for each of i8, i16, i32 and every odd +; width, all the same four lines; widening is one routine and one rule. A signed `@mod` wider than i64 (`i128`, +; K_WIDE) has no routine and is refused (REFUSE_WIDTH), and so is a float `@mod`. Pinned by rem_int.t27 too. ; `@log` of a comptime_float literal (#10405, token_floor's `@log(10.0)`) calls `log` of the literal's f64: ; Zig folds it with compiler_rt `logq`, which in Zig 0.16 is `log(@floatCast(a))`, the f64 routine, so the folded ; value is an f64 exactly. libm.t27's `log_of_a_literal_is_the_f64_routine_s` holds it to the run-time call and @@ -119,8 +125,8 @@ pub const PORT : str = "t27b_libm_"; pub const HELPER : str = "__t27b_libm_"; // The operand, as the glue lowered it: a typed f64, a typed f32, a comptime_float or comptime_int -// literal, anything else, a typed signed integer other than i64, an unsigned one, an i64 (`kind` reads the type). For `nan`, the type its operand names: f64, -// f32, or another (K_OTHER). +// literal, anything else, a typed signed integer narrower than i64, an unsigned one, an i64, a signed integer +// wider than i64 (`kind` reads the type). For `nan`, the type its operand names: f64, f32, or another (K_OTHER). pub const K_F64 : u8 = 0; pub const K_F32 : u8 = 1; pub const K_LITERAL : u8 = 2; @@ -128,6 +134,7 @@ pub const K_OTHER : u8 = 3; pub const K_INT : u8 = 4; pub const K_UINT : u8 = 5; pub const K_I64 : u8 = 6; +pub const K_WIDE : u8 = 7; // What the call lowers to: a call of the f64 or the f32 routine, or a refusal. pub const CALL_F64 : u8 = 0; @@ -142,13 +149,22 @@ pub const REFUSE_TYPE : u8 = 8; // Not a call: an integer `@rem` is t27b's binary operator OPERATOR_TEXT of the two operands. pub const OPERATOR : u8 = 9; pub const OPERATOR_TEXT : str = "%"; -// A call of the i64 routine (a signed `@mod`), and the refusal of a signed `@mod` of another width. +// A call of the i64 routine (a signed `@mod`), and the refusal of a signed `@mod` wider than i64. pub const CALL_I64 : u8 = 10; pub const REFUSE_WIDTH : u8 = 11; +// A narrower signed `@mod`: its operands widen to i64, it calls the i64 routine, and the result narrows back. +pub const CALL_WIDE : u8 = 12; + +/// Does the answer widen the operands to the routine's type (i64) and narrow its result back to theirs? The glue +/// wraps the call so when it does. +pub fn widens(act: u8) -> bool { + return act == CALL_WIDE; +} /// The kind of an operand by the name of its type, as t27b prints it: `f64`, `f32`, a typed integer (`i` or `u` -/// and digits: `i64`, `u8`, an odd width like `u7`), or anything else (`bool`, `ptr`, `usize` as a name). In -/// byte values: gen-rust emits a character literal as a Rust char, which a u8 cannot be compared with (#7604). +/// and digits: `i64`, `u8`, an odd width like `u7`), or anything else (`bool`, `ptr`, `usize` as a name). A signed +/// one is K_INT below 64 bits and K_WIDE above: four digits or more, or three from `i65` up. In byte values: +/// gen-rust emits a character literal as a Rust char, which a u8 cannot be compared with (#7604). pub fn kind(ty: str) -> u8 { if (ty.len < 2 or (ty[0] != 105 and ty[0] != 117 and ty[0] != 102)) { return K_OTHER; } var k : usize = 1; @@ -158,6 +174,7 @@ pub fn kind(ty: str) -> u8 { } if (ty[0] == 117) { return K_UINT; } if (ty.len == 3 and ty[0] == 105 and ty[1] == 54 and ty[2] == 52) { return K_I64; } + if (ty[0] == 105 and (ty.len > 3 or (ty.len == 3 and (ty[1] > 54 or (ty[1] == 54 and ty[2] > 52))))) { return K_WIDE; } if (ty[0] != 102) { return K_INT; } if (ty.len == 3 and ty[1] == 54 and ty[2] == 52) { return K_F64; } if (ty.len == 3 and ty[1] == 51 and ty[2] == 50) { return K_F32; } @@ -208,10 +225,11 @@ pub fn with_base(b: u8, act: u8, base_is_e: bool, type_is_value_s: bool) -> u8 { /// The decision. b: the builtin. operands: how many the call has. k: the operand's kind. pub fn plan(b: u8, operands: usize, k: u8) -> u8 { if (operands != arity(b)) { return REFUSE_ARITY; } - if ((k == K_INT or k == K_UINT or k == K_I64) and b == REM) { return OPERATOR; } + if ((k == K_INT or k == K_UINT or k == K_I64 or k == K_WIDE) and b == REM) { return OPERATOR; } if (k == K_UINT and b == MOD) { return OPERATOR; } if (k == K_I64 and b == MOD) { return CALL_I64; } - if (k == K_INT and b == MOD) { return REFUSE_WIDTH; } + if (k == K_INT and b == MOD) { return CALL_WIDE; } + if (k == K_WIDE and b == MOD) { return REFUSE_WIDTH; } if (k == K_LITERAL and b == LOG) { return CALL_F64; } if (k == K_F64 and has_f64(b)) { return CALL_F64; } if (k == K_F32 and has_f32(b)) { return CALL_F32; } @@ -239,7 +257,7 @@ pub fn routine(b: u8, act: u8) -> str { if (b == CEIL and act == CALL_F32) { return "__t27b_libm_ceilf"; } if (b == FLOOR and act == CALL_F64) { return "__t27b_libm_floor"; } if (b == CEIL and act == CALL_F64) { return "__t27b_libm_ceil"; } - if (b == MOD and act == CALL_I64) { return "__t27b_libm_mod"; } + if (b == MOD and (act == CALL_I64 or act == CALL_WIDE)) { return "__t27b_libm_mod"; } if (b == ROUND and act == CALL_F32) { return "__t27b_libm_roundf"; } if (b == TRUNC and act == CALL_F32) { return "__t27b_libm_truncf"; } if (b == REM and act == CALL_F32) { return "__t27b_libm_fmodf"; } @@ -289,7 +307,7 @@ pub fn why(act: u8) -> str { if (act == REFUSE_NO_F64) { return "libm.t27 has no f64 routine for it (compiler_rt's, or a float `@mod`, is not ported)"; } if (act == REFUSE_BASE) { return "its base is not std.math.e, the one base this plan lowers"; } if (act == REFUSE_TYPE) { return "its type operand is not its value's type"; } - if (act == REFUSE_WIDTH) { return "a signed `@mod` is ported for i64 only"; } + if (act == REFUSE_WIDTH) { return "a signed `@mod` wider than i64 is not ported (no i128 routine)"; } return "its routine in specs/tri/t27b/libm.t27 did not lower"; } @@ -615,8 +633,17 @@ test the_kind_is_read_from_the_type_s_name { assert kind("u64") == K_UINT; assert kind("i32") == K_INT; assert kind("i6") == K_INT; - assert kind("i640") == K_INT; - assert kind("i128") == K_INT; + assert kind("i640") == K_WIDE; + assert kind("i128") == K_WIDE; + assert kind("i65") == K_WIDE; + assert kind("i70") == K_WIDE; + assert kind("i99") == K_WIDE; + assert kind("i63") == K_INT; + assert kind("i60") == K_INT; + assert kind("i59") == K_INT; + assert kind("i8") == K_INT; + assert kind("u128") == K_UINT; + assert kind("f128") == K_OTHER; assert kind("bool") == K_OTHER; assert kind("ptr") == K_OTHER; assert kind("f16") == K_OTHER; @@ -640,12 +667,11 @@ test token_floor_s_floor_ceil_log_literal_and_mod { assert plan(EXP, 1, K_LITERAL) == REFUSE_LITERAL; assert plan(FLOOR, 1, K_LITERAL) == REFUSE_LITERAL; assert plan(LOG2, 1, K_LITERAL) == REFUSE_LITERAL; - // `@mod`: unsigned is `%`, an i64 calls the floored routine, another signed width is refused, a float too. + // `@mod`: unsigned is `%`, an i64 calls the floored routine, a float is refused. assert arity(MOD) == 2; assert plan(MOD, 2, K_UINT) == OPERATOR; assert plan(MOD, 2, K_I64) == CALL_I64; assert std.mem.eql(u8, routine(MOD, CALL_I64), "__t27b_libm_mod"); - assert plan(MOD, 2, K_INT) == REFUSE_WIDTH; assert routine(MOD, REFUSE_WIDTH).len == 0; assert std.mem.indexOf(u8, why(REFUSE_WIDTH), "i64") != null; assert plan(MOD, 2, K_F64) == REFUSE_NO_F64; @@ -658,3 +684,38 @@ test token_floor_s_floor_ceil_log_literal_and_mod { assert routine(REM, CALL_I64).len == 0; assert with_base(MOD, CALL_I64, false, false) == CALL_I64; } + +// #10423: a signed `@mod` narrower than i64 (arena_heap's and file_io_contract's `@mod(n, 2)` and `@mod(size, 4)` +// and reel_particles_mux's `@mod(ms, 1000)`, all of an i32) widens to the i64 routine and narrows back; only a +// signed one wider than i64 is still refused. +test a_narrow_signed_mod_widens_to_the_i64_routine { + assert plan(MOD, 2, kind("i32")) == CALL_WIDE; + assert plan(MOD, 2, kind("i8")) == CALL_WIDE; + assert plan(MOD, 2, kind("i16")) == CALL_WIDE; + assert plan(MOD, 2, kind("i7")) == CALL_WIDE; + assert plan(MOD, 2, kind("i63")) == CALL_WIDE; + assert plan(MOD, 2, kind("i64")) == CALL_I64; + assert plan(MOD, 2, kind("u32")) == OPERATOR; + assert plan(MOD, 2, kind("i128")) == REFUSE_WIDTH; + assert plan(MOD, 2, kind("i65")) == REFUSE_WIDTH; + assert plan(MOD, 1, kind("i32")) == REFUSE_ARITY; + // The same routine as an i64's: one routine, one rule. + assert std.mem.eql(u8, routine(MOD, CALL_WIDE), routine(MOD, CALL_I64)); + assert std.mem.eql(u8, routine(MOD, CALL_WIDE), "__t27b_libm_mod"); + // Only CALL_WIDE wraps the call; an i64 one and every float call do not. + assert widens(CALL_WIDE); + assert !widens(CALL_I64); + assert !widens(CALL_F64); + assert !widens(OPERATOR); + assert !widens(REFUSE_WIDTH); + // `@rem` of every integer stays the operator, the wide ones too; no other builtin takes CALL_WIDE. + assert plan(REM, 2, K_WIDE) == OPERATOR; + assert plan(REM, 2, K_INT) == OPERATOR; + assert plan(COS, 1, K_WIDE) == REFUSE_OPERAND; + assert plan(EXP, 1, K_WIDE) == REFUSE_OPERAND; + assert routine(REM, CALL_WIDE).len == 0; + assert routine(EXP, CALL_WIDE).len == 0; + assert std.mem.indexOf(u8, why(REFUSE_WIDTH), "wider than i64") != null; + assert std.mem.indexOf(u8, why(REFUSE_WIDTH), "did not lower") == null; + assert with_base(MOD, CALL_WIDE, false, false) == CALL_WIDE; +} From ea872991629a8c2933ec9f9d0d7c636c7f232753 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 18:32:30 +0700 Subject: [PATCH 2/2] t27b: regenerate libm_plan.rs, reseal, arena_heap and file_io_contract pass (Closes #10423) gen/rust/tri/t27b/libm_plan.rs regenerated with t27c gen-rust on the Railway lab. Seals for libm_plan and conformance/rem_int re-saved. The ledger moves specs/ml/arena_heap.t27 and specs/ml/file_io_contract.t27 to pass, so max_not_pass goes from 77 to 75. Co-Authored-By: Claude Opus 5.5 --- .../conformance_T27bConformanceRemInt.json | 18 +++++++-------- .trinity/seals/t27b_T27bLibmPlan.json | 18 +++++++-------- docs/reports/t27b_expectations.json | 6 ++--- gen/rust/tri/t27b/libm_plan.rs | 22 +++++++++++++++---- 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/.trinity/seals/conformance_T27bConformanceRemInt.json b/.trinity/seals/conformance_T27bConformanceRemInt.json index 1751c69816..4f95ab79d8 100644 --- a/.trinity/seals/conformance_T27bConformanceRemInt.json +++ b/.trinity/seals/conformance_T27bConformanceRemInt.json @@ -1,24 +1,24 @@ { - "built_by": "t27c-bootstrap@0.5.2+8ea472041", + "built_by": "t27c-bootstrap@0.5.2+14d7fb77d", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:fba2bc4ca42582b8fe1d740617af2bb690a604e6da93565cfb8027f512f95230", - "gen_hash_rust": "sha256:e1d78592d7020117c806c51d4f0eb88cd26f3a4e76b9357f465d7ca9146b7f86", - "gen_hash_verilog": "sha256:1d225dd532cb58ab45c4196116bd9a5a9e15f382a97a3d861b98501c9a61fba2", - "gen_hash_zig": "sha256:5bb64df244d6d26188a53c9e2503d26ddca665324ce2185142489298d21517ad", + "gen_hash_c": "sha256:4b66c90aff70fb7ee7a5d08216a5b9426d4eca75c485ee2cccd7f214bc5d09da", + "gen_hash_rust": "sha256:0902e5d88c053df87da007518ceba6dc625e01f3aa5b8643122e0393930a9ded", + "gen_hash_verilog": "sha256:a8028c37c484c21e32959ca4abf7b752d092d7dd701bc25c71ca919127ae0451", + "gen_hash_zig": "sha256:bb256b4c229ac55b8f5d564d2d170233421e6b4db60b4dcab0aa38f4d74a66ee", "module": "T27bConformanceRemInt", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T11:14:01Z", + "sealed_at": "2026-10-18T11:31:39Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:6cb6dc2002cab87fb3dd6010105291dee503677b6851a9c74d29ab58def11fe3", + "spec_hash": "sha256:f26be979bdc438b926d06c1b97ce4142a9d61403007b6547cc42557fd3b3e44c", "spec_path": "specs/tri/t27b/conformance/rem_int.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 6, - "total": 6, + "passed": 8, + "total": 8, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/t27b_T27bLibmPlan.json b/.trinity/seals/t27b_T27bLibmPlan.json index d26692d9a8..0e6c6a55bd 100644 --- a/.trinity/seals/t27b_T27bLibmPlan.json +++ b/.trinity/seals/t27b_T27bLibmPlan.json @@ -1,24 +1,24 @@ { - "built_by": "t27c-bootstrap@0.5.2+8ea472041", + "built_by": "t27c-bootstrap@0.5.2+14d7fb77d", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:bba02f9b9f4a1572d001f52952d7378e102e7502e5ef8bfe61b0329c780615b9", - "gen_hash_rust": "sha256:aea1217193961c1c858a2545c41e5eb8050bd320abc98904d5bc9ad05c60aeb0", - "gen_hash_verilog": "sha256:9d337661883412424964dc7f532d2edaaedd6b7543198f63a77c757e459ab3e5", - "gen_hash_zig": "sha256:10892615534ebe25a212d555d7d4c4ee0726c3b9dc72f2ca29a3518a2e3e20c0", + "gen_hash_c": "sha256:ddbefccfc3ff17740b9096bdf66caa211e86530a6432d8786e194b5b6369bf60", + "gen_hash_rust": "sha256:0f41ef8a8f00b2c4e2552ff4e1877c7e994ff2bca05c7816c87e27699c996d38", + "gen_hash_verilog": "sha256:51bec45a636988c99680b4c01136d653ee4173927d7415c8bfffd28234ab6242", + "gen_hash_zig": "sha256:51ea94cdba1ba50cea9fe4d30fe52a35a9ca8b1d1a326e9b678bf734d9351a8b", "module": "T27bLibmPlan", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T11:14:00Z", + "sealed_at": "2026-10-18T11:31:40Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:cf0b297711d7619d177ff2089aabcbf7dd8674adfea4c4062d1a9ed9b42ea229", + "spec_hash": "sha256:4b13a35b1cf27ee2de1ffd5104870fa57c4e76efcdc09d6e616909081b222a94", "spec_path": "specs/tri/t27b/libm_plan.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 16, - "total": 16, + "passed": 17, + "total": 17, "vacuous": 0 }, "toolchain": { diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 9e4e723fb8..4350020ffd 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": 77, + "max_not_pass": 75, "entries": [ {"path": "specs/a/b_c.t27", "t27b": "pass"}, {"path": "specs/a_b/c.t27", "t27b": "pass"}, @@ -448,8 +448,8 @@ {"path": "specs/ml/activation/silu_swish_activation.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/activation/softmax.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/activation/tanh_activation.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, - {"path": "specs/ml/arena_heap.t27", "t27b": "blocked", "blocker": "ExprCall(@mod)", "reason": "unimplemented"}, - {"path": "specs/ml/file_io_contract.t27", "t27b": "blocked", "blocker": "ExprCall(@mod)", "reason": "unimplemented"}, + {"path": "specs/ml/arena_heap.t27", "t27b": "pass"}, + {"path": "specs/ml/file_io_contract.t27", "t27b": "pass"}, {"path": "specs/ml/host_io_model.t27", "t27b": "blocked", "blocker": "ExprCall(undeclared fn)", "reason": "unimplemented"}, {"path": "specs/ml/igla_champion_capsule.t27", "t27b": "blocked", "blocker": "ExprIdentifier(undeclared)", "reason": "unimplemented"}, {"path": "specs/ml/layers/avgpool2d_layer.t27", "t27b": "pass"}, diff --git a/gen/rust/tri/t27b/libm_plan.rs b/gen/rust/tri/t27b/libm_plan.rs index 9c907e7d72..7fc33856b8 100644 --- a/gen/rust/tri/t27b/libm_plan.rs +++ b/gen/rust/tri/t27b/libm_plan.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 16 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 17 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. @@ -62,6 +62,8 @@ pub const K_UINT: u8 = 5; pub const K_I64: u8 = 6; +pub const K_WIDE: u8 = 7; + pub const CALL_F64: u8 = 0; pub const CALL_F32: u8 = 1; @@ -88,6 +90,12 @@ pub const CALL_I64: u8 = 10; pub const REFUSE_WIDTH: u8 = 11; +pub const CALL_WIDE: u8 = 12; + +pub fn widens(act: u8) -> bool { + return (act == CALL_WIDE); +} + pub fn kind(ty: &'static str) -> u8 { if ((ty.len() < 2) || (((ty.as_bytes()[0] != 105) && (ty.as_bytes()[0] != 117)) && (ty.as_bytes()[0] != 102))) { return K_OTHER; @@ -105,6 +113,9 @@ pub fn kind(ty: &'static str) -> u8 { if ((((ty.len() == 3) && (ty.as_bytes()[0] == 105)) && (ty.as_bytes()[1] == 54)) && (ty.as_bytes()[2] == 52)) { return K_I64; } + if ((ty.as_bytes()[0] == 105) && ((ty.len() > 3) || ((ty.len() == 3) && ((ty.as_bytes()[1] > 54) || ((ty.as_bytes()[1] == 54) && (ty.as_bytes()[2] > 52)))))) { + return K_WIDE; + } if (ty.as_bytes()[0] != 102) { return K_INT; } @@ -166,7 +177,7 @@ pub fn plan(b: u8, operands: usize, k: u8) -> u8 { if (operands != arity(b)) { return REFUSE_ARITY; } - if ((((k == K_INT) || (k == K_UINT)) || (k == K_I64)) && (b == REM)) { + if (((((k == K_INT) || (k == K_UINT)) || (k == K_I64)) || (k == K_WIDE)) && (b == REM)) { return OPERATOR; } if ((k == K_UINT) && (b == MOD)) { @@ -176,6 +187,9 @@ pub fn plan(b: u8, operands: usize, k: u8) -> u8 { return CALL_I64; } if ((k == K_INT) && (b == MOD)) { + return CALL_WIDE; + } + if ((k == K_WIDE) && (b == MOD)) { return REFUSE_WIDTH; } if ((k == K_LITERAL) && (b == LOG)) { @@ -248,7 +262,7 @@ pub fn routine(b: u8, act: u8) -> &'static str { if ((b == CEIL) && (act == CALL_F64)) { return "__t27b_libm_ceil"; } - if ((b == MOD) && (act == CALL_I64)) { + if ((b == MOD) && ((act == CALL_I64) || (act == CALL_WIDE))) { return "__t27b_libm_mod"; } if ((b == ROUND) && (act == CALL_F32)) { @@ -377,7 +391,7 @@ pub fn why(act: u8) -> &'static str { return "its type operand is not its value's type"; } if (act == REFUSE_WIDTH) { - return "a signed `@mod` is ported for i64 only"; + return "a signed `@mod` wider than i64 is not ported (no i128 routine)"; } return "its routine in specs/tri/t27b/libm.t27 did not lower"; }