wcc+w6c_ww: append() spread place-chain sources via cgplaceaddr (#35)
#35 (fold-5 blocker, PG6): the append() spread arm dispatched on SOURCE node kind — ident-local only; both fold-5 dup shapes loud-rejected on BOTH stages: deref-spine `append(dup, (*tsp)[0].caps...)` (add_thread regex.ha:569/572) and indexed `append(res, ts[0].caps...)` (search ha:820). Route every non-ident-local source through cgplaceaddr (the established place choke-point: C1 stores, C1.25 aggregate fields, FA1 append targets, #49 element sources): the source header ADDRESS resolves ONCE, pre-grow, into a fresh per-site @appsprscr spill, and every loop iteration re-reads .ptr/.len THROUGH the spilled header after the grow. This adapts the #49 split ruling to the spread's full-range copy: the chain's rvalues (deref-root pointer expr, index exprs) evaluate exactly once PRE-grow — an index reading the dst's len sees the pre-append value — while the source data base and len re-derive live each iteration, so a source header aliasing the dst header re-roots post-realloc. A header reached through a buffer the grow reallocs reads the STALE copy — bit-identical under the non-reclaiming rt/malloc, the same documented #49 stale-base hole (pinned by the spread_selfalias_chain row). Ident-local sources keep the legacy BP-disp emission byte-identical. New loudness with the same fix: the spread source's stamped type must chase to TY_SLICE/TY_STR — a [N]T array ident source previously read its first 16 DATA bytes as a {ptr,len} header, silently. Array wiring plus the remaining rvalue sources (CALL, slicing exprs) stay loud, filed task #27. Global-ident sources now resolve on cstage but are blocked by a pre-existing wwstage checker reject ("let: not assignable", task #29) — no dual-stage row until that closes. 806_append_place: reject_spread_src GRADUATES to a runtime row (spread_place_deref, the old reject source verbatim + readback); new rows spread_dup_copy (PG6 verbatim: both fold-5 shapes, 56B capture elements, copy-semantics mutation pin, want 139), spread_place_kinds (str 24B headers / narrow i32 / empty deref source), spread_growth_place (40-elem spread crossing cap doublings), spread_selfalias_chain (source header inside the dst's grown buffer), and the two new loud-tail rejects (CALL rvalue, [N]T array) pinning the "#35:" diagnostic on both stages. 87 fixtures green (was 70), per-row cs/ww asm byte-cmp included. Unblocks regex fold-5a: add_thread regex.ha:569/572 + search ha:820 spreads go from loud-bound to real spread.
This commit is contained in:
@@ -7481,19 +7481,56 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* local ident used to fall PAST the spread arm
|
||||
* into the single-value stores with the
|
||||
* N_SPREAD node (cstage garbage store; wwstage
|
||||
* silently SKIPPED it — divergent). Deferred
|
||||
* source shape, task #37. */
|
||||
* silently SKIPPED it — divergent). */
|
||||
if (vn->kind == N_SPREAD) {
|
||||
if (!vn->lhs || vn->lhs->kind != N_IDENT)
|
||||
fatal("#34: append() spread source "
|
||||
/* #35: only a {ptr,len,cap}-headered
|
||||
* source reads as a header below; a
|
||||
* [N]T array place IS its storage —
|
||||
* the ident path used to read its
|
||||
* first 16 data bytes as ptr/len,
|
||||
* silently. Loud until wired (task
|
||||
* #27); str shares the slice header
|
||||
* layout. */
|
||||
Type *itu = vn->lhs
|
||||
? type_chase_named(vn->lhs->type) : NULL;
|
||||
if (itu == NULL || (itu->kind != TY_SLICE
|
||||
&& itu->kind != TY_STR))
|
||||
fatal("#35: append() spread source "
|
||||
"shape unsupported (rule-7)");
|
||||
if (localfind(locals, vn->lhs->str) == 0)
|
||||
fatal("#34: append() spread source "
|
||||
"ident is not a local (rule-7)");
|
||||
}
|
||||
if (vn->kind == N_SPREAD &&
|
||||
vn->lhs && vn->lhs->kind == N_IDENT) {
|
||||
int it_off = localfind(locals, vn->lhs->str);
|
||||
int it_off = 0;
|
||||
int it_scr = 0;
|
||||
if (vn->lhs->kind == N_IDENT)
|
||||
it_off = localfind(locals, vn->lhs->str);
|
||||
if (it_off == 0) {
|
||||
/* #35: place-chain source (deref
|
||||
* spine, indexed chain, global
|
||||
* ident — the regex.ha:569/820
|
||||
* dup shapes) resolves its header
|
||||
* ADDRESS through cgplaceaddr
|
||||
* ONCE, pre-grow: the chain's
|
||||
* rvalues run exactly once (the
|
||||
* #49 split's pre-grow half) and
|
||||
* every iteration re-reads
|
||||
* .ptr/.len THROUGH the spilled
|
||||
* header post-grow (the live
|
||||
* re-derivation half). A header
|
||||
* reached through a buffer the
|
||||
* grow reallocs keeps Hare's
|
||||
* stale-base hole — see the #49
|
||||
* comment below. Rvalue sources
|
||||
* (CALL, slicing exprs) have no
|
||||
* place — loud, task #27. Fresh
|
||||
* spill slot per SITE for the
|
||||
* same nesting reason as
|
||||
* @apphdrscr above. */
|
||||
if (!cgplaceaddr(c, vn->lhs, D_BX, locals))
|
||||
fatal("#35: append() spread source "
|
||||
"shape unsupported (rule-7)");
|
||||
it_scr = local_alloc(c, &locals,
|
||||
"@appsprscr", 8, cg_frame);
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, it_scr));
|
||||
}
|
||||
int load_op = fldloadop(esub, esz);
|
||||
/* push counter (i) on stack */
|
||||
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
|
||||
@@ -7502,7 +7539,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
char *le = mklabel(c, "spr_e");
|
||||
label(c, ll);
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_CX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, it_off + 8), areg(D_DX));
|
||||
if (it_scr != 0) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, it_scr), areg(D_DX));
|
||||
ins2(c, A_MOVQ, amem(D_DX, 8), areg(D_DX));
|
||||
} else
|
||||
ins2(c, A_MOVQ, amem(D_BP, it_off + 8), areg(D_DX));
|
||||
ins2(c, A_CMPQ, areg(D_DX), areg(D_CX));
|
||||
ins1(c, A_JGE, abranch(le));
|
||||
if (el_wide) {
|
||||
@@ -7522,7 +7563,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
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, it_off), areg(D_BX));
|
||||
if (it_scr != 0) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, it_scr), areg(D_BX));
|
||||
ins2(c, A_MOVQ, amem(D_BX, 0), areg(D_BX));
|
||||
} else
|
||||
ins2(c, A_MOVQ, amem(D_BP, it_off), areg(D_BX));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_DX);
|
||||
@@ -7553,7 +7598,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
continue;
|
||||
}
|
||||
/* AX = items.ptr[i] */
|
||||
ins2(c, A_MOVQ, amem(D_BP, it_off), areg(D_BX));
|
||||
if (it_scr != 0) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, it_scr), areg(D_BX));
|
||||
ins2(c, A_MOVQ, amem(D_BX, 0), areg(D_BX));
|
||||
} else
|
||||
ins2(c, A_MOVQ, amem(D_BP, it_off), areg(D_BX));
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
||||
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
||||
|
||||
@@ -25244,21 +25244,53 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
// #34 review: a non-ident/non-local spread source used
|
||||
// to be silently SKIPPED here while cstage fell past
|
||||
// its spread arm into the single-value stores with the
|
||||
// N_SPREAD node (garbage store) — divergent. Deferred
|
||||
// source shape, task #37.
|
||||
// N_SPREAD node (garbage store) — divergent.
|
||||
let it: *node = vn.lhs;
|
||||
if (it == nil || it.kind != nkind.N_IDENT) {
|
||||
let m34p: str = "#34: append() spread source shape unsupported (rule-7)\n";
|
||||
os.write(2, m34p.ptr, m34p.len: u64);
|
||||
// #35: only a {ptr,len,cap}-headered source reads as a
|
||||
// header below; a [N]T array place IS its storage — the
|
||||
// ident path used to read its first 16 data bytes as
|
||||
// ptr/len, silently. Loud until wired (task #27); str
|
||||
// shares the slice header layout.
|
||||
let itu: *tinfo = nil;
|
||||
if (it != nil) { itu = it.type_: *tinfo; };
|
||||
for (itu != nil && itu.kind == tykind.TY_NAMED) { itu = itu.under; };
|
||||
if (itu == nil || (itu.kind != tykind.TY_SLICE
|
||||
&& itu.kind != tykind.TY_STR)) {
|
||||
let m35p: str = "#35: append() spread source shape unsupported (rule-7)\n";
|
||||
os.write(2, m35p.ptr, m35p.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let itlocal: *local = localfindnode(c, it.str);
|
||||
if (itlocal == nil) {
|
||||
let m34q: str = "#34: append() spread source ident is not a local (rule-7)\n";
|
||||
os.write(2, m34q.ptr, m34q.len: u64);
|
||||
os.exit(1);
|
||||
let it_off: i32 = 0;
|
||||
let itscr: i32 = 0;
|
||||
if (it.kind == nkind.N_IDENT) {
|
||||
let itlocal: *local = localfindnode(c, it.str);
|
||||
if (itlocal != nil) { it_off = itlocal.off; };
|
||||
};
|
||||
if (it_off == 0) {
|
||||
// #35: place-chain source (deref spine, indexed
|
||||
// chain, global ident — the regex.ha:569/820 dup
|
||||
// shapes) resolves its header ADDRESS through
|
||||
// cgplaceaddr ONCE, pre-grow: the chain's rvalues
|
||||
// run exactly once (the #49 split's pre-grow
|
||||
// half) and every iteration re-reads .ptr/.len
|
||||
// THROUGH the spilled header post-grow (the live
|
||||
// re-derivation half). A header reached through a
|
||||
// buffer the grow reallocs keeps Hare's
|
||||
// stale-base hole — see the #49 comment below.
|
||||
// Rvalue sources (CALL, slicing exprs) have no
|
||||
// place — loud, task #27. Fresh spill slot per
|
||||
// SITE for the same nesting reason as @apphdrscr
|
||||
// above.
|
||||
if (!cgplaceaddr(c, it, "BX")) {
|
||||
let m35q: str = "#35: append() spread source shape unsupported (rule-7)\n";
|
||||
os.write(2, m35q.ptr, m35q.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
itscr = localalloc(c, "@appsprscr", 8, nil);
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
let it_off: i32 = itlocal.off;
|
||||
let load_op: str = tnodeloadop(c, etnode, esz);
|
||||
if (!sndirect) {
|
||||
// FA1: no etnode behind `*p` — signedness off the
|
||||
@@ -25272,9 +25304,16 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
let le: str = mklabel(c, "spr_e");
|
||||
emitlabel(ll);
|
||||
emitline("\tMOVQ\t(SP), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((it_off + 8): i64);
|
||||
emitline("(BP), DX\n");
|
||||
if (itscr != 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP), DX\n");
|
||||
emitline("\tMOVQ\t8(DX), DX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((it_off + 8): i64);
|
||||
emitline("(BP), DX\n");
|
||||
};
|
||||
emitline("\tCMPQ\tDX, CX\n");
|
||||
emitline("\tJGE\t"); emitline(le); emitline("\n");
|
||||
if (elwide) {
|
||||
@@ -25293,9 +25332,16 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
if (itscr != 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tMOVQ\t(BX), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tCX, BX\n");
|
||||
cgappendslot(c, sndirect, sn_off, snscr, esz, "DX");
|
||||
let wk: i32 = 0;
|
||||
@@ -25342,9 +25388,16 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
if (itscr != 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tMOVQ\t(BX), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
|
||||
@@ -4810,21 +4810,53 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
// #34 review: a non-ident/non-local spread source used
|
||||
// to be silently SKIPPED here while cstage fell past
|
||||
// its spread arm into the single-value stores with the
|
||||
// N_SPREAD node (garbage store) — divergent. Deferred
|
||||
// source shape, task #37.
|
||||
// N_SPREAD node (garbage store) — divergent.
|
||||
let it: *node = vn.lhs;
|
||||
if (it == nil || it.kind != nkind.N_IDENT) {
|
||||
let m34p: str = "#34: append() spread source shape unsupported (rule-7)\n";
|
||||
os.write(2, m34p.ptr, m34p.len: u64);
|
||||
// #35: only a {ptr,len,cap}-headered source reads as a
|
||||
// header below; a [N]T array place IS its storage — the
|
||||
// ident path used to read its first 16 data bytes as
|
||||
// ptr/len, silently. Loud until wired (task #27); str
|
||||
// shares the slice header layout.
|
||||
let itu: *tinfo = nil;
|
||||
if (it != nil) { itu = it.type_: *tinfo; };
|
||||
for (itu != nil && itu.kind == tykind.TY_NAMED) { itu = itu.under; };
|
||||
if (itu == nil || (itu.kind != tykind.TY_SLICE
|
||||
&& itu.kind != tykind.TY_STR)) {
|
||||
let m35p: str = "#35: append() spread source shape unsupported (rule-7)\n";
|
||||
os.write(2, m35p.ptr, m35p.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let itlocal: *local = localfindnode(c, it.str);
|
||||
if (itlocal == nil) {
|
||||
let m34q: str = "#34: append() spread source ident is not a local (rule-7)\n";
|
||||
os.write(2, m34q.ptr, m34q.len: u64);
|
||||
os.exit(1);
|
||||
let it_off: i32 = 0;
|
||||
let itscr: i32 = 0;
|
||||
if (it.kind == nkind.N_IDENT) {
|
||||
let itlocal: *local = localfindnode(c, it.str);
|
||||
if (itlocal != nil) { it_off = itlocal.off; };
|
||||
};
|
||||
if (it_off == 0) {
|
||||
// #35: place-chain source (deref spine, indexed
|
||||
// chain, global ident — the regex.ha:569/820 dup
|
||||
// shapes) resolves its header ADDRESS through
|
||||
// cgplaceaddr ONCE, pre-grow: the chain's rvalues
|
||||
// run exactly once (the #49 split's pre-grow
|
||||
// half) and every iteration re-reads .ptr/.len
|
||||
// THROUGH the spilled header post-grow (the live
|
||||
// re-derivation half). A header reached through a
|
||||
// buffer the grow reallocs keeps Hare's
|
||||
// stale-base hole — see the #49 comment below.
|
||||
// Rvalue sources (CALL, slicing exprs) have no
|
||||
// place — loud, task #27. Fresh spill slot per
|
||||
// SITE for the same nesting reason as @apphdrscr
|
||||
// above.
|
||||
if (!cgplaceaddr(c, it, "BX")) {
|
||||
let m35q: str = "#35: append() spread source shape unsupported (rule-7)\n";
|
||||
os.write(2, m35q.ptr, m35q.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
itscr = localalloc(c, "@appsprscr", 8, nil);
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
let it_off: i32 = itlocal.off;
|
||||
let load_op: str = tnodeloadop(c, etnode, esz);
|
||||
if (!sndirect) {
|
||||
// FA1: no etnode behind `*p` — signedness off the
|
||||
@@ -4838,9 +4870,16 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
let le: str = mklabel(c, "spr_e");
|
||||
emitlabel(ll);
|
||||
emitline("\tMOVQ\t(SP), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((it_off + 8): i64);
|
||||
emitline("(BP), DX\n");
|
||||
if (itscr != 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP), DX\n");
|
||||
emitline("\tMOVQ\t8(DX), DX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((it_off + 8): i64);
|
||||
emitline("(BP), DX\n");
|
||||
};
|
||||
emitline("\tCMPQ\tDX, CX\n");
|
||||
emitline("\tJGE\t"); emitline(le); emitline("\n");
|
||||
if (elwide) {
|
||||
@@ -4859,9 +4898,16 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
if (itscr != 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tMOVQ\t(BX), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tCX, BX\n");
|
||||
cgappendslot(c, sndirect, sn_off, snscr, esz, "DX");
|
||||
let wk: i32 = 0;
|
||||
@@ -4908,9 +4954,16 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
if (itscr != 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tMOVQ\t(BX), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
|
||||
@@ -25244,21 +25244,53 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
// #34 review: a non-ident/non-local spread source used
|
||||
// to be silently SKIPPED here while cstage fell past
|
||||
// its spread arm into the single-value stores with the
|
||||
// N_SPREAD node (garbage store) — divergent. Deferred
|
||||
// source shape, task #37.
|
||||
// N_SPREAD node (garbage store) — divergent.
|
||||
let it: *node = vn.lhs;
|
||||
if (it == nil || it.kind != nkind.N_IDENT) {
|
||||
let m34p: str = "#34: append() spread source shape unsupported (rule-7)\n";
|
||||
os.write(2, m34p.ptr, m34p.len: u64);
|
||||
// #35: only a {ptr,len,cap}-headered source reads as a
|
||||
// header below; a [N]T array place IS its storage — the
|
||||
// ident path used to read its first 16 data bytes as
|
||||
// ptr/len, silently. Loud until wired (task #27); str
|
||||
// shares the slice header layout.
|
||||
let itu: *tinfo = nil;
|
||||
if (it != nil) { itu = it.type_: *tinfo; };
|
||||
for (itu != nil && itu.kind == tykind.TY_NAMED) { itu = itu.under; };
|
||||
if (itu == nil || (itu.kind != tykind.TY_SLICE
|
||||
&& itu.kind != tykind.TY_STR)) {
|
||||
let m35p: str = "#35: append() spread source shape unsupported (rule-7)\n";
|
||||
os.write(2, m35p.ptr, m35p.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let itlocal: *local = localfindnode(c, it.str);
|
||||
if (itlocal == nil) {
|
||||
let m34q: str = "#34: append() spread source ident is not a local (rule-7)\n";
|
||||
os.write(2, m34q.ptr, m34q.len: u64);
|
||||
os.exit(1);
|
||||
let it_off: i32 = 0;
|
||||
let itscr: i32 = 0;
|
||||
if (it.kind == nkind.N_IDENT) {
|
||||
let itlocal: *local = localfindnode(c, it.str);
|
||||
if (itlocal != nil) { it_off = itlocal.off; };
|
||||
};
|
||||
if (it_off == 0) {
|
||||
// #35: place-chain source (deref spine, indexed
|
||||
// chain, global ident — the regex.ha:569/820 dup
|
||||
// shapes) resolves its header ADDRESS through
|
||||
// cgplaceaddr ONCE, pre-grow: the chain's rvalues
|
||||
// run exactly once (the #49 split's pre-grow
|
||||
// half) and every iteration re-reads .ptr/.len
|
||||
// THROUGH the spilled header post-grow (the live
|
||||
// re-derivation half). A header reached through a
|
||||
// buffer the grow reallocs keeps Hare's
|
||||
// stale-base hole — see the #49 comment below.
|
||||
// Rvalue sources (CALL, slicing exprs) have no
|
||||
// place — loud, task #27. Fresh spill slot per
|
||||
// SITE for the same nesting reason as @apphdrscr
|
||||
// above.
|
||||
if (!cgplaceaddr(c, it, "BX")) {
|
||||
let m35q: str = "#35: append() spread source shape unsupported (rule-7)\n";
|
||||
os.write(2, m35q.ptr, m35q.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
itscr = localalloc(c, "@appsprscr", 8, nil);
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
let it_off: i32 = itlocal.off;
|
||||
let load_op: str = tnodeloadop(c, etnode, esz);
|
||||
if (!sndirect) {
|
||||
// FA1: no etnode behind `*p` — signedness off the
|
||||
@@ -25272,9 +25304,16 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
let le: str = mklabel(c, "spr_e");
|
||||
emitlabel(ll);
|
||||
emitline("\tMOVQ\t(SP), CX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((it_off + 8): i64);
|
||||
emitline("(BP), DX\n");
|
||||
if (itscr != 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP), DX\n");
|
||||
emitline("\tMOVQ\t8(DX), DX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((it_off + 8): i64);
|
||||
emitline("(BP), DX\n");
|
||||
};
|
||||
emitline("\tCMPQ\tDX, CX\n");
|
||||
emitline("\tJGE\t"); emitline(le); emitline("\n");
|
||||
if (elwide) {
|
||||
@@ -25293,9 +25332,16 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
if (itscr != 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tMOVQ\t(BX), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tCX, BX\n");
|
||||
cgappendslot(c, sndirect, sn_off, snscr, esz, "DX");
|
||||
let wk: i32 = 0;
|
||||
@@ -25342,9 +25388,16 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
vn = vn.next;
|
||||
continue;
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
if (itscr != 0) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(itscr: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tMOVQ\t(BX), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(it_off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
|
||||
@@ -59,8 +59,21 @@
|
||||
* 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) |
|
||||
* spread_place_deref | append(*p, (*q)[i].xs...) — the old #35 | 81
|
||||
* | reject source, graduated (cgplaceaddr) |
|
||||
* spread_dup_copy | PG6: deref-spine + indexed 56B-struct | 139
|
||||
* | spread sources (regex.ha:569/820 dup |
|
||||
* | shapes) + copy-semantics pin |
|
||||
* spread_place_kinds | place-sourced spread × str header / | 82
|
||||
* | narrow i32 / empty deref source |
|
||||
* spread_growth_place | 40-elem place-sourced spread crossing | 83
|
||||
* | cap doublings (dst base moves mid-loop) |
|
||||
* spread_selfalias_chain| source header inside the dst's grown | 119
|
||||
* | buffer — #49 stale-base hole pin |
|
||||
* reject_spread_call_src| CALL rvalue spread source — #35 | BUILD_FAIL
|
||||
* | residual (task #27) |
|
||||
* reject_spread_array_src| [N]T array spread source — was a | BUILD_FAIL
|
||||
* | silent 16-byte garbage header read |
|
||||
* reject_call_src | CALL rvalue element source — the #49 | BUILD_FAIL
|
||||
* | loud tail (#42-style bound) |
|
||||
*
|
||||
@@ -596,22 +609,169 @@ static const struct row rows[] = {
|
||||
"};\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. */
|
||||
{ "reject_spread_src",
|
||||
/* #35 graduation: the FA4-era designed boundary — a place-chain
|
||||
* spread SOURCE — now resolves through cgplaceaddr (header
|
||||
* address pre-grow, ptr/len re-read through it per iteration).
|
||||
* This row is the OLD reject source verbatim, now with elements
|
||||
* and a readback. */
|
||||
{ "spread_place_deref",
|
||||
"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 inner: []i64 = [];\n"
|
||||
"\tappend(inner, 5);\n"
|
||||
"\tappend(inner, 9);\n"
|
||||
"\tlet ys: []i64 = [];\n"
|
||||
"\tlet hs: []holder = [];\n"
|
||||
"\tappend(hs, holder { tag = 1, xs = inner });\n"
|
||||
"\tdup(&ys, &hs, 0);\n"
|
||||
"\tif (len(ys) != 2) { return 1; };\n"
|
||||
"\tif (ys[0] != 5 || ys[1] != 9) { return 2; };\n"
|
||||
"\treturn 81;\n"
|
||||
"};\n",
|
||||
81, NULL },
|
||||
|
||||
/* #35, the fold-5 dup shapes (PG6): (a) deref-spine source
|
||||
* `(*tsp)[0].caps...` (add_thread regex.ha:569/572), (b) indexed
|
||||
* source `ts[0].caps...` (search ha:820), 56B struct elements;
|
||||
* plus the COPY-semantics pin — mutating the dup must not touch
|
||||
* the source. */
|
||||
{ "spread_dup_copy",
|
||||
"package main;\n"
|
||||
"type capture = struct {\n"
|
||||
"\tcontent: str,\n"
|
||||
"\tstart: size,\n"
|
||||
"\tstart_bytesize: size,\n"
|
||||
"\tend: size,\n"
|
||||
"\tend_bytesize: size,\n"
|
||||
"};\n"
|
||||
"type thr = struct { pc: size, caps: []capture };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet c0: []capture = [];\n"
|
||||
"\tappend(c0, capture { content = \"x\", start = 3: size, start_bytesize = 3: size, end = 8: size, end_bytesize = 8: size });\n"
|
||||
"\tappend(c0, capture { content = \"yz\", start = 5: size, start_bytesize = 5: size, end = 9: size, end_bytesize = 9: size });\n"
|
||||
"\tlet ts: []thr = [];\n"
|
||||
"\tappend(ts, thr { pc = 0: size, caps = c0 });\n"
|
||||
"\tlet tsp = &ts;\n"
|
||||
"\tlet dup: []capture = [];\n"
|
||||
"\tappend(dup, (*tsp)[0].caps...);\n"
|
||||
"\tif (len(dup) != 2) { return 1; };\n"
|
||||
"\tif (len(dup[1].content) != 2 || dup[1].content[0] != 'y') { return 2; };\n"
|
||||
"\tlet res: []capture = [];\n"
|
||||
"\tappend(res, ts[0].caps...);\n"
|
||||
"\tif (len(res) != 2) { return 3; };\n"
|
||||
"\tif (res[0].end != 8 || res[1].start != 5) { return 4; };\n"
|
||||
"\tdup[0].start = 100: size;\n"
|
||||
"\tif (ts[0].caps[0].start != 3) { return 5; };\n"
|
||||
"\tlet acc = dup[0].start + dup[1].end + res[0].start + res[1].end_bytesize\n"
|
||||
"\t\t+ ts[0].caps[0].end + (len(dup): size) * 5;\n"
|
||||
"\treturn acc: i32;\n"
|
||||
"};\n",
|
||||
139, NULL },
|
||||
|
||||
/* #35 element kinds through a PLACE source: str 24B headers,
|
||||
* narrow i32 (scalar load/store width), and the empty source
|
||||
* (zero-iteration loop) through a deref. */
|
||||
{ "spread_place_kinds",
|
||||
"package main;\n"
|
||||
"type holder = struct { tag: i64, ss: []str, ns: []i32 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet h = holder { tag = 1, ss = [], ns = [] };\n"
|
||||
"\tappend(h.ss, \"ab\");\n"
|
||||
"\tappend(h.ss, \"cde\");\n"
|
||||
"\tappend(h.ns, 7: i32);\n"
|
||||
"\tlet hs: []holder = [];\n"
|
||||
"\tappend(hs, h);\n"
|
||||
"\tlet ss: []str = [];\n"
|
||||
"\tappend(ss, hs[0].ss...);\n"
|
||||
"\tif (len(ss) != 2) { return 1; };\n"
|
||||
"\tif (len(ss[0]) != 2 || ss[0][1] != 'b') { return 2; };\n"
|
||||
"\tif (len(ss[1]) != 3 || ss[1][2] != 'e') { return 3; };\n"
|
||||
"\tlet ns: []i32 = [];\n"
|
||||
"\tappend(ns, hs[0].ns...);\n"
|
||||
"\tif (len(ns) != 1 || ns[0] != 7) { return 4; };\n"
|
||||
"\tlet empty: []i64 = [];\n"
|
||||
"\tlet ep = ∅\n"
|
||||
"\tlet acc: []i64 = [];\n"
|
||||
"\tappend(acc, (*ep)...);\n"
|
||||
"\tif (len(acc) != 0) { return 5; };\n"
|
||||
"\treturn 82;\n"
|
||||
"};\n",
|
||||
82, NULL },
|
||||
|
||||
/* #35 growth across cap: a 40-element place-sourced spread into a
|
||||
* non-empty dst crosses several cap doublings — rt_ensure moves
|
||||
* the dst base mid-loop; every slot lands because the dst header
|
||||
* re-reads post-grow each iteration. */
|
||||
{ "spread_growth_place",
|
||||
"package main;\n"
|
||||
"type holder = struct { tag: i64, xs: []i64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet big: []i64 = [];\n"
|
||||
"\tlet i: i64 = 0;\n"
|
||||
"\tfor (i < 40) { append(big, i); i += 1; };\n"
|
||||
"\tlet hs: []holder = [];\n"
|
||||
"\tappend(hs, holder { tag = 2, xs = big });\n"
|
||||
"\tlet dst: []i64 = [];\n"
|
||||
"\tappend(dst, 100);\n"
|
||||
"\tappend(dst, hs[0].xs...);\n"
|
||||
"\tif (len(dst) != 41) { return 1; };\n"
|
||||
"\tif (dst[0] != 100 || dst[1] != 0 || dst[40] != 39) { return 2; };\n"
|
||||
"\treturn 83;\n"
|
||||
"};\n",
|
||||
83, NULL },
|
||||
|
||||
/* #35 aliasing: the source HEADER lives inside the dst's own
|
||||
* buffer (xs[0].kids reached through xs.ptr); the first grow
|
||||
* reallocs that buffer. The pre-grow-resolved header address then
|
||||
* reads the STALE copy — bit-identical under the non-reclaiming
|
||||
* rt/malloc, the documented #49 stale-base hole. Pins that the
|
||||
* spread terminates AND copies the right elements. */
|
||||
{ "spread_selfalias_chain",
|
||||
"package main;\n"
|
||||
"type tnode = struct { v: i64, kids: []tnode };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet inner: []tnode = [];\n"
|
||||
"\tappend(inner, tnode { v = 7, kids = [] });\n"
|
||||
"\tappend(inner, tnode { v = 9, kids = [] });\n"
|
||||
"\tlet xs: []tnode = [];\n"
|
||||
"\tappend(xs, tnode { v = 1, kids = inner });\n"
|
||||
"\tappend(xs, xs[0].kids...);\n"
|
||||
"\tif (len(xs) != 3) { return 1; };\n"
|
||||
"\tif (xs[0].v != 1 || xs[1].v != 7 || xs[2].v != 9) { return 2; };\n"
|
||||
"\treturn (81 + xs[1].v * 2 + xs[2].v * 2 + (len(xs): i64) * 2): i32;\n"
|
||||
"};\n",
|
||||
119, NULL },
|
||||
|
||||
/* The #35 residual boundary (task #27): a CALL rvalue spread
|
||||
* source has no place to resolve — stays loud until a
|
||||
* scratch-receive route lands. */
|
||||
{ "reject_spread_call_src",
|
||||
"package main;\n"
|
||||
"fn mk() []i64 = { let r: []i64 = []; append(r, 5); return r; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, mk()...);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "#34: append() spread source shape unsupported (rule-7)" },
|
||||
BUILD_FAIL, "#35: append() spread source shape unsupported (rule-7)" },
|
||||
|
||||
/* The #35 residual boundary (task #27): a [N]T ARRAY source place
|
||||
* IS its storage, not a {ptr,len,cap} header — pre-guard the
|
||||
* ident path silently read its first 16 data bytes as ptr/len.
|
||||
* Loud until wired. */
|
||||
{ "reject_spread_array_src",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: [2]i64 = [3, 4];\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, a...);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "#35: 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
|
||||
|
||||
Reference in New Issue
Block a user