wcc/cgen: #59 append/insert struct-literal value eval-order — eval-to-scratch pre-grow + precise copy (both-stage)
append/insert of a struct-LITERAL value evaluated the literal's field
exprs AFTER the grow, so a field reading the destination (e.g. len(xs))
saw the grown length. Both stages, #263 gate-blind (cs==ww byte-identical,
both wrong — runtime is the only net). #50 fixed the scalar/boxing value
arm; the struct-lit arm still post-grew.
Fix (mirror #50, both stages): resolve the struct, fill the literal into a
fresh per-site scratch (@appendstructscr, sized esz, survives rt_ensure +
nested-append clobber) BEFORE the grow, then copy scratch -> post-grow slot.
The copy uses the precise descending 8/4/2/1 ladder (the proven N_IDENT
struct arm directly below), NOT a raw 8B-word block copy: a struct's size
rounds to maxalign (check.c:916), so a sub-8B struct packs at a 4/2/1B
slice stride and an 8B copy over-writes past the slot — at a power-of-2
capacity boundary that clobbers the adjacent allocation (heap corruption,
both stages). The ladder never reads past esz (no uninit high bytes) nor
writes past the slot; esz=8 stays a single MOVQ (byte-id preserved).
insert() rides by construction: both stages desugar it to append and
re-dispatch into this arm. The #49 aplace path already uses the precise
ladder (verified, not exposed). #59 closes the last composite-value
eval-order hole in append/insert.
Pin: 946_append_structlit_evalorder_run — append / insert / narrow-neighbor
(i32-field at the cap boundary with an adjacent-allocation survival assert)
rows, each base-fail at 39432f7 and post-pass with cs==ww byte-id.
This commit is contained in:
@@ -27470,16 +27470,16 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
cgappendgrow(c, sndirect, sn_off, snscr, esz);
|
||||
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
|
||||
if (vn.kind == nkind.N_STRUCTLIT) {
|
||||
// #59 (#50's eval-order kin): the literal's field
|
||||
// exprs still eval POST-grow here — filed, not
|
||||
// folded.
|
||||
let scroff: i32 = localadd(c, "@appendscr", 8, nil);
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(scroff: i64);
|
||||
emitline("(BP)\n");
|
||||
// #59 (#50's eval-order kin): resolve the struct
|
||||
// info, then fill the literal into a fresh
|
||||
// per-SITE scratch (must stay live across
|
||||
// rt_ensure, and a nested append in a field expr
|
||||
// would clobber a dedup'd slot — the @apptagscr
|
||||
// rationale, #25/#31) BEFORE the grow, so the
|
||||
// field exprs see the pre-grow len. Then grow,
|
||||
// slot, raw-copy scratch->slot (mirror the #50
|
||||
// tagged arm above).
|
||||
let esi: *structinfo = structlookupchain(c, etnode);
|
||||
if (esi == nil && !sndirect && esubnamed != nil) {
|
||||
// FA1: no declared tnode to chain through —
|
||||
@@ -27494,10 +27494,60 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
os.write(2, m34s.ptr, m34s.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgstructlitfill(c, esi, vn, 1, scroff, "", 0);
|
||||
let stscr: i32 = localalloc(c, "@appendstructscr", esz, nil);
|
||||
cgstructlitfillbp(c, esi, vn, stscr);
|
||||
cgappendgrow(c, sndirect, sn_off, snscr, esz);
|
||||
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
|
||||
// Descending 8/4/2/1 ladder, not an 8B-word
|
||||
// loop: a plain struct's esz rounds to maxalign
|
||||
// (check.c:916), not 8, so a sub-8B / non-8B-
|
||||
// multiple element packs at its own stride — an
|
||||
// 8B copy of the last slot writes past the slice
|
||||
// buffer (the tagged arm above is safe only
|
||||
// because boxes are 8B-padded; #59). Mirrors the
|
||||
// N_IDENT source arm below.
|
||||
let ck: i32 = 0;
|
||||
for (ck + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((stscr + ck): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(ck: i64, "BX");
|
||||
emitline("\n");
|
||||
ck += 8;
|
||||
};
|
||||
if (ck + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff((stscr + ck): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg(ck: i64, "BX");
|
||||
emitline("\n");
|
||||
ck += 4;
|
||||
};
|
||||
if (ck + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff((stscr + ck): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg(ck: i64, "BX");
|
||||
emitline("\n");
|
||||
ck += 2;
|
||||
};
|
||||
if (ck + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff((stscr + ck): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg(ck: i64, "BX");
|
||||
emitline("\n");
|
||||
ck += 1;
|
||||
};
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
cgappendgrow(c, sndirect, sn_off, snscr, esz);
|
||||
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
|
||||
if (vn.kind == nkind.N_IDENT) {
|
||||
let sl: *local = localfindnode(c, vn.str);
|
||||
if (sl == nil) {
|
||||
|
||||
Reference in New Issue
Block a user