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 = {
|
||||
|
||||
@@ -1275,6 +1275,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;
|
||||
@@ -1333,11 +1409,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;
|
||||
@@ -2050,6 +2144,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;
|
||||
@@ -2938,6 +3043,7 @@ type parser = struct {
|
||||
curcol: i32,
|
||||
curtext: str,
|
||||
curuval: u64,
|
||||
curfval: f64,
|
||||
};
|
||||
|
||||
fn refill(p: *parser) void = {
|
||||
@@ -2949,6 +3055,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 = {
|
||||
@@ -4844,6 +4951,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");
|
||||
@@ -5613,6 +5735,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.
|
||||
@@ -5762,6 +5996,22 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
if (k == nkind.N_FLOATLIT) {
|
||||
// Materialise the f64 bit pattern in AX, push, then MOVSD it
|
||||
// into X0. The bits come from n.uval — the parser populates
|
||||
// it from the lexer's bitcast of t.fval, so this path stays
|
||||
// integer-only (no SSE in the cgen source). The f32
|
||||
// narrowing is handled at the consumer site, not here — the
|
||||
// literal always carries the full double precision until
|
||||
// typed by context.
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(n.uval: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
emitline("\tMOVSD\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
return;
|
||||
};
|
||||
if (k == nkind.N_RUNELIT) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(n.uval: i64);
|
||||
@@ -5794,15 +6044,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
|
||||
if (k == nkind.N_MATCH) { cgmatch(c, n); return; };
|
||||
|
||||
if (k == nkind.N_CAST) {
|
||||
// Type casts are mostly no-ops at the asm level for our
|
||||
// integer-shaped operands. Evaluate the source; AX holds
|
||||
// the bits unchanged. (Sign- or zero-extending narrow loads
|
||||
// to wider types is the loader's job, not cast's, in this
|
||||
// minimal cgen.)
|
||||
cgexpr(c, n.lhs);
|
||||
return;
|
||||
};
|
||||
if (k == nkind.N_CAST) { cgcast(c, n); return; };
|
||||
|
||||
if (k == nkind.N_DOT) { cgdot(c, n); return; };
|
||||
|
||||
@@ -6070,6 +6312,46 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
fn cgcast(c: *cgen, n: *node) void = {
|
||||
let srcfk: i32 = exprfloatkind(c, n.lhs);
|
||||
let dstf64: bool = isfloattype(c, n.rhs);
|
||||
let dstf32: bool = isf32type(c, n.rhs);
|
||||
let dstfk: i32 = 0;
|
||||
if (dstf32) { dstfk = 1; }
|
||||
else { if (dstf64) { dstfk = 2; }; };
|
||||
cgexpr(c, n.lhs);
|
||||
// 0=int, 1=f32, 2=f64. CVT picks one direction per combo;
|
||||
// same-kind casts (int↔int with widening differences,
|
||||
// f64→f64 etc.) stay no-ops at the asm level, matching the
|
||||
// pre-port behaviour for integer casts.
|
||||
if (srcfk == 0 && dstfk == 0) { return; };
|
||||
if (srcfk == 0 && dstfk == 2) {
|
||||
emitline("\tCVTSI2SD\tAX, X0\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 0 && dstfk == 1) {
|
||||
emitline("\tCVTSI2SS\tAX, X0\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 2 && dstfk == 0) {
|
||||
emitline("\tCVTTSD2SI\tX0, AX\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 1 && dstfk == 0) {
|
||||
emitline("\tCVTTSS2SI\tX0, AX\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 2 && dstfk == 1) {
|
||||
emitline("\tCVTSD2SS\tX0, X0\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 1 && dstfk == 2) {
|
||||
emitline("\tCVTSS2SD\tX0, X0\n");
|
||||
return;
|
||||
};
|
||||
// Same-kind float→float: nothing to emit.
|
||||
};
|
||||
|
||||
fn cgstrlit(c: *cgen, n: *node) void = {
|
||||
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
|
||||
// sites that expect a str arg pick these up directly.
|
||||
@@ -6089,6 +6371,19 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, nm);
|
||||
if (lc != nil) {
|
||||
let off: i32 = lc.off;
|
||||
// Float local: MOVSS / MOVSD into X0. Skips the AX shuffle
|
||||
// so consumers (cgbin, cgcast, return) pick up the SSE value
|
||||
// directly.
|
||||
if (isfloattype(c, lc.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, lc.tnode)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP), X0\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -6151,6 +6446,27 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
};
|
||||
return;
|
||||
};
|
||||
// Float global: same LEAQ-indirect shape, since MOVSS/
|
||||
// MOVSD have no D_EXTERN operand form in w6a.
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, nm)) {
|
||||
if (isfloattype(c, lv.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, lv.tnode)) { mov = "MOVSS"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t(CX), X0\n");
|
||||
return;
|
||||
};
|
||||
lv = nil;
|
||||
} else {
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
@@ -6755,6 +7071,26 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// then apply the unary op. AMP / STAR override AX with the
|
||||
// address / deref. The wasted load before AMP keeps our asm
|
||||
// byte-identical to the C version.
|
||||
let fk: i32 = exprfloatkind(c, n.lhs);
|
||||
if (n.op == tkind.TK_MINUS && fk != 0) {
|
||||
// Float negate: X0 = 0 - X0. Stash orig, load 0.0, subtract.
|
||||
// Zero bit pattern equals 0.0 for both f32 and f64 so we
|
||||
// reuse the integer-zero materialisation.
|
||||
let mov: str = "MOVSD";
|
||||
let sub: str = "SUBSD";
|
||||
if (fk == 1) { mov = "MOVSS"; sub = "SUBSS"; };
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(sub); emitline("\tX1, X0\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.lhs);
|
||||
if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; };
|
||||
if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; };
|
||||
@@ -6801,6 +7137,75 @@ fn cgbin(c: *cgen, n: *node) void = {
|
||||
let unsignd: bool = nodeisunsigned(c, n.lhs);
|
||||
if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); };
|
||||
|
||||
// Float arithmetic: both operands flow through X0. Spill rhs
|
||||
// across the stack (SUBQ/MOVSD/MOVSD/ADDQ) since there's no
|
||||
// general FP register saver. ADDSD/SUBSD/MULSD/DIVSD pick SS
|
||||
// variants for f32. Comparison uses UCOMISD + JCC and falls
|
||||
// out to the existing CMPQ-based path below.
|
||||
let lfk: i32 = exprfloatkind(c, n.lhs);
|
||||
let rfk: i32 = exprfloatkind(c, n.rhs);
|
||||
let fk: i32 = lfk;
|
||||
if (fk == 0) { fk = rfk; };
|
||||
if (fk != 0) {
|
||||
let mov: str = "MOVSD";
|
||||
if (fk == 1) { mov = "MOVSS"; };
|
||||
if (n.op == tkind.TK_PLUS ||
|
||||
n.op == tkind.TK_MINUS ||
|
||||
n.op == tkind.TK_STAR ||
|
||||
n.op == tkind.TK_SLASH) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
let op: str = "ADDSD";
|
||||
if (n.op == tkind.TK_MINUS) { op = "SUBSD"; };
|
||||
if (n.op == tkind.TK_STAR) { op = "MULSD"; };
|
||||
if (n.op == tkind.TK_SLASH) { op = "DIVSD"; };
|
||||
if (fk == 1) {
|
||||
if (n.op == tkind.TK_PLUS) { op = "ADDSS"; };
|
||||
if (n.op == tkind.TK_MINUS) { op = "SUBSS"; };
|
||||
if (n.op == tkind.TK_STAR) { op = "MULSS"; };
|
||||
if (n.op == tkind.TK_SLASH) { op = "DIVSS"; };
|
||||
};
|
||||
emitline("\t"); emitline(op); emitline("\tX1, X0\n");
|
||||
return;
|
||||
};
|
||||
let isfcmp: bool = false;
|
||||
let jcc: str = "";
|
||||
// UCOMISD/SS sets ZF/PF/CF; unordered (NaN) propagates as
|
||||
// "not equal / not less". JA/JAE/JB/JBE keys off CF which
|
||||
// matches the ordered comparisons we need.
|
||||
if (n.op == tkind.TK_EQ) { isfcmp = true; jcc = "JE"; };
|
||||
if (n.op == tkind.TK_NEQ) { isfcmp = true; jcc = "JNE"; };
|
||||
if (n.op == tkind.TK_LT) { isfcmp = true; jcc = "JB"; };
|
||||
if (n.op == tkind.TK_LE) { isfcmp = true; jcc = "JBE"; };
|
||||
if (n.op == tkind.TK_GT) { isfcmp = true; jcc = "JA"; };
|
||||
if (n.op == tkind.TK_GE) { isfcmp = true; jcc = "JAE"; };
|
||||
if (isfcmp) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
let ucomi: str = "UCOMISD";
|
||||
if (fk == 1) { ucomi = "UCOMISS"; };
|
||||
emitline("\t"); emitline(ucomi); emitline("\tX1, X0\n");
|
||||
let t: str = mklabel(c, "ct");
|
||||
let e: str = mklabel(c, "ce");
|
||||
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
||||
emitlabel(t);
|
||||
emitline("\tMOVQ\t$1, AX\n");
|
||||
emitlabel(e);
|
||||
return;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, n.lhs);
|
||||
@@ -6864,11 +7269,61 @@ fn cgbin(c: *cgen, n: *node) void = {
|
||||
|
||||
fn cgcall(c: *cgen, n: *node) void = {
|
||||
let nargs: i32 = pushargsrev(c, n.list);
|
||||
let i: i32 = 0;
|
||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||
// pops into the int stream (DI..R9) per the SysV ABI. Walk the
|
||||
// args list alongside the pop counter so we know each arg's
|
||||
// register class.
|
||||
let intidx: i32 = 0;
|
||||
let fpidx: i32 = 0;
|
||||
let a: *node = n.list;
|
||||
let popped: i32 = 0;
|
||||
for (a != nil) {
|
||||
let fk: i32 = exprfloatkind(c, a);
|
||||
if (fk != 0) {
|
||||
let mov: str = "MOVSD";
|
||||
if (fk == 1) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t(SP), ");
|
||||
emitline(fargregname(fpidx));
|
||||
emitline("\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
fpidx += 1;
|
||||
popped += 1;
|
||||
} else {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(argregname(intidx));
|
||||
emitline("\n");
|
||||
intidx += 1;
|
||||
popped += 1;
|
||||
// Multi-word args (str=2, slice/tagged=3): drain
|
||||
// the remaining words into successive int regs.
|
||||
let extra: i32 = 0;
|
||||
if (nodeisstr(c, a)) { extra = 1; };
|
||||
if (nodeisslice(c, a)) { extra = 2; };
|
||||
let e: i32 = 0;
|
||||
for (e < extra) {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(argregname(intidx));
|
||||
emitline("\n");
|
||||
intidx += 1;
|
||||
popped += 1;
|
||||
e += 1;
|
||||
};
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
// Drain any remaining slots that the arg-walker didn't account
|
||||
// for (tagged-union arg sizes > 8B, struct-by-value, etc.). The
|
||||
// existing C cgen pops these into the int stream, so the worst
|
||||
// case here is identical pre-port behaviour.
|
||||
let i: i32 = popped;
|
||||
for (i < nargs) {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(argregname(i));
|
||||
emitline(argregname(intidx));
|
||||
emitline("\n");
|
||||
intidx += 1;
|
||||
i += 1;
|
||||
};
|
||||
let callee: *node = n.lhs;
|
||||
@@ -7445,6 +7900,33 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
// address into CX and store both halves; the
|
||||
// asm has no `name+8(SB)` operand form.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
// Float global: rhs lands in X0; store via
|
||||
// LEAQ+indirect since MOVSS/MOVSD have no
|
||||
// D_EXTERN operand form.
|
||||
let lvf: *letvar = c.lets;
|
||||
let isfg: bool = false;
|
||||
let isf32g: bool = false;
|
||||
for (lvf != nil) {
|
||||
if (streq(lvf.name, nm)) {
|
||||
isfg = isfloattype(c, lvf.tnode);
|
||||
isf32g = isf32type(c, lvf.tnode);
|
||||
lvf = nil;
|
||||
} else {
|
||||
lvf = lvf.lvnext;
|
||||
};
|
||||
};
|
||||
if (isfg && n.op == tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32g) { mov = "MOVSS"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, (CX)\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (letvarisstr(c, nm)) {
|
||||
@@ -7499,6 +7981,26 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let lcstr: bool = false;
|
||||
let lcn: *local = localfindnode(c, nm);
|
||||
if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); };
|
||||
let lcf: bool = false;
|
||||
let lcf32: bool = false;
|
||||
if (lcn != nil) {
|
||||
lcf = isfloattype(c, lcn.tnode);
|
||||
lcf32 = isf32type(c, lcn.tnode);
|
||||
};
|
||||
// Float-typed local: rhs lands in X0; store via MOVSD/
|
||||
// MOVSS, no AX shuffle. Only plain `=` is wired; compound
|
||||
// float-assign isn't.
|
||||
if (lcf && n.op == tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
let mov: str = "MOVSD";
|
||||
if (lcf32) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -8020,6 +8522,19 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
// Float local: cgexpr leaves the value in X0. Spill via
|
||||
// MOVSS (f32, 4B) or MOVSD (f64, 8B).
|
||||
if (isfloattype(c, n.lhs)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, n.lhs)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
@@ -8403,9 +8918,29 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
let p: *node = params;
|
||||
let idx: i32 = 0;
|
||||
let fidx: i32 = 0;
|
||||
for (p != nil) {
|
||||
if (p.kind == nkind.N_PARAM) {
|
||||
let nm: str = p.str;
|
||||
if (isfloattype(c, p.lhs)) {
|
||||
// Float param: SysV uses the XMM stream
|
||||
// (X0..X7). 8B (f64) or 4B (f32) slot.
|
||||
let fsz: i32 = 8;
|
||||
if (isf32type(c, p.lhs)) { fsz = 4; };
|
||||
let off: i32 = localadd(c, nm, fsz, p.lhs);
|
||||
let mov: str = "MOVSD";
|
||||
if (fsz == 4) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitline(fargregname(fidx));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
fidx += 1;
|
||||
p = p.next;
|
||||
continue;
|
||||
};
|
||||
if (istaggedtype(p.lhs)) {
|
||||
// tagged-union param: spill size/8 registers
|
||||
// (tag + value words). Slot sized to match.
|
||||
@@ -9430,21 +9965,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");
|
||||
@@ -9971,6 +10522,21 @@ fn argregname(i: i32) str = {
|
||||
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 "?";
|
||||
};
|
||||
|
||||
// MODULE: w6c
|
||||
// selfhost/cmd/w6c/main.ww — port of cmd/w6c/main.c.
|
||||
//
|
||||
|
||||
@@ -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 "?";
|
||||
};
|
||||
|
||||
@@ -124,9 +124,29 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
let p: *node = params;
|
||||
let idx: i32 = 0;
|
||||
let fidx: i32 = 0;
|
||||
for (p != nil) {
|
||||
if (p.kind == nkind.N_PARAM) {
|
||||
let nm: str = p.str;
|
||||
if (isfloattype(c, p.lhs)) {
|
||||
// Float param: SysV uses the XMM stream
|
||||
// (X0..X7). 8B (f64) or 4B (f32) slot.
|
||||
let fsz: i32 = 8;
|
||||
if (isf32type(c, p.lhs)) { fsz = 4; };
|
||||
let off: i32 = localadd(c, nm, fsz, p.lhs);
|
||||
let mov: str = "MOVSD";
|
||||
if (fsz == 4) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitline(fargregname(fidx));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
fidx += 1;
|
||||
p = p.next;
|
||||
continue;
|
||||
};
|
||||
if (istaggedtype(p.lhs)) {
|
||||
// tagged-union param: spill size/8 registers
|
||||
// (tag + value words). Slot sized to match.
|
||||
|
||||
@@ -34,6 +34,22 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
if (k == nkind.N_FLOATLIT) {
|
||||
// Materialise the f64 bit pattern in AX, push, then MOVSD it
|
||||
// into X0. The bits come from n.uval — the parser populates
|
||||
// it from the lexer's bitcast of t.fval, so this path stays
|
||||
// integer-only (no SSE in the cgen source). The f32
|
||||
// narrowing is handled at the consumer site, not here — the
|
||||
// literal always carries the full double precision until
|
||||
// typed by context.
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(n.uval: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
emitline("\tMOVSD\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
return;
|
||||
};
|
||||
if (k == nkind.N_RUNELIT) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(n.uval: i64);
|
||||
@@ -66,15 +82,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
|
||||
if (k == nkind.N_MATCH) { cgmatch(c, n); return; };
|
||||
|
||||
if (k == nkind.N_CAST) {
|
||||
// Type casts are mostly no-ops at the asm level for our
|
||||
// integer-shaped operands. Evaluate the source; AX holds
|
||||
// the bits unchanged. (Sign- or zero-extending narrow loads
|
||||
// to wider types is the loader's job, not cast's, in this
|
||||
// minimal cgen.)
|
||||
cgexpr(c, n.lhs);
|
||||
return;
|
||||
};
|
||||
if (k == nkind.N_CAST) { cgcast(c, n); return; };
|
||||
|
||||
if (k == nkind.N_DOT) { cgdot(c, n); return; };
|
||||
|
||||
@@ -342,6 +350,46 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
fn cgcast(c: *cgen, n: *node) void = {
|
||||
let srcfk: i32 = exprfloatkind(c, n.lhs);
|
||||
let dstf64: bool = isfloattype(c, n.rhs);
|
||||
let dstf32: bool = isf32type(c, n.rhs);
|
||||
let dstfk: i32 = 0;
|
||||
if (dstf32) { dstfk = 1; }
|
||||
else { if (dstf64) { dstfk = 2; }; };
|
||||
cgexpr(c, n.lhs);
|
||||
// 0=int, 1=f32, 2=f64. CVT picks one direction per combo;
|
||||
// same-kind casts (int↔int with widening differences,
|
||||
// f64→f64 etc.) stay no-ops at the asm level, matching the
|
||||
// pre-port behaviour for integer casts.
|
||||
if (srcfk == 0 && dstfk == 0) { return; };
|
||||
if (srcfk == 0 && dstfk == 2) {
|
||||
emitline("\tCVTSI2SD\tAX, X0\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 0 && dstfk == 1) {
|
||||
emitline("\tCVTSI2SS\tAX, X0\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 2 && dstfk == 0) {
|
||||
emitline("\tCVTTSD2SI\tX0, AX\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 1 && dstfk == 0) {
|
||||
emitline("\tCVTTSS2SI\tX0, AX\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 2 && dstfk == 1) {
|
||||
emitline("\tCVTSD2SS\tX0, X0\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 1 && dstfk == 2) {
|
||||
emitline("\tCVTSS2SD\tX0, X0\n");
|
||||
return;
|
||||
};
|
||||
// Same-kind float→float: nothing to emit.
|
||||
};
|
||||
|
||||
fn cgstrlit(c: *cgen, n: *node) void = {
|
||||
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
|
||||
// sites that expect a str arg pick these up directly.
|
||||
@@ -361,6 +409,19 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, nm);
|
||||
if (lc != nil) {
|
||||
let off: i32 = lc.off;
|
||||
// Float local: MOVSS / MOVSD into X0. Skips the AX shuffle
|
||||
// so consumers (cgbin, cgcast, return) pick up the SSE value
|
||||
// directly.
|
||||
if (isfloattype(c, lc.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, lc.tnode)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP), X0\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -423,6 +484,27 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
};
|
||||
return;
|
||||
};
|
||||
// Float global: same LEAQ-indirect shape, since MOVSS/
|
||||
// MOVSD have no D_EXTERN operand form in w6a.
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, nm)) {
|
||||
if (isfloattype(c, lv.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, lv.tnode)) { mov = "MOVSS"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t(CX), X0\n");
|
||||
return;
|
||||
};
|
||||
lv = nil;
|
||||
} else {
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
@@ -1027,6 +1109,26 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// then apply the unary op. AMP / STAR override AX with the
|
||||
// address / deref. The wasted load before AMP keeps our asm
|
||||
// byte-identical to the C version.
|
||||
let fk: i32 = exprfloatkind(c, n.lhs);
|
||||
if (n.op == tkind.TK_MINUS && fk != 0) {
|
||||
// Float negate: X0 = 0 - X0. Stash orig, load 0.0, subtract.
|
||||
// Zero bit pattern equals 0.0 for both f32 and f64 so we
|
||||
// reuse the integer-zero materialisation.
|
||||
let mov: str = "MOVSD";
|
||||
let sub: str = "SUBSD";
|
||||
if (fk == 1) { mov = "MOVSS"; sub = "SUBSS"; };
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(sub); emitline("\tX1, X0\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.lhs);
|
||||
if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; };
|
||||
if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; };
|
||||
@@ -1073,6 +1175,75 @@ fn cgbin(c: *cgen, n: *node) void = {
|
||||
let unsignd: bool = nodeisunsigned(c, n.lhs);
|
||||
if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); };
|
||||
|
||||
// Float arithmetic: both operands flow through X0. Spill rhs
|
||||
// across the stack (SUBQ/MOVSD/MOVSD/ADDQ) since there's no
|
||||
// general FP register saver. ADDSD/SUBSD/MULSD/DIVSD pick SS
|
||||
// variants for f32. Comparison uses UCOMISD + JCC and falls
|
||||
// out to the existing CMPQ-based path below.
|
||||
let lfk: i32 = exprfloatkind(c, n.lhs);
|
||||
let rfk: i32 = exprfloatkind(c, n.rhs);
|
||||
let fk: i32 = lfk;
|
||||
if (fk == 0) { fk = rfk; };
|
||||
if (fk != 0) {
|
||||
let mov: str = "MOVSD";
|
||||
if (fk == 1) { mov = "MOVSS"; };
|
||||
if (n.op == tkind.TK_PLUS ||
|
||||
n.op == tkind.TK_MINUS ||
|
||||
n.op == tkind.TK_STAR ||
|
||||
n.op == tkind.TK_SLASH) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
let op: str = "ADDSD";
|
||||
if (n.op == tkind.TK_MINUS) { op = "SUBSD"; };
|
||||
if (n.op == tkind.TK_STAR) { op = "MULSD"; };
|
||||
if (n.op == tkind.TK_SLASH) { op = "DIVSD"; };
|
||||
if (fk == 1) {
|
||||
if (n.op == tkind.TK_PLUS) { op = "ADDSS"; };
|
||||
if (n.op == tkind.TK_MINUS) { op = "SUBSS"; };
|
||||
if (n.op == tkind.TK_STAR) { op = "MULSS"; };
|
||||
if (n.op == tkind.TK_SLASH) { op = "DIVSS"; };
|
||||
};
|
||||
emitline("\t"); emitline(op); emitline("\tX1, X0\n");
|
||||
return;
|
||||
};
|
||||
let isfcmp: bool = false;
|
||||
let jcc: str = "";
|
||||
// UCOMISD/SS sets ZF/PF/CF; unordered (NaN) propagates as
|
||||
// "not equal / not less". JA/JAE/JB/JBE keys off CF which
|
||||
// matches the ordered comparisons we need.
|
||||
if (n.op == tkind.TK_EQ) { isfcmp = true; jcc = "JE"; };
|
||||
if (n.op == tkind.TK_NEQ) { isfcmp = true; jcc = "JNE"; };
|
||||
if (n.op == tkind.TK_LT) { isfcmp = true; jcc = "JB"; };
|
||||
if (n.op == tkind.TK_LE) { isfcmp = true; jcc = "JBE"; };
|
||||
if (n.op == tkind.TK_GT) { isfcmp = true; jcc = "JA"; };
|
||||
if (n.op == tkind.TK_GE) { isfcmp = true; jcc = "JAE"; };
|
||||
if (isfcmp) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
let ucomi: str = "UCOMISD";
|
||||
if (fk == 1) { ucomi = "UCOMISS"; };
|
||||
emitline("\t"); emitline(ucomi); emitline("\tX1, X0\n");
|
||||
let t: str = mklabel(c, "ct");
|
||||
let e: str = mklabel(c, "ce");
|
||||
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
||||
emitlabel(t);
|
||||
emitline("\tMOVQ\t$1, AX\n");
|
||||
emitlabel(e);
|
||||
return;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, n.lhs);
|
||||
@@ -1136,11 +1307,61 @@ fn cgbin(c: *cgen, n: *node) void = {
|
||||
|
||||
fn cgcall(c: *cgen, n: *node) void = {
|
||||
let nargs: i32 = pushargsrev(c, n.list);
|
||||
let i: i32 = 0;
|
||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||
// pops into the int stream (DI..R9) per the SysV ABI. Walk the
|
||||
// args list alongside the pop counter so we know each arg's
|
||||
// register class.
|
||||
let intidx: i32 = 0;
|
||||
let fpidx: i32 = 0;
|
||||
let a: *node = n.list;
|
||||
let popped: i32 = 0;
|
||||
for (a != nil) {
|
||||
let fk: i32 = exprfloatkind(c, a);
|
||||
if (fk != 0) {
|
||||
let mov: str = "MOVSD";
|
||||
if (fk == 1) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t(SP), ");
|
||||
emitline(fargregname(fpidx));
|
||||
emitline("\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
fpidx += 1;
|
||||
popped += 1;
|
||||
} else {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(argregname(intidx));
|
||||
emitline("\n");
|
||||
intidx += 1;
|
||||
popped += 1;
|
||||
// Multi-word args (str=2, slice/tagged=3): drain
|
||||
// the remaining words into successive int regs.
|
||||
let extra: i32 = 0;
|
||||
if (nodeisstr(c, a)) { extra = 1; };
|
||||
if (nodeisslice(c, a)) { extra = 2; };
|
||||
let e: i32 = 0;
|
||||
for (e < extra) {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(argregname(intidx));
|
||||
emitline("\n");
|
||||
intidx += 1;
|
||||
popped += 1;
|
||||
e += 1;
|
||||
};
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
// Drain any remaining slots that the arg-walker didn't account
|
||||
// for (tagged-union arg sizes > 8B, struct-by-value, etc.). The
|
||||
// existing C cgen pops these into the int stream, so the worst
|
||||
// case here is identical pre-port behaviour.
|
||||
let i: i32 = popped;
|
||||
for (i < nargs) {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(argregname(i));
|
||||
emitline(argregname(intidx));
|
||||
emitline("\n");
|
||||
intidx += 1;
|
||||
i += 1;
|
||||
};
|
||||
let callee: *node = n.lhs;
|
||||
@@ -1717,6 +1938,33 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
// address into CX and store both halves; the
|
||||
// asm has no `name+8(SB)` operand form.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
// Float global: rhs lands in X0; store via
|
||||
// LEAQ+indirect since MOVSS/MOVSD have no
|
||||
// D_EXTERN operand form.
|
||||
let lvf: *letvar = c.lets;
|
||||
let isfg: bool = false;
|
||||
let isf32g: bool = false;
|
||||
for (lvf != nil) {
|
||||
if (streq(lvf.name, nm)) {
|
||||
isfg = isfloattype(c, lvf.tnode);
|
||||
isf32g = isf32type(c, lvf.tnode);
|
||||
lvf = nil;
|
||||
} else {
|
||||
lvf = lvf.lvnext;
|
||||
};
|
||||
};
|
||||
if (isfg && n.op == tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32g) { mov = "MOVSS"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, (CX)\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (letvarisstr(c, nm)) {
|
||||
@@ -1771,6 +2019,26 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let lcstr: bool = false;
|
||||
let lcn: *local = localfindnode(c, nm);
|
||||
if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); };
|
||||
let lcf: bool = false;
|
||||
let lcf32: bool = false;
|
||||
if (lcn != nil) {
|
||||
lcf = isfloattype(c, lcn.tnode);
|
||||
lcf32 = isf32type(c, lcn.tnode);
|
||||
};
|
||||
// Float-typed local: rhs lands in X0; store via MOVSD/
|
||||
// MOVSS, no AX shuffle. Only plain `=` is wired; compound
|
||||
// float-assign isn't.
|
||||
if (lcf && n.op == tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
let mov: str = "MOVSD";
|
||||
if (lcf32) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
|
||||
@@ -467,6 +467,19 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
// Float local: cgexpr leaves the value in X0. Spill via
|
||||
// MOVSS (f32, 4B) or MOVSD (f64, 8B).
|
||||
if (isfloattype(c, n.lhs)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, n.lhs)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1275,6 +1275,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;
|
||||
@@ -1333,11 +1409,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;
|
||||
@@ -2050,6 +2144,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;
|
||||
@@ -2938,6 +3043,7 @@ type parser = struct {
|
||||
curcol: i32,
|
||||
curtext: str,
|
||||
curuval: u64,
|
||||
curfval: f64,
|
||||
};
|
||||
|
||||
fn refill(p: *parser) void = {
|
||||
@@ -2949,6 +3055,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 = {
|
||||
@@ -4844,6 +4951,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");
|
||||
@@ -5613,6 +5735,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.
|
||||
@@ -5762,6 +5996,22 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
if (k == nkind.N_FLOATLIT) {
|
||||
// Materialise the f64 bit pattern in AX, push, then MOVSD it
|
||||
// into X0. The bits come from n.uval — the parser populates
|
||||
// it from the lexer's bitcast of t.fval, so this path stays
|
||||
// integer-only (no SSE in the cgen source). The f32
|
||||
// narrowing is handled at the consumer site, not here — the
|
||||
// literal always carries the full double precision until
|
||||
// typed by context.
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(n.uval: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
emitline("\tMOVSD\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
return;
|
||||
};
|
||||
if (k == nkind.N_RUNELIT) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(n.uval: i64);
|
||||
@@ -5794,15 +6044,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
|
||||
if (k == nkind.N_MATCH) { cgmatch(c, n); return; };
|
||||
|
||||
if (k == nkind.N_CAST) {
|
||||
// Type casts are mostly no-ops at the asm level for our
|
||||
// integer-shaped operands. Evaluate the source; AX holds
|
||||
// the bits unchanged. (Sign- or zero-extending narrow loads
|
||||
// to wider types is the loader's job, not cast's, in this
|
||||
// minimal cgen.)
|
||||
cgexpr(c, n.lhs);
|
||||
return;
|
||||
};
|
||||
if (k == nkind.N_CAST) { cgcast(c, n); return; };
|
||||
|
||||
if (k == nkind.N_DOT) { cgdot(c, n); return; };
|
||||
|
||||
@@ -6070,6 +6312,46 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
fn cgcast(c: *cgen, n: *node) void = {
|
||||
let srcfk: i32 = exprfloatkind(c, n.lhs);
|
||||
let dstf64: bool = isfloattype(c, n.rhs);
|
||||
let dstf32: bool = isf32type(c, n.rhs);
|
||||
let dstfk: i32 = 0;
|
||||
if (dstf32) { dstfk = 1; }
|
||||
else { if (dstf64) { dstfk = 2; }; };
|
||||
cgexpr(c, n.lhs);
|
||||
// 0=int, 1=f32, 2=f64. CVT picks one direction per combo;
|
||||
// same-kind casts (int↔int with widening differences,
|
||||
// f64→f64 etc.) stay no-ops at the asm level, matching the
|
||||
// pre-port behaviour for integer casts.
|
||||
if (srcfk == 0 && dstfk == 0) { return; };
|
||||
if (srcfk == 0 && dstfk == 2) {
|
||||
emitline("\tCVTSI2SD\tAX, X0\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 0 && dstfk == 1) {
|
||||
emitline("\tCVTSI2SS\tAX, X0\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 2 && dstfk == 0) {
|
||||
emitline("\tCVTTSD2SI\tX0, AX\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 1 && dstfk == 0) {
|
||||
emitline("\tCVTTSS2SI\tX0, AX\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 2 && dstfk == 1) {
|
||||
emitline("\tCVTSD2SS\tX0, X0\n");
|
||||
return;
|
||||
};
|
||||
if (srcfk == 1 && dstfk == 2) {
|
||||
emitline("\tCVTSS2SD\tX0, X0\n");
|
||||
return;
|
||||
};
|
||||
// Same-kind float→float: nothing to emit.
|
||||
};
|
||||
|
||||
fn cgstrlit(c: *cgen, n: *node) void = {
|
||||
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
|
||||
// sites that expect a str arg pick these up directly.
|
||||
@@ -6089,6 +6371,19 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, nm);
|
||||
if (lc != nil) {
|
||||
let off: i32 = lc.off;
|
||||
// Float local: MOVSS / MOVSD into X0. Skips the AX shuffle
|
||||
// so consumers (cgbin, cgcast, return) pick up the SSE value
|
||||
// directly.
|
||||
if (isfloattype(c, lc.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, lc.tnode)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP), X0\n");
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -6151,6 +6446,27 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
};
|
||||
return;
|
||||
};
|
||||
// Float global: same LEAQ-indirect shape, since MOVSS/
|
||||
// MOVSD have no D_EXTERN operand form in w6a.
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, nm)) {
|
||||
if (isfloattype(c, lv.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, lv.tnode)) { mov = "MOVSS"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t(CX), X0\n");
|
||||
return;
|
||||
};
|
||||
lv = nil;
|
||||
} else {
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), AX\n");
|
||||
@@ -6755,6 +7071,26 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// then apply the unary op. AMP / STAR override AX with the
|
||||
// address / deref. The wasted load before AMP keeps our asm
|
||||
// byte-identical to the C version.
|
||||
let fk: i32 = exprfloatkind(c, n.lhs);
|
||||
if (n.op == tkind.TK_MINUS && fk != 0) {
|
||||
// Float negate: X0 = 0 - X0. Stash orig, load 0.0, subtract.
|
||||
// Zero bit pattern equals 0.0 for both f32 and f64 so we
|
||||
// reuse the integer-zero materialisation.
|
||||
let mov: str = "MOVSD";
|
||||
let sub: str = "SUBSD";
|
||||
if (fk == 1) { mov = "MOVSS"; sub = "SUBSS"; };
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(sub); emitline("\tX1, X0\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.lhs);
|
||||
if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; };
|
||||
if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; };
|
||||
@@ -6801,6 +7137,75 @@ fn cgbin(c: *cgen, n: *node) void = {
|
||||
let unsignd: bool = nodeisunsigned(c, n.lhs);
|
||||
if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); };
|
||||
|
||||
// Float arithmetic: both operands flow through X0. Spill rhs
|
||||
// across the stack (SUBQ/MOVSD/MOVSD/ADDQ) since there's no
|
||||
// general FP register saver. ADDSD/SUBSD/MULSD/DIVSD pick SS
|
||||
// variants for f32. Comparison uses UCOMISD + JCC and falls
|
||||
// out to the existing CMPQ-based path below.
|
||||
let lfk: i32 = exprfloatkind(c, n.lhs);
|
||||
let rfk: i32 = exprfloatkind(c, n.rhs);
|
||||
let fk: i32 = lfk;
|
||||
if (fk == 0) { fk = rfk; };
|
||||
if (fk != 0) {
|
||||
let mov: str = "MOVSD";
|
||||
if (fk == 1) { mov = "MOVSS"; };
|
||||
if (n.op == tkind.TK_PLUS ||
|
||||
n.op == tkind.TK_MINUS ||
|
||||
n.op == tkind.TK_STAR ||
|
||||
n.op == tkind.TK_SLASH) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
let op: str = "ADDSD";
|
||||
if (n.op == tkind.TK_MINUS) { op = "SUBSD"; };
|
||||
if (n.op == tkind.TK_STAR) { op = "MULSD"; };
|
||||
if (n.op == tkind.TK_SLASH) { op = "DIVSD"; };
|
||||
if (fk == 1) {
|
||||
if (n.op == tkind.TK_PLUS) { op = "ADDSS"; };
|
||||
if (n.op == tkind.TK_MINUS) { op = "SUBSS"; };
|
||||
if (n.op == tkind.TK_STAR) { op = "MULSS"; };
|
||||
if (n.op == tkind.TK_SLASH) { op = "DIVSS"; };
|
||||
};
|
||||
emitline("\t"); emitline(op); emitline("\tX1, X0\n");
|
||||
return;
|
||||
};
|
||||
let isfcmp: bool = false;
|
||||
let jcc: str = "";
|
||||
// UCOMISD/SS sets ZF/PF/CF; unordered (NaN) propagates as
|
||||
// "not equal / not less". JA/JAE/JB/JBE keys off CF which
|
||||
// matches the ordered comparisons we need.
|
||||
if (n.op == tkind.TK_EQ) { isfcmp = true; jcc = "JE"; };
|
||||
if (n.op == tkind.TK_NEQ) { isfcmp = true; jcc = "JNE"; };
|
||||
if (n.op == tkind.TK_LT) { isfcmp = true; jcc = "JB"; };
|
||||
if (n.op == tkind.TK_LE) { isfcmp = true; jcc = "JBE"; };
|
||||
if (n.op == tkind.TK_GT) { isfcmp = true; jcc = "JA"; };
|
||||
if (n.op == tkind.TK_GE) { isfcmp = true; jcc = "JAE"; };
|
||||
if (isfcmp) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
let ucomi: str = "UCOMISD";
|
||||
if (fk == 1) { ucomi = "UCOMISS"; };
|
||||
emitline("\t"); emitline(ucomi); emitline("\tX1, X0\n");
|
||||
let t: str = mklabel(c, "ct");
|
||||
let e: str = mklabel(c, "ce");
|
||||
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
||||
emitlabel(t);
|
||||
emitline("\tMOVQ\t$1, AX\n");
|
||||
emitlabel(e);
|
||||
return;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, n.lhs);
|
||||
@@ -6864,11 +7269,61 @@ fn cgbin(c: *cgen, n: *node) void = {
|
||||
|
||||
fn cgcall(c: *cgen, n: *node) void = {
|
||||
let nargs: i32 = pushargsrev(c, n.list);
|
||||
let i: i32 = 0;
|
||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||
// pops into the int stream (DI..R9) per the SysV ABI. Walk the
|
||||
// args list alongside the pop counter so we know each arg's
|
||||
// register class.
|
||||
let intidx: i32 = 0;
|
||||
let fpidx: i32 = 0;
|
||||
let a: *node = n.list;
|
||||
let popped: i32 = 0;
|
||||
for (a != nil) {
|
||||
let fk: i32 = exprfloatkind(c, a);
|
||||
if (fk != 0) {
|
||||
let mov: str = "MOVSD";
|
||||
if (fk == 1) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t(SP), ");
|
||||
emitline(fargregname(fpidx));
|
||||
emitline("\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
fpidx += 1;
|
||||
popped += 1;
|
||||
} else {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(argregname(intidx));
|
||||
emitline("\n");
|
||||
intidx += 1;
|
||||
popped += 1;
|
||||
// Multi-word args (str=2, slice/tagged=3): drain
|
||||
// the remaining words into successive int regs.
|
||||
let extra: i32 = 0;
|
||||
if (nodeisstr(c, a)) { extra = 1; };
|
||||
if (nodeisslice(c, a)) { extra = 2; };
|
||||
let e: i32 = 0;
|
||||
for (e < extra) {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(argregname(intidx));
|
||||
emitline("\n");
|
||||
intidx += 1;
|
||||
popped += 1;
|
||||
e += 1;
|
||||
};
|
||||
};
|
||||
a = a.next;
|
||||
};
|
||||
// Drain any remaining slots that the arg-walker didn't account
|
||||
// for (tagged-union arg sizes > 8B, struct-by-value, etc.). The
|
||||
// existing C cgen pops these into the int stream, so the worst
|
||||
// case here is identical pre-port behaviour.
|
||||
let i: i32 = popped;
|
||||
for (i < nargs) {
|
||||
emitline("\tPOPQ\t");
|
||||
emitline(argregname(i));
|
||||
emitline(argregname(intidx));
|
||||
emitline("\n");
|
||||
intidx += 1;
|
||||
i += 1;
|
||||
};
|
||||
let callee: *node = n.lhs;
|
||||
@@ -7445,6 +7900,33 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
// address into CX and store both halves; the
|
||||
// asm has no `name+8(SB)` operand form.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
// Float global: rhs lands in X0; store via
|
||||
// LEAQ+indirect since MOVSS/MOVSD have no
|
||||
// D_EXTERN operand form.
|
||||
let lvf: *letvar = c.lets;
|
||||
let isfg: bool = false;
|
||||
let isf32g: bool = false;
|
||||
for (lvf != nil) {
|
||||
if (streq(lvf.name, nm)) {
|
||||
isfg = isfloattype(c, lvf.tnode);
|
||||
isf32g = isf32type(c, lvf.tnode);
|
||||
lvf = nil;
|
||||
} else {
|
||||
lvf = lvf.lvnext;
|
||||
};
|
||||
};
|
||||
if (isfg && n.op == tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32g) { mov = "MOVSS"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, (CX)\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (letvarisstr(c, nm)) {
|
||||
@@ -7499,6 +7981,26 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let lcstr: bool = false;
|
||||
let lcn: *local = localfindnode(c, nm);
|
||||
if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); };
|
||||
let lcf: bool = false;
|
||||
let lcf32: bool = false;
|
||||
if (lcn != nil) {
|
||||
lcf = isfloattype(c, lcn.tnode);
|
||||
lcf32 = isf32type(c, lcn.tnode);
|
||||
};
|
||||
// Float-typed local: rhs lands in X0; store via MOVSD/
|
||||
// MOVSS, no AX shuffle. Only plain `=` is wired; compound
|
||||
// float-assign isn't.
|
||||
if (lcf && n.op == tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
let mov: str = "MOVSD";
|
||||
if (lcf32) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -8020,6 +8522,19 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
// Float local: cgexpr leaves the value in X0. Spill via
|
||||
// MOVSS (f32, 4B) or MOVSD (f64, 8B).
|
||||
if (isfloattype(c, n.lhs)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, n.lhs)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
@@ -8403,9 +8918,29 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
let p: *node = params;
|
||||
let idx: i32 = 0;
|
||||
let fidx: i32 = 0;
|
||||
for (p != nil) {
|
||||
if (p.kind == nkind.N_PARAM) {
|
||||
let nm: str = p.str;
|
||||
if (isfloattype(c, p.lhs)) {
|
||||
// Float param: SysV uses the XMM stream
|
||||
// (X0..X7). 8B (f64) or 4B (f32) slot.
|
||||
let fsz: i32 = 8;
|
||||
if (isf32type(c, p.lhs)) { fsz = 4; };
|
||||
let off: i32 = localadd(c, nm, fsz, p.lhs);
|
||||
let mov: str = "MOVSD";
|
||||
if (fsz == 4) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t");
|
||||
emitline(fargregname(fidx));
|
||||
emitline(", ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
fidx += 1;
|
||||
p = p.next;
|
||||
continue;
|
||||
};
|
||||
if (istaggedtype(p.lhs)) {
|
||||
// tagged-union param: spill size/8 registers
|
||||
// (tag + value words). Slot sized to match.
|
||||
@@ -9430,21 +9965,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");
|
||||
@@ -9971,6 +10522,21 @@ fn argregname(i: i32) str = {
|
||||
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 "?";
|
||||
};
|
||||
|
||||
// MODULE: wwdump
|
||||
// selfhost/cmd/wwdump/main.ww — ww-side port of cmd/wwdump/main.c.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user