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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user