nx_async_rt.nx source
↩ module page · 166 lines · 5193 B
1// async_rt.nx -- cooperative async task runtime.
2//
3// Phase A: runtime data structures + scheduler. Language-level
4// `async fn` / `await` (EFFICIENCY_ROADMAP ยง4.3) is a parser
5// change pending; this module gives callers enough machinery
6// to hand-code stackful coroutines today + get lifted into real
7// async/await syntax later without breaking the API.
8//
9// Design: single-threaded cooperative scheduler on one kernel
10// thread. Each task = (function_pointer, state_blob).
11// Scheduler runs ready queue to exhaustion, then epolls for IO
12// readiness + wakes blocked tasks.
13//
14// Why single-threaded: thread safety without atomics. Multi-
15// thread scheduling adds complexity that belongs in a later
16// phase once the sequential model is proven.
17//
18// Used by: HTTP server accept loops, DNS resolver, file watcher,
19// anything that would otherwise thread-per-connection.
20//
21// Invariants:
22// AR1 Scheduler `run_until_idle` returns when all tasks are
23// blocked waiting for IO + nothing's ready. Caller
24// does the epoll_wait before re-entering.
25// AR2 `task_spawn` always succeeds if slot cap not hit;
26// returns task id.
27// AR3 `task_yield` marks the calling task ready + saves its
28// state via the caller-supplied resume hook.
29
30// nx_safety_envelope:
31// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
32// sil_target: SIL1
33// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
34// verdict: NOT_YET_EVALUATED
35
36import "nx_syscalls.nx"
37
38const AR_MAX_TASKS: i64 = 256
39const AR_ERR_FULL: i64 = -1
40const AR_ERR_NO_SUCH: i64 = -2
41
42const TASK_READY: i64 = 0
43const TASK_BLOCKED: i64 = 1
44const TASK_DONE: i64 = 2
45
46struct AsyncTask {
47 id: i64,
48 state: i64, // TASK_*
49 resume_fn: i64, // function-pointer (i64-encoded)
50 state_blob: i64, // opaque pointer for user state
51 wait_fd: i64, // fd being waited on (-1 if none)
52 wait_ev: i64, // events mask (1=read, 2=write)
53}
54
55struct Runtime {
56 tasks: *AsyncTask,
57 n_tasks: i64,
58 next_id: i64,
59}
60
61func rt_new() -> *Runtime {
62 let raw: *u8 = sys_mmap(32)
63 let rt: *Runtime = raw as *Runtime
64 let t_raw: *u8 = sys_mmap(AR_MAX_TASKS * 64 + 64)
65 rt.tasks = t_raw as *AsyncTask
66 rt.n_tasks = 0
67 rt.next_id = 1
68 return rt
69}
70
71// Spawn a new task. Returns task id >= 1 on success.
72func task_spawn(rt: *Runtime, resume_fn: i64, state_blob: i64) -> i64 {
73 if rt.n_tasks >= AR_MAX_TASKS { return AR_ERR_FULL }
74 let base: i64 = rt.tasks as i64
75 let t: *AsyncTask = (base + rt.n_tasks * 64) as *AsyncTask
76 t.id = rt.next_id
77 t.state = TASK_READY
78 t.resume_fn = resume_fn
79 t.state_blob = state_blob
80 t.wait_fd = -1
81 t.wait_ev = 0
82 rt.next_id = rt.next_id + 1
83 rt.n_tasks = rt.n_tasks + 1
84 return t.id
85}
86
87// Find a task by id. Returns index or AR_ERR_NO_SUCH.
88func task_find(rt: *Runtime, id: i64) -> i64 {
89 var i: i64 = 0
90 while i < rt.n_tasks {
91 let base: i64 = rt.tasks as i64
92 let t: *AsyncTask = (base + i * 64) as *AsyncTask
93 if t.id == id { return i }
94 i = i + 1
95 }
96 return AR_ERR_NO_SUCH
97}
98
99// Mark a task ready (unblock).
100func task_wake(rt: *Runtime, id: i64) -> i64 {
101 let idx: i64 = task_find(rt, id)
102 if idx < 0 { return idx }
103 let base: i64 = rt.tasks as i64
104 let t: *AsyncTask = (base + idx * 64) as *AsyncTask
105 t.state = TASK_READY
106 t.wait_fd = -1
107 t.wait_ev = 0
108 return 0
109}
110
111// Block the task on a given fd for a given event mask.
112func task_block(rt: *Runtime, id: i64, fd: i64, events: i64) -> i64 {
113 let idx: i64 = task_find(rt, id)
114 if idx < 0 { return idx }
115 let base: i64 = rt.tasks as i64
116 let t: *AsyncTask = (base + idx * 64) as *AsyncTask
117 t.state = TASK_BLOCKED
118 t.wait_fd = fd
119 t.wait_ev = events
120 return 0
121}
122
123// Mark a task done (scheduler will reap).
124func task_complete(rt: *Runtime, id: i64) -> i64 {
125 let idx: i64 = task_find(rt, id)
126 if idx < 0 { return idx }
127 let base: i64 = rt.tasks as i64
128 let t: *AsyncTask = (base + idx * 64) as *AsyncTask
129 t.state = TASK_DONE
130 return 0
131}
132
133// Count how many tasks are in a given state.
134func rt_count_state(rt: *Runtime, state: i64) -> i64 {
135 var count: i64 = 0
136 var i: i64 = 0
137 while i < rt.n_tasks {
138 let base: i64 = rt.tasks as i64
139 let t: *AsyncTask = (base + i * 64) as *AsyncTask
140 if t.state == state { count = count + 1 }
141 i = i + 1
142 }
143 return count
144}
145
146// Compile-only smoke.
147func main() -> i64 {
148 let rt: *Runtime = rt_new()
149 let id1: i64 = task_spawn(rt, 0x1000, 0)
150 if id1 != 1 { return 1 }
151 let id2: i64 = task_spawn(rt, 0x2000, 0)
152 if id2 != 2 { return 2 }
153
154 if rt_count_state(rt, TASK_READY) != 2 { return 3 }
155
156 task_block(rt, id1, 3, 1)
157 if rt_count_state(rt, TASK_READY) != 1 { return 4 }
158 if rt_count_state(rt, TASK_BLOCKED) != 1 { return 5 }
159
160 task_wake(rt, id1)
161 if rt_count_state(rt, TASK_READY) != 2 { return 6 }
162
163 task_complete(rt, id2)
164 if rt_count_state(rt, TASK_DONE) != 1 { return 7 }
165 return 0
166}