An explicit [N]T = [init] with more initializers than N silently
mis-compiled for N==0: the over-fill length-mismatch check was suppressed
when alen==0, because alen==0 doubles as the [_] infer-sentinel after
resolve_type collapses the two. So def/let [0]int=[1,2] silently resized
(cstage exit 2) or OOB-read/segfaulted (wwstage) instead of the loud
length-mismatch that [N]=[init>N] gets everywhere else.
The AST keeps the distinction the Type loses: [_] leaves the N_TARRAY
length-child NULL, an explicit [N] carries N_INTLIT. cstage adds an
is_infer_arr() helper, drops the alen>0 exemption at the over-fill check,
and gates the 4 infer-resize/no-init sites on is_infer_arr so an explicit
[0] flows to the over-fill -> loud. wwstage flips the one shared count
gate (checkarrlitfits) from declen>0 to arrtn.rhs!=nil, which also
dissolves a wwstage local-resize/module-OOB inconsistency.
[_] inference, [0]=[] empty, and [_]-no-init louding all preserved.
Under-long (count<N) stays out of scope (#10). byte-id 990-997 8/8.
test/wcc/820 table-driven; its one empty-[0] global row carves out
byte-id (pre-existing spurious-DATAW divergence, task #15).
def C:[N]str; C[i] was loud (undefined main.C) both stages. Three folded
fixes, one commit (splitting would ship a bisect point where wwstage
silently returns an element address instead of .len):
P0: the str-array static-init emitter dropped its vestigial directive
=="DATAW" gate so a def table rides the same DATAW-header + DATAR-reloc
path as let. A def str/slice table lives in DATAW by w6a's A_DATAR-holder
constraint -- placement only; def immutability stays checker-enforced.
P1: let_pre_intern / letpreintern walked N_LET only, so a def str-array's
element string-literals were never interned (dangling _S_n). Extracted a
pre_intern_strarray SSoT helper, called for a def str-array arm too, both
stages. Scoped to str fixed arrays; def []T / def [N][]T stay loud (#270).
P2: wwstage cgenexpr lacked a defvartnode fallback in the indexed-element
classify, so a def str-array element load returned the element address
instead of the slice header -- a silent miscompile. One line, aligning
wwstage up to cstage (which was correct). C[1].len now = 3 both stages,
byte-identical.
byte-id 990-997 8/8; w6c/w6c_ww move. test/wcc/819 table-driven. The
def-global scalar str index sibling (def S:str; S[0]) stays task #14.
A global fixed array's .ptr (= &A[0]) must take the SB base, but cstage
emitted frame-relative LEAQ off(BP) for BOTH let- and def-global arrays
-> *A.ptr read frame garbage (0 instead of the element). cstage-SILENT;
wwstage def-global was a loud link-error. The .ptr read arm now gates
off==0 && (let_islet || def_isarraydef) -> LEAQ name(SB), reusing the
def-array index base predicate (cgen.c:4367, the #94/#231/#48 class).
Locals (off != 0) stay BP-relative -- the 14 toolchain backing-ptr sites
unaffected.
wwstage let-global was already correct; this adds the missing def-global
arm (cgenexpr.ww), converging cstage/wwstage byte-identical across all
three flavors (local / let-global / def-global) and closing a latent
cstage-only let-global cs!=ww divergence.
Byte-id 990-997 8/8 (corpus has no global .ptr); w6c/w6c_ww binaries move
(cgen changed). test/wcc/818 table-driven, build+run+byte-id per flavor.
.cap on a fixed-size array is invalid (Hare has no capacity-read; arrays
can't grow) -> both stages now loud-reject at the checker. wwstage was
silently returning frame garbage for a local array's .cap; cstage typed
it then vaguely rejected at use. Unified to one early checker reject with
an identical diagnostic both stages.
.ptr on a fixed-size array is ratified VALID: array.ptr is &A[0], a
sanctioned ww spelling divergence from Hare; see task #13. The toolchain
already relies on it in 14 backing-pointer sites. WHY-doc added at both
checker .ptr-on-array sites. The def-global .ptr cgen base-selection bug
(#11) is a separate following commit.
Valid-program asm unchanged (byte-id 990-997 8/8); w6c/w6c_ww binaries
move (checker code changed). test/wcc/817 table-driven, model 684.
wwstage .len on a def-global array fell to the cgdot SB-fallback (w6l: undefined reference to 'len') — the #7 let-array arm gates on letvartnode (c.lets only), so def-globals (c.defs) missed it. Add a def .len-only arm in cgdot using the existing defvartnode (the def-side mirror of letvartnode), emitting the length immediate from the #11-stamped N_TARRAY length child. cstage cgen.c was already correct, so this is a wwstage-only source change: w6c unchanged, w6c_ww + wwdump regen'd (they embed the wcc cgen).
.ptr (cstage itself buggy — emits LEAQ (BP), filed GAP-A.ptr) and .cap (wwstage silent garbage; arrays have no cap, filed GAP-A.cap) are NOT folded (rule-11, separate concerns). Pin: table-driven test/wcc/816_def_arr_len (def [3] + [_] inferred + 1-elem + u8 stride .len, both stages + byte-id), teeth-proven.
ww had narrowed Hare's formattable (types::numeric) to a lone i64 arm, so a bare int/uint was not printable: cstage CORRECTLY rejected it, wwstage leniently accepted via the #128 size-keying. Append int|uint LAST (existing tags 0-4 frozen, zero byte-id churn) so BOTH stages accept by membership — closing the #128 int-path leniency by construction. int renders via the signed i64 path; uint via strconv.u64tos unsigned (+ a rawlenu64 width twin) — i64dec would render a high-bit uint negative. Staged cut: narrower widths + full types::numeric graduate per-caller (mirrors the f32-arm precedent).
fmt is NOT embedded in the selfhost compiler tools (grep-verified: 0 fmt fn-defs in w6c/wwdump combined.ww) — no combined.ww regen, compiler binaries unchanged. Pin: table-driven test/wcc/815_fmt_int_run (bare int, high-bit uint 2^63+1 positive, i64/str regression guards, byte-id).
def xs:[_]T=arrlit was sized 0 (no DATA emitted, garbage indexed reads) on BOTH stages, byte-id-identical: #7 wired [_] length-inference only on the let decl path, never def. cstage check.c N_DEF pass-2 infers the length from the initialiser and re-points both d->type and the SK_DEF Sym (an indexed read resolves the def through its Sym); wwstage check.ww runs inferarraylen before resolvewalk. Checker-only — cgen lays the DATA correctly once the length is stamped. w6c and wwdump combined.ww regen'd (both embed the wcc checker).
Pin: table-driven test/wcc/814_def_arr_infer_len (index reads int/u8/2d + 1-elem edge + negative build-fail), teeth-proven against a reverted inference. Filed separately, not folded (rule-11): def-global .len GAP-A (#7 cgdot twin), def str-array element DATA GAP-B (#270), [0]T-vs-[_] alen==0 conflation (pre-existing in the #7 let path too).
The str==/!= arm of cbinop had an N_IDENT fast-path that assumed the operand
was a local: localfind returns 0 for a module-global str, so it loaded
(BP)/8(BP) — saved-BP/retaddr garbage — into rt_streq. `p == sepstr` silently
compared garbage (returned wrong). Mirror #148's global branch at both sub-sites
(rhs/lhs): off==0 && let_islet -> LEAQ name(SB) base, load ptr/len. Distinct
per-site fast-path, not a shared choke (the by-value-global-arg family
#148/#150/#151 closes separately). cstage-only; the wwstage str== twin is #146
(-> #125 batch).
Pin test/wcc/989_strglobeq (table-driven: const+let globals, rhs+lhs ident,
==/!=, unequal + len>1 rows; teeth-proven). Surfaced by the lib/path c3
buffer-ops gate-1 oracle.
A let's own name was visible during its OWN initializer: cgen prepended the
new local into the name-keyed localfind chain BEFORE emitting the init, so
`let x = f(x)` read the fresh UNINIT slot, not the outer/param x. Both-wrong-
identical silent miscompile (gate-blind byte-id). Surfaced by path
dirname/basename (was the c3-posix path->p rename).
Align to Hare (harec check.c:1439 evals the init, then scope_insert). Fix,
both stages, IDENTICAL asm: reserve the frame slot BEFORE the init emits,
link the binding's name into the localfind chain only AFTER.
- cstage cgen.c: split localoff -> localslot(reserve)+link; N_LET's 12
case-level breaks -> goto letlink (tail links once); the inner-for break
is preserved; the 4 fatal() arms untouched.
- wwstage cgen.ww/cgenstmt.ww: new localreserve (= localalloc minus the
chain-link); cglet -> cgletbody(c,n,off) + a cglet wrapper that
reserves -> calls body -> links after.
Byte-id-safe on existing code: localfind is by-name, so deferring the link
is a no-op on every non-self-shadow let (grep = 0 self-shadow sites) — 990-997
stay green. Because both stages emit identical now-correct asm, byte-id
CANNOT catch this; the pin is a RUNTIME test, teeth-proven (revert -> pin
fails). test/wcc/989_letshadow{.ww,_run.c}: param-shadow, let-in-init shadow,
rename control, arrlit self-ref.
Embedded regen: selfhost/cmd/{w6c,wwdump}/main.combined.ww. Gate: all 325
passed, byte-id 990-997 green, w6c c587f4a1 / w6c_ww 7a69f898 (deterministic).
Faithful realignment of lib/path to Hare's buffer-centric API
(ref/hare/path/{stack,buffer}.ha). Replaces the old str-only path.ww
wholesale (zero consumers). Scope = stack-core: init() deferred (returns
~4KB (buffer|error), rides the #40 arc / #147); abs/dirname/basename land
in c3; extension/join dropped.
appendlit uses the #145 slice-copy-assign arm (buf.buf[lo:hi]=bs), no hand
loop. dot/dotdot are faithful module-global []u8 (D2 #148). MAX =
os.PATH_MAX-1. Divergences (size->i32 indices, frombytes, module-global
consts) cited inline.
Tests: lib/path/pathtest.ww (table-driven), wired at test/wcc/989_path_run.c;
push rows mirror stack.ha:107-111 verbatim incl the restored "/d"
intermediate. Slot 989 (overflow bucket) since 970 is taken.
C-first: cstage @test green; wwstage byte-id deferred to the #125 batch.
path classified M_WWREJECT in 989_lib_byteid (w6c_ww rejects the module-
global slice consts = #120/#29; + the #148 twin #151) — self-graduates back
to M_ID the day wwstage accepts. path moved off the 900_stdlib standalone
list (import-dependent: os.PATH_MAX def-dim + match over imported error
types; coverage at 989_path_run), per the bytes/fmt/os precedent.
Gate: all 324 passed, byte-id 990-997 green, w6c/w6c_ww unchanged
(lib-only, non-embedded).
The slice-IDENT call-arg fast path pushed the header words off off(BP)
where off=localfind(name); for a module-global slice localfind→0, so it
read saved-BP/RIP/caller garbage instead of name(SB). Add the global
branch (LEAQ name(SB) base, push 16/8/0 off it) mirroring the sibling
N_SLICE arm; local path unchanged. cstage-only: wwstage checker-rejects
the shape (#120), so byte-id-safe and the twin defers to #125. Unblocks
path c2-stack (dot/dotdot are faithful module-global []u8). Sibling
structarg fast-path filed #150.
Probe-first find for the path c2 appendlit (buf.buf[lo..hi]=bs): a
slice-copy-assign into a struct-field array sub-range emitted ZERO code —
silent NO-OP, both stages, both-wrong-identical (#263), so runtime is the
only net. N_ASSIGN gains an N_SLICE-LHS arm (cgen.c + cgenexpr.ww
slicebaseesz twin) reusing the N_SLICE-read base/esz cascade and copying
(hi-lo)*esz bytes from rhs.ptr via a runtime loop (len is runtime; no
REP/MOVSB). esz routed through the type table (rule 13; [N]u8->1). Hare
len(bs)==hi-lo assert deferred to #149.
A def-dimensioned array [MAX]u8 used as a struct field was BOTH-WRONG: cstage
loud-rejected ("array length must be an integer literal"); wwstage silently
sized the dim to 0, so the next field overlapped it (frame-smash). The
reference is neither stage — it is Hare: accept + fold the def.
cstage: fold the def into the dim via eval_def_const. The fold needs def NAMES
visible when resolve_typedecl walks struct bodies, so a stub loop binds
def-name stubs (type=NULL, filled in place by the existing def loop) before
resolve_typedecl — this extends check_file's existing names-first USE+TYPEDECL
pass to DEFs; def-TYPE resolution stays in its original order, and the
kind-filtered type lookup (#225) keeps the SK_DEF stub out of type position.
wwstage: one shared arrayelen(c, rhs) (INTLIT -> uval; else evaldefconst;
else 0) routed through astsize / tinfofornode / checkarrlitfits.
Closes#13's def-dim cstage-reject half (the slice-repeat clause stays open).
Pin test/wcc/951 (5 rows incl a cross-module os.PATH_MAX dim + a ~4KB shape;
teeth = cstage loud-reject + ww frame-smash). cgen-first blocker for the
path::buffer arc (type buffer = struct{[MAX]u8, ...}).
A void (size-0) error-singleton type-name used as a VALUE (return / let-init /
assign / call-arg) all share the N_IDENT non-local global-value load; the load
emitted MOVQ main.<singleton>(SB),AX for a payload symbol that never exists →
w6l undefined reference. Guard TY_VOID && !let && !def at the non-local
fallthrough so nothing is emitted; the enclosing widen arm stamps the variant
tag. wwstage was already tag-only correct — this aligns cstage up to it.
Pin test/wcc/949_void_error_singleton_run.c (6 rows, teeth = link-fail
pre-fix). cgen-first blocker for the path::buffer arc (error.ha is all !void).
A module-global let with a const-expr init (let s = 7*6) emitted NO DATA word: cstage LINK-FAILed (undefined main.s, loud), wwstage was SILENT (no DATA, MOVSXD on stale AX, exit 152). The DEF pass-2 arm already const-folds + stamps its rhs to N_INTLIT (the #88 eval_def_const/stamp_intlit machinery); the LET pass-2 arm omitted it. Mirror it: after the assignability check, fold the rhs and stamp N_INTLIT when the plain-literal fold missed AND the const-fold succeeded. The existing DATA-emit downstream then fires (DATAW 42 + load). Both stages, byte-identical. Closes the inferred const-expr global and the typed b-ii case (let s:i64=7*6, link-fail both stages) with one stamp.
Gated on genuine int-const success (the eval return value, not the out-param): str/struct/slice/call/runtime-operand rhs short-circuit before the stamp and are left untouched — never zeroed. Non-const rhs stays on its current loud route; div-by-zero stays loud. Latent in selfhost (no const-expr module globals → 990-997 byte-id unchanged).
Pin: 947 rows C1 inferred 7*6, C2 typed b-ii, C3 def-ref K*7, C4 unary-over-binop, C5 div-by-zero loud-guard; cs==ww byte-id.
defaultinferredlets gained a single unary peel: an inferred module-global let s = -42 (untyped_int annotation over N_UN(+/-/~) of N_INTLIT) was wwstage SILENT — no DATAW, no MOVQ, MOVSXD on stale AX (exit 168 vs cstage 214). Peel one unary level to the int literal and default the annotation to the 8B machine word int, so the inferred decl is structurally the typed control and the existing typed-path emit/read fires (DATAW + MOVQ, byte-identical to cstage). ww-only align-up; cstage already correct on neg.
Float leg (N_FLOATLIT) carved to #135: cstage integer-types inferred float globals (MOVQ not MOVSD), so a ww-only f64 default would be cs != ww (rule-10) — needs the both-stage cstage-use-site fix. Nested unary - -42 is #136 (single-level peel). #133 (const-expr 7*6) and unary-over-nonident stay loud.
Pin: 947 neg rows (inferred + typed control), cs == ww .s byte-id.
An inferred-literal scalar module-global (`let s = 42;`) was wwstage
silent-wrong: the checker stamps the annotation N_TNAME("untyped_int"),
which letscalarprim does not recognise, so letemitsize returns 0 — the
global is dropped from collectlets (no DATA emitted) AND cgident falls to
the silent module-leaf (no load), running garbage. cstage defaults
untyped_int to an 8B int before emit (DATAW + MOVQ), which is correct.
Fix (wwstage-only, align up to cstage): defaultinferredlets in cgen.ww,
called from cgfile (cgendecl.ww) before collectlets, rewrites the
annotation "untyped_int" -> "int" (8B machine word, NOT i32 — the #108
truncation trap is the opposite polarity) for a module-level N_LET whose
rhs is N_INTLIT. All three consumers (letemitsize, emitletdataw, cgident
global-read) then resolve a concrete int. cstage is untouched.
Scope: N_INTLIT only. A const-expr inferred global (`let s = 7*6;`, N_BIN)
stays on its existing path — that is a separate live cs!=ww silent
miscompile tracked as #133, out of scope here.
Pin: 947_inferred_scalar_global_run — inferred `let s=42` (42, base
wwstage garbage) + typed control, cs==ww byte-id.
append/insert of a struct-LITERAL value evaluated the literal's field
exprs AFTER the grow, so a field reading the destination (e.g. len(xs))
saw the grown length. Both stages, #263 gate-blind (cs==ww byte-identical,
both wrong — runtime is the only net). #50 fixed the scalar/boxing value
arm; the struct-lit arm still post-grew.
Fix (mirror #50, both stages): resolve the struct, fill the literal into a
fresh per-site scratch (@appendstructscr, sized esz, survives rt_ensure +
nested-append clobber) BEFORE the grow, then copy scratch -> post-grow slot.
The copy uses the precise descending 8/4/2/1 ladder (the proven N_IDENT
struct arm directly below), NOT a raw 8B-word block copy: a struct's size
rounds to maxalign (check.c:916), so a sub-8B struct packs at a 4/2/1B
slice stride and an 8B copy over-writes past the slot — at a power-of-2
capacity boundary that clobbers the adjacent allocation (heap corruption,
both stages). The ladder never reads past esz (no uninit high bytes) nor
writes past the slot; esz=8 stays a single MOVQ (byte-id preserved).
insert() rides by construction: both stages desugar it to append and
re-dispatch into this arm. The #49 aplace path already uses the precise
ladder (verified, not exposed). #59 closes the last composite-value
eval-order hole in append/insert.
Pin: 946_append_structlit_evalorder_run — append / insert / narrow-neighbor
(i32-field at the cap boundary with an adjacent-allocation survival assert)
rows, each base-fail at 39432f7 and post-pass with cs==ww byte-id.
A tuple LITERAL with a declared-tagged element reached the cursor-fill
helper (cg_tuple_lit_to_cursor) through the generic cgexpr(N_TUPLE) arm
with no declared type, so the element was stored stamped-keyed at its
constructed scalar width rather than widened into the declared tagged box.
Both consumers ran silent and wrong on both stages (#263 gate-blind:
cs==ww byte-identical, both wrong — runtime is the only net).
#64 massign: N_MASSIGN derives a declared tuple type from the lvalue
binding types and threads it into cg_tuple_lit_to_cursor + the receive
loop (mirror of the #57 N_LET wire); a `_` target falls back to the rhs
literal element type for cursor stride.
#68 call-arg: the send is made param-aware (fill over the PARAM tuple) and
the restage guard graduates a declared-tagged element to a real widen
(reusing cg_widen_tagged_store); nested tuple/struct/array elements and
tagged elements with no param decl stay rule-7 loud. The matching
pop/drain is made param-aware too so push count == pop count: a
param-aware send pushes the box's N words, so the drain must pop N or the
SysV arg sequence skews. This is a push/pop balance requirement of the
send change, not a separate latent under-drain (the standalone trailing-
arg drain is already correct at HEAD).
Closed by construction: the only remaining cg_tuple_lit_to_cursor caller
passing NULL/nil is the generic cgexpr(N_TUPLE) arm, provably non-widening
(constructed type == governing type). The four widening consumers — LET,
RETURN, MASSIGN, call-arg — are all decl-wired. Whole-tuple single-ident
reassign from a tuple literal is rule-7 loud (task #49), not a silent
widening consumer, so the residual NULL arm stays non-widening.
Pin: 945_tuple_lit_declblind_run — massign / call-arg / `_`-control /
call-arg-drain / nested-tuple-ERR rows, each base-fail at abd97e6 and
post-pass with cs==ww byte-id.
io.empty (discard+EOF stream, ref/hare/io/empty.ha:4-17) — needed by getopt's
two-pass printusage width measurement. Diverges from Hare's `const empty: *stream`:
a `let _empty_vt` + `fn empty()` that wires the fn-ptr slots per call, because
const-init of a vtable struct with fn-ptr fields is blocked (#118, ruled accept).
Co-discovered while making empty() byte-identical across stages: three
wwstage-only cgen fixes (cstage was already correct; wwstage aligned down):
- #129 sretretsize: consult the same-module pointer-alias before structlookup's
any-module struct fallback (io.stream = *vtable was mis-sized as memio's 56B
struct -> spurious sret save).
- #129 callsretsize: swap curmod to the callee's module before sret-size
classification (cross-module callee context).
- #130 cgassign global-struct tagged-union field store: add the missing arm
(was a 1-word store) mirroring cstage cgen.c:4893-4912.
The three are inseparable from io.empty here — splitting them out leaves a
divergent-asm intermediate (993/995 red), so they ride one commit per the
one-class gate-repair carve-out (#133-expanded precedent). Regenerates the
embedded combined.ww; 989_lib_byteid pins bufio + fmt graduated to M_ID.
(cgenexpr.ww fix-3 inline comment cites the #129 cluster; narrow to #130 on
next touch to avoid a regen for a comment.)
A cross-module `&module.fn` in a const emitted no static reloc (the
const was never defined -> w6l undefined-reference, both stages) and
wwstage's checker rejected the const fn-table. #117/#119 wired the
&fn->DATAR const-data reloc for SAME-module &fn only; charclass_map
(fold-6) needs cross-module (12x &ascii.isXXX).
cgen: add the N_DOT arm to the &fn->symbol helper (node_fnptr_sym /
nodefnptr + the two ww emit sites), emitting mafn(leaf, module-ident)
-- exactly the symbol a runtime &mod.fn or a direct cross-module call
already emits. The helper is the SSoT for both the scalar (#119) and
tuple-row (#117) const-data paths, so one arm closes both.
checker: type a cross-module `&mod.fn` as `*fn(...)` in the TK_AMP arm
(the N_DOT twin of #206's N_IDENT fn-ptr synthesis, gated on a resolved
SK_FN/N_FNDECL leaf), so isassignable affirmatively accepts the const
table -- aligning wwstage UP to cstage's actual acceptance reason
rather than by abdication. The SK_FN gate keeps a non-fn `&mod.var`
from synthesizing a fn type (the one pre-existing nonfn-scalar cs!=ww
slip is N_IDENT-base, untouched and reproduces same-module).
One consumer-coupled commit (the checker accept gates wwstage cgen, so
neither half is independently testable). Narrow: slice-row + scalar
only; fixed-array (#118) and struct-field (#129) stay separate. Both
stages emit the correct cross-module symbols at the right tuple-slot
offsets -> byte-identical (990-997 green). Pin 949_xmod_fnptr_const_run
(distinct fns so a wrong reloc is caught + the SK_FN-gate axis). This
was the last fold-6 cgen blocker; charclass_map is now unblocked.
Reading or storing a tuple element of an indexed array element was
broken across the board (the fold-6 read-path). One fused commit,
both stages, four faces of indexed tuple-element access:
- FIELD read `tbl[i].N`: was loud ("unsupported field-read shape" --
the field-read dispatch keyed on an N_IDENT base; an INDEX base fell
to a fatal). Now resolves &tbl[i] via the place-spine and reads the
field at addr+foff through the existing per-kind arms (str-triple /
scalar / fn-ptr).
- WHOLE read `let e = tbl[i]`: was a silent word0-only truncation
(plain-tuple kin of #37/#58, which covered only tagged). Now a full
cursor fill from &tbl[i].
- STORE `a[i] = (3,4)` (N_TUPLE-literal rhs): was a silent word0-only
store -- the write face of the read. The aggregate-store-into-index
site handled ident/dot/deref tuple rhs but not the literal; now it
materializes the literal and word-copies. Narrow: N_IDENT base only
(N_DOT/chained stay deferred, #270).
- for-range over a const-slice-of-tuple: was a divergent SEGV; now a
symmetric loud-stop on both stages (filed #122).
The store and read were a round-trip that passed test 809 only by luck
(broken store XOR broken read canceled). Fixing the read alone exposed
the silent store; rule-7 obliges fixing both, so 809 is now genuinely
correct, not luck-correct. Both faces are byte-id-blind (#263) -- the
net is a runtime round-trip pin with distinct-per-word values and a
real call clobbering the cursor registers between store and read, so a
word0-only store or read is caught. Both stages byte-identical
(990-997 green). Pin 947_tuple_index_read_run.
rob A-corrected: the broad TY_TUPLE arm stays; pin an accepted non-(str,*fn)
scalar-tuple slice in-suite. const [](i64,str) with TYPED 1i64 (untyped int
is ww-checker-rejected, #120); scalar-first so the i64 reads back via the
word0-correct whole-element cursor (#121: .1/word3 truncated). Distinct
values + len(t) + cs==ww byte-id lock the scalar-in-slice-backing path.
Base f8be2ae has no TY_TUPLE arm -> louds. Test-only; 17/17 ok.
cg_widen_tagged_store only handled a tuple LITERAL (N_TUPLE / cast-of-
N_TUPLE) widened into a tagged box; any addressable non-literal tuple
source -- IDENT var, INDEX tbl[i], DEREF *p -- hit the `else fatal`
("tuple-typed source shape unwired"). Both stages loud-identical
(honest, no silent miscompile). This blocked indexing a const tuple
table into a union (regex charclass_map[i] -> charset union).
Add an addressable-tuple-source arm, both stages (cgen.c +
cgenutil.ww twin). It resolves the source address via the cgplaceaddr
place-spine (covering ident/index/deref -- one mechanism, so the trio
is family-closed) and block-copies the tuple's type-table ->size bytes
into the box payload (after the 8B tag), then stamps the variant tag.
No re-slotting: a tuple's in-memory layout uses the same eslot strides
(str=24B header, *fn=8B, ...) as the box payload the literal loop
fills, so source-layout == dest-layout. The existing narrow-pack and
tag-unresolved guards stay as the honest boundary; CALL/sret tuple
sources (different receive, #68-kin) stay loud.
align-BOTH: both stages were loud (no runtime reference), and byte-id
is structurally blind to an identical-wrong emission -- so correctness
is proven by a RUNTIME read-back pin (944_nonlit_tuple_widen_run, per
shape: match-extract + assert str header + call the fn-ptr elem with
distinct fns so a stale pointer is caught). 936's old reject row
graduates to a run row. Both stages byte-identical (990-997 green).
wwstage's checker rejected a `case let t: ((void|size),(void|size),
size) =>` arm against a (tuple|error) scrutinee ("case: not a variant
of scrutinee"), while cstage accepts and runs it. typeeqast's N_TTUPLE
arm recurses per-element, but a tagged element (void|size) is
N_TTAGGED -> fell to the conservative catch-all `return false`, so the
whole tuple-compare failed. typeeqast is the sole acceptance route
(casevariantpairmatch is N_TNAME-only).
Add an N_TTAGGED arm to typeeqast, sibling of N_TTUPLE, mirroring
cstage type.c:288-300 (type_eq TY_TAGGED): position-by-position
variant compare over the tagged node's .list (direct nodes, not
.lhs-wrapped). cstage's nullable-flag check is deliberately not ported
(resolved-Type property, no ww AST analogue; moot for case-match).
A spread variant (TK_ELLIPSIS) in the .list is loud-rejected rather
than compared: a naive streq would silently accept a `...ab` case that
cstage rejects (a new cs!=ww over-accept the bare arm introduced).
Flattening the spread is deferred (#115); until then it louds, matching
cstage.
ww-only (cstage already accepts); the gap-A cgen store landed in
6a5bb3e. wwstage now accepts the b1c match and runs the full shape
byte-identical to cstage -> #47 (both gaps) closed. The deferred
full-b1c row in 944_tuple_tagged_union_run is promoted to a both-stage
runtime row. Checker change is acceptance-only/additive -> bootstrap
byte-id neutral (990-997 green).
A tuple containing a tagged-union element, used as a union member
(e.g. ((void|size),(void|size),size) | error), loud-stopped in the
cgen return-store: the tuple-in-union store walk had scalar/float/
str/slice element arms but no TY_TAGGED-element arm. A PLAIN
tuple-in-union already worked -- the blocker was the tagged element.
Add the recursive two-level widen arm at both stages
(cg_widen_tagged_store / cgwidentaggedstorebp): for each tagged
element, re-enter the tagged-box store (inner tag@slot+0,
payload@slot+8) at the element's tuple-payload offset, then stamp the
outer tuple tag. Slot strides come from the type table
(roundup8(eu->size)) -- the checker already sizes the shape correctly
(tuple->size measured 40, union box 48; check.c:715-720). The
recursion descends a finite type tree (a tagged element is never a
tuple literal, so it can't re-enter the tuple arm); unsupported
deeper nesting still louds via the existing size/tag guards.
Both stages get the same arm -> byte-id (990-997 green; additive,
bootstrap-neutral). cstage runs the full b1c shape
(construct+return+match-extract) as the runtime reference; wwstage's
store rides on byte-id until gap-B. gap-B (wwstage checker
match-acceptance of the tuple-with-tagged case pattern) is a separate
commit -- wwstage still louds the match honestly at the checker.
Pin 944_tuple_tagged_union_run.
Reading or writing a tagged field of an indexed array element
(xs[i].field) was broken on BOTH stages, byte-identically and
silently (#263 gate-blind): the arr[i].field branches had arms for
array/str/slice/float but no TY_TAGGED arm, so the tagged field fell
to the single-word scalar path. READ loaded only the tag word (stale
payload -> `xs[i].min as T` read garbage); ASSIGN stored the raw
unboxed scalar into the tag slot, corrupting the box.
Insert a TY_TAGGED cursor arm before each scalar fallback, both
sites both stages (cgen.c read + assign; cgenexpr.ww cgdot N_INDEX-lhs
read + cgassign indexed-field). READ mirrors cg_tagged_memread
(payload -> DX/CX/R8, tag -> AX last). ASSIGN synthesizes the tag for
the concrete variant (taggedvariantindext) and stores tag+payload via
the str/slice 3-word store spine -- not the source-remap widener
(concrete rhs has no source tag to remap).
>32B / multi-word / float payloads are loud-stopped at all four arms
(emission not yet wired; see #114). That shape is reachable today via
a narrow-variant ctor, so it louds rather than silently miscompiling.
Both stages get the same arm -> byte-id preserved (990-997 green; the
runtime is the net for this #263 class). Pin 944_idx_tagged_field_run
(read/assign runtime rows + >32B expect-loud rows).
wwstage pushargsrev treated a narrower tagged-union argument widened into
a wider tagged param slot as a concrete variant: taggedvariantindex<0
clamped the tag to 0 and pushed word0 only (deref/index/dot silent-wrong;
ident ran correct only by prefix-union tag-index luck). cstage is correct
(cg_widen_tagged_push routes src_is_tagged unconditionally); align ww UP.
Three arms in cgenutil.ww, all mirroring cstage cgen.c:
- slot-gate the ident aistagged short-circuit so a slot-differ tagged
ident falls to the widen path instead of the raw 2-word push;
- route a tagged source in the widensz>0 arm through @tagscr +
cgwidentaggedstore + push high->low (cgen.c cg_widen_tagged_push);
- cgwidentaggedstore cursor arm (<=32B INDEX/DOT) spills by source
width, zero-pads, and tag-remaps (cgen.c 2698-2714) — was dst-slot
spill of stale high regs with no pad and no remap.
Same-slot tagged->tagged is byte-id-neutral by construction (empty pad +
identity remap). cstage untouched; 4 legs x {aligned, misaligned-tag}
converge ww->cs byte-identical. Pin 944_tagged_widen_arg_run.
Drop the `!TY_ARRAY` exclusion in the bare-let no-rhs zero-fill (cgen.c
N_LET else + cgenstmt.ww cglet, both gated `sz>8 && !TY_ARRAY`) so an
uninit `[N]T` array local zero-fills like every other composite (Go-zero
per user ruling). The zero-fill extent is the array's chased ABI size
(lu->size / chased tinfo.size, rule-13 — never a hardcoded count*esz),
NOT the slot-padded letslotsize, so a non-8-multiple array ([20]u8 = 20)
zeroes its exact bytes instead of over-zeroing to the 24B slot. The
unrolled MOVQ/MOVL/MOVB run mirrors the existing composite path; the
largest real local array ([256]u8) is 32 MOVQs (pathbuf[4096] is a
module GLOBAL, BSS-filled — never on this stack path, so no large-fill
case exists).
Closes a gate-blind #263-class bug: `let a: [3]int;` (no init) read
whatever the stack held — a clean frame masked it (fresh stack = 0), a
dirtied frame exposed it (d_array=165 garbage). BOTH stages emitted no
fill, both-wrong-IDENTICAL, so the cs==ww byte-id net could not see it.
The load-bearing net is therefore a RUNTIME dirtied-stack zero-read
(944_array_zeroinit_run: array-elem / narrow [4]u32 / non-8-mult [20]u8
/ 2D + an initialized control), not asm presence.
Deliberate byte-id EVENT: every uninit-array source site gains zero-fill
insns, so the 990-997 .s MOVE vs the prior tree; cs==ww HOLDS (both add
the identical insns). The 990-997 byte-id + 995 self-rebuild staying
GREEN is the fixpoint proof — it proves every uninit compiler-array is
write-before-read, so the zero-fill is purely additive and the
ww1->ww2->ww3 self-rebuild fixpoint holds by construction. w6c/wwdump
main.combined.ww regenerated (cgenstmt.ww embeds there).
#84 is ARRAY-ONLY; the no-default reject-set (uninit tagged / plain-*T)
is split to #113, parked behind a ruling — selfhost relies on the
current (void|T) zero-fill (the "not-set-yet" idiom).
`&D[i]` over a module-level DEF array SEGV'd on BOTH stages: the
TK_AMP N_INDEX N_IDENT base classify checked only the local and let
legs, so a def-array base fell to a wrong else — cstage zero-based
the addend (XORQ BX,BX -> wild pointer, cgen.c) while wwstage
value-loaded the symbol (MOVQ name(SB) = D[0], not its address,
cgenexpr.ww complex-base fallback). Divergent asm, both wild.
Add one def-array leg per stage, mirroring the working let leg:
- cs: `def_isarraydef(base) -> LEAQ name(SB),BX` alongside let_islet.
- ww: the `defvartnode` fallback the read-side cgindex already takes
(cgenexpr.ww:1762) -> N_TARRAY classifies isglobalarr -> LEAQ
name(SB).
The def DATA symbol already exists (plain &D + D[i]-read work), so
once the base is the address the existing i*esz scale + ADDQ
round-trips. cs and ww now emit BYTE-IDENTICAL LEAQ-SB asm — the
both-broken -> both-correct convergence is the point (#263 class).
Rows (944_def_amp_idx_run, all 0/0 byte-id): amp_int [3]int,
amp_u32 [3]u32 esz=4 (narrow scale), amp_arg &D[2] as a func-arg;
controls ctrl_plain (&D), ctrl_read (D[i]), ctrl_2d (&M[1][1]) keep
working. *p spelled `let v: T = *p` — `*p: T` parses as `*(p: T)`.
OUT (filed #112): &D[..] slicing a def-array is a distinct parse
reject needing a Hare-fidelity ruling — not this leg.
Route the 16 routable bare-primsize GUARD sites (is-primitive /
struct-vs-prim dispatch) through the #101 aliasprimsize SSoT helper.
Byte-NEUTRAL by construction: an alias-narrow name is already
neutralized downstream by the same arm, so routing emits no new asm
(the empty-flip-set ken oracled).
Shape-A exclude-prim-early (3): cgenutil sretretsize / structparamsize
/ structfloatclass — `primsize>0 return` then structlookup→nil
returns the same value; route returns it early, same.
Shape-B prim-guard-then-structlookup (13): cgenutil 4604/4650 +
cgenexpr 4136/10244 + the 9-site CALL/assign cluster — primsize==0
→structlookup→nil→fall to normal; route skips the block→same normal.
Install the peellint bare-primsize FINALE (B7 lint-fuse contract):
tools/peellint now rejects any bare primsize() in the ww stage outside
the annotated whitelist. Evasion-hardened per the B7 lesson — a
character scan (comments + string/char literals stripped first) and a
LEFT+RIGHT word-bounded match of the bare `primsize` TOKEN (not just
`primsize(`), so the aliasprimsize() wrapper is never a hit and every
compiling spelling reds: the call primsize(nm), the paren-wrap
(primsize)(nm), the function-value bind `let p = primsize`, and any
line-split. ww-only (the C stage dealiases via type_chase_named, no
primsize symbol). Two independent exemption windows (peel-ok vs
primsize-ok) so neither rule blinds the other. Runs as a make-test dep.
Whitelist the 6 designed exemptions with primsize-ok WHY-annotations:
machinery — aliasprimsize body (SSoT chase) | typenodeprimresolved +
exprprimresolved (#11/#33 prim-resolver chasers) | cgcast leaf-loop +
cgenexpr #11 deref-store (own ps==0 fallback; route would regress
#11) | the primsize oracle/definition itself (nothing below to chase).
structural — elemsizeof x2 + paramfieldsize (chase lives in the -c
twin elemsizeofc; threading c is the dormant #110).
Empty-flip-set proof: zero C bytes; cstage binaries bit-identical;
bootstrap byte-id 990-997 + 950 all green (w6c == w6c_ww on the full
selfhost, self-rebuild identical); combined.ww (w6c + wwdump) regen
idempotent; sizelint 0; peellint 0 (raw-peel AND bare-primsize over the
whole tree = the close-by-construction proof, zero unwhitelisted
survivors). Tests: 944_peellint_gate +14 rows (bare / space-before-paren
/ name-at-EOL split / string-blind opener / paren-wrap / fn-value-bind
RED; aliasprimsize wrapper + primsize-ok annotated GREEN; corrupt
annotation RED; independent peel/primsize windows; C-file out-of-scope).
Closes the #101 primsize-alias family by construction. #109.
A PLAIN (non-alias) module-level tagged-union global SEGV'd on BOTH
stages: no static DATA was emitted (let_emit_size/letemitsize returned 0
for TY_TAGGED) so the global was never registered, and the match
scrutinee resolved it as a frame-local at offset 0 — reading saved BP as
the tag. Two sub-sites, one route (neither half ships alone — DATA
without SB-resolution still SEGVs; SB-resolution without DATA reads
nothing):
(a) DATA-emitter — a non-nullable TY_TAGGED arm emits the box that
byte-MIRRORS a runtime LOCAL of the same type: tag word at +0 (the
const-selected variant index via cg_tag_for_variant / taggedvariant-
index), payload at +8, zero-padded to the union box size. int and
str/slice literal variants are wired (str carries a DATAR ptr patch
at +8); any other variant payload loud-stops (rule 7). emit_tagged_
data + emittaggeddata are the per-stage twins; let_pre_intern/
letpreintern gain the matching str-variant intern. Nullable stays 0
so the (*T|void) one-word fold keeps the 8B scalar arm.
(b) match-scrutinee global resolution — the PLAIN-tagged twin of #78:
a global tagged ident scrutinee LEAQs name(SB) and copies the box
into an @match_spill slot the dispatch indexes off BP.
DATA target (mirror of the local box, verified byte-for-byte): for
(i32|str)=42 the 32B box is tag0 | 42@8 | zero-pad; for ="x" it is
tag1 | ptr0@8(DATAR _S_n) | len@16 | cap@24. cs and ww emit byte-
identical asm.
Pins (rob §3, dual-stage 910 cstage + 997 wwstage, attest_pass.ww): the
tagged global-vs-local byte-identity pin (match over the GLOBAL gives the
same arm/value as over a LOCAL — was SEGV both stages) and the str-
variant tag-1 pin, plus the #86 tuple global-vs-local lock-pin guarding
the already-correct emitter path.
929 fail_global_src graduates: a >48B tagged GLOBAL by-value arg now
resolves through the cgplaceaddr MEMORY-class arm (LEAQ g(SB) + blit)
instead of the #38b loud-stop, and runs correctly (uninit zero box ->
first variant); the row becomes a positive run pin. The struct-variant
>48B init still loud-stops via the data emitter.
The first-class-VALUE copy of a tagged ident (`let q = g`) stays a
pre-existing silent #49/#46 sibling (local and global identically),
filed separately — out of this fold's two sub-sites.
cglet's N_STRUCTLIT init arm resolved the struct by a bare
structlookup(c, sname). For an alias-NAMED literal
(`type rep2 = rep; let r = rep2{id=6}`) the type ref carries the
alias name "rep2" but only the base `rep` is registered, so the
lookup returned nil and the field-fill never fired. The nil then
split by slot size into two symptoms of one root:
- <=8B: the small-let scalar default zeroed the slot and DROPPED
the literal (SILENT wrong — the field read 0), and
- >8B: no fill arm matched, falling to the cglet "unhandled rhs
shape" LOUD (task #7/rule-7).
Route the arm through structlookupchain (the #92/W2 SSoT already
adopted at cgenstmt:1974/:2687), which chases the alias chain to the
base struct. trefn (rhs.lhs) is already the N_IDENT/N_TNAME type ref
structlookupchain accepts, so the bare sname extraction is dropped.
cstage operates on the resolved Type* via type_chase_named and was
always correct: ww-only align-UP, cs UNTOUCHED.
ROUTED (the two reachable silent sites, one class):
:2421 local N_STRUCTLIT let-init — the #63 repro hits it for
both the <=8B silent-zero and the >24B loud symptoms.
:1250 >24B sret RETURN twin (reviewer-63). sretretsize chases
the alias for the size GATE so this sret arm fires, but
the fill used the same bare structlookup(sname) — for an
alias-named >24B literal it returned nil and the fill was
SKIPPED, so the callee returned an uninitialised sret
buffer (SILENT wrong, runtime-0; cs correct). Same root,
same symptom, sibling site → folded by construction.
DECLINED (traced, not blind-routed; rule-11 + the #101 precedent):
:2625 N_IDENT struct-copy — also bare-structlookup but the copy
falls through to a generic path byte-identical with cstage;
both stages run correct. The post-copy field-READ diverges
(cs direct-offset vs ww LEAQ-indirect) = the #81/#65 alias
field-read class, out of #63 scope.
:2511 N_CALL struct-recv — blocked UPSTREAM by the aggregate-
return shape (#272/#277); ww louds at the sender.
:1363 <=24B register RETURN — alias case louds via the same
scalar-default catch (#277), not silently wrong.
The 2 already-chasing sites (1974/2687) untouched.
CONVERGENCE: m3_letinit_typed + m3_letinit_untyped (ww silent-zero ->
6/6 byte-id) + m6_letlit_alias (ww loud -> 7 byte-id) + sret_return_-
alias32 (ww silent-0 -> 10 byte-id), plus a non-alias control
(no-regress). Bootstrap byte-id NEUTRAL (selfhost has no
alias-struct-litinit/return; all 4 selfhost tools cs==ww confirmed).
Test: 944_alias_structlit_init_run (5 rows x cs-run + ww-run +
cs==ww byte-id = 15 checks), Makefile-wired.
A struct-literal array fill sized a narrow-alias element off a bare
primsize(name): `type my32 = u32` gave primsize("my32")=0, so the
element width defaulted to 8 and a [3]my32 strode MOVQ -24/-16/-8 —
field n collided with arr[2] (kw1_101 run exit 1). cstage chases
my32->u32->4 (MOVL stride-4) at the twin sites and is runtime-correct;
this is a ww-only align-up, cs untouched.
Fix: a new aliasprimsize(c, nm) SSoT helper — primsize(nm), else an
aliaslookup-chase N_TNAME loop then primsize — and route the SIZE-use
primsize() family through it. The 7 c-bearing bare-no-chase size-use
sites are routed: cgen:993 (letemitsize), cgenstmt:1896 (cgarrlitfillbp),
cgenutil:1579 (elemsizeofc fallback)/1657+1665 (nodeprimwidth)/4791
(cgstructlitfill = the kw1_101 site), cgenexpr:6902 (cgcall vararg esz).
This is the rule-13 close-by-construction shape (one accessor for
"resolved primitive size"), not a per-site patch.
kw1_101 is the SOLE asm mover (byte-id NO->YES, run 1->0, MOVL
stride-4); every other routed site is latent/byte-neutral. Bootstrap:
all 5 combined units stay w6c==w6c_ww byte-identical. sizelint 0,
peellint 0, test-unit 296/296.
Scope fence (rob route-7-decline-6 ruling): three DESIGNED-exemption
sites carry inline primsize-ok annotations — elemsizeof :1475/:1499 and
paramfieldsize :3541 are structural (no-`c`, non-chasing) BY DESIGN;
their alias-chasing twin elemsizeofc is the routed :1579 leg. These are
the #109 peellint-whitelist seeds. Three further declines are already
correct chasing paths, not bare-no-chase bug shapes (typenodeprimresolved
:2026 / exprprimresolved :2063 are the chase machinery itself; cgassign
:7631 already chases via typenodeprimresolved, #11). The ~17 GUARD sites
(is-primitive dispatch) + the peellint finale are the committed #109
follow-on. Threading `c` into the structural sizers is dormant #110.
#101
cstage type_default(TY_UNTYPED_INT) returned ty_i32 (4B): an unannotated
`let x = <v>` / `let a = [<v>,..]` silently TRUNCATED any value > 2^31
(5000000000 -> 705032704) and strode inferred arrays at 4. wwstage kept
the element raw untyped_int (size 0), which sized INCONSISTENTLY across
cgen — the array STORE strode the 8 sentinel but letslotsize under-
allocated the frame (SEGV) and cgindex strode the READ at 1. The two
stages were each wrong differently; #263 polarity: cstage was the
truncating side. int = machine word = 8B (Go-style, MEMORY
project_int_machine_word_derived_limits); Hare lowers a flexible iconst
to `int`, never a fixed i32 (ref/harec/src/types.c:835).
Fix, one root, both stages (FUSE — the cs default + the ww concrete
element must land together, else the inferred array is transient cs!=ww):
- cmd/wcc/type.c type_default(TY_UNTYPED_INT) ty_i32 -> ty_int. The
root; stops scalar AND array truncation at source.
- cmd/wcc/check.c N_ARRLIT empty-elt fallback ty_i32 -> ty_int. Symmetric
pair; count-0 array emits no stores, so byte-id-neutral.
- selfhost/cmd/wcc/check.ww exprtype N_ARRLIT: default the inferred
element's untyped flavor to concrete (untyped_int->int, _float->f64,
_str->str, _rune->rune, _bool->bool, mirror cstage type_default),
empty-elt "i32"->"int", and stamp the synthesized N_TARRAY's .type_ so
slotsize / elemsizeofc / letslotsize read its real [N]int size via the
type table (rule-13) — no letslotsize special-case (SSoT).
combined.ww regen (check.ww embed): w6c + wwdump.
ken v2 corpus re-census (160 files): EXACTLY 5 rows move, ALL CONVERGE
(byte-id YES + run exit 0, none both-wrong, zero regression):
m2_while #108 scalar via alias-bool loop
m8_range1 #104 for-range elem over alias [4]int
m8_range2 #104 over 2-level alias
m8_slice1 #103 inferred array + alias-slice init
m8_slice2 #103 + 2-level-alias slice + re-slice
Bootstrap byte-id neutral (5 combined units w6c==w6c_ww; 0 bare inferred
arrays in selfhost). Annotated controls untouched ([4]i32 stride-4,
[4]int stride-8, byte-id). Pinned in test/wcc/813_arrlit_infer_elem_run
(the 2 direct repros incl the >2^31 truncation teeth + all 5 movers +
controls; test-unit 296).
Closes#103 (inferred-array SEGV + truncation), #108 (cstage scalar
untyped-int truncation), #104 (for-range elem alias i32-stamp), and the
m8_slice []int-init acceptance divergence.
c1/c2 widened flatvariantidxt (selfhost) with the chain + structural
tag-synthesis arms and a >=2 ambiguity os.exit, scoped to the cgen
WIDEN consumer. But flatvariantidxt is a choke-point: the wwstage is/as
ACCEPTANCE gate (check.ww:4677, the #198 spread fallback) reuses it, so
the widening leaked into checker acceptance — vs base 329481c:
* `let v:(void|ali)=…; v is base` (ali=base): cstage rejects, wwstage
ACCEPTED+built — new cs!=ww acceptance divergence (rule-10 break);
* `(void|tb)`, `v is ta` (unrelated same-layout): same leak via the c2
structural arm;
* `(ali|ali2)`, `v is base`: wwstage DIED with the cgen-internal fatal
"flatvariantidxt: source alias chain reaches >=2 variants" DURING
CHECK — a cgen diag surfacing in the checker (layering).
cstage is unaffected: its is/as gate (check.c:2036) is independent of
cg_tag_for_variant (cgen-phase only), so the fuse was already broken at
this site — the cgen-helper change moved wwstage's CHECKER but not
cstage's. This contradicts the #95 fold scope ("cgen-tag fold, no
acceptance change except the ambiguity hard-error [at the widen site]").
Fix (rob-ruled): the is/as gate needs only nominal variant membership =
pass 1. Add an explicit `exactonly` mode to flatvariantidxt — the
checker caller passes true (returns after the exact loop: no chain/
structural arms, no os.exit), every cgen caller passes false (full
tag-synthesis, unchanged). Two consumers, two modes — the honest
representation, not a wrapper. cstage's cg_tag_for_variant has no twin
checker caller, so it stays full-only and is UNTOUCHED by c3 (rule-10
satisfied: the param changes no asm — cgen always passes false; the
checker now MATCHES cstage's reject). casevariantin still backs the
#198 spread fallback.
Pins (test/wcc/944_variant_chain_b95_run.c, +4 rows -> 56 checks):
isas_chain_reject / isas_unrel_reject — BOTH stages reject the leaked
is/as shapes (shared experr substring "not a variant"); the c1 chain +
c2 structural arms no longer widen acceptance.
isas_amb_reject_notcrash — the (ali|ali2)/`is base` shape rejects
CLEANLY (the cgen fatal text would be absent -> red), NOT a crash.
twin_prim_alias_amb — rob's obligated mixed prim/alias TWIN:
(int | ai) ai=int, source aj=int — both share the int bottom under
all-variants counting, so the cgen WIDEN (full mode) hard-errors
("source alias chain reaches >=2 variants"), pinned LOUD both stages.
The deferred question (should is/as EVER accept cgen's richer chain/
structural shapes? = a checker-strictness feature, both stages together)
is filed as task #107, explicitly NOT folded here.
Invariants: c1/c2 cgen behavior unchanged (all cgen callers pass false =
full mode); suite byte-id rows + the dissolution corpus hold. make all
0; sizelint 0; peellint 0 (the mode param adds no peel sites); combined.ww
regen idempotent; test-unit "all 295 tests passed". c3 touches ZERO
cstage bytes — cmd/w6c/cgen.c carries only the c1/c2 additions, and
cmd/wcc/check.c is unchanged from base 329481c.
A nominally-unrelated, structurally-equal NAMED source into a NAMED
variant (kb95_unrel: ta/tb same-layout structs, src ta -> (void|tb))
was LIVE both-wrong-identical byte-id silent: both checkers accept,
both cgens tagged 0. After c1's chain arm finds no shared chain
node, match the variant whose CHASED type type_eq's/typeeq's the
source's chased bottom — chased type EQUALITY only, no
type_is_assignable scalar import, no int widening (ken's binding
scalar warning). Same NAMED-source branch, both stages
(cg_tag_for_variant / flatvariantidxt), forced fuse.
Correctness reference, cite 1 — harec tagged_select_subtype P2+P3
(ref/harec/src/types.c:702-739), verbatim:
if (t->id == subtype->id) {
return t;
}
if (type_is_assignable(ctx, t, subtype)) {
selected = t;
++nassign;
}
...
if (nassign == 1) {
return selected;
}
return NULL;
with type_is_assignable's non-tagged path dealiasing both sides and
accepting composites only via interned pointer equality
(types.c:988-1002), verbatim:
if (type_dealias(ctx, to)->storage != STORAGE_TAGGED) {
to = type_dealias(ctx, to);
from = type_dealias(ctx, from);
}
...
if (to == from && to->storage != STORAGE_VOID) {
return true;
}
Cite 2 — type_hash interns bare composites STRUCTURALLY (banked as
types.c:72-81; verified in the vendored copy at types.c:444 +
struct/union arm :514-525), verbatim:
case STORAGE_UNION:
hash = fnv1a_size(hash, type->struct_union.packed);
for (const struct struct_field *field = type->struct_union.fields;
field; field = field->next) {
if (field->name) {
hash = fnv1a_s(hash, field->name);
}
hash = fnv1a_u32(hash, type_hash(field->type));
hash = fnv1a_size(hash, field->offset);
}
— no decl ident in the hash, so harec's two decls dealias to ONE
interned node and `to == from` holds: acceptance is DEFINITIONAL
under interning, not an arm whose text could be misread. Our store
does not intern; chased type equality is the non-interned rendering
of the same rule.
Honest divergence (the >=2-structural-match hard-error STAYS): under
harec's interning two structurally-identical variants are ONE type —
a union cannot contain it twice — so the ambiguity case is
unrepresentable there; our hard-error (twin texts, shared tail
"source structurally matches >=2 variants — ambiguous without
nominal layout (#95)") is the correct nominal-lossy-model rendering,
not a harec deviation.
Pin table: unrel_struct row added (kb95_unrel graduates ok/1-ok/1 ->
0/0, byte-id held) — suite now 48/48. All c1 rows unmoved.
Invariants: 163-row dissolution matrix at tip — same 3 family
graduations as c1, ZERO new movers; five mains cs-vs-ww byte-id OK;
make all 0; sizelint 0; peellint 0 (no new peel sites — the
structural leg reads only chased ends); all 944 suites + 808 green.
w6c_ww/wwdump_ww main.combined.ww regen'd.
A NAMED struct source that was not pointer-identical to a NAMED
variant fell through every pass of cg_tag_for_variant (cmd/w6c/
cgen.c) / flatvariantidxt (selfhost/cmd/wcc/cgenutil.ww) and the
widen stored tag 0 — both stages, byte-identical, gate-blind: wrong
tag on VALID code at any alias depth, in both chain directions
(.ai/ken-95-oracle.md §2: kb5_v2s1i, kb95_2lvl_i, kb95_deep_src,
kb95_deep_var all both-wrong-identical at base).
New pass 1b, identical both stages (the same route — forced fuse):
after pass-1 exact (unchanged, FIRST — the (str|linerr) protection,
harec's P1 short-circuit), a NAMED source matches the variant whose
NAMED chain shares a pointer-identical node with the source's chain
(an alias IS-A its base through the chain). Two linear NAMED chains
intersect iff they share their chased bottom node (ken §1), so the
walk is implemented as pointer identity of the chased ends through
type_chase_named/tichase — the blessed chase choke-point. NO raw
.under/->under hops were added, so the anticipated `peel-ok: nominal
chain walk (#95)` annotations are unnecessary and the peellint
whitelist is UNCHANGED (continues the B6/B7 fold-peels-into-chase
arc; peellint green).
Variants are counted UNGATED (bare prims are type-table singletons,
so a bare variant node can BE the source's chased bottom): the >=2
guard stays equivalent to harec's nassign>=2 -> NULL
(ref/harec/src/types.c:734-738, tagged_select_subtype P2/P3). >=2
chain hits hard-error with twin texts (prefix convention, shared
tail "source alias chain reaches >=2 variants — ambiguous without
nominal layout (#95)") — drew's ambiguity proviso extended to the
chained set; was a SILENT member-0 tag. Pass-2 bare-source fallback
unchanged. Chased type EQUALITY only — no type_is_assignable scalar
import, no int widening (ken's binding scalar warning).
Pin table (new suite test/wcc/944_variant_chain_b95_run.c, 45
checks, Makefile-wired):
GRADUATIONS exit 1->0 both stages: chain_1lvl_i (kb5_v2s1i
HEADLINE, byte-id held), chain_2lvl_i, chain_deep_src,
chain_deep_var (byte-id held), chain_call_bound81 (kb5_v2s1),
chain_call2_bound81 (kb4_v2_struct2, #95's original) — the two
CALL-src rows waive byte-id, pre-existing #81 zero-fill asm noise
(NO at base too).
NEW LOUD: chain_amb_loud (kb95_amb) — silent tag 0 -> hard-error
both stages.
MUST-NOT-MOVE held: chain_amb_srcA/B (pass-1 precedence),
nom_str/nom_err (#218 nominal regression pin), exact_ctl
(kb5_v2sE2), bare_ctl/bare_2lvl/bare_ambig/bare_ambig2 (pass-2
controls), callret_bound277 (kb5_v2sE #277 cells unchanged,
dual-cell pin).
Invariants: ken's 163-row dissolution matrix rerun — exactly 3
movers, all family graduations (v2s1i/v2s1/v2_struct2 1->0), zero
non-family movers, detectors unmoved. Five mains cs-vs-ww byte-id
OK (ww/w6c/w6a/w6l/wwdump). make all 0; sizelint 0; peellint 0; all
944 suites + 808 green. w6c_ww/wwdump_ww main.combined.ww regen'd
(cgenutil.ww embeds).
checkarrlitfits' nested recursion keyed on the raw elemtn kind; a
named-alias element type ([2]row, row=[2]int) arrives as N_TNAME, so
the inner overlong literal skipped the count+range checks and the
module static-DATA route emitted silently TRUNCATED data (ken's
m7c_global: DATAW 1,2,4,5 — exit-masked once the #60 read fix removed
the segv; cstage loud-rejects every spelling via its typed-literal
assignability net). #105: the W1 fill gate never runs on this route,
severity raised post-#60.
Fix: chase elemtn through resolvealias (transitive) at the recursion
gate — alias spellings of any depth take the same checks as the
direct shape at all four contexts funneling through the choke point
(module let / local let / def / struct-field). A direct N_TARRAY
passes through resolvealias unchanged, so accepted shapes are
byte-identical base→tip (m7c_global_ok + exact-fit alias
field/def/2lvl probed ASM-ID vs a base scratch build). The m7/m7b
local overlong rows stay loud, now via the earlier count-naming
checker diagnostic instead of the cgen #270-1c fatal. The
out-of-range narrow inner element louds "array element out of
range" exactly as the direct spelling already did on wwstage.
808_arrlit_overlong: 37 -> 50 checks (+1 accept control
alias_exact_module = ken's m7c_global_ok with a byte-id cell, +4 loud
flips alias_nested_{module,2lvl,def,field} pinning per-stage texts,
+1 REVIEW AMENDMENT alias_nested_local pinning the m7/m7b text move
— pre-fix ww was loud via the late cgen #270-1c fatal; the row reds
if the diag regresses off the checker count text).
989 ratchet zero flips — no lib module-level literal trips the gate.
Filed sibling, not folded: OUTER alias-of-array overlong
(let g: arr = [5 elems], arr=[4]int) still ww-silent-truncates at the
alias-blind call-site N_TARRAY gates; cs louds with the count text.
The last four raw `->under` reads outside the whitelist were the
static-DATA emitters' ELEMENT-type single peels (the outer type already
chased): emit_array_lit_bytes:14356, emit_strarray_data:14574,
emit_slice_data:14788, let_pre_intern:15088 -> type_chase_named.
:15088 is the :14574 row's label-order leg and must flip in the same
commit or _S_ labels intern in emit order, not decl order (the in-tree
comment at the site); the strarr row's byte-id is the coupling proof.
Behavior moves (ken B7 first-position oracle + impl pre-state, all
pre-observed at 05f7af7):
- [N]alias-struct + [N]alias-str globals graduate cs link-ERR
("undefined reference") -> 0/0 BYTE-ID (cs emits ww's DATAW).
- zero-consumer latent silence closed: a never-referenced
2-level-elem-alias global silently lacked DATA (no reference, no
link error); now emits, pinned by the byte-id cell.
- []alias-str diagnostic routing: the alias escaped the 3-way
slice-of-{str,slice,tagged} fatal onto the downstream "not a
foldable constant" text — now the intended 3-way text (== control).
- []alias-tagged DESIGNED NARROWING: the alias dodged the 3-way fatal
ENTIRELY — cs silently accepted + RAN WRONG for reachable consumer
shapes (review-verified at base: a len+payload-read probe exits 1;
the len-only row was luck-correct). Now loud with the 3-way text;
widen what the gate SEES, never what it ACCEPTS (B6-c2 precedent).
- kb7_slc/slc0 scalar legs byte-NEUTRAL (the synthesized-array
choke-point already handled them); full kb corpus sweep: movers are
exactly the two graduation shapes, nothing else.
tools/peellint (sizelint clone, dep of test/test-unit): character-scan
strips comments and string/char literals, then matches the under-token
accessor-spelling-wide — `->under`/`.under` in C (deref-dot is the
same peel), `.under` in ww, optional whitespace after the operator,
and the line-split continuation (operator at EOL, `under` next line).
Scope cmd/wcc + cmd/w6c + selfhost/cmd/wcc + lib/ww (lib/ww/typ.ww
ruled IN — it is type.c's ww mirror, the accessor layer itself);
`peel-ok`/`peellint-ok` annotations exempt a 10-line window. Green at
this tip = zero unwhitelisted raw peels survive; the gate lands in the
commit that deletes the last raw read (the-funnel-completing-commit-
carries-the-gate; sizelint precedent). Whitelist, 27 entries:
cmd/wcc/type.c :78 :141 construction, :162 chase body,
:180 :193 :214 recursive chase
cmd/wcc/check.c :102 :2572 resolve-state probes, :2586 construction
cmd/w6c/cgen.c :731 probe-cleared scan peel (B5-c1),
:813/:814 :834/:835 peel-ok #218 variant-match
lib/ww/typ.ww :316 construction, :374 :385 :410 :437 :447 :463
:475 :488 :514 recursive chase
selfhost/cmd/wcc/cgenutil.ww :1302 chase body (tichase),
:2759 probe-cleared peel
selfhost/cmd/wcc/check.ww :1815 construction (peellint-ok)
Negative validation wired into 944_peellint_gate (B4 precedent):
re-introduced raw peel (C and ww spellings) REDS the lint; corrupted
annotation (peel-okk-…, token-bounded matcher) REDS the lint; the
check.ww:3683 "io.underread" prose, a code read of a longer field, and
comment-quoted tokens are pinned green regression rows; real tree must
lint clean. 944_alias_emit_b7_run pins all four emit paths
table-driven (14 rows / 36 checks) incl. ken's ww observation cells
(ww checker rejects slice-literal globals, "let: not assignable" —
unmoved; plain []str louds at ww's own emitslicedata 3-way, pinned by
the shared needle).
REVIEW AMENDMENT (reviewer-B7, fix-what-you-find): the frozen tip's
regex matcher passed five compiling evasion spellings green — `t ->
under` spacing, `t->`/EOL + `under` next-line (both stages; ww parses
`t.`/EOL too), C deref-dot `(*t).under`, ww `t. under`, and a string
literal containing a block-comment opener that blinded the regex
comment-strip for the rest of the file. The matcher is now a
character scan (comments + string/char literals stripped before
matching) with the widened token rule above; all six spellings are
pinned RED rows in 944_peellint_gate (checks 10 -> 16). The 10-line
annotation window stays as designed (a peel within an annotation's
window is exempt by construction — the window IS the exemption
mechanism). Lint + test bytes only; zero compiler-source bytes moved
in review.
What this does NOT close, said out loud (f2-ruling): a consumer that
never spells `under` at all — a switch on t->kind that simply never
peels — has no token for the lint to see. The accessor+lint closes the
WRONG-PEEL class (single-peel where chase was needed) by construction;
the NO-PEEL class is closed only at sites where classification routes
through the internalized chasing helpers, and contained elsewhere by
the acceptance-commit-carries-tripwires doctrine, which stays standing
for every future acceptance widening. The gate does not make alias
bugs impossible; it makes the four-times-burned shape unwritable.
Rule-11 note: forced fuse — the four conversions ARE the last raw-read
deletions; peellint cannot be green one commit earlier (consumer-graph
-forces-the-fuse precedent, #61).
Invariants: cs asm byte-NEUTRAL on the whole bootstrap corpus (five
mains + smoke, base-input pre==post); five mains cs==ww byte-id at
tip; _ww binary quartet bit-identical to the W2 baseline (ww changes
are comment-only annotation bytes — codegen-inert, proven by the md5
hold); w6c_ww+wwdump main.combined.ww regen'd via make, idempotent;
989 lib ratchet zero flips (31 byte-id / 9 pinned-divergent / 3
pinned-wwreject across 43 units); sizelint 0; peellint 0;
make test-unit "all 294 tests passed" (292 + the two new suites).
The two ww-side gates from ken's B6-c3 STOP re-attribution
(.ai/ken-b6-oracle.md addendum): WWSTAGE was the alias-blind side at
exactly two cgenexpr.ww sites, both keyed on a bare name-keyed
structlookup that only holds struct-decl names, so an alias-NAMED
spelling missed and fell to the generic-but-runtime-correct route —
byte-divergent from the canonical dedicated shape cs pins post-B6-c3:
(1) `arr[i].f = v` indexed-elem struct-field STORE arm (the write
twin of the task-#8 READ arm, which already chases via tichase);
(2) `&p.f` ptr-field fallback in the TK_AMP N_DOT single-dot leg.
REVIEW AMENDMENT (reviewer-W2, rob's close-by-construction
obligation): the proof-sweep of the two arms found ONE same-class
survivor — (3) the value-struct `&x.f` leg, one leg below (2) in the
same single-DOT arm, same alias-blind bare structlookup. Probe
rW2_ampv reproduced the identical bit-proven mechanism (ww_alias !=
ww_plain; cs_alias == ww_plain) at both base and the frozen tip; the
other in-arm routes are clean (letvar* global helpers chase, str/len
pseudo-field alias legs are filed-loud #96, chained depth>=2 resolves
via tinfo). Same chase applied; arm survivor count is now zero by
the same grep+probe construction.
All three now resolve through structlookupchain (#22) — the name
layer's own alias-chain accessor (the cstage transitive-peel mirror),
already consumed by the sibling `p.f = v` assign arm and cgdot's
pointer-to-struct read. Plain rows short-circuit at its structlookup
head, so non-alias emission is byte-identical by construction; alias
rows land on the same structinfo as their plain twins, so the
dedicated emission converges bit-exactly. esz stays sound:
elemsizeofc reads the chased stamped tinfo (#8 leg).
Convergence proven with ken's pre-staged W2 instrument: ww_alias ==
ww_plain bit-IDENTICAL for kb6_idxf and kb6_ampf (cmp exit 0), which
also lands cs==ww byte-id on both rows; plain controls byte-id held
and their ww asm unchanged base->tip. Full ken-corpus matrix vs the
B6-tip baseline: movers are exactly {kb6_idxf, kb6_ampf} NO->YES;
detectors unmoved (kw1_101 cs0/ww1, fill2 both-loud, tuparg_c
cs0/ww1, fsarg2/fsarg0/try pinned texts verbatim, B5/B6 graduations
all byte-id). Zero behavioral change on any accepted program, zero
acceptance change in either direction.
ww-only: zero C-source bytes; cs binaries (w6c/ww/w6a/w6l) md5
bit-identical base->tip; w6c_ww + wwdump main.combined.ww regen'd via
make, idempotent. 989 lib ratchet: zero flips (31 byte-identical /
9 pinned-divergent #59 / 3 pinned-wwreject #59 across 43 units).
944_alias_cgen_b6_run: idxf_2lvl + ampf_2lvl graduate K_RUN_NOID ->
K_RUN (the two restored byte-id cells) — B6-c3's mutation teeth
restored (reviewer-B6 N-a closes); + amendment rows ampv_2lvl /
ampv_plain_ctl pin the third gate (checks 61 -> 69). All 9 944-family
suites green; sizelint 0; make test-unit "all 292 tests passed".
The exact B6-c5 set (rob b6 spec §2, numbering at 4cac1cb): :3820
(fn-symbol load u), :7201 (#235 len() tuple-elem bu), :7807
(type_default let-init region u), :7900 (append su, str→[]u8 region),
:9944 (`is` source u — the #37 memread predicate feed; the >32B cap
test now keys the CHASED size, mirroring ww's taggedmemread, per the
EYES condition — kb6_memread 40B-union byte-NEUTRALITY is the
regression net, held), :10224 (module-qualified value ref tu),
:10750/:10873 (tagged-union field loads, direct + through-ptr
tag_fu/ptag_fu). Raw `->under` in cgen.c 17→9 — the remaining 9 are
EXACTLY the designed whitelist survivors (:731 nullable_ptr_tag +
:813/:814 + :834/:835 cg_variant_match peel-ok-#218, #95's fold) and
B7's 4 emitter/intern lines (:14356 :14574 :14788 :15088). B6's 49
granted lines are fully retired; grep-verified.
FLIP: kb6_tfread (tagged-union field load direct + through-ptr over
alias struct, was both-correct divergent NO(4)) → 0/0 BYTE-ID; cs
converged onto ww's UNCHANGED asm (#263 polarity, cmp-proven) and the
alias shape equals the plain shape (kb6_tfread_p, same hash — ken's
c5 pre-test). kb6_len / kb6_gref / kb6_memread latent byte-NEUTRAL
as predicted.
TRAIN INVARIANT (held c1..tip): cs-only; w6c_ww/ww_ww bit-identical
to the 4cac1cb baselines (b6bddc8eb5c3ed8e805e50371d4b7017 /
4e9ca8741f19e1f68219ff799a5e5a14) at all five boundaries. cs movers
c4→c5 bounded to exactly {kb6_tfread}; rest of corpus + five mains
byte-NEUTRAL; kw1_101/fill2/tuparg_c/xampdef/amplen1 detectors
unmoved across the whole train; 989 ratchet zero flips.
944_alias_cgen_b6_run final table: 18→22 rows, 49→61 checks
(tfread_2lvl flip + len_2lvl/gref_2lvl controls + memread_40b cap-
watch pin). All 944-family suites green; sizelint 0.
The exact B6-c4 set (rob b6 spec §2, numbering at 4cac1cb): :9422
(N_MATCH Family-C identity-cast peel su), :9598 (match-bind base bu),
:9674 (tryprop non-call source u), :9718 (cg_ret_type peel r at the
cgexpr try region — same rt-route kinship as B5-c3's cgreturn chase:
the nullable/tagged propagate keys the chased enclosing return type
exactly as the return arms do), :9819 (tryunw twin u — LOUD-PRESERVING
ONLY, the #38b sret bound is alias-INDEPENDENT), :9986/:9991
(N_TYPEASSERT u + enum vu), :10103/:10105 (str→[]u8 cap-synth tu/fu),
:10131 (narrowing-cast tu). Raw `->under` in cgen.c 27→17 — remaining
= whitelist 5 (:731 :813/:814 :834/:835) + B7 emitters 4 + the c5
set 8, grep-verified exact.
FLIP: kb6_strcast (str→[]u8 over 2-level alias, was both-correct
divergent NO(2)) → 0/0 BYTE-ID; cs converged onto ww's UNCHANGED asm
(#263 polarity, cmp-proven) and the alias shape now equals the plain
shape (kb6_strcast_p, same hash — ken's c4 pre-test confirmed ww's
dedicated gate DOES fire on alias here, unlike the c3 sites).
kb6_try loud held both stages with the pinned #38b text (try_loud_38b
row); idcast/is/succ byte-id held; zero other movers.
TRAIN INVARIANT: cs-only; w6c_ww/ww_ww bit-identical to the 4cac1cb
baselines (b6bddc8e…/4e9ca874…). cs movers c3→c4 bounded to exactly
{kb6_strcast}; rest of corpus + five mains byte-NEUTRAL; detectors
unmoved; 989 ratchet zero flips.
944_alias_cgen_b6_run grows 14→18 rows, 38→49 checks: strcast_2lvl
flip + idcast_2lvl/is_2lvl controls + try_loud_38b both-loud pin.
All 944-family suites green; sizelint 0.
The exact B6-c3 set (rob b6 spec §2, numbering at 4cac1cb): :3949
(&ident classify ou), :4006 (&mod.G leaf lu), :4124/:4128 (&p.f
ptr-field fallback bu/inner), :5029/:5035/:5052 (indexed-elem
struct-field STORE elemu/inner/bu), :5937 (assign-region esub peel),
:10953/:10956 (chained N_DOT ptr lu/inner), :11046/:11052/:11067
(indexed-elem struct-field READ twin), :11285 (index-region esub peel
— gates key the chased esubu exactly as the ident arm, per the EYES
condition). Raw `->under` in cgen.c 41→27.
RE-ATTRIBUTION (ken c3-STOP addendum, adjudicated on his independent
scratch build): kb6_idxf and kb6_ampf did NOT land on the oracle's
predicted post-states — and the train direction is CORRECT anyway.
Bit-proven mechanism: post-chase cs_alias asm == ww_PLAIN (the
byte-id-gate-proven canonical dedicated shape) for BOTH rows; it is
WWSTAGE that is alias-blind at these two sites (ww_alias != ww_plain
— its indexed-elem struct-field store/read gates and the &p.f
ptr-field fallback don't fire on alias bases and fall to generic-but-
runtime-correct routes). Converging cs onto ww_alias would re-blind
cs — canonical is the convergence target. ampf's pre-c3 byte-id was
both-stages-on-the-generic-route identity (gate-blind, #263-class),
NOT M5 latency — oracle self-correction banked. Both rows pinned
K_RUN_NOID (cs-0 + ww-0, byte-id waived) + plain-control rows pin the
convergence target; they graduate to full byte-id when the filed
ww-side W2 fold (task #102) lands (ww gate chase; kw1/#100/W1 precedent; zero
metric-1 content). chdot/esub byte-NEUTRAL bound held.
TRAIN INVARIANT: cs-only; w6c_ww/ww_ww bit-identical to the 4cac1cb
baselines (b6bddc8e…/4e9ca874…). cs movers c2→c3 bounded to exactly
{kb6_idxf, kb6_ampf} — the named c3 family; both run-cells ok/0 held;
rest of corpus + five mains byte-NEUTRAL; detectors unmoved; 989
ratchet zero flips.
944_alias_cgen_b6_run grows 8→14 rows, 22→38 checks: idxf_2lvl/
ampf_2lvl NOID pins + idxf_plain_ctl/ampf_plain_ctl canonical-shape
pins + chdot_2lvl/esub_2lvl controls. All 944-family suites green;
sizelint 0.
The exact B6-c2 set (rob b6 spec §2): :8402 (callee fn-type resolve cu),
:8435 (variadic slice param vsu), :8561/:8563 + :8577/:8579 (tagged
widen-detect pu/au pairs, arg-class + #38b MEMORY-class) + the two
LOUD-PRESERVING chases :8829 (float-struct rule-7 fatal st) and :8934
(#32 tuple-arg rule-7 fatal targ). Raw `->under` in cgen.c 49→41.
LOUD-PRESERVING discipline: the :8829/:8934 chases widen what the gate
SEES, never what it ACCEPTS. DESIGNED ACCEPTANCE NARROWING (ken b6
oracle c2): kb6_fsarg2 — a 1-level-alias float-struct from a non-ident
source previously DODGED the #271/#165 fatal via the single peel; cs
accepted and GP-passed it runtime-correct by self-consistent luck
(caller+callee agreed on the wrong transport, no SSE eightbyte). Post-
c2 cs louds with the pinned #271/#165 text. ww's cell was already loud
at its own alias-return bound (#272/#276/#277 class) — fsarg2_bound
pins BOTH texts per-stage (experr_ww). fsarg0 plain control stays loud
both stages. :8934 is WATCH-ONLY (alias tuple-args are checker-blocked
upstream, #86/#99): kb5_tuparg_c two-key cells verified unmoved
(cs ok/0 + ww ok/1).
TRAIN INVARIANT: cs-only; w6c_ww/ww_ww bit-identical to the 4cac1cb
baselines (b6bddc8e…/4e9ca874…). cs movers bounded to exactly
kb6_fsarg2 (run-cell ok/0→ERR, no asm emitted — zero run-row movers);
rest of the corpus + five mains byte-NEUTRAL; kw1_101/fill2/tuparg_c/
xampdef/amplen1 detectors unmoved; 989 ratchet zero flips. kb6_sarg /
kb6_strarg / kb6_fsarg (ident twin) latent byte-NEUTRAL per ken's
structural bound.
944_alias_cgen_b6_run grows 3→8 rows, 9→22 checks: fsarg0_loud_ctl +
fsarg2_bound (per-stage experr pins; row struct gains experr_ww for
two-site loud pairs) + fsarg_ident_ctl/sarg_2lvl/strarg_2lvl controls.
All 944-family suites green; sizelint 0.
The exact B6-c1 set (rob b6 spec §2): cgexpr :6359 (tagged-local plain
reassign lu), :6403/:6405 (deref-target assign pu/vt), :6460/:6462
(deref compound-assign pu/vt), :6518 (str/slice/struct reassign lu) +
cgstmt :11696 (nomem null-propagate r), :12047 (assign base peel bu),
:13625 (destructure-reassign rhs ru — chased; the #64 citation above it
stays, the deferral is about the tuple-literal rhs ROUTE, not this
peel). Raw `->under` in cgen.c 58→49.
TRAIN INVARIANT: cs-only — zero selfhost/ or lib/ bytes move; w6c_ww/
ww_ww bit-identical to ken's 4cac1cb baselines (md5
b6bddc8eb5c3ed8e805e50371d4b7017 / 4e9ca8741f19e1f68219ff799a5e5a14).
cs movers bounded to exactly: kb6_streassign, kb5_wstore_a (the
named c1 family); the rest of the kb4/kb5/kb6/kna corpus + five
selfhost mains byte-NEUTRAL both stages; kw1_101 / fill2 / tuparg_c /
xampdef / amplen1 detectors unmoved. 989 lib ratchet: zero flips.
LIVE graduation: kb6_streassign (the :6518 lu single-peel missed
TY_STR at 2 alias levels, fell to the scalar default — `b = a` copied
the ptr WORD0 only, len/cap stale, cs silent exit 1; ww was the
runtime-correct full 3-word reference) → 0/0 byte-id. Designed
graduation: kb5_wstore_a (ken C1-CORR-2 seed — the ident-lhs N_ASSIGN
tagged gate :6359 is cgexpr INLINE, never reached B5's :2456 funnel)
→ wstore_a_2lvl re-pinned K_RUN_NOID→K_RUN in the b5 suite (84→85
checks). kb6_sreassign / kb6_dassign latent controls byte-NEUTRAL as
ken's structural bound predicts.
New 944_alias_cgen_b6_run row table (3 rows, 9 checks): streassign_2lvl
+ sreassign_2lvl/dassign_2lvl controls; Makefile wires
test_alias_cgen_b6_run into the unit list. All 944-family suites green;
sizelint 0.
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.
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.