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:
@@ -4677,6 +4677,8 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
return total;
|
||||
};
|
||||
if (k == N_TTAGGED){
|
||||
// Nullable `(*T | void)` collapses to a single 8B pointer.
|
||||
if (isnullabletype(typn)) { return 8; };
|
||||
// Slot = 8 (tag) + max(variant payload sizes), rounded up
|
||||
// to an 8-byte multiple so the reg-passing ABI (size/8
|
||||
// words) doesn't drop the last value register. Mirrors C
|
||||
@@ -4864,6 +4866,38 @@ fn istaggedtype(t: *node) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// isnullabletype — 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.
|
||||
export fn isnullabletype(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind != N_TTAGGED) { return false; };
|
||||
let a: *node = t.list;
|
||||
if (a == nil) { return false; };
|
||||
let b: *node = a.next;
|
||||
if (b == nil) { return false; };
|
||||
if (b.next != nil) { return false; };
|
||||
let aptr: bool = (a.kind == N_TPTR);
|
||||
let bptr: bool = (b.kind == N_TPTR);
|
||||
let avoid: bool = (a.kind == N_TNAME);
|
||||
if (avoid) { avoid = streq(a.str, "void"); };
|
||||
let bvoid: bool = (b.kind == N_TNAME);
|
||||
if (bvoid) { bvoid = streq(b.str, "void"); };
|
||||
if (aptr) { if (bvoid) { return true; }; };
|
||||
if (avoid) { if (bptr) { return true; }; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// nullableptrtag — 0-based index of the *T variant in a nullable
|
||||
// union. The void variant takes the other slot (0 or 1).
|
||||
export fn nullableptrtag(t: *node) i32 = {
|
||||
if (t == nil) { return 0; };
|
||||
if (t.kind != N_TTAGGED) { return 0; };
|
||||
let a: *node = t.list;
|
||||
if (a != nil) { if (a.kind == N_TPTR) { return 0; }; };
|
||||
return 1;
|
||||
};
|
||||
|
||||
// voidvariantindex — find the 0-based index of the `void` variant in a
|
||||
// tagged-union type expr, -1 if absent. Used by cgreturn to map bare
|
||||
// `return;` in a tagged-union-returning fn to the void variant's tag.
|
||||
@@ -5389,68 +5423,105 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
for (cs != nil) {
|
||||
let nxt: str = mklabel(c, "match_next");
|
||||
let pat: *node = cs.lhs;
|
||||
let nullable: bool = isnullabletype(scrutt);
|
||||
// Compute the variant tag for this arm. Default arm
|
||||
// (no pattern) skips the tag check.
|
||||
if (pat != nil) {
|
||||
let want: i32 = 0;
|
||||
if (scrutt != nil) {
|
||||
if (scrutt.kind == N_TTAGGED) {
|
||||
let patname: str;
|
||||
patname.ptr = nil; patname.len = 0;
|
||||
if (pat.kind == N_TNAME) { patname = pat.str; };
|
||||
let v: *node = scrutt.list;
|
||||
let idx: i32 = 0;
|
||||
let found: bool = false;
|
||||
for (v != nil) {
|
||||
if (v.kind == N_TNAME) {
|
||||
if (streq(v.str, patname)) {
|
||||
want = idx;
|
||||
found = true;
|
||||
v = nil;
|
||||
if (nullable) {
|
||||
// Discriminator = pointer-vs-null.
|
||||
// *T arm: skip if ptr == 0.
|
||||
// void arm: skip if ptr != 0.
|
||||
let ptr_tag: i32 = nullableptrtag(scrutt);
|
||||
let cur_tag: i32 = 0;
|
||||
if (pat.kind == N_TPTR) { cur_tag = ptr_tag; }
|
||||
else { if (ptr_tag == 0) { cur_tag = 1; }; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
if (cur_tag == ptr_tag) {
|
||||
emitline("\tJE\t");
|
||||
} else {
|
||||
emitline("\tJNE\t");
|
||||
};
|
||||
emitline(nxt);
|
||||
emitline("\n");
|
||||
} else {
|
||||
let want: i32 = 0;
|
||||
if (scrutt != nil) {
|
||||
if (scrutt.kind == N_TTAGGED) {
|
||||
let patname: str;
|
||||
patname.ptr = nil; patname.len = 0;
|
||||
if (pat.kind == N_TNAME) { patname = pat.str; };
|
||||
let v: *node = scrutt.list;
|
||||
let idx: i32 = 0;
|
||||
let found: bool = false;
|
||||
for (v != nil) {
|
||||
if (v.kind == N_TNAME) {
|
||||
if (streq(v.str, patname)) {
|
||||
want = idx;
|
||||
found = true;
|
||||
v = nil;
|
||||
};
|
||||
};
|
||||
if (v != nil) {
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
if (v != nil) {
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
if (!found) { want = 0; };
|
||||
};
|
||||
if (!found) { want = 0; };
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(want: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(nxt);
|
||||
emitline("\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(want: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(nxt);
|
||||
emitline("\n");
|
||||
};
|
||||
// Bind `let v: T` from the slot, if requested.
|
||||
let bn: str = cs.str;
|
||||
if (bn.len > 0) {
|
||||
if (pat != nil) {
|
||||
let bsz: i32 = 8;
|
||||
if (isstrtype(c, pat)) { bsz = 16; };
|
||||
// localalloc (not localadd): match-arm
|
||||
// binds don't dedup with same-named binds
|
||||
// in *other* matches, since C's cgexpr
|
||||
// allocates a fresh slot per match expr.
|
||||
let voff: i32 = localalloc(c, bn, bsz, pat);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(voff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (bsz == 16) {
|
||||
if (nullable) {
|
||||
// Bind the pointer (or skip for the
|
||||
// void arm, which has zero-size). The
|
||||
// value IS slot+0.
|
||||
if (pat.kind == N_TPTR) {
|
||||
let voff: i32 = localalloc(c, bn, 8, pat);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(voff: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
} else {
|
||||
let bsz: i32 = 8;
|
||||
if (isstrtype(c, pat)) { bsz = 16; };
|
||||
// localalloc (not localadd): match-arm
|
||||
// binds don't dedup with same-named binds
|
||||
// in *other* matches, since C's cgexpr
|
||||
// allocates a fresh slot per match expr.
|
||||
let voff: i32 = localalloc(c, bn, bsz, pat);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((voff + 8): i64);
|
||||
emitoff(voff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (bsz == 16) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((voff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -6509,8 +6580,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");
|
||||
@@ -6534,11 +6614,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");
|
||||
@@ -6578,6 +6663,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;
|
||||
@@ -6593,6 +6679,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.
|
||||
|
||||
@@ -442,68 +442,105 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
for (cs != nil) {
|
||||
let nxt: str = mklabel(c, "match_next");
|
||||
let pat: *node = cs.lhs;
|
||||
let nullable: bool = isnullabletype(scrutt);
|
||||
// Compute the variant tag for this arm. Default arm
|
||||
// (no pattern) skips the tag check.
|
||||
if (pat != nil) {
|
||||
let want: i32 = 0;
|
||||
if (scrutt != nil) {
|
||||
if (scrutt.kind == N_TTAGGED) {
|
||||
let patname: str;
|
||||
patname.ptr = nil; patname.len = 0;
|
||||
if (pat.kind == N_TNAME) { patname = pat.str; };
|
||||
let v: *node = scrutt.list;
|
||||
let idx: i32 = 0;
|
||||
let found: bool = false;
|
||||
for (v != nil) {
|
||||
if (v.kind == N_TNAME) {
|
||||
if (streq(v.str, patname)) {
|
||||
want = idx;
|
||||
found = true;
|
||||
v = nil;
|
||||
if (nullable) {
|
||||
// Discriminator = pointer-vs-null.
|
||||
// *T arm: skip if ptr == 0.
|
||||
// void arm: skip if ptr != 0.
|
||||
let ptr_tag: i32 = nullableptrtag(scrutt);
|
||||
let cur_tag: i32 = 0;
|
||||
if (pat.kind == N_TPTR) { cur_tag = ptr_tag; }
|
||||
else { if (ptr_tag == 0) { cur_tag = 1; }; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
if (cur_tag == ptr_tag) {
|
||||
emitline("\tJE\t");
|
||||
} else {
|
||||
emitline("\tJNE\t");
|
||||
};
|
||||
emitline(nxt);
|
||||
emitline("\n");
|
||||
} else {
|
||||
let want: i32 = 0;
|
||||
if (scrutt != nil) {
|
||||
if (scrutt.kind == N_TTAGGED) {
|
||||
let patname: str;
|
||||
patname.ptr = nil; patname.len = 0;
|
||||
if (pat.kind == N_TNAME) { patname = pat.str; };
|
||||
let v: *node = scrutt.list;
|
||||
let idx: i32 = 0;
|
||||
let found: bool = false;
|
||||
for (v != nil) {
|
||||
if (v.kind == N_TNAME) {
|
||||
if (streq(v.str, patname)) {
|
||||
want = idx;
|
||||
found = true;
|
||||
v = nil;
|
||||
};
|
||||
};
|
||||
if (v != nil) {
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
if (v != nil) {
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
if (!found) { want = 0; };
|
||||
};
|
||||
if (!found) { want = 0; };
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(want: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(nxt);
|
||||
emitline("\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(want: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(nxt);
|
||||
emitline("\n");
|
||||
};
|
||||
// Bind `let v: T` from the slot, if requested.
|
||||
let bn: str = cs.str;
|
||||
if (bn.len > 0) {
|
||||
if (pat != nil) {
|
||||
let bsz: i32 = 8;
|
||||
if (isstrtype(c, pat)) { bsz = 16; };
|
||||
// localalloc (not localadd): match-arm
|
||||
// binds don't dedup with same-named binds
|
||||
// in *other* matches, since C's cgexpr
|
||||
// allocates a fresh slot per match expr.
|
||||
let voff: i32 = localalloc(c, bn, bsz, pat);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(voff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (bsz == 16) {
|
||||
if (nullable) {
|
||||
// Bind the pointer (or skip for the
|
||||
// void arm, which has zero-size). The
|
||||
// value IS slot+0.
|
||||
if (pat.kind == N_TPTR) {
|
||||
let voff: i32 = localalloc(c, bn, 8, pat);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(voff: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
} else {
|
||||
let bsz: i32 = 8;
|
||||
if (isstrtype(c, pat)) { bsz = 16; };
|
||||
// localalloc (not localadd): match-arm
|
||||
// binds don't dedup with same-named binds
|
||||
// in *other* matches, since C's cgexpr
|
||||
// allocates a fresh slot per match expr.
|
||||
let voff: i32 = localalloc(c, bn, bsz, pat);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((voff + 8): i64);
|
||||
emitoff(voff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (bsz == 16) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((voff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -703,6 +703,8 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
return total;
|
||||
};
|
||||
if (k == N_TTAGGED){
|
||||
// Nullable `(*T | void)` collapses to a single 8B pointer.
|
||||
if (isnullabletype(typn)) { return 8; };
|
||||
// Slot = 8 (tag) + max(variant payload sizes), rounded up
|
||||
// to an 8-byte multiple so the reg-passing ABI (size/8
|
||||
// words) doesn't drop the last value register. Mirrors C
|
||||
@@ -890,6 +892,38 @@ fn istaggedtype(t: *node) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// isnullabletype — 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.
|
||||
export fn isnullabletype(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind != N_TTAGGED) { return false; };
|
||||
let a: *node = t.list;
|
||||
if (a == nil) { return false; };
|
||||
let b: *node = a.next;
|
||||
if (b == nil) { return false; };
|
||||
if (b.next != nil) { return false; };
|
||||
let aptr: bool = (a.kind == N_TPTR);
|
||||
let bptr: bool = (b.kind == N_TPTR);
|
||||
let avoid: bool = (a.kind == N_TNAME);
|
||||
if (avoid) { avoid = streq(a.str, "void"); };
|
||||
let bvoid: bool = (b.kind == N_TNAME);
|
||||
if (bvoid) { bvoid = streq(b.str, "void"); };
|
||||
if (aptr) { if (bvoid) { return true; }; };
|
||||
if (avoid) { if (bptr) { return true; }; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// nullableptrtag — 0-based index of the *T variant in a nullable
|
||||
// union. The void variant takes the other slot (0 or 1).
|
||||
export fn nullableptrtag(t: *node) i32 = {
|
||||
if (t == nil) { return 0; };
|
||||
if (t.kind != N_TTAGGED) { return 0; };
|
||||
let a: *node = t.list;
|
||||
if (a != nil) { if (a.kind == N_TPTR) { return 0; }; };
|
||||
return 1;
|
||||
};
|
||||
|
||||
// voidvariantindex — find the 0-based index of the `void` variant in a
|
||||
// tagged-union type expr, -1 if absent. Used by cgreturn to map bare
|
||||
// `return;` in a tagged-union-returning fn to the void variant's tag.
|
||||
|
||||
@@ -4677,6 +4677,8 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
return total;
|
||||
};
|
||||
if (k == N_TTAGGED){
|
||||
// Nullable `(*T | void)` collapses to a single 8B pointer.
|
||||
if (isnullabletype(typn)) { return 8; };
|
||||
// Slot = 8 (tag) + max(variant payload sizes), rounded up
|
||||
// to an 8-byte multiple so the reg-passing ABI (size/8
|
||||
// words) doesn't drop the last value register. Mirrors C
|
||||
@@ -4864,6 +4866,38 @@ fn istaggedtype(t: *node) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// isnullabletype — 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.
|
||||
export fn isnullabletype(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind != N_TTAGGED) { return false; };
|
||||
let a: *node = t.list;
|
||||
if (a == nil) { return false; };
|
||||
let b: *node = a.next;
|
||||
if (b == nil) { return false; };
|
||||
if (b.next != nil) { return false; };
|
||||
let aptr: bool = (a.kind == N_TPTR);
|
||||
let bptr: bool = (b.kind == N_TPTR);
|
||||
let avoid: bool = (a.kind == N_TNAME);
|
||||
if (avoid) { avoid = streq(a.str, "void"); };
|
||||
let bvoid: bool = (b.kind == N_TNAME);
|
||||
if (bvoid) { bvoid = streq(b.str, "void"); };
|
||||
if (aptr) { if (bvoid) { return true; }; };
|
||||
if (avoid) { if (bptr) { return true; }; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// nullableptrtag — 0-based index of the *T variant in a nullable
|
||||
// union. The void variant takes the other slot (0 or 1).
|
||||
export fn nullableptrtag(t: *node) i32 = {
|
||||
if (t == nil) { return 0; };
|
||||
if (t.kind != N_TTAGGED) { return 0; };
|
||||
let a: *node = t.list;
|
||||
if (a != nil) { if (a.kind == N_TPTR) { return 0; }; };
|
||||
return 1;
|
||||
};
|
||||
|
||||
// voidvariantindex — find the 0-based index of the `void` variant in a
|
||||
// tagged-union type expr, -1 if absent. Used by cgreturn to map bare
|
||||
// `return;` in a tagged-union-returning fn to the void variant's tag.
|
||||
@@ -5389,68 +5423,105 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
for (cs != nil) {
|
||||
let nxt: str = mklabel(c, "match_next");
|
||||
let pat: *node = cs.lhs;
|
||||
let nullable: bool = isnullabletype(scrutt);
|
||||
// Compute the variant tag for this arm. Default arm
|
||||
// (no pattern) skips the tag check.
|
||||
if (pat != nil) {
|
||||
let want: i32 = 0;
|
||||
if (scrutt != nil) {
|
||||
if (scrutt.kind == N_TTAGGED) {
|
||||
let patname: str;
|
||||
patname.ptr = nil; patname.len = 0;
|
||||
if (pat.kind == N_TNAME) { patname = pat.str; };
|
||||
let v: *node = scrutt.list;
|
||||
let idx: i32 = 0;
|
||||
let found: bool = false;
|
||||
for (v != nil) {
|
||||
if (v.kind == N_TNAME) {
|
||||
if (streq(v.str, patname)) {
|
||||
want = idx;
|
||||
found = true;
|
||||
v = nil;
|
||||
if (nullable) {
|
||||
// Discriminator = pointer-vs-null.
|
||||
// *T arm: skip if ptr == 0.
|
||||
// void arm: skip if ptr != 0.
|
||||
let ptr_tag: i32 = nullableptrtag(scrutt);
|
||||
let cur_tag: i32 = 0;
|
||||
if (pat.kind == N_TPTR) { cur_tag = ptr_tag; }
|
||||
else { if (ptr_tag == 0) { cur_tag = 1; }; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
if (cur_tag == ptr_tag) {
|
||||
emitline("\tJE\t");
|
||||
} else {
|
||||
emitline("\tJNE\t");
|
||||
};
|
||||
emitline(nxt);
|
||||
emitline("\n");
|
||||
} else {
|
||||
let want: i32 = 0;
|
||||
if (scrutt != nil) {
|
||||
if (scrutt.kind == N_TTAGGED) {
|
||||
let patname: str;
|
||||
patname.ptr = nil; patname.len = 0;
|
||||
if (pat.kind == N_TNAME) { patname = pat.str; };
|
||||
let v: *node = scrutt.list;
|
||||
let idx: i32 = 0;
|
||||
let found: bool = false;
|
||||
for (v != nil) {
|
||||
if (v.kind == N_TNAME) {
|
||||
if (streq(v.str, patname)) {
|
||||
want = idx;
|
||||
found = true;
|
||||
v = nil;
|
||||
};
|
||||
};
|
||||
if (v != nil) {
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
if (v != nil) {
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
if (!found) { want = 0; };
|
||||
};
|
||||
if (!found) { want = 0; };
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(want: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(nxt);
|
||||
emitline("\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(want: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(nxt);
|
||||
emitline("\n");
|
||||
};
|
||||
// Bind `let v: T` from the slot, if requested.
|
||||
let bn: str = cs.str;
|
||||
if (bn.len > 0) {
|
||||
if (pat != nil) {
|
||||
let bsz: i32 = 8;
|
||||
if (isstrtype(c, pat)) { bsz = 16; };
|
||||
// localalloc (not localadd): match-arm
|
||||
// binds don't dedup with same-named binds
|
||||
// in *other* matches, since C's cgexpr
|
||||
// allocates a fresh slot per match expr.
|
||||
let voff: i32 = localalloc(c, bn, bsz, pat);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(voff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (bsz == 16) {
|
||||
if (nullable) {
|
||||
// Bind the pointer (or skip for the
|
||||
// void arm, which has zero-size). The
|
||||
// value IS slot+0.
|
||||
if (pat.kind == N_TPTR) {
|
||||
let voff: i32 = localalloc(c, bn, 8, pat);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(voff: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
} else {
|
||||
let bsz: i32 = 8;
|
||||
if (isstrtype(c, pat)) { bsz = 16; };
|
||||
// localalloc (not localadd): match-arm
|
||||
// binds don't dedup with same-named binds
|
||||
// in *other* matches, since C's cgexpr
|
||||
// allocates a fresh slot per match expr.
|
||||
let voff: i32 = localalloc(c, bn, bsz, pat);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((voff + 8): i64);
|
||||
emitoff(voff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (bsz == 16) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((voff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -6509,8 +6580,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");
|
||||
@@ -6534,11 +6614,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");
|
||||
@@ -6578,6 +6663,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;
|
||||
@@ -6593,6 +6679,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