diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index dcddb6ed..3e6b3798 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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. diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 84b6a5de..56ed730c 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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"); + }; }; }; }; diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 4c8d4cb7..0ddef7ce 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -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. diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index d72e82a8..8284b757 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -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. diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index ea6597f1..cefbf3a7 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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.