Commit Graph

80 Commits

Author SHA1 Message Date
cf69cf2f06 w6c: key tagged natural-push call args on type equality, not slot size
pushargsrev's five aistagged gates (N_IDENT #55, N_CALL #21, N_INDEX
#12, N_DOT #22a, deref #35) treated a tagged arg as already-tagged
when its SLOT SIZE matched the param's. A same-slot subset union
((bool|void) into (i64|bool|void), both 16B) then natural-pushed the
narrower box's words carrying SOURCE tags — no re-layout, no
cg_widen_tag_remap twin — so the callee matched the wrong arm
(silent: probes exited 10/90 where cstage exits 30/27). cstage keys
widen detection on type equality (cgen.c:10000 same = (pu == au) ||
type_eq) and routes every non-same tagged source through the zeroed
scratch + tag remap; the slot-DIFFER wwstage path already mirrored
that byte-identically, so the fix computes cstage's same check once
(wsame) and replaces each slot-size test with it. This also erases
the last known cs!=ww shape divergence (the 16B-local staging vs
direct-push frame delta on prefix subsets).

8 subsetwiden_* fixtures own the class: call-result/ident/str-payload
/mid-arg remap (the wrong-arm shapes), prefix (the shape-divergence
repro), and bigslot/return-pos/struct-24-to-32 sibling guards. Corpus
pin 1740/343/22/209/1166/3480.
2026-08-08 19:01:05 +09:00
184afa3243 syntax+w6c: multi-type match patterns in wwstage (align up to cstage)
`case T1 | T2 =>` (binding-less; cstage parse.c:635-650 chains extra
types through cs->list, bindings stay single-type by design). Three
wwstage layers, no AST change (node.list already exists):
- parser (expr.ww): pipe loop after the non-let arm's first
  parsetype;
- checker (check.ww): resolvewalk N_MCASE now walks n.list so each
  alt gets its type_ stamp (exhaustiveness/casecovers were already
  alt-aware, built ahead of the parser);
- cgen (cgenexpr.ww): the single-pattern want-computation moves
  VERBATIM into matcharmwant; a cs.list arm emits CMPQ/JE per alt
  funneling into one match_body label, mirroring cgen.c:11118-11141
  incl. label mint order and the tag<0 clamp. Nullable arms keep
  ignoring alts (both stages).

Graduates the held e2e row (pin 1732/1158/3464); byte-identical on
the repro and the single-pattern control.
2026-08-08 17:15:41 +09:00
bbdd8bede6 syntax: accept the empty for () condition (align up to cstage)
cstage parse.c:1027 accepts `for ()` — N_FOR with a nil cond, no
for-else arm, no test emitted (an unconditional loop; a deliberate
Go-flavored ww divergence from Hare, original to the toolchain
import). The wwstage parser had no RPAREN early-out and rejected it.
Selfhost checker and cgen already nil-guard n.cond, so the parser arm
is the whole gap. Graduates the held e2e row (pin 1731/1157/3462).

