Commit Graph

909 Commits

Author SHA1 Message Date
c7d9dc92de selfhost: migrate tool sources to directory packages
Build w6a and w6l from package-main directories and expose the wcc backend through a narrow package API so w6c and wwdump no longer import implementation files. Retarget the remaining load-bearing fixtures and example sources to directory packages; retain the one intentional flat compiler collision as an explicitly composed raw unit.
2026-08-12 17:12:03 +09:00
90f9d60291 cmd: preserve package-test driver identity 2026-08-12 16:22:38 +09:00
5eec5d3711 cmd: bind package workdirs to driver identity 2026-08-12 15:51:44 +09:00
5a2e4ba28d cmd: honor package library roots in both stages 2026-08-12 15:18:46 +09:00
d78020baf7 cmd: preserve package attribution for tool failures 2026-08-12 14:17:41 +09:00
7dad3a6db4 cmd: invoke package tools with exact arguments 2026-08-12 14:13:51 +09:00
29e97ff8a1 cmd: share package builds across test directories 2026-08-12 13:54:15 +09:00
dd975d195c cmd: share package builds across test products 2026-08-12 11:42:24 +09:00
9862f0c05f cmd: build directory package test variants 2026-08-12 03:46:47 +09:00
e59881cb30 compiler: support package test runtime aliases 2026-08-12 03:46:28 +09:00
0ac0fd68ec cmd: compile packages from direct exports 2026-08-12 01:12:50 +09:00
9b970eb16a compiler: make package exports self-contained 2026-08-12 01:12:42 +09:00
52f6d6f0d4 cmd: build local package graphs 2026-08-11 22:26:07 +09:00
db96422f74 compiler: load imports and enforce package exports 2026-08-11 22:25:59 +09:00
a2faa8b071 w6c: keep nested unwrap destinations distinct 2026-08-09 04:52:43 +09:00
ac8ddee6c1 w6l: purge dead elf branches; pin rx extent 2026-08-09 04:37:33 +09:00
e720d4802f w6a: delete dead text state and redundant rex arm 2026-08-09 04:31:37 +09:00
c9425a3b46 wcc: delete unused warning counters and ww err twin 2026-08-09 04:19:22 +09:00
a85306e68f wwdump: fail ast mode on parser errors 2026-08-09 04:02:11 +09:00
00d0ed502d ww: resolve root argv0 and retire stale help paths 2026-08-09 03:56:37 +09:00
5f829399ea wcc: classify tuple destructure from the rhs type 2026-08-09 03:49:58 +09:00
edacb40bfe w6c: keep unwrap destination off the call stack 2026-08-09 03:44:12 +09:00
a030268c52 wcc: build generated labels at their exact size 2026-08-09 03:38:03 +09:00
0a16811ab1 w6c: preserve grouped and qualified types in wwi 2026-08-09 03:31:40 +09:00
cd11d6e573 ww: driver/coordinator robustness — dirent errors, EINTR, block comments
Three same-cluster robustness holes. enumeratedir treated a failed
getdents64 (r<0) as EOF: a mid-walk error silently truncated the
package source list, and a first-read error was misdiagnosed as
"directory contains no WW package sources" -- it now routes the
caller's "cannot read directory" arm. run_test_bin's waitpid had no
EINTR retry and its return was unchecked, so an interrupted wait
left status==0 and reported a false test PASS (the do_run twin
already retried). The coordinator's pkgskipspace now also skips
/* */ before the package clause like the driver's sep_skip_space --
a source opening with a block comment built under ww but failed
coordinator discovery.
2026-08-09 02:05:48 +09:00
999b110001 wcc: forrange bindings get a per-loop scope; dup binders reject
The TODO(#11) silent-accept of `for (let (a, a) .. xs)` rested on a
stale premise -- resolvewalk has per-block scopes since #53 and IS
the live w6c_ww checker pass. Bindings now install in a per-LOOP
scope (the N_MCASE pattern), so a duplicate name within one pattern
errs "redeclared in same scope" (cstage parity via stamptuplebinds'
scopedefine-nil check) while sequential same-name loops stay legal.
kwtab restores its stated alphabetical order (`import` before `is`,
both stages, kinds swapped in lockstep with names).
2026-08-09 01:59:33 +09:00
411515a83b wcc: checker-seam drain — spreads, tuple aliases, yield scoping
Four seams from one review cluster, all landing on the same checker
files; gates ran on the union (rule 11 body).

?-subset: the wwstage walk compared `...spread` ALIAS nodes on both
sides of the error-subset check, falsely rejecting a spread-carried
error return (`(i64 | ...errs)`, cs-accept/ww-reject) — both sides
now flatten like trycountvariants (cstage Tparams are pre-flattened
at type level).

Multi-let/multi-assign: the tuple gates keyed on the RAW rhs kind,
rejecting a NAMED tuple alias (`type pair = (i64,i64)`; cs clean-
reject, ww asserttyped stop) — both stages chase per #99 alias
transparency, wwstage rettupleof peels the alias so cgmassign keeps
the str element's len/cap stores, and the catA massign-alias error
pin re-rules to a run fixture. The N_MLET diagnostic also printed
its operands swapped (elem/declared reversed vs its wording).

Yield: match_yield_type didn't descend into N_SWITCH, so a yield
inside a switch arm typed the match void and dropped the value
(both stages); and a stray yield outside any match arm reached cgen
unchecked — both stages now gate on a match-arm counter (the
c->loops discipline).
2026-08-09 01:51:39 +09:00
503d1ab01e wcc: wwstage checks call arity at desugarcallargs
cstage rejects `add(1,2,3)` and `add(4)` (too many / not enough
arguments); wwstage ran no count check at this seam, so both built
-- a stray arg pushed silently, a missing one read garbage
(cs-reject/ww-accept build-verdict divergence). Surplus errs when
params exhaust with args left; missing errs on a leftover regular
param (a leftover TK_ELLIPSIS or FFI "..." is a legal zero-arg
variadic tail). fn-VALUE callees still bail at decl==nil -- their
whole typecheck, arity included, stays task #51.
2026-08-09 01:33:14 +09:00
cf9d83b209 wcc: enums compare nominally; folded members keep their enum stamp
Two composing defects made a two-enum union mis-tag in wwstage
(live cs!=ww: `let e: (color|shape) = shape.BALL` stored tag 0 —
the color arm — while cstage stored 1). type_eq/typeeq had no
TY_ENUM arm, so ANY two enums fell into the primitive default and
compared equal; enums are nominal (harec: an enum IS its alias
type) and now compare by node identity only. Underneath, the
wwstage post-order revisit re-stamped the constant-folded enum
member (an N_INTLIT) as untyped_int, clobbering the enum stamp the
N_DOT fold applied, so the widen matcher fell to its first-variant
fallback -- the #59.9 N_BIN guard now twins on N_INTLIT (cstage
cexpr is single-pass and never clobbered).
2026-08-09 01:26:31 +09:00
4a5f64fc3d w6a: bare negative constant parses honestly; name(reg) rejects
Two parse.c operand defects. A bare `-8` (no $) double-negated to
+8: a_parsenum lets strtoll consume the sign, and the caller's
`off * sign` re-applied it (the D_INDIR arm never multiplied, which
is why -8(BP) always worked); the wwstage twin was already correct,
so this was a latent cs!=ww on hand-written source. The name(REG)
non-SB arm faked a D_INDIR off an UNCHECKED reg_lookup and silently
discarded the parsed ident and +disp -- both stages; the shape has
no encoding and now rejects loud.
2026-08-09 01:12:41 +09:00
5006bf4fb9 w6l: link GNU long-name archive members; bad members fail the link
The archive walk skipped every member whose header name starts
with '/', which also dropped GNU long-name members (/N) -- any
member with a >15-char filename silently never linked. Only '/ '
(symbol index) and '//' (long-name table) are non-objects; /N
members index by content (names are never consulted). A member
that fails loadimage in the selective pull also stayed silently
unlinked with rc=0; it now reports and fails the link (rule 7).
wwstage w6l diagnostics ride the same .len idiom as the w6a sweep.
2026-08-09 01:07:19 +09:00
4fe2e25da4 w6a: branch labels stay out of the symtab (isglobal wired)
Every interned symbol -- including branch labels defined via
p.label -- was emitted STB_GLOBAL, so labels were collide-able
across objects and the header's "GLOBAL symbols only" contract was
enforced nowhere (asym.isglobal was set but never read). Labels
stay interned for fixup resolution; the symtab now carries only
exported definitions, undefined externs, and reloc-referenced syms
(a reloc pre-pass marks those). rt/ensure.o drops from 15 emitted
syms to 2. Bootstrap 991-995 hold byte-identical on the new format.
2026-08-09 01:07:19 +09:00
06c40a8bef w6a: MOVB REX for SI/DI/BP/SP; LEAQ/CALL shapes err; honest diag lengths
Three w6a defects, one component. MOVB with an SP/BP/SI/DI register
operand encoded AH/CH/DH/BH -- emit_rex suppressed the bare 0x40
that selects SPL/BPL/SIL/DIL (the comment claimed "we always emit
REX"; encode probe: `MOVB SI,(AX)` -> `88 30` = %dh). emit_rex8
forces the byte for low-byte codes 4-7; no current w6c output emits
those shapes, so all existing objects are unchanged. LEAQ and CALL
unsupported operand shapes fell through with zero bytes and no
errs++ (every MOV arm reports); both now err. Every hardcoded
os.write diagnostic length in the wwstage was one byte short
(truncating the newline/quote); all converted to the .len idiom so
the length cannot drift again.
2026-08-09 01:07:06 +09:00
f06c95e66b w6c: forrange bind cap loud; drain widen gates keyed on type-eq (#55)
Two silent seams in one sweep. The for-range destructure silently
dropped the 9th+ binding in both stages (bind arrays are sized 8);
the cap now hard-stops per the DEFER_MAX/LOOP_MAX discipline (the
shape is unreachable today -- every wide-tuple construction path
already loud-stops). argtaggedwidensz, the drain-side SSoT for
widened call args, still keyed "natural push" on slot-size equality
while pushargsrev flipped to type equality with #55 -- a same-slot
subset was classified widen by push, natural by drain (counts
coincide today; a classification reader desyncs, the #48 shape).
Both sides now share the wsame key; the widen-branch tag miss for a
concrete source joins the task-2 loud-stop family (tagged subset
sources keep t=0 -- their widentag is never read, the scratch store
remaps).

Lead 17 (enum fold-failure prev+1) DISPROVED: enumvalfold gates
every member init in the checker and its fold set is op-for-op
equal to cgen's enumevalmember, so the fallback only runs inside
already-failing compiles.
2026-08-09 00:52:27 +09:00
973dd964c9 wcc: structlit repeat-fill dim off the stamped tinfo (rule 13)
cgstructlitfill's `...` total read only an N_INTLIT dim node, so a
def-dimensioned [N]T struct field under-filled: elements 1..N-1 kept
the zero-fill, silently, wwstage only (cstage and the tn twin read
the stamped array length). Read tichase(fi.tnode.type_).alen first;
the surface node stays as the fallback for unstamped shapes.
2026-08-09 00:40:23 +09:00
f191e6e0e2 wcc: modulo is integer-only; compound ops carry their operand class
Hare's rule (harec check.c binarithm): % and the bitwise/shift five
are integer-only; + - * / need numeric operands. ww grouped % with
the numeric ops, and compound assigns never op-checked at all, so
`a % b` on floats compiled half-lowered (live cs!=ww divergence),
`a %= 2.0` plain-stored the rhs (op silently dropped, both stages),
and `s += "cd"` garbled str headers. Gate both at the checker, both
stages; the cgen float-compound fallbacks and the three unknown-
compound legacy defaults (deref/global/local) demote to rule-7 hard
stops. 34 compound-on-tagged/str/slice fixtures re-pin from the old
cgen "not wired" stops to the earlier checker diagnostics; 3 new
reject fixtures pin the closed shapes.
2026-08-09 00:32:41 +09:00
f0029227b5 wcc: cgdot/cgident dead-end paths are loud (rule 7)
cgdot's local-ident arm silently emitted nothing for a receiver
whose local record carries no type node (inference miss upstream),
leaving the consumer reading stale AX. cgident's tail silently
emitted nothing for any ident that resolved to no local/fn/def/let;
that silence is load-bearing ONLY for the #140 !void error-singleton
value (tag-only, the widen arm stamps the tag) -- keep exactly that
case, mirroring the cstage #140 guard, and hard-stop the rest.
2026-08-09 00:15:10 +09:00
57d33acd1f w6c: variant-tag miss is loud, never tag 0 (rule 7)
Every variant-index lookup miss (-1) was silently clamped to tag 0
across both stages: tagged return (value + bare-void), widen store
(struct/slice/str/float/scalar arms + push fast path + field store),
widen tag-remap (identity scan + emit loop), match case compare,
is/as typetest, tryprop error remap, and the alloc-nomem propagation
(clamped to 1). All misses are checker-rejected upstream today, so
the clamps were dead -- but any future checker/cgen seam gap would
mis-tag silently (wrong arm, wrong error, false success). Rule 7:
each site now hard-stops with a per-construct diagnostic; nullable
arms keep their raw -1 by design (a miss encodes the void polarity
for `case null`). Corpus asm byte-unchanged; bootstrap fixed point
holds.
2026-08-09 00:12:21 +09:00
94928470bc w6c: sret-wire the chained-leaf call store (o.a.b = wide())
The chained value-struct walker's N_CALL arm handled only <=24B
register returns; an sret-class rhs fell to the scalar tail and
stored ONE word (the sret dest pointer) into the leaf -- silent
truncation in BOTH stages, byteid-blind. Point the callee's hidden
RDI at the BP-relative leaf slot (the single-dot #234 arm verbatim);
a ptr-root/global chain joins the #234-tail loud-stop family.
2026-08-08 23:51:17 +09:00
62b9d20383 toolchain: banner purge + WHY-only comment sweep (rule 8)
selfhost/, cmd/, internal/ join the tree-wide sweep: every section
banner dies (91 selfhost + the cmd C-style dividers -> 0); narration
and stale contracts deleted (pre-#22 bundler notes, retired
single-PT_LOAD and no-archive claims, superseded ABI tables); every
ref/harec/qbe cite, task cite, encoding/ELF contract, and rule-10
twin pointer kept; lost lifetime/rationale lines restored where the
sweep over-cut (elf_globals ownership, kwtab linear-scan). Comment-
only proven: all five wwstage tool binaries byte-identical across
the sweep; test-commit, test-byteid (161+1399, 0 pinned-divergent),
and test-bootstrap (fixed point + 991-995 byte-id) all exit 0.
The read-through banked 66 latent-bug leads (checkpoint).
2026-08-08 23:14:03 +09:00
659e859f34 ww: test sources are *_test.ww only (Go contract)
Go compiles only _test.go files as tests; discovery now keys on the
_test.ww suffix alone. The line-leading-@test compatibility allowance
(noncanonical filenames admitted as test sources) is removed from both
driver stages and the coordinator. An @test declaration outside a
*_test.ww file is rejected loudly ("@test declaration outside
*_test.ww", wording byte-identical cs/ww) instead of silently running
under compose or silently dropping in a non-T build (#6). Tree audit
found zero real carriers; the two allowance fixtures flip canonical
(dep_test.ww, widget_test.ww). New pins: direnum attest-noncanon
reject row (both-stage stderr parity) and the coordinator
noncanonical_attest_rejected package row.
2026-08-08 20:38:02 +09:00
5a263cb115 test: library behavior owned by the coordinator lib/... walk
Go has no per-file test targets: the PACKAGE is the unit of testing
(`go test ./...`) and same-package test files compose together. The
59-target LIBRARY_TESTS fan-out (per-file -w workdirs, one Make rule
per suite) collapses to one line: `ww test -j $(JOBS) -w
out/wwbuild/wwtest-lib -I lib/ww lib/...` — the regex dir-route
precedent generalized. The lib/regex line and the per-file pattern
rule instance dissolve with it (test/lang keeps its own rule).

Measured before committing (-j4, strings edit row): old per-file
warm 4.7-4.9s; bare walk 5.2-5.6s — a real regression, so the
coordinator first gained the brief's persistent per-package workdir:
`-w DIR` on a package target forwards to wwpackage, which keys
DIR/<dir>_<pkg> per group and hands it to each inner `ww test -c`
build. Reuse stays entirely with the driver's existing
content-identity contract — the coordinator adds pure path policy,
no cache machinery. Both driver stages drop their package-target -w
rejects (forward instead); -w with -c stays rejected at the
coordinator (two ownership contracts). After: 3.9-4.0s on the edit
row, 1.0s warm no-op, 2.5s cold — faster than the old flow on every
row.

package_test's tree -w reject row becomes the positive contract
(cold+warm byte-stable stream, cs/ww same) plus the -c conflict
reject. libbyteid roster shape DECIDED: per-file fx entries stay —
every enrolled file is still standalone-buildable, so coverage is
byte-for-byte unchanged; the dir-mode entry form arrives only with
the B3 shared-helper split that first needs it. Docs: owner table,
target table, -w contract paragraph.
2026-08-08 19:35:50 +09:00
b52f30a894 wcc: reject module-scope alloc/call let initializers at check time
A module-scope let whose rhs runs code (alloc, call — peeled through
cast/?/! wrappers) emitted no DATAW slot: emit_lets' fold-fail
silently skipped the definition and every reference died at LINK
time with 'undefined reference', the one unacceptable failure mode
(rule 7). Hare's model rejects at check time (ref/harec/src/
check.c:4360 'Unable to evaluate initializer at compile time') and
routes runtime init through @init, which ww does not have — so both
frontends now reject at the declaration with identical wording.

alias_infptr_global flips compile->error as the alloc pin (its
letvartnode N_TPTR-over-N_TSTRUCT coverage lives on in the local
alias_infptr_{nest,slicecap} siblings); callinit_global_reject pins
the call shape. Corpus pin 1741/345/21/209/1166/3482.
2026-08-08 19:15:10 +09:00
84206ff4ed w6c: resolve ptr-to-array and slice-element aggregate sources (task #6 residue)
The two loud siblings filed in d3822d77: (a) an auto-deref *[N]T
indexed base (hs[k].result) failed cgplaceaddr's N_INDEX arm, which
knew only TY_SLICE/TY_ARRAY bases — add the one-pointer hop (the
(*hs)[k] form made implicit; load shape is the slice .ptr word's);
(b) a bare slice-element aggregate source (use(sl[k])) fell off
aggarg_srcaddr's ident-array-only N_INDEX arm into the #271 loud
stop — route the miss through the same cgplaceaddr fallback the
N_DOT arm uses. Both stages, byte-identical (probe matrix: let-copy,
field-assign, call-arg, return, indexed-store all IDENT).

After this, every ADDRESSABLE aggregate source resolves: ident
(local/module-let), deref, dot-chain, and index over array/slice/
ptr-array spines recurse through cgplaceaddr; the remaining loud
paths are rvalue sources (#40 family) and the #274 const-def arg,
both documented divergences. 8 rows added to idxdot_aggcopy_test.
2026-08-08 19:08:19 +09:00
cf69cf2f06 w6c: key tagged natural-push call args on type equality, not slot size
pushargsrev's five aistagged gates (N_IDENT #55, N_CALL #21, N_INDEX
#12, N_DOT #22a, deref #35) treated a tagged arg as already-tagged
when its SLOT SIZE matched the param's. A same-slot subset union
((bool|void) into (i64|bool|void), both 16B) then natural-pushed the
narrower box's words carrying SOURCE tags — no re-layout, no
cg_widen_tag_remap twin — so the callee matched the wrong arm
(silent: probes exited 10/90 where cstage exits 30/27). cstage keys
widen detection on type equality (cgen.c:10000 same = (pu == au) ||
type_eq) and routes every non-same tagged source through the zeroed
scratch + tag remap; the slot-DIFFER wwstage path already mirrored
that byte-identically, so the fix computes cstage's same check once
(wsame) and replaces each slot-size test with it. This also erases
the last known cs!=ww shape divergence (the 16B-local staging vs
direct-push frame delta on prefix subsets).

8 subsetwiden_* fixtures own the class: call-result/ident/str-payload
/mid-arg remap (the wrong-arm shapes), prefix (the shape-divergence
repro), and bigslot/return-pos/struct-24-to-32 sibling guards. Corpus
pin 1740/343/22/209/1166/3480.
2026-08-08 19:01:05 +09:00
854a532feb w6c: classify tagged call-result args by the stamped type
taggedcallslot gated on an N_IDENT callee + fnretlookup, so every
N_DOT / wwi-decl'd cross-module callee (and every fn-pointer call)
returned 0 and its tagged call-result ARG fell off the cursor-push
path into a 16B scratch spill where cstage PUSHQes AX/DX (cstage keys
the same push on args[i]->type, cgen.c tagged_arg_size). Read the
stamped result type instead — the #209/#211 predicate-to-stamp
discipline, same class as the fn-value cgident fix.

Graduates BOTH remaining DATABYTEID_DIVERGED pins
(r700_strings_byteindex frame 80->64, r839_xmod_nominal_match frame
32->16) — the ledger is EMPTY; the byteid gate ran 1389 data fixtures
at 0 pinned-divergent. Lang rows cover the same-module control,
fn-pointer, float-member, and str-member shapes (byte-identical each).

Separate latent lead banked, NOT closed here: the tagged
subset-widening call arg ((i64|bool) passed as (i64|bool|void))
byte-diverges pre-existing — cstage stages through a 16B local,
wwstage passes direct; runtime-equivalent.
2026-08-08 18:09:08 +09:00
192ddd4c66 ww: package-level -c -o names the single package's test artifact
The last Open-driver-work bullet. `-c -o <name>` replaces the fixed
<package>.test stem for exactly one package; the coordinator rejects a
multi-package fan-out ("cannot use -o with multiple packages", Go's
`go test -o` rule) and -o without -c is rejected at the driver
("needs -c for a package target" — a plain run executes from the temp
root, so a caller-owned name has nothing to name). Both driver stages
byte-identical wording; -S keeps its single-file-only reject.

package_test gains the contract row (naming, fixed-stem absence,
artifact runs, both rejects, both drivers); the tree-mode -o row's
pinned wording follows the contract.
2026-08-08 17:30:36 +09:00
184afa3243 syntax+w6c: multi-type match patterns in wwstage (align up to cstage)
`case T1 | T2 =>` (binding-less; cstage parse.c:635-650 chains extra
types through cs->list, bindings stay single-type by design). Three
wwstage layers, no AST change (node.list already exists):
- parser (expr.ww): pipe loop after the non-let arm's first
  parsetype;
- checker (check.ww): resolvewalk N_MCASE now walks n.list so each
  alt gets its type_ stamp (exhaustiveness/casecovers were already
  alt-aware, built ahead of the parser);
- cgen (cgenexpr.ww): the single-pattern want-computation moves
  VERBATIM into matcharmwant; a cs.list arm emits CMPQ/JE per alt
  funneling into one match_body label, mirroring cgen.c:11118-11141
  incl. label mint order and the tag<0 clamp. Nullable arms keep
  ignoring alts (both stages).

Graduates the held e2e row (pin 1732/1158/3464); byte-identical on
the repro and the single-pattern control.
2026-08-08 17:15:41 +09:00
d78137a051 w6c: fold dot chains through inferred-alloc struct pointers (#24 residue)
wwstage dotchainresolve gated its *T roots on an N_TPTR-over-N_TNAME
tnode; an inferred `let p = alloc(S{...})!` rides the checker-
SYNTHESIZED N_TPTR whose pointee is the struct BODY node (the #24
TNAME-normalize covers only the direct struct-lit binding), so the
root gate failed and chained p.field.pseudo reads fell to the
unfused deref arms — shape-only divergence vs cstage's type-keyed
fold (both stages runtime-correct; the historical field(SB) leak the
pin described was already fixed). Widen the local and global root
gates to accept the N_TSTRUCT pointee; correctness stays enforced by
the stamped-tinfo peel below (TY_PTR -> pointee TY_STRUCT).

Graduates alias_g73_heapfill out of DATABYTEID_DIVERGED (2 pins
remain, both the tagged-spill family); adds compile fixtures pinning
the newly-converged siblings (nested read+store, slice .cap,
inferred-global root; pin 1730/22/3460) and a runtime lang row that
mutates then re-points the heap base — a read bypassing the pointer
returns the wrong len.
2026-08-08 17:14:59 +09:00
9382eedbbb w6c: classify fn-value idents by the stamped TY_FN, not the leaf table
wwstage cgen re-derived fn-ness by NAME: cgident's fn arm gated on
fnretlookup, whose cross-module leaf fallback matches a foreign
same-leaf fn — inside aa.getv the bare `v` (aa's i32 global, stamped
by the #55 prefer fix) took the fn-address arm for main's `fn v` and
emitted LEAQ aa.v(SB) with no deref; the ww_ww-built 794 program
exited 0 instead of 7. cstage reads the checker-stamped type
(cgen.c:4315). Convert the three value-position sites to the stamp
(same predicate-to-stamp conversion as the #14 F7-c7 `&fn` arm):
cgident (now TY_FN -> def -> let, cstage's order — the def-first
order also mis-read a foreign scalar def shadowed by a curmod fn),
the cgdot module-qualified value arm, and the `&ident` #180 arm.
Call-position fnretlookup consumers are unchanged.

The 794 two-file program is now cs==ww byte-identical and both
drivers run exit 7; the carrier retires in the follow-up.
2026-08-08 17:13:05 +09:00