From fc49da44d8e46bc909b729ed30425c745772516d Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 04:44:57 +0900 Subject: [PATCH] os: graduate SYS_* defs to `nr` enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `type nr = enum i64 { READ, WRITE, OPEN, ... }`. syscall0..4 take `num: nr` so the wrong-arg-order trap is now a compile error (`syscall1(0i64, ...)` no longer typechecks — it has to be `syscall1(nr.READ, ...)`). Internal-only (callers outside os.ww never touched the constants), so no external API change. ABI is unchanged: nr's storage is i64 and rt_syscall's RDI is unchanged. The selfhost combined.ww files regenerate as a side effect of `make wwstage`. --- lib/os/os.ww | 76 +++++++++++++++------------- selfhost/cmd/w6a/main.combined.ww | 76 +++++++++++++++------------- selfhost/cmd/w6c/main.combined.ww | 76 +++++++++++++++------------- selfhost/cmd/w6l/main.combined.ww | 76 +++++++++++++++------------- selfhost/cmd/ww/main.combined.ww | 76 +++++++++++++++------------- selfhost/cmd/wwdump/main.combined.ww | 76 +++++++++++++++------------- selfhost/test/smoke.combined.ww | 76 +++++++++++++++------------- 7 files changed, 287 insertions(+), 245 deletions(-) diff --git a/lib/os/os.ww b/lib/os/os.ww index 0b09306f..f123389d 100644 --- a/lib/os/os.ww +++ b/lib/os/os.ww @@ -2,11 +2,11 @@ // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. -@symbol("rt_syscall") fn syscall0(num: i64) i64; -@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_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; @@ -18,21 +18,27 @@ 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_ACCESS: i64 = 21; -def SYS_DUP2: i64 = 33; -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; -def SYS_GETCWD: i64 = 79; -def SYS_GETDENTS64: i64 = 217; +// 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 @@ -53,22 +59,22 @@ export type whence = enum i32 { }; export fn exit(code: i32) void = { - syscall1(SYS_EXIT, code: i64); + 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(SYS_WRITE, fd: i64, buf: i64, n: i64); + return syscall3(nr.WRITE, fd: i64, buf: i64, n: i64); }; export fn read(fd: i32, buf: *u8, n: u64) i64 = { - return syscall3(SYS_READ, fd: i64, buf: i64, n: i64); + return syscall3(nr.READ, fd: i64, buf: i64, n: i64); }; export fn close(fd: i32) i32 = { - return syscall1(SYS_CLOSE, fd: i64): i32; + return syscall1(nr.CLOSE, fd: i64): i32; }; // dup2(2): make `newfd` refer to the same description as `oldfd`, @@ -76,7 +82,7 @@ export fn close(fd: i32) i32 = { // 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(SYS_DUP2, oldfd: i64, newfd: i64): i32; + return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32; }; // Fallible wrappers. The error variant is `oserror` (an i64 carrying @@ -99,7 +105,7 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_OPEN, path: i64, (flags as i32): i64, mode: i64): 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) = { @@ -112,7 +118,7 @@ export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = { // 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(SYS_LSEEK, fd: i64, off, (w as i32): i64); + return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64); }; // oserror — the underlying errno from a failed syscall, as a @@ -163,34 +169,34 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_ACCESS, path: i64, mode: i64): 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(SYS_UNLINK, path: i64): 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(SYS_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(SYS_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(SYS_EXECVE, path: i64, argv: i64, envp: i64): 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(SYS_WAIT4, pid: i64, status: i64, + return syscall4(nr.WAIT4, pid: i64, status: i64, options: i64, rusage: i64): i32; }; @@ -199,7 +205,7 @@ export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = { // 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(SYS_GETCWD, buf: i64, n: i64); + return syscall2(nr.GETCWD, buf: i64, n: i64); }; // getdents64(2) — Linux directory enumeration. The fd must be opened @@ -217,5 +223,5 @@ export fn getcwd(buf: *u8, n: u64) i64 = { // 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(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); + return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index a8dd469e..d8822050 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -3,11 +3,11 @@ // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. -@symbol("rt_syscall") fn syscall0(num: i64) i64; -@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_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; @@ -19,21 +19,27 @@ 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_ACCESS: i64 = 21; -def SYS_DUP2: i64 = 33; -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; -def SYS_GETCWD: i64 = 79; -def SYS_GETDENTS64: i64 = 217; +// 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 @@ -54,22 +60,22 @@ export type whence = enum i32 { }; export fn exit(code: i32) void = { - syscall1(SYS_EXIT, code: i64); + 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(SYS_WRITE, fd: i64, buf: i64, n: i64); + return syscall3(nr.WRITE, fd: i64, buf: i64, n: i64); }; export fn read(fd: i32, buf: *u8, n: u64) i64 = { - return syscall3(SYS_READ, fd: i64, buf: i64, n: i64); + return syscall3(nr.READ, fd: i64, buf: i64, n: i64); }; export fn close(fd: i32) i32 = { - return syscall1(SYS_CLOSE, fd: i64): i32; + return syscall1(nr.CLOSE, fd: i64): i32; }; // dup2(2): make `newfd` refer to the same description as `oldfd`, @@ -77,7 +83,7 @@ export fn close(fd: i32) i32 = { // 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(SYS_DUP2, oldfd: i64, newfd: i64): i32; + return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32; }; // Fallible wrappers. The error variant is `oserror` (an i64 carrying @@ -100,7 +106,7 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_OPEN, path: i64, (flags as i32): i64, mode: i64): 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) = { @@ -113,7 +119,7 @@ export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = { // 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(SYS_LSEEK, fd: i64, off, (w as i32): i64); + return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64); }; // oserror — the underlying errno from a failed syscall, as a @@ -164,34 +170,34 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_ACCESS, path: i64, mode: i64): 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(SYS_UNLINK, path: i64): 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(SYS_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(SYS_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(SYS_EXECVE, path: i64, argv: i64, envp: i64): 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(SYS_WAIT4, pid: i64, status: i64, + return syscall4(nr.WAIT4, pid: i64, status: i64, options: i64, rusage: i64): i32; }; @@ -200,7 +206,7 @@ export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = { // 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(SYS_GETCWD, buf: i64, n: i64); + return syscall2(nr.GETCWD, buf: i64, n: i64); }; // getdents64(2) — Linux directory enumeration. The fd must be opened @@ -218,7 +224,7 @@ export fn getcwd(buf: *u8, n: u64) i64 = { // 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(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); + return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; // MODULE: wcc diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 5c120999..6ded58d4 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -3,11 +3,11 @@ // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. -@symbol("rt_syscall") fn syscall0(num: i64) i64; -@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_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; @@ -19,21 +19,27 @@ 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_ACCESS: i64 = 21; -def SYS_DUP2: i64 = 33; -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; -def SYS_GETCWD: i64 = 79; -def SYS_GETDENTS64: i64 = 217; +// 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 @@ -54,22 +60,22 @@ export type whence = enum i32 { }; export fn exit(code: i32) void = { - syscall1(SYS_EXIT, code: i64); + 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(SYS_WRITE, fd: i64, buf: i64, n: i64); + return syscall3(nr.WRITE, fd: i64, buf: i64, n: i64); }; export fn read(fd: i32, buf: *u8, n: u64) i64 = { - return syscall3(SYS_READ, fd: i64, buf: i64, n: i64); + return syscall3(nr.READ, fd: i64, buf: i64, n: i64); }; export fn close(fd: i32) i32 = { - return syscall1(SYS_CLOSE, fd: i64): i32; + return syscall1(nr.CLOSE, fd: i64): i32; }; // dup2(2): make `newfd` refer to the same description as `oldfd`, @@ -77,7 +83,7 @@ export fn close(fd: i32) i32 = { // 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(SYS_DUP2, oldfd: i64, newfd: i64): i32; + return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32; }; // Fallible wrappers. The error variant is `oserror` (an i64 carrying @@ -100,7 +106,7 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_OPEN, path: i64, (flags as i32): i64, mode: i64): 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) = { @@ -113,7 +119,7 @@ export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = { // 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(SYS_LSEEK, fd: i64, off, (w as i32): i64); + return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64); }; // oserror — the underlying errno from a failed syscall, as a @@ -164,34 +170,34 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_ACCESS, path: i64, mode: i64): 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(SYS_UNLINK, path: i64): 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(SYS_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(SYS_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(SYS_EXECVE, path: i64, argv: i64, envp: i64): 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(SYS_WAIT4, pid: i64, status: i64, + return syscall4(nr.WAIT4, pid: i64, status: i64, options: i64, rusage: i64): i32; }; @@ -200,7 +206,7 @@ export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = { // 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(SYS_GETCWD, buf: i64, n: i64); + return syscall2(nr.GETCWD, buf: i64, n: i64); }; // getdents64(2) — Linux directory enumeration. The fd must be opened @@ -218,7 +224,7 @@ export fn getcwd(buf: *u8, n: u64) i64 = { // 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(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); + return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; // MODULE: wcc diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index e194abd1..c921ae55 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -3,11 +3,11 @@ // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. -@symbol("rt_syscall") fn syscall0(num: i64) i64; -@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_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; @@ -19,21 +19,27 @@ 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_ACCESS: i64 = 21; -def SYS_DUP2: i64 = 33; -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; -def SYS_GETCWD: i64 = 79; -def SYS_GETDENTS64: i64 = 217; +// 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 @@ -54,22 +60,22 @@ export type whence = enum i32 { }; export fn exit(code: i32) void = { - syscall1(SYS_EXIT, code: i64); + 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(SYS_WRITE, fd: i64, buf: i64, n: i64); + return syscall3(nr.WRITE, fd: i64, buf: i64, n: i64); }; export fn read(fd: i32, buf: *u8, n: u64) i64 = { - return syscall3(SYS_READ, fd: i64, buf: i64, n: i64); + return syscall3(nr.READ, fd: i64, buf: i64, n: i64); }; export fn close(fd: i32) i32 = { - return syscall1(SYS_CLOSE, fd: i64): i32; + return syscall1(nr.CLOSE, fd: i64): i32; }; // dup2(2): make `newfd` refer to the same description as `oldfd`, @@ -77,7 +83,7 @@ export fn close(fd: i32) i32 = { // 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(SYS_DUP2, oldfd: i64, newfd: i64): i32; + return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32; }; // Fallible wrappers. The error variant is `oserror` (an i64 carrying @@ -100,7 +106,7 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_OPEN, path: i64, (flags as i32): i64, mode: i64): 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) = { @@ -113,7 +119,7 @@ export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = { // 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(SYS_LSEEK, fd: i64, off, (w as i32): i64); + return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64); }; // oserror — the underlying errno from a failed syscall, as a @@ -164,34 +170,34 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_ACCESS, path: i64, mode: i64): 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(SYS_UNLINK, path: i64): 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(SYS_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(SYS_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(SYS_EXECVE, path: i64, argv: i64, envp: i64): 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(SYS_WAIT4, pid: i64, status: i64, + return syscall4(nr.WAIT4, pid: i64, status: i64, options: i64, rusage: i64): i32; }; @@ -200,7 +206,7 @@ export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = { // 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(SYS_GETCWD, buf: i64, n: i64); + return syscall2(nr.GETCWD, buf: i64, n: i64); }; // getdents64(2) — Linux directory enumeration. The fd must be opened @@ -218,7 +224,7 @@ export fn getcwd(buf: *u8, n: u64) i64 = { // 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(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); + return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; // MODULE: wcc diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index c6f25415..de180c9f 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -3,11 +3,11 @@ // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. -@symbol("rt_syscall") fn syscall0(num: i64) i64; -@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_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; @@ -19,21 +19,27 @@ 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_ACCESS: i64 = 21; -def SYS_DUP2: i64 = 33; -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; -def SYS_GETCWD: i64 = 79; -def SYS_GETDENTS64: i64 = 217; +// 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 @@ -54,22 +60,22 @@ export type whence = enum i32 { }; export fn exit(code: i32) void = { - syscall1(SYS_EXIT, code: i64); + 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(SYS_WRITE, fd: i64, buf: i64, n: i64); + return syscall3(nr.WRITE, fd: i64, buf: i64, n: i64); }; export fn read(fd: i32, buf: *u8, n: u64) i64 = { - return syscall3(SYS_READ, fd: i64, buf: i64, n: i64); + return syscall3(nr.READ, fd: i64, buf: i64, n: i64); }; export fn close(fd: i32) i32 = { - return syscall1(SYS_CLOSE, fd: i64): i32; + return syscall1(nr.CLOSE, fd: i64): i32; }; // dup2(2): make `newfd` refer to the same description as `oldfd`, @@ -77,7 +83,7 @@ export fn close(fd: i32) i32 = { // 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(SYS_DUP2, oldfd: i64, newfd: i64): i32; + return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32; }; // Fallible wrappers. The error variant is `oserror` (an i64 carrying @@ -100,7 +106,7 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_OPEN, path: i64, (flags as i32): i64, mode: i64): 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) = { @@ -113,7 +119,7 @@ export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = { // 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(SYS_LSEEK, fd: i64, off, (w as i32): i64); + return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64); }; // oserror — the underlying errno from a failed syscall, as a @@ -164,34 +170,34 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_ACCESS, path: i64, mode: i64): 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(SYS_UNLINK, path: i64): 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(SYS_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(SYS_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(SYS_EXECVE, path: i64, argv: i64, envp: i64): 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(SYS_WAIT4, pid: i64, status: i64, + return syscall4(nr.WAIT4, pid: i64, status: i64, options: i64, rusage: i64): i32; }; @@ -200,7 +206,7 @@ export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = { // 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(SYS_GETCWD, buf: i64, n: i64); + return syscall2(nr.GETCWD, buf: i64, n: i64); }; // getdents64(2) — Linux directory enumeration. The fd must be opened @@ -218,7 +224,7 @@ export fn getcwd(buf: *u8, n: u64) i64 = { // 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(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); + return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; // MODULE: wcc diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 4a8bb209..eb8c5b96 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -3,11 +3,11 @@ // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. -@symbol("rt_syscall") fn syscall0(num: i64) i64; -@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_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; @@ -19,21 +19,27 @@ 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_ACCESS: i64 = 21; -def SYS_DUP2: i64 = 33; -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; -def SYS_GETCWD: i64 = 79; -def SYS_GETDENTS64: i64 = 217; +// 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 @@ -54,22 +60,22 @@ export type whence = enum i32 { }; export fn exit(code: i32) void = { - syscall1(SYS_EXIT, code: i64); + 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(SYS_WRITE, fd: i64, buf: i64, n: i64); + return syscall3(nr.WRITE, fd: i64, buf: i64, n: i64); }; export fn read(fd: i32, buf: *u8, n: u64) i64 = { - return syscall3(SYS_READ, fd: i64, buf: i64, n: i64); + return syscall3(nr.READ, fd: i64, buf: i64, n: i64); }; export fn close(fd: i32) i32 = { - return syscall1(SYS_CLOSE, fd: i64): i32; + return syscall1(nr.CLOSE, fd: i64): i32; }; // dup2(2): make `newfd` refer to the same description as `oldfd`, @@ -77,7 +83,7 @@ export fn close(fd: i32) i32 = { // 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(SYS_DUP2, oldfd: i64, newfd: i64): i32; + return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32; }; // Fallible wrappers. The error variant is `oserror` (an i64 carrying @@ -100,7 +106,7 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_OPEN, path: i64, (flags as i32): i64, mode: i64): 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) = { @@ -113,7 +119,7 @@ export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = { // 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(SYS_LSEEK, fd: i64, off, (w as i32): i64); + return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64); }; // oserror — the underlying errno from a failed syscall, as a @@ -164,34 +170,34 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_ACCESS, path: i64, mode: i64): 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(SYS_UNLINK, path: i64): 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(SYS_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(SYS_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(SYS_EXECVE, path: i64, argv: i64, envp: i64): 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(SYS_WAIT4, pid: i64, status: i64, + return syscall4(nr.WAIT4, pid: i64, status: i64, options: i64, rusage: i64): i32; }; @@ -200,7 +206,7 @@ export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = { // 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(SYS_GETCWD, buf: i64, n: i64); + return syscall2(nr.GETCWD, buf: i64, n: i64); }; // getdents64(2) — Linux directory enumeration. The fd must be opened @@ -218,7 +224,7 @@ export fn getcwd(buf: *u8, n: u64) i64 = { // 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(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); + return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; // MODULE: wcc diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 8487549e..c39256a5 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -3,11 +3,11 @@ // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. -@symbol("rt_syscall") fn syscall0(num: i64) i64; -@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_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; @@ -19,21 +19,27 @@ 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_ACCESS: i64 = 21; -def SYS_DUP2: i64 = 33; -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; -def SYS_GETCWD: i64 = 79; -def SYS_GETDENTS64: i64 = 217; +// 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 @@ -54,22 +60,22 @@ export type whence = enum i32 { }; export fn exit(code: i32) void = { - syscall1(SYS_EXIT, code: i64); + 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(SYS_WRITE, fd: i64, buf: i64, n: i64); + return syscall3(nr.WRITE, fd: i64, buf: i64, n: i64); }; export fn read(fd: i32, buf: *u8, n: u64) i64 = { - return syscall3(SYS_READ, fd: i64, buf: i64, n: i64); + return syscall3(nr.READ, fd: i64, buf: i64, n: i64); }; export fn close(fd: i32) i32 = { - return syscall1(SYS_CLOSE, fd: i64): i32; + return syscall1(nr.CLOSE, fd: i64): i32; }; // dup2(2): make `newfd` refer to the same description as `oldfd`, @@ -77,7 +83,7 @@ export fn close(fd: i32) i32 = { // 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(SYS_DUP2, oldfd: i64, newfd: i64): i32; + return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32; }; // Fallible wrappers. The error variant is `oserror` (an i64 carrying @@ -100,7 +106,7 @@ export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_OPEN, path: i64, (flags as i32): i64, mode: i64): 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) = { @@ -113,7 +119,7 @@ export fn tryopen(path: *u8, flags: flag, mode: i32) (i32 | oserror) = { // 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(SYS_LSEEK, fd: i64, off, (w as i32): i64); + return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64); }; // oserror — the underlying errno from a failed syscall, as a @@ -164,34 +170,34 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = { // 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(SYS_ACCESS, path: i64, mode: i64): 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(SYS_UNLINK, path: i64): 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(SYS_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(SYS_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(SYS_EXECVE, path: i64, argv: i64, envp: i64): 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(SYS_WAIT4, pid: i64, status: i64, + return syscall4(nr.WAIT4, pid: i64, status: i64, options: i64, rusage: i64): i32; }; @@ -200,7 +206,7 @@ export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = { // 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(SYS_GETCWD, buf: i64, n: i64); + return syscall2(nr.GETCWD, buf: i64, n: i64); }; // getdents64(2) — Linux directory enumeration. The fd must be opened @@ -218,7 +224,7 @@ export fn getcwd(buf: *u8, n: u64) i64 = { // 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(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); + return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64); }; // MODULE: strconv