diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 8f349d31..0261a91a 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -12777,19 +12777,82 @@ export fn isf32type(c: *cgen, t: *node) bool = { return typeisf32(t.type_: *tinfo); }; +// classifytinfo — map a checker stamp (node.type_ as *tinfo) to the +// exprfloatkind value-class: f32 → 1, f64 → 2, integer/unknown/nil → 0. +// The TY_NAMED peel + TY_F32/F64 split live in typeisf32 / typeisfloat +// (typ.ww:437,350; cf. the split at check.ww:1323) — reuse them rather +// than re-implementing the peel (rule 13: route through the type table). +// #121 (Package B) collapse target: exprtype stamps each node's RESULT +// type on n.type_, so reading it here mirrors every structural arm at +// its actual node. Commit 2 uses it only to CROSS-CHECK the structural +// classifier (the consistency bridge below); commit 3 makes it the body. +fn classifytinfo(t: *tinfo) i32 = { + if (typeisf32(t)) { return 1; }; + if (typeisfloat(t)) { return 2; }; + return 0; +}; + // exprfloatkind — classify an expression's value-class so callers can -// pick float vs integer codegen without a full type system. Returns: -// 0 — integer-like (or unknown — same fallback the existing cgen -// takes today) -// 1 — f32 -// 2 — f64 +// pick float vs integer codegen without a full type system. Returns +// 0 integer-like/unknown, 1 f32, 2 f64. +// +// #121 (Package B) consistency BRIDGE — byte-id-NEUTRAL, TRANSIENT. +// The wwstage classifies float-ness STRUCTURALLY (exprfloatkindstruct), a +// gate-blind divergence from cstage's checker-stamp read (node_isfloat). +// Before flipping to the stamp (commit 3), this proves the stamp AGREES +// with the structural classifier on every float node the corpus exercises. +// A wrong float classification miscompiles silently and the bootstrap +// barely exercises floats, so the disagreement is surfaced LOUD, not +// absorbed. Whole bridge (struct body + this wrapper) is DELETED in +// commit 3; the residual sibling-evidence guard lands at that point as +// the permanent net. +// +// RELAXED condition — floatness-disagreement ONLY, NOT precision (Drew / +// Rob 2026-05-26): +// abort iff (structkind != 0) != (stampkind != 0). +// CAT A (float↔int): caught loud — a stamp-miss on a float node, or a +// stamp-extra on an int the structural arm correctly classifies as not- +// float; both are real bugs to surface. CAT B (f32-vs-f64 coarseness, +// e.g. structural N_FLOATLIT returns 2 unconditionally while stamp says +// 1 on a tsuffix-stamped 1.5f32): TOLERATED — the stamp is the +// authoritative source (cstage's single source), commit 3's flip +// converges toward it; the structural arm being f32-blind is the very +// reason for the collapse, not a bug to abort on. Non-load-bearing for +// asm in let-store contexts the corpus exercises (964 byte-id holds at +// HEAD with the f32 literals classified coarse f64). +// +// Returns structkind UNCHANGED → behaviour identical to pre-bridge. +export fn exprfloatkind(c: *cgen, n: *node) i32 = { + let structkind: i32 = exprfloatkindstruct(c, n); + let stampkind: i32 = classifytinfo(n.type_: *tinfo); + if ((structkind != 0) != (stampkind != 0)) { + let msg: str = "exprfloatkind: float node mis/un-stamped (cs!=ww) at "; + os.write(2, msg.ptr, msg.len: u64); + if (n.file.len > 0) { + os.write(2, n.file.ptr, n.file.len: u64); + os.write(2, ":".ptr, 1u64); + let ls: str = strconv.i32tos(n.line, strconv.base.DEC); + os.write(2, ls.ptr, ls.len: u64); + os.write(2, " ".ptr, 1u64); + }; + let kn: str = nkname(n.kind); + os.write(2, kn.ptr, kn.len: u64); + os.write(2, "\n".ptr, 1u64); + os.exit(1); + }; + return structkind; +}; + +// exprfloatkindstruct — the STRUCTURAL classifier (#121 commit-2 form; +// commit 3 deletes it once the bridge proves the stamp suffices). // Recognises: float literals, idents bound to float lets/locals, // chained casts whose target is float, and (recursively) the inner // expr of a non-narrowing wrapping construct. Anything we can't // pin down conservatively reports integer — the worst case is that // CVT* is skipped for an exotic case the user can still spell with -// an explicit local. -export fn exprfloatkind(c: *cgen, n: *node) i32 = { +// an explicit local. Recurses through exprfloatkind (the bridge), so +// the consistency check fires on every sub-node the recursion visits. +fn exprfloatkindstruct(c: *cgen, n: *node) i32 = { if (n == nil) { return 0; }; let k: nkind = n.kind; if (k == nkind.N_FLOATLIT) { return 2; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 5befd2a8..bf9b343e 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -1860,19 +1860,82 @@ export fn isf32type(c: *cgen, t: *node) bool = { return typeisf32(t.type_: *tinfo); }; +// classifytinfo — map a checker stamp (node.type_ as *tinfo) to the +// exprfloatkind value-class: f32 → 1, f64 → 2, integer/unknown/nil → 0. +// The TY_NAMED peel + TY_F32/F64 split live in typeisf32 / typeisfloat +// (typ.ww:437,350; cf. the split at check.ww:1323) — reuse them rather +// than re-implementing the peel (rule 13: route through the type table). +// #121 (Package B) collapse target: exprtype stamps each node's RESULT +// type on n.type_, so reading it here mirrors every structural arm at +// its actual node. Commit 2 uses it only to CROSS-CHECK the structural +// classifier (the consistency bridge below); commit 3 makes it the body. +fn classifytinfo(t: *tinfo) i32 = { + if (typeisf32(t)) { return 1; }; + if (typeisfloat(t)) { return 2; }; + return 0; +}; + // exprfloatkind — classify an expression's value-class so callers can -// pick float vs integer codegen without a full type system. Returns: -// 0 — integer-like (or unknown — same fallback the existing cgen -// takes today) -// 1 — f32 -// 2 — f64 +// pick float vs integer codegen without a full type system. Returns +// 0 integer-like/unknown, 1 f32, 2 f64. +// +// #121 (Package B) consistency BRIDGE — byte-id-NEUTRAL, TRANSIENT. +// The wwstage classifies float-ness STRUCTURALLY (exprfloatkindstruct), a +// gate-blind divergence from cstage's checker-stamp read (node_isfloat). +// Before flipping to the stamp (commit 3), this proves the stamp AGREES +// with the structural classifier on every float node the corpus exercises. +// A wrong float classification miscompiles silently and the bootstrap +// barely exercises floats, so the disagreement is surfaced LOUD, not +// absorbed. Whole bridge (struct body + this wrapper) is DELETED in +// commit 3; the residual sibling-evidence guard lands at that point as +// the permanent net. +// +// RELAXED condition — floatness-disagreement ONLY, NOT precision (Drew / +// Rob 2026-05-26): +// abort iff (structkind != 0) != (stampkind != 0). +// CAT A (float↔int): caught loud — a stamp-miss on a float node, or a +// stamp-extra on an int the structural arm correctly classifies as not- +// float; both are real bugs to surface. CAT B (f32-vs-f64 coarseness, +// e.g. structural N_FLOATLIT returns 2 unconditionally while stamp says +// 1 on a tsuffix-stamped 1.5f32): TOLERATED — the stamp is the +// authoritative source (cstage's single source), commit 3's flip +// converges toward it; the structural arm being f32-blind is the very +// reason for the collapse, not a bug to abort on. Non-load-bearing for +// asm in let-store contexts the corpus exercises (964 byte-id holds at +// HEAD with the f32 literals classified coarse f64). +// +// Returns structkind UNCHANGED → behaviour identical to pre-bridge. +export fn exprfloatkind(c: *cgen, n: *node) i32 = { + let structkind: i32 = exprfloatkindstruct(c, n); + let stampkind: i32 = classifytinfo(n.type_: *tinfo); + if ((structkind != 0) != (stampkind != 0)) { + let msg: str = "exprfloatkind: float node mis/un-stamped (cs!=ww) at "; + os.write(2, msg.ptr, msg.len: u64); + if (n.file.len > 0) { + os.write(2, n.file.ptr, n.file.len: u64); + os.write(2, ":".ptr, 1u64); + let ls: str = strconv.i32tos(n.line, strconv.base.DEC); + os.write(2, ls.ptr, ls.len: u64); + os.write(2, " ".ptr, 1u64); + }; + let kn: str = nkname(n.kind); + os.write(2, kn.ptr, kn.len: u64); + os.write(2, "\n".ptr, 1u64); + os.exit(1); + }; + return structkind; +}; + +// exprfloatkindstruct — the STRUCTURAL classifier (#121 commit-2 form; +// commit 3 deletes it once the bridge proves the stamp suffices). // Recognises: float literals, idents bound to float lets/locals, // chained casts whose target is float, and (recursively) the inner // expr of a non-narrowing wrapping construct. Anything we can't // pin down conservatively reports integer — the worst case is that // CVT* is skipped for an exotic case the user can still spell with -// an explicit local. -export fn exprfloatkind(c: *cgen, n: *node) i32 = { +// an explicit local. Recurses through exprfloatkind (the bridge), so +// the consistency check fires on every sub-node the recursion visits. +fn exprfloatkindstruct(c: *cgen, n: *node) i32 = { if (n == nil) { return 0; }; let k: nkind = n.kind; if (k == nkind.N_FLOATLIT) { return 2; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index e7d82233..a4f7c9fa 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -12777,19 +12777,82 @@ export fn isf32type(c: *cgen, t: *node) bool = { return typeisf32(t.type_: *tinfo); }; +// classifytinfo — map a checker stamp (node.type_ as *tinfo) to the +// exprfloatkind value-class: f32 → 1, f64 → 2, integer/unknown/nil → 0. +// The TY_NAMED peel + TY_F32/F64 split live in typeisf32 / typeisfloat +// (typ.ww:437,350; cf. the split at check.ww:1323) — reuse them rather +// than re-implementing the peel (rule 13: route through the type table). +// #121 (Package B) collapse target: exprtype stamps each node's RESULT +// type on n.type_, so reading it here mirrors every structural arm at +// its actual node. Commit 2 uses it only to CROSS-CHECK the structural +// classifier (the consistency bridge below); commit 3 makes it the body. +fn classifytinfo(t: *tinfo) i32 = { + if (typeisf32(t)) { return 1; }; + if (typeisfloat(t)) { return 2; }; + return 0; +}; + // exprfloatkind — classify an expression's value-class so callers can -// pick float vs integer codegen without a full type system. Returns: -// 0 — integer-like (or unknown — same fallback the existing cgen -// takes today) -// 1 — f32 -// 2 — f64 +// pick float vs integer codegen without a full type system. Returns +// 0 integer-like/unknown, 1 f32, 2 f64. +// +// #121 (Package B) consistency BRIDGE — byte-id-NEUTRAL, TRANSIENT. +// The wwstage classifies float-ness STRUCTURALLY (exprfloatkindstruct), a +// gate-blind divergence from cstage's checker-stamp read (node_isfloat). +// Before flipping to the stamp (commit 3), this proves the stamp AGREES +// with the structural classifier on every float node the corpus exercises. +// A wrong float classification miscompiles silently and the bootstrap +// barely exercises floats, so the disagreement is surfaced LOUD, not +// absorbed. Whole bridge (struct body + this wrapper) is DELETED in +// commit 3; the residual sibling-evidence guard lands at that point as +// the permanent net. +// +// RELAXED condition — floatness-disagreement ONLY, NOT precision (Drew / +// Rob 2026-05-26): +// abort iff (structkind != 0) != (stampkind != 0). +// CAT A (float↔int): caught loud — a stamp-miss on a float node, or a +// stamp-extra on an int the structural arm correctly classifies as not- +// float; both are real bugs to surface. CAT B (f32-vs-f64 coarseness, +// e.g. structural N_FLOATLIT returns 2 unconditionally while stamp says +// 1 on a tsuffix-stamped 1.5f32): TOLERATED — the stamp is the +// authoritative source (cstage's single source), commit 3's flip +// converges toward it; the structural arm being f32-blind is the very +// reason for the collapse, not a bug to abort on. Non-load-bearing for +// asm in let-store contexts the corpus exercises (964 byte-id holds at +// HEAD with the f32 literals classified coarse f64). +// +// Returns structkind UNCHANGED → behaviour identical to pre-bridge. +export fn exprfloatkind(c: *cgen, n: *node) i32 = { + let structkind: i32 = exprfloatkindstruct(c, n); + let stampkind: i32 = classifytinfo(n.type_: *tinfo); + if ((structkind != 0) != (stampkind != 0)) { + let msg: str = "exprfloatkind: float node mis/un-stamped (cs!=ww) at "; + os.write(2, msg.ptr, msg.len: u64); + if (n.file.len > 0) { + os.write(2, n.file.ptr, n.file.len: u64); + os.write(2, ":".ptr, 1u64); + let ls: str = strconv.i32tos(n.line, strconv.base.DEC); + os.write(2, ls.ptr, ls.len: u64); + os.write(2, " ".ptr, 1u64); + }; + let kn: str = nkname(n.kind); + os.write(2, kn.ptr, kn.len: u64); + os.write(2, "\n".ptr, 1u64); + os.exit(1); + }; + return structkind; +}; + +// exprfloatkindstruct — the STRUCTURAL classifier (#121 commit-2 form; +// commit 3 deletes it once the bridge proves the stamp suffices). // Recognises: float literals, idents bound to float lets/locals, // chained casts whose target is float, and (recursively) the inner // expr of a non-narrowing wrapping construct. Anything we can't // pin down conservatively reports integer — the worst case is that // CVT* is skipped for an exotic case the user can still spell with -// an explicit local. -export fn exprfloatkind(c: *cgen, n: *node) i32 = { +// an explicit local. Recurses through exprfloatkind (the bridge), so +// the consistency check fires on every sub-node the recursion visits. +fn exprfloatkindstruct(c: *cgen, n: *node) i32 = { if (n == nil) { return 0; }; let k: nkind = n.kind; if (k == nkind.N_FLOATLIT) { return 2; };