w6c+selfhost: cg_widen_tagged_store basereg + N_ASSIGN tagged field (closes #26)
Extended cg_widen_tagged_store (cstage) / cgwidentaggedstore (wwstage) to take a base_reg/basereg parameter so the primitive supports non-BP destinations. Cstage extends body in-place via via_outer gate + spill+scratch+copy-out; wwstage splits into wrapper (non-BP) + cgwidentaggedstorebp (BP-only) to dodge the no-goto constraint. New N_ASSIGN field TY_TAGGED branch routes through the primitive for all rhs shapes. Scope-adjacent: fieldsize recurses through N_TTAGGED via slotsize and TNAME-aliased-to-tagged via aliaslookup. Needed for the test fixtures. Wwstage read-side N_DOT-of-tagged-field source is filed as task #28; test rows use mark-canary verification until that lands.
This commit is contained in:
193
cmd/w6c/cgen.c
193
cmd/w6c/cgen.c
@@ -862,7 +862,7 @@ 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_tagged_store(Cg*, Local**, Type*, Node*, int, int, int);
|
||||
static void cg_widen_tag_remap(Cg*, Type*, Type*, int);
|
||||
|
||||
static void
|
||||
@@ -913,9 +913,21 @@ cg_widen_tag_remap(Cg *c, Type *du, Type *su, int slot_off)
|
||||
}
|
||||
|
||||
/* 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.
|
||||
* into base_reg+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.
|
||||
*
|
||||
* base_reg picks the addressing root for every write:
|
||||
* - D_BP: function-frame slot. The original layout — callers pass
|
||||
* a BP-relative slot_off and the function writes directly.
|
||||
* - else (e.g. D_BX for a *struct field, D_CX for a top-level
|
||||
* struct field): pointer-rooted dst. cgexpr inside this function
|
||||
* trashes every GPR, so we can't carry base_reg across — instead
|
||||
* we route every write through a fresh BP-rooted scratch slot,
|
||||
* reload base_reg from a temp spill at the end, and word-copy
|
||||
* scratch → (base_reg, slot_off). Caller is responsible for
|
||||
* loading base_reg with the dst address before the call; the
|
||||
* function preserves it across cgexpr via the spill.
|
||||
*
|
||||
* Branches by source shape (tagged_arg_size > 0 source counts as a
|
||||
* tagged subset — possibly with different variant indices):
|
||||
@@ -929,13 +941,34 @@ cg_widen_tag_remap(Cg *c, Type *du, Type *su, int slot_off)
|
||||
* - 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)
|
||||
int base_reg, int slot_off, int sz)
|
||||
{
|
||||
/* For pointer-rooted dst, materialise into a BP-rooted scratch
|
||||
* slot — body writes via `amem(D_BP, write_off + k)` — then copy
|
||||
* out. Spill base_reg first so cgexpr can clobber freely. */
|
||||
int via_outer = (base_reg != D_BP);
|
||||
int base_spill = 0;
|
||||
int write_off = slot_off;
|
||||
if (via_outer) {
|
||||
const char *spname = mklabel(c, "tagbase");
|
||||
base_spill = local_alloc(c, locals_p, spname, 8, cg_frame);
|
||||
ins2(c, A_MOVQ, areg(base_reg), amem(D_BP, base_spill));
|
||||
const char *scname = mklabel(c, "tagscr");
|
||||
write_off = local_alloc(c, locals_p, scname, sz, cg_frame);
|
||||
/* Pre-zero so str/scalar branches (which leave high words
|
||||
* untouched when sz exceeds the variant's footprint) still
|
||||
* deliver a clean slot to the copy-out. */
|
||||
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, write_off + k));
|
||||
}
|
||||
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));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, write_off + 0));
|
||||
if (via_outer) goto copy_out;
|
||||
return;
|
||||
}
|
||||
/* `expr: TaggedAlias` where the cast's destination IS the union
|
||||
@@ -970,29 +1003,30 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
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));
|
||||
amem(D_BP, write_off + k));
|
||||
}
|
||||
} else {
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + 0));
|
||||
amem(D_BP, write_off + 0));
|
||||
if (ssz > 8)
|
||||
ins2(c, A_MOVQ, areg(D_DX),
|
||||
amem(D_BP, slot_off + 8));
|
||||
amem(D_BP, write_off + 8));
|
||||
if (ssz > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, slot_off + 16));
|
||||
amem(D_BP, write_off + 16));
|
||||
if (ssz > 24)
|
||||
ins2(c, A_MOVQ, areg(D_R8),
|
||||
amem(D_BP, slot_off + 24));
|
||||
amem(D_BP, write_off + 24));
|
||||
}
|
||||
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));
|
||||
amem(D_BP, write_off + k));
|
||||
}
|
||||
cg_widen_tag_remap(c, du, su, slot_off);
|
||||
cg_widen_tag_remap(c, du, su, write_off);
|
||||
if (via_outer) goto copy_out;
|
||||
return;
|
||||
}
|
||||
/* Struct payload: zero the whole slot, then write fields/words
|
||||
@@ -1002,7 +1036,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
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));
|
||||
amem(D_BP, write_off + k));
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
if (src->kind == N_IDENT) {
|
||||
int soff = localfind(*locals_p, src->str);
|
||||
@@ -1012,7 +1046,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
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));
|
||||
amem(D_BP, write_off + 8 + k));
|
||||
k += 8;
|
||||
}
|
||||
if (k < ssz) {
|
||||
@@ -1026,7 +1060,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
ins2(c, lop,
|
||||
amem(D_BP, soff + k), areg(D_AX));
|
||||
ins2(c, lop, areg(D_AX),
|
||||
amem(D_BP, slot_off + 8 + k));
|
||||
amem(D_BP, write_off + 8 + k));
|
||||
}
|
||||
} else if (src->kind == N_STRUCTLIT) {
|
||||
for (Node *f = src->list; f; f = f->next) {
|
||||
@@ -1046,37 +1080,39 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
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));
|
||||
amem(D_BP, write_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));
|
||||
amem(D_BP, write_off + 8 + (int)foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, slot_off + 8 + (int)foff + 8));
|
||||
amem(D_BP, write_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));
|
||||
amem(D_BP, write_off + 8 + (int)foff));
|
||||
}
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, slot_off + 0));
|
||||
amem(D_BP, write_off + 0));
|
||||
if (via_outer) goto copy_out;
|
||||
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));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, write_off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, write_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));
|
||||
amem(D_BP, write_off + 0));
|
||||
if (via_outer) goto copy_out;
|
||||
return;
|
||||
}
|
||||
/* Slice payload: cgexpr leaves (AX=ptr, BX=len, CX=cap). The
|
||||
@@ -1084,12 +1120,13 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
* destination tagged-union slot be at least 32B. */
|
||||
if (type_isslice(st) || (su && su->kind == TY_SLICE)) {
|
||||
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));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, slot_off + 24));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, write_off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, write_off + 16));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, write_off + 24));
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, slot_off + 0));
|
||||
amem(D_BP, write_off + 0));
|
||||
if (via_outer) goto copy_out;
|
||||
return;
|
||||
}
|
||||
/* Scalar / pointer / etc. The high slot word (when sz > 16) is
|
||||
@@ -1099,9 +1136,21 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
* 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));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, write_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));
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), amem(D_BP, write_off + 0));
|
||||
copy_out:
|
||||
if (via_outer) {
|
||||
/* cgexpr above clobbered base_reg — reload from spill, then
|
||||
* word-copy scratch → caller's (base_reg, slot_off). */
|
||||
ins2(c, A_MOVQ, amem(D_BP, base_spill), areg(base_reg));
|
||||
for (int k = 0; k < sz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, write_off + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(base_reg, slot_off + k));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* cg_widen_tagged_push — call-site widening. For shapes where cgexpr
|
||||
@@ -1168,7 +1217,7 @@ cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
|
||||
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);
|
||||
cg_widen_tagged_store(c, locals_p, dst, src, D_BP, 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));
|
||||
@@ -1862,63 +1911,41 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
Type *ft = f->type;
|
||||
Type *fu = (ft && ft->kind == TY_NAMED)
|
||||
? ft->under : ft;
|
||||
/* Tagged-union field — full slot rewrite via the
|
||||
* shared widener so every rhs shape (whole-tagged
|
||||
* ident or expr with tag-remap, concrete-variant
|
||||
* widening of str/slice/struct/scalar/void) lands
|
||||
* the right tag + payload bytes. The pre-#26
|
||||
* branch synthesised a single tag from
|
||||
* cg_tag_for_variant and stored only AX at +8, so
|
||||
* whole-tagged rhs (vt == fu, no concrete tag)
|
||||
* silently wrote tag 0 and dropped trailing words.
|
||||
* cg_widen_tagged_store handles every shape by
|
||||
* branching on the source's resolved type. */
|
||||
if (fu && fu->kind == TY_TAGGED
|
||||
&& n->op == TK_ASSIGN) {
|
||||
int boff = localfind(locals, base->str);
|
||||
int is_global = (boff == 0 && !via_ptr
|
||||
&& let_islet(base->str));
|
||||
int foff = (int)f->offset;
|
||||
Type *vt = n->rhs ? n->rhs->type : NULL;
|
||||
int tag = cg_tag_for_variant(fu, vt);
|
||||
int addr_in_bx = via_ptr;
|
||||
if (via_ptr)
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff),
|
||||
int fsz = (int)fu->size;
|
||||
if (via_ptr) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, boff),
|
||||
areg(D_BX));
|
||||
cgexpr(c, n->rhs, locals);
|
||||
if (is_global)
|
||||
cg_widen_tagged_store(c, &locals,
|
||||
fu, n->rhs, D_BX, foff, fsz);
|
||||
} else if (is_global) {
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, base->str),
|
||||
areg(D_CX));
|
||||
if (type_isstr(vt)) {
|
||||
if (addr_in_bx) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BX, foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BX, foff + 16));
|
||||
} else if (is_global) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_CX, foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_CX, foff + 16));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, boff + foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, boff + foff + 16));
|
||||
}
|
||||
areg(D_BX));
|
||||
cg_widen_tagged_store(c, &locals,
|
||||
fu, n->rhs, D_BX, foff, fsz);
|
||||
} else {
|
||||
if (addr_in_bx)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BX, foff + 8));
|
||||
else if (is_global)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_CX, foff + 8));
|
||||
else
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, boff + foff + 8));
|
||||
cg_widen_tagged_store(c, &locals,
|
||||
fu, n->rhs, D_BP,
|
||||
boff + foff, fsz);
|
||||
}
|
||||
if (addr_in_bx)
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BX, foff + 0));
|
||||
else if (is_global)
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_CX, foff + 0));
|
||||
else
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, boff + foff + 0));
|
||||
break;
|
||||
}
|
||||
int fsz = (int)(f->type ? f->type->size : 8);
|
||||
@@ -2781,7 +2808,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, scr + k));
|
||||
cg_widen_tagged_store(c, &locals, esubu,
|
||||
n->rhs, scr, ssz);
|
||||
n->rhs, D_BP, scr, ssz);
|
||||
/* Compute &arr[i] → BX. */
|
||||
cgexpr(c, n->lhs->rhs, locals);
|
||||
if (ssz > 1) {
|
||||
@@ -2878,7 +2905,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off == 0) break;
|
||||
cg_widen_tagged_store(c, &locals, lu, n->rhs,
|
||||
off, (int)lu->size);
|
||||
D_BP, off, (int)lu->size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -3458,7 +3485,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (v_is_tagged) {
|
||||
cg_widen_tagged_store(c,
|
||||
&locals, velem,
|
||||
a, slot, esz);
|
||||
a, D_BP, slot, esz);
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, a, locals);
|
||||
@@ -5158,7 +5185,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
* 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) {
|
||||
cg_widen_tagged_store(c, locals, lu, n->rhs, off, sz);
|
||||
cg_widen_tagged_store(c, locals, lu, n->rhs, D_BP, off, sz);
|
||||
break;
|
||||
}
|
||||
/* slice expression initialiser: build a {ptr, len, cap} header
|
||||
@@ -5272,7 +5299,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
? ft->under : ft;
|
||||
if (fu && fu->kind == TY_TAGGED) {
|
||||
cg_widen_tagged_store(c, locals, fu,
|
||||
f->lhs, off + (int)foff,
|
||||
f->lhs, D_BP, off + (int)foff,
|
||||
(int)fu->size);
|
||||
continue;
|
||||
}
|
||||
@@ -5470,7 +5497,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, scr + k));
|
||||
cg_widen_tagged_store(c, locals, rt,
|
||||
n->lhs, scr, sz);
|
||||
n->lhs, D_BP, scr, sz);
|
||||
ins2(c, A_MOVQ, amem(D_BP, scr + 0),
|
||||
areg(D_AX));
|
||||
if (sz > 8)
|
||||
|
||||
@@ -5744,7 +5744,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
emitline("(BP)\n");
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, ptype, arg, scroff, widensz);
|
||||
cgwidentaggedstore(c, ptype, arg, "BP", scroff, widensz);
|
||||
let pp: i32 = widensz - 8;
|
||||
for (pp >= 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
@@ -6938,6 +6938,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
fn fieldsize(c: *cgen, tnode: *node) i32 = {
|
||||
if (tnode == nil) { return 8; };
|
||||
let k: nkind = tnode.kind;
|
||||
if (k == nkind.N_TTAGGED){ return slotsize(c, tnode); };
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = tnode.str;
|
||||
if (streq(nm, "str")) { return 16; };
|
||||
@@ -6957,6 +6958,13 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
|
||||
};
|
||||
return 4; // default storage is i32
|
||||
};
|
||||
// Type alias to a tagged-union — recurse through aliaslookup
|
||||
// so `e: ev` (where `ev = (i64 | i32)`) takes 16B in the
|
||||
// containing struct rather than the 8B default.
|
||||
if (c != nil) {
|
||||
let aliased: *node = aliaslookup(c, nm);
|
||||
if (aliased != nil) { return fieldsize(c, aliased); };
|
||||
};
|
||||
return 8;
|
||||
};
|
||||
if (k == nkind.N_TPTR) { return 8; };
|
||||
@@ -7633,9 +7641,20 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// cgwidentaggedstore — write tagged-union slot bytes for `src` into the
|
||||
// slot at BP+slot_off, sized to slot_sz. Mirrors cg_widen_tagged_store
|
||||
// in cmd/w6c/cgen.c. Branches by source shape:
|
||||
// cgwidentaggedstore — write tagged-union slot bytes for `src` into
|
||||
// the slot at `basereg`+slot_off, sized to slot_sz. Mirrors
|
||||
// cg_widen_tagged_store in cmd/w6c/cgen.c.
|
||||
//
|
||||
// `basereg` selects the addressing root:
|
||||
// - "BP": function-frame slot (let / assign / return / structlit /
|
||||
// array-elem scratch). Body writes straight to slot_off(BP).
|
||||
// - else (e.g. "BX" for *struct field, top-level struct LEAQ
|
||||
// base): pointer-rooted dst. cgexpr inside trashes every GPR,
|
||||
// so we route through a fresh BP-rooted scratch slot, spill
|
||||
// basereg before the body, reload after, then word-copy
|
||||
// scratch → (basereg, slot_off).
|
||||
//
|
||||
// Branches by source shape:
|
||||
// - nullable dst (8B slot): cgexpr → AX → slot+0.
|
||||
// - tagged src ident: copy slot words, zero-pad, tag-remap.
|
||||
// - tagged src via AX/DX/CX ABI (call / tagged-arr index): cgexpr,
|
||||
@@ -7646,7 +7665,53 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
|
||||
// tag last.
|
||||
// - str src: tag@+0, ptr@+8, len@+16.
|
||||
// - scalar src: tag@+0, value@+8.
|
||||
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
|
||||
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node,
|
||||
basereg: str, slot_off: i32, slot_sz: i32) void = {
|
||||
if (streq(basereg, "BP")) {
|
||||
cgwidentaggedstorebp(c, dst, src, slot_off, slot_sz);
|
||||
return;
|
||||
};
|
||||
// Pointer-rooted dst: spill basereg (cgexpr will trash it),
|
||||
// materialise into a BP-rooted scratch via the BP path, then
|
||||
// reload basereg and word-copy scratch → caller's slot.
|
||||
let bspill: i32 = localadd(c, "@tagbase", 8, nil);
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(basereg);
|
||||
emitline(", ");
|
||||
emitoff(bspill: i64);
|
||||
emitline("(BP)\n");
|
||||
let scr: i32 = localadd(c, "@tagscr", slot_sz, nil);
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let z: i32 = 0;
|
||||
for (z < slot_sz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((scr + z): i64);
|
||||
emitline("(BP)\n");
|
||||
z += 8;
|
||||
};
|
||||
cgwidentaggedstorebp(c, dst, src, scr, slot_sz);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(bspill: i64);
|
||||
emitline("(BP), ");
|
||||
emitline(basereg);
|
||||
emitline("\n");
|
||||
let k: i32 = 0;
|
||||
for (k < slot_sz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scr + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((slot_off + k): i64, basereg);
|
||||
emitline("\n");
|
||||
k += 8;
|
||||
};
|
||||
};
|
||||
|
||||
// cgwidentaggedstorebp — BP-rooted body. Called via cgwidentaggedstore
|
||||
// for the natural "BP" case and via the wrapper's scratch path for
|
||||
// pointer-rooted dst. Direct callers exist only in case of future
|
||||
// inlined uses inside this file; new code should call the wrapper.
|
||||
fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
|
||||
let dt: *node = resolvetagged(c, dst);
|
||||
if (dt == nil) { return; };
|
||||
// Nullable fold: one 8B word holding the pointer (or 0 for void).
|
||||
@@ -10793,7 +10858,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
let slot: i32 = doff + j * esz;
|
||||
if (velemtagged) {
|
||||
cgwidentaggedstore(c, varp.lhs,
|
||||
aa2, slot, esz);
|
||||
aa2, "BP", slot, esz);
|
||||
} else { if (velemstr) {
|
||||
cgexpr(c, aa2);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -11049,7 +11114,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (istaggedtype(c, lc.tnode)) {
|
||||
let lsz: i32 = slotsize(c, lc.tnode);
|
||||
cgwidentaggedstore(c, lc.tnode,
|
||||
n.rhs, lc.off, lsz);
|
||||
n.rhs, "BP", lc.off, lsz);
|
||||
return;
|
||||
};
|
||||
};
|
||||
@@ -11273,7 +11338,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, elemtn, n.rhs,
|
||||
scroff, slot_sz);
|
||||
"BP", scroff, slot_sz);
|
||||
cgexpr(c, idx);
|
||||
if (slot_sz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
@@ -11619,6 +11684,20 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fld)) {
|
||||
// Tagged-union field via *struct base — full slot
|
||||
// rewrite via cgwidentaggedstore basereg="BX". Pre-#26
|
||||
// fell through to the scalar store and dropped tag
|
||||
// + payload.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& istaggedtype(c, fi.tnode)) {
|
||||
let fsz: i32 = slotsize(c, fi.tnode);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
cgwidentaggedstore(c, fi.tnode,
|
||||
n.rhs, "BX", fi.foff, fsz);
|
||||
return;
|
||||
};
|
||||
// struct-typed field, struct-ident rhs through
|
||||
// *struct base: cgexpr can't materialise a whole
|
||||
// struct value, so word-copy from the rhs slot
|
||||
@@ -11769,6 +11848,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fld)) {
|
||||
// Tagged-union field in a direct struct local —
|
||||
// full slot rewrite at (lc.off + fi.foff)(BP)
|
||||
// via cgwidentaggedstore basereg="BP". Pre-#26
|
||||
// fell through and dropped tag + payload.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& istaggedtype(c, fi.tnode)) {
|
||||
let fsz: i32 = slotsize(c, fi.tnode);
|
||||
cgwidentaggedstore(c, fi.tnode,
|
||||
n.rhs, "BP", lc.off + fi.foff, fsz);
|
||||
return;
|
||||
};
|
||||
// struct-typed field, struct-ident rhs:
|
||||
// word-copy direct, skipping cgexpr (no
|
||||
// register convention for a whole struct
|
||||
@@ -12978,8 +13068,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, c.fnret, rhs, scroff,
|
||||
rsz);
|
||||
cgwidentaggedstore(c, c.fnret, rhs, "BP",
|
||||
scroff, rsz);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scroff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -13101,7 +13191,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// ABI call), struct payload (literal/ident), str payload,
|
||||
// scalar payload — with tag remap for tagged-subset widening.
|
||||
if (istaggedtype(c, tn)) {
|
||||
cgwidentaggedstore(c, tn, rhs, off, sz);
|
||||
cgwidentaggedstore(c, tn, rhs, "BP", off, sz);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
@@ -14118,6 +14208,61 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-union struct-field write: `s.f = v` or `(*p).f = v`
|
||||
// where f is a tagged-union field. cgassign delegates to
|
||||
// cgwidentaggedstore; for pointer-rooted dst the wrapper
|
||||
// allocates @tagbase (8B) and @tagscr (slot_sz). Both names
|
||||
// dedup with other tagged scratch users in the same function.
|
||||
if (n.kind == nkind.N_ASSIGN) {
|
||||
let alhs: *node = n.lhs;
|
||||
if (alhs != nil) {
|
||||
if (alhs.kind == nkind.N_DOT) {
|
||||
let abase: *node = alhs.lhs;
|
||||
if (abase != nil) {
|
||||
if (abase.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, abase.str);
|
||||
let btn: *node = nil;
|
||||
if (lc != nil) { btn = lc.tnode; }
|
||||
else { btn = letvartnode(c, abase.str); };
|
||||
let isptr: bool = false;
|
||||
let stype: *node = nil;
|
||||
if (btn != nil) {
|
||||
if (btn.kind == nkind.N_TPTR) {
|
||||
isptr = true;
|
||||
stype = btn.lhs;
|
||||
};
|
||||
if (btn.kind == nkind.N_TNAME) { stype = btn; };
|
||||
};
|
||||
if (stype != nil) {
|
||||
if (stype.kind == nkind.N_TNAME) {
|
||||
let si: *structinfo = structlookup(c, stype.str);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, alhs.str)) {
|
||||
if (istaggedtype(c, fi.tnode)) {
|
||||
if (isptr) {
|
||||
if (!scanseenmark(c, "@tagbase")) {
|
||||
total += 8;
|
||||
};
|
||||
if (!scanseenmark(c, "@tagscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
fi = nil;
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-union return with struct payload or tagged-subset
|
||||
// source — cgreturn materialises in @tagscr then loads
|
||||
// AX/DX/CX. Detect via the same rhsstructpayload predicate
|
||||
|
||||
@@ -204,6 +204,61 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-union struct-field write: `s.f = v` or `(*p).f = v`
|
||||
// where f is a tagged-union field. cgassign delegates to
|
||||
// cgwidentaggedstore; for pointer-rooted dst the wrapper
|
||||
// allocates @tagbase (8B) and @tagscr (slot_sz). Both names
|
||||
// dedup with other tagged scratch users in the same function.
|
||||
if (n.kind == nkind.N_ASSIGN) {
|
||||
let alhs: *node = n.lhs;
|
||||
if (alhs != nil) {
|
||||
if (alhs.kind == nkind.N_DOT) {
|
||||
let abase: *node = alhs.lhs;
|
||||
if (abase != nil) {
|
||||
if (abase.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, abase.str);
|
||||
let btn: *node = nil;
|
||||
if (lc != nil) { btn = lc.tnode; }
|
||||
else { btn = letvartnode(c, abase.str); };
|
||||
let isptr: bool = false;
|
||||
let stype: *node = nil;
|
||||
if (btn != nil) {
|
||||
if (btn.kind == nkind.N_TPTR) {
|
||||
isptr = true;
|
||||
stype = btn.lhs;
|
||||
};
|
||||
if (btn.kind == nkind.N_TNAME) { stype = btn; };
|
||||
};
|
||||
if (stype != nil) {
|
||||
if (stype.kind == nkind.N_TNAME) {
|
||||
let si: *structinfo = structlookup(c, stype.str);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, alhs.str)) {
|
||||
if (istaggedtype(c, fi.tnode)) {
|
||||
if (isptr) {
|
||||
if (!scanseenmark(c, "@tagbase")) {
|
||||
total += 8;
|
||||
};
|
||||
if (!scanseenmark(c, "@tagscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
fi = nil;
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-union return with struct payload or tagged-subset
|
||||
// source — cgreturn materialises in @tagscr then loads
|
||||
// AX/DX/CX. Detect via the same rhsstructpayload predicate
|
||||
|
||||
@@ -2745,7 +2745,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
let slot: i32 = doff + j * esz;
|
||||
if (velemtagged) {
|
||||
cgwidentaggedstore(c, varp.lhs,
|
||||
aa2, slot, esz);
|
||||
aa2, "BP", slot, esz);
|
||||
} else { if (velemstr) {
|
||||
cgexpr(c, aa2);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -3001,7 +3001,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (istaggedtype(c, lc.tnode)) {
|
||||
let lsz: i32 = slotsize(c, lc.tnode);
|
||||
cgwidentaggedstore(c, lc.tnode,
|
||||
n.rhs, lc.off, lsz);
|
||||
n.rhs, "BP", lc.off, lsz);
|
||||
return;
|
||||
};
|
||||
};
|
||||
@@ -3225,7 +3225,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, elemtn, n.rhs,
|
||||
scroff, slot_sz);
|
||||
"BP", scroff, slot_sz);
|
||||
cgexpr(c, idx);
|
||||
if (slot_sz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
@@ -3571,6 +3571,20 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fld)) {
|
||||
// Tagged-union field via *struct base — full slot
|
||||
// rewrite via cgwidentaggedstore basereg="BX". Pre-#26
|
||||
// fell through to the scalar store and dropped tag
|
||||
// + payload.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& istaggedtype(c, fi.tnode)) {
|
||||
let fsz: i32 = slotsize(c, fi.tnode);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
cgwidentaggedstore(c, fi.tnode,
|
||||
n.rhs, "BX", fi.foff, fsz);
|
||||
return;
|
||||
};
|
||||
// struct-typed field, struct-ident rhs through
|
||||
// *struct base: cgexpr can't materialise a whole
|
||||
// struct value, so word-copy from the rhs slot
|
||||
@@ -3721,6 +3735,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fld)) {
|
||||
// Tagged-union field in a direct struct local —
|
||||
// full slot rewrite at (lc.off + fi.foff)(BP)
|
||||
// via cgwidentaggedstore basereg="BP". Pre-#26
|
||||
// fell through and dropped tag + payload.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& istaggedtype(c, fi.tnode)) {
|
||||
let fsz: i32 = slotsize(c, fi.tnode);
|
||||
cgwidentaggedstore(c, fi.tnode,
|
||||
n.rhs, "BP", lc.off + fi.foff, fsz);
|
||||
return;
|
||||
};
|
||||
// struct-typed field, struct-ident rhs:
|
||||
// word-copy direct, skipping cgexpr (no
|
||||
// register convention for a whole struct
|
||||
|
||||
@@ -188,8 +188,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, c.fnret, rhs, scroff,
|
||||
rsz);
|
||||
cgwidentaggedstore(c, c.fnret, rhs, "BP",
|
||||
scroff, rsz);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scroff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -311,7 +311,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// ABI call), struct payload (literal/ident), str payload,
|
||||
// scalar payload — with tag remap for tagged-subset widening.
|
||||
if (istaggedtype(c, tn)) {
|
||||
cgwidentaggedstore(c, tn, rhs, off, sz);
|
||||
cgwidentaggedstore(c, tn, rhs, "BP", off, sz);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -174,7 +174,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
emitline("(BP)\n");
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, ptype, arg, scroff, widensz);
|
||||
cgwidentaggedstore(c, ptype, arg, "BP", scroff, widensz);
|
||||
let pp: i32 = widensz - 8;
|
||||
for (pp >= 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
@@ -1368,6 +1368,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
fn fieldsize(c: *cgen, tnode: *node) i32 = {
|
||||
if (tnode == nil) { return 8; };
|
||||
let k: nkind = tnode.kind;
|
||||
if (k == nkind.N_TTAGGED){ return slotsize(c, tnode); };
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = tnode.str;
|
||||
if (streq(nm, "str")) { return 16; };
|
||||
@@ -1387,6 +1388,13 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
|
||||
};
|
||||
return 4; // default storage is i32
|
||||
};
|
||||
// Type alias to a tagged-union — recurse through aliaslookup
|
||||
// so `e: ev` (where `ev = (i64 | i32)`) takes 16B in the
|
||||
// containing struct rather than the 8B default.
|
||||
if (c != nil) {
|
||||
let aliased: *node = aliaslookup(c, nm);
|
||||
if (aliased != nil) { return fieldsize(c, aliased); };
|
||||
};
|
||||
return 8;
|
||||
};
|
||||
if (k == nkind.N_TPTR) { return 8; };
|
||||
@@ -2063,9 +2071,20 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// cgwidentaggedstore — write tagged-union slot bytes for `src` into the
|
||||
// slot at BP+slot_off, sized to slot_sz. Mirrors cg_widen_tagged_store
|
||||
// in cmd/w6c/cgen.c. Branches by source shape:
|
||||
// cgwidentaggedstore — write tagged-union slot bytes for `src` into
|
||||
// the slot at `basereg`+slot_off, sized to slot_sz. Mirrors
|
||||
// cg_widen_tagged_store in cmd/w6c/cgen.c.
|
||||
//
|
||||
// `basereg` selects the addressing root:
|
||||
// - "BP": function-frame slot (let / assign / return / structlit /
|
||||
// array-elem scratch). Body writes straight to slot_off(BP).
|
||||
// - else (e.g. "BX" for *struct field, top-level struct LEAQ
|
||||
// base): pointer-rooted dst. cgexpr inside trashes every GPR,
|
||||
// so we route through a fresh BP-rooted scratch slot, spill
|
||||
// basereg before the body, reload after, then word-copy
|
||||
// scratch → (basereg, slot_off).
|
||||
//
|
||||
// Branches by source shape:
|
||||
// - nullable dst (8B slot): cgexpr → AX → slot+0.
|
||||
// - tagged src ident: copy slot words, zero-pad, tag-remap.
|
||||
// - tagged src via AX/DX/CX ABI (call / tagged-arr index): cgexpr,
|
||||
@@ -2076,7 +2095,53 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
|
||||
// tag last.
|
||||
// - str src: tag@+0, ptr@+8, len@+16.
|
||||
// - scalar src: tag@+0, value@+8.
|
||||
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
|
||||
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node,
|
||||
basereg: str, slot_off: i32, slot_sz: i32) void = {
|
||||
if (streq(basereg, "BP")) {
|
||||
cgwidentaggedstorebp(c, dst, src, slot_off, slot_sz);
|
||||
return;
|
||||
};
|
||||
// Pointer-rooted dst: spill basereg (cgexpr will trash it),
|
||||
// materialise into a BP-rooted scratch via the BP path, then
|
||||
// reload basereg and word-copy scratch → caller's slot.
|
||||
let bspill: i32 = localadd(c, "@tagbase", 8, nil);
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(basereg);
|
||||
emitline(", ");
|
||||
emitoff(bspill: i64);
|
||||
emitline("(BP)\n");
|
||||
let scr: i32 = localadd(c, "@tagscr", slot_sz, nil);
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let z: i32 = 0;
|
||||
for (z < slot_sz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((scr + z): i64);
|
||||
emitline("(BP)\n");
|
||||
z += 8;
|
||||
};
|
||||
cgwidentaggedstorebp(c, dst, src, scr, slot_sz);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(bspill: i64);
|
||||
emitline("(BP), ");
|
||||
emitline(basereg);
|
||||
emitline("\n");
|
||||
let k: i32 = 0;
|
||||
for (k < slot_sz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scr + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((slot_off + k): i64, basereg);
|
||||
emitline("\n");
|
||||
k += 8;
|
||||
};
|
||||
};
|
||||
|
||||
// cgwidentaggedstorebp — BP-rooted body. Called via cgwidentaggedstore
|
||||
// for the natural "BP" case and via the wrapper's scratch path for
|
||||
// pointer-rooted dst. Direct callers exist only in case of future
|
||||
// inlined uses inside this file; new code should call the wrapper.
|
||||
fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
|
||||
let dt: *node = resolvetagged(c, dst);
|
||||
if (dt == nil) { return; };
|
||||
// Nullable fold: one 8B word holding the pointer (or 0 for void).
|
||||
|
||||
@@ -5744,7 +5744,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
emitline("(BP)\n");
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, ptype, arg, scroff, widensz);
|
||||
cgwidentaggedstore(c, ptype, arg, "BP", scroff, widensz);
|
||||
let pp: i32 = widensz - 8;
|
||||
for (pp >= 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
@@ -6938,6 +6938,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
fn fieldsize(c: *cgen, tnode: *node) i32 = {
|
||||
if (tnode == nil) { return 8; };
|
||||
let k: nkind = tnode.kind;
|
||||
if (k == nkind.N_TTAGGED){ return slotsize(c, tnode); };
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = tnode.str;
|
||||
if (streq(nm, "str")) { return 16; };
|
||||
@@ -6957,6 +6958,13 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
|
||||
};
|
||||
return 4; // default storage is i32
|
||||
};
|
||||
// Type alias to a tagged-union — recurse through aliaslookup
|
||||
// so `e: ev` (where `ev = (i64 | i32)`) takes 16B in the
|
||||
// containing struct rather than the 8B default.
|
||||
if (c != nil) {
|
||||
let aliased: *node = aliaslookup(c, nm);
|
||||
if (aliased != nil) { return fieldsize(c, aliased); };
|
||||
};
|
||||
return 8;
|
||||
};
|
||||
if (k == nkind.N_TPTR) { return 8; };
|
||||
@@ -7633,9 +7641,20 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// cgwidentaggedstore — write tagged-union slot bytes for `src` into the
|
||||
// slot at BP+slot_off, sized to slot_sz. Mirrors cg_widen_tagged_store
|
||||
// in cmd/w6c/cgen.c. Branches by source shape:
|
||||
// cgwidentaggedstore — write tagged-union slot bytes for `src` into
|
||||
// the slot at `basereg`+slot_off, sized to slot_sz. Mirrors
|
||||
// cg_widen_tagged_store in cmd/w6c/cgen.c.
|
||||
//
|
||||
// `basereg` selects the addressing root:
|
||||
// - "BP": function-frame slot (let / assign / return / structlit /
|
||||
// array-elem scratch). Body writes straight to slot_off(BP).
|
||||
// - else (e.g. "BX" for *struct field, top-level struct LEAQ
|
||||
// base): pointer-rooted dst. cgexpr inside trashes every GPR,
|
||||
// so we route through a fresh BP-rooted scratch slot, spill
|
||||
// basereg before the body, reload after, then word-copy
|
||||
// scratch → (basereg, slot_off).
|
||||
//
|
||||
// Branches by source shape:
|
||||
// - nullable dst (8B slot): cgexpr → AX → slot+0.
|
||||
// - tagged src ident: copy slot words, zero-pad, tag-remap.
|
||||
// - tagged src via AX/DX/CX ABI (call / tagged-arr index): cgexpr,
|
||||
@@ -7646,7 +7665,53 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = {
|
||||
// tag last.
|
||||
// - str src: tag@+0, ptr@+8, len@+16.
|
||||
// - scalar src: tag@+0, value@+8.
|
||||
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
|
||||
fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node,
|
||||
basereg: str, slot_off: i32, slot_sz: i32) void = {
|
||||
if (streq(basereg, "BP")) {
|
||||
cgwidentaggedstorebp(c, dst, src, slot_off, slot_sz);
|
||||
return;
|
||||
};
|
||||
// Pointer-rooted dst: spill basereg (cgexpr will trash it),
|
||||
// materialise into a BP-rooted scratch via the BP path, then
|
||||
// reload basereg and word-copy scratch → caller's slot.
|
||||
let bspill: i32 = localadd(c, "@tagbase", 8, nil);
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(basereg);
|
||||
emitline(", ");
|
||||
emitoff(bspill: i64);
|
||||
emitline("(BP)\n");
|
||||
let scr: i32 = localadd(c, "@tagscr", slot_sz, nil);
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let z: i32 = 0;
|
||||
for (z < slot_sz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((scr + z): i64);
|
||||
emitline("(BP)\n");
|
||||
z += 8;
|
||||
};
|
||||
cgwidentaggedstorebp(c, dst, src, scr, slot_sz);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(bspill: i64);
|
||||
emitline("(BP), ");
|
||||
emitline(basereg);
|
||||
emitline("\n");
|
||||
let k: i32 = 0;
|
||||
for (k < slot_sz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scr + k): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((slot_off + k): i64, basereg);
|
||||
emitline("\n");
|
||||
k += 8;
|
||||
};
|
||||
};
|
||||
|
||||
// cgwidentaggedstorebp — BP-rooted body. Called via cgwidentaggedstore
|
||||
// for the natural "BP" case and via the wrapper's scratch path for
|
||||
// pointer-rooted dst. Direct callers exist only in case of future
|
||||
// inlined uses inside this file; new code should call the wrapper.
|
||||
fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = {
|
||||
let dt: *node = resolvetagged(c, dst);
|
||||
if (dt == nil) { return; };
|
||||
// Nullable fold: one 8B word holding the pointer (or 0 for void).
|
||||
@@ -10793,7 +10858,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
let slot: i32 = doff + j * esz;
|
||||
if (velemtagged) {
|
||||
cgwidentaggedstore(c, varp.lhs,
|
||||
aa2, slot, esz);
|
||||
aa2, "BP", slot, esz);
|
||||
} else { if (velemstr) {
|
||||
cgexpr(c, aa2);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -11049,7 +11114,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (istaggedtype(c, lc.tnode)) {
|
||||
let lsz: i32 = slotsize(c, lc.tnode);
|
||||
cgwidentaggedstore(c, lc.tnode,
|
||||
n.rhs, lc.off, lsz);
|
||||
n.rhs, "BP", lc.off, lsz);
|
||||
return;
|
||||
};
|
||||
};
|
||||
@@ -11273,7 +11338,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, elemtn, n.rhs,
|
||||
scroff, slot_sz);
|
||||
"BP", scroff, slot_sz);
|
||||
cgexpr(c, idx);
|
||||
if (slot_sz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
@@ -11619,6 +11684,20 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fld)) {
|
||||
// Tagged-union field via *struct base — full slot
|
||||
// rewrite via cgwidentaggedstore basereg="BX". Pre-#26
|
||||
// fell through to the scalar store and dropped tag
|
||||
// + payload.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& istaggedtype(c, fi.tnode)) {
|
||||
let fsz: i32 = slotsize(c, fi.tnode);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
cgwidentaggedstore(c, fi.tnode,
|
||||
n.rhs, "BX", fi.foff, fsz);
|
||||
return;
|
||||
};
|
||||
// struct-typed field, struct-ident rhs through
|
||||
// *struct base: cgexpr can't materialise a whole
|
||||
// struct value, so word-copy from the rhs slot
|
||||
@@ -11769,6 +11848,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fld)) {
|
||||
// Tagged-union field in a direct struct local —
|
||||
// full slot rewrite at (lc.off + fi.foff)(BP)
|
||||
// via cgwidentaggedstore basereg="BP". Pre-#26
|
||||
// fell through and dropped tag + payload.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& istaggedtype(c, fi.tnode)) {
|
||||
let fsz: i32 = slotsize(c, fi.tnode);
|
||||
cgwidentaggedstore(c, fi.tnode,
|
||||
n.rhs, "BP", lc.off + fi.foff, fsz);
|
||||
return;
|
||||
};
|
||||
// struct-typed field, struct-ident rhs:
|
||||
// word-copy direct, skipping cgexpr (no
|
||||
// register convention for a whole struct
|
||||
@@ -12978,8 +13068,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, c.fnret, rhs, scroff,
|
||||
rsz);
|
||||
cgwidentaggedstore(c, c.fnret, rhs, "BP",
|
||||
scroff, rsz);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scroff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -13101,7 +13191,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// ABI call), struct payload (literal/ident), str payload,
|
||||
// scalar payload — with tag remap for tagged-subset widening.
|
||||
if (istaggedtype(c, tn)) {
|
||||
cgwidentaggedstore(c, tn, rhs, off, sz);
|
||||
cgwidentaggedstore(c, tn, rhs, "BP", off, sz);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
@@ -14118,6 +14208,61 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-union struct-field write: `s.f = v` or `(*p).f = v`
|
||||
// where f is a tagged-union field. cgassign delegates to
|
||||
// cgwidentaggedstore; for pointer-rooted dst the wrapper
|
||||
// allocates @tagbase (8B) and @tagscr (slot_sz). Both names
|
||||
// dedup with other tagged scratch users in the same function.
|
||||
if (n.kind == nkind.N_ASSIGN) {
|
||||
let alhs: *node = n.lhs;
|
||||
if (alhs != nil) {
|
||||
if (alhs.kind == nkind.N_DOT) {
|
||||
let abase: *node = alhs.lhs;
|
||||
if (abase != nil) {
|
||||
if (abase.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, abase.str);
|
||||
let btn: *node = nil;
|
||||
if (lc != nil) { btn = lc.tnode; }
|
||||
else { btn = letvartnode(c, abase.str); };
|
||||
let isptr: bool = false;
|
||||
let stype: *node = nil;
|
||||
if (btn != nil) {
|
||||
if (btn.kind == nkind.N_TPTR) {
|
||||
isptr = true;
|
||||
stype = btn.lhs;
|
||||
};
|
||||
if (btn.kind == nkind.N_TNAME) { stype = btn; };
|
||||
};
|
||||
if (stype != nil) {
|
||||
if (stype.kind == nkind.N_TNAME) {
|
||||
let si: *structinfo = structlookup(c, stype.str);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, alhs.str)) {
|
||||
if (istaggedtype(c, fi.tnode)) {
|
||||
if (isptr) {
|
||||
if (!scanseenmark(c, "@tagbase")) {
|
||||
total += 8;
|
||||
};
|
||||
if (!scanseenmark(c, "@tagscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
fi = nil;
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-union return with struct payload or tagged-subset
|
||||
// source — cgreturn materialises in @tagscr then loads
|
||||
// AX/DX/CX. Detect via the same rhsstructpayload predicate
|
||||
|
||||
@@ -423,6 +423,72 @@ static const struct row rows[] = {
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Whole-tagged rhs to struct tagged-union field (task #26). cgen
|
||||
* N_ASSIGN had a TY_TAGGED branch that called cg_tag_for_variant
|
||||
* with the rhs type — when the rhs was the tagged union itself
|
||||
* (not a concrete variant), the function returned -1 and the
|
||||
* branch wrote literal 0 as the tag plus only AX as the payload,
|
||||
* dropping the original tag and trailing payload words. Fixed
|
||||
* by routing through cg_widen_tagged_store, which copies all slot
|
||||
* words from the source's tagged slot and runs cg_widen_tag_remap.
|
||||
*
|
||||
* Verification: the write must not bleed into the trailing `mark`
|
||||
* field. Direct struct local. Reading x.e back via match is a
|
||||
* separate code path (gated on a wwstage read-side bug for
|
||||
* tagged N_DOT — filed below as a follow-up); these rows pin the
|
||||
* write-side bug and let mark act as a canary for stray stores
|
||||
* past the field boundary. */
|
||||
{ "tagged_field_value_write_local_mark_canary",
|
||||
"type ev = (i64 | i32);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: ev = 33: i32;\n"
|
||||
" let x: holder;\n"
|
||||
" x.mark = 0x3a3a3a3a;\n"
|
||||
" x.e = v;\n"
|
||||
" if (x.mark != 0x3a3a3a3a) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Same shape, concrete-variant str widening. The str payload
|
||||
* occupies +8/+16 (ptr/len); a missing store to +16 (the pre-#26
|
||||
* shape that wrote only AX) would leave .len at whatever the
|
||||
* neighbouring memory held. mark canary at +24 catches a stray
|
||||
* AX-only path that wrote past 16B into the mark slot. */
|
||||
{ "tagged_field_value_write_str_variant_mark_canary",
|
||||
"type ev = (i32 | str);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: holder;\n"
|
||||
" x.mark = 0x5e5e5e5e;\n"
|
||||
" x.e = (\"hello\": ev);\n"
|
||||
" if (x.mark != 0x5e5e5e5e) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Pointer-rooted dst — `(*p).e = v` exercises the base-reg
|
||||
* parameterisation added to cg_widen_tagged_store in #26. The
|
||||
* wrapper spills BX (the *struct pointer), routes the body
|
||||
* through a BP-rooted scratch, reloads BX, and word-copies the
|
||||
* scratch to (BX, foff). Without that path the via_ptr arm of
|
||||
* the new TY_TAGGED branch would either trample BX during cgexpr
|
||||
* or write to a stale slot address. mark canary at +24 catches
|
||||
* a stray spill that wrote past the e field. */
|
||||
{ "tagged_field_value_write_via_ptr_mark_canary",
|
||||
"type ev = (i64 | i32);\n"
|
||||
"type holder = struct { e: ev, mark: i32 };\n"
|
||||
"fn fill(h: *holder) void = {\n"
|
||||
" let v: ev = 33: i32;\n"
|
||||
" h.mark = 0x77777777;\n"
|
||||
" h.e = v;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let x: holder;\n"
|
||||
" fill(&x);\n"
|
||||
" if (x.mark != 0x77777777) { return 1; };\n"
|
||||
" return 42;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
static int
|
||||
|
||||
Reference in New Issue
Block a user