// MODULE: os // os — process and filesystem facade. The body of each call lands // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. @symbol("rt_syscall") fn syscall0(num: nr) i64; @symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64; @symbol("rt_syscall") fn syscall2(num: nr, a: i64, b: i64) i64; @symbol("rt_syscall") fn syscall3(num: nr, a: i64, b: i64, c: i64) i64; @symbol("rt_syscall") fn syscall4(num: nr, 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; @symbol("rt_abort") fn abort(msg: str) void; // Hare-style runtime check. Caller passes a message that's printed // to stderr before exit(1). export fn assert(cond: bool, msg: str) void = { if (!cond) { abort(msg); }; }; // Linux amd64 syscall numbers. Internal to this module — passed as // the first arg of syscall0..4 via libwwrt's rt_syscall trampoline. // `nr` is the type so the call sites can't accidentally pass an // arbitrary i64 (`syscall1(0i64, ...)` no longer typechecks). type nr = enum i64 { READ = 0, WRITE = 1, OPEN = 2, CLOSE = 3, LSEEK = 8, ACCESS = 21, DUP2 = 33, GETPID = 39, FORK = 57, EXECVE = 59, EXIT = 60, WAIT4 = 61, UNLINK = 87, GETCWD = 79, GETDENTS64 = 217, }; // open(2) flags. Linux values, matching . Hare names them // `fs::flag::RDONLY` etc; we use the same leaf names so callers say // `os.flag.RDONLY` and `os.flag.WRONLY | os.flag.CREATE`. export type flag = enum i32 { RDONLY = 0, WRONLY = 1, RDWR = 2, CREATE = 64, // 0x40 TRUNC = 512, // 0x200 }; // lseek(2) whence. Hare names it `io::whence`. export type whence = enum i32 { SET = 0, CUR = 1, END = 2, }; export fn exit(code: i32) void = { syscall1(nr.EXIT, code: i64); }; // Raw, non-fallible primitives. These return Linux's int conventions // (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a // Hare-style fallible API use the wrappers below. export fn write(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(nr.WRITE, fd: i64, buf: i64, n: i64); }; export fn read(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(nr.READ, fd: i64, buf: i64, n: i64); }; export fn close(fd: i32) i32 = { return syscall1(nr.CLOSE, fd: i64): i32; }; // dup2(2): make `newfd` refer to the same description as `oldfd`, // closing `newfd` first if open. Returns `newfd` on success or a // negative errno. Used by w6c_ww to redirect stdout into an output // file without changing the cgen emit path. export fn dup2(oldfd: i32, newfd: i32) i32 = { return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32; }; // Fallible wrappers. The error variant is `oserror` (an i64 carrying // -errno). The sum type makes success/failure explicit and lets // callers `?` the result up the stack. export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { let r: i64 = read(fd, buf, n); if (r < 0) { return r: oserror; }; return r; }; export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { let r: i64 = write(fd, buf, n); if (r < 0) { return r: oserror; }; return r; }; // open — Linux open(2). Path must be NUL-terminated; callers using ww // `str` must ensure the bytes are followed by a 0 byte (literals are, // arena-copied paths usually are by construction). Returns -errno on // failure, fd otherwise. Higher-level callers prefer `tryopen`. export fn open(path: *u8, flags: flag, mode: i32) i32 = { return syscall3(nr.OPEN, path: i64, (flags as i32): i64, mode: i64): i32; }; export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = { let fd: i32 = open(path, flags, mode); if (fd < 0) { return fd: i64: oserror; }; return fd; }; // lseek — set/inspect the fd's position. Returns the new offset or // a negative errno. We use this for fstat-free file-size discovery // (open ⇒ lseek to end ⇒ lseek back). export fn lseek(fd: i32, off: i64, w: whence) i64 = { return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64); }; // oserror — the underlying errno from a failed syscall, as a // negative i64 (Linux's int convention; e.g. -2 = ENOENT). The // `!`-flagged alias makes ?-propagation pick this variant as the // error half of any (T | oserror) shape. Hare's analogue is // errors::errno carried inside io::error. export type oserror = !i64; // filesize — byte length of an open fd via lseek-to-end-and-back. export fn filesize(fd: i32) (i64 | oserror) = { let end: i64 = lseek(fd, 0i64, whence.END); if (end < 0) { return end: oserror; }; let r: i64 = lseek(fd, 0i64, whence.SET); if (r < 0) { return r: oserror; }; return end; }; // readall — keep reading until `n` bytes have arrived or the fd // closes early. Hare name (io::readall); the buffer is caller- // supplied, matching the Plan 9 subset convention. export fn readall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { let got: u64 = 0u64; for (got < n) { let r: i64 = read(fd, buf + got, n - got); if (r < 0) { return r: oserror; }; if (r == 0) { return got: i64; }; // short read: caller decides got += r: u64; }; return got: i64; }; // writeall — keep writing until `n` bytes have been accepted or the // fd refuses progress. Hare name (io::writeall). export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { let sent: u64 = 0u64; for (sent < n) { let r: i64 = write(fd, buf + sent, n - sent); if (r < 0) { return r: oserror; }; if (r == 0) { return sent: i64; }; sent += r: u64; }; 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 (F_OK=0). export fn access(path: *u8, mode: i32) i32 = { return syscall2(nr.ACCESS, path: i64, mode: i64): i32; }; // remove — unlink(2). Hare name; the underlying syscall is unlink(2). export fn remove(path: *u8) i32 = { return syscall1(nr.UNLINK, path: i64): i32; }; // getpid(2). Used by the driver to mint unique scratch paths. export fn getpid() i32 = { return syscall0(nr.GETPID): i32; }; // fork(2): 0 in the child, child pid in the parent, negative errno // on failure. export fn fork() i32 = { return syscall0(nr.FORK): i32; }; // execve(2): on success, does not return. export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = { return syscall3(nr.EXECVE, path: i64, argv: i64, envp: i64): i32; }; // wait4(2): wait for `pid` (or any child if -1), store status in // `*status`, return the pid that ended (or negative errno). export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = { return syscall4(nr.WAIT4, pid: i64, status: i64, options: i64, rusage: i64): i32; }; // getcwd(2) — Linux flavour. Writes the NUL-terminated cwd into `buf` // and returns the number of bytes written (including the NUL), or a // negative errno. The driver uses it to expand `.` to the cwd's // basename for `ww build` / `ww test`. export fn getcwd(buf: *u8, n: u64) i64 = { return syscall2(nr.GETCWD, buf: i64, n: i64); }; // getdents64(2) — Linux directory enumeration. The fd must be opened // with O_RDONLY on a directory. `buf` receives a packed sequence of // linux_dirent64 records: // // struct linux_dirent64 { // u64 d_ino; // 0..7 // i64 d_off; // 8..15 // u16 d_reclen; // 16..17 — total bytes for this record // u8 d_type; // 18 — DT_REG/DT_DIR/... // u8 d_name[]; // 19.. — NUL-terminated name + padding // }; // // Returns bytes written into `buf` (advance by d_reclen to walk), // 0 at end-of-directory, or a negative errno. export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; // MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator // (rt_alloc / rt_free), no libc. Each chunk is mmap'd; when the // current chunk runs out we link a fresh one. Freeing the arena // unmaps the chain. // // Memory handed out is 16-byte aligned. The C version under // cmd/wcc/ is retained until the three-stage bootstrap diffs clean. use os; def ALIGN: u64 = 16u64; def INIT_CHUNK: u64 = 65536u64; def MAX_CHUNK: u64 = 4194304u64; def ARENA_SZ: u64 = 48u64; // sizeof(arena), kept in sync below type arena = struct { buf: *u8, off: u64, cap: u64, next: *arena, total: u64, }; fn roundup(n: u64, a: u64) u64 = { return (n + a - 1u64) & ~(a - 1u64); }; export fn newarena() *arena = { let a: *arena = os.alloc(ARENA_SZ): *arena; a.buf = os.alloc(INIT_CHUNK): *u8; a.off = 0u64; a.cap = INIT_CHUNK; a.next = nil; a.total = 0u64; return a; }; // Grow: link a fresh chunk in front of the head. We push the old // chunk into `next` so the head always describes the current bump // region. Chunk size doubles up to MAX_CHUNK. fn grow(a: *arena, need: u64) bool = { let want: u64 = a.cap * 2u64; if (want < need) { want = need; }; if (want > MAX_CHUNK) { want = MAX_CHUNK; }; if (want < need) { return false; }; // single allocation too big let old: *arena = os.alloc(ARENA_SZ): *arena; old.buf = a.buf; old.off = a.off; old.cap = a.cap; old.next = a.next; old.total = 0u64; a.buf = os.alloc(want): *u8; a.off = 0u64; a.cap = want; a.next = old; return true; }; export fn amalloc(a: *arena, n: u64) *void = { let need: u64 = roundup(n, ALIGN); if (need > a.cap - a.off) { if (!grow(a, need)) { return nil; }; }; let p: *u8 = a.buf + a.off; a.off += need; a.total += need; // Zero the region. Plan 9 amalloc zeroes; we mirror that here so // the checker can assume freshly allocated nodes start at 0. let i: u64 = 0u64; for (i < need) { p[i] = 0u8; i += 1u64; }; return p: *void; }; // astrndup — copy `n` bytes into the arena and produce a NUL-terminated // view. Returns a `str` whose ptr is arena-owned and whose len is `n` // (the trailing NUL is past `len`, so callers reading exactly n bytes // see no padding). Used by the lexer to capture token text. export fn astrndup(a: *arena, src: *u8, n: u64) str = { let p: *u8 = amalloc(a, n + 1u64): *u8; let i: u64 = 0u64; for (i < n) { p[i] = src[i]; i += 1u64; }; p[n] = 0u8; let r: str; r.ptr = p; r.len = n: i32; return r; }; export fn freearena(a: *arena) void = { for (a != nil) { let next: *arena = a.next; os.free(a.buf: *void, a.cap); os.free(a: *void, ARENA_SZ); a = next; }; }; // MODULE: ww // selfhost/cmd/ww/main.ww — port of cmd/ww/main.c. // // The user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue: // // ww build foo.ww → w6c foo.ww > foo.s ; w6a foo.s > foo.o ; // w6l -o foo foo.o libwwrt.a // ww run foo.ww → build then exec // ww version → print version // // Tool paths default to siblings of $0 so a fresh build runs out of // out/bin/. Env-var overrides (WW_W6C / WW_W6A / WW_W6L / WW_LIB) are // not yet supported in this port; the bootstrap doesn't need them. use os; use mem; // All path/string scratch buffers go on the runtime page allocator. // One page is plenty for any path we build. def PATH_MAX: u64 = 4096u64; def CMD_MAX: u64 = 8192u64; // ---- C-string helpers -------------------------------------------------- fn cstrlen(p: *u8) u64 = { let n: u64 = 0u64; for (p[n] != 0u8) { n += 1u64; }; return n; }; fn cstreq(a: *u8, b: *u8) bool = { let i: u64 = 0u64; for (a[i] == b[i]) { if (a[i] == 0u8) { return true; }; i += 1u64; }; return false; }; // cstreqlit — compare a NUL-terminated *u8 to a ww string literal. fn cstreqlit(a: *u8, lit: str) bool = { let n: i32 = lit.len; let i: i32 = 0; for (i < n) { if (a[i] != lit[i]) { return false; }; i += 1; }; return a[n] == 0u8; }; // startswith — does a have b as a prefix? fn cstrstartswith(a: *u8, b: *u8) bool = { let i: u64 = 0u64; for (b[i] != 0u8) { if (a[i] != b[i]) { return false; }; i += 1u64; }; return true; }; // memcpy fn bytecpy(dst: *u8, src: *u8, n: u64) void = { let i: u64 = 0u64; for (i < n) { dst[i] = src[i]; i += 1u64; }; }; // Copy a NUL-terminated *u8 into dst starting at off; return the new // offset (without writing a NUL). fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = { let i: u64 = 0u64; for (src[i] != 0u8) { dst[off + i] = src[i]; i += 1u64; }; return off + i; }; // Same, but for a ww `str` (no NUL on the source side; we copy len bytes). fn strinto(dst: *u8, off: u64, src: str) u64 = { let n: i32 = src.len; let i: i32 = 0; for (i < n) { let iu: u64 = i: u64; dst[off + iu] = src[i]; i += 1; }; let nu: u64 = n: u64; return off + nu; }; // Write a single byte, return new offset. fn byteinto(dst: *u8, off: u64, c: u8) u64 = { dst[off] = c; return off + 1u64; }; // NUL-terminate at off and return the same off (handy when passing the // buffer to a syscall that expects a C-string). fn cstrseal(dst: *u8, off: u64) void = { dst[off] = 0u8; }; // ---- Tool-path resolution --------------------------------------------- // dirname-equivalent: copy argv[0] up to (but not including) the last // '/' into dst, NUL-terminated. If no slash, write ".". fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = { let n: u64 = cstrlen(argv0); let cut: u64 = 0u64; let i: u64 = 0u64; for (i < n) { if (argv0[i] == 47u8) { cut = i; }; // '/' i += 1u64; }; if (cut == 0u64) { dst[0u64] = 46u8; // '.' dst[1u64] = 0u8; return; }; if (cut + 1u64 >= dstsz) { cut = dstsz - 2u64; }; bytecpy(dst, argv0, cut); dst[cut] = 0u8; }; // Build "$dir/$name" (NUL-terminated) into a fresh page-sized buffer. fn joinpath(dir: *u8, name: *u8) *u8 = { let buf: *u8 = os.alloc(PATH_MAX): *u8; let off: u64 = cstrinto(buf, 0u64, dir); off = byteinto(buf, off, 47u8); off = cstrinto(buf, off, name); cstrseal(buf, off); return buf; }; // Same, but the second component is a ww `str` literal. fn joinpathlit(dir: *u8, name: str) *u8 = { let buf: *u8 = os.alloc(PATH_MAX): *u8; let off: u64 = cstrinto(buf, 0u64, dir); off = byteinto(buf, off, 47u8); off = strinto(buf, off, name); cstrseal(buf, off); return buf; }; // ---- Subprocess plumbing ---------------------------------------------- // procrun — fork, execve `path` with `argv` (NULL-terminated), wait. // Returns 0 on clean exit-0, 1 on any non-zero exit or signal kill, // -1 on fork/wait failure. fn procrun(path: *u8, argv: **u8) i32 = { let pid: i32 = os.fork(); if (pid < 0) { os.write(2, "ww: fork failed\n".ptr, 16u64); return -1; }; if (pid == 0) { os.execve(path, argv, nil: **u8); os.write(2, "ww: execve failed\n".ptr, 18u64); os.exit(127); }; let status: i32 = 0; let r: i32 = os.wait4(pid, &status, 0i32, nil: *void); if (r < 0) { os.write(2, "ww: wait4 failed\n".ptr, 17u64); return -1; }; // Linux wait status: low byte = signal (0 if exited cleanly), // next byte = exit code. if ((status & 127i32) != 0) { return 1; }; let code: i32 = (status >> 8i32) & 255i32; if (code != 0) { return 1; }; return 0; }; // ---- `use` resolution + source concatenation -------------------------- // // 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). type strnode = struct { s: str, snext: *strnode, }; type expctx = struct { a: *arena, // arena for path strings + the visited list out: i32, // fd we're writing the combined source to dirs: *u8, // ":"-separated search path (NUL-terminated) visit: *strnode, }; fn visitseen(c: *expctx, path: str) bool = { let n: *strnode = c.visit; for (n != nil) { if (n.s.len == path.len) { let i: i32 = 0; let eq: bool = true; for (i < path.len) { if (n.s[i] != path[i]) { eq = false; i = path.len; } else { i += 1; }; }; if (eq) { return true; }; }; n = n.snext; }; return false; }; fn visitadd(c: *expctx, path: str) void = { let n: *strnode = amalloc(c.a, 32u64): *strnode; n.s = path; n.snext = c.visit; c.visit = n; }; // Try /.ww then //.ww. Returns NUL-terminated // arena-resident path if found, else nil. fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = { // candidate 1: /.ww let buf: *u8 = amalloc(a, PATH_MAX): *u8; let off: u64 = 0u64; let i: u64 = 0u64; for (i < dirlen) { buf[off + i] = dir[i]; i += 1u64; }; off += dirlen; buf[off] = 47u8; off += 1u64; // '/' i = 0u64; for (i < namelen) { buf[off + i] = name[i]; i += 1u64; }; off += namelen; buf[off] = 46u8; off += 1u64; // '.' buf[off] = 119u8; off += 1u64; // 'w' buf[off] = 119u8; off += 1u64; // 'w' buf[off] = 0u8; if (os.access(buf, 0i32) == 0) { return buf; }; // candidate 2: //.ww let buf2: *u8 = amalloc(a, PATH_MAX): *u8; off = 0u64; i = 0u64; for (i < dirlen) { buf2[off + i] = dir[i]; i += 1u64; }; off += dirlen; buf2[off] = 47u8; off += 1u64; i = 0u64; for (i < namelen) { buf2[off + i] = name[i]; i += 1u64; }; off += namelen; buf2[off] = 47u8; off += 1u64; i = 0u64; for (i < namelen) { buf2[off + i] = name[i]; i += 1u64; }; off += namelen; buf2[off] = 46u8; off += 1u64; buf2[off] = 119u8; off += 1u64; buf2[off] = 119u8; off += 1u64; buf2[off] = 0u8; if (os.access(buf2, 0i32) == 0) { return buf2; }; return nil; }; // Walk a colon-separated dirlist, return first hit or nil. fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = { let total: u64 = cstrlen(dirs); let p: u64 = 0u64; for (p < total) { let q: u64 = p; for (q < total) { if (dirs[q] == 58u8) { break; }; // ':' q += 1u64; }; let seglen: u64 = q - p; if (seglen > 0u64) { let hit: *u8 = locatein(a, dirs + p, seglen, name, namelen); if (hit != nil) { return hit; }; }; p = q + 1u64; }; return nil; }; // ---- file slurp ------------------------------------------------------- fn slurp(pathcs: *u8) (*u8, u64) = { let fd: i32 = os.open(pathcs, os.flag.RDONLY, 0i32); if (fd < 0) { return nil, 0u64; }; let szr: (i64 | os.oserror) = os.filesize(fd); let n: i64 = 0i64; match (szr) { case let v: i64 => n = v; case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; let nu: u64 = n: u64; let buf: *u8 = os.alloc(nu + 1u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, nu); os.close(fd); let got: i64 = 0i64; match (rr) { case let v: i64 => got = v; case let e: os.oserror => return nil, 0u64; }; if (got != n) { return nil, 0u64; }; buf[nu] = 0u8; return buf, nu; }; fn isidentbyte(c: u8) bool = { if (c >= 97u8) { if (c <= 122u8) { return true; }; }; // a..z if (c >= 65u8) { if (c <= 90u8) { return true; }; }; // A..Z if (c >= 48u8) { if (c <= 57u8) { return true; }; }; // 0..9 if (c == 95u8) { return true; }; // _ if (c == 46u8) { return true; }; // . return false; }; // Scan one `use IDENT;` line out of [start, end). Returns the start of // the ident and its length, or (nil, 0) if no `use` here. The caller // passes a slice of the source: src points at the line start. fn scanuse(src: *u8, len: u64) (*u8, u64) = { let i: u64 = 0u64; // skip leading whitespace for (i < len) { if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; }; i += 1u64; }; if (i + 4u64 > len) { return nil, 0u64; }; if (src[i] != 117u8) { return nil, 0u64; }; // 'u' if (src[i + 1u64] != 115u8) { return nil, 0u64; }; // 's' if (src[i + 2u64] != 101u8) { return nil, 0u64; }; // 'e' let sep: u8 = src[i + 3u64]; if (sep != 32u8) { if (sep != 9u8) { return nil, 0u64; }; }; i += 4u64; for (i < len) { if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; }; i += 1u64; }; let idstart: u64 = i; for (i < len) { if (!isidentbyte(src[i])) { break; }; i += 1u64; }; let idlen: u64 = i - idstart; if (idlen == 0u64) { return nil, 0u64; }; return src + idstart, idlen; }; // Recursively expand `path` into c.out. Imported files are emitted // before their importer; cycles are broken via the visited set. // modulename — pick the source's containing-directory basename. So // `lib/os/os.ww` → "os"; `lib/ww/sym.ww` → "ww". Falls back // to the file's own basename (sans .ww) when there is no parent dir. // Returns ("",0) if `path` ends in a '/' (degenerate). fn modulename(path: *u8, plen: u64) (*u8, u64) = { if (plen == 0u64) { return nil, 0u64; }; // Find the last '/'. let last: u64 = plen; let i: u64 = plen; for (i > 0u64) { i -= 1u64; if (path[i] == 47u8) { last = i; i = 0u64; } else { if (i == 0u64) { last = plen; }; }; }; if (last == plen) { // No '/' — path is a bare filename. Use its stem. let n: u64 = plen; if (n >= 3u64) { if (path[n - 3u64] == 46u8) { if (path[n - 2u64] == 119u8) { if (path[n - 1u64] == 119u8) { n = n - 3u64; }; }; }; }; return path, n; }; // Find the '/' before `last` — segment between is the dir basename. let prev: u64 = 0u64; let found: bool = false; let j: u64 = last; for (j > 0u64) { j -= 1u64; if (path[j] == 47u8) { prev = j + 1u64; found = true; j = 0u64; }; }; if (!found) { prev = 0u64; }; return path + prev, last - prev; }; fn expand(c: *expctx, pathcs: *u8) void = { let plen: u64 = cstrlen(pathcs); let pathstr: str = astrndup(c.a, pathcs, plen); if (visitseen(c, pathstr)) { return; }; visitadd(c, pathstr); let bufp: *u8; let blen: u64; bufp, blen = slurp(pathcs); if (bufp == nil) { os.write(2, "ww: cannot read source\n".ptr, 23u64); return; }; // Pass 1: scan top-of-file `use X;` lines, recursively expand. let i: u64 = 0u64; for (i < blen) { // Find the end of the current line. 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 ipath: *u8 = locateimport(c.a, c.dirs, idp, idn); if (ipath != nil) { expand(c, ipath); }; }; i = j + 1u64; }; // Pass 2: prefix a `// MODULE: ` directive, then emit our // bytes. The marker is a comment to the C-side wcc and any other // reader; the ww-side wcc lexer recognises it and stamps each // top-level decl with the originating module so cgen can mangle // non-exported names by module. let mp: *u8; let mn: u64; mp, mn = modulename(pathcs, plen); if (mn > 0u64) { os.writeall(c.out, "// MODULE: ".ptr, 11u64); os.writeall(c.out, mp, mn); os.writeall(c.out, "\n".ptr, 1u64); }; os.writeall(c.out, bufp, blen); os.writeall(c.out, "\n".ptr, 1u64); }; // ---- Build pipeline --------------------------------------------------- // Strip the trailing ".ww" off `src` (a NUL-terminated path) into // `stem`, NUL-terminated. If there's no .ww, the stem is the whole // path. fn makestem(stem: *u8, src: *u8) void = { let n: u64 = cstrlen(src); let stop: u64 = n; if (n >= 3u64) { if (src[n - 3u64] == 46u8) { // '.' if (src[n - 2u64] == 119u8) { // 'w' if (src[n - 1u64] == 119u8) { // 'w' stop = n - 3u64; }; }; }; }; let i: u64 = 0u64; for (i < stop) { stem[i] = src[i]; i += 1u64; }; stem[stop] = 0u8; }; // Append a literal suffix to `stem` (which already lives in a buffer). fn appendlit(stem: *u8, suffix: str) *u8 = { let buf: *u8 = os.alloc(PATH_MAX): *u8; let off: u64 = cstrinto(buf, 0u64, stem); off = strinto(buf, off, suffix); cstrseal(buf, off); return buf; }; // linker flags bundled as a struct so buildone stays at the wwstage // w6c's 6-argument calling-convention limit. type lflags = struct { libdirs: **u8, nlibdirs: i32, libs: **u8, nlibs: i32, }; // buildone — compile `src` 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 path to the .ww file // out: NUL-terminated desired output path // incs: NUL-terminated colon-list of -I dirs (may be empty) // lf: extra linker flags (-L, -l); 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, out: *u8, incs: *u8, lf: *lflags) i32 = { let a: *arena = newarena(); 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: /../../lib let dotdotlib: *u8 = os.alloc(PATH_MAX): *u8; { let off: u64 = cstrinto(dotdotlib, 0u64, selfdir); off = strinto(dotdotlib, off, "/../../lib"); cstrseal(dotdotlib, off); }; // Compose searchpath: incs + ':' + dotdotlib (or just dotdotlib). let searchpath: *u8 = os.alloc(PATH_MAX * 2u64): *u8; { let off: u64 = 0u64; if (incs[0u64] != 0u8) { off = cstrinto(searchpath, off, incs); off = byteinto(searchpath, off, 58u8); // ':' }; off = cstrinto(searchpath, off, dotdotlib); cstrseal(searchpath, off); }; // stem, .s, .o, .combined.ww, libwwrt.a let stem: *u8 = os.alloc(PATH_MAX): *u8; makestem(stem, src); let asmf: *u8 = appendlit(stem, ".s"); let objf: *u8 = appendlit(stem, ".o"); let combined: *u8 = appendlit(stem, ".combined.ww"); // libwwrt.a path: /../lib/libwwrt.a let libwwrt: *u8 = os.alloc(PATH_MAX): *u8; { let off: u64 = cstrinto(libwwrt, 0u64, selfdir); off = strinto(libwwrt, off, "/../lib/libwwrt.a"); cstrseal(libwwrt, off); }; // Step 1: expand `use`s into the combined file. let cf: i32 = os.open(combined, os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644 if (cf < 0) { os.write(2, "ww: cannot open combined\n".ptr, 25u64); return 1; }; { let c: expctx; c.a = a; c.out = cf; c.dirs = searchpath; c.visit = nil; expand(&c, src); }; os.close(cf); // Step 2: w6c -o .s .combined.ww { let argv: **u8 = os.alloc(40u64): **u8; argv[0] = "w6c\0".ptr; argv[1] = "-o\0".ptr; argv[2] = asmf; argv[3] = combined; argv[4] = nil; if (procrun(c6, argv) != 0) { os.write(2, "ww: w6c failed\n".ptr, 15u64); return 1; }; }; // Step 3: w6a -o .o .s { let argv: **u8 = os.alloc(40u64): **u8; argv[0] = "w6a\0".ptr; argv[1] = "-o\0".ptr; argv[2] = objf; argv[3] = asmf; argv[4] = nil; if (procrun(a6, argv) != 0) { os.write(2, "ww: w6a failed\n".ptr, 15u64); return 1; }; }; // Step 4: w6l -o .o libwwrt.a [-L...] [-l...] { 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 = os.alloc((total: u64) * 8u64): **u8; argv[0] = "w6l\0".ptr; argv[1] = "-o\0".ptr; argv[2] = out; argv[3] = objf; argv[4] = libwwrt; 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) != 0) { os.write(2, "ww: w6l failed\n".ptr, 15u64); return 1; }; }; return 0; }; // ---- Module-by-name resolution ---------------------------------------- // // Mirrors cmd/ww/main.c:resolvemodule. Maps a name like "foo", "lib/foo", // "foo.ww", or "." to a concrete .ww file path: // 1. literal .ww that exists → use as-is // 2. "." → /.ww → that, if it exists // 3. /.ww → that, if it exists // 4. walk search path (cwd:incs:/../../lib): // /.ww or //.ww fn cstrendswithlit(p: *u8, lit: str) bool = { let plen: u64 = cstrlen(p); let slen: u64 = lit.len: u64; if (plen < slen) { return false; }; let off: u64 = plen - slen; let i: i32 = 0; for (i < lit.len) { let iu: u64 = i: u64; if (p[off + iu] != lit[i]) { return false; }; i += 1; }; return true; }; // basenameoff — return the offset of the last path segment within `p` // (i.e. one past the final '/'). Returns 0 if there's no slash. fn basenameoff(p: *u8, plen: u64) u64 = { let start: u64 = 0u64; let i: u64 = 0u64; for (i < plen) { if (p[i] == 47u8) { start = i + 1u64; }; // '/' i += 1u64; }; return start; }; // arenadupcstr — copy `plen` bytes from `src` into a fresh NUL-sealed // arena buffer. fn arenadupcstr(a: *arena, src: *u8, plen: u64) *u8 = { let buf: *u8 = amalloc(a, plen + 1u64): *u8; let i: u64 = 0u64; for (i < plen) { buf[i] = src[i]; i += 1u64; }; buf[plen] = 0u8; return buf; }; // builddirmodulepath — alloc /.ww, NUL-terminated, in `a`. fn builddirmodulepath(a: *arena, dir: *u8, dlen: u64, base: *u8, blen: u64) *u8 = { let need: u64 = dlen + 1u64 + blen + 3u64 + 1u64; let buf: *u8 = amalloc(a, need): *u8; let off: u64 = 0u64; let i: u64 = 0u64; for (i < dlen) { buf[off] = dir[i]; off += 1u64; i += 1u64; }; buf[off] = 47u8; off += 1u64; // '/' i = 0u64; for (i < blen) { buf[off] = base[i]; off += 1u64; i += 1u64; }; buf[off] = 46u8; off += 1u64; // '.' buf[off] = 119u8; off += 1u64; // 'w' buf[off] = 119u8; off += 1u64; // 'w' buf[off] = 0u8; return buf; }; // buildsearchpath — compose the colon-separated lookup path used by // resolvemodule's case (4). Order: "." : : /../../lib fn buildsearchpath(a: *arena, selfdir: *u8, incs: *u8) *u8 = { let buf: *u8 = amalloc(a, PATH_MAX * 2u64): *u8; let off: u64 = 0u64; buf[off] = 46u8; off += 1u64; // '.' if (incs != nil) { if (incs[0u64] != 0u8) { buf[off] = 58u8; off += 1u64; // ':' off = cstrinto(buf, off, incs); }; }; buf[off] = 58u8; off += 1u64; off = cstrinto(buf, off, selfdir); off = strinto(buf, off, "/../../lib"); cstrseal(buf, off); return buf; }; fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = { let nlen: u64 = cstrlen(name); // (1) literal .ww file that exists if (cstrendswithlit(name, ".ww")) { if (os.access(name, 0i32) == 0) { return arenadupcstr(a, name, nlen); }; }; // (2) "." → cwd's .ww if (nlen == 1u64) { if (name[0u64] == 46u8) { // '.' let cwd: *u8 = amalloc(a, PATH_MAX): *u8; let r: i64 = os.getcwd(cwd, PATH_MAX); if (r <= 0i64) { return nil; }; let cwdlen: u64 = (r: u64) - 1u64; // strip trailing NUL let bo: u64 = basenameoff(cwd, cwdlen); let blen: u64 = cwdlen - bo; let dot: *u8 = amalloc(a, 2u64): *u8; dot[0] = 46u8; dot[1] = 0u8; let probe: *u8 = builddirmodulepath(a, dot, 1u64, cwd + bo, blen); if (os.access(probe, 0i32) == 0) { return probe; }; return nil; }; }; // (3) /.ww — directory-as-module let bo: u64 = basenameoff(name, nlen); let probe: *u8 = builddirmodulepath(a, name, nlen, name + bo, nlen - bo); if (os.access(probe, 0i32) == 0) { return probe; }; // (4) search path lookup let search: *u8 = buildsearchpath(a, selfdir, incs); return locateimport(a, search, name, nlen); }; // ---- Subcommand handlers ---------------------------------------------- fn writeusage(fd: i32) void = { let s: str = "usage: ww [-V] [args...]\n -V print version and exit\n build [path] compile module to a static binary (path defaults to cwd)\n run [path] ... build then exec, passing extra args to the program\n test [path] build and run *_test.ww in the module (path defaults to cwd)\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then $WW_LIB-equiv for foo.ww or foo/foo.ww\n lib/foo directory: build lib/foo/foo.ww\n . build the cwd's .ww\n"; os.write(fd, s.ptr, s.len: u64); }; fn doversion() i32 = { os.write(1, "ww 0.0\n".ptr, 7u64); return 0; }; // Compute the basename of src (without trailing ".ww") into a fresh // buffer. Used as the default output path for `ww build`. fn defaultoutpath(src: *u8) *u8 = { let n: u64 = cstrlen(src); let start: u64 = 0u64; let i: u64 = 0u64; for (i < n) { if (src[i] == 47u8) { start = i + 1u64; }; // '/' i += 1u64; }; let out: *u8 = os.alloc(PATH_MAX): *u8; let off: u64 = 0u64; let j: u64 = start; for (j < n) { out[off] = src[j]; off += 1u64; j += 1u64; }; // Strip ".ww" if present. if (off >= 3u64) { if (out[off - 3u64] == 46u8) { if (out[off - 2u64] == 119u8) { if (out[off - 1u64] == 119u8) { off -= 3u64; }; }; }; }; cstrseal(out, off); return out; }; fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let a: *arena = newarena(); let src: *u8 = nil; let incs: *u8 = os.alloc(PATH_MAX * 2u64): *u8; let incoff: u64 = 0u64; cstrseal(incs, 0u64); let maxlflags: i32 = 32; let libdirs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; let nlibdirs: i32 = 0; let libs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; let nlibs: i32 = 0; let i: i32 = start; for (i < argc) { let p: *u8 = argv[i]; if (p[0u64] == 45u8) { // '-' if (p[1u64] == 73u8) { // '-I' let dir: *u8 = nil; if (p[2u64] != 0u8) { dir = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww build: -I needs an argument\n".ptr, 31u64); return 2; }; i += 1; dir = argv[i]; }; if (incoff > 0u64) { incs[incoff] = 58u8; // ':' incoff += 1u64; }; incoff = cstrinto(incs, incoff, dir); cstrseal(incs, incoff); } else { if (p[1u64] == 76u8) { // '-L' let dir: *u8 = nil; if (p[2u64] != 0u8) { dir = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww build: -L needs an argument\n".ptr, 31u64); return 2; }; i += 1; dir = argv[i]; }; if (nlibdirs >= maxlflags) { os.write(2, "ww build: too many -L\n".ptr, 22u64); return 2; }; libdirs[nlibdirs] = dir; nlibdirs += 1; } else { if (p[1u64] == 108u8) { // '-l' let nm: *u8 = nil; if (p[2u64] != 0u8) { nm = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww build: -l needs an argument\n".ptr, 31u64); return 2; }; i += 1; nm = argv[i]; }; if (nlibs >= maxlflags) { os.write(2, "ww build: too many -l\n".ptr, 22u64); return 2; }; libs[nlibs] = nm; nlibs += 1; } else { os.write(2, "ww build: unknown flag\n".ptr, 23u64); return 2; }; }; }; } else { if (src == nil) { src = p; }; }; i += 1; }; if (src == nil) { // default to cwd module let dot: *u8 = amalloc(a, 2u64): *u8; dot[0] = 46u8; dot[1] = 0u8; src = dot; }; let resolved: *u8 = resolvemodule(a, selfdir, src, incs); if (resolved == nil) { os.write(2, "ww build: cannot find module\n".ptr, 29u64); return 1; }; let out: *u8 = defaultoutpath(resolved); let lf: lflags; lf.libdirs = libdirs; lf.nlibdirs = nlibdirs; lf.libs = libs; lf.nlibs = nlibs; return buildone(selfdir, resolved, out, incs, &lf); }; // Format the scratch path /tmp/ww_run_ into buf. Returns NUL- // terminated buf. Pid is folded in decimal manually since we don't // import strconv. fn makeruntmp(buf: *u8) void = { let off: u64 = 0u64; off = strinto(buf, off, "/tmp/ww_run_"); let pid: i32 = os.getpid(); // itoa for non-negative pid let dig: [16]u8; let n: i32 = 0; if (pid <= 0) { dig[n] = 48u8; // '0' n += 1; } else { let v: i32 = pid; for (v > 0) { dig[n] = ((v % 10) + 48): u8; n += 1; v = v / 10; }; }; let k: i32 = n - 1; for (k >= 0) { buf[off] = dig[k]; off += 1u64; k -= 1; }; cstrseal(buf, off); }; fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let a: *arena = newarena(); let src: *u8 = nil; let passstart: i32 = -1; // first argv idx to pass through to program let incs: *u8 = os.alloc(PATH_MAX * 2u64): *u8; let incoff: u64 = 0u64; cstrseal(incs, 0u64); let maxlflags: i32 = 32; let libdirs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; let nlibdirs: i32 = 0; let libs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; let nlibs: i32 = 0; let i: i32 = start; for (i < argc) { if (passstart >= 0) { i = argc; } // stop, leave rest for exec else { let p: *u8 = argv[i]; if (p[0u64] == 45u8) { if (p[1u64] == 73u8) { let dir: *u8 = nil; if (p[2u64] != 0u8) { dir = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww run: -I needs an argument\n".ptr, 29u64); return 2; }; i += 1; dir = argv[i]; }; if (incoff > 0u64) { incs[incoff] = 58u8; incoff += 1u64; }; incoff = cstrinto(incs, incoff, dir); cstrseal(incs, incoff); } else { if (p[1u64] == 76u8) { let dir: *u8 = nil; if (p[2u64] != 0u8) { dir = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww run: -L needs an argument\n".ptr, 29u64); return 2; }; i += 1; dir = argv[i]; }; if (nlibdirs >= maxlflags) { os.write(2, "ww run: too many -L\n".ptr, 20u64); return 2; }; libdirs[nlibdirs] = dir; nlibdirs += 1; } else { if (p[1u64] == 108u8) { let nm: *u8 = nil; if (p[2u64] != 0u8) { nm = p + 2u64; } else { if (i + 1 >= argc) { os.write(2, "ww run: -l needs an argument\n".ptr, 29u64); return 2; }; i += 1; nm = argv[i]; }; if (nlibs >= maxlflags) { os.write(2, "ww run: too many -l\n".ptr, 20u64); return 2; }; libs[nlibs] = nm; nlibs += 1; } else { os.write(2, "ww run: unknown flag\n".ptr, 21u64); return 2; }; }; }; i += 1; } else { if (src == nil) { src = p; i += 1; } else { passstart = i; // remaining args go to the program }; }; }; }; if (src == nil) { let dot: *u8 = amalloc(a, 2u64): *u8; dot[0] = 46u8; dot[1] = 0u8; src = dot; }; let resolved: *u8 = resolvemodule(a, selfdir, src, incs); if (resolved == nil) { os.write(2, "ww run: cannot find module\n".ptr, 27u64); return 1; }; let tmp: *u8 = os.alloc(PATH_MAX): *u8; makeruntmp(tmp); let lf: lflags; lf.libdirs = libdirs; lf.nlibdirs = nlibdirs; lf.libs = libs; lf.nlibs = nlibs; if (buildone(selfdir, resolved, tmp, incs, &lf) != 0) { os.remove(tmp); return 1; }; // exec with [tmp, argv[passstart..argc), nil] let nextra: i32 = 0; if (passstart >= 0) { nextra = argc - passstart; }; let total: i32 = nextra + 2; let execargv: **u8 = os.alloc((total: u64) * 8u64): **u8; execargv[0] = tmp; let k: i32 = 0; for (k < nextra) { execargv[k + 1] = argv[passstart + k]; k += 1; }; execargv[nextra + 1] = nil; let rc: i32 = procrun(tmp, execargv); os.remove(tmp); return rc; }; // ---- ww test ---------------------------------------------------------- // // Mirrors cmd/ww/main.c:dotest. Two modes: // single-file: build+run a literal *.ww file, return its exit code // 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) i32 = { let tmp: *u8 = os.alloc(PATH_MAX): *u8; makeruntmp(tmp); if (buildone(selfdir, src, tmp, "\0".ptr, nil) != 0) { os.remove(tmp); return 1; }; let execargv: **u8 = os.alloc(16u64): **u8; execargv[0] = tmp; execargv[1] = nil; let rc: i32 = procrun(tmp, execargv); os.remove(tmp); return rc; }; fn rundirtests(selfdir: *u8, dir: *u8) i32 = { let fd: i32 = os.open(dir, os.flag.RDONLY, 0i32); if (fd < 0) { os.write(2, "ww test: cannot open directory\n".ptr, 31u64); return 1; }; let pass: i32 = 0; let fail: i32 = 0; let buf: *u8 = os.alloc(8192u64): *u8; let dirlen: u64 = cstrlen(dir); let n: i64 = os.getdents64(fd, buf, 8192u64); for (n > 0i64) { let off: u64 = 0u64; let nu: u64 = n: u64; for (off < nu) { // d_reclen at offset+16 (u16 LE), d_name at offset+19 (cstr) let blo: u64 = (buf[off + 16u64]): u64; let bhi: u64 = (buf[off + 17u64]): u64; let reclen: u64 = blo + (bhi * 256u64); let name: *u8 = buf + off + 19u64; if (cstrendswithlit(name, "_test.ww")) { let nlen: u64 = cstrlen(name); // path = / let path: *u8 = os.alloc(PATH_MAX): *u8; let poff: u64 = cstrinto(path, 0u64, dir); path[poff] = 47u8; poff += 1u64; let i: u64 = 0u64; for (i < nlen) { path[poff + i] = name[i]; i += 1u64; }; poff += nlen; cstrseal(path, poff); // incs = so test files can `use ` siblings let tincs: *u8 = os.alloc(PATH_MAX): *u8; let ic: u64 = cstrinto(tincs, 0u64, dir); cstrseal(tincs, ic); let tmp: *u8 = os.alloc(PATH_MAX): *u8; makeruntmp(tmp); let bres: i32 = buildone(selfdir, path, tmp, tincs, nil); if (bres != 0) { fail += 1; os.write(2, "FAIL ".ptr, 5u64); os.write(2, name, nlen); os.write(2, " (build)\n".ptr, 9u64); } else { let execargv: **u8 = os.alloc(16u64): **u8; execargv[0] = tmp; execargv[1] = nil; let rc: i32 = procrun(tmp, execargv); if (rc == 0) { pass += 1; os.write(1, "ok ".ptr, 5u64); os.write(1, name, nlen); os.write(1, "\n".ptr, 1u64); } else { fail += 1; os.write(2, "FAIL ".ptr, 5u64); os.write(2, name, nlen); os.write(2, "\n".ptr, 1u64); }; }; os.remove(tmp); }; off += reclen; }; n = os.getdents64(fd, buf, 8192u64); }; os.close(fd); if (fail == 0) { return 0; }; return 1; }; fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let a: *arena = newarena(); let target: *u8; if (start >= argc) { let dot: *u8 = amalloc(a, 2u64): *u8; dot[0] = 46u8; dot[1] = 0u8; target = dot; } else { target = argv[start]; }; // single-file mode: literal *.ww that exists if (cstrendswithlit(target, ".ww")) { if (os.access(target, 0i32) == 0) { return runsingletest(selfdir, target); }; }; // otherwise treat target as a directory; enumerate *_test.ww return rundirtests(selfdir, target); }; // ---- Entry ------------------------------------------------------------- export fn main(argc: i32, argv: **u8) i32 = { if (argc < 1) { writeusage(2); return 2; }; // selfdir = dirname(argv[0]) let selfdir: *u8 = os.alloc(PATH_MAX): *u8; selfdirinto(selfdir, PATH_MAX, argv[0]); if (argc < 2) { writeusage(2); return 2; }; let cmd: *u8 = argv[1]; if (cstreqlit(cmd, "-V")) { return doversion(); }; if (cstreqlit(cmd, "version")) { return doversion(); }; if (cstreqlit(cmd, "-h")) { writeusage(1); return 0; }; if (cstreqlit(cmd, "--help")) { writeusage(1); return 0; }; if (cstreqlit(cmd, "build")) { return dobuild(selfdir, argv, argc, 2); }; if (cstreqlit(cmd, "run")) { return dorun(selfdir, argv, argc, 2); }; if (cstreqlit(cmd, "test")) { return dotest(selfdir, argv, argc, 2); }; os.write(2, "ww: unknown subcommand\n".ptr, 23u64); writeusage(2); return 2; };