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:
@@ -143,6 +143,25 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// Nullable folded `(*T | void)`: just one word; AX is
|
||||
// already the pointer (or 0). No shuffle, no tag.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
// Forwarding a fallible call: `return f();` where f
|
||||
// also returns a tagged union. The result is already
|
||||
// in (AX=tag, DX=v0, CX=v1) — no shuffle, no tag.
|
||||
// Mirrors the rhsreturnstagged path in cglet and the
|
||||
// !type_istagged guard in C cgen's N_RETURN.
|
||||
let forwardtagged: bool = false;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let calleename: str;
|
||||
calleename.ptr = nil; calleename.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { calleename = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (istaggedtype(rt)) { forwardtagged = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
if (isnullabletype(c.fnret)) {
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
@@ -151,6 +170,13 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
if (forwardtagged) {
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
emitline("\tRET\n");
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
let idx: i32 = taggedvariantindex(c, c.fnret, rhs);
|
||||
if (nodeisstr(c, rhs)) {
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
@@ -213,7 +239,12 @@ fn cgexprstmt(c: *cgen, n: *node) void = {
|
||||
fn cglet(c: *cgen, n: *node) void = {
|
||||
let nm: str = n.str;
|
||||
let sz: i32 = letslotsize(c, n);
|
||||
let off: i32 = localadd(c, nm, sz, n.lhs);
|
||||
// `let x = f()?` has no annotation but the cgen's struct-field
|
||||
// paths need a tnode to dispatch off. Infer from f's tagged
|
||||
// success variant — see inferletcalltype.
|
||||
let tn: *node = n.lhs;
|
||||
if (tn == nil) { tn = inferletcalltype(c, n.rhs); };
|
||||
let off: i32 = localadd(c, nm, sz, tn);
|
||||
if (n.rhs != nil) {
|
||||
let rhs: *node = n.rhs;
|
||||
// Tagged-union init: `let r: (T | E) = expr;`.
|
||||
@@ -222,8 +253,8 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// just spill all three.
|
||||
// - Otherwise rhs is a bare variant value: pack tag +
|
||||
// value(s).
|
||||
if (istaggedtype(n.lhs)) {
|
||||
let nullable: bool = isnullabletype(n.lhs);
|
||||
if (istaggedtype(tn)) {
|
||||
let nullable: bool = isnullabletype(tn);
|
||||
let rhsreturnstagged: bool = false;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
@@ -266,7 +297,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
let tagidx: i32 = taggedvariantindex(c, n.lhs, rhs);
|
||||
let tagidx: i32 = taggedvariantindex(c, tn, rhs);
|
||||
if (tagidx < 0) { tagidx = 0; };
|
||||
if (nodeisstr(c, rhs)) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -841,9 +872,9 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
bind_signed[nbinds] = signf;
|
||||
let bnm: str = m.str;
|
||||
if (bnm.len > 0) {
|
||||
bind_off[nbinds] = localadd(c, bnm, slot_sz, nil);
|
||||
bind_off[nbinds] = localadd(c, bnm, slot_sz, tp);
|
||||
} else {
|
||||
bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil);
|
||||
bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, tp);
|
||||
};
|
||||
field_off += fsz;
|
||||
nbinds += 1;
|
||||
@@ -863,9 +894,12 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
||||
bind_signed[0] = paramissigned(elemt);
|
||||
};
|
||||
if (n.str.len > 0) {
|
||||
bind_off[0] = localadd(c, n.str, slot_sz, nil);
|
||||
// Register with elem tnode so x.field on a loop
|
||||
// var resolves through the standard local-typed
|
||||
// path instead of falling into the SB fallback.
|
||||
bind_off[0] = localadd(c, n.str, slot_sz, elemt);
|
||||
} else {
|
||||
bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil);
|
||||
bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, elemt);
|
||||
};
|
||||
nbinds = 1;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user