From 403625e433888914da793b227574e6e58d7c9b7d Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 4 Jun 2026 12:13:34 +0900 Subject: [PATCH] wcc+w6c_ww: >48B tagged args from place-resolved sources (#38b cont.) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- cmd/w6c/cgen.c | 17 ++- selfhost/cmd/w6c/main.combined.ww | 18 +++- selfhost/cmd/wcc/cgenutil.ww | 18 +++- selfhost/cmd/wwdump/main.combined.ww | 18 +++- test/wcc/929_tagged_memarg_run.c | 155 ++++++++++++++++++++++++++- 5 files changed, 220 insertions(+), 6 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 5d4a7ff6..62a85eaa 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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 diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 944103b1..1c55d8e7 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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); }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 8216ea7e..2f8f317d 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -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); }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index e65cef83..b6fb8ca0 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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); }; diff --git a/test/wcc/929_tagged_memarg_run.c b/test/wcc/929_tagged_memarg_run.c index 7a2b92da..d744ff6c 100644 --- a/test/wcc/929_tagged_memarg_run.c +++ b/test/wcc/929_tagged_memarg_run.c @@ -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 #include @@ -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;