|
|
|
|
@@ -118,8 +118,9 @@ struct_arg_size(Type *t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Tagged-union arg byte size: 16 (8B variants) or 24 (16B variants).
|
|
|
|
|
* Both fit in our 3-register classification. Returns 0 if not a
|
|
|
|
|
* tagged union or too large to pass in registers. */
|
|
|
|
|
* Nullable-folded `(*T | void)` collapses to 8 bytes (just the
|
|
|
|
|
* pointer). Returns 0 if not a tagged union or too large to pass
|
|
|
|
|
* in registers. */
|
|
|
|
|
static int
|
|
|
|
|
tagged_arg_size(Type *t)
|
|
|
|
|
{
|
|
|
|
|
@@ -130,6 +131,32 @@ tagged_arg_size(Type *t)
|
|
|
|
|
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
|
|
|
|
|
node_istaggedarg(Node *n)
|
|
|
|
|
{
|
|
|
|
|
@@ -1713,11 +1740,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|
|
|
|
} else if (node_istaggedarg(args[i])) {
|
|
|
|
|
/* Tagged-return ABI: AX=tag, DX=val0[, CX=val1].
|
|
|
|
|
* 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);
|
|
|
|
|
if (sz > 16)
|
|
|
|
|
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));
|
|
|
|
|
} else {
|
|
|
|
|
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
|
|
|
|
|
* 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;
|
|
|
|
|
Type *st = s ? s->type : NULL;
|
|
|
|
|
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 sl_off;
|
|
|
|
|
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
|
|
|
|
|
* a scratch slot so we can index out the tag/value. The
|
|
|
|
|
* 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,
|
|
|
|
|
cg_frame);
|
|
|
|
|
cgexpr(c, s, locals);
|
|
|
|
|
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 (slot_size > 16)
|
|
|
|
|
ins2(c, A_MOVQ, areg(D_CX),
|
|
|
|
|
amem(D_BP, sl_off + 16));
|
|
|
|
|
if (!is_nullable) {
|
|
|
|
|
ins2(c, A_MOVQ, areg(D_DX),
|
|
|
|
|
amem(D_BP, sl_off + 8));
|
|
|
|
|
if (slot_size > 16)
|
|
|
|
|
ins2(c, A_MOVQ, areg(D_CX),
|
|
|
|
|
amem(D_BP, sl_off + 16));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
char *end = mklabel(c, "match_end");
|
|
|
|
|
for (Node *cs = n->list; cs; cs = cs->next) {
|
|
|
|
|
@@ -1893,7 +1933,18 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|
|
|
|
if (cs->type != NULL) {
|
|
|
|
|
int tag = cg_tag_for_variant(su, cs->type);
|
|
|
|
|
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 | ... =>`:
|
|
|
|
|
* if the tag matches any of the alts,
|
|
|
|
|
* jump to body; otherwise to the next
|
|
|
|
|
@@ -1923,16 +1974,34 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|
|
|
|
Type *bt = cs->type;
|
|
|
|
|
Type *bu = (bt && bt->kind == TY_NAMED)
|
|
|
|
|
? bt->under : bt;
|
|
|
|
|
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));
|
|
|
|
|
if (is_nullable) {
|
|
|
|
|
/* Bind *T or void to a local. The
|
|
|
|
|
* value IS the slot's pointer word; no
|
|
|
|
|
* payload to copy. void binding is
|
|
|
|
|
* unusable (size 0), so only emit for
|
|
|
|
|
* the *T variant. */
|
|
|
|
|
if (bu && bu->kind == TY_PTR) {
|
|
|
|
|
int voff = localoff(c, &locals,
|
|
|
|
|
cs->str, 8, cg_frame);
|
|
|
|
|
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);
|
|
|
|
|
@@ -1948,12 +2017,29 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|
|
|
|
* current function's return (with a tag remap to the
|
|
|
|
|
* enclosing fn's variant order). On success, unwrap to the
|
|
|
|
|
* 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);
|
|
|
|
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
|
|
|
|
if (u && u->kind == TY_NAMED) u = u->under;
|
|
|
|
|
Type *r = cg_ret_type;
|
|
|
|
|
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);
|
|
|
|
|
Type *succ_t = NULL;
|
|
|
|
|
if (u && u->kind == TY_TAGGED) {
|
|
|
|
|
@@ -1992,10 +2078,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
|
|
|
|
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);
|
|
|
|
|
Type *succ_t = NULL;
|
|
|
|
|
if (u && u->kind == TY_TAGGED) {
|
|
|
|
|
@@ -2018,19 +2116,28 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|
|
|
|
}
|
|
|
|
|
case N_TYPETEST: {
|
|
|
|
|
/* `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
|
|
|
|
|
* value words, so cgexpr's AX-only result is enough for both
|
|
|
|
|
* register-returning callers (AX=tag) and N_IDENT locals
|
|
|
|
|
* (cgexpr loads slot+0 → AX, which is the tag). */
|
|
|
|
|
* Result is bool (0/1) in AX. Nullable: discriminator is
|
|
|
|
|
* pointer-vs-null, not a tag. */
|
|
|
|
|
cgexpr(c, n->lhs, locals);
|
|
|
|
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
|
|
|
|
if (u && u->kind == TY_NAMED) u = u->under;
|
|
|
|
|
Type *vt = n->rhs ? n->rhs->type : NULL;
|
|
|
|
|
int tag = cg_tag_for_variant(u, vt);
|
|
|
|
|
char *ne = mklabel(c, "is_ne");
|
|
|
|
|
char *done = mklabel(c, "is_done");
|
|
|
|
|
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
|
|
|
|
|
ins1(c, A_JNE, abranch(ne));
|
|
|
|
|
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_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));
|
|
|
|
|
ins1(c, A_JMP, abranch(done));
|
|
|
|
|
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
|
|
|
|
|
* does not load it. Mirror match's pattern: resolve a slot
|
|
|
|
|
* 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;
|
|
|
|
|
Type *st = s ? s->type : NULL;
|
|
|
|
|
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
|
|
|
|
|
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 sl_off = 0;
|
|
|
|
|
if (s && s->kind == N_IDENT && s->str) {
|
|
|
|
|
@@ -2063,12 +2173,35 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|
|
|
|
slot_size, cg_frame);
|
|
|
|
|
cgexpr(c, s, locals);
|
|
|
|
|
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 (slot_size > 16)
|
|
|
|
|
ins2(c, A_MOVQ, areg(D_CX),
|
|
|
|
|
amem(D_BP, sl_off + 16));
|
|
|
|
|
if (!(u && u->kind == TY_TAGGED && u->nullable)) {
|
|
|
|
|
ins2(c, A_MOVQ, areg(D_DX),
|
|
|
|
|
amem(D_BP, sl_off + 8));
|
|
|
|
|
if (slot_size > 16)
|
|
|
|
|
ins2(c, A_MOVQ, areg(D_CX),
|
|
|
|
|
amem(D_BP, sl_off + 16));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
|
|
|
|
|
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`).
|
|
|
|
|
* Synthesise the tag from rhs's static type and store it
|
|
|
|
|
* 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) {
|
|
|
|
|
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);
|
|
|
|
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
|
|
|
|
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;
|
|
|
|
|
if (rt->kind == TY_NAMED) rt = rt->under;
|
|
|
|
|
if (rt && rt->kind == TY_TAGGED) {
|
|
|
|
|
int tag = cg_tag_for_variant(rt, ty_void);
|
|
|
|
|
if (tag < 0) tag = 0;
|
|
|
|
|
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
|
|
|
|
if (rt->nullable) {
|
|
|
|
|
/* bare `return;` is the void/null
|
|
|
|
|
* 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));
|
|
|
|
|
ins1(c, A_POPQ, areg(D_BP));
|
|
|
|
|
ins0(c, A_RET);
|
|
|
|
|
@@ -2795,7 +2943,11 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|
|
|
|
if (rt && rt->kind == TY_TAGGED) {
|
|
|
|
|
Type *vt = n->lhs->type;
|
|
|
|
|
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);
|
|
|
|
|
if (type_isstr(vt)) {
|
|
|
|
|
/* AX=ptr, BX=len → DX=ptr,
|
|
|
|
|
|