Commit Graph

918 Commits

Author SHA1 Message Date
850746cfa8 wcc/ww: str-to-slice cap synth keys on the source type
Casting a global str to []u8 dropped the cap=len synth (the trailing
MOVQ BX,CX) — the synth was gated on a local-ident source shape.
Key it on the source type so local/global/field/call sources all get
the header. Review item #19.
2026-06-12 21:00:59 +09:00
c405e777d3 wcc: single-dot field compound assignment routes through one combine helper
s.f *= v silently became s.f = v (and the other non-+=/-= ops dropped
likewise) in BOTH stages across five lvalue sub-arms: via-ptr field,
direct local field, str/slice pseudo-field, and the two global-field
forms. Funnel all five through a shared combine dispatch
(cgdotfieldcombine / cg_dotfield_combine) emitting the load-OP-store
sequence at field width, hard-erroring the unhandled kinds — close-by-
construction so no arm stays on the old PLUSEQ-only path (the #133
BUS-routing lesson; #227 sites A/B are the closed siblings). The
refactor routes the corpus's existing +=/-= sites through the same
helper output-identically (byte-id held). Review item #34.

Both stages move in one commit: one emission contract; splitting the
halves would leave the byte-id gates red in between.
2026-06-12 19:29:58 +09:00
1be0e6b5db wcc: indexed-field compound assignment wires all ten ops, both stages
arr[i].field /= %= <<= >>= silently dropped the op (load-combine-store
emitted plain assignment) in BOTH stages — gate-blind, the #133 class.
Route every compound op through the combine dispatch at the indexed-
field arm and hard-error the unhandled operand kinds (float/str/slice/
tagged), per the #133 template (3986818). The runtime-correct target is
the op's own algebra (a OP= b == a = a OP b). Review item #33.

Both stages move in one commit: the fix is a single emission contract —
splitting cstage cgen.c from selfhost cgenexpr.ww would leave the
byte-id gates red between the halves.
2026-06-12 19:26:24 +09:00
02eb867036 wcc/ww: cgtypeassert gains the nullable arm
'as' on a nullable value compared the pointer itself to a tag (the
missing arm). Mirror cgtypetest's nullable fold and the cstage twin
(cgen.c:10694). Review item #17.
2026-06-12 19:22:51 +09:00
6f3c896fea wcc/ww: cgtryprop/cgtryunw gain the nullable arm
Try-propagation on a nullable value fell to the tagged path and
compared the pointer to a tag — the null check came out inverted.
Mirror ww's own cgtypetest nullable fold (cgenexpr.ww:652) and the
cstage twin (cgen.c:10366/10504). Review item #15.
2026-06-12 19:19:26 +09:00
87367ae332 wcc/ww: match-as-expression unifies arm yields (coarse-family gate)
exprtype N_MATCH took the first arm's yield type without walking the
rest — int-vs-str arms silently produced garbage downstream. Walk all
arms: typeeq-equal accepts; a definite coarse-family mismatch
(num/str/bool via the new yieldclass classifier, unknown classes stay
lenient) rejects. Same-family non-assignable pairs remain lenient —
the documented precision residual is task #52 (needs tinfo-level
type_assignable). The wave's table-driven reject test lands here:
989_catA_f2_reject, 19 rows x 2 stages, each member pre-fix-red-proven.
2026-06-12 11:31:53 +09:00
780e680c1b wcc/ww: checktryprop flattens spread variants in the success count
The F8 multi-success gate counted a `...inner` spread as one variant,
bypassing the multi-success reject. New trycountvariants recursively
flattens spreads (the #209 recursion; cstage check.c:2160). Also
carries the rule-7 deferral cites for the adjacent task-#50/#51 holes
(exprtypeoftry lookups, desugarcallargs fn-ptr bail, checkisas) — the
attempted scopelookupprefer hardening is byte-id-blocked by the #50
curmod layer (evidence in the task).
2026-06-12 11:28:31 +09:00
6f2ed0cc57 wcc/ww: reject non-tuple multi-assign rhs (resolvewalk N_MASSIGN)
ww accepted a non-tuple rhs in a destructuring assignment that cstage
rejects (check.c:2562); the destructure then read garbage words.
2026-06-12 11:24:58 +09:00
6c380a53ea wcc/ww: reject tuple-literal arity mismatch (checktuplearrfits)
A tuple literal with the wrong arity was silently accepted and the
extra/missing elements mis-stored. Mirror cstage type.c:416 +
check.c:2386.
2026-06-12 11:21:39 +09:00
bd86fa5f46 wcc/ww: size/align intercept unconditionally; unknown type is loud
The size()/align() intercept hid behind a shadow gate with no cstage
twin — a shadowed name silently folded to zero. Intercept
unconditionally and make an unresolvable type a loud error, mirroring
cstage check.c:93/1538; the dead shadow gate is dropped.
2026-06-12 11:18:19 +09:00
353b50489f wcc/ww: binoptype/unoptype operand-kind gates (arith/bitwise/ordered/logical)
wwstage ran no operand-kind check on binary/unary operators: str+str
compiled to integer ADD on the 24B header (silent garbage). Gate each
operator class on the operand kind, mirroring cstage check.c:1186-1238
wording; the pre-existing ptr-arith arm aligns to intkindast so
ptr+untyped_int keeps compiling (byte-id-neutral, the over-reject the
self-compile gate caught). New intkindast/numkindast/boolkindast
predicates.
2026-06-12 11:14:48 +09:00
6f11763462 wcc/ww: reject non-constant array dimension (tinfofornode N_TARRAY)
arrayelen silently folded a non-const dimension to 0 — the 0-sized
slot aliased its neighbor (review item: cs loud / ww rc=0 clobber).
Mirror cstage check.c:715. Test rows land with the wave's final commit.
2026-06-12 11:11:33 +09:00
9f4626c546 wcc/ww: global float-struct by-value arg keys the SSE drain off the type
A module-global float-bearing struct passed by value drained all-GP —
the SSE-cursor classify keyed on the node shape and missed the global
ident; key off the stamped struct type (per-eightbyte classify,
F7-flavored stamp fix). Review item #31; dual-stage rows red-proven.
2026-06-12 09:24:39 +09:00
e416885d96 wcc/ww: widen of a module-global tagged ident copies the whole box
Widening a global tagged union into a wider tagged slot copied
nothing of the box; treat the global ident as a tagged source and
copy the full box from g(SB) through the nested arm. Both-wrong pair:
cstage spills frame garbage as the box (filed task #44, residual
non-deterministic so the rows assert divergence only). The non-nested
SUBSET-widen shape remains open as task #49 (site comment at the
fall-through). Review item #51.
2026-06-12 09:21:16 +09:00
32848194d7 wcc/ww: widen of a module-global struct ident copies the full payload
Widening a global struct into a tagged slot copied word0 only; copy
the full payload from g(SB). Both-wrong pair: cstage zero-fills the
payload (filed task #43); rows pin ww-runtime-correct with the
documented cstage residual. Review item #50.
2026-06-12 09:16:52 +09:00
4b8c8fd9ed wcc/ww: return of a module-global struct ident copies the global's bytes
Returning a global struct by name emitted nothing into the return
scratch (ww) — copy from the g(SB) base. Both-wrong pair: cstage
zeroes the retscr instead (filed task #42); rows pin ww-runtime-correct
with the documented cstage residual. Review item #41.
2026-06-12 09:13:26 +09:00
b3a355744f wcc/ww: tagged GLOBAL reassign stores tag and payload
Reassigning a module-global tagged union stored the payload into the
tag word; emit the full tag+payload store to g(SB). Both-wrong pair:
cstage silently DROPS the store entirely (filed task #41) — rows pin
ww-runtime-correct and the documented cstage residual. Review item #32.
2026-06-12 09:10:16 +09:00
94bfca761c wcc/ww: dotbaseaddr #128b probe gates on an untyped module-qualifier inner
The probe accepted any inner ident, hijacking same-leaf locals as a
module qualifier; gate on the untyped(module) inner only (mirror the
cstage twin). Review item #20; dual-stage rows red-proven.
2026-06-12 09:06:51 +09:00
4961a91d14 wcc/ww: is/as on a module-global tagged ident loads from g(SB)
The global tagged ident operand read saved BP instead of the global:
'is' compared garbage as the tag; 'as' never had a payload. Route the
load through the g(SB) base — tag at +0, payload at +8, cap at +16 for
str (one mechanism, both consumers). The 'is' half aligns ww UP
(cs==ww pinned); the 'as' half is a both-wrong pair — cstage spills an
uninitialized payload register (its N_TYPEASSERT assumes cgexpr filled
AX/DX/CX; filed as task #46), so its rows assert ww-runtime-correct
with the cs divergence documented until #46 lands. Review item #18.
2026-06-12 09:03:40 +09:00
1eceb46ac3 wcc/ww: global-str default-hi slice arg loads its len word
Slicing a module-global str with default hi emitted nothing for the
bound; load the len word from g(SB)+8 (mirror the cstage twin).
Review item #47; dual-stage rows red-proven.
2026-06-12 09:00:15 +09:00
fee84528da wcc/ww: global-struct slice-field store widens the str arm to slices
A slice-typed field of a module-global struct stored only the str-form
words; widen the arm to the full slice header via the g(SB) base
(mirror cgen.c sibling arm). Review item #35; dual-stage rows red-proven.
2026-06-12 08:57:04 +09:00
e0df2adf47 wcc/ww: tagged-union normalization at tinfofornode (never-drop, dedup, collapse, nullable fold)
wwstage computed tagged sizes/tags off the raw variant list — size()
folded wrong constants (size((*u8|void)) 16 vs 8, (i32|never) 16 vs 4)
and duplicate variants got divergent tag numbering vs cstage, while
ww's own cgen layout folded nullable but its size() didn't. Make
tinfofornode's N_TTAGGED arm the normalization SSoT mirroring cstage
resolve_type (check.c:801-882): never-drop, duplicate dedup via
structural typeeq, single-variant collapse, nullable fold on the
normalized pair; astsize/astalign delegate, and voidvariantindex reads
the normalized ti.params (cgen.c:900-911) so construct/match/void tag
readers agree. Corpus-neutral (zero-move on all combineds);
989_tagnorm_run pins the folds dual-stage, red-proven. Review items
#1/#3; residual #45 filed (AST-keyed nullable gate at global emit).
2026-06-12 07:03:45 +09:00
d6052e0829 wcc/ww: nodefnptr keys the bare-ident arm on the stamped type
nodefnptr matched bare idents by NAME against the fn table, so a
global var colliding with a fn leaf classified as a fn pointer —
wwstage silently built what cstage rejects at link (review finding
#14). Key on the stamped type; the #124 &mod.fn arm is preserved.
989_fnptrcollide_run pins both stages reject (red 1/2 pre-fix:
wwstage built rc=7).
2026-06-12 00:21:16 +09:00
47b971330b wcc/ww: tagged-element classify keys on the stamp, not a base whitelist
cgindex's tagged-element classification whitelisted base node kinds;
call- and slice-based tagged elements fell off the list and dropped
the payload words (review finding #23). Classify by the stamped
element type. 989_taggedidx_run pins cs==ww (red 2/6 pre-fix).
2026-06-12 00:17:55 +09:00
0625af1309 wcc/ww: nodeisunsigned resolves module-global idents via the stamp
A bare module-global unsigned operand got signed IDIV/SAR/Jcc — the
predicate's ident arm only consulted the local table, so globals fell
through to signed (review finding #25). Read the stamp for the global
arm; corpus emission is unmoved (no bootstrap code div/shift/cmps a
bare unsigned global). 989_gunsigned_run pins cs==ww (red 3/8
pre-fix).
2026-06-12 00:14:32 +09:00
7afc4df652 wcc/ww: paramfieldsize sizes slice/tuple fields through the type table
for-range destructure of an element with a slice- or tuple-typed field
strode by the default 8 (paramfieldsize had no N_TSLICE/N_TTUPLE arms),
silently reading the wrong words (review finding #43; live repro
cs=42 vs ww=8). Add the arms routed through tinfo per rule 13.
989_tupfieldsize_run pins cs==ww (red 1/2 pre-fix); rows assert
convergence, not absolutes — cstage's own single-word destructure-load
bug is filed as task #40. The N_TARRAY arm is deferred (task #39,
rule-7 comment at the fall-through).
2026-06-12 00:11:11 +09:00
f25f5021d7 wcc/ww: chained-index str/slice element loads the full header
cgindex read one word for a str/slice ELEMENT of a chained index
(xs[i][j], f().s[i]) — the element-kind gate keyed on node shape and
missed non-simple bases, dropping the 24B/16B header load (review
finding #22). Key on the element-type stamp; 989_chainidx_run pins
cs==ww (red 3/8 pre-fix).
2026-06-12 00:07:45 +09:00
cbbfd1a4a6 wcc/ww: nodeisslice/nodeisstr gain the N_INDEX arm (stamp-keyed)
A slice/str ELEMENT of an indexed expression passed as a call-arg
pushed one word instead of the 24B/16B header — the predicates had no
N_INDEX arm, so element-typed args fell to the scalar path (review
findings #45/#46). Read the element-type stamp; dual-stage rows in
989_idxarg_run pin cs==ww (red 2/8 on pre-fix binaries).
2026-06-12 00:04:22 +09:00
6afa72afea wcc/ww: nodeisslice/nodeisstr local idents read the checker stamp
Collapse the localfindnode tnode read to the stamped n.type_ — a
zero-delta mechanic validation for the F7 predicate type-keying arc:
self-compile byte-id holds on all four bootstrap combineds, proving
n.type_ matches the local-table truth on every corpus local before
any behavior-changing conversion lands. The localfindnode gate stays
so a global-not-def ident still routes false.
2026-06-12 00:01:01 +09:00
556a65ee86 wcc: general call-arg typecheck via assignability union, both stages
wwstage's desugarcallargs ran no general per-arg typecheck (only the
narrow #258 array-to-slice arm): any mistyped scalar call-arg silently
miscompiled (int read as a 24B slice header; the -T face was a user
const __wwtests building a garbage test binary). Route every call-arg
through the predicate union isassignable()||assignableaddrfn(),
mirroring cstage type_assignable||assignable_addrfn and the check.c:1869
diagnostic. Confident scalar/aggregate and aggregate/aggregate
kind-mismatch rejects live in shared isassignable; the concrete-to-
tagged arm is shape-matched-lenient via tagshape() (AST mirror of cgen
taggedvariantindext) so genuine variant members keep flowing while
shape-mismatched aggregates reject. Reserve __wwtests under -T in both
stages (mirror the main reservation, check.c:2996). New table-driven
989_callarg_typecheck, 31 fixtures, reject rows proven red on pre-fix
binaries.

Deferred, filed, site-commented: the assign seam rides #178->#36
(typeeqast cannot compare variadic/module-qualified fn sigs); the
same-coarse-shape same-leaf nominal collision over-accept rides #37
(#10/#66 — the distinguishing module is absent from the AST surface
isassignable operates on).
2026-06-11 20:45:02 +09:00
8d2d157a58 wcc/ww: flexible rune-const promotion at scalar seams (coercerunelit)
wwstage stamped N_RUNELIT as concrete rune (cstage: untyped_rune,
check.c:1296), so an uncast rune literal into a fitting integer slot
over-rejected where harec accepts (promote_flexible). Add coercerunelit
(mirror of coercefloatlit) at the scalar seams — let-init, return,
call-arg, index-store; coarse accept, no range check (mirror cstage
type.c:379); array-literal elements keep the range gate
(arrlit_init_fits, types.c:923). Teeth: the fnmatch.ww '\\': u8
workaround cast and getopttest's 'X': u8 index-store casts are removed
and compile green. Checker-only (no e.type_ restamp): byte-id held.
2026-06-11 20:41:20 +09:00
fa17459558 wcc/ww: exprtype stamps bare fn rvalue with its fn type, not return type
For an SK_FN sym, exprtype's N_IDENT and N_DOT arms returned decl.lhs
(the return type), so a bare fn rvalue compared as its return type —
latent silent mis-accept of mismatched-signature fn assignment (the
706/768 over-reject under any general check was the visible face).
Synthesize the N_TFN instead (mirror of assignableaddrfn's synth), and
add the N_TFN-vs-N_TFN confident-mismatch reject in isassignable.
harec: resolve_function check.c:4279-4288 (obj->type=fntype),
EXP_ACCESS check.c:341-343 (bare ident yields obj->type, no decay),
call check.c:1573, assignability types.c:1001; cstage twin
check.c:1313 + build_fn_type check.c:2611. cgen is name-keyed on fn
rvalues (never reads the stamp): zero asm delta, byte-id held.
2026-06-11 20:37:47 +09:00
b9c4562135 ww test: @test name-filter via fnmatch (both stages)
`ww test <file> <pattern>` runs only the @test fns whose names match the
fnmatch glob; no pattern runs all (byte-for-byte the pre-filter path);
zero matches prints "No tests run" and exits 0 (Hare ground truth
ref/hare/test/+test.ha:114-117). A pattern in directory mode is rejected
"ww test: pattern needs a single test file" (rc 2), identical wording in
both twins (cmd/ww/main.c do_test + selfhost/cmd/ww/main.ww dotest).

Mechanism (a): rt/start.s stashes argc/argv into rt_argc/rt_argv getters
(rt_envp twin shape, -T synth untouched so 990-997 byte-id holds);
lib/os.args() rebuilds the []str view, build-once-cached; lib/test/run.ww
imports fnmatch and filters av[1..] (argv[0] is the binary path). The
driver forwards the 2nd positional as argv[1] via fork/execv (cstage) /
procrun (wwstage) so glob metachars aren't shell-expanded.

os.args() is the first `alloc`-caller in the base os module, so os.ww now
imports rt — the `alloc` builtin's malloc lowers to rt_malloc only when
the rt binding is bundled (mirror lib/strings/strings.ww:30); without it a
plain `ww build` of any os-importing program links bare libc `malloc`
(undefined). os is bundled by ~every program, so this is load-bearing.

The lib/test floor rises os-only -> os+fnmatch+ascii+strings in every -T
build; the bundled `ascii` module vs a `@test fn ascii` collision that
exposed is closed by the preceding #30 promote commit. 989_test_filter
pins the full matrix on both twins byte-identically; 949 gains the
dir-mode reject row. (#17)
2026-06-11 04:51:17 +09:00
64f0ddf01b wcc/ww: promote module-name SK_USE to value kind (use_alias), aligned to cstage
A primary-package top-level decl whose leaf also names a bundled module
(`type sym` vs `import sym`; the lib/test->fnmatch->ascii -T floor's
`ascii` module vs a `@test fn ascii`) collided in the flat scope: #23's
installtop dup-check false-fired "duplicate fn/type" where cstage
coexists. cstage keys the module name out of the value namespace by
PROMOTING the same-leaf SK_USE in place to the value kind with
use_alias=1 (cmd/wcc/check.c:2831/2848/2871/2907/2928/2951), so one
correctly-kinded sym serves bare refs (call/structlit/var) while
`name.member` still resolves the module via the `kind==SK_USE ||
use_alias` N_DOT guards (check.c:87/1337).

This REVERSES wwstage's documented two-sym coexistence design
(lib/ww/sym.ww scopelookupuselocal): keeping the SK_USE as a separate
coexisting sym ripples into every bare-reference resolver (~25
scopelookup sites), and a missed site is a byte-id-consistent-but-wrong
cat-A risk the gate cannot prove away — the same failure mode retired
with the name-keyed variant-match cluster. The promote model is correct
by construction: one sym of the right kind, identical to cstage.

sym gains a use_alias field; the three N_DOT/N_CALL module-qualified
guards honor use_alias. cstage installs every SK_USE in a dedicated
first pass, so its value-arm promote is order-INDEPENDENT; wwstage
installs in source order, so BOTH directions of the collision are
promoted to reach cstage's identical single-sym end state:
  - use-before-value (`import aa` then `fn aa`): installtop promotes the
    pre-installed SK_USE to the value kind (use_alias=1).
  - value-before-use (`fn aa` then `import aa`): installdecl's N_USE arm
    promotes the pre-installed value sym in place (set use_alias=1,
    no coexisting SK_USE), mirroring cstage's self-import N_USE arm
    (check.c:2823-2834 `if (prev) prev->use_alias = 1`).
Both orders compile + are cs/ww byte-identical AND byte-identical to
each other. Cannot split: promote without the guards leaves
`name.member` red on the promoted sym; the guards without promote are
inert (no use_alias is ever set) — no bisect-clean intermediate. (#30)

Pins (910/997): modfn_coexist_ok (use-before-value) AND
modfn_coexist_vbu_ok (value-before-use) both accept on cstage + are
cs/ww byte-id + run to exit 6 (bare fn and qualified module both
resolve); the dup_fn row still rejects both stages (regression). fnmatch
byte-id holds. The order-dependence is exactly what regresses silently,
so both orders are pinned.
2026-06-11 04:51:07 +09:00
00b9933ee0 wcc/cgen: enum as int is a value conversion, not a tagged assertion (ww stage)
The value-passthrough arm gated on node shape (isenumexpr); a
constant-folded enum member reaches cgen as an int-literal node,
missed the arm, and fell into the tagged-union assertion path
(CMPQ $0 + JNE -> exit 1) — silent wrong code for every `enum as
int`, fnmatch's flag tests included. Gate on the stamped operand
type (tichase == TY_ENUM) at the cgtypeassert choke point, mirroring
cstage cgen.c N_TYPEASSERT; dead node-shape helpers deleted. Tagged
`as` assertion path byte-id-unmoved (control rows). Six @test pins
in attest_pass.ww, dual-stage. (#27b-team, cat-A)
2026-06-11 02:37:32 +09:00
19e9535ef4 wcc/ww: reject duplicate top-level decls, aligned to cstage
installdecl routes all four kinds (fn/type/def/let) through installtop,
which turns scopedefineinmodule's nil return into cstage's exact
"duplicate <kind> <name>" reject, keyed (name,mod) so cross-package
same-leaf decls coexist. Builtin redecls are dropped, not dup-errored:
cstage never scopes builtins (lookup_builtin first, check.c:69), so a
user redecl is dead there — wwstage mirrors via scopesamekeysym +
no-source-decl test. -T synth __wwtests installs direct, mirroring
check.c:3079. Closes the silent dup-fn hole (user fn run vs lib/test
run built a broken test binary with no diagnostic). Per-kind reject
rows + cross-package/builtin accept byte-id rows + -T collision parity
row in 910/997. (#23-team, category-A addendum closed)
2026-06-11 00:57:43 +09:00
16c83e70d3 ww test: fork-isolated record-and-continue harness (lib/test, both stages)
lib/test/run.ww: fork+wait4 runner; each @test runs in its own child,
abort/SEGV/FPE decoded from wait-status, failures recorded and the run
continues; exit = fail count. Tests are hermetic: module globals do not
persist test-to-test (fresh fork image; sanctioned divergence from
harec's shared-process __test_main, no setjmp/signal layer needed).
-T synth (both stages) emits a module-global (str,*fn() void) table +
return run(table) instead of straight-line calls. Driver twins bundle
lib/test under test mode and gain ww test -c/-o (go test -c) so the
byte-id gates diff the same artifact the real path builds. Gates
989/910/997 rewired onto it; new 911 pins record-and-continue across
all three fault classes; 949 +3 rows. (#17-team commit-2)
2026-06-11 00:08:39 +09:00
08a76cf4c8 wcc: drop @test fns from non-T builds, both stages (harec check.c:3941)
Splice @test N_FNDECLs out of the unit after the body-check passes,
mirroring harec's checked-but-not-emitted: a broken @test body still
errors loudly in non-T; @test-free units are emission-unchanged.
910/997 table rows pin keep/test x non-T/-T, head+consecutive unlink,
undef-body reject, and plain-calls-dropped loud link-fail. w6c+wwdump
combined.ww regen. (#6-team)
2026-06-10 22:20:15 +09:00
2338ea5d59 ww: lib tests run via -T test mode; bare mains retired (closes @test conversion)
'ww test' gains the istest build path (-T injection in build_one/
buildone) and do_test/dotest accept -I, mirroring do_run - both twins.
The 35 converted lib tests drop their interim bare mains (-T
synthesizes the entry from @test fns and rejects a user main); their
35 C run-drivers flip 'ww run' -> 'ww test'; 989_lib_byteid compiles
lib tests under -T (8 user-main probe fixtures stay non-T, gated on
the fixture field). Abort-on-first-failure stands until the deferred
record-and-continue harness lands with the multi-package arc.
2026-06-10 20:45:46 +09:00
5b212e51cf wcc/cgen: zero-init sub-8-byte bare lets, both stages; bytes test honest (#16-team)
A bare 'let x: T;' with 1 <= size(T) <= 7 matched no zero-fill arm in
either stage (8B and >8B were already zeroed) - 'let c: [3]u8;' read
stack garbage. User-ruled zero-value semantics: cstage gate sz>8 ->
sz>0; wwstage zsz==8 arm hoisted above the fill-run arm (required -
8B would otherwise route into the run and diverge) and run gate
zsz>0. New 840 pin: dirty-frame probe rows, dual-dim (run + cs/ww
byte-id); discriminators fail exit-154 on pre-fix binaries.

Fused with the lib/bytes test conversion (rule 11): either half alone
turns 967 red. The old exit(signalled+10) wrapped a real 1782-count
ltrim failure to exit 0 - green depended on the garbage. Converted to
assert form (completes the 35/35 @test conversion); ltrim rows keep
the bare 'let c: [3]u8;' as the consumer proof of the fix.
2026-06-10 20:08:58 +09:00
44c47c3ea0 ww: build intermediates follow -o output stem, not the source dir (test-perf T3a)
build_one/buildone gain an explicit objstem: 'ww build -o X' derives
main.{combined.ww,s,o} beside X; 'ww run' uses its per-pid /tmp stem;
default no--o stays next-to-source (load-bearing for the make regen of
tracked combined.ww and the freshness gate). Realizes the redirect TODO
in 995_self_rebuild.c. Kills the concurrent-test torn-read race on
selfhost/cmd/<tool>/main.* (the 991 transient). 993/995/949 pass -o
into their per-pid workdirs; 949's '-o /dev/null' relied on the old
driver ignoring -o (audited: the only live driver case). Atomic
combined write (os.rename) deferred to its own commit - lib/os lacks
rename and that addition is an import-floor regen.
2026-06-10 17:02:28 +09:00
4ff79bff0f ww: loud fatal on unresolvable import, inline-package aware (#16 ENFORCE-driver)
Both driver twins: an import that neither locates as a file nor is
satisfied by an inline 'package <name>' declaration in the unit is now
fatal "ww: cannot find package <name>" (was a silent skip that masked
dead imports and typos). The inline scan is a new every-line helper on
the uncapped comment-skip core - peekpackage stops at the first decl,
and single-file multi-package fixtures declare several. 949 pins both
branches (miss->fatal, inline->build+run); 993 adds ww_ww parity.
2026-06-10 15:19:10 +09:00
6f781f3d71 selfhost: drop dead 'import mem' (#16 PREP-deadimport)
mem.ww was retired in Phase-0; these two imports survived only because
the driver silently skipped unresolvable imports. Provably dead: no mem
module exists and both units build identically without them. Removal
precedes the loud driver, which would otherwise fatal on them.
2026-06-10 15:19:10 +09:00
9f8df525c2 wcc/check: reject self-import, both stages (#16 ENFORCE-checker)
check-(c): a package importing itself (any spelling) is a hard error,
mirroring Go. Predicate is leaf==owner at the N_USE/installdecl seam —
sound only after the PREP commits (dotted-test renames, package-less
boundary directive). Identical wording both stages; diagnostics-only,
byte-id-neutral. 948 pins the reject in both compilers; 708's
pos_selfimp (which pinned the abolished self-import skip) converts to
neg_selfimp + new pos_crossmod preserving the param-shadow tolerance
the case existed for. Checks (a) unused and (b)/(d) name-membership
stay deferred to the multi-package arc: imports are filename-keyed
pulls, so those need import->file provenance this compiler lacks.
2026-06-10 15:18:58 +09:00
49a5173f3f wcc+ww: //ww:module-reset boundary directive; package-less files keep module "" (#16 PREP-main)
Driver twins emit the line-comment directive only before package-less
files (peekpackage==nil); both lexers tokenize it (TK_MODRESET, appended
=87 so existing token values hold) and both parsers reset curmod — a
package-less file's decls get module "" instead of inheriting the last
bundled package (the sticky-curmod leak, task #11). Withdrawn
alternative: injecting 'package main' flips non-entry symbols
bare->main-prefixed (FFI-visible, broke 764). Codegen-neutral by proof:
bare symbols preserved, both stages emit byte-identical asm for a
directive-bearing combined. Transitional until strict-package rejects
package-less files outright. Includes 737 bad-deep pin for the
PREP-peek >2048 edge + 904/toktest rows for the new token.
2026-06-10 14:07:35 +09:00
2227d698c6 ww: drop wwstage peekpackage 2048-byte cap, align to cstage line-scan (#16 PREP-peek)
Pre-existing rule-10 asymmetry: 4 lib files (decimal/ftos/stof/memio)
declare package past byte 2048, so the capped wwstage peek returned nil
where cstage's uncapped line-scan found the decl — corrupting the
strict-package check and any peek consumer. Mirrors cstage peek_package
semantics exactly; prerequisite for the upcoming package-less boundary
directive to inject identically in both twins.
2026-06-10 13:27:12 +09:00
b795c320c9 wcc: -T test-mode collects @test fns + synthesizes entry, both stages (#15)
@test was parsed then dropped (no consumer); `ww test` needed a hand-written
main listing each test by hand, so adding a @test and forgetting the call
silently skipped it. -T makes the checker collect @test N_FNDECLs in source
order, loud-reject a user main, and append a synthetic
`export fn main() i32 { t0(); ...; return 0; }` at the install->body-check seam;
the existing cgfn emits it (cgen untouched) -> byte-identical by construction.
Mirrors harec's checker-side is_test placement.

Plan-9-lean reduction (user-sanctioned, reinstatable post-CSP): sequential,
abort/nonzero=fail; no setjmp isolation, no fnmatch filter, no file:line.

910/997 rewired from a regex scanner to driving `w6c -T` directly (thin trusted
drivers; the @test content stays ww), with a cross-stage byte-id assert on the
-T output. attest_userman/attest_badsig pin the user-main and bad-signature
rejects.
2026-06-10 02:01:20 +09:00
fddd167ce8 wcc/check: A7 honest-floor tagged-subset reject closes wide→narrow miscompile (wwstage)
wwstage's tagged→tagged subset-assign arm accepted all (lenient escape), silently miscompiling implicit wide→narrow: a (int|bool|str) holding a str, assigned to (int|bool), ran the int arm and read the str pointer as int. cstage rejects loud; this escape was the lone divergence.

Replace the escape with cstage's subset walk (src ⊆ dst): a src variant is covered iff typeeqast matches (structural — []u8/nested/primitives) OR both are N_TNAME with equal leaf names. The leaf bridge covers cross-module forwards where a callee's bare inline-union variant (utf8's `done`) meets the consumer's qualified `utf8.done` — raw typeeqast can't, and the escape was masking it for every forward. Mirrors casecovers (check.ww:4136). cstage untouched (align-up); spread-bearing unions keep the escape (#199b orthogonal).

Honest floor: leaf-only defers true module identity to #10 — a callee's defining module for an inline-union return is unrecoverable at check-time (the call node is gone; #211 fnretlookupmod is cgen-only). Retained divergence = cross-module same-leaf-collision over-accept (absent from bootstrap), documented at the site and filed as #10-A7 / census cat-A.

Test 989_tagged_subset_reject is table-driven with composition-discriminating rows: []u8 subset (typeeqast-only), bare↔qualified xmod forward (leaf-only), xmod named genuine-absence (reject). 352 green; w6c unchanged, w6c_ww 4b4496b8→4b316f01.
2026-06-10 00:22:22 +09:00
758d3ec8c3 wcc/check: #14 nominal typeeqast (aliassym) closes A6 cross-module variant identity (wwstage)
typeeqast's N_TNAME arm compared variant types by SURFACE string
(streq(aa.str, bb.str)), so a cross-module type referenced bare
(oserror) vs qualified (os.oserror) mis-compared unequal -> A6:
concrete->tagged silently mis-identified the variant. Resolve each
name to its canonical type sym before comparing: keep the streq
fast-path, else aliassym(c,aa)==aliassym(c,bb) (ww's existing resolver
maps bare [#53] and qualified [#51] to the same SK_TYPE sym). AST
analog of cstage type.c:278 TY_NAMED a==b / harec types.c:579
ident_equal -- NO interning. `c` threaded into 21 typeeqast sites.
B-full Layer 1: closes A6/#14; Layer 2 (A7 tagged->tagged subset
reject) stays deferred to the #199b flatten arc. Byte-id-neutral;
graduates shlex #59.15 (989 M_WWREJECT->M_ID). test/wcc/839 pins
both-stage symmetry (discriminating teeth = 989 #59.15, per 839 doc).
2026-06-09 21:27:40 +09:00
5c28c90837 wcc/cgen: loud-align static-global tagged cast-init (wwstage align to cstage)
A module-global tagged init via cast (`let g: u = true: u;`) silently
emitted tag=0; cstage louds. Route emittaggeddata through flatvariantidx
(the cstage cg_tag_for_variant twin) so an unsupported variant louds while
valid str/slice cast-inits still accept+run. test/wcc/838.
2026-06-09 19:08:12 +09:00