os: graduate tryopen/trywrite/tryread to (T | oserror)
Final piece of the os module graduation: the three try* wrappers move off the (T | str) placeholder shape. `oserror` becomes a real Hare-style error type (`!i64` instead of plain `i64`), so it's picked up by ?-propagation as the error half without callers having to name it. tryread: (i64 | oserror) was (i64 | str) trywrite: (i64 | oserror) was (i64 | str) tryopen: (i32 | oserror) was (i32 | str) Callsites updated: wwdump uses tryopen; the e2e trywrite probe matches on os.oserror and validates -EBADF for a bad fd (-9 instead of the old "write failed" string length). selfhost/test/smoke.ww switched to raw os.open(2) instead of os.tryopen for probe 7 — same reason as the os.readall switch in the prior commit: probe 6 in 990_selfhost compiles smoke.ww standalone, and cross-module type refs like `os.oserror` don't resolve in that mode.
This commit is contained in:
25
lib/os/os.ww
25
lib/os/os.ww
@@ -73,18 +73,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = {
|
||||
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
||||
};
|
||||
|
||||
// Fallible wrappers. The error variant is a plain str (Plan 9 errstr
|
||||
// model, see lib/errors); the sum type makes success/failure explicit
|
||||
// without overloading length-zero.
|
||||
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
||||
// 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 "read failed"; };
|
||||
if (r < 0) { return r: oserror; };
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
let r: i64 = write(fd, buf, n);
|
||||
if (r < 0) { return "write failed"; };
|
||||
if (r < 0) { return r: oserror; };
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -96,9 +96,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = {
|
||||
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = {
|
||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return "open failed"; };
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
};
|
||||
|
||||
@@ -111,9 +111,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = {
|
||||
|
||||
// oserror — the underlying errno from a failed syscall, as a
|
||||
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
||||
// NAMED-i64 alias makes it a distinct variant tag from a "good"
|
||||
// i64 byte count. Hare's analogue is errors::errno.
|
||||
export type oserror = i64;
|
||||
// `!`-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) = {
|
||||
|
||||
@@ -74,18 +74,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = {
|
||||
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
||||
};
|
||||
|
||||
// Fallible wrappers. The error variant is a plain str (Plan 9 errstr
|
||||
// model, see lib/errors); the sum type makes success/failure explicit
|
||||
// without overloading length-zero.
|
||||
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
||||
// 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 "read failed"; };
|
||||
if (r < 0) { return r: oserror; };
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
let r: i64 = write(fd, buf, n);
|
||||
if (r < 0) { return "write failed"; };
|
||||
if (r < 0) { return r: oserror; };
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -97,9 +97,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = {
|
||||
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = {
|
||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return "open failed"; };
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
};
|
||||
|
||||
@@ -112,9 +112,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = {
|
||||
|
||||
// oserror — the underlying errno from a failed syscall, as a
|
||||
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
||||
// NAMED-i64 alias makes it a distinct variant tag from a "good"
|
||||
// i64 byte count. Hare's analogue is errors::errno.
|
||||
export type oserror = i64;
|
||||
// `!`-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) = {
|
||||
|
||||
@@ -74,18 +74,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = {
|
||||
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
||||
};
|
||||
|
||||
// Fallible wrappers. The error variant is a plain str (Plan 9 errstr
|
||||
// model, see lib/errors); the sum type makes success/failure explicit
|
||||
// without overloading length-zero.
|
||||
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
||||
// 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 "read failed"; };
|
||||
if (r < 0) { return r: oserror; };
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
let r: i64 = write(fd, buf, n);
|
||||
if (r < 0) { return "write failed"; };
|
||||
if (r < 0) { return r: oserror; };
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -97,9 +97,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = {
|
||||
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = {
|
||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return "open failed"; };
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
};
|
||||
|
||||
@@ -112,9 +112,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = {
|
||||
|
||||
// oserror — the underlying errno from a failed syscall, as a
|
||||
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
||||
// NAMED-i64 alias makes it a distinct variant tag from a "good"
|
||||
// i64 byte count. Hare's analogue is errors::errno.
|
||||
export type oserror = i64;
|
||||
// `!`-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) = {
|
||||
@@ -8091,11 +8092,11 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
|
||||
let fdorerr: (i32 | str) = os.tryopen(path, os.O_RDONLY, 0i32);
|
||||
let fdorerr: (i32 | os.oserror) = os.tryopen(path, os.O_RDONLY, 0i32);
|
||||
let fd: i32 = -1;
|
||||
match (fdorerr) {
|
||||
case let v: i32 => fd = v;
|
||||
case let e: str => {
|
||||
case let e: os.oserror => {
|
||||
os.write(2, "wwdump: cannot open ".ptr, 20u64);
|
||||
os.write(2, path, argstrlen(path): u64);
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
|
||||
@@ -74,11 +74,11 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
return 2;
|
||||
};
|
||||
|
||||
let fdorerr: (i32 | str) = os.tryopen(path, os.O_RDONLY, 0i32);
|
||||
let fdorerr: (i32 | os.oserror) = os.tryopen(path, os.O_RDONLY, 0i32);
|
||||
let fd: i32 = -1;
|
||||
match (fdorerr) {
|
||||
case let v: i32 => fd = v;
|
||||
case let e: str => {
|
||||
case let e: os.oserror => {
|
||||
os.write(2, "wwdump: cannot open ".ptr, 20u64);
|
||||
os.write(2, path, argstrlen(path): u64);
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
|
||||
@@ -74,18 +74,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = {
|
||||
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
||||
};
|
||||
|
||||
// Fallible wrappers. The error variant is a plain str (Plan 9 errstr
|
||||
// model, see lib/errors); the sum type makes success/failure explicit
|
||||
// without overloading length-zero.
|
||||
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
||||
// 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 "read failed"; };
|
||||
if (r < 0) { return r: oserror; };
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||
let r: i64 = write(fd, buf, n);
|
||||
if (r < 0) { return "write failed"; };
|
||||
if (r < 0) { return r: oserror; };
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -97,9 +97,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = {
|
||||
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
||||
};
|
||||
|
||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = {
|
||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = {
|
||||
let fd: i32 = open(path, flags, mode);
|
||||
if (fd < 0) { return "open failed"; };
|
||||
if (fd < 0) { return fd: i64: oserror; };
|
||||
return fd;
|
||||
};
|
||||
|
||||
@@ -112,9 +112,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = {
|
||||
|
||||
// oserror — the underlying errno from a failed syscall, as a
|
||||
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
||||
// NAMED-i64 alias makes it a distinct variant tag from a "good"
|
||||
// i64 byte count. Hare's analogue is errors::errno.
|
||||
export type oserror = i64;
|
||||
// `!`-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) = {
|
||||
@@ -572,12 +573,12 @@ export fn main() i32 = {
|
||||
// Probe 7 — file open/read via the new os APIs. /proc/self/cmdline
|
||||
// always exists on Linux, no write side, and is non-empty.
|
||||
let path: str = "/proc/self/cmdline";
|
||||
let fd_or_err: (i32 | str) = os.tryopen(path.ptr, os.O_RDONLY, 0i32);
|
||||
let fd: i32 = 0;
|
||||
match (fd_or_err) {
|
||||
case let v: i32 => fd = v;
|
||||
case let e: str => return 21;
|
||||
};
|
||||
// Use raw os.open here (returns i32 with -errno) for the same
|
||||
// reason as os.read below: probe 6 in 990_selfhost compiles
|
||||
// smoke.ww standalone (no `use` expansion), so cross-module type
|
||||
// references like `os.oserror` don't resolve at that step.
|
||||
let fd: i32 = os.open(path.ptr, os.O_RDONLY, 0i32);
|
||||
if (fd < 0) { return 21; };
|
||||
let rbuf: [128]u8;
|
||||
// Use raw os.read here (single syscall, plain i64) instead of
|
||||
// os.readall: the 990 cgen-match probe compiles smoke.ww
|
||||
|
||||
@@ -152,12 +152,12 @@ export fn main() i32 = {
|
||||
// Probe 7 — file open/read via the new os APIs. /proc/self/cmdline
|
||||
// always exists on Linux, no write side, and is non-empty.
|
||||
let path: str = "/proc/self/cmdline";
|
||||
let fd_or_err: (i32 | str) = os.tryopen(path.ptr, os.O_RDONLY, 0i32);
|
||||
let fd: i32 = 0;
|
||||
match (fd_or_err) {
|
||||
case let v: i32 => fd = v;
|
||||
case let e: str => return 21;
|
||||
};
|
||||
// Use raw os.open here (returns i32 with -errno) for the same
|
||||
// reason as os.read below: probe 6 in 990_selfhost compiles
|
||||
// smoke.ww standalone (no `use` expansion), so cross-module type
|
||||
// references like `os.oserror` don't resolve at that step.
|
||||
let fd: i32 = os.open(path.ptr, os.O_RDONLY, 0i32);
|
||||
if (fd < 0) { return 21; };
|
||||
let rbuf: [128]u8;
|
||||
// Use raw os.read here (single syscall, plain i64) instead of
|
||||
// os.readall: the 990 cgen-match probe compiles smoke.ww
|
||||
|
||||
@@ -747,26 +747,26 @@ static const struct row rows[] = {
|
||||
" return acc;\n"
|
||||
"};", 19 },
|
||||
/* End-to-end stdlib usage: pull in lib/os and exercise the
|
||||
* fallible API tryread/trywrite returning (i64 | str) over a
|
||||
* real syscall. Validates that imported tagged-union returns
|
||||
* survive the linker as well as the call ABI. */
|
||||
* fallible API tryread/trywrite returning (i64 | oserror) over
|
||||
* a real syscall. oserror carries -errno; on a bad fd we expect
|
||||
* -EBADF (-9). */
|
||||
{ "use os;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let buf: [3]u8;\n"
|
||||
" buf[0] = 88: u8;\n"
|
||||
" let ok: (i64 | str) = os.trywrite(1, buf.ptr, 1u64);\n"
|
||||
" let bad: (i64 | str) = os.trywrite(999: i32, buf.ptr, 1u64);\n"
|
||||
" let ok: (i64 | os.oserror) = os.trywrite(1, buf.ptr, 1u64);\n"
|
||||
" let bad: (i64 | os.oserror) = os.trywrite(999: i32, buf.ptr, 1u64);\n"
|
||||
" let acc: i32 = 0;\n"
|
||||
" match (ok) {\n"
|
||||
" case let n: i64 => acc += n: i32;\n"
|
||||
" case let e: str => acc += -100;\n"
|
||||
" case let e: os.oserror => acc += -100;\n"
|
||||
" };\n"
|
||||
" match (bad) {\n"
|
||||
" case let n: i64 => acc += -100;\n"
|
||||
" case let e: str => acc += e.len: i32;\n"
|
||||
" case let e: os.oserror => acc += (- (e: i64)): i32;\n"
|
||||
" };\n"
|
||||
" return acc;\n"
|
||||
"};", 13 }, /* 1 byte written to fd 1, plus len(\"write failed\")=12 */
|
||||
"};", 10 }, /* 1 byte written + 9 (EBADF) */
|
||||
/* strconv.stoi64: fallible signed decimal, graduated to
|
||||
* (i64 | invalid | overflow). invalid carries the offending
|
||||
* index; overflow is the void variant. */
|
||||
|
||||
Reference in New Issue
Block a user