wcc,ww: flip driver to separate-compilation only; delete build_one amalgamator (M4 E3-C1, #22)

build_one_sep (per-package compile + .wwi interfaces + link) becomes the
sole build path. do_build/do_run/do_test and the ww twins all route
through it; --sep is now an accepted no-op and the run-rejects-sep guard
is removed.

Deleted the single-file amalgamator, both stages: build_one, expand,
expand_dir, peek_package (+ the wwstage twins + strictpkgmismatch).
unit_has_package is retained -- the sep scan loop's inline-package check
needs it. The sep-shared helpers (enumerate_dir_ww, locate_import*,
import_path_form, ImportSet, and ww counterparts) stay; they back the
surviving sep path.

Restores missing-package enforcement under sep by construction: the sep
scan loop loudly rejects an unresolvable import (cannot find package
<name>) unless the package is defined inline in the same unit -- matching
the deleted amalgamator and closing the silent-accept the flip would
otherwise introduce.

All 5 wwstage tools relink (each is built via the now-sep `ww build`);
emitted asm is byte-identical to the combined build per bootstrap input,
so the binary md5 delta is pure link layout, not codegen.

selfhost/cmd/ww/main.combined.ww is now stale and unregenerable (its
writer build_one is deleted); #90 deletes it next.

Test retargets folded in (rule-11 carve-out, #61/#133 precedent): each
asserts post-flip-only behavior, is un-pre-migratable unlike #93/#94/#103,
and splitting reddens one side. Closes #97.
- 989_slttypepref -> dir-package layout (xb imports xa so both same-leaf
  `invalid` types are in scope at xb.f); inline-multipackage was the
  amalgamator shape, deleted with the flip.
- 989_sepbuild_run -> run --sep now genuinely runs (exit 7), not the old
  loud-reject (exit 2); + a private per-pid WW_PKGCACHE so the cs/ww
  per-package byte-id compare on the shared real lib pkgs (rt/time/os) no
  longer races concurrent siblings on the global out/.pkgcache (the flip
  made sep the sole path, so every test now contends that cache).
- 737_direnum -> the deleted strictpkgmismatch "differs from" wording ->
  sep's "does not match import path" (shared substring, wwstage terser #68).
- 989_lib_byteid -> corpus-completeness scan excludes generated .sepwork
  scratch (the old `! -name '*.combined.ww'` exclude didn't cover the new
  sep artifact).
This commit is contained in:
2026-06-18 02:51:31 +09:00
parent f98033293e
commit 33edc386f1
6 changed files with 275 additions and 1047 deletions

View File

@@ -181,12 +181,13 @@ fn procrun(path: *u8, argv: **u8) i32 = {
return code;
};
// ---- `use` resolution + source concatenation --------------------------
// ---- import resolution + visited-set -----------------------------------
//
// Recursive expansion: for each `use IDENT;` we find at the top of
// `path`, resolve via the colon-separated `dirs`, expand the imported
// file first, then append our own bytes. Already-visited paths are
// skipped (linear scan; typical builds visit a handful of modules).
// The separate-compilation producer scans each unit's top-of-file
// `import IDENT;` lines and resolves them via the colon-separated `dirs`
// search path. A per-scan visited set (linear; typical builds visit a
// handful of modules) breaks cycles. (E3-C1: the legacy single-file
// source concatenator was deleted — sep is the sole path. Task #87.)
type strnode = struct {
s: str,
@@ -509,307 +510,6 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
return src + idstart, idlen;
};
// expand — emit one file's bytes verbatim into the combined stream,
// after recursive-expanding its top-of-file `import X;` imports.
// Each source declares its own `package <name>;` (parser stamps
// decls).
fn expand(c: *expctx, pathcs: *u8, modpath: str) void = {
let plen: u64 = cstrlen(pathcs);
let view: str;
view.ptr = pathcs;
view.len = plen: i32;
let pathstr: str = strings.dup(view);
if (visitseen(c, pathstr)) { return; };
visitadd(c, pathstr);
let bufp: *u8;
let blen: u64;
bufp, blen = slurp(pathcs);
if (bufp == nil) {
cerr("ww: cannot read source\n");
return;
};
// Pass 1: scan top-of-file `import X;` lines, recursively expand.
let i: u64 = 0u64;
for (i < blen) {
let j: u64 = i;
for (j < blen) {
if (bufp[j] == 10u8) { break; }; // '\n'
j += 1u64;
};
let idp: *u8;
let idn: u64;
idp, idn = scanuse(bufp + i, j - i);
if (idp != nil) {
let isdir: i32 = 0;
let ipath: *u8 = locateimport(c.dirs, idp, idn,
&isdir);
if (ipath != nil) {
// M1 #22 (isdir-gated, rob-ratified): only DIRECTORY
// imports are package boundaries that path-mangle. A
// single-file import (`import opcodes;` → opcodes.ww
// declaring `package w6a`) is an intra-package file-split:
// it keeps its in-file `package` clause as its module
// (no directive → reset → package-clause mangling).
if (isdir != 0) {
let mv: str;
mv.ptr = idp;
mv.len = idn: i32;
let modstr: str = strings.dup(mv);
expanddir(c, ipath, modstr);
} else {
expand(c, ipath, "");
};
} else {
// #16 ENFORCE-driver (rob A): a locate-miss is
// legal when the package is defined INLINE in the
// same unit (single-file multi-package). leaf = the
// last dotted component of the import name; if an
// inline `package <leaf>` exists -> silent skip
// (the checker binds it), else fatal. cstage twin in
// cmd/ww/main.c; fatal text identical.
let lstart: u64 = 0u64;
let lk: u64 = 0u64;
for (lk < idn) {
if (idp[lk] == 46u8) { lstart = lk + 1u64; }; // '.'
lk += 1u64;
};
let leafp: *u8 = idp + lstart;
let leafn: u64 = idn - lstart;
if (!unithaspackage(bufp, blen, leafp, leafn)) {
cerr("ww: cannot find package ");
os.write(2, idp, idn);
cerr("\n");
os.exit(1);
};
};
};
i = j + 1u64;
};
// #16 option-B: a package-less file's decls would otherwise inherit
// the preceding bundled module's sticky curmod (parser parse.ww). Emit
// a curmod-reset boundary directive so the lexer/parser attribute the
// file to the primary module ("") — fixes the self-import false-fire
// and the leaked-prefix bug, codegen-neutral (bare symbols kept; not
// `package main`, which would main-prefix them). A packaged file's own
// `package` decl already sets curmod, so it needs nothing — keeping
// the directive out of every tracked combined.ww. (Task #11.)
if (modpath.len != 0) {
// M1 #22: import-reached file carries its full dotted path so
// codegen mangles symbols on the path, not the leaf clause.
let dm: str = "//ww:module ";
os.writeall(c.out, dm.ptr, dm.len: u64);
os.writeall(c.out, modpath.ptr, modpath.len: u64);
os.writeall(c.out, "\n".ptr, 1u64);
} else {
// Root/primary: always reset so a preceding imported section's
// sticky pathmod is cleared; a packaged primary's own `package`
// clause then sets curmod fresh (pathmod NULL → real clause).
let d: str = "//ww:module-reset\n";
os.writeall(c.out, d.ptr, d.len: u64);
};
os.writeall(c.out, bufp, blen);
os.writeall(c.out, "\n".ptr, 1u64);
};
// Scan `pathcs` for its first non-comment-non-blank line; if it
// starts with `package <name>;` return the package name as a fresh
// heap-allocated NUL-terminated *u8, else nil. Same shape as cstage
// peek_package.
//
// Reads the WHOLE file (via slurp), not a fixed prefix: cstage's
// peek_package scans line-by-line with fgets and no total cap, stopping
// at the first non-comment-non-blank line. A prior 2048-byte read cap
// here diverged from cstage on files whose `package` decl sits behind a
// >2048-byte comment header (strconv decimal/ftos/stof, memio) —
// returning nil and making the #16 D-i injection asymmetric (rule-10
// break, byte-id divergence in the regenerated combined). The corpus has
// no line >2047 chars, so a whole-file line scan matches cstage's
// per-line fgets byte-for-byte on every real input.
fn peekpackage(pathcs: *u8) *u8 = {
let bufp: *u8;
let nu: u64;
bufp, nu = slurp(pathcs);
if (bufp == nil) { return nil; };
let p: u64 = 0u64;
for (p < nu) {
let q: u64 = p;
for (q < nu) {
if (bufp[q] == 10u8) { break; }; // '\n'
q += 1u64;
};
let s: u64 = p;
for (s < q) {
if (bufp[s] != 32u8) {
if (bufp[s] != 9u8) { break; };
};
s += 1u64;
};
if (s < q) {
let line: str;
line.ptr = bufp + s;
line.len = (q - s): i32;
// hasprefix("//") subsumes the old s+1<q guard.
if (strings.hasprefix(line, "//")) {
p = q + 1u64;
continue;
};
// s+8<=q guard kept: hasprefix("package") needs only 7
// bytes, but the sep read at bufp[s+7] needs s+7 < q.
if (s + 8u64 <= q) {
if (strings.hasprefix(line, "package")) {
let sep: u8 = bufp[s + 7u64];
if (sep == 32u8) { }
else { if (sep != 9u8) { return nil; }; };
let t: u64 = s + 8u64;
for (t < q) {
if (bufp[t] != 32u8) {
if (bufp[t] != 9u8) { break; };
};
t += 1u64;
};
let start: u64 = t;
for (t < q) {
let ch: u8 = bufp[t];
let isalpha: bool = false;
if (ch >= 97u8) { if (ch <= 122u8) { isalpha = true; }; };
if (ch >= 65u8) { if (ch <= 90u8) { isalpha = true; }; };
if (ch >= 48u8) { if (ch <= 57u8) { isalpha = true; }; };
if (ch == 95u8) { isalpha = true; };
if (!isalpha) { break; };
t += 1u64;
};
let plen: u64 = t - start;
if (plen == 0u64) { return nil; };
let r: []u8 = alloc([], plen + 1u64)!;
let k: u64 = 0u64;
for (k < plen) { r[k] = bufp[start + k]; k += 1u64; };
r[plen] = 0u8;
return r.ptr;
};
};
return nil;
};
p = q + 1u64;
};
return nil;
};
// unithaspackage — does the unit buffer declare `package <leaf>;` ANYWHERE?
// #16 ENFORCE-driver (rob A) cstage unit_has_package twin: distinguishes a
// genuinely-missing import from one satisfied by an INLINE package in the
// same single-file multi-package unit (`package aa; ... package main;
// import aa;`). Scans EVERY line (comment-skip) — not just the first
// package decl (peekpackage stops there). Decision byte-identical to
// cstage so the skip/fatal choice + driver output match (rule 10).
fn unithaspackage(buf: *u8, buflen: u64, leafp: *u8, leafn: u64) bool = {
let p: u64 = 0u64;
for (p < buflen) {
let q: u64 = p;
for (q < buflen) { if (buf[q] == 10u8) { break; }; q += 1u64; };
let s: u64 = p;
for (s < q) {
if (buf[s] != 32u8) { if (buf[s] != 9u8) { break; }; };
s += 1u64;
};
if (s < q) {
let line: str;
line.ptr = buf + s;
line.len = (q - s): i32;
if (strings.hasprefix(line, "//")) { p = q + 1u64; continue; };
if (s + 8u64 <= q) {
if (strings.hasprefix(line, "package")) {
let sep: u8 = buf[s + 7u64];
let oksep: bool = false;
if (sep == 32u8) { oksep = true; }
else { if (sep == 9u8) { oksep = true; }; };
if (oksep) {
let t: u64 = s + 8u64;
for (t < q) {
if (buf[t] != 32u8) { if (buf[t] != 9u8) { break; }; };
t += 1u64;
};
let m: u64 = 0u64;
let eq: bool = true;
for (m < leafn) {
if (t + m >= q) { eq = false; break; };
if (buf[t + m] != leafp[m]) { eq = false; break; };
m += 1u64;
};
if (eq) {
let after: u64 = t + leafn;
let term: bool = false;
if (after >= q) { term = true; }
else {
let c: u8 = buf[after];
if (c == 59u8) { term = true; } // ';'
else { if (c == 32u8) { term = true; }
else { if (c == 9u8) { term = true; }; }; };
};
if (term) { return true; };
};
};
};
};
};
p = q + 1u64;
};
return false;
};
// Strict-same-package error helper. Bundled here per task #22
// brief — failure mode is dir-enum's own.
fn strictpkgmismatch(file: *u8, pkg: *u8, dirpkg: *u8, dirpath: *u8) void = {
cerr("ww: ");
os.write(2, file, cstrlen(file));
cerr(": package ");
os.write(2, pkg, cstrlen(pkg));
cerr(" differs from ");
os.write(2, dirpkg, cstrlen(dirpkg));
cerr(" in same module dir ");
os.write(2, dirpath, cstrlen(dirpath));
cerr("\n");
os.exit(1);
};
// expanddir — enumerate <dirpath>/*.ww (skip *test.ww and
// *.combined.ww), byte-sort, recurse into each. Mirrors
// ref/hare/hare/module/srcs.ha:183 `_findsrcs` minus tag handling.
// The visited set keys on concrete file paths so multi-file modules
// are pulled once. Strict-same-package: all enumerated files must
// declare the same `package <name>;` (task #23 subset; failure
// mode native to dir-enum).
fn expanddir(c: *expctx, dirpath: *u8, modpath: str) void = {
let names: **u8;
let n: i32;
names, n = enumeratedir(dirpath);
let dlen: u64 = cstrlen(dirpath);
let dirpkg: *u8 = nil;
let i: i32 = 0;
for (i < n) {
let nlen: u64 = cstrlen(names[i]);
let fp: []u8 = alloc([], dlen + 1u64 + nlen + 1u64)!;
let k: u64 = 0u64;
for (k < dlen) { fp[k] = dirpath[k]; k += 1u64; };
fp[dlen] = 47u8; // '/'
k = 0u64;
for (k < nlen) { fp[dlen + 1u64 + k] = names[i][k]; k += 1u64; };
fp[dlen + 1u64 + nlen] = 0u8;
let pkg: *u8 = peekpackage(fp.ptr);
if (pkg != nil) {
if (dirpkg == nil) { dirpkg = pkg; }
else { if (!cstreq(dirpkg, pkg)) {
strictpkgmismatch(fp.ptr, pkg, dirpkg, dirpath);
}; };
};
expand(c, fp.ptr, modpath);
i += 1;
};
};
// ---- Build pipeline ---------------------------------------------------
// Strip the trailing ".ww" off `src` (a NUL-terminated path) into
@@ -933,6 +633,68 @@ fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
return buf.ptr;
};
// unithaspackage — does the unit buffer declare `package <leaf>;` ANYWHERE?
// #16 ENFORCE-driver (rob A) cstage unit_has_package twin: distinguishes a
// genuinely-missing import from one satisfied by an INLINE package in the
// same single-file multi-package unit (`package aa; ... package main;
// import aa;`). Scans EVERY line (comment-skip) — not just the first
// package decl (peekpackage stops there). Decision byte-identical to
// cstage so the skip/fatal choice + driver output match (rule 10).
fn unithaspackage(buf: *u8, buflen: u64, leafp: *u8, leafn: u64) bool = {
let p: u64 = 0u64;
for (p < buflen) {
let q: u64 = p;
for (q < buflen) { if (buf[q] == 10u8) { break; }; q += 1u64; };
let s: u64 = p;
for (s < q) {
if (buf[s] != 32u8) { if (buf[s] != 9u8) { break; }; };
s += 1u64;
};
if (s < q) {
let line: str;
line.ptr = buf + s;
line.len = (q - s): i32;
if (strings.hasprefix(line, "//")) { p = q + 1u64; continue; };
if (s + 8u64 <= q) {
if (strings.hasprefix(line, "package")) {
let sep: u8 = buf[s + 7u64];
let oksep: bool = false;
if (sep == 32u8) { oksep = true; }
else { if (sep == 9u8) { oksep = true; }; };
if (oksep) {
let t: u64 = s + 8u64;
for (t < q) {
if (buf[t] != 32u8) { if (buf[t] != 9u8) { break; }; };
t += 1u64;
};
let m: u64 = 0u64;
let eq: bool = true;
for (m < leafn) {
if (t + m >= q) { eq = false; break; };
if (buf[t + m] != leafp[m]) { eq = false; break; };
m += 1u64;
};
if (eq) {
let after: u64 = t + leafn;
let term: bool = false;
if (after >= q) { term = true; }
else {
let c: u8 = buf[after];
if (c == 59u8) { term = true; } // ';'
else { if (c == 32u8) { term = true; }
else { if (c == 9u8) { term = true; }; }; };
};
if (term) { return true; };
};
};
};
};
};
p = q + 1u64;
};
return false;
};
// Scan one source file for top-level `import IDENT;`. A DIRECTORY import
// is a package boundary: add as a direct dep of pi. A FILE import is an
// intra-package split: fold its imports into pi. Mirrors cstage
@@ -989,6 +751,29 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
return -1;
};
};
} else {
// #16 ENFORCE-driver (rob A): a locate-miss is legal
// when the package is defined INLINE in the same unit
// (single-file multi-package). leaf = last dotted
// component; inline `package <leaf>` -> skip (the
// checker binds it), else fatal. E3-C1: the combined
// path that owned the genuine-missing case is gone, so
// the sep producer enforces it here (INV-2). cstage
// twin; fatal text identical.
let lstart: u64 = 0u64;
let lk: u64 = 0u64;
for (lk < idn) {
if (idp[lk] == 46u8) { lstart = lk + 1u64; }; // '.'
lk += 1u64;
};
let leafp: *u8 = idp + lstart;
let leafn: u64 = idn - lstart;
if (!unithaspackage(bufp, blen, leafp, leafn)) {
cerr("ww: cannot find package ");
os.write(2, idp, idn);
cerr("\n");
os.exit(1);
};
};
};
i = j + 1u64;
@@ -1786,235 +1571,6 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
return 0;
};
// buildone — compile `src` (file or directory) into the executable
// named `out`.
// selfdir: NUL-terminated dir containing this driver and the
// wwstage tools (w6c_ww/w6a_ww/w6l_ww)
// src: NUL-terminated entry path (file or directory).
// entryisdir: non-zero when src is a module directory.
// out: NUL-terminated desired output path
// objstem: when non-nil, redirects the .s/.o/.combined.ww side
// files to live beside this stem instead of next to the
// source (T3 / task #15). `ww run` and `ww build -o` pass
// it so concurrent builds never share next-to-source fixed
// paths; nil keeps the old next-to-source layout (the make
// tracked-combined.ww regen + #110 gate depend on it). Twin
// of cmd/ww/main.c build_one's objstem.
// incs: NUL-terminated colon-list of -I dirs (may be empty)
// lf: extra linker flags (-L<dir>, -l<name>); may be nil
//
// The ww-side driver shells to the ww-side tools so a `ww_ww build`
// touches no C-built code at runtime. The C `ww` driver in cmd/ww/
// still drives the C-built w6c/w6a/w6l. Test 993 pins the two
// pipelines to byte-identical output on a corpus.
fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, incs: *u8, lf: *lflags, istest: i32) i32 = {
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
// Default lib search path: <selfdir>/../../lib
let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!;
dotdotlib.len = os.PATH_MAX;
{
let off: u64 = cstrinto(dotdotlib.ptr, 0u64, selfdir);
off = strinto(dotdotlib.ptr, off, "/../../lib");
cstrseal(dotdotlib.ptr, off);
};
// Compute the source directory. For a file entry: bytes of `src`
// up to the last '/' (or "." when src has no '/'). For a dir
// entry: the dir itself (less trailing slashes). Hare's CWD-first
// convention assumes you're running from the module dir; our
// wrappers don't cd, so dirname(src) stands in as the closest
// analog. Source-dir wins ties over the system path (cc -I.).
let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!;
srcd.len = os.PATH_MAX;
if (entryisdir != 0) {
let slen: u64 = cstrlen(src);
let k: u64 = 0u64;
for (k < slen) { srcd[k] = src[k]; k += 1u64; };
for (slen > 1u64) {
if (srcd[slen - 1u64] != 47u8) { break; };
slen -= 1u64;
};
srcd[slen] = 0u8;
} else {
let slen: u64 = cstrlen(src);
let last: u64 = slen;
let found: bool = false;
let i: u64 = slen;
for (i > 0u64) {
i -= 1u64;
if (src[i] == 47u8) { // '/'
last = i;
found = true;
i = 0u64;
};
};
if (found) {
let k: u64 = 0u64;
for (k < last) { srcd[k] = src[k]; k += 1u64; };
srcd[last] = 0u8;
} else {
srcd[0] = 46u8; // '.'
srcd[1] = 0u8;
};
};
// Compose searchpath: srcd + ':' + incs + ':' + dotdotlib.
let searchpath: []u8 = alloc([], (os.PATH_MAX: u64) * 3u64)!;
searchpath.len = ((os.PATH_MAX: u64) * 3u64): i32;
{
let off: u64 = cstrinto(searchpath.ptr, 0u64, srcd.ptr);
off = byteinto(searchpath.ptr, off, 58u8); // ':'
if (incs[0u64] != 0u8) {
off = cstrinto(searchpath.ptr, off, incs);
off = byteinto(searchpath.ptr, off, 58u8); // ':'
};
off = cstrinto(searchpath.ptr, off, dotdotlib.ptr);
cstrseal(searchpath.ptr, off);
};
// Stem for .s/.o/.combined.ww side files. Dir entry: <dir>/<base>;
// file entry: src stripped of .ww.
let stem: []u8 = alloc([], (os.PATH_MAX: u64))!;
stem.len = os.PATH_MAX;
if (entryisdir != 0) {
let dlen: u64 = cstrlen(srcd.ptr);
let bo: u64 = basenameoff(srcd.ptr, dlen);
let off: u64 = cstrinto(stem.ptr, 0u64, srcd.ptr);
stem[off] = 47u8; off += 1u64; // '/'
let i: u64 = bo;
for (i < dlen) { stem[off] = srcd[i]; off += 1u64; i += 1u64; };
cstrseal(stem.ptr, off);
} else {
makestem(stem.ptr, src);
};
let effstem: *u8 = stem.ptr;
if (objstem != nil) { effstem = objstem; };
let asmf: *u8 = appendlit(effstem, ".s");
let objf: *u8 = appendlit(effstem, ".o");
let combined: *u8 = appendlit(effstem, ".combined.ww");
// libwwrt.a path: <selfdir>/../lib/libwwrt.a
let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!;
libwwrt.len = os.PATH_MAX;
{
let off: u64 = cstrinto(libwwrt.ptr, 0u64, selfdir);
off = strinto(libwwrt.ptr, off, "/../lib/libwwrt.a");
cstrseal(libwwrt.ptr, off);
};
// Step 1: expand imports into the combined file. Dir entry →
// enumerate the module dir; file entry → start at the file.
let cf: i32 = os.open(pathstr(combined), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
if (cf < 0) {
cerr("ww: cannot open combined\n");
return 1;
};
{
let c: expctx;
c.out = cf;
c.dirs = searchpath.ptr;
c.visit = nil;
// #17 auto-bundle lib/test: the -T synth's main calls lib/test's
// run(), but @test files don't `import test;`. Pull it like an
// implicit import through the same locate+expand path (the visit
// set dedupes a fixture that imports it explicitly). cstage twin
// in cmd/ww/main.c buildone.
if (istest != 0) {
let td: i32 = 0;
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
if (tp != nil) {
if (td != 0) { expanddir(&c, tp, "test"); }
else { expand(&c, tp, ""); };
};
};
if (entryisdir != 0) { expanddir(&c, srcd.ptr, ""); }
else { expand(&c, src, ""); };
};
os.close(cf);
// Step 2: w6c [-T] -o <stem>.s <stem>.combined.ww
{
let argv: []*u8 = alloc([], 6u64)!;
let p: i32 = 0;
argv[p] = "w6c\0".ptr; p += 1;
if (istest != 0) { argv[p] = "-T\0".ptr; p += 1; };
argv[p] = "-o\0".ptr; p += 1;
argv[p] = asmf; p += 1;
argv[p] = combined; p += 1;
argv[p] = nil; p += 1;
argv.len = p;
if (procrun(c6, argv.ptr) != 0) {
cerr("ww: w6c failed\n");
return 1;
};
};
// Step 3: w6a -o <stem>.o <stem>.s
{
let argv: []*u8 = alloc([], 5u64)!;
argv.len = 5;
argv[0] = "w6a\0".ptr;
argv[1] = "-o\0".ptr;
argv[2] = objf;
argv[3] = asmf;
argv[4] = nil;
if (procrun(a6, argv.ptr) != 0) {
cerr("ww: w6a failed\n");
return 1;
};
};
// Step 4: w6l -o <out> <stem>.o libwwrt.a [-L<dir>...] [-l<name>...]
{
let nldirs: i32 = 0;
let nllibs: i32 = 0;
let ldirs: **u8 = nil;
let llibs: **u8 = nil;
if (lf != nil) {
nldirs = lf.nlibdirs;
nllibs = lf.nlibs;
ldirs = lf.libdirs;
llibs = lf.libs;
};
// argv slots: 5 fixed (w6l, -o, out, objf, libwwrt)
// + 2 * nlibdirs (-L, dir)
// + 2 * nlibs (-l, name)
// + 1 nil terminator.
let total: i32 = 5 + 2 * nldirs + 2 * nllibs + 1;
let argv: []*u8 = alloc([], total: u64)!;
argv.len = total;
argv[0] = "w6l\0".ptr;
argv[1] = "-o\0".ptr;
argv[2] = out;
argv[3] = objf;
argv[4] = libwwrt.ptr;
let pos: i32 = 5;
let k: i32 = 0;
for (k < nldirs) {
argv[pos] = "-L\0".ptr;
argv[pos + 1] = ldirs[k];
pos += 2;
k += 1;
};
k = 0;
for (k < nllibs) {
argv[pos] = "-l\0".ptr;
argv[pos + 1] = llibs[k];
pos += 2;
k += 1;
};
argv[pos] = nil;
if (procrun(l6, argv.ptr) != 0) {
cerr("ww: w6l failed\n");
return 1;
};
};
return 0;
};
// ---- Module-by-name resolution ----------------------------------------
//
// Mirrors cmd/ww/main.c:resolvemodule. Maps a name like "foo", "lib/foo",
@@ -2154,7 +1710,6 @@ fn defaultoutpath(src: *u8) *u8 = {
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let src: *u8 = nil;
let wantsep: i32 = 0; // --sep: M3-tail separate-compilation path
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
@@ -2173,8 +1728,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
for (i < argc) {
let p: *u8 = argv[i];
if (p[0u64] == 45u8) { // '-'
if (cstreqlit(p, "--sep")) { // M3-tail separate-compile
wantsep = 1;
if (cstreqlit(p, "--sep")) { // E3-C1: sep is sole path; accepted no-op (#87)
} else { if (p[1u64] == 73u8) { // '-I'
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
@@ -2290,10 +1844,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.nlibdirs = nlibdirs;
lf.libs = libs.ptr;
lf.nlibs = nlibs;
if (wantsep != 0) {
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
};
return buildone(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
};
// Format the scratch path /tmp/ww_run_<pid> into buf. Returns NUL-
@@ -2348,9 +1899,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
else {
let p: *u8 = argv[i];
if (p[0u64] == 45u8) {
if (cstreqlit(p, "--sep")) { // build-only; run rejects (rule 10)
cerr("ww run: --sep is only valid with build\n");
return 2;
if (cstreqlit(p, "--sep")) { // E3-C1: sep is sole path; accepted no-op (#87)
} else { if (p[1u64] == 73u8) {
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
@@ -2452,7 +2001,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.nlibs = nlibs;
// objstem = tmp → intermediates at /tmp/ww_run_<pid>.{s,o,combined.ww},
// never next to the source (T3).
if (buildone(selfdir, resolved, isdir, tmp.ptr, tmp.ptr, incs.ptr, &lf, 0i32) != 0) {
if (buildonesep(selfdir, resolved, isdir, tmp.ptr, tmp.ptr, incs.ptr, &lf, 0i32) != 0) {
os.remove(pathstr(tmp.ptr));
return 1;
};
@@ -2482,7 +2031,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
// directory: open the dir, getdents64, build+run each *_test.ww,
// report ok/FAIL per file, return 0 iff all pass.
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8, pattern: *u8, usesep: i32) i32 = {
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8, pattern: *u8) i32 = {
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
tmp.len = os.PATH_MAX;
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
@@ -2496,19 +2045,13 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *
makeruntmp(tmp.ptr);
outp = tmp.ptr;
};
// #79 E1: --sep routes the test build through the separate-compilation
// producer (buildonesep, istest=1) instead of the amalgamator.
let bres: i32 = 0;
if (usesep != 0) {
let lf: lflags;
lf.libdirs = nil;
lf.nlibdirs = 0;
lf.libs = nil;
lf.nlibs = 0;
bres = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf, 1i32);
} else {
bres = buildone(selfdir, src, 0, outp, objstem, incs, nil, 1i32);
};
// E3-C1: separate compilation is the sole build path (task #87).
let lf: lflags;
lf.libdirs = nil;
lf.nlibdirs = 0;
lf.libs = nil;
lf.nlibs = 0;
let bres: i32 = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf, 1i32);
if (bres != 0) {
if (outstem == nil) { os.remove(pathstr(outp)); };
return 1;
@@ -2576,7 +2119,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
tmp.len = os.PATH_MAX;
makeruntmp(tmp.ptr);
let bres: i32 = buildone(selfdir, path.ptr, 0, tmp.ptr, nil, tincs.ptr, nil, 1i32);
let bres: i32 = buildonesep(selfdir, path.ptr, 0, tmp.ptr, nil, tincs.ptr, nil, 1i32);
if (bres != 0) {
fail += 1;
cerr("FAIL ");
@@ -2639,16 +2182,11 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
// twin: cmd/ww/main.c do_test (error wording identical).
let compileonly: i32 = 0;
let outstem: *u8 = nil;
// #79 E1: --sep routes a single-file test through the separate-
// compilation producer (buildonesep, istest=1). Dir-mode --sep is
// deferred to E2. cstage twin: do_test use_sep.
let usesep: i32 = 0;
let i: i32 = start;
for (i < argc) {
let p: *u8 = argv[i];
if (p[0u64] == 45u8) { // '-'
if (cstreqlit(p, "--sep")) { // #79: separate-compile test
usesep = 1;
if (cstreqlit(p, "--sep")) { // E3-C1: sep is sole path; accepted no-op (#87)
} else { if (p[1u64] == 73u8) { // '-I'
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
@@ -2698,7 +2236,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
// single-file mode: literal *.ww that exists
if (cstrendswithlit(target, ".ww")) {
if (os.access(pathstr(target), 0i32) == 0) {
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem, patarg, usesep);
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem, patarg);
};
};
@@ -2706,11 +2244,6 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
cerr("ww test: -c/-o need a single test file\n");
return 2;
};
// #79 E1: dir-mode --sep deferred to E2; single-file proves the path.
if (usesep != 0) {
cerr("ww test: --sep needs a single test file\n");
return 2;
};
// #17: a name-filter pattern is per-binary; dir mode builds one binary
// per *_test.ww, so a single pattern can't route. cstage twin parity.
if (patarg != nil) {