Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .trinity/seals/conformance_T27bConformanceDiscardValue.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
{
"built_by": "t27c-bootstrap@0.5.2+cc19d1fcd",
"built_by": "t27c-bootstrap@0.5.2+2406a4804",
"closure": [],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:4ba0526ee65484a6caff69638d9d2547e4be110c634943ddb8d875e5cff80bde",
"gen_hash_rust": "sha256:904ee5dd1e21938aa8a83e79a5a9691e441cbcfc0a2a6dae9de349e6d8611329",
"gen_hash_verilog": "sha256:29acc6a1c4265c46bb0d548c327f7579b3e0925222ebfc244666a231ccf1b0fc",
"gen_hash_zig": "sha256:dd98f3ff424315514c3d1a45e9420445e99aaf3394bdf699b75ab606e6b5eff5",
"gen_hash_c": "sha256:6e798a1fffefceac7b1dc0cc004b1b3fb374ab646afab2a963b310ebea7e9fc5",
"gen_hash_rust": "sha256:b0b38fb1a793859907e948d4ff4020ca80bd506e4596b63e4d889b5ea7ecbcf6",
"gen_hash_verilog": "sha256:7d6be8a04c789770cccd45784b62f941ed986dd880786b85fa70ea2841430032",
"gen_hash_zig": "sha256:3e31bd84b0b55af614dd1b80cbb7e59e54000419f783b18a602475887989d45d",
"module": "T27bConformanceDiscardValue",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-11T10:29:42Z",
"sealed_at": "2026-10-18T12:10:45Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:a5f6edc44c94854f3408c2a3865e16cf5dd79b391e97f8ad660343c6319b963c",
"spec_hash": "sha256:6fc308ef2a9f7b74c4434bf5f3b5d6b3d2f2f8cc353d7ba9ada98b54100c4420",
"spec_path": "specs/tri/t27b/conformance/discard_value.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 5,
"total": 5,
"passed": 7,
"total": 7,
"vacuous": 0
},
"toolchain": {
Expand Down
18 changes: 9 additions & 9 deletions .trinity/seals/t27b_T27bBuiltinPlan.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
{
"built_by": "t27c-bootstrap@0.5.2+933019ba0",
"built_by": "t27c-bootstrap@0.5.2+2406a4804",
"closure": [],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:d054e3e99ffff92cd9b93cc556a8506f748ceee680268be7b46ee0d6b78b14ac",
"gen_hash_rust": "sha256:044dd36292ab58299abd87917b32adb6cb35d2cfbdfe94bc92a649c9b5d7ceea",
"gen_hash_verilog": "sha256:94722616c003934462bb0cd6e5f7caefcc2a49af6552bd501d682d998097069a",
"gen_hash_zig": "sha256:5fdfea1b1db111f0b4a6da94a23cd6f17f1d8ddce96ea9346f124d5a3c0e309f",
"gen_hash_c": "sha256:6bf58071429d33fc3905ff0138bfb71b0235892b4eeadf59a364a379eb5ae619",
"gen_hash_rust": "sha256:1acc36ea764234af3ee1ab370e597112d2ee479c0889cd1a095feb8a76e8dce3",
"gen_hash_verilog": "sha256:2d125304b7ec1f801389b8291057cc11734f087cc50216cb7987092023c15958",
"gen_hash_zig": "sha256:5b6a34a34439e6c77854b22b56402c5c02efb9e1397f7b2406c2cf2abd37408d",
"module": "T27bBuiltinPlan",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-11T11:36:25Z",
"sealed_at": "2026-10-18T12:10:40Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:8db4e5d5388dff99686aaa49f321c8cc9b9a8037ebd1a36739a0c39806542c67",
"spec_hash": "sha256:2b60d22aa2a5c3257a35493afcef91cf2676d80dc90c2398dcc651de680ff2dc",
"spec_path": "specs/tri/t27b/builtin_plan.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 21,
"total": 21,
"passed": 25,
"total": 25,
"vacuous": 0
},
"toolchain": {
Expand Down
28 changes: 28 additions & 0 deletions .trinity/seals/t27b_T27bDebugPrintPlan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"built_by": "t27c-bootstrap@0.5.2+2406a4804",
"closure": [],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:8e292206b9a88366682ee75d3f362191173ed1aae697d41687417e51a4699ee6",
"gen_hash_rust": "sha256:e5ea6380433b7c19ae66a4d87662428b91aea2be3b061d71826819f92a100220",
"gen_hash_verilog": "sha256:d4deff22a0420527e95a7a54f01407910590fd45c1c820d153443d4d7034a7f4",
"gen_hash_zig": "sha256:4be8ca1150a9df8996c1ec57444d889159f6cc000f5060812a2d01df027e71ce",
"module": "T27bDebugPrintPlan",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-18T12:10:44Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:a477d355cc161a2692690d1f9d2a19c96d4278d57a1e3863554ac4faf9bfd669",
"spec_path": "specs/tri/t27b/debug_print_plan.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 22,
"total": 22,
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d",
"zig": "0.16.0"
}
}
107 changes: 40 additions & 67 deletions cli/t27b/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ mod lp; // t27c gen-rust of specs/tri/t27b/literal_plan.t27: a literal's text as
mod rv; // t27c gen-rust of specs/tri/t27b/ref_var_plan.t27: a local the body assigns is a `var`, scalars too
#[path = "../../../gen/rust/tri/t27b/discard_plan.rs"] #[allow(dead_code, unused_parens)]
mod dp; // t27c gen-rust of specs/tri/t27b/discard_plan.t27: `_ = e;`, deleted where the reference deletes it
#[path = "../../../gen/rust/tri/t27b/debug_print_plan.rs"] #[allow(dead_code, unused_parens)]
mod dbg; // t27c gen-rust of specs/tri/t27b/debug_print_plan.t27: `std.debug.print`
#[path = "../../../gen/rust/tri/t27b/lazy_sig_plan.rs"] #[allow(dead_code, unused_parens)]
mod ls; // t27c gen-rust of specs/tri/t27b/lazy_sig_plan.t27: `anytype`, `[*]T` on a fn nothing analyzed reaches
#[path = "../../../gen/rust/tri/t27b/any_param_plan.rs"] #[allow(dead_code, unused_parens)]
Expand Down Expand Up @@ -2618,7 +2620,33 @@ impl<'a> Lower<'a> {
out.push(Stmt::Eval(call));
return Ok(());
}
match self.expr(e)? {
let v = self.expr(e)?;
self.drop_value(v, out)
}

