w6c+w6c_ww: append() stores the full element width per element kind (fix #34)
Both stages lowered the append element store as one sized mov from AX — correct only for scalars <= 8B. A str/slice element kept only .ptr (byte-id-blind), a tagged element got its raw payload written into the tag slot (the #12 pathology, no boxing), a struct element kept only its first qword. wwstage additionally fed rt_ensure membsz from bare elemsizeof, whose 8-sentinel under-allocated and mis-strided named tagged/struct elements (the #8 family; cs!=ww on the SI imm + stride). Fix, keyed on the DECLARED slice local's element type (cstage su->sub->size as before; wwstage elemsizeofc off the stamped tnode — never the value node, the #25/#31 esz=0 trap), applied to both the single-value and spread bodies (2 arms x 2 stages): - scalar 1/2/4/8: untouched (u8 asm byte-identical to pre-fix). - str/slice: AX/BX/CX pushed across rt_ensure, dst in DX (BX holds the element .len after the pops — the #24 register discipline), 3-word store. - tagged: grow first, dst -> BX, box via the #12 widen choke-point (cg_widen_tagged_store / cgwidentaggedstore via_outer). - struct: grow first; literal -> dst spilled to per-fn @appendscr (cached on cstage to mirror wwstage's @-prefix localadd dedup) + structlit fill DST_PTR_LOCAL; local ident -> word-copy; any other source shape is a rule-7 loud-stop, never a silent scalar fall-through. struct-from-call deferred. - spread: the source element is already a fully-formed T (tag included), so the wide arm grows first and whole-width word-copies &items[i] -> dst, recomputing both addresses from the slice headers after the possibly-reallocating rt_ensure. The elemsizeofc swap also corrects the named-scalar-alias membsz (wwstage fed SI=$8 where cstage fed $4); no in-tree consumer appended to such a slice, so nothing was riding the wrong 8 (lib/selfhost append sites are all u8). Test 800_append_wide_elem: 13 rows (runtime readback per kind, 2-append realloc survival, spread str+tagged, @appendscr dedup, enum-alias esz, loud-stop build-fail) + per-row cs==ww byte-id, which subsumes the frame canary.
This commit is contained in:
@@ -4107,7 +4107,12 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
let snlocal: *local = localfindnode(c, sn.str);
|
||||
if (snlocal == nil) { return; };
|
||||
let sn_off: i32 = snlocal.off;
|
||||
let esz: i32 = elemsizeof(snlocal.tnode);
|
||||
// #34: esz off the DECLARED slice local's stamped tnode via
|
||||
// elemsizeofc — bare elemsizeof returns the 8 sentinel for a
|
||||
// named tagged/struct element (the #8 family; cgappend was never
|
||||
// upgraded), under-feeding rt_ensure's membsz AND mis-striding
|
||||
// the slot index vs cstage's su->sub->size.
|
||||
let esz: i32 = elemsizeofc(c, snlocal.tnode);
|
||||
let etnode: *node = nil;
|
||||
if (snlocal.tnode != nil) {
|
||||
let stk: nkind = snlocal.tnode.kind;
|
||||
@@ -4116,6 +4121,30 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; };
|
||||
};
|
||||
let store_op: str = tnodestoreop(c, etnode, esz);
|
||||
// #34 element-kind store dispatch: the scalar 1-word store below
|
||||
// silently gutted every wide element (str/slice 24B header,
|
||||
// tagged box, struct body). Kind off the stamped slice tinfo —
|
||||
// the value node's literal tinfo is the #25/#31 esz=0 trap.
|
||||
// Mirrors cstage cgen.c's append arm + the #270/#12/#20
|
||||
// array-literal element dispatch (cgarrlitfillbp).
|
||||
let sti: *tinfo = nil;
|
||||
if (snlocal.tnode != nil) { sti = snlocal.tnode.type_: *tinfo; };
|
||||
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
|
||||
let esubti: *tinfo = nil;
|
||||
if (sti != nil) { esubti = sti.sub; };
|
||||
for (esubti != nil && esubti.kind == tykind.TY_NAMED) {
|
||||
esubti = esubti.under;
|
||||
};
|
||||
let elstr: bool = esubti != nil && esubti.kind == tykind.TY_STR;
|
||||
let elslice: bool = esubti != nil && esubti.kind == tykind.TY_SLICE;
|
||||
let eltagged: bool = esubti != nil && esubti.kind == tykind.TY_TAGGED;
|
||||
let elstruct: bool = esubti != nil && esubti.kind == tykind.TY_STRUCT;
|
||||
let elwide: bool = elstr || elslice || eltagged || elstruct;
|
||||
if (!elwide && esz > 8) {
|
||||
let m34k: str = "#34: append() element kind unsupported (rule-7)\n";
|
||||
os.write(2, m34k.ptr, m34k.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
|
||||
let vn: *node = sn.next;
|
||||
for (vn != nil) {
|
||||
@@ -4138,6 +4167,93 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), DX\n");
|
||||
emitline("\tCMPQ\tDX, CX\n");
|
||||
emitline("\tJGE\t"); emitline(le); emitline("\n");
|
||||
if (elwide) {
|
||||
// #34: a spread element is already a fully-formed
|
||||
// T in the source slice (tag included), so a
|
||||
// whole-width word-copy is the store — no boxing.
|
||||
// Grow FIRST: rt_ensure may realloc, so both
|
||||
// addresses are recomputed from the slice headers
|
||||
// after the call (i reloads from the counter
|
||||
// slot; CX was clobbered).
|
||||
emitline("\tADDQ\t$1, ");
|
||||
emitoff((sn_off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(sn_off: i64);
|
||||
emitline("(BP), DI\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", SI\n");
|
||||
emitline("\tCALL\trt_ensure(SB)\n");
|
||||
emitline("\tMOVQ\t(SP), CX\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tADDQ\tCX, BX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((sn_off + 8): i64);
|
||||
emitline("(BP), CX\n");
|
||||
emitline("\tSUBQ\t$1, CX\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(sn_off: i64);
|
||||
emitline("(BP), DX\n");
|
||||
emitline("\tADDQ\tCX, DX\n");
|
||||
let wk: i32 = 0;
|
||||
for (wk + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(wk: i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(wk: i64, "DX");
|
||||
emitline("\n");
|
||||
wk += 8;
|
||||
};
|
||||
if (wk + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitdispreg(wk: i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg(wk: i64, "DX");
|
||||
emitline("\n");
|
||||
wk += 4;
|
||||
};
|
||||
if (wk + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitdispreg(wk: i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg(wk: i64, "DX");
|
||||
emitline("\n");
|
||||
wk += 2;
|
||||
};
|
||||
if (wk + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitdispreg(wk: i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg(wk: i64, "DX");
|
||||
emitline("\n");
|
||||
wk += 1;
|
||||
};
|
||||
emitline("\tADDQ\t$1, (SP)\n");
|
||||
emitline("\tJMP\t"); emitline(ll); emitline("\n");
|
||||
emitlabel(le);
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
@@ -4183,6 +4299,147 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
if (elstr || elslice) {
|
||||
// #34: 24B {ptr,len,cap} header. cgexpr leaves
|
||||
// AX/BX/CX; all three must survive rt_ensure. dst
|
||||
// lands in DX, NOT BX — the pops put the element
|
||||
// .len back in BX (the #24 register discipline).
|
||||
cgexpr(c, vn);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tADDQ\t$1, ");
|
||||
emitoff((sn_off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(sn_off: i64);
|
||||
emitline("(BP), DI\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", SI\n");
|
||||
emitline("\tCALL\trt_ensure(SB)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((sn_off + 8): i64);
|
||||
emitline("(BP), CX\n");
|
||||
emitline("\tSUBQ\t$1, CX\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(sn_off: i64);
|
||||
emitline("(BP), DX\n");
|
||||
emitline("\tADDQ\tCX, DX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tMOVQ\tAX, (DX)\n");
|
||||
emitline("\tMOVQ\tBX, 8(DX)\n");
|
||||
emitline("\tMOVQ\tCX, 16(DX)\n");
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
if (eltagged || elstruct) {
|
||||
// #34: no register form survives rt_ensure for these —
|
||||
// grow FIRST, then fill through the dst pointer
|
||||
// (tagged: the #12 widen choke-point cgexprs the value
|
||||
// internally; struct: literal fill / ident word-copy).
|
||||
emitline("\tADDQ\t$1, ");
|
||||
emitoff((sn_off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(sn_off: i64);
|
||||
emitline("(BP), DI\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", SI\n");
|
||||
emitline("\tCALL\trt_ensure(SB)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((sn_off + 8): i64);
|
||||
emitline("(BP), CX\n");
|
||||
emitline("\tSUBQ\t$1, CX\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(sn_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tADDQ\tCX, BX\n");
|
||||
if (eltagged) {
|
||||
cgwidentaggedstore(c, esubti, vn, "BX", 0, esz);
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
if (vn.kind == nkind.N_STRUCTLIT) {
|
||||
let scroff: i32 = localadd(c, "@appendscr", 8, nil);
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(scroff: i64);
|
||||
emitline("(BP)\n");
|
||||
let esi: *structinfo = structlookupchain(c, etnode);
|
||||
if (esi == nil) {
|
||||
let m34s: str = "#34: append() struct element has no structinfo (rule-7)\n";
|
||||
os.write(2, m34s.ptr, m34s.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgstructlitfill(c, esi, vn, 1, scroff, "", 0);
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
if (vn.kind == nkind.N_IDENT) {
|
||||
let sl: *local = localfindnode(c, vn.str);
|
||||
if (sl == nil) {
|
||||
let m34i: str = "#34: append() struct element source ident is not a local (rule-7)\n";
|
||||
os.write(2, m34i.ptr, m34i.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let soff: i32 = sl.off;
|
||||
let ck: i32 = 0;
|
||||
for (ck + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((soff + 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((soff + 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((soff + 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((soff + ck): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg(ck: i64, "BX");
|
||||
emitline("\n");
|
||||
ck += 1;
|
||||
};
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
let m34e: str = "#34: append() struct element source shape unsupported (rule-7)\n";
|
||||
os.write(2, m34e.ptr, m34e.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgexpr(c, vn);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
emitline("\tADDQ\t$1, ");
|
||||
|
||||
Reference in New Issue
Block a user