From 9bd0d8bc8127e19965148c03e15ac60195501c63 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 5 Jun 2026 11:52:08 +0900 Subject: [PATCH] wcc: #5 F1 promote type_chase_named + transitive-peel acceptance align-cs-up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promote type_chase_named from cmd/w6c/cgen.c (static) to cmd/wcc/type.c (exported via ww.h) and re-route every checker single-NAMED-peel through it: check.c's ~28 inline ternaries + 3 ad-hoc loops, type.c's assignability/untyped/borrow/opaque peels. type_eq's nominal identity (check.c:114) and the resolve machinery guards stay untouched. The re-route IS the acceptance align-up — cstage loud-rejected alias shapes wwstage accepts AND runs Hare-right (F0 census, harec dealiases at every consumer): - #54 binop alias-vs-base: unify_arith gains the harec type_promote arm (ref/harec/src/check.c:1083-1105) — one-sided alias + dealias-equal promotes to the ALIAS side; alias-vs-alias stays rejected. - alias-cond family: if/for/&&/||/! chase-then-bool (harec check.c:2141/2515/3229/3572). assert stays loud (F0 2a symmetric). - #70 field access through 2-level alias chains (ken c3_chain3). - assignability through the full chain (harec types.c:989-996 dealias-both): return/init/assign legs, F0 8b idx/slice walls. - alias-of-ptr deref (harec types.c:19-22 type_dereference). The widening reaches cgen arms whose own single peels then misbehaved — both classes are closed IN THIS COMMIT so no intermediate state ships a loud->silent flip (bisect no-silent invariant): - index family: the 8b acceptance hit ptr-load base + esz=1 (SEGV / prefix-luck) — idx_eff + the N_INDEX read / index-write / &base[i] / N_SLICE (expr + call-arg) / N_FORRANGE / aggarg_srcaddr-index / castsrcprim-dot / match-field base classifies chase. - kind classifiers (ken #61-root-verify v3 find): a 2-level f64 alias param reached cg_isfloat's single peel and classified INT — silent wrong-register-class. cg_isfloat / type_isf32 / fld_isfloat / type_isstr / type_isslice chase. ken's v3 row is pinned with credit. Bootstrap asm is byte-identical before/after (w6c on every main.combined.ww cmp-equal vs a pristine 738d7f4 scratch; 989 lib_byteid pins unchanged): 2-level chains were checker-walled pre-F1, so no previously-accepted program changes shape. test: 944_alias_accept_run (20 rows): acceptance graduations pinned runtime + byte-id both stages; idx/slice/range/slice-param rows cs-only until the wwstage #60 esz family lands (F2 batch 1); cs-only harec-parity loud pin for alias-vs-alias binop; assert stays-loud row; ken-v3 + f64/str/slice kind rows. Mutation-checked at 738d7f4. reviewer-F1 fold — the same invariant, outside the F0 census: this commit ADMITS 2+-level alias slice/str/aggregate types in STRUCT FIELD position, therefore this commit must keep them correct-or-loud. The cgen FIELD-TYPE gates single-peeled, so the slice/str 3-word arms fell to word0-only scalar tails — accept-and-corrupt, ww correct, every shape loud at the pristine base. Chased (probe-proven, byte-id graduations): single-dot field store + via-ptr twin, struct-lit fill, chained store-walk LEAF (the #71 walk chases hops, not leaves), chained-ptr-field store, single-dot / via-ptr / chained-walk field reads (clobber-probed — word0 reads luck-passed on stale BX/CX). The six unprobed sibling gates (indexed-elem store/read, ptr-chain read, heap fill, tuple-elem read, static emit) hard-error via fld_alias_tripwire on a 2+-level alias over an aggregate base, citing task #73 (the family's scheduled chase); <=1-level and scalar bases never fire — zero behavior change for any pre-#5-legal program (five selfhost mains cmp-identical vs the pristine 738d7f4 scratch). test: 944 +11 rows (9 K_RUN byte-id, wholeread K_RUN_CS [#60 ww half + pre-existing 1-level read-spine divergence], #73 tripwire K_BUILDERR_CS pin); 1-level controls per gate in /tmp/revF1. --- Makefile | 7 + cmd/w6c/cgen.c | 165 +++++--- cmd/wcc/check.c | 148 ++++--- cmd/wcc/type.c | 63 ++- cmd/wcc/ww.h | 1 + test/wcc/944_alias_accept_run.c | 711 ++++++++++++++++++++++++++++++++ 6 files changed, 955 insertions(+), 140 deletions(-) create mode 100644 test/wcc/944_alias_accept_run.c diff --git a/Makefile b/Makefile index edeee030..01bf67c3 100644 --- a/Makefile +++ b/Makefile @@ -303,6 +303,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_subslice_ptresz_run \ $(BIN)/test_deref_slice_store_run \ $(BIN)/test_alias_decl_order_size_run \ + $(BIN)/test_alias_accept_run \ $(BIN)/test_tuple_nary_destructure_run \ $(BIN)/test_rvalue_tuple_destructure_run \ $(BIN)/test_overcap_tuple_field_store_run \ @@ -1453,6 +1454,12 @@ $(BIN)/test_alias_decl_order_size_run: test/wcc/944_alias_decl_order_size_run.c $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_alias_accept_run: test/wcc/944_alias_accept_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 ce05fabb..537d6752 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -176,25 +176,18 @@ static int nyields; static int cg_isfloat(Type *t) { - if (t == NULL) return 0; - if (t->kind == TY_NAMED) t = t->under; + /* Transitive chase (#5 F1): the acceptance align opened 2-level + * float/str/slice aliases to these kind classifiers — a single + * peel mis-classed them scalar/INT (ken v3: f64-alias param read + * the wrong register class once the checker admitted it). */ + t = type_chase_named(t); if (t == NULL) return 0; return t->kind == TY_F32 || t->kind == TY_F64 || t->kind == TY_UNTYPED_FLOAT; } -/* type_chase_named — walk the TY_NAMED.under chain to the deepest non- - * named type. Chain-of-aliases (#22): `type b = a; type a = struct;` - * stacks two TY_NAMED layers — a single peel leaves `t` pointing at - * the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT, - * TY_SLICE, TY_TAGGED, TY_PTR) miss and the codegen silently falls - * through to a scalar shape. Mirror of wwstage's structlookupchain. */ -static Type * -type_chase_named(Type *t) -{ - while (t && t->kind == TY_NAMED) t = t->under; - return t; -} +/* type_chase_named lives in cmd/wcc/type.c since the #5 alias arc — the + * checker's acceptance sites share the transitive peel with cgen. */ /* cg_sret_retsize — sret classifier; defined after the tuple register- * return helpers (tuple_rseq / tuple_eslot / fld_isfloat) it consults @@ -211,8 +204,7 @@ node_isfloat(Node *n) static int type_isstr(Type *t) { - if (t == NULL) return 0; - if (t->kind == TY_NAMED) t = t->under; + t = type_chase_named(t); if (t == NULL) return 0; return t->kind == TY_STR || t->kind == TY_UNTYPED_STR; } @@ -226,8 +218,7 @@ node_isstr(Node *n) static int type_isslice(Type *t) { - if (t == NULL) return 0; - if (t->kind == TY_NAMED) t = t->under; + t = type_chase_named(t); return t && t->kind == TY_SLICE; } @@ -317,8 +308,7 @@ tuple_eslot(Type *t) static int type_isf32(Type *t) { - if (t == NULL) return 0; - if (t->kind == TY_NAMED) t = t->under; + t = type_chase_named(t); return t && t->kind == TY_F32; } @@ -350,8 +340,7 @@ static int fld_isfloat(Type *t, int *isf32) { if (isf32) *isf32 = 0; - if (t == NULL) return 0; - if (t->kind == TY_NAMED) t = t->under; + t = type_chase_named(t); if (t == NULL) return 0; if (t->kind == TY_F64) return 1; if (t->kind == TY_UNTYPED_FLOAT) return 1; @@ -444,6 +433,32 @@ 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 @@ -502,11 +517,9 @@ castsrcprim(Node *n, int *sz, int *unsignd) /* Real struct field only. .len / .cap / .ptr on str / * slice / array are pseudo-fields wwstage doesn't see. */ Type *bt = n->lhs ? n->lhs->type : NULL; - Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt; - if (bu && bu->kind == TY_PTR) { - Type *st = bu->sub; - bu = (st && st->kind == TY_NAMED) ? st->under : st; - } + Type *bu = type_chase_named(bt); + if (bu && bu->kind == TY_PTR) + bu = type_chase_named(bu->sub); if (bu && bu->kind == TY_STRUCT) { t = n->type; } @@ -1250,9 +1263,12 @@ static Type * idx_eff(Type *t) { if (t == NULL) return NULL; - Type *u = type_unwrap(t); + /* Transitive chase (#5 alias arc): the checker now admits index + * bases through 2-level alias chains (F0 8b); a single unwrap left + * eff TY_NAMED → sub NULL → esz=1 byte loads off the chain. */ + Type *u = type_chase_named(t); if (u && u->kind == TY_PTR && u->sub) { - Type *p = type_unwrap(u->sub); + Type *p = type_chase_named(u->sub); if (p && p->kind == TY_ARRAY) return p; } return u; @@ -2100,7 +2116,7 @@ aggarg_srcaddr(Cg *c, Node *src, int dst, Local *locals) Node *base = src->lhs; Node *idx = src->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) ? (int)bu->sub->size : 1; @@ -3032,7 +3048,11 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit, break; } } - Type *fu = (ft && ft->kind == TY_NAMED) ? ft->under : ft; + /* Transitive chase (#5-F1 fold): a 2-level-alias slice/str + * field fell past every kind arm to the word0-only scalar + * tail — silent, reachable only via this commit's + * acceptance (reviewer-F1 s1 probe). */ + Type *fu = type_chase_named(ft); if (fu && fu->kind == TY_TAGGED) { /* Tagged store: reload BX first (if non-BP) so the * widener sees a valid base reg. The widener itself @@ -4132,8 +4152,7 @@ cgexpr(Cg *c, Node *n, Local *locals) Node *base = opnd->lhs; Node *idx = opnd->rhs; Type *bt = base ? base->type : NULL; - Type *bu = (bt && bt->kind == TY_NAMED) - ? bt->under : bt; + Type *bu = type_chase_named(bt); Type *eff = idx_eff(bt); int esz = (eff && eff->sub) ? (int)eff->sub->size : 1; @@ -4703,8 +4722,11 @@ cgexpr(Cg *c, Node *n, Local *locals) * work cleanly. * - else: struct local, BP-relative. */ Type *ft = f->type; - Type *fu = (ft && ft->kind == TY_NAMED) - ? ft->under : ft; + /* Transitive chase (#5-F1 fold): 2-level alias + * slice/str field skipped the 3-word arm — ptr + * word stored, len/cap dropped (reviewer-F1 + * ix2/s2/s3 probes; c1-acceptance-reached). */ + Type *fu = type_chase_named(ft); /* Tagged-union field — full slot rewrite via the * shared widener so every rhs shape (whole-tagged * ident or expr with tag-remap, concrete-variant @@ -4749,8 +4771,7 @@ cgexpr(Cg *c, Node *n, Local *locals) int is_global = (boff == 0 && !via_ptr && let_islet(base->str)); int foff = (int)f->offset; - Type *str_fu = (f->type && f->type->kind == TY_NAMED) - ? f->type->under : f->type; + Type *str_fu = type_chase_named(f->type); /* str/slice field: str IS []u8, so both store the full * 3-word {ptr,len,cap} that rhs cgexpr leaves in * (AX,BX,CX) at field+0/+8/+16. Address scratch must @@ -5025,6 +5046,8 @@ 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; int fsz = (int)(ft ? ft->size : 8); @@ -5272,8 +5295,11 @@ cgexpr(Cg *c, Node *n, Local *locals) { f = fl; break; } if (f != NULL) { Type *ft = f->type; - Type *fu = (ft && ft->kind == TY_NAMED) - ? ft->under : ft; + /* Transitive chase (#5-F1 fold): + * 2-level alias slice/str field + * skipped the 3-word arm + * (reviewer-F1 p1 probe). */ + Type *fu = type_chase_named(ft); int fsz = (int)(ft ? ft->size : 8); int store_op = fldstoreop(ft, fsz); int foff = (int)f->offset; @@ -5576,9 +5602,12 @@ cgexpr(Cg *c, Node *n, Local *locals) } break; } - Type *fu = (leaf_type - && leaf_type->kind == TY_NAMED) - ? leaf_type->under : leaf_type; + /* Transitive chase (#5-F1 fold): the walk + * HOPS chase (#71) but the LEAF gate + * single-peeled — 2-level alias slice leaf + * fell to the scalar tail (reviewer-F1 s4 + * probe). */ + Type *fu = type_chase_named(leaf_type); int fsz = (int)(leaf_type ? leaf_type->size : 8); int store_op = fldstoreop(leaf_type, fsz); @@ -5881,7 +5910,7 @@ cgexpr(Cg *c, Node *n, Local *locals) if (n->lhs->kind == N_INDEX && n->lhs->lhs && !place_slit) { Node *base = n->lhs->lhs; Type *bt = base->type; - Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt; + Type *u = type_chase_named(bt); int is_arr = u && u->kind == TY_ARRAY; int is_sl = u && u->kind == TY_SLICE; int is_ptr = u && u->kind == TY_PTR; @@ -7805,6 +7834,8 @@ 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; if (fu && fu->kind == TY_STR) { @@ -8642,8 +8673,7 @@ cgexpr(Cg *c, Node *n, Local *locals) Node *lo = args[i]->rhs; Node *hi = args[i]->cond; Type *bt = base ? base->type : NULL; - Type *bu = (bt && bt->kind == TY_NAMED) ? - bt->under : bt; + Type *bu = type_chase_named(bt); /* esz from the type table for an N_IDENT base * (#76) or an N_DOT array/slice-field base * (#257: scale by the field's element width via @@ -9394,7 +9424,7 @@ cgexpr(Cg *c, Node *n, Local *locals) * value words at +8/+16) is contiguous within the struct, * so no spill is needed. */ Type *bt = s->lhs->type; - Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt; + Type *bu = type_chase_named(bt); Tfield *f = NULL; if (bu && bu->kind == TY_STRUCT) { for (Tfield *fl = bu->fields; fl; fl = fl->next) { @@ -10372,9 +10402,11 @@ cgexpr(Cg *c, Node *n, Local *locals) areg(D_AX)); goto dot_done; } - Type *fu = (leaf_type - && leaf_type->kind == TY_NAMED) - ? leaf_type->under : leaf_type; + /* Transitive chase (#5-F1 fold): + * read twin of the store-walk + * leaf gate (reviewer-F1 r2b + * clobber probe). */ + Type *fu = type_chase_named(leaf_type); /* tagged leaf (#38a): load the box into * the tagged cursor (AX=tag, DX=val0, * R8=val2 before CX=val1 — base_reg may @@ -10588,6 +10620,8 @@ 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; int op = fldloadop(tp->type, fsz); @@ -10742,8 +10776,12 @@ cgexpr(Cg *c, Node *n, Local *locals) * folds onto the slice arm (#1/Phase 3 collapse). * base_reg may be CX for globals; load .cap LAST so * the base survives the earlier reads. */ - Type *str_fu = (f->type && f->type->kind == TY_NAMED) - ? f->type->under : f->type; + /* Transitive chase (#5-F1 fold): the slice half + * single-peeled while the str half (type_isstr) + * recursed — a 2-level alias slice field read + * loaded ptr only, len/cap rode stale registers + * (reviewer-F1 r1b clobber probe). */ + Type *str_fu = type_chase_named(f->type); if ((str_fu && str_fu->kind == TY_SLICE) || type_isstr(f->type)) { ins2(c, A_MOVQ, @@ -10860,8 +10898,10 @@ cgexpr(Cg *c, Node *n, Local *locals) * pointer, so load .len LAST — the earlier loads * still index off the original base. str folds * onto the slice arm (#1/Phase 3 collapse). */ - Type *str_fu = (f->type && f->type->kind == TY_NAMED) - ? f->type->under : f->type; + /* Transitive chase (#5-F1 fold): via-ptr twin + * of the BP-base read gate (reviewer-F1 r3 + * clobber probe). */ + Type *str_fu = type_chase_named(f->type); if ((str_fu && str_fu->kind == TY_SLICE) || type_isstr(f->type)) { ins2(c, A_MOVQ, @@ -10913,6 +10953,8 @@ 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; /* tagged leaf (#38a): AX holds the *struct @@ -11070,6 +11112,8 @@ 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; /* #270-1a: an `[N]T`-typed field of an @@ -11232,7 +11276,7 @@ cgexpr(Cg *c, Node *n, Local *locals) && let_islet(n->lhs->str)) { bt = let_var_type(n->lhs->str); } - Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt; + Type *u = type_chase_named(bt); Type *eff = idx_eff(bt); int esz = 1; if (eff && eff->sub) esz = (int)eff->sub->size; @@ -11419,7 +11463,8 @@ cgexpr(Cg *c, Node *n, Local *locals) Node *lo = n->rhs; Node *hi = n->cond; Type *bt = base ? base->type : NULL; - Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt; + /* Transitive chase (#5 alias arc) — see the N_INDEX twin. */ + Type *bu = type_chase_named(bt); /* esz from the type table for an N_IDENT base (#76) or an * N_DOT array/slice-field base (#252: a struct-field slice * `s.obuf[lo:hi]` must scale by the field's element width, not @@ -13173,10 +13218,13 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) * or pulls each tuple field into its respective local. */ Node *slc = n->lhs; Type *st = slc ? slc->type : NULL; - Type *u = (st && st->kind == TY_NAMED) ? st->under : st; + /* Transitive chase (#5 alias arc): the checker admits range + * bases through 2-level alias chains (F0 8b); the single peel + * left u TY_NAMED → esz=1 + the non-array base arm (MOVQ of + * array words as a pointer — SEGV). */ + Type *u = type_chase_named(st); int esz = (u && u->sub) ? (int)u->sub->size : 1; - Type *etu = (u && u->sub && u->sub->kind == TY_NAMED) - ? u->sub->under : (u ? u->sub : NULL); + Type *etu = type_chase_named(u ? u->sub : NULL); int destruct = (n->list != NULL); /* allocate temp slots: _i (8B), _len (8B). #70: a NON-IDENT @@ -14191,6 +14239,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; if (fu && fu->kind == TY_STRUCT) { diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 749f3998..4885a0b9 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -344,7 +344,7 @@ addrfn_ptr_matches(Type *ptr, Type *fnty) { if (ptr == NULL || ptr->kind != TY_PTR) return 0; Type *ref = ptr->sub; - if (ref && ref->kind == TY_NAMED) ref = ref->under; + ref = type_chase_named(ref); if (ref == NULL || ref->kind != TY_FN) return 0; return type_eq(ref, fnty); } @@ -377,7 +377,7 @@ assignable_addrfn(Checker *c, Type *dst, Node *rhs) if (s == NULL || s->kind != SK_FN) return 0; Type *fnty = s->type; if (fnty == NULL || fnty->kind != TY_FN) return 0; - Type *du = (dst->kind == TY_NAMED) ? dst->under : dst; + Type *du = type_chase_named(dst); if (du == NULL) return 0; if (du->kind == TY_PTR) return addrfn_ptr_matches(du, fnty); if (du->kind == TY_TAGGED) { @@ -408,7 +408,7 @@ static int arrlit_init_fits(Checker *c, Type *dt, Node *rhs) { if (rhs == NULL || rhs->kind != N_ARRLIT) return 0; - Type *u = (dt && dt->kind == TY_NAMED) ? dt->under : dt; + Type *u = type_chase_named(dt); /* #25: a SLICE target is admitted via the same per-element coercion * as the array path — the #258 borrow demands an exact element * type_eq, which an arrlit's self-stamped [N] can't meet for @@ -417,7 +417,7 @@ arrlit_init_fits(Checker *c, Type *dt, Node *rhs) if (u == NULL || (u->kind != TY_ARRAY && u->kind != TY_SLICE)) return 0; Type *et = u->sub; - Type *eu = (et && et->kind == TY_NAMED) ? et->under : et; + Type *eu = type_chase_named(et); u64 count = 0; for (Node *e = rhs->list; e; e = e->next) { if (e->kind == N_FIELD && e->str @@ -705,8 +705,7 @@ resolve_type(Checker *c, Node *n) * the checker-says-8/cgen-does-16 split behind the * packed-tuple miscompile family (#32/#33/#48). */ if (tp->type) { - Type *eu = tp->type->kind == TY_NAMED - ? tp->type->under : tp->type; + Type *eu = type_chase_named(tp->type); if (eu && (eu->kind == TY_STR || eu->kind == TY_SLICE || eu->kind == TY_TAGGED)) @@ -753,8 +752,7 @@ resolve_type(Checker *c, Node *n) * (possibly NAMED) inner tagged union into the * enclosing union — matches Hare's parse-time * unwrap flag on each tagged_type entry. */ - Type *vu = spread && vt && vt->kind == TY_NAMED - ? vt->under : vt; + Type *vu = spread ? type_chase_named(vt) : vt; if (vu && vu->kind == TY_TAGGED && (spread || vt->kind == TY_TAGGED)) { for (Tparam *src = vu->params; src; src = src->next) { @@ -885,7 +883,7 @@ resolve_type(Checker *c, Node *n) /* embed (anonymous struct or bare-name): the inner type * must be a struct; its fields are promoted to the outer * scope with offsets shifted by the embed base. */ - Type *inner = (ft && ft->kind == TY_NAMED) ? ft->under : ft; + Type *inner = type_chase_named(ft); if (inner == NULL || inner->kind != TY_STRUCT) { err(c, f->pos, "embedded type must be a struct"); off += ft ? ft->size : 0; @@ -982,6 +980,27 @@ unify_arith(Checker *c, Pos p, Type *a, Type *b) if (type_isuntyped(a) && type_assignable(b, a)) return b; if (type_isuntyped(b) && type_assignable(a, b)) return a; if (type_eq(a, b)) return a; + /* One-sided alias vs its (transitive) base promotes to the ALIAS + * side; alias vs DIFFERENT alias stays rejected even when the + * bases agree. Mirrors harec type_promote (ref/harec/src/check.c: + * 1083-1105: ALIAS+ALIAS → NULL, then dealias-equal → the alias + * operand). wwstage accepts these shapes and runs them on the + * chased width (F0 m1_binop/m1_binop2, ww runtime-correct). + * + * ERROR axis guard: `type invalid = !i32` must NOT promote against + * a plain i32 — type_eq ignores iserror (primitives compare by + * kind), but harec interns flags into DISTINCT types, so flagged- + * vs-unflagged never reaches type_promote's dealias-equal arm. The + * #246 loud (strconv.invalid != i32, pinned by 949_errtype_compare + * on BOTH stages) rides this reject. */ + { + Type *da = type_chase_named(a); + Type *db = type_chase_named(b); + if (!(a->kind == TY_NAMED && b->kind == TY_NAMED) && + da && db && da->iserror == db->iserror && + type_eq(da, db)) + return (a->kind == TY_NAMED) ? a : b; + } return err(c, p, "operands have differing types %s and %s", type_name(c->a, a), type_name(c->a, b)); } @@ -1017,9 +1036,7 @@ coerce_floatlit(Node *n, Type *target) return; /* Chase the full alias chain (wwstage resolvealias does the same), so * a doubly-aliased f32 target stamps in both stages or neither. */ - Type *u = target; - while (u && u->kind == TY_NAMED) - u = u->under; + Type *u = type_chase_named(target); if (u == NULL || u->kind != TY_F32) return; if (n->kind == N_FLOATLIT && n->type == ty_untyped_float) @@ -1054,7 +1071,7 @@ static int reject_arrlit_borrow(Checker *c, Type *dst, Node *expr) { if (expr == NULL || expr->kind != N_ARRLIT) return 0; - Type *du = (dst && dst->kind == TY_NAMED) ? dst->under : dst; + Type *du = type_chase_named(dst); if (du == NULL || du->kind != TY_SLICE) return 0; err(c, expr->pos, "array literal cannot borrow as a slice here; " "bind it to a `let` first"); @@ -1066,9 +1083,8 @@ desugar_arrayslice(Checker *c, Type *dst, Node *expr) { if (dst == NULL || expr == NULL || expr->type == NULL) return; - Type *du = (dst->kind == TY_NAMED) ? dst->under : dst; - Type *su = (expr->type->kind == TY_NAMED) ? expr->type->under - : expr->type; + Type *du = type_chase_named(dst); + Type *su = type_chase_named(expr->type); if (du == NULL || su == NULL || du->kind != TY_SLICE || su->kind != TY_ARRAY) return; @@ -1119,12 +1135,18 @@ cbinop(Checker *c, Node *n) err(c, n->pos, "ordered comparison on non-numeric"); (void)unify_arith(c, n->pos, l, r); return ty_bool; - case TK_AND: case TK_OR: - if (!(l == ty_bool || l == ty_untyped_bool || l == ty_err)) + case TK_AND: case TK_OR: { + /* Bool-ness through the alias chain — harec dealiases at the + * logical-binop consumer (ref/harec/src/check.c:3229); ww + * accepts + runs the alias-bool operand (F0 m2_andor). */ + Type *lu = type_chase_named(l); + Type *ru = type_chase_named(r); + if (!(lu == ty_bool || lu == ty_untyped_bool || l == ty_err)) err(c, n->pos, "left of %s is not bool", tokname(n->op)); - if (!(r == ty_bool || r == ty_untyped_bool || r == ty_err)) + if (!(ru == ty_bool || ru == ty_untyped_bool || r == ty_err)) err(c, n->pos, "right of %s is not bool", tokname(n->op)); return ty_bool; + } default: return err(c, n->pos, "unsupported binary op %s", tokname(n->op)); } @@ -1139,20 +1161,28 @@ cunop(Checker *c, Node *n) if (!type_isnum(t)) return err(c, n->pos, "%s on non-numeric", tokname(n->op)); return t; - case TK_NOT: - if (!(t == ty_bool || t == ty_untyped_bool || t == ty_err)) + case TK_NOT: { + /* harec dealiases at the `!` consumer (ref/harec/src/check.c: + * 3572); ww accepts + runs the alias-bool operand (F0 m2_bang). */ + Type *u = type_chase_named(t); + if (!(u == ty_bool || u == ty_untyped_bool || t == ty_err)) err(c, n->pos, "! on non-bool"); return ty_bool; + } case TK_TILDE: if (!type_isint(t)) return err(c, n->pos, "~ on non-integer"); return t; - case TK_STAR: /* deref */ + case TK_STAR: { /* deref */ if (t == ty_err) return ty_err; - if (t->kind != TY_PTR) + /* harec type_dereference dealiases the operand (ref/harec/src/ + * types.c:19-22); wwstage unoptype TK_STAR resolvealias-chases. */ + Type *u = type_chase_named(t); + if (u == NULL || u->kind != TY_PTR) return err(c, n->pos, "cannot deref non-pointer %s", type_name(c->a, t)); - return t->sub; + return u->sub; + } case TK_AMP: /* address-of */ /* Slice/str pseudo-fields .len/.cap surface as i32 but live * in 8B-aligned slots in the header (ptr@0, len@8, cap@16). @@ -1165,9 +1195,9 @@ cunop(Checker *c, Node *n) (strcmp(n->lhs->str, "len") == 0 || strcmp(n->lhs->str, "cap") == 0)) { Type *bt = n->lhs->lhs->type; - Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt; + Type *bu = type_chase_named(bt); if (bu && bu->kind == TY_PTR) bu = bu->sub; - if (bu && bu->kind == TY_NAMED) bu = bu->under; + bu = type_chase_named(bu); if (bu && (bu->kind == TY_SLICE || bu->kind == TY_STR)) return type_ptr(c->a, ty_i64); } @@ -1269,8 +1299,7 @@ cexpr(Checker *c, Node *n) * (named) enum type itself, so bitwise ops between * members yield the same enum type via type_eq. */ if (ms && ms->kind == SK_TYPE && ms->type) { - Type *u = (ms->type->kind == TY_NAMED) - ? ms->type->under : ms->type; + Type *u = type_chase_named(ms->type); if (u && u->kind == TY_ENUM) { for (Tfield *f = u->fields; f; f = f->next) { if (f->name && n->str && @@ -1295,9 +1324,9 @@ cexpr(Checker *c, Node *n) } Type *base = cexpr(c, n->lhs); if (base == NULL || base == ty_err) return n->type = ty_err; - Type *u = (base->kind == TY_NAMED) ? base->under : base; + Type *u = type_chase_named(base); if (u && u->kind == TY_PTR) u = u->sub; - if (u && u->kind == TY_NAMED) u = u->under; + u = type_chase_named(u); /* Enum member access via a qualified base, e.g. `os.whence.CUR`. * The inner N_DOT resolved through SK_USE → the SK_TYPE sym's * named type. Fold the outer access to the member literal. */ @@ -1364,7 +1393,7 @@ cexpr(Checker *c, Node *n) if (idx != ty_err && !type_isint(idx)) err(c, n->pos, "index must be integer"); if (base == ty_err) return n->type = ty_err; - Type *u = (base->kind == TY_NAMED) ? base->under : base; + Type *u = type_chase_named(base); if (u && (u->kind == TY_SLICE || u->kind == TY_ARRAY)) { /* #108(b): indexing needs the element size; `[]opaque` * is a legal (sized) header but its element is unsized. @@ -1447,9 +1476,9 @@ cexpr(Checker *c, Node *n) n->list->kind == N_DOT) { Node *dot = n->list; Type *bt = cexpr(c, dot->lhs); - Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt; + Type *u = type_chase_named(bt); if (u && u->kind == TY_PTR) u = u->sub; - if (u && u->kind == TY_NAMED) u = u->under; + u = type_chase_named(u); u64 off = 0; int found = 0; if (u && u->kind == TY_STRUCT) { @@ -1544,8 +1573,7 @@ cexpr(Checker *c, Node *n) err(c, n->pos, "delete: operand must be an indexing or slicing expression"); else { Type *bt = d->lhs ? d->lhs->type : NULL; - while (bt && bt->kind == TY_NAMED) - bt = bt->under; + bt = type_chase_named(bt); /* harec check.c:2024 wording; a * fixed-size [N]T base and a str * base land here. */ @@ -1583,8 +1611,7 @@ cexpr(Checker *c, Node *n) err(c, n->pos, "insert: operand must be an indexing expression xs[i]"); else { Type *bt = d->lhs ? d->lhs->type : NULL; - while (bt && bt->kind == TY_NAMED) - bt = bt->under; + bt = type_chase_named(bt); /* harec check.c:807 wording; a * fixed-size [N]T base lands here. */ if (bt == NULL || bt->kind != TY_SLICE) @@ -1696,7 +1723,7 @@ cexpr(Checker *c, Node *n) (void)cexpr(c, a); return n->type = ty_err; } - Type *u = (ft->kind == TY_NAMED) ? ft->under : ft; + Type *u = type_chase_named(ft); if (u == NULL || u->kind != TY_FN) return n->type = err(c, n->pos, "calling non-function %s", type_name(c->a, ft)); @@ -1794,7 +1821,7 @@ cexpr(Checker *c, Node *n) } else { t = resolve_type(c, n->lhs); } - Type *u = (t && t->kind == TY_NAMED) ? t->under : t; + Type *u = type_chase_named(t); for (Node *f = n->list; f; f = f->next) { Type *vt = cexpr(c, f->lhs); if (u && u->kind == TY_STRUCT) { @@ -1837,7 +1864,7 @@ cexpr(Checker *c, Node *n) Type *base = cexpr(c, n->lhs); if (n->rhs) (void)cexpr(c, n->rhs); if (n->cond) (void)cexpr(c, n->cond); - Type *u = (base && base->kind == TY_NAMED) ? base->under : base; + Type *u = type_chase_named(base); if (u && u->kind == TY_ARRAY) return n->type = type_slice(c->a, u->sub); if (u && u->kind == TY_SLICE) @@ -1856,14 +1883,14 @@ cexpr(Checker *c, Node *n) } case N_RECV: { Type *t = cexpr(c, n->lhs); - Type *u = (t && t->kind == TY_NAMED) ? t->under : t; + Type *u = type_chase_named(t); if (u && u->kind == TY_CHAN) return n->type = u->sub; return n->type = err(c, n->pos, "<- expects chan, got %s", type_name(c->a, t)); } case N_MATCH: { Type *st = cexpr(c, n->lhs); - Type *u = (st && st->kind == TY_NAMED) ? st->under : st; + Type *u = type_chase_named(st); if (u == NULL || u->kind != TY_TAGGED) { return n->type = err(c, n->pos, "match on non-tagged-union %s", type_name(c->a, st)); @@ -1968,13 +1995,13 @@ cexpr(Checker *c, Node *n) * whether the expression returns bool (is) or the variant * itself (as). */ if (n->rhs) n->rhs->type = vt; - Type *u = (t && t->kind == TY_NAMED) ? t->under : t; + Type *u = type_chase_named(t); /* Enum ↔ integer cast: `enumval as intT` or `int as enumT`. * Reinterpret-only — the storage shape is already integer, so * cgen treats the cast as a no-op (the value lives in the same * register). The `is` form is rejected; enums aren't sums. */ Type *uu = u; - Type *vu = (vt && vt->kind == TY_NAMED) ? vt->under : vt; + Type *vu = type_chase_named(vt); int lhs_enum = uu && uu->kind == TY_ENUM; int rhs_enum = vu && vu->kind == TY_ENUM; if (n->kind == N_TYPEASSERT && (lhs_enum || rhs_enum) && @@ -2012,7 +2039,7 @@ cexpr(Checker *c, Node *n) } case N_TRYPROP: case N_TRYUNW: { Type *t = cexpr(c, n->lhs); - Type *u = (t && t->kind == TY_NAMED) ? t->under : t; + Type *u = type_chase_named(t); if (u == NULL || u->kind != TY_TAGGED) { return n->type = err(c, n->pos, "%s on non-tagged-union %s", @@ -2049,7 +2076,7 @@ cexpr(Checker *c, Node *n) } if (n->kind == N_TRYPROP && has_errors) { Type *r = c->ret; - Type *ru = (r && r->kind == TY_NAMED) ? r->under : r; + Type *ru = type_chase_named(r); if (ru == NULL || ru->kind != TY_TAGGED) { err(c, n->pos, "?: enclosing function must return a tagged " @@ -2097,8 +2124,7 @@ cexpr(Checker *c, Node *n) tp->type = cexpr(c, e); Type *ed = type_default(tp->type); if (ed && ed->align > al) al = ed->align; - Type *eu = (ed && ed->kind == TY_NAMED) - ? ed->under : ed; + Type *eu = type_chase_named(ed); if (eu && (eu->kind == TY_STR || eu->kind == TY_SLICE || eu->kind == TY_TAGGED)) sz += (eu->size + 7) & ~(u64)7; @@ -2154,9 +2180,7 @@ clet(Checker *c, Node *n) * init) can't infer its length — that is a loud error, never a * silent zero-length array (rule 7, #7). */ if (declared && declared->kind == TY_ARRAY && declared->alen == 0) { - Type *iu = initt - ? ((initt->kind == TY_NAMED) ? initt->under : initt) - : NULL; + Type *iu = type_chase_named(initt); if (iu && iu->kind == TY_ARRAY) declared = type_array(c->a, declared->sub, iu->alen); else @@ -2195,8 +2219,7 @@ clet(Checker *c, Node *n) } } if (has_arr_repeat && declared) { - Type *du = (declared->kind == TY_NAMED) ? declared->under - : declared; + Type *du = type_chase_named(declared); if (du && du->kind == TY_ARRAY) has_arr_repeat = 0; } @@ -2303,7 +2326,11 @@ cstmt(Checker *c, Node *n) } case N_IF: { Type *ct = cexpr(c, n->cond); - if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool) + /* harec dealiases at the if-cond consumer (ref/harec/src/ + * check.c:2141); ww accepts + runs alias-bool (F0 m2_if). + * assert stays UN-chased — both stages loud there (F0 2a). */ + Type *cu = type_chase_named(ct); + if (ct != ty_err && cu != ty_bool && cu != ty_untyped_bool) err(c, n->pos, "if condition must be bool, got %s", type_name(c->a, ct)); cstmt(c, n->body); @@ -2315,14 +2342,14 @@ cstmt(Checker *c, Node *n) c->cur = newscope(c->a, saved); c->loops++; Type *st = cexpr(c, n->lhs); - Type *u = (st && st->kind == TY_NAMED) ? st->under : st; + Type *u = type_chase_named(st); Type *elem = NULL; if (u && (u->kind == TY_SLICE || u->kind == TY_ARRAY)) elem = u->sub; else if (u && u->kind == TY_STR) elem = ty_u8; else err(c, n->pos, "for-range needs slice/array/str"); if (n->list != NULL) { /* tuple destructure: each name binds to a tuple field */ - Type *etu = (elem && elem->kind == TY_NAMED) ? elem->under : elem; + Type *etu = type_chase_named(elem); Tparam *tp = (etu && etu->kind == TY_TUPLE) ? etu->params : NULL; for (Node *nm = n->list; nm; nm = nm->next) { Type *ft = tp ? tp->type : ty_err; @@ -2355,7 +2382,10 @@ cstmt(Checker *c, Node *n) if (n->lhs) cstmt(c, n->lhs); /* init may be a let or expr */ if (n->cond) { Type *ct = cexpr(c, n->cond); - if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool) + /* harec dealiases at the loop-cond consumer (ref/ + * harec/src/check.c:2515); F0 m2_while. */ + Type *cu = type_chase_named(ct); + if (ct != ty_err && cu != ty_bool && cu != ty_untyped_bool) err(c, n->pos, "for condition must be bool, got %s", type_name(c->a, ct)); } @@ -2855,9 +2885,7 @@ check_file(Checker *c, Node *file) * the right `.len`. */ if (d->type && d->type->kind == TY_ARRAY && d->type->alen == 0) { - Type *iu = rt - ? ((rt->kind == TY_NAMED) ? rt->under : rt) - : NULL; + Type *iu = type_chase_named(rt); if (iu && iu->kind == TY_ARRAY) { d->type = type_array(c->a, d->type->sub, iu->alen); diff --git a/cmd/wcc/type.c b/cmd/wcc/type.c index 3a449bae..570d5c54 100644 --- a/cmd/wcc/type.c +++ b/cmd/wcc/type.c @@ -147,6 +147,22 @@ type_named(Arena *a, const char *name, Type *under) return t; } +/* type_chase_named — walk the TY_NAMED.under chain to the deepest non- + * named type. Chain-of-aliases (#22): `type b = a; type a = struct;` + * stacks two TY_NAMED layers — a single peel leaves `t` pointing at + * the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT, + * TY_SLICE, TY_TAGGED, TY_PTR) miss and the consumer silently falls + * through to a scalar shape. Mirror of wwstage's structlookupchain / + * resolvealias and harec's type_dealias (ref/harec/src/types.c:53). + * Promoted from cmd/w6c/cgen.c for the #5 alias arc so the checker's + * acceptance sites and the cgen classify sites share one peel. */ +Type * +type_chase_named(Type *t) +{ + while (t && t->kind == TY_NAMED) t = t->under; + return t; +} + int type_isint(Type *t) { @@ -297,11 +313,11 @@ type_assignable(Type *dst, Type *src) * src appears as a variant of dst (Hare-style subset). Tag * remap at the use site handles different variant indices. * Checked before the untyped branch so untyped literals flow - * through to a variant's typed slot. Unwraps a NAMED alias on - * either side so `type result = (T|E);` accepts variants too. */ + * through to a variant's typed slot. Chases the NAMED alias chain + * on either side so `type result = (T|E);` accepts variants too. */ { - Type *du = (dst->kind == TY_NAMED) ? dst->under : dst; - Type *su = (src->kind == TY_NAMED) ? src->under : src; + Type *du = type_chase_named(dst); + Type *su = type_chase_named(src); if (du && du->kind == TY_TAGGED && !(su && su->kind == TY_TAGGED)) { /* #199(α): direct variant only — no transitive drill into @@ -314,8 +330,7 @@ type_assignable(Type *dst, Type *src) * `is`/`as`'s non-recursive variant lookup. Callers compose * `let inner: Wrapper = sub; let r: parent = inner;`. */ for (Tparam *p = du->params; p; p = p->next) { - Type *pu = (p->type && p->type->kind == TY_NAMED) - ? p->type->under : p->type; + Type *pu = type_chase_named(p->type); if (pu && pu->kind == TY_TAGGED) { if (type_eq(p->type, src)) return 1; continue; @@ -335,8 +350,7 @@ type_assignable(Type *dst, Type *src) * the subset loop below would reject. SSoT with `is`/`as` * variant lookup (#198 family). */ for (Tparam *dp = du->params; dp; dp = dp->next) { - Type *pu = (dp->type && dp->type->kind == TY_NAMED) - ? dp->type->under : dp->type; + Type *pu = type_chase_named(dp->type); if (pu && pu->kind == TY_TAGGED && type_eq(dp->type, src)) return 1; } @@ -354,15 +368,15 @@ type_assignable(Type *dst, Type *src) /* Untyped → typed: only if the typed kind can hold the value. */ if (type_isuntyped(src)) { + Type *du = type_chase_named(dst); if (src->kind == TY_UNTYPED_INT && type_isnum(dst)) return 1; if (src->kind == TY_UNTYPED_FLOAT && type_isfloat(dst)) return 1; - if (src->kind == TY_UNTYPED_STR && (dst->kind == TY_STR || - (dst->kind == TY_NAMED && dst->under && dst->under->kind == TY_STR))) return 1; + if (src->kind == TY_UNTYPED_STR && du && du->kind == TY_STR) + return 1; if (src->kind == TY_UNTYPED_RUNE && (type_isint(dst) || dst->kind == TY_RUNE)) return 1; - if (src->kind == TY_UNTYPED_BOOL && (dst->kind == TY_BOOL || - (dst->kind == TY_NAMED && dst->under && dst->under->kind == TY_BOOL))) return 1; + if (src->kind == TY_UNTYPED_BOOL && du && du->kind == TY_BOOL) + return 1; if (src->kind == TY_UNTYPED_NIL) { - Type *du = (dst->kind == TY_NAMED) ? dst->under : dst; if (du && (du->kind == TY_PTR || du->kind == TY_SLICE || du->kind == TY_CHAN || du->kind == TY_FN)) return 1; @@ -370,11 +384,16 @@ type_assignable(Type *dst, Type *src) return 0; } - /* Named on either side: compare to the underlying. NAMED is a - * distinct type from its under; but assignment from under to - * named (and vice-versa) is allowed in this minimal checker. */ - if (dst->kind == TY_NAMED && type_eq(dst->under, src)) return 1; - if (src->kind == TY_NAMED && type_eq(dst, src->under)) return 1; + /* Named on either side: assignable through the FULL alias chain. + * harec type_is_assignable dealiases both sides whenever the dst + * is not tagged (ref/harec/src/types.c:993-996; the tagged dst + * returned above) — so base↔alias and alias↔alias-of-same-base + * all flow. The pre-#5 single peel rejected any 2-level chain + * (`return x*2` from a myint2 local louded at the int return). */ + if (dst->kind == TY_NAMED || src->kind == TY_NAMED) { + if (type_eq(type_chase_named(dst), type_chase_named(src))) + return 1; + } /* Tuple-to-tuple: element-wise assignable. */ if (dst->kind == TY_TUPLE && src->kind == TY_TUPLE) { @@ -397,8 +416,8 @@ type_assignable(Type *dst, Type *src) * {.ptr=&arr[0], .len=N, .cap=N} is byte-identical across stages. * Element types must match exactly — no element decay. */ { - Type *du = (dst->kind == TY_NAMED) ? dst->under : dst; - Type *su = (src->kind == TY_NAMED) ? src->under : src; + Type *du = type_chase_named(dst); + Type *su = type_chase_named(src); if (du && su && du->kind == TY_SLICE && su->kind == TY_ARRAY && su->alen != SIZE_UNDEFINED && type_eq(du->sub, su->sub)) return 1; @@ -421,8 +440,8 @@ type_assignable(Type *dst, Type *src) * Both rules fire only when the destination element is opaque, so * they are inert on the opaque-free selfhost corpus. */ { - Type *du = (dst->kind == TY_NAMED) ? dst->under : dst; - Type *su = (src->kind == TY_NAMED) ? src->under : src; + Type *du = type_chase_named(dst); + Type *su = type_chase_named(src); if (du && su && du->kind == su->kind && (du->kind == TY_PTR || du->kind == TY_SLICE) && du->sub && du->sub->kind == TY_OPAQUE) diff --git a/cmd/wcc/ww.h b/cmd/wcc/ww.h index cd2d3886..3928885e 100644 --- a/cmd/wcc/ww.h +++ b/cmd/wcc/ww.h @@ -472,6 +472,7 @@ Type *type_chan(Arena*, Type *sub); Type *type_named(Arena*, const char *name, Type *under); const char *type_name(Arena*, Type*); /* arena'd debug string */ int type_eq(Type *a, Type *b); /* structural equality */ +Type *type_chase_named(Type *t); /* transitive TY_NAMED peel */ int type_isint(Type *t); int type_isfloat(Type *t); int type_isnum(Type *t); diff --git a/test/wcc/944_alias_accept_run.c b/test/wcc/944_alias_accept_run.c new file mode 100644 index 00000000..91d0b41f --- /dev/null +++ b/test/wcc/944_alias_accept_run.c @@ -0,0 +1,711 @@ +/* + * 944_alias_accept_run — #5 alias arc F1: cstage acceptance ALIGN-UP + * through the transitive TY_NAMED chase (type_chase_named, promoted to + * cmd/wcc/type.c). harec dealiases at every promotion/cond/assign + * consumer (ref/harec/src/types.c:53 type_dealias; :989-996 + * type_is_assignable; src/check.c:1083-1105 type_promote; :2141 if, + * :2515 for, :3229 &&/||, :3572 !) — wwstage already accepted these + * shapes AND ran them Hare-right (F0 census /tmp/f0/livelist.md), so + * the cstage single-peel rejects were the lean side aligned UP. + * + * row | shape | want + * ---------------------+--------------------------------------+----- + * binop_alias_base | myint + int (F0 m1_binop) | 0 + * binop_2level | myint2 % int, myu / u32 (m1_binop2) | 0 + * ret_through_2level | fn(x: myint2) int = x * 2 | 0 + * assign_alias_alias | k1 → k2 (same base; harec dealias- | + * | both admits) | 0 + * binop_alias_vs_alias | k1 + k2 — cstage stays LOUD per | + * | harec type_promote ALIAS+ALIAS→NULL | err(cs) + * cond_if/for/bang/ | alias-of-bool at every cond consumer | + * andor/fnparam | (F0 m2_* family) | 0 + * assert_stays_loud | alias-bool assert — BOTH stages loud | + * | (F0 2a DISSOLVED-SYMMETRIC; the Hare | + * | widening is parked on #5) | err + * dot_2level | field access via a3=b3=c3=struct | + * | (task #70, ken c3_chain3) | 0 + * deref_alias_ptr | *p through pi2=pi=*int | 0 + * idx/slice/range_ | index/slice/for-range over 2-level | + * 2level, slice_arg | alias bases (F0 8b) — cs cgen index | + * | family chased (idx_eff + base | + * | classify); CS-ONLY rows: the wwstage | + * | half is the task-#60 esz family (F2 | + * | batch 1) — flip to K_RUN when it | + * | lands | 0(cs) + * float_alias_param* / | ken-v3 leg: 2-level f64/str/slice | + * str_alias_2level / | aliases at the cgen KIND classifiers | + * slice_alias_param | (slice leg cs-only, #60) | 0 + * slicefield_* / | reviewer-F1 leg: 2-level alias of | + * strfield_store_2lvl | slice/str as a STRUCT FIELD type — | + * | cgen field gates chased (stores, | + * | struct-lit, walk leaves, reads); | + * | unprobed gates LOUD-tripwired (#73, | + * | K_BUILDERR_CS pin) | 0 + * + * K_RUN rows also assert cstage/wwstage asm byte-id (acceptance + * graduations land byte-identical — F0's accept side was wwstage). + * K_RUN_CS / K_BUILDERR_CS rows run the cstage driver only, each + * citing the task that graduates them. binop_alias_vs_alias is + * CS-only loud: the wwstage checker stays silent on typed mismatches + * by design (5-lite discipline) — cs pins the harec-parity reject. + * NNN<950, self-contained (/tmp, no imports) — rule-14's selfhost- + * sibling race does not apply (941/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 drivers, exit==want, + byte-id */ +#define K_BUILDERR 1 /* build FAILS with experr on BOTH drivers */ +#define K_RUN_CS 2 /* cstage only — ww half pending (cite at row) */ +#define K_BUILDERR_CS 3 /* cstage-only loud (ww silent by design) */ + +struct row { const char *label; const char *src; int want; + int kind; const char *experr; }; + +/* errlog_has — a BUILDERR row 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[] = { + { "binop_alias_base", + "package main;\n" + "type myint = int;\n" + "export fn main() i32 = {\n" + " let a: myint = 5;\n" + " let b: int = 3;\n" + " let c = a + b;\n" + " if (c != 8) { return 1; };\n" + " let d: myint = 2;\n" + " if (a * d != 10) { return 2; };\n" + " if (a < b) { return 3; };\n" + " if (a - b != 2) { return 4; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "binop_2level", + "package main;\n" + "type myint = int;\n" + "type myint2 = myint;\n" + "type myu = u32;\n" + "export fn main() i32 = {\n" + " let a: myint2 = 7;\n" + " let b: int = 2;\n" + " if (a % b != 1) { return 1; };\n" + " let u: myu = 10: u32;\n" + " let v: u32 = 3: u32;\n" + " if (u / v != 3: u32) { return 2; };\n" + " if (u + v != 13: u32) { return 3; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "ret_through_2level", + "package main;\n" + "type myint = int;\n" + "type myint2 = myint;\n" + "fn dbl(x: myint2) int = {\n" + " return x * 2;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: myint2 = 21;\n" + " if (dbl(a) != 42) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "assign_alias_alias", + "package main;\n" + "type k1 = int;\n" + "type k2 = int;\n" + "export fn main() i32 = {\n" + " let x: k1 = 5;\n" + " let y: k2 = x;\n" + " if (y != 5) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* harec type_promote: ALIAS+ALIAS → NULL even on a shared base + * (ref/harec/src/check.c:1087-1089). wwstage's checker is silent + * on typed mismatches (5-lite) — cs-only pin. */ + { "binop_alias_vs_alias", + "package main;\n" + "type k1 = int;\n" + "type k2 = int;\n" + "export fn main() i32 = {\n" + " let x: k1 = 5;\n" + " let y: k2 = 3;\n" + " if (x + y != 8) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_BUILDERR_CS, "operands have differing types" }, + { "cond_if", + "package main;\n" + "type myb = bool;\n" + "export fn main() i32 = {\n" + " let b: myb = true;\n" + " if (b) { return 0; };\n" + " return 1;\n" + "};\n", 0, K_RUN, NULL }, + { "cond_for", + "package main;\n" + "type myb = bool;\n" + "export fn main() i32 = {\n" + " let b: myb = true;\n" + " let i: int = 0;\n" + " for (b) {\n" + " i = i + 1;\n" + " if (i >= 3) { b = false; };\n" + " };\n" + " if (i != 3) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "cond_bang", + "package main;\n" + "type myb = bool;\n" + "export fn main() i32 = {\n" + " let b: myb = false;\n" + " let n = !b;\n" + " if (!n) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "cond_andor", + "package main;\n" + "type myb = bool;\n" + "export fn main() i32 = {\n" + " let b: myb = true;\n" + " let c: myb = false;\n" + " if (b && !c) { } else { return 1; };\n" + " if (c || b) { } else { return 2; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "cond_fnparam", + "package main;\n" + "type myb = bool;\n" + "fn pick(b: myb) i32 = {\n" + " if (b) { return 0; };\n" + " return 1;\n" + "};\n" + "export fn main() i32 = {\n" + " let b: myb = true;\n" + " return pick(b);\n" + "};\n", 0, K_RUN, NULL }, + { "assert_stays_loud", + "package main;\n" + "type myb = bool;\n" + "export fn main() i32 = {\n" + " let b: myb = true;\n" + " assert(b);\n" + " return 0;\n" + "};\n", 0, K_BUILDERR, "assert: cond must be bool" }, + { "dot_2level", + "package main;\n" + "type a3 = b3;\n" + "type b3 = c3;\n" + "type c3 = struct { x: size, y: size, z: size };\n" + "export fn main() i32 = {\n" + " if (size(a3) != 24) { return 1; };\n" + " if (size((void | a3)) != 32) { return 2; };\n" + " let v: a3;\n" + " v.x = 1; v.y = 2; v.z = 3;\n" + " if (v.x + v.y + v.z != 6) { return 3; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "deref_alias_ptr", + "package main;\n" + "type pi = *int;\n" + "type pi2 = pi;\n" + "export fn main() i32 = {\n" + " let v: int = 41;\n" + " let p: pi2 = &v;\n" + " if (*p != 41) { return 1; };\n" + " *p = 7;\n" + " if (v != 7) { return 2; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* ---- reviewer-F1 find: 2-level alias of slice/str as a STRUCT + * FIELD type. The acceptance above admits every construction; the + * cgen FIELD-TYPE gates single-peeled, so the slice/str 3-word + * arms fell to word0-only scalar tails — SILENT wrong (ww correct, + * every shape LOUD at pristine 738d7f4 = opened by this commit). + * Chased gates: single-dot store (+via-ptr), struct-lit fill, + * chained store-walk leaf, chained-ptr-field store, single-dot / + * via-ptr / chained-walk reads. Each row pins one gate; the + * noise() call clobbers BX/CX so stale-register luck can't pass a + * word0-only header. Unprobed sibling gates are LOUD-tripwired, + * pinned by the #73 row at the end of this block. Rows whose + * `.len` readback rides the chained-dot READ walk are K_RUN_CS + * here (runtime-correct both stages; cs takes the generic spine + * pre-#71) and flip to K_RUN at the #71 walk-chase commit, which + * graduates their byte-id. */ + { "slicefield_store_2lvl", + "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 b: box;\n" + " b.n = 5;\n" + " b.s = a[0:3];\n" + " if (b.n != 5) { return 9; };\n" + " if (b.s[0] != 700) { return 1; };\n" + " if (b.s[2] != 900) { return 2; };\n" + " if (b.s.len != 3) { return 3; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ->K_RUN at the #71 walk-chase commit */ + { "slicefield_range_2lvl", + "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 b: box;\n" + " b.s = a[0:3];\n" + " b.n = 5;\n" + " if (b.s.len != 3) { return 1; };\n" + " if (b.s[2] != 900) { return 2; };\n" + " for (let x .. b.s) {\n" + " if (x < 700) { return 3; };\n" + " };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ->K_RUN at the #71 walk-chase commit */ + { "strfield_store_2lvl", + "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 b: box;\n" + " b.s = \"hello\";\n" + " b.n = 5;\n" + " if (b.s.len != 5) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ->K_RUN at the #71 walk-chase commit */ + { "slicefield_structlit_2lvl", + "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 b: box = box { s = a[0:3], n = 5: int };\n" + " if (b.n != 5) { return 1; };\n" + " if (b.s.len != 3) { return 2; };\n" + " if (b.s[2] != 900) { return 3; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ->K_RUN at the #71 walk-chase commit */ + { "slicefield_viaptr_2lvl", + "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 b: box;\n" + " let p = &b;\n" + " p.s = a[0:3];\n" + " if (p.s.len != 3) { return 1; };\n" + " if (p.s[2] != 900) { return 2; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ->K_RUN at the #71 walk-chase commit */ + { "slicefield_chainstore_2lvl", + "package main;\n" + "type sl = []int;\n" + "type sl2 = sl;\n" + "type inner = struct { s: sl2 };\n" + "type outer = struct { i: inner };\n" + "export fn main() i32 = {\n" + " let a: [3]int = [700: int, 800: int, 900: int];\n" + " let v: outer;\n" + " v.i.s = a[0:3];\n" + " if (v.i.s.len != 3) { return 1; };\n" + " if (v.i.s[2] != 900) { return 2; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ->K_RUN at the #71 walk-chase commit */ + { "slicefield_ptrchain_2lvl", + "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" + "export fn main() i32 = {\n" + " let a: [3]int = [700: int, 800: int, 900: int];\n" + " let i: inner;\n" + " i.n = 1;\n" + " let v: outer;\n" + " v.pi = &i;\n" + " v.pi.s = a[0:3];\n" + " if (i.s.len != 3) { return 1; };\n" + " if (i.s[2] != 900) { return 2; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ->K_RUN at the #71 walk-chase commit */ + { "slicefield_chainread_2lvl", + "package main;\n" + "type sl = []int;\n" + "type sl2 = sl;\n" + "type inner = struct { s: sl2 };\n" + "type outer = struct { i: inner };\n" + "fn noise(x: int) int = {\n" + " return x * 7 + 3;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [3]int = [700: int, 800: int, 900: int];\n" + " let v: outer;\n" + " v.i.s = a[0:3];\n" + " let k = noise(9);\n" + " if (k != 66) { return 9; };\n" + " let w = v.i.s;\n" + " if (w.len != 3) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "slicefield_ptrread_2lvl", + "package main;\n" + "type sl = []int;\n" + "type sl2 = sl;\n" + "type box = struct { s: sl2, n: int };\n" + "fn noise(x: int) int = {\n" + " return x * 7 + 3;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [3]int = [700: int, 800: int, 900: int];\n" + " let b: box;\n" + " b.s = a[0:3];\n" + " let p = &b;\n" + " let k = noise(9);\n" + " if (k != 66) { return 9; };\n" + " let w = p.s;\n" + " if (w.len != 3) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* whole-field read into a local: cs runtime-correct post-chase + * (the clobbered-register r1b probe), but the `let w = b.s` + * receive spine diverges cs vs ww at ONE level already (byte-id + * NO at pristine 738d7f4 = pre-existing, #71-style divergence, + * filed under #73's census) and ww's `w[i]` read is the task-#60 + * esz family — cs-only until both land. */ + { "slicefield_wholeread_2lvl", + "package main;\n" + "type sl = []int;\n" + "type sl2 = sl;\n" + "type box = struct { s: sl2, n: int };\n" + "fn noise(x: int) int = {\n" + " return x * 7 + 3;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [3]int = [700: int, 800: int, 900: int];\n" + " let b: box;\n" + " b.s = a[0:3];\n" + " let k = noise(9);\n" + " if (k != 66) { return 9; };\n" + " let w = b.s;\n" + " if (w.len != 3) { return 1; };\n" + " if (w[2] != 900) { return 2; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + /* #73 tripwire pin: the UNPROBED 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). */ + { "slicefield_idx_tripwire_73", + "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, K_BUILDERR_CS, + "unswept single-peel gate (#73)" }, + /* 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", + "package main;\n" + "type arr = [4]int;\n" + "type arr2 = arr;\n" + "export fn main() i32 = {\n" + " let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " if (a[2] != 3000) { return 1; };\n" + " a[1] = 9999;\n" + " if (a[1] != 9999) { return 2; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + { "slice_2level", + "package main;\n" + "type sl = []int;\n" + "type sl2 = sl;\n" + "export fn main() i32 = {\n" + " let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " let s: sl2 = a[1:3];\n" + " if (s.len != 2) { return 1; };\n" + " if (s[0] != 2000) { return 2; };\n" + " s[1] = 7777;\n" + " if (a[2] != 7777) { return 3; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + { "range_2level", + "package main;\n" + "type arr = [4]int;\n" + "type arr2 = arr;\n" + "export fn main() i32 = {\n" + " let a: arr2 = [1: int, 2: int, 3: int, 4: int];\n" + " let sum: int = 0;\n" + " for (let x .. a) {\n" + " sum = sum + x;\n" + " };\n" + " if (sum != 10) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + { "slice_of_alias_arg", + "package main;\n" + "type arr = [4]int;\n" + "type arr2 = arr;\n" + "fn sum(s: []int) int = {\n" + " let t: int = 0;\n" + " for (let x .. s) { t = t + x; };\n" + " return t;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " let s = a[1:3];\n" + " if (s.len != 2) { return 1; };\n" + " if (s[1] != 3000) { return 2; };\n" + " if (sum(a[0:4]) != 10000) { return 3; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + /* ---- ken-v3 leg: the acceptance align above opens 2-level + * float/str/slice aliases to the cgen KIND classifiers + * (cg_isfloat/type_isf32/fld_isfloat/type_isstr/type_isslice), + * whose single peel classed them scalar/INT — the f64-alias param + * would read the wrong register class (cs exit 1 SILENT, ww + * correct). The classifiers chase IN THIS SAME COMMIT so the + * widening never ships a loud->silent flip (rule 7; ken root- + * verify /tmp/ken_f1, his v3 row below). */ + { "float_alias_param_kenv3", + "package main;\n" + "type fa = fb;\n" + "type fb = f64;\n" + "fn take(x: fa) f64 = {\n" + " return x * 2.0;\n" + "};\n" + "export fn main() i32 = {\n" + " if (take(1.5) != 3.0) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "float_alias_param", + "package main;\n" + "type f1t = f64;\n" + "type f2t = f1t;\n" + "fn half(x: f2t) f64 = {\n" + " return x / 2.0;\n" + "};\n" + "export fn main() i32 = {\n" + " let v: f2t = 5.0;\n" + " if (half(v) != 2.5) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "str_alias_2level", + "package main;\n" + "type s1t = str;\n" + "type s2t = s1t;\n" + "fn taillen(s: s2t) int = {\n" + " return (s.len: int);\n" + "};\n" + "export fn main() i32 = {\n" + " let v: s2t = \"hello\";\n" + " if (v.len != 5) { return 1; };\n" + " if (taillen(v) != 5) { return 2; };\n" + " let w: s2t = v;\n" + " if (w.len != 5) { return 3; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* slice leg: cs classify fixed; the ww half of the indexed read + * is the task-#60 esz family — cs-only until F2. Values >255 + * break the esz=1 prefix-luck. */ + { "slice_alias_param", + "package main;\n" + "type b1 = []int;\n" + "type b2 = b1;\n" + "fn first(s: b2) int = {\n" + " return s[0];\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [3]int = [1000: int, 2000: int, 3000: int];\n" + " let s: b2 = a[0:3];\n" + " if (first(s) != 1000) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ +}; + +static int +run_driver(const char *driver, const struct row *r, int i) +{ + char src[96], tmpdir[96], errf[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/aac_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/aac_%d_d_%d", getpid(), i); + snprintf(errf, sizeof errf, "/tmp/aac_%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 (r->kind == K_BUILDERR || r->kind == K_BUILDERR_CS) { + int ok = (brc != 0) + && (r->experr == NULL || errlog_has(errf, r->experr)); + if (!ok) + fprintf(stderr, "row[%s]: %s expected loud builderr " + "\"%s\" (brc=%d)\n", r->label, driver, + r->experr ? r->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 != r->want) { + fprintf(stderr, "row[%s]: %s exit %d, want %d\n", + r->label, driver, got, r->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/aac_asm_%d_%d.ww", getpid(), i); + snprintf(cs, sizeof cs, "/tmp/aac_asm_%d_%d_c.s", getpid(), i); + snprintf(ws, sizeof ws, "/tmp/aac_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++; + if (run_driver(cdrv, &rows[i], i) != 0) fail++; + } + if (access(wdrv, X_OK) == 0) { + for (int i = 0; i < n; i++) { + if (rows[i].kind == K_RUN_CS + || rows[i].kind == K_BUILDERR_CS) + continue; + total++; + if (run_driver(wdrv, &rows[i], i) != 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_accept: %d/%d checks failed\n", + fail, total); + return 1; + } + printf("alias_accept: %d/%d ok\n", total, total); + return 0; +}