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:
@@ -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");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user