cgen: route aggregate field-to-field assignment through the aggregate copier
The direct-field assignment arms enumerate CALL, STRUCTLIT, and local IDENT producers; an addressable N_DOT/N_INDEX/deref rhs fell through to the scalar tail, so a 16-byte struct field copied only its first word. Resolve both places through the existing address funnels and use the tail-aware aggregate copier. Both stages.
This commit is contained in:
@@ -5257,6 +5257,39 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
fatal("array-literal store at assignment "
|
||||
"unwired (task #32)");
|
||||
}
|
||||
/* A plain aggregate field-to-field assignment is a memory copy,
|
||||
* not a scalar expression/store. The legacy direct-field arms
|
||||
* below enumerate CALL, STRUCTLIT, and local IDENT producers; an
|
||||
* addressable N_DOT/N_INDEX/deref rhs fell through, so a 16-byte
|
||||
* time.instant copied only its first word. Resolve both places
|
||||
* through the existing generic address funnels and use the single
|
||||
* tail-aware aggregate copier. Calls/literals/unwraps stay on their
|
||||
* specialized ABI paths, and tagged/str/slice fields are excluded by
|
||||
* the destination type gate. */
|
||||
if (n->op == TK_ASSIGN && n->lhs
|
||||
&& n->lhs->kind == N_DOT && n->lhs->lhs
|
||||
&& (n->lhs->lhs->kind == N_IDENT
|
||||
|| n->lhs->lhs->kind == N_DOT
|
||||
|| (n->lhs->lhs->kind == N_UN
|
||||
&& n->lhs->lhs->op == TK_STAR))
|
||||
&& n->rhs) {
|
||||
Type *au = type_chase_named(n->lhs->type);
|
||||
int memrhs = n->rhs->kind == N_IDENT
|
||||
|| n->rhs->kind == N_DOT
|
||||
|| n->rhs->kind == N_INDEX
|
||||
|| (n->rhs->kind == N_UN && n->rhs->op == TK_STAR);
|
||||
if (au && (au->kind == TY_STRUCT || au->kind == TY_ARRAY
|
||||
|| au->kind == TY_TUPLE) && memrhs) {
|
||||
if (!cgplaceaddr(c, n->lhs, D_BX, locals))
|
||||
fatal("aggregate field destination unresolved");
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
if (!aggarg_srcaddr(c, n->rhs, D_SI, locals))
|
||||
fatal("aggregate field source unresolved");
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
cg_aggcopy(c, (int)au->size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* #16: a single-dot aggregate-field unwrap store whose base
|
||||
* is a module-GLOBAL value-struct (`g.f = mk()!`) or a
|
||||
* CHAINED struct field (`o.m.f = mk()!`). The #12 single-dot
|
||||
|
||||
@@ -8719,6 +8719,48 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// A plain aggregate field-to-field assignment is a memory copy, not
|
||||
// a scalar expression/store. The direct-field arms below enumerate
|
||||
// CALL, STRUCTLIT and local IDENT producers; an addressable DOT/INDEX/
|
||||
// deref rhs fell through, so a 16-byte time.instant copied one word.
|
||||
// Resolve both places through the existing address funnels and use the
|
||||
// canonical tail-aware aggregate copier. Calls/literals/unwraps and
|
||||
// tagged/str/slice fields remain on their specialized ABI paths.
|
||||
if (lhs != nil && n.rhs != nil && n.op == syntax.tkind.TK_ASSIGN) {
|
||||
if (lhs.kind == syntax.nkind.N_DOT && lhs.lhs != nil
|
||||
&& (lhs.lhs.kind == syntax.nkind.N_IDENT
|
||||
|| lhs.lhs.kind == syntax.nkind.N_DOT
|
||||
|| (lhs.lhs.kind == syntax.nkind.N_UN
|
||||
&& lhs.lhs.op == syntax.tkind.TK_STAR))) {
|
||||
let au: *syntax.tinfo = lhs.type_: *syntax.tinfo;
|
||||
au = tichase(au);
|
||||
let memrhs: bool = n.rhs.kind == syntax.nkind.N_IDENT
|
||||
|| n.rhs.kind == syntax.nkind.N_DOT
|
||||
|| n.rhs.kind == syntax.nkind.N_INDEX
|
||||
|| (n.rhs.kind == syntax.nkind.N_UN
|
||||
&& n.rhs.op == syntax.tkind.TK_STAR);
|
||||
if (au != nil && memrhs) {
|
||||
if (au.kind == syntax.tykind.TY_STRUCT
|
||||
|| au.kind == syntax.tykind.TY_ARRAY
|
||||
|| au.kind == syntax.tykind.TY_TUPLE) {
|
||||
if (!cgplaceaddr(c, lhs, "BX")) {
|
||||
let md: str = "aggregate field destination unresolved\n";
|
||||
os.write(2, md.ptr, md.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
if (!aggargsrcaddr(c, n.rhs, "SI")) {
|
||||
let ms: str = "aggregate field source unresolved\n";
|
||||
os.write(2, ms.ptr, ms.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
aggcopy(c, au.size: i32);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// #21: a COMPOUND op on a whole tagged-union IDENT (`g OP= v`
|
||||
// with g:(int|bool)) is nonsense — the ident load-combine-store
|
||||
// tail below reads and writes one word of the {payload,tag} box,
|
||||
|
||||
73
test/lang/aggregate_field_copy_test.ww
Normal file
73
test/lang/aggregate_field_copy_test.ww
Normal file
@@ -0,0 +1,73 @@
|
||||
// Addressable aggregate fields must copy every byte. A scalar fallback used
|
||||
// to copy only the first word of a 16-byte nested value under both stages.
|
||||
|
||||
package aggregate_field_copy_test;
|
||||
|
||||
type pair = struct {
|
||||
first: i64,
|
||||
second: i64,
|
||||
};
|
||||
|
||||
type outer = struct {
|
||||
guard: i64,
|
||||
value: pair,
|
||||
tail: i64,
|
||||
};
|
||||
|
||||
type nested = struct {
|
||||
outer: outer,
|
||||
};
|
||||
|
||||
let globalsource: outer;
|
||||
let globaltarget: outer;
|
||||
|
||||
fn check(v: *outer, first: i64, second: i64) void = {
|
||||
assert(v.value.first == first);
|
||||
assert(v.value.second == second);
|
||||
assert(v.guard == 91);
|
||||
assert(v.tail == 92);
|
||||
};
|
||||
|
||||
@test fn aggregate_field_memory_copy() void = {
|
||||
let source: outer;
|
||||
source.value.first = 11;
|
||||
source.value.second = 12;
|
||||
let target: outer;
|
||||
target.guard = 91;
|
||||
target.value.first = 1;
|
||||
target.value.second = 2;
|
||||
target.tail = 92;
|
||||
target.value = source.value;
|
||||
check(&target, 11, 12);
|
||||
|
||||
let pointer: *outer = ⌖
|
||||
source.value.first = 21;
|
||||
source.value.second = 22;
|
||||
pointer.value = source.value;
|
||||
check(&target, 21, 22);
|
||||
|
||||
let deep: nested;
|
||||
deep.outer.value.first = 31;
|
||||
deep.outer.value.second = 32;
|
||||
target.value = deep.outer.value;
|
||||
check(&target, 31, 32);
|
||||
|
||||
let indexed: [2]pair = [pair{first=5,second=6},
|
||||
pair{first=35,second=36}];
|
||||
target.value = indexed[1];
|
||||
check(&target, 35, 36);
|
||||
|
||||
let pointed: pair = pair{first=37,second=38};
|
||||
let sourcepointer: *pair = &pointed;
|
||||
target.value = *sourcepointer;
|
||||
check(&target, 37, 38);
|
||||
|
||||
globalsource.value.first = 41;
|
||||
globalsource.value.second = 42;
|
||||
globaltarget.guard = 91;
|
||||
globaltarget.value.first = 3;
|
||||
globaltarget.value.second = 4;
|
||||
globaltarget.tail = 92;
|
||||
globaltarget.value = globalsource.value;
|
||||
check(&globaltarget, 41, 42);
|
||||
};
|
||||
Reference in New Issue
Block a user