Commit Graph

372 Commits

Author SHA1 Message Date
0e1ab38586 selfhost/cmd/wcc: stamp e.type_ for N_ARRLIT (A.6.1.7)
Port head-only of cstage cmd/wcc/check.c:1198-1211 to exprtype's
new N_ARRLIT arm:

  - Walk e.list, skip the N_FIELD repeat marker (lib/ww/parse/
    expr.ww:100-107).
  - First non-skipped element via recursive exprtype → elt.
  - Count non-skipped elements.
  - Empty list → mktname("i32") per cstage L1209.
  - Synthesize N_TARRAY{lhs=elt, rhs=INTLIT{uval=count}}, stamp
    tinfofornode, return.

One documented divergence from cstage: the inline type_default
lift (cstage L1206 — untyped_int → i32 etc) is skipped. Wwstage
uniformly returns the AST-level untyped name and defers defaulting
to the assignability sink; the N_INTLIT arm (check.ww:1388-1392)
and alloc-value-form (check.ww:1509) follow the same shape, so
defaulting here would be the outlier. No cgen consumer reads
e.type_ on N_ARRLIT today (verified via grep of cgen*.ww); A.6.3
will move the default to the read site or hoist a typedefault
helper — out of α scope.

α scope per Drew (ref/hare/hare/ast/expr.ha:215-218 — Hare's
array_literal is the uniform `{expand, values}` shape, no
named/anonymous split, so head-only is the natural cadence). No
hint plumbing, no unify check on mixed-type elements (cstage uses
first-element-wins).

Phase 1 A.6 step 7 of ~8 — closes the A.6.1 per-kind stamp series.
A.6.2 next: post-checker assertion that every expression node has
type_ set + fail-suite gap sweep.

Verified 132/132 incl. 995_self_rebuild byte-identity.
2026-05-21 19:53:31 +09:00
5f19015e16 selfhost/cmd/wcc: stamp e.type_ for N_STRUCTLIT (A.6.1.6)
Port head-only of cstage cmd/wcc/check.c:1161-1197 to exprtype's
new N_STRUCTLIT arm:

  - N_IDENT lhs: scopelookupprefer → SK_TYPE sym; stamp
    tinfofornode(sym.decl.lhs), return that body. Mirrors cstage
    L1166-1173.
  - Synthetic type-expr lhs (e.g. `(*T){...}`): stamp
    tinfofornode(e.lhs), return e.lhs directly. Mirrors cstage
    L1174-1176.
  - e.lhs == nil: bail (future Hare anonymous-lit shape ww
    doesn't parse yet — lib/ww/parse/expr.ww:147-148 always
    plants the TYPE_IDENT).

Field-level walk (cstage L1178-1194) stays parked behind #23 /
Phase 2; field-value exprs still get their own n.type_ via the
post-order dispatch at L460-489 (N_STRUCTLIT is in the kind
list since A.6.0).

One documented divergence from cstage: lenient on missing-
struct-type / non-SK_TYPE sym (cstage L1170 errors; wwstage
falls through to nil under the established scruttype L656 /
A.6.1.5b N_DOT struct-miss policy).

α scope per Drew (ref/hare/hare/ast/expr.ha:229-237 — Hare's
struct_literal AST distinguishes named/anonymous alias; ww
only parses the named form, so the hint param plumbed in A.6.0
stays unused for this arm). β/γ (hint plumbing into clet/
cassign; A.6.1.7 preempt) deferred per rule 11.

Phase 1 A.6 step 6 of ~8 — Hare struct_literal surface closed;
N_ARRLIT (A.6.1.7) is the genuine hint-consumer next.

Verified 132/132 incl. 995_self_rebuild byte-identity.
2026-05-21 19:28:45 +09:00
aa73e73013 selfhost/cmd/wcc: stamp N_DOT struct/pseudo/tuple in exprtype (A.6.1.5b)
Port cstage cmd/wcc/check.c:833-866 stamp cases to exprtype's
N_DOT arm. Three sub-cases append to the A.6.1.5a basetn-walk
block, all pure type_ annotations — none mutate e.kind:

  - Pseudo-fields .len / .cap / .ptr on slice / str / array
    (cstage L833-842). `len` and `cap` stamp i32; `ptr` synthesises
    an N_TPTR over the element (u8 for str, bu.lhs else).
  - Struct field walk on N_TSTRUCT (cstage L843-849) — match
    N_TFIELD by name, return f.lhs and stamp.
  - Tuple positional access `t.0`, `t.1`, … on N_TTUPLE
    (cstage L850-866). fldnumidx (cgenutil SSoT for decimal-only
    parsing) yields the index; walk bu.list .next idx times.

Two divergences from cstage, documented inline:
  - Wwstage carries str as N_TNAME("str") (no dedicated N_TSTR);
    the pseudo-field guard tests the trio shape directly.
  - Wwstage falls through to nil on struct/tuple miss instead
    of erroring (lenient policy per scruttype L656).

Cumulative N_DOT arm now ~162 LOC, under Rob's 250-LOC tripwire.
No new helpers; fldnumidx reused. #56 mod.fn() parser-rep stays
parked.

Phase 1 A.6 step 5b of ~8 — closes Hare's access_field surface
(ref/hare/hare/ast/expr.ha:7-38). Cgenutil's paired walker still
derives the same shapes; A.6.3 collapses those onto these stamps.

Verified 132/132 incl. 995_self_rebuild byte-identity.
2026-05-21 19:08:02 +09:00
894d966707 selfhost/cmd/wcc: fold N_DOT module/enum in exprtype (A.6.1.5a)
Port cstage cmd/wcc/check.c:740-832 fold cases to exprtype's new
N_DOT arm. Two paths land:

  - Module-qualified ref (`pkg.x`): SK_USE leaf via
    scopelookupinmodule, type returned from the resolved sym's
    decl.lhs. Mirrors cstage L749-775.
  - Enum member fold to N_INTLIT: bare `EnumT.MEMBER` (cstage
    L780-803) and outer `pkg.EnumT.MEMBER` where the inner N_DOT
    folded via case 1 (cstage L805-832, including TPTR peel at L808).
    foldtointlit rewrites kind/uval/str/lhs/rhs; type_ stamps from
    the enum body.

Stamp surface (struct field + pseudo-field .len/.cap/.ptr) lands
in A.6.1.5b. #56 mod.fn() parser-rep stays parked.

