/* * 805_placeaddr_store — cstage and wwstage agree, byte-for-byte and at * runtime, that a store through a deref-of-pointer-to-slice/array * element field — `(*ts)[i].field = v` and `(*ts)[i].field OP= v` — * lands the value (F6, task #4 of the regex fold-2b blockers; the * run_thread hot shape `threads: *[]thread`). * * Pre-C1 BOTH stages compiled this shape to NOTHING, byte-identically: * the lhs N_DOT spine roots at N_UN(STAR), so the `arr[i].field` arm * (idxbase must be N_IDENT) and the chained-ptr-field arm (base must * be *struct) both miss, and the N_ASSIGN dispatch fell off the switch * silently — rhs never even evaluated. The fix routes base-address * derivation through the cgplaceaddr resolver (cmd/w6c/cgen.c, mirror * selfhost/cmd/wcc/cgenexpr.ww); each call-site keeps its own * load/store emission, and every N_DOT lvalue the resolver can't * address now dies LOUD ("unsupported assign target shape") instead of * silently dropping (rule 7). C1.25 (#23) wires the aggregate field * STORE on top of the resolver: literal rhs materialises into a FRESH * per-use @placescr slot then word-copies to the resolved address; * addressable rhs (ident/global/dot/deref) takes its source address * straight from the #265/#268 dispatch. Field kinds the resolver arm * does not wire yet (float / tagged / aggregate-compound / str-slice * compound / call-rhs into aggregate) hard-stop with their own * diagnostics — the reject rows pin the exact text on BOTH stages. * Non-DOT lvalue tail residue is task #22. * * row | shape | want * --------------------+----------------------------------------+------ * store_size_neighbor | (*ts)[1].pc = 9, elem 0 intact | 19 * store_bool_compound | p7a4 verbatim: =, +=, bool store | 0 * widths_unsigned | u8/u16/u32 stores (MOVB/MOVW/MOVL) | 42 * widths_signed | i8/i16/i32 negative stores + readback | 43 * compound_ops | += -= *= |= /= (DIVQ + IDIVQ) %= <<= | 44 * str_field_member | (*ts)[i].name = "hi" (3-word header) | 45 * array_base_deref | (*ta)[i].pc via *[3]t, = and += | 46 * runtime_call_idx | (*ts)[geti()].pc — call-idx clobber | 47 * compound_bits_shr | &= ^= >>= signed (SARQ) + uns (SHRQ) | 48 * compound_narrow | u8/i8/i16/u16/i32/u32 OP= (narrow | 49 * | fldloadop: MOVSBQ/MOVZBQ/MOVSWQ/...) | * slice_field_member | (*ts)[i].xs = s ([]i64, 3-word header) | 50 * neutral_ident_bases | x.f / a[i].f / p.f = and += (untouched | 51 * | enumerated arms — runtime-pins the | * | resolver's asm-neutrality claim) | * agg_structlit_40b | C1.25: 40B capture literal store via | 52 * | fresh @placescr + word-copy, incl ... | * agg_from_ident_deref| C1.25: aggregate from ident + *p rhs | 53 * agg_nested_lit | C1.25: nested struct literal (#18) | 54 * agg_array_field_odd | C1.25: [3]u8 field, MOVW/MOVB tails | 55 * agg_fresh_two_stores| C1.25: two same-size structlit stores | 56 * | in ONE fn — distinct @placescr slots | * agg_array_field_movl| C1.25: [3]u32 field, MOVQ+MOVL tail | 57 * reject_agg_compound | compound on aggregate field | BUILD_FAIL * reject_agg_call_sret| >24B call rhs (#234-tail) | BUILD_FAIL * reject_agg_call_reg | ≤24B call rhs (task #24) | BUILD_FAIL * reject_float | f64 field store | BUILD_FAIL * reject_tagged | tagged-union field store | BUILD_FAIL * reject_str_compound | (*ts)[i].name += — str/slice compound | BUILD_FAIL * reject_tail | h.arr[1].pc (resolver-unwired N_DOT) | BUILD_FAIL * * 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. */ #include #include #include #include #include #include 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[] = { { "store_size_neighbor", "package main;\n" "type thread = struct { pc: size, matched: bool };\n" "fn setpc(ts: *[]thread, i: size) void = { (*ts)[i].pc = 9; };\n" "export fn main() i32 = {\n" "\tlet ts: []thread = [];\n" "\tappend(ts, thread { pc = 1, matched = false });\n" "\tappend(ts, thread { pc = 2, matched = false });\n" "\tsetpc(&ts, 1);\n" "\treturn (ts[0].pc*10 + ts[1].pc): i32;\n" "};\n", 19, NULL }, /* The original F6 probe (scratch/fold2b_probes/p7a4), graduated * verbatim: plain scalar store, compound +=, bool store. */ { "store_bool_compound", "package main;\n" "type thread = struct { pc: size, matched: bool };\n" "fn setpc(ts: *[]thread, i: size) void = {\n" "\t(*ts)[i].pc = 9;\n" "};\n" "fn bump(ts: *[]thread, i: size) void = {\n" "\t(*ts)[i].pc += 1;\n" "};\n" "fn markm(ts: *[]thread, i: size) void = {\n" "\t(*ts)[i].matched = true;\n" "};\n" "export fn main() i32 = {\n" "\tlet ts: []thread = [];\n" "\tappend(ts, thread { pc = 7, matched = false });\n" "\tsetpc(&ts, 0);\n" "\tif (ts[0].pc != 9) { return 1; };\n" "\tbump(&ts, 0);\n" "\tif (ts[0].pc != 10) { return 2; };\n" "\tmarkm(&ts, 0);\n" "\tif (!ts[0].matched) { return 3; };\n" "\treturn 0;\n" "};\n", 0, NULL }, { "widths_unsigned", "package main;\n" "type t = struct { a: u8, b: u16, c: u32 };\n" "fn seta(ts: *[]t, i: size) void = { (*ts)[i].a = 200u8; };\n" "fn setb(ts: *[]t, i: size) void = { (*ts)[i].b = 60000u16; };\n" "fn setc(ts: *[]t, i: size) void = { (*ts)[i].c = 70000u32; };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { a = 5u8, b = 6u16, c = 7u32 });\n" "\tappend(ts, t { a = 5u8, b = 6u16, c = 7u32 });\n" "\tseta(&ts, 1); setb(&ts, 1); setc(&ts, 1);\n" "\tif (ts[1].a != 200u8) { return 1; };\n" "\tif (ts[1].b != 60000u16) { return 2; };\n" "\tif (ts[1].c != 70000u32) { return 3; };\n" "\tif (ts[0].a != 5u8) { return 4; };\n" "\tif (ts[0].c != 7u32) { return 5; };\n" "\treturn 42;\n" "};\n", 42, NULL }, { "widths_signed", "package main;\n" "type t = struct { a: i8, b: i16, c: i32 };\n" "fn seta(ts: *[]t, i: size) void = { (*ts)[i].a = -7i8; };\n" "fn setb(ts: *[]t, i: size) void = { (*ts)[i].b = -300i16; };\n" "fn setc(ts: *[]t, i: size) void = { (*ts)[i].c = -70000i32; };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { a = 1i8, b = 2i16, c = 3i32 });\n" "\tappend(ts, t { a = 1i8, b = 2i16, c = 3i32 });\n" "\tseta(&ts, 1); setb(&ts, 1); setc(&ts, 1);\n" "\tif (ts[1].a != -7i8) { return 1; };\n" "\tif (ts[1].b != -300i16) { return 2; };\n" "\tif (ts[1].c != -70000i32) { return 3; };\n" "\tif (ts[0].a != 1i8) { return 4; };\n" "\treturn 43;\n" "};\n", 43, NULL }, /* All-arm compound coverage: unsigned size /= (DIVQ), signed int * /= and i64 %= on negatives (CQO+IDIVQ), <<=, and the plain * += -= *= |= trio on a fourth field. */ { "compound_ops", "package main;\n" "type t = struct { s: int, u: size, n: i64, m: i64 };\n" "fn divs(ts: *[]t, i: size) void = { (*ts)[i].s /= 4; };\n" "fn modn(ts: *[]t, i: size) void = { (*ts)[i].n %= 5; };\n" "fn divu(ts: *[]t, i: size) void = { (*ts)[i].u /= 3; };\n" "fn shl(ts: *[]t, i: size) void = { (*ts)[i].u <<= 2; };\n" "fn addm(ts: *[]t, i: size) void = { (*ts)[i].m += 7; };\n" "fn subm(ts: *[]t, i: size) void = { (*ts)[i].m -= 2; };\n" "fn mulm(ts: *[]t, i: size) void = { (*ts)[i].m *= 3; };\n" "fn orm(ts: *[]t, i: size) void = { (*ts)[i].m |= 4; };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { s = 4, u = 5, n = 6, m = 1 });\n" "\tappend(ts, t { s = -12, u = 9, n = -13, m = 4 });\n" "\tdivs(&ts, 1);\n" "\tif (ts[1].s != -3) { return 1; };\n" "\tmodn(&ts, 1);\n" "\tif (ts[1].n != -3) { return 2; };\n" "\tdivu(&ts, 1);\n" "\tif (ts[1].u != 3) { return 3; };\n" "\tshl(&ts, 1);\n" "\tif (ts[1].u != 12) { return 4; };\n" "\taddm(&ts, 1);\n" "\tsubm(&ts, 1);\n" "\tmulm(&ts, 1);\n" "\torm(&ts, 1);\n" "\tif (ts[1].m != 31) { return 5; };\n" "\tif (ts[0].s != 4) { return 6; };\n" "\treturn 44;\n" "};\n", 44, NULL }, /* str field: the rhs leaves {ptr,len,cap} in (AX,BX,CX); the * place address stages through DX so the triple survives. */ { "str_field_member", "package main;\n" "type t = struct { pc: size, name: str };\n" "fn setname(ts: *[]t, i: size) void = { (*ts)[i].name = \"hi\"; };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { pc = 1, name = \"\" });\n" "\tappend(ts, t { pc = 2, name = \"\" });\n" "\tsetname(&ts, 1);\n" "\tlet nm: str = ts[1].name;\n" "\tif (nm.len != 2) { return 1; };\n" "\tif (nm[0] != 104u8) { return 2; };\n" "\tif (ts[0].name.len != 0) { return 3; };\n" "\treturn 45;\n" "};\n", 45, NULL }, /* *[N]T base: the array place IS the element storage (no .ptr * hop), unlike the slice-header deref the rows above take. */ { "array_base_deref", "package main;\n" "type t = struct { pc: size, matched: bool };\n" "fn setarr(ta: *[3]t, i: size) void = { (*ta)[i].pc = 9; };\n" "fn bumparr(ta: *[3]t, i: size) void = { (*ta)[i].pc += 2; };\n" "export fn main() i32 = {\n" "\tlet ta: [3]t;\n" "\tta[0].pc = 1; ta[1].pc = 2; ta[2].pc = 3;\n" "\tsetarr(&ta, 1);\n" "\tif (ta[1].pc != 9) { return 1; };\n" "\tbumparr(&ta, 1);\n" "\tif (ta[1].pc != 11) { return 2; };\n" "\tif (ta[0].pc != 1) { return 3; };\n" "\tif (ta[2].pc != 3) { return 4; };\n" "\treturn 46;\n" "};\n", 46, NULL }, /* Call-result index: cgexpr(idx) inside the resolver clobbers * caller-saved regs; the spilled rhs and place address must * survive. */ { "runtime_call_idx", "package main;\n" "type t = struct { pc: size, matched: bool };\n" "fn geti() size = { return 1; };\n" "fn setpc(ts: *[]t) void = { (*ts)[geti()].pc = 9; };\n" "fn bump(ts: *[]t) void = { (*ts)[geti()].pc += 1; };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { pc = 1, matched = false });\n" "\tappend(ts, t { pc = 2, matched = false });\n" "\tsetpc(&ts);\n" "\tif (ts[1].pc != 9) { return 1; };\n" "\tbump(&ts);\n" "\tif (ts[1].pc != 10) { return 2; };\n" "\tif (ts[0].pc != 1) { return 3; };\n" "\treturn 47;\n" "};\n", 47, NULL }, /* The remaining compound arms: ANDQ/XORQ, and BOTH >>= forms — * SARQ (signed, sign bit must replicate) vs SHRQ (unsigned). */ { "compound_bits_shr", "package main;\n" "type t = struct { s: int, u: size, n: i64, m: i64 };\n" "fn ands(ts: *[]t, i: size) void = { (*ts)[i].m &= 6; };\n" "fn xors(ts: *[]t, i: size) void = { (*ts)[i].m ^= 3; };\n" "fn sars(ts: *[]t, i: size) void = { (*ts)[i].s >>= 2; };\n" "fn shrs(ts: *[]t, i: size) void = { (*ts)[i].u >>= 1; };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { s = -16, u = 8, n = 0, m = 7 });\n" "\tands(&ts, 0);\n" "\tif (ts[0].m != 6) { return 1; };\n" "\txors(&ts, 0);\n" "\tif (ts[0].m != 5) { return 2; };\n" "\tsars(&ts, 0);\n" "\tif (ts[0].s != -4) { return 3; };\n" "\tshrs(&ts, 0);\n" "\tif (ts[0].u != 4) { return 4; };\n" "\treturn 48;\n" "};\n", 48, NULL }, /* Narrow-width compounds: the widths rows above only exercise * plain `=` (fldstoreop); compounds also take the fldloadop * narrow LOAD (MOVSBQ/MOVZBQ/MOVSWQ/MOVZWQ/MOVSXD/MOVL) — i8/i16 * negatives pin the sign-extension, u8 250 pins zero-extension. */ { "compound_narrow", "package main;\n" "type t = struct { a: i8, b: u8, c: i16, d: u16, e: i32, f: u32 };\n" "fn adda(ts: *[]t, i: size) void = { (*ts)[i].a += 1i8; };\n" "fn addb(ts: *[]t, i: size) void = { (*ts)[i].b += 200u8; };\n" "fn subc(ts: *[]t, i: size) void = { (*ts)[i].c -= 300i16; };\n" "fn shld(ts: *[]t, i: size) void = { (*ts)[i].d <<= 3u16; };\n" "fn mule(ts: *[]t, i: size) void = { (*ts)[i].e *= -3i32; };\n" "fn orf(ts: *[]t, i: size) void = { (*ts)[i].f |= 8u32; };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { a = -5i8, b = 50u8, c = 100i16, d = 2u16, e = 7i32, f = 5u32 });\n" "\tadda(&ts, 0);\n" "\tif (ts[0].a != -4i8) { return 1; };\n" "\taddb(&ts, 0);\n" "\tif (ts[0].b != 250u8) { return 2; };\n" "\tsubc(&ts, 0);\n" "\tif (ts[0].c != -200i16) { return 3; };\n" "\tshld(&ts, 0);\n" "\tif (ts[0].d != 16u16) { return 4; };\n" "\tmule(&ts, 0);\n" "\tif (ts[0].e != -21i32) { return 5; };\n" "\torf(&ts, 0);\n" "\tif (ts[0].f != 13u32) { return 6; };\n" "\treturn 49;\n" "};\n", 49, NULL }, /* Slice field, same 3-word path as str. Field init goes through * an ident-bound empty slice, NOT `xs = []`: a bare empty-slice * literal as a struct-lit field inside an append arg leaves a * garbage header on MASTER too (pre-existing, task #9 family) — * this row pins the F6 store, not that bug. */ { "slice_field_member", "package main;\n" "type t = struct { pc: size, xs: []i64 };\n" "fn setxs(ts: *[]t, i: size, s: []i64) void = { (*ts)[i].xs = s; };\n" "export fn main() i32 = {\n" "\tlet empty: []i64 = [];\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { pc = 1, xs = empty });\n" "\tappend(ts, t { pc = 2, xs = empty });\n" "\tlet s: []i64 = [];\n" "\tappend(s, 11);\n" "\tappend(s, 22);\n" "\tsetxs(&ts, 1, s);\n" "\tif (ts[1].xs.len != 2) { return 1; };\n" "\tif (ts[1].xs[1] != 22) { return 2; };\n" "\tif (ts[0].xs.len != 0) { return 3; };\n" "\tif (ts[0].pc != 1) { return 4; };\n" "\tif (ts[1].pc != 2) { return 5; };\n" "\treturn 50;\n" "};\n", 50, NULL }, /* Ident-rooted lvalues stay with the enumerated arms (the * resolver must never fire for them) — this row runtime-pins the * shapes the 78-probe asm-neutrality sweep diffed statically. */ { "neutral_ident_bases", "package main;\n" "type t = struct { pc: size, matched: bool };\n" "export fn main() i32 = {\n" "\tlet x: t = t { pc = 1, matched = false };\n" "\tx.pc = 5;\n" "\tx.pc += 2;\n" "\tlet a: [2]t;\n" "\ta[0].pc = 3; a[1].pc = 4;\n" "\ta[1].pc += 10;\n" "\tlet p: *t = &x;\n" "\tp.pc = 20;\n" "\tp.pc += 1;\n" "\tif (x.pc != 21) { return 1; };\n" "\tif (a[1].pc != 14) { return 2; };\n" "\tif (a[0].pc != 3) { return 3; };\n" "\treturn 51;\n" "};\n", 51, NULL }, /* The fold-2b 40B capture store (p7b/p7_composed) — wired in a * follow-up resolver commit; until then it must die LOUD, never * the pre-C1 silent drop. */ /* C1.25 (#23): the run_thread 40B capture store, graduated from * BUILD_FAIL (C1's loud boundary) to wired. Literal rhs goes * through a FRESH per-use @placescr materialise + word-copy; the * `...` setter pins the autofill zero loop; neighbour element + * sibling field readbacks pin the stride and copy length. */ /* Readback is RAW bytes over ts.ptr (the 804 tagged_56b * precedent): a depth-2 read behind an index (ts[1].cap.start) * is the F4 walker gap (task #6) and link-fails today on the * cgen.c:9038 global-leaf fallback — typed readbacks graduate * with the C2 read-walker. Layout: t={pc@0,cap@8}, size 48; * capture={content(str ptr@0,len@8,cap@16),start@24,end@32}. * Elem1 base 48 → content.len byte 64, start 80, end 88; * elem0 → 16/32/40. Stored values fit one byte. */ { "agg_structlit_40b", "package main;\n" "type capture = struct { content: str, start: size, end: size };\n" "type t = struct { pc: size, cap: capture };\n" "fn setcap(ts: *[]t, i: size) void = {\n" "\t(*ts)[i].cap = capture { content = \"hit\", start = 2, end = 4 };\n" "};\n" "fn clearcap(ts: *[]t, i: size) void = {\n" "\t(*ts)[i].cap = capture { content = \"z\", ... };\n" "};\n" "export fn main() i32 = {\n" "\tlet z: capture = capture { content = \"\", start = 0, end = 0 };\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { pc = 7, cap = z });\n" "\tappend(ts, t { pc = 8, cap = z });\n" "\tsetcap(&ts, 1);\n" "\tlet bp: *u8 = ts.ptr: *u8;\n" "\tif (bp[64] != 3u8) { return 1; };\n" "\tif (bp[80] != 2u8) { return 2; };\n" "\tif (bp[88] != 4u8) { return 3; };\n" "\tif (ts[1].pc != 8) { return 4; };\n" "\tif (bp[16] != 0u8) { return 5; };\n" "\tif (ts[0].pc != 7) { return 6; };\n" "\tclearcap(&ts, 1);\n" "\tif (bp[64] != 1u8) { return 7; };\n" "\tif (bp[80] != 0u8) { return 8; };\n" "\tif (bp[88] != 0u8) { return 9; };\n" "\treturn 52;\n" "};\n", 52, NULL }, /* Addressable aggregate sources via the #265/#268 dispatch: * local ident (LEAQ slot) and deref (*pc). Raw-byte readback per * the agg_structlit_40b layout note (F4 blocks typed depth-2). */ { "agg_from_ident_deref", "package main;\n" "type capture = struct { content: str, start: size, end: size };\n" "type t = struct { pc: size, cap: capture };\n" "fn copycap(ts: *[]t, i: size, src: capture) void = {\n" "\t(*ts)[i].cap = src;\n" "};\n" "fn derefcap(ts: *[]t, i: size, pc: *capture) void = {\n" "\t(*ts)[i].cap = *pc;\n" "};\n" "export fn main() i32 = {\n" "\tlet z: capture = capture { content = \"\", start = 0, end = 0 };\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { pc = 7, cap = z });\n" "\tappend(ts, t { pc = 8, cap = z });\n" "\tlet a: capture = capture { content = \"ab\", start = 1, end = 3 };\n" "\tcopycap(&ts, 1, a);\n" "\tlet bp: *u8 = ts.ptr: *u8;\n" "\tif (bp[64] != 2u8) { return 1; };\n" "\tif (bp[80] != 1u8) { return 2; };\n" "\tif (bp[88] != 3u8) { return 3; };\n" "\tlet b: capture = capture { content = \"wxyz\", start = 5, end = 9 };\n" "\tderefcap(&ts, 0, &b);\n" "\tif (bp[16] != 4u8) { return 4; };\n" "\tif (bp[32] != 5u8) { return 5; };\n" "\tif (bp[40] != 9u8) { return 6; };\n" "\tif (bp[80] != 1u8) { return 7; };\n" "\treturn 53;\n" "};\n", 53, NULL }, /* Nested struct-typed literal field inside the stored literal — * the #18 recursion through cg_structlit_fill must land the inner * bytes in the @placescr image before the copy. */ { "agg_nested_lit", "package main;\n" "type inner = struct { x: i64, y: i64 };\n" "type big = struct { a: i64, ib: inner };\n" "type t = struct { pc: size, bg: big };\n" "fn setbg(ts: *[]t, i: size) void = {\n" "\t(*ts)[i].bg = big { a = 1, ib = inner { x = 2, y = 3 } };\n" "};\n" "export fn main() i32 = {\n" "\tlet z: big = big { a = 0, ib = inner { x = 0, y = 0 } };\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { pc = 7, bg = z });\n" "\tsetbg(&ts, 0);\n" "\tlet bp: *u8 = ts.ptr: *u8;\n" "\tif (bp[8] != 1u8) { return 1; };\n" "\tif (bp[16] != 2u8) { return 2; };\n" "\tif (bp[24] != 3u8) { return 3; };\n" "\tif (ts[0].pc != 7) { return 4; };\n" "\treturn 54;\n" "};\n", 54, NULL }, /* [3]u8 array field from an ident source — TY_ARRAY aggregate arm * + the MOVW/MOVB copy tails (fsz=3). */ { "agg_array_field_odd", "package main;\n" "type t = struct { pc: size, arr: [3]u8 };\n" "fn setarr(ts: *[]t, i: size, src: [3]u8) void = {\n" "\t(*ts)[i].arr = src;\n" "};\n" "export fn main() i32 = {\n" "\tlet a3: [3]u8 = [9u8, 8u8, 7u8];\n" "\tlet z3: [3]u8 = [0u8, 0u8, 0u8];\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { pc = 5, arr = z3 });\n" "\tsetarr(&ts, 0, a3);\n" "\tif (ts[0].arr[0] != 9u8) { return 1; };\n" "\tif (ts[0].arr[1] != 8u8) { return 2; };\n" "\tif (ts[0].arr[2] != 7u8) { return 3; };\n" "\tif (ts[0].pc != 5) { return 4; };\n" "\treturn 55;\n" "};\n", 55, NULL }, /* Two same-size structlit stores in ONE fn — each must get its * own FRESH @placescr slot and both must land (the #31 multi- * live discipline pin). The stronger shape — a structlit whose * FIELD expr recurses into a same-size aggregate assign, the * fresh-per-use rationale proper — is not constructible today: * match-expr (the only stmt-carrying expr) is a parse reject in * field position; fresh-per-use stays defensive (rob ruling). */ { "agg_fresh_two_stores", "package main;\n" "type capture = struct { content: str, start: size, end: size };\n" "type t = struct { pc: size, cap: capture };\n" "fn settwo(ts: *[]t) void = {\n" "\t(*ts)[0].cap = capture { content = \"aa\", start = 1, end = 2 };\n" "\t(*ts)[1].cap = capture { content = \"bbb\", start = 3, end = 4 };\n" "};\n" "export fn main() i32 = {\n" "\tlet z: capture = capture { content = \"\", start = 0, end = 0 };\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { pc = 7, cap = z });\n" "\tappend(ts, t { pc = 8, cap = z });\n" "\tsettwo(&ts);\n" "\tlet bp: *u8 = ts.ptr: *u8;\n" "\tif (bp[16] != 2u8) { return 1; };\n" "\tif (bp[32] != 1u8) { return 2; };\n" "\tif (bp[40] != 2u8) { return 3; };\n" "\tif (bp[64] != 3u8) { return 4; };\n" "\tif (bp[80] != 3u8) { return 5; };\n" "\tif (bp[88] != 4u8) { return 6; };\n" "\treturn 56;\n" "};\n", 56, NULL }, /* [3]u32 field (fsz=12) — the MOVL copy tail, untouched by the * 40B (all-MOVQ) and [3]u8 (MOVW+MOVB) rows. * * Matrix honesty, TY_TUPLE: the arm wires tuple fields and a * local-ident tuple source stores correctly (cstage-verified), * but a dual-stage row is blocked by three PRE-EXISTING tuple * gaps outside this arm: by-value tuple param drops word 2 at * runtime (both stages, master too), wwstage tuple-let init * `let a: (i64,i64) = (3,4)` drops the word-2 store (cs≠ww), * and `a.0 =` element assign is unwired (loud). Tuple-field row * graduates with that family (filed). */ { "agg_array_field_movl", "package main;\n" "type t = struct { pc: size, arr: [3]u32 };\n" "fn setarr(ts: *[]t, i: size, src: [3]u32) void = {\n" "\t(*ts)[i].arr = src;\n" "};\n" "export fn main() i32 = {\n" "\tlet a3: [3]u32 = [9u32, 8u32, 7u32];\n" "\tlet z3: [3]u32 = [0u32, 0u32, 0u32];\n" "\tlet ts: []t = [];\n" "\tappend(ts, t { pc = 5, arr = z3 });\n" "\tsetarr(&ts, 0, a3);\n" "\tif (ts[0].arr[0] != 9u32) { return 1; };\n" "\tif (ts[0].arr[1] != 8u32) { return 2; };\n" "\tif (ts[0].arr[2] != 7u32) { return 3; };\n" "\tif (ts[0].pc != 5) { return 4; };\n" "\treturn 57;\n" "};\n", 57, NULL }, /* Compound on an aggregate field is meaningless — stays loud. */ { "reject_agg_compound", "package main;\n" "type capture = struct { content: str, start: size, end: size };\n" "type t = struct { pc: size, cap: capture };\n" "fn addcap(ts: *[]t, i: size, src: capture) void = {\n" "\t(*ts)[i].cap += src;\n" "};\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tlet a: capture = capture { content = \"\", start = 0, end = 0 };\n" "\taddcap(&ts, 0, a);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "assign-resolver: compound on aggregate field not wired (rule-7)" }, /* sret-class call rhs (>24B return) needs a runtime-RDI dest — * the #234-tail deferral. */ { "reject_agg_call_sret", "package main;\n" "type capture = struct { content: str, start: size, end: size };\n" "type t = struct { pc: size, cap: capture };\n" "fn mk() capture = {\n" "\treturn capture { content = \"x\", start = 1, end = 2 };\n" "};\n" "fn setcap(ts: *[]t, i: size) void = { (*ts)[i].cap = mk(); };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tsetcap(&ts, 0);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "assign-resolver: sret call into aggregate field unwired " "(#234-tail/rule-7)" }, /* ≤24B reg-return call rhs — deferred, task #24. */ { "reject_agg_call_reg", "package main;\n" "type pair = struct { a: i64, b: i64 };\n" "type t = struct { pc: size, pr: pair };\n" "fn mk() pair = { return pair { a = 1, b = 2 }; };\n" "fn setpr(ts: *[]t, i: size) void = { (*ts)[i].pr = mk(); };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tsetpr(&ts, 0);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "assign-resolver: call result into aggregate field unwired " "(task #24/rule-7)" }, { "reject_float", "package main;\n" "type t = struct { pc: size, f: f64 };\n" "fn setf(ts: *[]t, i: size) void = { (*ts)[i].f = 1.5; };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tsetf(&ts, 0);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "assign-resolver: float field not wired (rule-7)" }, { "reject_tagged", "package main;\n" "type v = (i64 | bool);\n" "type t = struct { pc: size, tg: v };\n" "fn settg(ts: *[]t, i: size) void = { (*ts)[i].tg = 5; };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\tsettg(&ts, 0);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "assign-resolver: tagged field not wired (rule-7)" }, /* Compound on a str field reaches cgen (the checker admits it) — * it must hit the resolver's own loud stop, not the silent tail. */ { "reject_str_compound", "package main;\n" "type t = struct { pc: size, name: str };\n" "fn addname(ts: *[]t, i: size) void = { (*ts)[i].name += \"x\"; };\n" "export fn main() i32 = {\n" "\tlet ts: []t = [];\n" "\taddname(&ts, 0);\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "assign-resolver: compound on str/slice field not wired (rule-7)" }, /* An N_DOT lvalue the C1 resolver does not wire (index base is a * dot chain, not a deref) — pre-C1 this was a SILENT no-op store; * the loud dispatch tail is the close-by-construction net. */ { "reject_tail", "package main;\n" "type t = struct { pc: size, matched: bool };\n" "type holder = struct { arr: [3]t, n: i64 };\n" "export fn main() i32 = {\n" "\tlet h: holder = holder { n = 0, ... };\n" "\th.arr[1].pc = 5;\n" "\treturn 0;\n" "};\n", BUILD_FAIL, "unsupported assign target shape" }, }; /* 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/plad_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/plad_%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 resolver emission is written fresh on both * sides, so this is the converged-by-construction gate: any drift in * the idx-scale/spill/deref 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/plad_asm_%d_%d.ww", getpid(), i); snprintf(cs, sizeof cs, "/tmp/plad_asm_%d_%d_c.s", getpid(), i); snprintf(ws, sizeof ws, "/tmp/plad_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, "placeaddr_store: 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, "placeaddr_store[%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, "placeaddr_store: %d/%d fixtures failed\n", fail, total); return 1; } printf("placeaddr_store: %d fixtures passed\n", total); return 0; }