wcc: nullable pointer folding for (*T | void)
A tagged union with exactly one `*T` variant and one `void` variant collapses to a single 8-byte pointer slot, where the null bit pattern is the void variant and any non-null is the *T variant. Mirrors Hare's `(*T | null)` ABI optimisation. Detected in resolve_type when the post-flatten variant list has exactly two entries of the right shape; Type.nullable = 1 and size = 8. Codegen branches every tagged-handling site on the flag: - match: discriminator = pointer-vs-zero, not slot+0 tag word. Binding for the *T case copies the same word (the pointer itself) rather than slot+8. - is/as: same ptr-vs-zero discriminator. - ?: null = error (propagate AX=0 to caller's matching null encoding); non-null = success (AX is already the pointer). - !: null aborts; non-null falls through with AX = pointer. - let-init / return: spill or set just AX (no tag/value pair). - call-arg push: push only AX, not the now-unused DX/CX. Prologue spill already pulled size/8 = 1 arg register via the existing tagged-arg loop, so no change needed there. Two existing helpers in cgen.c get nullable-aware spelling: type_isnullable() and nullable_ptr_tag() (which variant index is the *T side; the void side is the other one). The Hare-style `(*T | null)` spelling isn't supported — `null` is not a type keyword in ww. Callers use `void` instead, which is already a real type. The result is the same bit-level layout.
This commit is contained in:
236
cmd/w6c/cgen.c
236
cmd/w6c/cgen.c
@@ -118,8 +118,9 @@ struct_arg_size(Type *t)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Tagged-union arg byte size: 16 (8B variants) or 24 (16B variants).
|
/* Tagged-union arg byte size: 16 (8B variants) or 24 (16B variants).
|
||||||
* Both fit in our 3-register classification. Returns 0 if not a
|
* Nullable-folded `(*T | void)` collapses to 8 bytes (just the
|
||||||
* tagged union or too large to pass in registers. */
|
* pointer). Returns 0 if not a tagged union or too large to pass
|
||||||
|
* in registers. */
|
||||||
static int
|
static int
|
||||||
tagged_arg_size(Type *t)
|
tagged_arg_size(Type *t)
|
||||||
{
|
{
|
||||||
@@ -130,6 +131,32 @@ tagged_arg_size(Type *t)
|
|||||||
return (int)t->size;
|
return (int)t->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* type_isnullable — TY_TAGGED with the (*T | void) one-word fold. */
|
||||||
|
static int
|
||||||
|
type_isnullable(Type *t)
|
||||||
|
{
|
||||||
|
if (t == NULL) return 0;
|
||||||
|
if (t->kind == TY_NAMED) t = t->under;
|
||||||
|
return t && t->kind == TY_TAGGED && t->nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nullable_ptr_tag — index of the *T variant in a nullable union.
|
||||||
|
* Returns 0 or 1; the void variant takes the other slot. */
|
||||||
|
static int
|
||||||
|
nullable_ptr_tag(Type *t)
|
||||||
|
{
|
||||||
|
if (t == NULL) return 0;
|
||||||
|
if (t->kind == TY_NAMED) t = t->under;
|
||||||
|
if (t == NULL || t->kind != TY_TAGGED) return 0;
|
||||||
|
int i = 0;
|
||||||
|
for (Tparam *p = t->params; p; p = p->next, i++) {
|
||||||
|
Type *pu = (p->type && p->type->kind == TY_NAMED)
|
||||||
|
? p->type->under : p->type;
|
||||||
|
if (pu && pu->kind == TY_PTR) return i;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
node_istaggedarg(Node *n)
|
node_istaggedarg(Node *n)
|
||||||
{
|
{
|
||||||
@@ -1713,11 +1740,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
} else if (node_istaggedarg(args[i])) {
|
} else if (node_istaggedarg(args[i])) {
|
||||||
/* Tagged-return ABI: AX=tag, DX=val0[, CX=val1].
|
/* Tagged-return ABI: AX=tag, DX=val0[, CX=val1].
|
||||||
* Push high-to-low so pop drains tag first (into
|
* Push high-to-low so pop drains tag first (into
|
||||||
* arg-reg[0]), then values into arg-reg[1..]. */
|
* arg-reg[0]), then values into arg-reg[1..].
|
||||||
|
* Nullable (sz=8): AX holds the pointer, no
|
||||||
|
* value-word registers — push just AX. */
|
||||||
int sz = tagged_arg_size(args[i]->type);
|
int sz = tagged_arg_size(args[i]->type);
|
||||||
if (sz > 16)
|
if (sz > 16)
|
||||||
ins1(c, A_PUSHQ, areg(D_CX));
|
ins1(c, A_PUSHQ, areg(D_CX));
|
||||||
ins1(c, A_PUSHQ, areg(D_DX));
|
if (sz > 8)
|
||||||
|
ins1(c, A_PUSHQ, areg(D_DX));
|
||||||
ins1(c, A_PUSHQ, areg(D_AX));
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
} else {
|
} else {
|
||||||
ins1(c, A_PUSHQ, areg(D_AX));
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
@@ -1838,10 +1868,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
*
|
*
|
||||||
* Slot layout: [+0]=tag, [+8]=value0, [+16]=value1. The third
|
* Slot layout: [+0]=tag, [+8]=value0, [+16]=value1. The third
|
||||||
* word is only meaningful for variants whose payload is >8B
|
* word is only meaningful for variants whose payload is >8B
|
||||||
* (e.g. str). Bindings sized 16B (str) copy two words. */
|
* (e.g. str). Bindings sized 16B (str) copy two words.
|
||||||
|
*
|
||||||
|
* Nullable folded `(*T | void)`: slot is one 8B word holding
|
||||||
|
* the pointer; null IS the void variant. Discriminator =
|
||||||
|
* value, not a separate tag. */
|
||||||
Node *s = n->lhs;
|
Node *s = n->lhs;
|
||||||
Type *st = s ? s->type : NULL;
|
Type *st = s ? s->type : NULL;
|
||||||
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
|
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||||
|
int is_nullable = type_isnullable(st);
|
||||||
int slot_size = (su && su->kind == TY_TAGGED) ? (int)su->size : 16;
|
int slot_size = (su && su->kind == TY_TAGGED) ? (int)su->size : 16;
|
||||||
int sl_off;
|
int sl_off;
|
||||||
if (s->kind == N_IDENT) {
|
if (s->kind == N_IDENT) {
|
||||||
@@ -1877,15 +1912,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
/* Spill non-ident scrutinees (e.g. `match (foo()?)`) into
|
/* Spill non-ident scrutinees (e.g. `match (foo()?)`) into
|
||||||
* a scratch slot so we can index out the tag/value. The
|
* a scratch slot so we can index out the tag/value. The
|
||||||
* call ABI for tagged returns is AX=tag, DX=value0,
|
* call ABI for tagged returns is AX=tag, DX=value0,
|
||||||
* CX=value1 — copy each word into the slot. */
|
* CX=value1 — copy each word into the slot. Nullable
|
||||||
|
* returns are single-word: AX is the pointer; spill
|
||||||
|
* only that. */
|
||||||
sl_off = localoff(c, &locals, "@match_spill", slot_size,
|
sl_off = localoff(c, &locals, "@match_spill", slot_size,
|
||||||
cg_frame);
|
cg_frame);
|
||||||
cgexpr(c, s, locals);
|
cgexpr(c, s, locals);
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
||||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, sl_off + 8));
|
if (!is_nullable) {
|
||||||
if (slot_size > 16)
|
ins2(c, A_MOVQ, areg(D_DX),
|
||||||
ins2(c, A_MOVQ, areg(D_CX),
|
amem(D_BP, sl_off + 8));
|
||||||
amem(D_BP, sl_off + 16));
|
if (slot_size > 16)
|
||||||
|
ins2(c, A_MOVQ, areg(D_CX),
|
||||||
|
amem(D_BP, sl_off + 16));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
char *end = mklabel(c, "match_end");
|
char *end = mklabel(c, "match_end");
|
||||||
for (Node *cs = n->list; cs; cs = cs->next) {
|
for (Node *cs = n->list; cs; cs = cs->next) {
|
||||||
@@ -1893,7 +1933,18 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
if (cs->type != NULL) {
|
if (cs->type != NULL) {
|
||||||
int tag = cg_tag_for_variant(su, cs->type);
|
int tag = cg_tag_for_variant(su, cs->type);
|
||||||
ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX));
|
ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX));
|
||||||
if (cs->list != NULL) {
|
if (is_nullable) {
|
||||||
|
/* discriminator = pointer-vs-null.
|
||||||
|
* *T variant: skip if ptr == 0.
|
||||||
|
* void variant: skip if ptr != 0. */
|
||||||
|
int ptr_tag = nullable_ptr_tag(su);
|
||||||
|
int want_ptr = (tag == ptr_tag);
|
||||||
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||||
|
if (want_ptr)
|
||||||
|
ins1(c, A_JE, abranch(next));
|
||||||
|
else
|
||||||
|
ins1(c, A_JNE, abranch(next));
|
||||||
|
} else if (cs->list != NULL) {
|
||||||
/* Multi-pattern `case T1 | T2 | ... =>`:
|
/* Multi-pattern `case T1 | T2 | ... =>`:
|
||||||
* if the tag matches any of the alts,
|
* if the tag matches any of the alts,
|
||||||
* jump to body; otherwise to the next
|
* jump to body; otherwise to the next
|
||||||
@@ -1923,16 +1974,34 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
Type *bt = cs->type;
|
Type *bt = cs->type;
|
||||||
Type *bu = (bt && bt->kind == TY_NAMED)
|
Type *bu = (bt && bt->kind == TY_NAMED)
|
||||||
? bt->under : bt;
|
? bt->under : bt;
|
||||||
int bsz = (bu && bu->kind == TY_STR) ? 16 : 8;
|
if (is_nullable) {
|
||||||
int voff = localoff(c, &locals, cs->str, bsz,
|
/* Bind *T or void to a local. The
|
||||||
cg_frame);
|
* value IS the slot's pointer word; no
|
||||||
int nwords = (bsz + 7) / 8;
|
* payload to copy. void binding is
|
||||||
for (int w = 0; w < nwords; w++) {
|
* unusable (size 0), so only emit for
|
||||||
ins2(c, A_MOVQ,
|
* the *T variant. */
|
||||||
amem(D_BP, sl_off + 8 + 8*w),
|
if (bu && bu->kind == TY_PTR) {
|
||||||
areg(D_AX));
|
int voff = localoff(c, &locals,
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
cs->str, 8, cg_frame);
|
||||||
amem(D_BP, voff + 8*w));
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, sl_off + 0),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, voff));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int bsz = (bu && bu->kind == TY_STR)
|
||||||
|
? 16 : 8;
|
||||||
|
int voff = localoff(c, &locals, cs->str,
|
||||||
|
bsz, cg_frame);
|
||||||
|
int nwords = (bsz + 7) / 8;
|
||||||
|
for (int w = 0; w < nwords; w++) {
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, sl_off + 8 + 8*w),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, voff + 8*w));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cgstmt(c, cs->body, &locals, cg_frame);
|
cgstmt(c, cs->body, &locals, cg_frame);
|
||||||
@@ -1948,12 +2017,29 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* current function's return (with a tag remap to the
|
* current function's return (with a tag remap to the
|
||||||
* enclosing fn's variant order). On success, unwrap to the
|
* enclosing fn's variant order). On success, unwrap to the
|
||||||
* success-variant ABI: ≤8B values in AX; str values in
|
* success-variant ABI: ≤8B values in AX; str values in
|
||||||
* (AX=ptr, BX=len). */
|
* (AX=ptr, BX=len).
|
||||||
|
*
|
||||||
|
* Nullable: AX is the pointer; *T variant is the success
|
||||||
|
* (any non-null), void variant is the error (null). The
|
||||||
|
* enclosing fn's null encoding is the same — RET with AX=0
|
||||||
|
* if propagating; otherwise leave AX as-is on success. */
|
||||||
cgexpr(c, n->lhs, locals);
|
cgexpr(c, n->lhs, locals);
|
||||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||||
if (u && u->kind == TY_NAMED) u = u->under;
|
if (u && u->kind == TY_NAMED) u = u->under;
|
||||||
Type *r = cg_ret_type;
|
Type *r = cg_ret_type;
|
||||||
if (r && r->kind == TY_NAMED) r = r->under;
|
if (r && r->kind == TY_NAMED) r = r->under;
|
||||||
|
if (u && u->kind == TY_TAGGED && u->nullable) {
|
||||||
|
char *cont = mklabel(c, "tryprop_ok");
|
||||||
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||||
|
ins1(c, A_JNE, abranch(cont));
|
||||||
|
/* null = error: propagate. AX already 0; matches
|
||||||
|
* the enclosing nullable encoding if it has one. */
|
||||||
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||||
|
ins1(c, A_POPQ, areg(D_BP));
|
||||||
|
ins0(c, A_RET);
|
||||||
|
label(c, cont);
|
||||||
|
break;
|
||||||
|
}
|
||||||
int s_tag = cg_tagged_success_tag(u);
|
int s_tag = cg_tagged_success_tag(u);
|
||||||
Type *succ_t = NULL;
|
Type *succ_t = NULL;
|
||||||
if (u && u->kind == TY_TAGGED) {
|
if (u && u->kind == TY_TAGGED) {
|
||||||
@@ -1992,10 +2078,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case N_TRYUNW: {
|
case N_TRYUNW: {
|
||||||
/* On error variant: exit(1) directly via the syscall. */
|
/* On error variant: exit(1) directly via the syscall.
|
||||||
|
* Nullable: null = error; non-null = success (AX is the
|
||||||
|
* pointer, ready to use). */
|
||||||
cgexpr(c, n->lhs, locals);
|
cgexpr(c, n->lhs, locals);
|
||||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||||
if (u && u->kind == TY_NAMED) u = u->under;
|
if (u && u->kind == TY_NAMED) u = u->under;
|
||||||
|
if (u && u->kind == TY_TAGGED && u->nullable) {
|
||||||
|
char *cont = mklabel(c, "tryunw_ok");
|
||||||
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||||
|
ins1(c, A_JNE, abranch(cont));
|
||||||
|
ins2(c, A_MOVQ, aimm(1), areg(D_DI));
|
||||||
|
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
||||||
|
ins0(c, A_SYSCALL);
|
||||||
|
label(c, cont);
|
||||||
|
break;
|
||||||
|
}
|
||||||
int s_tag = cg_tagged_success_tag(u);
|
int s_tag = cg_tagged_success_tag(u);
|
||||||
Type *succ_t = NULL;
|
Type *succ_t = NULL;
|
||||||
if (u && u->kind == TY_TAGGED) {
|
if (u && u->kind == TY_TAGGED) {
|
||||||
@@ -2018,19 +2116,28 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
case N_TYPETEST: {
|
case N_TYPETEST: {
|
||||||
/* `e is T` — Compare scrutinee tag against T's variant index.
|
/* `e is T` — Compare scrutinee tag against T's variant index.
|
||||||
* Result is bool (0/1) in AX. We only need the tag, not the
|
* Result is bool (0/1) in AX. Nullable: discriminator is
|
||||||
* value words, so cgexpr's AX-only result is enough for both
|
* pointer-vs-null, not a tag. */
|
||||||
* register-returning callers (AX=tag) and N_IDENT locals
|
|
||||||
* (cgexpr loads slot+0 → AX, which is the tag). */
|
|
||||||
cgexpr(c, n->lhs, locals);
|
cgexpr(c, n->lhs, locals);
|
||||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||||
if (u && u->kind == TY_NAMED) u = u->under;
|
if (u && u->kind == TY_NAMED) u = u->under;
|
||||||
Type *vt = n->rhs ? n->rhs->type : NULL;
|
Type *vt = n->rhs ? n->rhs->type : NULL;
|
||||||
int tag = cg_tag_for_variant(u, vt);
|
|
||||||
char *ne = mklabel(c, "is_ne");
|
char *ne = mklabel(c, "is_ne");
|
||||||
char *done = mklabel(c, "is_done");
|
char *done = mklabel(c, "is_done");
|
||||||
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
|
if (u && u->kind == TY_TAGGED && u->nullable) {
|
||||||
ins1(c, A_JNE, abranch(ne));
|
int tag = cg_tag_for_variant(u, vt);
|
||||||
|
int ptr_tag = nullable_ptr_tag(u);
|
||||||
|
int want_ptr = (tag == ptr_tag);
|
||||||
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||||
|
if (want_ptr)
|
||||||
|
ins1(c, A_JE, abranch(ne));
|
||||||
|
else
|
||||||
|
ins1(c, A_JNE, abranch(ne));
|
||||||
|
} else {
|
||||||
|
int tag = cg_tag_for_variant(u, vt);
|
||||||
|
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
|
||||||
|
ins1(c, A_JNE, abranch(ne));
|
||||||
|
}
|
||||||
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
|
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
|
||||||
ins1(c, A_JMP, abranch(done));
|
ins1(c, A_JMP, abranch(done));
|
||||||
label(c, ne);
|
label(c, ne);
|
||||||
@@ -2047,12 +2154,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* the value lives at slot+8/+16 — cgexpr's single-MOVQ path
|
* the value lives at slot+8/+16 — cgexpr's single-MOVQ path
|
||||||
* does not load it. Mirror match's pattern: resolve a slot
|
* does not load it. Mirror match's pattern: resolve a slot
|
||||||
* offset (existing local or a fresh @asrt_spill) and index
|
* offset (existing local or a fresh @asrt_spill) and index
|
||||||
* out tag/value from memory. */
|
* out tag/value from memory.
|
||||||
|
*
|
||||||
|
* Nullable: the slot's word IS the pointer. *T variant
|
||||||
|
* asserts non-null; void variant asserts null. The value
|
||||||
|
* left in AX after the check is the pointer itself. */
|
||||||
Node *s = n->lhs;
|
Node *s = n->lhs;
|
||||||
Type *st = s ? s->type : NULL;
|
Type *st = s ? s->type : NULL;
|
||||||
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
|
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||||
Type *vt = n->type;
|
Type *vt = n->type;
|
||||||
int tag = cg_tag_for_variant(u, vt);
|
|
||||||
int slot_size = (u && u->kind == TY_TAGGED) ? (int)u->size : 16;
|
int slot_size = (u && u->kind == TY_TAGGED) ? (int)u->size : 16;
|
||||||
int sl_off = 0;
|
int sl_off = 0;
|
||||||
if (s && s->kind == N_IDENT && s->str) {
|
if (s && s->kind == N_IDENT && s->str) {
|
||||||
@@ -2063,12 +2173,35 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
slot_size, cg_frame);
|
slot_size, cg_frame);
|
||||||
cgexpr(c, s, locals);
|
cgexpr(c, s, locals);
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
||||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, sl_off + 8));
|
if (!(u && u->kind == TY_TAGGED && u->nullable)) {
|
||||||
if (slot_size > 16)
|
ins2(c, A_MOVQ, areg(D_DX),
|
||||||
ins2(c, A_MOVQ, areg(D_CX),
|
amem(D_BP, sl_off + 8));
|
||||||
amem(D_BP, sl_off + 16));
|
if (slot_size > 16)
|
||||||
|
ins2(c, A_MOVQ, areg(D_CX),
|
||||||
|
amem(D_BP, sl_off + 16));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
char *ok = mklabel(c, "asrt_ok");
|
char *ok = mklabel(c, "asrt_ok");
|
||||||
|
if (u && u->kind == TY_TAGGED && u->nullable) {
|
||||||
|
int tag = cg_tag_for_variant(u, vt);
|
||||||
|
int ptr_tag = nullable_ptr_tag(u);
|
||||||
|
int want_ptr = (tag == ptr_tag);
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX));
|
||||||
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||||
|
if (want_ptr)
|
||||||
|
ins1(c, A_JNE, abranch(ok));
|
||||||
|
else
|
||||||
|
ins1(c, A_JE, abranch(ok));
|
||||||
|
ins2(c, A_MOVQ, aimm(1), areg(D_DI));
|
||||||
|
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
||||||
|
ins0(c, A_SYSCALL);
|
||||||
|
label(c, ok);
|
||||||
|
/* AX already holds the pointer (or 0 for void
|
||||||
|
* variant, where the result type has size 0 and
|
||||||
|
* no consumer reads it). */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int tag = cg_tag_for_variant(u, vt);
|
||||||
ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX));
|
ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX));
|
||||||
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
|
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
|
||||||
ins1(c, A_JE, abranch(ok));
|
ins1(c, A_JE, abranch(ok));
|
||||||
@@ -2547,10 +2680,19 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
* 2) rhs is a bare variant value (e.g. `let r: (i64|i32) = 7`).
|
* 2) rhs is a bare variant value (e.g. `let r: (i64|i32) = 7`).
|
||||||
* Synthesise the tag from rhs's static type and store it
|
* Synthesise the tag from rhs's static type and store it
|
||||||
* alongside the value. str-typed rhs flows as
|
* alongside the value. str-typed rhs flows as
|
||||||
* (AX=ptr, BX=len) so we store both halves. */
|
* (AX=ptr, BX=len) so we store both halves.
|
||||||
|
*
|
||||||
|
* Nullable folded `(*T | void)`: the slot is a single 8B
|
||||||
|
* pointer. Both the value-from-call and bare-variant paths
|
||||||
|
* simplify to "spill AX". void variant stores 0; *T variant
|
||||||
|
* stores the pointer. */
|
||||||
if (n->rhs && lu && lu->kind == TY_TAGGED) {
|
if (n->rhs && lu && lu->kind == TY_TAGGED) {
|
||||||
Type *rt = n->rhs->type;
|
Type *rt = n->rhs->type;
|
||||||
if (type_istagged(rt)) {
|
if (lu->nullable) {
|
||||||
|
cgexpr(c, n->rhs, *locals);
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, off + 0));
|
||||||
|
} else if (type_istagged(rt)) {
|
||||||
cgexpr(c, n->rhs, *locals);
|
cgexpr(c, n->rhs, *locals);
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
||||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
||||||
@@ -2780,9 +2922,15 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
Type *rt = cg_ret_type;
|
Type *rt = cg_ret_type;
|
||||||
if (rt->kind == TY_NAMED) rt = rt->under;
|
if (rt->kind == TY_NAMED) rt = rt->under;
|
||||||
if (rt && rt->kind == TY_TAGGED) {
|
if (rt && rt->kind == TY_TAGGED) {
|
||||||
int tag = cg_tag_for_variant(rt, ty_void);
|
if (rt->nullable) {
|
||||||
if (tag < 0) tag = 0;
|
/* bare `return;` is the void/null
|
||||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
* variant: emit AX = 0. */
|
||||||
|
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
|
||||||
|
} else {
|
||||||
|
int tag = cg_tag_for_variant(rt, ty_void);
|
||||||
|
if (tag < 0) tag = 0;
|
||||||
|
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||||
|
}
|
||||||
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||||
ins1(c, A_POPQ, areg(D_BP));
|
ins1(c, A_POPQ, areg(D_BP));
|
||||||
ins0(c, A_RET);
|
ins0(c, A_RET);
|
||||||
@@ -2795,7 +2943,11 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
if (rt && rt->kind == TY_TAGGED) {
|
if (rt && rt->kind == TY_TAGGED) {
|
||||||
Type *vt = n->lhs->type;
|
Type *vt = n->lhs->type;
|
||||||
cgexpr(c, n->lhs, *locals);
|
cgexpr(c, n->lhs, *locals);
|
||||||
if (!type_istagged(vt)) {
|
if (rt->nullable) {
|
||||||
|
/* AX already holds the pointer (or
|
||||||
|
* 0 if the value was `nil` / `void`).
|
||||||
|
* No tag word, no shuffle. */
|
||||||
|
} else if (!type_istagged(vt)) {
|
||||||
int tag = cg_tag_for_variant(rt, vt);
|
int tag = cg_tag_for_variant(rt, vt);
|
||||||
if (type_isstr(vt)) {
|
if (type_isstr(vt)) {
|
||||||
/* AX=ptr, BX=len → DX=ptr,
|
/* AX=ptr, BX=len → DX=ptr,
|
||||||
|
|||||||
@@ -247,6 +247,28 @@ resolve_type(Checker *c, Node *n)
|
|||||||
if (nv == 0) return ty_never;
|
if (nv == 0) return ty_never;
|
||||||
if (nv == 1 && head) return head->type;
|
if (nv == 1 && head) return head->type;
|
||||||
t->params = head;
|
t->params = head;
|
||||||
|
/* Nullable pointer folding: `(*T | void)` collapses to a
|
||||||
|
* single 8-byte pointer slot; null bit pattern is the void
|
||||||
|
* variant. Mirrors Hare's `(*T | null)`. Detected on exact
|
||||||
|
* two-variant shape with one TY_PTR and one TY_VOID. */
|
||||||
|
if (nv == 2) {
|
||||||
|
Tparam *a = head;
|
||||||
|
Tparam *b = head->next;
|
||||||
|
Type *au = (a->type && a->type->kind == TY_NAMED)
|
||||||
|
? a->type->under : a->type;
|
||||||
|
Type *bu = (b->type && b->type->kind == TY_NAMED)
|
||||||
|
? b->type->under : b->type;
|
||||||
|
int aptr = au && au->kind == TY_PTR;
|
||||||
|
int bptr = bu && bu->kind == TY_PTR;
|
||||||
|
int avoid = au && au->kind == TY_VOID;
|
||||||
|
int bvoid = bu && bu->kind == TY_VOID;
|
||||||
|
if ((aptr && bvoid) || (avoid && bptr)) {
|
||||||
|
t->nullable = 1;
|
||||||
|
t->size = 8;
|
||||||
|
t->align = 8;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
/* Round value payload up to an 8-byte multiple so the slot
|
/* Round value payload up to an 8-byte multiple so the slot
|
||||||
* layout (tag + N value words) stays word-aligned. The reg-
|
* layout (tag + N value words) stays word-aligned. The reg-
|
||||||
* passing ABI counts size/8 words; 12-byte unions like
|
* passing ABI counts size/8 words; 12-byte unions like
|
||||||
|
|||||||
@@ -408,6 +408,10 @@ struct Type {
|
|||||||
* through NAMED aliases. Variants with
|
* through NAMED aliases. Variants with
|
||||||
* iserror=1 are the propagation target of
|
* iserror=1 are the propagation target of
|
||||||
* the `?` operator. */
|
* the `?` operator. */
|
||||||
|
int nullable;/* TY_TAGGED with exactly `(*T | void)` —
|
||||||
|
* stored as a single 8-byte pointer; null
|
||||||
|
* is the void variant. Mirrors Hare's
|
||||||
|
* `(*T | null)` folding. */
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Type *ty_void, *ty_bool, *ty_rune;
|
extern Type *ty_void, *ty_bool, *ty_rune;
|
||||||
|
|||||||
@@ -145,6 +145,10 @@ static const struct row rows[] = {
|
|||||||
"fn parse() (invalid | i64 | overflow) = { return 0i64; }; "
|
"fn parse() (invalid | i64 | overflow) = { return 0i64; }; "
|
||||||
"fn caller() i32 = { let v: i64 = parse()?; return v: i32; };",
|
"fn caller() i32 = { let v: i64 = parse()?; return v: i32; };",
|
||||||
"enclosing function must return a tagged union" },
|
"enclosing function must return a tagged union" },
|
||||||
|
|
||||||
|
/* nullable pointer folding accepts `(*T | void)` */
|
||||||
|
{ "fn lookup(p: *i32) (*i32 | void) = { return p; };", "ok" },
|
||||||
|
{ "fn lookup() (*i32 | void) = { return; };", "ok" }, /* bare return → null */
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|||||||
@@ -538,6 +538,44 @@ static const struct row rows[] = {
|
|||||||
" };\n"
|
" };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};", 3 },
|
"};", 3 },
|
||||||
|
/* Nullable pointer folding: `(*T | void)` is one 8-byte word
|
||||||
|
* where null = void variant. match/is/as/?/! all key off the
|
||||||
|
* pointer-vs-null discriminator instead of a separate tag. */
|
||||||
|
{ "fn lookup(p: *i32, b: bool) (*i32 | void) = {\n"
|
||||||
|
" if (b) { return p; };\n"
|
||||||
|
" return;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn use_arg(r: (*i32 | void)) i32 = {\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let q: *i32 => return *q;\n"
|
||||||
|
" case void => return 99;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let x: i32 = 42;\n"
|
||||||
|
" let ok: i32 = use_arg(lookup(&x, true));\n"
|
||||||
|
" let no: i32 = use_arg(lookup(&x, false));\n"
|
||||||
|
" if (ok != 42) { return 1; };\n"
|
||||||
|
" if (no != 99) { return 2; };\n"
|
||||||
|
" return 7;\n"
|
||||||
|
"};", 7 },
|
||||||
|
/* Nullable with is/as: discriminator is ptr-vs-null. */
|
||||||
|
{ "fn lookup(p: *i32, b: bool) (*i32 | void) = {\n"
|
||||||
|
" if (b) { return p; };\n"
|
||||||
|
" return;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let x: i32 = 42;\n"
|
||||||
|
" let r1: (*i32 | void) = lookup(&x, true);\n"
|
||||||
|
" let r2: (*i32 | void) = lookup(&x, false);\n"
|
||||||
|
" let acc: i32 = 0;\n"
|
||||||
|
" if (r1 is *i32) { acc += 1; };\n"
|
||||||
|
" if (r2 is void) { acc += 2; };\n"
|
||||||
|
" let p: *i32 = r1 as *i32;\n"
|
||||||
|
" if (*p == 42) { acc += 4; };\n"
|
||||||
|
" return acc;\n"
|
||||||
|
"};", 7 },
|
||||||
/* `!`-flagged error variants: success picked by absence of `!`,
|
/* `!`-flagged error variants: success picked by absence of `!`,
|
||||||
* errors picked by presence. Tag remap still works across
|
* errors picked by presence. Tag remap still works across
|
||||||
* different variant orders between operand and enclosing fn. */
|
* different variant orders between operand and enclosing fn. */
|
||||||
|
|||||||
Reference in New Issue
Block a user