Two documented divergences from cstage:
  - No use_alias gate — wwstage uses sym.mod disambiguation per
    installdecl L195-207; SK_USE alone suffices.
  - Cstage errors on unknown enum member; wwstage falls through
    to nil under the lenient-check policy at scruttype L656.

Check-side enum eval is literal-only (N_INTLIT + nil-auto-
increment); cgen.ww's enumevalmember has the full const-folder
for cgen-side. Every selfhost+lib enum uses explicit literals
today (audited 2026-05-21). Tracked as task #7.

Phase 1 A.6 step 5a of ~8 — bisect-clean split per Hare's AST
access_identifier vs access_field axis
(ref/hare/hare/ast/expr.ha:7-38).

Verified 132/132 incl. 995_self_rebuild byte-identity.
2026-05-21 18:46:34 +09:00
d119b058a0 CLAUDE.md: rule 14 — test-unit (inner-loop) vs test (bootstrap gate)
Adds rule 14 documenting the test target split. test-unit (~3.8s)
is the inner-loop fast path; full make test (~8:30) is the
bootstrap-correctness gate. Never push without test green.

The skip set (950, 990-997) lives in test/run; new wwstage-driver
tests added in that range automatically participate. Phase split
background cited: ww_ww writes intermediates adjacent to traversed
sources (task #15), so concurrent wwstage-driver tests would race.
2026-05-21 18:10:18 +09:00
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).
2026-05-21 18:07:13 +09:00
be363f4451 test/wcc/995_self_rebuild: stage source in workdir to avoid main.s race
ww_ww build writes its intermediates (.combined.ww/.s/.o/binary)
next to the source path. When 995 invoked `ww_ww build /abs/path/
selfhost/cmd/wwdump/main.ww` in parallel with 991/992/994 (which
read selfhost/cmd/<tool>/main.s as a fixture), the build mid-wrote
main.s and the fixture-reading test saw a partial 520B view of the
1.4MB file → "C 1422712 vs ww 520 bytes" assembler-byte-diff fail.

The race was latent pre-31594e4 too: parallel test/run + serial
995 always finished 995 last (3min wall), so 991 was already done
by the time 995's wwdump build wrote main.s. Internal-parallel 995
finishes in 1m47, opening the window.

Fix: cp the source to <workdir>/build.ww before invoking ww_ww,
build from build.ww. Driver writes intermediates as build.{combined.ww,
s,o} and binary `build` in workdir; canonical selfhost/cmd/<tool>/
main.* are never touched by 995. Imports still resolve via -I, but
those paths are read-only fixtures.

Verified 3 consecutive `make test` runs 132/132 (4:18, 3:22, 3:16).
2026-05-21 17:05:58 +09:00
31594e4cae test/wcc/995_self_rebuild: fork-parallel the 5-tool rebuild loop
Was sequential `for (i = 0; i < 5; i++) rebuild_one(...)` at ~3:15
wall. Split into spawn_build (fork+system ww_ww build) phase and
waitpid+slurp_eq collect phase. The five tool builds now run
concurrently; wall drops from sum(builds) to max(builds).

Per-tool workdir already pid-keyed (/tmp/wwsr_<pid>_<tool>); add
build.err per workdir so concurrent diagnostics don't merge —
collect phase replays the err file on failure. 180s timeout still
inside the cd && ... && ww_ww build string.

All five tools' failure status collected before reporting, so a
multi-divergence run names every tool (not just the first).

