ww: driver shells to wwstage tools

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.
This commit is contained in:
2026-05-11 11:20:23 +09:00
parent 218d8469ff
commit 502b304841
7 changed files with 272 additions and 43 deletions

View File

@@ -6,6 +6,7 @@
@symbol("rt_syscall") fn syscall1(num: i64, a: i64) i64;
@symbol("rt_syscall") fn syscall2(num: i64, a: i64, b: i64) i64;
@symbol("rt_syscall") fn syscall3(num: i64, a: i64, b: i64, c: i64) i64;
@symbol("rt_syscall") fn syscall4(num: i64, a: i64, b: i64, c: i64, d: i64) i64;
@symbol("rt_alloc") fn alloc(n: u64) *void;
@symbol("rt_free") fn free(p: *void, n: u64) void;
@@ -17,12 +18,18 @@ export fn assert(cond: bool, msg: str) void = {
if (!cond) { abort(msg); };
};
def SYS_READ: i64 = 0;
def SYS_WRITE: i64 = 1;
def SYS_OPEN: i64 = 2;
def SYS_CLOSE: i64 = 3;
def SYS_LSEEK: i64 = 8;
def SYS_EXIT: i64 = 60;
def SYS_READ: i64 = 0;
def SYS_WRITE: i64 = 1;
def SYS_OPEN: i64 = 2;
def SYS_CLOSE: i64 = 3;
def SYS_LSEEK: i64 = 8;
def SYS_ACCESS: i64 = 21;
def SYS_GETPID: i64 = 39;
def SYS_FORK: i64 = 57;
def SYS_EXECVE: i64 = 59;
def SYS_EXIT: i64 = 60;
def SYS_WAIT4: i64 = 61;
def SYS_UNLINK: i64 = 87;
// open(2) flags. Linux values, matching <fcntl.h>.
def O_RDONLY: i32 = 0;
@@ -127,6 +134,42 @@ export fn writefull(fd: i32, buf: *u8, n: u64) i64 = {
return sent: i64;
};
// ---- process and filesystem helpers used by the `ww` driver ----------
// access(2): returns 0 if the file is reachable, negative errno
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
export fn access(path: *u8, mode: i32) i32 = {
return syscall2(SYS_ACCESS, path: i64, mode: i64): i32;
};
// unlink(2).
export fn unlink(path: *u8) i32 = {
return syscall1(SYS_UNLINK, path: i64): i32;
};
// getpid(2). Used by the driver to mint unique scratch paths.
export fn getpid() i32 = {
return syscall0(SYS_GETPID): i32;
};
// fork(2): 0 in the child, child pid in the parent, negative errno
// on failure.
export fn fork() i32 = {
return syscall0(SYS_FORK): i32;
};
// execve(2): on success, does not return.
export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = {
return syscall3(SYS_EXECVE, path: i64, argv: i64, envp: i64): i32;
};
// wait4(2): wait for `pid` (or any child if -1), store status in
// `*status_out`, return the pid that ended (or negative errno).
export fn wait4(pid: i32, status_out: *i32, options: i32, rusage: *void) i32 = {
return syscall4(SYS_WAIT4, pid: i64, status_out: i64,
options: i64, rusage: i64): i32;
};
// selfhost/cmd/wwc/mem.ww — port of cmd/wwc/mem.c.
//
// Bump arena allocator. Backed by the runtime page allocator