Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 48 additions & 20 deletions bend2/comp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1304,9 +1304,13 @@ export function io_type(book: Bend.Book): HTerm | null {
return xs?.length === 1 ? xs[0] : null;
}

function io_status(): boolean {
return ty_adt(io_type(FL.book))?.k === "U32";
}

export function io_run(book: Bend.Book, args: string[]): number {
const src = `${js_lib(book)}\n${RUNTIME_MAIN}\ncli_args = ${
JSON.stringify(args)};\nreturn io_run(${js_sat("main")});`;
JSON.stringify(args)};\nreturn io_run(${js_sat("main")}, ${io_status()});`;
return new Function("require", src)(import.meta.require);
}

Expand Down Expand Up @@ -2883,6 +2887,7 @@ export function compile_book(book: Bend.Book): string {
`#define WL_TABLE ${entries.map((s) => `WL_X(${s.fid})`).join(" ")
} WL_X(FID_EXIT)`, `#define MAIN_FID ${seg_fid("main")}`,
`#define MAIN_PURE ${Number(show !== null)}`,
`#define MAIN_STATUS ${Number(io_status())}`,
`#define BLK_SHR ${Number(FL.hot.has("t:Array"))}`);
const tabs = [defs.join("\n"), ...[...FL.tabs].map(([r, i]) =>
`CONSTV u64 TAB_${i}[] = { ${r} };`)].join("\n\n");
Expand Down Expand Up @@ -3247,7 +3252,7 @@ export function js_book(book: Bend.Book): string {
const show = show_main();
return `${lib}\n${RUNTIME_MAIN}\ncli(process.argv.slice(1));\nio_exit(${
js_sat("main")}, ${JSON.stringify(show && show.map((c) =>
typeof c === "string" ? Bend.name_key(c) : c))});`;
typeof c === "string" ? Bend.name_key(c) : c))}, ${io_status()});`;
}

// RuntimeC
Expand Down Expand Up @@ -5372,6 +5377,13 @@ typedef Term (*Effect)(Env e, Term* f, IoWork* w);
Effect io_eff_rows[sizeof CID_T / sizeof *CID_T];
static u32 io_live;

static int io_code(u32 code) {
if (code > 255) {
err_fail("exit status must be between 0 and 255");
}
return code;
}

static u64 io_tick(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
Expand Down Expand Up @@ -5418,6 +5430,9 @@ static IoWork* io_runs;
static IoWork* io_park;
static IoWork* io_jobs;

static IoWork* io_root;
static u32 io_result;

static void io_push(IoWork** q, IoWork* a) {
IoWork* l = *q != NULL ? *q : a;
a->next = l->next;
Expand All @@ -5440,9 +5455,7 @@ static void io_spawn(Term m) {
io_live += 1;
}

// io_park stays in deadline order (time 0, none, sorts last; ties keep
// their park order), so io_wait wakes due timers in the order they expire.
// It links both ways, so io_park_cut drops a waiter in O(1).
// Deadline order: time 0 last, stable ties. Doubly linked for O(1) removal.
static void io_park_add(IoWork* w) {
IoWork* p = io_park;
if (p == NULL || p->time - 1 <= w->time - 1) {
Expand Down Expand Up @@ -5882,6 +5895,10 @@ static void io_step(Env e, IoWork* a) {
Term req = corpus_eval(e.mem, term_tsk(FID(Clo~apply), ap));
u32 c = (u32)term_aux(req);
if (c == CID(Emit)) {
if (MAIN_STATUS && a == io_root) {
io_result = e.mem[term_peek(e.mem, req)];
io_root = NULL;
}
term_drop(e, req);
free(a);
io_live -= 1;
Expand All @@ -5891,8 +5908,9 @@ static void io_step(Env e, IoWork* a) {
u32 n = cid_arity(c);
spare_free(e, cls_fit(n), ctr_take(e, req, n, fs));
if (c == CID(Halt)) {
int code = io_code(fs[0]);
io_errs(e, fs[1]);
exit((int)(u32)fs[0]);
exit(code);
}
if (io_eff_rows[c] == NULL) {
err_fail("an alien request");
Expand Down Expand Up @@ -5921,6 +5939,9 @@ OUTLINE void io_loop(u64* H) {
return;
#endif
io_spawn(m);
if (MAIN_STATUS) {
io_root = io_runs;
}
u64 look = 0;
for (u32 n = 0;; n += 1) {
if (io_runs == NULL) {
Expand Down Expand Up @@ -6012,7 +6033,7 @@ int main(int argc, char** argv) {
}
io_loop(corpus_setup(why == NULL, thr > 0 ? thr : cpu_count(), mem));
io_sync();
return 0;
return MAIN_STATUS ? io_code(io_result) : 0;
}

#endif
Expand Down Expand Up @@ -6154,21 +6175,23 @@ function show_val(D, d, v, chain) {
// Io
// ==

// Apple arm64 passes variadic fcntl flags on the stack, so io_sys
// binds fcntl there with the flags as the ninth fixed argument. A
// parked effect waits for fd (a write when out) or until at
// (performance.now()), either one undefined when unused; once due, io_wait
// calls more at once, as C calls pack, and resumes k with its value, while
// undefined parks it again. The waits stay in deadline order, as io_park
// does in C.
// Apple arm64 fcntl flags are stack argument 9. Deadline-ordered waits accept
// fd/out or at (performance.now()); more (C: pack) resumes k, undefined re-parks.

function io_code(code) {
if (code < 0 || code > 255) {
throw "bend: exit status must be between 0 and 255";
}
return code;
}

function io_exit(main, show) {
function io_exit(main, show, status) {
try {
if (show !== null) {
io_out(1, io_bytes(show_val(show, 0, run_loop(main()), 0) + "\n"));
process.exit(0);
}
process.exit(io_run(main));
process.exit(io_run(main, status));
} catch (e) {
io_errs(String(e));
process.exit(1);
Expand Down Expand Up @@ -6351,16 +6374,20 @@ function io_park_on(fd, out, k, more, at) {
ws.splice(i + 1, 0, { fd, out, k, more, at });
}

function io_run(m) {
function io_run(m, status) {
const io = { runs: [], live: 0, waits: [] };
globalThis.BEND_IO = io;
let code = 0;
try {
io_push(run_loop(m()), (x) => ({ $: "Emit", value: x }), true);
io_push(run_loop(m()), (x) => {
if (status) code = x;
return { $: "Emit", value: x };
}, true);
let look = 0;
for (let n = 0;; n += 1) {
if (io.runs.length === 0) {
if (io.live === 0) {
return 0;
return status ? io_code(code) : 0;
}
if (io.waits.length === 0) {
io_errs("bend: deadlock: every computation waits on a channel");
Expand All @@ -6384,8 +6411,9 @@ function io_run(m) {
break;
}
if (op.$ === "Halt") {
const halt = io_code(op.code);
io_errs(op.message);
return op.code;
return halt;
}
const run = $0eff[op.$];
if (run === undefined) {
Expand Down
14 changes: 7 additions & 7 deletions guide/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,13 +368,13 @@ def main() -> IO(Unit):
IO.print(text)
```

