wcc: collapse exprfloatkind structural body onto checker stamp (#121)

wwstage exprfloatkind now reads n.type_ via classifytinfo (typeisf32→1,
typeisfloat→2, else 0) — wwstage classifies float-ness from the cstage-
equal stamp; delete the 9-arm structural body + the transient bridge.

Permanent residual sibling-evidence guards: cgbin float-arith (primary,
fires on unstamped float operand), tupstore + cgwidentaggedstore float
arms (pins, unreachable today, contract assertions against future
regression). cgcast excluded (int→float CVTSI2SD legitimately nil-typed).

Closes the gate-blind cs≠ww float family root (STATUS:14, named (b)-phase
goal). Builds on 98e1665 (A-narrow N_IDENT destructure stamp) +
1c4cea4 (transient floatness-disagreement bridge).
This commit is contained in:
2026-05-26 18:30:18 +09:00
parent 1c4cea4e2c
commit 0a2747ce5b
5 changed files with 387 additions and 570 deletions

View File

@@ -2884,6 +2884,55 @@ fn cgbin(c: *cgen, n: *node) void = {
let fk: i32 = lfk;
if (fk == 0) { fk = rfk; };
if (fk != 0) {
// #121 (Package B) RESIDUAL sibling-evidence guard. The
// exprfloatkind collapse reads n.type_ as the SSoT; an
// UNSTAMPED operand (type_==nil) misclassifies as 0 and would
// silently take the integer arm of a float binop — exactly the
// gate-blind miscompile the structural oracle used to mask
// (and the bridge proved corpus-clean for at 1c4cea4). With
// the structural net gone, a future N_DOT-callee float-tuple
// destructure (`let (frac, exp) = math.frexpf64(x);` —
// blocked on #16/#17 N_DOT-callee stamp) would leave its
// bindings unstamped → fall through here without this assert.
// Predicate: operand whose own efk==0 (not classified as
// float) AND type_==nil (UNSTAMPED, not a stamped non-float
// like an int passed through a deliberate path). Sibling
// evidence: the binop is float (fk!=0), so the operand should
// either be float (efk!=0) or a stamped non-float (e.g. int);
// nil-typed is the dangerous case. cgcast (cgenexpr.ww:417) is
// EXCLUDED — int→float source is legitimately a CVTSI2SD
// target. Loud-abort idiom mirrors cgenstmt.ww:1405/1475 +
// asserttyped file:line at check.ww:3340-3344.
if (lfk == 0) { if (n.lhs != nil) { if (n.lhs.type_ == nil) {
let msg: str = "cgbin float-arith: lhs operand unstamped (#121 sibling-evidence) at ";
os.write(2, msg.ptr, msg.len: u64);
if (n.lhs.file.len > 0) {
os.write(2, n.lhs.file.ptr, n.lhs.file.len: u64);
os.write(2, ":".ptr, 1u64);
let ls: str = strconv.i32tos(n.lhs.line, strconv.base.DEC);
os.write(2, ls.ptr, ls.len: u64);
os.write(2, " ".ptr, 1u64);
};
let kn: str = nkname(n.lhs.kind);
os.write(2, kn.ptr, kn.len: u64);
os.write(2, "\n".ptr, 1u64);
os.exit(1);
}; }; };
if (rfk == 0) { if (n.rhs != nil) { if (n.rhs.type_ == nil) {
let msg: str = "cgbin float-arith: rhs operand unstamped (#121 sibling-evidence) at ";
os.write(2, msg.ptr, msg.len: u64);
if (n.rhs.file.len > 0) {
os.write(2, n.rhs.file.ptr, n.rhs.file.len: u64);
os.write(2, ":".ptr, 1u64);
let ls: str = strconv.i32tos(n.rhs.line, strconv.base.DEC);
os.write(2, ls.ptr, ls.len: u64);
os.write(2, " ".ptr, 1u64);
};
let kn: str = nkname(n.rhs.kind);
os.write(2, kn.ptr, kn.len: u64);
os.write(2, "\n".ptr, 1u64);
os.exit(1);
}; }; };
let mov: str = "MOVSD";
if (fk == 1) { mov = "MOVSS"; };
if (n.op == tkind.TK_PLUS ||