The wcc test drivers ran `ww build <bare-/tmp src>` with no -o, so the compiler's <stem>.sepwork scratch landed beside the source and was never cleaned: unbounded /tmp growth (2195 stale dirs observed) that fills tmpfs and fabricates phantom test failures + silent harness aborts, and for in-repo fixture builds leaked .sepwork into the tracked tree. Each leaking build now writes its source + output inside a per-invocation tmpdir, passes -o <tmpdir>/<stem> so the .sepwork lands inside it, and rm -rf's the tmpdir on every exit path -- including fopen-fail and the expected-fail reject builds (scratch is mkdir'd before the build can fail). `ww run` and explicit-`-o`/byte-id helpers are left as-is; the 990/993 byte-id comparison logic is byte-for-byte unchanged. Two items filed separately (this commit holds the no-Makefile / no-main.c rail): - #13: a stale <src>.s byte-id readback (749) silently no-ops since separate-compile emits .s to <ostem>.sepwork/__root.s; documented inline. - #14: build-system Makefile recipes build selfhost/cmd/*/main.ww with no -o and leak main.sepwork in-tree (bounded, gitignored; own commit). One concern -- sepwork leak hygiene -- across 228 drivers; uniform transform applied per-file and two-round reviewed. make test: all 402 passed, zero net-new /tmp scratch, zero test-driven in-repo .sepwork.
1488 lines
57 KiB
C
1488 lines
57 KiB
C
/*
|
|
* 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.
|
|
*
|
|
* C2 (F4 + FA3-cstage, task #6): the READ side. cgplaceaddr grew an
|
|
* N_IDENT root and recursion-driven N_INDEX bases; case N_DOT routes
|
|
* every TYPED read no enumerated arm matched through the resolver, and
|
|
* BOTH silent fallbacks died: the module-leaf `MOVQ <leaf>(SB)` is
|
|
* gated to UNTYPED chains (pre-C2 it swallowed any unmatched dot chain
|
|
* — a silent global read of a colliding symbol), and the offset-blind
|
|
* cgexpr catch-all is gated to untyped-str pseudo-fields (pre-C2 a
|
|
* nonzero-offset field behind a deref-index spine read element word 0;
|
|
* offset-0 scalars worked by coincidence). TK_AMP's silent tail is the
|
|
* same resolver-or-loud (`&threads[0].cap` used to SEGFAULT on deref,
|
|
* reviewer-A route). The C1.25 raw-byte readbacks graduate to typed
|
|
* depth-2 reads below. wwstage cgdot/cgun mirror symmetrically; the
|
|
* ptr-chained global-root remainder stays LOUD (task #37).
|
|
*
|
|
* C3 (F7+F10, task #8): wwstage's cgdot N_INDEX-base arm re-keyed from
|
|
* tnode KINDs + structlookup-by-name onto the checker-stamped tinfo
|
|
* (#209/#211 name-keyed→tinfo-SSoT cluster), mirroring cstage's arm
|
|
* 1:1. Pre-C3 a base typed via an N_TNAME alias (`type result =
|
|
* []capture`) missed the name-keyed arm and died at the interim C2
|
|
* loud guard (and pre-C2, fell silently into the SB fallback). The
|
|
* c3_* rows below pin the alias-typed base class plus the composed
|
|
* regex hot shape and the free()-operand consumption (FA6).
|
|
*
|
|
* 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
|
|
* arr_field_idx_store | h.arr[1].pc = / += (C1's reject_tail, | 58
|
|
* | graduated by the C2 resolver arms) |
|
|
* read_depth2_idx | ts[i].cap.start/.end/.content.len — | 59
|
|
* | typed depth-2 behind an IDENT index |
|
|
* read_depth2_deref | (*ts)[i].cap.start + offset-0 .pc | 60
|
|
* read_widths_spine | i8..u32/f64/f32/[3]u8 behind (*p)[i] | 61
|
|
* read_slicefield | let h = (*p)[i].xs (FA3/pA5) + .cap | 62
|
|
* amp_spine | &ts[1].cap / &(*p)[0].cap — deref, | 63
|
|
* | compound through the pointer |
|
|
* neutral_reads | ident-root chain, "abc".len, (*p).f — | 64
|
|
* | untouched-arm asm-neutrality pins |
|
|
* reject_read_tagged | (*ts)[i].tg read behind spine | BUILD_FAIL
|
|
* reject_read_callbase| mk()[0].pc read (unaddressable base) | BUILD_FAIL
|
|
* reject_assign_callbs| mk()[0].pc = (assign-tail text keeps | BUILD_FAIL
|
|
* | its carrier post arr_field_idx_store) |
|
|
* reject_addrof_callbs| &mk()[0].pc (TK_AMP loud tail) | BUILD_FAIL
|
|
* c3_alias_slice_reads| F10: alias-slice base l[i].f — narrow | 65
|
|
* | signed/unsigned, i64, f64, str, []T, |
|
|
* | [N]T-field-then-index |
|
|
* c3_alias_arr_viaptr | F10: alias [2]S base + alias []*S | 66
|
|
* | (viaptr element) reads |
|
|
* c3_letbound_alias | p11b essence sans task-#14 `?`: let | 67
|
|
* | m: result = f(); m[i].field reads |
|
|
* c3_composed_match | F7: match(re.insts[(*ts)[i].pc]) + | 68
|
|
* | (*ts)[i] compound/store (p7 hot shape) |
|
|
* c3_free_operand | FA6: free((*ts)[i].slicefield) reads | 69
|
|
* | through the operand (pA9) |
|
|
* c4_let_slice_elem | F5: let e = xs[0], 16B struct elem | 70
|
|
* | (p6min13 graduated — cstage emitted |
|
|
* | NOTHING, wwstage 8B truncation) |
|
|
* c4_let_elem_sizes | F5: 24B/40B/12B(MOVQ+MOVL tail) elems | 71
|
|
* c4_let_deref_leaf | reviewer-C2 inheritance: let c = | 72
|
|
* | (*ts)[i].cap (40B leaf behind deref |
|
|
* | spine; ww loud retired, cs no-copy |
|
|
* | fixed) |
|
|
* c4_range_byvalue | F5/FC0: by-value range payload full- | 73
|
|
* | width copy — []struct (sum BOTH |
|
|
* | fields), []capture 40B (str field |
|
|
* | header), []str (24B header .len) |
|
|
* c4_range_edges | range edges: empty slice (zero iters), | 74
|
|
* | binder MUTATION (by-VALUE: writes must |
|
|
* | not reach the slice), 12B elem |
|
|
* | (MOVQ+MOVL tail in the range copy) |
|
|
* reject_let_unhandled| `let p: pair = mk()!` — non-place rhs | BUILD_FAIL
|
|
* | dies at C4's loud let tail (was |
|
|
* | SILENT: cs no init, ww 8B) |
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
|
|
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 was RAW bytes over ts.ptr until C2 — a depth-2 read
|
|
* behind an index (ts[1].cap.start) was the F4 walker gap (task
|
|
* #6) and link-failed on the cgen.c global-leaf fallback. C2
|
|
* graduation: typed depth-2 readbacks (the documented note). */
|
|
{ "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"
|
|
"\tif (ts[1].cap.content.len != 3) { return 1; };\n"
|
|
"\tif (ts[1].cap.start != 2) { return 2; };\n"
|
|
"\tif (ts[1].cap.end != 4) { return 3; };\n"
|
|
"\tif (ts[1].pc != 8) { return 4; };\n"
|
|
"\tif (ts[0].cap.content.len != 0) { return 5; };\n"
|
|
"\tif (ts[0].pc != 7) { return 6; };\n"
|
|
"\tclearcap(&ts, 1);\n"
|
|
"\tif (ts[1].cap.content.len != 1) { return 7; };\n"
|
|
"\tif (ts[1].cap.start != 0) { return 8; };\n"
|
|
"\tif (ts[1].cap.end != 0) { return 9; };\n"
|
|
"\treturn 52;\n"
|
|
"};\n",
|
|
52, NULL },
|
|
|
|
/* Addressable aggregate sources via the #265/#268 dispatch:
|
|
* local ident (LEAQ slot) and deref (*pc). Typed depth-2
|
|
* readbacks since C2 (were raw bytes over ts.ptr). */
|
|
{ "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"
|
|
"\tif (ts[1].cap.content.len != 2) { return 1; };\n"
|
|
"\tif (ts[1].cap.start != 1) { return 2; };\n"
|
|
"\tif (ts[1].cap.end != 3) { return 3; };\n"
|
|
"\tlet b: capture = capture { content = \"wxyz\", start = 5, end = 9 };\n"
|
|
"\tderefcap(&ts, 0, &b);\n"
|
|
"\tif (ts[0].cap.content.len != 4) { return 4; };\n"
|
|
"\tif (ts[0].cap.start != 5) { return 5; };\n"
|
|
"\tif (ts[0].cap.end != 9) { return 6; };\n"
|
|
"\tif (ts[1].cap.start != 1) { 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"
|
|
"\tif (ts[0].cap.content.len != 2) { return 1; };\n"
|
|
"\tif (ts[0].cap.start != 1) { return 2; };\n"
|
|
"\tif (ts[0].cap.end != 2) { return 3; };\n"
|
|
"\tif (ts[1].cap.content.len != 3) { return 4; };\n"
|
|
"\tif (ts[1].cap.start != 3) { return 5; };\n"
|
|
"\tif (ts[1].cap.end != 4) { 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)" },
|
|
|
|
/* C1's reject_tail (index base is a dot chain), graduated by the
|
|
* C2 resolver arms (N_IDENT root + recursive N_INDEX bases) —
|
|
* store, compound, readback, and neighbour-intact pins. */
|
|
{ "arr_field_idx_store",
|
|
"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"
|
|
"\th.arr[1].pc += 2;\n"
|
|
"\tif (h.arr[1].pc != 7) { return 1; };\n"
|
|
"\tif (h.arr[0].pc != 0) { return 2; };\n"
|
|
"\tif (h.arr[2].pc != 0) { return 3; };\n"
|
|
"\treturn 58;\n"
|
|
"};\n",
|
|
58, NULL },
|
|
|
|
/* C2 (F4): typed depth-2 reads behind an IDENT-indexed root —
|
|
* the p6min9/p6min10 read shape (nonzero offsets, both elements,
|
|
* pseudo .len behind the spine, and the global-collision check:
|
|
* a module global named `end` must NOT be read). Elements are
|
|
* built with NESTED literals, not `cap = rc1` ident rhs — the
|
|
* probes' own construction shape is the #36 under-copy (struct-
|
|
* ident field rhs copies 8B), which keeps p6min9/p6min10
|
|
* themselves blocked on #36 with their read-half fixed here. */
|
|
{ "read_depth2_idx",
|
|
"package main;\n"
|
|
"type capture = struct { content: str, start: size, end: size };\n"
|
|
"type t = struct { pc: size, cap: capture };\n"
|
|
"let end: size = 777;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet ts: []t = [];\n"
|
|
"\tappend(ts, t { pc = 5, cap = capture { content = \"abc\", start = 11, end = 13 } });\n"
|
|
"\tappend(ts, t { pc = 6, cap = capture { content = \"wy\", start = 21, end = 23 } });\n"
|
|
"\tif (ts[0].cap.end == 777) { return 66; };\n"
|
|
"\tif (ts[0].cap.end != 13) { return 1; };\n"
|
|
"\tif (ts[0].cap.start != 11) { return 2; };\n"
|
|
"\tif (ts[1].cap.end != 23) { return 3; };\n"
|
|
"\tif (ts[1].cap.content.len != 2) { return 4; };\n"
|
|
"\tif (ts[1].pc != 6) { return 5; };\n"
|
|
"\treturn 59;\n"
|
|
"};\n",
|
|
59, NULL },
|
|
|
|
/* C2 (F4): the same chain behind a DEREF-rooted index — plus the
|
|
* offset-0 scalar (.pc) that pre-C2 cstage read correctly only by
|
|
* COINCIDENCE through the offset-blind catch-all (ken's FA3
|
|
* correction); now it routes through the resolver in both
|
|
* stages. */
|
|
{ "read_depth2_deref",
|
|
"package main;\n"
|
|
"type capture = struct { content: str, start: size, end: size };\n"
|
|
"type t = struct { pc: size, cap: capture };\n"
|
|
"fn rd(ts: *[]t) i32 = {\n"
|
|
"\tif ((*ts)[0].pc != 5) { return 1; };\n"
|
|
"\tif ((*ts)[0].cap.start != 11) { return 2; };\n"
|
|
"\tif ((*ts)[0].cap.end != 13) { return 3; };\n"
|
|
"\tif ((*ts)[1].cap.end != 23) { return 4; };\n"
|
|
"\treturn 0;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet ts: []t = [];\n"
|
|
"\tappend(ts, t { pc = 5, cap = capture { content = \"abc\", start = 11, end = 13 } });\n"
|
|
"\tappend(ts, t { pc = 6, cap = capture { content = \"wy\", start = 21, end = 23 } });\n"
|
|
"\tlet rc: i32 = rd(&ts);\n"
|
|
"\tif (rc != 0) { return rc; };\n"
|
|
"\treturn 60;\n"
|
|
"};\n",
|
|
60, NULL },
|
|
|
|
/* C2 (F4): leaf-kind matrix behind the deref-index spine — narrow
|
|
* signed/unsigned (fldloadop sign/zero extension), f64/f32 via
|
|
* X0, and an `[N]u8` field leaf (address semantics, outer index
|
|
* applies). */
|
|
{ "read_widths_spine",
|
|
"package main;\n"
|
|
"type inner = struct { b: i8, ub: u8, w: i16, uw: u16, d: i32, ud: u32, f: f64, g: f32, arr: [3]u8 };\n"
|
|
"type box = struct { pad: size, it: inner };\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet bs: []box = [];\n"
|
|
"\tappend(bs, box { pad = 1, it = inner { b = -5i8, ub = 200u8, w = -300i16, uw = 60000u16, d = -70000, ud = 4000000000u32, f = 1.5, g = 2.5f32, arr = [7u8, 8u8, 9u8] } });\n"
|
|
"\tlet p: *[]box = &bs;\n"
|
|
"\tif ((*p)[0].it.b != -5i8) { return 1; };\n"
|
|
"\tif ((*p)[0].it.ub != 200u8) { return 2; };\n"
|
|
"\tif ((*p)[0].it.w != -300i16) { return 3; };\n"
|
|
"\tif ((*p)[0].it.uw != 60000u16) { return 4; };\n"
|
|
"\tif ((*p)[0].it.d != -70000) { return 5; };\n"
|
|
"\tif ((*p)[0].it.ud != 4000000000u32) { return 6; };\n"
|
|
"\tif ((*p)[0].it.f != 1.5) { return 7; };\n"
|
|
"\tif ((*p)[0].it.g != 2.5f32) { return 8; };\n"
|
|
"\tif ((*p)[0].it.arr[1] != 8u8) { return 9; };\n"
|
|
"\treturn 61;\n"
|
|
"};\n",
|
|
61, NULL },
|
|
|
|
/* FA3/pA5 graduated: 24B slice-header field read behind a deref-
|
|
* index spine — pre-C2 cstage took the offset-blind catch-all
|
|
* (one wrong-offset MOVQ + stale BX/CX as the header), wwstage
|
|
* silently emitted NOTHING. Plus the .cap pseudo behind the same
|
|
* spine (recurses through the resolver). */
|
|
{ "read_slicefield",
|
|
"package main;\n"
|
|
"type box = struct { pc: size, xs: []size };\n"
|
|
"fn grab(p: *[]box, i: size) i32 = {\n"
|
|
"\tlet h: []size = (*p)[i].xs;\n"
|
|
"\tif (len(h) != 2) { return 1; };\n"
|
|
"\tif (h[0] != (7: size)) { return 2; };\n"
|
|
"\tif (h[1] != (8: size)) { return 3; };\n"
|
|
"\tif ((*p)[i].xs.cap < 2) { return 4; };\n"
|
|
"\treturn 0;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet src: []size = [];\n"
|
|
"\tappend(src, (7: size));\n"
|
|
"\tappend(src, (8: size));\n"
|
|
"\tlet bs: []box = [];\n"
|
|
"\tappend(bs, box { pc = 1, xs = src });\n"
|
|
"\tlet rc: i32 = grab(&bs, 0);\n"
|
|
"\tif (rc != 0) { return rc; };\n"
|
|
"\treturn 62;\n"
|
|
"};\n",
|
|
62, NULL },
|
|
|
|
/* Reviewer-A route: address-of a field through an indexed /
|
|
* deref-indexed spine, then deref + compound through the pointer.
|
|
* Pre-C2 BOTH stages silently dropped the & (stale AX as the
|
|
* address — gate-blind SEGFAULT). */
|
|
{ "amp_spine",
|
|
"package main;\n"
|
|
"type capture = struct { content: str, start: size, end: size };\n"
|
|
"type t = struct { pc: size, cap: capture };\n"
|
|
"fn bump(cp: *capture) void = { cp.end += 100; };\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet ts: []t = [];\n"
|
|
"\tappend(ts, t { pc = 5, cap = capture { content = \"abc\", start = 11, end = 13 } });\n"
|
|
"\tappend(ts, t { pc = 6, cap = capture { content = \"wy\", start = 21, end = 23 } });\n"
|
|
"\tlet cp: *capture = &ts[1].cap;\n"
|
|
"\tif (cp.start != 21) { return 1; };\n"
|
|
"\tbump(cp);\n"
|
|
"\tif (ts[1].cap.end != 123) { return 2; };\n"
|
|
"\tlet pp: *[]t = &ts;\n"
|
|
"\tlet dp: *capture = &(*pp)[0].cap;\n"
|
|
"\tif (dp.end != 13) { return 3; };\n"
|
|
"\treturn 63;\n"
|
|
"};\n",
|
|
63, NULL },
|
|
|
|
/* Asm-neutrality pins for the arms the C2 gates must NOT disturb:
|
|
* ident-rooted value chain (chain walker), `(*p).f` retarget,
|
|
* untyped-str literal pseudo-fields (the gated catch-all's only
|
|
* legitimate tenant). A tuple-positional pin is blocked by the
|
|
* pre-existing #33 wwstage tuple-let word-2 drop (filed). */
|
|
{ "neutral_reads",
|
|
"package main;\n"
|
|
"type capture = struct { content: str, start: size, end: size };\n"
|
|
"type t = struct { pc: size, cap: capture };\n"
|
|
"fn pcof(p: *t) size = { return (*p).pc; };\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet x: t = t { pc = 9, cap = capture { content = \"hi\", start = 1, end = 2 } };\n"
|
|
"\tif (x.cap.start != 1) { return 1; };\n"
|
|
"\tif (x.cap.content.len != 2) { return 2; };\n"
|
|
"\tif (pcof(&x) != 9) { return 3; };\n"
|
|
"\tif (\"abc\".len != 3) { return 4; };\n"
|
|
"\treturn 64;\n"
|
|
"};\n",
|
|
64, NULL },
|
|
|
|
/* Tagged leaf behind a spine: no canonical register convention
|
|
* is emitted by the read-resolver yet — must die LOUD, not load
|
|
* 8 of 16+ bytes (rule 7). */
|
|
{ "reject_read_tagged",
|
|
"package main;\n"
|
|
"type v = (i64 | bool);\n"
|
|
"type t = struct { pc: size, tg: v };\n"
|
|
"fn rd(ts: *[]t, i: size) i64 = {\n"
|
|
"\tlet x: v = (*ts)[i].tg;\n"
|
|
"\treturn 1;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet ts: []t = [];\n"
|
|
"\treturn rd(&ts, 0): i32;\n"
|
|
"};\n",
|
|
BUILD_FAIL, "read-resolver: tagged field read not wired (rule-7)" },
|
|
|
|
/* A call-result index base has no storage address — the resolver
|
|
* must refuse and the read tail must be LOUD (pre-C2: cstage
|
|
* emitted an offset-blind garbage read, wwstage nothing). */
|
|
{ "reject_read_callbase",
|
|
"package main;\n"
|
|
"type t = struct { pc: size, matched: bool };\n"
|
|
"fn mk() []t = {\n"
|
|
"\tlet ts: []t = [];\n"
|
|
"\tappend(ts, t { pc = 3, matched = true });\n"
|
|
"\treturn ts;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tif (mk()[0].pc != 3) { return 1; };\n"
|
|
"\treturn 0;\n"
|
|
"};\n",
|
|
BUILD_FAIL, "unsupported field-read shape" },
|
|
|
|
/* The assign-tail diagnostic keeps a carrier now that C1's
|
|
* reject_tail shape (h.arr[1].pc) graduated: a call-base lvalue
|
|
* is unaddressable. */
|
|
{ "reject_assign_callbase",
|
|
"package main;\n"
|
|
"type t = struct { pc: size, matched: bool };\n"
|
|
"fn mk() []t = {\n"
|
|
"\tlet ts: []t = [];\n"
|
|
"\tappend(ts, t { pc = 3, matched = true });\n"
|
|
"\treturn ts;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tmk()[0].pc = 2;\n"
|
|
"\treturn 0;\n"
|
|
"};\n",
|
|
BUILD_FAIL, "unsupported assign target shape" },
|
|
|
|
/* TK_AMP loud tail (the C2 replacement for the silent drop). */
|
|
{ "reject_addrof_callbase",
|
|
"package main;\n"
|
|
"type t = struct { pc: size, matched: bool };\n"
|
|
"fn mk() []t = {\n"
|
|
"\tlet ts: []t = [];\n"
|
|
"\tappend(ts, t { pc = 3, matched = true });\n"
|
|
"\treturn ts;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet p: *size = &mk()[0].pc;\n"
|
|
"\treturn (*p): i32;\n"
|
|
"};\n",
|
|
BUILD_FAIL, "unsupported address-of shape" },
|
|
|
|
/* C3 (task #8) F10 class: the index BASE is typed via an N_TNAME
|
|
* alias — pre-C3 wwstage's name-keyed arm missed it (interim C2
|
|
* loud guard). Field-kind matrix mirrors the cstage arm's
|
|
* dispatch: narrow signed/unsigned, 8B, float, str header, slice
|
|
* header, [N]T field as outer-index base (#270-1a). Construction
|
|
* goes through DIRECT-typed locals: the alias-typed `= []` /
|
|
* arrlit-init/global-DATA sites are a separate filed family
|
|
* (unmasked behind C3, not cgdot). */
|
|
{ "c3_alias_slice_reads",
|
|
"package main;\n"
|
|
"type wide = struct { b: u8, w: i16, d: u32, q: i64, f: f64,\n"
|
|
"\ts: str, xs: []size, m: [2]u32 };\n"
|
|
"type wlist = []wide;\n"
|
|
"fn mkw(k: i64) wide = {\n"
|
|
"\tlet xs: []size = [];\n"
|
|
"\tlet x0: size = (10 + k): size;\n"
|
|
"\tlet x1: size = (20 + k): size;\n"
|
|
"\tappend(xs, x0);\n"
|
|
"\tappend(xs, x1);\n"
|
|
"\tlet m0: u32 = (40 + k): u32;\n"
|
|
"\tlet m1: u32 = (50 + k): u32;\n"
|
|
"\treturn wide { b = (k + 1): u8, w = (0 - k): i16,\n"
|
|
"\t\td = (100 + k): u32, q = k, f = 2.5f64, s = \"ab\",\n"
|
|
"\t\txs = xs, m = [m0, m1] };\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet l0: []wide = [];\n"
|
|
"\tlet w1: wide = mkw(1);\n"
|
|
"\tlet w2: wide = mkw(2);\n"
|
|
"\tappend(l0, w1);\n"
|
|
"\tappend(l0, w2);\n"
|
|
"\tlet l: wlist = l0;\n"
|
|
"\tif (l[0].b != 2u8) { return 1; };\n"
|
|
"\tif (l[0].w != -1i16) { return 2; };\n"
|
|
"\tif (l[1].d != 102u32) { return 3; };\n"
|
|
"\tif (l[1].q != 2i64) { return 4; };\n"
|
|
"\tif (l[0].f != 2.5f64) { return 5; };\n"
|
|
"\tif (l[0].s.len != 2) { return 6; };\n"
|
|
"\tif (l[1].xs[1] != (22: size)) { return 7; };\n"
|
|
"\tif (l[0].m[1] != 51u32) { return 8; };\n"
|
|
"\treturn 65;\n"
|
|
"};\n",
|
|
65, NULL },
|
|
|
|
/* F10 sibling bases: alias-typed [2]S array (local), and an
|
|
* alias-typed []*S whose ELEMENT is a pointer (the arm's viaptr
|
|
* deref hop). */
|
|
{ "c3_alias_arr_viaptr",
|
|
"package main;\n"
|
|
"type wide = struct { b: u8, q: i64, xs: []size };\n"
|
|
"type warr = [2]wide;\n"
|
|
"type pw = *wide;\n"
|
|
"type plist = []pw;\n"
|
|
"fn mkw(k: i64) wide = {\n"
|
|
"\tlet xs: []size = [];\n"
|
|
"\tlet x0: size = (10 + k): size;\n"
|
|
"\tappend(xs, x0);\n"
|
|
"\treturn wide { b = (k + 1): u8, q = k, xs = xs };\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet w3: wide = mkw(3);\n"
|
|
"\tlet w4: wide = mkw(4);\n"
|
|
"\tlet a0: [2]wide = [w3, w4];\n"
|
|
"\tlet a: warr = a0;\n"
|
|
"\tif (a[0].q != 3i64) { return 1; };\n"
|
|
"\tif (a[1].b != 5u8) { return 2; };\n"
|
|
"\tlet w5: wide = mkw(5);\n"
|
|
"\tlet ps0: []pw = [];\n"
|
|
"\tappend(ps0, &w5);\n"
|
|
"\tlet ps: plist = ps0;\n"
|
|
"\tif (ps[0].q != 5i64) { return 3; };\n"
|
|
"\tif (ps[0].b != 6u8) { return 4; };\n"
|
|
"\tif (ps[0].xs[0] != (15: size)) { return 5; };\n"
|
|
"\treturn 66;\n"
|
|
"};\n",
|
|
66, NULL },
|
|
|
|
/* p11b's F10 essence with the task-#14-gated `?` factored out: a
|
|
* let bound from a fn returning the alias, indexed-field reads off
|
|
* it (m's tnode is the N_TNAME alias `result`). */
|
|
{ "c3_letbound_alias",
|
|
"package main;\n"
|
|
"type capture = struct { content: str, start: size, end: size };\n"
|
|
"type result = []capture;\n"
|
|
"fn mk() result = {\n"
|
|
"\tlet caps: []capture = [];\n"
|
|
"\tappend(caps, capture { content = \"xy\", start = 1, end = 3 });\n"
|
|
"\tappend(caps, capture { content = \"z\", start = 4, end = 5 });\n"
|
|
"\treturn caps;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet m: result = mk();\n"
|
|
"\tif (len(m) != 2) { return 1; };\n"
|
|
"\tif (m[0].start != 1) { return 2; };\n"
|
|
"\tif (m[0].content.len != 2) { return 3; };\n"
|
|
"\tif (m[1].end != 5) { return 4; };\n"
|
|
"\treturn 67;\n"
|
|
"};\n",
|
|
67, NULL },
|
|
|
|
/* F7: the composed regex run_thread hot shape — a tagged-slice
|
|
* field indexed by a deref-index-field chain, match-dispatched,
|
|
* plus compound/store back through the same spine (p7_composed
|
|
* core; the strings.frombytes tail lives with task #29). */
|
|
{ "c3_composed_match",
|
|
"package main;\n"
|
|
"type inst_lit = rune;\n"
|
|
"type inst_any = void;\n"
|
|
"type inst_match = bool;\n"
|
|
"type inst = (inst_lit | inst_any | inst_match);\n"
|
|
"type re_t = struct { insts: []inst, n_reps: size };\n"
|
|
"type capture = struct { content: str, start: size, end: size };\n"
|
|
"type thread = struct { pc: size, root_capture: capture, failed: bool };\n"
|
|
"fn step(re: *re_t, ts: *[]thread, i: size) i32 = {\n"
|
|
"\tmatch (re.insts[(*ts)[i].pc]) {\n"
|
|
"\tcase let lt: inst_lit => {\n"
|
|
"\t\t(*ts)[i].root_capture = capture { content = \"hit\", start = 2, end = 4 };\n"
|
|
"\t\t(*ts)[i].pc += 1;\n"
|
|
"\t\treturn 1;\n"
|
|
"\t};\n"
|
|
"\tcase inst_any => {\n"
|
|
"\t\t(*ts)[i].failed = true;\n"
|
|
"\t\treturn 2;\n"
|
|
"\t};\n"
|
|
"\tcase let m: inst_match => {\n"
|
|
"\t\t(*ts)[i].pc += 2;\n"
|
|
"\t\treturn 3;\n"
|
|
"\t};\n"
|
|
"\t};\n"
|
|
"\treturn 9;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet insts: []inst = [];\n"
|
|
"\tappend(insts, ('a': inst_lit));\n"
|
|
"\tlet av: inst_any;\n"
|
|
"\tlet v: inst = av;\n"
|
|
"\tappend(insts, v);\n"
|
|
"\tappend(insts, (true: inst_match));\n"
|
|
"\tlet re: re_t = re_t { insts = insts, n_reps = 0 };\n"
|
|
"\tlet ts: []thread = [];\n"
|
|
"\tlet z: capture = capture { content = \"\", start = 0, end = 0 };\n"
|
|
"\tappend(ts, thread { pc = 0, root_capture = z, failed = false });\n"
|
|
"\tif (step(&re, &ts, 0) != 1) { return 1; };\n"
|
|
"\tif (ts[0].pc != 1) { return 2; };\n"
|
|
"\tif (ts[0].root_capture.start != 2) { return 3; };\n"
|
|
"\tif (step(&re, &ts, 0) != 2) { return 4; };\n"
|
|
"\tif (!ts[0].failed) { return 5; };\n"
|
|
"\tlet p: *[]thread = &ts;\n"
|
|
"\t(*p)[0].pc += 1;\n"
|
|
"\tif (ts[0].pc != 2) { return 6; };\n"
|
|
"\tif (step(&re, &ts, 0) != 3) { return 7; };\n"
|
|
"\tif (ts[0].pc != 4) { return 8; };\n"
|
|
"\treturn 68;\n"
|
|
"};\n",
|
|
68, NULL },
|
|
|
|
/* FA6 (pA9): free() consumes its operand via cgexpr — the
|
|
* deref-index-field reads inside the operand are C3's reads, the
|
|
* free arm itself is sound. Pure acceptance row. */
|
|
{ "c3_free_operand",
|
|
"package main;\n"
|
|
"type capture = struct { content: str, start: size };\n"
|
|
"type thread = struct { pc: size, captures: []capture,\n"
|
|
"\trep_counters: []size };\n"
|
|
"fn delete_thread(i: size, threads: *[]thread) void = {\n"
|
|
"\tfree((*threads)[i].captures);\n"
|
|
"\tfree((*threads)[i].rep_counters);\n"
|
|
"\tdelete((*threads)[i]);\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet z: []capture;\n"
|
|
"\tlet zr: []size;\n"
|
|
"\tlet xs: []thread = [];\n"
|
|
"\tappend(xs, thread { pc = 1, captures = z, rep_counters = zr });\n"
|
|
"\tappend(xs, thread { pc = 2, captures = z, rep_counters = zr });\n"
|
|
"\tappend(xs, thread { pc = 3, captures = z, rep_counters = zr });\n"
|
|
"\tdelete_thread(1, &xs);\n"
|
|
"\tif (len(xs) != 2) { return 1; };\n"
|
|
"\tif (xs[0].pc != 1) { return 2; };\n"
|
|
"\tif (xs[1].pc != 3) { return 3; };\n"
|
|
"\treturn 69;\n"
|
|
"};\n",
|
|
69, NULL },
|
|
|
|
/* C4 (F5, task #7): `let e = xs[0]` from a SLICE base — the #265
|
|
* source-addr enumeration had TY_ARRAY/N_DOT/N_INDEX bases but no
|
|
* TY_SLICE base, so cstage emitted NOTHING (slot uninitialised)
|
|
* and wwstage fell to its scalar default (8B truncation) —
|
|
* gate-blind cs≠ww. p6min13 graduated verbatim; the slice base
|
|
* now resolves through cgplaceaddr.
|
|
*
|
|
* LANDMINE (task #43): the UNANNOTATED form `let e = xs[0]` is
|
|
* still cs≠ww — wwstage's aggn/letslotsize gates are annotation-
|
|
* keyed and skip the copy arm (8B truncation) while cstage keys
|
|
* the stamped n->type and full-copies. No dual-driver row can pin
|
|
* a divergent shape; every row below annotates. Do NOT read the
|
|
* annotated rows as covering the inferred form. */
|
|
{ "c4_let_slice_elem",
|
|
"package main;\n"
|
|
"type th = struct { pc: size, matched: bool };\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []th = [];\n"
|
|
"\tappend(xs, th { pc = 7, matched = true });\n"
|
|
"\tlet e: th = xs[0];\n"
|
|
"\tif (e.pc != 7) { return 1; };\n"
|
|
"\tif (!e.matched) { return 2; };\n"
|
|
"\treturn 70;\n"
|
|
"};\n",
|
|
70, NULL },
|
|
|
|
/* C4 size matrix: 24B (3-word run), 40B (str header + 2 words),
|
|
* and 12B (maxalign 4 → MOVQ+MOVL tail) elements. */
|
|
{ "c4_let_elem_sizes",
|
|
"package main;\n"
|
|
"type t3 = struct { a: i64, b: i64, c: i64 };\n"
|
|
"type capture = struct { content: str, start: size, end: size };\n"
|
|
"type t12 = struct { x: u32, y: u32, z: u32 };\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []t3 = [];\n"
|
|
"\tappend(xs, t3 { a = 1, b = 2, c = 3 });\n"
|
|
"\tappend(xs, t3 { a = 4, b = 5, c = 6 });\n"
|
|
"\tlet e: t3 = xs[1];\n"
|
|
"\tif (e.a != 4) { return 1; };\n"
|
|
"\tif (e.b != 5) { return 2; };\n"
|
|
"\tif (e.c != 6) { return 3; };\n"
|
|
"\tlet cs: []capture = [];\n"
|
|
"\tappend(cs, capture { content = \"abc\", start = 11, end = 13 });\n"
|
|
"\tlet c: capture = cs[0];\n"
|
|
"\tif (c.content.len != 3) { return 4; };\n"
|
|
"\tif (c.start != 11) { return 5; };\n"
|
|
"\tif (c.end != 13) { return 6; };\n"
|
|
"\tlet ns: []t12 = [];\n"
|
|
"\tappend(ns, t12 { x = 7u32, y = 8u32, z = 9u32 });\n"
|
|
"\tlet n: t12 = ns[0];\n"
|
|
"\tif (n.x != 7u32) { return 7; };\n"
|
|
"\tif (n.y != 8u32) { return 8; };\n"
|
|
"\tif (n.z != 9u32) { return 9; };\n"
|
|
"\treturn 71;\n"
|
|
"};\n",
|
|
71, NULL },
|
|
|
|
/* Reviewer-C2 inheritance: aggregate leaf behind a deref spine
|
|
* into a let — `let c = (*ts)[i].cap`. Pre-C4 wwstage loud-bailed
|
|
* at its cgdot aggregate-leaf gate (correct per rule 7) while
|
|
* cstage SILENTLY emitted no copy; both now route the let copy
|
|
* through the resolver. */
|
|
{ "c4_let_deref_leaf",
|
|
"package main;\n"
|
|
"type capture = struct { content: str, start: size, end: size };\n"
|
|
"type t = struct { pc: size, cap: capture };\n"
|
|
"fn grab(ts: *[]t, i: size) i32 = {\n"
|
|
"\tlet c: capture = (*ts)[i].cap;\n"
|
|
"\tif (c.start != 11) { return 1; };\n"
|
|
"\tif (c.end != 13) { return 2; };\n"
|
|
"\tif (c.content.len != 3) { return 3; };\n"
|
|
"\treturn 0;\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet ts: []t = [];\n"
|
|
"\tappend(ts, t { pc = 5, cap = capture { content = \"abc\", start = 11, end = 13 } });\n"
|
|
"\tlet rc: i32 = grab(&ts, 0);\n"
|
|
"\tif (rc != 0) { return rc; };\n"
|
|
"\treturn 72;\n"
|
|
"};\n",
|
|
72, NULL },
|
|
|
|
/* C4 (F5/FC0): by-value range payload copies through the same
|
|
* full-width machinery — pre-C4 the single fldloadop word
|
|
* truncated every aggregate element to 8B (cstage), wwstage's
|
|
* structural esz fell to its 8-sentinel (or 1 for non-ident
|
|
* scrutinees: regex.finish's cs≠ww hunk) and the ww checker left
|
|
* the binding typeless (asserttyped bail on any field read).
|
|
* Rows: []struct sum of BOTH fields, []capture (40B, str header
|
|
* inside), []str (24B header, .len readback). */
|
|
{ "c4_range_byvalue",
|
|
"package main;\n"
|
|
"type th = struct { pc: size, weight: size };\n"
|
|
"type capture = struct { content: str, start: size, end: size };\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet xs: []th = [];\n"
|
|
"\tappend(xs, th { pc = 7, weight = 100 });\n"
|
|
"\tappend(xs, th { pc = 8, weight = 200 });\n"
|
|
"\tlet total: size = 0;\n"
|
|
"\tfor (let t .. xs) {\n"
|
|
"\t\ttotal += t.pc;\n"
|
|
"\t\ttotal += t.weight;\n"
|
|
"\t};\n"
|
|
"\tif (total != 315) { return 1; };\n"
|
|
"\tlet cs: []capture = [];\n"
|
|
"\tappend(cs, capture { content = \"abc\", start = 11, end = 13 });\n"
|
|
"\tappend(cs, capture { content = \"wy\", start = 21, end = 23 });\n"
|
|
"\tlet csum: size = 0;\n"
|
|
"\tfor (let cp .. cs) {\n"
|
|
"\t\tcsum += cp.content.len: size;\n"
|
|
"\t\tcsum += cp.start;\n"
|
|
"\t\tcsum += cp.end;\n"
|
|
"\t};\n"
|
|
"\tif (csum != 73) { return 2; };\n"
|
|
"\tlet ss: []str = [];\n"
|
|
"\tappend(ss, \"hello\");\n"
|
|
"\tappend(ss, \"worldly\");\n"
|
|
"\tlet slen: size = 0;\n"
|
|
"\tfor (let s .. ss) {\n"
|
|
"\t\tslen += s.len: size;\n"
|
|
"\t};\n"
|
|
"\tif (slen != 12) { return 3; };\n"
|
|
"\treturn 73;\n"
|
|
"};\n",
|
|
73, NULL },
|
|
|
|
/* C4 range edges: zero-iteration over an empty slice; binder
|
|
* MUTATION pinning Hare's by-VALUE payload semantics (writes to
|
|
* the binder land in its frame slot copy, never the slice — a
|
|
* by-reference regression would flip xs[0]); and a 12B element
|
|
* exercising the sized MOVL tail of the range word-copy (the
|
|
* 16B/24B/40B rows above are all full-word). */
|
|
{ "c4_range_edges",
|
|
"package main;\n"
|
|
"type th = struct { pc: size, matched: bool };\n"
|
|
"type t12 = struct { x: u32, y: u32, z: u32 };\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet es: []th = [];\n"
|
|
"\tfor (let t .. es) { return 1; };\n"
|
|
"\tlet xs: []th = [];\n"
|
|
"\tappend(xs, th { pc = 7, matched = true });\n"
|
|
"\tlet cnt: size = 0;\n"
|
|
"\tfor (let t2 .. xs) { t2.pc = 99; t2.matched = false; cnt += 1; };\n"
|
|
"\tif (cnt != 1) { return 2; };\n"
|
|
"\tif (xs[0].pc != 7) { return 3; };\n"
|
|
"\tif (!xs[0].matched) { return 4; };\n"
|
|
"\tlet ns: []t12 = [];\n"
|
|
"\tappend(ns, t12 { x = 1u32, y = 2u32, z = 3u32 });\n"
|
|
"\tappend(ns, t12 { x = 4u32, y = 5u32, z = 6u32 });\n"
|
|
"\tlet sum: size = 0;\n"
|
|
"\tfor (let n .. ns) { sum += n.x: size; sum += n.y: size; sum += n.z: size; };\n"
|
|
"\tif (sum != 21) { return 5; };\n"
|
|
"\treturn 74;\n"
|
|
"};\n",
|
|
74, NULL },
|
|
|
|
/* C4 loud tail: a non-place rhs (tagged unwrap into a struct let)
|
|
* has no source address and nothing below the aggregate arm can
|
|
* initialise a >8B slot — pre-C4 cstage emitted NO init and
|
|
* wwstage stored 8B, both silent. */
|
|
{ "reject_let_unhandled",
|
|
"package main;\n"
|
|
"type pair = struct { a: i64, b: i64 };\n"
|
|
"fn mk() (pair | nomem) = {\n"
|
|
"\treturn pair { a = 1, b = 2 };\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet p: pair = mk()!;\n"
|
|
"\treturn p.a: i32;\n"
|
|
"};\n",
|
|
BUILD_FAIL,
|
|
"let: aggregate init from unhandled rhs shape (task #7/rule-7)" },
|
|
};
|
|
|
|
/* 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 tmpdir[64], src[128], outbin[128], errlog[128], rmcmd[160];
|
|
char cmd[1200];
|
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/plad_%d_d_%d", getpid(), i);
|
|
mkdir(tmpdir, 0755);
|
|
snprintf(src, sizeof src, "%s/plad_%d_%d.ww", tmpdir, getpid(), i);
|
|
snprintf(outbin, sizeof outbin, "%s/plad_%d_%d", tmpdir, getpid(), i);
|
|
snprintf(errlog, sizeof errlog, "%s/err", tmpdir);
|
|
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
|
|
|
FILE *f = fopen(src, "wb");
|
|
if (!f) { runwait(rmcmd); return -1; }
|
|
fputs(r->src, f);
|
|
fclose(f);
|
|
|
|
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>%s",
|
|
driver, outbin, 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 */
|
|
}
|
|
runwait(rmcmd);
|
|
return rc;
|
|
}
|
|
|
|
int got = runwait(outbin);
|
|
|
|
runwait(rmcmd);
|
|
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;
|
|
}
|