Every bind is annotated, and `x : T = v` binds a pure value in the middle of a
block. A fallible effect answers `Result<&1, &1, U32 & String, A>`: `IO.try`
unwraps it or exits with the error, and `IO.die` exits with your own. `IO.args`
answers the command line, less the runtime's own options (a `--` ends them):
its head is the program as invoked, like C's `argv[0]`. A handle (`File`,
`Socket`, `Window`) is an affine, opaque value, so every effect on one hands it
back beside its result, and no program can forge or reuse one.
Binds need types; `x : T = v` binds a pure value. Fallible effects answer
`Result<&1, &1, U32 & String, A>`; `IO.try` unwraps or exits with the error.
`IO.die` exits with your message. An `IO(U32)` main exits silently with its
result; `IO(Unit)` exits 0. Main/die codes outside 0..255 fail with a diagnostic
and exit 1. `IO.args` includes the program name and omits runtime options before
`--`. Handles are affine and opaque: effects return them beside results,
preventing forging or reuse.

`TCP.listen(host, port)` and `UDP.bind(host, port)` bind the IPv4 literal
`host`: `"127.0.0.1"` serves this machine only, `"0.0.0.0"` every interface.
Expand Down
6 changes: 2 additions & 4 deletions tests/io/channel_fork_join.bend
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# io (io_channel_fork_join): bend3's channel round trip on bend's
# surface. A spawned producer sends 11 and 22 into a channel with
# room for four and closes it; main receives both in order (Some) and
# prints 1122, then forks a pure value and joins it. main's value is
# not the exit status: the process exits 0 once every computation
# has answered.
# prints 1122, then forks and joins a pure value, returning success.
import Base

law producer:
Expand Down Expand Up @@ -40,7 +38,7 @@ def main.go(c):
z : Unit <- IO.print(U32.show((got(x) * 100 + got(y) : U32)))
t : Chan(U32) <- IO.fork(U32, IO.pure(U32, 5))
w : U32 <- IO.join(U32, t)
return w
return 0

law main:
IO(U32)
Expand Down
7 changes: 7 additions & 0 deletions tests/io/die_status_invalid.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Base

def main() -> IO(Unit):
IO.die(Unit, 256, "x")

#|bend: exit status must be between 0 and 255
#|exit 1
9 changes: 9 additions & 0 deletions tests/io/exit_status.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Base

def main() -> IO(U32):
do IO<U32>:
IO.print_err("usage error")
return 2

#|usage error
#|exit 2
22 changes: 22 additions & 0 deletions tests/io/exit_status_children.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Base

def grandchild() -> IO(U32):
do IO<U32>:
IO.print("grandchild finished")
return 9

def child() -> IO(U32):
do IO<U32>:
IO.sleep(1)
IO.spawn(U32, grandchild())
IO.print("child finished")
return 7

def main() -> IO(U32):
do IO<U32>:
IO.spawn(U32, child())
return 2

#|child finished
#|grandchild finished
#|exit 2
8 changes: 8 additions & 0 deletions tests/io/exit_status_invalid.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Base

def main() -> IO(U32):
do IO<U32>:
return 256

#|bend: exit status must be between 0 and 255
#|exit 1
7 changes: 7 additions & 0 deletions tests/io/exit_status_limit.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Base

def main() -> IO(U32):
do IO<U32>:
return 255

#|exit 255
2 changes: 1 addition & 1 deletion tests/io/fiber_fork_join.bend
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ def main():
a : Unit <- IO.join(Unit, t)
u : Unit <- IO.spawn(Unit, IO.pure(Unit, Unit{}))
b : Unit <- IO.print("joined")
return 9
return 0

#|joined
6 changes: 2 additions & 4 deletions tests/io/print_write.bend
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# io (io_print_write): print (newline) and write (raw) sequence in
# order through the event loop, and U32.show feeds a computed string
# to an effect. Ported from bend3; bend3's result-word exit code was
# bend3 CLI behavior -- bend's loop discards the Emit word, and the
# interp lane pins no exit line.
# to an effect.
import Base

law main:
Expand All @@ -13,7 +11,7 @@ def main():
a : Unit <- IO.print("hello")
b : Unit <- IO.write("num=")
c : Unit <- IO.print(U32.show((40 + 2 : U32)))
return 7
return 0

#|hello
#|num=42