w6c+selfhost: cgdot N_DOT tagged-field source ABI (closes #28)
cgdot of a tagged-union struct field previously dropped the AX/DX/CX/R8 payload-register convention used by tagged-union returns: cstage's direct-struct branch stopped at CX (size > 16) and never loaded R8 (slice-payload variants, slot 32B); the via_ptr branch had no TY_TAGGED handler at all, falling through to fldloadop and yielding only the tag in AX. The N_DOT scrutinee fallback in N_MATCH similarly stored only AX into the spill slot. Wwstage cgdot had no TY_TAGGED branch in any of the direct, *struct, or top-level-global field-load paths, and cgmatch's non-ident scrutinee branch didn't recognise N_DOT — dispatch always computed want = 0 and the spill scratch was hardcoded 24B. The combined effect: any code reading `s.taggedfield` and consuming more than one quadword of the payload saw garbage in the upper halves. Cstage: extended the direct-struct TY_TAGGED branch with an R8 load for size > 24 (CX still loaded last so global LEAQ-into-CX rooting survives), added a parallel TY_TAGGED handler to the via_ptr (TY_PTR inner TY_STRUCT) field branch, and extended the N_DOT scrutinee spill fallback in N_MATCH to write DX/CX/R8 alongside AX. Wwstage: new cgloadtaggedfield helper emits the four-register load with CX-last ordering, and dotfieldtnode resolves a field's declared type node for a local-ident or *struct base. cgdot grew three TY_TAGGED branches (direct local, *struct deref staging in BX, top-level global through CX). cgmatch's non-ident-scrutinee branch grew an N_DOT type- extraction path mirroring the N_CALL / N_INDEX shapes and now sizes the @match_spill slot from slotsize(scrutt) so slice-payload variants don't overflow the historical 24B alloc. rhstaggedabicall accepts N_DOT so `let copy: ev = h.e;` and tagged-arg call sites pass through the tagged-source spill branch of cgwidentaggedstore. Out of scope for #28 and left as separate latents: wwstage's match-arm bind for a TY_STRUCT-typed variant copies only 8B (cstage falls back to bu->size; wwstage's bsz=8 default), and the variant-index lookup for an i64 literal in (i32 | i64) picks the wrong tag on the write side. Both surface in struct-payload tagged unions and merit their own tasks; the new test rows steer clear so #28's fix verifies end-to-end on scalar / str / slice payloads. Test 693_dot_tagged_source — three variant shapes (16B i64, 24B str, 32B slice) read from direct local, *struct param, top-level global, and let-init round-trip. The 32B-slice rows verify v.cap (R8 / +24) so dropping the upper-word load isn't masked by len-only checks; the top-level-global row routes the write through *p because the direct global-LHS tagged store is a separate wwstage gap (followup). Three negative controls (untagged i32 / str / slice fields) keep the new TY_TAGGED guard from shadowing the existing field-load paths. Wired into make test; 37 tests total. Bootstrap ww2 == ww3 == ww4 byte-identical.
This commit is contained in:
@@ -3875,12 +3875,27 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int boff = localfind(locals, s->lhs->str);
|
||||
sl_off = boff + (int)f->offset;
|
||||
} else {
|
||||
/* fall back to spill */
|
||||
/* fall back to spill — `match (h.e)` where
|
||||
* h is *struct. cgexpr → cgdot now leaves the
|
||||
* AX=tag, DX=val0, CX=val1[, R8=val2] shape
|
||||
* (task #28), so spill all words the variant
|
||||
* may carry. Pre-#28 only AX landed and the
|
||||
* dispatch fired on a stale slot. */
|
||||
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));
|
||||
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));
|
||||
if (slot_size > 24)
|
||||
ins2(c, A_MOVQ, areg(D_R8),
|
||||
amem(D_BP, sl_off + 24));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Spill non-ident scrutinees (e.g. `match (foo()?)`) into
|
||||
@@ -4648,10 +4663,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
for (Tfield *f = u->fields; f; f = f->next) {
|
||||
if (strcmp(f->name, n->str) != 0) continue;
|
||||
/* tagged-union field: load AX=tag, DX=val0,
|
||||
* CX=val1 (last, since for globals CX is also
|
||||
* the base addr). Mirrors the tagged-return
|
||||
* ABI so the let-init / match dispatch shapes
|
||||
* just work. */
|
||||
* CX=val1, R8=val2 (CX last, since for globals
|
||||
* CX is also the base addr; load R8 before CX
|
||||
* so the base address survives the +24 read).
|
||||
* Mirrors the tagged-return ABI so the let-init
|
||||
* / match dispatch shapes just work. The val2
|
||||
* word fires for slice-variant tagged-unions
|
||||
* (slot = 8 tag + 24 slice header = 32B). */
|
||||
Type *tag_fu = (f->type && f->type->kind == TY_NAMED)
|
||||
? f->type->under : f->type;
|
||||
if (tag_fu && tag_fu->kind == TY_TAGGED) {
|
||||
@@ -4660,6 +4678,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
amem(base_reg, fo + 0), areg(D_AX));
|
||||
ins2(c, A_MOVQ,
|
||||
amem(base_reg, fo + 8), areg(D_DX));
|
||||
if (tag_fu->size > 24)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(base_reg, fo + 24),
|
||||
areg(D_R8));
|
||||
if (tag_fu->size > 16)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(base_reg, fo + 16),
|
||||
@@ -4744,6 +4766,36 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
||||
for (Tfield *f = inner->fields; f; f = f->next) {
|
||||
if (strcmp(f->name, n->str) != 0) continue;
|
||||
/* tagged-union field through *struct: BX
|
||||
* already holds the *struct pointer. Load
|
||||
* the four payload regs from (BX, f->offset)
|
||||
* — BX is not a target (AX/DX/CX/R8), so
|
||||
* load order is harmless. Mirrors the direct-
|
||||
* struct branch above so consumers see the
|
||||
* same tagged-return register shape
|
||||
* regardless of pointer rooting. Pre-#28 fell
|
||||
* through to fldloadop and dropped the
|
||||
* payload words. */
|
||||
Type *ptag_fu = (f->type && f->type->kind == TY_NAMED)
|
||||
? f->type->under : f->type;
|
||||
if (ptag_fu && ptag_fu->kind == TY_TAGGED) {
|
||||
int fo = (int)f->offset;
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BX, fo + 0),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BX, fo + 8),
|
||||
areg(D_DX));
|
||||
if (ptag_fu->size > 16)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BX, fo + 16),
|
||||
areg(D_CX));
|
||||
if (ptag_fu->size > 24)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BX, fo + 24),
|
||||
areg(D_R8));
|
||||
break;
|
||||
}
|
||||
/* str field through *struct: read len into a
|
||||
* scratch first (it's at +8) so loading ptr
|
||||
* into AX last leaves (AX=ptr, BX=len). We
|
||||
|
||||
Reference in New Issue
Block a user