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

@@ -845,21 +845,37 @@ fn emitletdataw(c: *cgen, file: *node) void = {
let fsz: i32 = letvarisfloat(c, nm);
if (fsz > 0) {
// Float global: 4B (f32) or 8B (f64).
// The selfhost parser doesn't lex
// N_FLOATLIT yet, so only zero-init
// reaches this path. C cgen emits
// identical bytes for the zero-init
// case; FLOATLIT-init lives in C cgen
// only.
// Two init shapes:
// - no rhs: emit fsz zero bytes
// - N_FLOATLIT: bake the IEEE bits the
// parser stashed in r.uval (lexer
// bit-casts t.fval into t.uval). f32
// emits the low 4 bytes; f64 emits 8.
let bits: u64 = 0u64;
let ok: bool = true;
if (d.rhs != nil) { ok = false; };
if (d.rhs != nil) {
let r: *node = d.rhs;
for (r != nil) {
if (r.kind != nkind.N_CAST) { break; };
r = r.lhs;
};
ok = false;
if (r != nil) {
if (r.kind == nkind.N_FLOATLIT) {
bits = r.uval;
ok = true;
};
};
};
if (ok) {
emitline("DATAW ");
emitsymname(c, nm);
emitline("(SB),\"");
let i: i32 = 0;
let nb: u64 = bits;
for (i < fsz) {
emitdatawbyte(0u8);
emitdatawbyte((nb & 255u64): u8);
nb = nb >> 8u64;
i += 1;
};
emitline("\"\n");
@@ -1385,3 +1401,18 @@ fn argregname(i: i32) str = {
if (i == 5) { return "R9"; };
return "?";
};
// fargregname — XMM scalar-float arg registers (SysV: X0..X7).
// Parallel to argregname / sysv_argregs; float args advance their
// own counter so int and float arg slots don't conflict.
export fn fargregname(i: i32) str = {
if (i == 0) { return "X0"; };
if (i == 1) { return "X1"; };
if (i == 2) { return "X2"; };
if (i == 3) { return "X3"; };
if (i == 4) { return "X4"; };
if (i == 5) { return "X5"; };
if (i == 6) { return "X6"; };
if (i == 7) { return "X7"; };
return "?";
};