make test wall: 4m31s → 3m17s on 8 cores. 132/132 green.
2026-05-21 16:41:03 +09:00
65a6cac6a0 test: parallelize test/run via xargs -P; 180s per-tool timeout
test/run fans test/wcc/*.c across $(nproc) workers via xargs -0 -n2 -P
$JOBS; each worker writes <prefix>.status and an optional <prefix>.fail
sentinel into a mktemp results dir. The driver collects in glob order so
output stays deterministic across runs.

Wraps every ww/w6c/w6a/w6l/wwdump invocation in 990-995 with
`timeout 180`, bounding orphan compilers under 8-way contention. The
direct runwait(exe) calls for fresh-built test binaries get the same
treatment via a "timeout 180 %s" snprintf hop. Motivation: pid 2101 was
a w6c_ww that ran 42h on /tmp/wcas_asm_1326_15.ww until we killed it
during this session; timeout makes that impossible.

993's /tmp/ww_d_hello.ww is now pid-keyed (snprintf %d getpid); without
it concurrent runs (or future shards of 993 itself) would race on the
staging file.

Wall: 9m59s → 4m31s on 8 cores. Same 132/132 status.
2026-05-21 16:32:27 +09:00
0715ec9662 selfhost/cmd/wcc: stamp e.type_ for N_INDEX (A.6.1.4)
Port cstage cmd/wcc/check.c:870-894 to exprtype. indexresult yields
the resolved element tnode for slice/array, u8 for str, T for *[N]T
(decay) and *T (generic), and []T for *[]T (no decay, mirrors the
Hare-faithful rule at check.c:883-885). The arm stamps e.type_ via
tinfofornode same shape as the A.6.1.3 BIN/UN pattern.

Phase 1 A.6 step 4 of 6 — slot the indexing result type into the
mandatory post-checker invariant. Cgenutil still derives index
stride from AST shape (indexbaseesz, elemsizeofc, indexvaluetnode);
A.6.3 will collapse those onto the now-stamped N_INDEX type_.

Verified 132/132 incl. 995_self_rebuild byte-identity.
2026-05-21 16:08:48 +09:00
ada99973ee selfhost/cmd/wcc: stamp e.type_ for N_BIN + N_UN (A.6.1.3)
Adds unifyarith / binoptype / unoptype mirroring cstage
cmd/wcc/check.c:580-596, 598-640, 642-687. Two new exprtype
arms stamp e.type_ for N_BIN and N_UN via the same
tinfofornode pattern as A.6.1.1/.2.

unifyarith: untyped+untyped (prefer float), untyped+typed
(typed via isassignable), typed+typed (typeeqast), fallback ltn.
binoptype: ptr arith (ptr±int → ptr, ptr-ptr → i64), bitwise/
arith → unifyarith, comparison + logical → bool.
unoptype: deref (resolvealias+unwrapbang then N_TPTR.lhs),
addrof → *T with slice/str pseudo-field .len/.cap widening
to *i64 (mirrors check.c:672-682), unary +/-/^/!.

Wwstage stays silent on operator-type errors per existing
checker discipline; cstage flags the same shapes.

Verified 132/132 incl. 995_self_rebuild byte-identity.
2026-05-21 15:31:03 +09:00
19095e4b50 selfhost/cmd/wcc: stamp e.type_ for N_CALL + drop fold hook (A.6.1.2)
Six N_CALL stamp sites in exprtype:
- alloc slice form: tt = ([]u8 | nomem)
- alloc value form: tt = (*T | nomem)
- size/align/offset fold sites: untyped_int (mirrors cstage
  cmd/wcc/check.c:926/958 which sets n->type = ty_untyped_int
  after the fold; the returned mktname("i32") is the assignability
  target for callers, not the constant's own type)
- regular call: s.decl.lhs (mirrors cstage's build_fn_type ret)

Five error-path nil returns deliberately don't stamp.

Drop the size/align/offset trigger hook in resolvewalk: the A.6.0
end-of-fn general N_CALL dispatch already fires exprtype on every
N_CALL, making the targeted hook redundant. Pre-edit relied on
double-dispatch (hook → fold → re-dispatch → N_INTLIT stamp);
post-edit folds and stamps in one pass. Final n.type_ identical.

Hook removal + stamps bundled per CLAUDE.md rule 11: same-concern
(N_CALL handling) and the removal is what justifies the inline
fold-site stamps replacing the double-dispatch path.

Verified 132/132 incl. 995_self_rebuild byte-identity.
2026-05-21 14:54:29 +09:00
c0fe38c101 selfhost/cmd/wcc: stamp e.type_ for CAST/TRY/TYPEASSERT/TYPETEST (A.6.1.1)
Phase 1 A.6.1 commit 1. Extends A.6.0's literal+ident type_
population to the CAST + TRY family + TYPEASSERT/TYPETEST arms in
exprtype. Mirrors cstage cmd/wcc/check.c:737 (N_CAST) and
1332-1435 (TYPETEST/TYPEASSERT/TRYPROP/TRYUNW) where n->type is
stamped on each value-returning path.

N_TRYPROP / N_TRYUNW only stamp the value-returning paths
(success-variant match inside the for-loop, no-error tail at
ou.list); the three nil-returning early-outs are intentionally
left unstamped.

Verified 132/132 incl. 995_self_rebuild byte-identity.
2026-05-21 14:27:09 +09:00
321e7ca615 selfhost/cmd/wcc: plumb exprtype hint + resolvewalk dispatch (A.6.0)
A.6.0: extend `exprtype(c, e)` → `exprtype(c, e, hint)` and dispatch
post-order on every expression-yielding node kind in resolvewalk.

hint is threaded but unused by every arm; A.6.1's STRUCTLIT/ARRLIT
arms consume it (harec's check_expression result_type shape per
feedback_hare_frontend_reference.md). Dispatch fires existing literal
+ ident stamps universally; per-kind stamp coverage lands in A.6.1+.
Cgen reads tnode.type_ (not expression-node type_), so byte-identity
holds.

Verified 132/132 incl. 995_self_rebuild.
2026-05-21 13:58:25 +09:00
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.
2026-05-21 13:24:27 +09:00
353dffb5e8 lib/ww + wcc + w6c + wwdump: strip *arena cascade (γ-6)
amalloc has 0 callers post-γ-2; the *arena threaded through
newnode/newscope/newtype/prim/typesinit/type{ptr,slice,array,chan,
named}/lexinit/parserinit/joindotted/checkinit/arenau64tos/cgeninit
and the scope.a / tctx.a / lex.a / parser.a / checker.a / cgen.a
fields are vestigial.

Drop `import mem;` from 15 files, remove six struct fields, strip
*arena from 14 signatures, update ~120 call sites across lib/ww +
wcc + w6c + wwdump. selfhost/test/sym_link.ww fixture drops the
newarena/freearena probe; still exits 42 on scopedefine/scopelookup.
Both main.combined.ww auto-regenerated.

Comments retidied: typ.ww "once per arena" → "once per program";
parse.ww drops "arena-build" qualifier on joindotted; sym.ww drops
mem-sibling-imports rationale.

Verified 132/132 incl. 994_w6c_ww + 995_self_rebuild byte-identity
(the primary symmetric-stages gate).
2026-05-21 13:01:57 +09:00
f83e65b82a selfhost/cmd/ww: drop dead builddirmodulepath (γ-3b)
Unreferenced after γ-5 *arena cascade. Removes the helper body
and its preceding doc comment; main.combined.ww auto-regenerated.

Verified 132/132 incl. 993_ww_ww + 995_self_rebuild byte-identity.
2026-05-21 12:28:16 +09:00
7f2a6aa9f3 selfhost/cmd/ww: strip *arena cascade (γ-5)
amalloc has 0 callers post-γ-2; the *arena threaded through ww
driver's importpathform / locatein / locateimport / enumeratedir /
peekpackage / arenadupcstr / builddirmodulepath / buildsearchpath /
resolvemodule and the expctx.a field are vestigial.

Drop `import mem;`, remove expctx.a, strip *arena from 9 signatures,
update 9 call sites. Drop 4 dead `let a: *arena = newarena();` in
buildone/dobuild/dorun/dotest. Comments retidied. main.combined.ww
auto-regenerated.

builddirmodulepath body intact — still 0 callers; γ-5b drops it.
arenadupcstr name is now a misnomer (task #10).

Verified 132/132 incl. 993_ww_ww + 995_self_rebuild byte-identity.
2026-05-21 12:17:25 +09:00
6faf00223e selfhost/cmd/w6l: strip *arena cascade (γ-4)
amalloc has 0 callers post-γ-2; the *arena threaded through w6l's
mklnk/resolvelib/cstrtostr/elfglobals/dcstrtostr and the lnk.a
field are vestigial.

Drop `import mem;` from sym/main/obj/dyn/dynout, remove lnk.a
struct field, strip *arena from the five signatures, update 13
call sites. Drop a dead `let a: *arena = l.a;` in dynout. Comments
at pass.ww/obj.ww/main.ww retidied to match post-strip reality.
main.combined.ww auto-regenerated.

Verified 132/132 incl. 992_w6l_ww + 996_dyn_ww + 995_self_rebuild
byte-identity.
2026-05-21 12:00:46 +09:00
12e0b4057f selfhost/cmd/w6a: strip *arena cascade (γ-3)
amalloc has 0 callers post-γ-2; the *arena threaded through w6a's
init/dupstr/bufinit and the asm_.a / buf.a fields are vestigial.

Drop `import mem;` from opcodes/parse/obj/main, remove asm_.a and
buf.a struct fields, strip *arena from init/dupstr/bufinit
signatures, update 12 call sites. main.combined.ww auto-regenerated.

Comment at main.ww:36 retidied "argv/arena" → "argv-style" to match
post-strip reality.

Verified 132/132 incl. 991_w6a_ww + 995_self_rebuild byte-identity.
2026-05-21 11:46:13 +09:00
3dae4d9e9a selfhost/cmd: astrndup → strings.dup view (γ-2); drop wcc.astrndup
The final 2 astrndup callers in w6a (main.ww fname capture and the
dupstr wrapper in parse.ww) now use the uniform γ-1 shape:

    let view: str;
    view.ptr = src;
    view.len = n: i32;
    out = strings.dup(view);

With both call sites converted, wcc.astrndup is dead and removed
from selfhost/cmd/wcc/mem.ww. amalloc + arena bootstrap stay
(other callers; #7 Phase B/C territory).

The two `// astrndup until #11 (w6a types shadow) is fixed.`
WHY-pointers are obsolete (#11 landed in 6696e95) and dropped per
CLAUDE.md rule 8.

dupstr in parse.ww keeps its (*arena, *u8, u64) signature; the
vestigial *arena param is tracked by task #10.

Verified 132/132 incl. 991_w6a_ww + 995_self_rebuild byte-identity.
2026-05-21 11:21:07 +09:00
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.
2026-05-21 11:04:40 +09:00
917d6250fc lib/ww/lex + selfhost/cmd: astrndup → strings.dup view (γ-1)
#7 Phase A first cut. 13 of 15 astrndup callers converted to the
explicit (*u8, n) → str view + strings.dup shape (ref/hare/strings/
dup.ha:7). astrndup export stays in selfhost/cmd/wcc/mem.ww — 2 w6a
sites blocked by the selfhost/cmd/w6a/types.ww shadow (filed as #11)
and carry an inline WHY pointer until the rename ships.

NUL-dependence audit: no consumer reads token text past `.len`. tok.text
flows through fputq (length-bounded) and parse.curtext → n.str (streq-
based dispatch across check/cgenutil); p.file is written via os.write
(ptr,len); selfhost/cmd/ww/main.ww's astrndup'd pathstr only flows into
visitseen/visitadd's manual byte-loop, while all OS calls in that file
use the unrelated `pathstr(*u8) str` view helper on the raw pointer.

Empty-str sites (lex.ww:579, 624, 640) collapse to strings.dup of an
empty view; strings.dup short-circuits len==0 (lib/strings/strings.ww:72)
and returns {nil, 0} — observationally identical to astrndup's prior
{arena_1byte, 0}.

Sites:
 - lib/ww/lex/lex.ww (8: 450, 529, 554, 564, 579, 624, 640, 794)
 - selfhost/cmd/w6c/main.ww:139
 - selfhost/cmd/w6l/dyn.ww:111 (inside dcstrtostr)
 - selfhost/cmd/w6l/obj.ww:185 (inside cstrtostr)
 - selfhost/cmd/ww/main.ww:531

Wrappers (dupstr/cstrtostr/dcstrtostr) keep their bodies; deletion
deferred to #10.
2026-05-21 10:41:54 +09:00
fd7dee985e cgen + memio: cgoutarena → memio.dynamic, grow → dynamicgrow (β-3)
Phase 0 last β-shape site. Two concerns in one commit because the
refactor surfaced the rename:

 - selfhost/cmd/wcc/cgen.ww  cgout buffer (cgoutbuf/cap/len + arena +
   cgout_grow + CGOUT_INIT_CAP) → memio.state + io.stream behind a
   one-shot lazy-init guard. cgout_enable drops its *arena param;
   memio.reset in cgout_flush keeps the buffer sticky across fns so
   the arena's amortisation survives — re-init per fn would abandon
   the buffer and re-grow from 0 via the 8→…→65536 ladder for every
   function (no io.close path → no os.free).

 - lib/memio/memio.ww  private fn grow → dynamicgrow. Symmetric with
   dynamicwrite / dynamicclose; required because cstage bundles all
   imported modules into a flat TU and resolves private fns by
   unqualified name, so the new `import memio;` in wcc's bundle
   collided with selfhost/cmd/wcc/mem.ww's arena `grow`. Module-aware
   private-fn scoping in cstage is task #9.

@test fn dynamicgrow in memiotest.ww (same package as memio.ww)
renamed to dynamicgrowcases to free the name; new suffix mirrors the
file's existing fixedwritecases / borrowedreadcases convention.

Lazy-init guard cgoutinit. memio.dynamic runs once on first
cgout_enable; subsequent enables just set cgoutmode. Mirrors
lib/log/log.ww:124 ensureinit. Without it, ~14 mmap syscalls per fn
and ~100 MiB+ cumulative leak on a typical bootstrap.

io.write bare discard in emitbytes mirrors lib/log/log.ww:169 —
memio.dynamicwrite never returns io.closed (memio.ww:166).

Verified 132/132 incl. 995_self_rebuild byte-identity.
2026-05-21 10:11:40 +09:00
f07a032104 selfhost/cmd/w6a+w6l: rt.malloc/amalloc β grow → alloc([], n)! (β-2)
Phase 0 second β-batch. 6 alloc sites across 3 β grow loops:
 - selfhost/cmd/w6a/obj.ww:100,108  buf bufinit/bufgrow (amalloc)
 - selfhost/cmd/w6a/asm.ww:26,63    asm_ emitbyte/emitdatabyte text+data (rt.malloc)
 - selfhost/cmd/w6l/obj.ww:113,135  lnk emittext/emitdata text+data (rt.malloc)

Same β shape as d9f0972: `*u8 = (rt.malloc|amalloc)(_, n): *u8`
→ `[]u8 = alloc([], n)!`, inner copy bounded by the manual length
counter (b.n / textlen / datalen) unchanged, struct field stays
*u8, writeback via .ptr. Bear-trap N/A (slice .len = 0 never read).

w6a/asm.ww `import rt;` (line 15) is now dead — deferred to a
post-Phase-0 import-hygiene sweep, mirrors 368b85e.

Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-21 09:38:40 +09:00
d9f097250b lib/ww/lex + w6a/parse: amalloc β grow → alloc([], n)! (β-1)
Phase 0 first β-batch. 4 amalloc sites across 2 β grow loops:
 - lib/ww/lex/lex.ww:570,592 lexstr string-literal escape buf
 - selfhost/cmd/w6a/parse.ww:513,548 DATA "..." escape-payload buf

Both follow the established α-per-alloc shape with loop logic left
manual: `*u8 = amalloc(_, cap): *u8` → `[]u8 = alloc([], cap)!`,
inner copy `nb[i] = old[i]` unchanged (slice indexing emits identical
asm to *u8 indexing — no .len bounds compare), `.ptr` extracted at
the *u8 consumer (s.ptr, pr.bytes). Bear-trap N/A — byte count tracked
in locals (nb, blen), slice .len = 0 never read.

Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-21 09:14:39 +09:00
f2ea7fccec selfhost/cmd/w6l/dynout+obj: amalloc → alloc([], N)! (α/γ-11)
Phase 0 eleventh α/γ-batch. ~23 amalloc sites:
 - w6l/dynout.ww: 22 (~18 α u8 ELF section buffers + 3 γ pointer
   arrays — dynsyms []*lsym, sosused []*lso, vernameptrarr []*u8)
 - w6l/obj.ww:346 standalone α hdrsize u8

All single-shot fixed-count (NOT β grow as originally classified
in the phase0-mapper audit). α: `[]u8 = alloc([], N)!` + .ptr at
the dwr*/drd*/dbcopy callees. γ: element-count was bytes/8.

