selfhost: fix several wwstage cgen miscompilations
Surfaced via examples/lisp, which had to work around the following in
source. Each lowering now matches cstage on the same shape.
- cgassign / cgdot: two-level field through a non-pointer sub-struct.
`(*L).cur.kind = k` (cur a struct-by-value field of L) silently
dropped the store; the corresponding read fell into the SB-symbol
fallback and the linker reported `undefined reference to kind`. The
two new branches resolve outer-field offset + inner-field offset
and emit a single direct store/load at the combined slot, both for
T-by-value and *T-base shapes.
- cgdot: `xs[i].field` chains the trailing field load through the
N_INDEX result for [N]T / []T / *T element-of-struct-ptr. The
cgforrange loop variable now carries the elem tnode so the same
fast path covers `for (let x .. xs) { x.field }`.
- cgindex / cgassign: top-level `[N]T` array and `*T` pointer used
as an index base. cgindex now emits LEAQ name(SB) (array) or
MOVQ name(SB) (pointer) with the correct element scaling; without
this the fallback emitted neither base and walked off the saved
BP slot. Adds letvartnode() helper, an N_TARRAY branch to
letemitsize so the array shows up in c.lets, and an N_TARRAY
initialiser path in emitletdataw that lays the literal bytes into
DATAW.
- cglet / scanlocals: infer the local's tnode for an unannotated
`let x = f()` / `let x = f()?`. inferletcalltype() reads the
callee's declared return; `?` and `!` strip to the success variant
so a tagged-union let allocates the full 24B slot and the
struct-field dispatch in cgdot/cgassign sees the right type.
letslotsize now defers to slotsize on the inferred type.
- slotsize: follow type aliases for tagged-union variants. With
`type parserr = !str;`, the variant slot was 8B instead of the
required 16B; the tagged let stomped on the next slot at the
AX/DX/CX spill.
- cgreturn: tagged-union return forwarding. `return f();` where f
also returns a tagged union now passes the (tag, payload1,
payload2) triple through unchanged instead of re-wrapping it.
- cgreturn / cglet / taggedvariantindex: dispatch by variant name
with module-qualified-vs-bare matching, and recognise N_STRUCTLIT
as the variant tag for `return eof{};`. cgexpr default emits
`MOVQ $0, AX` so the surrounding return shuffle isn't left with
a stale AX.
- isstrtype / nodeisstr: resolve through `!T` aliases. `parserr =
!str` was not propagating the str-shape to the rhs check and the
MOVQ BX,CX shuffle was being dropped from str-typed local
returns.
- exprfloatkind: recognise `p.field` as f64/f32 when the struct
field is so declared, so `v.fval: i64` lowers to CVTTSD2SI on X0.
- cgassign: str field on a direct struct local writes both halves.
`L.src = s;` previously dropped s.len.
- cgcall: pop into the int reg window only up to 6 (DI..R9); rest
stays on the stack and the caller emits ADDQ to clean up.
cgfnparams accepts >6-arg signatures by registering the overflow
params at positive BP offsets (16+8*k(BP)), no spill instruction
emitted.
All 26 harness tests pass; bootstrap reaches a byte-stable fixed
point at ww3 == ww4.
This commit is contained in:
@@ -381,6 +381,20 @@ fn localalloc(c: *cgen, name: str, sz: i32, tnode: *node) i32 = {
|
||||
return off;
|
||||
};
|
||||
|
||||
// localaddstack — register a param at a positive BP offset. Used for
|
||||
// args that overflow the 6 SysV int / 8 float reg windows; the caller
|
||||
// pushes them in reverse, so each spilled arg lives at 16(BP), 24(BP),
|
||||
// etc. (after the saved RIP+BP). No spill instruction is emitted; the
|
||||
// slot IS the caller's stack slot.
|
||||
fn localaddstack(c: *cgen, name: str, tnode: *node, off: i32) void = {
|
||||
let l: *local = amalloc(c.a, 48u64): *local;
|
||||
l.name = name;
|
||||
l.off = off;
|
||||
l.tnode = tnode;
|
||||
l.lnext = c.locals;
|
||||
c.locals = l;
|
||||
};
|
||||
|
||||
fn localadd(c: *cgen, name: str, sz: i32, tnode: *node) i32 = {
|
||||
// Name-based slot reuse for N_LETs and params: if `name` is
|
||||
// already declared in this function, return its existing
|
||||
@@ -634,6 +648,22 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
|
||||
for (t != nil) {
|
||||
if (t.kind == nkind.N_TPTR) { return 8; };
|
||||
if (t.kind == nkind.N_TSLICE) { return 24; };
|
||||
if (t.kind == nkind.N_TARRAY) {
|
||||
let lenn: *node = t.rhs;
|
||||
let elemn: *node = t.lhs;
|
||||
let alen: i32 = 1;
|
||||
if (lenn != nil) {
|
||||
if (lenn.kind == nkind.N_INTLIT) { alen = lenn.uval: i32; };
|
||||
};
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
return alen * esz;
|
||||
};
|
||||
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||
let nm: str = t.str;
|
||||
if (letscalarprim(nm)) { return 8; };
|
||||
@@ -683,6 +713,19 @@ fn isletvar(c: *cgen, name: str) bool = {
|
||||
// aliases to mirror C cgen's `let_isstr`. Used by cgident/cgdot/
|
||||
// cgassign to pick the (LEAQ, MOVQ, MOVQ) sequence over the bare
|
||||
// MOVQ scalar load.
|
||||
// letvartnode — direct lookup of a top-level let's tnode. Used by
|
||||
// cgindex / cgassign to detect global `[N]T` arrays and `*T`
|
||||
// pointers, where the addressing path needs LEAQ name(SB) (array)
|
||||
// or MOVQ name(SB) (pointer) and the element size from T.
|
||||
fn letvartnode(c: *cgen, name: str) *node = {
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
if (streq(lv.name, name)) { return lv.tnode; };
|
||||
lv = lv.lvnext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
fn letvarisstr(c: *cgen, name: str) bool = {
|
||||
let lv: *letvar = c.lets;
|
||||
for (lv != nil) {
|
||||
@@ -1055,6 +1098,73 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
emitline("\"\n");
|
||||
};
|
||||
};
|
||||
// Top-level `[N]T = [a, b, ...]` array global.
|
||||
// Emits N*esz bytes with each element's bytes
|
||||
// little-endian for the declared primitive width.
|
||||
// Without this, `let arr: [N]T = ...` references
|
||||
// from function bodies link-fail with `undefined
|
||||
// reference to arr`, and bare-name addressing
|
||||
// (LEAQ arr(SB)) inside cgindex / cgassign has no
|
||||
// symbol to bind to.
|
||||
if (d.lhs != nil) {
|
||||
if (d.lhs.kind == nkind.N_TARRAY) {
|
||||
let elemn: *node = d.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
let total: i32 = sz;
|
||||
let alen: i32 = total / esz;
|
||||
let elems: *node = nil;
|
||||
if (d.rhs != nil) {
|
||||
if (d.rhs.kind == nkind.N_ARRLIT) {
|
||||
elems = d.rhs.list;
|
||||
};
|
||||
};
|
||||
emitline("DATAW ");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB),\"");
|
||||
let i: i32 = 0;
|
||||
let e: *node = elems;
|
||||
let fillv: u64 = 0u64;
|
||||
let inrepeat: bool = false;
|
||||
for (i < alen) {
|
||||
let v: u64 = fillv;
|
||||
if (!inrepeat && e != nil) {
|
||||
if (e.kind == nkind.N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
// `..., ...` repeat marker: prior v stays.
|
||||
inrepeat = true;
|
||||
} else {
|
||||
if (e.lhs != nil) {
|
||||
if (e.lhs.kind == nkind.N_INTLIT) { v = e.lhs.uval; };
|
||||
if (e.lhs.kind == nkind.N_RUNELIT) { v = e.lhs.uval; };
|
||||
};
|
||||
fillv = v;
|
||||
e = e.next;
|
||||
};
|
||||
} else {
|
||||
if (e.kind == nkind.N_INTLIT) { v = e.uval; };
|
||||
if (e.kind == nkind.N_RUNELIT) { v = e.uval; };
|
||||
fillv = v;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
let nb: u64 = v;
|
||||
let b: i32 = 0;
|
||||
for (b < esz) {
|
||||
emitdatawbyte((nb & 255u64): u8);
|
||||
nb = nb >> 8u64;
|
||||
b += 1;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
emitline("\"\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
d = d.next;
|
||||
|
||||
Reference in New Issue
Block a user