A module-global base has no frame slot; treating its local-lookup miss
as offset zero wrote .ptr/.len/.cap at the caller return address. LEAQ
the symbol like the struct-field global arms do. Both stages.
The a[i] = src copy loop enumerated ident/field/deref sources; an
N_INDEX rhs fell to the scalar tail and truncated the element. Route
it through the generic place-address funnel. Both stages.
The direct-field assignment arms enumerate CALL, STRUCTLIT, and local
IDENT producers; an addressable N_DOT/N_INDEX/deref rhs fell through to
the scalar tail, so a 16-byte struct field copied only its first word.
Resolve both places through the existing address funnels and use the
tail-aware aggregate copier. Both stages.
A str `def` has no name(SB) header; the streq push arms read a frame
slot that does not exist. Load the literal through cgexpr (AX=ptr,
BX=len) instead. Both stages.
wwstage exprtype's N_CALL fn-value arm peeled ALL pointer levels before the
TY_FN gate, so a deref-less `pf(21)` where pf:**fn type-checked and lowered --
then segfaulted at runtime (a silent miscompile). cstage peels exactly one
level and loud-rejects (the C6a discipline, check.c:1947).
Align wwstage DOWN: peel one level (loop -> if); a remaining non-TY_FN callee
hits a loud "calling non-function" reject mirroring cstage's message. Both
stages now reject the exotic deref-less `**fn`/`***fn` shape; the legitimate
`(*pf)(21)` and one-level deref-less `f(21)` (f:*fn) still compile + run.
Multi-level autoderef is a separate deferred FEATURE, not a miscompile to lower
(rule 7/10 -- align the richer stage down to the leaner, no value ships).
ww-only change (cstage is the correct oracle); a reject emits no asm, so the
byte-id baselines and LANGBYTEID floor are unchanged.
Pins: cfail test/wcc/data/fnptr_pp_derefless_reject (both stages reject,
reddens-on-revert -- the silent miscompile resurfaces if the fix is reverted) +
test/lang/fnptr_derefless_call_test (positive guard (*pf)(21)==42 and one-level
f(21), value-asserted + byte-id, so the fix does not over-reject the legitimate
one-level autoderef).
Completes the #14 close-by-construction begun by the helper extraction (e7fefa3): every <=24B aggregate register-store now routes through cg_agg_reg_store/cgaggregstore and handles all tail sizes. B/C/D/G fix a silent both-stage drop of a 3/5/6/7-byte tail (byte-id-blind: both stages dropped identically, so the gate could not see it). A/F enable a previously loud-rejected shape (a cgen backend gap, not a type rejection; harec accepts and lowers it). G (global g=f() array) routes symmetrically, dest_padded=false. The #11 arr[i].f scratch loops fold into the helper (dest_padded=true, byte-id zero-change), completing the grep-audit. Pins value-assert each eightbyte (the class is byte-id-blind) and redden under each stage's independent revert; site H's 3/5/6/7 let-receive stays a loud fatal (#22).
cg_structlit_fill/cgstructlitfill dispatched scalar field stores as {1->MOVB, 4->MOVL, else->MOVQ} with no fsz==2 case, so a 2-byte field was stored with an 8-byte MOVQ. Interior over-stores were harmlessly overwritten by the next field, but the LAST field at the frame edge corrupted the saved base pointer: an (S|e) union success variant places the struct payload after the 8B tag, landing the last field at -4(BP), so MOVQ AX,-4(BP) wrote into saved [BP] and POPQ BP restored garbage — a silent both-stage caller-frame clobber. Route the scalar store through the existing fldstoreop/fieldstoreop helper ({1->MOVB,2->MOVW,4->MOVL,else->MOVQ}), both stages; the #13 graduation comments already pre-documented this resolution. Pure width fix, no loud-stop (scalar widths are always {1,2,4,8} and narrowing is always correct). Value-asserting pin: an i64 sentinel live across the union-maker call (detects the clobber directly) + all members, with a non-union control.
A struct/array success variant in an (S|e)! / r? unwrap dropped eightbytes on BOTH stages (byte-id blind). Two layers: (L1) the unwrap success shuffle (cgtrytaggedshift) matched no arm for a struct/array success and fell to a bare MOVQ DX,AX, materializing only w0 — widen the existing nested-TAGGED shift's gate to admit TY_STRUCT/TY_ARRAY (the in-cap union packs the payload as raw GP words past the tag, so that shift is exact); (L2) the aggregate store arms gated on rhs.kind==N_CALL and stored one word for an unwrap rhs — relax to also admit N_TRYUNW/N_TRYPROP at the three silent store shapes (arr[i]=, single-dot field, indexed-field), reusing the materialise scratch path (now #10-correct). Rule-7 LOUD-STOP for a float-bearing success variant (an SSE eightbyte cannot ride the GP {AX,DX,CX} shift, #165). The four already-loud unwrap consumers (let-receive #7, call-arg #271, assign-existing #49, resolver-field #24) stay loud; global/chained single-dot field (#16) and the sub-8-tail-through-unwrap union-maker frame clobber (#15) are separate follow-ups. Value-asserting pin, reddens under each stage's independent revert.
The in-cap aggregate-receive materialise emitted a single narrow tail MOV that fell to MOVB for a 3/5/6/7-byte sub-8 tail, storing one byte while the scratch->dest copy read the full tail from uninitialised scratch — silently dropping members at the C2c whole-element arm (arr[i]=mk()) and loud-stopping at the #11 field arm. The scratch slot is ceil-8 padded (local_alloc/localadd round to 8) and the copy reads only tsz bytes, so flipping the tail default MOVB->MOVQ stores the full register harmlessly into the slot's own pad (in-bounds for in-cap <=24B); 1/2/4-byte tails stay byte-identical. Both stages symmetric. Removes the now-redundant #11 sub-8-tail loud-stop (keeps the float #165 and over-cap #234 loud-stops). The same narrow-tail materialise recurs at 6 other cstage sites (task #14).
Retires the obsolete idx_dot_aggret_subtail_loud //ww:error fixture (both stages now compile the case) and converts it to a positive cstage run-test; the struct-field shape is byte-id-divergent only via the pre-existing #9 frame-size bug, so the value pin uses array-field shapes. Value-asserting, reddens under each stage's independent revert.
The N_TRYUNW/N_TRYPROP success shuffle materialized {ptr,len,cap} into {AX,BX,CX} only when the success variant was a str; a slice success got only MOVQ DX,AX (ptr), leaving every slice-unwrap consumer (call-arg push, let-receive store, ident-source) reading junk .len/.cap — silent on BOTH stages (byte-id blind, cstage not the oracle). Widen the success gate to type_isstr||type_isslice (cstage) / typeisstr||typeisslice (wwstage) at all four shuffle sites; str and slice share the identical 24B header shuffle. Stays str||slice-specific — a struct success variant uses a different {AX,DX,CX} ABI (task #12). Value-asserting pin (len!=cap, poison-decoy) reddens under each stage's independent revert.
Follows #6 Mech A (Fix-R); order forced (C1 first or the slice call-arg push reddens byte-id).
nodeisslice/nodeisstr lacked an N_TRYUNW/N_TRYPROP arm, so a str/slice produced by an unwrap (f()!, r!, r?) and passed as a call arg fell to the 1-word scalar push, dropping .len/.cap; cstage's type-keyed node_isslice/node_isstr already pushed 3 words. Add the type-keyed arm reading the checker-stamped success-variant n.type_, mirroring #9's N_UN/TK_STAR arm. Fixes the str case (wwstage align-up to cstage); the slice success shuffle that both stages still get wrong is fixed in the Mech B follow-up.
The arr[i].f=src legacy assign block enumerated scalar field-type arms then fell to a 1-word scalar default, so a non-call aggregate source (ident/dot/index) cgexpr'd only its first word into AX and stored one eightbyte — silent on BOTH stages (byte-id blind). The non-indexed bases (local/deref/chained/global) reach the general assign resolver's canonical aggargsrcaddr+aggcopy; the indexed arm short-circuited before it. Route the indexed base through the block's own proven &arr[i] spine into the same aggargsrcaddr+aggcopy emitters (DRY — no third copy), dual-site symmetric. Unlike #11's in-cap arm, the source is a memory address so aggcopy is a pure memcpy: float bits and the sub-8 tail transport verbatim, no loud-stop needed. Did not fall through to the general resolver because its cgplaceaddr N_INDEX arm rejects a *[N]S (TY_PTR) base (latent resolver gap, filed separately).
Contained to the indexed base + non-call aggregate-field rhs; value-asserting pins redden under each stage's independent revert.
The arr[i].f=mk() assign arm had no aggregate-field sub-arm, so a by-value aggregate field receive fell to the scalar default (one MOVQ, dropping DX/CX) — silent on BOTH stages (byte-id blind). Add a dual-site symmetric in-cap N_CALL arm mirroring C2c (c83a340): scratch-first materialise AX/DX/CX, then word-copy to (fi.foff+k*8) within &arr[i], sizing from the natural field size fi.fsz (not slotsize). Rule-7 LOUD-STOP for the three cases the in-cap GP path cannot transport: over-cap sret (#11c/#234), a float-bearing field whose eightbyte classifies SSE (#11/#165), and a 3/5/6/7-byte sub-8 tail the single narrow tail MOV cannot express (the general cascade tail is the shared C2c/#11 follow-up, task #10). Value-asserting pins (poison-seeded, redden under each stage's independent revert) plus cfail pins for the three loud-stops.
Contained to the indexed base + in-cap call rhs; arr[i].f=src (#11b) and over-cap (#11c) are separate.
nodeisslice/nodeisstr lacked an N_UN(TK_STAR) arm, so a deref-source slice/str call arg (f(*h), h:*[]T) fell to the scalar single-PUSHQ default — marshalling only .ptr and dropping .len/.cap. Add the type-keyed arm (read checker-stamped n.type_, mirror cstage node_isslice/node_isstr and the sibling N_DOT/N_INDEX arms). cgen already loads the full 24B header (C1b c67f362); this fixes only the call-arg push/pop count. wwstage-only align-up; cstage was always correct.
cstage rejected a deref-less function-pointer call `f(21)` (f: *fn...) with
"calling non-function" while wwstage accepted it and ran correctly -- a
cs!=ww divergence. Hare auto-dereferences a pointer callee to its fn type
before the call. Peel one pointer level after the named-type chase so
cstage accepts a `*fn` callee, matching wwstage and Hare.
One level only, deliberately: the #181-cgen indirect-call path lowers the
callee VALUE as the target (CALL AX), which is the fn address for a single
`*fn` but only the address-of the fn-ptr for `**fn`. A multi-level peel
would accept `**fn` past what cgen can lower -- a silent both-stage
miscompile (returns garbage). cstage stays loud on `**fn` (rule 7);
wwstage's loop-accept of all levels (check.ww:3763) is the over-permissive
side, filed for align-down plus the deeper cgen multi-level autoderef.
Surfaced by the codegen miscompile hunt (finding C6a). Pinned by
test/lang/fnptr_derefless_call_test.ww (deref-less + canonical (*f)(21),
multi-arg, alias-chain, tuple-return; reverting the peel reds the
deref-less rows at compile).
`alloc(Outer{ x = Inner{q=10} })` dropped the nested struct-literal field:
the alloc path had its own inline fill loop with only scalar/float/str
arms, so a field whose value is itself an N_STRUCTLIT fell to the scalar
tail and stored MOVQ $0 (cgexpr leaves a whole aggregate in no register)
over the inner slot. Both stages emitted the identical wrong fill, so the
byte-id gate was blind to it.
Route alloc's fill through the existing shared structlit-fill helper (the
one the BP-relative/global/local structlit sites already use -- it handles
nested-struct recursion, N_ARRLIT, str/slice and tagged) via a new 4th
destination mode DST_PTR_SP that reloads the heap base from (SP). This
deletes alloc's divergent inline loop, the lone site lacking the recursion.
As a side effect it also fixes a latent slice-field drop in the driver's
own alloc(sepgraph{...}) (pkg.len/.cap were dropped; the consumer reads
neither -- g.n is the count SSoT). Nested-array fields are closed in-class;
a nested tuple-LITERAL field now errors loudly and symmetrically (the #49
non-addressable gap, previously dropped silently at alloc only).
Surfaced by the codegen miscompile hunt (finding C7c). Pinned by
test/lang/alloc_nested_field_test.ww (nested struct depth 1+2, nested
array, adjacent multi-nested, sibling-no-clobber; reddens on revert).
Routing preservation proven: the whole test/lang corpus is byte-identical
HEAD vs fixed except the new pin; self-compile byte-id (990-996) green.
`arr[i] = mk()` where mk returns an in-cap (<=24B) struct/tuple/array
left the result in the #4 cgreturn registers (AX/DX/CX), but the
N_ASSIGN-into-N_INDEX path had no arm for an N_CALL rhs, so it fell to
the scalar store tail: only member 0 was written and the index scale
clobbered CX. Both stages emitted byte-identical wrong code (the
documented-but-silent #31-G gap), so the byte-id gate was blind to it.
Add an in-cap N_CALL-rhs arm: materialise the return into a frame scratch
first (keeping the CALL at the frame's natural 16B alignment), resolve
&arr[i], then word-copy the full eightbyte count + sub-8 tail -- mirroring
the #4 receive shape and the #270-1b copy. The eightbyte count derives
from the element size in the type table. Over-cap returns (#234),
non-call rhs (#270-1b) and tuple literals (#121) are unaffected; the
sibling field/deref shapes stay loud (#24).
Surfaced by the codegen miscompile hunt (finding C2c). Pinned by
test/lang/idx_aggret_recv_test.ww (10 value-asserting rows: 2/3-eightbyte
structs+tuples, array elem, sub-8 tail, const/runtime index, all four
base shapes; reddens on revert).
An untyped float literal defaults to f64, so in an f32 context it was
materialized as f64 then bit-truncated by a raw MOVSS (low-32 reinterpret)
rather than narrowed -- e.g. `let x: f32 = 2.0f32; x * 3.0` multiplied by
0.0f. Twelve byte-id-gate-blind both-wrong miscompiles, all this one cause
(compare, binop, call-arg, struct-field, array-elem against an untyped
literal).
Broaden coerce_floatlit to stamp the untyped fconst type_=f32 across the
f32-context sites (assign rhs, call-arg, struct-field, array-elem) and to
descend the implicit-cast shapes (peel unary +/-/cast, recurse binop
operands AND the binop node, recurse arrlit elems), mirroring harec's
lower_implicit_cast. The existing CVTSD2SS gate then fires; cgen is
unchanged. f64 contexts are untouched -- the stamp is gated on TY_F32.
Surfaced by the float codegen sub-hunt (= the deferred #120). Pinned by
test/lang/f32_untyped_narrow_test.ww (22 value-asserting rows incl. f64
controls; reddens on revert).
`let s: str = *h` (a str/slice loaded by value through *str / *[]T)
fell through the N_UN deref arm to the scalar load, emitting a single
MOVQ that read only the 8B .ptr and left .len/.cap from stale registers,
so len(*p) returned garbage. Both stages emitted byte-identical wrong
code, so the self-compile byte-id gate was blind to it. Add a str/slice
arm that loads the full {ptr,len,cap} via cgslicehdr when the chased
pointee is TY_STR/TY_SLICE.
Surfaced by the codegen miscompile hunt (finding C1b). Pinned by
test/lang/deref_hdr_test.ww, which interposes a different-sized decoy
header so the test reddens when the arm is reverted.
wwstage rejected a module-level `const/let []T = [arrlit]` global with "let: not assignable"; cstage accepts (textbook Hare, ref/hare/path/stack.ha:30). The arrlit->slice admission in checkletassign was gated local-only; lift it to module scope too, aligning wwstage UP to cstage's arrlit_init_fits (check.c:3406-3409, slice arm 519-520). cstage unchanged.
Two guards the un-gating requires: the n.rhs.lhs=arr stash stays local-only (a module decl keeps its raw N_ARRLIT for DATA emit, so stashing would leave an untyped count node for the pass-3 asserttyped walker); and tuple-element slice globals are excluded at module scope, because the synthesis delegates element checks to isassignable which lacks a strict tuple arm (#38) -- a [](str,*fn) table would over-accept a sig-mismatched &fn that cstage's strict type_assignable rejects (#124) -- so they stay on the existing typeeqast path.
Closes two divergences 944_alias_emit_b7 pinned: Group A (cstage-runs/ww-rejects) migrates to test/lang/slice_global_arg_test.ww (promoted from _runonly, now cs==ww byte-id); Group B converges to a shared emit_slice_data reject with the identical diagnostic.
fold-2 chunk C5 (drew's Fam8-13 plan), the highest-risk chunk: 21 match/tagged
value-row C drivers re-homed. 20 -> test/lang/*_test.ww @test row-tables + 12
runww //ww:error carriers (both stages reject). The global-tag cluster
(globtag*/globstructwiden/taggedderefstore/...), which sits on the #15/#17
global-ptr fix, was empirically probed byte-id CLEAN -- the predicted hotspot
surfaced ZERO fresh cs!=ww. Carves: variant_chain_b95 #81 -> _runonly (genuinely
diverges at HEAD); callret_bound277 #277 -> slim C pin (cs-runs/ww-rejects),
mutation-gated. 929_tagged_memarg kept whole (SSE-ABI asm conformance). 19
drivers deleted, 944_variant_chain slimmed to the #277 pin.
The match-on-tagged-struct-field divergence (former #26) probed RESOLVED for all
its cited shapes (938 voidstr_field/recursion_torture, tagnorm dedup_match all
byte-id cs==ww + value-correct) -- closed no-reproducer-at-HEAD, attribution to
#15/#17 INFERRED. Those rows migrate as normal byte-id @test and serve as the
REGRESSION SENTINEL for the inferred close (a resurgence trips the gate).
LANGBYTEID floor 93->113; test count 374->355 (19 deleted; 929 + 944_variant_chain
kept). do-not-auto-batch (926_tagscr/940_global_sret/940_str_forrange) untouched.
fold-2 chunk C2 (drew's Fam8-13 plan): 14 tuple value-row C drivers migrate to
15 test/lang/*_test.ww @test row-tables (the +1 is 954_tuprecv, slimmed not
deleted -- its value rows split out while the asserttyped-stamp dimension stays
as a carrier-split C pin, mutation-proven non-vacuous). Reject rows move to 32
test/wcc/data/*/case.ww //ww:error carriers (runww asserts the substring in
BOTH stages). The test-lang byte-id (LANGBYTEID) gate gives cs==ww automatically
and is strictly more sensitive than re-running the wwstage leg; floor 59->74.
Tuple surfaced zero cs!=ww as the plan predicted -- no value-only carve. The 945
trio folds in here; 940_global_sret / 940_str_forrange / 926_tagscr untouched
(routed to drew per-file). Test count 402->388 = the 14 retired drivers.
fold-2 chunk C1 (drew's Fam8-13 plan): 960_opaque_decl_run.c and
962_opaque_assign_cast_run.c were value-row C drivers. Migrate their 3+3
cases to test/lang/opaque_decl_test.ww and opaque_assign_cast_test.ww as
@test row-tables. The test-lang byte-id (LANGBYTEID) gate gives cs==ww
automatically -- 960 was cstage-only-run before, so this strengthens it.
Both byte-id clean, no cs!=ww carve. Retire the 2 C drivers (LANGBYTEID floor
57->59) and repoint a dead comment ref in lib/sort/sort.ww. 961_opaque_guards
(reject/guards) stays in C -- fold-3 territory, not in the Fam12 fold-2
worklist.
#152 link-the-let-before-its-init miscompile (miscompiled IDENTICALLY on both stages, so byte-id 990-997 was blind; only a runtime value check catches it). Lifted the run fixture into test/lang/letshadow_test.ww (primitive @test value pin; byteid-eligible — symmetric, cs==ww). byteid floor 56->57. Closes the fold-3 #7 umbrella (last C test twin retired).
#154 cstage-only str== global-header miscompile (str== fast-path read the global str header off BP+0 instead of name(SB); byte-id 990-997 blind to a runtime-value miscompile). Lifted the run fixture into test/lang/strglobeq_test.ww (primitive @test value pin; byteid-eligible — post-fix cs==ww). byteid floor 55->56.
949_dotfield_compound + 949_idxfield_compound are one bug class (#133-lineage compound-assign load-op-store on field lvalues; #34/#33, #263 carve-out) sharing the combine + hard-error path, so the two C carriers fuse into one commit: 26 value rows -> test/lang @test row-tables (primitive-only asserts), 8 reject rows -> runww //ww:error dual-stage carriers. byteid floor 50->52.
#133 indexed-scalar compound-assign. The 21 rows split by observability:
18 value rows -> test/lang/idx_compound_test.ww (one @test fn each,
primitive-only asserts, slot-poison + neighbor-unchanged read-back);
3 reject rows -> test/wcc/data/idx_compound_{float_indexed,str_indexed,
chained_ptr_float}/case.ww as //ww:error, asserting BOTH stages reject on
the shared diagnostic body (cstage's leading prefix excluded). The C twin
is retired; its coverage is a strict superset of the original rows.
First reject-bearing fold-3 migration -- validates the runww dual-stage
home end to end. LANGBYTEID_EXPECTED_MIN 49 -> 50.
cgassign had dedicated N_DOT-store arms for a local-ptr base, a global
value-struct, and chained bases, but none for a global-pointer scalar
field. That case fell through to the generic cgplaceaddr/dotchainaddr
route, which folds the field offset (ADDQ $foff,BX) then stores to (BX).
cstage emits a single displacement store (MOVQ AX,foff(BX)) via its
via_ptr global scalar arm, so the two stages diverged on asm shape
(rule 10). Both forms are runtime-correct here -- BX is a fresh throwaway
in the generic route -- so this was a byte-id divergence, not a
miscompile.
Add the missing displacement-store arm, predicate-mirroring cstage's
via_ptr global scalar arm exactly: plain assignment only, scalar field
only; non-scalar field types stay on the generic path (their global-ptr
deref is a separate deferred item). glob_ptr_field_test.ww gains an
off-8 row as the regression pin -- offset-0 cannot catch it because
ADDQ $0 is suppressed.
Surfaced by the fold-2 Fam-5 migration.
Continue fold-2: migrate the struct-by-value / sret round-trip family from
bespoke build+run C twins to test/lang @test, retiring each twin in the same
commit. Runtime coverage MOVES from $(TESTS) to test-lang (T1 runs+asserts via
`ww test`) + test-lang-byteid (T2 keeps cs==ww .s byte-id); the $(TESTS)
headline drops 6. Every assert is a primitive int/u8/bool comparison (no
fmt/strconv in the assert path); each returned struct/tuple FIELD is asserted
individually so a dropped/mis-offset/over-wide word FAILS. 46 @test cases, a
strict superset of the 46 C rows (799=4, 925=10, 930=6, 949odd=7, 949chained=9,
949aggret=10).
799_tuple_sret_receive_run.c -> tuple_sret_receive_test.ww (#10 Fold B: over-4-GP tuple `([]u8,[]u8)` sret RECEIVE — destructure/single-var/return-forward/reassign; len() only on destructured bindings, never len(t.N))
925_sret_struct_return_run.c -> sret_struct_return_test.ww (#23: >24B sret round-trip; 32B/40B/nested/slice-payload, reassign-receive, struct16-by-value-arg #11 collision, N_IDENT return rhs, forward #9 simple/multi-arg/slice)
930_sret_narrow_field_run.c -> sret_narrow_field_test.ww (#33: sret narrow trailing-field copy — bool/u8/i16/i32 + mixed bool+i32+i64 after the 24B slice)
949_oddstruct_byval_ret_run.c -> oddstruct_byval_ret_test.ww (#107: by-value return of odd sub-8 size {3,5,6,7} single-eightbyte + 8/12/24 boundaries)
949_chained_dot_struct_copy_run.c -> chained_dot_struct_copy_test.ww (#107 sibling: chained-DOT `t.m.l = s` natural-size tail copy {0..7} + via-CX global dest; neighbour z is the oracle)
949_aggret_source_run.c -> aggret_source_test.ww (#272: aggregate return from every addressable source — arrlit/N_DOT/N_INDEX/deref/ident + >24B sret arm + global-receive caller-half)
This family is sret / struct-by-value-return (the #107/#38/#271/#272 ABI area):
all 6 new files are byte-id cs==ww (no fold-5 divergence surfaced). Bump
LANGBYTEID_EXPECTED_MIN 43->49 to ratchet the new corpus floor.
Continue fold-2: migrate the pointer-deref / narrow-load / pointer-array-stride
family from bespoke build+run C twins to test/lang @test, retiring each twin in
the same commit. Runtime coverage MOVES from $(TESTS) to test-lang (T1 runs +
asserts via `ww test`) + test-lang-byteid (T2 keeps cs==ww .s byte-id); the
$(TESTS) headline drops 5. Every assert is a primitive int/bool comparison with
a width-preserving (`==`) sink so a stale high half or wrong stride FAILS; the
narrow-signed rows route through an `: i32` cast against a 64B-widened literal
to force sign-extension onto the load. Each .c row maps to one inline @test fn
(50 cases total, strict superset of the C rows):
947_deref_narrow_run.c -> deref_narrow_test.ww (#116, 10 rows: *p reads pointee width not 8B MOVQ; i32/u32/u8/i8/i16/u16 + !i32-alias + enum-i8 + bool/i64 controls)
949_ptrarr_index_run.c -> ptrarr_index_test.ww (#61, 23 rows: p[i]/(&p[i])/(*p)[i] stride by size(T); {1,2,4,8}B, const+var idx, param/local/cast bases, neighbor guards, nested *[2][3], *[3]str header, siphash round())
949_dotbase_arr_run.c -> dotbase_arr_test.ww (#135, 3 rows: (*struct).arrayfield[i] read/write/compound addresses the field)
944_def_amp_idx_run.c -> def_amp_idx_test.ww (#94, 6 rows: &D[i] over a def-array; +plain/read/2D controls)
944_alias_amp_idx_run.c -> alias_amp_idx_test.ww (#5, 8 rows: &a[i] over an alias-typed base classifies off the chased type; local/global, narrow, fwd-ref, plain+str controls)
The two sibling .c (949_dotbase_addr_slice_run, 944_alias_def_addr_run) stay in
$(TESTS): the first is run-only byteid=0 (#254 cs!=ww rows), the second carries
a LOUD rule-7 reject row — both routed to fold-3 (task #7). Bump
LANGBYTEID_EXPECTED_MIN 38->43.
Continue fold-2: migrate the global-value read/store/addr-of family (non-tagged)
from bespoke build+run C twins to test/lang @test, retiring each twin in the
same commit. Runtime coverage MOVES from $(TESTS) to test-lang (T1 runs+asserts
via `ww test`) + test-lang-byteid (T2 keeps cs==ww .s byte-id); the $(TESTS)
headline drops 3 (421->418). All asserts are primitive int comparisons; fresh
globals are zeroed, so a store that misses the symbol reads back 0 and FAILS.
989_globptrfield_run.c -> glob_ptr_field_test.ww (scalar field store through a module-global *struct pointer loads the pointer from gp(SB), not saved BP; runtime gp=&backing form, static-init is #48-blocked)
989_globstructret_run.c -> glob_struct_ret_test.ww (returning a module-global struct ident by value copies g's bytes; rsz 16 + rsz 24, each field asserted)
989_dotbasehijack_run.c -> dotbase_hijack_test.ww (indexing an [N]T field of a module-global struct addresses the struct field, not an unrelated global sharing the field name; +typed-inner control +colliding-global-intact pin)
glob_ptr_field keeps only the .c's offset-0 row: an added non-zero-offset row
(gp.g=9) surfaced a cs!=ww divergence (cstage folds the offset into the store
displacement `MOVQ AX,8(BX)`; wwstage emits `ADDQ $8,BX; MOVQ AX,(BX)`) — a
latent global-*struct-pointer field-store divergence beyond the .c's coverage,
reported for the backlog, not carried here. Bump LANGBYTEID_EXPECTED_MIN 35->38.
Continue fold-2: migrate the #73 whole-struct field-copy ragged-tail test from a
bespoke build+run C twin to test/lang @test, retiring the twin in the same
commit. Runtime coverage MOVES from $(TESTS) to test-lang (T1 runs+asserts via
`ww test`) + test-lang-byteid (T2 keeps cs==ww .s byte-id); coverage is
preserved, the $(TESTS) headline drops 1 (422->421). All four rows are pure
value-rows (no reject rows): each poisons `mark` at the inner struct's natural
offset, copies, then asserts every field back with primitive int comparisons, so
an 8-byte MOVQ that over-writes the ragged-tail successor FAILS.
989_structcopytail_run.c -> structcopytail_test.ww (tail2/tail6/tail7 ragged-tail copy preserves mark; ctl8 8-aligned control)
This is the only Family-3 file with no build-must-fail rows; the three
compound-OP= twins (948_idx_compound, 949_dotfield_compound, 949_idxfield_compound)
carry loud //ww:error reject rows a runtime @test cannot replicate and are
DEFERRED to a fold-3 value-split + reject-carrier pass (they stay fully in
$(TESTS), no coverage lost).
Bump LANGBYTEID_EXPECTED_MIN 34->35 to ratchet the new corpus floor.
Continue fold-2: migrate drew's Family 2 (def-dimensioned array len/cap/slice
resolution) from bespoke build+run C twins to test/lang @test, retiring each
twin in the same commit. Runtime coverage MOVES from $(TESTS) to test-lang (T1
runs+asserts via `ww test`) + test-lang-byteid (T2 keeps cs==ww .s byte-id);
coverage is preserved, the $(TESTS) headline drops 3 (425->422). All asserts are
primitive int comparisons (no fmt/strconv in the assert path); the defcap rows
poison cap != len and assert both words so a dropped cap word FAILS.
989_defdim_field_run.c -> defdim_field_test.ww (`.len` field-read on a def-dim [MAX]T resolves from the type table: local/let-global/def cgdot arms + sum)
989_defdim_slice_run.c -> defdim_slice_test.ww (slicing a def-dim [MAX]T resolves len AND cap from the type table: default-hi + cgbasecap, local+global)
989_defdim_argslice_run.c -> defdim_argslice_test.ww (def-dim slice passed as a call arg resolves default-hi len in the N_SLICE arg-push arms; litctrl pins the N_INTLIT path)
Bump LANGBYTEID_EXPECTED_MIN 31->34 to ratchet the new corpus floor.
Continue fold-2 (after 374e97b): migrate the remaining Family-1 str/slice
global + literal + index + call-arg group from bespoke build+run C twins to
test/lang @test, retiring each twin in the same commit. Runtime coverage MOVES
from $(TESTS) to test-lang (T1 runs+asserts via `ww test`) + test-lang-byteid
(T2 keeps cs==ww .s byte-id); coverage is preserved, the $(TESTS) headline
drops 9 (434->425). All asserts are primitive int/u8/bool comparisons (no
fmt/strconv in the assert path); the slice-store/index rows reset the global
each fn and sum ADJACENT elements so a dropped/mis-strided/over-wide word FAILS.
989_globslicefield_run.c -> glob_slice_field_test.ww (slice field of a global struct: g.f=<slice> stores full 24B header; len/cap/non-zero-offset + str/scalar controls)
989_globstrslice_run.c -> glob_str_slice_arg_test.ww (global str sliced with default hi passed as call arg loads its len word; explicit-hi control)
989_trystr_run.c -> try_str_unwrap_test.ww (`!` unwrap of str-success tagged union shuffles the str header for ident-source/error-first/success-first)
797_len_strglobal_run.c -> len_str_global_test.ww (len(str-global) loads .len via name(SB); local-str control)
801_litstr_pseudo_run.c -> lit_str_pseudo_test.ww (string-literal .len/.ptr pseudo-field; empty/multibyte + arg-passthrough)
803_globalidx_run.c -> global_index_test.ww (global str/slice index read/addr-of/store/compound, esz 1/4; local regression pins)
903_tuple_elem_slice_len.c -> tuple_elem_slice_len_test.ww (len(t.N) of a slice/str tuple element loads .len at +8; 2/3-slice, str-slice both orders)
927_composite_call_arg_run.c-> composite_call_arg_test.ww (slice-returning CALL passed inline as a composite arg; canonical/letslice/two-call/middle/nested/scalar/tagged)
952_slicecopy_assign_run.c -> slice_copy_assign_test.ww (bulk slice-copy-assign `arr[lo:hi]=bs`, esz 1/4, field/via-ptr/local bases; reslice-read companion)
rd_reslice asserts the TRUE value 360 (the .c twin's want=104 was 360 & 0xFF,
an exit-code truncation). 723_composite_call_arg.c's comment repointed to the
new test/lang location. 802_lenidx_run.c is DEFERRED (it carries //ww:error
reject rows — needs a value-rows-only split + a slim reject carrier, a fold-3
pass). Bump LANGBYTEID_EXPECTED_MIN 22->31 to ratchet the new corpus floor.
Migrate the slice/str-header core of drew's Family 1 from bespoke
build+run C twins to test/lang @test, retiring each now-redundant twin in
the same commit. Runtime coverage MOVES from $(TESTS) to test-lang (T1
runs+asserts via `ww test`) + test-lang-byteid (T2 keeps cs==ww .s
byte-id); coverage is preserved, the $(TESTS) headline drops 6 (440->434).
All asserts are primitive int/u8/bool comparisons (no fmt/strconv in the
assert path); the slice-store families poison the slot (cap!=len) and read
it back so a dropped data word FAILS.
928_str_abi_run.c -> str_abi_test.ww (str 24B ABI: .len/.cap across literal/arg/return/field/tuple/deref/index/tagged)
941_slice_store_cap_run.c -> slice_store_cap_test.ww (slice value store through indexed/field/chained lhs writes full 24B header; cap==8)
942_subslice_cap_run.c -> subslice_cap_test.ww (sub-slice cap = base_cap-lo; array/slice/str/append-no-realloc/hi-default)
943_subslice_ptresz_run.c -> subslice_ptresz_test.ww (sub-slice ptr advances lo*esz bytes; esz 2/4/8, let + call-arg)
944_deref_slice_store_run.c-> deref_slice_store_test.ww(*p=sliceval whole-deref store writes 24B header; cap==8)
949_f6_header_run.c -> f6_header_test.ww (str/slice header partial load/store; .cap/.len after clobber)
Bump LANGBYTEID_EXPECTED_MIN 16->22 to ratchet the new corpus floor.
Fan out the str-cap read (933-936) and store (937-939) families into in-language @test files, following the 932 str_elem_cap template. Additive: the *_run.c stay in the C corpus (they are the only wwstage-runtime net for these cs==ww byte-id-blind shapes); de-dup deferred to fold 6.
Per-shape @test fns, not a data table: each fn varies the codegen shape (base reg / chain depth / tuple return-ABI / store position), so the row-array idiom (blocked by #111) would lose coverage. Read family keeps the spoil()/register-clobber + junk==44 discrimination where the .c has it; store family pre-poisons the slot via a path distinct from the store under test. Asserts are primitives only.
Two more value-observable families ported additively (the .c sources
keep running in $(TESTS); de-dup deferred to fold 6).
932_str_elem_cap -> str_elem_cap_test: a str-element N_INDEX value
read must load the full 24B {ptr,len,cap}, not {ptr,len} (F2); each
shape poisons cap != len so a 2-word read fails the .cap assert.
Template for the 933-939 cap family.
957_size_type -> size_type_test: `size` binds in type position and
coexists with the size(T) operator (#85); a green row is the bind
proof.
Three more value-observable behavior families ported from the C corpus
to in-language @test, routed by the ratified observability rule (value
-> test/lang @test; process-outcome stays in runww). Additive: the .c
sources keep running in $(TESTS), so no byte-id coverage is removed --
de-dup is deferred to fold 6 (task #12).
906_callret_unsigned_arith -> callret_unsigned_test: call-result
unsigned opcode select keyed by callee return type (#168, the N_CALL
twin of gunsigned's module-global #134); operands flow through real
calls so the return-type arm is exercised, not N_IDENT.
912_sar_shr -> sar_shr_test: signed >> / >>= must emit SAR not SHR
(#136); asserts the i64/i32 value directly, dropping the C 8-bit
exit-code encoding.
793_widen_pad_zero -> widen_pad_test: widening a narrow value into a
wider tagged slot zeroes the high pad words (#227).
Batch 2 of the test-arch reframe. These three are codegen-SHAPE tests,
not data-row tests: each subject is a distinct node shape (N_IDENT
module-global read; chained N_INDEX m[i][k]; index-base node-kind). A
[N]struct row-table would interpose its own N_INDEX/N_DOT lowering and
mask the shape under test, so each uses per-shape @test fns with direct
asserts (rob-ratified rule: table where the row is data, per-fn where
the row is a codegen shape). Lossless from the matching
989_{gunsigned,chainidx,idxarg}_run.c; additive (C kept); both stages green.
Proof-of-path for the test-arch reframe: pure-behavior rows become
in-language @test row-tables run by `ww test`, reserving C drivers for
the two irreducible jobs (byte-id + bootstrap). 110_uniesc's 6 value
rows become [N]struct row-tables in a new test/lang/ tree; the trailing
.wwi round-trip stays in C as external-observer residue. Additive — the
C rows are kept; de-dup and make-rewiring land at fold 6 (task #12).
Green on both stages.