cstage+selfhost+test: cgen N_ASSIGN whole-STRUCT (call+structlit, 5 sites)
Receive side of #4's cgreturn ABI (aee8149) for TY_STRUCT lvalues of size <=24B. Producer materialises rhs into AX=bytes[0..7], DX=[8..15], CX=[16..23], zero-padded to 24B; receive sites here read the regs and write only `declared sz` bytes — MOVQ for full 8B chunks plus a sized tail (MOVL/MOVW/MOVB) by the *declared* struct size. ASYMMETRY: do NOT mirror the sender's three uniform MOVQs, else trailing 1..7B chunks overrun the next local slot. Tail chunks in {3,5,6,7} are unreachable under WW struct align rules (size%align==0) and fall through. Five sites wired in each stage (cstage cgen.c, wwstage cgenexpr.ww + cgenstmt.ww), call-result + structlit rhs at each: - N_LET `let s: T = bar()` / `= T{...}` cgenstmt cglet - N_ASSIGN N_IDENT-lhs `s = bar()` / `= T{...}` cgenexpr cgassign - N_ASSIGN single-DOT local-base `o.f = ...` - N_ASSIGN single-DOT ptr-base auto-deref `p.f = ...` - N_ASSIGN single-DOT global-base `g.f = ...` - N_ASSIGN chained-DOT depth>=2 `o.m.in = ...` (The four dot-flavors share one shape pattern, hence "5 sites".) Where the dst addr needs scratch (ptr-base/global-base/via_cx), it is loaded into BX after the call so CX stays as the third value word; for structlit field-walks BX is reloaded before each store since cgexpr clobbers AX/BX between fields. wwstage needed a new `structnaturalsize(si)` helper (cgenutil.ww): si.totsize is mis-named — it's slot-padded to 8 by registerstruct for stack-slot use, while the receive ABI wants the type's natural size (max(foff+fsz)). Splitting si.totsize into naturalsize + slotsize is tracked as the wwstage struct sizing follow-up (task #15); until that lands, the helper recovers the natural size at receive sites. Test 701_cgassign_struct.c (18 rows, 3 checks each — cstage value, wwstage value, asm byte-identity), wired in Makefile after 698. The headline ASYMMETRY case is the 20B `{i32×5}` row: sender pads to 24B via three MOVQs, receiver writes MOVQ AX +0, MOVQ DX +8, MOVL CX +16. A regression to a MOVQ tail there overruns 4B past the slot and flips the exit-code check. smoke.combined.ww is the auto-regen ride-along of strings.freeall landing in714d089(worker-shlex). Pre-existing gaps surfaced and tracked separately (not fixed here, out of scope): - task #16: silent drop of `(*p).f = ...` explicit-deref dot lhs. - task #17: silent zero of nested STRUCTLIT field in N_LET / N_ASSIGN initializer — the field_chain and field_global test rows use explicit field writes (`o.m.t = 10i64;`) rather than nested literals as a fixture-level workaround. - task #9: module-name-mangle for fn labels avoided in the field_global_call fixture by `let g: outer;` (no init). make test: 59/59. 994_w6c_ww + 995_self_rebuild PASS — bootstrap byte-identity is the load-bearing proof for this commit's scope.
This commit is contained in:
538
cmd/w6c/cgen.c
538
cmd/w6c/cgen.c
@@ -2005,12 +2005,203 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* struct-typed field, struct-ident rhs: cgexpr cannot
|
||||
* materialise a whole struct value in registers, so
|
||||
* word-copy from the rhs slot directly to the dest
|
||||
* field. Other rhs shapes (struct-returning call,
|
||||
* struct literal) stay broken at the cgexpr level —
|
||||
* tracked as #27. */
|
||||
/* struct-typed field, three rhs shapes:
|
||||
* - N_IDENT: word-copy from the rhs slot directly
|
||||
* onto the destination field. cgexpr cannot
|
||||
* materialise a whole struct value in registers
|
||||
* for an arbitrary local, so we read field words
|
||||
* straight from the source slot.
|
||||
* - N_CALL (added with #5): cgexpr leaves the value
|
||||
* in AX/DX/CX per #4's cgreturn ABI; sized stores
|
||||
* write only the declared field size — MOVQ for
|
||||
* full 8B chunks plus MOVL/MOVW/MOVB tail. See
|
||||
* the N_LET receive site for the ASYMMETRY
|
||||
* rationale. cgreturn touches only AX/DX/CX, so
|
||||
* BX stays free for the dst-addr load after the
|
||||
* call.
|
||||
* - N_STRUCTLIT (added with #5): field-by-field
|
||||
* store; for via_ptr/is_global the dst base addr
|
||||
* is reloaded into BX before each store so cgexpr
|
||||
* can clobber AX/BX between fields. */
|
||||
if (n->op == TK_ASSIGN && str_fu
|
||||
&& str_fu->kind == TY_STRUCT
|
||||
&& (int)str_fu->size <= 24
|
||||
&& n->rhs && n->rhs->kind == N_CALL
|
||||
&& (str_fu->size % 8 == 0
|
||||
|| str_fu->size % 8 == 1
|
||||
|| str_fu->size % 8 == 2
|
||||
|| str_fu->size % 8 == 4)) {
|
||||
int ssz = (int)str_fu->size;
|
||||
cgexpr(c, n->rhs, locals);
|
||||
int regs[3] = { D_AX, D_DX, D_CX };
|
||||
int full = ssz / 8;
|
||||
int tail = ssz % 8;
|
||||
int base_reg, base_disp;
|
||||
if (via_ptr || is_global) {
|
||||
if (via_ptr)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, boff),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
base_reg = D_BX;
|
||||
base_disp = foff;
|
||||
} else {
|
||||
base_reg = D_BP;
|
||||
base_disp = boff + foff;
|
||||
}
|
||||
for (int i = 0; i < full; i++)
|
||||
ins2(c, A_MOVQ, areg(regs[i]),
|
||||
amem(base_reg,
|
||||
base_disp + i * 8));
|
||||
if (tail > 0) {
|
||||
int op = (tail == 4) ? A_MOVL
|
||||
: (tail == 2) ? A_MOVW
|
||||
: A_MOVB;
|
||||
ins2(c, op, areg(regs[full]),
|
||||
amem(base_reg,
|
||||
base_disp + full * 8));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (n->op == TK_ASSIGN && str_fu
|
||||
&& str_fu->kind == TY_STRUCT
|
||||
&& n->rhs && n->rhs->kind == N_STRUCTLIT) {
|
||||
int ssz = (int)str_fu->size;
|
||||
/* TK_ELLIPSIS autofill: zero-fill the field
|
||||
* region first so unmentioned inner fields
|
||||
* read as 0 (mirrors N_LET / N_IDENT-lhs
|
||||
* structlit branches). */
|
||||
if (n->rhs->op == TK_ELLIPSIS) {
|
||||
ins2(c, A_XORQ, areg(D_AX),
|
||||
areg(D_AX));
|
||||
int zbase_reg, zbase_disp;
|
||||
if (via_ptr || is_global) {
|
||||
if (via_ptr)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, boff),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
zbase_reg = D_BX;
|
||||
zbase_disp = foff;
|
||||
} else {
|
||||
zbase_reg = D_BP;
|
||||
zbase_disp = boff + foff;
|
||||
}
|
||||
int zi = 0;
|
||||
while (zi + 8 <= ssz) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(zbase_reg,
|
||||
zbase_disp + zi));
|
||||
zi += 8;
|
||||
}
|
||||
while (zi + 4 <= ssz) {
|
||||
ins2(c, A_MOVL, areg(D_AX),
|
||||
amem(zbase_reg,
|
||||
zbase_disp + zi));
|
||||
zi += 4;
|
||||
}
|
||||
while (zi < ssz) {
|
||||
ins2(c, A_MOVB, areg(D_AX),
|
||||
amem(zbase_reg,
|
||||
zbase_disp + zi));
|
||||
zi += 1;
|
||||
}
|
||||
}
|
||||
for (Node *fn = n->rhs->list; fn;
|
||||
fn = fn->next) {
|
||||
u64 inner_foff = 0;
|
||||
int fsz = 8;
|
||||
Type *ft = NULL;
|
||||
for (Tfield *fl = str_fu->fields;
|
||||
fl; fl = fl->next) {
|
||||
if (strcmp(fl->name, fn->str) == 0) {
|
||||
inner_foff = fl->offset;
|
||||
fsz = (int)(fl->type
|
||||
? fl->type->size : 8);
|
||||
ft = fl->type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Type *fu = (ft && ft->kind == TY_NAMED)
|
||||
? ft->under : ft;
|
||||
if (fu && fu->kind == TY_TAGGED) {
|
||||
if (via_ptr || is_global) {
|
||||
if (via_ptr)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, boff),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
cg_widen_tagged_store(c,
|
||||
&locals, fu, fn->lhs,
|
||||
D_BX,
|
||||
foff + (int)inner_foff,
|
||||
(int)fu->size);
|
||||
} else {
|
||||
cg_widen_tagged_store(c,
|
||||
&locals, fu, fn->lhs,
|
||||
D_BP,
|
||||
boff + foff + (int)inner_foff,
|
||||
(int)fu->size);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, fn->lhs, locals);
|
||||
int sl_isf32 = 0;
|
||||
if (fld_isfloat(ft, &sl_isf32)) {
|
||||
int mov = sl_isf32
|
||||
? A_MOVSS : A_MOVSD;
|
||||
if (via_ptr || is_global) {
|
||||
if (via_ptr)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, boff),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_BX,
|
||||
foff + (int)inner_foff));
|
||||
} else {
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_BP,
|
||||
boff + foff + (int)inner_foff));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
int op = A_MOVQ;
|
||||
if (fsz == 1) op = A_MOVB;
|
||||
else if (fsz == 4) op = A_MOVL;
|
||||
if (via_ptr || is_global) {
|
||||
if (via_ptr)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, boff),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, base->str),
|
||||
areg(D_BX));
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BX,
|
||||
foff + (int)inner_foff));
|
||||
} else {
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BP,
|
||||
boff + foff + (int)inner_foff));
|
||||
}
|
||||
}
|
||||
(void)ssz;
|
||||
break;
|
||||
}
|
||||
if (n->op == TK_ASSIGN && str_fu
|
||||
&& str_fu->kind == TY_STRUCT
|
||||
&& n->rhs && n->rhs->kind == N_IDENT
|
||||
@@ -2621,9 +2812,192 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* TY_STRUCT terminal: word-copy the rhs slot onto
|
||||
* base+total_off. Only N_IDENT rhs is wired —
|
||||
* other shapes route through #27. */
|
||||
/* TY_STRUCT terminal in the chained-DOT walker:
|
||||
* three rhs shapes — mirror of the single-dot
|
||||
* branch.
|
||||
* - N_IDENT: word-copy from rhs local slot.
|
||||
* - N_CALL (added with #5): cgexpr → AX/DX/CX
|
||||
* per #4's cgreturn ABI; sized stores per
|
||||
* declared field size. cgreturn touches only
|
||||
* AX/DX/CX so via_cx loads the dst addr into
|
||||
* BX (not CX) after the call to keep CX as
|
||||
* the third value word.
|
||||
* - N_STRUCTLIT (added with #5): field-by-field
|
||||
* store; via_cx reloads BX before each store
|
||||
* so cgexpr can clobber AX/BX between fields.
|
||||
*/
|
||||
if (fu && fu->kind == TY_STRUCT
|
||||
&& fsz <= 24
|
||||
&& n->rhs && n->rhs->kind == N_CALL
|
||||
&& (fsz % 8 == 0 || fsz % 8 == 1
|
||||
|| fsz % 8 == 2 || fsz % 8 == 4)) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
int regs[3] = { D_AX, D_DX, D_CX };
|
||||
int full = fsz / 8;
|
||||
int tail = fsz % 8;
|
||||
int base_reg, base_off;
|
||||
if (via_cx) {
|
||||
if (ptr_root)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, base_disp),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, cur->str),
|
||||
areg(D_BX));
|
||||
base_reg = D_BX;
|
||||
base_off = total_off;
|
||||
} else {
|
||||
base_reg = D_BP;
|
||||
base_off = base_disp + total_off;
|
||||
}
|
||||
for (int i = 0; i < full; i++)
|
||||
ins2(c, A_MOVQ, areg(regs[i]),
|
||||
amem(base_reg,
|
||||
base_off + i * 8));
|
||||
if (tail > 0) {
|
||||
int op = (tail == 4) ? A_MOVL
|
||||
: (tail == 2) ? A_MOVW
|
||||
: A_MOVB;
|
||||
ins2(c, op, areg(regs[full]),
|
||||
amem(base_reg,
|
||||
base_off + full * 8));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (fu && fu->kind == TY_STRUCT
|
||||
&& n->rhs && n->rhs->kind == N_STRUCTLIT) {
|
||||
int ssz = fsz;
|
||||
if (n->rhs->op == TK_ELLIPSIS) {
|
||||
ins2(c, A_XORQ, areg(D_AX),
|
||||
areg(D_AX));
|
||||
int zbase_reg, zbase_off;
|
||||
if (via_cx) {
|
||||
if (ptr_root)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, base_disp),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, cur->str),
|
||||
areg(D_BX));
|
||||
zbase_reg = D_BX;
|
||||
zbase_off = total_off;
|
||||
} else {
|
||||
zbase_reg = D_BP;
|
||||
zbase_off = base_disp + total_off;
|
||||
}
|
||||
int zi = 0;
|
||||
while (zi + 8 <= ssz) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(zbase_reg,
|
||||
zbase_off + zi));
|
||||
zi += 8;
|
||||
}
|
||||
while (zi + 4 <= ssz) {
|
||||
ins2(c, A_MOVL, areg(D_AX),
|
||||
amem(zbase_reg,
|
||||
zbase_off + zi));
|
||||
zi += 4;
|
||||
}
|
||||
while (zi < ssz) {
|
||||
ins2(c, A_MOVB, areg(D_AX),
|
||||
amem(zbase_reg,
|
||||
zbase_off + zi));
|
||||
zi += 1;
|
||||
}
|
||||
}
|
||||
for (Node *fn = n->rhs->list; fn;
|
||||
fn = fn->next) {
|
||||
u64 inner_foff = 0;
|
||||
int ifsz = 8;
|
||||
Type *ift = NULL;
|
||||
for (Tfield *fl = fu->fields; fl;
|
||||
fl = fl->next) {
|
||||
if (strcmp(fl->name,
|
||||
fn->str) == 0) {
|
||||
inner_foff = fl->offset;
|
||||
ifsz = (int)(fl->type
|
||||
? fl->type->size : 8);
|
||||
ift = fl->type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Type *ifu = (ift
|
||||
&& ift->kind == TY_NAMED)
|
||||
? ift->under : ift;
|
||||
if (ifu && ifu->kind == TY_TAGGED) {
|
||||
if (via_cx) {
|
||||
if (ptr_root)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, base_disp),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, cur->str),
|
||||
areg(D_BX));
|
||||
cg_widen_tagged_store(c,
|
||||
&locals, ifu, fn->lhs,
|
||||
D_BX,
|
||||
total_off + (int)inner_foff,
|
||||
(int)ifu->size);
|
||||
} else {
|
||||
cg_widen_tagged_store(c,
|
||||
&locals, ifu, fn->lhs,
|
||||
D_BP,
|
||||
base_disp + total_off + (int)inner_foff,
|
||||
(int)ifu->size);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, fn->lhs, locals);
|
||||
int sf32 = 0;
|
||||
if (fld_isfloat(ift, &sf32)) {
|
||||
int mov = sf32
|
||||
? A_MOVSS : A_MOVSD;
|
||||
if (via_cx) {
|
||||
if (ptr_root)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, base_disp),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, cur->str),
|
||||
areg(D_BX));
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_BX,
|
||||
total_off + (int)inner_foff));
|
||||
} else {
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_BP,
|
||||
base_disp + total_off + (int)inner_foff));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
int op = A_MOVQ;
|
||||
if (ifsz == 1) op = A_MOVB;
|
||||
else if (ifsz == 4) op = A_MOVL;
|
||||
if (via_cx) {
|
||||
if (ptr_root)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, base_disp),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, cur->str),
|
||||
areg(D_BX));
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BX,
|
||||
total_off + (int)inner_foff));
|
||||
} else {
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BP,
|
||||
base_disp + total_off + (int)inner_foff));
|
||||
}
|
||||
}
|
||||
(void)ssz;
|
||||
break;
|
||||
}
|
||||
if (fu && fu->kind == TY_STRUCT
|
||||
&& n->rhs && n->rhs->kind == N_IDENT
|
||||
&& localfind(locals, n->rhs->str) != 0) {
|
||||
@@ -3048,6 +3422,115 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* Struct local reassignment: `s = expr;` where s is
|
||||
* a TY_STRUCT local of size <=24B. Two rhs shapes,
|
||||
* mirroring cglet's N_STRUCTLIT and the call-result
|
||||
* branch above:
|
||||
* - N_STRUCTLIT: walk fields, store at off+foff
|
||||
* directly (same shape as the let-init branch).
|
||||
* - N_CALL: cgexpr → AX/DX/CX, sized stores per the
|
||||
* same ASYMMETRY rules documented at the N_LET
|
||||
* receive site (MOVQ for full 8B chunks plus
|
||||
* MOVL/MOVW/MOVB tail). The struct-IDENT word-copy
|
||||
* rhs shape (s = p) is left unwired; #5 is scoped to
|
||||
* the receive side of #4's cgreturn (calls + literals).
|
||||
* Sizes >24B and non-{0,1,2,4}-byte tails fall through
|
||||
* to the existing scalar path. */
|
||||
if (lu && lu->kind == TY_STRUCT
|
||||
&& (int)lu->size <= 24) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off != 0) {
|
||||
int sz = (int)lu->size;
|
||||
if (n->rhs && n->rhs->kind == N_STRUCTLIT) {
|
||||
/* TK_ELLIPSIS autofill: zero-fill the
|
||||
* slot first so unmentioned fields read
|
||||
* as 0 (mirrors cglet's structlit). */
|
||||
if (n->rhs->op == TK_ELLIPSIS) {
|
||||
ins2(c, A_XORQ, areg(D_AX),
|
||||
areg(D_AX));
|
||||
int zi = 0;
|
||||
while (zi + 8 <= sz) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + zi));
|
||||
zi += 8;
|
||||
}
|
||||
while (zi + 4 <= sz) {
|
||||
ins2(c, A_MOVL, areg(D_AX),
|
||||
amem(D_BP, off + zi));
|
||||
zi += 4;
|
||||
}
|
||||
while (zi < sz) {
|
||||
ins2(c, A_MOVB, areg(D_AX),
|
||||
amem(D_BP, off + zi));
|
||||
zi += 1;
|
||||
}
|
||||
}
|
||||
for (Node *f = n->rhs->list; f;
|
||||
f = f->next) {
|
||||
u64 foff = 0;
|
||||
int fsz = 8;
|
||||
Type *ft = NULL;
|
||||
for (Tfield *fl = lu->fields; fl;
|
||||
fl = fl->next) {
|
||||
if (strcmp(fl->name, f->str) == 0) {
|
||||
foff = fl->offset;
|
||||
fsz = (int)(fl->type
|
||||
? fl->type->size : 8);
|
||||
ft = fl->type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Type *fu = (ft
|
||||
&& ft->kind == TY_NAMED)
|
||||
? ft->under : ft;
|
||||
if (fu && fu->kind == TY_TAGGED) {
|
||||
cg_widen_tagged_store(c,
|
||||
&locals, fu, f->lhs,
|
||||
D_BP, off + (int)foff,
|
||||
(int)fu->size);
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, f->lhs, locals);
|
||||
int sl_isf32 = 0;
|
||||
if (fld_isfloat(ft, &sl_isf32)) {
|
||||
int mov = sl_isf32
|
||||
? A_MOVSS : A_MOVSD;
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_BP,
|
||||
off + (int)foff));
|
||||
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,
|
||||
off + (int)foff));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (n->rhs && n->rhs->kind == N_CALL
|
||||
&& (sz % 8 == 0 || sz % 8 == 1
|
||||
|| sz % 8 == 2
|
||||
|| sz % 8 == 4)) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
int regs[3] = { D_AX, D_DX, D_CX };
|
||||
int full = sz / 8;
|
||||
int tail = sz % 8;
|
||||
for (int i = 0; i < full; i++)
|
||||
ins2(c, A_MOVQ, areg(regs[i]),
|
||||
amem(D_BP, off + i * 8));
|
||||
if (tail > 0) {
|
||||
int op = (tail == 4) ? A_MOVL
|
||||
: (tail == 2) ? A_MOVW
|
||||
: A_MOVB;
|
||||
ins2(c, op, areg(regs[full]),
|
||||
amem(D_BP, off + full * 8));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (n->lhs->kind == N_IDENT) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
@@ -5440,6 +5923,43 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* Whole-struct receive for sizes <=24B (call-result rhs).
|
||||
* Counterpart of #4's cgreturn ABI: cgexpr leaves
|
||||
* AX=bytes[0..7], DX=bytes[8..15], CX=bytes[16..23], zero-
|
||||
* padded to 24B by the producer.
|
||||
*
|
||||
* ASYMMETRY (do NOT mirror the sender): producer emits three
|
||||
* uniform MOVQs into a zero-padded 24B scratch slot; the
|
||||
* receiver must write only `sz` bytes — MOVQ for full 8B
|
||||
* chunks plus a sized tail (MOVL/MOVW/MOVB) by the *declared*
|
||||
* struct size. Otherwise a trailing 1..7-byte chunk would
|
||||
* overrun into the next local slot.
|
||||
*
|
||||
* Tail chunks in {3,5,6,7} (would need shift-and-store from
|
||||
* the register) are unreachable under WW struct alignment
|
||||
* rules (field aligns force size%align==0); the guard
|
||||
* excludes them so they fall through to the existing scalar
|
||||
* path rather than emit a stomping MOVQ tail. Sizes >24B also
|
||||
* fall through (sret deferred, same constraint as #4). */
|
||||
if (n->rhs && n->rhs->kind == N_CALL && lu
|
||||
&& lu->kind == TY_STRUCT && sz <= 24
|
||||
&& (sz % 8 == 0 || sz % 8 == 1
|
||||
|| sz % 8 == 2 || sz % 8 == 4)) {
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
int regs[3] = { D_AX, D_DX, D_CX };
|
||||
int full = sz / 8;
|
||||
int tail = sz % 8;
|
||||
for (int i = 0; i < full; i++)
|
||||
ins2(c, A_MOVQ, areg(regs[i]),
|
||||
amem(D_BP, off + i * 8));
|
||||
if (tail > 0) {
|
||||
int op = (tail == 4) ? A_MOVL
|
||||
: (tail == 2) ? A_MOVW : A_MOVB;
|
||||
ins2(c, op, areg(regs[full]),
|
||||
amem(D_BP, off + full * 8));
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* array literal initialiser: `let xs: [N]T = [a, b, c];`.
|
||||
* Walk elements in declaration order, store each at off + i*esz
|
||||
* using the right width for the element type. The trailing
|
||||
|
||||
Reference in New Issue
Block a user