Adjacent asymmetries left filed, not fixed here (rule 11): the brace
form `for { }`, the empty middle clause of the 3-clause form, and
the REVERSE case (wwstage accepts 2-clause `for (cond; post)` which
cstage rejects).
2026-08-08 17:15:20 +09:00
d78137a051 w6c: fold dot chains through inferred-alloc struct pointers (#24 residue)
wwstage dotchainresolve gated its *T roots on an N_TPTR-over-N_TNAME
tnode; an inferred `let p = alloc(S{...})!` rides the checker-
SYNTHESIZED N_TPTR whose pointee is the struct BODY node (the #24
TNAME-normalize covers only the direct struct-lit binding), so the
root gate failed and chained p.field.pseudo reads fell to the
unfused deref arms — shape-only divergence vs cstage's type-keyed
fold (both stages runtime-correct; the historical field(SB) leak the
pin described was already fixed). Widen the local and global root
gates to accept the N_TSTRUCT pointee; correctness stays enforced by
the stamped-tinfo peel below (TY_PTR -> pointee TY_STRUCT).

Graduates alias_g73_heapfill out of DATABYTEID_DIVERGED (2 pins
remain, both the tagged-spill family); adds compile fixtures pinning
the newly-converged siblings (nested read+store, slice .cap,
inferred-global root; pin 1730/22/3460) and a runtime lang row that
mutates then re-points the heap base — a read bypassing the pointer
returns the wrong len.
2026-08-08 17:14:59 +09:00
dc33af6217 w6c: remap legacy-union error tags in wwstage ? propagation
The #173 remap loop in cgtryprop gated on the tparam's explicit `!`
flag only; a LEGACY union (no marks anywhere) classifies error
variants POSITIONALLY (index 0 is success, cstage cg_variant_is_error
cgen.c:905) — so wwstage emitted no remap and propagated the callee's
raw tag into a differently-ordered caller union, a silent wrong arm
(cstage exit 7, wwstage 9 on the banked r700 row). Add the
variantiserror twin and gate the loop on it; refresh the drifted
cstage cite.

Graduates the held e2e row into the corpus (pin 1727/1156/3454) and
adds legacy-mode rows to test/lang/tryprop_tag_remap_test.ww:
reversed order both directions, slot-0 remap, str payload integrity,
a two-hop chain whose pre-fix runtime pass was double-miss tag
cancellation (the lang byteid leg pins the emitted remap blocks),
and a same-order zero-emission control.
2026-08-08 17:11:50 +09:00
934f19f884 test: own the 926/929/671 run legs as corpus fixtures
The three carriers retired this session moved their compile/byte-id/
asm-marker legs to test/asm/tagsret_test.ww, test/asm/memarg_test.ww,
and test/misc/packedwwi_test.ww, and audit-assigned their run-exit
legs to corpus fixtures that were never created. Create them, sources
byte-faithful to the carriers' row tables; every fixture also rides
the corpus byte-id sweep, which stayed green (no new DATABYTEID pin).

926_tagged_sret_run.c run rows (all exit 0):
  wide_lit_roundtrip          -> r926_sret_wide_lit_roundtrip
  wide_local_roundtrip        -> r926_sret_wide_local_roundtrip
  wide_assign_receive         -> r926_sret_wide_assign_receive
  wide_match_scrutinee        -> r926_sret_wide_match_scrutinee
  wide_forward_exact          -> r926_sret_wide_forward_exact
  wide_str_error_variant      -> r926_sret_wide_str_error_variant
  wide_multicall              -> r926_sret_wide_multicall
  wide_bare_return_void       -> r926_sret_wide_bare_return_void
  wide_s4_repro               -> r926_sret_wide_s4_repro
  errno_shaped_tail_read      -> r926_sret_errno_shaped_tail_read
  boundary_s3_register        -> r926_sret_boundary_s3_register
  boundary_str_nomem_register -> r926_sret_boundary_str_nomem_register
  wide_discard_stmt           -> r926_sret_wide_discard_stmt
(the seven buildfail rows stay with r926_sret_reject_*.)

929_tagged_memarg_run.c run rows (exit 0 unless noted):
  mem56_ident_allvariants  -> r929_memarg_mem56_ident_allvariants
  mem64_ident_lateword     -> r929_memarg_mem64_ident_lateword
  mem56_widen_concrete     -> r929_memarg_mem56_widen_concrete
  mem56_subset_remap       -> r929_memarg_mem56_subset_remap
  mem56_srcshapes          -> r929_memarg_mem56_srcshapes
  mem56_mixed_orders       -> r929_memarg_mem56_mixed_orders
  mem56_call_in_loop       -> r929_memarg_mem56_call_in_loop
  mem56_twomem             -> r929_memarg_mem56_twomem
  mem56_inst_shape         -> r929_memarg_mem56_inst_shape
  mem56_elem_source        -> r929_memarg_mem56_elem_source
  mem56_elem_deref_spine   -> r929_memarg_mem56_elem_deref_spine
  mem56_elem_nested_spine  -> r929_memarg_mem56_elem_nested_spine
  mem56_elem_mixed_regs    -> r929_memarg_mem56_elem_mixed_regs
  mem56_elem_callidx_twomem -> r929_memarg_mem56_elem_callidx_twomem
  mem56_elem_global_slice  -> r929_memarg_mem56_elem_global_slice
  boundary48_register      -> r929_memarg_boundary48_register
  global_uninit_memarg_src -> r929_memarg_global_uninit_memarg_src (91)
  memarg_idcast_peeled     -> r929_memarg_idcast_peeled
(the four buildfail rows stay with r929_memarg_reject_*.)

671_struct_packed.c value rows (exit = layout value):
  unpacked_size    -> r671_unpacked_size (16)
  packed_size      -> r671_packed_size (9)
  packed_off_b     -> r671_packed_off_b (1)
  unpacked_off_b   -> r671_unpacked_off_b (8)
  packed_align     -> r671_packed_align (8)
  packed5_size     -> r671_packed5_size (5)
  packed5_off_b    -> r671_packed5_off_b (1)
  p3_packed_size   -> r671_p3_packed_size (4)
  p3_packed_off_c  -> r671_p3_packed_off_c (3)
  p3_unpacked_size -> r671_p3_unpacked_size (6)
  field_roundtrip  -> r671_field_roundtrip (6)
  arr_stride_size  -> r671_arr_stride_size (15)
  arr_stride_run   -> r671_arr_stride_run (33)
  byval_arg        -> r671_byval_arg (107)
  frame_offset     -> r671_frame_offset (20)
Owner-cited, no fixture: wwi_roundtrip needs a real multi-file -I
tree (a single-file two-package form emits no .wwi), owned by
test/misc/packedwwi_test.ww wwiroundtrip (exit 91 on both drivers);
identity_reject is stage-asymmetric, owned there as identityreject.

Pins: corpuscount 1680 -> 1726, runexitcount 1109 -> 1155,
nativecount 3360 -> 3452, corpushash refreshed.
2026-08-08 15:14:25 +09:00
90b1005478 test: point attest_userrun comment at the ported collide owner 2026-08-08 14:34:59 +09:00
89a4b82ba3 test: port 944_alias_cgen_b5_run to a compile fixture
944_alias_cgen_b5_run.c -> alias_g73_heapfill (//ww:compile), joining
its migrated alias_* siblings. The carrier's stated fixture blocker
('//ww:compile links') is stale: the COMPILE cell runs the bare
frontend, no link. The live #24 field(SB) deref-read leak enters
DATABYTEID_DIVERGED, upgrading the carrier's silent byte-id waiver
with a graduation tripwire.
2026-08-08 14:33:23 +09:00
a4839d0176 test: port 915/949 module-global access carriers to corpus fixtures
915_arr_module_index_run.c -> r915_arr_mod_idx0 (run), r915_arr_mod_
  idx2 (run-exit 1), r915_arr_mod_idx4 (run-exit 6); the single-file
  rows were already owned by r915_arr_local_* + test-data-byteid.
949_xmod_fnptr_const_run.c -> r949_xmod_fnptr_const_table (run),
  r949_xmod_fnptr_wrong_sig, r949_xmod_fnptr_nonfn_dotted (error
  'not assignable' both stages).

The 915 module rows upgrade from 994-delegated to per-fixture cs==ww
byte identity; 949's 2-file -I tree was not load-bearing — inline
packages exercise the same N_DOT &mod.fn mangle (mafn(leaf, module)).
2026-08-08 14:33:23 +09:00
5a8b662d6e test: port 839_xmod_nominal_typeeqast to corpus fixtures
839_xmod_nominal_typeeqast.c -> r839_xmod_nominal_match (run-exit 42),
  r839_xmod_a6_concrete (run), r839_xmod_incompat_concrete (error
  'not assignable'), r839_xmod_distinct_alias (error 'ambiguous
  without nominal layout').

r839_xmod_nominal_match enters DATABYTEID_DIVERGED: the carrier waived
byte-id for the pre-existing cross-module tagged-return spill
divergence; the DIVERGED row makes it loud-on-fix instead. The
typeeqast-layer discriminator itself stays with the lib byteid gate
(test/byteid/libbyteid_test.ww), as the carrier documented.
2026-08-08 14:33:23 +09:00
2680fba303 test: port 787/929 cross-module match carriers to corpus fixtures
787_xmod_variant_match.c -> r787_xmod_variant_no_default (run-exit 21),
  r787_xmod_variant_bind_default (42), r787_xmod_variant_shape_a (12)
  (pkg/errs module dirs), r787_xmod_variant_foreign_qualifier (staged
  error: w6c 'is not a variant of', w6c_ww rejects via the #95
  ambiguity guard — the carrier asserted reject polarity only)
929_match_4arm_cross_module_run.c -> r929_match_{4arm_shadowed_
  canonical,3arm_shadowed,5arm_shadowed,6arm_shadowed,4arm_mixed_kinds,
  4arm_shadowed_reverse} (run; single-file a+b packages, dropping the
  carrier's accidental doubled 'package b;' line)

929's byte-id upgrades from 994-delegated to per-fixture via
test-data-byteid; the asm-level distinct-CMPQ sentinel remains 728's
separate concern. All rows probed green on both stages.
2026-08-08 14:33:23 +09:00
c78bf6ebca test: port the xmod same-leaf collision carriers to corpus fixtures
752_modparam_callee.c -> r752_modparam_xmod_variadic_vs_scalar,
  r752_modparam_variadic_no_collision (run; alpha/beta module dirs)
784_xmod_alias_struct_collide_run.c -> r784_xmod_alias_struct_collide
  (run-exit 42), r784_xmod_alias_struct_symmetric (21),
  r784_xmod_alias_control (30) (sa/sb module dirs)
793_xmod_struct_argpush_collide_run.c -> r793_xmod_argpush_
  {recvpush_t16 107, fieldread_t16 107, combined_t16 114,
  recvpush_t12 6} (m1/m2 module dirs)
797_xmod_struct_field_layout_collide_run.c -> r797_xmod_layout_
  {ptrread_t16 107, letcopy_t16 107, addrptr_pq 10, addrval_pq 10,
  ptrwrite_t16 109, valwrite_t16 109} (m1/m2) + r797_nestfill_box (66)

r770-style sibling module dirs express the trees; the fixture corpus
runs both drivers natively (784's wwstage-build-only leg upgrades to
build+run) and test-data-byteid owns each carrier's aggregated per-
package .s compare (letcopy_t16's real discriminator). All 16 probed
green on both stages including per-package byte identity.
2026-08-08 14:33:23 +09:00
be852d51d9 test: port 732_def_const_fold residue to corpus fixtures
732_def_const_fold.c -> r732_def_xmod_fold (run-exit 42; stronger than
  the carrier's DATA-row grep — links AND reads the folded cross-module
  value), r732_def_xmod_cycle (error 'def value: reference chain too
  deep', upgrading the needle-less exit!=0 check).

The five same-module exec rows and both same-module fail-loud rows were
already owned by the r732_def_* fixtures; their cs-vs-ww byte identity
rides test-data-byteid, which now also covers the xmod fold row. The
timeout-124 hang distinction devolves to the wwfixture harness timeout.
2026-08-08 14:33:23 +09:00
f45360bbec test: port 696/697/699 resolver carriers to corpus fixtures
696_modtype_leaf_collision.c -> r696_modtype_leaf_pos (run-exit 26),
  r696_modtype_leaf_neg (staged error; w6c 'no field', w6c_ww
  asserttyped: dot — the old no-wwstage-checkfile comment is stale)
697_samemod_prefer.c -> r697_samemod_prefer (compile)
699_use_promote_alias.c -> r699_usepromote_{type,fn,def,var}
  (run-exit 42 each)

Single-file multi-package form folds the modcollision/samemodprefer/
usepromote data trees into the fixtures; the trees retire with their
carriers. The dir-tree .wwi transport of colliding type decls is owned
by test/byteid/wwi_test.ww. Coverage upgrades: 697 and the 699 rows
gain the wwstage leg the carriers gated or skipped, and all positive
rows enter the blanket test-data-byteid net.
2026-08-08 14:33:23 +09:00
50d70e74e4 w6c: append() accepts struct CALL rvalue elements (#34)
Evaluate the call pre-grow into a per-site scratch, receiving by the
N_LET matrix (sret / float / odd-tail / GP), then grow, slot, sized
ladder. Both stages, byte-identical. The two reject pins graduate to
16B GP accept rows; three new fixtures cover 24B GP, 3B/4B tails,
16B float, and 40B sret.
2026-08-08 14:10:55 +09:00
956246b3f0 test: migrate 700_e2e rows to corpus fixtures; retire the carrier
136 rows migrated, 0 already owned, 3 wwstage-failing rows held out.
2026-08-08 14:10:46 +09:00
74c9243ca3 test: retire 911_attest_record; record-continue owned by test/package 2026-08-08 13:47:16 +09:00
e1141330f2 test: retire 13 byteid carriers into the corpus blanket
Ten of the thirteen were already fully twinned — their runtime rows
migrated in earlier waves (r56_def_*, r79_*, r78_*, r926_*,
r989_m1union_*) and the carriers survived only for a cstage-vs-wwstage
byte-compare the blanket test-data-byteid comparator owns once the
source is a corpus fixture. The other three needed their remaining
inline sources added first: r756_* (4, alias-chain unwrap),
r844_bid_src (size(u64) untyped-int compare), r989_structframe_* (3,
struct-local frame layout). Every new fixture was verified both-stage:
compile exit 0 twice, .s byte-identical, runtime exit as pinned.

The five ww_ww-driver-leg carriers among them (785/786/789/790/
m1union) follow the 815/940/951 precedent: content identity owned by
their corpus twins, driver-leg identity owned by 989_lib_byteid's
sweep. Byte/artifact partition 24 -> 11; carriers 147 -> 134; corpus
pin 1495/2990; the stale 915 sweep figure in the docs corrected to
the true 1,157 non-error count.
2026-08-08 02:57:55 +09:00
67f39256f6 cgen: resolve chained-DOT fn-ptr field callees
fnptrcalleetfn's N_DOT arm required an N_IDENT base, so a.b.cb(...)
fell to the name registry with an empty module hint and emitted
CALL cb(SB) (undefined symbol; cstage calls the stamped ptr indirect).
dotbasestructinfo resolves the base chain through the struct registry
— each link a struct- or *struct-typed field — and the single-dot
path routes through the same resolver unchanged. Closes the last open
shape of the #59.8 name-keyed callee family. Fixture fnptrfield_chain;
corpus pin 1487/2974.
2026-08-08 02:48:05 +09:00
430c7e0546 check: compare variadic fn params at the declared element type
installparams normalizes a decl's `T...` param lhs to []T in place
(cstage instead sets the resolved tp->type and never mutates AST), so
typeeqast compared a decl-synthesized fn type's []i64 against a fn
TYPE expr's surface i64 and confidently rejected
`let f: fn(args: i64...) void = sum;` — cstage accepts and runs. The
normalization wrapper now carries an op marker and typeeqast peels
exactly it, so both sides compare at the declared element type without
admitting genuinely different element depths. Fixture fnptrlet_variadic;
corpus pin 1486/2972.
2026-08-08 02:47:08 +09:00
5abb1e6069 wwstage: port struct embedding; graduate #59.13
The last frontend-gap pin: wwstage had no Hare struct embedding
(struct { hash.hash, ... }), rejecting lib/crypto/sha256 at parse.

- parse.ww: the three member forms (named / anonymous struct / bare
  dotted-ident embed), consume-then-branch since this parser has no
  peek; embeds carry f.str == "" and the type in f.lhs.
- check.ww N_TSTRUCT flatten: promote the inner struct's flattened
  fields at base+src.offset (check.c:961-990); the embed is one
  nested-struct unit in the slot ladder; the resolved inner AST is
  planted on the TFIELD rhs for cgen.
- check.ww walkers: astoffset / exprtype N_DOT / #251 struct-lit
  field lookups descend embeds through shared helpers; the collision
  and non-struct-embed rejects live in validatestructfields (the
  once-per-decl diagnostic site).
- cgenutil.ww registerstruct: regfieldrun walks the AST against the
  flattened tfield cursor, descending embeds via the planted inner
  AST so promoted fieldinfo entries keep the inner field's own name
  and type node.
- wwi printers unchanged (both stages already emit nameless fields).

sha256_test compiles byte-identically end to end and its 6 tests
pass; 989_lib_byteid is now 44 id / 0 divergent / 0 wwreject.
Fixtures r5913_* (promoted rw, offset shift, anonymous embed,
two-level embed + promoted fn-ptr callee, three rejects); corpus pin
1485/2970.
2026-08-08 02:37:50 +09:00
6553d60e91 cgen: peel *fn field types for local struct-field callees
fnptrcalleetfn's N_DOT arm accepted only a bare N_TFN field tnode, so
a call through a field declared `cb: *fn(...)` missed, fell through to
the name registry, and emitted CALL cb(SB) — an undefined symbol
(loud at link; cstage calls the stamped ptr-to-fn indirectly). Same
TPTR peel the N_IDENT arm already had. Fixture fnptrfield_call covers
the by-value and via-pointer shapes; corpus pin 1478/2956.
2026-08-08 02:28:33 +09:00
f6628b985e test: migrate 24 residual carriers into the fixture corpus
Audit-driven migrate-and-retire wave (one read-only auditor per
carrier batch, verdicts row-checked through the full corpus gate):
every retired carrier's assertions are now owned by declarative
fixtures or an existing owner. 238 new fixtures land (corpus 1,239 ->
1,477; cells 2,954), covering enum/str/slice/tagged array elements,
inferred-length arrays, global slice/str zero-init and literals,
global array/pointer field reads and stores, struct-literal slice
fields, struct returns, float arithmetic, size/int limits, sort and
log-vstream behavior, place-addressed stores, and append places.
Rejection rows STRENGTHEN the old nonzero-exit checks to required
diagnostic fragments; all byte-id claims fold into the blanket
test-data-byteid sweep (now 1,142 compared, 0 pinned-divergent).
Four carriers were fully redundant with existing fixtures/lang tests
(749, 722, 765, and 771's byteid rows) and retire without new rows.
Native carriers 171 -> 147 (residual 135 -> 111).
2026-08-08 01:29:15 +09:00
3f7452814b cgen: resolve local fn-ptr callees for variadic arg prep
The wwstage variadic call classification was name-keyed: a fn-ptr
FIELD call whose local base shadowed the current module name
(lib/log's log.println(log, args...)) picked the module fn's
signature — nfixed off by one, the fixed arg boxed into the gather,
the spread emitted as zeros — and a no-collision fn-ptr callee missed
the registry entirely, leaking the raw N_SPREAD as a single $0 word
(SIGSEGV / exit 255 in 8 of 11 logtest tests on the wwstage leg).
fnptrcalleetfn resolves a local fn-ptr callee (bare local or struct
field) to its N_TFN once, shared by the CALL-target choice,
callee_variadic_param (with the []T wrap registry params get from
installparams), calleecvariadic, and the widening param lookup, so
target and arg prep can never disagree. Graduates the #59.8 logtest
pin — DATABYTEID_DIVERGED-era M_DIVERGE count is now zero.
2026-08-08 01:07:23 +09:00
c18005d833 check: stamp SK_TYPE value idents with the per-decl NAMED
A TYPE name used as a VALUE (an error-singleton `return too_long;`)
stamped the flattened BODY type, so structurally identical !void
singletons in one union were indistinguishable and flatvariantidxt
loud-rejected the ambiguity — the real cause of the lib/path wwstage
reject (the pinned #120/#29 global-slice-const blame was stale; that
family had drained). The N_IDENT arm resolves through a synthesized
TNAME (the #66 N_STRUCTLIT precedent); the module-qualified N_DOT twin
reads the sym's cached NAMED. Graduates the path M_WWREJECT pin (#142).
2026-08-08 01:00:03 +09:00
125f626697 check: type the N_BIN operand of as in the wwstage
An enum OR-fold under `as` ((m.A|m.B) as u32) was never typed:
scruttype resolves IDENT/DOT only, and the post-order restamp then
re-derived the folded member literals as untyped_int. The unstamped
operand missed cgtypeassert's #27b enum-reinterpret gate and lowered
as a phantom tagged assert — unconditional exit 1 at runtime on the
wwstage leg. checkisas now types an N_BIN lhs and the N_BIN restamp
preserves an existing enum stamp. Graduates the #59.9 stattest pin.
2026-08-08 00:59:02 +09:00
c421c2b20a cgen: key the str/slice arg recognizers off the checker stamp
The wwstage nodeisstr/nodeisslice recognizers were name-keyed for
every non-local shape: an indirect fn-pointer callee ((*f)() — the
errnotest #59.5 divergence, streq receiving a shifted register file)
and module-global let/const idents (path.sepstr — union-widen pushes
zero-filled len/cap) both fell to false while cstage keys off the
checker stamp unconditionally. Both recognizers now fall back to the
stamped n.type_; push and pop sites share them, so the drain stays
balanced by construction. Graduates the #59.5 errnotest pin.
2026-08-08 00:58:04 +09:00
0b23f9fb31 test: fold driver-parity byteid into 989_lib_byteid, retire 815/940/951
Wwstage-driver-leg byte identity gets one owner. The three carriers'
content identity is already blanket-owned by their r815_/r940_/r951_
corpus twins via test-data-byteid; their only non-redundant assertion
was the ww_ww driver leg, which 989_lib_byteid's 43-unit sweep covers
apart from the zero-dep root-only edge — folded in as a new probe row
(44 units). r940 fixture comments re-cited off the retired carrier.
2026-08-08 00:36:11 +09:00
66251cc52b cgen: default the hi bound of a slice/str-field slice, both stages
x.slicefield[:] / x.strfield[2:] emitted MOVQ $0 for the omitted hi
bound on BOTH stages (N_IDENT-gated dispatch; symmetric, so byte
identity never caught it) in all four sites: cgexpr N_SLICE + pushargs
(cstage), cgslice + pushargsrev (wwstage). The new arm re-evaluates
the pure field read for its {ptr,len,cap} header and takes .len,
covering local, viaptr, dot-chain, (*p), arr[i], and global inners.
Call inners still loud-reject upstream. Sibling of the #252/#257
array-field arms.
2026-08-08 00:29:51 +09:00
cc22abfc04 cgen: load the full header for a wwstage global-dot slice field
let x: []T = g.buf on a module-global struct dispatched only str
fields to the 3-word header load; a []T field fell to the scalar
tail (ptr word only), so len/cap read stale registers and the shape
was byteid-divergent against cstage's TY_STR||TY_SLICE arm (#263).
2026-08-08 00:23:55 +09:00
8a9be47f65 test/wcc/data: pin the drained divergences at runtime
The four graduated pin families byte-compared under test-data-byteid
but their original fixtures masked the miscompiles at runtime (lone
frame slot, dead code path, pointer accident). Add the discriminating
programs as run fixtures: adjacent-element clobber for the 2-byte
deref store, second-variant match dispatch for fn-type dedup, payload
integrity across the tagged widen of a padded struct, and base64
clear() actually zeroing its buffers. Corpus pins move to 1,229
fixtures / 137 run / 766 run-exit / 2,458 cells with the new identity
hash; the data-byteid floor moves to 915.
2026-08-07 23:55:01 +09:00
83c8a4f34f test/wcc: retire 990_selfhost; its live assertions move to their owners
Every probe's assertion is owned by a current gate: the compile and
link probes by make all and the bootstrap fixed point; build/run and
cs/ww byte identity by the fixture corpus, test-data-byteid, and
989_lib_byteid; wwstage driver and toolchain parity by 993/995;
checker-diagnostic parity by the corpus' both-stage //ww:error rows.
The wwdump -t/-a dump-parity probes gated the frontend port's
convergence, which the compiler-output identity gates now own end to
end; carrier ran green at retirement.

What was still uniquely alive migrates: smoke.ww becomes corpus
fixture selfhost_smoke (upgraded from a cstage-only build to both
frontends, byte-identical, exit 42 on both toolchains; corpus pins
move to 1,225/763/2,450 with the new identity hash in the same
commit), and sym_link.ww's scope/sym behavior rows become in-language
lib/ww/syntax/symtest.ww under LIBRARY_TESTS. uses.ww (parser-stub-era
-a fixture) and the already-orphaned tagged_ptr_ret.ww/trypromote.ww
retire with the probe corpus. Bootstrap native gates drop to six;
frontend numeric-sync comments now cite the rule-6 mirror instead of
the retired diff probe.
2026-08-07 23:34:44 +09:00
d456263f7e test/wcc/data: grow the declarative compiler corpus to 1,224 fixtures
The r-prefixed waves absorb the runtime, reject, and byte-compare rows
of the migrated native carriers; each fixture is one directory with one
case.ww and a //ww:error, //ww:compile, //ww:run, or //ww:run-exit
directive covering both frontends.
2026-08-07 23:21:04 +09:00
3d79735964 check: reject a deref-less multi-level **fn call, not silently miscompile (#14)
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).
2026-06-29 15:49:01 +09:00
7b9488706b parse: enforce strict-package — reject package-less files (#24a)
Flip the soft-default to a hard "missing package clause" error symmetrically in
both stages (cmd/wcc/parse.c + lib/ww/syntax/parse.ww): the first real decl of a
primary section with empty pathmod/resetmod and no seen clause is now rejected.
Closes the documented soft-default divergence (the 63-wrapper carve-out).

The gate flip can't be split from the migration it breaks, so this is one atomic
commit: ~80 test/wcc wrappers gain `package main;` via a shared wwtestpkg.h
helper, 6 data fixtures plus 17 asm-grep assertions update for the bare->main.<leaf>
root-helper mangle shift, and rt/ declares `package rt;` with @symbol pinning the
bare rt_ensure/rt_malloc linker names.

Root mangling narrows: the executable entry `main` stays bare (existing
carve-out), but root helper symbols become main.X. The #84 cluster is rewritten
to assert main.run distinct from aa.run/test.run; its cgen fix and bare machinery
are retained — still load-bearing for package-less module-reset deps. New
table-driven test 782_strict_package.c (6 rows, both stages).

Retiring //ww:module-reset is deferred to #24b: it is load-bearing (clears the
.wwi pathmod so the body's package clause asserts), not a vestige; fusing its
removal here would be a silent mismatch.

All byte-id gates green; full make test reports "all 335 tests passed".
2026-06-29 03:55:26 +09:00
d0ce55cab8 check: reject an untyped array-literal borrow into a union slice variant (#18) 2026-06-28 11:27:24 +09:00
6b36b050d7 check: reject an array-literal borrow into a union slice success variant (#13)
A stack array literal returned into a tagged-union slice success variant (fn mk() ([]i32|e) = { return [10,20,30]; }) slipped past reject_arrlit_borrow — it bailed when the dst was not TY_SLICE, but a union dst is TY_TAGGED — so cgen built an all-zeros slice header: a silent both-stage miscompile (and the .ptr would dangle anyway, no outliving backing). Extend the reject to chase a TY_TAGGED dst to its slice success variant, then apply the existing reject; this is the #25/#31 treatment seen through the union, and matches Hare (rule-9). Reached by all acceptance sites (return/assign/call-arg) so the class closes by construction. Both stages converge on an identical accept/reject decision (an array literal assignable to a union is assignable to a slice or array variant; neither stage can accept it). Full support — promoting the literal to an outliving backing — is the separate #33 arc. Compile-error fixture + a positive over-reach guard (a real slice into the union still compiles). No asm emitted by a reject, so byte-id is unchanged (no floor ratchet).
2026-06-28 02:44:23 +09:00
b3f4990979 cgen: store the full register into padded scratch for a 3/5/6/7-byte aggregate tail (#10)
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.
2026-06-27 23:44:41 +09:00
a0e330b283 cgen: store all eightbytes when an in-cap aggregate call returns into a field of an indexed element (#11)
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.
2026-06-27 18:49:03 +09:00
74cc35d488 test: migrate Fam4 static-init/DATA-emit value tests to @test (#30)
Continues the test-arch tower past Fam8-13. 11 module-level static-init
/ DATA-emit value drivers move from test/wcc/*_run.c into @test row-
tables under test/lang/; every classification empirically re-probed at
HEAD (refuting two stale worklist tags).

- value rows -> test/lang/*_test.ww (11 files)
- reject rows -> runww //ww:error carriers (3, dual-stage non-vacuous;
  947 const-divzero confirmed a both-stage compile-reject, not run-exit)
- 840_zeroinit, 944_array_zeroinit, 989_arrlit_tail_zero kept as byte-id
  .c pins (zero-over-dirtied-frame / DATAW-length is byte-id-blind to a
  runtime @test; #263), mutation-gated
- repoint two stale comment refs to deleted test names (719,
  989_structlocal_frame)

Migrated static-init @test ride the cs==ww T2 byte-id gate, preserving
DATA-emit byte-id. 2D global-struct array-field read (#137/#150)
confirmed cs==ww + correct at HEAD. Coverage parity verified row-by-row;
two-round reviewed. Floor ratchet follows.
2026-06-25 01:32:32 +09:00
132ea4ee60 test: migrate Fam13 misc checker/coercion value tests to @test (#5-C6)
Final fold-2 chunk. The 12 Fam13 single-file value drivers move from
test/wcc/*_run.c into in-language @test row-tables under test/lang/:

- value rows -> test/lang/*_test.ww (12 files)
- reject rows -> runww //ww:error carriers (13, dual-stage non-vacuous)
- nullable abort rows -> runww //ww:run-exit 1 carriers (3)
- 953_globalslice_arg -> _runonly (cs!=ww checker divergence, #28)
- 788 value_not_type_neg + 953_arrlit_slice reject_assign kept as slim
  rc-only .c pins (divergent-diag dual-reject, mutation-gated); 788 #29

Coverage parity verified row-by-row vs each retired driver; advisor-
ratified carve taxonomy; two-round reviewed. Floor ratchet follows.
2026-06-24 22:59:32 +09:00
246e5bb90e test: migrate Fam9 match/tagged value tests to @test (#5-C5)
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.
2026-06-24 20:42:08 +09:00
60dec4a6bf test: migrate Fam11 float value tests to @test, keep ABI-conformance pins (#5-C4)
fold-2 chunk C4 (drew's Fam8-13 plan): 13 float value-row C drivers re-homed.
11 migrate to test/lang/*_test.ww @test row-tables (exact IEEE-bit asserts);
1 float-overflow reject row -> a runww //ww:error carrier. 956_tuprecv_f64
slims to a w6c_ww asserttyped pin (20 value rows -> @test; the stamp dimension
can't be a value/byte-id @test) -- mutation-proven non-vacuous (break #121
stamp -> RED 6/6 -> restore -> GREEN) + an in-test vacuity self-check.
946_structparam/structret stay whole: their SSE register-class .s-grep (SysV
ABI conformance, #165/#171a) is the genuine defect-guard, not @test-expressible.
Float was the predicted SSE-cursor byte-id hotspot -- zero fresh cs!=ww
surfaced; 951_f64cgen (cstage-only before) byte-ids clean. LANGBYTEID floor
82->93; test count 384->374 (10 deleted drivers; 956 + the 2 946 kept).
2026-06-24 03:25:47 +09:00
3ee1906497 test: migrate Fam10 alias value tests to @test, carve divergences to pins (#5-C3)
fold-2 chunk C3 (drew's Fam8-13 plan): 8 alias value-row C drivers, 205 rows
re-homed with zero loss -- 177 value -> 8 test/lang/alias_*_test.ww @test
row-tables; 16 reject -> runww //ww:error carriers (both stages reject);
3 cs!=ww value rows -> 2 *_runonly_test.ww (T1, byte-id-excluded, #60/#81);
9 irreducible asymmetric rows -> slim C pins, each ticket-cited and
mutation-proven non-vacuous:
  - accept amplen1/2,ampcap2: cs runs / ww rejects 'unsupported address-of
    shape' (#96)
  - cgen_b5 g73_heapfill: cs!=ww .s + ww link-fails on self-contained alloc;
    compile-smoke pin (#24)
  - cgen_b6 fsarg2_bound: both reject, different msgs, each vs the correct
    stage (#271/#165 cs vs #272/#276/#277 ww)
  - emit_b7 slc/slcstr/slctag _2lvl + slc_plain_ctl: slice-literal static-init
    divergence (#120/#29-kin, ken-d2-oracle)
4 fully-migrated drivers deleted, 4 slimmed-in-place to hold only the
irreducible pins. LANGBYTEID floor 74->82 (8 new byte-id @test files); test
count 388->384 (4 deleted; 4 slimmed kept). do-not-auto-batch files not in
Fam10.
2026-06-24 02:45:09 +09:00
07b3c74ab0 test: migrate Fam8 tuple value tests to @test + reject carriers (#5-C2)
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.
2026-06-24 01:44:13 +09:00
094ccc63f0 test: migrate 944_alias_def_addr to @test + runww reject, retire C twin (fold-3)
#88 alias/def/address-of family: 5 value rows -> test/lang/alias_def_addr_test.ww (primitive-only asserts), 1 reject (str-def-non-addressable, shared body 'cannot take address of non-addressable def') -> runww //ww:error dual-stage carrier. byteid floor 53->54.
2026-06-22 17:10:22 +09:00
04f163dde6 test: migrate 802_lenidx to @test + runww rejects, retire C twin (fold-3)
#19 len/index family: value rows -> test/lang/lenidx_test.ww (primitive-only asserts), 3 reject (callres/sliceexpr/strlit) -> runww //ww:error dual-stage carriers. byteid floor 52->53.
2026-06-22 16:53:49 +09:00
057e805cf0 test: migrate dotfield/idxfield compound-assign to @test + runww rejects, retire C twins (fold-3)
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.
2026-06-22 16:34:33 +09:00
af3c49a4c7 test: migrate 948_idx_compound to @test + runww rejects, retire C twin (fold-3)
#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.
2026-06-22 16:02:01 +09:00
d48b53d04d runww: //ww:error arm asserts both stages reject (dual-stage)
A rejected program emits no .s, so the test-lang-byteid (T2) gate cannot
cover wwstage-reject -- yet the retired C twins asserted that BOTH stages
reject with the same diagnostic. runww's //ww:error arm was cstage-only,
so migrating reject rows onto it would silently drop the wwstage-reject
coverage the C twins carried.

Run w6c_ww (wwstage) alongside w6c (cstage) on each //ww:error case and
require both to fail with the shared diagnostic body present. The body is
identical across stages; only cstage's leading prefix differs, so the
substring matches the body alone (no file:line). An ERROR row now reports
PASS dual / FAIL cstage / FAIL wwstage. w6c_ww resolves off the same $BIN
as the C twins -- no new harness threading.

Two pilot reject cases (runww_dup_main_reject, runww_dup_type_reject)
exercise the dual-stage path; the wwstage leg is proven non-vacuous (a
cstage-rejects/wwstage-accepts case reports FAIL wwstage).

Prerequisite for migrating fold-3 reject rows onto runww.
2026-06-22 15:34:36 +09:00