wcc: stamp un-suffixed f32-context float literals (#104 fold-2)

fold-1 narrows a float literal at materialisation only when its node
already carries an f32 type — the `f32` suffix. The common un-suffixed
case `let x: f32 = 1.0` stays ty_untyped_float through the checker, so
the node is never f32-typed: the literal materialises as a 64-bit double
and the f32 consumer reads the low 4 bytes (0.0f for clean values).

Stamp such a literal f32 when an f32 target type is in context, the way
harec's lower_implicit_cast does (ref/harec/src/check.c:148): a float
literal's bit pattern is target-dependent, unlike a width-agnostic int
immediate, so the value-producing node must carry the type. Scoped to
untyped_float -> f32 only (f64 already works via cgen's double default).

coerce_floatlit (cstage clet + cstmt N_RETURN) / coercefloatlit (wwstage
resolvewalk's post-order N_LET / N_RETURN handler) are logically
identical. The wwstage stamp is placed AFTER the child re-walk: the
post-order exprtype dispatch re-stamps a bare N_FLOATLIT back to
untyped_float, so coercing earlier (checkletassign) would be undone.

Scope is let-init and return ONLY, aligned down to the leaner wwstage
(rule 10). The wwstage cgen's exprfloatkind hardcodes a float literal to
f64 and cgbin / the unary negate pick f32 off the operands, not the node
stamp — so a stamped literal in an arith-binop / behind a unary minus
narrows in cstage (ADDSS) but not wwstage (ADDSD), a byte-id break. The
wwstage checker also has no assign / param-typed call-arg / per-field
struct-lit site. binop, unary-minus, assign, call-arg, struct-field wait
on #120 (wwstage cgen + checker build-out).

965_f32stamp_run: cstage run + cs==ww byte-id over un-suffixed let-init
and return literals, the hole 964 left open. Regen w6c/wwdump
combined.ww embeds.
This commit is contained in:
2026-05-26 12:06:47 +09:00
parent acaf0152da
commit a1dff13ec1
6 changed files with 374 additions and 0 deletions

View File

@@ -7609,6 +7609,17 @@ fn resolvewalk(c: *checker, n: *node) void = {
};
};
// #104 fold-2: narrow a bare f32-context float literal AFTER the
// child walk above — the post-order exprtype dispatch (below) re-
// stamps a bare N_FLOATLIT back to untyped_float, so coercing earlier
// (e.g. in checkletassign) would be undone. Placed here, the f32
// stamp on n.rhs / n.lhs sticks; cgen's fold-1 narrow then fires. let
// / return only — see coercefloatlit's docstring for the rule-10 scope
// (the cstage twin coerces in clet / cstmt N_RETURN). c.fnret is set
// by resolvefnbody for the enclosing fn, mirroring checkretassign.
if (k == nkind.N_LET) { coercefloatlit(c, n.rhs, n.lhs); };
if (k == nkind.N_RETURN) { coercefloatlit(c, n.lhs, c.fnret); };
// A.6.0: post-order dispatch of exprtype on every expression-yielding
// node kind so n.type_ stamps fire universally — not only when reached
// through checkletassign / checkretassign / checktryprop / the size-
@@ -8877,6 +8888,31 @@ fn unifyarith(c: *checker, ltn: *node, rtn: *node) *node = {
return ltn;
};
// coercefloatlit — twin of cstage cmd/wcc/check.c coerce_floatlit (see there
// for the full rationale + the rule-10 scope note). Stamp an un-suffixed
// float literal (whose type_ is the untyped_float singleton) as f32 when the
// target type resolves to f32, so fold-1's cgen narrow (isf32type, cgenexpr.
// ww) fires off the now-f32 node.type_. SCOPED to a DIRECT untyped_float
// N_FLOATLIT at let-init / return only: the wwstage cgen's exprfloatkind
// (cgenutil.ww) hardcodes N_FLOATLIT -> f64 and cgbin / the unary negate pick
// f32 off the operands' float-kind, not the node stamp, so a stamped literal
// inside an arith-binop / behind a unary minus does NOT narrow there —
// binop / unary-minus / assign / call-arg / struct-field wait on #120.
fn coercefloatlit(c: *checker, e: *node, target: *node) void = {
if (e == nil) { return; };
if (target == nil) { return; };
let tu: *node = resolvealias(c, unwrapbang(target));
if (tu == nil) { return; };
if (tu.kind != nkind.N_TNAME) { return; };
if (!streq(tu.str, "f32")) { return; };
if (e.kind == nkind.N_FLOATLIT) {
if ((e.type_: *tinfo) == c.tc.tyuntypedfloat) {
let f32t: *node = mktname(c, "f32");
e.type_ = tinfofornode(c, f32t): *void;
};
};
};
// binoptype — derive the result tnode of an N_BIN operator expression.
// Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes
// returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons /

View File

@@ -458,6 +458,17 @@ fn resolvewalk(c: *checker, n: *node) void = {
};
};
// #104 fold-2: narrow a bare f32-context float literal AFTER the
// child walk above — the post-order exprtype dispatch (below) re-
// stamps a bare N_FLOATLIT back to untyped_float, so coercing earlier
// (e.g. in checkletassign) would be undone. Placed here, the f32
// stamp on n.rhs / n.lhs sticks; cgen's fold-1 narrow then fires. let
// / return only — see coercefloatlit's docstring for the rule-10 scope
// (the cstage twin coerces in clet / cstmt N_RETURN). c.fnret is set
// by resolvefnbody for the enclosing fn, mirroring checkretassign.
if (k == nkind.N_LET) { coercefloatlit(c, n.rhs, n.lhs); };
if (k == nkind.N_RETURN) { coercefloatlit(c, n.lhs, c.fnret); };
// A.6.0: post-order dispatch of exprtype on every expression-yielding
// node kind so n.type_ stamps fire universally — not only when reached
// through checkletassign / checkretassign / checktryprop / the size-
@@ -1726,6 +1737,31 @@ fn unifyarith(c: *checker, ltn: *node, rtn: *node) *node = {
return ltn;
};
// coercefloatlit — twin of cstage cmd/wcc/check.c coerce_floatlit (see there
// for the full rationale + the rule-10 scope note). Stamp an un-suffixed
// float literal (whose type_ is the untyped_float singleton) as f32 when the
// target type resolves to f32, so fold-1's cgen narrow (isf32type, cgenexpr.
// ww) fires off the now-f32 node.type_. SCOPED to a DIRECT untyped_float
// N_FLOATLIT at let-init / return only: the wwstage cgen's exprfloatkind
// (cgenutil.ww) hardcodes N_FLOATLIT -> f64 and cgbin / the unary negate pick
// f32 off the operands' float-kind, not the node stamp, so a stamped literal
// inside an arith-binop / behind a unary minus does NOT narrow there —
// binop / unary-minus / assign / call-arg / struct-field wait on #120.
fn coercefloatlit(c: *checker, e: *node, target: *node) void = {
if (e == nil) { return; };
if (target == nil) { return; };
let tu: *node = resolvealias(c, unwrapbang(target));
if (tu == nil) { return; };
if (tu.kind != nkind.N_TNAME) { return; };
if (!streq(tu.str, "f32")) { return; };
if (e.kind == nkind.N_FLOATLIT) {
if ((e.type_: *tinfo) == c.tc.tyuntypedfloat) {
let f32t: *node = mktname(c, "f32");
e.type_ = tinfofornode(c, f32t): *void;
};
};
};
// binoptype — derive the result tnode of an N_BIN operator expression.
// Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes
// returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons /

View File

@@ -7609,6 +7609,17 @@ fn resolvewalk(c: *checker, n: *node) void = {
};
};
// #104 fold-2: narrow a bare f32-context float literal AFTER the
// child walk above — the post-order exprtype dispatch (below) re-
// stamps a bare N_FLOATLIT back to untyped_float, so coercing earlier
// (e.g. in checkletassign) would be undone. Placed here, the f32
// stamp on n.rhs / n.lhs sticks; cgen's fold-1 narrow then fires. let
// / return only — see coercefloatlit's docstring for the rule-10 scope
// (the cstage twin coerces in clet / cstmt N_RETURN). c.fnret is set
// by resolvefnbody for the enclosing fn, mirroring checkretassign.
if (k == nkind.N_LET) { coercefloatlit(c, n.rhs, n.lhs); };
if (k == nkind.N_RETURN) { coercefloatlit(c, n.lhs, c.fnret); };
// A.6.0: post-order dispatch of exprtype on every expression-yielding
// node kind so n.type_ stamps fire universally — not only when reached
// through checkletassign / checkretassign / checktryprop / the size-
@@ -8877,6 +8888,31 @@ fn unifyarith(c: *checker, ltn: *node, rtn: *node) *node = {
return ltn;
};
// coercefloatlit — twin of cstage cmd/wcc/check.c coerce_floatlit (see there
// for the full rationale + the rule-10 scope note). Stamp an un-suffixed
// float literal (whose type_ is the untyped_float singleton) as f32 when the
// target type resolves to f32, so fold-1's cgen narrow (isf32type, cgenexpr.
// ww) fires off the now-f32 node.type_. SCOPED to a DIRECT untyped_float
// N_FLOATLIT at let-init / return only: the wwstage cgen's exprfloatkind
// (cgenutil.ww) hardcodes N_FLOATLIT -> f64 and cgbin / the unary negate pick
// f32 off the operands' float-kind, not the node stamp, so a stamped literal
// inside an arith-binop / behind a unary minus does NOT narrow there —
// binop / unary-minus / assign / call-arg / struct-field wait on #120.
fn coercefloatlit(c: *checker, e: *node, target: *node) void = {
if (e == nil) { return; };
if (target == nil) { return; };
let tu: *node = resolvealias(c, unwrapbang(target));
if (tu == nil) { return; };
if (tu.kind != nkind.N_TNAME) { return; };
if (!streq(tu.str, "f32")) { return; };
if (e.kind == nkind.N_FLOATLIT) {
if ((e.type_: *tinfo) == c.tc.tyuntypedfloat) {
let f32t: *node = mktname(c, "f32");
e.type_ = tinfofornode(c, f32t): *void;
};
};
};
// binoptype — derive the result tnode of an N_BIN operator expression.
// Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes
// returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons /