4db680690af2f545f2eae587915e12be525d89fc
131 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 1140a590bf |
wcc: str -> 24B {ptr,len,cap}, 3-reg ABI -- parity with []u8 (both stages)
A ww `str` becomes a 24-byte {ptr,len,cap} value, identical in layout to
[]u8 -- the enabling prerequisite for the Phase 2 `str == []u8` collapse.
Both stages, atomically:
- ty_str 16->24B; str value flows 3-reg AX/BX/CX (was 2-reg); str literals
emit cap (=len).
- str in a tagged union grows to a 32B slot, using the AX/DX/CX/R8 4th-word
path already used by 32B slice-variant unions -- str-variant is now
structurally identical.
- tuple (scalar,str) return: 4-reg AX/DX/CX/R8 + 32B receive, extending the
existing type-keyed return (no sret).
- str == []u8 for index and .ptr/.len/.cap, kind-gated where size-based
dispatch collided at 24B; cstage and wwstage mirror exactly.
- table-driven runtime coverage: test/wcc/928_str_abi_run.c.
Cannot be split (rule 10/11): a 24B str and a 16B str cannot coexist across
the two compiler stages without breaking byte-identity, so the size change
and every dependent ABI/codegen site land in one atomic commit, both stages.
Known follow-ups (zero corpus impact, tracked): str-literal global .cap
static-init; >16B struct by-value (pre-existing); tagged-union
match-scrutinee stage divergence (pre-existing).
|
|||
| d9345555c0 |
rt: bump-over-mmap allocator (rt_segmalloc + rt_free no-op)
rt_malloc was a bare mmap-per-call wrapper: every allocation, even a 32-72B AST/symbol node, consumed a page-rounded 4KB region and was never freed. w6a/w6c emit ~4 such nodes per .s line, so assembling a 145K-line file burned ~625K pages (~2.5GB); test 995's 5 concurrent self-rebuilds then OOM'd. The defect is linear and str-independent -- the str->24B codegen merely enlarged .s files past the cliff. Replace it with the no-free SUBSET of Hare's allocator (ref/hare/rt/malloc.ha): 2MiB chunk-bump (CHUNKSZ malloc.ha:24, ALIGN malloc.ha:14), oversized (>CHUNKSZ) requests direct-mmap'd. The bin/freelist/META machinery exists only to support free, which ww does not have, so it is omitted. Policy lives in rt/malloc.ww; the raw mmap primitive stays in rt/alloc.s as rt_segmalloc -- Hare's malloc/segmalloc split. rt_free becomes a documented no-op (os.free re-exports it for the public API, so the symbol must stay); rt/ensure.ww drops its now-impossible reclaim. Zero-init is preserved: the bump never reuses memory, so every byte is fresh MAP_ANONYMOUS-zeroed. w6a_ww on a 145K-line .s: 2165MB -> 28MB (~glibc parity, C w6a 24MB). .o output byte-identical; both stages emit identical asm. make test 134/134. |
|||
| 3036ba766d |
selfhost/cmd/wcc: type-key tagged variant match (#66, Phase-N step 3)
The user-ruled B-full semantic change: flip tagged-union variant matching from surface-NAME to TYPE-identity (typeeq over tinfo.params), mirroring cstage cg_variant_match (cmd/w6c/cgen.c:451). A cross-module `a.T` != `b.T` and `type linerr=!str` != str are now distinguished by the per-decl TY_NAMED pointer (Phase-N #64). ww has no type_assignable, so the untyped/loose arm keeps the str/slice shape fallback (rule-10 align-down). The 5 helpers (flatvariantidx, flatslicevariantidx, taggedvariantindex, cgtagvariantidx, cgmatch dispatch) flip; nomem propagation (NAMED-name scan, no source value) and the f64 widen arm (float-kind classification, no pattern node) are not arm-by-value discrimination and stay name/kind-keyed. The flip requires value nodes to carry nominal identity. exprtype's N_STRUCTLIT arm stamped the flattened body, so `overflow{}` (overflow=!void) got TY_VOID and missed its variant -- fixed to stamp the per-decl NAMED (mktname(lhs.str) -> tinfofornode reuses the #64 NAMED build/cache, same ptr the union variant resolved to), mirroring the N_CAST/N_IDENT arms + cstage. Returns the body node unchanged (only e.type_ rides NAMED); struct-lit layout is unaffected -- cgstructlitfill is structlookup(name)-keyed, never reads NAMED.fields. The fix now hits all `T{}` stamps, kept byte-id by the #63/#65 structural-walker peels. 931_variant_typekey_run: table-driven, both stages, /tmp-isolated. Two rows widen an alias-FIRST variant from a call (no surface name): `(linerr|str)` str-via-call -> idx 1, `(ec|i32)` i32-via-call -> idx 1. Empirically discriminating: FAILS pre-flip (wwstage falls to the leading-shape variant, exit 10; cstage exit 0) and PASSES post-flip -- locking in the capability byte-id can't reach (the corpus has no name-key/type-key-disagreeing co-variant, which is why name-keying survived). make test 134/134 (byte-id 990-997 green; 995 self-rebuild green). |
|||
| f8aac547b9 |
selfhost/cmd/wcc: extend enum fold to constexpr set (A.6.2.1a)
check.ww's N_DOT enum-fold (L1724-1810) walks the enum body to resolve each `EnumT.MEMBER` access; the pre-#22 walker only accepted N_INTLIT for a member's lhs and bailed on every richer shape via `return nil`. Wwstage compensated at codegen time through cgen.ww's enumevalmember (cgen.ww:158-227), so program semantics held; the gap was visible only in check.ww's e.type_ stamp coverage, which A.6.2.1e's post-checker assertion will land on. Lift the literal-only branch into an `enumvalfold(body, until, e, *u64) bool` helper alongside foldtointlit. The accepted set mirrors cstage cmd/wcc/check.c:185-208 (fold_int_literal) + :210-284 (eval_enum_value) and harec's enum-resolve constexpr eval at ref/harec/src/check.c:4419-4434: literal leaves (INTLIT/RUNELIT/TRUE/FALSE/NIL), unary +/-/~, binary +/-/*//% & | ^ << >>, and N_IDENT sibling backref bounded by `until` per harec's lnext forward-only-ref discipline (ref/harec/src/check.c:4436-4438). Both N_DOT call sites (inner `EnumT.MEMBER`, outer `pkg.EnumT.MEMBER` via base-resolve) delegate non-literal lhs to enumvalfold instead of bailing. Closes #7. Lands on the A.6.2.1a slot per PLAN.md / Drew's 5-lite plan; subsequent A.6.2.1b-d retire the remaining bail paths before A.6.2.1e enables the assertion. Add test/wcc/759_check_enum_fold.c — table-driven, modelled on 631_def_neg_global.c. 17 rows cover each new shape (INTLIT, RUNELIT, sibling backref, unary +/-/~, all ten binops, chained backref). Exit-code rows pin per-shape fold correctness through both stages (cgen reads the mutated N_INTLIT, so a wrong fold leaks into the constant); asm-byte-id rows pin the symmetric-emit contract between cstage's eval_enum_value and wwstage's enumvalfold. `make sizelint` clean. `make test` green 133/133 (132 pre + new 759). |
|||
| 46d303f7e1 |
test: split fast/full, parallelize 995, race-correct two-phase test/run
test/run fans out test/wcc/*.c except 950 and 990-997 across $(nproc) xargs workers, then runs the byte-id gates sequentially in phase 2. Each worker writes <prefix>.status into a mktemp results dir; the collector emits them in glob order for deterministic output. Phase 2 sequentiality sidesteps the race introduced by ww_ww writing intermediates next to every traversed source (#15) — concurrent reads of selfhost/cmd/<tool>/main.{combined.ww,s,o} would see partial bytes (#16). `make test-unit` (UNIT=1) skips phase 2 entirely for a 3.8s inner-loop check; `make test` runs the full 132 in ~8:30. test-unit goal aligns with the selfhost-bootstrap project goal: 990-997 + 950 are the toolchain-rebuild gates, fast feedback is for unit work below them. 990-995 wrap every ww/w6c/w6a/w6l/wwdump invocation in `timeout 180`; 995's hardcoded /tmp/ww_d_hello.ww is now pid-keyed. 995 forks its 5 ww_ww builds concurrently (waitpid for collection) so its solo wall drops from 3:15 to 1:32. Phase-2 split (parallel readers + sequential writers) deferred to #17, post #15. Wall: 9:59 → 8:32 (full) / 3.8s (test-unit, ~158× from baseline). |
|||
| 17765942f9 |
selfhost/cmd/wcc: delete mem.ww (γ-7, Phase 0 close)
mem.ww has 0 callers post-γ-6 — newarena/amalloc/grow/freearena/ roundup all unreferenced after the *arena cascade strip. Drop the 91-line module. Makefile: remove mem.ww from 5 dep lists (wwdump_ww, w6c_ww, w6a_ww, w6l_ww, ww_ww); drop `-I selfhost/cmd/wcc` from w6a_ww/ w6l_ww/ww_ww build invocations (wwdump_ww + w6c_ww still need it for check.ww/cgen*.ww). test/wcc/990_selfhost.c: drop 6 mem.ww entries from probe_codegen, probe_dump_diff (×2), probe_resolve, probe_dump_stable, and probe_cgen_match file lists. lib/memio/memio.ww: dynamicgrow doc comment reframed as historical context (collision source is gone, but task #9 keeps the module-prefixed name conservative against future collisions). Two dead `import mem;` lines remain in selfhost/cmd/w6a/asm.ww and selfhost/test/uses.ww; tolerated silently by ww build, swept in task #8. main.combined.ww auto-regenerated for w6a/w6c/wwdump. Verified 132/132 incl. 994_w6c_ww + 995_self_rebuild byte-identity. Phase 0 closes. |
|||
| 6696e95da4 |
selfhost/cmd/w6a: rename types.ww → opcodes.ww (#11)
The file declares `package w6a;` (not a real `types` module), so `import types;` was only the bundler file-key. The shadow against lib/types/ blocked any w6a TU that wanted to pull lib/strings or lib/bytes (both transitively reach `types.I64_MAX`); the w6a-local shadow won the source-dir-first resolver, and `types.I64_MAX` came back undefined → "ordered comparison on non-numeric". Rename the file to its actual role — opcode + register enums mirroring 6.out.h — and update the four import lines + Makefile prereq. No symbol-call sites changed: every constant already resolves unqualified inside the w6a package. Unblocks #7 astrndup workaround comments in main.ww / parse.ww; γ-2 (convert those two sites + delete astrndup export from selfhost/cmd/wcc/mem.ww) becomes mechanical. Verified 132/132 incl. 991_w6a_ww. |
|||
| d68d3c7eb4 |
lib: extract rt module from os, sweep imports
Hare puts runtime allocation in rt::, not os:: (ref/hare/rt/malloc.ha:27,
README). ww's `@symbol("rt_alloc") fn alloc(n: u64) *void;` lived at
lib/os/os.ww as a historical bootstrap shortcut; this commit relocates
it to a new lib/rt/malloc.ww and sweeps every site that depended on
`import os` for the alloc decl over to `import rt`.
This is commit 1 of 3 in the lib/rt extraction (#35):
1. (this) move decl, sweep imports — preserves shape
2. rename rt_alloc → rt_malloc (#38)
3. nullable return type + OOM-propagating builtin lowering (#39)
No rename here. Symbol stays rt_alloc, function stays `alloc`, return
stays *void. Behavior identical — same ffi resolution outcome, just
sourced from a different module file. The rt::ensure runtime helper at
selfhost/rt/ensure.ww is its own compilation unit with a local decl and
is untouched.
Side effect: every wcc cgen file used `rt` as a local *node variable
name for "return type." `import rt;` shadows the module, so each
selfhost/cmd/wcc/{check,cgenstmt,cgenexpr,cgenutil}.ww site renamed
to `rtyp`. Mechanical follow-through; only the wcc module-import was
forced to do this rename.
Verified 132/132 + 995_self_rebuild byte-identity (5 wwstage tools
round-trip byte-identical).
|
|||
| af1549d9c5 |
selfhost/cmd/wcc: cgalloc str-field store + regression test
wwstage cgalloc N_STRUCTLIT branch emitted MOVQ AX,foff(BX) for every non-float field. For a str field the cgexpr result is (AX=ptr, BX=len) and the single MOVQ clobbered BX with the heap pointer, dropping len. Mirror cmd/w6c/cgen.c:4184-4190: isstrtype branch routes through CX so BX=len survives. TY_STR only — slice/tagged/fn-pair have the same gap on both stages (task #23, parked behind Phase 2). New test/wcc/758_cgalloc_str_field.c is table-driven (6 rows), fails without the fix under wwstage with predicted exit codes. |
|||
| f80927201b |
tools/sizelint + CLAUDE.md rule 13: gate hardcoded size literals
Drew's Hare-discipline framing: "no hardcoded size literals anywhere in the compiler." This session spent 32 commits sweeping after-the-fact and STILL kept introducing new bypass sites in our own structural work (A.5's tupleelemslot/fieldslotsize most recently). The cure is a gate that catches new violations at commit time, not a deeper sweep. tools/sizelint (sh+gawk): - Always-on: `.size = NN` / `->size = NN` / `prim(...,"name",NN,...)`. - Context-gated literals (NN(u64|i64) and `return NN`) in files or fns matching size|slot|elem|field|stride|paramfield|tinfo|primtype| slotsize|letemit|tagged. - Allow-list via `// sizelint-ok: <reason>` or `/* sizelint-ok: ... */`. - Comment strip happens after allow-list match so prose mentions of 16/24 stay quiet. Makefile: `test: all sizelint $(TESTS)` so the gate runs before any binary builds. CLAUDE.md rule 13 documents the discipline + escape hatch + optional pre-commit-hook symlink. Audit caught 3 real cstage bugs (cmd/wcc/check.c resolve_type:1002, 1079, 1531 hardcoded `tt->size = 16` / `= 32` for tagged-with-ptr and tagged-with-slice payloads — should read `8 + sub.size`). Fixed inline; behavioral no-op today (pt->size=16, st->size=24, sub.size=24 match the prior literals) but the SSoT seam carries forward through #1/#34/#65. 8 SSoT-seed allow-lists added (cstage type.c ty_str/ty_slice prim factories; wwstage primtypesize/tyslicesize; lib/ww/typ.ww tystr + slice fields + their main.combined.ww mirrors). One amalloc-overalloc allow-list at lib/ww/typ.ww:273 cites pending #36 (typed amalloc). #66 filed for extending the filter once #65 routes lib/bytes + lib/getopt's sizeof(slice) / sizeof(option) literals through SSoT — naive line-pattern extension would false-positive on 22+ ELF wire- format sites in dynout.ww. 131/131 + 994 + 995 + bootstrap green with `make sizelint` exit 0. |
|||
| f8770d1502 |
selfhost/cmd/wcc/cgenutil+test: slotsize zero for void, recurse N_TBANG
Wwstage's slotsize had a catch-all `return 8` for any N_TNAME where
primsize's `> 0` guard failed. `primsize("void") == 0` (correct —
void is zero-sized per cmd/wcc/type.c:46), so void landed on the
catch-all. (void | !void) then sized as `8 (tag) + max(8, 8) = 16`
instead of `8 + 0 = 8`, and the phantom payload word made
cgwidentaggedstore spill DX for the let-init — diverging from
cstage's `8`-byte slot.
Two narrow additions per rule 10 (align wwstage DOWN to cstage):
1. N_TBANG case at the top of slotsize, recurse on .lhs. Mirrors
cstage resolve_type N_TBANG which copies the underlying type's
size unchanged.
2. `void => 0` in N_TNAME BEFORE the primsize guard, so the SSoT
matches cmd/wcc/type.c:46.
757_letbind_void_bang_void exercises three shapes — void-arm,
invalid-arm, full natural-form fromutf8 — and pins cstage/wwstage
asm byte-identity per row.
lib/strings/strings.ww fromutf8 WHY-comment drops the Bug-B
SIGSEGV caveat (measurement artifact: original test linked without
rt/start.s; RET popped argc). Keeps #19 dependency for the
eventual collapse to `utf8.validate(in)?`.
Hare matches ww's design (void zero-sized, !T inherits T's
layout); this is a pure wwstage implementation gap, not a
divergence to argue about.
|
|||
| a1d9f36d11 |
selfhost+cstage+test: graduate alias-chain unwrap to transitive (#22)
Single-peel TY_NAMED.under bottoms out at the inner alias when chain length is 2+, surfaces in two stages with different mechanisms: cstage's gates inline `if (t->kind == TY_NAMED) t = t->under` at every callsite (cgreturn, cglet sizing, cgexpr N_DOT, cgassign N_DOT, cg_sret_retsize) — graduated to a while-loop via new type_chase_named helper across 11 sites. wwstage routes all field-walks through structlookup, which registers only direct struct definitions (not aliases) — missing the alias-recurse fallback. New structlookupchain helper mirrors slotsize's N_TARRAY arm precedent; sretretsize + 4 cgenexpr.ww sites route through it. Splitting would either land cstage without unblocking wwstage's strings.tokenize wrapper shape (rule 10 byte-id regression) or land wwstage without cstage gate parity (breaking 995 self-rebuild). 756 sentinel exercises 4 rows × cstage RC + wwstage RC + byte-id = 12 fixtures; pre-fix rows 2 + 4 (slice-fields single alias, i32 double alias) fail on both RC and byte-id. The ~67 cstage / ~26 wwstage candidate sibling sites are #17-style structural-close follow-up; this commit fixes the immediate strings.tokenize-wrapper blockers. |
|||
| d5e8d699d1 |
selfhost: graduate wwstage &N_DOT[N_INDEX] to cstage canonical lean form (#21)
Latent #21 has two surface shapes — register polarity in cgun TK_AMP N_INDEX's complex-base arm, and indexbaseesz's over-broad .ptr pseudo-field gate — that share a single semantic path: &N_DOT[N_INDEX] where the inner N_DOT cannot be peeled into a plain ident base. Polarity-A (cgenexpr.ww) lifted to cstage's three-line shape; stride-B (cgenutil.ww) narrowed so the .ptr arm only fires on actual str/slice inners and falls through to the generic struct-field arm for struct N_TNAME bases. The fixes compose at the same call site (esz from indexbaseesz, then the IMULQ-or-elide gate, then complex-base emit), so splitting them into two commits would leave a half-fixed intermediate — neither half stands alone as a bisect-clean closure. Sentinel 755_amp_dot_idx exercises both shapes across 4 stride classes (slice-elem 24, struct-elem 16, u8 stride-1 elide, i64 stride-8); pre-fix 5/12 fail, post-fix 12/12 ok. Latent silent miscompile in lib/memio + lib/bufio's .ptr[i] shape also unmasked. |
|||
| f0b8c25b29 |
selfhost+cstage+test: graduate *[]T indexing to slice-element type (#20)
Cstage and wwstage share the latent: check.c's N_INDEX bespoke TY_PTR-over-TY_SLICE clause peeled the slice in `*[]T[i]` and returned the element of the element, while wwstage's elemsizeof had no N_TSLICE arm for the post-N_TPTR-peel elem and fell to the 8B catch-all. Splitting leaves one stage broken on the exact `*[]T[i]` shape the new 754 sentinel asserts byte-identical between stages (rule 11). The companion 24B per-element copy emit is a separate codegen wedge already pinned inline at cmd/w6c/cgen.c:6518; out-of-scope here and noted in the fixture header. |
|||
| 006df414aa |
selfhost+test: route convenience-wrapper N_DOT probes via fnretlookupmod (#17)
Structural close of the #4-trio convenience-wrapper audit. Session-6's
#4-trio + #11/#16 graduated individual lookup helpers (fnret/fnparams/
enum/struct/def) to same-module-first via *mod variants. The close
didn't enumerate every cgcall-context callsite — convenience wrappers
that take a *node callee and probe its return shape via bare-leaf
fnretlookup stripped the N_DOT module hint, same wedge shape as #16
(callee_variadic_param,
|
|||
| d9b0c90fbc |
selfhost+test: route callee_variadic_param N_DOT via fnparamslookupmod (#16)
Latent silent miscompile surfaced by worker-strcontains3 attempting strings.contains tagged-variadic graduation: wwstage cgcall's callee_variadic_param helper (cgenutil.ww:60-70) consumed the N_DOT callee's leaf via callee.str but routed bare fnparamslookup — bypassed the module hint at callee.lhs.str. When two modules export same-leaf fns with differing variadic shapes (e.g. strings.contains(str|rune)... + bytes.contains scalar (u8|[]u8)), the bare walk returned the wrong fn's params for arg-prep while the CALL targeted the correct module-qualified symbol — ABI mismatch. Direct sister of #34 ( |
|||
| 5ed6293330 |
selfhost+test: bump wwstage varargseq per cgcall (#8)
Latent surface from #15: cgcall variadic-gather block read seq from n.uval, which post-#15 is always 0 because scanlocals (which used to stamp it during pre-pass) was deleted. Every variadic callsite in a fn aliased to @vararg_d_0 / @vararg_sl_0. When two callsites in one fn had differing arities, the second hit #15's first-use+fail-loud guard ("localadd: @-prefix slot grew within fn") — correctly, since the slot was being asked to grow mid-fn. Fix: read seq from c.varargseq + bump in cgcall's gather branch. Mirrors cstage's mklabel("vararg_d/sl") natural seq bump. cgeninit zeroes c.varargseq per-fn (existing), so the counter is correctly per-fn scoped. cgen.ww varargseq comment refreshed — replaces stale "bumped only at emit time" misclaim with the post-#15 per-call shape + the #15 grow-on-pin discipline that surfaced the wedge. 751_vararg_seq_percall: table-driven 3 rows x 2 stages = 6 fixtures. mixed_arity_two_calls (the wedge), same_arity_two_calls (no-regress), three_arity_drift (1/2/3 mints @vararg_d_0/1/2). make test 125/125; ww2==ww3==ww4 byte-id holds via 995_self_rebuild. Surfaced by worker-strcontains2 attempting strings.contains tagged- variadic graduation — mixed-arity spec test rows triggered the wedge. Unblocks #9 + #10 (strings/bytes.contains). |
|||
| d2c64bc962 |
selfhost+cstage+test: module-scope mklabel labels (#13)
Latent silent miscompile: cstage + wwstage mklabel emitted <fn>_<prefix>_<seq> with no module qualification, so two top-level fns sharing a leaf across modules (e.g. bytes.index + strings.index) emitted colliding labels into the same combined .s. Last assembler symbol-definition won; JNE/JMP rel32 resolved to the wrong fn's body. Repro (HEAD pre-fix): two_modules_same_leaf row in 750 — mod1.locate + mod2.locate sharing match-over-(u8|[]u8)+for shape. mod1.locate's JMP misresolved into mod2's body, exit 10. Post-fix: exit 0. Latent already at HEAD: bytes.contains_match_next_1 + strings.contains_match_next_1 collide today but the corpus had no forwarding path that surfaced it. cmd/w6c/cgen.c + selfhost/cmd/wcc/cgen.ww mklabel: prepend <module>. when c->cur_mod / c.curmod non-NULL/non-empty. Plan-9 convention extension: TEXT directive already uses <module>.<fnname> (lex.c:18 a_isidcont accepts '.'); mklabel now mirrors that for local labels. Both stages symmetric per rule 10. Fragment input (no `package`) collapses to pre-fix shape — no cross-unit risk. 750_mklabel_modscoped: table-driven 3 rows x 2 stages = 6 sub-cases (two_modules_same_leaf, bytes_strings_contains, same_module_same_leaf non-regression). All required substrings asserted via grep + runtime rc check. make test 124/124; ww2==ww3==ww4 byte-id holds via 995_self_rebuild. @-prefix slot keys (cg_tagbase, cg_tagscr, @retscr) are orthogonal (local_alloc keys, not mklabel emissions). |
|||
| cbf10427df |
selfhost+test: graduate wwstage sum-typed N_INDEX call-arg to tagged ABI (#12)
pushargsrev's widening detection was N_IDENT-only — N_INDEX of a sum-typed slice element fell through to the scalar widening branch, which hardcoded the param's first-variant tag (MOVQ $1, AX) and pushed AX as a single scalar word. Callees that match-dispatched on the runtime tag always ran the static-guess arm on garbage. cstage knew the arg's type via check.c so its widen[] flag stayed off and the natural-push tagged-arg arm pushed CX/DX/AX (high → low) high → low. wwstage now mirrors via two narrow arms in pushargsrev: the aistagged guard treats N_INDEX-of-sum-typed-element matching the param slot as already-tagged, and the natural-push fallthrough emits PUSHQ CX / DX / AX for the same shape. Both arms gate on istaggedtype(indexvaluetnode(arg)) so literal- and ident-source sum args stay on their existing paths. Sentinel 749_sumtype_forward table-drives the three forward shapes (N_INDEX, N_IDENT, literal) and asserts per-stage runtime plus a byte-id window over the callsite asm. Combined.ww regen for wwdump_ww and w6c_ww follows the cgen source change; smoke.combined.ww unaffected. Tests: 123/123 pass; bootstrap fixed point holds (ww2==ww3==ww4). |
|||
| 5609d0456f |
selfhost+cstage+test: graduate frame growth to first-use+fail-loud (#15)
Subsumes #36. Drop wwstage scanlocals pre-pass; both stages converge on first-use+fail-loud frame growth, rule-10 polarity DOWN to leaner side. #36's surfaces (frame-total divergence on match-arm case-let; sibling offset divergence in variadic+iter+match-prev compositions) close naturally — running-max c.frame includes every first-use binding. selfhost/cmd/wcc: add atlocals persistent @-prefix registry surviving cgblock save/restore; add cgoutbuf/cgoutmode/cgout_enable/disable/flush for deferred prologue (emit body to buffer, finalise c.frame, then TEXT/SUBQ + flush); localadd @-prefix dedups against atlocals + fail-louds on size-grow (rule 7 — no silent truncate); cgreturn-tagged routes through @retscr (was colliding with @tagscr on arg-widen sizes); variadic gather esz uses raw primsize (rune->4) not slotsize (rune->8) — matches cstage and fixes the #36 sibling runtime miscompile in non-leaf variadic+iter+match-prev callees. cmd/w6c/cgen.c: drop the over-allocation hack ("for byte-id with wwstage scanlocals reservation") since wwstage no longer over-reserves; add fail-loud on @sretscr size-grow; @tagscr sites pass actual slot_sz instead of stale c.tagscrsz. 748_size_strategy_convergence: table-driven 4 rows x 2 stages (tag_variadic_runearm, trim_iter_match_prev, variadic_gather_rune_stride, leaf_baseline). Each exercises a #36 surface shape; 8/8 ok. Net -1565 lines. Sister latents filed as cosmetic (cs/ws frame size drift on multiple-variadic-call fns): labelseq drift + varargseq stuck at 0 — both bootstrap-byte-id safe (ww2==ww3==ww4 holds since both ww2 and ww3 are wwstage outputs). make test 122/122; ww2==ww3==ww4 byte-id holds via 995_self_rebuild. |
|||
| 7a278c1a2d |
selfhost+cstage+test: graduate deflookup mod-qualified same-module-first (#11)
cstage Sdef walk #2 N_DOT branch used c->cur_mod where n->lhs->str is the correct module hint. Sister of #4c wwstage graduation; same shape as the TY_FN branch which already uses mafn(c, n->str, n->lhs->str). cmd/w6c/cgen.c: add sdef_mod_match_hint(s, hint); walk #2 routes hint first then head-pick fallback, matching #4a/#28/#31/#34 *mod variant pattern. selfhost: add deflookuprhsmod(c, name, mod); cgdot N_DOT mod-qualified str-def value-load routes through it. Rule-10 symmetric stages: both stages now share the lhs.str polarity (was: both used cur_mod / cur-module hint). 747_def_modqual_modshadow: table-driven sentinel — gamma calls alpha.MSG with beta.MSG (same-leaf-name) at head of c.defs/sdefs. want_imm "$38," (alpha strlit len), bad_imm "$27," (beta strlit len), plus cs-vs-ws byte-id. Reverting cstage walk #2 to head-pick → fails $38 on cstage + diverges cs-vs-ws; reverting wwstage cgdot to plain deflookuprhs → fails $38 on wwstage. make test 121/121; ww2==ww3==ww4 byte-id holds. |
|||
| d985622cb1 |
selfhost+test: strlit-inline str-def value-load shape (#12)
Class A silent miscompile. wwstage cgenexpr.ww cgident's bare-ident deflookup→true branch and cgdot's module-qualified leaf branch emitted `MOVQ <mod>.<name>(SB), AX` for a `def MSG: str = "..."` value reference — a load from a SB symbol that emit_data never writes. Str defs are not laid out at SB; they live as interned strlits the .ptr/.len fold (post-#4c) and value-load consume. Cstage already strlit-inlines via Sdef walks #1 (case N_IDENT non-local) and #2 (case N_DOT untyped-lhs); wwstage now matches the (LEAQ _S_<n>(SB), MOVQ $<len>, BX) emit shape per rule 10. Surfaced by reviewer-def during #4c R3 while attempting option (B) for the cstage Sdef walks #1/#2 prefer-pass — both walks' cs-vs-ws byte-id sentinel rows could not pass while wwstage emitted the bogus DATAW shape. Filed as #12 and deferred until the wwstage emit shape was fixed. Unblocks #11 + #13 (cstage prefer-pass graduations). Latent: no in-tree corpus referenced a str def as a value (only as .ptr/.len via cgdot field-fold) prior to lib/strings c3 — same corpus-coverage-blind shape as the #4a-#4e graduations. 746_strdef_inline pins both sites with 2 rows: bare ident + mod-qualified. Each row asserts `LEAQ _S_` + `MOVQ $<strlit_len>,` inside the caller TEXT before RET, anti-checks the pre-fix `<mod>.<name>(SB)` symbol-load, and cs-vs-ws byte-id per row. 120/120 ok. ww2 == ww3 == ww4 byte-id holds. |
|||
| 049ebc14a1 |
selfhost+lib+test: route cgcall + nodeis{slice,str} N_DOT through fnretlookupmod (#34)
Class A silent miscompile, surfaced by landing strings.slice in Hare's natural delegation form `fromutf8_unsafe(utf8.slice(begin, end))` (ref/hare/strings/iter.ha:75). strings.slice itself returns str, so the inner utf8.slice (cross-module N_DOT) call's cgcall return-ABI fixup hit post-#4e fnretlookup's same-module-first walk and grabbed strings.slice's own str return — emitted a spurious `MOVQ DX, BX` after the cross-module CALL even though utf8.slice returns []u8 (selfhost/cmd/wcc/cgenexpr.ww cgcall return-ABI fixup, line 3249-3261 pre-fix). Every other consumer of cgcall:3249's str-shuffle decision sat on the same bare-leaf table and was silently miscompiling on the same collision shape pre-#34. Sibling: nodeisslice + nodeisstr N_CALL arms in selfhost/cmd/wcc/cgenutil.ww were N_IDENT-only — for a cross- module N_DOT call returning a slice or str, pushargsrev fell through to the natural 1-word PUSHQ AX, dropping the `.len` (and `.cap` for slices) of the return value when consumed as a call arg. strings.slice's body passes utf8.slice's []u8 result to fromutf8_unsafe; pre-fix wwstage pushed 1 word vs cstage's 3, breaking the receiver's slice-3-pop drain. Cstage carries no sister bug: cmd/w6c/cgen.c reads return shape from the typed `n->lhs->type` (TY_FN sig) for both str-shuffle and slice-/str-arg push counts — module-aware via the typed AST, sidestepping any bare-leaf table. Mirror of #4e's cstage-no- sister-bug note. Fix: route cgcall return-ABI fixup + nodeisslice/nodeisstr N_CALL arms through fnretlookupmod with `callee.lhs.str` (N_DOT qualifier) or `c.curmod` (N_IDENT). Mirror of #28 fnparamslookupmod / #31 fnretlookupmod N_DOT re-routing. Remaining bare-leaf fnretlookup consumer sites (~8 sites across cgenexpr/cgenutil/cgenstmt/cgendecl listed in task #34a) stay on the graduated bare-leaf path — none of the present-corpus N_DOT leaf collisions have return-shape divergence at those sites. A future stdlib port introducing a return-shape-divergent same-leaf N_DOT collision will need the *mod re-routing — filed as #34a sibling-latents. Bundled three concerns per rule 11: cgcall fix, nodeisslice/ nodeisstr fix, and strings.slice retire + sentinel. (a) alone leaves strings.slice byte-id breaking on slice-arg push count. (b) alone leaves a phantom MOVQ DX, BX on the inner cross- module CALL. (c) alone fails 995_self_rebuild without (a)+(b). The three cannot land separately bisect-cleanly; the 745 sentinel pins the primary repro (cgcall str-shuffle) which sentinel-flips on a cgcall:3257 revert. 745_fnret34_modshadow pins the fix with 1 row: caller.slice returns str (same leaf as the cross-module callee, divergent return shape); caller.run calls myutf8.slice returning []u8. Asserts CALL myutf8.slice present inside caller.run TEXT + `MOVQ DX, BX` anti-check on each stage plus cs-vs-ws byte-id. strings.slice retired in lib/strings/strings.ww: the deferral block becomes the natural Hare delegation form with two local utf8.decoder reconstructions for the iterator endpoints — ww has no anonymous-embed (parallel to the existing `move` helper). iter_slice_cases mirrors ref/hare/strings/iter.ha:110-127; sidesteps the Hare `let t = s;` iterator-copy via fresh strings.iter() to stay clear of #35's sibling latents. 119/119 ok. ww2 == ww3 == ww4 byte-id holds. |
|||
| d84704e389 |
cgen+test: copy struct >8B local-ident rhs in N_LET (#32)
let p2: T = p1; where T is a struct >8B and rhs is a local ident
silently dropped most of the copy. Cstage's N_LET fell past every
specialized rhs branch (str/tuple/tagged/structlit/call) without
matching the bare-ident case, then past the sz==8 fallback (false)
to the no-rhs zero-init (false: rhs present), emitting zero
instructions — the dest slot read fresh-stack zeros. Wwstage's
cglet fell to cgexpr+MOVQ AX which loads only the first qword
(cgident shape for struct ident), and for sz==16 slots the
str-init tail then stored a stale BX into +8. Reads after the
let saw whatever the stack held: silent partial copy.
Both stages now byte-copy src slot → dst slot per qword with
a sized tail (MOVL/MOVB) for natural sizes not 8-aligned.
Mirrors cg_widen_tagged_store's struct-ident payload copy.
744_letcopy_struct pins the four struct shapes (3×i32, i32+str,
i32+[]u8, i32+tagged) on asm-presence in both stages, cmp -s
byte-id, and runtime exit code via both drivers.
Scope: only N_IDENT rhs at the local-ident-found path. Filed as
siblings (no in-tree consumer today, bootstrap byte-id proves it):
- N_DOT / N_INDEX / N_UN(deref) struct rhs.
- Top-level (non-local) struct ident rhs.
- TY_TUPLE same-shape ident-copy bug.
Row (a) uses tri{a=11, b=22, c=33} structlit init for p1 to
isolate this fix from STATUS-3 #15/#26c (no-rhs zero-init sz=12
vs sz=16 slot-padded divergence between stages, separate task).
Row (d) runtime check uses only p2.a to isolate from match-on-
tagged-field scrutinee spill divergence (same task).
118/118 ok. ww2 == ww3 == ww4 byte-id holds.
|
|||
| 3bd9b1d56f |
cstage+test: store .len/.cap on every variadic-pack element (#16)
cstage variadic gather stored only AX (.ptr) per element; .len and .cap read stack residue at the callee. Tagged-union variadic path escaped because cg_widen_tagged_store wrote the full slot — but primitive-type variadics (str..., slice...) silently dropped the trailing fields. Selfhost only uses tagged-union variadics (formattable...) so bootstrap byte-id ww2==ww3==ww4 stayed green; the bug surfaced in worker-strings pre-flight (session 5) on the Hare-faithful concat(strs: str...) shape. Per-element store branch now mirrors selfhost/cmd/wcc/cgenexpr.ww velemstr (AX→slot+0, BX→slot+8) and velemslice (AX→slot+0, BX→slot+8, CX→slot+16). Also swap dname-before-sname allocation order in the variadic-pack frame layout to match wwstage scanlocals + localadd order (cgendecl.ww:507-516 and cgenexpr.ww:2949-2954); without the swap post-fix asm has correct stores at mismatched offsets vs wwstage. Rule-10 alignment: cstage UP to wwstage's already-correct primitive variadic path. 743_variadic_pack pins the contract: asm-presence ≥3 ptr-stores + ≥3 len-stores in caller TEXT on both stages, plus cs-vs-ws cmp -s byte-id per row. 117/117 ok. Bootstrap byte-id ww2==ww3==ww4 holds. Unblocks: lib/bytes contains-variadic, lib/strings sub variadic, and the concat/trim/contains family that c1 shipped non-variadic. |
|||
| c40edaa7f9 |
selfhost+test: gate wwstage w6c cgen on parser errors (#17)
Wwstage w6c_ww silently exited 0 on parse errors (stderr noise only). The driver ran cgen on the broken AST then only checked l.errs; ps.errs was never read, so callers downstream (`ww build`, make rules) saw no signal and proceeded with junk asm. Cstage cmd/w6c/main.c gates on `l.errs || p.errs` before cgen; mirror that, hoisting the check above cgfile so the broken AST never reaches codegen. New test 742_parse_error pins the contract on both binaries: writes a known-bad fragment to a pid-scoped /tmp file, runs cstage w6c unconditionally and wwstage w6c_ww if available, asserts both exit non-zero. Pre-fix wwstage exited 0 with junk asm; post-fix exits 1 with parse: messages preserved on stderr. 116/116 ok. ww2 == ww3 == ww4 byte-id holds (no behavior change for valid input). |
|||
| d78956e5df |
make: isolate wwstage rules in per-target build dirs to fix -j rename race (#29)
The five $(BIN)/*_ww rules all built via `cd $(BIN) && ./ww build ... ; mv $(BIN)/main $@`. The ww driver writes the linker output to <cwd>/<basename(src)> and has no -o flag, so every wwstage rule landed on out/bin/main before its mv. Under `make -j8` the writes and renames interleaved, leaving w6c_ww with a wwdump_ww (or other) payload — silent under -j because the mv usually succeeded against whatever `main` happened to exist at that instant. Symptom downstream: test runs failing with `wwdump: cannot open -o`, recovered only by falling back to -j1. Build each rule in its own $(BIN)/<target>.d/ so the shared `main` name is per-target. Switch -I and source args to $(CURDIR)/... absolute paths and invoke ww by absolute path so the driver's self_dir-relative libwwrt.a lookup still resolves to out/lib. Side files (.combined.ww/.s/.o) land next to source via the parser's source-path stem derivation, not cwd — unaffected. nocc target has similar shape but its 4 builds are sequential recipe lines inside one target so make -j can't parallelize them; not at risk today. 115/115 ok under both -j8 and -j1 from clean. ww2 == ww3 == ww4 byte-id holds. |
|||
| 4f1d7a462d |
selfhost+test: route N_DOT base through indexvaluetnode + scanlocals for N_INDEX-lhs cgassign chain (#28+#30)
Wwstage's N_INDEX-lhs cgassign dispatch chain had a triple-site N_DOT base gap (sister latents filed during #24 / #27 review): Read (#28): `obj.mat[i][k]` over a struct field mat: **u8. cgindex routes the outer N_INDEX's N_INDEX base through indexvaluetnode; the recursion bottomed out at the inner N_INDEX's N_DOT base with bt=nil. esz fell through to 8 + signed_elem to false — wwstage emitted a stray outer `MOVQ $8, CX; IMULQ CX, AX` plus `MOVQ (AX), AX` (8-byte read over a 1-byte u8) instead of cstage's bare `MOVZBQ (AX), AX`. Write (#30): `obj.arr[i] = v` over a struct field arr: [N]Tagged (e.g. (i64|str)). cgassign's N_DOT-base arm computed esz via indexbaseesz but never set elemtn, so the tagged-element store gate missed and the 24-byte tagged slot was overwritten by a single scalar MOVQ — wrong-width store + tag/payload junk in the upper 16 bytes. Cstage walks `n->lhs->type` directly via the typed AST (cmd/w6c/cgen.c idx_eff + the N_INDEX-lhs N_ASSIGN branch). Wwstage now mirrors via indexvaluetnode, which #24 ( |
|||
| 3ba19227ba |
selfhost+test: route chained N_INDEX outer element size through indexvaluetnode on write path (#27)
Wwstage cgassign's N_INDEX-lhs base-inspection (cgenexpr.ww) only
computed esz/elemtn when base.kind == N_IDENT or N_DOT. For a
chained `names[i][k] = v` (names: **u8) the outer N_INDEX has
base.kind == N_INDEX; esz fell through to the default 8 so the
outer store emitted `MOVQ AX, (BX)` into a 1-byte u8 slot (8 bytes
written — adjacent memory corrupted) plus a stray
`MOVQ $8, CX; IMULQ CX, AX` scaling on the outer index that cstage
doesn't emit. Wrong-width-store: the byte slot was written as 8
bytes and the outer offset multiplied by sizeof *u8 instead of
sizeof u8.
Cstage walks `n->lhs->type` directly via the typed AST at the
N_ASSIGN N_INDEX-lhs branch (cmd/w6c/cgen.c eff->sub->size = 1).
Wwstage now mirrors via indexvaluetnode (already graduated for
cgindex in #24, commit
|
|||
| aa8ca47943 |
selfhost+test: route chained N_INDEX outer element size through indexvaluetnode (#24)
Wwstage cgindex's base-inspection (cgenexpr.ww) only computed esz/
signed_elem when base.kind == N_IDENT or N_DOT. For a chained
`names[i][k]` (names: **u8) the outer N_INDEX has base.kind ==
N_INDEX; esz fell through to the default 8 so the outer load
emitted `MOVQ (AX), AX` over a 1-byte u8 plus a stray
`MOVQ $8, CX; IMULQ CX, AX` scaling on the outer index that cstage
doesn't emit. Wrong-width-narrow-load: the byte was read as 8 bytes
(reaching into adjacent memory) and the outer offset multiplied by
sizeof *u8 instead of sizeof u8.
Cstage walks n->lhs->type directly via the typed AST
(cmd/w6c/cgen.c idx_eff → eff->sub->size at N_INDEX). Wwstage
needed the parallel via indexvaluetnode — return the value-type
of an N_INDEX expression by stripping one element layer off base's
type, recursing for chained inner. cgindex's else-if chain now
adds the N_INDEX arm: call indexvaluetnode + elemsizeofc/
elemissignedc.
Class A wwstage cgen UNDER. Surfaced first time the codebase
exercised the **T[i][k] shape — through expanddir in
selfhost/cmd/ww/main.ww (post-#22 dir-enum, commit
|
|||
| 9e0816e199 |
cmd+selfhost+lib+test: directory-as-module enumeration in driver (#22)
Replace the cmd/ww + selfhost driver's file-walk import resolver with true directory enumeration. `import encoding.utf8;` now finds the lib/encoding/utf8/ directory and concatenates every *.ww file in it (excluding *test.ww and the driver's *.combined.ww artifacts) in byte-wise sorted order, instead of just finding the single lib/encoding/utf8/utf8.ww file. Mirrors Hare's hare/module/srcs.ha:183 _findsrcs minus tag handling. Lookup order in both stages: (1) <dir>/<dot-as-slash>/ as directory → enumerate. (2) <dir>/<dot-as-slash>.ww as file. The legacy <dir>/<name>/<name>.ww shape from #18's retained divergence is dropped per rule-9 Hare-fidelity — Hare has no foo/foo.ha fallback; a module IS the directory. Symmetric across cstage (cmd/ww/main.c via opendir+qsort+stat) and wwstage (selfhost/cmd/ww/main.ww via existing lib/os.getdents64 + os.stat — no new lib/os surface needed; the rundirtests() walker in main.ww from #18 was the model). Bootstrap ww2.s==ww3.s==ww4.s byte-identical post-change. Bundling justification (rule 11): strict-same-package validation is bundled because the failure mode is dir-enum's own (a non-dir-enum compilation unit cannot trigger mismatch across enumerated files). The natural enforcement site is the driver — the parser can't distinguish dir-enum concat from file-walk concat. Both stages peek each file's first `package <name>;` line in expand_dir / expanddir and exit(1) on mismatch with a precise error pointing at the offending file. Hare's hare/module/srcs.ha:131 has the same constraint via its README gate. Other half of #23 (strict missing-package error tightening — 63 inline-source test wrappers blocker) stays deferred per its filing. Parser side (cmd/wcc/parse.c parseuse + lib/ww/parse/decl.ww parseuse): n->str now carries only the LEAF identifier from a dotted import. With the driver translating the full dotted path to a directory walk, the checker only needs the package bareword (last component) for the N_USE → decl disambiguation walk in check.c's src_imports / decl_mod. Mirrors Hare's `use encoding::utf8;` → `utf8::name` semantics (ref/hare/hare/ast/import.ha:7). Migration: lib/ww/sym.ww drops `import typ; import ast;`; lib/ww/parse/parse.ww drops `import expr; import stmt; import decl;`; lib/ww/lex/lex.ww drops `import tok;` — all sibling imports auto-resolve via the new dir-enum when callers import the package directory. lib/strings/, lib/encoding/utf8/utf8test.ww migrate `import utf8;` → `import encoding.utf8;`. Makefile drops -I lib/encoding/utf8 stopgap from wwdump_ww + w6c_ww. Seven test wrappers (700_e2e, 966_strings_run, 970_fmt_run, 971_log_run, 972_fnmatch_run, 982_getopt_run, 990_selfhost) and 995_self_rebuild drop the -I lib/encoding/utf8 runtime stopgap. Tests: new 737_direnum C wrapper + test/wcc/data/direnum/ fixtures pin (a) cross-pkg multi-file dir-enum build at runtime (both stages must succeed) and (b) strict-same-package mismatch error (both stages must surface "differs from" + exit non-zero). 738_module_decl gains row 6 pinning the n_use->str leaf-only storage post-parser change. Retained workaround at selfhost/cmd/ww/main.ww expanddir loop: `names[i][k]` nested-deref-then-index split into `let nm: *u8 = names[i]; nm[k]` because wwstage cgen miscompiles the chained form (treats inner u8 element as 8B sizeof *u8 instead of 1B sizeof u8: extra MOVQ $8 + IMULQ on the inner index, MOVQ instead of MOVZBQ load). Inline rule-8 WHY comment cites task #24 (wwstage cgen chained-index inner element size on **T). Two-step form routes through the bare-pointer index path which both stages handle byte-identically. Class A wwstage cgen UNDER (chained-index inner element size on **T) surfaced first time the codebase exercises the **T[i][k] shape via enumeratedir() — corpus-coverage-blind landmine pattern, same family as the trio (#27/#28/#31) from STATUS-5. 112/112 ok. ww2 == ww3 == ww4 byte-id holds. |
|||
| 79d9528a00 |
toolchain+lib+test: Go-style package/import keywords (#18)
User-mandated language redesign: source files declare their own
namespace via the new `package <name>;` keyword and pull dependencies
via `import <path>;`. Both keywords use Plan-9 `.` separator (user
override on Hare's `::` — `import encoding.utf8;`). Internal token-
kind enum values TK_MODULE=86 and TK_USE=17 kept stable for 990
wwdump byte-diff symmetry; only kwtab strings + tokname spellings
rotated. Executables (selfhost/cmd/{ww,w6c,w6a,w6l,wwdump}/main.ww)
declare `package main;` per Go convention; lib/ + selfhost/cmd/wcc/
files declare their parent-dir basename.
One-commit bundle per the brief's all-at-once directive: a per-stage
split breaks bootstrap byte-id mid-rewrite (cstage with new keyword
can't parse old `module`/`use` files and vice-versa). Body documents
the bundle per rule 11.
Two retained divergences from the user's stated ask, both filed per
rule 7 / rule 8 with inline task pointers at the deferred sites:
Task #22 — Directory-as-module enumeration in the driver. User
asked: "module is combination of files in directory" (golang/hare
shape). After this commit lib/ww/{ast,sym,typ}.ww all declare
`package ww;` but are still pulled into the compilation unit via
explicit sibling `import` chains (sym.ww does `import ast;` etc.),
not via dir enumeration. The cstage scaffold for true dir
enumeration was drafted and reverted because the symmetric wwstage
port requires a ww-side opendir/readdir wrapper around getdents64
(~150-200 lines new ww). Inline citation at locate_import_in /
locatein in both stages points to task #22.
Task #23 — Parser strict missing-`package` error. The original
brief mandated: parser errors when a .ww source omits `package
<name>;` as its first non-comment item. Softened here to silent-
default because 63 test wrappers (200_parse, 100_lex, 300_check,
400_w6c, ..., the inline-source-fragment family) build ad-hoc ww
source strings that lack `package` and the strict error cascaded
into 60+ test failures. Migration is mechanical-sed but deferred
so this commit ships green. Inline citation at parsefile in both
stages points to task #23.
Node.module renamed to Node.nmod and modent.module to modent.nmod
in wwstage source — the field name `module` would collide with the
freshly-reserved TK_MODULE token. The rename is left in place as
clean separator between AST-field-name and reserved-keyword
namespaces. Cstage's n->module retained — C has no `package` or
`module` keyword.
rt/ensure.ww deliberately ships WITHOUT a package declaration so
its `export fn rt_ensure` keeps the bare linker symbol; adding
`package rt;` would mangle to `rt.rt_ensure` and break libwwrt.a
linkage. Documented at the file head.
111/111 ok (110 + new 738_module_decl sentinel). 995_self_rebuild
byte-id holds (ww2 == ww3 == ww4). All 5 frozen
selfhost/cmd/*/main.combined.ww regenerated under the new driver.
CLAUDE.md rule 5 amended with the language-layer divergence note.
|
|||
| 069548d424 |
cstage+test: graduate hidden-name mklabel sites to @-prefix SSoT (#26)
Class A frame-layout landmine pre-located; #26c queued for size-
strategy convergence per rule 10.
Cstage's tagged-scratch sites previously stamped per-call labels
via mklabel "tagbase"/"tagscr"/"argscr"/"idxscr", bumping labelseq
once per call and allocating a fresh frame slot. Wwstage routes
the same sites through localadd("@tagbase", ...) and
localadd("@tagscr", c.tagscrsz, nil) — the @-dedup shares ONE
slot per name per fn and never touches labelseq. @tagscr is
shared across THREE wwstage sites: cgenutil.ww:180 pushargsrev
struct-payload widen, cgenutil.ww:2918 cgwidentaggedstore
via_outer, cgenexpr.ww:3524 cgindex tagged-element. Worker's
initial draft introduced cg_argscr / cg_idxscr as separate
cache vars — names that don't exist in wwstage. Per rob's rule-10
amendment those collapsed to a single cg_tagscr shared across
the 3 sites, matching wwstage's @tagscr SSoT exactly.
Cstage now caches two slots matching wwstage's namespace exactly:
cg_tagbase (8B base spill, 1 site at cgwidentaggedstore via_outer)
and cg_tagscr (sized scratch shared across the 3 sites above).
Eliminates per-call labelseq bumps and per-call frame churn.
Class A byte-id drift (silent corpus-coverage-blind landmine)
closed for the 1-name shape match. Model: STATUS-3 #15 commit
|
|||
| a8d1df6090 |
selfhost+test: graduate bare-leaf fnretlookup same-module-first (#4e)
Class A silent miscompile, latent until two modules export the same
fn leaf name with diverging return-type categories (str vs scalar,
tagged vs not, tuple vs not, float vs int, struct-payload-size).
Wwstage's fnretlookup (selfhost/cmd/wcc/cgen.ww) walked c.fnrets
head-first by fname and returned the FIRST match's rtype. cgcall's
str-shuffle decision (cgenexpr.ww:3249) handed it calleename (the
bare leaf from an N_IDENT callee); a same-leaf foo registered later
(at head) returning str then mis-fired isstrtype(c, rt) for an
i64-returning callee, emitting a spurious MOVQ DX, BX after the
CALL — the SysV (AX, DX) → ww str (AX, BX) shuffle — corrupting
BX even though the callee never returned an str pair. Every other
bare-leaf consumer (taggedcallslot, callsretsize, exprfloatkind,
rhstaggedabicall, tuple destructure in cglet/cgmlet, fn-rvalue
LEAQ in cgident, cgtry{prop,unw} success-shuffle) keys on the same
fnretlookup return and was silently miscompiling under the same
collision shape.
Cstage carries no sister bug: cmd/wcc/check.c N_CALL routes
cexpr(c, n->lhs) through scope_lookup_prefer for an N_IDENT callee,
then cmd/w6c/cgen.c reads the return type from the typed
n->lhs->type's TY_FN sig — module-aware via the typed AST,
sidestepping any bare-leaf table. cs vs ws diverged on every same-
leaf fn return-category collision but no in-tree corpus declares
two same-leaf fns with diverging return categories today: 995
stays green (same surfacing pattern as #4a enumlookup post-strings,
#4b structlookup, #4c def, #4d fnparams).
Eighth and FINAL leaf of the trio graduation (after #27 aliaslookup,
#28 fnparams *mod*-variant, #31 fnret *mod*-variant, #4a enum, #4b
struct, #4c def, #4d fnparams bare-leaf). fnretlookupmod (the N_DOT
consumer at cgen.ww:1585) already exists post-#31; this commit
graduates only the BARE-LEAF entry point with a same-module-first
walk mirroring fnparamslookup's two-pass shape (#4d). 12+ bare-leaf
callsites consume the graduated lookup uniformly — none separately
re-routed to fnretlookupmod since the in-tree N_DOT collisions
(strings.next vs utf8.next; bytes.hasprefix vs strings.hasprefix
and equivalents) all have invariant return shape across the
colliding overloads. A future stdlib port introducing a return-
category-divergent same-leaf N_DOT collision will need the *mod
re-routing — file at that surfacing.
Pre-flight on 995_self_rebuild green: rob's brief warned 1-2 byte-
id surfaces possible because bare-leaf graduation could flip
MOVQ↔MOVSXD or push-count on selfhost compile paths not routed
through *lookupmod. Audit confirms the corpus has bare-leaf same-
name fn pairs (compare in lib/strings vs lib/time; next in utf8
vs strings) but downstream consumer behavior is invariant under
both shapes — cross-module calls all go through N_DOT →
fnretlookupmod, not the bare-leaf path. Zero actual surfaces.
731_fnret_bare_leaf_shadow pins the fix with 1 row: alpha defines
fn foo() i64 + fn alphacaller() i64 = { return foo(); }, beta
defines fn foo() str declared LAST in source so beta.foo prepends
to the head of c.fnrets. alphacaller's bare foo() must compile
against alpha.foo's i64 return (no str-shuffle) even with beta.foo
at the head of c.fnrets. Asserts CALL alpha.foo inside the right
TEXT sym + bad_imm MOVQ DX, BX anti-check on each stage plus
cs-vs-ws byte-id per row.
|
|||
| 862715d7df |
selfhost+test: graduate bare-leaf fnparamslookup same-module-first (#4d)
Class A silent miscompile, latent until two modules export the same fn leaf name with diverging tagged-vs-scalar param shapes. Wwstage's fnparamslookup (selfhost/cmd/wcc/cgen.ww) walked c.fnrets head-first by fname and returned the FIRST match's params. cgcall's N_IDENT branch (cgenexpr.ww:2875) handed it the bare leaf; pushargsrev's istaggedtype(c, pt) then fired against the wrong-module foo's param-type. A foo(7) call against a same-leaf (i32 | void) param re-laid the i32 arg into a 2-word tagged slot (MOVQ $7 push + MOVQ $0 tag push + 2 POPs into DI/SI) instead of the caller-intended single push (MOVQ $7 push + POPQ DI). Cstage carries no sister bug: cmd/wcc/check.c N_CALL routes cexpr(c, n->lhs) through scope_lookup_prefer for an N_IDENT callee, then cmd/w6c/cgen.c reads params from the typed n->lhs->type's TY_FN sig — module-aware via typed AST, sidestepping any bare-leaf table. cs vs ws diverged on every same-leaf fn collision but no in-tree corpus declares two same-leaf fns with diverging tagged-vs- scalar param shapes (same surfacing pattern as #4a enumlookup post-strings, #4b structlookup, #4c def): 995 stays green. Seventh leaf of the trio graduation (after #27 aliaslookup, #28 fnparams *mod*-variant, #31 fnret *mod*-variant, #4a enum, #4b struct, #4c def). fnparamslookupmod (the N_DOT consumer at cgenexpr.ww:2876) already exists post-#28; this commit graduates only the BARE-LEAF entry point with a same-module-first walk mirroring aliaslookup's two-pass shape (cgen.ww:75). Three bare- leaf callsites consume the graduated lookup uniformly: cgcall N_IDENT branch at cgenexpr.ww:2875 (load-bearing for the tagged- widening shape), cglocalsize scratch reservation at cgendecl.ww:420 (fires only on tagged-param + struct-payload arg), and callee_variadic_param at cgenutil.ww:66 (fires only on variadic callee). The latter two also accept N_DOT callees and feed the bare leaf — pre-graduation those head-picked, post-graduation they prefer same-module. NOT separately re-routed to fnparamslookupmod in this commit: the only in-tree N_DOT cross- module fn collisions (strings.next vs utf8.next; bytes.hasprefix vs strings.hasprefix and equivalents) all have invariant param shape across the colliding overloads, so widening/scratch/variadic behavior is invariant either way for sites 2 and 3 on the present corpus. A future stdlib port introducing a tagged-vs-scalar or variadic-vs-non-variadic same-leaf N_DOT collision shape will need the *mod re-routing — file at that surfacing. 732_fnparams_bare_leaf_shadow pins the fix with 1 row: alpha defines fn foo(x: i32) i32 and fn alphacaller() i32 = { return foo(7); }, beta defines fn foo(x: (i32|void)) i32 declared LAST in source so beta.foo prepends to the head of c.fnrets. alphacaller's bare foo(7) must compile against alpha.foo's i32 param (single PUSHQ/POPQ DI shape) even with beta.foo at the head of c.fnrets. Asserts the matching POPQ DI inside the right TEXT sym + bad_imm POPQ SI anti-check on each stage plus cs-vs-ws byte-id per row. |
|||
| 4bd4ed925a |
selfhost+cstage+test: graduate deflookup/deflookuprhs same-module-first (#4c)
Class A silent miscompile, latent until two modules export the same str-typed def leaf name and the .ptr/.len field-fold path consumes the wrong-module strlit address/length. Wwstage's deflookuprhs (selfhost/cmd/wcc/cgen.ww) walked c.defs head-first by dname; cgdot's .ptr/.len field-fold handed it the bare leaf from N_IDENT.str, silently inlining the wrong-module strlit. Cstage carries the same shape at cmd/w6c/cgen.c (Sdef walk #3 N_DOT field-fold): Sdef keyed by name only, head-pick on every cross-module collision. No in-tree corpus declares two same-leaf str defs, so 995_self_rebuild stayed green (same surfacing pattern as #4a enumlookup post-strings and #4b structlookup). Sixth leaf of the trio leaf-name lookup graduation (after #27 aliaslookup, #28 fnparams, #31 fnret, #4a enum, #4b struct). Same bundle precedent as #4a (which bundled wwstage enumlookup + enumlookupmod + cstage scope_lookup_prefer sister fix under one structural concern): four sister changes ship together. - defent +dmod field; collectdefs captures d.module. - wwstage deflookup two-pass walk — cosmetic (bool return is invariant under head-pick vs same-module-first), kept for structural symmetry with deflookuprhs. - wwstage deflookuprhs two-pass walk — load-bearing for the .ptr/.len field fold. - cstage Sdef +mod field; sdef_collect captures d->module raw (matches cgfn's raw cur_mod convention); new sdef_mod_match helper handles NULL-safe strcmp; cstage Sdef walk #3 N_DOT field-fold graduation (sister of wwstage deflookuprhs). Two additional cstage Sdef walks (N_IDENT bare load + N_DOT mod- qualified fallback) are DEFERRED. Both consume wwstage's cgenexpr.ww:553 path which is independently broken (str-def bare/ qualified reference emits MOVQ symname(SB) where strlit-inline is required); sentinel rows for those walks fail cs-vs-ws byte-id regardless of the cstage prefer-pass behavior. Per rule 7 the prefer-pass cannot ship without sentinels. Filed: task #11 (cstage walk #2 also needs n->lhs->str as hint source rather than cur_mod, matching #4a/#28/#31's *mod variant pattern) + task #12 (wwstage str-def symbol-load fix that unblocks both deferrals). 735_def_modshadow pins the fix with 1 row: bare-leaf .len of MSG in module alpha must fold against alpha's own def MSG (strlit length 41) even with beta's same-leaf 27-char def MSG at the head of c.defs / sdefs. Asserts the matching immediate inside the right TEXT sym + bad_imm anti-check on both stages plus byte-id between stages. |
|||
| f8d2f92316 |
selfhost+test: graduate structlookup same-module-first (#4b)
Class A silent miscompile, latent until two modules export the same struct leaf name. Wwstage's structlookup (selfhost/cmd/wcc/cgenutil.ww) walked c.structs head-first by sname, returning the FIRST match. cgdot's *struct field-load branch handed it inner.str (the bare leaf from a parsed N_TPTR whose inner is N_TNAME) and the head-pick silently emitted the wrong-module field offset — a displacement against BX that loaded whatever the colliding-module struct happened to align there. Cstage carries no sister bug: resolve_typename (cmd/wcc/check.c:65) already routes bare-leaf TY_STRUCT names through scope_lookup_prefer per c->cur_mod, and cgen.c reads fi.foff off the typed Sym — cs vs ws asm diverged on every bare- leaf collision but no in-tree corpus declares two same-leaf structs, so 995_self_rebuild stayed green (same surfacing pattern as #4a enumlookup post-strings). Fifth leaf of the trio leaf-name lookup graduation (after #27 aliaslookup, #28/#31 fnparams/fnretlookupmod, #4a enumlookup): structlookup grows a same-module-first walk before the head-walk fallback, mirroring aliaslookup's two-pass shape. No structlookupmod variant — pkg.S collapses at parse time (lib/ww/parse/parse.ww joindotted) into a single N_TNAME str routed through the existing embedded-dot smod==pkg branch, so there's no cgdot-style N_DOT consumer surface to add a *mod variant for (deferred per rob until one surfaces). No cstage symmetric fix needed for the same reason the bug doesn't surface there. 734_struct_modshadow pins the fix with 2 rows: row 1 bare-leaf in module M must fold against M's own S even with another module's same-leaf S at the head of c.structs (asserts the matching field- load disp inside the right TEXT sym + bad disp NOT-presence anti- check + byte-id between stages); row 2 pkg-qualified alpha.S from inside alpha is defensive coverage of the pre-existing embedded-dot smod==pkg branch — same path pre/post-fix (no sentinel-flip on this commit), pinned here so a future regression to the embedded-dot lookup is caught. |
|||
| 45339d2f5b |
selfhost+cstage+test: graduate enumlookup same-module-first + N_DOT enumlookupmod (#4a)
Class A silent miscompile, latent until two modules export the same enum leaf name. Wwstage's enumlookup (selfhost/cmd/wcc/cgen.ww) walked c.enums head-first by ename; cgdot handed it the bare leaf from N_DOT.lhs.str for both `Color.MEMBER` (lhs N_IDENT) and `pkg.Color.MEMBER` (lhs N_DOT) shapes, silently dropping the explicit qualifier on the second. Cstage's enum-member fold (cmd/wcc/check.c cexpr N_DOT) was carrying the same head-pick on the lhs-ident lookup — pre-fix the mismatch surfaced as a "not assignable to <same-leaf>" checker error rather than a silent wrong-constant because resolve_typename for the fn return spec already used scope_lookup_prefer correctly, so the rhs's wrong- module-Color clashed with the return type's right-module-Color. No in-tree corpus currently declares two same-leaf enums, so 995_self_rebuild stayed green and the latent miscompile only surfaces once a stdlib port introduces the collision (same shape as #27 surfacing when lib/strings dragged utf8's invalid alias into the chain alongside strconv's invalid). Fourth leaf of the trio leaf-name lookup graduation (after #27 aliaslookup, #28 fnparamslookupmod, #31 fnretlookupmod): wwstage enumlookup grows a same-module-first walk before the head-walk fallback, mirroring aliaslookup's two-pass shape (cgen.ww:75). The N_DOT consumer surface — `pkg.Enum.MEMBER`, already used in-corpus by os.flag.RDONLY, temp.mode.RDWR, os.whence.SET etc. — routes through a new enumlookupmod variant with the explicit N_DOT.lhs.lhs.str as the mod qualifier (mirror of fnret/ fnparamslookupmod). Cstage's check.c cexpr N_DOT lhs lookup graduates from scope_lookup to scope_lookup_prefer to align symmetrically (rule 10: both stages pick same-module-first on the bare-leaf shape). 733_enum_modshadow pins both surfaces with 3 rows: row 1 bare-leaf in module M must fold against M's own Color even with another module's same-leaf Color at the head of c.enums; row 2 same-module `mod.Color.MEMBER` from inside that mod pins the API surface; row 3 cross-module `othermod.Color.MEMBER` from a third module with no local Color sentinel-flips the cgdot etmod tracking + enumlookupmod path independently of row 1's same-module-first fallback. Asserts the matching \$N, immediate inside the right TEXT sym + bad_imm NOT-presence anti-check on both stages plus byte-id between stages per row. |
|||
| 0d96196f90 |
cstage+test: walk fields for sret callee struct-copy width (#33)
Callee N_IDENT word-copy loop was driven off slot-padded rt->size;
trailing narrow field (e.g. bool@32 in 33B/40B struct) widened to
MOVQ at the loop tail, diverging from wwstage's natural-size MOVB.
Class A cgen divergence. 9th unmask of session 5, corpus-coverage-
blind on the cstage side — no in-tree lib struct had a narrow
(bool/u8/i8/i16) trailing field until lib/strings.iterator landed
`reverse: bool` per Hare's ref/hare/strings/iter.ha:8.
Pre-fix: cstage cgreturn's sret arm (cgen.c) used `int sz =
(int)rt->size` for its chained `while (k+8<=sz)` MOVQ-MOVL-MOVW-MOVB
copy loop. rt->size is slot-padded (8-rounded for downstream
frame alloc), e.g. 40B for {i32, []u8, bool}. Loop emitted MOVQ
at offset 32 covering the 1-byte bool tail plus 7 bytes of
padding into the caller's sret slot — clobbering the next 7 bytes
of caller frame on read-side. Wwstage's mirror loop drives off
sretretsize → structnaturalsize → max(foff+fsz) = 33B, so it
correctly stops at offset 32 and emits MOVB.
Polarity catalog: cstage OVER-WIDE — slot-padded size driving the
field-copy width. Convergence cstage → wwstage's structnaturalsize
discipline (rule 10 inverse: leaner-correct side wins).
Fix: compute natural size locally in the N_IDENT/N_STRUCTLIT sret
arm via walk over `rt->fields` (max foff+fsz), mirroring wwstage's
`structnaturalsize` (cgenutil.ww:1377). Frame allocation and
`cg_sret_retsize` (used for the caller-side @sretscr slot)
intentionally keep using rt->size — caller scratch sizing is
separate from callee per-field copy width.
Surfaced by lib/strings commit-2 (#30) iterator pre-flight when
the Hare-faithful `reverse: bool` field tripped the cstage-only
mis-width on 993/995 byte-id (strconv → strings → wwdump_ww +
w6c_ww). lib/strings c2 was stashed to land cleanly after this fix.
Note (out-of-scope): the chained MOV loop has no MOVW arm in
either stage, so a trailing i16 emits 2× MOVB at consecutive
offsets. Worth a future cleanup; both stages agree today.
Tests:
- 730_sret_narrow_field pins narrow-MOV store + load width and
no-MOVQ@trailing-offset assertions for bool / u8 / i16 / i32
trailing fields in a 33B-natural struct. 4 rows × 5 sentinels
= 20 fixtures. cmp -s cstage vs wwstage byte-id per row.
- 930_sret_narrow_field_run runtime-pins bool true/false, u8
high-bit, i16 negative, i32 negative, mixed (bool+i32+i64 after
slice) — 6 scenarios × 2 stages = 12 rows.
104/104 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
|
|||
| b787641ef9 |
selfhost+test: route N_DOT match scrutinee through fnretlookupmod (#31)
Wwstage matchscrutt now mirrors cstage's typed-AST scrutinee-type lookup for module-qualified mod.fn(...) callees, restoring per-arm tag dispatch on cross-module shadowed-name 4-arm matches. Class A runtime miscompile, silent across collectfnrets shadowing — was the 8th unmask of session 5. Pre-fix: wwstage's matchscrutt N_DOT branch (cgenutil.ww:2061) called `fnretlookup(c, callee.str)` — name-only resolution. collectfnrets prepends to c.fnrets, so when a caller fn (e.g. lib/strings's `next`) shadows a callee fn-name (utf8's `next`), the prepend chain has the caller's narrower tagged return at the head. matchscrutt then resolved the scrutinee type to the WRONG tagged shape, and variantindex lookups for arms past the shadowing caller's variant count returned -1 → want=0 → match-arm `CMPQ $0, AX` for arms 2 and 3 on a (rune | done | more | invalid) probe. Effect: arms 2/3 silently unreachable even when the runtime tag matched, falling through to default. Cstage gets the scrutinee type via the checker-set callee type on the N_DOT node, so picks the correct utf8.next return shape. Polarity catalog: wwstage UNDER — fnretlookup missing module- preferring discipline. **Third leaf in the same trio**: #27 (aliaslookupmod), #28 (fnparamslookupmod), #31 (fnretlookupmod). Pattern is recurring; full graduation of all leaf-name lookups to same-module-first is a candidate for STATUS-3 task #1 variant-widen consolidation refactor (deferred to next session opener per rob). Fix: new fnretlookupmod helper in cgen.ww (same-module-first walk, fallback to existing first-match — cell-for-cell mirror of fnparamslookupmod from #28). matchscrutt N_DOT branch extracts `cmod` from callee.lhs.str and routes through the helper. Other 13 fnretlookup callsites untouched per #28's "fix only what has a real consumer" discipline. fnret.fmod field + collectfnrets f.fmod assignment already landed in #28. Surfaced by lib/strings commit-2 pre-flight: probe iter+next shape calls utf8.next; the probe's own `fn next` shadows utf8.next at the c.fnrets head. Bootstrap-stable because no selfhost-corpus path shadows a fn name across modules with a wider tagged return on the shadowed side; lib/strings.iter pulling utf8.next under wwstage was the first exerciser. Filed follow-up (NOT in scope here): #32 wwstage runtime stomp on utf8.next via *iterator caller — separate Class A surfaced by 929 direct utf8.next regression row design. #31's fix is correct in isolation; #32 blocks lib/strings commit 2 (#30). Tests: - 728_match_4arm_cross_module pins distinct CMPQ $K, AX tags in TEXT b.next via bitmap covering [0..arms), robust to arm ordering. Three cross-module shadowed-name shapes × cmp -s byte-id. Sentinel-flip-verified: revert fnretlookupmod route → 3/6 wwstage fixtures fail "arm K repeats tag $0 (collapse)". - 929_match_4arm_cross_module_run runtime-pins 6 rows × 2 stages per-arm exit-code shape: 3/4/5/6-arm boundary, mixed (i32|str|rune|u8), reverse arm-order in match source. Confirms bug follows fnretlookup-resolved type, not match source order. 102/102 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id). |
|||
| a651883c14 |
lib/strings+test: Hare port (dup/concat/trim/index/contains/has{pre,suf}fix/compare/utf8)
Hare-faithful index/predicate family per ref/hare/strings/{dup,
concat,trim,index,suffix,contains,compare,utf8}.ha. Non-variadic
subset (concat 2-arg, trim single-rune, contains single-needle)
pending task #16 — cstage variadic-pack drops .len on multi-field
element types; ship the Hare-faithful single-arg shape now, file
the variadic upgrade as follow-up. `sub` follow-up filed as #29
(commit 2 with iterator + utf8.chars relocation).
Surface: dup, concat, trim/trimprefix/trimsuffix (single rune),
hasprefix, hassuffix (both with (str|rune) sum needle),
byteindex, rbyteindex (both with (str|rune) sum needle),
contains (single str needle), compare, toutf8, fromutf8_unsafe,
runebytes helper. (str|rune) match arms route the rune via
utf8.encoderune into a [4]u8 scratch then bytes.index/rindex —
drew-devault's directive for clean Hare-fidelity over invented
ASCII-only rune-byte arms.
byteindex / rbyteindex rune-arm semantic correction —
corpus-coverage-blind unmask. Pre-existing impl scanned for
`r: u8` (broken for all rune values >0x7F since strings.ww first
landed; no caller exercised it). Replaced with utf8.encoderune-
based scan via runebytes helper. Severity-marker: silent
wrong-result for any non-ASCII rune needle, masked by zero
in-tree callers until lib/strings + utf8 chain pulled the shape
in.
Build-system propagation: lib/strings depends transitively on
lib/encoding/utf8 (via byteindex's rune arm). cmd/ww driver's
locate_import_in (cmd/ww/main.c:85) walks `<dir>/<name>.ww` and
`<dir>/<name>/<name>.ww` only — `use utf8;` doesn't find
lib/encoding/utf8/utf8.ww without explicit `-I lib/encoding/utf8`.
Propagated through 5 wwstage-tool Makefile targets + 7 test
wrappers + test/wcc/995_self_rebuild.c sprintf lines. Task #17
filed for the principled resolver fix (subdir walk vs Hare's
qualified `use encoding::utf8;` notation).
This commit chain (#15 strings) surfaced 7 cgen bugs during
landing: #16 cstage variadic-pack, #17 resolver nested-paths,
#27 aliaslookup leaf-collision, #22 zero-init !void/void-alias
let-decl, #15-cstage retscr SSoT name, #24 composite CALL return
as composite arg, #28 N_DOT calleeparams. All blocking ones
fixed (#16/#17 deferred-with-stopgap, others fixed in their
respective commits). Pre-flight + stop-and-surface discipline
held throughout — no workarounds shipped in stdlib.
Tests:
- 966_strings_run drives lib/strings/stringstest.ww via ww run.
15 @test fns: dup (alloc, multibyte), concat (empty, lopsided,
multibyte), trim/ltrim/rtrim incl. 4-byte rune U+1D68A,
hasprefix/hassuffix with (str|rune) incl. multibyte,
byteindex/rbyteindex both arms 1/2/3/4-byte rune coverage,
compare. Cited from ref/hare/strings/+test.ha where vectors
apply.
100/100 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
|
|||
| c34abf47b1 |
selfhost+test: route N_DOT callee through fnparamslookupmod (#28)
Wwstage cgcall now mirrors cstage's typed-AST callee-params lookup for module-qualified mod.fn(...) calls, restoring tagged- union widening on cross-module slice args. Class A runtime miscompile — masked from 995_self_rebuild because wwstage tools don't call bytes.index directly; surfaced by lib/strings landing dragging utf8 + bytes into the wwstage-tool dep chain via strings.byteindex's `bytes.X(toutf8(...), n)` call sites. Pre-fix: wwstage's cgcall (cgenexpr.ww) looked up calleeparams only when callee.kind == N_IDENT. For N_DOT callees (the module-qualified mod.fn() form), calleeparams stayed nil → pushargsrev's widening detection gated on param != nil never fired → wwstage fell through to the N_IDENT-slice fast path pushing only 3 slot words (cap, len, ptr) WITHOUT the variant tag. Receiving fn's `match (needle)` then dispatched on (needle.ptr in CX) instead of needle.tag, with R8/R9 carrying .len/.cap instead of .ptr/.len. Wrong arm + wrong payload. Cstage handles N_DOT natively via the checker-set type on n->lhs->type (cmd/w6c/cgen.c:4156-4165), so cg_widen_tagged_push slice path pushes 4 words including tag. Polarity catalog: wwstage UNDER — calleeparams lookup missing N_DOT dispatch arm. Sister to #19 (N_TSLICE variantindex arm), #21 (N_CALL pushargsrev arm), #24 (N_CALL nodeisslice arm), #27 (aliaslookup same-mod-first). The pattern: wwstage dispatchers keep missing arms cstage has natively via typed-AST resolution. Convergence wwstage → cstage (rule 10's spirit overrides letter when correctness is at stake — Path 2 of aligning cstage DOWN would create a runtime miscompile in both stages). Fix: cgcall N_DOT branch pulls module from callee.lhs.str and function name from callee.str, calls new fnparamslookupmod helper. Helper does same-module-first walk then existing first-match fallback (mirrors #27's aliaslookup fix shape). New fnret.fmod field carries module identity; collectfnrets sets f.fmod = d.module at registration. Module-qualified pkg.fn path unchanged. Tests: - 727_modcall_widen_slice pins MOVQ $1 + PUSHQ AX (tag-synth) before the receiving fn's CALL on canonical mod.fn(slice, ...) shape with the callee param widened to a tagged union. Three assertions per row: cstage tag-synth presence, wwstage tag-synth presence, cstage↔wwstage cmp -s byte-id. Sentinel- flip-verified: comment out fnparamslookupmod call → wwstage tag-synth absent + cmp diverges. 99/99 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id). |
|||
| 85af051cd1 |
selfhost+test: prefer same-module aliaslookup match (#27)
wwstage UNDER — aliaslookup's leaf-only first-match walk let a
cross-module leaf collision (`type invalid = !i32;` ahead of
`type invalid = !void;` in c.aliases) shadow module M's own
alias. Silent-correct-by-zero-init: the let-decl prologue zeroed
the slot 8B-wide (typeis8byteprimitive's void-aliased path,
post-#22), so MOVSXD on the misresolved !i32 produced the right
value while diverging from cstage's MOVQ — bootstrap byte-id
held until any caller bumped the alias-chain ordering. Mirrors
cstage scope_lookup_prefer (cmd/wcc/check.c:65); module-
qualified pkg.alias path unchanged.
Polarity catalog: wwstage UNDER — aliaslookup missing module-
preferring scope discipline. Convergence wwstage → cstage's
resolver pattern (rule 10; cstage already correct via
scope_lookup_prefer). Two-pass walk: same-module first, then
existing first-match fallback. Sea-of-stars shape preserved.
Surfaced by lib/strings landing: utf8's `type invalid = !void;`
and strconv's `type invalid = !i32;` registered in the same flat
c.aliases under one combined.ww, with strconv's later-registered
entry sitting at the head of the chain. utf8.next/decode's
`return e;` (e: invalid) packed via MOVSXD instead of MOVQ. Four
sites in main.s, contributing to 993/995 byte-id divergence in
the wwstage rebuild path.
Tests:
- 726_alias_leaf_collision row 1 pins MOVQ post-zero-init on
both stages and cstage↔wwstage cmp -s byte-id for the
`(invalid:!void via beta)` shape with `alpha.invalid = !i32`
seeded ahead in c.aliases. Sentinel-flip-verified: revert →
wwstage emits MOVSXD post-zero-init + cmp diverges.
- Row 2 (i32_local_read_keeps_movsxd) gates against future
symptom-fix attempts: legitimate `let i: i32; return (i:i64);`
must still emit MOVSXD on both stages (>=2 narrow signed loads
in the promote fn TEXT). Independent of the aliaslookup fix.
98/98 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
|
|||
| 987391bd12 |
cstage+test: route cgreturn @retscr through fixed-name SSoT (#15)
cgen.c's two ≤24B / tagged-widen return scratch allocations called
mklabel(c, "retscr"), bumping labelseq once per function with a
struct or tagged return. Wwstage's mirror uses the fixed `@retscr`
name through `c.retscroff` SSoT (post-#14
|
|||
| c893c4bc37 |
selfhost+test: zero-init !void / void-alias let-decl slots (#22)
Wwstage's cglet skipped MOVQ $0 for sz=8 slots that cstage zero-inits unconditionally — !void error types (utf8.invalid) and void-alias variant tags (utf8.done / utf8.more) drifted byte-id post-utf8 + lib/strings; promotes STATUS-3 #22 from latent to bootstrap-blocking. Cstage emits MOVQ $0, -K(BP) in the prologue for any sz=8 let-decl slot via the natural type-fallthrough; wwstage's `typeis8byteprimitive` helper returned false on N_TBANG and on N_TNAME pointing to an alias that resolves to void, so the gate never fired and the slot stayed uninitialised. Polarity catalog: wwstage UNDER — `typeis8byteprimitive` classifier too narrow at N_TBANG and void-alias N_TNAME. Convergence wwstage → cstage's natural sz=8 fallthrough (rule 10). N_TBANG arm recurses on inner type (cmd/wcc/check.c:290 resolve_type copies T's kind, only sets iserror — so !T is 8B iff T is 8B); void-alias N_TNAME resolves through alias- recursion the same way. Tests: - 724_letdecl_zeroinit pins MOVQ $0, -K(BP) presence between function prologue and body on canonical !void and void- alias rows, plus cmp -s byte-id between stages per row. Filed follow-up (NOT in scope here): #25 wwstage 8B struct without rhs still under-emits (structlookup != nil short- circuits the classifier). Same family as STATUS-4 #36 primsize composite-aware sizing. No in-tree consumer. 96/96 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id). |
|||
| 0e2c6cd893 |
selfhost+test: route composite CALL return through nodeisslice (#24)
Wwstage call-arg-emit recognized slice args only when source was IDENT/SLICE/CAST/DOT. For N_CALL returning []T the natural-push fallthrough emitted one PUSHQ AX (lost .len/.cap) and cgcall's pop-count under-drained by 2 words — corrupting R8/R9 and every subsequent arg. Class A runtime miscompile with stack misalignment and 3-POPs-of-garbage at the receiving call. Sister to #21 (tagged-CALL arg) but for plain []u8 slice, not tagged-variant — wwstage's pushargsrev grew the tagged-CALL arm at #21 and never grew the plain-composite arm. Surfaced by lib/strings landing's `bytes.X(toutf8(in), p)` call sites: 995_self_rebuild's wwstage rebuild tripped on byte-id divergence at w6c_ww + wwdump_ww emit. 967_bytes_run was green because `ww run` exercises the cstage path. Corpus-coverage-blind on the wwstage side until lib/strings pulled the chain through wwstage compilation. Fix is minimal: `nodeisslice` (selfhost/cmd/wcc/cgenutil.ww) gains an N_CALL arm structurally identical to the existing N_CALL arm in `nodeisstr` (only swap: isslicetype for isstrtype). The downstream natural-push slice path (PUSHQ CX/BX/AX, extra=2 pop-count) was already correct — it just needed the N_CALL-of-slice-return shape to be recognized as a slice. pushargsrev and cgcall untouched. Polarity catalog: wwstage UNDER — missing N_CALL arm in slice shape recognition. Convergence wwstage → cstage per rule 10 (cstage reads typed-AST `type_isslice` natively). Tests: - 723_composite_call_arg pins the 3-PUSH order (CX, BX, AX) between `CALL view` and next CALL on canonical `f(g())` shape, plus cstage vs wwstage cmp -s byte-id. - 927_composite_call_arg_run runtime-pins 7 rows × 2 stages = 14 fixtures: canonical, slice-CALL + let-slice (hasprefix shape), two composite-CALL args (arg-shift collision), middle-argpos, nested composite-in-composite, slice + scalar pop-count mix, tagged-CALL regression alongside (confirms #21 still holds). 95/95 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id). |
|||
| 53c9e46c21 |
selfhost+test: route N_TSLICE variant through shape-aware index helper (#19)
Class A wwstage cgen miscompile, silent until wwstage path engaged.
Pre-fix wwstage's name-keyed flatvariantidx returned -1 for `[]T`
variants (pat.str empty on N_TSLICE), so cgmatch and
cgtagvariantidx collapsed every `(scalar | []T)` arm to tag 0.
Internally consistent within wwstage; cstage's structural
`type_eq` (cmd/w6c/cgen.c:466 cg_tag_for_variant) matched
correctly. Bootstrap stayed green because no selfhost-corpus path
exercises `(scalar | []T)` until lib/bytes / lib/strings landing
pulls bytes.index through wwstage compilation — 967_bytes_run
uses `ww run` (cstage only), so the wwstage path was never
exercised.
Polarity catalog entry: wwstage UNDER (missing N_TSLICE dispatch
arm in variantindex lookup), not REVERSE — worker's deeper read
corrected rob's initial diagnosis. cstage's structural type-eq is
the leaner-correct side; wwstage converges to it per rule 10.
Fix: new `flatslicevariantidx` helper in cgenutil.ww keyed on
N_TSLICE shape walking pat.lhs against vt.lhs alongside the existing
name-keyed flatvariantidx; extend `taggedvariantindex` shape-fallback
with a `wantslice == ivisslice` axis alongside the existing str
axis; route N_TSLICE in cgenexpr.ww's cgtagvariantidx (is/as)
and cgmatch (case) through the helper. No edits to cgenmatch's
dispatch codegen (CMPQ/JNE/spill) — that's symptom, the bug is
in the variantindex lookup.
Surfaced the 7th corpus-coverage-blind unmask of session 5 (sister
shape to STATUS-4 #11 / #14 / #21 wwstage UNDER family). Latent
within lib/bytes (
|
|||
| a6abac22d8 |
lib/bytes+test: Hare port (equal / index / rindex / contains / has{prefix,suffix} / reverse / zero)
Mirrors ref/hare/bytes/{equal,index,contains,reverse,zero}.ha for
the in-tree subset used by lib/encoding, lib/bufio, lib/memio;
converts 4 hextest sites from local beq to bytes.equal and drops
the now-dead beq in utf8test.
Surface:
- equal(a, b: []u8) bool
- index(s: []u8, needle: (u8 | []u8)) (i32 | void)
- rindex(s: []u8, needle: (u8 | []u8)) (i32 | void)
- contains(s: []u8, needle: (u8 | []u8)) bool
- hasprefix(s, pre: []u8) bool
- hassuffix(s, suf: []u8) bool
- reverse(s: []u8) void (already present, citation added)
- zero(s: []u8) void (already present, citation added)
Two documented Hare-fidelity gaps (cited in lib/bytes/bytes.ww
header, no in-tree caller demands them yet):
- index_slice / rindex_slice use naive O(n·m). Hare specialises
2/3/4-byte needles + falls back to Crochemore-Perrin two-way
(ref/hare/bytes/two_way.ha). Correctness equivalent.
- contains takes a single needle. Hare uses variadic
needle: (u8 | []u8)... (ref/hare/bytes/contains.ha:5).
Tests: 967_bytes_run drives lib/bytes/bytestest.ww via ww run.
Eight @test fns × table-driven row sets: equal (5), index_byte
(5), index_slice (10), rindex_byte (3), rindex_slice (3),
contains (4), hasprefix (6 verbatim from contains.ha:25),
hassuffix (6 verbatim from contains.ha:40).
Call-site conversions in the same commit (the conversions are
the proof the API is wired): lib/encoding/hex/hextest.ww drops
the local beq helper and 4 callers switch to bytes.equal;
lib/encoding/utf8/utf8test.ww drops the dead beq helper.
91/91 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
|
|||
| 793734c1e0 |
lib/encoding/utf8+test: Hare port (decoder / next / encoderune / runesz / utf8sz / validate)
Hoehrmann DFA from ref/hare/encoding/utf8/decodetable.ha flattened to 1D [2048]i8 (task #20: 2D-array jagged cgen still pending); encoderune takes a caller buffer matching lib/encoding/hex.encode; done/more/invalid all spelled as plain void aliases per lib/io's eof precedent. Surface ports decoder + decode + next + encoderune + runesz + utf8sz + validate from ref/hare/encoding/utf8/{types, decode,encode,rune}.ha. next() polarity rewritten from Hare's `(state-1):uint >> 31` to an explicit `if state == 0` branch because ww's uint is 64-bit (cmd/wcc/type.c:58); same effect, no hidden 32-bit assumption. Deferred (no in-tree callers): prev, slice, position, remaining, appendrune, strencode, strdecode. String iteration (chars/ newchars/nextchar in the session-4 draft) dropped per Hare discipline — belongs in lib/strings::iterator, not encoding/utf8. Tests: - 968_utf8_run drives lib/encoding/utf8/utf8test.ww via ww run. 21 @test fns: boundaries (ASCII, 2-byte, 3-byte, 4-byte encode/decode), surrogate/overlong/out-of-range/bad-continuation reject, max-in-range (U+10FFFF) accept, truncated→more, done@EOI, validate empty/mixed/malformed, encode/decode roundtrip. Two rows ported from ref/hare/encoding/utf8/decode.ha @test that were missing in the session-4 draft: bad-continuation [0xC2,0xFF]→invalid and max-in-range [0xF4,0x8F,0xBF,0xBF]→ U+10FFFF. - 9xx stdlib runtime slot range extended from 970-989 to 960-989 to accommodate utf8 at 968 (970-989 block was full). 90/90 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id). |
|||
| 7e0c280691 |
cstage+selfhost+test: System V AMD64 sret discipline for >24B struct return (#23)
Class B shared miscompile pre-fix: cstage skipped the CALL emit at the
receive site (frame collapsed, exit 11); wwstage emitted CALL but
truncated 32B return to AX only (slice payload garbage, segfault on
g.b[0]). Both stages now lower plain TY_STRUCT > 24B through the SysV
sret discipline: caller pre-allocates dest, passes &dest in RDI as a
hidden first-arg (user args shift to SI/DX/CX/R8/R9/+stack), callee
saves RDI to @sretarg at the prologue and writes through it, returns
RDI in RAX. Surfaced by lib/encoding/utf8 pre-flight when the
Hoehrmann decoder (32B) hit 698_cgreturn_struct.c's OUT-OF-SCOPE
marker.
Scope: plain TY_STRUCT > 24B only — tagged unions, tuples, str, slice
keep their existing register-return ABIs. `return f()` forwarding
from a sret callee is fail-loud-not-wired (compile-time error in
both stages, follow-up filed); the workaround `let r = f(); return
r;` is wired and byte-identical. Discard-context calls (`f();` of an
sret-returning function) share a per-fn single-slot @sretscr;
consecutive discards reuse the same slot.
698_cgreturn_struct.c's OUT-OF-SCOPE marker retired in the same
commit; three positive rows (32B quad, 32B decoder, 40B five) now
assert the sret discipline across both stages via byte-id diff.
Tests:
- 721_sret_struct_return pins three asm-presence sentinels per
row: (a) LEAQ -K(BP), DI immediately before CALL at the receive
site, (b) MOVQ -K(BP), AX before RET in the callee (sret return-
the-pointer), (c) negative-assert no MOVQ AX, -K(BP) capture for
return type >8B. Three rows × both stages × cmp -s byte-id.
- 925_sret_struct_return_run runtime-pins 7 rows × 2 stages
including the collision row (25B+ struct BOTH returned AND passed
by-value as arg — catches arg-shift, sister site to #11), nested
struct payload, slice payload, reassign-receive, N_IDENT return
rhs.
89/89 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
|