Commit Graph

799 Commits

Author SHA1 Message Date
4cac1cbb89 wcc_ww/cgen: W1 #100 struct-lit fill gate keys off the chased stamped tinfo
The ww twin of B5-c1's :3195 cg_structlit_fill chase — closes the
R4-blessed one-train acceptance divergence (cs-loud / ww-SILENT-WRONG
on alias-element array fills, ken FLAG-3 / kb5_fill2). The #249 fill
arm's loud-gate element classification was alias-blind on two of four
keys: a raw N_TSLICE kind test and a structlookup leaf-name probe —
`type el = el0;` matched neither, bypassed the gate, and fell to the
scalar tail (one word per element, silent exit 1, gate-blind #263
class). Now: issliceel via isslicetype (stamped tinfo, the str/tagged
keys' existing route) and isstructel via tichase + TY_STRUCT kind —
the tnodeisagg discipline the neighboring #49 arm already uses, twin
of cstage type_chase_named at cgen.c:3179. NO new helper.

Loudness-alignment only, zero acceptance movement elsewhere: probe
matrix holds direct str/slice/struct/tagged elem LOUD (twin texts),
int/alias-int/f64 elem fills build + run 0/0 byte-id, 1- and 2-level
alias struct/slice/tagged elems now LOUD both stages.

ww-only: cs side untouched (zero C bytes; w6c up-to-date across the
edit). B5 detector rows hold post-B5 expected state (targ/tmem/wpush/
null/succ/f32p/def93 all 0/0 byte-id). 989 ratchet ZERO flips
(31 ID / 9 pinned-divergent / 3 pinned-wwreject). combined.ww regen'd
for both embedded targets (w6c_ww, wwdump).

944_alias_cgen_b5_run: fill2 graduates the dual-cell K_CSERR_WWRUN
pin to the K_BUILDERR LOUD-HOLD pair (fill0-class, shared experr
tail); fill0 control holds. Check count holds at 84/84 — the kind
flip is cell-count-neutral (2 driver cells either side, no byte-id
cell). All 944-family suites green; sizelint 0; test-unit all 291
passed. Closes #100; the R4 divergence window closes with it.
2026-06-06 03:09:54 +09:00
837fdb4753 cgen: B5-c4 #93 deref-index pointee chase — the one spurious MOVQ, #263-polarity
Trace at the c3 tip: cs-vs-ww diff on l2_local/kb5_def93 = exactly ONE
line, a spurious `MOVQ (AX), AX`. The deciding site is the cgexpr
N_UN(STAR) pointee classify (`ru`): the single peel left a 2-LEVEL
alias pointee TY_NAMED, the ARRAY skip (#61-C — an array value IS its
address, #270-1a) missed, and the scalar load pulled a[0]'s VALUE as
the index base — wild pointer, SIGSEGV 139 on cs. KEN #263-POLARITY:
cs is the WRONG side; ww chases and is the runtime-correct reference —
cs converges on WW's asm. Single-site grant: the one `ru` computation
(shared by the FN/ARRAY/TAGGED skip predicates) → type_chase_named.
Raw `->under` in cgen.c 59→58. #93 CLOSES.

TRAIN INVARIANT holds at the tip: cs-only; _ww binaries bit-identical
to the bcd948d baseline md5s across all four commits. cs movers vs the
c3 tip bounded to EXACTLY the deref-index shapes: l2_local, kb4_x93,
kb5_def93. Zero ww movers. Detector pinned: kb4_xampdef STAYS 139/139
(#94, out-of-train — `&D[i]` indexed def base, a different site).

Graduations (cs SEGV-139 / ww 0, BYTE-DIVERGE → 0/0 BYTE-ID):
g93_l2_local (the banked rob spelling), g93_def (kb5_def93, the
natural `(*p)[2]` def twin). g93_1lvl_ctl (1-level control) held 0/0
byte-id throughout.

944_alias_cgen_b5_run 28→31 rows (84 checks); 944 family green;
sizelint 0.
2026-06-06 02:26:29 +09:00
140755d221 cgen: B5-c3 #89 cgreturn return-position widen — rt/vu chase, trace-first
Trace at the c2 tip (rebuilt binaries, #80-c4 form): ret_widen's mk()
emitted `MOVQ -16(BP),AX / MOVQ AX,DX / MOVQ $0,CX` — the scalar
shuffle arm, payload word 1 ZEROED (s.b/e.aux dropped, cs silent
exit 1). Deciding predicate: the vu single peel left an alias struct
source TY_NAMED → isstruct false → scalar arm. The rt single peel was
the succ half: a 2-LEVEL alias return type stayed NAMED → the whole
tagged-return block was skipped → no tag synthesis at any return
(kb5_succ's three paired return-position insertions). Fix = the three
granted peels (bare-return rt, value-return rt, vu) →
type_chase_named; the route predicates (istagged/isstruct/istuple/
passthrough) key on the chased vu. Raw `->under` in cgen.c 62→59.
#89 CLOSES.

TRAIN INVARIANT holds: cs-only; _ww binaries bit-identical to the
bcd948d baseline md5s. cs movers vs the c2 tip bounded to EXACTLY the
return-route family: ret_widen, kb4_xret, kna_ret_errunion, kb5_succ.
Zero ww movers; detectors pinned (kb4_xampdef stays 139/139 #94;
def93/x93/l2_local stay cs-SEGV — c4's targets; fill/tuparg/v2
families unchanged).

Graduations (cs1/ww0 BYTE-DIVERGE → 0/0 BYTE-ID): g89_ret_widen (the
banked spelling), g89_ret_errunion (the live e.aux truncation seed),
and succ_2lvl flips K_RUN_NOID → K_RUN exactly as C1-CORR-1 predicted
(pins-follow-the-layer). g89_ret_named_ctl (bare NAMED control) held
0/0 byte-id throughout.

944_alias_cgen_b5_run 25→28 rows (75 checks); 944 family green;
sizelint 0.
2026-06-06 02:23:18 +09:00
e36af4fe7e cgen: B5-c2 #73 graduation — the 6 tripwired field gates chase, tripwire deleted
Close-by-construction replacing containment — the designed graduation
path from F1-c1's commit body. The 6 fld_alias_tripwire call sites
(indexed-elem field store/read, heap struct-lit field fill, tuple-elem
read, ptr-chain field read, static struct-lit emit) chase their fu
through type_chase_named; the 6 tripwire calls AND the helper itself
(incl. its :447 peel) are DELETED. Raw `->under` in cgen.c 69→62.
#73 CLOSES.

TRAIN INVARIANT holds: cs-only; w6c_ww/ww_ww bit-identical to the
bcd948d baseline md5s (28ad889042bad8006f1997cbcec94805 /
4e9ca8741f19e1f68219ff799a5e5a14). ZERO new corpus movers vs the c1
tip (kb*/kna corpus + five mains byte-NEUTRAL — the chased gates only
fire on 2+-level alias aggregate fields, none in corpus).

Gate-arm graduations (pre = loud "#73" fatal on cs, ww ok/0; post =
0/0 BYTE-ID): slice/str/tagged arms at the indexed STORE gate
(g73_idxstore/g73_strfield/g73_tagfield), slice arm at the indexed
READ / ptr-chain READ / tuple-elem READ gates (g73_idxread/g73_ptrread/
g73_tupread), nested-struct arm at the static emit gate
(g73_static_struct). The existing 944_alias_accept_run tripwire bound
row graduates K_BUILDERR_CS → K_RUN.

Two arms cannot pin the full 0/0 byte-id cell — documented, not silent:
  - heap fill (g73_heapfill, COMPILE-only pin): bare /tmp programs
    never link malloc (both stages, pre-existing infra) and ww's
    deref-field READ carries the #24-kin field(SB) leak; the chased
    FILL bytes verified byte-id by hand against the plain (non-alias)
    control — divergence shape identical, all of it in the pre-existing
    read sites.
  - str-field static emit (g73_static_str): the #73 fatal gave way to
    the pre-existing #129 A.2 foldability loud on cs — now both-loud
    twin texts (fill0 class), pinned as K_BUILDERR.
  - struct-copy arm: NO runnable repro reaches these gates — indexed
    whole-struct field reads take the (already chased, byte-id) address
    spine, and tuple-of-struct louds upstream on BOTH stages (#54-kin
    "aggregate init from unhandled rhs shape"). The arm's only carrier
    was the heap gate, covered by the compile pin above.

944_alias_cgen_b5_run grows 16→25 rows (65 checks); all 944-family
suites green; sizelint 0.
2026-06-06 02:19:39 +09:00
1cc663f494 cgen: B5-c1 helper+funnel single peels fold into type_chase_named — 19 sites, cs-only
The exact F2b c1 set (rob next-arc spec + B5 re-rule): node_tuplearg:249,
fld_issigned:409, castsrcprim:501/:531, struct_float_class:598,
tagged_arg_size:640, tagged_memarg_size:661, type_isnullable:740,
nullable_ptr_tag:750, cg_tagged_success_tag:860, cg_variant_is_error:876,
cg_tag_for_variant:899, type_istagged:953, type_unwrap:1269 + the widen/
fill funnel entries cg_widen_tagged_store:2456/:2480/:2483,
cg_widen_tagged_push:2905, cg_structlit_fill:3195. Raw `->under` in
cgen.c 88→69. Riding per re-rule R1: peel-ok-#218 annotations at
cg_variant_match/cg_variant_struct_match (citing ken's b5 oracle §4 —
chasing those four peels graduates zero v2_struct rows; the real fix is
a both-stage NAMED-source arm, task #95) and the :755 peel-ok annotation
mirroring ww cgenutil.ww:2758 (probe-cleared, 018ef66). :447 untouched
(c2's grant).

TRAIN INVARIANT: cs-only — zero selfhost/ or lib/ bytes move; w6c_ww/
ww_ww/w6a_ww/w6l_ww bit-identical to the bcd948d baselines (md5
28ad889042bad8006f1997cbcec94805 / 4e9ca8741f19e1f68219ff799a5e5a14).
cs movers bounded to exactly: kb5_targ, kb5_tmem, kb5_wpush, kb5_null,
kb5_f32p, kb5_fill2, kb5_tuparg_c; five selfhost mains + the rest of the
kb2/kb3/kb4/kb5/kna corpus byte-NEUTRAL both stages.

LIVE graduations: kb5_targ (tagged_arg_size sized a 2-level alias union
param 0 → wrong arg path, cs silent exit 1) and kb5_tmem (>48B memarg
twin) → 0/0 byte-id. Divergence flips to byte-id: wpush/null/f32p.

#85 CLOSES as SITE-CLOSURE with ZERO live graduations: type_unwrap's
two consumers (:14716/:14907, both tuple-global layout walks that want
the chased view) are checker-DEAD on cs for alias tuples (#86 upstream)
— correctness there is by-construction, pinned by tupglobal_bound86.

DESIGNED DIVERGENCE (re-rule R4, task #100): the :3195 chase flips
kb5_fill2 from both-wrong-IDENTICAL-silent (gate-blind, both stages
accepted and ran wrong byte-identically) to cs-LOUD / ww-silent-wrong.
A loud, disclosed, pinned divergence over a silent miscompile; rejected
programs emit no asm so the byte-id gates hold. Dual-cell pin
(fill2_bound100): cs experr + ww run-exit-1 both asserted; fill0
both-loud control holds. #100 (the ww twin gate) fires immediately
after B5 so the window is one train wide.

Oracle corrections at the c1 boundary (ken c1-BOUNDARY ADDENDUM,
verified on his independent scratch build; rob ack'd, scope unchanged):
  C1-CORR-1: kb5_succ does NOT flip here — its residual divergence is
  exactly three paired return-position tag syntheses, the cgreturn
  return-route family (:12278/:12318/:12322). Joins c3's graduation
  set; pinned succ_bound_c3 K_RUN_NOID until then.
  C1-CORR-2 (corrects re-rule R2): kb5_wstore_a does NOT flip — the
  ident-lhs N_ASSIGN tagged store gates in the cgexpr INLINE set (B6),
  never reaching the :2456 funnel; cs byte-neutral here. Pinned
  wstore_a_bound_b6 K_RUN_NOID; byte-id rides B6.
  C1-CORR-3 (corrects re-rule R3 + ken FLAG-2): the :249 chase is NOT
  purely latent — the CAST spelling (kb5_tuparg_c) earned a LIVE cs
  graduation (cs ok/1 → ok/0, correct tuple-arg classify); ww still
  runs wrong (task #99). Two-key pin tuparg_cast_bound99: cs-0 earned +
  ww-1 pinned observed-wrong; byte-id re-pins to full 0/0 when #99's ww
  fix lands.

New 944_alias_cgen_b5_run row table (16 rows, 40 checks): controls
signed/wstore/wstore1 byte-NEUTRAL as predicted (kind-keyed tests are
the only behavior-visible peels — type.c classifiers already recurse);
literal tuparg spellings stay dual-cell bounds (#99/#86). All 944-family
suites green; sizelint 0.
2026-06-06 02:15:14 +09:00
bcd948de88 wcc_ww/check: c4 #80 bare-binder forrange dealiases the iterable's type expr
F2a batch-4 c4. RE-PROBED AFTER c1 per spec: still live at the c3
train base with a REBUILT w6c_ww (the mechanical tichase collapse
didn't cover it — this read is AST-node-keyed, not tinfo-keyed).

REPRO (.ai/scratch/repro_f2a1_b4.ww): `untyped_lit * rangevar` over a
range-for of an alias-typed slice (`type slk = []int`) — wwstage
checker dies "asserttyped: bin" at the binop; cs accepts and runs 0.

TRACE: check.ww resolvewalk N_FORRANGE bare-binder arm — the binder's
element type comes from kind-testing the scrutinee's type expr
(N_TSLICE/N_TARRAY), but an alias-typed iterable arrives as N_TNAME:
both tests miss, the binder falls to the N_FORRANGE fallback decl,
stays untyped, and the first binop over it bails. cs twin types the
binding at scope_define (check.c N_FORRANGE) — accepts.

FIX (single site, the one the repro traces to, per grant): dealias
via the existing resolvealias(unwrapbang(it)) idiom before the kind
tests. The TUPLE-DESTRUCTURE arm carries the same unresolved tests
but is NOT in-grant (fixing the bare arm is not a no-op, so the
re-spelling clause does not apply) — FILED as task #97; currently
double-masked bounded-loud (cs louds upstream at #270-1c so the
alias-tuple-slice iterable is unconstructible on cs; ww asserttyped).

Rows (944): rangevar_alias2 (the repro, cs0/ww-reject -> 0/0 byte-id)
+ rangevar_plain_ctl (non-alias control, held throughout). 944
202/202. Corpus: five-mains ww NEUTRAL vs base on identical inputs
(checker-acceptance-only change; no alias range-for in corpus).
combined.ww regens ride along.
2026-06-06 01:04:19 +09:00
10d886906f wcc_ww/check: c3 spread-member + &len/cap base walks chase the full alias chain
F2a batch-4 c3, the check.ww behavior pair (exact set). Both cs twins
ALREADY full-chase at this SHA — ww-only align-up, no fuse:

B1 (check.ww:2038 census line; peel was at :2041 post-c1): the
`...inner` spread member peeled ONE level, so a 2-level-alias inner
union escaped the TY_TAGGED splice test and rode as a SURFACE member —
the outer box sized off the inner union's own header (probe sp_alias2:
runtime 0/0 BOTH but byte-DIVERGE, ww frame $48 vs $32). cs twin
chases (check.c:755 type_chase_named, the spec's :660-667 cite moved).
Fix: vu = tichase(vu) under the isspread gate, mirroring cs's
`spread ? type_chase_named(vt) : vt`. sp_alias2 graduates 0/0 byte-id;
sp_alias1 1-level control held throughout.

B2 (check.ww:2273/:2275): the &x.len/&x.cap base walk hand-peeled
NAMED->PTR->NAMED one level per hop — 2-level alias bases fell out of
the slice/str detect and took the generic *opt typing. cs twin chases
both hops (check.c:1198-1201). Fix: tichase per hop, exact cs mirror.

B2 ROUTE-TRACE (flagged, not edited): the spec's "post 0/0" cell for
amplen2 is NOT achievable inside this grant — OBSERVED pre-fix that
ww dies LATER at the cgen ADDRESS tail ("unsupported address-of
shape") for ALL alias bases 1+ LEVEL (cs runs 0): task #96 (ken
b4-oracle), a cgenexpr.ww site outside c3's check.ww set. The chase
here aligns the stamped type only (latent until #96); rows pinned
LOUD-HOLD via the new K_RUN_CS_WWERR kind (cs builds+runs; the ww
build must keep FAILING loud with the pinned diagnostic — a silent
ww accept-and-run is the regression the bound row exists to catch),
citing #96; they graduate K_RUN with it. Also OBSERVED: the let-init
spelling (`let s: sl1 = [1,2,3]`) is blocked further upstream by the
rows use the param route.

Pin rows (944): sp_alias1 K_RUN hold, sp_alias2 K_RUN graduation,
amplen_plain K_RUN control hold, amplen1/amplen2/ampcap2
K_RUN_CS_WWERR LOUD-HOLD bound on #96 (ww leg asserts "unsupported
address-of shape"). 944: 196/196.

Corpus: five-mains NEUTRAL vs base on identical inputs, cs==ww holds
(both clusters zero-in-corpus). combined.ww regens ride along.
2026-06-06 01:03:59 +09:00
4e174099bd cgen: c2 variant pass-2 structural fallback chases the full NAMED chain — both stages fused
F2a batch-4 c2. Site: cgenutil.ww flatvariantidxt pass-2 (was :2895
at 74195ac, :2903 at 4adf914 post-batch-3) + cs twin cg_tag_for_variant
(cmd/w6c/cgen.c:920-933).

The structural fallback matched a bare source against a NAMED variant
by peeling exactly ONE level (pu.under compare) — a 2-level-alias
variant (type a=*X; type b=a) missed every pass and the widen
defaulted to tag 0, SILENT (the legacy-#17-comment class; that
comment's "task #17" label is retired here — current task #17 is the
unrelated arrlit item).

cs-twin probe DECIDED THE FUSE (spec obligation): v2_alias2 (bare
*i64 into 2-level ptr-alias variant) ran exit 1/1 BOTH-WRONG-IDENTICAL
byte-id pre-fix — cs has the identical single peel, so both stages fix
in this commit (NOT ww-only align-up). NOTE: the cs BINARY is NOT
frozen this train — this commit legitimately moves cstage codegen;
movers must stay bounded to the c2 family (verdict-sweep obligation).
Fix: typeeq/type_eq against tichase/type_chase_named of the variant;
the TY_NAMED gate keeps bare variants in pass-1's exact domain; drew's
>=2-candidate hard-error now guards the CHASED match set (v2_ambig
pins it HOLDING; v2_ambig2 pins the RATIFIED acceptance NARROWING —
2-level twins flip build-accepted-silent-mis-tag -> hard-error BOTH
stages, FLAG-P1/kb4_v2_ambig2). Not nominal-sensitive beyond the
documented proviso — the chase only deepens the structural compare;
nominal choice among >=2 candidates still hard-errors (#209/#211 hold
condition does not trip).

ROUTE-TRACE (rob's §3 ruling) — outcome (b): post-c2 the c4_bool2
shape (2-level bool alias variant, concrete-bool source on ww) runs
0/0 fully BYTE-ID — ww's concrete bool (the #90 stamp divergence,
still open) now reaches the variant through the chased structural
fallback and converges with cs's untyped-funnel route on the same
tag. The graduation therefore belongs to THIS commit: 944
untyped_bool2lvl_bound90 flips K_RUN_CS -> K_RUN here; c5 (#90 stamp
flip) pins its own acceptance rows.

Pin rows (944, all OBSERVED at the c1 base):
  v2_ctrl    bare *T into 1-level NAMED-*T (io vtable shape)
             pre 0/0 byte-id -> post HOLDS (the #15 consumer);
  v2_alias2  bare *T into 2-level alias variant
             pre cs1/ww1 byte-id (both-wrong) -> post 0/0 byte-id;
  v2_struct2 bare anonymous-let struct into 2-level alias variant
             pre cs1/ww1 -> post 0/0 runtime; byte-id SKIPPED via new
             K_RUN_NOID row kind: asm diverges on 3 PRE-EXISTING cglet
             zero-fill lines (cs XORQ+2 stores, ww none — #81 class,
             runtime-correct both, orthogonal to the tag; observed
             identical pre/post). Flip to K_RUN when that closes. The
             adjacent NAMED-source shape is task #95 (ken b4-oracle),
             OUT of this set;
  v2_ambig   bare source matching >=2 NAMED variants (1-level twins)
             -> hard-error HOLDS both stages (K_BUILDERR, diag pinned);
  v2_ambig2  2-LEVEL twin variants — the RATIFIED narrowing pin:
             pre build-ACCEPTED both stages (chase-less fallback
             matched neither twin; silent mis-tag, byte-identical)
             -> post HARD-ERROR both stages (K_BUILDERR, diag pinned).

Corpus: five-mains NEUTRAL vs the c1 build on identical inputs (both
stages — the 2-level variant shape is zero-in-corpus, as the old
comment predicted); cs==ww byte-id holds; 944 181/181.
combined.ww regens ride along.
2026-06-06 01:03:22 +09:00
b26b3dbdc5 wcc_ww: c1 collapse plain TY_NAMED chase loops into tichase across cgenstmt/cgen/check — byte-id neutral
F2a batch-4 c1, mechanical, per rob's spec census (re-verified at
4adf914; check.ww/cgenstmt.ww cites were stable as predicted, the
two cgenutil [B3-SHIFT] cites moved +8: :2746->:2754, :2895->:2903).

42 conversions, every site eyes-classified plain nil+NAMED:
- cgenstmt.ww 26: one-line :149 :325 :352 :532 :551 :594 :715 :732
  :938 :940 :1577 :1629 :1782 :2230 :2575 :3372 :3678 + multi-line
  :986 :1912 :2287 :2291 :2702 :2759 :2823 :3037 :3715 (all plain,
  no extra guards).
- cgen.ww 13: one-line :1505 :1541 :1658 :1691 :1697 :1947 :1951
  :2065 :2109 :2118 + multi-line :2239 :2648 :2663 (incl. the
  emitdefconstants struct/array arms — plain).
- check.ww 3: :1627 tupleelemslot, :1673 fieldslotsize, :4111 arrlit
  elem chase; WHY comments kept. tichase visibility verified: same
  `package wcc` (cgenstmt/cgen already call it) — no second helper.

Survivors (classified, untouched):
- cgenutil.ww :1301 tichase's own body (the accessor) + :2754
  nullableptrtag single peel (batch-2 c3-B2 PROBE-CLEARED, 018ef66 —
  peel-ok annotation added this commit, cites the banked record; cs
  twin cmd/w6c/cgen.c:747 verified at this SHA) + :2903
  flatvariantidxt pass-2 guard (batch-4 c2's site).
- check.ww :1765 comment + :1805 construction write (peellint-ok
  annotation added — the one WRITE building the NAMED link) + :2038
  spread peel (c3-B1) + :2273/:2275 &len/cap hand-walk (c3-B2) +
  :3666 comment FALSE POSITIVE ("io.underread" matches `.under`).
- cgenexpr.ww :2886 comment only.

Lint note for the peellint finale: the `.under` token-match must be
token-bounded — check.ww:3666's "io.underread" is a comment
false-positive at a non-boundary match.

Behavior sites (:2038, :2273/:2275, :2903) and #80's site excluded
per spec — they ride c2-c4.

Byte-neutral proof: rebuilt w6c_ww on the five BASE main.combined.ww
inputs -> all five .s byte-identical to the base build's outputs;
cs==ww byte-id holds on the five REGENERATED combineds; test-unit
290 green. w6c_ww + wwdump combined.ww regens ride along.
2026-06-05 23:58:54 +09:00
4adf914f2f wcc_ww/cgen: #92 rhsstructpayload N_STRUCTLIT arm through structlookupchain
The N_STRUCTLIT arm still did bare structlookup on the literal's type
name — an alias-named struct LITERAL widened into a union (`type ali =
base; let v: (void|ali) = ali{...}`) missed the registered structinfo
and fell to the scalar widen arm: word0-only/dropped payload, cs 0 /
ww EXIT 1, byte-id NO (reviewer-B2 find, pre-existing at 486f7f8's
parent). Exactly the class batch-2 c2 closed for the N_IDENT-local arm
of the SAME function; same funnel fix — route through structlookupchain
(cgenutil.ww:1758) and return si.sname so every consumer's re-lookup
hits the REGISTERED name.

One pre-authorized rider: structlookupchain's entry gate accepted only
N_TNAME, but a struct literal's type ref parses as N_IDENT (expression
position, lib/ww/parse/expr.ww builds s.lhs as N_IDENT — OBSERVED).
Widened to `N_TNAME || N_IDENT`; the entry-kind predicate is spelled
twice (the early-return and the chain loop's first iteration — the
loop reassigns cur to aliaslookup results, which are always N_TNAME,
so later iterations are untouched). Consumer census, all 10 call
sites: cgenutil:3187(+this arm; post-commit position), cgenexpr:2927/3006/8775/9011,
cgenstmt:3033 guard on N_TNAME explicitly before calling;
cgenexpr:5756 (etnode = type-spec .lhs), cgenstmt:1978 (arrtn.lhs or
the synthesized #79 N_TNAME), cgenstmt:2695 (cglet n.lhs declared
type spec) pass parsetype products only — parsetype builds zero
N_IDENT nodes (lib/ww/parse/parse.ww). No existing caller can pass
N_IDENT: the widening is strictly accepting-more; existing callers
unmoved. NO second inline chase in the arm (close-by-construction).
Base spellings byte-id: structlookup hits at the chain entry and
si.sname == the literal's own name — same string out, same asm.

Pin: 944_alias_accept_run union_slit_{base_ctl,alias,alias2,order} —
base control holds 0/0; 1/2-level alias + permuted decl order graduate
cs0/ww1-byte-id-NO -> 0/0 byte-id. #63 PROBED post-c3 per the
enrollment rule: does NOT green at this site (8B alias struct-lit
let-init still ww silent-zero-fill exit 1; 16B still loud "aggregate
init from unhandled rhs shape") — its miss is the cgenstmt let-init
dispatch, not rhsstructpayload; documented on task #63, left red, no
row enrolled.

Light gates: test-unit 290 green; sizelint 0; 989 ratchet zero flips;
five-mains NEUTRAL vs master-74195ac scratch on identical inputs +
cs==ww on all five. combined.ww regens ride along (#110).
2026-06-05 23:38:54 +09:00
3e9a6955e7 wcc_ww/cgen: #88 defisaddressable array leg chases the stamped def type
The `&D` addressability gate (defisaddressable, cgen.ww) keyed its
array leg on the UNCHASED syntactic dtnode (N_TARRAY) — a def whose
declared type is an ALIAS of an array missed the gate and fell to the
rule-7 loud error, but the gate was lying: the ww def-array DATA
emitter (emitdefconstants' array arm) already peels TY_NAMED off
d.lhs.type_ transitively, so the alias def HAS a DATA symbol
(probe-OBSERVED: `DATA main.D(SB)` emitted byte-id by both stages for
the &-less program). Gate-only fix — tichase(dtn.type_) == TY_ARRAY —
restores gate == emission set exactly; no emitter twin, no half-state.
The struct leg (defvarstructinfo) already chased; plain [N]T defs
agree under tnode and chased reads, so existing rows are
byte-id-neutral by construction. cstage gates TK_AMP on the
def_isarraydef registry fed by the g-fold-G1 chased let_isarray
(cgen.c:3975-3977, 1446) and runs every row 0 — align ww UP.

Pin: 944_alias_def_addr_run, 6 rows (plain + struct-def controls
hold 0/0; 1/2-level alias + fwd-ref decl order graduate ww-LOUD ->
0/0 byte-id; str-def &S error-path STAYS LOUD both stages with a
byte-identical diagnostic — the rule-7 tail text is compared w6c vs
w6c_ww, so a silent reject or a divergent message both fail the row).
def_l2's readback casts to the base array ptr: the natural (*p)[2]
spelling over a 2-LEVEL-alias pointee trips a SEPARATE pre-existing
CSTAGE double-deref (spurious MOVQ (AX),AX, SEGV; def-independent, ww
correct) — filed as task #93 (#85 type_unwrap kin, F2b OUT), not
fixed here (site-set form).

Light gates: test-unit 290 green; sizelint 0; 989 ratchet zero flips;
five-mains NEUTRAL vs master-74195ac scratch on identical inputs +
cs==ww on all five. combined.ww regens ride along (#110).
2026-06-05 23:38:41 +09:00
d5cb1bd69e wcc_ww/cgen: #82 cgun &base[i] classify off the chased stamped base type
The TK_AMP N_INDEX arm keyed arrayness off the SYNTACTIC tnode (local
leg isarr at the baselocal read; global leg isglobalarr/isglobalptr at
the letvartnode read) — an alias-typed base (tnode N_TNAME) missed the
N_TARRAY gate, so the base materialized as MOVQ (element-0 VALUE)
instead of LEAQ (storage address): wild pointer, SEGV/corruption on
the deref. SILENT class (metric-1). The global leg graduated from
latent to live when g-fold #77/#78 landed alias-global DATA emit.

Fix re-keys both legs off tichase(base.type_) gated on TY_NAMED — the
landed cgindex #60 idiom (cgenexpr.ww:1800-1820). cstage already
classifies off the chased type (type_chase_named, cmd/w6c/cgen.c:
4172-4188) and is the runtime-correct reference: align ww UP. esz does
NOT move — elemsizeofc chases internally since batch-2 (PREMISE-2
probe-confirmed via amp_narrow: stride right, base wrong pre-fix).
Non-alias rows byte-id-neutral by construction (TY_NAMED gate).

Pin: 944_alias_amp_idx_run, 8 rows through the taken pointer (plain
local/global+str controls hold 0/0; 1/2-level alias local + global,
fwd-ref decl order, narrow [4]u32 graduate cs0/wwSEGV-byte-id-NO ->
0/0 byte-id). Probed OUT, filed not fixed (spec §1 NOTE-2): &D[i]
def-array base breaks at a DIFFERENT site both stages (cs XORQ BX,BX
zero-base cgen.c:4209-4212, ww complex-base fallback; both SEGV 139).

Light gates: test-unit 289 green; sizelint 0; 989 ratchet zero flips
(31 ID / 9 DIVERGE / 3 WWREJECT pins hold); five-mains NEUTRAL vs
master-74195ac scratch build on identical inputs + cs==ww on all
five. combined.ww regens ride along (#110).
2026-06-05 23:06:46 +09:00
74195ac4dc wcc_ww/cgen: c4 tyassignableuntyped full-chase — structural alignment, zero acceptance graduations, graduations ride #90
F2a batch-2 c4, per rob's FINAL RULING (i) (banked, .ai/rob-batch2-spec.md):
ww-only — the cs twin (cmd/wcc/type.c:369-385) is already harec-shaped
since F1 9bd0d8b. Port of ref/harec/src/types.c:989-996
type_is_assignable structure: full dealias of the dst when its chased
kind is not tagged (du = tichase(dst); the STR/BOOL/NIL arms read du);
the tagged variant drill keeps the ORIGINAL p.type_ and detects a
variant's taggedness via the full chase (was one-level — a 2-level
alias-tagged variant slipped INTO the recursion); INT/FLOAT/RUNE keep
the recursive predicates on the unpeeled dst, exactly as cs.

STRUCTURAL ALIGNMENT, ZERO ACCEPTANCE GRADUATIONS, GRADUATIONS RIDE
#90: on wwstage no bool/str literal reaches this funnel today — the
checker stamps N_TRUE/N_FALSE CONCRETE bool (check.ww:2440) where cs
stamps ty_untyped_bool (check.c:1234), the bool member of the #14
N_STRLIT stamp cluster (task #90, filed this train; its metadata
carries the graduation obligations: BOTH layers, the stamp AND the
flatvariantidxt pass-2 one-level pu.under guard at :2868/#17, must
clear before the bound rows flip). The live silent-wrong this would
close — `type bb2 = bb1 = bool; let v: (void|bb2) = true; v is bb2`
runs cs 0 / ww 1, single-line asm diff (tag store MOVQ $1 vs $0) —
therefore does NOT graduate here: the row dies one layer up
(pins-follow-the-layer).

test 944 (+7 rows): untyped_hold33_pin ((void|T) untyped-int init,
the #33 tagged-dst drill HOLD) + untyped_tagdst_2lvl_hold (2-level
alias tagged dst) + untyped_str2lvl_ctl (masked twin — the shape
fallback rescues str, documents the rescue) + untyped_int2lvl_ctl +
untyped_bool1lvl_ctl all K_RUN 0/0; untyped_bool2lvl_bound90
K_RUN_CS BOUND citing #90; untyped_nestvar_bound K_BUILDERR_CS BOUND
expected-state row (#199-alpha + #90) — untyped literal into a
NESTED-tagged alias-wrapped variant: cs CHECKER loud-rejects
("not assignable", the #199-alpha ww-stricter no-transitive-drill
rule, type.c:316-324) while ww's node-keyed checker accepts and the
binary runs 0 OBSERVED post-chase; the acceptance gate is check.ww
(batch-4 aligns ww down; the row flips to loud-BOTH then; ww half
pinned via #90 metadata — the harness has no cs-reject+ww-runs
mode). Every row's cs column OBSERVED (built+run) at dispatch-SHA cs
per the ratified condition; probes at /tmp/b2/c4.

989 lib-byteid ratchet: ZERO flips (31 ID / 9 DIVERGE / 3 WWREJECT
hold exactly as pinned). Byte-id: five-mains NEUTRAL on identical
inputs vs the c3 build; cs==ww on the regenerated five; test-unit 288
green. combined.ww regens ride along.
2026-06-05 22:36:55 +09:00
018ef6680e wcc_ww/cgen: c3 in-file peel sites — exprprimresolved base walk chases (B1); elemsizeofc alias-indexable leg (B3); B2 probe-cleared
F2a batch-2 c3, probe-decided per rob's spec.

B1 (exprprimresolved N_DOT, was :2073/:2075/:2078): the hand-rolled
NAMED->PTR->NAMED->STRUCT walk ran out at a 3-level alias base or a
ptr-to-2-level base — the cast-source width stayed unknowable on
wwstage only, so the #33 identity clamp was emitted where cstage
skipped it (runtime-correct both, byte-id NO, gate-blind). cs twin
castsrcprim is F1-chased at both hops (type_chase_named) -> ww-only
align-up, no fuse. Probes: b1b_3lvl + b1b_ptr2lvl graduate 0/0
byte-id; 1/2-level controls held throughout (the 2-peel covered
2 levels by accident); the FIELD-u chase is asm-neutral (cs keeps a
single peel there, sound via type_isint NAMED recursion + NAMED size
carrying the underlying's — probe b1c_fld2lvl byte-id). 944 rows
castprim_3lvl_base / castprim_ptr2lvl_base / castprim_2lvl_base_ctl.

B2 (nullableptrtag variant scan :2719): PROBE-CLEARED, NO FIX. The
2-level ptr-alias nullable variant (b|void), the order-swapped
(void|b) that defeats the return-0 fallback, AND 3-level all run 0/0
byte-id both stages — the constructible variant params never carry a
2+-level NAMED at this scan. cs twin nullable_ptr_tag (cgen.c:747)
has the identical single peel: parity, both correct on every
constructible shape. NOT nominal-sensitive (shape-keyed first-PTR
scan, no name choice exists) — the spec's #209/#211 hold condition
does not trip. The single-peel if stays, classified survivor.

B3 (elemsizeofc): an alias-NAMED INDEXABLE (`type grid = [3]cell`)
arrives as a bare N_TNAME — elemsizeof's name arm knows only
str/prims and answered the 1-sentinel through the `direct != 8`
short-circuit (#60 esz-1 family, outer-array leg). New N_TNAME leg
answers from the chased stamped tinfo via idxeffti. #83 caller
probes (rule: no unprobed caller row):
- append leg: UNREACHABLE on ww — `let xs: nums = [];` rejected by
  the wwstage CHECKER ("let: not assignable") even at 1 level; cs
  accepts+runs. check.ww is batch-4 scope -> documented on #83.
- alloc-empty leg: BOTH stages loud-reject the alias-typed binding
  hint ("cannot infer slice element type", check.ww:2547 + cs twin)
  — symmetric loud, alias-blind hint documented on #83.
- arr[i].field= float leg: plain [3]cell runs 0/0 byte-id; the
  alias-named outer array LOUDS on ww at its own site
  (cgenexpr.ww:11033 assign-resolver float-field bound), cs runs —
  fails upstream of elemsizeofc, documented on #83.
- subslice-of-alias-array call-arg probe: already 0/0 pre-change
  (the #60/#257 stamped-tinfo keying answers first) — the internal
  chase is presently LATENT defense-in-depth; ZERO callers enroll.
Remaining elemsizeofc internal loops (eti/esub) fold to tichase.

Byte-id: five-mains NEUTRAL vs the c2 build on identical inputs;
cs==ww on the regenerated five; 944 138/138; test-unit 288 green.
combined.ww regens ride along.
2026-06-05 21:55:51 +09:00
f7fd3e4850 wcc_ww/cgen: #62 Layer-2 (ww half) — rhsstructpayload chases the alias chain; widen-store su at entry
The wwstage tagged widen's struct-payload classify was name-keyed:
rhsstructpayload's N_IDENT arm did a bare structlookup on the local's
declared TNAME, so an alias name (ali->base) missed the registered
structinfo and the value fell past the struct arm to the SCALAR widen
arm — word0-only box payload, words 1+ zero-filled. At normal decl
order this was BOTH-WRONG-IDENTICAL with cstage pre-F1 (byte-id YES,
gate-blind; F0 m5b_match1 exit 2/2). Choke-point fix: route the arm
through structlookupchain (the name-domain twin of cstage's
su = type_chase_named, c138605) and return the REGISTERED struct
name so every consumer's re-lookup hits. All three consumers
graduate through the one funnel (proof-grep: rhsstructpayload is the
only struct-payload classifier): cgwidentaggedstorebp's struct arm
(let/assign/match), pushargsrev's widen route (the call-arg twin),
and cgreturn's needswiden (cgenstmt.ww:1022). The variant TAG still
keys on the un-chased stamped type — the member's nominal identity
is the alias (cg_tag_for_variant precedent).

Rider, same store family: cgwidentaggedstorebp now chases the source
classify ONCE at entry (post cast peels, where src is final) —
`su = tichase(src.type_)` replaces the five per-arm chases
(nested-arm su / memread su37 / >32B+cast-guard sf37 / tuple-guard
stu72 / tuple-arm stu, all reading the same src.type_), mirroring
cstage cg_widen_tagged_store's su position. dt entry loop + the
remaining in-function plain chases (castu/ceti/fti/fvt) collapse to
tichase — mechanical, excluded from c1 only because the function was
c2 scope wholesale.

Runtime graduation (banked rows .ai/impl62r_layer2_rows.md):
m5b_match1 (L2-1 norm) + m5_match (L2-2 fwd) cs0/ww2 -> 0/0 byte-id;
L2-4 3-word (last payload word) 0/0; push-twin arg row 0/0; control
m5b_match0 holds 0/0 (ken confirms at verdict). test 944: the four
K_RUN_CS rows flip to K_RUN (both drivers + byte-id), 129/129.

SURFACED, FILED NOT FIXED (task #89): the RETURN-position leg
(`return x` of an alias struct into a (void|ali) fn return) was
both-wrong-identical (cs1/ww1 byte-id) and is now transiently
cs1/ww0 byte-id NO — ww is the runtime-correct side; cstage
cgreturn's own route into the widen misses the alias (F1 c138605
covered store+push only). F1-precedent transient divergence, noted
per the L2 rows file's acceptance clause.

Selfhost byte-id: new w6c_ww on the c1-HEAD five main.combined.ww —
byte-identical to the c1 build's output (the fix is asm-neutral on
the corpus); cs==ww on the regenerated five. make test-unit green
(288). Closes task #62's ww half — fix-landed-pending-gate (#71
precedent). combined.ww regens ride along.
2026-06-05 21:46:36 +09:00
f19d0cb14d wcc_ww/cgen: collapse cgenutil.ww's plain TY_NAMED chase loops into tichase — byte-id neutral
Mechanical sweep, F2a batch 2 commit 1 (alias arc #5, rob batch-2 spec
c1; same conversion rule as batch-1 ef93b16). Every loop matching the
exact plain shape

    for (X != nil && X.kind == tykind.TY_NAMED) { X = X.under; };

(modulo variable name and one-line vs three-line layout, body exactly
the peel, guard exactly nil+NAMED) becomes `X = tichase(X);`. Census
of cgenutil.ww at 486f7f8: 63 raw `.under` lines, eyes-classified;
38 plain-shape loops converted.

Survivor enumeration (raw `.under` lines left, by post-sweep line):
- 1297 comment text only — not a read.
- 1301 tichase's own body — the accessor itself.
- 1531/1553 elemsizeofc internals — c3/B3 behavior scope (task #83),
  excluded from c1 per spec.
- 2073/2075/2078 exprprimresolved N_DOT single-peel ifs — c3/B1.
- 2719 nullableptrtag variant-scan single-peel if — c3/B2.
- 2868 flatvariantidxt structural-fallback guard (`pu.under != nil &&
  typeeq(pu.under, want)`) — one-level BY DESIGN pending task #17;
  not the plain shape.
- 2895/2904/2923-2924/2934-2935 tyassignableuntyped one-level
  unwraps — c4 acceptance scope.
- 3415/3471/3526/3674/3731/3783/3796/3861/4083/4089 inside
  cgwidentaggedstore(bp) — c2 L2-twin scope, excluded wholesale.

Byte-id evidence: post-sweep w6c_ww vs pre-sweep (486f7f8 build) on
the five HEAD main.combined.ww — byte-identical .s on every input;
cs w6c vs new w6c_ww on the regenerated combined.ww — byte-identical
all five; make test-unit green (288). combined.ww regens ride along
(cgenutil.ww embeds in w6c + wwdump).
2026-06-05 21:40:27 +09:00
486f7f87f9 wcc_ww/cgen: #77 alias-NAMED global ARRAY emit — tichase at the dispatch entry (g-fold G2)
ww half of the #77+#78 fused g-fold train; completes the family. cs
half landed as the previous commit (G1) — the two ship together, one
gated train, per the fuse ruling on both tasks.

Root: the global DATA emit walk dispatched on the UNCHASED decl tnode —
a NO-PEEL consumer (zero `.under` tokens on the path; it never learned
aliases exist). An alias-typed global array's N_TNAME matched no arm
and the documented skip-policy ate the decl: w6c_ww referenced
main.g(SB) but emitted zero DATAW → loud `w6l: undefined reference to
main.g` on every direct alias-global array row (ken NEW-1, all k_gidx*
shapes). Every other kind was already chased (letvarisstr/isslice/
isfloat/isstruct walk aliaslookup chains; the tuple gate walks tnodes;
emitarraydata/emitslicedata chase tinfo internally; letemitsize walks —
registration was never the gap), probe-confirmed: only array rows
failed ww-side.

Fix: ONE tichase at the dispatch entry, per the spec's entry-point rule
— not per-arm. Dispatch arms touched (enumerated):
  emitletdataw (cgen.ww): hoisted `dti = tichase(d.lhs.type_)` at the
    per-decl entry; the isarr8 scalar-shortcut gate and the array arm
    now key on dti.kind == TY_ARRAY (were d.lhs.kind == N_TARRAY) and
    emitarraydata receives dti; the struct zero-fill arm's inline
    TY_NAMED loop collapses into the same dti (ef93b16 precedent,
    byte-neutral). str/float/struct/slice/tuple gates unchanged.
  letpreintern (cgen.ww): the #18 [N]str array-leg gate keyed on the
    N_TARRAY tnode while its body already chased the tinfo — gate now
    keys on the chased kind, so alias-typed [N]str globals pre-intern
    their _S_ labels in decl order (label-order parity with cstage;
    the inner elem chase collapses into tichase).
For non-alias decls tichase is identity (same tinfo pointer) — the
emitted bytes are unchanged by construction; full byte-id invariant
holds (test-unit 288/288 incl. the new table).

main.combined.ww (w6c + wwdump) regenerated by `make` — diff verified
content-identical to the cgen.ww hunks, nothing else.

Graduation table committed as test/wcc/944_alias_global_decl_run.c —
23 rows x {cs run, ww run, byte-id} = 69 checks green. This table IS
the permanent guard: the path is lint-invisible (NO-PEEL — nothing for
the future peellint to see), so only a runtime+byte-id row pins it.
Rows: plain control; alias array 1-lvl read/write/decl-order; 2-lvl
read + order-permuted write (the #78 silent saved-BP rows); [4]u32
narrow-esz; scalar/str/f64/f32 2-lvl; alias-of-named-struct field w/r
(the cs SEGV-at-one-user-level row) + 3-layer + STRUCTLIT init; slice
2-lvl literal; [2]str 1-lvl/2-lvl (letpreintern label leg); def-side
2-lvl array/struct/float; no-regression holds (alias global SLICE,
alias ELEMENT [2]row). Values >255, LAST element asserted.

Probe-OUT rows documented in the test header, filed not pinned: #86
(named-tuple global init: cs checker loud-reject vs ww accept), #87
(plain tagged global: cs silent-wrong vs ww loud-reject, non-alias).
2026-06-05 21:10:35 +09:00
00f71b9781 w6c/cgen: #78 alias-NAMED global decls — let_* + DATA-emit entry chase (g-fold G1)
cs half of the #77+#78 fused g-fold train (rob spec .ai/rob-gfold-spec.md
+ ENROLLMENT RULING 2026-06-05). NEITHER COMMIT FFs ALONE — G2 (wwstage
emit dispatch, #77) completes the train; until G2 lands, ww alias-global
ARRAY rows remain loud link-ERR by design (documented below).

Root: the let_* helper family was single-peel (`u = (t->kind==TY_NAMED)
? t->under : t`) — a 2-level alias chain (or ONE user alias over a named
struct) left u TY_NAMED, so let_collect never registered the global, no
DATA was emitted, and the let_islet-gated load paths fell through to the
frame-local path at offset 0: a silent saved-BP read (probe-verified: cs
emitted zero DATAW and zero main.g references for a2/st1/t2/sl2).

Converted to type_chase_named (6 helpers, per the enrollment ruling —
probes forced let_isstr/let_isslice in beyond the spec's enumerated 4;
non-severable, ruling banked in the spec file):
  let_emit_size  (:1164)  consumers :1408 let_collect gate, :14889
                 emit_lets, :15196 let_pre_intern str-leg — all
                 top-level d->type
  let_isstr      (:1208)  consumer :3894 N_IDENT global load gate
  let_isslice    (:1218)  consumers :3894, :14987 emit_lets slice arm,
                 :15080 emit_defs loud-stop
  let_isstruct   (:1229)  consumers :1432 def registry, :14923 8B-scalar
                 short-circuit gate, :14996/:15062 struct emit arms
  let_isarray    (:1240)  consumers :1445 def registry, :14923,
                 :14975/:14997/:15071 array emit arms
  let_isfloat    (:1251)  consumers :1507 def addressability, :3908
                 N_IDENT float load (non-local branch only — locals take
                 the off!=0 branch at :3793), :14891/:15052 float emit
All consumers sit on top-level-decl or non-local-ident paths; no local
consumer exists. Corpus census: zero >=2-NAMED-layer global decl types
anywhere in lib/selfhost/cmd (all named globals are depth-1: io.vtable,
memio.stream, errno, duration, floatinfo, encoding, ...) — conversion is
identity on the whole existing-green corpus; full byte-id invariant
holds (test-unit 287/287, sizelint clean).

Condition-3 members (ruling: "own inline peel on the routed path = same
family, enroll if it fixes at the same chase" — verified: every enrolled
probe row graduates at this chase, none elsewhere): the routed-to DATA
emitters re-peeled at entry and return-0'd into the silent skip path.
Converted the OUTER-type entry resolution only:
  emit_struct_lit_bytes :14238, emit_struct_data :14363,
  emit_array_lit_bytes :14401, emit_strarray_data :14618,
  emit_array_data :14791, emit_slice_data :14830,
  let_pre_intern array-leg :15131
ELEMENT-type peels in those helpers are untouched (different axis, out
of this fold). type_unwrap itself is NOT converted (#85, explicit OUT);
its two remaining consumers (:14711/:14902) are tuple-arm-only, behind
the checker reject filed as #86.

Probe matrix (banked /tmp/implG_probes.md + /tmp/implG/): 34 rows, both
stages. Post-G1: every cs alias-global row runs 0 — a2/a2o (the #78
silent saved-BP rows), st1/st2 (silent SEGV at one user alias level),
stlit/t2/sl2/d_a2/d_st2/tsa2 (silent-wrong), s2/s2o/f2/f2s/d_f2 (loud),
u1/tsa1/a1* (held green). All ww-green rows byte-id YES. ww array rows
stay loud link-ERR until G2 (`w6l: undefined reference to main.g`).
Controls + holds (plain globals, alias-ELEMENT el1, alias-slice sl1)
unchanged. OUT, filed: #86 (named-tuple global init, checker), #87
(plain tagged global, cs silent vs ww loud — not alias-family).
2026-06-05 20:45:49 +09:00
ef93b1637e wcc_ww/cgen: collapse cgenexpr.ww's plain TY_NAMED chase loops into tichase — byte-id neutral
Mechanical sweep, F2a batch 1 commit 2 (alias arc #5, rob F2 ruling:
one chased accessor is the only spelled way to dealias; this
enumeration seeds the future peellint whitelist). Every loop matching
the exact plain shape

    for (X != nil && X.kind == tykind.TY_NAMED) { X = X.under; };

(modulo variable name and one-line vs three-line layout, body exactly
the peel, guard exactly nil+NAMED) becomes `X = tichase(X);`. Census of
cgenexpr.ww at commit 1: 71 `.under` lines — 49 one-line peels + 21
multi-line-spelled peels, all eyes-classified as the plain shape, all
70 converted (count-asserted).

Survivor enumeration (raw `.under` occurrences left in cgenexpr.ww):
- cgenexpr.ww:2886 — comment text only ("follows the resolved
  NAMED.under pointer"), no code read. Classification: not a read.
Zero code-level raw `.under` reads remain in this file.

Byte-id evidence: pre-sweep w6c_ww (commit-1 build) vs post-sweep
w6c_ww compared over a 31-input corpus — all selfhost/cmd/*/
main.combined.ww (the compiler's own full source) plus the #60/#79
probe set — identical exit codes and byte-identical .s for every
input. make test-unit green (287). combined.ww regens ride along
(cgenexpr.ww is an embedded source).
2026-06-05 20:14:49 +09:00
da81a4c86e wcc_ww/cgen: #60+#79 alias-NAMED array/slice ELEMENT paths read the chased tinfo — tichase lands, SEGV families graduate byte-id
One class: alias-blind base+esz at the array/slice ELEMENT paths —
index read/write, slice-expr, for-range, and literal-init store. The
wwstage cgen derived element size and base addressing from the
type-AST tnode; an alias-typed base (`type arr = [4]int; let a: arr`)
shows only the N_TNAME leaf, so esz fell to a sentinel (1 on the read
side, 8 on the init-store side) and the base classified as a POINTER
(MOVQ of array words, no IMULQ): m8b_idx1/range1 SEGV 139, m8b_slice1
silent-wrong past little-endian prefix-luck (m8c_slice1big exit 2),
m7c global [2]row read SEGV via the alias-blind element-is-array
classify, and (#79, ken F2a1 oracle) `type A=[4]u32; let a:A=[...]`
stored MOVQ stride-8 over a stride-4 slot — elements 2/3 landed at
0(BP)/+8(BP), a saved-BP/RIP smash masked whenever esz==8. cstage
reads everything off the chased stamped type (type_chase_named/
idx_eff, correct post-F1), so every fixed shape graduates
ww-SEGV/silent-wrong -> 0/0 byte-id.

New tichase() in cgenutil.ww: nil-passthrough transitive TY_NAMED
peel, exact twin of cmd/wcc/type.c:160-162. Routed sites, all gated on
the stamped type being TY_NAMED (non-alias paths byte-identical):

- cgindex (cgenexpr.ww): elem facts (esz/signed/float/f32) off
  tichase(n.type_); etn falls back to n for the tagged/str/slice
  classify; LEAQ-vs-MOVQ base off the chased kind; elem-is-array
  supplemented by tinfoisarray(n.type_) for alias ELEMENTS (m7c).
- cgassign N_INDEX store + compound arms (cgenexpr.ww): esz +
  elemtn=lhs (the stamped-element idiom of the N_DOT/N_INDEX arms);
  chased-kind base classify at all four LEAQ/MOVQ sites.
- cgslice + cgbasecap (cgenexpr.ww): esz, base classify, default-hi
  (TY_ARRAY -> $alen / TY_SLICE|TY_STR -> +8 len), cap word at +16;
  global-str cap keeps the #73 carve-out.
- cgforrange (cgenstmt.ww, cross-file leg: the range pin cannot green
  without it): esz, element-node synthesis off .sub (FC0 precedent),
  isarr/isslicestr classify, alen off the chased tinfo.
- cgarrlitfillbp (cgenstmt.ww, #79): an alias [count]T arrtn is the
  N_TNAME leaf (elemn nil) — synthesise the element node off the
  chased sub so the existing prim/agg/slice/tagged/narrow dispatch
  works unchanged; `...` repeat bound off the chased alen (cstage
  cg_arrlit_fill_bp receives the pre-chased bu and reads bu->alen).
  #8-PAIR COVERAGE: this is the STORE half of #8's two size-sources.
  The elemsizeofc READ half chases the ELEMENT internally (idxeffti +
  esub peel, the #8 fix) but NOT an alias-typed INDEXABLE node — that
  leg is covered at its #60-family call sites by the gates above
  (cgindex/cgslice/store/compound/cgforrange/pusharg). Remaining
  alias-blind elemsizeofc callers are enumerated as residuals below.
- bare-let classify (cgenstmt.ww, #79 rider): `let a: arrk;` with an
  alias-to-array type took the composite zero-fill cstage doesn't
  emit (cstage keys the no-init shape on the chased lu->kind: arrays
  keep the per-index-write contract; an 8B alias-array still falls to
  the single MOVQ $0 arm). Required for the loopfill_1024 pin's
  byte-id; closes the array kind of the uninit-alias divergence.
- pusharg N_SLICE (cgenutil.ww, pulled in by the same pin rule: the
  944 slice_of_alias_arg row is a distinct lowering from cgslice):
  esz, base classify, default-hi.

Tests: new 944_alias_idx_family_run (19 rows: idx/slice/range/init
controls + 1-level + 2-level + decl-order permutations + index store
+ compound (+=, *=) + #79 [4]u32 literal-init + alias `[v...]` repeat
+ uninit [1024] loop-fill + slice1big (1000 elems, values >255,
LAST-element readback, default-hi, .cap, range count) + re-slice of an
alias slice + range over an alias slice + m7c global 2D + GLOBAL
alias-slice indexed read + slice-as-call-arg; dual-stage run +
per-row byte-id; LAST elements asserted throughout). The six
944_alias_accept_run rows citing "#60 (F2 batch 1)" flip K_RUN_CS ->
K_RUN (incl. slicefield_wholeread_2lvl: its 738d7f4-era receive-spine
divergence no longer reproduces at the F1-merged base, verified
byte-id + 0/0). 989_lib_byteid checked: no DIVERGE entry graduates
(the test fails loudly on graduation; lib has no alias-base consumers
— the shape SEGVed before this fix).

NOT pinned (g-fold territory, #77/#78): direct alias-typed global
ARRAY rows. Expected state probe-verified UNCHANGED by this diff:
`let g: arr = [...]` -> ww link-ERR (no DATA emitted), cs 1-level
runs 0, cs 2-level runs WRONG (silent). The alias-GLOBAL base legs
added here (isglobalarr reclassify, global default-hi/cap) are
cs-aligned but runtime-unreachable until the DATA emit lands.

Residuals filed with the team: alias-blind elemsizeofc callers not in
the #60 pin family — cgun &a[i] addr-of (cgenexpr.ww:4638 region,
task #82), append() on an alias-typed slice local (:5287),
`alloc([], n)` into an alias-slice let (cgenstmt.ww:2159),
arr[i].field= float store (:8536); tagged-element READ under an
alias base keeps the ident-arm nullable semantics; checker
asserttyped on `untyped_lit * rangevar` over an alias slice
(pre-existing, check.ww is batch 4, task #80); uninit alias-to-STRUCT
zero-fill unchanged (correct: cstage fills composites);
range-destructure over alias-to-tuple-slice.

selfhost/cmd/{w6c,wwdump}/main.combined.ww regenerated (cgen*.ww are
embedded sources).
2026-06-05 20:14:49 +09:00
ac63951d54 w6c/cgen: #71 chained-dot walks chase alias-typed hops — direct offsets, byte-id graduation
rob probe-ruled F1 enrollment (fold-or-file decided by the dispatch
test): the chained-dot STORE walk, its READ twin, and the addr-of
sibling each single-peeled every hop's type, so an alias-typed field
(type fa = inner; outer{x: fa}) aborted the offset-folding fast arm and
fell to the generic address spine — store via cgplaceaddr
(PUSHQ/LEAQ/ADDQ/POPQ), read via ADDQ-per-hop. Runtime-correct BOTH
stages; byte-id NO vs wwstage's folded direct MOVQ offsets
(reviewer-62r diamond find). Chasing the walk hops (+ the read arm's
leaf gate and the two ptr-root sub peels) flips cs onto the fast arm =
wwstage's asm exactly.

Blast radius measured per rob's caution: bootstrap asm cmp-identical
vs the pristine 738d7f4 scratch on all five main.combined.ww, 989
lib_byteid pins unchanged (31/9/3), every control row byte-id — the
flip moves ONLY the #71 shapes.

test: 944_alias_accept_run +2 rows, both decl orders: nested store +
last-field readback; the fwd row adds the &v.y.b addr-of + deref-write
leg. Mutation-checked at the 738d7f4 scratch: both rows byte-id-diff
there; 70/70 green here.

Also graduates the reviewer-F1 slicefield rows: their `.len` readbacks
ride this walk, so the 7 K_RUN_CS rows from commit 1 flip to K_RUN
(byte-id) here.
2026-06-05 18:34:15 +09:00
c138605563 w6c/cgen: #62 Layer-2 (cs half) — widen store/push su chases the alias chain
The tagged widen's source classify (`su`) single-peeled: a 2-level
chain ali->base->struct left su TY_NAMED, so an alias-NAMED struct
union member fell past the struct arm to the SCALAR arm — word0-only
payload, words 1+ zero-filled. At normal decl order this was BOTH-
WRONG-IDENTICAL with wwstage (byte-id YES, gate-blind; F0 m5b_match1
exit 2/2). Two sites, the only widen entries: cg_widen_tagged_store
(let/assign/match BP path) and cg_widen_tagged_push (the call-arg twin
— surfaced by an F1 probe: fn((void|ali)) arg ran 1/1 both-wrong-
identical). The variant TAG still keys on the un-chased st — the
member's nominal identity is the alias (cg_tag_for_variant), only the
copy-width classify chases.

CS-ONLY half: wwstage's twin (rhsstructpayload name-keyed structlookup
+ its push twin, selfhost/cmd/wcc/cgenutil.ww:3062 vs structlookupchain
:1691) lands in F2 per the serial plan — until then these shapes are
transiently cs!=ww (was identical-wrong). Bootstrap asm cmp-identical
vs a pristine 738d7f4 scratch build on all five main.combined.ww, so
the 990-997 byte-id gates are untouched.

test: 944_alias_accept_run +6 rows from the banked set
/tmp/impl62r_layer2_rows.md — L2-1/2 norm+fwd store, L2-4 3-word width
loop (last payload word checked), the push-twin arg row, L2-5 base
control (ken's gold invariant), and L2-3 (`v as ali`) pinned at task
exit 2/2/2/1 there; 64/64 green here.
2026-06-05 18:33:47 +09:00
b9dd29706b w6c/cgen: #61 alias-NAMED struct param classify — transitive chase at the ABI choke
A 2-level alias param (`type row = st; type st = struct{a,b,c}`) fell
through the single NAMED peel at every classify site, so BOTH ends of
the call moved one eightbyte of a multi-word struct: the caller's
node_isstructarg/node_isaggarg said scalar, the callee prologue spilled
ONLY DI, and s.b/s.c read 8(BP)/(BP) — saved-BP/return-address garbage.
SILENT runtime-wrong (F0 m5_arg/m8_arg1/m8b_arg1lit: cs exit 1, ww
correct, byte-id NO).

Route the four classify chokes through type_chase_named: struct_arg_size
+ aggarg_size (shared by caller push AND the size axes), struct_float_
class (the #165 SSE eightbyte leg), and the fn-prologue param classify
pu. Caller and callee key off the same helpers, so the pair cannot
half-land. cs converges to wwstage's already-correct asm — all probe
rows graduate to byte-id YES; bootstrap asm cmp-identical vs master
(2-level alias params unused in selfhost).

test: 944_alias_accept_run +5 rows — fwd-ref / lit-init / 40B
5-eightbyte aggarg leg / f64 struct_float_class leg, every row checking
the LAST field with distinct values, + base-named control. Mutation-
checked at 738d7f4: the four alias rows exit 1 (the silent-wrong
signature) and byte-id-diff there; 55/55 green here.
2026-06-05 18:33:47 +09:00
9bd0d8bc81 wcc: #5 F1 promote type_chase_named + transitive-peel acceptance align-cs-up
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.
2026-06-05 18:33:47 +09:00
738d7f481c wcc/check: #62 typedecl layout is decl-order-INDEPENDENT — demand-resolve forward refs + loud cycle guard (#69)
check_file resolved typedecl bodies in file order with an eager
under->size copy, so any body referencing a typedecl declared LATER
read its size-0 placeholder and baked it in: alias size 0, tagged-
union maxsz 0 (the F0 m5_match $48-frame under-allocated box), struct
field offsets collapsed, array element stride 0 — a whole cstage-only
family (7 size()-probe rows, all cs-fail/ww-pass pre-fix). wwstage's
demand-driven tinfofornode was order-independent on every row, so this
aligns cstage UP to the measured runtime-correct side (the #263-era
ruling; rule 10's align-down governs acceptance surface, not layout
correctness). Oracle: ken /tmp/ken_62_oracle.md — union size is 8B tag
+ roundup8(max CHASED member size), a fixed point over the module,
never a function of decl order.

resolve_typename now resolves a referenced-but-unresolved typedecl on
demand via resolve_typedecl (cycle-guarded by Type.resolving); the
pass-1.5 loop funnels through the same helper. No consumer can see an
unresolved placeholder by construction.

CYCLE GUARD — #69 ABSORBED into this rider (rob's rider condition):
true typedecl cycles now LOUD-reject on BOTH stages — "circular type
dependency" — mirroring harec's in_progress check (ref/harec/src/
check.c:4767 "Circular dependency for '%s'"). Pre-guard: cs silently
sized cycles 0; wwstage HUNG on an alias cycle (`type a = b; type
b = a` — ken's hang probe /tmp/ken62/c1_cycle.ww, killed at the 20s
timeout) and stack-overflowed on a struct value cycle. The check sits
at the VALUE-position size consumers only (alias root, struct field,
array elem, tuple member, union member), so the legal pointer
self-ref (`type node = struct { next: *node }`, the io.stream shape)
stays accepted, byte-id. wwstage gets the twin tinfo.resolving flag
(lib/ww/typ.ww) + circularnamed in check.ww; its arm loud-STOPS
(os.exit) rather than accumulating — wwstage's AST-level alias
walkers (resolvealias, aliaslookup chains) follow TNAME->TNAME by
name, blind to the tinfo table, and spin on a cyclic alias graph even
after the table edge is cut to tyerr (measured); cstage accumulates,
its single-peel ternaries cannot loop.

TWO-LAYER SPLIT — this is ONE bug number (#62) deliberately split
across THREE commits (this rider + F1 + F2), per ken's sizes-correct ≠
payload-correct proof: in NORMAL decl order both stages size the box
correctly (16/24, frames $64) yet both still run exit 2 — the box
STORE is word0-only, a chase-blind copy-WIDTH lookup in cgen, NOT the
type table. EXPECTED-FAIL after this commit: m5b_match1/m5_match stay
exit-2 both stages (now byte-id BOTH orders; pre-fix the fwd order was
$48-frame divergent). The Layer-2 sites and destinations:
  - F1 (cstage): cg_widen_tagged_store single NAMED peel,
    cmd/w6c/cgen.c ~2464 — the type_chase_named census family.
  - F2 (wwstage): rhsstructpayload bare name-keyed structlookup, no
    alias chase, selfhost/cmd/wcc/cgenutil.ww:3062 (structlookupchain
    :1691 already exists).
Banked runtime payload-readback rows for F1/F2: /tmp/impl62r_layer2_rows.md.

Test 944_alias_decl_order_size_run: every size class pinned in BOTH
decl orders (sizes, named union, struct field offsets, array elem,
2-level chain — norm + fwd twins, prefix-luck-breaking last-word
readbacks), 3 cycle BUILDERR rows + the legal ptr-self-ref row,
(void|base) no-regress control; dual-stage + per-row byte-id (arrelem
rows byte-id exempt: pre-existing #60 index-over-alias divergence,
order-independent, cited at the rows). lib/ww/typ.ww is an embedded
source: both main.combined.ww regen'd + committed (freshness gate).
2026-06-05 10:46:17 +09:00
d14a23b85e cgen: #57 in-cap tuple cursor fill keys on the DECLARED element type — tagged elems from concrete rvalues widen, both stages
The N_TUPLE literal's stamped type is CONSTRUCTED from its elements
(check.c N_TUPLE keeps untyped/concrete element types; assignability
is consumer-side), so the in-cap cursor fill — count
(tuple_lit_gpwords/tuplitgpwords) + push (tuple_lit_push_elem/
tuplitpushelem) — never saw the DECLARED tuple type. A declared-TAGGED
element whose expr is a concrete rvalue (`return (5: size, 9)` into
(un16, size)) counted ONE word and skipped the widen entirely: 2 words
sent against the receiver's declared 3-word walk, every later element
read garbage. Both stages, byte-identical, gate-blind (ken /tmp/ken57
p8/p9: t.1 read entry-junk). The let-literal twin
(`let t: (un16, size) = (5: size, 9)`) and the tagged-SECOND-elem
shift broke identically (probes q1/q2). The over-cap (sret) arm
already walks declared params (#240/#22b) — only the in-cap path was
declared-blind.

Fix threads the declared tuple type into the ONE shared helper pair
and its two loop sites:
  - tuple_lit_gpwords/tuplitpushelem take the declared elem type;
    declared-TAGGED + concrete rvalue widens into the shared tagged
    scratch (cg_tagscr_slot/tagscradd + cg_widen_tagged_store/
    cgwidentaggedstore, the cgreturn tagged-@retscr shape) and pushes
    the box words; declared-TAGGED gates the SSE row off (a (void|f64)
    box rides INTEGER eightbytes). Tagged->tagged subset (eslot
    mismatch) louds — the #23/#40 widening-remap family.
  - cg_tuple_lit_to_cursor/cgtuplelittocursor grow a decl param;
    cgreturn's in-cap N_TUPLE loops thread cg_ret_type/c.fnret.list
    (the same pp/pt walk its over-cap arm does); the N_LET in-cap
    tuple arm passes the declared type for an N_TUPLE rhs; the bare
    cgexpr route passes NULL/nil (emission unchanged).

Ident-elem sources keep the existing slot-load push byte-identically
(t57_ident_no_regress); the CALL-elem tripwire stays loud (#41,
t57_loud_call_elem). RESIDUAL FILED, not folded (rule 11): the
N_MASSIGN destructure-reassign literal rhs routes through the bare
cgexpr path (decl=NULL) and stays silent-wrong — probe q5_massign,
task #64, cited at the massign arm both stages. The annotated
multi-let spelling (`let (a, b): (un, size) = lit`) does not parse
(both stages), so N_MLET has no declared-literal route.

941 rows t57_*: return (named + inline union), let-literal, tagged
second elem, float payload, bare-untyped payload (rides the #33
chooser through the new wire), ident anchor, loud CALL tripwire;
ken's adversarial shapes (tagged-MID elem, two tagged rvalue elems
incl. void, plain-f64 SSE coexisting with a declared-tagged box), the
in-cap/over-cap boundary loud (k57d), and the NEW #57 tag-remap loud
pinned. Pre-fix at e8977a4: p8/p9 rows exit 1, q1_let exit 1,
q2_mixed exit 2.

Task #57.
2026-06-05 09:42:50 +09:00
80e7096f25 cgen: #50 append/insert tagged-dst VALUE boxes PRE-grow, both stages
The #34 value-store dispatch's tagged arm ran the widen choke-point
(cg_widen_tagged_store / cgwidentaggedstore — which cgexprs the value
internally) AFTER cg_append_grow, so a value expression reading the
destination (xs.len) saw post-grow state. Hare evaluates arguments
before the call's effects; the scalar/str/slice arms already ordered
value-first. insert() inherited the bug through its desugar-to-append
(9861f73) — one boxing choke-point, so this is the whole fix. Box the
value into a fresh per-site @apptagscr frame scratch pre-grow (zero,
BP-rooted widen store), then raw-copy the finished box into the new
slot post-grow.

Consumer evidence: regex fold-5b's {,0} rows (reviewer-5b's mutant;
ken's corrected-root matrix /tmp/ken_silent, f50v4_tagged exit 15 both
stages, byte-id, gate-blind). lib/regex.ww:643-647's pre-bound
split_target workaround comment GRADUATES with this commit; the
comment update itself rides the next regex touch per the standing
rule.

test/807: 57 -> 84 fixtures. New rows: tagged_pregrow_val (f50v4
exact), scalar_pregrow_val (no-regress control), tagged_selfref_val,
tagged_str_payload, tagged_regex_minrep (the {,0} shape standalone),
tagged_append_pregrow_val (direct append, the fix site),
tagged_realloc_selfref_loop (ken k50a: old-base read across actual
rt_ensure base moves), tagged_seq_positions (ken k50b: sequenced
inserts at 0/mid, each len-reading) and tagged_void_variant (ken k50c:
tag-only box through the fresh scratch). Mutation at e8977a4 fails
exactly the five eval-order-discriminating tagged rows, both stages.

The STRUCT-LITERAL value arm keeps its post-grow field-expr fill —
same eval-order class, different arm; filed (ww-core #59), cited
at-site, not folded.
2026-06-05 09:31:23 +09:00
24e02b259c wcc/check: inferred-let tuple literal carries its slot-layout size — 0-size local smashed saved BP/RIP (#44)
The N_TUPLE expr arm built its TY_TUPLE with size 0 (only the
annotated N_TTUPLE resolve_type route computed the layout), and
type_default passes TY_TUPLE through, so an inferred
`let t = (4: size, 2: size)` planted a 0-size local at offset 0 —
the element stores landed on the saved BP/RIP and main segfaulted
on RET (cstage; the arg shape instead fell to the global-symbol
path and link-failed). wwstage (exprtype N_TUPLE -> tinfofornode)
was runtime-correct throughout — cstage aligns UP to it; all
fixed shapes are now byte-id. Slot rule mirrors the N_TTUPLE twin
and cgen tuple_eslot, with untyped elements sized at their
type_default (element types stay untyped for the consumer-side
assignability contract).

7 table rows in 941 pin the class (cast/bare/mixed/float elems,
destructure-from-local, call-arg, nested); each fails at master
e8977a4 cstage (segfault or link-fail + byte-id NO).
2026-06-05 09:08:01 +09:00
e8977a413d cgen: #38a chained-dot TAGGED leaf — cursor read + widen store, value-chain + ptr-chain, both stages
A tagged leaf at the end of a dot chain (depth >= 2) fell to the
one-word scalar tail in FOUR walkers — the value-chain read spine,
the value-chain assign walker, the chained-*struct read branch, and
the chained-*struct assign branch — in BOTH stages, byte-identical,
gate-blind:

  READ:   one MOVQ pulled the TAG word only; is-tests passed by
          tag-luck while as/match/let consumers read stale DX/CX/R8
          as payload (ken x5c: o.r.min as size added entry-zero DX —
          want 28 got 20; ken b8 same through a mid-chain *rep).
  ASSIGN: one sized store landed the rhs ON the tag slot, payload
          kept its old bytes (ken x5d: `o.r.min = 8: size` made the
          following is-test false).

The single-dot arms were already correct — each fix is that arm
verbatim at the chained site:

  read:   the tagged cursor (AX=tag, DX=val0, R8=val2 before
          CX=val1 — the base may live in CX/AX; >32B box leaves its
          ADDRESS in AX, the #37 mem-read convention). wwstage rides
          the existing cgloadtaggedfield helper; the *struct-base
          branches stage AX into BX first (cursor targets AX).
  assign: full slot rewrite via the shared widener
          (cg_widen_tagged_store / cgwidentaggedstore, base BX/BP),
          plain `=` only — compound on tagged stays loud (#133).

The nested-LITERAL half of the old #38 filing dissolved: the
canonical fill's tagged-field widen delegation (#23, 413aafa) already
covers any depth — x5c's literal init emits a correct tag at HEAD;
only the chained READ/ASSIGN halves were live (probe-proven, asm at
/tmp/pB).

Probes converge byte-id and runtime-correct: x5c/d/e (28), depth-2
(b3/b9), (void|str) 32B box + match readback (b5), *outer root (b7),
mid-chain *rep (b8), non-tagged control (f38a). 938 rows chained_*
pin all six shapes. Residuals filed, not folded (rule 11): the
INDEX-spine sibling xs[i].min payload read (task #58); tagged-field
GLOBALS stay loud at DATA emit (#129 A.2), so the walkers'
global arms are mirror-only today.

Task #38.
2026-06-05 08:39:40 +09:00
9fad59354f cgen: #33 wwstage untyped-source variant match — mirror cg_variant_match's type_assignable arm
A bare untyped init into a tagged union resolved its variant through
taggedvariantindext's str/slice SHAPE fallback, whose first
non-str/slice variant can be void: `let e: (void | size) = 5` stored
tag 0 while the is/as side resolved size to 1 — runtime-FALSE is-test
on wwstage only (cstage resolves untyped sources in cg_variant_match
:801 via type_assignable; cs=0/ww=1 on ken's f33, divergent asm,
gate-blind).

Fix adds the untyped-source arm at the top of cgvariantmatch — the
single flatvariantidxt pass-1 predicate, same funnel position as
cstage — backed by tyassignableuntyped, a focused tinfo-keyed mirror
of type_assignable's untyped→typed subset (cmd/wcc/type.c:355-370)
plus its concrete→tagged variant drill (:316-324). Typed/loose
sources keep the shape fallback unchanged; tuple-in-union keeps its
AST-shape loud-stops (#241/#242, checked: TY_TUPLE is never untyped).

Probes converge byte-id: f33 let-init, assign-after-void, bool-leading
skip, bare arg widen, untyped-str, cast no-drift control. 938 row
untyped_int_bare_widen pins all of them (pre-fix ww_run=1 at 322667b).

Frees the fold-5b at-site #33 workaround (lib/regex/regex.ww:772
cast-form pin `min = (0: size)`) — the bare Hare-verbatim `min = 0`
spelling now tags correctly; the regex comment update rides the next
regex touch.

Task #33.
2026-06-05 08:38:20 +09:00
322667b820 cgen: #35/#46 Family C — tagged transport from deref/cast/unwrap sources goes mem-based, both stages
A tagged value reaching a transport consumer from a DEREF, CAST, or
UNWRAP source materialized as ONE scalar word — the N_UN(STAR) arm's
localloadop pulled word0 (the tag) and every cursor consumer then
transported garbage payload (cs pushed stale DX, ww stored 0/garbage;
divergent asm AND silent-wrong on both stages — ken f35 asm proof +
ken37v D3a/D3b). Consumers × sources × sizes all wrong: arg push,
let-init, assign, return, match scrutinee, as, widen — from *p at
16/24/32B, from identity/widening tagged casts, and from `?`/`!`
whose success variant is itself tagged (nested box: payload words
dropped). `is` and ww-match-16B passed only by stale-register luck.

Fix extends the #37 mem-based machinery (26d3754) below the 32B cap
instead of re-deriving:

  - cg_tagged_memread/taggedmemread: an N_UN(STAR) deref of a
    non-nullable tagged box is mem-based at ANY size — the pointer
    value IS the box address. The N_UN emitter skips the scalar load
    (joins the TY_FN/TY_ARRAY value-is-address skip); the existing
    size-generic memread arms in widen-store/match/as pick the source
    up unchanged, `is` loads the tag through the address.
  - arg push: the memread fatal becomes the mem-based push (words
    high→low from (AX)) — closes #35's word0-only push and wires the
    33-48B INDEX/DOT loud as a side effect. Pop side drains via
    pushargsrev's returned word count, unchanged.
  - cg_tagged_castpeel/taggedcastpeel: tagged→tagged casts are
    transport-transparent; peel at the call-arg widen decision and the
    widen-store entry so the ident/deref arms see the carrier and the
    remap keys on the operand type. The identity-only subset
    (cg_tagged_idcastpeel) applies at is/as/match, which key variant
    indices on the scrutinee's own type; surviving non-identity casts
    die LOUD there and at the widen cursor arms (rule 7) instead of
    word0 garbage. The peel also wires 929's >48B memarg same-type
    cast row (place resolves post-peel; loud pin flips to a run row).
  - tryprop/tryunw: IDENT sources load the cursor from their slot,
    memread sources from the box address (≤32B); >32B non-call and
    global tagged idents die loud (rule 7). A TAGGED success variant
    shifts the nested box past the outer tag (twin of the #241 tuple
    shift) — closes the unwrap-source half (ken unw16).
  - wwstage alignment UP to the cstage type-keyed routes:
    rhstaggedabicall admits N_UN deref + N_TRYPROP/N_TRYUNW (stamped
    type), matchscrutt carries the N_UN stamped type (spill size +
    variant indices; was nil → tag-0 clamp + mis-sized spill),
    cgreturn routes memread sources through the widener (the
    fall-through wrapped the un-deref'd POINTER as payload), and
    pushargsrev's aistagged gate admits the deref kind.

Emitters and consumers ship as ONE commit: they share the memread
contract, and splitting opens a transient window where a wired
emitter hands an address to an unwired consumer — the #61/#37
route-sharing fuse. The test flips ride along because they pin the
flip itself: 941's two #37 deref loud-symmetry pins become run rows
(the loud is now wired), 929's fail_rvalue_cast becomes
memarg_idcast_peeled.

No-drift bar held: ≤32B IDENT/INDEX/DOT sources emit byte-identical
asm vs master 4c46d3a (probe corpus nd1: ident let/match/arg,
struct-field, indexed element, call, nullable, ident-widen — both
stages IDENTICAL); is-on-deref is incidentally byte-identical too
(the tag load moved from the emitter to the consumer).

Tests: 941 grows 252→272 checks — ken's exact f35/D3a/D3b shapes,
each consumer × source × size cell (16/24/32/56B, str + struct
payloads), neighbor-guard row, identity-cast arg, widening-cast let
(payload checked — the old cs pass was is-only luck), success-first
unwrap-to-tagged + ident/deref unwrap, the 56B slice-deref let+match
flips (payload-pinned), and 2 rule-7 loud pins (global tagged `?`,
cast-to-third-union). Reviewer-C rows commit ken's remaining
adversarial shapes (gC1 deref-wrapped cast arg, gC2 void-variant
deref, gC3 slice-element-pointer deref, gC6 56B memarg-leg deref arg
— that one a regression pin, already place-resolved at base), a 40B
deref ARG (the 33-48B mem-push leg, silent word0 at base), and a
multi-arg pop-balance row (tagged-deref arg mid-list, called twice —
the original #35 1-push-2-pops symptom). At base 4c46d3a the impl
rows fail 53/254 (silent-wrong exits, cs≠ww asm, missing louds); the
reviewer rows kill at base too (flip rows LOUD, others wrong-exit)
except the gC6 pin. At HEAD 272/272 + 929 22 rows + test-unit 284.

Residuals (filed separately, pre-existing): #216 success-tag
divergence — error-FIRST unions emit CMPQ $1 (cs s_tag) vs CMPQ $0
(ww first-param) and ww's first-param success-type read misses the
tagged shift; match-expr tagged yield is cstage-checker-rejected
while ww runs it (the #34/#43 acceptance family); `as` binding a
STRUCT payload at let-init stays loud ("aggregate init from unhandled
rhs shape"); wwstage tagged-source arg-widen-into-WIDER-slot (the
pushargsrev #21-comment out-of-scope boundary, task #55) — the deref
leg of that family is now cstage-correct via the widen-store memread
arm but stays wwstage-silent-wrong (joins the pre-existing INDEX leg;
ident leg runs by prefix-luck under-push); truncating scalar cast as
a box payload drops the conversion (task #56, both stages, untouched
by the peel — scalar→scalar casts are never peeled).
2026-06-05 07:39:24 +09:00
4c46d3afde cgen: #49 aggregate-ASSIGN word0-only family — one mem-to-mem funnel (cg_aggcopy), both stages
Whole-aggregate reassignment `b = a` fell to the N_ASSIGN scalar tail
and copied ONE MOVQ — word 0 of any struct/array/tuple — in BOTH
stages, byte-identical, gate-blind (ken f49_min asm proof; latent
because lib style is let-init, whose #265/#268 copy is full-width).
Same class at three more positions: struct-lit FIELD init from an
ident source (`outer{.., r = r}`, the #38 non-tagged half), the deref
place `*p = s` (#31-A), and the module-let global `g = a` / `g = pt{..}`.

Fix: extract the C1.25 assign-resolver word-copy tail verbatim into
cg_aggcopy/aggcopy — the ONE place-resolved (SI)->(BX) aggregate copy
— and wire it at the N_ASSIGN ident-aggregate arm (local + global),
the deref-place divert into the existing resolver aggregate arm, and
the structlit-fill aggregate-field arm, all fed by aggarg_srcaddr
(the closed #265/#268 dispatch). The new arms key on the FULL alias
chase (type_chase_named / chased stamped tinfo, the #22 precedent) in
BOTH stages — the region's single-peel `lu`/`fu` would miss
`type b = a; type a = struct` on cstage while the wwstage twin fired
(ken R1, gA3b: master cs ran the word0 corruption, exit 2; now 0).
Non-addressable aggregate rhs (tuple-lit, unhandled call shapes) dies
LOUD (rule 7) instead of silently truncating: #31-E `*p = (3,4)` and
#31-G's deref flavor `*p = mk()` are now loud both stages (the INDEX
flavor `a[i] = mk()` stays in the legacy INDEX arm — receive
machinery, not this funnel; still filed under #31). #31-B rides: the
cstage-only <=24B gate before cg_structlit_fill_bp is lifted (the
wwstage twin never gated — a >24B literal reassign was
cs-zero/ww-filled, rule-10 break). Global structlit reassign rides
the existing DST_GLOBAL fill machinery.

Unsplit (rule 11): the assign arm, fill arm and deref divert all
route through the one new funnel (cg_aggcopy + aggarg_srcaddr) in
both stages; splitting by site or by stage would ship a transient
cs!=ww (gate-red) or a funnel with no consumer.

941 t2_reject_chain_arg: the row's tuple-LITERAL field fill now louds
at the #49 fill arm before reaching the pinned ARG-site reject; the
fill switches to an ident source (newly working via the fill arm) so
the original arg-site pin still fires.

test/wcc/812_agg_assign_width.c: 17 runtime-readback rows (the only
oracle for a gate-blind class) + per-row asm byte-id; every row fails
at 7545bf7 (ken matrix f49_min/f49b/f49c/fA_16b/f38b + gA3b/gA6 +
impl-A probes; reviewer-A re-probed 5 rows + the gA2 12B shape at a
master git-archive scratch). Alias rows use FIELD-WISE init: the
struct-LIT spelling louds earlier at the pre-existing task-#7
aggregate-let bound on wwstage (the #5 alias-arc's hole, not this
funnel's). Reviewer-A amendment (test-only, K5 self-certify): add
the ken-gA2 odd-size row (12B {u32,u32,u32}, maxalign 4 — pins the
MOVL tail; master both stages exit 3) and gA4's neighbor guards on
the deref row, completing ken's validated matrix in the committed
suite.
2026-06-05 06:18:45 +09:00
7545bf7dcd regex: fold 5b — repetition {m,n} (the { arm + run_thread inst_repeat + rep prefill)
All three loud bounds flip: the { metachar (ha:368-402, inclusive
advance bound), the run_thread inst_repeat arm (ha:669-684, is/as
verbatim per the #42 fence), and the search rep_counters prefill
(ha:763-765, count-loop respell of the sized-fill alloc). The
deferred-metachar table EMPTIES — every metacharacter compiles; the
POSIX class body is the only loud surface left in lib/regex.

Two silent compiler finds surfaced and filed, respells drew-signed:
#49 (whole-struct assign from a match binding w/ tagged fields
corrupts them context-dependently — the parse_repetition unwrap goes
field-wise in-arm) and #50 (insert() grows the dst before evaluating
its value arg, +1 split mis-target on Hare's len(insts)+2 payload —
pre-bound, the '?'/'|' arm convention).

Activation table: the {m,n} matrix (+test.ha:443-460) incl. the
open-ended (0,7) and {,0}de rows, the \{ \} escape pair, the :635
5a carve-out, the {0,}/{1,}/{0,1} twins (cross-spelling agreement
with their 5a */+/? siblings), the Various composed rows minus the
[[:class:]] row (POSIX abort, fold-4 ruling), findall fo{2,}, plus
ww-added multibyte {2} and long-input {1,} stress rows.
2026-06-05 04:50:27 +09:00
c0c15945be regex: fold 5b — parse_repetition leaf (ha:486-545)
Dead until the { arm lands (tranche-A precedent); 13-row direct
private-fn table, error texts byte-exact. Hare's verbatim
((void|size),(void|size),size) tuple return can't cross a union
boundary yet — cstage's (tuple|error) return store is cgen-unwired and
wwstage's variant-match rejects the tuple case arm (filed #47) — so it
respells as the private repparts struct per the scope-fold5 §3
pre-signed fallback; graduates back to the tuple when #47 closes.
Riders: call-result .N tuple read loud-rejects (filed #48, bind-first
local); strings.index's standing i32 convention (#8) stays internal,
widened at each size boundary; ha:494's same-name re-bind is rejected,
second local feb.
2026-06-05 04:48:56 +09:00
26d375410e w6c+w6c_ww: >32B tagged INDEX/DOT reads go mem-based — box address in AX (#37)
A tagged box wider than the AX/DX/CX/R8 cursor (size > TUPLE_GPCAP*8)
read via N_INDEX or N_DOT now leaves its ADDRESS in AX — joining the
sret-call mem-based class the #38b gates already speak — and every
cursor consumer branches on one shared predicate (cg_tagged_memread /
taggedmemread) before reading AX as the tag. <=32B keeps the cursor
byte-for-byte (32B-at-cap asm proven identical to base on both the
t.N and INDEX routes).

Emitters: N_INDEX ident+fallback arms, N_DOT tuple-element (flips the
#22b loud bound), N_DOT struct-field + ptr-chained-field (were silent
clamps at 32B); wwstage twins gate cgloadtaggedfield at the helper
choke-point. Consumers: match spill x2 and the widen-store subset +
nested arms (the let/assign/return-widen/arg-widen/vararg choke-point)
copy the box from memory, then share the existing zero-pad + tag-remap
tail; `is` loads the tag through the address; `as` spills mem-based.
Rule-7 loud bounds replace silent cursor garbage for the non-mem-based
>32B kinds, `?`/`!`, and the 33-48B in-reg tagged arg push (mem-based
push stays the #35 family); the exact-type >32B return passthrough
from INDEX/DOT flips from its #38b loud into the widener route. The
pre-existing >48B memarg stack blit (cgplaceaddr) never used the
cursor and is pinned unchanged.

Reviewer-37 amendment: the non-mem-based >32B loud was ONE-SIDED on
two wwstage routes — cgwidentaggedstorebp had no fall-through guard
at all (`let w = *p` on a 56B box: cstage loud, wwstage silent word0
truncation), and cgmatch's guard keyed on matchscrutt-resolved
spillsz, which defaults under cap for kinds matchscrutt can't resolve
(N_UN deref et al), so `match (*p)` slipped it the same way. Both now
loud off the stamped src/scrut type_ (the kind-blind key cstage
already uses), restoring the rule-10 symmetry the body claims.

Emitters and consumers ship as ONE commit: they share the memread
contract, and splitting would open a transient window where a wired
emitter hands an address to an unwired consumer (silent garbage) —
the #61-precedent route-sharing fuse. The CX-global-tuple-base LEAQ
arm is TRIPWIRE wiring: a >32B tagged global-tuple element is
unreachable today (module-level tuple inits are int/str-literal-only;
tagged elements loud at the DATA emit), and the LEAQ keeps the same
base_reg generality as the cursor walk it replaces (ken note, task
record).

This was the last 5b compiler gate: `match insts[pc]` on the regex
inst union (inst_lit|inst_repeat, 56B) was silent-wrong gate-blind
byte-id on both stages (payload words 3+ dropped past the R8 clamp).
test 941 grows 165->200 checks: the #22b BUILDERR pin flips to a
runtime row, plus the 56B driver match, str+nested-tagged payload,
let/is/assign, indexed return, widening (identity and reversed-order
remap), 56B memarg, ken's X1 composition row, 32B-at-cap INDEX
boundary, the ptr-chained p.f match (BX-arm) and (*p)[i] fallback-arm
rows, and the two deref loud-symmetry BUILDERR pins. At base f272068
the 11 bug rows fail (2 BUILDERR flips + 7 silent-wrong + 2 missing-
loud pins, both drivers); the anchors pass. Oracle: ken PASS at
bf21964b pre-amendment; re-bind on the amended tree pending (source
bytes changed: cgenutil.ww/cgenexpr.ww louds + combined.ww regen).
2026-06-05 04:06:19 +09:00
f272068940 test/run: missing test binary is a loud FAIL, not a silent SKIP
A test/wcc/NNN_*.c with no Makefile-wired binary used to write a SKIP
status, count toward "all N tests passed", and leave MAKE_TEST_EXIT=0.
Two live instances: 953_arrlit_slice_run skipped under a green gate for
weeks (committed unwired at bf1037d, wired in the previous commit), and
a 938 hit the same hole mid-gate. The runner now treats a missing binary
as FAIL naming the file and the target to add, touches the .fail marker,
and so flips the summary + exit code. Closed by construction: make test
builds every $(TESTS) target before the runner walks test/wcc/*.c, so
the missing-binary path is reachable only by an unwired file — there is
no legitimate missing-binary SKIP to preserve. Intentional skips keep
their existing visible forms (UNIT-mode non-enumeration; in-test per-row
skip messages), neither of which folds into the pass count.

Count math: the summary N is unchanged (a SKIP already incremented ran);
what changes is honesty — pre-fix "all N passed" could include silent
skips, post-fix every counted test actually executed. A bare sh test/run
without the make-built binaries now fails loud instead of green-skipping
the whole suite.
2026-06-05 03:01:32 +09:00
ec7e8af6e9 Makefile: wire 953_arrlit_slice_run (committed unwired at bf1037d)
The test .c landed with the #25/#31 fix but its $(BIN)/test_arrlit_slice_run
target was never added, so the runner SKIPped it on every `make test` since
— while it still counted toward "all N tests passed". Wiring per the
953_arraytoslice_run pattern; the test passes 13/13 at HEAD (cstage run +
cs==ww byte-id + reject rows). The runner-side hole that let an unwired
test skip silently into the pass count is closed in the follow-up commit.
2026-06-05 03:00:08 +09:00
a72e8158c7 w6c+w6c_ww: over-cap (sret) tuple returns carry tagged elements — MEMORY-class box copy (#22b)
The over-cap SEND gains a per-element tagged arm: a declared-TAGGED
element copies its whole box mem-to-mem through the sret pointer
(@sretarg reloaded into DX, eslot/8 AX-bounced word pairs) from its
ident-local slot — cgexpr can't source it (tagged ident load is
word0-only; the AX/DX/CX/R8 box cursor would collide with the DX
dest-base reload). Non-ident/widening sources stay loud (call-source
is task #41 per rob's loud+filed ruling, widening #23/#40). The
receive legs were already eslot-generic from #22a except the wwstage
MASSIGN sret copy-out, whose wide=(str||slice) key dropped a tagged
element to the scalar 8B branch — re-keyed to the accessor's eslot>8.

The over-cap DECISION now rides cg_sret_retsize/sretretsize on the
DECLARED return type — the same SSoT the prologue @sretarg wiring and
the caller use. The old expr-shape count disagreed with the classifier
for declared-tagged elements with unwidened-payload exprs: the emit
took the register path against an sret-reading caller — built clean,
ran garbage, BOTH stages, byte-id, gate-blind (941 b22_reject_widen_
skew). A rule-7 net behind the branch catches any future inverse skew.
The same classify/emit class on the LET side: wwstage's cgexpr cursor
materialiser counts a void literal elem as 0 words, so a void-bearing
over-cap literal init slipped its loud and fell past every store arm
to NOTHING (cs louded via the ken-R1 net; ww ran silent-wrong) —
wwstage gains the same net. foff advances ride tuple_eslot/tupeslotn
(THE accessor): ken's K4 proved the old wide?esz:8 void-elem 8-advance
was a LIVE silent miscompile at base (declared-void-elem over-cap
return, send wrote +8 where every receive walks the 0-slot).

The unwiring also made >32B boxes transportable, exposing the t.N
tagged read's unbounded k<eslot/8 cursor walk: cstage OOB-indexed
tuple_rseq[4] (invalid asm), wwstage tupreg-clamped k>=3 to R8 (silent
payload-word-3 drop). Bounded loud both stages; the mem-based box read
is #37's fold (transport itself is size-generic and correct — 941
b22_big_box_mlet). One commit by the #61 route-sharing precedent:
unwiring the send is the only thing that makes the ww massign arm,
both skew nets, and the >32B read reachable — the one split order
that avoids a transient silent-wrong build (send last) lands those
arms DEAD, unexercisable by any test until the unwiring commit, so a
defect in them would bisect to the wrong commit; fused, every arm
lands with the 941 rows that pin it, preserving the #22a exit
invariant (zero silent tagged-tuple paths: correct-via-accessor or
LOUD with a task cite; proof-greps in-tree — tuple_ebytes/tupebytes 0
hits, all remaining tuple_rseq/tupreg walks cap-validated upstream).

Driver: regex fold-5b parse_repetition ((void|size),(void|size),size)
= 5 GP eightbytes > TUPLE_GPCAP — pinned end-to-end (construct, send,
let/MLET/MASSIGN receive, t.N is/as, destructure) plus head/mid/tail,
str+tagged and SSE-over-cap mixes, `_` skip, and the reject pins: 941
165/165 (15 new rows), test-unit 281/281. ken runtime-oracle PASS x2
(independent hand-math layout audit, K1-K5 probes); reviewer-22b
amendments (test-only): ken-K5 32B-exact box-at-cap row, both-tagged
2-elem row, chained-sret row, #37-family cite pinned in the big-box
experr. Residuals filed: #39 return-forward divergence, #41
call-source tripwire, #37 mem-based box read.
2026-06-05 02:47:14 +09:00
f88dbb01e2 wcc_ww/check: inferred struct-lit let plants the synthesized TNAME — field(SB) name-leak + tagged-field assign bound (#24)
For an annotation-less `let p = pt{...}` checkletassign planted exprtype's
N_STRUCTLIT result — the struct decl's BODY node (N_TSTRUCT, per #66) — as
the let's type. Every cgen local-arm dispatch (cgdot read, cgassign
tagged-field store, the alias peel) is N_TNAME-keyed, so the body matched
no arm: field reads fell to the module-qualified fallback and emitted the
FIELD NAME as a global symbol (MOVQ f(SB) — link-fail, #211 name-leak
family; silent corruption if a same-named global exists), and a tagged-
field assign fell to the assign-resolver TY_TAGGED loud bound. Both PG5
wwstage symptoms, one root; plain structs leaked too. Normalizing the
inferred binding to the synthesized TNAME (mktname + tinfofornode stamp)
routes every consumer down the already-byte-id annotated path. cstage
needs no twin: check.c:1477 clet carries Sym.type (tinfo) and its
emission is annotation-invariant (probed). Test 811: 10 rows x 2 drivers
+ 10 asm-byte-id; pre-fix wwstage link-fails every unannotated row
(incl. the `...` autofill and parenthesized forms; nested s.f.g ran
but cs!=ww asm).
2026-06-05 02:39:09 +09:00
413aafa599 w6c+w6c_ww: tagged-union struct-lit payload fills via the canonical fill (#23)
The widen choke-point's struct-payload arm carried its own inline
N_STRUCTLIT field loop -- a parallel fill that drifted from
cg_structlit_fill/cgstructlitfill: no tagged-field widen arm, so a
(void|T)-typed field's raw scalar landed in the field's TAG word
(silent truncation past the first tagged field, both stages,
byte-id, gate-blind; prober-9 PG5). Delete both loops and delegate
to the canonical fill at the payload base: one fill path, one widen
path, mutually recursive. Inherits the nested-struct/call/arrlit
field arms and closes a latent fsz==2 cs!=ww (old ww loop's
fieldstoreop MOVW vs cstage MOVQ). Test 938: 15-row table-driven
runtime readback (incl. ellipsis autofill, offset-0 tagged field,
(void|str) payload, 3-level widen-fill recursion torture), all 13
bug rows silent-fail at master 6699158; 2 rows skip the byte-id
check loudly (pre-existing match-on-tagged-FIELD readback cs!=ww,
master-confirmed, separate family).
2026-06-05 02:32:39 +09:00
66991585d6 wcc+w6c+w6c_ww: tagged tuple elements take their full slot — tuple_eslot accessor (#22a)
slot = roundup8(size(elem)) — 8B is a FLOOR, not a ceiling (user-
ratified 2026-06-04; the #237 fieldslotsize precedent; (str,str)=48B
predates this — tagged was the one truncated >8B kind). Pre-fix the
checkers truncated a tagged element to one 8B slot and every cgen
transport walk strode wide=(STR||SLICE)-else-8: cstage read the
NEIGHBOR slot, wwstage read ZEROS — both-wrong-differently, so the
byte-id gates were blind (prober-9 PG1, /tmp/p9).

One stride accessor per stage — tuple_eslot (cgen.c) / tupeslot
(cgenstmt.ww) — now feeds every tuple walk; the per-site predicates
are deleted as absorbed. Sizer: check.c N_TTUPLE + check.ww
tupleelemslot gain the TY_TAGGED arm (astsize already rides the type
table since commit 0). Routes flipped to the accessor, both stages
symmetric: cursor producers (lit/slot-to-cursor; tagged ident
elements load their box from the slot — cgexpr's tagged ident load
is word0-only), let-receive (tuple_store/tupstore generalized to
eslot/8 words), N_RETURN send, by-value param receive, arg
restage/drain (tagged stays loud per C-t2), destructure
(MLET/MASSIGN, ident + sret + in-cap), t.N read + len(t.N) +
global-g.N offset walks (t.N gains the tagged box load: AX=tag,
DX/CX/R8=payload — the is/as spill cursor), sret classifier, DATA
emit. wwstage cgtagvariantidx gains the #67 stamped-carrier arm
(flatvariantidxt on .type_) — its AST-only key silently clamped
't.0 is size' to tag 0; fused here because the tuple-element read
this commit wires is its only exercisable consumer.

Exit invariant: zero silent tagged-tuple paths — in-cap shapes
(<=4 GP eightbytes) are correct end-to-end; everything else is LOUD:
over-cap sret return (#22b, task #28), call-arg (C-t2 #32),
non-local literal element sources (#22b/#23), tuple-in-union payload
(#242/#22b), global static-init, element write (pre-existing).
Closure proof-grep at HEAD: 'tuple_ebytes|tupebytes' -> 0 hits;
'wide.*=.*(TY_SLICE|TY_STR)' tuple-walk survivors are all behind
tagged loud-guards (cgen.c:2535/2568/12013 widen-store + over-cap
send; cgenutil.ww:3527 twin).

Latent cross-checks closed by the accessor: wwstage MLET-sret strode
esz (4 for i32) vs cstage 8; wwstage param-receive strode slotsize
(composite slotsize) vs cstage 8; both now the accessor's floor-8.
Tagged inits in pins use the CAST form (5: size) — the bare
untyped-int widen-store mis-tag is pre-existing at master and filed
(task #33).

941 gains 13 rows: t22_* size/align folds (+ void-elem 0-slot pin),
the full runtime round-trip (read, is/as, both element orders, void
variant, destructure, literal-let), two-tagged-elem in-cap and
float+tagged in BOTH orders (ken k1/k2 regression-pinned as rows),
t.N-as-call-arg, 5 loud pins (arg, over-cap return, call-source
element, global init, element write), and the sret_narrow_mix_*
triple below. Runtime rows exit-checked under both drivers +
byte-id. 129/129; unit tests green; sizelint clean.

ken R1 (re-validation fix): an OVER-CAP tuple init whose rhs is not a
CALL fell past every cstage N_LET store arm to NOTHING — silent
uninitialized-frame reads — while wwstage loud-rejected the same
shape. Pre-existing for (str,str) literals; the #22a tagged slots
routed >16B-box tagged shapes into it (k5b/k5c/k5d, base-correct
because base truncated them in-cap). cstage now routes the rhs
through cgexpr (the cursor materialisers carry the exact wwstage
loud texts) with a trailing fatal as the net; 941 pins both the
tagged and the (str,str) spellings of the hole.

reviewer-22 (review fixes, folded): the MLET-receive slot flip above
landed ALONE on the wwstage over-cap sret family — the ww RETURN-send
and MASSIGN-receive still strode packed esz (4 for u32), so
`let (a,b,c) = f()` over (str,u32,str) read c at slot offset 32 while
ww's send wrote it packed at 28: a ww runtime REGRESSION vs base
(base was packed-consistent on both sides — ran right, byte-diff).
Both walks now stride the slot (send mirrors cstage's
`wide ? esz : 8`; MASSIGN strides tupeslotn) — closing, in the same
stroke, the pre-existing base skews: ww `let t = f()` over-cap
narrow-mix read (reader slot-laid vs send packed, runtime-wrong at
base) and the cs≠ww asm on all three routings. Second find: t.N
tagged element as a CALL ARG — cstage rides its generic
node_istaggedarg cursor push, but wwstage's kind-gated aistagged
missed N_DOT and mis-routed the box into the widening branch
(taggedvariantindex -1 clamped to 0: callee read variant 0, silent,
ww-only). cgenutil.ww gains the N_DOT arm (aistagged + pushargsrev),
the #67 stamped-carrier twin of the N_INDEX (#12) arm. The N_DOT arm
was a LIVE wwstage stdlib miscompile: hextest's import graph carried
two t.N tagged-arg sites (base .s diff = exactly the clamped-tag
PUSHQ pair) — the 989 lib-byteid ratchet caught the convergence and
#59.4 graduates to M_ID (runtime-correct per 979_hex_run). Probes:
/tmp/r22. Residual filed: tagged arg from deref/cast/unwrap sources
is a word0-only read on BOTH stages (pre-existing, base-confirmed) —
generalizing wwstage's kind gate to cstage's type-keyed check rides
that task.
2026-06-05 01:37:35 +09:00
ed62e8199f wcc_ww/check: tuple size() fold reads the type table — packed-sum C-t0 escape (#22 commit 0)
The wwstage size()/align() fold walks the AST (astsize), and its
N_TTUPLE arm still summed PACKED element sizes — C-t0 flipped the
checker type table (tupleelemslot) and cstage's N_TTUPLE to the
ratified slot layout but missed this second wwstage sizer.
size((u32,u32)) folded to 16 on cstage and 8 on wwstage: a silent
cs≠ww in every folded tuple-size constant, plus the recursive
escapes (a tuple inside struct/array size computation under the
fold). Runtime-confirmed at 74767c7.

astsize N_TTUPLE now reads the tuple tinfo, making tupleelemslot
the single wwstage tuple sizer; the tagged-element slot fix (#22
22a) lands in that one place next. 941 gains c0_sizefold_slot +
c0_sizefold_recursive rows (exit-checked under both drivers +
byte-id).
2026-06-05 01:05:12 +09:00
a2c2bbc6b1 regex: fold 5a — capture groups (compile ( ) arms + run_thread groupstart/groupend + search spread)
compile: '(' (ha:317-323) appends inst_groupstart(capture_idx) and
grows jump_idxs per level; ')' (ha:324-334) appends the void
inst_groupend, fixes up the level's pending alternation jumps (#70
range + #58 assert), range-deletes them (#8) and closes the level;
the loop-exit done arm gains the Unmatched-'(' check (ha:277-282).
The anchors' group_level arms, the postfix inst_groupend/groupstart
arms and find_last_groupstart's success path go live unchanged.

run_thread: inst_groupstart (ha:636-652) fill-grows captures to
idx+1 (count-loop spelling of Hare's 3-arg fill-append) and opens
the group with the SIZE_MAX end sentinel via the #20-fixed indexed
struct store; inst_groupend (ha:653-668) closes the innermost
unclosed capture (ha:655's 2-clause for respelled — ww has no
cond;post form) and slices content from the bytesize span through
the addr-of-element pointer.

search: the ha:820 loud bound flips to the real capture spread
(#35/#25); the pad fill self-activates for unset trailing groups.

Tests: ( ) graduate from the metachar-loud table into real-text
error rows (+ ww-added anchor-in-group / Unused-on-groupstart
re-verify rows); hand-built groupstart/groupend arm cases; the 5a
find table (+test.ha:257-275 group/alternation, :499-503/:607 jump
bugs, :610-621 submatch family, :635/:640 alternation-group,
:649-665 nested minus the 5b {m,n} twins, + ww-added multibyte
row) with len(res) pinned per row; submatch content rows
(+test.ha:704-708 + ww-added multibyte) — the 5a acceptance gate.
2026-06-05 00:49:42 +09:00
0e61db1857 regex: fold 5a — add_thread dups parent captures/rep_counters (ha:568-573)
The loud bound (capture dup not yet portable, #35/#34/#7) flips to the
real dup now that #35's spread place-chain sources landed: fresh slice
header + spread-append, the D3 spelling of Hare's alloc-dup. The
ok/defer-if frees drop (free() is the documented no-op, #27).
Dup-independence rows drive add_thread directly: values carried,
backing independent both directions, empty parent → empty dup.
2026-06-05 00:36:33 +09:00
06b0fea98b w6c+w6c_ww: struct-lit store into indexed/deref/field place fills via resolver (#20)
A struct-LITERAL rhs aimed at an N_INDEX element (a[i] = pt{...},
(*ts)[i].caps[k] = capture{...}), an N_UN deref place (*p = pt{...}),
or an indexed-base FIELD place (a[i].f = pt{...}, reviewer-20 sibling)
fell to a scalar store tail in BOTH stages: cgexpr on a struct
literal emits nothing (AX=0) and one MOVQ zeroed the place's first
word — every field silently dropped, a str-leading element's
content.ptr nulled (downstream SEGFAULT). Byte-identically wrong, so
every byte-id gate was blind; runtime pins added.

Fix: divert struct-lit-rhs INDEX/UN-STAR/DOT-over-INDEX places past
the legacy arms and widen the F6 assign-resolver gate
(N_DOT -> N_DOT|N_INDEX|N_UN); the existing C1.25 aggregate arm
materialises the literal into a fresh per-use @placescr slot and
word-copies to the cgplaceaddr-resolved address. No new path;
@placescr alloc site stays single per stage. Rider (task #32): an
array-LITERAL rhs at assignment — unwired for EVERY place kind, same
silent zero-word tail — now dies loud at one choke-point until the
fill lands; build-fail rows pin it.

Gates regex fold-5a (run_thread groupstart capture store,
regex.ha:643-651). Residual adjacent gaps (deref ident-rhs truncation,
>24B ident reassign cs!=ww, struct compound acceptance, value-global
DATAW, tuple-lit deref truncation, CALL-rhs RAX-only store) probed
pre-existing and filed as tasks #31 A-G / #32.
2026-06-05 00:19:47 +09:00
0ce98cc5dc wcc+w6c_ww: append() spread place-chain sources via cgplaceaddr (#35)
#35 (fold-5 blocker, PG6): the append() spread arm dispatched on
SOURCE node kind — ident-local only; both fold-5 dup shapes
loud-rejected on BOTH stages: deref-spine `append(dup,
(*tsp)[0].caps...)` (add_thread regex.ha:569/572) and indexed
`append(res, ts[0].caps...)` (search ha:820).

Route every non-ident-local source through cgplaceaddr (the
established place choke-point: C1 stores, C1.25 aggregate fields, FA1
append targets, #49 element sources): the source header ADDRESS
resolves ONCE, pre-grow, into a fresh per-site @appsprscr spill, and
every loop iteration re-reads .ptr/.len THROUGH the spilled header
after the grow. This adapts the #49 split ruling to the spread's
full-range copy: the chain's rvalues (deref-root pointer expr, index
exprs) evaluate exactly once PRE-grow — an index reading the dst's
len sees the pre-append value — while the source data base and len
re-derive live each iteration, so a source header aliasing the dst
header re-roots post-realloc. A header reached through a buffer the
grow reallocs reads the STALE copy — bit-identical under the
non-reclaiming rt/malloc, the same documented #49 stale-base hole
(pinned by the spread_selfalias_chain row). Ident-local sources keep
the legacy BP-disp emission byte-identical.

New loudness with the same fix: the spread source's stamped type must
chase to TY_SLICE/TY_STR — a [N]T array ident source previously read
its first 16 DATA bytes as a {ptr,len} header, silently. Array wiring
plus the remaining rvalue sources (CALL, slicing exprs) stay loud,
filed task #27. Global-ident sources now resolve on cstage but are
blocked by a pre-existing wwstage checker reject ("let: not
assignable", task #29) — no dual-stage row until that closes.

806_append_place: reject_spread_src GRADUATES to a runtime row
(spread_place_deref, the old reject source verbatim + readback); new
rows spread_dup_copy (PG6 verbatim: both fold-5 shapes, 56B capture
elements, copy-semantics mutation pin, want 139), spread_place_kinds
(str 24B headers / narrow i32 / empty deref source),
spread_growth_place (40-elem spread crossing cap doublings),
spread_selfalias_chain (source header inside the dst's grown buffer),
and the two new loud-tail rejects (CALL rvalue, [N]T array) pinning
the "#35:" diagnostic on both stages. 87 fixtures green (was 70),
per-row cs/ww asm byte-cmp included.

Unblocks regex fold-5a: add_thread regex.ha:569/572 + search ha:820
spreads go from loud-bound to real spread.
2026-06-05 00:00:00 +09:00
1bcf2726cf wcc+w6c+w6c_ww: delete() range form delete(xs[lo:hi]) (fold-5a P2)
Hare's delete also takes a slicing place (harec check.c:1981-2027
EXPR_SLICE; Hare spells it delete(xs[i..j])): remove [lo, hi) — shift
[hi..len) down count = hi-lo strides, len -= count, cap unchanged; lo
defaults 0, hi defaults len, so delete(xs[:]) clears the slice with
storage retained. Checker accepts N_SLICE next to N_INDEX (object must
chase to a slice, harec :2024); the old range-unimplemented reject and
its #35 cite drop.

Lowering (both stages, converged byte-identical by construction) is the
single-element arm's same-slice whole-stride word-copy loop with a
DYNAMIC src offset (count*esz via a src register) instead of the
constant one-stride. Base shapes: local slice ident, deref-of-local,
plus NEW indexed local-slice base xs[g][lo:hi] — the fold-5a consumer
shape (regex.ha:333 delete(jump_idxs[group_level][..]); outer stride
off the type table). Bounds stay implicit, inheriting the documented
single-element posture (no index checks anywhere in cgen). Operands
evaluate left-to-right, exactly once, before the shift (harec order);
only the header ADDRESS is taken before operand eval, so a bound
expression's writes through the slice land before the copy.

test/809: 64 fixtures — full/explicit/re-clear/head/mid/tail/empty
a:a/end-boundary len:len/explicit 0:0 on a never-appended (nil-ptr)
slice, single-vs-range equivalence, cap preservation, esz 1/2/4/8/16/24
copy tails against the dynamic src, operand order-of-eval (lo/hi CALLs
fire once each, in order) + aliasing-visibility pins, the EXACT
[][]size regex consumer shape, deref base, 2 reject rows w/ diagnostic
text; every accept row cs==ww asm byte-id. test/804: reject_range row
retired (form now accepted), reject_nonindex text follows the widened
message.
2026-06-04 23:37:08 +09:00
60e61315bc ww/lex: fold float literals through strconv.stof64 — 1-ULP cs≠ww class (#62)
wwstage's parsef64 (naive i64-accumulator + pow-10 fold) diverged from
cstage's strtod: >19-digit mantissas overflowed the accumulator (sign-bit
garbage), DBL_MIN was +1 ULP, DBL_MAX -2 ULP — the #59.10 ratchet pin.
C-strtod oracle confirms cstage correctly rounded on every vector, so
wwstage aligns to it by dogfooding strconv.stof64 (correctly-rounded
decimal engine, already imported by lex.ww). Overflow literals now
reject in both stages (stof64 overflow -> errat, mirroring ERANGE).

Fix + #59.10 M_DIVERGE->M_ID graduation + pins land together per the
ratchet's designed flow (the gate trips loud demanding graduation):
oracle-pinned vectors in toktest.ww floatfold_cases (lexer-unit) and
989_floatlit_run (compiler fold: runtime bits + byte-id + overflow
reject parity). Retained subnormal accept-set asymmetry filed as task
#21, documented at the lexnum site.
2026-06-04 23:25:07 +09:00
74767c70cc wcc/check+wcc_ww/check: reject overlong array literal — frame-smash class (#71)
An array literal with more elements than the declared [N] passed the
per-element accept-if-fits checks in both stages and cgen then stored
every element at its natural offset, writing past the slot: local
frames smashed silently (the repeat form [1,2,3...] into [2]int wrote
at the saved BP), module DATA corrupted neighbours. All four
declaration contexts (local let, module let, def, struct-field
literal) funnel through one choke point per stage — arrlit_init_fits
(check.c) / checkarrlitfits (check.ww) — which now pre-counts the
literal (skipping the ... marker) and rejects count > N naming both
counts.

cstage clet's blanket has_arr_repeat bypass is narrowed to non-array
declared targets: repeat literals into arrays now run the same
overlong + #130 range checks wwstage's checkletassign always ran
(the bypass let [2]u8 = [999...] dodge the range check cstage-only).

checkarrlitfits also recurses into NESTED array-literal elements
(declared elem node N_TARRAY): cstage catches the nested shape
through its typed-literal assignability net, which wwstage's untyped
elements have no analog of — [2][2]int = [[1,2,3],[4,5]] at module
scope silently emitted corrupted DATA (1,2,4,5) and the struct-field
twin likewise. Recursion through the one choke point closes any
depth; a named-alias element type still bypasses — task #16.

alen==0/nil-length stays exempt ([0]/[_] sentinel conflation and
un-inferred [_] in def/struct-field — task #11); a non-INTLIT length
child (def-named [N]) is exempt in wwstage — task #13; under-long
literals keep their current accept (Hare rejects — task #10);
wwstage's overlong accept at assign/call-arg/return position (cstage
already rejects) is task #12; exact-fit bare-int nested cs-reject/
ww-accept divergence is pre-existing — task #17.
2026-06-04 22:48:53 +09:00
3daf134395 lib: retire os.assert/abort shims — assert/abort are builtins (#58 respell)
The flat checker scope makes ANY decl named assert/abort anywhere in
the combined unit disable the builtin unit-wide (the #45 shadow shape:
scope_lookup_prefer's cross-module fallback finds it). lib carried
three colliding @symbol("rt_abort") shims (os, time, strconv/stof)
plus the os.assert wrapper, so a bare assert(cond) in ANY program
importing os mis-bound os.assert and failed arity — a hard blocker for
regex fold-5 (regex.ha:660/670 bring builtin-assert mass). Ruled
respell-now per the recurrence test (#45 -> #58).

Delete the shims and the os.assert wrapper; every bare abort(msg)
caller (regex, strings, utf8, hash, getopt, encoding/*, time, stof)
now lands on the builtin, and the ~40 os.assert(c, m) sites respell to
the builtin assert(c, m) — restoring the exact Hare spelling the lib
ports diverged from (e.g. ref/hare/bytes/tokenize.ha:23). os.assert
had no Hare counterpart (Hare's assert is a language builtin); rule-9
wrapper removed. temp/dirs/bufio already use the non-colliding rtabort
spelling and keep it.

Now-dead 'import os;' lines kept (pre-existing precedent:
lib/strconv/strconv.ww carries one); a tree-wide dead-import sweep is
a separate concern. regex.ww's if+abort workarounds citing #58 stay
for the fold-5 owner to fold back into assert.

combined.ww regenerated for all five selfhost tools + the smoke
fixture via make.
2026-06-04 22:42:47 +09:00