build: pass direct exports to package compilers

This commit is contained in:
2026-08-12 18:42:22 +09:00
parent b373c445bd
commit edd3f4ee83
13 changed files with 678 additions and 611 deletions

View File

@@ -741,16 +741,12 @@ type lflags = struct {
// every package on its own (`w6c -c`), then flat-links the `.o` set.
// Separate compilation is the SOLE build path (E3-C1 flip, task #87).
//
// Each w6c pass is BOTH consumer (reads dep `.wwi` as import scope) AND
// producer (writes this package's `.wwi` via -I). Reverse-topo order
// guarantees a package's deps' `.wwi` exist before it compiles.
//
// Every direct dep is tagged by its FULL DOTTED import path on prepend
// (`//ww:module <path>`), so the definer's qualified symbol equals the
// consumer's qualified reference and the sep `.o`s link. A dependency's
// compiler-owned `.wwi` carries its reachable public foreign type facts;
// separately prepending transitive interfaces is neither necessary nor
// allowed. The unit composition is byte-identical to the cstage driver.
// Each w6c pass is BOTH consumer (reads each direct dep `.wwi` through a
// separate --import argument) AND producer (writes this package's `.wwi`
// via -I). Reverse-topo order guarantees a package's deps' `.wwi` exist.
// The canonical import path paired with each self-contained export preserves
// qualified symbol identity; transitive exports remain outside the compile
// action. Unit composition is byte-identical to the cstage driver.
def SEP_MAXPKG: i32 = 256;
@@ -775,6 +771,7 @@ type seppkg = struct {
failed: bool,
testsupport: bool,
loaded: bool,
exportchanged: bool,
emitcontext: i32,
contextstate: []u8,
bindings: []sepbind,
@@ -929,6 +926,7 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
g.pkg[g.n].failed = false;
g.pkg[g.n].testsupport = false;
g.pkg[g.n].loaded = false;
g.pkg[g.n].exportchanged = false;
g.pkg[g.n].emitcontext = -1;
let cslot: []u8 = alloc([], SEP_MAXCONTEXT: u64)!;
cslot.len = SEP_MAXCONTEXT;
@@ -1546,8 +1544,8 @@ fn sepvalidatemoduleclosure(g: *sepgraph, order: []i32, n: i32,
return 0;
};
// Imported source never enters this body: its direct export data was
// prepended above, while this package owns exactly its sorted source set.
// No imported source or export enters this body: the package owns exactly its
// sorted source set.
fn sepwriteall(fd: i32, buf: *u8, n: u64) bool = {
match (os.writeall(fd, buf, n)) {
case let wrote: i64 => return wrote == n: i64;
@@ -1589,12 +1587,9 @@ fn sepemitbody(fd: i32, path: *u8, modpath: *u8) i32 = {
return 0;
};
// Compose pi's sep-unit at `unitf`: its byte-sorted DIRECT dependency
// `.wwi`s, each tagged by its dotted path, then pi's own body under
// //ww:module-reset. Compiler exports are self-contained for public type
// facts; the linker separately retains the reachable archive closure.
fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8,
unitf: *u8) i32 = {
// Compose pi's sep-unit from only pi's byte-sorted sources. Direct exports are
// separate compiler inputs; the linker retains the reachable archive closure.
fn sepcomposeunit(g: *sepgraph, pi: i32, unitf: *u8) i32 = {
if (g.pkg[pi].emitcontext < 0
|| g.pkg[pi].emitcontext >= g.ncontext) { return -1; };
let u: i32 = os.open(pathstr(unitf), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
@@ -1602,31 +1597,6 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8,
cerr("ww: cannot open unit\n");
return -1;
};
let k: i32 = 0;
for (k < g.pkg[pi].ndeps) {
let dj: i32 = g.pkg[pi].deps[k];
let wwi: *u8 = sepfname(g, dj, scratch, ".wwi");
let wb: *u8;
let wn: u64;
wb, wn = slurp(wwi);
if (wb == nil) {
cerr("ww: missing wwi\n");
os.close(u);
return -1;
};
let dm: str = "//ww:module ";
if (!sepwriteall(u, dm.ptr, dm.len: u64)
|| !sepwriteall(u, g.pkg[dj].path,
cstrlen(g.pkg[dj].path))
|| !sepwriteall(u, "\n".ptr, 1u64)
|| !sepwriteall(u, wb, wn)
|| !sepwriteall(u, "\n".ptr, 1u64)) {
cerr("ww: cannot compose package unit\n");
os.close(u);
return -1;
};
k += 1;
};
let bodyrc: i32 = 0;
if (g.pkg[pi].isdir != 0) {
let i: i32 = 0;
@@ -1720,7 +1690,7 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = {
};
// buildonesep — discover deps, reverse-topo,
// the transitive producer loop (one `w6c -c -I` per package, dep-first,
// the dependency-first producer loop (one `w6c -c -I` per package,
// each dependency `.o` wrapped in its own deterministic per-package `.a`), then a
// reverse-topo `w6l` of the root `.o` + dependency `.a` set + libwwrt.a.
// Side files land in a cold `<stem>.sepwork` scratch dir. Twin of cstage
@@ -1728,9 +1698,10 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = {
// A `-w DIR` workdir is a caller-owned persistent package-artifact tree
// that replaces the fresh `.sepwork` scratch. Staleness is pure content
// identity, never mtime: a package is reused only when its freshly
// composed unit byte-equals the committed unit AND the driver/tool copies
// recorded in the dir byte-equal the live executables — every decision is
// identity, never mtime: a package is reused only when its freshly composed
// unit byte-equals the committed unit, no direct dependency emitted a changed
// export, AND the driver/tool copies recorded in the dir byte-equal the live
// executables — every decision is
// reproducible by hand with cmp(1) against plain files. Artifacts commit
// via temp + rename with the unit renamed last, so a killed build can
// never leave a committed unit vouching for uncommitted artifacts. The
@@ -1840,14 +1811,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
fn workdirstamptext(istest: i32, emitasm: i32) str = {
if (istest != 0) {
if (emitasm != 0) {
return "ww workdir fmt 6 mode test asm 1\n";
return "ww workdir fmt 7 mode test asm 1\n";
};
return "ww workdir fmt 6 mode test asm 0\n";
return "ww workdir fmt 7 mode test asm 0\n";
};
if (emitasm != 0) {
return "ww workdir fmt 5 mode build asm 1\n";
return "ww workdir fmt 6 mode build asm 1\n";
};
return "ww workdir fmt 5 mode build asm 0\n";
return "ww workdir fmt 6 mode build asm 0\n";
};
fn stampmatches(path: *u8, want: str) bool = {
@@ -2338,15 +2309,23 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
};
let needsexport: bool = !g.pkg[pi].root || rootpackage;
let needsarchive: bool = !g.pkg[pi].root || rootpackage;
if (sepcomposeunit(g, pi, scratch, cu) < 0) {
if (sepcomposeunit(g, pi, cu) < 0) {
g.pkg[pi].failed = true;
anyfailed = true;
oi += 1;
continue;
};
let depschanged: bool = false;
let changedk: i32 = 0;
for (changedk < g.pkg[pi].ndeps) {
if (g.pkg[g.pkg[pi].deps[changedk]].exportchanged) {
depschanged = true;
};
changedk += 1;
};
let fresh: bool = false;
if (warm) {
if (!staleall) {
if (!staleall && !depschanged) {
fresh = fileequal(unitnew, unitf);
if (fresh) { fresh = fileisreg(asmf); };
if (fresh) {
@@ -2390,6 +2369,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
let alen: u64 = 8u64;
if (!needsexport) { alen = 6u64; if (roott) { alen = 9u64; }; };
if (supportt) { alen += 2u64; };
alen += (g.pkg[pi].ndeps: u64) * 3u64;
let argv: []str = alloc([], alen)!;
append(argv, "w6c");
if (roott) {
@@ -2402,6 +2382,14 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
append(argv, testsupportmodule);
};
append(argv, "-c");
let importk: i32 = 0;
for (importk < g.pkg[pi].ndeps) {
let dj: i32 = g.pkg[pi].deps[importk];
append(argv, "--import");
append(argv, pathstr(g.pkg[dj].path));
append(argv, pathstr(sepfname(g, dj, scratch, ".wwi")));
importk += 1;
};
if (needsexport) {
append(argv, "-I");
append(argv, pathstr(cw));
@@ -2430,6 +2418,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
continue;
};
};
if (needsexport) {
if (!warm || !fileequal(wwinew, wwi)) {
g.pkg[pi].exportchanged = true;
};
};
if (emitasm == 0) {
let argv: []str = alloc([], 4u64)!;
append(argv, "w6a");