/// `std.debug.print(fmt, .{ args })` (debug_print_plan.t27): its arguments run in order, once the format fits them.
fn debug_print(&mut self, c: &Node, out: &mut Vec<Stmt>) -> R<()> {
self.see(c);
let (lit, tup) = (c.children.first().filter(|f| f.kind == NodeKind::ExprLiteral && f.extra_kind == "string"), c.children.get(1).filter(|t| t.kind == NodeKind::ExprTuple));
let mut act = dbg::plan(!self.unanalyzed_fn, c.children.len(), lit.is_some(), tup.is_some());
if let (dbg::CHECK, Some(t), Some(f)) = (act, tup, lit) {
let mut kinds = Vec::new();
for a in &t.children {
let v = self.expr(a)?;
kinds.push(match &v {
Val::Ct(_) => dbg::K_INT, Val::Cf(_) => dbg::K_FLOAT,
Val::E(Expr { ty, .. }) | Val::M(Place { ty: LTy::S(ty), .. }) => dbg::scalar(*ty == Ty::Bool, ty.is_float(), ty.is_int()),
_ if self.is_str(&v) || matches!(&v, Val::M(Place { ty: LTy::Slice(e, _) | LTy::Arr(e, _), .. }) if **e == LTy::S(Ty::U8)) => dbg::K_BYTES, _ => dbg::K_OTHER,
});
self.drop_value(v, out)?;
}
act = dbg::check(f.value.as_bytes(), &kinds);
}
if matches!(act, dbg::SKIP | dbg::EVAL) { Ok(()) } else { self.reject(dbg::what(act), dbg::why(act).to_string()) }
}

