selfhost: mirror nullable pointer folding for (*T | void)
C-cgen-side nullable folding landed in f4efaac. This commit catches
the selfhost cgen up so a wwstage-compiled binary produces the same
ABI for `(*T | void)`.
- cgenutil: isnullabletype(), nullableptrtag() helpers shaped to
the selfhost cgen's AST-only world view (it doesn't carry a Type
with a .nullable flag — it walks N_TTAGGED node lists). slotsize
returns 8 for nullable.
- cgenexpr cgmatch: nullable arm uses pointer-vs-null discriminator
and binds only the *T case (void has size 0).
- cgenstmt cglet: nullable target spills only AX (no tag word, no
value-word DX/CX).
- cgenstmt cgreturn: nullable return passes AX through with no
shuffle; bare `return;` emits AX = 0 (void encoding).
Verified end-to-end: a fn returning `(*i32 | void)` compiled by the
wwstage cgen produces the same exit code as the C-cgen build. The
995 fixed-point gate stays green — no selfhost source uses nullable
yet, so the existing tagged paths are still byte-identical.
This commit is contained in:
@@ -99,8 +99,17 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// For str variant, cgexpr leaves (AX=ptr, BX=len), so we
|
||||
// shuffle DX←AX (ptr) and CX←BX (len), then load tag.
|
||||
// For other variants, cgexpr leaves AX, shuffle DX←AX.
|
||||
// Nullable folded `(*T | void)`: just one word; AX is
|
||||
// already the pointer (or 0). No shuffle, no tag.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
cgexpr(c, rhs);
|
||||
if (isnullabletype(c.fnret)) {
|
||||
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");
|
||||
@@ -124,11 +133,16 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// the void variant: emit its tag. Payload is undefined
|
||||
// (void has size 0). Otherwise zero AX for determinism.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
let idx: i32 = voidvariantindex(c.fnret);
|
||||
if (idx < 0) { idx = 0; };
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(idx: i64);
|
||||
emitline(", AX\n");
|
||||
if (isnullabletype(c.fnret)) {
|
||||
// null = void variant; AX = 0.
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
} else {
|
||||
let idx: i32 = voidvariantindex(c.fnret);
|
||||
if (idx < 0) { idx = 0; };
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(idx: i64);
|
||||
emitline(", AX\n");
|
||||
};
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
emitline("\tRET\n");
|
||||
@@ -168,6 +182,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// - Otherwise rhs is a bare variant value: pack tag +
|
||||
// value(s).
|
||||
if (istaggedtype(n.lhs)) {
|
||||
let nullable: bool = isnullabletype(n.lhs);
|
||||
let rhsreturnstagged: bool = false;
|
||||
if (rhs.kind == N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
@@ -183,6 +198,16 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
if (nullable) {
|
||||
// Slot is one 8B word; AX is the pointer
|
||||
// (or 0 for null/void). Same path whether
|
||||
// the rhs is a call or a bare variant.
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
if (rhsreturnstagged) {
|
||||
// Spill size/8 registers (tag + value words).
|
||||
// Slots smaller than 24 don't carry a CX word.
|
||||
|
||||
Reference in New Issue
Block a user