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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user