diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index ab274b68..30f00a9b 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -102,6 +102,13 @@ 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; +/* #49 split-resolve stashes: the source chain's PRE-grow rvalues — + * deref-root pointer value (@appendsroot) and scaled index offset + * (@appendsoff) — must survive rt_ensure so the POST-grow base + * re-derivation can add them back. Same per-fn name-cache discipline + * as @appendscr. */ +static int cg_appendsroot; +static int cg_appendsoff; /* 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 @@ -7114,6 +7121,112 @@ 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). */ + /* #49 (#35's single-element sibling): a + * place-chain source (indexed field + * `threads[i].root_capture` regex.ha:819, + * deref spine, computed index) SPLITS + * around the grow per the #49 ruling: + * the chain's rvalues (deref-root + * pointer expr, index expr) evaluate + * exactly once PRE-grow — an index + * reading the slice header sees the + * pre-append len, Hare's argument + * order — and only the BASE re-derives + * POST-grow from the live storage, so + * a self-append source re-roots in the + * post-realloc buffer. harec resolves + * an aggregate source address wholly + * PRE-grow (gen.c: gen_load returns + * the address for STORAGE_STRUCT, + * gen_store copies after rt.ensure) — + * a use-after-free under a reclaiming + * allocator; per #263 we align to the + * runtime-correct side, not the + * reference. A pointer ALIASING the + * grown buffer keeps Hare's own + * stale-base hole (sound today only + * because rt/malloc.ww never + * reclaims). Spec not vendored + * (ref/hare/docs = man pages only), + * spec-silence assumed — re-verify if + * the spec is ever vendored. + * Bounded shapes: root (local/global + * ident | deref) + at most one index + * + trailing direct fields; all else + * stays on the #34 fatal (incl. CALL + * rvalues, the #42-style bound). */ + int aplace = 0, afld = 0, aidx_esz = 0; + int abase_slice = 0, aroot_off = 0; + Node *aroot = NULL, *aidx = NULL; + if (el_struct && vn->kind != N_STRUCTLIT + && vn->kind != N_IDENT) { + Node *ch = vn; + int aok = 1; + while (aok && ch->kind == N_DOT) { + Node *ab = ch->lhs; + Type *abu = ab ? type_chase_named(ab->type) : NULL; + Tfield *af = NULL; + if (abu && abu->kind == TY_STRUCT) + for (Tfield *fl = abu->fields; fl; fl = fl->next) + if (strcmp(fl->name, ch->str) == 0) { af = fl; break; } + if (af == NULL) { aok = 0; break; } + afld += (int)af->offset; + ch = ab; + } + if (aok && ch->kind == N_INDEX) { + Node *ab = ch->lhs; + Type *abu = ab ? type_chase_named(ab->type) : NULL; + Type *aet = type_chase_named(ch->type); + if (ab == NULL || abu == NULL || aet == NULL + || (abu->kind != TY_SLICE && abu->kind != TY_ARRAY)) { + aok = 0; + } else { + abase_slice = abu->kind == TY_SLICE; + aidx_esz = (int)aet->size; + aidx = ch->rhs; + ch = ab; + } + } + if (aok) { + if (ch->kind == N_IDENT) { + aroot_off = localfind(locals, ch->str); + if (aroot_off == 0 + && !let_islet(ch->str) + && !def_isstructdef(ch->str) + && !def_isarraydef(ch->str)) + aok = 0; + } else if (!(ch->kind == N_UN && ch->op == TK_STAR)) { + aok = 0; + } + } + if (!aok) + fatal("#34: append() struct element source " + "shape unsupported (rule-7)"); + aroot = ch; + if (aroot->kind == N_UN) { + if (cg_appendsroot == 0) + cg_appendsroot = local_alloc(c, + &locals, "@appendsroot", 8, + cg_frame); + cgexpr(c, aroot->lhs, locals); + ins2(c, A_MOVQ, areg(D_AX), + amem(D_BP, cg_appendsroot)); + } + if (aidx != NULL) { + if (cg_appendsoff == 0) + cg_appendsoff = local_alloc(c, + &locals, "@appendsoff", 8, + cg_frame); + cgexpr(c, aidx, locals); + if (aidx_esz > 1) { + ins2(c, A_MOVQ, aimm(aidx_esz), areg(D_CX)); + ins2(c, A_IMULQ, areg(D_CX), areg(D_AX)); + } + ins2(c, A_MOVQ, areg(D_AX), + amem(D_BP, cg_appendsoff)); + } + aplace = 1; + } cg_append_grow(c, sn_direct, sn_off, sn_scr, esz); cg_append_slot(c, sn_direct, sn_off, @@ -7163,6 +7276,70 @@ cgexpr(Cg *c, Node *n, Local *locals) } continue; } + if (aplace) { + /* phase 2: dst slot to + * @appendscr, base from the + * live storage, stashed + * offsets back on top. */ + if (cg_appendscr == 0) + cg_appendscr = local_alloc(c, + &locals, "@appendscr", 8, + cg_frame); + ins2(c, A_MOVQ, areg(D_BX), + amem(D_BP, cg_appendscr)); + if (aroot->kind == N_IDENT) { + if (aroot_off != 0) + ins2(c, A_LEAQ, + amem(D_BP, aroot_off), + areg(D_BX)); + else + ins2(c, A_LEAQ, + masym(c, aroot->str), + areg(D_BX)); + } else { + ins2(c, A_MOVQ, + amem(D_BP, cg_appendsroot), + areg(D_BX)); + } + if (aidx != NULL) { + if (abase_slice) + ins2(c, A_MOVQ, + amem(D_BX, 0), + areg(D_BX)); + ins2(c, A_MOVQ, + amem(D_BP, cg_appendsoff), + areg(D_AX)); + ins2(c, A_ADDQ, areg(D_AX), + areg(D_BX)); + } + if (afld != 0) + ins2(c, A_ADDQ, aimm(afld), + areg(D_BX)); + ins2(c, A_MOVQ, + amem(D_BP, cg_appendscr), + areg(D_DX)); + int k = 0; + for (; k + 8 <= esz; k += 8) { + ins2(c, A_MOVQ, amem(D_BX, k), areg(D_AX)); + ins2(c, A_MOVQ, areg(D_AX), amem(D_DX, k)); + } + if (k + 4 <= esz) { + ins2(c, A_MOVL, amem(D_BX, k), areg(D_AX)); + ins2(c, A_MOVL, areg(D_AX), amem(D_DX, k)); + k += 4; + } + if (k + 2 <= esz) { + ins2(c, A_MOVW, amem(D_BX, k), areg(D_AX)); + ins2(c, A_MOVW, areg(D_AX), amem(D_DX, k)); + k += 2; + } + if (k + 1 <= esz) { + ins2(c, A_MOVB, amem(D_BX, k), areg(D_AX)); + ins2(c, A_MOVB, areg(D_AX), amem(D_DX, k)); + k += 1; + } + continue; + } fatal("#34: append() struct element source " "shape unsupported (rule-7)"); } @@ -11999,6 +12176,8 @@ cgfn(Cg *c, FILE *out, Node *fn) cg_tagbase_sz = 0; cg_ntagscr = 0; cg_appendscr = 0; + cg_appendsroot = 0; + cg_appendsoff = 0; cg_sret_arg_off = 0; cg_sret_dest_off = 0; cg_sret_dest_sym = NULL; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 0301d773..4afc5164 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -24965,6 +24965,131 @@ 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). + // #49 (#35's single-element sibling): a place-chain + // source (indexed field `threads[i].root_capture` + // regex.ha:819, deref spine, computed index) SPLITS + // around the grow per the #49 ruling: the chain's + // rvalues (deref-root pointer expr, index expr) + // evaluate exactly once PRE-grow — an index reading + // the slice header sees the pre-append len, Hare's + // argument order — and only the BASE re-derives + // POST-grow from the live storage, so a self-append + // source re-roots in the post-realloc buffer. harec + // resolves an aggregate source address wholly PRE-grow + // (gen.c: gen_load returns the address for + // STORAGE_STRUCT, gen_store copies after rt.ensure) — + // a use-after-free under a reclaiming allocator; per + // #263 we align to the runtime-correct side, not the + // reference. A pointer ALIASING the grown buffer keeps + // Hare's own stale-base hole (sound today only because + // rt/malloc.ww never reclaims). Spec not vendored + // (ref/hare/docs = man pages only), spec-silence + // assumed — re-verify if the spec is ever vendored. + // Bounded shapes: root (local/global ident | deref) + + // at most one index + trailing direct fields; all else + // stays on the #34 loud exit (incl. CALL rvalues, the + // #42-style bound). + let aplace: bool = false; + let afld: i32 = 0; + let aidxesz: i32 = 0; + let abaseslice: bool = false; + let arootoff: i32 = 0; + let asroot: i32 = 0; + let asoff: i32 = 0; + let aroot: *node = nil; + let aidx: *node = nil; + if (elstruct && vn.kind != nkind.N_STRUCTLIT && vn.kind != nkind.N_IDENT) { + let ch: *node = vn; + let aok: bool = true; + for (aok && ch.kind == nkind.N_DOT) { + let ab: *node = ch.lhs; + let af: *tfield = nil; + if (ab != nil) { + let abu: *tinfo = ab.type_: *tinfo; + for (abu != nil && abu.kind == tykind.TY_NAMED) { abu = abu.under; }; + if (abu != nil && abu.kind == tykind.TY_STRUCT) { + let fl: *tfield = abu.fields; + for (fl != nil) { + if (streq(fl.name, ch.str)) { af = fl; break; }; + fl = fl.tnext; + }; + }; + }; + if (af == nil) { aok = false; break; }; + afld += af.offset: i32; + ch = ab; + }; + if (aok && ch.kind == nkind.N_INDEX) { + let ab: *node = ch.lhs; + let aet: *tinfo = ch.type_: *tinfo; + for (aet != nil && aet.kind == tykind.TY_NAMED) { aet = aet.under; }; + let abu: *tinfo = nil; + if (ab != nil) { + abu = ab.type_: *tinfo; + for (abu != nil && abu.kind == tykind.TY_NAMED) { abu = abu.under; }; + }; + if (ab == nil || abu == nil || aet == nil + || (abu.kind != tykind.TY_SLICE && abu.kind != tykind.TY_ARRAY)) { + aok = false; + } else { + abaseslice = abu.kind == tykind.TY_SLICE; + aidxesz = aet.size: i32; + aidx = ch.rhs; + ch = ab; + }; + }; + if (aok) { + if (ch.kind == nkind.N_IDENT) { + let rl: *local = localfindnode(c, ch.str); + if (rl != nil) { + arootoff = rl.off; + } else { + let gok: bool = isletvar(c, ch.str); + if (!gok) { + if (defvarstructinfo(c, ch.str) != nil) { gok = true; }; + }; + if (!gok) { + let dtn: *node = defvartnode(c, ch.str); + if (dtn != nil) { + if (dtn.kind == nkind.N_TARRAY) { gok = true; }; + }; + }; + if (!gok) { aok = false; }; + }; + } else { + if (ch.kind != nkind.N_UN || ch.op != tkind.TK_STAR) { + aok = false; + }; + }; + }; + if (!aok) { + let m34p: str = "#34: append() struct element source shape unsupported (rule-7)\n"; + os.write(2, m34p.ptr, m34p.len: u64); + os.exit(1); + }; + aroot = ch; + if (aroot.kind == nkind.N_UN) { + asroot = localadd(c, "@appendsroot", 8, nil); + cgexpr(c, aroot.lhs); + emitline("\tMOVQ\tAX, "); + emitoff(asroot: i64); + emitline("(BP)\n"); + }; + if (aidx != nil) { + asoff = localadd(c, "@appendsoff", 8, nil); + cgexpr(c, aidx); + if (aidxesz > 1) { + emitline("\tMOVQ\t$"); + emitint(aidxesz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tMOVQ\tAX, "); + emitoff(asoff: i64); + emitline("(BP)\n"); + }; + aplace = true; + }; cgappendgrow(c, sndirect, sn_off, snscr, esz); cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); if (eltagged) { @@ -25043,6 +25168,86 @@ fn cgappend(c: *cgen, n: *node) void = { vn = vn.next; continue; }; + if (aplace) { + // phase 2: dst slot to @appendscr, base from + // the live storage, stashed offsets back on + // top. + let pscroff: i32 = localadd(c, "@appendscr", 8, nil); + emitline("\tMOVQ\tBX, "); + emitoff(pscroff: i64); + emitline("(BP)\n"); + if (aroot.kind == nkind.N_IDENT) { + if (arootoff != 0) { + emitline("\tLEAQ\t"); + emitoff(arootoff: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, aroot.str); + emitline("(SB), BX\n"); + }; + } else { + emitline("\tMOVQ\t"); + emitoff(asroot: i64); + emitline("(BP), BX\n"); + }; + if (aidx != nil) { + if (abaseslice) { + emitline("\tMOVQ\t(BX), BX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(asoff: i64); + emitline("(BP), AX\n"); + emitline("\tADDQ\tAX, BX\n"); + }; + if (afld != 0) { + emitline("\tADDQ\t$"); + emitint(afld: i64); + emitline(", BX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(pscroff: i64); + emitline("(BP), DX\n"); + let pk: i32 = 0; + for (pk + 8 <= esz) { + emitline("\tMOVQ\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 8; + }; + if (pk + 4 <= esz) { + emitline("\tMOVL\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVL\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 4; + }; + if (pk + 2 <= esz) { + emitline("\tMOVW\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVW\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 2; + }; + if (pk + 1 <= esz) { + emitline("\tMOVB\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVB\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 1; + }; + vn = vn.next; + continue; + }; let m34e: str = "#34: append() struct element source shape unsupported (rule-7)\n"; os.write(2, m34e.ptr, m34e.len: u64); os.exit(1); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index ae9719e7..1c01c6d0 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -4845,6 +4845,131 @@ 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). + // #49 (#35's single-element sibling): a place-chain + // source (indexed field `threads[i].root_capture` + // regex.ha:819, deref spine, computed index) SPLITS + // around the grow per the #49 ruling: the chain's + // rvalues (deref-root pointer expr, index expr) + // evaluate exactly once PRE-grow — an index reading + // the slice header sees the pre-append len, Hare's + // argument order — and only the BASE re-derives + // POST-grow from the live storage, so a self-append + // source re-roots in the post-realloc buffer. harec + // resolves an aggregate source address wholly PRE-grow + // (gen.c: gen_load returns the address for + // STORAGE_STRUCT, gen_store copies after rt.ensure) — + // a use-after-free under a reclaiming allocator; per + // #263 we align to the runtime-correct side, not the + // reference. A pointer ALIASING the grown buffer keeps + // Hare's own stale-base hole (sound today only because + // rt/malloc.ww never reclaims). Spec not vendored + // (ref/hare/docs = man pages only), spec-silence + // assumed — re-verify if the spec is ever vendored. + // Bounded shapes: root (local/global ident | deref) + + // at most one index + trailing direct fields; all else + // stays on the #34 loud exit (incl. CALL rvalues, the + // #42-style bound). + let aplace: bool = false; + let afld: i32 = 0; + let aidxesz: i32 = 0; + let abaseslice: bool = false; + let arootoff: i32 = 0; + let asroot: i32 = 0; + let asoff: i32 = 0; + let aroot: *node = nil; + let aidx: *node = nil; + if (elstruct && vn.kind != nkind.N_STRUCTLIT && vn.kind != nkind.N_IDENT) { + let ch: *node = vn; + let aok: bool = true; + for (aok && ch.kind == nkind.N_DOT) { + let ab: *node = ch.lhs; + let af: *tfield = nil; + if (ab != nil) { + let abu: *tinfo = ab.type_: *tinfo; + for (abu != nil && abu.kind == tykind.TY_NAMED) { abu = abu.under; }; + if (abu != nil && abu.kind == tykind.TY_STRUCT) { + let fl: *tfield = abu.fields; + for (fl != nil) { + if (streq(fl.name, ch.str)) { af = fl; break; }; + fl = fl.tnext; + }; + }; + }; + if (af == nil) { aok = false; break; }; + afld += af.offset: i32; + ch = ab; + }; + if (aok && ch.kind == nkind.N_INDEX) { + let ab: *node = ch.lhs; + let aet: *tinfo = ch.type_: *tinfo; + for (aet != nil && aet.kind == tykind.TY_NAMED) { aet = aet.under; }; + let abu: *tinfo = nil; + if (ab != nil) { + abu = ab.type_: *tinfo; + for (abu != nil && abu.kind == tykind.TY_NAMED) { abu = abu.under; }; + }; + if (ab == nil || abu == nil || aet == nil + || (abu.kind != tykind.TY_SLICE && abu.kind != tykind.TY_ARRAY)) { + aok = false; + } else { + abaseslice = abu.kind == tykind.TY_SLICE; + aidxesz = aet.size: i32; + aidx = ch.rhs; + ch = ab; + }; + }; + if (aok) { + if (ch.kind == nkind.N_IDENT) { + let rl: *local = localfindnode(c, ch.str); + if (rl != nil) { + arootoff = rl.off; + } else { + let gok: bool = isletvar(c, ch.str); + if (!gok) { + if (defvarstructinfo(c, ch.str) != nil) { gok = true; }; + }; + if (!gok) { + let dtn: *node = defvartnode(c, ch.str); + if (dtn != nil) { + if (dtn.kind == nkind.N_TARRAY) { gok = true; }; + }; + }; + if (!gok) { aok = false; }; + }; + } else { + if (ch.kind != nkind.N_UN || ch.op != tkind.TK_STAR) { + aok = false; + }; + }; + }; + if (!aok) { + let m34p: str = "#34: append() struct element source shape unsupported (rule-7)\n"; + os.write(2, m34p.ptr, m34p.len: u64); + os.exit(1); + }; + aroot = ch; + if (aroot.kind == nkind.N_UN) { + asroot = localadd(c, "@appendsroot", 8, nil); + cgexpr(c, aroot.lhs); + emitline("\tMOVQ\tAX, "); + emitoff(asroot: i64); + emitline("(BP)\n"); + }; + if (aidx != nil) { + asoff = localadd(c, "@appendsoff", 8, nil); + cgexpr(c, aidx); + if (aidxesz > 1) { + emitline("\tMOVQ\t$"); + emitint(aidxesz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tMOVQ\tAX, "); + emitoff(asoff: i64); + emitline("(BP)\n"); + }; + aplace = true; + }; cgappendgrow(c, sndirect, sn_off, snscr, esz); cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); if (eltagged) { @@ -4923,6 +5048,86 @@ fn cgappend(c: *cgen, n: *node) void = { vn = vn.next; continue; }; + if (aplace) { + // phase 2: dst slot to @appendscr, base from + // the live storage, stashed offsets back on + // top. + let pscroff: i32 = localadd(c, "@appendscr", 8, nil); + emitline("\tMOVQ\tBX, "); + emitoff(pscroff: i64); + emitline("(BP)\n"); + if (aroot.kind == nkind.N_IDENT) { + if (arootoff != 0) { + emitline("\tLEAQ\t"); + emitoff(arootoff: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, aroot.str); + emitline("(SB), BX\n"); + }; + } else { + emitline("\tMOVQ\t"); + emitoff(asroot: i64); + emitline("(BP), BX\n"); + }; + if (aidx != nil) { + if (abaseslice) { + emitline("\tMOVQ\t(BX), BX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(asoff: i64); + emitline("(BP), AX\n"); + emitline("\tADDQ\tAX, BX\n"); + }; + if (afld != 0) { + emitline("\tADDQ\t$"); + emitint(afld: i64); + emitline(", BX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(pscroff: i64); + emitline("(BP), DX\n"); + let pk: i32 = 0; + for (pk + 8 <= esz) { + emitline("\tMOVQ\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 8; + }; + if (pk + 4 <= esz) { + emitline("\tMOVL\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVL\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 4; + }; + if (pk + 2 <= esz) { + emitline("\tMOVW\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVW\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 2; + }; + if (pk + 1 <= esz) { + emitline("\tMOVB\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVB\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 1; + }; + vn = vn.next; + continue; + }; let m34e: str = "#34: append() struct element source shape unsupported (rule-7)\n"; os.write(2, m34e.ptr, m34e.len: u64); os.exit(1); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 3efbe775..d510a536 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -24965,6 +24965,131 @@ 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). + // #49 (#35's single-element sibling): a place-chain + // source (indexed field `threads[i].root_capture` + // regex.ha:819, deref spine, computed index) SPLITS + // around the grow per the #49 ruling: the chain's + // rvalues (deref-root pointer expr, index expr) + // evaluate exactly once PRE-grow — an index reading + // the slice header sees the pre-append len, Hare's + // argument order — and only the BASE re-derives + // POST-grow from the live storage, so a self-append + // source re-roots in the post-realloc buffer. harec + // resolves an aggregate source address wholly PRE-grow + // (gen.c: gen_load returns the address for + // STORAGE_STRUCT, gen_store copies after rt.ensure) — + // a use-after-free under a reclaiming allocator; per + // #263 we align to the runtime-correct side, not the + // reference. A pointer ALIASING the grown buffer keeps + // Hare's own stale-base hole (sound today only because + // rt/malloc.ww never reclaims). Spec not vendored + // (ref/hare/docs = man pages only), spec-silence + // assumed — re-verify if the spec is ever vendored. + // Bounded shapes: root (local/global ident | deref) + + // at most one index + trailing direct fields; all else + // stays on the #34 loud exit (incl. CALL rvalues, the + // #42-style bound). + let aplace: bool = false; + let afld: i32 = 0; + let aidxesz: i32 = 0; + let abaseslice: bool = false; + let arootoff: i32 = 0; + let asroot: i32 = 0; + let asoff: i32 = 0; + let aroot: *node = nil; + let aidx: *node = nil; + if (elstruct && vn.kind != nkind.N_STRUCTLIT && vn.kind != nkind.N_IDENT) { + let ch: *node = vn; + let aok: bool = true; + for (aok && ch.kind == nkind.N_DOT) { + let ab: *node = ch.lhs; + let af: *tfield = nil; + if (ab != nil) { + let abu: *tinfo = ab.type_: *tinfo; + for (abu != nil && abu.kind == tykind.TY_NAMED) { abu = abu.under; }; + if (abu != nil && abu.kind == tykind.TY_STRUCT) { + let fl: *tfield = abu.fields; + for (fl != nil) { + if (streq(fl.name, ch.str)) { af = fl; break; }; + fl = fl.tnext; + }; + }; + }; + if (af == nil) { aok = false; break; }; + afld += af.offset: i32; + ch = ab; + }; + if (aok && ch.kind == nkind.N_INDEX) { + let ab: *node = ch.lhs; + let aet: *tinfo = ch.type_: *tinfo; + for (aet != nil && aet.kind == tykind.TY_NAMED) { aet = aet.under; }; + let abu: *tinfo = nil; + if (ab != nil) { + abu = ab.type_: *tinfo; + for (abu != nil && abu.kind == tykind.TY_NAMED) { abu = abu.under; }; + }; + if (ab == nil || abu == nil || aet == nil + || (abu.kind != tykind.TY_SLICE && abu.kind != tykind.TY_ARRAY)) { + aok = false; + } else { + abaseslice = abu.kind == tykind.TY_SLICE; + aidxesz = aet.size: i32; + aidx = ch.rhs; + ch = ab; + }; + }; + if (aok) { + if (ch.kind == nkind.N_IDENT) { + let rl: *local = localfindnode(c, ch.str); + if (rl != nil) { + arootoff = rl.off; + } else { + let gok: bool = isletvar(c, ch.str); + if (!gok) { + if (defvarstructinfo(c, ch.str) != nil) { gok = true; }; + }; + if (!gok) { + let dtn: *node = defvartnode(c, ch.str); + if (dtn != nil) { + if (dtn.kind == nkind.N_TARRAY) { gok = true; }; + }; + }; + if (!gok) { aok = false; }; + }; + } else { + if (ch.kind != nkind.N_UN || ch.op != tkind.TK_STAR) { + aok = false; + }; + }; + }; + if (!aok) { + let m34p: str = "#34: append() struct element source shape unsupported (rule-7)\n"; + os.write(2, m34p.ptr, m34p.len: u64); + os.exit(1); + }; + aroot = ch; + if (aroot.kind == nkind.N_UN) { + asroot = localadd(c, "@appendsroot", 8, nil); + cgexpr(c, aroot.lhs); + emitline("\tMOVQ\tAX, "); + emitoff(asroot: i64); + emitline("(BP)\n"); + }; + if (aidx != nil) { + asoff = localadd(c, "@appendsoff", 8, nil); + cgexpr(c, aidx); + if (aidxesz > 1) { + emitline("\tMOVQ\t$"); + emitint(aidxesz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tMOVQ\tAX, "); + emitoff(asoff: i64); + emitline("(BP)\n"); + }; + aplace = true; + }; cgappendgrow(c, sndirect, sn_off, snscr, esz); cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); if (eltagged) { @@ -25043,6 +25168,86 @@ fn cgappend(c: *cgen, n: *node) void = { vn = vn.next; continue; }; + if (aplace) { + // phase 2: dst slot to @appendscr, base from + // the live storage, stashed offsets back on + // top. + let pscroff: i32 = localadd(c, "@appendscr", 8, nil); + emitline("\tMOVQ\tBX, "); + emitoff(pscroff: i64); + emitline("(BP)\n"); + if (aroot.kind == nkind.N_IDENT) { + if (arootoff != 0) { + emitline("\tLEAQ\t"); + emitoff(arootoff: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, aroot.str); + emitline("(SB), BX\n"); + }; + } else { + emitline("\tMOVQ\t"); + emitoff(asroot: i64); + emitline("(BP), BX\n"); + }; + if (aidx != nil) { + if (abaseslice) { + emitline("\tMOVQ\t(BX), BX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(asoff: i64); + emitline("(BP), AX\n"); + emitline("\tADDQ\tAX, BX\n"); + }; + if (afld != 0) { + emitline("\tADDQ\t$"); + emitint(afld: i64); + emitline(", BX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(pscroff: i64); + emitline("(BP), DX\n"); + let pk: i32 = 0; + for (pk + 8 <= esz) { + emitline("\tMOVQ\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 8; + }; + if (pk + 4 <= esz) { + emitline("\tMOVL\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVL\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 4; + }; + if (pk + 2 <= esz) { + emitline("\tMOVW\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVW\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 2; + }; + if (pk + 1 <= esz) { + emitline("\tMOVB\t"); + emitdispreg(pk: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVB\tAX, "); + emitdispreg(pk: i64, "DX"); + emitline("\n"); + pk += 1; + }; + vn = vn.next; + continue; + }; let m34e: str = "#34: append() struct element source shape unsupported (rule-7)\n"; os.write(2, m34e.ptr, m34e.len: u64); os.exit(1); diff --git a/test/wcc/806_append_place.c b/test/wcc/806_append_place.c index 6c8e88bf..c242ede0 100644 --- a/test/wcc/806_append_place.c +++ b/test/wcc/806_append_place.c @@ -44,8 +44,25 @@ * | @apphdrscr slot (a shared slot clobbers)| * identroot_dot | h.xs via *holder param — C1.5's reject, | 73 * | graduated by C2's resolver ident root | + * idxfield_struct | append(res, threads[i].root_capture) | 74 + * | 56B indexed-FIELD source (#49, ha:819) | + * idx_computed_elem | append(ds, bs[i + 1]) computed-index | 75 + * | whole-element source | + * derefspine_field | append(*r, (*q)[i].rc) param spines | 76 + * | both sides | + * deref_src | append(*ds, *p) deref source | 77 + * self_append_realloc | append(bs, bs[i]) ×33 — source inside | 78 + * | the grown slice, resolve-after-grow | + * selfidx_oldlen_once | append(bs, bs[f(bs)]) — index expr runs | 80 + * | ONCE, PRE-grow (sees old len): the #49 | + * | ruling's split-order semantics pin | + * elem_kinds_place | place-resolved SOURCE × every other | 79 + * | element kind (scalar/narrow/str/slice/ | + * | tagged via the pre-existing arms) | * reject_spread_src | non-ident spread SOURCE through a deref | BUILD_FAIL * | target (the #35 designed boundary) | + * reject_call_src | CALL rvalue element source — the #49 | BUILD_FAIL + * | loud tail (#42-style bound) | * * BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring, * both stages) — a build that fails for any other reason (parse error, @@ -395,6 +412,190 @@ static const struct row rows[] = { "};\n", 73, NULL }, + /* #49 (#35's single-element sibling): the struct-element append + * arm dispatched on SOURCE node kind — N_STRUCTLIT and N_IDENT + * only; every place-resolvable chain (the regex.ha:819 + * result-build line `append(res, threads[best_idx].root_capture)`) + * died on the rule-7 fatal. The fix routes the remaining shapes + * through cgplaceaddr (the C1 resolver): dst slot spills to + * @appendscr, source resolves AFTER the grow, whole-width + * word-copy through DX. Five rows: indexed-field 56B capture, + * computed-index whole element, deref-spine through params, + * deref source, self-append across reallocs. */ + { "idxfield_struct", + "package main;\n" + "type capture = struct { content: str, start: size,\n" + "\tstart_bytesize: size, end: size, end_bytesize: size };\n" + "type thread = struct { pc: size, root_capture: capture,\n" + "\tmatched: bool };\n" + "export fn main() i32 = {\n" + "\tlet threads: []thread = [];\n" + "\tappend(threads, thread { pc = 7, ... });\n" + "\tappend(threads, thread { pc = 1, root_capture = capture {\n" + "\t\tcontent = \"bcd\", start = 1, start_bytesize = 2,\n" + "\t\tend = 4, end_bytesize = 5 }, ... });\n" + "\tlet res: []capture = [];\n" + "\tlet i: size = 1;\n" + "\tappend(res, threads[i].root_capture);\n" + "\tif (len(res) != 1) { return 1; };\n" + "\tif (res[0].start != 1) { return 2; };\n" + "\tif (res[0].start_bytesize != 2) { return 3; };\n" + "\tif (res[0].end != 4) { return 4; };\n" + "\tif (res[0].end_bytesize != 5) { return 5; };\n" + "\tif (res[0].content.len != 3) { return 6; };\n" + "\tif (threads[1].root_capture.end != 4) { return 7; };\n" + "\treturn 74;\n" + "};\n", + 74, NULL }, + + { "idx_computed_elem", + "package main;\n" + "type box = struct { pc: size, a: i64, b: i64, x: i64 };\n" + "export fn main() i32 = {\n" + "\tlet bs: []box = [];\n" + "\tappend(bs, box { pc = 1, a = 10, ... });\n" + "\tappend(bs, box { pc = 2, a = 20, ... });\n" + "\tappend(bs, box { pc = 3, a = 30, b = 7, x = 8 });\n" + "\tlet ds: []box = [];\n" + "\tlet i: i64 = 1;\n" + "\tappend(ds, bs[i + 1]);\n" + "\tif (len(ds) != 1) { return 1; };\n" + "\tif (ds[0].pc != 3) { return 2; };\n" + "\tif (ds[0].a != 30) { return 3; };\n" + "\tif (ds[0].b != 7 || ds[0].x != 8) { return 4; };\n" + "\treturn 75;\n" + "};\n", + 75, NULL }, + + { "derefspine_field", + "package main;\n" + "type cap = struct { s: size, e: size };\n" + "type th = struct { pc: size, rc: cap, m: bool };\n" + "fn pick(r: *[]cap, q: *[]th, i: size) void = {\n" + "\tappend(*r, (*q)[i].rc);\n" + "};\n" + "export fn main() i32 = {\n" + "\tlet ts: []th = [];\n" + "\tappend(ts, th { pc = 1, rc = cap { s = 5, e = 9 }, ... });\n" + "\tlet rs: []cap = [];\n" + "\tpick(&rs, &ts, 0);\n" + "\tif (len(rs) != 1) { return 1; };\n" + "\tif (rs[0].s != 5) { return 2; };\n" + "\tif (rs[0].e != 9) { return 3; };\n" + "\treturn 76;\n" + "};\n", + 76, NULL }, + + { "deref_src", + "package main;\n" + "type box = struct { pc: size, a: i64, b: i64, x: i64 };\n" + "fn addsrc(ds: *[]box, p: *box) void = { append(*ds, *p); };\n" + "export fn main() i32 = {\n" + "\tlet b: box = box { pc = 4, a = 40, b = 41, x = 42 };\n" + "\tlet ds: []box = [];\n" + "\taddsrc(&ds, &b);\n" + "\tif (len(ds) != 1) { return 1; };\n" + "\tif (ds[0].pc != 4) { return 2; };\n" + "\tif (ds[0].a != 40 || ds[0].b != 41 || ds[0].x != 42) { return 3; };\n" + "\treturn 77;\n" + "};\n", + 77, NULL }, + + /* Self-append: the source element lives in the SLICE BEING GROWN, + * so its address is only valid post-rt_ensure — pins the + * resolve-AFTER-grow order across several cap-doubling reallocs. + * Sentinels around the header pin the frame. */ + { "self_append_realloc", + "package main;\n" + "type box = struct { pc: size, a: i64, b: i64, x: i64 };\n" + "export fn main() i32 = {\n" + "\tlet lo: i64 = 111;\n" + "\tlet bs: []box = [];\n" + "\tlet hi: i64 = 222;\n" + "\tappend(bs, box { pc = 9, a = 100, b = 200, x = 300 });\n" + "\tlet i: i32 = 0;\n" + "\tfor (i < 33) {\n" + "\t\tappend(bs, bs[i]);\n" + "\t\ti += 1;\n" + "\t};\n" + "\tif (len(bs) != 34) { return 1; };\n" + "\tif (bs[0].pc != 9 || bs[33].pc != 9) { return 2; };\n" + "\tif (bs[17].a != 100 || bs[33].b != 200) { return 3; };\n" + "\tif (bs[33].x != 300) { return 4; };\n" + "\tif (lo != 111 || hi != 222) { return 5; };\n" + "\treturn 78;\n" + "};\n", + 78, NULL }, + + /* The #49 ruling's order pin, positive semantics row (converged + * with Hare, not a documented divergence): the source chain's + * rvalues — here a CALLED index helper reading the slice's len — + * evaluate exactly ONCE, PRE-grow. calls != 1 catches a + * double-evaluation; bs[2] != old-last catches a post-grow + * resolution (the helper would see the bumped len and return the + * uninitialized new slot — the pre-split emission failed exactly + * there, exit 3). */ + { "selfidx_oldlen_once", + "package main;\n" + "type box = struct { pc: i64, a: i64 };\n" + "let calls: i64 = 0;\n" + "fn lastidx(xs: []box) i64 = {\n" + "\tcalls += 1;\n" + "\tlet n: i64 = len(xs): i64;\n" + "\treturn n - 1;\n" + "};\n" + "export fn main() i32 = {\n" + "\tlet bs: []box = [];\n" + "\tappend(bs, box { pc = 11, a = 1 });\n" + "\tappend(bs, box { pc = 4242, a = 2 });\n" + "\tappend(bs, bs[lastidx(bs)]);\n" + "\tif (calls != 1) { return 1; };\n" + "\tif (len(bs) != 3) { return 2; };\n" + "\tif (bs[2].pc != 4242 || bs[2].a != 2) { return 3; };\n" + "\tif (bs[0].pc != 11) { return 4; };\n" + "\treturn 80;\n" + "};\n", + 80, NULL }, + + /* Element-kind × place-source matrix: struct was the ONLY kind + * the source-shape dispatch rejected — scalar (wide + narrow), + * str-header, slice-header and tagged sources route through the + * pre-existing cgexpr-based arms and already worked at 796d41b. + * This row is the regression net pinning that matrix (runtime + + * byte-id, dual-driver), not a fix pin. */ + { "elem_kinds_place", + "package main;\n" + "type box = struct { n: i64, m: i32, name: str, xs: []u8 };\n" + "type tv = (i64 | void);\n" + "export fn main() i32 = {\n" + "\tlet inner: []u8 = [];\n" + "\tappend(inner, 7u8);\n" + "\tappend(inner, 8u8);\n" + "\tlet bs: []box = [];\n" + "\tappend(bs, box { n = 41, m = 9, name = \"hello\", xs = inner });\n" + "\tlet ns: []i64 = [];\n" + "\tappend(ns, bs[0].n);\n" + "\tlet ms: []i32 = [];\n" + "\tappend(ms, bs[0].m);\n" + "\tlet ss: []str = [];\n" + "\tappend(ss, bs[0].name);\n" + "\tlet vs: [][]u8 = [];\n" + "\tappend(vs, bs[0].xs);\n" + "\tlet ts: []tv = [];\n" + "\tappend(ts, bs[0].n);\n" + "\tif (len(ns) != 1 || ns[0] != 41) { return 1; };\n" + "\tif (len(ms) != 1 || ms[0] != 9) { return 2; };\n" + "\tif (len(ss) != 1 || len(ss[0]) != 5) { return 3; };\n" + "\tif (len(vs) != 1 || len(vs[0]) != 2) { return 4; };\n" + "\tif (vs[0][1] != 8u8) { return 5; };\n" + "\tmatch (ts[0]) {\n" + "\tcase let v: i64 => { if (v != 41) { return 6; }; };\n" + "\tcase void => { return 7; };\n" + "\t};\n" + "\treturn 79;\n" + "};\n", + 79, NULL }, + /* 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. */ @@ -411,6 +612,20 @@ static const struct row rows[] = { "\treturn 0;\n" "};\n", BUILD_FAIL, "#34: append() spread source shape unsupported (rule-7)" }, + + /* The #49 boundary: a CALL rvalue source has no place to resolve + * — stays loud (the #42-style bound) until a scratch-receive + * route lands. */ + { "reject_call_src", + "package main;\n" + "type box = struct { pc: size, a: i64 };\n" + "fn mk() box = { return box { pc = 1, a = 2 }; };\n" + "export fn main() i32 = {\n" + "\tlet bs: []box = [];\n" + "\tappend(bs, mk());\n" + "\treturn 0;\n" + "};\n", + BUILD_FAIL, "#34: append() struct element source shape unsupported (rule-7)" }, }; /* errlog_has — the build-failure stderr must carry the row's expected