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:
11
Makefile
11
Makefile
@@ -392,6 +392,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_delete_elem \
|
||||
$(BIN)/test_placeaddr_store \
|
||||
$(BIN)/test_tryprop_multisuccess \
|
||||
$(BIN)/test_append_place \
|
||||
$(BIN)/test_struct_tuple_field_slot \
|
||||
$(BIN)/test_widen_pad_zero_run \
|
||||
$(BIN)/test_named_ptr_alias_variant_widen \
|
||||
@@ -1035,6 +1036,16 @@ $(BIN)/test_tryprop_multisuccess: test/wcc/806_tryprop_multisuccess.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# FA1 (task #15, regex fold-2b): append(*p, v) / append(*p, xs...)
|
||||
# through a pointer-to-slice place via the cgplaceaddr resolver —
|
||||
# runtime rows across element kinds + spread + realloc/frame-integrity
|
||||
# + loud-tail reject rows w/ exact diagnostic text + cs==ww asm byte-id.
|
||||
$(BIN)/test_append_place: test/wcc/806_append_place.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# #237: a tuple-typed struct field must contribute its real slot width to
|
||||
# the enclosing struct's slotsize — pure cs==ww .s byte-id (frame size).
|
||||
$(BIN)/test_struct_tuple_field_slot: test/wcc/930_struct_tuple_field_slot.c \
|
||||
|
||||
154
cmd/w6c/cgen.c
154
cmd/w6c/cgen.c
@@ -102,6 +102,16 @@ static int cg_ntagscr;
|
||||
* Cached per name per fn to mirror wwstage's localadd `@`-prefix
|
||||
* dedup, else two struct appends in one fn diverge the frame. */
|
||||
static int cg_appendscr;
|
||||
/* FA1 (#15) @apphdrscr — 8B spill of the resolver-derived slice-header
|
||||
* ADDRESS for append() through a non-ident-local target (`append(*p,
|
||||
* v)`). rt_ensure may realloc .ptr but never moves the header, so the
|
||||
* spilled address stays valid across the call; every access reloads
|
||||
* from the slot because registers don't survive it. Allocated fresh
|
||||
* per append SITE (no per-fn cache, no decl here): a nested
|
||||
* append-through-pointer inside a value expression (match-yield arm)
|
||||
* spills its own resolve, and a shared slot would feed the outer
|
||||
* grow/slot reloads the INNER target's header — silent cross-slice
|
||||
* corruption (806 reentrant_value row). */
|
||||
/* System V AMD64 sret discipline (task #23). Plain TY_STRUCT returns
|
||||
* with size > 24B are passed via a hidden first-arg pointer (RDI) to
|
||||
* a caller-prealloc dest; the callee writes through that pointer and
|
||||
@@ -2088,6 +2098,50 @@ cgplaceaddr(Cg *c, Node *n, int dst_reg, Local *locals)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FA1 (#15): append() header-place access, 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. grow = 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. */
|
||||
static void
|
||||
cg_append_grow(Cg *c, int direct, int off, int scr, int esz)
|
||||
{
|
||||
if (direct) {
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_BP, off + 8));
|
||||
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_DI));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, amem(D_BP, scr), areg(D_DI));
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_DI, 8));
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
|
||||
ins1(c, A_CALL, masym(c, "rt_ensure"));
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
static void
|
||||
cg_append_slot(Cg *c, int direct, int off, int scr, int esz, int dst)
|
||||
{
|
||||
if (direct) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_CX));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, amem(D_BP, scr), areg(dst));
|
||||
ins2(c, A_MOVQ, amem(dst, 8), areg(D_CX));
|
||||
}
|
||||
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
||||
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
||||
}
|
||||
if (direct)
|
||||
ins2(c, A_MOVQ, amem(D_BP, off), areg(dst));
|
||||
else
|
||||
ins2(c, A_MOVQ, amem(dst, 0), areg(dst));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(dst));
|
||||
}
|
||||
|
||||
/* cg_structlit_fill modes — see helper docstring. */
|
||||
enum {
|
||||
DST_BP = 0,
|
||||
@@ -6842,6 +6896,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
Type *esub = su ? su->sub : NULL;
|
||||
int sn_off = (sn->kind == N_IDENT)
|
||||
? localfind(locals, sn->str) : 0;
|
||||
/* FA1 (#15): a non-ident-local target used to silently
|
||||
* 0-default sn_off, making 0(BP)/8(BP) the "header" —
|
||||
* rt_ensure then corrupted the caller frame. Non-direct
|
||||
* targets resolve through cgplaceaddr below; a shape it
|
||||
* can't address is loud. */
|
||||
int sn_direct = sn_off != 0;
|
||||
int store_op = fldstoreop(esub, esz);
|
||||
/* #34 element-kind store dispatch: the scalar 1-word
|
||||
* store below silently gutted every wide element
|
||||
@@ -6858,6 +6918,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (!el_wide && esz > 8)
|
||||
fatal("#34: append() element kind "
|
||||
"unsupported (rule-7)");
|
||||
int sn_scr = 0;
|
||||
if (!sn_direct) {
|
||||
if (!cgplaceaddr(c, sn, D_BX, locals))
|
||||
fatal("#15: append() target place "
|
||||
"unsupported (rule-7)");
|
||||
/* Fresh slot per SITE, not a per-fn cache: a
|
||||
* nested append-through-pointer inside a value
|
||||
* expression (match-yield arm) spills its own
|
||||
* resolve; a shared slot would hand the outer
|
||||
* grow/slot reloads the inner target's header.
|
||||
* local_alloc never dedups by name. */
|
||||
sn_scr = local_alloc(c, &locals,
|
||||
"@apphdrscr", 8, cg_frame);
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, sn_scr));
|
||||
}
|
||||
for (Node *vn = sn->next; vn; vn = vn->next) {
|
||||
/* #34 review: a spread whose source is not a
|
||||
* local ident used to fall PAST the spread arm
|
||||
@@ -6897,10 +6973,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* from the slice headers after the
|
||||
* call (i reloads from the counter
|
||||
* slot; CX was clobbered). */
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
|
||||
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI));
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
|
||||
ins1(c, A_CALL, masym(c, "rt_ensure"));
|
||||
cg_append_grow(c, sn_direct, sn_off,
|
||||
sn_scr, esz);
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_CX));
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
||||
@@ -6908,14 +6982,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_BP, it_off), areg(D_BX));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX));
|
||||
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
||||
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_DX));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(D_DX));
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_DX);
|
||||
int k = 0;
|
||||
for (; k + 8 <= esz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_BX, k), areg(D_AX));
|
||||
@@ -6952,18 +7020,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, load_op, amem(D_BX, 0), areg(D_AX));
|
||||
/* ensure + store one element */
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
|
||||
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI));
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
|
||||
ins1(c, A_CALL, masym(c, "rt_ensure"));
|
||||
ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX));
|
||||
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
||||
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_BX));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
|
||||
cg_append_grow(c, sn_direct, sn_off,
|
||||
sn_scr, esz);
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_BX);
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
|
||||
/* loop tail */
|
||||
@@ -6983,16 +7043,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
ins1(c, A_PUSHQ, areg(D_CX));
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
|
||||
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI));
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
|
||||
ins1(c, A_CALL, masym(c, "rt_ensure"));
|
||||
ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX));
|
||||
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
||||
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_DX));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(D_DX));
|
||||
cg_append_grow(c, sn_direct, sn_off,
|
||||
sn_scr, esz);
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_DX);
|
||||
ins1(c, A_POPQ, areg(D_CX));
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
@@ -7007,18 +7061,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* the dst pointer (tagged: the #12 widen
|
||||
* choke-point cgexprs the value internally;
|
||||
* struct: literal fill / ident word-copy). */
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
|
||||
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI));
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
|
||||
ins1(c, A_CALL, masym(c, "rt_ensure"));
|
||||
ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX));
|
||||
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
||||
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_BX));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
|
||||
cg_append_grow(c, sn_direct, sn_off,
|
||||
sn_scr, esz);
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_BX);
|
||||
if (el_tagged) {
|
||||
cg_widen_tagged_store(c, &locals,
|
||||
esub, vn, D_BX, 0, esz);
|
||||
@@ -7069,18 +7115,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
cgexpr(c, vn, locals); /* val → AX */
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
|
||||
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI));
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
|
||||
ins1(c, A_CALL, masym(c, "rt_ensure"));
|
||||
ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX));
|
||||
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
||||
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_BX));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
|
||||
cg_append_grow(c, sn_direct, sn_off,
|
||||
sn_scr, esz);
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_BX);
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
587
test/wcc/806_append_place.c
Normal file
587
test/wcc/806_append_place.c
Normal file
@@ -0,0 +1,587 @@
|
||||
/*
|
||||
* 806_append_place — cstage and wwstage agree, byte-for-byte and at
|
||||
* runtime, that `append(*p, v)` / `append(*p, items...)` through a
|
||||
* pointer-to-slice place lands the element (FA1, task #15 of the regex
|
||||
* fold-2b blockers; the add_thread hot shape `threads: *[]thread`).
|
||||
*
|
||||
* Pre-fix this was ONE mirrored choke-point with TWO failure modes:
|
||||
* cstage 0-defaulted the header base for any non-ident target
|
||||
* (`sn_off = (sn->kind==N_IDENT) ? localfind : 0`), 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). The fix re-keys the append lowering from BP-displacement
|
||||
* assumptions onto a resolver-provided header PLACE (cgplaceaddr's
|
||||
* third consumer after C1 assign-stores and C1.25 aggregate-field
|
||||
* stores): the derived header address is spilled to a per-fn
|
||||
* @apphdrscr slot so it survives rt_ensure (realloc moves .ptr, never
|
||||
* the header), and every header access reloads from the slot.
|
||||
* Ident-local targets keep the legacy BP-disp emission byte-identical.
|
||||
* Any target place the resolver can't address dies LOUD (rule 7) —
|
||||
* the old silent corruption can't come back through an unwired shape.
|
||||
*
|
||||
* row | shape | want
|
||||
* ---------------------+-----------------------------------------+------
|
||||
* scalar_param | append(*p, v) i64 via *[]i64 param | 60
|
||||
* scalar_u8_multi | append(*q, a, b) u8 via local ptr | 61
|
||||
* spread_param | append(*p, src...) i64 spread | 62
|
||||
* spread_narrow_signed | append(*p, src...) i32 (MOVSXD load | 63
|
||||
* | keyed off stamped tinfo, no tnode) |
|
||||
* spread_wide | append(*p, src...) str (24B word-copy) | 64
|
||||
* str_elem | append(*p, "hi") 3-word header | 65
|
||||
* slice_elem | append(*p, one) [][]u8 3-word header | 66
|
||||
* tagged_elem | append(*p, 42) (i64|void) widen-box | 67
|
||||
* struct_lit | pA6 verbatim: append(*p, box{...}) | 68
|
||||
* struct_ident | append(*p, b) local struct word-copy | 69
|
||||
* realloc_loop | 100 appends via param, branched callee, | 70
|
||||
* | cap-crossing reallocs, full multi-field |
|
||||
* | readback + caller-frame sentinels |
|
||||
* deref_spine | append((*q)[i].xs, v) resolver spine | 71
|
||||
* neutral_direct | ident-local appends (the legacy arm — | 72
|
||||
* | runtime-pins the asm-neutrality claim) |
|
||||
* reentrant_value | append(*p, match{.. append(*q,..) ..}) | 33
|
||||
* | — nested indirect append inside the |
|
||||
* | outer's value expr; pins the per-SITE |
|
||||
* | @apphdrscr slot (a shared slot clobbers)|
|
||||
* reject_identroot_dot | h.xs via *holder param (resolver ident | BUILD_FAIL
|
||||
* | root unwired — was silent corruption) |
|
||||
* reject_spread_src | non-ident spread SOURCE through a deref | BUILD_FAIL
|
||||
* | target (the #35 designed boundary) |
|
||||
*
|
||||
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
|
||||
* both stages) — a build that fails for any other reason (parse error,
|
||||
* crash) is a vacuous reject and fails the row.
|
||||
*
|
||||
* Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id.
|
||||
*
|
||||
* Readbacks deliberately avoid the still-open F2/F5 read shapes
|
||||
* (`len(xs[i].field)`, `let s = xs[i].field`) — those are tracked
|
||||
* separately; this test pins the append WRITE path.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* want == BUILD_FAIL: the row must FAIL to build on both stages AND
|
||||
* emit expect_err on stderr (rule 7 — never a silent acceptance;
|
||||
* without the message check a row would pass vacuously on any
|
||||
* unrelated build failure). */
|
||||
#define BUILD_FAIL (-2147483647 - 1)
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want;
|
||||
const char *expect_err; /* BUILD_FAIL rows: required stderr substring */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* The FA1 minimum: two appends through a *[]i64 param, then a
|
||||
* third through a local pointer binding. */
|
||||
{ "scalar_param",
|
||||
"package main;\n"
|
||||
"fn add(p: *[]i64, v: i64) void = { append(*p, v); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tadd(&xs, 5);\n"
|
||||
"\tadd(&xs, 9);\n"
|
||||
"\tif (len(xs) != 2) { return 1; };\n"
|
||||
"\tif (xs[0] != 5 || xs[1] != 9) { return 2; };\n"
|
||||
"\tlet q: *[]i64 = &xs;\n"
|
||||
"\tappend(*q, 11);\n"
|
||||
"\tif (len(xs) != 3 || xs[2] != 11) { return 3; };\n"
|
||||
"\treturn 60;\n"
|
||||
"};\n",
|
||||
60, NULL },
|
||||
|
||||
/* esz=1 store (MOVB) + multi-value list through one resolve. */
|
||||
{ "scalar_u8_multi",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet bs: []u8 = [];\n"
|
||||
"\tlet q: *[]u8 = &bs;\n"
|
||||
"\tappend(*q, 1u8, 2u8);\n"
|
||||
"\tif (len(bs) != 2) { return 1; };\n"
|
||||
"\tif (bs[0] != 1u8 || bs[1] != 2u8) { return 2; };\n"
|
||||
"\treturn 61;\n"
|
||||
"};\n",
|
||||
61, NULL },
|
||||
|
||||
{ "spread_param",
|
||||
"package main;\n"
|
||||
"fn addall(p: *[]i64, src: []i64) void = { append(*p, src...); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 1, 2);\n"
|
||||
"\tlet ys: []i64 = [];\n"
|
||||
"\taddall(&ys, xs);\n"
|
||||
"\taddall(&ys, xs);\n"
|
||||
"\tif (len(ys) != 4) { return 1; };\n"
|
||||
"\tif (ys[0] != 1 || ys[1] != 2 || ys[3] != 2) { return 2; };\n"
|
||||
"\treturn 62;\n"
|
||||
"};\n",
|
||||
62, NULL },
|
||||
|
||||
/* Indirect mode has no declared tnode — the spread element LOAD
|
||||
* op (sign extension) must come off the stamped tinfo; negative
|
||||
* i32 elements pin MOVSXD. */
|
||||
{ "spread_narrow_signed",
|
||||
"package main;\n"
|
||||
"fn addall(p: *[]i32, src: []i32) void = { append(*p, src...); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i32 = [];\n"
|
||||
"\tappend(xs, -7i32, 9i32);\n"
|
||||
"\tlet ys: []i32 = [];\n"
|
||||
"\taddall(&ys, xs);\n"
|
||||
"\tif (len(ys) != 2) { return 1; };\n"
|
||||
"\tif (ys[0] != -7i32) { return 2; };\n"
|
||||
"\tif (ys[1] != 9i32) { return 3; };\n"
|
||||
"\treturn 63;\n"
|
||||
"};\n",
|
||||
63, NULL },
|
||||
|
||||
/* Wide (24B) spread element word-copy through the deref target. */
|
||||
{ "spread_wide",
|
||||
"package main;\n"
|
||||
"fn addall(p: *[]str, src: []str) void = { append(*p, src...); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ss: []str = [];\n"
|
||||
"\tappend(ss, \"ab\");\n"
|
||||
"\tappend(ss, \"cde\");\n"
|
||||
"\tlet tt: []str = [];\n"
|
||||
"\taddall(&tt, ss);\n"
|
||||
"\tif (len(tt) != 2) { return 1; };\n"
|
||||
"\tlet a: str = tt[0];\n"
|
||||
"\tlet b: str = tt[1];\n"
|
||||
"\tif (a.len != 2 || b.len != 3) { return 2; };\n"
|
||||
"\tif (b[0] != 'c') { return 3; };\n"
|
||||
"\treturn 64;\n"
|
||||
"};\n",
|
||||
64, NULL },
|
||||
|
||||
/* str element: cgexpr leaves {ptr,len,cap} in AX/BX/CX; all three
|
||||
* must survive rt_ensure AND the indirect header reload. */
|
||||
{ "str_elem",
|
||||
"package main;\n"
|
||||
"fn adds(p: *[]str, v: str) void = { append(*p, v); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ss: []str = [];\n"
|
||||
"\tadds(&ss, \"hi\");\n"
|
||||
"\tadds(&ss, \"world\");\n"
|
||||
"\tif (len(ss) != 2) { return 1; };\n"
|
||||
"\tlet a: str = ss[0];\n"
|
||||
"\tlet b: str = ss[1];\n"
|
||||
"\tif (a.len != 2 || b.len != 5) { return 2; };\n"
|
||||
"\tif (a[0] != 'h' || b[0] != 'w') { return 3; };\n"
|
||||
"\treturn 65;\n"
|
||||
"};\n",
|
||||
65, NULL },
|
||||
|
||||
{ "slice_elem",
|
||||
"package main;\n"
|
||||
"fn addv(p: *[][]u8, v: []u8) void = { append(*p, v); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet vv: [][]u8 = [];\n"
|
||||
"\tlet one: []u8 = [];\n"
|
||||
"\tappend(one, 5u8, 6u8);\n"
|
||||
"\taddv(&vv, one);\n"
|
||||
"\tif (len(vv) != 1) { return 1; };\n"
|
||||
"\tlet got: []u8 = vv[0];\n"
|
||||
"\tif (len(got) != 2 || got[1] != 6u8) { return 2; };\n"
|
||||
"\treturn 66;\n"
|
||||
"};\n",
|
||||
66, NULL },
|
||||
|
||||
/* Tagged element: the #12 widen choke-point boxes through the
|
||||
* resolver-derived slot pointer (grow-first, no register form). */
|
||||
{ "tagged_elem",
|
||||
"package main;\n"
|
||||
"type tu = (i64 | void);\n"
|
||||
"fn addt(p: *[]tu, v: i64) void = { append(*p, v); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []tu = [];\n"
|
||||
"\taddt(&ts, 42);\n"
|
||||
"\tif (len(ts) != 1) { return 1; };\n"
|
||||
"\tlet t0: tu = ts[0];\n"
|
||||
"\tif (!(t0 is i64)) { return 2; };\n"
|
||||
"\treturn 67;\n"
|
||||
"};\n",
|
||||
67, NULL },
|
||||
|
||||
/* The pA6 repro, verbatim shapes: struct-literal element fill
|
||||
* through @appendscr while the header address sits in @apphdrscr. */
|
||||
{ "struct_lit",
|
||||
"package main;\n"
|
||||
"type box = struct { pc: size, matched: bool };\n"
|
||||
"fn add(p: *[]box, v: size) void = {\n"
|
||||
"\tappend(*p, box { pc = v, matched = false });\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet bs: []box = [];\n"
|
||||
"\tadd(&bs, 5);\n"
|
||||
"\tadd(&bs, 9);\n"
|
||||
"\tif (len(bs) != 2) { return 1; };\n"
|
||||
"\tif (bs[0].pc != 5) { return 2; };\n"
|
||||
"\tif (bs[1].pc != 9) { return 3; };\n"
|
||||
"\treturn 68;\n"
|
||||
"};\n",
|
||||
68, NULL },
|
||||
|
||||
{ "struct_ident",
|
||||
"package main;\n"
|
||||
"type box = struct { pc: size, matched: bool };\n"
|
||||
"fn add(p: *[]box, v: size) void = {\n"
|
||||
"\tlet b: box = box { pc = v, matched = true };\n"
|
||||
"\tappend(*p, b);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet bs: []box = [];\n"
|
||||
"\tadd(&bs, 7);\n"
|
||||
"\tif (len(bs) != 1) { return 1; };\n"
|
||||
"\tif (bs[0].pc != 7) { return 2; };\n"
|
||||
"\tif (!bs[0].matched) { return 3; };\n"
|
||||
"\treturn 69;\n"
|
||||
"};\n",
|
||||
69, NULL },
|
||||
|
||||
/* The corruption symptom row: 100 appends through the param in a
|
||||
* loop with a branched callee — crosses several cap-doubling
|
||||
* reallocs. Sentinels on BOTH sides of the slice local pin the
|
||||
* caller frame (pre-fix cstage incremented 8(BP) and passed (BP)
|
||||
* to rt_ensure → caller-frame corruption); the full readback pins
|
||||
* every element across the realloc moves. */
|
||||
{ "realloc_loop",
|
||||
"package main;\n"
|
||||
"type box = struct { pc: size, matched: bool };\n"
|
||||
"fn add(p: *[]box, v: size) void = {\n"
|
||||
"\tif (v % 2 == 0) {\n"
|
||||
"\t\tappend(*p, box { pc = v, matched = true });\n"
|
||||
"\t} else {\n"
|
||||
"\t\tappend(*p, box { pc = v, matched = false });\n"
|
||||
"\t};\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet lo: i64 = 0x5151;\n"
|
||||
"\tlet bs: []box = [];\n"
|
||||
"\tlet hi: i64 = 0x7272;\n"
|
||||
"\tlet i: size = 0;\n"
|
||||
"\tfor (i < 100) {\n"
|
||||
"\t\tadd(&bs, i);\n"
|
||||
"\t\ti += 1;\n"
|
||||
"\t};\n"
|
||||
"\tif (len(bs) != 100) { return 1; };\n"
|
||||
"\tlet j: size = 0;\n"
|
||||
"\tfor (j < 100) {\n"
|
||||
"\t\tif (bs[j].pc != j) { return 2; };\n"
|
||||
"\t\tif (bs[j].matched != (j % 2 == 0)) { return 3; };\n"
|
||||
"\t\tj += 1;\n"
|
||||
"\t};\n"
|
||||
"\tif (lo != 0x5151 || hi != 0x7272) { return 4; };\n"
|
||||
"\treturn 70;\n"
|
||||
"};\n",
|
||||
70, NULL },
|
||||
|
||||
/* Deeper resolver spine: the header is a slice FIELD of an element
|
||||
* behind a deref ((*q)[i].xs — N_DOT over N_INDEX over N_UN).
|
||||
* Readback via a *holder ptr-dot, a sound read path. */
|
||||
{ "deref_spine",
|
||||
"package main;\n"
|
||||
"type holder = struct { tag: i64, xs: []i64 };\n"
|
||||
"fn addspine(q: *[]holder, i: i64, v: i64) void = {\n"
|
||||
"\tappend((*q)[i].xs, v);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hs: []holder = [];\n"
|
||||
"\tlet h0: holder = holder { tag = 1, xs = [] };\n"
|
||||
"\tappend(hs, h0);\n"
|
||||
"\taddspine(&hs, 0, 41);\n"
|
||||
"\taddspine(&hs, 0, 43);\n"
|
||||
"\tlet hp: *holder = &hs[0];\n"
|
||||
"\tlet g: []i64 = hp.xs;\n"
|
||||
"\tif (len(g) != 2) { return 1; };\n"
|
||||
"\tif (g[0] != 41 || g[1] != 43) { return 2; };\n"
|
||||
"\tif (hp.tag != 1) { return 3; };\n"
|
||||
"\treturn 71;\n"
|
||||
"};\n",
|
||||
71, NULL },
|
||||
|
||||
/* Ident-local targets stay with the legacy BP-disp emission (the
|
||||
* resolver must never fire for them) — this row runtime-pins the
|
||||
* shapes the before/after asm sweep diffed statically. */
|
||||
{ "neutral_direct",
|
||||
"package main;\n"
|
||||
"type box = struct { pc: size, matched: bool };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 7);\n"
|
||||
"\tappend(xs, 8, 9);\n"
|
||||
"\tlet ys: []i64 = [];\n"
|
||||
"\tappend(ys, xs...);\n"
|
||||
"\tlet ss: []str = [];\n"
|
||||
"\tappend(ss, \"hi\");\n"
|
||||
"\tlet ps: []box = [];\n"
|
||||
"\tappend(ps, box { pc = 1, matched = false });\n"
|
||||
"\tif (len(xs) != 3 || xs[2] != 9) { return 1; };\n"
|
||||
"\tif (len(ys) != 3 || ys[0] != 7) { return 2; };\n"
|
||||
"\tif (len(ss) != 1 || len(ps) != 1) { return 3; };\n"
|
||||
"\treturn 72;\n"
|
||||
"};\n",
|
||||
72, NULL },
|
||||
|
||||
/* Reentrancy: a nested append-through-pointer inside the outer
|
||||
* append's VALUE expression (match-yield arm) spills its own
|
||||
* header address. Pins the per-SITE @apphdrscr slot — a shared
|
||||
* per-fn slot hands the outer's post-rt_ensure reloads the inner
|
||||
* target's header and the outer element lands in the wrong
|
||||
* slice (silent, both counts wrong). */
|
||||
{ "reentrant_value",
|
||||
"package main;\n"
|
||||
"type tu = (i64 | void);\n"
|
||||
"fn nest(p: *[]i64, q: *[]i64, t: tu) void = {\n"
|
||||
"\tappend(*p, match (t) {\n"
|
||||
"\tcase let x: i64 => {\n"
|
||||
"\t\tappend(*q, 500);\n"
|
||||
"\t\tyield x;\n"
|
||||
"\t};\n"
|
||||
"\tcase void => {\n"
|
||||
"\t\tyield 0;\n"
|
||||
"\t};\n"
|
||||
"\t});\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: []i64 = [];\n"
|
||||
"\tlet b: []i64 = [];\n"
|
||||
"\tlet t: tu = 7;\n"
|
||||
"\tnest(&a, &b, t);\n"
|
||||
"\tif (len(a) != 1) { return 1; };\n"
|
||||
"\tif (len(b) != 1) { return 2; };\n"
|
||||
"\tif (a[0] != 7) { return 3; };\n"
|
||||
"\tif (b[0] != 500) { return 4; };\n"
|
||||
"\treturn 33;\n"
|
||||
"};\n",
|
||||
33, NULL },
|
||||
|
||||
/* Ident-rooted dot target (h.xs through *holder): the resolver
|
||||
* does not wire ident roots (they belong to the enumerated arms;
|
||||
* none exists for append yet) — pre-fix this shape silently
|
||||
* corrupted the frame in cstage; now it must die LOUD. */
|
||||
{ "reject_identroot_dot",
|
||||
"package main;\n"
|
||||
"type holder = struct { tag: i64, xs: []i64 };\n"
|
||||
"fn addfield(h: *holder, v: i64) void = { append(h.xs, v); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hl: holder = holder { tag = 2, xs = [] };\n"
|
||||
"\taddfield(&hl, 9);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "#15: append() target place unsupported (rule-7)" },
|
||||
|
||||
/* The FA4 designed boundary (task #35, old #37): a spread SOURCE that is
|
||||
* not an ident local must stay loud even now that the deref
|
||||
* TARGET resolves. */
|
||||
{ "reject_spread_src",
|
||||
"package main;\n"
|
||||
"type holder = struct { tag: i64, xs: []i64 };\n"
|
||||
"fn dup(p: *[]i64, q: *[]holder, i: i64) void = {\n"
|
||||
"\tappend(*p, (*q)[i].xs...);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ys: []i64 = [];\n"
|
||||
"\tlet hs: []holder = [];\n"
|
||||
"\tdup(&ys, &hs, 0);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "#34: append() spread source shape unsupported (rule-7)" },
|
||||
};
|
||||
|
||||
/* errlog_has — the build-failure stderr must carry the row's expected
|
||||
* diagnostic; any other failure (parse error, crash) is a vacuous
|
||||
* reject and must not pass. */
|
||||
static int
|
||||
errlog_has(const char *path, const char *needle)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
char buf[8192];
|
||||
size_t got = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[got] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[64], tmpdir[64], errlog[80], cmd[1200];
|
||||
snprintf(src, sizeof src, "/tmp/applp_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/applp_%d_d_%d", getpid(), i);
|
||||
snprintf(errlog, sizeof errlog, "%s.err", src);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>%s",
|
||||
tmpdir, driver, src, errlog);
|
||||
if (runwait(cmd) != 0) {
|
||||
int rc = -1;
|
||||
if (r->want != BUILD_FAIL) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
} else if (r->expect_err &&
|
||||
!errlog_has(errlog, r->expect_err)) {
|
||||
fprintf(stderr, "row[%s]: %s build failed without "
|
||||
"expected diagnostic \"%s\"\n",
|
||||
r->label, driver, r->expect_err);
|
||||
rc = -3; /* failed, but for the wrong reason */
|
||||
}
|
||||
unlink(src); unlink(errlog); rmdir(tmpdir);
|
||||
return rc;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(errlog); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
|
||||
* w6c_ww and diff. The header-place emission is written fresh on both
|
||||
* sides, so this is the converged-by-construction gate: any drift in
|
||||
* the resolve/spill/reload sequence shows here. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char src[64], cs[64], ws[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/applp_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/applp_asm_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/applp_asm_%d_%d_w.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
unlink(src);
|
||||
return -1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
unlink(src); unlink(cs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
int rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
unlink(src); unlink(cs); unlink(ws);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[2080];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[2120];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[2120];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "append_place: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
int bad = rows[i].want == BUILD_FAIL
|
||||
? (got != -1) : (got != rows[i].want);
|
||||
if (bad) {
|
||||
fprintf(stderr,
|
||||
"append_place[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].want == BUILD_FAIL)
|
||||
continue;
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"append_place: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("append_place: %d fixtures passed\n", total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user