check: narrow untyped float literals to f32 in f32 context (#120)
An untyped float literal defaults to f64, so in an f32 context it was materialized as f64 then bit-truncated by a raw MOVSS (low-32 reinterpret) rather than narrowed -- e.g. `let x: f32 = 2.0f32; x * 3.0` multiplied by 0.0f. Twelve byte-id-gate-blind both-wrong miscompiles, all this one cause (compare, binop, call-arg, struct-field, array-elem against an untyped literal). Broaden coerce_floatlit to stamp the untyped fconst type_=f32 across the f32-context sites (assign rhs, call-arg, struct-field, array-elem) and to descend the implicit-cast shapes (peel unary +/-/cast, recurse binop operands AND the binop node, recurse arrlit elems), mirroring harec's lower_implicit_cast. The existing CVTSD2SS gate then fires; cgen is unchanged. f64 contexts are untouched -- the stamp is gated on TY_F32. Surfaced by the float codegen sub-hunt (= the deferred #120). Pinned by test/lang/f32_untyped_narrow_test.ww (22 value-asserting rows incl. f64 controls; reddens on revert).
This commit is contained in:
@@ -2696,27 +2696,70 @@ fn unifyarith(c: *checker, e: *syntax.node, ltn: *syntax.node, rtn: *syntax.node
|
||||
};
|
||||
|
||||
// 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.
|
||||
// for the full rationale). 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 untyped_float -> f32. #120 broadens the reach (was
|
||||
// let-init / return only): descend the harec lower_implicit_cast operand
|
||||
// shapes so every untyped float LEAF in an f32 context gets the stamp — a
|
||||
// unary ± / paren-cast wrapper, both arith-binop operands (the only path that
|
||||
// reaches a literal-on-both-sides under an f32 target, per-leaf so no f64
|
||||
// intermediate double-rounds), and each array-literal element against the
|
||||
// array's element type. The comparison sibling (no f32 target above) is in
|
||||
// binoptype.
|
||||
fn coercefloatlit(c: *checker, e: *syntax.node, target: *syntax.node) void = {
|
||||
if (e == nil) { return; };
|
||||
if (target == nil) { return; };
|
||||
let tu: *syntax.node = resolvealias(c, unwrapbang(target));
|
||||
if (tu == nil) { return; };
|
||||
if (tu.kind != syntax.nkind.N_TNAME) { return; };
|
||||
if (!syntax.streq(tu.str, "f32")) { return; };
|
||||
if (e.kind == syntax.nkind.N_FLOATLIT) {
|
||||
if ((e.type_: *syntax.tinfo) == c.tc.tyuntypedfloat) {
|
||||
let f32t: *syntax.node = mktname(c, "f32");
|
||||
e.type_ = tinfofornode(c, f32t): *void;
|
||||
let k: syntax.nkind = e.kind;
|
||||
if (k == syntax.nkind.N_UN) {
|
||||
if (e.op == syntax.tkind.TK_MINUS || e.op == syntax.tkind.TK_PLUS) {
|
||||
coercefloatlit(c, e.lhs, target);
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (k == syntax.nkind.N_CAST) {
|
||||
coercefloatlit(c, e.lhs, target);
|
||||
return;
|
||||
};
|
||||
if (k == syntax.nkind.N_BIN) {
|
||||
if (e.op == syntax.tkind.TK_PLUS || e.op == syntax.tkind.TK_MINUS ||
|
||||
e.op == syntax.tkind.TK_STAR || e.op == syntax.tkind.TK_SLASH) {
|
||||
coercefloatlit(c, e.lhs, target);
|
||||
coercefloatlit(c, e.rhs, target);
|
||||
// harec lowers the binop's RESULT to the hint too, not only
|
||||
// its operands. A literal-on-both-sides binop node stays
|
||||
// untyped_float; cstage's arith cgen keys op-width on the
|
||||
// BINOP node (cgen.c:4944), so the node must carry f32 to keep
|
||||
// both stages on ADDSS. Mirror cstage coerce_floatlit N_BIN.
|
||||
if (tu.kind == syntax.nkind.N_TNAME && syntax.streq(tu.str, "f32")) {
|
||||
if ((e.type_: *syntax.tinfo) == c.tc.tyuntypedfloat) {
|
||||
let f32b: *syntax.node = mktname(c, "f32");
|
||||
e.type_ = tinfofornode(c, f32b): *void;
|
||||
};
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (k == syntax.nkind.N_ARRLIT) {
|
||||
if (tu.kind == syntax.nkind.N_TARRAY) {
|
||||
let it: *syntax.node = e.list;
|
||||
for (it != nil) {
|
||||
coercefloatlit(c, it, tu.lhs);
|
||||
it = it.next;
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (k == syntax.nkind.N_FLOATLIT) {
|
||||
if (tu.kind == syntax.nkind.N_TNAME && syntax.streq(tu.str, "f32")) {
|
||||
if ((e.type_: *syntax.tinfo) == c.tc.tyuntypedfloat) {
|
||||
let f32t: *syntax.node = mktname(c, "f32");
|
||||
e.type_ = tinfofornode(c, f32t): *void;
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2771,6 +2814,16 @@ fn binoptype(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
let op: syntax.tkind = e.op;
|
||||
let ltn: *syntax.node = exprtype(c, e.lhs, nil);
|
||||
let rtn: *syntax.node = exprtype(c, e.rhs, nil);
|
||||
// #120 (B): a binop/compare with one f32 operand lowers an untyped-
|
||||
// float peer to f32 — harec unifies both operands to the operand type
|
||||
// (ref/harec/src/check.c:1347-1348). A comparison's result is bool, so
|
||||
// no f32 target is above its operands and this sibling is their only
|
||||
// lowering path. coercefloatlit's internal f32-target + untyped-leaf
|
||||
// gates make each call inert unless the OTHER operand resolves f32 and
|
||||
// this one carries an untyped float leaf; a both-untyped pair stays f64.
|
||||
// Mirror cstage cbinop (cmd/wcc/check.c:1190).
|
||||
coercefloatlit(c, e.rhs, ltn);
|
||||
coercefloatlit(c, e.lhs, rtn);
|
||||
// #38/F2: ptr ± int / int + ptr use intkindast (the type_isint mirror,
|
||||
// incl untyped_int / untyped_rune / alias-chased) — NOT isinttypeast,
|
||||
// which misses untyped_int. cstage's ptr-arith arm gates on type_isint
|
||||
@@ -3949,8 +4002,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
let fi: *syntax.node = e.list;
|
||||
for (fi != nil) {
|
||||
if (fi.kind == syntax.nkind.N_FIELD
|
||||
&& fi.lhs != nil
|
||||
&& fi.lhs.kind == syntax.nkind.N_ARRLIT) {
|
||||
&& fi.lhs != nil) {
|
||||
let ftn: *syntax.node = nil;
|
||||
let tf: *syntax.node = stn.list;
|
||||
for (tf != nil) {
|
||||
@@ -3960,7 +4012,13 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
tf = tf.next;
|
||||
};
|
||||
if (ftn != nil) {
|
||||
checkarrlitfits(c, ftn, fi.lhs);
|
||||
// #120: `S{ f: 1.0 }` narrows the
|
||||
// field init to the field's f32.
|
||||
// Mirror cstage N_STRUCTLIT site.
|
||||
coercefloatlit(c, fi.lhs, ftn);
|
||||
if (fi.lhs.kind == syntax.nkind.N_ARRLIT) {
|
||||
checkarrlitfits(c, ftn, fi.lhs);
|
||||
};
|
||||
};
|
||||
};
|
||||
fi = fi.next;
|
||||
@@ -5666,6 +5724,10 @@ fn desugarcallargs(c: *checker, n: *syntax.node) void = {
|
||||
};
|
||||
if (param.op != syntax.tkind.TK_ELLIPSIS) {
|
||||
let atype: *syntax.node = exprtype(c, a, nil);
|
||||
// #120: `f(1.0)` narrows the arg literal to the
|
||||
// param's f32. Mirror cstage cmd/wcc/check.c N_CALL
|
||||
// coerce_floatlit at the param-typed arg.
|
||||
coercefloatlit(c, a, param.lhs);
|
||||
// #29: a rune literal narrowing into an integer param
|
||||
// (`take('b')` where take(b: u8)). Override atype to the
|
||||
// integer target so c3's general isassignable accepts,
|
||||
@@ -5745,6 +5807,9 @@ fn checkassign(c: *checker, n: *syntax.node) void = {
|
||||
};
|
||||
let ltn: *syntax.node = exprtype(c, n.lhs, nil);
|
||||
let rtn: *syntax.node = exprtype(c, n.rhs, nil);
|
||||
// #120: `w = 1.0` narrows the rhs literal to the lvalue's f32. Mirror
|
||||
// cstage cmd/wcc/check.c N_ASSIGN coerce_floatlit.
|
||||
coercefloatlit(c, n.rhs, ltn);
|
||||
// #29: a rune literal narrowing into an integer assign/index-store
|
||||
// target (`buf[i] = 'F'`). Override rtn to the integer target so a
|
||||
// general assign typecheck accepts, mirroring cstage's coarse
|
||||
|
||||
Reference in New Issue
Block a user