fix: aarch64-linux port of the M:1 fiber runtime (sched.sx)
Port library/modules/std/sched.sx to run on aarch64-linux alongside aarch64-macOS, validated byte-identical on both via Apple `container`. Per-OS bits are comptime-branched: - MAP_AP (mmap MAP_ANON flag): linux 0x22 / macOS 0x1002. - fd-readiness backend: epoll on linux, kqueue on darwin (epoll import scoped to the linux branch). block_on_fd, the run-loop Mode-2 drain, and cancel_io_waiter_for each branch; the epoll paths EPOLL_CTL_DEL on fire and on early-wake (EPOLLONESHOT only disables a registration; kqueue EV_ONESHOT auto-removes it). - first-entry trampoline: a per-OS hand-written global-asm symbol becomes a naked sx fn fib_tramp (mov x0,x19; br x20) + register-indirect dispatch (spawn presets regs[1] == x20 == &fib_dispatch), dropping the per-OS .global symbol entirely. Fixes issue 0193 Bug A: the trampoline redesign bus-errored on the go/wait/sleep capstone (1817) until `export "fib_dispatch"` was restored. Without the export, fib_dispatch reverts to sx's internal ABI (x0 = implicit context, first arg self shifted to x1) while the trampoline hands self over in x0 (C-ABI); on first entry the body runs (x1 happens to alias self) but the closure then loads regs[1] == &fib_dispatch as its first capture and re-invokes fib_dispatch forever -> stack overflow -> bus error. The export pins fib_dispatch to the C-ABI (self in x0), matching the trampoline. Root cause found via lldb on an AOT build; confirmed against the compiler source. Bug B (a top-level asm block wrapped in inline-if is dropped during the comptime-conditional flatten) is carved out to issue 0194 (OPEN) -- no live trigger remains, since the naked-fn trampoline sidesteps it. 1811/1814/1816/1817 run byte-identical on the aarch64-macOS host and in an aarch64-linux container; full suite green (817/0). Documents the fiber runtime in readme.md.
This commit is contained in:
46
readme.md
46
readme.md
@@ -30,6 +30,7 @@ main :: () {
|
||||
- Pattern matching on enums, optionals, and type categories
|
||||
- C interop via `extern` / `export` and `#import c`
|
||||
- Inline assembly as a first-class expression
|
||||
- Colorblind async via a pure-sx cooperative fiber runtime (no function coloring)
|
||||
- Targets: macOS (ARM64, x86_64), Linux (x86_64, ARM64), Windows (x86_64), WebAssembly
|
||||
|
||||
## Usage
|
||||
@@ -511,6 +512,51 @@ fence(.seq_cst); // standalone memory fence
|
||||
combinations are compile errors. The same operations run at compile time (`#run`)
|
||||
under single-threaded semantics.
|
||||
|
||||
### Async / Concurrency (`modules/std/sched.sx`)
|
||||
|
||||
A pure-sx cooperative fiber runtime — **colorblind async**, with no `async` /
|
||||
`await` keywords and no function coloring. Any function can suspend; a `Scheduler`
|
||||
drives any number of stackful fibers, each on its own guard-paged stack. The
|
||||
high-level API is `go` to spawn a task and `wait` to suspend until it completes:
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
sched :: #import "modules/std/sched.sx";
|
||||
|
||||
main :: () {
|
||||
s := sched.Scheduler.init();
|
||||
ps := @s; // closures capture by value — capture a pointer to the scheduler
|
||||
|
||||
// The coordinator runs as a fiber so `wait` has a fiber to park.
|
||||
s.spawn(() => {
|
||||
a := ps.go(() -> i64 => { ps.sleep(30); 100 }); // launch async tasks
|
||||
b := ps.go(() -> i64 => { ps.sleep(10); 20 });
|
||||
c := ps.go(() -> i64 => { ps.sleep(20); 3 });
|
||||
|
||||
sum := (a.wait() or 0) + (b.wait() or 0) + (c.wait() or 0); // 123
|
||||
print("sum: {}\n", sum);
|
||||
});
|
||||
|
||||
s.run(); // drive the scheduler until all fibers finish
|
||||
}
|
||||
```
|
||||
|
||||
Tasks complete in deadline order, not spawn or await order. The runtime offers:
|
||||
|
||||
- **`go(work) -> *Task($R)`** / **`wait() -> R !TaskErr`** / **`cancel()`** — the
|
||||
task layer. `wait` rides the `!` error channel so a cancel surfaces as
|
||||
`error.Canceled`.
|
||||
- **`spawn`**, **`yield_now`**, **`suspend_self`**, **`wake`** — the raw fiber
|
||||
primitives the task layer is built on.
|
||||
- **`sleep(ms)`** / **`now_ms()`** — timer-driven suspension on a virtual clock
|
||||
(deterministic, no real wall time).
|
||||
- **`block_on_fd(fd, want_read)`** — suspend until a file descriptor is ready,
|
||||
backed by kqueue (darwin) or epoll (linux).
|
||||
|
||||
It's an M:1 model (cooperative, no preemption — so no data races between fibers
|
||||
and no atomics needed across them), built on `abi(.naked)` context switching over
|
||||
guarded `mmap` stacks. Currently aarch64-pinned (macOS + Linux).
|
||||
|
||||
### Command-line interface (`modules/std/cli.sx`)
|
||||
|
||||
`std.cli` builds command-line front-ends over an explicit logical argv: `os_args`
|
||||
|
||||
Reference in New Issue
Block a user