cgen: B6-c1 assign/reassign family single peels fold into type_chase_named — 9 lines, cs-only

The exact B6-c1 set (rob b6 spec §2): cgexpr :6359 (tagged-local plain
reassign lu), :6403/:6405 (deref-target assign pu/vt), :6460/:6462
(deref compound-assign pu/vt), :6518 (str/slice/struct reassign lu) +
cgstmt :11696 (nomem null-propagate r), :12047 (assign base peel bu),
:13625 (destructure-reassign rhs ru — chased; the #64 citation above it
stays, the deferral is about the tuple-literal rhs ROUTE, not this
peel). Raw `->under` in cgen.c 58→49.

TRAIN INVARIANT: cs-only — zero selfhost/ or lib/ bytes move; w6c_ww/
ww_ww bit-identical to ken's 4cac1cb baselines (md5
b6bddc8eb5c3ed8e805e50371d4b7017 / 4e9ca8741f19e1f68219ff799a5e5a14).
cs movers bounded to exactly: kb6_streassign, kb5_wstore_a (the
named c1 family); the rest of the kb4/kb5/kb6/kna corpus + five
selfhost mains byte-NEUTRAL both stages; kw1_101 / fill2 / tuparg_c /
xampdef / amplen1 detectors unmoved. 989 lib ratchet: zero flips.

LIVE graduation: kb6_streassign (the :6518 lu single-peel missed
TY_STR at 2 alias levels, fell to the scalar default — `b = a` copied
the ptr WORD0 only, len/cap stale, cs silent exit 1; ww was the
runtime-correct full 3-word reference) → 0/0 byte-id. Designed
graduation: kb5_wstore_a (ken C1-CORR-2 seed — the ident-lhs N_ASSIGN
tagged gate :6359 is cgexpr INLINE, never reached B5's :2456 funnel)
→ wstore_a_2lvl re-pinned K_RUN_NOID→K_RUN in the b5 suite (84→85
checks). kb6_sreassign / kb6_dassign latent controls byte-NEUTRAL as
ken's structural bound predicts.

New 944_alias_cgen_b6_run row table (3 rows, 9 checks): streassign_2lvl
+ sreassign_2lvl/dassign_2lvl controls; Makefile wires
test_alias_cgen_b6_run into the unit list. All 944-family suites green;
sizelint 0.
This commit is contained in:
2026-06-06 03:48:54 +09:00
parent 4cac1cbb89
commit 1f14becdf3
4 changed files with 300 additions and 23 deletions

View File

@@ -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 \

View File

@@ -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) {

View File

@@ -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

View File

@@ -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 <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;
}
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;
}