build_one now invokes 6c_ww / 6a_ww / 6l_ww from $self_dir, not
the C-built binaries that share the directory. After this change
`ww_ww build foo.ww` touches no cstage code at runtime — the
fresh-checkout cstage is still needed to bring the wwstage into
existence, but day-to-day work runs on the ww toolchain end to
end. The C `ww` driver in cmd/ww/ still drives the C 6c/6a/6l.
Test 993 (which used to be trivial — both drivers invoked the
same C tools) now meaningfully compares the cstage pipeline
against the wwstage pipeline on hello + wwdump and confirms
byte-identical exes.
The .combined.ww files for 6a/6l/ww/wwdump and smoke are
regenerated by the ww driver's `expand()` step; their diff is
the lib/os dup2 wrapper and the cgen.ww port from the prior two
commits, propagating into the bootstrap inputs.
selfhost/cmd/6c/main.ww is a thin packaging of the wwc cgen — slurp
a .ww file, run lex+parse+cgen, write Plan 9 amd64 asm to the path
given by -o. The cgen routines in selfhost/cmd/wwc/cgen.ww write
directly to fd 1, so we use dup2 to redirect stdout into the
output file rather than thread an fd through every emit helper.
Adds the SYS_DUP2=33 wrapper in lib/os.
Makefile wires $(BIN)/6c_ww alongside the other wwstage tools and
adds $(BIN)/test_6c_ww to the TESTS list.
test/wwc/994_6c_ww.c diffs 6c_ww byte-for-byte against
`wwdump_ww -c` on five in-source programs plus the four selfhost
main.combined.ww files: same cgen reached through two binaries, so
any divergence is a packaging bug in selfhost/cmd/6c.
We deliberately don't diff against C-side 6c here — 990 probe 5
already covers that on the subset the ww cgen handles today.
Closes the byte-identity gap between selfhost/cmd/wwc/cgen.ww and
the C cgen, so a ww_ww-built binary matches the cstage-built binary
on the same input. The bootstrap fixed point was already green;
these are the bytes inside that fixed point that diverged from
what cmd/6c emits.
Slot allocation:
- local_add dedups by name (mirror cmd/6c/cgen.c:localoff). Two
`let cp: pos;` in disjoint if-branches share one slot. The
stored tnode is refreshed on each hit so a later `let m: *node`
shadowing an earlier `let m: i32` sees its own type when
emitting `m.next` — without this the N_DOT cgen fell into the
SB-symbol fallback and the linker complained about undefined
`next`.
- scan_locals dedups at frame-size time to keep the prologue SUBQ
in sync. cgfn seeds c.locals with param-name stubs before the
scan so a body's `let <param-name>` reuses the param slot, then
resets c.locals before emission so real offsets get installed.
- local_alloc (no dedup) for N_MCASE bindings: C cgen handles a
match as an expression with by-value `locals`, so two separate
matches each get fresh slots for `v`/`e`.
Per-instruction matching:
- `return;` in a void fn zeros AX (C cgen falls through to
cgexpr_int(c, 0)).
- N_INTLIT prints i64 (signed), not u64. FNV-1a's offset basis
now prints as `$-3750763034362895579`, matching `$%lld`.
- *p = strexpr push order swapped to PUSH AX / PUSH BX → POP CX
/ POP AX (cgen.c:1033-1041).
Feature port from 635818e (the half that the wwdump corpus
actually exercises):
- N_IDENT used as a value with fn type now LEAQs through
ffi_resolve, so `let fp = some_ffi_fn;` emits the C symbol.
- N_CALL on a bare ident checks local_find_node first; a local
fn-pointer dispatches as `cgexpr(callee); CALL AX` instead of
`CALL ident(SB)`.
Tests 990 probe 6 and 994 are byte-identical on mem/err/tok/smoke
+ the four selfhost main.combined.ww files. Bootstrap still
ww2 == ww3.