obj.ww:346 keeps the `*u8` legacy alias via `let mb: *u8 = mbs.ptr;`
since `armember.data` is `*u8` and gets stored across the function;
heap memory survives the local slice header (no GC, process-exit
reclaim).

Verified 132/132 incl. 992_w6l_ww + 995_self_rebuild + 996_dyn_ww
byte-identity.
2026-05-21 08:37:12 +09:00
c91d684708 selfhost/cmd/ww+w6l/main.ww: amalloc → alloc([], N)! (α-10)
Phase 0 #8 tenth α/γ-batch. 14 amalloc sites:
 - ww/main.ww: 11 (path bufs in importpathform/locatein/peekpackage/
   expanddir/arenadupcstr/builddirmodulepath/buildsearchpath + γ
   names/nlens in enumeratedir)
 - w6l/main.ww: 3 (resolvelib cstr+NUL paths)

α: *u8 → []u8 + .ptr at consumer; γ: **u8 / *u64 → []*u8 / []u64
with element-count alloc (was bytes/8).

Verified 132/132 + 995_self_rebuild byte-identity. ~27 amalloc
sites remain across 5 files; most blocked on #7 (astrndup),
#8 (β grow loops in w6a/parse, w6a/obj, w6l/dynout), #10 (cgoutarena).
2026-05-21 04:36:20 +09:00
6ff38f52bb lib/ww: migrate 5 typed amalloc sites to alloc(T{...})! (typed-9)
Phase 0 critical lib/ww/ gap (frontend used by BOTH cstage + wwstage,
not covered by phase0-mapper's selfhost/cmd/ audit). 5 typed-struct
amalloc sites + 1 γ pointer-array + 1 α byte buffer:

 - lib/ww/ast.ww newnode (node, 20 fields, was 208u64 over-sized)
 - lib/ww/typ.ww newtype (tinfo, 13 fields, was 112u64 over-sized)
 - lib/ww/typ.ww tinfocachebind (tinfocacheent, was 32u64 sizelint-ok)
 - lib/ww/sym.ww newscope (scope, 6 fields, was 64u64) + buckets γ
 - lib/ww/sym.ww scopedefineinmodule (sym, 10 fields, was 112u64)
 - lib/ww/parse/parse.ww joindotted (α []u8 + .ptr extract)

Retires 4 rule-7 over-sized amalloc workarounds plus a #36
tinfocacheent sizelint-ok. WHY-comments documenting the workarounds
are dropped (no longer applicable — alloc(T{...})! sizes from the
type table).

ast.ww newnode's `fval = 0: f64` carries a 3-line WHY comment naming
the 990_selfhost TK_FLOAT-count diff probe (lex.ww:382 precedent for
the same cast pattern). Bare `0.0` here would shift the dump-diff
token-input scope and break 990's byte-identity probe.

β grow loops in lib/ww/lex/lex.ww:570,592 deferred — separate sweep.

Verified 132/132 + 995_self_rebuild byte-identity. Net -63 lines.
2026-05-21 04:18:52 +09:00
a3e4c6942f selfhost/cmd/wcc/check.ww: arenau64tos amalloc → alloc([], 24)! (α-9) 2026-05-21 03:26:23 +09:00
4f4504d10a selfhost/cmd/w6a/parse + lib/ww/lex: amalloc → alloc([], N)! (α-8)
Phase 0 #8 eighth α-batch. 3 sites:
 - w6a/parse.ww:198 nextline newline-hit branch
 - w6a/parse.ww:208 nextline EOF-no-newline branch
 - lib/ww/lex/lex.ww:457 float underscore-strip buffer

All α: alloc([], n+1)! + p[k] indexing + .ptr at the consumer
(`return buf.ptr, n` for parse; `parsef64(clean.ptr, j)` for lex).
No struct-field shape change, no escape.

Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-21 03:14:04 +09:00
4972ab4a5c cgen: loop/yield/defer fixed-max buffers raw-ptr → []T (#9)
Phase 0 #9. cgen struct fields loopendbuf/loopcontbuf/yieldbuf/
deferbuf change from `*str`/`**node` over-allocated arena chunks
to `[]str`/`[]*node` slices. The 4 alloc sites in cgeninit drop
the byte-count form (`LOOP_MAX*24u64`, `DEFER_MAX*8u64`) for
element-count (`LOOP_MAX: u64`, `DEFER_MAX: u64`). 10 caller
sites in cgenstmt.ww/cgenexpr.ww use `[i]` indexing which works
identically for slice-shaped struct fields.

Two-line let-then-assign idiom for the 4 inits is a real checker
limitation: alloc's element-deferred `[]u8` → `[]T` retype only
fires in let-init (checkletassign N_TSLICE LHS), and cglet's
alloc-slice writeback shortcut (cgenstmt.ww:577) only fires in
let-init too. Direct `c.field = alloc([], N)!` would silently
emit a scalar alloc with a junk slice header. Filed #49 for the
checker enhancement; the let-then-assign is Hare-idiomatic in
the meantime.

Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-21 02:58:30 +09:00
775b271dc7 examples: α/γ rt.malloc → alloc([], N)! (cmatrix + lispcore)
Phase 0 #8 seventh α-batch. 11 sites across two examples:
 - cmatrix.ww: cmat struct (alloc(cmat{})!) + 4 γ i32 arrays
   (head/length/speed/counter) + 1 α u8 (glyphs)
 - lispcore.ww: 1 α arena chunk u8 + 2 γ i32 (sym_off/sym_len)
   + 2 α u8 (sym_blob, obuf)

γ pattern uses element count (was bytes/4). Struct fields stay
`*T` (legacy) so callers extract via intermediate `*_sl` local
slice + `.ptr` assign — verbose but mechanical until struct-field
slice types are part of a separate refactor.

Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-21 02:41:37 +09:00
0f011661b6 cmd: α-6 amalloc → alloc([], N)! (cgen mklabel/mkscratchname/internstrlit)
Phase 0 #8 sixth α-batch. 3 sites in selfhost/cmd/wcc/cgen.ww — all
runtime-N byte buffers returning a `*u8` via a constructed str.
Same shape as 7c2403c's cgenutil.ww:108 mkvarargname conversion.

Remaining cgen.ww amalloc: :541-546 (LOOP_MAX/DEFER_MAX context-
struct arrays → task #9, struct-field type change) and :700
(cgoutarena package-global → task #10, memio.dynamic refactor).

Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-21 02:28:58 +09:00
7c2403cc4c cmd: α/γ-5 rt.malloc/amalloc → alloc([], N)! (w6l stack-promotes + cgenutil mkvarargname)
Phase 0 #8/#11 small batch. 4 sites:

w6l/main.ww δ stack-promotes (3):
 - :67 appenddec — 16B → [16]u8
 - :106 islinkable — 8B → [8]u8, &mp[0] to os.read
 - :176 isso — 20B → [20]u8, &mp[0] to os.read

wcc/cgenutil.ww:108 mkvarargname α (1):
 - amalloc → alloc([], n)!. Standard slice indexing
   (`p[k]` not `p.ptr[k]`) — ww's slice subscript has no
   bounds check (cgenexpr.ww:816-856 in cgindex), same
   shape as the dup pilot (4c07ef0).

Closes #11 (arenau64tos was re-routed via #8 separately;
the δ-shape was the genuinely trivial case). Advances #46.

Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-21 02:17:50 +09:00
368b85ea33 cmd: α/γ-4 rt.malloc → alloc([], N)! (w6a/w6c/w6l file slurps + sized bufs)
Phase 0 #8 fourth α-batch. 10 α/γ conversions across 6 files:
 - w6a/main.ww slurp (1)
 - w6c/main.ww slurp (1)
 - w6l/dyn.ww slurpso (1)
 - w6l/obj.ww slurp (1)
 - w6l/dynout.ww filebuf at :661 (1; threads .ptr through ~50
   dwr*/dbcopy callees — that's the 139-line w6l/dynout diff)
 - w6l/out.ww hdr at :92 (1)
 - w6l/main.ww 4 sites: bufp + γ {inputs, libdirs, lflags} (4)

Deferred:
 - w6l/main.ww 3× 16/8/20-byte δ fixed-bufs at :67/:106/:176 →
   stack-promote in a separate sub-task.
 - w6l/obj.ww :113/:135 β text-buffer growth → memio.dynamic refactor.

Pattern unchanged from 47918d3/00d88ff/e9eb67d: `alloc([], N: u64)!` +
`buf.len = N: i32;` + `buf.ptr` for callees taking `*u8`. γ uses
element-count (was bytes): `maxinputs: u64` not `maxinputs*8u64`.

Verified 132/132 + 995_self_rebuild byte-identity. Advances #45.
2026-05-21 01:59:58 +09:00
e9eb67de04 cmd: α/γ-3 rt.malloc → alloc([], N)! (ww/wwdump main)
Phase 0 #8 third α/γ-batch. 33 sites total: 32 in selfhost/cmd/ww/
main.ww (driver) — 22 α `*u8` byte buffers + 10 γ `**u8` pointer
arrays — and 1 α in selfhost/cmd/wwdump/main.ww (file-slurp buffer,
previously amalloc).

Patterns:
- α: `let buf: []u8 = alloc([], N: u64)!; buf.len = N: i32;` then
  `buf.ptr` to extract `*u8` for callees that still take raw pointer
  (cstrinto/byteinto/readall/getdents64/...).
- γ: `let arr: []*u8 = alloc([], N)!; arr.len = N;` element-count
  semantics (was bytes; ww slice alloc takes element count).

i32 .len cast: ww's slice.len is i32 so `.len = N` from a u64
source requires an explicit `: i32` cast or silent-zero results.

The previously-flagged `out = rt.malloc(PATH_MAX): *u8` reassignment
in dobuild migrates cleanly: locally allocate `outbuf: []u8`, then
`out = outbuf.ptr` to preserve the `*u8` shape for the else-branch
from defaultoutpath. No GC + process-exit reclaim makes the bare
.ptr lifetime-safe (no free path needed).

0 sites deferred. Verified make test 132/132 + 995_self_rebuild
byte-identity. Advances #44.
2026-05-21 01:27:27 +09:00
00d88ff9fc lib: α-batch-2 rt.malloc → alloc([], N)! (path/shlex/fmt/ostest)
Phase 0 #8 second α-batch. 7 sites: lib/path/path.ww `join` ×4,
lib/shlex/shlex.ww `dupstr`, lib/fmt/fmt.ww `asprintf` tight-copy,
lib/os/ostest.ww `test_alloc_free_roundtrip`. Same dup-pilot pattern
(4c07ef0, 47918d3): `alloc([], N)!` + `buf.len = N;` +
`return strings.frombytes(buf);`.

Side effects:
- path/shlex/fmt: import switches `rt` → `strings` (callers now
  reference `strings.frombytes`, not `rt.malloc` direct).
- ostest.ww: `import rt;` retained — the alloc builtin lowers to
  `CALL malloc(SB)` which resolves via rt's @symbol("rt_malloc")
  decl. Other files reach rt transitively via `import strings`;
  ostest only imports os, so it needs the explicit rt import.
- shlex stale comment "avoid strings dep" stripped — strings is now
  in scope.

Verified make test 132/132 + 995_self_rebuild byte-identity.
Advances #43.
2026-05-21 01:01:13 +09:00
47918d3ced lib: drop _unsafe convention; rename fromutf8_unsafe → frombytes; strings α-batch (concat/join/lpad/rpad)
CLAUDE.md rule 9 amended with the explicit carve-out: ww is C/Plan-9-
lineage — no GC, no "safe" baseline to be unsafe relative to — so the
Hare `_unsafe` suffix flags an axis ww doesn't have. The convention
is dropped wholesale in lib/.

Concrete changes:
- lib/strings: `fromutf8_unsafe` → `frombytes` (pure reinterpret). The
  validating sibling `fromutf8` is deleted entirely (28 lines, plus its
  84-line fromutf8_cases test). Callers that need validation write the
  two lines inline at the IO source: `utf8.validate(b)?;
  let s = strings.frombytes(b);`. `fromutf8` name reserved for a future
  true validating helper.
- lib/strings α-batch: concat/join/lpad/rpad migrate from
  `rt.malloc(N): *u8` to `alloc([], N)!` + `buf.len = N;` +
  `return frombytes(buf);`. Same dup-pilot pattern (4c07ef0). Task #41.
- lib/memio header comment trimmed: drops a stale reference to
  "lib has no fromutf8 today"; cites the rule-9 carve-out instead.
- Caller renames across selfhost combined.ww files (auto-regen) +
  cgenutil.ww comment ref.

Rule-11 disclosure on the bundle: the rename and the α-batch are
nominally separable concerns (symbol-naming policy vs amalloc→
alloc-slice migration), but they touch the same 4 functions in
lib/strings/strings.ww — the α-batch's first emission of `frombytes`
postdates the rename. The α-batch was applied on top of the rename
sweep mid-flight by the pre-commit reviewer; splitting them back
out is fiddly text surgery for marginal bisect value. The rename is
the primary concern; α-batch is one entry in #8's sized-slice
migration.

Verified: make test 132/132, 995_self_rebuild byte-identity holds.
Closes #42; advances #41.
2026-05-21 00:35:14 +09:00
4c07ef0552 lib/strings/dup: rt.malloc → alloc([], n)! slice form
Pilot for task #8 (runtime-N alloc API). `alloc([], n)!` yields a
slice with cap=n, len=0; explicit `buf.len = s.len;` lifts the len
before the fromutf8_unsafe reinterpret. Same shape as
ref/hare/strings/dup.ha:15 modulo ww not yet having `append`
(task #36) — open-coded byte loop in lieu of static-append.

Verified 132/132 + 995_self_rebuild byte-identity. Pattern is the
template for the next α-category sites (concat/join/lpad/rpad/...).
2026-05-20 23:38:03 +09:00
a376ec89eb lib/rt: rename rt_alloc → rt_malloc; rt.alloc → rt.malloc
Hare's canonical runtime allocator is rt::malloc with linker symbol
rt.malloc (ref/hare/rt/malloc.ha:27,78). ww kept the dot→underscore
Plan 9 convention (CLAUDE.md rule 4) so the linker symbol becomes
rt_malloc; the lib/rt exported function name becomes malloc; ww
callers say rt.malloc(...).

The language builtin keyword stays `alloc(T)!` — unchanged from Hare
(ref/hare/hare/lex/token.ha:21 ltok::ALLOC, parse/expr.ha:398
builtin()). The rename only touches the lowered linker symbol and the
exported function name behind it; the user-facing syntax for
heap-allocation is identical to Hare.

Surface:
- rt/alloc.s: TEXT rt_alloc → TEXT rt_malloc, labels updated
- lib/rt/malloc.ww: @symbol("rt_malloc") fn malloc(...) (was rt_alloc/alloc)
- rt/ensure.ww: local FFI decl + call site updated to malloc; `!` dropped
  on the direct FFI call (rt_malloc returns *void, not a tagged union)
- 18 .ww callers: rt.alloc(...) → rt.malloc(...)
- cstage cmd/wcc/check.c + wwstage selfhost/cmd/wcc/check.ww
  alloc-builtin suppression gate routes through ffi_resolve("malloc")
  for the lowering; the user-shadow check still keys on the BUILTIN
  KEYWORD "alloc" since that is what `alloc(...)` parses as. Adding
  "malloc" to the user-shadow check was unnecessary and was reverted
  during pre-commit review.
- cstage cmd/w6c/cgen.c: 2× ffi_resolve("alloc") → ffi_resolve("malloc")
- wwstage cgenexpr/cgenstmt: 2× ffiresolve(c, "alloc") → ffiresolve(c, "malloc")
- Test fixtures (700_e2e, 758_cgalloc_str_field, 990_selfhost, 992_w6l_ww,
  selfhost/test/tagged_ptr_ret.ww): updated inline ww sources to the new
  decl + call form

This is commit 2 of 3 in the lib/rt extraction (#38). Commit 3 closes
the OOM contract — return type becomes nullable *void and the builtin
lowering null-checks + propagates nomem.

Verified 132/132 + 995_self_rebuild byte-identity (5 wwstage tools
round-trip identical) + make clean cold rebuild.
2026-05-20 22:11:34 +09:00
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).
2026-05-20 20:39:52 +09:00
a1ee817906 selfhost/cmd/ww/main.ww: 3× ".\0" amalloc → stack [2]u8
dobuild/dorun/dotest each allocated a 2-byte heap "." prefix buffer via
amalloc, set dot[0]='.'; dot[1]=0; passed dot as *u8 to a callee, then
let the arena chunk live forever. The dot pointer never escapes the
function — every callee chain (resolvemodule, cstrendswithlit,
rundirtests/runsingletest) byte-copies its input into a fresh arena
allocation before returning, never storing the original pointer.

Replace with `let dot: [2]u8 = ['.': u8, 0u8]; ... &dot[0]`. Both
stages allocate a fresh frame slot per let at function-frame entry
(localoff cstage / localadd wwstage), so the slot lives across the
synchronous callee.

Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-20 19:28:59 +09:00
b060822bd8 selfhost/cmd/ww/main.ww: migrate visitadd strnode amalloc to alloc(T{...})!
Phase 0 batch 4. Single typed-struct site in the driver. Retires the
32u64 over-size workaround on a 24-byte strnode (selfhost/CLAUDE.md
trap #1 — amalloc < struct corrupts the next slot).

14 other amalloc sites in ww/main.ww are runtime-N path/name buffers
(13 → task #8) and 3 fixed-max ".\0" prefix buffers (→ task #9).
wwdump/main.ww's 1 site is a runtime-N file-size buffer (→ task #8).

Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-20 19:12:10 +09:00
65c92e2c8d selfhost/cmd/wcc/cgen.ww: migrate 16 amalloc sites to alloc(T{...})!
Phase 0 batch 3b. collect* paths + intern + localadd/alloc/addstack.
Retires five rule-7 over-sized amalloc workarounds at :245 (enumtype
80→72), :265 (enummember), :918 (strlit), :1653 (fnret 80→72), :2060
(ffi) — alloc(T{...})! sizes from the type table, so the magic-byte
paranoia comments (the #35 block) go away with the literals.

Deferred: 3 internstrlit *u8 runtime-N buffers (#8), 4 LOOP_MAX/
DEFER_MAX fixed-max arrays at :541-546 (#9), 1 cgoutarena package-
global at :700 (#10).

Net -68 lines. Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-20 18:58:00 +09:00
1798ef02ef selfhost/cmd/wcc: migrate 2 cgenutil amalloc sites to alloc(T{...})!
Phase 0 batch 3a. structinfo registration + fieldinfo per-field in
registerstruct (cgenutil.ww). Both relied on amalloc-zero for fields=nil
and totsize=0 (structinfo) and finext=nil (fieldinfo); MAP_ANON-zero
covers the same slots.

check.ww:842 (arenau64tos 24B scratch) deferred to #11.
cgenutil.ww:108 (mkvarargname runtime-N) deferred to #8.

Verified 132/132 + 995_self_rebuild byte-identity.
2026-05-20 18:39:24 +09:00
104ba3cc0a selfhost/cmd/{w6a,w6l}: migrate 10 amalloc sites to alloc(T{...})!
Phase 0 batch 2. Sites: w6a/parse.ww (asym, aprog, aoperand ×2),
w6l/main.ww (lnk), w6l/obj.ww (defent, armember, lobj, lrel ×2).

obj.ww's hdrsize *u8 buffer at line 344 + 7 other runtime-N byte
buffers across the three files (path bufs, n+1 cstr copies, cap
realloc) stay deferred to #8.

Verified via 991_w6a_ww + 992_w6l_ww + 995_self_rebuild +
996_dyn_ww byte-identity. make test 132/132.
2026-05-20 18:25:38 +09:00
469ec85551 selfhost/cmd/{w6a,w6l}: migrate 6 amalloc sites to alloc(T{...})!
Phase 0 batch 1. Six typed-struct allocations switch from
amalloc(arena, NNu64): *T over-sized byte counts to alloc(T{...})!
with partial struct literal initialization. MAP_ANON-zero from
rt_alloc covers any field the literal omits — same contract the
amalloc bump arena provided via its explicit zero loop, but
without the rule-13 size literal at the call site.

Converted:
 - w6a/asm.ww  addreloc, addrelocdata, addfixup (areloc, afixup)
 - w6l/sym.ww  intern (lsym)
 - w6l/dyn.ww  loadso (lso), lexport

lexport's `if (vernamecs == nil) { e.version.ptr = nil;
e.version.len = 0i32; }` branch dropped — MAP_ANON-zero provides
the empty-version slot for free; the inverted `if (vernamecs !=
nil)` only takes the dcstrtostr path.

w6l/sym.ww:20 comment updated from "amalloc-zeroing" to
"alloc-zeroing gives 0, not -1" so the documented mechanism
matches the call.

w6a/obj.ww's 2 remaining amalloc sites (bufinit + bufgrow) are
runtime-N *u8 byte buffers, deferred to #8 (runtime-N alloc API).

Verified via 991_w6a_ww + 992_w6l_ww + 995_self_rebuild +
996_dyn_ww byte-identity. make test 132/132.
2026-05-20 18:07:32 +09:00
d617a698b0 selfhost/cmd/wcc: route cgalloc field-store foff through emitdispreg
Four ad-hoc emit sites in cgalloc's N_STRUCTLIT field-store loop
(cgenexpr.ww:2770-2802) wrote the displacement via
emitint(foff: i64); emitline("(REG)\n"), producing 0(REG) for
foff=0. cstage's txt.c:130-134 omits the zero displacement, so
ww2.s (cstage compiling wwstage) and ww3.s (wwstage compiling
wwstage) would diverge the moment any selfhost site migrates to
alloc(T{...})!. Dormant today only because selfhost source has
no alloc(T{...})! yet.

Route the four sites through emitdispreg (cgen.ww:786), the
existing SSoT that already omits zero displacement.

Extends test/wcc/758_cgalloc_str_field.c with 4 table-driven
asm_disp_rows pinning the displacement text for {str/int/f64
at offset 0, str at offset 8}. Internal subtest count: 16 → 20.
The 3 foff=0 rows fail without the fix.
2026-05-20 17:41:41 +09:00
4c51bce244 selfhost/cmd/wcc: route cgalloc CALL through ffiresolve
Wwstage's cgalloc hardcoded `CALL rt_alloc(SB)` at cgenexpr.ww:2747 and
cgenstmt.ww:647. Cstage already routes through ffi_resolve("alloc")
at cmd/w6c/cgen.c:4149 — when a fixture lacks the @symbol("rt_alloc")
decl in scope, cstage falls back to `CALL alloc(SB)` while wwstage
still emits `CALL rt_alloc(SB)`. The divergence is dormant in
ww build (combined.ww always pulls lib/os/os.ww's decl) but activates
under direct `w6c file.ww` and any other single-file path.

Replace the hardcoded line with the ffiresolve(c, "alloc") pattern
already used for user-function calls. The @symbol decl in lib/os/os.ww
is unchanged and propagates via the combine step.

Extends test/wcc/758_cgalloc_str_field.c with 4 table-driven asm rows
that compile a fixture via direct w6c (no combine) and `cmp` the
CALL <sym>(SB) line between stages. The 3 noscope rows fail without
the fix and pass with it; the withsym row pins the positive ffi-hit
path. Test count internal: 12 → 16; total make test: 132/132.
2026-05-20 17:18:32 +09:00