diff --git a/.trinity/seals/conformance_T27bConformanceLibmLogf.json b/.trinity/seals/conformance_T27bConformanceLibmLogf.json index 2955d7127a..e8fdd9d499 100644 --- a/.trinity/seals/conformance_T27bConformanceLibmLogf.json +++ b/.trinity/seals/conformance_T27bConformanceLibmLogf.json @@ -1,9 +1,9 @@ { - "built_by": "t27c-bootstrap@0.5.2+5d5d2cdef", + "built_by": "t27c-bootstrap@0.5.2+8ea472041", "closure": [ { "spec": "specs/tri/t27b/libm.t27", - "spec_hash": "sha256:d4414973506e15dbe7e061ff41077da40fc844d55c4647c7ef970ff47192a378" + "spec_hash": "sha256:bb2d59aae61115c012a7062f1e7b19c6becb1985fcb5913109cc8dd4727601ee" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -14,7 +14,7 @@ "module": "T27bConformanceLibmLogf", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-09T14:59:45Z", + "sealed_at": "2026-10-18T11:14:32Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:5ba2c342e8d8677a4fa381c538c758f3ed6a9e270b7c9d8d320384518f00d8d0", "spec_path": "specs/tri/t27b/conformance/libm_logf.t27", @@ -23,7 +23,8 @@ "failing": [], "forced": false, "passed": 6, - "total": 6 + "total": 6, + "vacuous": 0 }, "toolchain": { "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", diff --git a/.trinity/seals/conformance_T27bConformanceLibmPowf.json b/.trinity/seals/conformance_T27bConformanceLibmPowf.json index 62fd229cde..a6671cec0a 100644 --- a/.trinity/seals/conformance_T27bConformanceLibmPowf.json +++ b/.trinity/seals/conformance_T27bConformanceLibmPowf.json @@ -1,9 +1,9 @@ { - "built_by": "t27c-bootstrap@0.5.2+dfcf30c4a", + "built_by": "t27c-bootstrap@0.5.2+8ea472041", "closure": [ { "spec": "specs/tri/t27b/libm.t27", - "spec_hash": "sha256:f7086aa8a7f25aa5bce5729f0dd63dd9852591096f69ad10641fbc4cebec64e4" + "spec_hash": "sha256:bb2d59aae61115c012a7062f1e7b19c6becb1985fcb5913109cc8dd4727601ee" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -14,7 +14,7 @@ "module": "T27bConformanceLibmPowf", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-09T22:21:26Z", + "sealed_at": "2026-10-18T11:14:35Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:85821ccd9875656eb388a87c6accd788f1a6fcfa3498547378e09a65ec876f6c", "spec_path": "specs/tri/t27b/conformance/libm_powf.t27", @@ -23,7 +23,8 @@ "failing": [], "forced": false, "passed": 6, - "total": 6 + "total": 6, + "vacuous": 0 }, "toolchain": { "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", diff --git a/.trinity/seals/conformance_T27bConformanceLibmRounding.json b/.trinity/seals/conformance_T27bConformanceLibmRounding.json index 606e174bc3..3e47ca9956 100644 --- a/.trinity/seals/conformance_T27bConformanceLibmRounding.json +++ b/.trinity/seals/conformance_T27bConformanceLibmRounding.json @@ -1,29 +1,30 @@ { - "built_by": "t27c-bootstrap@0.5.2+5d5d2cdef", + "built_by": "t27c-bootstrap@0.5.2+8ea472041", "closure": [ { "spec": "specs/tri/t27b/libm.t27", - "spec_hash": "sha256:d4414973506e15dbe7e061ff41077da40fc844d55c4647c7ef970ff47192a378" + "spec_hash": "sha256:bb2d59aae61115c012a7062f1e7b19c6becb1985fcb5913109cc8dd4727601ee" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:afdbaa78df9aaeb454ab448c98e5ef6dc7e9b01d2f1051d7bd3cc2d54e818433", - "gen_hash_rust": "sha256:af62c5acd6c4fbf87e136e976cfbdd42417788f85ececc741146ae8e6ebb4c1a", - "gen_hash_verilog": "sha256:5d9822c0f36bdf6a68bb3578d44e160fd7f5ae57a4a589dd0aa6d89daf5765f1", - "gen_hash_zig": "sha256:7717c7922c14602ea131553af33bc84b8e7408d037b326c78c6a21d55cc7cdcb", + "gen_hash_c": "sha256:856073fc6a349dd6302b7668ceb904356cb89a1cd2929ec6b82dd98d5b6dec61", + "gen_hash_rust": "sha256:1110044ff659cc7dfec6203afc5fcc4b65434b2f3efb8ececa92b417e8caf0b4", + "gen_hash_verilog": "sha256:b07789baaa6a8da13ef4f6463a3fc6c50688ff9f65a381e9ffb9dd91e50e4863", + "gen_hash_zig": "sha256:fadd5f667ca7b6ed44baba164d5d585cdcaafd5f28037d214d0b0d961186d20a", "module": "T27bConformanceLibmRounding", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-09T14:59:47Z", + "sealed_at": "2026-10-18T11:14:01Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:8957bfba1bea5bfb90db4b913c081bc08f302b2b4446b1a107eaff8eed25ea88", + "spec_hash": "sha256:38e07bbcdfd7304554cc26c7fb4f5a5263f3cb1e8bd5466fd38b39337ad6feff", "spec_path": "specs/tri/t27b/conformance/libm_rounding.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 5, - "total": 5 + "passed": 8, + "total": 8, + "vacuous": 0 }, "toolchain": { "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", diff --git a/.trinity/seals/conformance_T27bConformanceLibmStdMath.json b/.trinity/seals/conformance_T27bConformanceLibmStdMath.json index 960c55a8d5..fc48e3dd0a 100644 --- a/.trinity/seals/conformance_T27bConformanceLibmStdMath.json +++ b/.trinity/seals/conformance_T27bConformanceLibmStdMath.json @@ -1,9 +1,9 @@ { - "built_by": "t27c-bootstrap@0.5.2+5d5d2cdef", + "built_by": "t27c-bootstrap@0.5.2+8ea472041", "closure": [ { "spec": "specs/tri/t27b/libm.t27", - "spec_hash": "sha256:d4414973506e15dbe7e061ff41077da40fc844d55c4647c7ef970ff47192a378" + "spec_hash": "sha256:bb2d59aae61115c012a7062f1e7b19c6becb1985fcb5913109cc8dd4727601ee" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -14,7 +14,7 @@ "module": "T27bConformanceLibmStdMath", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-09T14:59:49Z", + "sealed_at": "2026-10-18T11:14:35Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:46d16893692257fab86e9688458ec165f34965c5642bb7271d6209bfb5d06180", "spec_path": "specs/tri/t27b/conformance/libm_std_math.t27", @@ -23,7 +23,8 @@ "failing": [], "forced": false, "passed": 7, - "total": 7 + "total": 7, + "vacuous": 0 }, "toolchain": { "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", diff --git a/.trinity/seals/conformance_T27bConformanceRemInt.json b/.trinity/seals/conformance_T27bConformanceRemInt.json index 7fa3e0aa1e..1751c69816 100644 --- a/.trinity/seals/conformance_T27bConformanceRemInt.json +++ b/.trinity/seals/conformance_T27bConformanceRemInt.json @@ -1,24 +1,25 @@ { - "built_by": "t27c-bootstrap@0.5.2+9a73c9883", + "built_by": "t27c-bootstrap@0.5.2+8ea472041", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:ffa76615872926b972027c331c46ccc8877f5458f70709aba0f4d2585f08f4bb", - "gen_hash_rust": "sha256:024c952eafbf87cb41f1a226340bf64ef77df54bec6df58bfa3b62c73f1f196e", - "gen_hash_verilog": "sha256:820beadf7954d96fe8556827a621579239ab3fa60ded2aa99680bac06e913541", - "gen_hash_zig": "sha256:93d60e8fdff9193c0e1199191604ee87cc8558dc64d6617334264b010752f080", + "gen_hash_c": "sha256:fba2bc4ca42582b8fe1d740617af2bb690a604e6da93565cfb8027f512f95230", + "gen_hash_rust": "sha256:e1d78592d7020117c806c51d4f0eb88cd26f3a4e76b9357f465d7ca9146b7f86", + "gen_hash_verilog": "sha256:1d225dd532cb58ab45c4196116bd9a5a9e15f382a97a3d861b98501c9a61fba2", + "gen_hash_zig": "sha256:5bb64df244d6d26188a53c9e2503d26ddca665324ce2185142489298d21517ad", "module": "T27bConformanceRemInt", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-10T14:12:56Z", + "sealed_at": "2026-10-18T11:14:01Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:1d209c9a3fc42132de9dfe00125106cbea0fbb10acb409b9a34396a4a300c3d7", + "spec_hash": "sha256:6cb6dc2002cab87fb3dd6010105291dee503677b6851a9c74d29ab58def11fe3", "spec_path": "specs/tri/t27b/conformance/rem_int.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 4, - "total": 4 + "passed": 6, + "total": 6, + "vacuous": 0 }, "toolchain": { "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", diff --git a/.trinity/seals/t27b_T27bLibm.json b/.trinity/seals/t27b_T27bLibm.json index affcd63c93..c4e39481a1 100644 --- a/.trinity/seals/t27b_T27bLibm.json +++ b/.trinity/seals/t27b_T27bLibm.json @@ -1,24 +1,24 @@ { - "built_by": "t27c-bootstrap@0.5.2+39d5af36b", + "built_by": "t27c-bootstrap@0.5.2+8ea472041", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:58b23c2e16d47adc167ec6e2d0592af6438ea34b6de01e35668f25ef0cefd605", - "gen_hash_rust": "sha256:57296efd12854ef27cf9f180728b2fde77edc0c84feba39e1b80077052de8ce6", - "gen_hash_verilog": "sha256:0b9c80e3dae4f86e979c6bd6cc09eabd63ff6ce10286b0ee9a4622c48cbb5c74", - "gen_hash_zig": "sha256:f9b3decf7f1dd65f59a8d0bc97b678734626db5c04229f8180c766ce31e4f009", + "gen_hash_c": "sha256:693fb3a509396e1c81680e2dabf04bdee382bd54d312c21e20dd863452d48030", + "gen_hash_rust": "sha256:56811beafc887cb8773c9a21d9f5e4e4686fc94a550d359c0dbda2f0164db126", + "gen_hash_verilog": "sha256:88588ac711a6efe50d1eb069130c43b7ec0a63d2bed153190b1ff261796d2760", + "gen_hash_zig": "sha256:a29f31fedc07b5e0cf8709743216fa5726493984e06e5a864c910788e5c54bec", "module": "T27bLibm", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-11T04:13:20Z", + "sealed_at": "2026-10-18T11:13:57Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:62fa9277171c545081235a4021a976bd0234e724751489a3152835536d268aaf", + "spec_hash": "sha256:bb2d59aae61115c012a7062f1e7b19c6becb1985fcb5913109cc8dd4727601ee", "spec_path": "specs/tri/t27b/libm.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 29, - "total": 29, + "passed": 30, + "total": 30, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/t27b_T27bLibmPlan.json b/.trinity/seals/t27b_T27bLibmPlan.json index 68a52fcaba..d26692d9a8 100644 --- a/.trinity/seals/t27b_T27bLibmPlan.json +++ b/.trinity/seals/t27b_T27bLibmPlan.json @@ -1,24 +1,24 @@ { - "built_by": "t27c-bootstrap@0.5.2+39d5af36b", + "built_by": "t27c-bootstrap@0.5.2+8ea472041", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:6f70b1c72bee6a5a01f074d8bdf8133aa6503e21377719b1e0833b22b43b1f64", - "gen_hash_rust": "sha256:2419c5ee6968dccffe2236913208646b02f619ee73ca67885833d7c6796172eb", - "gen_hash_verilog": "sha256:bd7648b75bee2c18e802aa51e2e78767c71132870b6dab441994af9cf10af868", - "gen_hash_zig": "sha256:fddb93572b708c69c124bb1dc8cceee2211b23ca07b23790073b0ffb132acd04", + "gen_hash_c": "sha256:bba02f9b9f4a1572d001f52952d7378e102e7502e5ef8bfe61b0329c780615b9", + "gen_hash_rust": "sha256:aea1217193961c1c858a2545c41e5eb8050bd320abc98904d5bc9ad05c60aeb0", + "gen_hash_verilog": "sha256:9d337661883412424964dc7f532d2edaaedd6b7543198f63a77c757e459ab3e5", + "gen_hash_zig": "sha256:10892615534ebe25a212d555d7d4c4ee0726c3b9dc72f2ca29a3518a2e3e20c0", "module": "T27bLibmPlan", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-11T04:13:20Z", + "sealed_at": "2026-10-18T11:14:00Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:19682872c60a8286cc843f662560b50d619f9b0eccdf48b4881c2db17703f177", + "spec_hash": "sha256:cf0b297711d7619d177ff2089aabcbf7dd8674adfea4c4062d1a9ed9b42ea229", "spec_path": "specs/tri/t27b/libm_plan.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 15, - "total": 15, + "passed": 16, + "total": 16, "vacuous": 0 }, "toolchain": { diff --git a/cli/t27b/src/lower.rs b/cli/t27b/src/lower.rs index 691452e542..598be641c6 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 integer `@rem` is t27b's `%`): a call of its routine from libm.t27, compiler_rt's + /// 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 /// 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); @@ -3823,15 +3823,15 @@ impl<'a> Lower<'a> { Val::Cf(..) | Val::Ct(_) => xp::K_LITERAL, _ => xp::K_OTHER, }; - let named = if k == xp::K_F64 { "f64" } else { "f32" }; + let (named, ty0) = (if k == xp::K_F64 { "f64" } else { "f32" }, match &v { Val::E(e) => e.ty, _ if k == xp::K_F32 => Ty::F32, _ => Ty::F64 }); let base_e = n.children.get(1).filter(|c| c.name == "e").is_some_and(|c| self.std_const(c).is_some()); let a = xp::with_base(b, xp::plan(b, n.children.len(), k), base_e, n.children.first().is_some_and(|c| c.name == named)); 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(_)) => { - let mut args = if t { vec![] } else { vec![self.reg(v)?] }; + (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(if k == xp::K_F64 { Ty::F64 } else { Ty::F32 }))?; // `@rem(x, 2.0)`: the first one's type + 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 } })) diff --git a/gen/rust/tri/t27b/libm_plan.rs b/gen/rust/tri/t27b/libm_plan.rs index f252ba54c6..9c907e7d72 100644 --- a/gen/rust/tri/t27b/libm_plan.rs +++ b/gen/rust/tri/t27b/libm_plan.rs @@ -1,10 +1,10 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 15 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 16 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. -pub const BUILTINS: &'static str = "@exp @log std.math.nan std.math.isNan std.math.isPositiveInf std.math.isNegativeInf @floor @ceil @round @trunc @rem @log2 @log10 std.math.inf std.math.log1p std.math.log std.math.pow @cos"; +pub const BUILTINS: &'static str = "@exp @log std.math.nan std.math.isNan std.math.isPositiveInf std.math.isNegativeInf @floor @ceil @round @trunc @rem @log2 @log10 std.math.inf std.math.log1p std.math.log std.math.pow @cos @mod"; pub const EXP: u8 = 0; @@ -42,6 +42,8 @@ pub const POW: u8 = 16; pub const COS: u8 = 17; +pub const MOD: u8 = 18; + pub const PORT: &'static str = "t27b_libm_"; pub const HELPER: &'static str = "__t27b_libm_"; @@ -56,6 +58,10 @@ 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 CALL_F64: u8 = 0; pub const CALL_F32: u8 = 1; @@ -78,6 +84,10 @@ pub const OPERATOR: u8 = 9; pub const OPERATOR_TEXT: &'static str = "%"; +pub const CALL_I64: u8 = 10; + +pub const REFUSE_WIDTH: u8 = 11; + 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; @@ -89,6 +99,12 @@ pub fn kind(ty: &'static str) -> u8 { } k = (k + 1); } + if (ty.as_bytes()[0] == 117) { + return K_UINT; + } + 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] != 102) { return K_INT; } @@ -102,15 +118,15 @@ pub fn kind(ty: &'static str) -> u8 { } pub fn has_f32(b: u8) -> bool { - return ((b != LOG1P) && (b != COS)); + return (((b != LOG1P) && (b != COS)) && (b != MOD)); } pub fn has_f64(b: u8) -> bool { - return ((((b <= IS_NEG_INF) || (b == ROUND)) || ((b >= INF) && (b <= LOG_E))) || (b == COS)); + return ((((b <= CEIL) || (b == ROUND)) || ((b >= INF) && (b <= LOG_E))) || (b == COS)); } pub fn arity(b: u8) -> usize { - if (b == REM) { + if ((b == REM) || (b == MOD)) { return 2; } if (b == LOG_E) { @@ -150,9 +166,21 @@ pub fn plan(b: u8, operands: usize, k: u8) -> u8 { if (operands != arity(b)) { return REFUSE_ARITY; } - if ((k == K_INT) && (b == REM)) { + if ((((k == K_INT) || (k == K_UINT)) || (k == K_I64)) && (b == REM)) { + return OPERATOR; + } + if ((k == K_UINT) && (b == MOD)) { return OPERATOR; } + if ((k == K_I64) && (b == MOD)) { + return CALL_I64; + } + if ((k == K_INT) && (b == MOD)) { + return REFUSE_WIDTH; + } + if ((k == K_LITERAL) && (b == LOG)) { + return CALL_F64; + } if ((k == K_F64) && has_f64(b)) { return CALL_F64; } @@ -214,6 +242,15 @@ pub fn routine(b: u8, act: u8) -> &'static str { if ((b == CEIL) && (act == CALL_F32)) { return "__t27b_libm_ceilf"; } + if ((b == FLOOR) && (act == CALL_F64)) { + return "__t27b_libm_floor"; + } + if ((b == CEIL) && (act == CALL_F64)) { + return "__t27b_libm_ceil"; + } + if ((b == MOD) && (act == CALL_I64)) { + return "__t27b_libm_mod"; + } if ((b == ROUND) && (act == CALL_F32)) { return "__t27b_libm_roundf"; } @@ -311,12 +348,15 @@ pub fn what(b: u8) -> &'static str { if (b == COS) { return "ExprCall(@cos)"; } + if (b == MOD) { + return "ExprCall(@mod)"; + } return "ExprCall(libm)"; } pub fn why(act: u8) -> &'static str { if (act == REFUSE_ARITY) { - return "it takes one operand (`@rem` two, `std.math.log` and `std.math.pow` three)"; + return "it takes one operand (`@rem` and `@mod` two, `std.math.log` and `std.math.pow` three)"; } if (act == REFUSE_LITERAL) { return "the operand is a literal, which Zig types from the result location or folds at 128-bit precision"; @@ -325,10 +365,10 @@ pub fn why(act: u8) -> &'static str { return "the operand is not an f64 or an f32"; } if (act == REFUSE_NO_F32) { - return "libm.t27 has no f32 routine for it (std.math's log1p_32 and compiler_rt's cosf are not ported)"; + return "libm.t27 has no f32 routine for it (std.math's log1p_32, compiler_rt's cosf and a float `@mod` are not ported)"; } if (act == REFUSE_NO_F64) { - return "libm.t27 has no f64 routine for it (compiler_rt's is not ported)"; + 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"; @@ -336,6 +376,9 @@ pub fn why(act: u8) -> &'static str { 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"; + } return "its routine in specs/tri/t27b/libm.t27 did not lower"; } diff --git a/specs/tri/t27b/conformance/libm_rounding.t27 b/specs/tri/t27b/conformance/libm_rounding.t27 index 6f029ae8e2..85541a0907 100644 --- a/specs/tri/t27b/conformance/libm_rounding.t27 +++ b/specs/tri/t27b/conformance/libm_rounding.t27 @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 ; specs/tri/t27b/conformance/libm_rounding.t27 -- `@floor`, `@ceil`, `@round`, `@trunc` and `@rem` of an f32 against ; specs/tri/t27b/libm.t27's ports of compiler_rt `floorf`, `ceilf`, `roundf`, `truncf` and `fmodf` (gHashTag/t27#7819, -; part of #7746, a lane of #6063). Every test here passes `t27c test-report`; t27b must give the same verdict on -; each, or refuse the file. +; part of #7746, a lane of #6063), and `@floor` and `@ceil` of an f64 against its `floor` and `ceil` +; (#10405). Every test here passes `t27c test-report`; t27b must give the same verdict on each, or refuse +; the file. ; Under the reference each test holds a port (`use tri::t27b::libm;` brings it in) to Zig's builtin, bit for bit: -; on the t27c lab `@floor`, `@ceil` and `@trunc` are the `roundss` instruction, `@round` is compiler_rt `roundf`, -; `@rem` is compiler_rt `fmodf`. Under t27b the builtin is a call of the same port, under t27b's reserved name, so +; on the t27c lab `@floor`, `@ceil` and `@trunc` are the `roundss` instruction (`roundsd` for an f64), `@round` is +; compiler_rt `roundf`, `@rem` is compiler_rt `fmodf`. Under t27b the builtin is a call of the same port, under t27b's reserved name, so ; the sweeps hold t27b's run of the port to the reference's bits, and the fixed values below to Zig's. ; The tests live here, not in libm.t27, so that `t27b test --check` has a file's whole interpreter fuel for them. ; phi^2 + 1/phi^2 = 3 | TRINITY @@ -185,3 +186,69 @@ test rem_gives_the_reference_bits { const d: f32 = rem_of(f32_of(0xFF800001), 2.0); assert(d != d); } + +// The f64 ports against `@floor` and `@ceil`, bit for bit, over the bit patterns start, start + step, .. (n of them). +fn rd64_misses(start: u64, step: u64, n: u32) -> u32 { + var misses: u32 = 0; + var b: u64 = start; + var i: u32 = 0; + while (i < n) { + const x: f64 = f64_of(b); + if (bits_of_f64(t27b_libm_floor(x)) != bits_of_f64(@floor(x))) { + misses = misses + 1; + } + if (bits_of_f64(t27b_libm_ceil(x)) != bits_of_f64(@ceil(x))) { + misses = misses + 1; + } + b = b + step; + i = i + 1; + } + return misses; +} + +fn floor64_of(x: f64) -> f64 { + return @floor(x); +} + +fn ceil64_of(x: f64) -> f64 { + return @ceil(x); +} + +test f64_floor_and_ceil_agree_with_zig_on_every_exponent { + // Both signs, every exponent, the infinities and the NaNs: 0x0000000000000001 .. 0x7FFF...., then the negatives. + assert(rd64_misses(0x0000000000000001, 3074457345618258, 3000) == 0); + assert(rd64_misses(0x8000000000000001, 3074457345618258, 2990) == 0); +} + +test f64_floor_and_ceil_agree_near_one_and_near_two_to_the_52 { + // 0.25 .. 16 and -0.25 .. -16, then the last fractions below 2^52 and the first integers above it. + assert(rd64_misses(0x3FD0000000000000, 9007199254741, 3000) == 0); + assert(rd64_misses(0xBFD0000000000000, 9007199254741, 3000) == 0); + assert(rd64_misses(0x432FFFFFFFFFF000, 19, 600) == 0); + assert(rd64_misses(0xC32FFFFFFFFFF000, 19, 600) == 0); +} + +test f64_floor_and_ceil_give_the_reference_bits { + // As token_floor uses them: a row index from a fraction, a cell count rounded up. + assert(floor64_of(2.75) == 2.0); + assert(floor64_of(-0.5) == -1.0); + assert(floor64_of(-2.0) == -2.0); + assert(ceil64_of(2.25) == 3.0); + assert(ceil64_of(-2.75) == -2.0); + assert(ceil64_of(4503599627370495.5) == 4503599627370496.0); + assert(floor64_of(4503599627370495.5) == 4503599627370495.0); + assert(floor64_of(9007199254740993.0) == 9007199254740992.0); + // Signed zeros: a negative that rounds up to zero keeps its sign; a least subnormal floors to +0, ceils to 1. + assert(bits_of_f64(ceil64_of(-0.5)) == 0x8000000000000000); + assert(bits_of_f64(floor64_of(f64_of(0x8000000000000000))) == 0x8000000000000000); + assert(bits_of_f64(floor64_of(f64_of(0x0000000000000001))) == 0x0000000000000000); + assert(bits_of_f64(ceil64_of(f64_of(0x0000000000000001))) == 0x3FF0000000000000); + assert(bits_of_f64(floor64_of(f64_of(0x8000000000000001))) == 0xBFF0000000000000); + // A NaN stays a NaN, quieted with its payload kept; an infinity stays itself. + assert(bits_of_f64(floor64_of(f64_of(0x7FF0000000000001))) == 0x7FF8000000000001); + assert(bits_of_f64(ceil64_of(f64_of(0xFFF0000000000123))) == 0xFFF8000000000123); + assert(bits_of_f64(ceil64_of(f64_of(0x7FF8000000000000))) == 0x7FF8000000000000); + assert(floor64_of(f64_of(0xFFF0000000000000)) == f64_of(0xFFF0000000000000)); + assert(bits_of_f64(t27b_libm_floor(f64_of(0x7FF0000000000001))) == 0x7FF8000000000001); + assert(bits_of_f64(t27b_libm_ceil(-0.25)) == 0x8000000000000000); +} diff --git a/specs/tri/t27b/conformance/rem_int.t27 b/specs/tri/t27b/conformance/rem_int.t27 index 01c1236261..e106ed442a 100644 --- a/specs/tri/t27b/conformance/rem_int.t27 +++ b/specs/tri/t27b/conformance/rem_int.t27 @@ -4,8 +4,12 @@ ; `t27c test-report`; t27b must give the same verdict on each, or refuse the file. gen-zig prints `@rem` as written. ; - `@rem(a, b)` is the truncated remainder: the sign of the dividend, and `@divTrunc(a, b) * b + @rem(a, b) == a`, ; of signed and unsigned operands, with a literal beside a typed integer taking that integer's type. -; Not here (it fails the test, so no passing spec can hold it): a run-time zero divisor, which is a safety panic -; in Zig and a DivZero trap in t27b (specs/tri/t27b/libm_plan.t27 lowers the call as t27b's `%`). +; - `@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. +; 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 `%`). ; phi^2 + 1/phi^2 = 3 | TRINITY module T27bConformanceRemInt; @@ -59,3 +63,54 @@ test unsigned_and_narrow_operands { assert ri_rem_byte(-128, 3) == -2; assert ri_rem_byte(127, -10) == 7; } + +fn ri_mod(a: i64, b: i64) -> i64 { + return @mod(a, b); +} + +fn ri_umod(a: u32, b: u32) -> u32 { + return @mod(a, b); +} + +// specs/t27f/reel_particles.t27's `lcg`, as written. +fn ri_lcg(seed: i64) -> i64 { + return @mod(seed * 1103515245 + 12345, 2147483648); +} + +test mod_takes_the_sign_of_the_divisor { + assert ri_mod(7, 3) == 1; + assert ri_mod(-7, 3) == 2; + assert ri_mod(-6, 3) == 0; + assert ri_mod(6, 3) == 0; + assert ri_mod(2, 5) == 2; + assert ri_mod(-2, 5) == 3; + assert ri_mod(0, 7) == 0; + assert ri_mod(-1, 2147483648) == 2147483647; + assert ri_mod(-2147483649, 2147483648) == 2147483647; + assert ri_mod(9223372036854775807, 2) == 1; + assert ri_mod(-9223372036854775807, 2) == 1; + var a : i64 = -23; + while (a <= 23) { + const m : i64 = ri_mod(a, 6); + assert m >= 0; + assert m < 6; + assert @rem(a - m, 6) == 0; + a = a + 1; + } +} + +test mod_of_unsigned_and_the_lcg { + assert ri_umod(4000000000, 7) == 3; + assert ri_umod(5, 9) == 5; + assert ri_lcg(0) == 12345; + assert ri_lcg(1) == 1103527590; + assert ri_lcg(-1) == 1043980748; + var s : i64 = 42; + var i : i64 = 0; + while (i < 40) { + s = ri_lcg(s); + assert s >= 0; + assert s < 2147483648; + i = i + 1; + } +} diff --git a/specs/tri/t27b/libm.t27 b/specs/tri/t27b/libm.t27 index 07a4132872..b16f5dea7c 100644 --- a/specs/tri/t27b/libm.t27 +++ b/specs/tri/t27b/libm.t27 @@ -897,6 +897,84 @@ fn t27b_libm_trunc(t27b_x: f64) -> f64 { return @bitCast(t27b_u & (t27b_libm_ones64 ^ t27b_m)); } +// compiler_rt `floor` and `ceil` (f64) for specs/t27f/token_floor.t27 (#10405), as `floorf` and `ceilf` above, on +// 64 bits: `@floor` and `@ceil` of an f64 are `roundsd` on the t27c lab, so a NaN is quieted, as frintm and frintp +// quiet it. conformance/libm_rounding.t27 holds them to Zig's builtins on every exponent. +const t27b_libm_frac64: u64 = 0x000FFFFFFFFFFFFF; +const t27b_libm_negzero64: u64 = 0x8000000000000000; + +fn t27b_libm_floor(t27b_x: f64) -> f64 { + if (t27b_x != t27b_x) { + return t27b_x + t27b_x; + } + var t27b_u: u64 = @bitCast(t27b_x); + const t27b_be: i32 = @intCast((t27b_u >> 52) & 0x7FF); + const t27b_e: i32 = t27b_be - 1023; + if (t27b_e >= 52) { + return t27b_x; + } + if (t27b_e >= 0) { + const t27b_m: u64 = t27b_libm_frac64 >> t27b_e; + if ((t27b_u & t27b_m) == 0) { + return t27b_x; + } + if ((t27b_u >> 63) == 1) { + t27b_u = t27b_u + t27b_m; + } + return @bitCast(t27b_u & (t27b_libm_ones64 ^ t27b_m)); + } + if ((t27b_u >> 63) == 0) { + return 0.0; + } + if ((t27b_u & 0x7FFFFFFFFFFFFFFF) != 0) { + return -1.0; + } + return t27b_x; +} + +fn t27b_libm_ceil(t27b_x: f64) -> f64 { + if (t27b_x != t27b_x) { + return t27b_x + t27b_x; + } + var t27b_u: u64 = @bitCast(t27b_x); + const t27b_be: i32 = @intCast((t27b_u >> 52) & 0x7FF); + const t27b_e: i32 = t27b_be - 1023; + if (t27b_e >= 52) { + return t27b_x; + } + if (t27b_e >= 0) { + const t27b_m: u64 = t27b_libm_frac64 >> t27b_e; + if ((t27b_u & t27b_m) == 0) { + return t27b_x; + } + if ((t27b_u >> 63) == 0) { + t27b_u = t27b_u + t27b_m; + } + return @bitCast(t27b_u & (t27b_libm_ones64 ^ t27b_m)); + } + if ((t27b_u >> 63) != 0) { + return @bitCast(t27b_libm_negzero64); + } + if ((t27b_u & 0x7FFFFFFFFFFFFFFF) != 0) { + return 1.0; + } + return t27b_x; +} + +// `@mod` of two i64 (#10405, specs/t27f/reel_particles.t27's `lcg`): Zig's floored remainder, the sign of the +// divisor. Zig 0.16 Debug panics unless the divisor is positive; a zero divisor traps in `@rem` already. The +// truncated remainder is moved up by the divisor when it is negative, which cannot overflow: -b < r < 0. +fn t27b_libm_mod(t27b_a: i64, t27b_b: i64) -> i64 { + if (t27b_b < 0) { + @panic("remainder division by zero or negative value"); + } + const t27b_r: i64 = @rem(t27b_a, t27b_b); + if (t27b_r < 0) { + return t27b_r + t27b_b; + } + return t27b_r; +} + // pow.zig `isOddInteger`: |x| >= 2^53 is even; else `math.modf(x)` has no fraction and an odd integer part. fn t27b_libm_is_odd_integer(t27b_x: f64) -> bool { if (@abs(t27b_x) >= 9007199254740992.0) { @@ -1831,6 +1909,29 @@ test log_of_a_compile_time_operand_has_the_run_time_bits { assert(@log(near_one) == log_of(0.95)); } +// `@log` of a float literal (#10405, specs/t27f/token_floor.t27's `@log(10.0)`): Zig folds a comptime_float +// with compiler_rt `logq`, which is `log` of the operand cast to f64, so the folded value is an f64's exact decimal. +// An f128-precise fold would differ from it in the 17th digit; these decimals are the f64 results to the last digit. +test log_of_a_literal_is_the_f64_routine_s { + assert(@log(10.0) == 2.30258509299404590109361379290930926799774169921875); + assert(@log(2.0) == 0.69314718055994528622676398299518041312694549560546875); + assert(@log(3.0) == 1.0986122886681097821082175869378261268138885498046875); + assert(@log(0.95) == -0.05129329438755057812837634401148534379899501800537109375); + assert(@log(0.710994125) == -0.341091112222880299409411009037285111844539642333984375); + assert(@log(10.0) == log_of(10.0)); + assert(@log(2.0) == log_of(2.0)); + assert(@log(0.5) == log_of(0.5)); + assert(@log(1.0) == 0.0); + assert(@log(1.05) == log_of(1.05)); + assert(@log(0.7785005) == log_of(0.7785005)); + assert(@log(1.0e-300) == log_of(1.0e-300)); + assert(@log(1.0e300) == log_of(1.0e300)); + assert(@log(2.718281828459045) == log_of(2.718281828459045)); + // As token_floor writes it: a typed operand over a literal's. + const x: f64 = 1234.5; + assert(@log(x) / @log(10.0) == log_of(1234.5) / log_of(10.0)); +} + invariant log_folded_at_compile_time_is_compiler_rt: log_of(10.0) == 2.302585092994046; test cross_entropy_of_a_one_hot_target { diff --git a/specs/tri/t27b/libm_plan.t27 b/specs/tri/t27b/libm_plan.t27 index b9d7236892..a80763ad13 100644 --- a/specs/tri/t27b/libm_plan.t27 +++ b/specs/tri/t27b/libm_plan.t27 @@ -16,7 +16,9 @@ ; `floorf`, `ceilf`, `roundf`, `truncf` and `fmodf`. On the t27c lab the reference's `@floor`, `@ceil` and ; `@trunc` are the `roundss` instruction, which the ports match bit for bit (a NaN quieted, as it does); ; `@round` and `@rem` are those compiler_rt calls. `@rem` takes two operands: the second takes the first -; one's type, as a literal beside it does in Zig. Their f64 routines are not ported: an f64 is refused. +; one's type, as a literal beside it does in Zig. Of their f64 routines `floor` and `ceil` are ported (#10405, +; specs/t27f/token_floor.t27: `@floor` and `@ceil` of an f64 are `roundsd` on the t27c lab); an f64 `@round` calls +; `round` (below), and an f64 `@trunc` or `@rem` is refused. ; `@rem` of a typed integer (#8627, for specs/math/constants.t27's `@rem(e, 2)`) is Zig's truncated remainder: ; the sign of the dividend, `@divTrunc(a, b) * b + @rem(a, b) == a`, and a safety panic on a zero divisor. That ; is t27b's own `%` (ArithOp::Rem, whose zero divisor traps), so the answer is OPERATOR: the glue lowers the call @@ -24,6 +26,14 @@ ; integer takes its type). MIN rem -1 is 0, as Zig folds it; Zig's run-time result there is the backend's, and ; no spec holds it. A comptime_int first operand stays refused (REFUSE_LITERAL), as a float literal is. ; Pinned by specs/tri/t27b/conformance/rem_int.t27. +; `@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. +; `@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 +; to the exact decimal of that f64, which a value folded at 128-bit precision would miss. ; `@log`, `@log2` and `@log10` of an f32 (#7822) call libm.t27's ports of compiler_rt `logf`, `log2f` and ; `log10f`, the fdlibm algorithms on the bits (not the table-driven f64 `log`). An f64 `@log2` or `@log10` is ; refused: compiler_rt's f64 `log2` and `log10` are not ported. @@ -62,10 +72,10 @@ ; The `std.math` fns take an f64 or an f32 the same way, and `nan` its type. For `logf`, `log2f` and ; `log10f` it was measured again for #7822: a comptime-folded `@log`, `@log2` and `@log10` of a typed f32 ; equal the run-time call on 24000 points each (every exponent of both signs, and densely near 1), 0 differ. -; Refused: any other number of operands than the builtin takes; a comptime_float or comptime_int operand (Zig takes -; its type from the result location, or folds it as a comptime_float at 128-bit precision, which t27b -; does not model); any other operand (an integer other than `@rem`'s, which Zig refuses, a bool, an aggregate, a type -; other than f64 and f32); an f64 `@floor`, `@ceil`, `@trunc`, `@rem`, `@log2`, `@log10` or `std.math.pow`; an +; Refused: any other number of operands than the builtin takes; a comptime_float or comptime_int operand but +; `@log`'s float literal (Zig takes its type from the result location, or folds it as a comptime_float, which +; t27b does not model); any other operand (an integer other than `@rem`'s and `@mod`'s, which Zig refuses, a bool, an aggregate, a type +; other than f64 and f32); an f64 `@trunc`, `@rem`, `@log2`, `@log10` or `std.math.pow`; an ; f32 `std.math.log1p` or `@cos`; and a `std.math.log` whose base is not `std.math.e` or whose type is not its value's. ; -- ; The oracle is draft PR #7426 (branch claude/t27b-lane1-exp, 3f403d6c8): its hand-written lowering passed @@ -81,7 +91,7 @@ module T27bLibmPlan; /// The builtins and `std.math` fns whose routines libm.t27 holds, separated by one space. A builtin's code is /// its position. -pub const BUILTINS : str = "@exp @log std.math.nan std.math.isNan std.math.isPositiveInf std.math.isNegativeInf @floor @ceil @round @trunc @rem @log2 @log10 std.math.inf std.math.log1p std.math.log std.math.pow @cos"; +pub const BUILTINS : str = "@exp @log std.math.nan std.math.isNan std.math.isPositiveInf std.math.isNegativeInf @floor @ceil @round @trunc @rem @log2 @log10 std.math.inf std.math.log1p std.math.log std.math.pow @cos @mod"; pub const EXP : u8 = 0; pub const LOG : u8 = 1; pub const NAN : u8 = 2; @@ -101,19 +111,23 @@ pub const LOG1P : u8 = 14; pub const LOG_E : u8 = 15; pub const POW : u8 = 16; pub const COS : u8 = 17; +/// `@mod` of two integers (#10405). +pub const MOD : u8 = 18; /// The prefix of every routine in libm.t27, and the one its copies take in a program. 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 integer (`kind` reads the type). For `nan`, the type its operand names: f64, +// 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). pub const K_F64 : u8 = 0; pub const K_F32 : u8 = 1; pub const K_LITERAL : u8 = 2; pub const K_OTHER : u8 = 3; pub const K_INT : u8 = 4; +pub const K_UINT : u8 = 5; +pub const K_I64 : u8 = 6; // What the call lowers to: a call of the f64 or the f32 routine, or a refusal. pub const CALL_F64 : u8 = 0; @@ -128,6 +142,9 @@ 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. +pub const CALL_I64 : u8 = 10; +pub const REFUSE_WIDTH : u8 = 11; /// 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 @@ -139,27 +156,29 @@ pub fn kind(ty: str) -> u8 { if (ty[k] < 48 or ty[k] > 57) { return K_OTHER; } k = k + 1; } + 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] != 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; } return K_OTHER; } -/// Does libm.t27 hold builtin b's routine for an f32? All but `std.math.log1p`'s and `@cos`'s: std.math's -/// `log1p_32` and compiler_rt's `cosf` are not ported. +/// Does libm.t27 hold builtin b's routine for an f32? All but `std.math.log1p`'s, `@cos`'s and `@mod`'s: std.math's +/// `log1p_32` and compiler_rt's `cosf` are not ported, and a float `@mod` is not either. pub fn has_f32(b: u8) -> bool { - return b != LOG1P and b != COS; + return b != LOG1P and b != COS and b != MOD; } -/// Does libm.t27 hold builtin b's routine for an f64? `@floor`, `@ceil`, `@trunc`, `@rem`, `@log2`, `@log10` and -/// `std.math.pow` have only their f32 one. +/// Does libm.t27 hold builtin b's routine for an f64? `@trunc`, `@rem`, `@log2`, `@log10` and `std.math.pow` have +/// only their f32 one, and `@mod` neither. pub fn has_f64(b: u8) -> bool { - return b <= IS_NEG_INF or b == ROUND or (b >= INF and b <= LOG_E) or b == COS; + return b <= CEIL or b == ROUND or (b >= INF and b <= LOG_E) or b == COS; } /// How many operands builtin b takes. pub fn arity(b: u8) -> usize { - if (b == REM) { return 2; } + if (b == REM or b == MOD) { return 2; } if (b == LOG_E) { return 3; } if (b == POW) { return 3; } return 1; @@ -189,7 +208,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 and b == REM) { return OPERATOR; } + if ((k == K_INT or k == K_UINT or k == K_I64) 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_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; } if (k == K_F64) { return REFUSE_NO_F64; } @@ -214,6 +237,9 @@ pub fn routine(b: u8, act: u8) -> str { if (b == IS_NEG_INF and act == CALL_F32) { return "__t27b_libm_isneginff"; } if (b == FLOOR and act == CALL_F32) { return "__t27b_libm_floorf"; } 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 == 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"; } @@ -250,18 +276,20 @@ pub fn what(b: u8) -> str { if (b == LOG_E) { return "ExprCall(std.math.log)"; } if (b == POW) { return "ExprCall(std.math.pow)"; } if (b == COS) { return "ExprCall(@cos)"; } + if (b == MOD) { return "ExprCall(@mod)"; } return "ExprCall(libm)"; } /// Why: the words after the builtin's name. pub fn why(act: u8) -> str { - if (act == REFUSE_ARITY) { return "it takes one operand (`@rem` two, `std.math.log` and `std.math.pow` three)"; } + if (act == REFUSE_ARITY) { return "it takes one operand (`@rem` and `@mod` two, `std.math.log` and `std.math.pow` three)"; } if (act == REFUSE_LITERAL) { return "the operand is a literal, which Zig types from the result location or folds at 128-bit precision"; } if (act == REFUSE_OPERAND) { return "the operand is not an f64 or an f32"; } - if (act == REFUSE_NO_F32) { return "libm.t27 has no f32 routine for it (std.math's log1p_32 and compiler_rt's cosf are not ported)"; } - if (act == REFUSE_NO_F64) { return "libm.t27 has no f64 routine for it (compiler_rt's is not ported)"; } + if (act == REFUSE_NO_F32) { return "libm.t27 has no f32 routine for it (std.math's log1p_32, compiler_rt's cosf and a float `@mod` are not ported)"; } + 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"; } return "its routine in specs/tri/t27b/libm.t27 did not lower"; } @@ -325,10 +353,10 @@ test the_rounding_builtins_and_rem_call_their_f32_routines { assert std.mem.eql(u8, routine(TRUNC, CALL_F32), "__t27b_libm_truncf"); assert std.mem.eql(u8, routine(REM, CALL_F32), "__t27b_libm_fmodf"); // No f64 routine: refused, and named so. - assert plan(FLOOR, 1, K_F64) == REFUSE_NO_F64; + assert plan(TRUNC, 1, K_F64) == REFUSE_NO_F64; assert plan(REM, 2, K_F64) == REFUSE_NO_F64; assert routine(ROUND, REFUSE_NO_F64).len == 0; - assert !has_f64(FLOOR); + assert !has_f64(TRUNC); assert !has_f64(REM); assert has_f64(IS_NEG_INF); // `@rem` takes two operands, the others one. @@ -401,7 +429,6 @@ test the_log_family_of_an_f32_calls_compiler_rt_s_fdlibm_routines { test a_literal_and_any_other_operand_are_refused { assert plan(EXP, 1, K_LITERAL) == REFUSE_LITERAL; assert plan(EXP, 1, K_OTHER) == REFUSE_OPERAND; - assert plan(LOG, 1, K_LITERAL) == REFUSE_LITERAL; assert plan(LOG, 1, K_OTHER) == REFUSE_OPERAND; assert routine(EXP, REFUSE_LITERAL).len == 0; assert routine(EXP, REFUSE_OPERAND).len == 0; @@ -416,14 +443,14 @@ test the_arity_is_asked_first { assert plan(LOG, 2, K_F32) == REFUSE_ARITY; } -// Over the eighteen builtins, 0..3 operands and the four kinds: 27 of the 288 cases call a routine (an f64 -// for each of the first six, `@round`, `inf`, `log1p`, `std.math.log` and `@cos`; an f32 for each but `log1p` and -// `@cos`), all with the builtin's own number of operands. Every other case is a refusal, which names no routine and +// Over the nineteen builtins, 0..3 operands and the four kinds: 30 of the 304 cases call a routine (an f64 +// for each of the first eight, `@round`, `inf`, `log1p`, `std.math.log` and `@cos`, and for `@log`'s literal; an f32 +// for each but `log1p`, `@cos` and `@mod`), all with the builtin's own number of operands. Every other case is a refusal, which names no routine and // says why. test only_a_typed_float_with_a_routine_calls { var calls : u32 = 0; var b : u8 = 0; - while (b < 18) { + while (b < 19) { var ops : usize = 0; while (ops < 4) { var i : u32 = 0; @@ -432,7 +459,7 @@ test only_a_typed_float_with_a_routine_calls { if (act == CALL_F64 or act == CALL_F32) { calls = calls + 1; assert ops == arity(b); - assert (act == CALL_F64) == (lp_kind(i) == K_F64); + assert (act == CALL_F64) == (lp_kind(i) == K_F64 or (b == LOG and lp_kind(i) == K_LITERAL)); assert (act == CALL_F32) == (lp_kind(i) == K_F32); assert act == CALL_F32 or has_f64(b); assert std.mem.indexOf(u8, routine(b, act), HELPER) != null; @@ -447,11 +474,11 @@ test only_a_typed_float_with_a_routine_calls { } b = b + 1; } - assert calls == 27; + assert calls == 30; } test the_routines_are_renamed_into_the_reserved_prefix { - assert std.mem.eql(u8, BUILTINS, "@exp @log std.math.nan std.math.isNan std.math.isPositiveInf std.math.isNegativeInf @floor @ceil @round @trunc @rem @log2 @log10 std.math.inf std.math.log1p std.math.log std.math.pow @cos"); + assert std.mem.eql(u8, BUILTINS, "@exp @log std.math.nan std.math.isNan std.math.isPositiveInf std.math.isNegativeInf @floor @ceil @round @trunc @rem @log2 @log10 std.math.inf std.math.log1p std.math.log std.math.pow @cos @mod"); assert EXP == 0; assert LOG == 1; assert NAN == 2; @@ -461,6 +488,7 @@ test the_routines_are_renamed_into_the_reserved_prefix { assert LOG_E == 15; assert POW == 16; assert COS == 17; + assert MOD == 18; assert std.mem.eql(u8, PORT, "t27b_libm_"); assert std.mem.eql(u8, HELPER, "__t27b_libm_"); assert std.mem.indexOf(u8, HELPER, PORT) != null; @@ -472,8 +500,8 @@ test the_routines_are_renamed_into_the_reserved_prefix { assert !std.mem.eql(u8, routine(EXP, CALL_F64), routine(EXP, CALL_F32)); assert !std.mem.eql(u8, routine(EXP, CALL_F64), routine(LOG, CALL_F64)); assert !std.mem.eql(u8, routine(IS_POS_INF, CALL_F32), routine(IS_NEG_INF, CALL_F32)); - assert routine(COS + 1, CALL_F64).len == 0; - assert routine(COS + 1, CALL_F32).len == 0; + assert routine(MOD + 1, CALL_F64).len == 0; + assert routine(MOD + 1, CALL_F32).len == 0; assert std.mem.indexOf(u8, routine(REM, CALL_F32), PORT) != null; } @@ -496,7 +524,8 @@ test the_refusals_name_the_call_and_say_why { assert std.mem.eql(u8, what(LOG_E), "ExprCall(std.math.log)"); assert std.mem.eql(u8, what(POW), "ExprCall(std.math.pow)"); assert std.mem.eql(u8, what(COS), "ExprCall(@cos)"); - assert std.mem.eql(u8, what(COS + 1), "ExprCall(libm)"); + assert std.mem.eql(u8, what(MOD), "ExprCall(@mod)"); + assert std.mem.eql(u8, what(MOD + 1), "ExprCall(libm)"); assert std.mem.indexOf(u8, why(REFUSE_BASE), "std.math.e") != null; assert std.mem.indexOf(u8, why(REFUSE_ARITY), "three") != null; assert std.mem.indexOf(u8, why(REFUSE_NO_F32), "no f32 routine") != null; @@ -530,7 +559,7 @@ test inf_log1p_log_e_and_an_f64_round_call_their_routines { assert std.mem.eql(u8, routine(ROUND, CALL_F64), "__t27b_libm_round"); assert has_f64(ROUND); assert !has_f64(TRUNC); - assert !has_f64(CEIL); + assert has_f64(CEIL); // std.math.log(T, base, x): three operands, the value third, `log`'s routine. assert arity(LOG_E) == 3; assert operand(LOG_E) == 2; @@ -563,6 +592,8 @@ test std_math_log_needs_base_e_and_its_value_s_type { test an_integer_rem_is_t27b_s_remainder_operator { assert plan(REM, 2, K_INT) == OPERATOR; + assert plan(REM, 2, K_UINT) == OPERATOR; + assert plan(REM, 2, K_I64) == OPERATOR; assert std.mem.eql(u8, OPERATOR_TEXT, "%"); assert routine(REM, OPERATOR).len == 0; assert plan(REM, 1, K_INT) == REFUSE_ARITY; @@ -578,9 +609,13 @@ test an_integer_rem_is_t27b_s_remainder_operator { test the_kind_is_read_from_the_type_s_name { assert kind("f64") == K_F64; assert kind("f32") == K_F32; - assert kind("i64") == K_INT; - assert kind("u8") == K_INT; - assert kind("u7") == K_INT; + assert kind("i64") == K_I64; + assert kind("u8") == K_UINT; + assert kind("u7") == K_UINT; + 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("bool") == K_OTHER; assert kind("ptr") == K_OTHER; @@ -590,3 +625,36 @@ test the_kind_is_read_from_the_type_s_name { assert kind("u3x") == K_OTHER; assert kind("") == K_OTHER; } + +// #10405: specs/t27f/token_floor.t27's f64 `@floor` and `@ceil`, its `@log(10.0)`, and reel_particles' i64 `@mod`. +test token_floor_s_floor_ceil_log_literal_and_mod { + assert plan(FLOOR, 1, K_F64) == CALL_F64; + assert plan(CEIL, 1, K_F64) == CALL_F64; + assert std.mem.eql(u8, routine(FLOOR, CALL_F64), "__t27b_libm_floor"); + assert std.mem.eql(u8, routine(CEIL, CALL_F64), "__t27b_libm_ceil"); + assert has_f64(FLOOR); + assert !std.mem.eql(u8, routine(FLOOR, CALL_F64), routine(FLOOR, CALL_F32)); + // A float literal: only `@log` calls, its f64 routine; the others stay refused. + assert plan(LOG, 1, K_LITERAL) == CALL_F64; + assert std.mem.eql(u8, routine(LOG, plan(LOG, 1, K_LITERAL)), "__t27b_libm_log"); + 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. + 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; + assert plan(MOD, 2, K_F32) == REFUSE_NO_F32; + assert plan(MOD, 2, K_LITERAL) == REFUSE_LITERAL; + assert plan(MOD, 1, K_I64) == REFUSE_ARITY; + assert plan(MOD, 2, K_OTHER) == REFUSE_OPERAND; + assert !has_f64(MOD); + assert !has_f32(MOD); + assert routine(REM, CALL_I64).len == 0; + assert with_base(MOD, CALL_I64, false, false) == CALL_I64; +}