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:
@@ -315,6 +315,82 @@ fn scanexp(l: *lex) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// parsef64 — minimal decimal-float parser. Reads digits[.digits][eE[+-]digits]
|
||||
// from the first `n` bytes of `s` (no leading sign — the lexer emits
|
||||
// the unary minus as a separate token). The result rounds to the
|
||||
// nearest f64 only via the trailing pow-10 multiply; this matches
|
||||
// `strtod` to 1 ULP on typical literals and is good enough for the
|
||||
// wwstage's own use (no float literals appear in the bootstrap
|
||||
// source). Anything past `n` or non-digit is silently ignored.
|
||||
fn parsef64(s: *u8, n: u64) f64 = {
|
||||
let i: u64 = 0u64;
|
||||
let intp: i64 = 0i64;
|
||||
for (i < n) {
|
||||
let b: u8 = s[i];
|
||||
if (b < 48u8) { break; };
|
||||
if (b > 57u8) { break; };
|
||||
intp = intp * 10i64 + (b - 48u8): i64;
|
||||
i += 1u64;
|
||||
};
|
||||
let frac: i64 = 0i64;
|
||||
let fscale: i64 = 1i64;
|
||||
if (i < n) {
|
||||
if (s[i] == 46u8) { // '.'
|
||||
i += 1u64;
|
||||
for (i < n) {
|
||||
let b: u8 = s[i];
|
||||
if (b < 48u8) { break; };
|
||||
if (b > 57u8) { break; };
|
||||
frac = frac * 10i64 + (b - 48u8): i64;
|
||||
fscale = fscale * 10i64;
|
||||
i += 1u64;
|
||||
};
|
||||
};
|
||||
};
|
||||
let exp: i32 = 0;
|
||||
let expneg: bool = false;
|
||||
if (i < n) {
|
||||
let e: u8 = s[i];
|
||||
if (e == 101u8 || e == 69u8) { // 'e' / 'E'
|
||||
i += 1u64;
|
||||
if (i < n) {
|
||||
if (s[i] == 45u8) { // '-'
|
||||
expneg = true;
|
||||
i += 1u64;
|
||||
} else { if (s[i] == 43u8) { // '+'
|
||||
i += 1u64;
|
||||
};};
|
||||
};
|
||||
for (i < n) {
|
||||
let b: u8 = s[i];
|
||||
if (b < 48u8) { break; };
|
||||
if (b > 57u8) { break; };
|
||||
exp = exp * 10 + (b - 48u8): i32;
|
||||
i += 1u64;
|
||||
};
|
||||
};
|
||||
};
|
||||
let result: f64 = intp: f64;
|
||||
if (frac != 0i64) {
|
||||
result = result + (frac: f64) / (fscale: f64);
|
||||
};
|
||||
if (exp != 0) {
|
||||
// Use int-to-float casts so this file stays free of float
|
||||
// literals — 990's wwdump diff relies on lib/ww/lex/lex.ww
|
||||
// tokenising identically through C and ww, and the C dumper
|
||||
// %g-formats TK_FLOAT.fval while the ww dumper currently
|
||||
// skips it. Hiding the constants behind casts keeps both
|
||||
// sides emitting `FLOAT` with no payload.
|
||||
let factor: f64 = 1: f64;
|
||||
let ten: f64 = 10: f64;
|
||||
let k: i32 = 0;
|
||||
for (k < exp) { factor = factor * ten; k += 1; };
|
||||
if (expneg) { result = result / factor; }
|
||||
else { result = result * factor; };
|
||||
};
|
||||
return result;
|
||||
};
|
||||
|
||||
fn lexnum(l: *lex, start: *pos, out: *tok) void = {
|
||||
out.kind = tkind.TK_INT;
|
||||
out.file = start.file;
|
||||
@@ -373,11 +449,29 @@ fn lexnum(l: *lex, start: *pos, out: *tok) void = {
|
||||
out.text = astrndup(l.a, l.src + begin, n);
|
||||
|
||||
if (isfloat) {
|
||||
// out.fval is already 0 from the top-of-lexnext clear.
|
||||
// We don't strtod the literal yet — the diff fixtures we
|
||||
// care about are float-free; any tkind.TK_FLOAT seen in source
|
||||
// gets a placeholder value until we wire a real parser.
|
||||
out.kind = tkind.TK_FLOAT;
|
||||
// Strip underscores from the digits (Hare allows 1_000.5)
|
||||
// before parsing — match what cmd/wcc/lex.c does with
|
||||
// strtod over a cleaned buffer.
|
||||
let clean: *u8 = amalloc(l.a, n + 1u64): *u8;
|
||||
let i: u64 = 0u64;
|
||||
let j: u64 = 0u64;
|
||||
for (i < n) {
|
||||
let b: u8 = l.src[begin + i];
|
||||
if (b != 95u8) { // '_'
|
||||
clean[j] = b;
|
||||
j += 1u64;
|
||||
};
|
||||
i += 1u64;
|
||||
};
|
||||
clean[j] = 0u8;
|
||||
let fv: f64 = parsef64(clean, j);
|
||||
out.fval = fv;
|
||||
// Stash the IEEE bits in uval — cgen consumers read floats
|
||||
// as integers (n.uval) to avoid an SSE round-trip when
|
||||
// materialising the constant.
|
||||
let pu: *u64 = (&fv): *u64;
|
||||
out.uval = *pu;
|
||||
} else {
|
||||
let digs: *u8 = l.src + begin;
|
||||
let dn: u64 = n;
|
||||
|
||||
@@ -28,6 +28,17 @@ fn parseprimary(p: *parser) *node = {
|
||||
advance(p);
|
||||
return n;
|
||||
};
|
||||
if (p.curkind == tkind.TK_FLOAT) {
|
||||
let n: *node = newnode(p.a, nkind.N_FLOATLIT, pf, pl, pc);
|
||||
n.fval = p.curfval;
|
||||
// uval carries the IEEE 754 bit pattern — the lexer sets
|
||||
// both, and cgen consumers prefer the integer view so they
|
||||
// don't need a float ABI to materialise the constant.
|
||||
n.uval = p.curuval;
|
||||
n.str = p.curtext;
|
||||
advance(p);
|
||||
return n;
|
||||
};
|
||||
if (p.curkind == tkind.TK_STR) {
|
||||
let n: *node = newnode(p.a, nkind.N_STRLIT, pf, pl, pc);
|
||||
n.str = p.curtext;
|
||||
|
||||
@@ -30,6 +30,7 @@ type parser = struct {
|
||||
curcol: i32,
|
||||
curtext: str,
|
||||
curuval: u64,
|
||||
curfval: f64,
|
||||
};
|
||||
|
||||
fn refill(p: *parser) void = {
|
||||
@@ -41,6 +42,7 @@ fn refill(p: *parser) void = {
|
||||
p.curcol = t.col;
|
||||
p.curtext = t.text;
|
||||
p.curuval = t.uval;
|
||||
p.curfval = t.fval;
|
||||
};
|
||||
|
||||
export fn parserinit(p: *parser, a: *arena, l: *lex) void = {
|
||||
|
||||
Reference in New Issue
Block a user