The four wwstage whole-struct field-copy sites (cgenexpr.ww) copied
`ssi.totsize` — the slot-padded, round-8 structinfo size — instead of
the SOURCE struct's natural size. cstage copies `f->type->size` (the
field struct's aligned r.size; cmd/w6c/cgen.c:5302). wwstage over-copied
into the field's slot padding.
Fix: length = copysrcnatsize(c, n.rhs) = tichase(src.type_).size, read
from the SOURCE node's stamped tinfo (the checker's natural r.size,
check.ww N_TSTRUCT). This never reads structinfo / fi.foff / fi.fsz, so
it is correct at HEAD unconditionally and independent of the
registerstruct natural-offset change (#44/#55) that follows — a pure
wwstage convergence onto the length cstage already emits. Distinct from
the existing structnaturalsize (structinfo max(foff+fsz), a #44-coupled
source).
LENGTH ONLY. The ragged-tail completeness (both stages' field copies
inline a tail handling only {4,1}; a natural size %8 in {2,3,5,6,7}
falls through to an 8-byte MOVQ over-read) is a SEPARATE both-stage
class — cstage cgen.c:5302 has the identical incomplete tail — folded
into #73 (route both stages' field copies through the canonical greedy
aggcopy emitter). Touching only ww's tail here would create a gate-blind
cs!=ww on narrow-tail inputs, so it is deliberately left for the
both-stage fix.
No isolated runtime repro: the over-copy writes [natural, totsize),
which under HEAD's slot-padded field layout is the field's OWN padding
(the successor parks at the next slot). It only becomes a clobber once
#44 packs the successor at its natural offset (the 681 ragged_tail_12B
regression that forced this ordering). So this commit is byte-id-clean
and a no-op on the present corpus; its proof is the all-green run plus
the #44 commit that depends on it.
buf.len on a [MAX]u8 returned 0 — the cgdot len arms (local/global/
def) and letemitsize only read an N_INTLIT dimension. Route a
non-literal dimension through tichase().alen (the #21 fix mirrored
into the field-read consumers; .ptr arms are dim-independent).
Review-era task #56.
alloc(alias{...}) keyed the size and field-fill off the syntactic
alias name — it under-allocated and emitted zero field stores. Chase
the alias via structlookupchain to the resolved struct (depth-2
chains verified). The scalar else-branch keeps its pre-existing
benign cs!=ww divergence, surfaced here and deferred as task #57
(site note at the arm). Review item #26.
The ?/! success-is-str decision was name-keyed off the FIRST variant
and only handled call operands — an ident operand with junk registers
unwrapped garbage, and error-first unions picked the wrong variant.
Key on the stamped success variant (successvariant + typeisstr,
mirror cgen.c:10459-10466/10595-10602). Review item #16.
Slicing an array whose dimension is a def constant gave len 0 — the
default-hi and cgbasecap arms only read an N_INTLIT dimension. Route
the dimension through the type table (one root, four arms: default-hi
and cgbasecap, local and global each), byte-identical for the def-dim
SLICE shape. The def-dim array .len/.ptr FIELD-read keeps the
N_INTLIT-only limitation — filed as task #56 (cgdot sibling).
Review item #21.
The arg-drain loop checked node_isfloat before popping a widened
arg's GP words, so a float arg adjacent to a widened (tagged) arg
read the wrong stack slot: the f64 took the widened payload (#30), or
the float arm ate the tag word into X0 and the payload landed in DI
as the tag (#48). One missing branch, two manifestations — mirror
cstage's widen-first pop (cgen.c:9650-9665). Review items #30+#48
(fold reviewer-verified one-mechanism against the cstage twin).
A str field reached through a chained dot (o.i.s) emitted two loads
(ptr, len) and stored a stale CX as the cap — both stages, at any
non-zero chain depth. Emit the full header at the chained-dot leaf.
Review item #29.
Both stages move in one commit: one emission contract; splitting
would leave the byte-id gates red between the halves.
Reading a slice-typed tuple element (t.0) loaded only the pointer
word; len and cap took whatever was left in BX/CX — silent garbage in
BOTH stages once anything clobbered the registers between build and
read. Load all three header words at the tuple-element arm. Review
item #28.
Both stages move in one commit: one emission contract; splitting
would leave the byte-id gates red between the halves.
Casting a global str to []u8 dropped the cap=len synth (the trailing
MOVQ BX,CX) — the synth was gated on a local-ident source shape.
Key it on the source type so local/global/field/call sources all get
the header. Review item #19.
s.f *= v silently became s.f = v (and the other non-+=/-= ops dropped
likewise) in BOTH stages across five lvalue sub-arms: via-ptr field,
direct local field, str/slice pseudo-field, and the two global-field
forms. Funnel all five through a shared combine dispatch
(cgdotfieldcombine / cg_dotfield_combine) emitting the load-OP-store
sequence at field width, hard-erroring the unhandled kinds — close-by-
construction so no arm stays on the old PLUSEQ-only path (the #133
BUS-routing lesson; #227 sites A/B are the closed siblings). The
refactor routes the corpus's existing +=/-= sites through the same
helper output-identically (byte-id held). Review item #34.
Both stages move in one commit: one emission contract; splitting the
halves would leave the byte-id gates red in between.
arr[i].field /= %= <<= >>= silently dropped the op (load-combine-store
emitted plain assignment) in BOTH stages — gate-blind, the #133 class.
Route every compound op through the combine dispatch at the indexed-
field arm and hard-error the unhandled operand kinds (float/str/slice/
tagged), per the #133 template (3986818). The runtime-correct target is
the op's own algebra (a OP= b == a = a OP b). Review item #33.
Both stages move in one commit: the fix is a single emission contract —
splitting cstage cgen.c from selfhost cgenexpr.ww would leave the
byte-id gates red between the halves.
'as' on a nullable value compared the pointer itself to a tag (the
missing arm). Mirror cgtypetest's nullable fold and the cstage twin
(cgen.c:10694). Review item #17.
Try-propagation on a nullable value fell to the tagged path and
compared the pointer to a tag — the null check came out inverted.
Mirror ww's own cgtypetest nullable fold (cgenexpr.ww:652) and the
cstage twin (cgen.c:10366/10504). Review item #15.
A module-global float-bearing struct passed by value drained all-GP —
the SSE-cursor classify keyed on the node shape and missed the global
ident; key off the stamped struct type (per-eightbyte classify,
F7-flavored stamp fix). Review item #31; dual-stage rows red-proven.
Reassigning a module-global tagged union stored the payload into the
tag word; emit the full tag+payload store to g(SB). Both-wrong pair:
cstage silently DROPS the store entirely (filed task #41) — rows pin
ww-runtime-correct and the documented cstage residual. Review item #32.
The probe accepted any inner ident, hijacking same-leaf locals as a
module qualifier; gate on the untyped(module) inner only (mirror the
cstage twin). Review item #20; dual-stage rows red-proven.
The global tagged ident operand read saved BP instead of the global:
'is' compared garbage as the tag; 'as' never had a payload. Route the
load through the g(SB) base — tag at +0, payload at +8, cap at +16 for
str (one mechanism, both consumers). The 'is' half aligns ww UP
(cs==ww pinned); the 'as' half is a both-wrong pair — cstage spills an
uninitialized payload register (its N_TYPEASSERT assumes cgexpr filled
AX/DX/CX; filed as task #46), so its rows assert ww-runtime-correct
with the cs divergence documented until #46 lands. Review item #18.
A slice-typed field of a module-global struct stored only the str-form
words; widen the arm to the full slice header via the g(SB) base
(mirror cgen.c sibling arm). Review item #35; dual-stage rows red-proven.
cgindex's tagged-element classification whitelisted base node kinds;
call- and slice-based tagged elements fell off the list and dropped
the payload words (review finding #23). Classify by the stamped
element type. 989_taggedidx_run pins cs==ww (red 2/6 pre-fix).
cgindex read one word for a str/slice ELEMENT of a chained index
(xs[i][j], f().s[i]) — the element-kind gate keyed on node shape and
missed non-simple bases, dropping the 24B/16B header load (review
finding #22). Key on the element-type stamp; 989_chainidx_run pins
cs==ww (red 3/8 pre-fix).
The value-passthrough arm gated on node shape (isenumexpr); a
constant-folded enum member reaches cgen as an int-literal node,
missed the arm, and fell into the tagged-union assertion path
(CMPQ $0 + JNE -> exit 1) — silent wrong code for every `enum as
int`, fnmatch's flag tests included. Gate on the stamped operand
type (tichase == TY_ENUM) at the cgtypeassert choke point, mirroring
cstage cgen.c N_TYPEASSERT; dead node-shape helpers deleted. Tagged
`as` assertion path byte-id-unmoved (control rows). Six @test pins
in attest_pass.ww, dual-stage. (#27b-team, cat-A)
Indexing a non-ident pointer-yielding base -- a direct cast
((&a):*[4]u32)[i], a call result mk(&a)[i], a slice, a type-assertion --
used wwstage's default 8-byte element stride/load instead of the real
element type's, reading garbage (cast-base i32 index: cs=30, ww=0). The
cgindex esz derivation gated on a whitelist of base node-kinds (DOT /
UN-deref / INDEX); an N_CAST/N_CALL/N_SLICE/N_TYPEASSERT base matched none.
Rather than extend the whitelist (whack-a-mole), this mirrors cstage's
uniform idx_eff read: N_INDEX keeps its own arm (chained-index byte-id
preserved), and every other non-ident base now derives esz/stride/load-
width/signedness from the stamped n.type_ -- closing the class by
construction (base set ident/dot/un/index/cast/call/slice/typeassert).
cstage was already correct (uniform); w6c md5 unchanged. byte-id 990-997
8/8, no lib pin flips. test/wcc/830 (9 base shapes, byte-id per width,
signed + unsigned). Folds the N_CALL sibling #22.
wwstage compiled str ==/!= as a single CMPQ on the eager-eval'd ptr word
(len ignored), so two distinct-pointer equal-content strings compared
unequal. cstage was already correct (CALLs rt_streq, the #154 cbinop fix).
The wwstage cgbin had no str-awareness -- every comparison fell to the
generic CMPQ tail; the #154 fix was never mirrored.
wwstage-only: a cgstreqpush helper + a str ==/!= branch at the top of
cgbin (before the generic eval collapses the header), byte-matching cstage
cbinop:4564-4623 -- push rhs/lhs (len,ptr), POPQ DI/SI/DX/CX, CALL
rt_streq, XORQ $1 for !=. Gated on typeisstr (= cstage node_isstr, which
also catches module-global str idents). cstage cgen unchanged (w6c md5
unchanged). The str== .s is byte-identical cs==ww for local, global,
aliased, chained, and condition operands.
Graduates 3 lib byte-id pins (test/wcc/989_lib_byteid #59.1 asciitest,
#59.11 toktest, #59.12 asttest) M_DIVERGE->M_ID -- they used == on str and
were pinned divergent because of this bug; now byte-identical. byte-id
990-997 8/8. test/wcc/827 table-driven.
An error-first tagged union -- error variant at tag 0, success at tag 1+,
e.g. (myerr | u16) -- was silently miscompiled by wwstage: the try/propagate
codegen hardcoded success = tag 0, so the actual success value (tag 1)
failed the CMPQ $0 and fell to the error path -> exit(1) instead of the
value (44). cstage was correct (computes the success tag via
cg_tagged_success_tag = first non-error variant).
wwstage-only: a successtag/successvariant helper (mirroring cstage) replaces
the hardcoded tag-0 / first-param assumption at all four try sites --
cgtryprop (?), cgtryunw (!), and the two latent shift sites cgtrytupleshift
+ cgtrytaggedshift (which bite an error-first union with an aggregate
success payload). Success-first unions (the Hare idiom + what the selfhost
uses) keep successtag=0 -> CMPQ $0 unchanged -> byte-id-neutral on 990-997.
cstage untouched (w6c md5 unchanged).
byte-id 990-997 8/8. test/wcc/825 table-driven (errfirst must/prop +
tuple-success + success-first control). A separate nested-tagged-union
construction divergence is filed (#10/#125).
let pi = 3.5; pi * 2.0 (an inferred-type float module-global) was silently
miscompiled by wwstage: untyped_float wasn't defaulted, so letemitsize
sized it 0 -> no DATAW emitted -> the pi load was dropped, X0 kept a stale
spill -> 2.0*2.0 = 4 not 7. cstage became correct via #150-B's sym-repoint
(stamps f64 -> MOVSD), so this aligns wwstage UP, byte-identical.
wwstage-only: cgen.ww defaultinferredlets gains the untyped_float->f64 arm
(mirrors the untyped_int->int arm; the codebase's own #135-deferred
carve-out at cgen.ww:1079-1082, unblocked now that #150-B killed the
rule-10 divergence it feared), and cgenexpr.ww cgident gets a letfloatprim
fallback (the same primitive-TNAME SSoT letemitsize already uses, since a
renamed primitive TNAME carries no tinfo stamp). cstage cgen unchanged
(w6c md5 unchanged). int-inferred globals stay integer.
byte-id 990-997 8/8. test/wcc/824 table-driven. The N_CAST-no-recurse
parity (check.c:1276) is filed separately (#19).
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.
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.
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.
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.)
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.
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).
`&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.
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
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.
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 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).
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).
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).
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.
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.
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.
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).
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.
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).
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.