w6c: fall to the place resolver for indexed dot-chain aggregate sources
The dot-chain SOURCE-address walkers (cg_dotchain_addr / dotchainaddr) deliberately have no index hop — their AX-clean spill contract serves the chained dotbase arm — so an aggregate copy from elem[k].field (N_DOT over N_INDEX) fell out of the walk. Every aggarg_srcaddr consumer loud-stopped (arg push #271, aggregate return #272, field assign, structlit fill, ident reassign) EXCEPT the indexed element-store arm, which left the walker's result UNCHECKED and copied through a stale SI — a silent both-stage miscompile (out[j] = hs[k].result read garbage). Task #6. Both stages, two twin edits each: the aggarg_srcaddr N_DOT arm falls back to cgplaceaddr (the C4/#40 place resolver, emission-free-on- failure so the fallback starts clean), and the indexed store site checks the walker before the same fallback. Still loud (filed follow-ups): auto-deref pointer-to-array indexed bases (hs: *[4]T; hs[k].field — cgplaceaddr's index arm takes no deref hop; the explicit (*hs)[k].field form works), and the bare N_INDEX slice-element aggregate source.
This commit is contained in:
@@ -2079,6 +2079,7 @@ cg_base_cap(Cg *c, Node *base, Type *bu, Local *locals, int dst)
|
|||||||
|
|
||||||
static void cgexpr(Cg*, Node*, Local*);
|
static void cgexpr(Cg*, Node*, Local*);
|
||||||
static void cgstmt(Cg*, Node*, Local**, int*);
|
static void cgstmt(Cg*, Node*, Local**, int*);
|
||||||
|
static int cgplaceaddr(Cg*, Node*, int, Local*);
|
||||||
static void cg_widen_tagged_push(Cg*, Local**, Type*, Node*, int);
|
static void cg_widen_tagged_push(Cg*, Local**, Type*, Node*, int);
|
||||||
static void cg_widen_tagged_store(Cg*, Local**, Type*, Node*, int, int, int);
|
static void cg_widen_tagged_store(Cg*, Local**, Type*, Node*, int, int, int);
|
||||||
static void cg_widen_tag_remap(Cg*, Type*, Type*, int);
|
static void cg_widen_tag_remap(Cg*, Type*, Type*, int);
|
||||||
@@ -2278,8 +2279,16 @@ aggarg_srcaddr(Cg *c, Node *src, int dst, Local *locals)
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (src->kind == N_DOT)
|
if (src->kind == N_DOT) {
|
||||||
return cg_dotchain_addr(c, src, dst, locals);
|
if (cg_dotchain_addr(c, src, dst, locals))
|
||||||
|
return 1;
|
||||||
|
/* task #6: an N_INDEX link inside the dot chain
|
||||||
|
* (handles[k].result) — the chain walker has no index hop
|
||||||
|
* (AX-clean spill contract for cg_dotbase_addr); the place
|
||||||
|
* resolver is the C4/#40 fallback, and the failed walk is
|
||||||
|
* emission-free so the fallback starts clean. */
|
||||||
|
return cgplaceaddr(c, src, dst, locals);
|
||||||
|
}
|
||||||
if (src->kind == N_INDEX) {
|
if (src->kind == N_INDEX) {
|
||||||
Node *base = src->lhs;
|
Node *base = src->lhs;
|
||||||
Node *idx = src->rhs;
|
Node *idx = src->rhs;
|
||||||
@@ -7373,11 +7382,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins2(c, A_LEAQ,
|
ins2(c, A_LEAQ,
|
||||||
masym(c, n->rhs->str),
|
masym(c, n->rhs->str),
|
||||||
areg(D_SI));
|
areg(D_SI));
|
||||||
} else if (n->rhs->kind == N_DOT) {
|
} else {
|
||||||
cg_dotchain_addr(c, n->rhs, D_SI, locals);
|
/* task #6: the chain walker declines an
|
||||||
} else if (!cgplaceaddr(c, n->rhs, D_SI, locals)) {
|
* N_INDEX link (emission-free) — its
|
||||||
fatal("indexed aggregate assignment source "
|
* result was unchecked here, so the copy
|
||||||
"unresolved");
|
* below read through a stale SI. Fall to
|
||||||
|
* the generic place resolver. */
|
||||||
|
int dok = 0;
|
||||||
|
if (n->rhs->kind == N_DOT)
|
||||||
|
dok = cg_dotchain_addr(c,
|
||||||
|
n->rhs, D_SI, locals);
|
||||||
|
if (!dok && !cgplaceaddr(c, n->rhs,
|
||||||
|
D_SI, locals))
|
||||||
|
fatal("indexed aggregate "
|
||||||
|
"assignment source "
|
||||||
|
"unresolved");
|
||||||
}
|
}
|
||||||
ins1(c, A_POPQ, areg(D_BX)); /* dest */
|
ins1(c, A_POPQ, areg(D_BX)); /* dest */
|
||||||
int k = 0;
|
int k = 0;
|
||||||
|
|||||||
@@ -1689,7 +1689,13 @@ fn aggargsrcaddr(c: *cgen, src: *syntax.node, dst: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if (src.kind == syntax.nkind.N_DOT) {
|
if (src.kind == syntax.nkind.N_DOT) {
|
||||||
return dotchainaddr(c, src, dst);
|
if (dotchainaddr(c, src, dst)) { return true; };
|
||||||
|
// task #6: an N_INDEX link inside the dot chain
|
||||||
|
// (handles[k].result) — the chain walker has no index hop
|
||||||
|
// (AX-clean spill contract for dotbaseaddr); the place
|
||||||
|
// resolver is the C4/#40 fallback, and the failed walk is
|
||||||
|
// emission-free so the fallback starts clean.
|
||||||
|
return cgplaceaddr(c, src, dst);
|
||||||
};
|
};
|
||||||
if (src.kind == syntax.nkind.N_INDEX) {
|
if (src.kind == syntax.nkind.N_INDEX) {
|
||||||
let base: *syntax.node = src.lhs;
|
let base: *syntax.node = src.lhs;
|
||||||
@@ -9778,15 +9784,26 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
|||||||
emitsymname(c, n.rhs.str);
|
emitsymname(c, n.rhs.str);
|
||||||
emitline("(SB), SI\n");
|
emitline("(SB), SI\n");
|
||||||
};
|
};
|
||||||
} else { if (n.rhs.kind == syntax.nkind.N_DOT) {
|
|
||||||
dotchainaddr(c, n.rhs, "SI");
|
|
||||||
} else {
|
} else {
|
||||||
if (!cgplaceaddr(c, n.rhs, "SI")) {
|
// task #6: the chain walker
|
||||||
let msrc: str = "indexed aggregate assignment source unresolved\n";
|
// declines an N_INDEX link
|
||||||
os.write(2, msrc.ptr, msrc.len: u64);
|
// (emission-free) — its result
|
||||||
os.exit(1);
|
// was unchecked here, so the
|
||||||
|
// copy below read through a
|
||||||
|
// stale SI. Fall to the
|
||||||
|
// generic place resolver.
|
||||||
|
let dok: bool = false;
|
||||||
|
if (n.rhs.kind == syntax.nkind.N_DOT) {
|
||||||
|
dok = dotchainaddr(c, n.rhs, "SI");
|
||||||
};
|
};
|
||||||
};};};
|
if (!dok) {
|
||||||
|
if (!cgplaceaddr(c, n.rhs, "SI")) {
|
||||||
|
let msrc: str = "indexed aggregate assignment source unresolved\n";
|
||||||
|
os.write(2, msrc.ptr, msrc.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};};
|
||||||
emitline("\tPOPQ\tBX\n"); // dest
|
emitline("\tPOPQ\tBX\n"); // dest
|
||||||
let kc: i32 = 0;
|
let kc: i32 = 0;
|
||||||
for (kc + 8 <= esz) {
|
for (kc + 8 <= esz) {
|
||||||
|
|||||||
133
test/lang/idxdot_aggcopy_test.ww
Normal file
133
test/lang/idxdot_aggcopy_test.ww
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// idxdot_aggcopy_test — aggregate copies whose SOURCE is an
|
||||||
|
// indexed-then-dotted chain (elem[k].field). The dot-chain address
|
||||||
|
// walker has no index hop (AX-clean spill contract), so these shapes
|
||||||
|
// ride the generic place-resolver fallback (task #6); the indexed
|
||||||
|
// element-store row pins the once-SILENT unchecked-walker site that
|
||||||
|
// copied through a stale SI in both stages.
|
||||||
|
|
||||||
|
package idxdot_aggcopy_test;
|
||||||
|
|
||||||
|
type inner = struct {
|
||||||
|
a: i64,
|
||||||
|
b: i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
type handle = struct {
|
||||||
|
id: i64,
|
||||||
|
result: inner,
|
||||||
|
};
|
||||||
|
|
||||||
|
type mid = struct {
|
||||||
|
pad: i64,
|
||||||
|
in_: inner,
|
||||||
|
};
|
||||||
|
|
||||||
|
type outer = struct {
|
||||||
|
tag: i64,
|
||||||
|
m: mid,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn mk() [4]handle = {
|
||||||
|
let hs: [4]handle;
|
||||||
|
hs[0] = handle{id = 1, result = inner{a = 1, b = 2}};
|
||||||
|
hs[1] = handle{id = 2, result = inner{a = 3, b = 4}};
|
||||||
|
hs[2] = handle{id = 9, result = inner{a = 11, b = 31}};
|
||||||
|
hs[3] = handle{id = 4, result = inner{a = 5, b = 6}};
|
||||||
|
return hs;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn use(v: inner) i64 = {
|
||||||
|
return v.a + v.b;
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn letcopy() void = {
|
||||||
|
let hs: [4]handle = mk();
|
||||||
|
let k: i64 = 2;
|
||||||
|
let r: inner = hs[k].result;
|
||||||
|
assert(r.a == 11);
|
||||||
|
assert(r.b == 31);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn callarg() void = {
|
||||||
|
let hs: [4]handle = mk();
|
||||||
|
let k: i64 = 2;
|
||||||
|
assert(use(hs[k].result) == 42);
|
||||||
|
};
|
||||||
|
|
||||||
|
type bag = struct {
|
||||||
|
n: i64,
|
||||||
|
res: inner,
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn fieldassign() void = {
|
||||||
|
let hs: [4]handle = mk();
|
||||||
|
let k: i64 = 2;
|
||||||
|
let g: bag;
|
||||||
|
g.n = 0;
|
||||||
|
g.res = hs[k].result;
|
||||||
|
assert(g.res.a == 11);
|
||||||
|
assert(g.res.b == 31);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn pickresult(hs: *[4]handle, k: i64) inner = {
|
||||||
|
return (*hs)[k].result;
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn aggreturn() void = {
|
||||||
|
let hs: [4]handle = mk();
|
||||||
|
let r: inner = pickresult(&hs, 2);
|
||||||
|
assert(r.a == 11);
|
||||||
|
assert(r.b == 31);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn slicebase() void = {
|
||||||
|
let hs: [4]handle = mk();
|
||||||
|
let sl: []handle = hs[0:4];
|
||||||
|
let k: i64 = 2;
|
||||||
|
assert(use(sl[k].result) == 42);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn nesteddot() void = {
|
||||||
|
let os_: [3]outer;
|
||||||
|
os_[1] = outer{tag = 7, m = mid{pad = 0, in_ = inner{a = 20, b = 22}}};
|
||||||
|
let k: i64 = 1;
|
||||||
|
let l: inner = os_[k].m.in_;
|
||||||
|
assert(l.a == 20);
|
||||||
|
assert(l.b == 22);
|
||||||
|
};
|
||||||
|
|
||||||
|
// the once-silent shape: indexed element STORE from an indexed-dotted
|
||||||
|
// source (unchecked walker left SI stale; both stages copied garbage).
|
||||||
|
@test fn indexedstore() void = {
|
||||||
|
let hs: [4]handle = mk();
|
||||||
|
let out: [3]inner;
|
||||||
|
out[0] = inner{a = 1, b = 1};
|
||||||
|
out[1] = inner{a = 2, b = 2};
|
||||||
|
out[2] = inner{a = 3, b = 3};
|
||||||
|
let k: i64 = 2;
|
||||||
|
let j: i64 = 1;
|
||||||
|
out[j] = hs[k].result;
|
||||||
|
assert(out[1].a == 11);
|
||||||
|
assert(out[1].b == 31);
|
||||||
|
assert(out[0].a == 1);
|
||||||
|
assert(out[2].a == 3);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn identreassign() void = {
|
||||||
|
let hs: [4]handle = mk();
|
||||||
|
let k: i64 = 2;
|
||||||
|
let r: inner = inner{a = 0, b = 0};
|
||||||
|
r = hs[k].result;
|
||||||
|
assert(r.a == 11);
|
||||||
|
assert(r.b == 31);
|
||||||
|
};
|
||||||
|
|
||||||
|
// control: the LHS twin (store INTO elem[k].field) was already wired.
|
||||||
|
@test fn lhsstore() void = {
|
||||||
|
let hs: [4]handle = mk();
|
||||||
|
let k: i64 = 0;
|
||||||
|
let r: inner = inner{a = 40, b = 41};
|
||||||
|
hs[k].result = r;
|
||||||
|
assert(hs[0].result.a == 40);
|
||||||
|
assert(hs[0].result.b == 41);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user