wcc+w6c_ww: append through pointer-to-slice place via cgplaceaddr (FA1)

Re-key the append() lowering from BP-displacement assumptions onto a
resolver-provided header PLACE (task #15, the add_thread hard-blocker;
cgplaceaddr's third consumer after C1/C1.25). One mirrored choke-point,
two failure modes: cstage 0-defaulted sn_off for any non-ident target,
so 0(BP)/8(BP) became the "slice header" and rt_ensure corrupted the
CALLER frame (SIGSEGV); wwstage cgappend silently emitted nothing
(gate-blind cs!=ww).

cg_append_grow/cg_append_slot (mirror cgappendgrow/cgappendslot) factor
the 5 grow + 5 slot header-access sites. Ident-local targets keep the
legacy BP-disp emission byte-identical (probed across all 9 existing
source shapes, before/after .s). Non-ident targets resolve once through
cgplaceaddr and spill the header address to an @apphdrscr slot:
rt_ensure may realloc .ptr but never moves the header, so the slot
stays valid; every access reloads from it. The slot is allocated fresh
per append SITE, not cached per fn: a nested append-through-pointer
inside a value expression (match-yield arm) spills its own resolve, and
a shared slot would hand the outer grow/slot reloads the inner target's
header — silent cross-slice corruption (pinned by the reentrant_value
row). Indirect mode keys esz/element-kind/load-op off the
checker-stamped target tinfo (no declared tnode behind `*p`; the
#209/#211 discipline). Unwired target places die LOUD "#15: append()
target place unsupported (rule-7)" on BOTH stages — the
silent-corruption class is closed by construction.

The FA4/#35 boundary is unchanged: non-ident spread SOURCES stay loud
(pinned by a reject row). Surfaced pre-existing checker divergence
filed as task #34 (wwstage rejects global slice-lit let).

test/wcc/806: 14 runtime rows (element kinds x target shapes, spread,
narrow-signed spread load, cap-crossing realloc loop with branched
callee + caller-frame sentinels, deref-spine target, nested-append
reentrancy, direct-arm neutrality pin) + 2 exact-text reject rows,
both drivers + per-row cs==ww asm byte-id.
This commit is contained in:
2026-06-04 10:25:56 +09:00
parent 48df04a8ca
commit b630a7cf20
6 changed files with 1186 additions and 463 deletions

View File

@@ -24402,6 +24402,77 @@ fn cgalloc(c: *cgen, n: *node) void = {
emitlabel(donel);
};
// cgappendgrow — FA1 (#15): append() header-place grow, cgplaceaddr's
// append consumer. direct = ident-local header in the frame (BP-disp —
// the legacy emission, kept byte-identical); indirect = header address
// pre-spilled to @apphdrscr by the resolver. len+=1, &hdr→DI, esz→SI,
// CALL rt_ensure. In indirect mode the len bump goes through DI so the
// loaded address doubles as the call argument. Mirrors cstage
// cg_append_grow.
fn cgappendgrow(c: *cgen, direct: bool, off: i32, scr: i32, esz: i32) void = {
if (direct) {
emitline("\tADDQ\t$1, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
emitline("\tLEAQ\t");
emitoff(off: i64);
emitline("(BP), DI\n");
} else {
emitline("\tMOVQ\t");
emitoff(scr: i64);
emitline("(BP), DI\n");
emitline("\tADDQ\t$1, 8(DI)\n");
};
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", SI\n");
emitline("\tCALL\trt_ensure(SB)\n");
};
// cgappendslot — post-rt_ensure slot address: CX = (len-1)*esz,
// dst = .ptr + CX. Clobbers AX (the IMUL immediate) and CX, like the
// emission it replaces; dst must not be AX or CX. Mirrors cstage
// cg_append_slot.
fn cgappendslot(c: *cgen, direct: bool, off: i32, scr: i32, esz: i32, dst: str) void = {
if (direct) {
emitline("\tMOVQ\t");
emitoff((off + 8): i64);
emitline("(BP), CX\n");
} else {
emitline("\tMOVQ\t");
emitoff(scr: i64);
emitline("(BP), ");
emitline(dst);
emitline("\n");
emitline("\tMOVQ\t8(");
emitline(dst);
emitline("), 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");
};
if (direct) {
emitline("\tMOVQ\t");
emitoff(off: i64);
emitline("(BP), ");
emitline(dst);
emitline("\n");
} else {
emitline("\tMOVQ\t(");
emitline(dst);
emitline("), ");
emitline(dst);
emitline("\n");
};
emitline("\tADDQ\tCX, ");
emitline(dst);
emitline("\n");
};
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
// Each value gets:
@@ -24422,22 +24493,52 @@ fn cgalloc(c: *cgen, n: *node) void = {
fn cgappend(c: *cgen, n: *node) void = {
let sn: *node = n.list;
if (sn == nil) { return; };
if (sn.kind != nkind.N_IDENT) { return; };
let snlocal: *local = localfindnode(c, sn.str);
if (snlocal == nil) { return; };
let sn_off: i32 = snlocal.off;
// #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);
// FA1 (#15): the old `sn.kind != N_IDENT return` and
// `snlocal == nil → return` gates were SILENT zero-emission
// (gate-blind cs≠ww: cstage 0-defaulted the header base and
// corrupted the caller frame instead). A non-ident-local target
// now resolves its header address through cgplaceaddr; a shape
// the resolver can't address is loud.
let sndirect: bool = false;
let sn_off: i32 = 0;
let snlocal: *local = nil;
if (sn.kind == nkind.N_IDENT) {
snlocal = localfindnode(c, sn.str);
if (snlocal != nil) {
sndirect = true;
sn_off = snlocal.off;
};
};
let esz: i32 = 0;
let etnode: *node = nil;
if (snlocal.tnode != nil) {
let stk: nkind = snlocal.tnode.kind;
if (stk == nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; };
let sti: *tinfo = nil;
if (sndirect) {
// #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.
esz = elemsizeofc(c, snlocal.tnode);
if (snlocal.tnode != nil) {
let stk: nkind = snlocal.tnode.kind;
if (stk == nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; };
sti = snlocal.tnode.type_: *tinfo;
};
} else {
// FA1: `*p` has no declared tnode — key esz/element kind off
// the checker-STAMPED target tinfo (#209/#211 discipline), the
// same source cstage reads (sn->type → su->sub->size).
sti = sn.type_: *tinfo;
let fsti: *tinfo = sti;
for (fsti != nil && fsti.kind == tykind.TY_NAMED) { fsti = fsti.under; };
if (fsti != nil && fsti.sub != nil) { esz = fsti.sub.size: i32; };
if (esz <= 0) {
let m15z: str = "#15: append() target element size unresolved (rule-7)\n";
os.write(2, m15z.ptr, m15z.len: u64);
os.exit(1);
};
};
let store_op: str = tnodestoreop(c, etnode, esz);
// #34 element-kind store dispatch: the scalar 1-word store below
@@ -24446,11 +24547,13 @@ fn cgappend(c: *cgen, n: *node) void = {
// 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; };
// FA1: pre-peel handle — the indirect struct-lit fill keys its
// structinfo off the NAMED element tinfo's name (the same leaf
// structlookupchain resolves from the declared tnode).
let esubnamed: *tinfo = esubti;
for (esubti != nil && esubti.kind == tykind.TY_NAMED) {
esubti = esubti.under;
};
@@ -24464,6 +24567,26 @@ fn cgappend(c: *cgen, n: *node) void = {
os.write(2, m34k.ptr, m34k.len: u64);
os.exit(1);
};
let snscr: i32 = 0;
if (!sndirect) {
if (!cgplaceaddr(c, sn, "BX")) {
let m15p: str = "#15: append() target place unsupported (rule-7)\n";
os.write(2, m15p.ptr, m15p.len: u64);
os.exit(1);
};
// Spill across rt_ensure: realloc moves .ptr, never the
// header, so the slot stays valid for every later reload.
// Fresh slot per SITE via localalloc (NOT localadd: its `@`
// dedup would share one slot per fn, and a nested
// append-through-pointer inside a value expression —
// match-yield arm — would clobber the outer's spilled header
// address: silent cross-slice corruption. Mirrors cstage's
// never-deduping local_alloc at the same point.)
snscr = localalloc(c, "@apphdrscr", 8, nil);
emitline("\tMOVQ\tBX, ");
emitoff(snscr: i64);
emitline("(BP)\n");
};
let vn: *node = sn.next;
for (vn != nil) {
@@ -24487,6 +24610,12 @@ fn cgappend(c: *cgen, n: *node) void = {
};
let it_off: i32 = itlocal.off;
let load_op: str = tnodeloadop(c, etnode, esz);
if (!sndirect) {
// FA1: no etnode behind `*p` — signedness off the
// stamped element tinfo, the predicate cstage's
// fldloadop applies to su->sub.
load_op = loadopsz(typeissigned(esubti), esz);
};
emitline("\tSUBQ\t$8, SP\n");
emitline("\tMOVQ\t$0, (SP)\n");
let ll: str = mklabel(c, "spr_l");
@@ -24506,16 +24635,7 @@ fn cgappend(c: *cgen, n: *node) void = {
// 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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
emitline("\tMOVQ\t(SP), CX\n");
if (esz > 1) {
emitline("\tMOVQ\t$");
@@ -24527,20 +24647,7 @@ fn cgappend(c: *cgen, n: *node) void = {
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");
cgappendslot(c, sndirect, sn_off, snscr, esz, "DX");
let wk: i32 = 0;
for (wk + 8 <= esz) {
emitline("\tMOVQ\t");
@@ -24597,30 +24704,8 @@ fn cgappend(c: *cgen, n: *node) void = {
emitline("\tADDQ\tCX, BX\n");
emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n");
emitline("\tPUSHQ\tAX\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");
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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
emitline("\tPOPQ\tAX\n");
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
emitline("\tADDQ\t$1, (SP)\n");
@@ -24639,28 +24724,8 @@ fn cgappend(c: *cgen, n: *node) void = {
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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "DX");
emitline("\tPOPQ\tCX\n");
emitline("\tPOPQ\tBX\n");
emitline("\tPOPQ\tAX\n");
@@ -24675,30 +24740,8 @@ fn cgappend(c: *cgen, n: *node) void = {
// 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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
if (eltagged) {
cgwidentaggedstore(c, esubti, vn, "BX", 0, esz);
vn = vn.next;
@@ -24710,6 +24753,14 @@ fn cgappend(c: *cgen, n: *node) void = {
emitoff(scroff: i64);
emitline("(BP)\n");
let esi: *structinfo = structlookupchain(c, etnode);
if (esi == nil && !sndirect && esubnamed != nil) {
// FA1: no declared tnode to chain through —
// the stamped NAMED element tinfo carries the
// same leaf structlookupchain would resolve.
if (esubnamed.kind == tykind.TY_NAMED) {
esi = structlookup(c, esubnamed.name);
};
};
if (esi == nil) {
let m34s: str = "#34: append() struct element has no structinfo (rule-7)\n";
os.write(2, m34s.ptr, m34s.len: u64);
@@ -24773,30 +24824,8 @@ fn cgappend(c: *cgen, n: *node) void = {
};
cgexpr(c, vn);
emitline("\tPUSHQ\tAX\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");
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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
emitline("\tPOPQ\tAX\n");
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
vn = vn.next;

View File

@@ -4324,6 +4324,77 @@ fn cgalloc(c: *cgen, n: *node) void = {
emitlabel(donel);
};
// cgappendgrow — FA1 (#15): append() header-place grow, cgplaceaddr's
// append consumer. direct = ident-local header in the frame (BP-disp —
// the legacy emission, kept byte-identical); indirect = header address
// pre-spilled to @apphdrscr by the resolver. len+=1, &hdr→DI, esz→SI,
// CALL rt_ensure. In indirect mode the len bump goes through DI so the
// loaded address doubles as the call argument. Mirrors cstage
// cg_append_grow.
fn cgappendgrow(c: *cgen, direct: bool, off: i32, scr: i32, esz: i32) void = {
if (direct) {
emitline("\tADDQ\t$1, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
emitline("\tLEAQ\t");
emitoff(off: i64);
emitline("(BP), DI\n");
} else {
emitline("\tMOVQ\t");
emitoff(scr: i64);
emitline("(BP), DI\n");
emitline("\tADDQ\t$1, 8(DI)\n");
};
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", SI\n");
emitline("\tCALL\trt_ensure(SB)\n");
};
// cgappendslot — post-rt_ensure slot address: CX = (len-1)*esz,
// dst = .ptr + CX. Clobbers AX (the IMUL immediate) and CX, like the
// emission it replaces; dst must not be AX or CX. Mirrors cstage
// cg_append_slot.
fn cgappendslot(c: *cgen, direct: bool, off: i32, scr: i32, esz: i32, dst: str) void = {
if (direct) {
emitline("\tMOVQ\t");
emitoff((off + 8): i64);
emitline("(BP), CX\n");
} else {
emitline("\tMOVQ\t");
emitoff(scr: i64);
emitline("(BP), ");
emitline(dst);
emitline("\n");
emitline("\tMOVQ\t8(");
emitline(dst);
emitline("), 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");
};
if (direct) {
emitline("\tMOVQ\t");
emitoff(off: i64);
emitline("(BP), ");
emitline(dst);
emitline("\n");
} else {
emitline("\tMOVQ\t(");
emitline(dst);
emitline("), ");
emitline(dst);
emitline("\n");
};
emitline("\tADDQ\tCX, ");
emitline(dst);
emitline("\n");
};
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
// Each value gets:
@@ -4344,22 +4415,52 @@ fn cgalloc(c: *cgen, n: *node) void = {
fn cgappend(c: *cgen, n: *node) void = {
let sn: *node = n.list;
if (sn == nil) { return; };
if (sn.kind != nkind.N_IDENT) { return; };
let snlocal: *local = localfindnode(c, sn.str);
if (snlocal == nil) { return; };
let sn_off: i32 = snlocal.off;
// #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);
// FA1 (#15): the old `sn.kind != N_IDENT return` and
// `snlocal == nil → return` gates were SILENT zero-emission
// (gate-blind cs≠ww: cstage 0-defaulted the header base and
// corrupted the caller frame instead). A non-ident-local target
// now resolves its header address through cgplaceaddr; a shape
// the resolver can't address is loud.
let sndirect: bool = false;
let sn_off: i32 = 0;
let snlocal: *local = nil;
if (sn.kind == nkind.N_IDENT) {
snlocal = localfindnode(c, sn.str);
if (snlocal != nil) {
sndirect = true;
sn_off = snlocal.off;
};
};
let esz: i32 = 0;
let etnode: *node = nil;
if (snlocal.tnode != nil) {
let stk: nkind = snlocal.tnode.kind;
if (stk == nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; };
let sti: *tinfo = nil;
if (sndirect) {
// #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.
esz = elemsizeofc(c, snlocal.tnode);
if (snlocal.tnode != nil) {
let stk: nkind = snlocal.tnode.kind;
if (stk == nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; };
sti = snlocal.tnode.type_: *tinfo;
};
} else {
// FA1: `*p` has no declared tnode — key esz/element kind off
// the checker-STAMPED target tinfo (#209/#211 discipline), the
// same source cstage reads (sn->type → su->sub->size).
sti = sn.type_: *tinfo;
let fsti: *tinfo = sti;
for (fsti != nil && fsti.kind == tykind.TY_NAMED) { fsti = fsti.under; };
if (fsti != nil && fsti.sub != nil) { esz = fsti.sub.size: i32; };
if (esz <= 0) {
let m15z: str = "#15: append() target element size unresolved (rule-7)\n";
os.write(2, m15z.ptr, m15z.len: u64);
os.exit(1);
};
};
let store_op: str = tnodestoreop(c, etnode, esz);
// #34 element-kind store dispatch: the scalar 1-word store below
@@ -4368,11 +4469,13 @@ fn cgappend(c: *cgen, n: *node) void = {
// 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; };
// FA1: pre-peel handle — the indirect struct-lit fill keys its
// structinfo off the NAMED element tinfo's name (the same leaf
// structlookupchain resolves from the declared tnode).
let esubnamed: *tinfo = esubti;
for (esubti != nil && esubti.kind == tykind.TY_NAMED) {
esubti = esubti.under;
};
@@ -4386,6 +4489,26 @@ fn cgappend(c: *cgen, n: *node) void = {
os.write(2, m34k.ptr, m34k.len: u64);
os.exit(1);
};
let snscr: i32 = 0;
if (!sndirect) {
if (!cgplaceaddr(c, sn, "BX")) {
let m15p: str = "#15: append() target place unsupported (rule-7)\n";
os.write(2, m15p.ptr, m15p.len: u64);
os.exit(1);
};
// Spill across rt_ensure: realloc moves .ptr, never the
// header, so the slot stays valid for every later reload.
// Fresh slot per SITE via localalloc (NOT localadd: its `@`
// dedup would share one slot per fn, and a nested
// append-through-pointer inside a value expression —
// match-yield arm — would clobber the outer's spilled header
// address: silent cross-slice corruption. Mirrors cstage's
// never-deduping local_alloc at the same point.)
snscr = localalloc(c, "@apphdrscr", 8, nil);
emitline("\tMOVQ\tBX, ");
emitoff(snscr: i64);
emitline("(BP)\n");
};
let vn: *node = sn.next;
for (vn != nil) {
@@ -4409,6 +4532,12 @@ fn cgappend(c: *cgen, n: *node) void = {
};
let it_off: i32 = itlocal.off;
let load_op: str = tnodeloadop(c, etnode, esz);
if (!sndirect) {
// FA1: no etnode behind `*p` — signedness off the
// stamped element tinfo, the predicate cstage's
// fldloadop applies to su->sub.
load_op = loadopsz(typeissigned(esubti), esz);
};
emitline("\tSUBQ\t$8, SP\n");
emitline("\tMOVQ\t$0, (SP)\n");
let ll: str = mklabel(c, "spr_l");
@@ -4428,16 +4557,7 @@ fn cgappend(c: *cgen, n: *node) void = {
// 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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
emitline("\tMOVQ\t(SP), CX\n");
if (esz > 1) {
emitline("\tMOVQ\t$");
@@ -4449,20 +4569,7 @@ fn cgappend(c: *cgen, n: *node) void = {
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");
cgappendslot(c, sndirect, sn_off, snscr, esz, "DX");
let wk: i32 = 0;
for (wk + 8 <= esz) {
emitline("\tMOVQ\t");
@@ -4519,30 +4626,8 @@ fn cgappend(c: *cgen, n: *node) void = {
emitline("\tADDQ\tCX, BX\n");
emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n");
emitline("\tPUSHQ\tAX\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");
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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
emitline("\tPOPQ\tAX\n");
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
emitline("\tADDQ\t$1, (SP)\n");
@@ -4561,28 +4646,8 @@ fn cgappend(c: *cgen, n: *node) void = {
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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "DX");
emitline("\tPOPQ\tCX\n");
emitline("\tPOPQ\tBX\n");
emitline("\tPOPQ\tAX\n");
@@ -4597,30 +4662,8 @@ fn cgappend(c: *cgen, n: *node) void = {
// 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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
if (eltagged) {
cgwidentaggedstore(c, esubti, vn, "BX", 0, esz);
vn = vn.next;
@@ -4632,6 +4675,14 @@ fn cgappend(c: *cgen, n: *node) void = {
emitoff(scroff: i64);
emitline("(BP)\n");
let esi: *structinfo = structlookupchain(c, etnode);
if (esi == nil && !sndirect && esubnamed != nil) {
// FA1: no declared tnode to chain through —
// the stamped NAMED element tinfo carries the
// same leaf structlookupchain would resolve.
if (esubnamed.kind == tykind.TY_NAMED) {
esi = structlookup(c, esubnamed.name);
};
};
if (esi == nil) {
let m34s: str = "#34: append() struct element has no structinfo (rule-7)\n";
os.write(2, m34s.ptr, m34s.len: u64);
@@ -4695,30 +4746,8 @@ fn cgappend(c: *cgen, n: *node) void = {
};
cgexpr(c, vn);
emitline("\tPUSHQ\tAX\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");
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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
emitline("\tPOPQ\tAX\n");
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
vn = vn.next;

View File

@@ -24402,6 +24402,77 @@ fn cgalloc(c: *cgen, n: *node) void = {
emitlabel(donel);
};
// cgappendgrow — FA1 (#15): append() header-place grow, cgplaceaddr's
// append consumer. direct = ident-local header in the frame (BP-disp —
// the legacy emission, kept byte-identical); indirect = header address
// pre-spilled to @apphdrscr by the resolver. len+=1, &hdr→DI, esz→SI,
// CALL rt_ensure. In indirect mode the len bump goes through DI so the
// loaded address doubles as the call argument. Mirrors cstage
// cg_append_grow.
fn cgappendgrow(c: *cgen, direct: bool, off: i32, scr: i32, esz: i32) void = {
if (direct) {
emitline("\tADDQ\t$1, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
emitline("\tLEAQ\t");
emitoff(off: i64);
emitline("(BP), DI\n");
} else {
emitline("\tMOVQ\t");
emitoff(scr: i64);
emitline("(BP), DI\n");
emitline("\tADDQ\t$1, 8(DI)\n");
};
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", SI\n");
emitline("\tCALL\trt_ensure(SB)\n");
};
// cgappendslot — post-rt_ensure slot address: CX = (len-1)*esz,
// dst = .ptr + CX. Clobbers AX (the IMUL immediate) and CX, like the
// emission it replaces; dst must not be AX or CX. Mirrors cstage
// cg_append_slot.
fn cgappendslot(c: *cgen, direct: bool, off: i32, scr: i32, esz: i32, dst: str) void = {
if (direct) {
emitline("\tMOVQ\t");
emitoff((off + 8): i64);
emitline("(BP), CX\n");
} else {
emitline("\tMOVQ\t");
emitoff(scr: i64);
emitline("(BP), ");
emitline(dst);
emitline("\n");
emitline("\tMOVQ\t8(");
emitline(dst);
emitline("), 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");
};
if (direct) {
emitline("\tMOVQ\t");
emitoff(off: i64);
emitline("(BP), ");
emitline(dst);
emitline("\n");
} else {
emitline("\tMOVQ\t(");
emitline(dst);
emitline("), ");
emitline(dst);
emitline("\n");
};
emitline("\tADDQ\tCX, ");
emitline(dst);
emitline("\n");
};
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
// Each value gets:
@@ -24422,22 +24493,52 @@ fn cgalloc(c: *cgen, n: *node) void = {
fn cgappend(c: *cgen, n: *node) void = {
let sn: *node = n.list;
if (sn == nil) { return; };
if (sn.kind != nkind.N_IDENT) { return; };
let snlocal: *local = localfindnode(c, sn.str);
if (snlocal == nil) { return; };
let sn_off: i32 = snlocal.off;
// #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);
// FA1 (#15): the old `sn.kind != N_IDENT return` and
// `snlocal == nil → return` gates were SILENT zero-emission
// (gate-blind cs≠ww: cstage 0-defaulted the header base and
// corrupted the caller frame instead). A non-ident-local target
// now resolves its header address through cgplaceaddr; a shape
// the resolver can't address is loud.
let sndirect: bool = false;
let sn_off: i32 = 0;
let snlocal: *local = nil;
if (sn.kind == nkind.N_IDENT) {
snlocal = localfindnode(c, sn.str);
if (snlocal != nil) {
sndirect = true;
sn_off = snlocal.off;
};
};
let esz: i32 = 0;
let etnode: *node = nil;
if (snlocal.tnode != nil) {
let stk: nkind = snlocal.tnode.kind;
if (stk == nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; };
let sti: *tinfo = nil;
if (sndirect) {
// #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.
esz = elemsizeofc(c, snlocal.tnode);
if (snlocal.tnode != nil) {
let stk: nkind = snlocal.tnode.kind;
if (stk == nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; };
if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; };
sti = snlocal.tnode.type_: *tinfo;
};
} else {
// FA1: `*p` has no declared tnode — key esz/element kind off
// the checker-STAMPED target tinfo (#209/#211 discipline), the
// same source cstage reads (sn->type → su->sub->size).
sti = sn.type_: *tinfo;
let fsti: *tinfo = sti;
for (fsti != nil && fsti.kind == tykind.TY_NAMED) { fsti = fsti.under; };
if (fsti != nil && fsti.sub != nil) { esz = fsti.sub.size: i32; };
if (esz <= 0) {
let m15z: str = "#15: append() target element size unresolved (rule-7)\n";
os.write(2, m15z.ptr, m15z.len: u64);
os.exit(1);
};
};
let store_op: str = tnodestoreop(c, etnode, esz);
// #34 element-kind store dispatch: the scalar 1-word store below
@@ -24446,11 +24547,13 @@ fn cgappend(c: *cgen, n: *node) void = {
// 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; };
// FA1: pre-peel handle — the indirect struct-lit fill keys its
// structinfo off the NAMED element tinfo's name (the same leaf
// structlookupchain resolves from the declared tnode).
let esubnamed: *tinfo = esubti;
for (esubti != nil && esubti.kind == tykind.TY_NAMED) {
esubti = esubti.under;
};
@@ -24464,6 +24567,26 @@ fn cgappend(c: *cgen, n: *node) void = {
os.write(2, m34k.ptr, m34k.len: u64);
os.exit(1);
};
let snscr: i32 = 0;
if (!sndirect) {
if (!cgplaceaddr(c, sn, "BX")) {
let m15p: str = "#15: append() target place unsupported (rule-7)\n";
os.write(2, m15p.ptr, m15p.len: u64);
os.exit(1);
};
// Spill across rt_ensure: realloc moves .ptr, never the
// header, so the slot stays valid for every later reload.
// Fresh slot per SITE via localalloc (NOT localadd: its `@`
// dedup would share one slot per fn, and a nested
// append-through-pointer inside a value expression —
// match-yield arm — would clobber the outer's spilled header
// address: silent cross-slice corruption. Mirrors cstage's
// never-deduping local_alloc at the same point.)
snscr = localalloc(c, "@apphdrscr", 8, nil);
emitline("\tMOVQ\tBX, ");
emitoff(snscr: i64);
emitline("(BP)\n");
};
let vn: *node = sn.next;
for (vn != nil) {
@@ -24487,6 +24610,12 @@ fn cgappend(c: *cgen, n: *node) void = {
};
let it_off: i32 = itlocal.off;
let load_op: str = tnodeloadop(c, etnode, esz);
if (!sndirect) {
// FA1: no etnode behind `*p` — signedness off the
// stamped element tinfo, the predicate cstage's
// fldloadop applies to su->sub.
load_op = loadopsz(typeissigned(esubti), esz);
};
emitline("\tSUBQ\t$8, SP\n");
emitline("\tMOVQ\t$0, (SP)\n");
let ll: str = mklabel(c, "spr_l");
@@ -24506,16 +24635,7 @@ fn cgappend(c: *cgen, n: *node) void = {
// 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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
emitline("\tMOVQ\t(SP), CX\n");
if (esz > 1) {
emitline("\tMOVQ\t$");
@@ -24527,20 +24647,7 @@ fn cgappend(c: *cgen, n: *node) void = {
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");
cgappendslot(c, sndirect, sn_off, snscr, esz, "DX");
let wk: i32 = 0;
for (wk + 8 <= esz) {
emitline("\tMOVQ\t");
@@ -24597,30 +24704,8 @@ fn cgappend(c: *cgen, n: *node) void = {
emitline("\tADDQ\tCX, BX\n");
emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n");
emitline("\tPUSHQ\tAX\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");
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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
emitline("\tPOPQ\tAX\n");
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
emitline("\tADDQ\t$1, (SP)\n");
@@ -24639,28 +24724,8 @@ fn cgappend(c: *cgen, n: *node) void = {
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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "DX");
emitline("\tPOPQ\tCX\n");
emitline("\tPOPQ\tBX\n");
emitline("\tPOPQ\tAX\n");
@@ -24675,30 +24740,8 @@ fn cgappend(c: *cgen, n: *node) void = {
// 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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
if (eltagged) {
cgwidentaggedstore(c, esubti, vn, "BX", 0, esz);
vn = vn.next;
@@ -24710,6 +24753,14 @@ fn cgappend(c: *cgen, n: *node) void = {
emitoff(scroff: i64);
emitline("(BP)\n");
let esi: *structinfo = structlookupchain(c, etnode);
if (esi == nil && !sndirect && esubnamed != nil) {
// FA1: no declared tnode to chain through —
// the stamped NAMED element tinfo carries the
// same leaf structlookupchain would resolve.
if (esubnamed.kind == tykind.TY_NAMED) {
esi = structlookup(c, esubnamed.name);
};
};
if (esi == nil) {
let m34s: str = "#34: append() struct element has no structinfo (rule-7)\n";
os.write(2, m34s.ptr, m34s.len: u64);
@@ -24773,30 +24824,8 @@ fn cgappend(c: *cgen, n: *node) void = {
};
cgexpr(c, vn);
emitline("\tPUSHQ\tAX\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");
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");
cgappendgrow(c, sndirect, sn_off, snscr, esz);
cgappendslot(c, sndirect, sn_off, snscr, esz, "BX");
emitline("\tPOPQ\tAX\n");
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
vn = vn.next;