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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user