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:
7
Makefile
7
Makefile
@@ -227,6 +227,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_modtype_leaf_collision \
|
||||
$(BIN)/test_samemod_prefer \
|
||||
$(BIN)/test_cgreturn_struct \
|
||||
$(BIN)/test_cgassign_struct \
|
||||
$(BIN)/test_use_promote_alias \
|
||||
$(BIN)/test_field_signed $(BIN)/test_frame_argcount \
|
||||
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
||||
@@ -374,6 +375,12 @@ $(BIN)/test_cgreturn_struct: test/wcc/698_cgreturn_struct.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_cgassign_struct: test/wcc/701_cgassign_struct.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_use_promote_alias: test/wcc/699_use_promote_alias.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
|
||||
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
|
||||
|
||||
@@ -476,6 +476,32 @@ export fn dup(s: str) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// freeall — release every str element in `s` (those that were
|
||||
// individually allocated) plus the slice's backing storage. Mirrors
|
||||
// Hare's strings::freeall — the natural disposer for any function
|
||||
// returning a fresh `[]str` of dup'd elements (e.g. shlex.split).
|
||||
//
|
||||
// Each element is freed via os.free at its own length; the slice
|
||||
// header storage is freed at `cap * 16` bytes (one str = 16B). Empty
|
||||
// elements (`{nil, 0}` from a zero-length dup) are skipped — calling
|
||||
// os.free on a nil pointer at len 0 would tickle the rt_free guard
|
||||
// that the runtime treats as a logic bug.
|
||||
//
|
||||
// `cap == 0` means the slice was never grown (empty `[]str` with no
|
||||
// backing allocation); skip the header free in that case too.
|
||||
export fn freeall(s: []str) void = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i].len > 0) {
|
||||
os.free(s[i].ptr: *void, s[i].len: u64);
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (s.cap > 0) {
|
||||
os.free(s.ptr: *void, (s.cap: u64) * 16u64);
|
||||
};
|
||||
};
|
||||
|
||||
// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's
|
||||
// strings::rbyteindex. Rune needle scans for the byte that encodes it
|
||||
// (ASCII only); str needle scans for the substring. Empty str needle
|
||||
@@ -6891,6 +6917,31 @@ fn nodeprimwidth(c: *cgen, n: *node) i32 = {
|
||||
|
||||
// ---- type-driven slot sizing ----------------------------------------
|
||||
|
||||
// structnaturalsize — type-natural size of `si`, i.e. max(foff +
|
||||
// fsz) across declared fields. Mirrors cstage's `lu->size` for a
|
||||
// TY_STRUCT (rounded only to the struct's maxalign).
|
||||
//
|
||||
// NOTE: si.totsize is mis-named — it's actually the *slot-padded*
|
||||
// size (rounded up to 8 for stack-slot use; see registerstruct's
|
||||
// tail `if ((off & 7) != 0) ...`). Frame allocation, [N]foo stride,
|
||||
// and similar consumers want that slot-padded number. The
|
||||
// receive-side ABI (#5) and any future "TYPE size, not slot size"
|
||||
// query wants the natural size. Until si.totsize is split into
|
||||
// si.naturalsize + si.slotsize (tracked as the wwstage-sizing
|
||||
// follow-up task), recover the type-natural size from the field
|
||||
// chain here.
|
||||
fn structnaturalsize(si: *structinfo) i32 = {
|
||||
if (si == nil) { return 0; };
|
||||
let n: i32 = 0;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let end: i32 = fi.foff + fi.fsz;
|
||||
if (end > n) { n = end; };
|
||||
fi = fi.finext;
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
// Exact match first: bare-from-source struct names and already-
|
||||
// leafed lookups hit here directly.
|
||||
@@ -12250,11 +12301,148 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
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
|
||||
// to *(struct_ptr + fi.foff). Other rhs shapes
|
||||
// (call result, struct literal) tracked as #27.
|
||||
// struct-typed field via *struct base — three
|
||||
// rhs shapes (call/structlit added with #5;
|
||||
// closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX per #4's cgreturn
|
||||
// ABI; load *struct ptr into BX after the
|
||||
// call, sized stores per natural struct size.
|
||||
// N_STRUCTLIT: field-walk; reload BX before
|
||||
// each store so cgexpr can clobber AX/BX.
|
||||
// si.totsize is slot-padded; use
|
||||
// structnaturalsize for the type-size query.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -12411,10 +12599,134 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
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
|
||||
// value). #27 covers non-ident rhs.
|
||||
// struct-typed field on a direct struct
|
||||
// local — three rhs shapes (call/structlit
|
||||
// added with #5; closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX; sized stores
|
||||
// directly at (lc.off+fi.foff)(BP).
|
||||
// N_STRUCTLIT: field-walk; each inner
|
||||
// field stored at +fi.foff+inner_foff(BP).
|
||||
// BP-rel direct, no addr scratch needed.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((lc.off + fi.foff + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((lc.off + fi.foff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((lc.off + fi.foff + ifi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((lc.off + fi.foff + ifi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -12593,10 +12905,144 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
// struct-typed field, struct-ident rhs on global
|
||||
// struct base: word-copy direct. Skips cgexpr —
|
||||
// no register convention for a whole struct value.
|
||||
// #27 covers non-ident rhs.
|
||||
// struct-typed field on a global struct base —
|
||||
// three rhs shapes (call/structlit added with
|
||||
// #5; closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX; LEAQ base into BX
|
||||
// after call, sized stores per natural size.
|
||||
// N_STRUCTLIT: field-walk; reload BX per store.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -12883,11 +13329,236 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
return;
|
||||
};
|
||||
// TY_STRUCT terminal: word-copy the rhs slot onto
|
||||
// base+totaloff. Only N_IDENT rhs is wired — other
|
||||
// shapes (call result, struct literal) route through
|
||||
// #27. cgexpr is skipped (no whole-struct register
|
||||
// convention); reads come straight from the rhs slot.
|
||||
// TY_STRUCT terminal: three rhs shapes:
|
||||
// - N_IDENT: word-copy from the rhs local slot
|
||||
// (cgexpr is skipped — no whole-struct register
|
||||
// convention for an arbitrary local).
|
||||
// - 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.
|
||||
// cgreturn touches only AX/DX/CX so for
|
||||
// ptrroot/global we load 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; for ptrroot/global the dst addr is
|
||||
// reloaded into BX before each store so cgexpr
|
||||
// can clobber AX/BX between fields.
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& leaffi.tnode != nil
|
||||
&& leaffi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(leaffi.tnode.str) == 0) {
|
||||
let lsi: *structinfo = structlookup(c, leaffi.tnode.str);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
if (lsz <= 24) {
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
};
|
||||
let full: i32 = lsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
if (viacx) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((totaloff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((rootoff + totaloff + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
if (viacx) {
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((totaloff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((rootoff + totaloff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& leaffi.tnode != nil
|
||||
&& leaffi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(leaffi.tnode.str) == 0) {
|
||||
let lsi: *structinfo = structlookup(c, leaffi.tnode.str);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
} else {
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let fi: *fieldinfo = lsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((totaloff + fi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((rootoff + totaloff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
} else {
|
||||
let fsz: i32 = fi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (fsz == 1) { op = "MOVB"; };
|
||||
if (fsz == 4) { op = "MOVL"; };
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((totaloff + fi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((rootoff + totaloff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
&& leaffi.tnode != nil
|
||||
@@ -13306,6 +13977,150 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
lcf = isfloattype(c, lcn.tnode);
|
||||
lcf32 = isf32type(c, lcn.tnode);
|
||||
};
|
||||
// Struct-typed local reassignment: `s = expr;` where s
|
||||
// is a TY_STRUCT local of size <=24B. Two rhs shapes
|
||||
// (mirrors cglet's N_STRUCTLIT and the call-result
|
||||
// receive branch):
|
||||
// - N_STRUCTLIT: walk fields, store at off+foff
|
||||
// directly. ASYMMETRY-safe (no register copy from
|
||||
// the caller; values come from cgexpr).
|
||||
// - N_CALL: cgexpr → AX/DX/CX, sized stores per the
|
||||
// declared struct size — MOVQ for full 8B chunks
|
||||
// plus MOVL/MOVW/MOVB tail. See cglet receive
|
||||
// site for the ASYMMETRY rationale.
|
||||
// Struct-IDENT word-copy rhs (s = p) is left unwired;
|
||||
// #5 is scoped to receive-side of #4 (calls + literals).
|
||||
// fsz dispatch uses the explicit {1→MOVB, 4→MOVL, else
|
||||
// MOVQ} pattern (not fieldstoreop) to match cstage
|
||||
// cgen.c N_ASSIGN byte-identically — wwstage's
|
||||
// fieldstoreop returns MOVW for fsz==2 which cstage
|
||||
// doesn't emit (tracked separately as the cstage/
|
||||
// wwstage MOVW divergence task).
|
||||
if (lcn != nil) {
|
||||
let lctn: *node = lcn.tnode;
|
||||
let lcsname: str;
|
||||
lcsname.ptr = nil; lcsname.len = 0;
|
||||
if (lctn != nil) {
|
||||
if (lctn.kind == nkind.N_TNAME) {
|
||||
lcsname = lctn.str;
|
||||
};
|
||||
};
|
||||
if (lcsname.len > 0) {
|
||||
let lcsi: *structinfo = structlookup(c, lcsname);
|
||||
if (lcsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lcnsz: i32 = structnaturalsize(lcsi);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT) {
|
||||
let lcsz: i32 = lcnsz;
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lcsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lcsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lcsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let fi: *fieldinfo = lcsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((off + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
fi = nil;
|
||||
} else {
|
||||
let fsz: i32 = fi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (fsz == 1) { op = "MOVB"; };
|
||||
if (fsz == 4) { op = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL) {
|
||||
let lcsz: i32 = lcnsz;
|
||||
if (lcsz <= 24) {
|
||||
let tlm: i32 = lcsz - (lcsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
let full: i32 = lcsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((off + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((off + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Float-typed local: rhs lands in X0; store via MOVSD/
|
||||
// MOVSS, no AX shuffle. Compound (+= -= *= /=) loads
|
||||
// slot into X1, combines into X1, stores X1 back —
|
||||
@@ -14080,6 +14895,79 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// 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 writes 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} (unreachable under WW struct
|
||||
// alignment rules — field aligns force size%align==0) fall
|
||||
// through to the generic scalar store rather than emit a
|
||||
// stomping MOVQ tail. Sizes >24B also fall through (sret
|
||||
// deferred, same constraint as #4). Mirrors the cstage
|
||||
// cgen.c N_LET receive branch.
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (tn != nil) {
|
||||
if (tn.kind == nkind.N_TNAME) {
|
||||
sname = tn.str;
|
||||
};
|
||||
};
|
||||
if (sname.len > 0) {
|
||||
let lsi: *structinfo = structlookup(c, sname);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8) for
|
||||
// stack-slot use; the receive ABI needs the
|
||||
// TYPE's natural size — see structnaturalsize.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (lsz <= 24) {
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, rhs);
|
||||
let full: i32 = lsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((off + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((off + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
// Float local: cgexpr leaves the value in X0. Spill via
|
||||
// MOVSS (f32, 4B) or MOVSD (f64, 8B).
|
||||
|
||||
@@ -3745,11 +3745,148 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
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
|
||||
// to *(struct_ptr + fi.foff). Other rhs shapes
|
||||
// (call result, struct literal) tracked as #27.
|
||||
// struct-typed field via *struct base — three
|
||||
// rhs shapes (call/structlit added with #5;
|
||||
// closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX per #4's cgreturn
|
||||
// ABI; load *struct ptr into BX after the
|
||||
// call, sized stores per natural struct size.
|
||||
// N_STRUCTLIT: field-walk; reload BX before
|
||||
// each store so cgexpr can clobber AX/BX.
|
||||
// si.totsize is slot-padded; use
|
||||
// structnaturalsize for the type-size query.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -3906,10 +4043,134 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
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
|
||||
// value). #27 covers non-ident rhs.
|
||||
// struct-typed field on a direct struct
|
||||
// local — three rhs shapes (call/structlit
|
||||
// added with #5; closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX; sized stores
|
||||
// directly at (lc.off+fi.foff)(BP).
|
||||
// N_STRUCTLIT: field-walk; each inner
|
||||
// field stored at +fi.foff+inner_foff(BP).
|
||||
// BP-rel direct, no addr scratch needed.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((lc.off + fi.foff + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((lc.off + fi.foff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((lc.off + fi.foff + ifi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((lc.off + fi.foff + ifi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -4088,10 +4349,144 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
// struct-typed field, struct-ident rhs on global
|
||||
// struct base: word-copy direct. Skips cgexpr —
|
||||
// no register convention for a whole struct value.
|
||||
// #27 covers non-ident rhs.
|
||||
// struct-typed field on a global struct base —
|
||||
// three rhs shapes (call/structlit added with
|
||||
// #5; closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX; LEAQ base into BX
|
||||
// after call, sized stores per natural size.
|
||||
// N_STRUCTLIT: field-walk; reload BX per store.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -4378,11 +4773,236 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
return;
|
||||
};
|
||||
// TY_STRUCT terminal: word-copy the rhs slot onto
|
||||
// base+totaloff. Only N_IDENT rhs is wired — other
|
||||
// shapes (call result, struct literal) route through
|
||||
// #27. cgexpr is skipped (no whole-struct register
|
||||
// convention); reads come straight from the rhs slot.
|
||||
// TY_STRUCT terminal: three rhs shapes:
|
||||
// - N_IDENT: word-copy from the rhs local slot
|
||||
// (cgexpr is skipped — no whole-struct register
|
||||
// convention for an arbitrary local).
|
||||
// - 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.
|
||||
// cgreturn touches only AX/DX/CX so for
|
||||
// ptrroot/global we load 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; for ptrroot/global the dst addr is
|
||||
// reloaded into BX before each store so cgexpr
|
||||
// can clobber AX/BX between fields.
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& leaffi.tnode != nil
|
||||
&& leaffi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(leaffi.tnode.str) == 0) {
|
||||
let lsi: *structinfo = structlookup(c, leaffi.tnode.str);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
if (lsz <= 24) {
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
};
|
||||
let full: i32 = lsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
if (viacx) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((totaloff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((rootoff + totaloff + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
if (viacx) {
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((totaloff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((rootoff + totaloff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& leaffi.tnode != nil
|
||||
&& leaffi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(leaffi.tnode.str) == 0) {
|
||||
let lsi: *structinfo = structlookup(c, leaffi.tnode.str);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
} else {
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let fi: *fieldinfo = lsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((totaloff + fi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((rootoff + totaloff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
} else {
|
||||
let fsz: i32 = fi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (fsz == 1) { op = "MOVB"; };
|
||||
if (fsz == 4) { op = "MOVL"; };
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((totaloff + fi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((rootoff + totaloff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
&& leaffi.tnode != nil
|
||||
@@ -4801,6 +5421,150 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
lcf = isfloattype(c, lcn.tnode);
|
||||
lcf32 = isf32type(c, lcn.tnode);
|
||||
};
|
||||
// Struct-typed local reassignment: `s = expr;` where s
|
||||
// is a TY_STRUCT local of size <=24B. Two rhs shapes
|
||||
// (mirrors cglet's N_STRUCTLIT and the call-result
|
||||
// receive branch):
|
||||
// - N_STRUCTLIT: walk fields, store at off+foff
|
||||
// directly. ASYMMETRY-safe (no register copy from
|
||||
// the caller; values come from cgexpr).
|
||||
// - N_CALL: cgexpr → AX/DX/CX, sized stores per the
|
||||
// declared struct size — MOVQ for full 8B chunks
|
||||
// plus MOVL/MOVW/MOVB tail. See cglet receive
|
||||
// site for the ASYMMETRY rationale.
|
||||
// Struct-IDENT word-copy rhs (s = p) is left unwired;
|
||||
// #5 is scoped to receive-side of #4 (calls + literals).
|
||||
// fsz dispatch uses the explicit {1→MOVB, 4→MOVL, else
|
||||
// MOVQ} pattern (not fieldstoreop) to match cstage
|
||||
// cgen.c N_ASSIGN byte-identically — wwstage's
|
||||
// fieldstoreop returns MOVW for fsz==2 which cstage
|
||||
// doesn't emit (tracked separately as the cstage/
|
||||
// wwstage MOVW divergence task).
|
||||
if (lcn != nil) {
|
||||
let lctn: *node = lcn.tnode;
|
||||
let lcsname: str;
|
||||
lcsname.ptr = nil; lcsname.len = 0;
|
||||
if (lctn != nil) {
|
||||
if (lctn.kind == nkind.N_TNAME) {
|
||||
lcsname = lctn.str;
|
||||
};
|
||||
};
|
||||
if (lcsname.len > 0) {
|
||||
let lcsi: *structinfo = structlookup(c, lcsname);
|
||||
if (lcsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lcnsz: i32 = structnaturalsize(lcsi);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT) {
|
||||
let lcsz: i32 = lcnsz;
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lcsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lcsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lcsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let fi: *fieldinfo = lcsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((off + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
fi = nil;
|
||||
} else {
|
||||
let fsz: i32 = fi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (fsz == 1) { op = "MOVB"; };
|
||||
if (fsz == 4) { op = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL) {
|
||||
let lcsz: i32 = lcnsz;
|
||||
if (lcsz <= 24) {
|
||||
let tlm: i32 = lcsz - (lcsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
let full: i32 = lcsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((off + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((off + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Float-typed local: rhs lands in X0; store via MOVSD/
|
||||
// MOVSS, no AX shuffle. Compound (+= -= *= /=) loads
|
||||
// slot into X1, combines into X1, stores X1 back —
|
||||
|
||||
@@ -648,6 +648,79 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// 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 writes 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} (unreachable under WW struct
|
||||
// alignment rules — field aligns force size%align==0) fall
|
||||
// through to the generic scalar store rather than emit a
|
||||
// stomping MOVQ tail. Sizes >24B also fall through (sret
|
||||
// deferred, same constraint as #4). Mirrors the cstage
|
||||
// cgen.c N_LET receive branch.
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (tn != nil) {
|
||||
if (tn.kind == nkind.N_TNAME) {
|
||||
sname = tn.str;
|
||||
};
|
||||
};
|
||||
if (sname.len > 0) {
|
||||
let lsi: *structinfo = structlookup(c, sname);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8) for
|
||||
// stack-slot use; the receive ABI needs the
|
||||
// TYPE's natural size — see structnaturalsize.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (lsz <= 24) {
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, rhs);
|
||||
let full: i32 = lsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((off + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((off + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
// Float local: cgexpr leaves the value in X0. Spill via
|
||||
// MOVSS (f32, 4B) or MOVSD (f64, 8B).
|
||||
|
||||
@@ -1148,6 +1148,31 @@ fn nodeprimwidth(c: *cgen, n: *node) i32 = {
|
||||
|
||||
// ---- type-driven slot sizing ----------------------------------------
|
||||
|
||||
// structnaturalsize — type-natural size of `si`, i.e. max(foff +
|
||||
// fsz) across declared fields. Mirrors cstage's `lu->size` for a
|
||||
// TY_STRUCT (rounded only to the struct's maxalign).
|
||||
//
|
||||
// NOTE: si.totsize is mis-named — it's actually the *slot-padded*
|
||||
// size (rounded up to 8 for stack-slot use; see registerstruct's
|
||||
// tail `if ((off & 7) != 0) ...`). Frame allocation, [N]foo stride,
|
||||
// and similar consumers want that slot-padded number. The
|
||||
// receive-side ABI (#5) and any future "TYPE size, not slot size"
|
||||
// query wants the natural size. Until si.totsize is split into
|
||||
// si.naturalsize + si.slotsize (tracked as the wwstage-sizing
|
||||
// follow-up task), recover the type-natural size from the field
|
||||
// chain here.
|
||||
fn structnaturalsize(si: *structinfo) i32 = {
|
||||
if (si == nil) { return 0; };
|
||||
let n: i32 = 0;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let end: i32 = fi.foff + fi.fsz;
|
||||
if (end > n) { n = end; };
|
||||
fi = fi.finext;
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
// Exact match first: bare-from-source struct names and already-
|
||||
// leafed lookups hit here directly.
|
||||
|
||||
@@ -476,6 +476,32 @@ export fn dup(s: str) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// freeall — release every str element in `s` (those that were
|
||||
// individually allocated) plus the slice's backing storage. Mirrors
|
||||
// Hare's strings::freeall — the natural disposer for any function
|
||||
// returning a fresh `[]str` of dup'd elements (e.g. shlex.split).
|
||||
//
|
||||
// Each element is freed via os.free at its own length; the slice
|
||||
// header storage is freed at `cap * 16` bytes (one str = 16B). Empty
|
||||
// elements (`{nil, 0}` from a zero-length dup) are skipped — calling
|
||||
// os.free on a nil pointer at len 0 would tickle the rt_free guard
|
||||
// that the runtime treats as a logic bug.
|
||||
//
|
||||
// `cap == 0` means the slice was never grown (empty `[]str` with no
|
||||
// backing allocation); skip the header free in that case too.
|
||||
export fn freeall(s: []str) void = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i].len > 0) {
|
||||
os.free(s[i].ptr: *void, s[i].len: u64);
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (s.cap > 0) {
|
||||
os.free(s.ptr: *void, (s.cap: u64) * 16u64);
|
||||
};
|
||||
};
|
||||
|
||||
// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's
|
||||
// strings::rbyteindex. Rune needle scans for the byte that encodes it
|
||||
// (ASCII only); str needle scans for the substring. Empty str needle
|
||||
@@ -6891,6 +6917,31 @@ fn nodeprimwidth(c: *cgen, n: *node) i32 = {
|
||||
|
||||
// ---- type-driven slot sizing ----------------------------------------
|
||||
|
||||
// structnaturalsize — type-natural size of `si`, i.e. max(foff +
|
||||
// fsz) across declared fields. Mirrors cstage's `lu->size` for a
|
||||
// TY_STRUCT (rounded only to the struct's maxalign).
|
||||
//
|
||||
// NOTE: si.totsize is mis-named — it's actually the *slot-padded*
|
||||
// size (rounded up to 8 for stack-slot use; see registerstruct's
|
||||
// tail `if ((off & 7) != 0) ...`). Frame allocation, [N]foo stride,
|
||||
// and similar consumers want that slot-padded number. The
|
||||
// receive-side ABI (#5) and any future "TYPE size, not slot size"
|
||||
// query wants the natural size. Until si.totsize is split into
|
||||
// si.naturalsize + si.slotsize (tracked as the wwstage-sizing
|
||||
// follow-up task), recover the type-natural size from the field
|
||||
// chain here.
|
||||
fn structnaturalsize(si: *structinfo) i32 = {
|
||||
if (si == nil) { return 0; };
|
||||
let n: i32 = 0;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let end: i32 = fi.foff + fi.fsz;
|
||||
if (end > n) { n = end; };
|
||||
fi = fi.finext;
|
||||
};
|
||||
return n;
|
||||
};
|
||||
|
||||
fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
// Exact match first: bare-from-source struct names and already-
|
||||
// leafed lookups hit here directly.
|
||||
@@ -12250,11 +12301,148 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
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
|
||||
// to *(struct_ptr + fi.foff). Other rhs shapes
|
||||
// (call result, struct literal) tracked as #27.
|
||||
// struct-typed field via *struct base — three
|
||||
// rhs shapes (call/structlit added with #5;
|
||||
// closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX per #4's cgreturn
|
||||
// ABI; load *struct ptr into BX after the
|
||||
// call, sized stores per natural struct size.
|
||||
// N_STRUCTLIT: field-walk; reload BX before
|
||||
// each store so cgexpr can clobber AX/BX.
|
||||
// si.totsize is slot-padded; use
|
||||
// structnaturalsize for the type-size query.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -12411,10 +12599,134 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
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
|
||||
// value). #27 covers non-ident rhs.
|
||||
// struct-typed field on a direct struct
|
||||
// local — three rhs shapes (call/structlit
|
||||
// added with #5; closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX; sized stores
|
||||
// directly at (lc.off+fi.foff)(BP).
|
||||
// N_STRUCTLIT: field-walk; each inner
|
||||
// field stored at +fi.foff+inner_foff(BP).
|
||||
// BP-rel direct, no addr scratch needed.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((lc.off + fi.foff + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((lc.off + fi.foff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((lc.off + fi.foff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((lc.off + fi.foff + ifi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((lc.off + fi.foff + ifi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -12593,10 +12905,144 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
// struct-typed field, struct-ident rhs on global
|
||||
// struct base: word-copy direct. Skips cgexpr —
|
||||
// no register convention for a whole struct value.
|
||||
// #27 covers non-ident rhs.
|
||||
// struct-typed field on a global struct base —
|
||||
// three rhs shapes (call/structlit added with
|
||||
// #5; closes #27 marker here):
|
||||
// N_IDENT: word-copy from rhs slot.
|
||||
// N_CALL: cgexpr → AX/DX/CX; LEAQ base into BX
|
||||
// after call, sized stores per natural size.
|
||||
// N_STRUCTLIT: field-walk; reload BX per store.
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
let full: i32 = ssz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((fi.foff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(fi.tnode.str) == 0) {
|
||||
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structnaturalsize(ssi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= ssz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= ssz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < ssz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let ifi: *fieldinfo = ssi.fields;
|
||||
for (ifi != nil) {
|
||||
if (streq(ifi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, ifi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, ifi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
} else {
|
||||
let ifsz: i32 = ifi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (ifsz == 1) { op = "MOVB"; };
|
||||
if (ifsz == 4) { op = "MOVL"; };
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((fi.foff + ifi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
ifi = nil;
|
||||
};
|
||||
} else {
|
||||
ifi = ifi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.op == tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
@@ -12883,11 +13329,236 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
return;
|
||||
};
|
||||
// TY_STRUCT terminal: word-copy the rhs slot onto
|
||||
// base+totaloff. Only N_IDENT rhs is wired — other
|
||||
// shapes (call result, struct literal) route through
|
||||
// #27. cgexpr is skipped (no whole-struct register
|
||||
// convention); reads come straight from the rhs slot.
|
||||
// TY_STRUCT terminal: three rhs shapes:
|
||||
// - N_IDENT: word-copy from the rhs local slot
|
||||
// (cgexpr is skipped — no whole-struct register
|
||||
// convention for an arbitrary local).
|
||||
// - 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.
|
||||
// cgreturn touches only AX/DX/CX so for
|
||||
// ptrroot/global we load 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; for ptrroot/global the dst addr is
|
||||
// reloaded into BX before each store so cgexpr
|
||||
// can clobber AX/BX between fields.
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL
|
||||
&& leaffi.tnode != nil
|
||||
&& leaffi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(leaffi.tnode.str) == 0) {
|
||||
let lsi: *structinfo = structlookup(c, leaffi.tnode.str);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
if (lsz <= 24) {
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
};
|
||||
let full: i32 = lsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
if (viacx) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitdispreg((totaloff + i * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((rootoff + totaloff + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
if (viacx) {
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitdispreg((totaloff + full * 8): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((rootoff + totaloff + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT
|
||||
&& leaffi.tnode != nil
|
||||
&& leaffi.tnode.kind == nkind.N_TNAME
|
||||
&& primsize(leaffi.tnode.str) == 0) {
|
||||
let lsi: *structinfo = structlookup(c, leaffi.tnode.str);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((totaloff + zi): i64, "BX");
|
||||
emitline("\n");
|
||||
zi += 1;
|
||||
};
|
||||
} else {
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((rootoff + totaloff + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let fi: *fieldinfo = lsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg((totaloff + fi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((rootoff + totaloff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
} else {
|
||||
let fsz: i32 = fi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (fsz == 1) { op = "MOVB"; };
|
||||
if (fsz == 4) { op = "MOVL"; };
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg((totaloff + fi.foff): i64, "BX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((rootoff + totaloff + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_IDENT
|
||||
&& leaffi.tnode != nil
|
||||
@@ -13306,6 +13977,150 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
lcf = isfloattype(c, lcn.tnode);
|
||||
lcf32 = isf32type(c, lcn.tnode);
|
||||
};
|
||||
// Struct-typed local reassignment: `s = expr;` where s
|
||||
// is a TY_STRUCT local of size <=24B. Two rhs shapes
|
||||
// (mirrors cglet's N_STRUCTLIT and the call-result
|
||||
// receive branch):
|
||||
// - N_STRUCTLIT: walk fields, store at off+foff
|
||||
// directly. ASYMMETRY-safe (no register copy from
|
||||
// the caller; values come from cgexpr).
|
||||
// - N_CALL: cgexpr → AX/DX/CX, sized stores per the
|
||||
// declared struct size — MOVQ for full 8B chunks
|
||||
// plus MOVL/MOVW/MOVB tail. See cglet receive
|
||||
// site for the ASYMMETRY rationale.
|
||||
// Struct-IDENT word-copy rhs (s = p) is left unwired;
|
||||
// #5 is scoped to receive-side of #4 (calls + literals).
|
||||
// fsz dispatch uses the explicit {1→MOVB, 4→MOVL, else
|
||||
// MOVQ} pattern (not fieldstoreop) to match cstage
|
||||
// cgen.c N_ASSIGN byte-identically — wwstage's
|
||||
// fieldstoreop returns MOVW for fsz==2 which cstage
|
||||
// doesn't emit (tracked separately as the cstage/
|
||||
// wwstage MOVW divergence task).
|
||||
if (lcn != nil) {
|
||||
let lctn: *node = lcn.tnode;
|
||||
let lcsname: str;
|
||||
lcsname.ptr = nil; lcsname.len = 0;
|
||||
if (lctn != nil) {
|
||||
if (lctn.kind == nkind.N_TNAME) {
|
||||
lcsname = lctn.str;
|
||||
};
|
||||
};
|
||||
if (lcsname.len > 0) {
|
||||
let lcsi: *structinfo = structlookup(c, lcsname);
|
||||
if (lcsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8);
|
||||
// receive ABI needs the TYPE's natural size.
|
||||
let lcnsz: i32 = structnaturalsize(lcsi);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_STRUCTLIT) {
|
||||
let lcsz: i32 = lcnsz;
|
||||
if (n.rhs.op == tkind.TK_ELLIPSIS) {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zi: i32 = 0;
|
||||
for (zi + 8 <= lcsz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 8;
|
||||
};
|
||||
for (zi + 4 <= lcsz) {
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 4;
|
||||
};
|
||||
for (zi < lcsz) {
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff((off + zi): i64);
|
||||
emitline("(BP)\n");
|
||||
zi += 1;
|
||||
};
|
||||
};
|
||||
let fldn: *node = n.rhs.list;
|
||||
for (fldn != nil) {
|
||||
if (fldn.kind == nkind.N_FIELD) {
|
||||
let fnm: str = fldn.str;
|
||||
let fi: *fieldinfo = lcsi.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fnm)) {
|
||||
cgexpr(c, fldn.lhs);
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) {
|
||||
mov = "MOVSS";
|
||||
};
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((off + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
fi = nil;
|
||||
} else {
|
||||
let fsz: i32 = fi.fsz;
|
||||
let op: str = "MOVQ";
|
||||
if (fsz == 1) { op = "MOVB"; };
|
||||
if (fsz == 4) { op = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
fi = nil;
|
||||
};
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
fldn = fldn.next;
|
||||
};
|
||||
return;
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL) {
|
||||
let lcsz: i32 = lcnsz;
|
||||
if (lcsz <= 24) {
|
||||
let tlm: i32 = lcsz - (lcsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
let full: i32 = lcsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((off + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((off + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Float-typed local: rhs lands in X0; store via MOVSD/
|
||||
// MOVSS, no AX shuffle. Compound (+= -= *= /=) loads
|
||||
// slot into X1, combines into X1, stores X1 back —
|
||||
@@ -14080,6 +14895,79 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// 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 writes 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} (unreachable under WW struct
|
||||
// alignment rules — field aligns force size%align==0) fall
|
||||
// through to the generic scalar store rather than emit a
|
||||
// stomping MOVQ tail. Sizes >24B also fall through (sret
|
||||
// deferred, same constraint as #4). Mirrors the cstage
|
||||
// cgen.c N_LET receive branch.
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (tn != nil) {
|
||||
if (tn.kind == nkind.N_TNAME) {
|
||||
sname = tn.str;
|
||||
};
|
||||
};
|
||||
if (sname.len > 0) {
|
||||
let lsi: *structinfo = structlookup(c, sname);
|
||||
if (lsi != nil) {
|
||||
// si.totsize is slot-padded (rounded to 8) for
|
||||
// stack-slot use; the receive ABI needs the
|
||||
// TYPE's natural size — see structnaturalsize.
|
||||
let lsz: i32 = structnaturalsize(lsi);
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (lsz <= 24) {
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, rhs);
|
||||
let full: i32 = lsz / 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
let reg: str = "AX";
|
||||
if (i == 1) { reg = "DX"; };
|
||||
if (i == 2) { reg = "CX"; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(reg);
|
||||
emitline(", ");
|
||||
emitoff((off + i * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tlm > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tlm == 4) { top = "MOVL"; };
|
||||
if (tlm == 2) { top = "MOVW"; };
|
||||
let treg: str = "AX";
|
||||
if (full == 1) { treg = "DX"; };
|
||||
if (full == 2) { treg = "CX"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(treg);
|
||||
emitline(", ");
|
||||
emitoff((off + full * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, rhs);
|
||||
// Float local: cgexpr leaves the value in X0. Spill via
|
||||
// MOVSS (f32, 4B) or MOVSD (f64, 8B).
|
||||
|
||||
@@ -368,6 +368,32 @@ export fn dup(s: str) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// freeall — release every str element in `s` (those that were
|
||||
// individually allocated) plus the slice's backing storage. Mirrors
|
||||
// Hare's strings::freeall — the natural disposer for any function
|
||||
// returning a fresh `[]str` of dup'd elements (e.g. shlex.split).
|
||||
//
|
||||
// Each element is freed via os.free at its own length; the slice
|
||||
// header storage is freed at `cap * 16` bytes (one str = 16B). Empty
|
||||
// elements (`{nil, 0}` from a zero-length dup) are skipped — calling
|
||||
// os.free on a nil pointer at len 0 would tickle the rt_free guard
|
||||
// that the runtime treats as a logic bug.
|
||||
//
|
||||
// `cap == 0` means the slice was never grown (empty `[]str` with no
|
||||
// backing allocation); skip the header free in that case too.
|
||||
export fn freeall(s: []str) void = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i].len > 0) {
|
||||
os.free(s[i].ptr: *void, s[i].len: u64);
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (s.cap > 0) {
|
||||
os.free(s.ptr: *void, (s.cap: u64) * 16u64);
|
||||
};
|
||||
};
|
||||
|
||||
// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's
|
||||
// strings::rbyteindex. Rune needle scans for the byte that encodes it
|
||||
// (ASCII only); str needle scans for the substring. Empty str needle
|
||||
|
||||
435
test/wcc/701_cgassign_struct.c
Normal file
435
test/wcc/701_cgassign_struct.c
Normal file
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* 701_cgassign_struct — whole-struct receive ABI for sizes <= 24B
|
||||
* (receive side of #4's cgreturn, task #5 #27).
|
||||
*
|
||||
* #4 wired the producer side: `return s;` materialises bytes into
|
||||
* AX (bytes 0..7), DX (8..15), CX (16..23), zero-padded to 24B. The
|
||||
* #4 test (698_cgreturn_struct.c) only pins that cgreturn doesn't
|
||||
* crash; the result is discarded by the caller. #5 wires the
|
||||
* receive side end-to-end:
|
||||
*
|
||||
* - `let s: foo = bar()` — N_LET call-result rhs lands AX/DX/CX
|
||||
* into the slot with sized stores (MOVQ for full 8B chunks,
|
||||
* MOVL/MOVB/MOVW tail by *declared* struct size — the receiver
|
||||
* must NOT mirror the sender's three uniform MOVQs, else
|
||||
* trailing 1..7 bytes overrun the next slot).
|
||||
* - `let s: foo = foo{...}` — already wired by #4's predecessor;
|
||||
* this test pins the value path.
|
||||
* - `s = bar()` reassign — symmetric N_ASSIGN N_IDENT-lhs.
|
||||
* - `s = foo{...}` reassign — symmetric N_STRUCTLIT shape.
|
||||
*
|
||||
* What this test pins:
|
||||
* - cstage and wwstage emit byte-identical asm for every fixture
|
||||
* (catches any drift in the receive-side store sequence).
|
||||
* - END-TO-END VALUE CORRECTNESS: each fixture's main returns a
|
||||
* deterministic exit code derived from the received struct's
|
||||
* fields. A regression in the receive ABI (e.g., DX→+0 instead
|
||||
* of +8, or an over-wide tail MOVQ overrunning the slot) shows
|
||||
* up as a `got != want` exit-code mismatch, not a no-crash
|
||||
* pass.
|
||||
*
|
||||
* Coverage:
|
||||
* - 8B (one_i64): smallest struct, only AX is meaningful.
|
||||
* - 16B (pair_i64): two-word AX/DX.
|
||||
* - 20B (five_i32): three-word AX/DX/CX with MOVL tail — the
|
||||
* headline ASYMMETRY case. A naïve MOVQ tail here would stomp
|
||||
* 4 bytes past the slot.
|
||||
* - 24B (trip_i64): three-word, no tail.
|
||||
* - 24B mix (i32+i32+i64+i64): registerstruct-packed shape.
|
||||
*
|
||||
* Each shape covers two rhs forms (call-result, struct-literal)
|
||||
* across two lvalue forms (let-init, reassign).
|
||||
*
|
||||
* Sizes >24B are OUT OF SCOPE — sret is deferred (same constraint
|
||||
* as #4). Tail chunks in {3,5,6,7} are unreachable under WW
|
||||
* struct alignment rules (field aligns force size%align == 0); the
|
||||
* receive branches guard those out and fall through.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* 8B, call-result let-init. exit = s.v (= 7). */
|
||||
{ "one_i64_let_call",
|
||||
"type one = struct { v: i64 };\n"
|
||||
"fn mk() one = { return one { v = 7i64 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: one = mk();\n"
|
||||
" return s.v: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* 8B, struct-literal let-init. exit = s.v (= 11). */
|
||||
{ "one_i64_let_lit",
|
||||
"type one = struct { v: i64 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: one = one { v = 11i64 };\n"
|
||||
" return s.v: i32;\n"
|
||||
"};\n",
|
||||
11 },
|
||||
/* 8B, call-result reassign. exit = s.v (= 13). */
|
||||
{ "one_i64_reassign_call",
|
||||
"type one = struct { v: i64 };\n"
|
||||
"fn mk() one = { return one { v = 13i64 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: one = one { v = 0i64 };\n"
|
||||
" s = mk();\n"
|
||||
" return s.v: i32;\n"
|
||||
"};\n",
|
||||
13 },
|
||||
/* 8B, struct-literal reassign. exit = s.v (= 17). */
|
||||
{ "one_i64_reassign_lit",
|
||||
"type one = struct { v: i64 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: one = one { v = 0i64 };\n"
|
||||
" s = one { v = 17i64 };\n"
|
||||
" return s.v: i32;\n"
|
||||
"};\n",
|
||||
17 },
|
||||
/* 16B, call-result let-init. exit = a + b (= 3 + 5 = 8).
|
||||
* Pins that DX lands at +8 and AX at +0. */
|
||||
{ "pair_i64_let_call",
|
||||
"type pair = struct { a: i64, b: i64 };\n"
|
||||
"fn mk() pair = { return pair { a = 3i64, b = 5i64 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: pair = mk();\n"
|
||||
" return (s.a + s.b): i32;\n"
|
||||
"};\n",
|
||||
8 },
|
||||
/* 16B, struct-literal reassign. exit = a + b (= 4 + 6 = 10). */
|
||||
{ "pair_i64_reassign_lit",
|
||||
"type pair = struct { a: i64, b: i64 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: pair = pair { a = 0i64, b = 0i64 };\n"
|
||||
" s = pair { a = 4i64, b = 6i64 };\n"
|
||||
" return (s.a + s.b): i32;\n"
|
||||
"};\n",
|
||||
10 },
|
||||
/* 20B (five-i32), call-result let-init — the ASYMMETRY
|
||||
* headline case. AX bytes 0..7 hold a,b. DX bytes 8..15 hold
|
||||
* c,d. CX low 4 bytes hold e; CX upper 4 bytes are pad zero.
|
||||
* The receiver MUST store CX as MOVL (4B), not MOVQ (8B);
|
||||
* a MOVQ would overrun into the next local slot. Sum check:
|
||||
* 1+2+3+4+5 = 15. */
|
||||
{ "five_i32_let_call",
|
||||
"type five = struct { a: i32, b: i32, c: i32, d: i32, e: i32 };\n"
|
||||
"fn mk() five = {\n"
|
||||
" return five { a = 1, b = 2, c = 3, d = 4, e = 5 };\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: five = mk();\n"
|
||||
" return s.a + s.b + s.c + s.d + s.e;\n"
|
||||
"};\n",
|
||||
15 },
|
||||
/* 20B (five-i32), struct-literal reassign. Same tail-MOVL
|
||||
* pin as above but exercises the structlit-field-walk path. */
|
||||
{ "five_i32_reassign_lit",
|
||||
"type five = struct { a: i32, b: i32, c: i32, d: i32, e: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: five = five { a = 0, b = 0, c = 0, d = 0, e = 0 };\n"
|
||||
" s = five { a = 2, b = 4, c = 6, d = 8, e = 10 };\n"
|
||||
" return s.a + s.b + s.c + s.d + s.e;\n"
|
||||
"};\n",
|
||||
30 },
|
||||
/* 24B, call-result let-init — the headline three-word ABI.
|
||||
* AX/DX/CX each carry one full word, no tail. Sum check:
|
||||
* 11+22+33 = 66. */
|
||||
{ "trip_i64_let_call",
|
||||
"type trip = struct { x: i64, y: i64, z: i64 };\n"
|
||||
"fn mk() trip = { return trip { x = 11i64, y = 22i64, z = 33i64 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: trip = mk();\n"
|
||||
" return (s.x + s.y + s.z): i32;\n"
|
||||
"};\n",
|
||||
66 },
|
||||
/* 24B, struct-literal reassign. Same word-shape as above. */
|
||||
{ "trip_i64_reassign_lit",
|
||||
"type trip = struct { x: i64, y: i64, z: i64 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: trip = trip { x = 0i64, y = 0i64, z = 0i64 };\n"
|
||||
" s = trip { x = 14i64, y = 28i64, z = 42i64 };\n"
|
||||
" return (s.x + s.y + s.z): i32;\n"
|
||||
"};\n",
|
||||
84 },
|
||||
/* 24B mix (registerstruct-packed): {i32, i32, i64, i64}.
|
||||
* Layout: a@+0, b@+4, c@+8, d@+16 (totsize 24). AX carries
|
||||
* a+b packed, DX = c, CX = d. Sum check: 1+2+3+4 = 10. */
|
||||
{ "mix_let_call",
|
||||
"type mix = struct { a: i32, b: i32, c: i64, d: i64 };\n"
|
||||
"fn mk() mix = { return mix { a = 1, b = 2, c = 3i64, d = 4i64 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: mix = mk();\n"
|
||||
" return (s.a + s.b) + (s.c + s.d): i32;\n"
|
||||
"};\n",
|
||||
10 },
|
||||
/* dst.field = call() — single-dot struct-typed field via
|
||||
* local struct base. Exercises the cgen.c:1996+ N_DOT.N_IDENT
|
||||
* struct-field branch (cstage) and cgenexpr.ww:~4046 single-
|
||||
* dot local-base site (wwstage). Sum check: 3+4+0 = 7. */
|
||||
{ "field_local_call",
|
||||
"type inner = struct { a: i64, b: i64 };\n"
|
||||
"type outer = struct { i: inner, t: i64 };\n"
|
||||
"fn mki() inner = { return inner { a = 3i64, b = 4i64 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 0i64 };\n"
|
||||
" o.i = mki();\n"
|
||||
" return (o.i.a + o.i.b + o.t): i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* dst.field = T{...} — same single-dot site, struct-literal
|
||||
* rhs. Sum check: 11+22+5 = 38. */
|
||||
{ "field_local_lit",
|
||||
"type inner = struct { a: i64, b: i64 };\n"
|
||||
"type outer = struct { i: inner, t: i64 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 5i64 };\n"
|
||||
" o.i = inner { a = 11i64, b = 22i64 };\n"
|
||||
" return (o.i.a + o.i.b + o.t): i32;\n"
|
||||
"};\n",
|
||||
38 },
|
||||
/* dst.field = call() via *struct base (auto-deref through
|
||||
* pointer). Exercises cgenexpr.ww:~3752 single-dot ptr-base
|
||||
* site (wwstage). Sum check: 30+40+100 = 170. */
|
||||
{ "field_ptr_call",
|
||||
"type inner = struct { a: i64, b: i64 };\n"
|
||||
"type outer = struct { i: inner, t: i64 };\n"
|
||||
"fn mki() inner = { return inner { a = 30i64, b = 40i64 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 100i64 };\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" p.i = mki();\n"
|
||||
" return (o.i.a + o.i.b + o.t): i32;\n"
|
||||
"};\n",
|
||||
170 },
|
||||
/* dst.field = T{...} via *struct base, struct-literal rhs.
|
||||
* Sum check: 7+8+50 = 65. */
|
||||
{ "field_ptr_lit",
|
||||
"type inner = struct { a: i64, b: i64 };\n"
|
||||
"type outer = struct { i: inner, t: i64 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 50i64 };\n"
|
||||
" let p: *outer = &o;\n"
|
||||
" p.i = inner { a = 7i64, b = 8i64 };\n"
|
||||
" return (o.i.a + o.i.b + o.t): i32;\n"
|
||||
"};\n",
|
||||
65 },
|
||||
/* dst.field = call() via top-level let-global base.
|
||||
* Exercises cgenexpr.ww:~4352 single-dot global-base site
|
||||
* (wwstage) and the corresponding cstage is_global path.
|
||||
* `let g: T;` (no init) avoids the module-name-mangle path
|
||||
* that initialised globals hit (cf. task #17). Sum check:
|
||||
* 70+80+100 = 250 (≤ 255, fits in process exit code). */
|
||||
{ "field_global_call",
|
||||
"type inner = struct { a: i64, b: i64 };\n"
|
||||
"type outer = struct { i: inner, t: i64 };\n"
|
||||
"let g: outer;\n"
|
||||
"fn mki() inner = { return inner { a = 70i64, b = 80i64 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" g.t = 100i64;\n"
|
||||
" g.i = mki();\n"
|
||||
" return (g.i.a + g.i.b + g.t): i32;\n"
|
||||
"};\n",
|
||||
250 },
|
||||
/* Chained `s.f.g = call()` — depth-2 dot lhs. Exercises the
|
||||
* cstage chained walker (cgen.c:~2626) and wwstage walker
|
||||
* (cgenexpr.ww:~4381). The initialiser uses explicit field
|
||||
* writes (not a nested struct-literal), since cgen's nested-
|
||||
* STRUCTLIT-as-field-value path is a separate gap. Bare
|
||||
* `let o: outer;` zero-fills the slot. Sums sized to fit a
|
||||
* process exit code (8 bits): 5+6+10+20 = 41. */
|
||||
{ "field_chain_call",
|
||||
"type inner = struct { a: i64, b: i64 };\n"
|
||||
"type middle = struct { in: inner, t: i64 };\n"
|
||||
"type outer = struct { m: middle, x: i64 };\n"
|
||||
"fn mki() inner = { return inner { a = 5i64, b = 6i64 }; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer;\n"
|
||||
" o.m.t = 10i64;\n"
|
||||
" o.x = 20i64;\n"
|
||||
" o.m.in = mki();\n"
|
||||
" return (o.m.in.a + o.m.in.b + o.m.t + o.x): i32;\n"
|
||||
"};\n",
|
||||
41 },
|
||||
/* Chained `s.f.g = T{...}` — depth-2 struct-literal rhs.
|
||||
* Sum check: 11+22+10+20 = 63. */
|
||||
{ "field_chain_lit",
|
||||
"type inner = struct { a: i64, b: i64 };\n"
|
||||
"type middle = struct { in: inner, t: i64 };\n"
|
||||
"type outer = struct { m: middle, x: i64 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let o: outer;\n"
|
||||
" o.m.t = 10i64;\n"
|
||||
" o.x = 20i64;\n"
|
||||
" o.m.in = inner { a = 11i64, b = 22i64 };\n"
|
||||
" return (o.m.in.a + o.m.in.b + o.m.t + o.x): i32;\n"
|
||||
"};\n",
|
||||
63 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/wcas_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcas_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
|
||||
* w6c_ww and diff. Catches receive-side codegen drift between the
|
||||
* stages, which 995_self_rebuild covers globally but doesn't surface
|
||||
* as a focused-fixture failure. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char src[64], cs[64], ws[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/wcas_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/wcas_asm_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/wcas_asm_%d_%d_w.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
unlink(src);
|
||||
return -1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
unlink(src); unlink(cs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
int rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
unlink(src); unlink(cs); unlink(ws);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
/* Compile+run for each (driver, row). */
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "cgassign_struct: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"cgassign_struct[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Asm byte-identity diff, only when both stages exist. */
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"cgassign_struct: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("cgassign_struct: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user