code wiki / (root) / nx_async_rt.nx

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}