diff --git a/Makefile b/Makefile index 4df5bac4..cb3af3b4 100644 --- a/Makefile +++ b/Makefile @@ -309,6 +309,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_alias_def_addr_run \ $(BIN)/test_alias_global_decl_run \ $(BIN)/test_alias_cgen_b5_run \ + $(BIN)/test_alias_cgen_b6_run \ $(BIN)/test_tuple_nary_destructure_run \ $(BIN)/test_rvalue_tuple_destructure_run \ $(BIN)/test_overcap_tuple_field_store_run \ @@ -1495,6 +1496,12 @@ $(BIN)/test_alias_cgen_b5_run: test/wcc/944_alias_cgen_b5_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_alias_cgen_b6_run: test/wcc/944_alias_cgen_b6_run.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_tuple_nary_destructure_run: test/wcc/945_tuple_nary_destructure_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 6cbde21f..6ebd322c 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -6356,7 +6356,7 @@ cgexpr(Cg *c, Node *n, Local *locals) if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN && n->lhs->type) { Type *lt = n->lhs->type; - Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt; + Type *lu = type_chase_named(lt); if (lu && lu->kind == TY_TAGGED) { int rhs_sret_call = n->rhs && n->rhs->kind == N_CALL @@ -6400,9 +6400,9 @@ cgexpr(Cg *c, Node *n, Local *locals) if (n->lhs && n->lhs->kind == N_UN && n->lhs->op == TK_STAR && n->op == TK_ASSIGN && !place_slit && !deref_agg) { Type *pt = n->lhs->lhs ? n->lhs->lhs->type : NULL; - Type *pu = (pt && pt->kind == TY_NAMED) ? pt->under : pt; + Type *pu = type_chase_named(pt); Type *vt = (pu && pu->kind == TY_PTR) ? pu->sub : NULL; - if (vt && vt->kind == TY_NAMED) vt = vt->under; + vt = type_chase_named(vt); /* `*p = v` for *f64 / *f32: cgexpr leaves the value in X0, * not AX. Spill X0 to the stack, evaluate the pointer * (clobbers AX/BX freely), then reload X0 and MOVSD/MOVSS @@ -6457,9 +6457,9 @@ cgexpr(Cg *c, Node *n, Local *locals) if (n->lhs && n->lhs->kind == N_UN && n->lhs->op == TK_STAR && n->op != TK_ASSIGN) { Type *pt = n->lhs->lhs ? n->lhs->lhs->type : NULL; - Type *pu = (pt && pt->kind == TY_NAMED) ? pt->under : pt; + Type *pu = type_chase_named(pt); Type *vt = (pu && pu->kind == TY_PTR) ? pu->sub : NULL; - if (vt && vt->kind == TY_NAMED) vt = vt->under; + vt = type_chase_named(vt); int sz = vt ? (int)vt->size : 8; int load_op = fldloadop(vt, sz); int store_op = fldstoreop(vt, sz); @@ -6515,7 +6515,7 @@ cgexpr(Cg *c, Node *n, Local *locals) if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN && n->lhs->type) { Type *lt = n->lhs->type; - Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt; + Type *lu = type_chase_named(lt); /* str/slice local/let: str IS []u8, so both store the full * 3-word {ptr,len,cap} from (AX,BX,CX) at off+0/+8/+16 * (local) or via &name(SB) → DI scratch (global — CX holds @@ -11693,7 +11693,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins2(c, A_CMPQ, aimm(0), areg(D_AX)); ins1(c, A_JNE, abranch(ok)); Type *r = cg_ret_type; - if (r && r->kind == TY_NAMED) r = r->under; + r = type_chase_named(r); int nidx = cg_tag_for_variant(r, ty_nomem); if (nidx < 0) nidx = 1; ins2(c, A_MOVQ, aimm(nidx), areg(D_AX)); @@ -12043,8 +12043,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) Node *base = n->rhs->lhs; Node *idx = n->rhs->rhs; Type *bt = base ? base->type : NULL; - Type *bu = (bt && bt->kind == TY_NAMED) - ? bt->under : bt; + Type *bu = type_chase_named(bt); if (base && base->kind == N_IDENT && bu && bu->kind == TY_ARRAY) { int esz = (bu->sub) @@ -13622,7 +13621,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) * (silent skew) — the #57 decl wire stops at return/let. */ cgexpr(c, n->rhs, *locals); Type *rt = n->rhs ? n->rhs->type : NULL; - Type *ru = (rt && rt->kind == TY_NAMED) ? rt->under : rt; + Type *ru = type_chase_named(rt); Tparam *tp0 = (ru && ru->kind == TY_TUPLE) ? ru->params : NULL; int mf32; if (sret_recv > 0) { diff --git a/test/wcc/944_alias_cgen_b5_run.c b/test/wcc/944_alias_cgen_b5_run.c index d259cec9..15d6d3bd 100644 --- a/test/wcc/944_alias_cgen_b5_run.c +++ b/test/wcc/944_alias_cgen_b5_run.c @@ -26,12 +26,11 @@ * wstore_2lvl/_1lvl | kb5_wstore/wstore1: let-route | * | controls (du arrives chased | * | upstream — ken FLAG-1), NEUTRAL | 0/0 - * wstore_a_bound_b6 | kb5_wstore_a: ASSIGN-route re-store | - * | — both-correct divergent; the | - * | ident-lhs N_ASSIGN gates in the | - * | cgexpr INLINE set (B6), NOT the | - * | :2456 funnel (C1-CORR-2, corrects | - * | rob R2) — byte-id waived until B6 | 0/0 + * wstore_a_2lvl | kb5_wstore_a: ASSIGN-route re-store | + * | — was both-correct divergent; the | + * | ident-lhs N_ASSIGN gate (:6359) is | + * | cgexpr INLINE, NOT the :2456 funnel | + * | (C1-CORR-2) — GRADUATED at B6-c1 | 0/0 * succ_2lvl | kb5_succ: `?`-success remap over | * | 2-lvl alias result — was divergent | * | at RETURN positions only (C1-CORR-1)| @@ -269,12 +268,11 @@ static const struct row rows[] = { " };\n" " return 0;\n" "};\n", 0, 0, K_RUN, NULL }, - /* C1-CORR-2 (ken c1-boundary addendum): the assign-route - * divergence does NOT close in this train — the ident-lhs - * N_ASSIGN tagged store gates in the cgexpr INLINE set (B6), - * never reaching the :2456 funnel. Both stages runtime-correct; - * byte-id rides B6. */ - { "wstore_a_bound_b6", + /* C1-CORR-2 → GRADUATED at B6-c1: the ident-lhs N_ASSIGN tagged + * store gate (:6359) was the cgexpr INLINE peel B5 could not + * reach; the B6-c1 chase closed it (pins-follow-the-layer, ken + * B6 oracle C1 seed). */ + { "wstore_a_2lvl", "package main;\n" "type u0 = (void | i64);\n" "type u = u0;\n" @@ -286,7 +284,7 @@ static const struct row rows[] = { " case void => { return 2; };\n" " };\n" " return 0;\n" - "};\n", 0, 0, K_RUN_NOID, NULL }, /* byte-id: B6 */ + "};\n", 0, 0, K_RUN, NULL }, /* graduated: B6-c1 */ /* C1-CORR-1 → GRADUATED at c3: the residual divergence was * exactly three paired return-position tag syntheses — the * cgreturn return-route family; the c3 rt chase closed it diff --git a/test/wcc/944_alias_cgen_b6_run.c b/test/wcc/944_alias_cgen_b6_run.c new file mode 100644 index 00000000..95b87d59 --- /dev/null +++ b/test/wcc/944_alias_cgen_b6_run.c @@ -0,0 +1,273 @@ +/* + * 944_alias_cgen_b6_run — #5 alias arc F2b (B6 train): the cgen.c + * cgexpr/cgstmt INLINE single-peel sweep through type_chase_named, by + * consumer family — c1 assign/reassign (9 lines), c2 call-arg (8), + * c3 addr-of/field-walk/index spine (14), c4 cast/is/try (10), + * c5 reads/len/globals (8). All cs-only: every graduation is a + * DIVERGE→byte-id flip (cs aligns UP to ww's already-chasing asm); + * zero selfhost/lib bytes move across the train, so the _ww binaries + * stay bit-identical (the crossing detector). + * + * Row sources are ken's B6 first-position oracle probes + * (/tmp/ken_b5/src/kb6_*, .ai/ken-b6-oracle.md, matrix at 4cac1cb). + * + * row | shape (oracle row) | cs/ww + * --------------------+-------------------------------------+------ + * ---- c1 (assign/reassign: :6359 :6403/:6405 :6460/:6462 ------- + * :6518 + cgstmt :11696 :12047 :13625) ------------------- + * streassign_2lvl | kb6_streassign: 2-lvl alias str | + * | reassign `b = a` — cs copied WORD0 | + * | (ptr) ONLY, len/cap stale (silent | + * | exit 1); the :6518 lu gate fell to | + * | the scalar default. LIVE graduation | 0/0 + * wstore_a (B5 file) | kb5_wstore_a graduates K_RUN_NOID → | + * | K_RUN in 944_alias_cgen_b5_run.c — | + * | the ident-lhs N_ASSIGN tagged gate | + * | (:6359) was the divergence (ken | + * | C1-CORR-2, he owns the flip) | 0/0 + * sreassign_2lvl | kb6_sreassign: whole-struct alias | + * | reassign — latent control | 0/0 + * dassign_2lvl | kb6_dassign: (*p).f= over alias ptr | + * | — latent control | 0/0 + * + * K_RUN rows build+run BOTH drivers (cs exit==cswant, ww exit==wwwant) + * and assert cstage/wwstage asm byte-id. K_BUILDERR rows must FAIL + * with experr on BOTH drivers; experr_ww overrides the ww-side text + * when the two stages loud at DIFFERENT sites (per-stage texts). + * NNN<950, self-contained /tmp sources, no imports (944 precedent). + */ +#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; +} + +static int +slurp_eq(const char *a, const char *b) +{ + FILE *fa = fopen(a, "rb"); + FILE *fb = fopen(b, "rb"); + if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } + int rc = 0; + for (;;) { + int ca = fgetc(fa), cb = fgetc(fb); + if (ca != cb) { rc = -1; break; } + if (ca == EOF) break; + } + fclose(fa); fclose(fb); + return rc; +} + +#define K_RUN 0 /* build+run BOTH, exits==wants, + byte-id */ +#define K_BUILDERR 1 /* build FAILS with experr on BOTH drivers; + * experr_ww overrides the ww-side text */ + +struct row { const char *label; const char *src; + int cswant; int wwwant; int kind; + const char *experr; const char *experr_ww; }; + +/* errlog_has — a builderr cell must fail WITH its diagnostic; any other + * failure (parse error, crash, hang-kill) is a vacuous reject (940 + * precedent). */ +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 const struct row rows[] = { + /* ---- c1: assign/reassign family */ + /* LIVE c1 graduation: the :6518 reassign gate single-peeled the + * 2-level alias str lhs, missed the TY_STR kind test, and fell + * to the scalar default — ONE MOVQ stored the ptr word, len/cap + * kept the dead value's header (cs silent exit 1, ww correct: + * full 3-word {ptr,len,cap} store, Hare value semantics). */ + { "streassign_2lvl", + "package main;\n" + "type ms0 = str;\n" + "type ms = ms0;\n" + "export fn main() i32 = {\n" + " let a: ms = \"hello\";\n" + " let b: ms = \"x\";\n" + " b = a;\n" + " if (len(b) != 5) { return 1; };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL, NULL }, + { "sreassign_2lvl", + "package main;\n" + "type st0 = struct { a: i64, b: i64, c: i64 };\n" + "type st = st0;\n" + "export fn main() i32 = {\n" + " let x: st; x.a = 1; x.b = 2; x.c = 3;\n" + " let y: st; y.a = 0; y.b = 0; y.c = 0;\n" + " y = x;\n" + " if (y.c != 3 || y.b != 2) { return 1; };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL, NULL }, + { "dassign_2lvl", + "package main;\n" + "type st0 = struct { a: i64, b: i64 };\n" + "type st = st0;\n" + "export fn main() i32 = {\n" + " let x: st; x.a = 1; x.b = 2;\n" + " let p: *st = &x;\n" + " (*p).b = 9;\n" + " if (x.b != 9) { return 1; };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL, NULL }, +}; + +static int +run_driver(const char *driver, const struct row *r, int i, int expect_err, + int want, const char *experr) +{ + char src[96], tmpdir[96], errf[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/ab6_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/ab6_%d_d_%d", getpid(), i); + snprintf(errf, sizeof errf, "/tmp/ab6_%d_e_%d", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, + "cd %s && timeout 20 %s build %s >/dev/null 2>%s", + tmpdir, driver, src, errf); + int brc = runwait(cmd); + if (expect_err) { + int ok = (brc != 0) + && (experr == NULL || errlog_has(errf, experr)); + if (!ok) + fprintf(stderr, "row[%s]: %s expected loud builderr " + "\"%s\" (brc=%d)\n", r->label, driver, + experr ? experr : "", brc); + unlink(src); unlink(errf); rmdir(tmpdir); + return ok ? 0 : 1; + } + if (brc != 0) { + fprintf(stderr, "row[%s]: build via %s failed\n", + r->label, driver); + unlink(src); unlink(errf); rmdir(tmpdir); + return -1; + } + + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char outbin[256]; + 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(outbin); unlink(errf); rmdir(tmpdir); + if (got != want) { + fprintf(stderr, "row[%s]: %s exit %d, want %d\n", + r->label, driver, got, want); + return 1; + } + return 0; +} + +static int +asm_byte_identical(const char *bin, const struct row *r, int i) +{ + char src[96], cs[96], ws[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/ab6_asm_%d_%d.ww", getpid(), i); + snprintf(cs, sizeof cs, "/tmp/ab6_asm_%d_%d_c.s", getpid(), i); + snprintf(ws, sizeof ws, "/tmp/ab6_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; + } + int rc = slurp_eq(cs, ws); + 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], wdrv[2120]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + + int n = (int)(sizeof rows / sizeof rows[0]); + int total = 0, fail = 0; + + for (int i = 0; i < n; i++) { + total++; + int cs_err = rows[i].kind == K_BUILDERR; + if (run_driver(cdrv, &rows[i], i, cs_err, + rows[i].cswant, rows[i].experr) != 0) fail++; + } + if (access(wdrv, X_OK) == 0) { + for (int i = 0; i < n; i++) { + total++; + int ww_err = rows[i].kind == K_BUILDERR; + const char *we = rows[i].experr_ww + ? rows[i].experr_ww : rows[i].experr; + if (run_driver(wdrv, &rows[i], i, ww_err, + rows[i].wwwant, we) != 0) fail++; + } + for (int i = 0; i < n; i++) { + if (rows[i].kind != K_RUN) + continue; + total++; + if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; + } + } + + if (fail) { + fprintf(stderr, "alias_cgen_b6: %d/%d checks failed\n", + fail, total); + return 1; + } + printf("alias_cgen_b6: %d/%d ok\n", total, total); + return 0; +}