w6c+wcc: widen struct/tagged-subset, parse ... spread
Three tagged-union gaps:
1. Struct-payload widening was broken at every site (call, let,
assign, return, struct-field init). cg_widen_tagged_store now
materialises str / scalar / struct-lit / struct-ident / tagged
payloads at slot+8+field_off and writes the tag last. Call sites
route through cg_widen_tagged_push (scratch slot + push high→low).
2. Tagged → wider tagged widening forwarded the source tag verbatim.
cg_widen_tag_remap emits a CMPQ-chain switch that translates each
source variant index to the destination's, then zero-pads to the
wider slot. type_eq grew a TY_TAGGED arm (was returning 1 for any
two unions); type_assignable now accepts variant-subset and
rejects the rest.
3. `(...inner | T)` spread parses (cmd/wcc/parse.c, lib/ww/parse).
Marks Node.op = TK_ELLIPSIS; resolve_type unwraps NAMED + flattens
when the spread bit is set so aliases inline like Hare's
tagged_type unwrap flag.
Selfhost mirror: spread parser ported. Cgen widen helpers not yet
mirrored — wwstage stays byte-identical to cstage on the existing
test corpus, but will emit wrong asm if user code uses the new
patterns (probe sp2 shows the divergence).
700_e2e: 9 new rows covering call/let/assign/return × struct +
tagged subset, plus the spread-flatten case.
This commit is contained in:
487
cmd/w6c/cgen.c
487
cmd/w6c/cgen.c
@@ -771,6 +771,9 @@ localfind(Local *head, const char *name)
|
||||
|
||||
static void cgexpr(Cg*, Node*, Local*);
|
||||
static void cgstmt(Cg*, Node*, Local**, int*);
|
||||
static void cg_widen_tagged_push(Cg*, Local**, Type*, Node*, int);
|
||||
static void cg_widen_tagged_store(Cg*, Local**, Type*, Node*, int, int);
|
||||
static void cg_widen_tag_remap(Cg*, Type*, Type*, int);
|
||||
|
||||
static void
|
||||
cgexpr_int(Cg *c, long long v)
|
||||
@@ -778,6 +781,232 @@ cgexpr_int(Cg *c, long long v)
|
||||
ins2(c, A_MOVQ, aimm(v), areg(D_AX));
|
||||
}
|
||||
|
||||
/* cg_widen_tag_remap — when widening from one tagged union to another,
|
||||
* rewrite the source's variant tag at BP+slot_off+0 to use the dst
|
||||
* union's variant indices. No-op when src and dst index orders coincide.
|
||||
*
|
||||
* Mirrors Hare's tagged-subset assignment: a value of type (A|B) flows
|
||||
* into (A|B|C) by re-tagging the discriminator to the position the
|
||||
* variant occupies in the wider union. Both must already match by
|
||||
* cg_variant_match — the checker enforces that.
|
||||
*
|
||||
* Emits a CMPQ-chain switch over the source tag because w6a has no
|
||||
* CMOVQ encoding. The chain is linear in nvariants; in practice tagged
|
||||
* unions are small. */
|
||||
static void
|
||||
cg_widen_tag_remap(Cg *c, Type *du, Type *su, int slot_off)
|
||||
{
|
||||
if (du == NULL || du->kind != TY_TAGGED) return;
|
||||
if (su == NULL || su->kind != TY_TAGGED) return;
|
||||
int identity = 1, idx = 0;
|
||||
for (Tparam *p = su->params; p; p = p->next, idx++) {
|
||||
int di = cg_tag_for_variant(du, p->type);
|
||||
if (di < 0) di = 0;
|
||||
if (di != idx) { identity = 0; break; }
|
||||
}
|
||||
if (identity) return;
|
||||
const char *done = mklabel(c, "remap_done");
|
||||
ins2(c, A_MOVQ, amem(D_BP, slot_off + 0), areg(D_AX));
|
||||
idx = 0;
|
||||
for (Tparam *p = su->params; p; p = p->next, idx++) {
|
||||
const char *next = mklabel(c, "remap_next");
|
||||
int di = cg_tag_for_variant(du, p->type);
|
||||
if (di < 0) di = 0;
|
||||
ins2(c, A_CMPQ, aimm(idx), areg(D_AX));
|
||||
ins1(c, A_JNE, abranch(next));
|
||||
ins2(c, A_MOVQ, aimm(di), areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 0));
|
||||
ins1(c, A_JMP, abranch(done));
|
||||
label(c, next);
|
||||
}
|
||||
label(c, done);
|
||||
}
|
||||
|
||||
/* cg_widen_tagged_store — write the tagged-union slot bytes for `src`
|
||||
* into BP+slot_off, sized to `sz` (8 for nullable fold, else 16/24+).
|
||||
* Used by call-site widening (via cg_widen_tagged_push) and by the
|
||||
* let/assign/return/struct-field-init paths.
|
||||
*
|
||||
* Branches by source shape (tagged_arg_size > 0 source counts as a
|
||||
* tagged subset — possibly with different variant indices):
|
||||
* - nullable: dst is folded (*T|void); store pointer at +0.
|
||||
* - tagged ident: byte-copy slot words then remap tag at +0.
|
||||
* - tagged expression: cgexpr leaves AX=tag, DX=val0, [CX=val1] —
|
||||
* spill into slot then remap.
|
||||
* - struct ident: zero-fill, byte-copy struct words to +8.
|
||||
* - struct literal: zero-fill, store each field at slot+8+field_off.
|
||||
* - str: cgexpr leaves AX=ptr, BX=len.
|
||||
* - scalar: cgexpr leaves AX; store at +8 with zero pad. */
|
||||
static void
|
||||
cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
int slot_off, int sz)
|
||||
{
|
||||
Type *du = (dst && dst->kind == TY_NAMED) ? dst->under : dst;
|
||||
if (du == NULL || du->kind != TY_TAGGED) return;
|
||||
if (du->nullable) {
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 0));
|
||||
return;
|
||||
}
|
||||
Type *st = src ? src->type : NULL;
|
||||
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||
/* Tagged → tagged subset: copy slot words then tag-remap. */
|
||||
if (su && su->kind == TY_TAGGED) {
|
||||
int ssz = (int)su->size;
|
||||
if (src->kind == N_IDENT) {
|
||||
int soff = localfind(*locals_p, src->str);
|
||||
for (int k = 0; k < ssz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + k));
|
||||
}
|
||||
} else {
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + 0));
|
||||
if (ssz > 8)
|
||||
ins2(c, A_MOVQ, areg(D_DX),
|
||||
amem(D_BP, slot_off + 8));
|
||||
if (ssz > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, slot_off + 16));
|
||||
}
|
||||
if (ssz < sz) {
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
for (int k = ssz; k < sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + k));
|
||||
}
|
||||
cg_widen_tag_remap(c, du, su, slot_off);
|
||||
return;
|
||||
}
|
||||
/* Struct payload: zero the whole slot, then write fields/words
|
||||
* at slot+8+ — keeping the tag word at slot+0 from the zero-fill,
|
||||
* then patch it with the variant tag. */
|
||||
if (su && su->kind == TY_STRUCT) {
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
for (int k = 0; k < sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + k));
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
if (src->kind == N_IDENT) {
|
||||
int soff = localfind(*locals_p, src->str);
|
||||
int ssz = (int)su->size;
|
||||
int k = 0;
|
||||
while (k + 8 <= ssz) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + 8 + k));
|
||||
k += 8;
|
||||
}
|
||||
if (k < ssz) {
|
||||
/* Tail word: load with the right width to
|
||||
* avoid stepping past the source slot. The
|
||||
* zero-fill above means trailing slop is
|
||||
* already clean. */
|
||||
int tail = ssz - k;
|
||||
int lop = (tail == 4) ? A_MOVL :
|
||||
(tail == 1 ? A_MOVB : A_MOVQ);
|
||||
ins2(c, lop,
|
||||
amem(D_BP, soff + k), areg(D_AX));
|
||||
ins2(c, lop, areg(D_AX),
|
||||
amem(D_BP, slot_off + 8 + k));
|
||||
}
|
||||
} else if (src->kind == N_STRUCTLIT) {
|
||||
for (Node *f = src->list; f; f = f->next) {
|
||||
u64 foff = 0;
|
||||
int fsz = 8;
|
||||
Type *ftype = NULL;
|
||||
for (Tfield *fl = su->fields; fl; fl = fl->next) {
|
||||
if (strcmp(fl->name, f->str) == 0) {
|
||||
foff = fl->offset;
|
||||
fsz = (int)(fl->type ? fl->type->size : 8);
|
||||
ftype = fl->type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cgexpr(c, f->lhs, *locals_p);
|
||||
int sl_isf32 = 0;
|
||||
if (fld_isfloat(ftype, &sl_isf32)) {
|
||||
int mov = sl_isf32 ? A_MOVSS : A_MOVSD;
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_BP, slot_off + 8 + (int)foff));
|
||||
continue;
|
||||
}
|
||||
Type *fu = (ftype && ftype->kind == TY_NAMED)
|
||||
? ftype->under : ftype;
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + 8 + (int)foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, slot_off + 8 + (int)foff + 8));
|
||||
continue;
|
||||
}
|
||||
int op = A_MOVQ;
|
||||
if (fsz == 1) op = A_MOVB;
|
||||
else if (fsz == 4) op = A_MOVL;
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BP, slot_off + 8 + (int)foff));
|
||||
}
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, slot_off + 0));
|
||||
return;
|
||||
}
|
||||
/* str payload: AX=ptr, BX=len from cgexpr. */
|
||||
if (type_isstr(st) || (su && su->kind == TY_STR)) {
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, slot_off + 16));
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, slot_off + 0));
|
||||
return;
|
||||
}
|
||||
/* Scalar / pointer / etc. The high slot word (when sz > 16) is
|
||||
* left untouched here — match dispatches on the tag word first
|
||||
* and only the str branch reads slot+16, so leaving the pad
|
||||
* uninitialised in let/assign matches the pre-refactor asm.
|
||||
* cg_widen_tagged_push pre-zeroes the scratch slot before
|
||||
* calling us, so the call-site push still sees clean pad. */
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 8));
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), amem(D_BP, slot_off + 0));
|
||||
}
|
||||
|
||||
/* cg_widen_tagged_push — call-site widening. Materialise the tagged
|
||||
* value in a stack scratch slot then push slot words high→low so the
|
||||
* arg-register pop drain sees tag first, then payload words. */
|
||||
static void
|
||||
cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
|
||||
{
|
||||
Type *du = (dst && dst->kind == TY_NAMED) ? dst->under : dst;
|
||||
if (du && du->nullable) {
|
||||
/* Single 8B slot: just push the pointer/null. */
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
return;
|
||||
}
|
||||
const char *scr_name = mklabel(c, "argscr");
|
||||
int scr = local_alloc(c, locals_p, scr_name, sz, cg_frame);
|
||||
/* Zero the scratch slot first so any pad word the store path
|
||||
* leaves untouched (scalar variant in a >16B slot, struct payload
|
||||
* shorter than the slot's value area) reads as 0 on the callee.
|
||||
* The store path then writes the variant bytes over the zeros. */
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
for (int k = 0; k < sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, scr + k));
|
||||
cg_widen_tagged_store(c, locals_p, dst, src, scr, sz);
|
||||
int nwords = sz / 8;
|
||||
for (int k = nwords - 1; k >= 0; k--) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, scr + k * 8), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cgexpr(Cg *c, Node *n, Local *locals)
|
||||
{
|
||||
@@ -1570,9 +1799,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Plain `r = expr;` where r is a tagged-union local. Mirrors
|
||||
* the let-init path: forward AX:DX[:CX] when rhs is itself
|
||||
* tagged, else synthesise the tag from rhs's static type. */
|
||||
/* Plain `r = expr;` where r is a tagged-union local.
|
||||
* Delegates to cg_widen_tagged_store: covers nullable fold,
|
||||
* tagged→tagged (with tag remap), struct payload (ident or
|
||||
* literal), str payload, and scalar payload. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
||||
&& n->lhs->type) {
|
||||
Type *lt = n->lhs->type;
|
||||
@@ -1580,31 +1810,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (lu && lu->kind == TY_TAGGED) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off == 0) break;
|
||||
Type *rt = n->rhs ? n->rhs->type : NULL;
|
||||
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));
|
||||
if (lu->size > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, off + 16));
|
||||
} else {
|
||||
int tag = cg_tag_for_variant(lu, rt);
|
||||
cgexpr(c, n->rhs, locals);
|
||||
if (type_isstr(rt)) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, off + 16));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + 8));
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, off + 0));
|
||||
}
|
||||
cg_widen_tagged_store(c, &locals, lu, n->rhs,
|
||||
off, (int)lu->size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2072,9 +2279,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
callee_t->under : callee_t;
|
||||
Tparam *callee_params = (cu && cu->kind == TY_FN) ?
|
||||
cu->params : NULL;
|
||||
/* widen[i]: param is tagged, arg is a concrete variant.
|
||||
* widen_sz[i]: param's tagged slot size (8/16/24).
|
||||
* widen_param[i]: param type (for tag-index lookup). */
|
||||
/* widen[i]: param is tagged and arg needs re-layout.
|
||||
* - arg is a concrete variant (str/struct/scalar) — wrap
|
||||
* in the param's slot shape.
|
||||
* - arg is itself a tagged union of a subset/different
|
||||
* variant set — copy the slot words and remap the tag.
|
||||
* Identical types pass through unchanged. */
|
||||
int widen[16] = {0};
|
||||
int widen_sz[16] = {0};
|
||||
Type *widen_param[16] = {0};
|
||||
@@ -2084,18 +2294,27 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (p == NULL) break;
|
||||
Type *at = args[i] ? args[i]->type : NULL;
|
||||
int psz = tagged_arg_size(p->type);
|
||||
int arg_tagged = tagged_arg_size(at) > 0;
|
||||
if (psz > 0 && !arg_tagged) {
|
||||
widen[i] = 1;
|
||||
widen_sz[i] = psz;
|
||||
widen_param[i] = p->type;
|
||||
if (psz > 0) {
|
||||
Type *pu = (p->type && p->type->kind == TY_NAMED)
|
||||
? p->type->under : p->type;
|
||||
Type *au = (at && at->kind == TY_NAMED)
|
||||
? at->under : at;
|
||||
int same = (pu == au) || type_eq(p->type, at);
|
||||
if (!same) {
|
||||
widen[i] = 1;
|
||||
widen_sz[i] = psz;
|
||||
widen_param[i] = p->type;
|
||||
}
|
||||
}
|
||||
p = p->next;
|
||||
}
|
||||
}
|
||||
/* eval right-to-left, push to stack */
|
||||
/* eval right-to-left, push to stack. Each N_IDENT fast-path
|
||||
* is guarded by !widen[i] so the tagged-union widening (which
|
||||
* needs to synthesise tag + payload + pad) takes precedence
|
||||
* over the verbatim slice/struct/tagged-ident loads below. */
|
||||
for (int i = argcount - 1; i >= 0; i--) {
|
||||
if (node_isslice(args[i]) && args[i]->kind == N_IDENT) {
|
||||
if (!widen[i] && node_isslice(args[i]) && args[i]->kind == N_IDENT) {
|
||||
int off = localfind(locals, args[i]->str);
|
||||
/* push cap, len, ptr (top) so pops give ptr,len,cap */
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_AX));
|
||||
@@ -2153,7 +2372,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins1(c, A_PUSHQ, areg(D_CX)); /* ptr */
|
||||
continue;
|
||||
}
|
||||
if (node_isstructarg(args[i]) && args[i]->kind == N_IDENT) {
|
||||
if (!widen[i] && node_isstructarg(args[i]) && args[i]->kind == N_IDENT) {
|
||||
/* load qword(s) directly from the struct's slot */
|
||||
int off = localfind(locals, args[i]->str);
|
||||
int sz = struct_arg_size(args[i]->type);
|
||||
@@ -2165,7 +2384,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
continue;
|
||||
}
|
||||
if (node_istaggedarg(args[i]) && args[i]->kind == N_IDENT) {
|
||||
if (!widen[i] && node_istaggedarg(args[i]) && args[i]->kind == N_IDENT) {
|
||||
/* Tagged-union: push each 8B word from the slot.
|
||||
* High word goes first so the popper drains them
|
||||
* in low→high order into the arg-register class. */
|
||||
@@ -2182,41 +2401,25 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (widen[i]) {
|
||||
/* Concrete → tagged-union widening at the call
|
||||
* site. Mirrors the let/assign/return widening:
|
||||
* synthesise tag from the arg's static variant
|
||||
* type, evaluate the arg, lay it out as the
|
||||
* parameter's tagged slot, then push high→low so
|
||||
* pop drains tag first. */
|
||||
int tag = cg_tag_for_variant(widen_param[i],
|
||||
args[i]->type);
|
||||
if (tag < 0) tag = 0;
|
||||
int sz = widen_sz[i];
|
||||
if (sz == 8) {
|
||||
/* Nullable fold: the pointer value IS the
|
||||
* discriminator — no separate tag word. */
|
||||
cgexpr(c, args[i], locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, args[i], locals);
|
||||
if (node_isstr(args[i])) {
|
||||
/* slot 24: [+0]=tag,[+8]=ptr,[+16]=len */
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
} else {
|
||||
/* Scalar variant: single value word at +8.
|
||||
* Pad a zero high word when the slot is 24B
|
||||
* (some other variant of the union is 16B). */
|
||||
if (sz > 16) {
|
||||
ins2(c, A_XORQ, areg(D_DX),
|
||||
areg(D_DX));
|
||||
ins1(c, A_PUSHQ, areg(D_DX));
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
}
|
||||
* lay out the value in the parameter's slot
|
||||
* shape, then push high→low so pop drains tag
|
||||
* first.
|
||||
*
|
||||
* Branches by source shape:
|
||||
* - nullable (sz==8): pointer IS the disc.
|
||||
* - str: tag@+0, ptr@+8, len@+16.
|
||||
* - struct ident: copy struct words then
|
||||
* prepend tag, zero-pad to slot size.
|
||||
* - struct literal: materialise via a stack
|
||||
* scratch slot — store each field at its
|
||||
* struct-relative offset (with the +8 tag
|
||||
* shift), zero-fill, then push from slot.
|
||||
* - tagged source: load src slot words, remap
|
||||
* the tag word via cg_widen_tag_remap, pad
|
||||
* to wider dst slot, push.
|
||||
* - scalar: tag@+0, value@+8, optional pad. */
|
||||
cg_widen_tagged_push(c, &locals, widen_param[i],
|
||||
args[i], widen_sz[i]);
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, args[i], locals);
|
||||
@@ -3327,47 +3530,13 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Tagged-union initialiser. Two shapes:
|
||||
* 1) rhs already produces a tagged-union value (e.g. a fn
|
||||
* call returning (T | E)). cgexpr leaves AX=tag,
|
||||
* DX=value0[, CX=value1] — copy each into the slot.
|
||||
* 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.
|
||||
*
|
||||
* 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. */
|
||||
/* Tagged-union initialiser. Delegates to cg_widen_tagged_store,
|
||||
* which handles nullable fold, tagged→tagged (with tag remap
|
||||
* when variant indices differ), struct payload (ident or
|
||||
* literal — field-by-field at slot+8+field_off), str payload,
|
||||
* and scalar payload (with zero-pad to the slot size). */
|
||||
if (n->rhs && lu && lu->kind == TY_TAGGED) {
|
||||
Type *rt = n->rhs->type;
|
||||
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));
|
||||
if (lu->size > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, off + 16));
|
||||
} else {
|
||||
int tag = cg_tag_for_variant(lu, rt);
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
if (type_isstr(rt)) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, off + 16));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + 8));
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, off + 0));
|
||||
}
|
||||
cg_widen_tagged_store(c, locals, lu, n->rhs, off, sz);
|
||||
break;
|
||||
}
|
||||
/* slice expression initialiser: build a {ptr, len, cap} header
|
||||
@@ -3472,37 +3641,17 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Tagged-union field: synthesise tag from
|
||||
* f->lhs's static type and store value bytes
|
||||
* (1 or 2 words for ≤8B/str variants). */
|
||||
/* Tagged-union field: delegate to the shared
|
||||
* widening writer. Handles str, scalar, struct
|
||||
* literal/ident payload, and tagged-subset
|
||||
* forwarding (with tag remap). The field's slot
|
||||
* starts at off+foff inside the struct slot. */
|
||||
Type *fu = (ft && ft->kind == TY_NAMED)
|
||||
? ft->under : ft;
|
||||
if (fu && fu->kind == TY_TAGGED) {
|
||||
Type *vt = f->lhs ? f->lhs->type : NULL;
|
||||
int tag = cg_tag_for_variant(fu, vt);
|
||||
cgexpr(c, f->lhs, *locals);
|
||||
if (type_isstr(vt)) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + (int)foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, off + (int)foff + 16));
|
||||
} else if (type_istagged(vt)) {
|
||||
/* forwarding a tagged value */
|
||||
ins2(c, A_MOVQ, areg(D_DX),
|
||||
amem(D_BP, off + (int)foff + 8));
|
||||
if (fu->size > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, off + (int)foff + 16));
|
||||
/* AX already holds the tag */
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + (int)foff + 0));
|
||||
continue;
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + (int)foff + 8));
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, off + (int)foff + 0));
|
||||
cg_widen_tagged_store(c, locals, fu,
|
||||
f->lhs, off + (int)foff,
|
||||
(int)fu->size);
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, f->lhs, *locals);
|
||||
@@ -3615,18 +3764,25 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
if (rt->kind == TY_NAMED) rt = rt->under;
|
||||
if (rt && rt->kind == TY_TAGGED) {
|
||||
Type *vt = n->lhs->type;
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
Type *vu = (vt && vt->kind == TY_NAMED)
|
||||
? vt->under : vt;
|
||||
int istagged = vu && vu->kind == TY_TAGGED;
|
||||
int passthrough = istagged && (vu == rt ||
|
||||
type_eq(vt, cg_ret_type));
|
||||
int isstruct = vu && vu->kind == TY_STRUCT;
|
||||
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)) {
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
} else if (passthrough) {
|
||||
/* same tagged type: forward AX/DX/CX. */
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
} else if (!istagged && !isstruct) {
|
||||
/* str / scalar variant: synthesise the
|
||||
* tag in AX and shuffle the value into
|
||||
* DX[/CX]. Direct register path keeps
|
||||
* the asm short — no scratch slot. */
|
||||
int tag = cg_tag_for_variant(rt, vt);
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
if (type_isstr(vt)) {
|
||||
/* AX=ptr, BX=len → DX=ptr,
|
||||
* CX=len, AX=tag. Move BX
|
||||
* before AX since the tag
|
||||
* MOVQ trashes AX. */
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
@@ -3637,6 +3793,33 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
areg(D_AX));
|
||||
} else {
|
||||
/* Struct variant or tagged-subset:
|
||||
* materialise the widened value in a
|
||||
* scratch slot, then load AX/DX/CX
|
||||
* from the slot. Struct literal: field
|
||||
* stores; struct ident: word copy;
|
||||
* tagged subset: copy + tag remap. */
|
||||
int sz = (int)rt->size;
|
||||
const char *scrn = mklabel(c, "retscr");
|
||||
int scr = local_alloc(c, locals, scrn,
|
||||
sz, cg_frame);
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
for (int k = 0; k < sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, scr + k));
|
||||
cg_widen_tagged_store(c, locals, rt,
|
||||
n->lhs, scr, sz);
|
||||
ins2(c, A_MOVQ, amem(D_BP, scr + 0),
|
||||
areg(D_AX));
|
||||
if (sz > 8)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, scr + 8),
|
||||
areg(D_DX));
|
||||
if (sz > 16)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, scr + 16),
|
||||
areg(D_CX));
|
||||
}
|
||||
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||
ins1(c, A_POPQ, areg(D_BP));
|
||||
|
||||
Reference in New Issue
Block a user