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;
|
return total;
|
||||||
};
|
};
|
||||||
if (k == N_TTAGGED){
|
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
|
// Slot = 8 (tag) + max(variant payload sizes), rounded up
|
||||||
// to an 8-byte multiple so the reg-passing ABI (size/8
|
// to an 8-byte multiple so the reg-passing ABI (size/8
|
||||||
// words) doesn't drop the last value register. Mirrors C
|
// words) doesn't drop the last value register. Mirrors C
|
||||||
@@ -4864,6 +4866,38 @@ fn istaggedtype(t: *node) bool = {
|
|||||||
return false;
|
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
|
// 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
|
// 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.
|
// `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) {
|
for (cs != nil) {
|
||||||
let nxt: str = mklabel(c, "match_next");
|
let nxt: str = mklabel(c, "match_next");
|
||||||
let pat: *node = cs.lhs;
|
let pat: *node = cs.lhs;
|
||||||
|
let nullable: bool = isnullabletype(scrutt);
|
||||||
// Compute the variant tag for this arm. Default arm
|
// Compute the variant tag for this arm. Default arm
|
||||||
// (no pattern) skips the tag check.
|
// (no pattern) skips the tag check.
|
||||||
if (pat != nil) {
|
if (pat != nil) {
|
||||||
let want: i32 = 0;
|
if (nullable) {
|
||||||
if (scrutt != nil) {
|
// Discriminator = pointer-vs-null.
|
||||||
if (scrutt.kind == N_TTAGGED) {
|
// *T arm: skip if ptr == 0.
|
||||||
let patname: str;
|
// void arm: skip if ptr != 0.
|
||||||
patname.ptr = nil; patname.len = 0;
|
let ptr_tag: i32 = nullableptrtag(scrutt);
|
||||||
if (pat.kind == N_TNAME) { patname = pat.str; };
|
let cur_tag: i32 = 0;
|
||||||
let v: *node = scrutt.list;
|
if (pat.kind == N_TPTR) { cur_tag = ptr_tag; }
|
||||||
let idx: i32 = 0;
|
else { if (ptr_tag == 0) { cur_tag = 1; }; };
|
||||||
let found: bool = false;
|
emitline("\tMOVQ\t");
|
||||||
for (v != nil) {
|
emitoff(scrutoff: i64);
|
||||||
if (v.kind == N_TNAME) {
|
emitline("(BP), AX\n");
|
||||||
if (streq(v.str, patname)) {
|
emitline("\tCMPQ\t$0, AX\n");
|
||||||
want = idx;
|
if (cur_tag == ptr_tag) {
|
||||||
found = true;
|
emitline("\tJE\t");
|
||||||
v = nil;
|
} 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) {
|
if (!found) { want = 0; };
|
||||||
v = v.next;
|
|
||||||
idx += 1;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
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.
|
// Bind `let v: T` from the slot, if requested.
|
||||||
let bn: str = cs.str;
|
let bn: str = cs.str;
|
||||||
if (bn.len > 0) {
|
if (bn.len > 0) {
|
||||||
if (pat != nil) {
|
if (pat != nil) {
|
||||||
let bsz: i32 = 8;
|
if (nullable) {
|
||||||
if (isstrtype(c, pat)) { bsz = 16; };
|
// Bind the pointer (or skip for the
|
||||||
// localalloc (not localadd): match-arm
|
// void arm, which has zero-size). The
|
||||||
// binds don't dedup with same-named binds
|
// value IS slot+0.
|
||||||
// in *other* matches, since C's cgexpr
|
if (pat.kind == N_TPTR) {
|
||||||
// allocates a fresh slot per match expr.
|
let voff: i32 = localalloc(c, bn, 8, pat);
|
||||||
let voff: i32 = localalloc(c, bn, bsz, pat);
|
emitline("\tMOVQ\t");
|
||||||
emitline("\tMOVQ\t");
|
emitoff(scrutoff: i64);
|
||||||
emitoff((scrutoff + 8): i64);
|
emitline("(BP), AX\n");
|
||||||
emitline("(BP), AX\n");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitoff(voff: i64);
|
||||||
emitoff(voff: i64);
|
emitline("(BP)\n");
|
||||||
emitline("(BP)\n");
|
};
|
||||||
if (bsz == 16) {
|
} 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");
|
emitline("\tMOVQ\t");
|
||||||
emitoff((scrutoff + 16): i64);
|
emitoff((scrutoff + 8): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff((voff + 8): i64);
|
emitoff(voff: i64);
|
||||||
emitline("(BP)\n");
|
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
|
// For str variant, cgexpr leaves (AX=ptr, BX=len), so we
|
||||||
// shuffle DX←AX (ptr) and CX←BX (len), then load tag.
|
// shuffle DX←AX (ptr) and CX←BX (len), then load tag.
|
||||||
// For other variants, cgexpr leaves AX, shuffle DX←AX.
|
// 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)) {
|
if (istaggedtype(c.fnret)) {
|
||||||
cgexpr(c, rhs);
|
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);
|
let idx: i32 = taggedvariantindex(c, c.fnret, rhs);
|
||||||
if (nodeisstr(c, rhs)) {
|
if (nodeisstr(c, rhs)) {
|
||||||
emitline("\tMOVQ\tBX, CX\n");
|
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
|
// the void variant: emit its tag. Payload is undefined
|
||||||
// (void has size 0). Otherwise zero AX for determinism.
|
// (void has size 0). Otherwise zero AX for determinism.
|
||||||
if (istaggedtype(c.fnret)) {
|
if (istaggedtype(c.fnret)) {
|
||||||
let idx: i32 = voidvariantindex(c.fnret);
|
if (isnullabletype(c.fnret)) {
|
||||||
if (idx < 0) { idx = 0; };
|
// null = void variant; AX = 0.
|
||||||
emitline("\tMOVQ\t$");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
emitint(idx: i64);
|
} else {
|
||||||
emitline(", AX\n");
|
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("\tMOVQ\tBP, SP\n");
|
||||||
emitline("\tPOPQ\tBP\n");
|
emitline("\tPOPQ\tBP\n");
|
||||||
emitline("\tRET\n");
|
emitline("\tRET\n");
|
||||||
@@ -6578,6 +6663,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
// - Otherwise rhs is a bare variant value: pack tag +
|
// - Otherwise rhs is a bare variant value: pack tag +
|
||||||
// value(s).
|
// value(s).
|
||||||
if (istaggedtype(n.lhs)) {
|
if (istaggedtype(n.lhs)) {
|
||||||
|
let nullable: bool = isnullabletype(n.lhs);
|
||||||
let rhsreturnstagged: bool = false;
|
let rhsreturnstagged: bool = false;
|
||||||
if (rhs.kind == N_CALL) {
|
if (rhs.kind == N_CALL) {
|
||||||
let callee: *node = rhs.lhs;
|
let callee: *node = rhs.lhs;
|
||||||
@@ -6593,6 +6679,16 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
cgexpr(c, rhs);
|
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) {
|
if (rhsreturnstagged) {
|
||||||
// Spill size/8 registers (tag + value words).
|
// Spill size/8 registers (tag + value words).
|
||||||
// Slots smaller than 24 don't carry a CX word.
|
// Slots smaller than 24 don't carry a CX word.
|
||||||
|
|||||||
@@ -442,68 +442,105 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
for (cs != nil) {
|
for (cs != nil) {
|
||||||
let nxt: str = mklabel(c, "match_next");
|
let nxt: str = mklabel(c, "match_next");
|
||||||
let pat: *node = cs.lhs;
|
let pat: *node = cs.lhs;
|
||||||
|
let nullable: bool = isnullabletype(scrutt);
|
||||||
// Compute the variant tag for this arm. Default arm
|
// Compute the variant tag for this arm. Default arm
|
||||||
// (no pattern) skips the tag check.
|
// (no pattern) skips the tag check.
|
||||||
if (pat != nil) {
|
if (pat != nil) {
|
||||||
let want: i32 = 0;
|
if (nullable) {
|
||||||
if (scrutt != nil) {
|
// Discriminator = pointer-vs-null.
|
||||||
if (scrutt.kind == N_TTAGGED) {
|
// *T arm: skip if ptr == 0.
|
||||||
let patname: str;
|
// void arm: skip if ptr != 0.
|
||||||
patname.ptr = nil; patname.len = 0;
|
let ptr_tag: i32 = nullableptrtag(scrutt);
|
||||||
if (pat.kind == N_TNAME) { patname = pat.str; };
|
let cur_tag: i32 = 0;
|
||||||
let v: *node = scrutt.list;
|
if (pat.kind == N_TPTR) { cur_tag = ptr_tag; }
|
||||||
let idx: i32 = 0;
|
else { if (ptr_tag == 0) { cur_tag = 1; }; };
|
||||||
let found: bool = false;
|
emitline("\tMOVQ\t");
|
||||||
for (v != nil) {
|
emitoff(scrutoff: i64);
|
||||||
if (v.kind == N_TNAME) {
|
emitline("(BP), AX\n");
|
||||||
if (streq(v.str, patname)) {
|
emitline("\tCMPQ\t$0, AX\n");
|
||||||
want = idx;
|
if (cur_tag == ptr_tag) {
|
||||||
found = true;
|
emitline("\tJE\t");
|
||||||
v = nil;
|
} 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) {
|
if (!found) { want = 0; };
|
||||||
v = v.next;
|
|
||||||
idx += 1;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
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.
|
// Bind `let v: T` from the slot, if requested.
|
||||||
let bn: str = cs.str;
|
let bn: str = cs.str;
|
||||||
if (bn.len > 0) {
|
if (bn.len > 0) {
|
||||||
if (pat != nil) {
|
if (pat != nil) {
|
||||||
let bsz: i32 = 8;
|
if (nullable) {
|
||||||
if (isstrtype(c, pat)) { bsz = 16; };
|
// Bind the pointer (or skip for the
|
||||||
// localalloc (not localadd): match-arm
|
// void arm, which has zero-size). The
|
||||||
// binds don't dedup with same-named binds
|
// value IS slot+0.
|
||||||
// in *other* matches, since C's cgexpr
|
if (pat.kind == N_TPTR) {
|
||||||
// allocates a fresh slot per match expr.
|
let voff: i32 = localalloc(c, bn, 8, pat);
|
||||||
let voff: i32 = localalloc(c, bn, bsz, pat);
|
emitline("\tMOVQ\t");
|
||||||
emitline("\tMOVQ\t");
|
emitoff(scrutoff: i64);
|
||||||
emitoff((scrutoff + 8): i64);
|
emitline("(BP), AX\n");
|
||||||
emitline("(BP), AX\n");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitoff(voff: i64);
|
||||||
emitoff(voff: i64);
|
emitline("(BP)\n");
|
||||||
emitline("(BP)\n");
|
};
|
||||||
if (bsz == 16) {
|
} 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");
|
emitline("\tMOVQ\t");
|
||||||
emitoff((scrutoff + 16): i64);
|
emitoff((scrutoff + 8): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff((voff + 8): i64);
|
emitoff(voff: i64);
|
||||||
emitline("(BP)\n");
|
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
|
// For str variant, cgexpr leaves (AX=ptr, BX=len), so we
|
||||||
// shuffle DX←AX (ptr) and CX←BX (len), then load tag.
|
// shuffle DX←AX (ptr) and CX←BX (len), then load tag.
|
||||||
// For other variants, cgexpr leaves AX, shuffle DX←AX.
|
// 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)) {
|
if (istaggedtype(c.fnret)) {
|
||||||
cgexpr(c, rhs);
|
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);
|
let idx: i32 = taggedvariantindex(c, c.fnret, rhs);
|
||||||
if (nodeisstr(c, rhs)) {
|
if (nodeisstr(c, rhs)) {
|
||||||
emitline("\tMOVQ\tBX, CX\n");
|
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
|
// the void variant: emit its tag. Payload is undefined
|
||||||
// (void has size 0). Otherwise zero AX for determinism.
|
// (void has size 0). Otherwise zero AX for determinism.
|
||||||
if (istaggedtype(c.fnret)) {
|
if (istaggedtype(c.fnret)) {
|
||||||
let idx: i32 = voidvariantindex(c.fnret);
|
if (isnullabletype(c.fnret)) {
|
||||||
if (idx < 0) { idx = 0; };
|
// null = void variant; AX = 0.
|
||||||
emitline("\tMOVQ\t$");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
emitint(idx: i64);
|
} else {
|
||||||
emitline(", AX\n");
|
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("\tMOVQ\tBP, SP\n");
|
||||||
emitline("\tPOPQ\tBP\n");
|
emitline("\tPOPQ\tBP\n");
|
||||||
emitline("\tRET\n");
|
emitline("\tRET\n");
|
||||||
@@ -168,6 +182,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
// - Otherwise rhs is a bare variant value: pack tag +
|
// - Otherwise rhs is a bare variant value: pack tag +
|
||||||
// value(s).
|
// value(s).
|
||||||
if (istaggedtype(n.lhs)) {
|
if (istaggedtype(n.lhs)) {
|
||||||
|
let nullable: bool = isnullabletype(n.lhs);
|
||||||
let rhsreturnstagged: bool = false;
|
let rhsreturnstagged: bool = false;
|
||||||
if (rhs.kind == N_CALL) {
|
if (rhs.kind == N_CALL) {
|
||||||
let callee: *node = rhs.lhs;
|
let callee: *node = rhs.lhs;
|
||||||
@@ -183,6 +198,16 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
cgexpr(c, rhs);
|
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) {
|
if (rhsreturnstagged) {
|
||||||
// Spill size/8 registers (tag + value words).
|
// Spill size/8 registers (tag + value words).
|
||||||
// Slots smaller than 24 don't carry a CX word.
|
// Slots smaller than 24 don't carry a CX word.
|
||||||
|
|||||||
@@ -703,6 +703,8 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
|||||||
return total;
|
return total;
|
||||||
};
|
};
|
||||||
if (k == N_TTAGGED){
|
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
|
// Slot = 8 (tag) + max(variant payload sizes), rounded up
|
||||||
// to an 8-byte multiple so the reg-passing ABI (size/8
|
// to an 8-byte multiple so the reg-passing ABI (size/8
|
||||||
// words) doesn't drop the last value register. Mirrors C
|
// words) doesn't drop the last value register. Mirrors C
|
||||||
@@ -890,6 +892,38 @@ fn istaggedtype(t: *node) bool = {
|
|||||||
return false;
|
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
|
// 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
|
// 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.
|
// `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;
|
return total;
|
||||||
};
|
};
|
||||||
if (k == N_TTAGGED){
|
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
|
// Slot = 8 (tag) + max(variant payload sizes), rounded up
|
||||||
// to an 8-byte multiple so the reg-passing ABI (size/8
|
// to an 8-byte multiple so the reg-passing ABI (size/8
|
||||||
// words) doesn't drop the last value register. Mirrors C
|
// words) doesn't drop the last value register. Mirrors C
|
||||||
@@ -4864,6 +4866,38 @@ fn istaggedtype(t: *node) bool = {
|
|||||||
return false;
|
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
|
// 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
|
// 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.
|
// `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) {
|
for (cs != nil) {
|
||||||
let nxt: str = mklabel(c, "match_next");
|
let nxt: str = mklabel(c, "match_next");
|
||||||
let pat: *node = cs.lhs;
|
let pat: *node = cs.lhs;
|
||||||
|
let nullable: bool = isnullabletype(scrutt);
|
||||||
// Compute the variant tag for this arm. Default arm
|
// Compute the variant tag for this arm. Default arm
|
||||||
// (no pattern) skips the tag check.
|
// (no pattern) skips the tag check.
|
||||||
if (pat != nil) {
|
if (pat != nil) {
|
||||||
let want: i32 = 0;
|
if (nullable) {
|
||||||
if (scrutt != nil) {
|
// Discriminator = pointer-vs-null.
|
||||||
if (scrutt.kind == N_TTAGGED) {
|
// *T arm: skip if ptr == 0.
|
||||||
let patname: str;
|
// void arm: skip if ptr != 0.
|
||||||
patname.ptr = nil; patname.len = 0;
|
let ptr_tag: i32 = nullableptrtag(scrutt);
|
||||||
if (pat.kind == N_TNAME) { patname = pat.str; };
|
let cur_tag: i32 = 0;
|
||||||
let v: *node = scrutt.list;
|
if (pat.kind == N_TPTR) { cur_tag = ptr_tag; }
|
||||||
let idx: i32 = 0;
|
else { if (ptr_tag == 0) { cur_tag = 1; }; };
|
||||||
let found: bool = false;
|
emitline("\tMOVQ\t");
|
||||||
for (v != nil) {
|
emitoff(scrutoff: i64);
|
||||||
if (v.kind == N_TNAME) {
|
emitline("(BP), AX\n");
|
||||||
if (streq(v.str, patname)) {
|
emitline("\tCMPQ\t$0, AX\n");
|
||||||
want = idx;
|
if (cur_tag == ptr_tag) {
|
||||||
found = true;
|
emitline("\tJE\t");
|
||||||
v = nil;
|
} 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) {
|
if (!found) { want = 0; };
|
||||||
v = v.next;
|
|
||||||
idx += 1;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
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.
|
// Bind `let v: T` from the slot, if requested.
|
||||||
let bn: str = cs.str;
|
let bn: str = cs.str;
|
||||||
if (bn.len > 0) {
|
if (bn.len > 0) {
|
||||||
if (pat != nil) {
|
if (pat != nil) {
|
||||||
let bsz: i32 = 8;
|
if (nullable) {
|
||||||
if (isstrtype(c, pat)) { bsz = 16; };
|
// Bind the pointer (or skip for the
|
||||||
// localalloc (not localadd): match-arm
|
// void arm, which has zero-size). The
|
||||||
// binds don't dedup with same-named binds
|
// value IS slot+0.
|
||||||
// in *other* matches, since C's cgexpr
|
if (pat.kind == N_TPTR) {
|
||||||
// allocates a fresh slot per match expr.
|
let voff: i32 = localalloc(c, bn, 8, pat);
|
||||||
let voff: i32 = localalloc(c, bn, bsz, pat);
|
emitline("\tMOVQ\t");
|
||||||
emitline("\tMOVQ\t");
|
emitoff(scrutoff: i64);
|
||||||
emitoff((scrutoff + 8): i64);
|
emitline("(BP), AX\n");
|
||||||
emitline("(BP), AX\n");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitoff(voff: i64);
|
||||||
emitoff(voff: i64);
|
emitline("(BP)\n");
|
||||||
emitline("(BP)\n");
|
};
|
||||||
if (bsz == 16) {
|
} 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");
|
emitline("\tMOVQ\t");
|
||||||
emitoff((scrutoff + 16): i64);
|
emitoff((scrutoff + 8): i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff((voff + 8): i64);
|
emitoff(voff: i64);
|
||||||
emitline("(BP)\n");
|
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
|
// For str variant, cgexpr leaves (AX=ptr, BX=len), so we
|
||||||
// shuffle DX←AX (ptr) and CX←BX (len), then load tag.
|
// shuffle DX←AX (ptr) and CX←BX (len), then load tag.
|
||||||
// For other variants, cgexpr leaves AX, shuffle DX←AX.
|
// 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)) {
|
if (istaggedtype(c.fnret)) {
|
||||||
cgexpr(c, rhs);
|
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);
|
let idx: i32 = taggedvariantindex(c, c.fnret, rhs);
|
||||||
if (nodeisstr(c, rhs)) {
|
if (nodeisstr(c, rhs)) {
|
||||||
emitline("\tMOVQ\tBX, CX\n");
|
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
|
// the void variant: emit its tag. Payload is undefined
|
||||||
// (void has size 0). Otherwise zero AX for determinism.
|
// (void has size 0). Otherwise zero AX for determinism.
|
||||||
if (istaggedtype(c.fnret)) {
|
if (istaggedtype(c.fnret)) {
|
||||||
let idx: i32 = voidvariantindex(c.fnret);
|
if (isnullabletype(c.fnret)) {
|
||||||
if (idx < 0) { idx = 0; };
|
// null = void variant; AX = 0.
|
||||||
emitline("\tMOVQ\t$");
|
emitline("\tMOVQ\t$0, AX\n");
|
||||||
emitint(idx: i64);
|
} else {
|
||||||
emitline(", AX\n");
|
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("\tMOVQ\tBP, SP\n");
|
||||||
emitline("\tPOPQ\tBP\n");
|
emitline("\tPOPQ\tBP\n");
|
||||||
emitline("\tRET\n");
|
emitline("\tRET\n");
|
||||||
@@ -6578,6 +6663,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
// - Otherwise rhs is a bare variant value: pack tag +
|
// - Otherwise rhs is a bare variant value: pack tag +
|
||||||
// value(s).
|
// value(s).
|
||||||
if (istaggedtype(n.lhs)) {
|
if (istaggedtype(n.lhs)) {
|
||||||
|
let nullable: bool = isnullabletype(n.lhs);
|
||||||
let rhsreturnstagged: bool = false;
|
let rhsreturnstagged: bool = false;
|
||||||
if (rhs.kind == N_CALL) {
|
if (rhs.kind == N_CALL) {
|
||||||
let callee: *node = rhs.lhs;
|
let callee: *node = rhs.lhs;
|
||||||
@@ -6593,6 +6679,16 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
cgexpr(c, rhs);
|
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) {
|
if (rhsreturnstagged) {
|
||||||
// Spill size/8 registers (tag + value words).
|
// Spill size/8 registers (tag + value words).
|
||||||
// Slots smaller than 24 don't carry a CX word.
|
// Slots smaller than 24 don't carry a CX word.
|
||||||
|
|||||||
Reference in New Issue
Block a user