wcc+w6c_ww: >48B tagged args from place-resolved sources (#38b cont.)

Task #40 (FB3): the #38b MEMORY-class staging only accepted ident /
aggarg_srcaddr-enumerated sources, loud-stopping on a slice-element
arg — the regex run_thread loop-condition shape
is_consuming_inst(re.insts[threads[i].pc]) (regex.ha:602) and B4's
(*p)[i] spelling. The staging now falls back to cgplaceaddr (the F6
resolver) for the slot's source address; the existing word-push
machinery is unchanged. Placed AFTER aggarg_srcaddr so every
pre-#40 shape keeps its asm byte-exact (verified vs a master w6c).

Boundaries kept loud (rule 7): exact-type rvalue (no place to
resolve), sret-class call source (#40-family follow-up), global
tagged let (task #25), variadic element, register-overflow mixing.
Diagnostic re-worded to match: slice-element is wired, rvalue and
unresolvable-place sources are not.

test/929 grows 6 run rows (element source with literal+computed
index, deref-spine, nested re.insts[threads[i].pc] spelling,
element mixed with register args both orders, fn-call index with
two mem args per call — the inner CALL runs inside the mem
pre-pass with the other slot already staged — and global-slice
element, the storage-backed twin of the loud fail_global_src
boundary — all build-fail loud at master) and 1 reject row pinning
the new boundary text on both stages.
This commit is contained in:
2026-06-04 12:13:34 +09:00
parent f3f74c9b21
commit 403625e433
5 changed files with 220 additions and 6 deletions

View File

@@ -7391,8 +7391,23 @@ cgexpr(Cg *c, Node *n, Local *locals)
memslots += msz / 8;
continue;
}
/* #40/FB3: a place the enumerated arms miss —
* slice element, deref-spine element — resolves
* through the F6 resolver. AFTER aggarg_srcaddr
* so every pre-#40 shape keeps its asm; the
* resolver balances its own pushes, so the words
* already staged below stay put. */
if (cgplaceaddr(c, args[i], D_SI, locals)) {
for (int k = msz/8 - 1; k >= 0; k--) {
ins2(c, A_MOVQ, amem(D_SI, k*8),
areg(D_AX));
ins1(c, A_PUSHQ, areg(D_AX));
}
memslots += msz / 8;
continue;
}
fatal("#38b: >48B tagged arg from unsupported source "
"kind %d (slice-element and rvalue sources "
"kind %d (rvalue and unresolvable-place sources "
"unwired)", args[i]->kind);
}
/* eval right-to-left, push to stack. Each N_IDENT fast-path

View File

@@ -16000,7 +16000,23 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
};
return rest + memsz / 8;
};
let mu: str = "#38b: >48B tagged arg from unsupported source kind (slice-element and rvalue sources unwired)\n";
// #40/FB3: a place the enumerated arms miss — slice
// element, deref-spine element — resolves through the F6
// resolver. AFTER aggargsrcaddr so every pre-#40 shape
// keeps its asm; the resolver balances its own pushes, so
// the words already staged below stay put.
if (cgplaceaddr(c, arg, "SI")) {
let w: i32 = memsz / 8 - 1;
for (w >= 0) {
emitline("\tMOVQ\t");
emitoff((w*8): i64);
emitline("(SI), AX\n");
emitline("\tPUSHQ\tAX\n");
w -= 1;
};
return rest + memsz / 8;
};
let mu: str = "#38b: >48B tagged arg from unsupported source kind (rvalue and unresolvable-place sources unwired)\n";
os.write(2, mu.ptr, mu.len: u64);
os.exit(1);
};

View File

@@ -214,7 +214,23 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
};
return rest + memsz / 8;
};
let mu: str = "#38b: >48B tagged arg from unsupported source kind (slice-element and rvalue sources unwired)\n";
// #40/FB3: a place the enumerated arms miss — slice
// element, deref-spine element — resolves through the F6
// resolver. AFTER aggargsrcaddr so every pre-#40 shape
// keeps its asm; the resolver balances its own pushes, so
// the words already staged below stay put.
if (cgplaceaddr(c, arg, "SI")) {
let w: i32 = memsz / 8 - 1;
for (w >= 0) {
emitline("\tMOVQ\t");
emitoff((w*8): i64);
emitline("(SI), AX\n");
emitline("\tPUSHQ\tAX\n");
w -= 1;
};
return rest + memsz / 8;
};
let mu: str = "#38b: >48B tagged arg from unsupported source kind (rvalue and unresolvable-place sources unwired)\n";
os.write(2, mu.ptr, mu.len: u64);
os.exit(1);
};

View File

@@ -16000,7 +16000,23 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = {
};
return rest + memsz / 8;
};
let mu: str = "#38b: >48B tagged arg from unsupported source kind (slice-element and rvalue sources unwired)\n";
// #40/FB3: a place the enumerated arms miss — slice
// element, deref-spine element — resolves through the F6
// resolver. AFTER aggargsrcaddr so every pre-#40 shape
// keeps its asm; the resolver balances its own pushes, so
// the words already staged below stay put.
if (cgplaceaddr(c, arg, "SI")) {
let w: i32 = memsz / 8 - 1;
for (w >= 0) {
emitline("\tMOVQ\t");
emitoff((w*8): i64);
emitline("(SI), AX\n");
emitline("\tPUSHQ\tAX\n");
w -= 1;
};
return rest + memsz / 8;
};
let mu: str = "#38b: >48B tagged arg from unsupported source kind (rvalue and unresolvable-place sources unwired)\n";
os.write(2, mu.ptr, mu.len: u64);
os.exit(1);
};

View File

@@ -29,8 +29,10 @@
* Rows flagged `buildfail` must be rejected by BOTH stages with that
* row's EXACT #38b diagnostic — pinning WHICH guard fired (the
* remaining unwired sub-shapes: sret-class call source, global-let
* source, variadic element, register-overflow mixing caller- and
* callee-side).
* source, exact-type rvalue, variadic element, register-overflow
* mixing caller- and callee-side). #40/FB3 wired the place-resolved
* sources (slice element, deref-spine, nested index) via the
* cgplaceaddr fallback — those are run rows now.
*/
#include <stdio.h>
#include <stdlib.h>
@@ -309,6 +311,140 @@ static const struct row rows[] = {
" return 0;\n"
"};\n",
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
/* #40/FB3: exact-type SLICE-ELEMENT source — `f(xs[i])`, the
* regex run_thread loop-condition shape
* (is_consuming_inst(re.insts[threads[i].pc]), regex.ha:602).
* Staged from a cgplaceaddr-resolved address; literal and
* computed indices, every variant, late payload word per arm. */
{ "mem56_elem_source",
MEM56_TYPES
MEM56_PROBE
"export fn main() i32 = {\n"
" let xs: []t_u = [];\n"
" let a: t_u = ('x': t_lit);\n"
" append(xs, a);\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(xs, b);\n"
" if (probe(xs[0]) != 1) { return 1; };\n"
" if (probe(xs[1]) != 3) { return 2; };\n"
" let i: size = 1;\n"
" if (probe(xs[i]) != 3) { return 3; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
/* #40/FB3: deref-spine element source — `f((*p)[i])`, B4's
* actual spelling (slice reached through a pointer). */
{ "mem56_elem_deref_spine",
MEM56_TYPES
MEM56_PROBE
"export fn main() i32 = {\n"
" let xs: []t_u = [];\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(xs, b);\n"
" let p: *[]t_u = &xs;\n"
" if (probe((*p)[0]) != 3) { return 1; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
/* #40/FB3: the full nested spelling — dot-base slice indexed
* by a field of another indexed element
* (re.insts[threads[i].pc]). */
{ "mem56_elem_nested_spine",
MEM56_TYPES
"type re_t = struct { flag: i64, insts: []t_u };\n"
"type thr = struct { pc: size, gen: i64 };\n"
MEM56_PROBE
"export fn main() i32 = {\n"
" let xs: []t_u = [];\n"
" let a: t_u = ('x': t_lit);\n"
" append(xs, a);\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(xs, b);\n"
" let re: re_t = re_t { flag = 4, insts = xs };\n"
" let threads: []thr = [];\n"
" append(threads, thr { pc = 1, gen = 5 });\n"
" if (probe(re.insts[threads[0].pc]) != 3) { return 1; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
/* #40/FB3: element source mixed with register-class args, both
* orders — the resolver's transient AX/CX clobbers must not
* disturb the staged words or the register cursors. */
{ "mem56_elem_mixed_regs",
MEM56_TYPES
"fn before(k: i64, s: str, a: t_u) i64 = {\n"
" if (k != 5) { return 91; };\n"
" if (s.len != 3) { return 92; };\n"
" match (a) {\n"
" case let r: t_rep => { return k + (r.m3: i64); };\n"
" case => { return 93; };\n"
" };\n"
" return 99;\n"
"};\n"
"fn after(a: t_u, k: i64, s: str) i64 = {\n"
" if (k != 6) { return 91; };\n"
" if (s.len != 3) { return 92; };\n"
" match (a) {\n"
" case let r: t_rep => { return k + (r.m3: i64); };\n"
" case => { return 93; };\n"
" };\n"
" return 99;\n"
"};\n"
"export fn main() i32 = {\n"
" let xs: []t_u = [];\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(xs, b);\n"
" if (before(5, \"abc\", xs[0]) != 1239) { return 1; };\n"
" if (after(xs[0], 6, \"abc\") != 1240) { return 2; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
/* #40/FB3: fn-CALL index (the 2c computed-pc shape) + TWO mem
* args per call, both orders. The inner CALL runs inside the
* mem pre-pass with the other arg's 7 words already staged —
* pins the resolver's push-balance claim; $112 = both slots. */
{ "mem56_elem_callidx_twomem",
MEM56_TYPES
MEM56_PROBE
"fn pick() size = {\n"
" return 1;\n"
"};\n"
"fn two(a: t_u, b: t_u) i32 = {\n"
" return probe(a) * 10 + probe(b);\n"
"};\n"
"export fn main() i32 = {\n"
" let xs: []t_u = [];\n"
" let a: t_u = ('x': t_lit);\n"
" append(xs, a);\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(xs, b);\n"
" if (two(xs[0], xs[pick()]) != 13) { return 1; };\n"
" if (two(xs[pick()], xs[0]) != 31) { return 2; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$112, SP\n", NULL, 0, NULL },
/* #40/FB3: element of a GLOBAL slice let — the resolver's
* let_islet arm, sound because slice globals have DATA storage
* (the element lives behind .ptr). The storage-LESS twin (a
* global tagged VALUE, let_emit_size=0, task #25) must stay
* loud — fail_global_src pins that side of the boundary. */
{ "mem56_elem_global_slice",
MEM56_TYPES
"let gxs: []t_u;\n"
MEM56_PROBE
"export fn main() i32 = {\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(gxs, b);\n"
" if (probe(gxs[0]) != 3) { return 1; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
/* BOUNDARY: 40B payload = EXACTLY 48B slot — must stay on the
* register convention (no mem cleanup ADDQ). An off-by-one in
* tagged_memarg_size flips every 48B-slot call in the tree. */
@@ -435,6 +571,21 @@ static const struct row rows[] = {
0, NULL, NULL, 1,
"#38b: >48B tagged arg mixed with register-overflow stack "
"args unwired" },
/* LOUD-STOP: exact-type RVALUE source (same-type cast) — no
* place to resolve; stays loud post-#40. Marker is the
* parenthetical tail shared verbatim by both stages (cstage
* interposes the numeric node kind), complementing
* fail_global_src's prefix pin of the same guard. */
{ "fail_rvalue_cast",
MEM56_TYPES
MEM56_PROBE
"export fn main() i32 = {\n"
" let a: t_u = ('x': t_lit);\n"
" if (probe((a: t_u)) != 1) { return 1; };\n"
" return 0;\n"
"};\n",
0, NULL, NULL, 1,
"(rvalue and unresolvable-place sources unwired)" },
};
static const char *g_bin;