diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 3ab1a3a4..3a83f357 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -433,32 +433,6 @@ fldstoreop(Type *t, int sz) return A_MOVQ; } -/* fld_alias_tripwire — #73 guard at the single-peel field gates this - * fold did NOT chase. The #5-F1 acceptance admits 2+-level alias field - * types everywhere; a still-NAMED type after one peel skips a gate's - * multi-word arms (slice/str 3-word, tagged widener, struct copy) for - * a word0 scalar tail — accept-and-corrupt. Loud over silent (rule 7); - * chase + per-arm probes are task #73. Fires ONLY on 2+-level chains - * over an aggregate base: <=1-level (the entire pre-#5-F1-legal set) - * and scalar/float bases (width-driven tails, depth-safe) never fire. */ -static void -fld_alias_tripwire(Type *ft, const char *site) -{ - Type *u = (ft && ft->kind == TY_NAMED) ? ft->under : ft; - if (u == NULL || u->kind != TY_NAMED) return; - Type *base = type_chase_named(u); - if (base == NULL) return; - switch (base->kind) { - case TY_SLICE: case TY_STR: case TY_TAGGED: - case TY_STRUCT: case TY_TUPLE: case TY_ARRAY: - fatal("%s: 2+-level alias field type '%s' at unswept " - "single-peel gate (#73)", site, - ft->name ? ft->name : "?"); - default: - return; - } -} - /* castsrcprim — structural (size, unsigned) of an N_CAST's source * expression, mirroring wwstage's exprprimresolved in * selfhost/cmd/wcc/cgenutil.ww. The cgen-stage match has to be @@ -5079,10 +5053,7 @@ cgexpr(Cg *c, Node *n, Local *locals) if (f != NULL && (is_arr || is_sl || is_ptr) && off != 0) { Type *ft = f->type; - fld_alias_tripwire(ft, - "indexed-elem field store"); - Type *fu = (ft && ft->kind == TY_NAMED) - ? ft->under : ft; + Type *fu = type_chase_named(ft); int fsz = (int)(ft ? ft->size : 8); int store_op = fldstoreop(ft, fsz); int foff = (int)f->offset; @@ -7870,10 +7841,7 @@ cgexpr(Cg *c, Node *n, Local *locals) * CX=cap). Route the heap base through DX so all * three survive — CX now holds cap, BX holds len * (#1/Phase 3). */ - fld_alias_tripwire(ftype, - "heap struct-lit field fill"); - Type *fu = (ftype && ftype->kind == TY_NAMED) - ? ftype->under : ftype; + Type *fu = type_chase_named(ftype); if (fu && fu->kind == TY_STR) { ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_DX)); ins2(c, A_MOVQ, areg(D_AX), @@ -10657,10 +10625,7 @@ cgexpr(Cg *c, Node *n, Local *locals) } if (tp != NULL) { int fsz = (int)(tp->type ? tp->type->size : 8); - fld_alias_tripwire(tp->type, - "tuple-elem read"); - Type *fu = (tp->type && tp->type->kind == TY_NAMED) - ? tp->type->under : tp->type; + Type *fu = type_chase_named(tp->type); int op = fldloadop(tp->type, fsz); int off = localfind(locals, n->lhs->str); /* C-t3 (#48): GLOBAL tuple base — LEAQ the @@ -10990,10 +10955,7 @@ cgexpr(Cg *c, Node *n, Local *locals) if (strcmp(f->name, n->str) != 0) continue; cgexpr(c, n->lhs, locals); /* AX = inner ptr */ Type *ft = f->type; - fld_alias_tripwire(ft, - "ptr-chain field read"); - Type *fu = (ft && ft->kind == TY_NAMED) - ? ft->under : ft; + Type *fu = type_chase_named(ft); /* tagged leaf (#38a): AX holds the *struct * base and the tagged cursor targets AX * (tag) — stage the base in BX, then the @@ -11149,10 +11111,7 @@ cgexpr(Cg *c, Node *n, Local *locals) areg(D_AX)); int foff = (int)f->offset; Type *ft = f->type; - fld_alias_tripwire(ft, - "indexed-elem field read"); - Type *fu = (ft && ft->kind == TY_NAMED) - ? ft->under : ft; + Type *fu = type_chase_named(ft); /* #270-1a: an `[N]T`-typed field of an * array element (`a[i].m[j]`) — leave the * field's ADDRESS, a base for the outer @@ -14283,9 +14242,7 @@ emit_struct_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, u64 base) } Node *vr = v; while (vr != NULL && vr->kind == N_CAST) vr = vr->lhs; - fld_alias_tripwire(f->type, "static struct-lit emit"); - Type *fu = (f->type && f->type->kind == TY_NAMED) - ? f->type->under : f->type; + Type *fu = type_chase_named(f->type); if (fu && fu->kind == TY_STRUCT) { /* Recurse into nested struct lit. Pre-#145 the parser/ * checker has its own gap on inner-N_STRUCTLIT field diff --git a/test/wcc/944_alias_accept_run.c b/test/wcc/944_alias_accept_run.c index 38d02186..5e567026 100644 --- a/test/wcc/944_alias_accept_run.c +++ b/test/wcc/944_alias_accept_run.c @@ -460,12 +460,11 @@ static const struct row rows[] = { " if (w[2] != 900) { return 2; };\n" " return 0;\n" "};\n", 0, K_RUN, NULL }, /* graduated: #60 (F2a batch 1) */ - /* #73 tripwire pin: the UNPROBED single-peel field gates + /* #73 GRADUATED (B5-c2): the tripwired single-peel field gates * (indexed-elem store/read, ptr-chain read, heap fill, tuple-elem - * read, static emit) hard-error on a 2+-level alias over an - * aggregate base instead of emitting a word0 tail. ww accepts and - * runs these — cs-only loud until #73's chase. 1-level controls - * verified per gate (/tmp/revF1 t*c probes). */ + * read, static emit) now chase the field type — the loud bound + * flipped to working 0/0 byte-id. Per-gate-arm rows live in + * 944_alias_cgen_b5_run. */ { "slicefield_idx_tripwire_73", "package main;\n" "type sl = []int;\n" @@ -477,8 +476,7 @@ static const struct row rows[] = { " xs[0].s = a[0:3];\n" " if (xs[0].s.len != 3) { return 1; };\n" " return 0;\n" - "};\n", 0, K_BUILDERR_CS, - "unswept single-peel gate (#73)" }, + "};\n", 0, K_RUN, NULL }, /* graduated: #73 (B5-c2) */ /* Values >255 break the esz=1 prefix-luck; the write-then-read * pins the IMULQ stride on both the read and write spines. */ { "idx_2level", diff --git a/test/wcc/944_alias_cgen_b5_run.c b/test/wcc/944_alias_cgen_b5_run.c index 98910725..8a5b6c6c 100644 --- a/test/wcc/944_alias_cgen_b5_run.c +++ b/test/wcc/944_alias_cgen_b5_run.c @@ -70,6 +70,28 @@ * | #85 type_unwrap close is SITE- | * | closure with zero live graduations | * | — this row pins the bound | err/0 + * ---- c2 (#73 graduation: tripwire deleted, 6 fu gates chased) -- + * g73_idxstore / | slice / str / tagged 2-lvl alias | + * g73_strfield / | field at the indexed-elem STORE | + * g73_tagfield | gate — pre = loud "#73" fatal | 0/0 + * g73_idxread / | slice arm at the indexed-elem READ, | + * g73_ptrread / | ptr-chain READ and tuple-elem READ | + * g73_tupread | gates — pre = loud "#73" fatal | 0/0 + * g73_static_struct | nested-struct arm at the static | + * | struct-lit emit gate — pre = "#73" | 0/0 + * g73_heapfill | str arm at the heap struct-lit fill | + * | gate — COMPILE-only pin: bare /tmp | + * | programs never link malloc (both | + * | stages, pre-existing infra) and the | + * | ww deref-field READ carries the | + * | #24-kin field(SB) leak, so run + | + * | byte-id cells are unavailable; the | + * | FILL bytes themselves verified | + * | byte-id by hand (= plain control) | asm + * g73_static_str | str-field static emit: the #73 | + * | fatal gave way to the #129 A.2 | + * | foldability loud — now fill0-class | + * | both-loud (twin texts) | err/err * * K_RUN rows build+run BOTH drivers (cs exit==cswant, ww exit==wwwant) * and assert cstage/wwstage asm byte-id. K_RUN_NOID asserts both run @@ -120,6 +142,9 @@ slurp_eq(const char *a, const char *b) #define K_CSERR_WWRUN 3 /* dual-cell bound row: cstage build must * FAIL with experr; wwstage build+run, * exit==wwwant — cite the blocking task */ +#define K_COMPILES 4 /* w6c AND w6c_ww must COMPILE (no run, no + * byte-id) — cite why the other cells are + * unavailable at the row */ struct row { const char *label; const char *src; int cswant; int wwwant; int kind; const char *experr; }; @@ -381,6 +406,155 @@ static const struct row rows[] = { " return 0;\n" "};\n", 0, 0, K_CSERR_WWRUN, "not assignable" }, /* cs: task #86 */ + /* ---- c2: #73 graduation — the F1 tripwire's containment + * replaced by the designed chase (close-by-construction). Each + * row was the loud "#73" fatal on cs at the c1 tip (ww ok/0); + * post-c2 the gates read the chased field type and every + * runnable arm lands 0/0 byte-id. Values exceed 255 / use the + * LAST element so a word0 tail can't pass by prefix-luck; + * noise() clobbers BX/CX on the read rows. */ + { "g73_idxstore", + "package main;\n" + "type sl = []int;\n" + "type sl2 = sl;\n" + "type box = struct { s: sl2, n: int };\n" + "export fn main() i32 = {\n" + " let a: [3]int = [700: int, 800: int, 900: int];\n" + " let xs: [2]box;\n" + " xs[0].s = a[0:3];\n" + " if (xs[0].s.len != 3) { return 1; };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL }, + { "g73_strfield", + "package main;\n" + "type s1t = str;\n" + "type s2t = s1t;\n" + "type box = struct { s: s2t, n: int };\n" + "fn noise(x: int) int = { return x * 7 + 3; };\n" + "export fn main() i32 = {\n" + " let xs: [2]box;\n" + " xs[1].s = \"hello\";\n" + " xs[1].n = 4;\n" + " let k = noise(9);\n" + " if (k != 66) { return 9; };\n" + " let w = xs[1].s;\n" + " if (w.len != 5) { return 1; };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL }, + { "g73_tagfield", + "package main;\n" + "type tu0 = (void | i64);\n" + "type tu = tu0;\n" + "type box = struct { u: tu, n: int };\n" + "export fn main() i32 = {\n" + " let xs: [2]box;\n" + " xs[0].n = 5;\n" + " xs[0].u = 42i64;\n" + " if (xs[0].n != 5) { return 1; };\n" + " match (xs[0].u) {\n" + " case let v: i64 => { if (v != 42) { return 2; }; };\n" + " case void => { return 3; };\n" + " };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL }, + /* store rides the (F1-chased) single-dot via-ptr route so the + * indexed-elem READ gate fires in isolation. */ + { "g73_idxread", + "package main;\n" + "type sl = []int;\n" + "type sl2 = sl;\n" + "type box = struct { s: sl2, n: int };\n" + "fn noise(x: int) int = { return x * 7 + 3; };\n" + "export fn main() i32 = {\n" + " let a: [3]int = [700: int, 800: int, 900: int];\n" + " let xs: [2]box;\n" + " let p = &xs[1];\n" + " p.s = a[0:3];\n" + " p.n = 4;\n" + " let k = noise(9);\n" + " if (k != 66) { return 9; };\n" + " let w = xs[1].s;\n" + " if (w.len != 3) { return 1; };\n" + " if (w[2] != 900) { return 2; };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL }, + { "g73_ptrread", + "package main;\n" + "type sl = []int;\n" + "type sl2 = sl;\n" + "type inner = struct { s: sl2, n: int };\n" + "type outer = struct { pi: *inner };\n" + "fn noise(x: int) int = { return x * 7 + 3; };\n" + "export fn main() i32 = {\n" + " let a: [3]int = [700: int, 800: int, 900: int];\n" + " let i: inner;\n" + " i.s = a[0:3];\n" + " i.n = 1;\n" + " let v: outer;\n" + " v.pi = &i;\n" + " let k = noise(9);\n" + " if (k != 66) { return 9; };\n" + " let w = v.pi.s;\n" + " if (w.len != 3) { return 1; };\n" + " if (w[2] != 900) { return 2; };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL }, + { "g73_tupread", + "package main;\n" + "type sl = []int;\n" + "type sl2 = sl;\n" + "export fn main() i32 = {\n" + " let a: [3]int = [700: int, 800: int, 900: int];\n" + " let t: (sl2, int) = (a[0:3], 5);\n" + " let s = t.0;\n" + " if (s.len != 3) { return 1; };\n" + " if (t.1 != 5) { return 2; };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL }, + { "g73_static_struct", + "package main;\n" + "type in0 = struct { a: i64, b: i64 };\n" + "type in1 = in0;\n" + "type box = struct { i: in1, n: i64 };\n" + "let g: box = box { i = in0 { a = 4, b = 9 }, n = 5 };\n" + "export fn main() i32 = {\n" + " if (g.n != 5) { return 1; };\n" + " if (g.i.b != 9) { return 2; };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL }, + /* COMPILE-only pin: bare /tmp programs never link malloc (both + * stages, pre-existing infra) and the ww deref-field READ + * carries the #24-kin field(SB) leak — run + byte-id cells are + * unavailable. The chased FILL bytes were verified byte-id by + * hand against the plain control (/tmp/b5/work). Pre-c2 the cs + * cell was the "#73" fatal — this row pins its absence. */ + { "g73_heapfill", + "package main;\n" + "type s1t = str;\n" + "type s2t = s1t;\n" + "type box = struct { s: s2t, n: int };\n" + "export fn main() i32 = {\n" + " let p = alloc(box { s = \"hello\", n = 5 })!;\n" + " if (p.n != 5) { return 1; };\n" + " if (p.s.len != 5) { return 2; };\n" + " return 0;\n" + "};\n", 0, 0, K_COMPILES, NULL }, + /* str-field static emit: the #73 fatal gave way to the + * pre-existing #129 A.2 foldability loud — both-loud twin texts + * now (the fill0 class), pinned so neither side regresses to a + * silent emit. */ + { "g73_static_str", + "package main;\n" + "type s1t = str;\n" + "type s2t = s1t;\n" + "type box = struct { s: s2t, n: int };\n" + "let g: box = box { s = \"hello\", n = 5: int };\n" + "export fn main() i32 = {\n" + " if (g.n != 5) { return 1; };\n" + " if (g.s.len != 5) { return 2; };\n" + " return 0;\n" + "};\n", 0, 0, K_BUILDERR, + "#129 A.2 scope" }, }; static int @@ -436,6 +610,30 @@ run_driver(const char *driver, const struct row *r, int i, int expect_err, return 0; } +/* asm_compiles — K_COMPILES cell: the named stage compiler must emit + * asm without a fatal. */ +static int +asm_compiles(const char *bin, const char *cc, const struct row *r, int i) +{ + char src[96], outs[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/ab5_cmp_%d_%d.ww", getpid(), i); + snprintf(outs, sizeof outs, "/tmp/ab5_cmp_%d_%d.s", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + snprintf(cmd, sizeof cmd, "%s/%s -o %s %s 2>/dev/null", + bin, cc, outs, src); + int rc = runwait(cmd); + if (rc != 0) + fprintf(stderr, "row[%s]: %s failed to compile\n", + r->label, cc); + unlink(src); unlink(outs); + return rc != 0; +} + static int asm_byte_identical(const char *bin, const struct row *r, int i) { @@ -492,6 +690,11 @@ main(void) for (int i = 0; i < n; i++) { total++; + if (rows[i].kind == K_COMPILES) { + if (asm_compiles(bin, "w6c", &rows[i], i) != 0) + fail++; + continue; + } int cs_err = rows[i].kind == K_BUILDERR || rows[i].kind == K_CSERR_WWRUN; if (run_driver(cdrv, &rows[i], i, cs_err, @@ -500,6 +703,11 @@ main(void) if (access(wdrv, X_OK) == 0) { for (int i = 0; i < n; i++) { total++; + if (rows[i].kind == K_COMPILES) { + if (asm_compiles(bin, "w6c_ww", + &rows[i], i) != 0) fail++; + continue; + } int ww_err = rows[i].kind == K_BUILDERR; if (run_driver(wdrv, &rows[i], i, ww_err, rows[i].wwwant) != 0) fail++;