fn drop_value(&mut self, v: Val, out: &mut Vec<Stmt>) -> R<()> {
match v {
Val::Poison => return Err(()),
Val::E(x) | Val::P(x, _) => out.push(Stmt::Eval(x)),
Val::M(p) if !pure_addr(&p.addr) => out.push(Stmt::Eval(p.addr)),
Expand All @@ -2627,7 +2655,6 @@ impl<'a> Lower<'a> {
Ok(())
}

/// `undefined;`, the body stub a port leaves where plumbing was. t27c's
/// Zig backend emits it as is, and Zig rejects it ("value of type
/// '@TypeOf(undefined)' ignored") only in a fn it analyzes, i.e. one
/// something analyzed references. In a fn nothing reaches, the reference
Expand Down Expand Up @@ -2951,71 +2978,24 @@ impl<'a> Lower<'a> {
fn call_stmt(&mut self, c: &Node, discard: bool, out: &mut Vec<Stmt>) -> R<()> {
self.see(c);
match c.name.as_str() {
// `@compileAssert` is not a separate construct in the reference:
// t27c's Zig backend lowers it through the very arm that lowers
// `assert` (`node.name == "@compileAssert" || node.name ==
// "assert"`), so both give the same `if (!(cond))` check. Inside
// an `invariant` that check sits in a `comptime` block and a false
// condition fails the reference's compile; here it fails the
// invariant when run. Either way the file does not pass, and the
// operands are held to the same comptime rules (`self.comptime`).
// `@compileAssert` is `assert` in the reference (builtin_plan.t27), under the same comptime rules.
"assert" | "@compileAssert" => {
// `assert(cond, "msg")`: t27c's Zig backend emits
// `if (!(cond)) @panic("msg")` and never evaluates the
// message, so the verdict is the one-argument assert's. Only a
// string-literal message is accepted: anything else would be a
// value the reference silently drops, and matching that is a
// claim this backend has not checked.
match c.children.len() {
1 => {}
2 => {
let m = &c.children[1];
if m.kind != NodeKind::ExprLiteral || m.extra_kind != "string" {
return self.reject(
"ExprCall(assert with non-literal message)",
format!("assert message is a {:?}, not a string literal", m.kind),
);
}
}
n => {
return self.reject("ExprCall(assert with message)", format!("assert with {} arguments", n));
}
}
let act = bp::assert_stmt(c.children.len(), c.children.get(1).is_some_and(|m| m.kind == NodeKind::ExprLiteral && m.extra_kind == "string"));
if act != bp::A_OK { return self.reject(bp::assert_what(act), bp::assert_why(act).to_string()); }
let cond = self.cond(&c.children[0])?;
let site = self.site(TrapKind::Assert, "assert".into(), Ty::Bool);
out.push(Stmt::Assert { cond, site });
Ok(())
}
// `@setEvalBranchQuota(n);` raises the backward-branch budget of
// Zig's compile-time evaluation and does nothing at run time. t27b
// does not model that budget, so a higher one opens no gap: the
// statement lowers to nothing. Zig wants a compile-time `u32`
// operand; anything else is refused.
"@setEvalBranchQuota" => {
if c.children.len() != 1 {
return self.reject(
"ExprCall(@setEvalBranchQuota)",
format!("with {} arguments", c.children.len()),
);
}
let arg = &c.children[0];
let v = self.expr(arg)?;
let n = match &v {
Val::Poison => return Err(()),
Val::Ct(n) => Some(*n),
Val::E(Expr { ty, kind: ExprKind::Const(n) }) if ty.is_int() => {
Some(*n)
}
let n = match c.children.first().map(|a| self.expr(a)).transpose()? {
Some(Val::Poison) => return Err(()),
Some(Val::Ct(n)) => Some(n),
Some(Val::E(Expr { ty, kind: ExprKind::Const(n) })) if ty.is_int() => Some(n),
_ => None,
};
match n {
Some(n) if (0..=u32::MAX as i128).contains(&n) => Ok(()),
Some(n) => self.reject("ExprCall(@setEvalBranchQuota)", format!("of {}, outside u32", n)),
None => {
let d = self.val_desc(&v);
self.reject("ExprCall(@setEvalBranchQuota)", format!("of {}, not a compile-time integer", d))
}
}
let act = bp::quota_stmt(c.children.len(), n.is_some(), n.map_or(0, |n| i64::try_from(n).unwrap_or(-1)));
if act == bp::Q_OK { Ok(()) } else { self.reject(bp::QUOTA, bp::quota_why(act).to_string()) }
}
// `@panic("m");` the reference does not return (builtin_plan.t27): a trap that always fires.
"@panic" => {
Expand All @@ -3028,6 +3008,7 @@ impl<'a> Lower<'a> {
out.push(Stmt::Assert { cond: Expr { ty: Ty::Bool, kind: ExprKind::Const(0) }, site });
Ok(())
}
"std.debug.print" => self.debug_print(c, out),
"assert_eq" => {
if c.children.len() != 2 {
return self.reject(
Expand Down Expand Up @@ -3132,15 +3113,7 @@ impl<'a> Lower<'a> {
}
};
if c.children.len() != params.len() {
return self.reject(
"ExprCall",
format!(
"`{}` takes {} arguments, {} given",
c.name,
params.len(),
c.children.len()
),
);
return self.reject("ExprCall", format!("`{}` takes {} arguments, {} given", c.name, params.len(), c.children.len()));
}
let mut args = Vec::new();
for (i, a) in c.children.iter().enumerate() {
Expand Down
4 changes: 2 additions & 2 deletions cli/t27b/tests/tail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,11 +359,11 @@ fn f32_rounding_and_rem_call_their_ports() {

/// `_ = e;` (collect_type_decls.t27): at the top of a fn body the reference's dead-store pass deletes it unless e
/// holds a call; anywhere else e runs and its value is dropped (specs/tri/t27b/discard_plan.t27; `t27c test-report`
/// passes the conformance spec 5/5, none vacuous). A nested `_ = a + b;` that overflows traps on both sides.
/// passes the conformance spec 7/7, none vacuous). A nested `_ = a + b;` that overflows traps on both sides.
#[test]
fn a_discarded_value_runs_unless_the_reference_deletes_it() {
let r = run(include_str!("../../../specs/tri/t27b/conformance/discard_value.t27"));
assert!(r.len() == 5 && names_ok(&r).iter().all(|(_, inv, ok)| !inv && *ok), "{:?}", names_ok(&r));
assert!(r.len() == 7 && names_ok(&r).iter().all(|(_, inv, ok)| !inv && *ok), "{:?}", names_ok(&r));
let src = "module a;\n\nfn f(a: u8, b: u8) -> u8 {\n _ = a + b;\n return a;\n}\n\ntest t {\n assert(f(200, 100) == 200);\n}\n";
assert_eq!(names_ok(&run(src)), vec![("t", false, true)]);
let r = run(&src.replace(" _ = a + b;\n", " if (a > 0) {\n _ = a + b;\n }\n"));
Expand Down
5 changes: 3 additions & 2 deletions docs/reports/t27b_expectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": 75,
"max_not_pass": 74,
"entries": [
{"path": "specs/a/b_c.t27", "t27b": "pass"},
{"path": "specs/a_b/c.t27", "t27b": "pass"},
Expand Down Expand Up @@ -210,7 +210,7 @@
{"path": "specs/compiler/zig_empty_typed_array.t27", "t27b": "pass"},
{"path": "specs/compiler/zig_field_syntax.t27", "t27b": "pass"},
{"path": "specs/compiler/zig_float_call_cast.t27", "t27b": "pass"},
{"path": "specs/compiler/zig_open_slice.t27", "t27b": "blocked", "blocker": "ExprCall(std.*)", "reason": "unimplemented"},
{"path": "specs/compiler/zig_open_slice.t27", "t27b": "pass"},
{"path": "specs/compiler/zig_primitive_bindings.t27", "t27b": "pass"},
{"path": "specs/compiler/zig_scoped_type.t27", "t27b": "pass"},
{"path": "specs/compiler/zig_shadow_initializer.t27", "t27b": "pass"},
Expand Down Expand Up @@ -1659,6 +1659,7 @@
{"path": "specs/tri/t27b/conformance/wide_int_const.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/conformance/zig_keyword_field.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/const_div_plan.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/debug_print_plan.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/discard_plan.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/dogfood.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/elf.t27", "t27b": "pass"},
Expand Down
68 changes: 67 additions & 1 deletion gen/rust/tri/t27b/builtin_plan.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated from .t27 spec
// DO NOT EDIT — generated by t27c
// NOT LOWERED BY THIS BACKEND: 21 test(s), 0 invariant(s), 0 function(s) with no body.
// NOT LOWERED BY THIS BACKEND: 25 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.

Expand Down Expand Up @@ -248,6 +248,72 @@ pub fn panic_why(act: u8) -> &'static str {
return "";
}

pub const A_OK: u8 = 0;

pub const A_ARITY: u8 = 1;

pub const A_MESSAGE: u8 = 2;

pub fn assert_stmt(args: usize, literal: bool) -> u8 {
if (args == 1) {
return A_OK;
}
if (args != 2) {
return A_ARITY;
}
if !(literal) {
return A_MESSAGE;
}
return A_OK;
}

pub fn assert_what(act: u8) -> &'static str {
if (act == A_MESSAGE) {
return "ExprCall(assert with non-literal message)";
}
return "ExprCall(assert with message)";
}

pub fn assert_why(act: u8) -> &'static str {
if (act == A_MESSAGE) {
return "assert message is not a string literal";
}
return "assert takes a condition and at most a string-literal message";
}

pub const Q_OK: u8 = 0;

pub const Q_ARITY: u8 = 1;

pub const Q_NOT_CONST: u8 = 2;

pub const Q_RANGE: u8 = 3;

pub const QUOTA: &'static str = "ExprCall(@setEvalBranchQuota)";

pub fn quota_stmt(args: usize, ct: bool, n: i64) -> u8 {
if (args != 1) {
return Q_ARITY;
}
if !(ct) {
return Q_NOT_CONST;
}
if ((n < 0) || (n > 4294967295)) {
return Q_RANGE;
}
return Q_OK;
}

pub fn quota_why(act: u8) -> &'static str {
if (act == Q_ARITY) {
return "Zig's @setEvalBranchQuota takes one operand";
}
if (act == Q_NOT_CONST) {
return "the operand is not a compile-time integer";
}
return "the operand is outside u32";
}

pub const Z_SCALAR: u8 = 0;

pub const Z_STRUCT: u8 = 1;
Expand Down
Loading
Loading