selfhost: port float lex + expression cgen — feature parity with C

Lexer: `lexnum` now parses the digit/exponent tail into an f64 via a
new `parsef64` (decimal-only, integer-arith driver + pow-10 multiply,
no strtod). The IEEE bits are also stashed in tok.uval via pointer
reinterpret so cgen consumers stay integer-only.

Parser: TK_FLOAT → N_FLOATLIT, carrying both fval and uval. Parser
state grows curfval to plumb the lexer's f64 through refill.

cgen:
  - cgfloatlit reads n.uval and materialises X0 via the standard
    MOVQ-PUSHQ-MOVSD-ADDQ trampoline.
  - cglet, cgident, cgassign learn float-typed branches: MOVSS/MOVSD
    for locals; LEAQ-indirect MOVSS/MOVSD for globals.
  - cgbin handles ADDSD/SUBSD/MULSD/DIVSD (+ SS variants) and
    UCOMISD/UCOMISS-based comparisons. cgun handles float negate
    via the `0 - X0` shape C cgen uses.
  - cgcast routes int↔float and f32↔f64 through CVTSI2SD/CVTTSD2SI/
    CVTSD2SS/CVTSS2SD and their SS twins.
  - cgcall + pushargsrev push float args via SUBQ+MOVSD and pop into
    the X0..X7 stream, tracked by a per-class counter alongside the
    int DI..R9 stream. cgfnparams loads float params from the same
    stream.
  - emitletdataw bakes FLOATLIT init bits into DATAW (4B for f32,
    8B for f64).

Tests: smoke programs (literal init, reassign, arithmetic, fn args/
returns, casts) produce byte-identical asm through `w6c` and
`wwdump_ww -c`, and the resulting binary exits with the same value
whether compiled by the C or wwstage toolchain. Full `make test` is
26/26 and `make bootstrap` still reaches its byte-identical
ww2==ww3==ww4 fixed point.
This commit is contained in:
2026-05-12 14:21:50 +09:00
parent a9b804935c
commit 5155ba55f3
10 changed files with 1767 additions and 69 deletions

View File

@@ -135,6 +135,21 @@ fn pushargsrev(c: *cgen, arg: *node) i32 = {
};
};
};
// Float arg: cgexpr leaves the value in X0. Push 8 bytes from
// X0 via SUBQ+MOVSD so cgcall's pop side can drain into the
// XMM stream (X0..X7). f32 still occupies 8B on the stack —
// the MOVSS load on the pop side touches only the low 4.
let fk: i32 = exprfloatkind(c, arg);
if (fk != 0) {
cgexpr(c, arg);
let mov: str = "MOVSD";
if (fk == 1) { mov = "MOVSS"; };
emitline("\tSUBQ\t$8, SP\n");
emitline("\t");
emitline(mov);
emitline("\tX0, (SP)\n");
return rest + 1;
};
cgexpr(c, arg);
if (nodeisslice(c, arg)) {
emitline("\tPUSHQ\tCX\n");
@@ -904,6 +919,118 @@ fn istaggedtype(t: *node) bool = {
return false;
};
// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution.
fn isf32typeraw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };
return streq(t.str, "f32");
};
fn isf64typeraw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };
return streq(t.str, "f64");
};
// isfloattype — f32 / f64 (and aliases of those). Used by cglet,
// cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-prologue to
// dispatch the MOVSS/MOVSD-shaped paths.
export fn isfloattype(c: *cgen, t: *node) bool = {
if (isf32typeraw(t)) { return true; };
if (isf64typeraw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
if (isf32typeraw(r)) { return true; };
if (isf64typeraw(r)) { return true; };
return false;
};
// isf32type — narrower predicate: true only for f32 (after alias
// resolution). f64 returns false. Used to pick MOVSS vs MOVSD and
// the SS-variant arithmetic / cast opcodes.
export fn isf32type(c: *cgen, t: *node) bool = {
if (isf32typeraw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
return isf32typeraw(r);
};
// 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
// 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 = {
if (n == nil) { return 0; };
let k: nkind = n.kind;
if (k == nkind.N_FLOATLIT) { return 2; };
if (k == nkind.N_CAST) {
if (isf32type(c, n.rhs)) { return 1; };
if (isfloattype(c, n.rhs)) { return 2; };
return 0;
};
if (k == nkind.N_IDENT) {
let lc: *local = localfindnode(c, n.str);
if (lc != nil) {
if (isf32type(c, lc.tnode)) { return 1; };
if (isfloattype(c, lc.tnode)) { return 2; };
return 0;
};
let lv: *letvar = c.lets;
for (lv != nil) {
if (streq(lv.name, n.str)) {
if (isf32type(c, lv.tnode)) { return 1; };
if (isfloattype(c, lv.tnode)) { return 2; };
return 0;
};
lv = lv.lvnext;
};
return 0;
};
if (k == nkind.N_UN) {
// Unary on a float (TK_MINUS) returns float; everything
// else is integer-coded.
if (n.op == tkind.TK_MINUS) {
return exprfloatkind(c, n.lhs);
};
return 0;
};
if (k == nkind.N_BIN) {
// Arithmetic binops inherit the operands' kind. Comparison
// (eq/ne/lt/...) returns bool — integer.
let op: tkind = n.op;
if (op == tkind.TK_PLUS) { return exprfloatkind(c, n.lhs); };
if (op == tkind.TK_MINUS) { return exprfloatkind(c, n.lhs); };
if (op == tkind.TK_STAR) { return exprfloatkind(c, n.lhs); };
if (op == tkind.TK_SLASH) { return exprfloatkind(c, n.lhs); };
return 0;
};
if (k == nkind.N_CALL) {
// Look up the callee's declared return type — fnretlookup
// returns the type-AST. Routes float-returning fns through
// the X0 ABI so cglet / cgassign know to spill from X0.
let nm: str;
nm.ptr = nil; nm.len = 0;
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_IDENT) { nm = n.lhs.str; };
};
if (nm.len > 0) {
let rt: *node = fnretlookup(c, nm);
if (isf32type(c, rt)) { return 1; };
if (isfloattype(c, rt)) { return 2; };
};
return 0;
};
return 0;
};
// isnullabletype — nkind.N_TTAGGED with exactly two children, one *T and
// one `void`. Folds to a single 8-byte pointer slot per Hare's
// `(*T | null)` semantics. Mirrors check.c's resolve_type detection.