Commit Graph

1088 Commits

Author SHA1 Message Date
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
e57fcf230b make: wire test-run into make test (pre-push reject gate)
test-run drives runww.ww over the test/wcc/data/*/case.ww behavior and
//ww:error cases, but was not a prerequisite of any aggregate, so the
reject surface was ungated. With runww's arm now dual-stage, gate it.

Added to the pre-push `test` target only -- not test-unit, not the
test-commit content-key tier. runww execve's its children with nil envp,
so a WW_PKGCACHE override cannot reach them; they fall back to the default
out/.pkgcache that $(TESTS) and the byte-id gates also use. A normal
prereq would let make -j overlap test-run with those writers, so test-run
carries an order-only prereq (| $(TESTS) test-lang test-lang-byteid) and
is scheduled strictly after every default-cache writer. No cache
isolation is added: it would be inert under nil envp, and the ordering
prevents the race.

True per-run cache isolation stays blocked on an os envp() forwarder.
2026-06-22 15:34:36 +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
214ced303f wwstage: displacement store for global-ptr scalar field, align to cstage
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.
2026-06-22 14:56:11 +09:00
d8e7470555 test: migrate struct/sret round-trip family-7 to test/lang @test, retire C twins (fold-2)
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.
2026-06-22 13:52:41 +09:00
c9c5f6406f test: migrate deref/narrow/stride family-6 to test/lang @test, retire C twins (fold-2)
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.
2026-06-22 13:34:28 +09:00
a6b74e46c4 test: migrate global value r/w family-5 to test/lang @test, retire C twins (fold-2)
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.
2026-06-22 12:31:23 +09:00
b879d38b0f test: migrate structcopytail #73 struct-copy-tail family to test/lang @test, retire C twin (fold-2)
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.
2026-06-22 12:11:46 +09:00
a367bf984e test: migrate defdim len/cap family to test/lang @test, retire C twins (fold-2)
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.
2026-06-22 12:04:03 +09:00
438efab8c6 test: migrate str/slice global family-1 batch to test/lang @test, retire C twins (fold-2)
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.
2026-06-22 11:52:41 +09:00
374e97b9e8 test: migrate str/slice header family-1 batch to test/lang @test, retire C twins (fold-2)
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.
2026-06-22 09:02:40 +09:00
f864e9f9bc test: retire 15 T2+T1-covered byte-id .c twins (fold-6 batch-1)
These test/wcc/*_run.c carriers were migrated to test/lang @test packages
(value-asserting under test-lang/T1) and now have a byte-id home under
test-lang-byteid/T2. With T1 wired into make test + test-commit (prior
commit), union(T1 runtime value-asserts + T2 .s byte-id) covers everything
each .c proved, so the C twins are pure-deletion redundant. Each retired
family below names the T1 twin that now carries its value obligation; every
twin was verified to READ the value/slot and ASSERT it (not merely compile),
including the pre-zeroed-slot families (it poisons cap!=len / spoils the base
so a wrong data word fails the read-back, which byte-id alone cannot catch).

  989_gunsigned_run            -> gunsigned_test (global unsigned opcode select)
  989_chainidx_run             -> chainidx_test (chained m[i][k] header load)
  989_idxarg_run               -> idxarg_test (indexed elem call-arg header push)
  906_callret_unsigned_arith   -> callret_unsigned_test (call-return unsigned arm)
  912_sar_shr_run              -> sar_shr_test (signed >> emits SAR)
  793_widen_pad_zero_run       -> widen_pad_test (#227 high-pad zeroing; reads pad)
  932_str_elem_cap_run         -> str_elem_cap_test (str-elem .cap full 24B load)
  957_size_type_run            -> size_type_test (`size` type-position bind)
  933_str_field_cap_run        -> str_field_cap_test (str field .cap read)
  934_str_chained_field_cap_run-> str_chained_field_cap_test (chained field .cap)
  935_str_tuple_elem_cap_run   -> str_tuple_elem_cap_test (tuple-elem str .cap)
  936_str_arrfield_cap_run     -> str_arrfield_cap_test (array-field str .cap)
  937_str_arrfield_store_cap_run  -> str_arrfield_store_cap_test (array-field store)
  938_str_chainfield_store_cap_run-> str_chainfield_store_cap_test (chain-field store)
  939_str_massign_store_cap_run   -> str_massign_store_cap_test (multi-assign store)

110_uniesc_run.c is KEPT: it carries a .wwi round-trip sep-build (exit 42)
the uniesc_test twin does not replicate (HAS-UNIQUE-MODE, deferred).
2026-06-22 08:37:57 +09:00
bb4ff6e27f make: wire test-lang (T1) into make test + test-commit
test-lang runs each test/lang/*_test.ww @test through the cstage ww
driver (the runtime value-assert leg, T1). It existed as a standalone
target but was not in any aggregate gate, so the pre-push gate carried
only test-lang-byteid (T2) for these families — and T2 is compile-only
(-c, .s byte-id), blind to a both-stages-wrong value. Wiring T1 in gives
make test and test-commit the runtime value coverage that the test/wcc
.c twins currently provide, the precondition for retiring those twins.

Kept out of test-unit (inner loop stays fast) and run-always in
test-commit (NOT in the content-key skip set — caching a value gate
reintroduces silent-skip-of-value). Isolated WW_PKGCACHE=$(OUT)/langcache
so concurrent make -j cannot corrupt the default cache.
2026-06-22 08:31:06 +09:00
f56b056ed3 make: add test-lang-byteid T2 cross-stage .s byte-id gate
Compiles every test/lang/*_test.ww twice through the same cstage ww
orchestrator swapping only the frontend (WW_W6C = w6c vs w6c_ww) and
asserts every per-package <pkg>.s (incl the synth-main __root.s) is
byte-identical. This is the rule-10 stage-symmetry gate test/runww.ww
reserves as T2. .s-only (Q3); .o/.wwi are 991/992's surface. Distinct
WW_PKGCACHE per leg, both caches+sepworks wiped per file. Recipe is
parameterized over an input-class (file-list + build-verb); only the
test/lang class ships now, fold-6 slots in build --sep for selfhost/data.
Wired into make test (pre-push) only, not test-commit/test-unit.
2026-06-22 07:56:20 +09:00
e1740fff10 test: migrate 933-939 str-cap family to test/lang @test
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.
2026-06-22 03:54:54 +09:00
08975c11c9 test: migrate str-elem-cap/size-in-type behavior to test/lang @test
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.
2026-06-22 03:01:09 +09:00
7db30bf609 test: migrate callret-unsigned/sar-shr/widen-pad behavior to test/lang @test
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).
2026-06-22 02:53:16 +09:00
f3743b3c7f test: run test/lang @test corpus via the test-lang make target
The four test/lang/*_test.ww in-language @test files had no make target
running them — dead coverage that could rot silently. Add a standalone
test-lang target running each through the cstage `ww test` subcommand
(one package per invocation; set -e gives the gate teeth). Imports
resolve via the driver's self_dir/../../lib fallback, so no lib env.

Kept out of make test/test-unit/test-commit: that wiring is fold 6 and
is gated on the T1 behavior corpus reaching parity before byte-id is
demoted to pre-push.
2026-06-22 02:39:38 +09:00
ca9376acde test: migrate gunsigned/chainidx/idxarg behavior to test/lang @test
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.
2026-06-22 02:04:56 +09:00
2f788305b4 test: migrate 110_uniesc rows to in-language @test (test/lang pilot)
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.
2026-06-22 01:48:38 +09:00
70ef7681a6 test: make runww the cstage-only T1 behavioral gate
Per the tier+stage split (USER-approved): the routine wwstage-behavior
run is redundant -- T2 byte-id (cstage.s vs wwstage.s, strictly more
sensitive) plus T1's cstage run together cover both stages. Drop the
wwstage path from runww; cstage==wwstage convergence moves to a separate
pre-push byte-id tool (T2). Update the test-run target comment to match.
2026-06-22 00:16:53 +09:00
c7eeb475b1 test: make test-run target for the runww behavior harness (fold 2)
Drives test/runww.ww over every test/wcc/data/*/case.ww through both
stages and propagates the harness exit code. Infra only; not wired into
`make test` yet (fold 6).
2026-06-21 23:48:36 +09:00
93b671808c os: getenvs() []str + single-walker getenv (Hare env surface)
Mirror Hare's os env surface: getenvs() builds an owned []str of
NAME=VALUE entries from the rt_envp table (platform_environ.ha:41),
and getenv iterates it (environ.ha:32) so there is exactly one env
walker. strings.dup is unusable here -- lib/strings imports os, so os
importing strings would cycle; the owned copy is inlined (dup.ha:7).
test_getenvs_entries pins the []str shape non-vacuously.
2026-06-21 23:41:18 +09:00
b817e5d498 test: ww-native behavior harness (runww.ww) + 2 pilot cases
First piece of the Go-model test rebuild: runww.ww is a ww program (the
test/run.go analog) that drives compiler cases through BOTH stages and
asserts behavior/diagnostics. Directives //ww:run / //ww:run-exit N /
//ww:error "<substr>" / //ww:compile; spawn+stderr-capture lift the
driver's procrun (main.ww:160) + dup2(2). The //ww:error check requires
rc!=0 AND the diagnostic substring (the #20 non-vacuity guard -- a crash
can't pass), and a malformed error directive fails loudly.

Bulk corpus migration + ww test wiring are follow-up folds; case spawns
need os.envp() (#28) and per-pid /tmp paths (#29) first.
2026-06-21 15:28:59 +09:00
0f0910ae13 tools: scope sizelint glob off sepwork intermediates (#17)
sizelint's find globbed sepwork build intermediates (e.g. *.unit.ww)
mid-regen, causing spurious failures under parallel load (impl-f32 and
impl-speed both hit it). Prune *.sepwork dirs from the source glob;
tracked-source coverage unchanged.
2026-06-21 15:11:25 +09:00
e8ef1e061c test: require diagnostic on reject rows, close crash-vacuity (#20)
Reject helpers checked only rc!=0, so a SEGFAULT (exit 139, or the
driver's "w6c failed" exit 1) counted as a clean reject -- a wwstage
crash could pass vacuously (it did, latently, on bodied bare-... pre
#11). Every reject row now captures stderr and requires the actual
diagnostic substring (rc!=0 AND strstr) across all 16 reject tests; a
crash emits no diagnostic, so it now fails. This is the
differential-reject backstop (#15): both stages must cleanly reject
with the expected message.

Two genuine cstage/wwstage diagnostic-body divergences are documented
inline via per-stage substrings, not papered over (845 tuple parse,
catA_f2 tuple arity); catalogued in #21.
2026-06-21 13:04:59 +09:00
1f1efb273a check: gate C-style ... to bodiless decls, both stages (#11)
A bodied fn with a bare C-style `...` was silently accepted by cstage
and SEGFAULTED wwstage (resolvefnbody walked a typeless `...` param).
Gate it: bare C-`...` is allowed only on bodiless decls (extern /
@symbol prototypes), the real FFI path; Hare-style `T...` is unaffected.

ww restricts C-`...` to bodiless decls pending vastart/vaarg/vaend
builtins (#16); harec permits bodied C-variadic fns (check.c:3656) -- a
documented divergence, reopened when #16 lands.

Test 852 runs both stages; its reject rows require the gate's diagnostic
(not merely a nonzero exit), so a crash can't pass them vacuously.
2026-06-21 12:14:31 +09:00
c814856550 wwstage: C-FFI variadic call codegen parity with cstage (#10)
Mirror cstage's C-variadic call handling in the ww self-host: parse a
bare `...` param (decl.ww), skip param-keyed desugar for it to avoid a
nil-deref (check.ww), and emit AL = XMM-reg count plus CVTSS2SD
promotion of f32 args in the variadic tail (cgenutil.ww, cgenexpr.ww).
Closes the cat-A wwstage silent miscompile (AL=0, unpromoted f32 tail).

Parse/check/cgen are one atomic align-up (parse alone miscompiles, so
not bisect-splittable). 989_ffivariadic now runs dual-stage (cstage ww
+ wwstage ww_ww), 12/12; w6c==w6c_ww byte-identical. Byte-id alone is
blind here (the bootstrap calls no float-bearing C variadic), so the
ww_ww runtime rows are the real net.
2026-06-21 11:50:18 +09:00
8f0ce09f2a w6c: promote f32 arg to double in C-variadic tail (#14)
C99 6.5.2.2p6 requires float->double promotion for floats in the
variadic region of a call; w6c emitted MOVSS (4B), so a C variadic's
va_arg(double) read 8B of garbage. Promote at push (CVTSS2SD) so the
8B slot holds a real double, covering the XMM-reg and >8-float spill
cases uniformly; fi/AL XMM-count is unchanged.

cstage only; the wwstage twin rides C2 (#10), which adds wwstage's
C-variadic-call codegen.
2026-06-21 11:14:14 +09:00
294f4c93fd w6c: set variadic-call AL to XMM-reg count, not hardcoded 0 (catB-54 C1)
SysV §3.5.7 requires a variadic call to set AL = number of vector (XMM) regs used for the variable float args; the C callee gates its xmm-save-area stores on `test %al,%al`, so the old hardcoded XORQ AX,AX (AL=0) made va_arg(double) read garbage for any float-bearing C variadic call. Emit MOVQ $fi,AX (fi = the in-scope XMM cursor, ≤8); w6a has no MOVL-immediate encoding so MOVQ is the assemblable form and sets AL=fi identically. fi==0 keeps XORQ → byte-identical to pre-fix for no-float variadic calls. Runtime test 989_ffivariadic links a cc-compiled va_arg(double) fixture (zero relocs/undefined, w6l-linkable) and sweeps N=3/5/8 floats (N=2 is vacuous via stale-stack aliasing). C1 of the C-FFI-variadic align-up (USER ruling); C2 wwstage + C3 bodiless gate follow. ref/qbe/amd64/sysv.c:384. 454 green.
2026-06-20 23:21:58 +09:00
b9692b14f1 check: reject non-integer index operand in wwstage (catB-17)
Mirrors cstage check.c:1491-1495 (type_isint via the syntax.typeisint tinfo chaser, which chases TY_NAMED.under/TY_ENUM.sub — not the AST-keyed isinttypeast that would falsely reject an alias-int index). Record-and-continue, before the base-bail. Reject path emits no asm so cstage==wwstage byte-id holds (453 green). Pre-existing index double-emit deferred (#6).
2026-06-20 20:12:11 +09:00
7589b1bf0b check: reject invalid enum decls in wwstage (catB-2)
The wwstage checker silently accepted enums with a non-integer storage type, duplicate members, or a non-constant member value; cstage already rejects all three (cmd/wcc/check.c:1000-1042). Add validateenummembers, a pure read-only diagnostic dispatched once per enum decl from resolvewalk's N_TENUM arm (check.ww:791, beside stampenumvals -- not the per-query recompute arms), mirroring the catB-7/14 validatestructfields pattern. Storage gate uses typeisint on the resolved tinfo (the exact type_isint mirror: chases TY_NAMED.under and TY_ENUM.sub, so an int-alias storage is accepted; raw-AST isinttypeast would not). Duplicate members: O(n^2) name walk. Unfoldable values reuse enumvalfold with until=member (forward-only). Emits via cerr + c.errs, no mutation, so valid-program codegen is unchanged and cstage==wwstage byte-id holds.

wwstage's value-fold message is intentionally generic where cstage's eval_enum_value gives per-reason text (enumvalfold returns a bool, not a reason); both reject. Documented at the site, filed as follow-up task #10.

Test: new table-driven both-stage reject test 850_enum_reject (non-int storage / duplicate member / forward-ref rows + a distinct-member control whose backward-ref value builds and runs). Full make test: 452 green incl. 990-997 byte-id.
2026-06-19 15:01:08 +09:00
c024c09bc6 check: reject duplicate struct field name in wwstage (catB-7/14)
The wwstage checker silently accepted a struct with repeated field names; cstage already rejects it (cmd/wcc/check.c:925-947). Add validatestructfields, dispatched once per struct decl from resolvewalk's eager type-decl arm (check.ww:792, sibling to the N_TENUM stampenumvals fire): a pure read-only O(n^2) named-field dup walk that emits "duplicate field 'X'" via cerr + c.errs, with no mutation -- valid-program codegen is unchanged so cstage==wwstage byte-id holds. Named fields only; ww has no struct embedding, so cstage's embed-collision arm is intentionally not ported (separate parser gap, catB-89).

Test: new table-driven both-stage reject test 849_dupfield_reject (adjacent / non-adjacent / different-type dup rows + a distinct-field control that builds and runs). Full make test: 451 green incl. 990-997 byte-id.
2026-06-19 14:41:57 +09:00
e9f64a0727 getopt: free opts at size(option) (32B), not a stale 24B literal (F-E)
option = struct{flag:rune, value:str} is 32B now that str is 24B (Phase 3: cstage type.c:68, wwstage typ.ww:239 -- rune 4 + pad 4 + str 24). The three free sites (tryparse's two error paths + finish) freed (cap)*24u64 -- an 8-byte-per-element under-free of a buffer that append() grew at the real 32B stride, plus a rule-13 hardcoded-size-literal violation. Route all three through size(option).

Also corrects the stale 16B-era layout comments, including the error struct: error is 32B, so its (void|error) return rides sret/MEMORY (the 40B slot exceeds the 24B register cap), not the register ABI -- verified sound at runtime (ken; see #38).

Test (getopttest.ww, test 982_getopt_run): optionsizepin pins size(option)==32 as the regression guard; freeroundtrip is a table over {0,1,3} options exercising tryparse-then-finish. Full make test: 450 green incl. 990-997 byte-id.
2026-06-19 14:24:07 +09:00
801d105c0f w6c: chained-DOT struct field-copy tail uses 4/2/1 ladder, not over-MOVQ (#9)
The depth->=2 chained `t.m.l = s` struct-ident field copy selected its tail with (tail==4)?MOVL:(tail==1?MOVB:MOVQ), so every tail in {2,3,5,6,7} fell to an 8-byte MOVQ that over-wrote past the destination field — clobbering a @packed neighbour (t.m.l=s wrote s's slop over t.m.z: cstage exit 0 vs wwstage's correct 222) and diverging cs!=ww gate-blind. Replace with a descending 4/2/1 MOVL/MOVW/MOVB ladder comparing to ssz (the field's natural type-table size), aligning cstage UP to wwstage's sized ladder + cg_aggcopy. New table-driven gate 949_chained_dot_struct_copy_run (7 sizes x runtime-exit oracle on both drivers + cs==ww byte-id; negative-control proven).
2026-06-19 13:41:44 +09:00
1466f50a09 w6c: receive sub-8 odd-sized by-value struct/array CALL return (#107)
A struct/array sized {3,5,6,7} returned by value fell past every N_LET call-receive arm (the sized-tail arm handles {1,2,4}, the memcpy arm gates on sz>8), so cstage emitted nothing and silently DROPPED the CALL — garbage exit plus a gate-blind cs!=ww divergence (wwstage emitted CALL+MOVQ via its generic let fallback). Align cstage UP: emit the CALL + one MOVQ AX, the SysV single-eightbyte receive (ref/qbe/amd64/sysv.c retr). New table-driven gate 949 (runtime exit oracle on both drivers + cs==ww byte-id; negative-control proven).
2026-06-19 12:47:23 +09:00
3bb381ef48 ww: clean per-pid sep-build scratch dir on run/test (#59)
ww run / ww test created /tmp/ww_{run,test}_<pid>.sepwork/ but removed only
the built binary, leaking the scratch DIR every invocation — the tmpfs filler
(98,811 entries blocked the gate twice). Add a keepscratch param to
build_one_sep: a thin wrapper rm -rfs the impl scratch at the single
choke-point when keepscratch==0 AND the path ends ".sepwork" (covers every
return, success+error; fires post-link, pre-run). do_build keeps it (the
byte-id gates read <stem>.sepwork from ww build -o); do_run/do_test clean;
do_test no-o redirects scratch into /tmp. Both stages symmetric; reuses the
existing shell rm -rf idiom (lib/os.removeall = #109). Test 989_sepscratch_run
is self-scoped by child pid (non-flaky) with a KEEP control + revert-verified
non-vacuity. Full gate: 448 pass, zero new run/test leaks. (ww_d_* relic +
historical bulk = one-time sweep + agent-probe discipline, not code.)
2026-06-19 00:58:57 +09:00
5adacd2eee gitignore: ignore *.sepwork/ sep-build scratch dirs (#91) 2026-06-19 00:25:58 +09:00
027f90c572 parse,check,type,wwi: @packed struct attribute, both stages (#51)
Hare/harec @packed struct layout: no inter-field or trailing padding, align =
max field align (NOT forced to 1) — matches harec type_store.c + types.c:621
(packed{u8,u64}=size 9/align 8). Parser consumes inline @packed (loud-rejects
unknown struct attrs, both stages); layout gates padding on !packed; cstage
type_eq enforces packed type-distinctness; the .wwi producer round-trips
"struct @packed {". wwstage sets slotsize=size for packed so its composite-ABI
copy matches cstage byte-for-byte. cstage identity is faithful; wwstage identity
rides the deferred #224 nominal-resolvealias arc (#108). Both stages byte-id;
447 tests pass.
2026-06-19 00:17:49 +09:00
0197dfb9e6 lex,wwi: \u/\U unicode escapes + wide-rune .wwi round-trip, both stages (#50)
Hare-faithful \u (4 hex) / \U (8 hex) escapes; \x/\u/\U share one codepoint
path (ref/hare/hare/lex/lex.ha lex_unicode); string literals UTF-8-encode
multi-byte codepoints (cstage inline utf8enc, wwstage utf8.encoderune). The
.wwi producer rune serializer now emits \u/\U so exported wide-rune defs
round-trip (was a fatal >0xFF). Reject >0x10FFFF and surrogates with Hare-
verbatim error strings. Closes the int-cast spelling divergence (#48 RUNE_MAX).
Both stages byte-identical; 446 tests pass.
2026-06-18 22:47:53 +09:00
64cf3c4094 rm dead combined.ww + retarget 901 to sep-feed; sweep amalgamator remnants (M4 E4, #90)
The E3 flip (#87) made sep the sole compile path and deleted the
combined.ww writer, leaving the six committed *.combined.ww files dead.
Remove them and the last references to the retired amalgamator.

- rm the 6 tracked *.combined.ww (selfhost/cmd/{w6a,w6c,w6l,ww,wwdump}/
  main.combined.ww + selfhost/test/smoke.combined.ww). Verified no live
  build path or gate still feeds one as compiler INPUT.
- 901_asserttyped_gap: its 5 combined.ww gap fixtures were the last
  combined.ww INPUT consumers (4 already missing/vacuous post-flip, only
  smoke.combined.ww still fed). Retarget all 5 to sep-feed via a
  resolveunit helper (whole-package-dir copy -> `ww build --sep` ->
  <stem>.sepwork/__root.unit.ww), mirroring 990's #89 pattern; the 3
  import-free test fixtures stay raw-fed. All 8 counts hold at 0 (A-D
  coverage, vacuous since the flip, is live again).
- INV-2 (the driver's unresolvable-import-is-fatal guard) is KEPT; only
  its "Mirrors the deleted expand" lineage tail is swept. The #110
  combined_ww_fresh freshness gate was already removed in #89 (5f85852).
- Sweep dangling amalgamator lineage comments (build_one/buildone/expand/
  peek_package/peekpackage + stale combined.ww/combined intermediates)
  in cmd/ww/main.c + selfhost/cmd/ww/main.ww, symmetrically (rule-10),
  and the stale Makefile combined.ww test-comments (enumcap bigmod.unit.ww
  + 784/787/792/794/848 sep .s cmp + make-smoke sep self-compile).

Closes M4 and epic #22. all 445 pass; 990/993/994/995 byte-id HOLD;
sizelint clean.
2026-06-18 21:21:50 +09:00
a9778ec000 wcc,ww,os: atomic pkgcache store via temp+rename, both stages (#104)
The out/.pkgcache content-keyed store copied each artifact IN-PLACE
(cp -f / copyfile) to the fixed paths P.wwi/P.o/P.key. Key-last gave
crash-consistency but NOT concurrent-read safety: two same-stage builds
of a shared lib pkg (rt/time/os) target one out/.pkgcache/<pkg>/P.{wwi,o};
once an early finisher writes P.key, a later build's cache_lookup copies
P.wwi/P.o while a mid-finisher is still mid-write -> torn read -> corrupt
link / cs!=ww. The key is content-only, so it is purely the non-atomic
write.

Fix (Go-build-cache pattern, both stages in lock-step, rule 10): write
each artifact to a per-pid same-dir temp (P.wwi.tmp.<pid> etc.) then
rename() into place. Same dir => rename is atomic (cross-fs is not);
per-pid temp => concurrent writers don't clobber each other mid-copy;
content-keyed => last-writer-wins is byte-identical. Key renamed LAST so
a reader that sees the new key always finds complete artifacts. On any
mid-store error the per-pid temps are unlinked so a failed store leaves
no litter (cstage goto cleanup; wwstage cachermtmp helper).

  cstage cmd/ww/main.c cache_store: libc rename(2) + getpid().
  wwstage selfhost/cmd/ww/main.ww cachestore: new os.rename + cachetmp.
  lib/os/os.ww: add rename(2) (RENAME=82), ref/hare/os/os.ha:17 -- returns
    raw i32 errno like sibling remove/mkdir/rmdir (ww's os is the flat
    syscall floor, no fs:: layer); a second pathbuf2 slot holds newpath
    since kpath's single pathbuf can't carry both paths.

cache_lookup is unchanged: it reads cache->private scratch, and an atomic
source is never torn.

The torn-read race is closed BY CONSTRUCTION; a deterministic behavioral
regression-guard isn't feasible through the product build path (content-
keying => concurrent COLD builds all MISS+STORE, never HIT-read a mid-store
entry; a warm cache is never re-stored). The deferred white-box guard is
TASK #105. A WHY-comment at both fix sites records this.

Tests: 989_sepbuild_run KEEPS its private per-pid WW_PKGCACHE -- the
comment is corrected: the pin is NOT a torn-read mask (closed by
construction) but cold-compile isolation for the test's INTERMEDIATE
(.s/.unit.ww) byte-id compare, which a cache HIT legitimately skips
producing. The former 989_pkgcache_atomic_run is renamed to
989_pkgcache_concurrent_run and HONESTLY relabeled: it is a concurrent
shared-cache build-correctness smoke (N concurrent --sep builds sharing
one cache -> every binary byte-identical to an isolated reference + correct
run, both stages), NOT a torn-read/atomicity proof (a review revert-
experiment proved the original claim vacuous). Shrunk to 4 concurrent
builds x 1 batch x both stages. COLD/dev-only, off every byte-id/bootstrap
gate.

selfhost/cmd/ww/main.combined.ww remains stale (its writer was deleted at
the M4 E3-C1 flip; #90 deletes the file) -- not regenerated.

make test: all 445 passed; make sizelint clean; 990-997 byte-id hold.
2026-06-18 20:45:12 +09:00
33edc386f1 wcc,ww: flip driver to separate-compilation only; delete build_one amalgamator (M4 E3-C1, #22)
build_one_sep (per-package compile + .wwi interfaces + link) becomes the
sole build path. do_build/do_run/do_test and the ww twins all route
through it; --sep is now an accepted no-op and the run-rejects-sep guard
is removed.

Deleted the single-file amalgamator, both stages: build_one, expand,
expand_dir, peek_package (+ the wwstage twins + strictpkgmismatch).
unit_has_package is retained -- the sep scan loop's inline-package check
needs it. The sep-shared helpers (enumerate_dir_ww, locate_import*,
import_path_form, ImportSet, and ww counterparts) stay; they back the
surviving sep path.

Restores missing-package enforcement under sep by construction: the sep
scan loop loudly rejects an unresolvable import (cannot find package
<name>) unless the package is defined inline in the same unit -- matching
the deleted amalgamator and closing the silent-accept the flip would
otherwise introduce.

All 5 wwstage tools relink (each is built via the now-sep `ww build`);
emitted asm is byte-identical to the combined build per bootstrap input,
so the binary md5 delta is pure link layout, not codegen.

selfhost/cmd/ww/main.combined.ww is now stale and unregenerable (its
writer build_one is deleted); #90 deletes it next.

Test retargets folded in (rule-11 carve-out, #61/#133 precedent): each
asserts post-flip-only behavior, is un-pre-migratable unlike #93/#94/#103,
and splitting reddens one side. Closes #97.
- 989_slttypepref -> dir-package layout (xb imports xa so both same-leaf
  `invalid` types are in scope at xb.f); inline-multipackage was the
  amalgamator shape, deleted with the flip.
- 989_sepbuild_run -> run --sep now genuinely runs (exit 7), not the old
  loud-reject (exit 2); + a private per-pid WW_PKGCACHE so the cs/ww
  per-package byte-id compare on the shared real lib pkgs (rt/time/os) no
  longer races concurrent siblings on the global out/.pkgcache (the flip
  made sep the sole path, so every test now contends that cache).
- 737_direnum -> the deleted strictpkgmismatch "differs from" wording ->
  sep's "does not match import path" (shared substring, wwstage terser #68).
- 989_lib_byteid -> corpus-completeness scan excludes generated .sepwork
  scratch (the old `! -name '*.combined.ww'` exclude didn't cover the new
  sep artifact).
2026-06-18 19:32:42 +09:00
f98033293e test: retarget 809_idx_structlit_store asm byte-id to sep .sepwork layout (M4 E3, #103)
The last flip casualty in the test-migration class (the filed #66
"WW_W6C under sep" framing was a phantom — cstage honors the override;
see re-scoped #66 for the real wwstage align-up). 809_idx_structlit_store's
asm_byte_identical drove `bin/ww build` then read <src>.s next-to-source;
the flip relocates asm to .sepwork/, so it now drives `ww build --sep -o
<stem>` (cstage + WW_W6C=w6c_ww wwstage legs) and byte-diffs the
concatenated per-package .sepwork/*.s. callee_capture imports strings, so
concat-all is the faithful equivalent of the old whole-program compare.
Adding --sep is flip-invariant (green pre- and post-flip). 809_delete_range
is flip-invariant (direct w6c -o), left untouched. Test-only; all 5 pins HOLD.
2026-06-18 17:59:25 +09:00
107d31cc01 test: re-express 989_enumcap_run enumeratedir-cap gate to sep .sepwork layout (M4 E3, #94)
94c, the last C-mig piece. The #65 enumeratedir-cap gate built a 300-file
bigmod dir (past the old 256 cap) and byte-compared cstage vs wwstage
.combined.ww to prove both enumerate the complete file set. The flip
deletes combined.ww, so the comparison now targets each stage's
<stem>.sepwork/bigmod.unit.ww (one `package bigmod;` clause per enrolled
file). count_pkgs asserts the full nfiles count and `cmp` byte-compares
the two stages' unit, so a silent cap-drop on either side reddens both
paths. >cap fixture unchanged. Test-only; all 5 binary pins HOLD.
2026-06-18 17:39:27 +09:00
fa365f00b2 test: re-express 989_lib_byteid byte-id corpus gate to sep .sepwork layout (M4 E3, #94)
94b of the C-mig2 split (own commit: lone phase-1 test-commit cache
gate). The corpus gate now drives two --sep builds (ww / ww_ww) per lib
unit and byte-diffs the concatenated per-package .sepwork/*.s. Keying on
every per-package .s — the imported lib body (e.g. log.s) and lib/test
(test.s), never collapsed to __root.s — preserves the cs!=ww lib-body
coverage the gate exists for. Tri-mode (ID/DIVERGE/WWREJECT) per module,
import-landed sentinel, and corpus_complete scan all preserved. 43/43
(net=M_ID via #102). Test-only; all 5 binary pins HOLD.
2026-06-18 17:26:09 +09:00
4583977ce0 lib/net: export sockaddrin for sep export-hygiene (M4 E3, #102)
connect/bind export `*sockaddrin`, so under --sep the .wwi producer's
check_exported_type (both stages, correctly) rejects an exported decl
referencing the unexported type. Invisible on the old combined path
(net inlined, fed to w6c without -I). Hare exports sockaddr_in
(ref/hare/sys/+linux/socket.ha:11) and lib/net plays Hare's sys role,
so the caller must be able to name it. Same class as #48. No codegen
change (export is a checker property); byte-id holds.
2026-06-18 17:26:09 +09:00
bbd2ad390a test: retarget Pattern-A combined-feed gates to sep .sepwork layout (M4 E3, #94)
94a of the C-mig2 split (rob rule-11): 13 Pattern-A gates that fed a
.combined.ww to w6c/w6c_ww now drive two --sep builds (ww / ww_ww) and
byte-diff the concatenated per-package .sepwork/*.s, mirroring #93's
convention. Lands pre-flip while combined.ww still exists as the
reversible safety net. Test-only; all 5 binary pins HOLD.

989_enumcap_run deferred to 94b (its decisive assertion is a combined.ww
content diff, not a .s diff).
2026-06-18 16:44:41 +09:00
7b6f24adea w6c,ww: mangle an imported package's fn main under separate compilation (M4 E3, #99)
The bare-`main` carve-out (which keeps the link entry's main unmangled)
keyed on `leaf == "main" && imported == 0`. Under the combined path a
dependency's body folds in with imported==1, so only the root's main
stayed bare. Under separate compilation each package is its own unit and
a dependency's body carries a path-mangling module-reset but imported==0
(#57) — so an imported `fn main` matched the carve-out, emitted a bare
`TEXT main`, and collided with the root entry (`w6l: duplicate symbol
main`). The combined path was unaffected, so this only surfaced under sep.

Gate the carve-out with sep_isdep = (wwiout != NULL): the producer emits a
.wwi output only for dependency units, never for the root/link-entry unit
(root stripped, #69), symmetric on both stages. Only the root unit's main
now stays bare; an imported package's main mangles on its import path
(e.g. aa.bb.main). Both stages.

Gate: test/wcc/989_depmain_sep.c (table-driven, dotted + single-component
shapes, both stages; asserts the mangled dep main + a single bare root
main + cs==ww byte-id; combined path stays neutral).
2026-06-18 12:47:41 +09:00
200f51ca94 ww: resolve self-named import to the dir-package, not a sibling file (M4 E3, #98)
The driver searchpath is srcd-first (srcd = the entry file's directory).
A co-located black-box test lib/<mod>/<mod>test.ww makes srcd=lib/<mod>,
so resolving `import <mod>` hit the sibling-FILE branch lib/<mod>/<mod>.ww
and folded it inline into the consumer unit under the wrong module tag
("package <mod> does not match import path <importer>") — 7 lib-run tests
fail under separate compilation. The combined amalgamator tolerated the
co-location; only sep surfaced it.

Resolve a package directory-first: walk ALL searchpath entries for a
directory match, and only fall back to a file match if no directory
exists anywhere. A dir-package now beats a same-named sibling file (fixes
the self-named shadow), while a leaf package with no directory (e.g.
lib/encoding/hex) still resolves via its file. This realizes the driver's
"a module is the directory" intent; the originally-specced per-directory
suppression was rejected because it broke leaf packages (rob-pike). Both
stages (cmd/ww/main.c + selfhost twin). The dir-beats-earlier-file
precedence change is latent and loud-failing (#101).

Move-set: ww + ww_ww (driver) only; w6c_ww/wwdump_ww/w6a_ww/w6l_ww HOLD.
Gate: test/wcc/989_coloimport_sep.c (table-driven, both stages).
2026-06-18 12:09:25 +09:00