nx_interrupt_broker.nx source
↩ module page · 297 lines · 10722 B
1// nx_interrupt_broker.nx -- sub-50ms preemption between concurrent generators.
2//
3// Per CARDINAL [[feedback-parallel-companion-multimodal-dnd-real-time]]:
4// when the operator presses a button mid-image-gen, the substrate must
5// preempt the long-running compute (save checkpoint + yield) so the
6// click-response (game action, system event) lands inside 50ms. V1
7// ships the preemption verdict + checkpoint discipline + deadline
8// tracking that the dispatcher/conductor obeys.
9//
10// V1 ships: interrupt request enum, deadline class enum, preemption
11// decision (yield / continue / refuse) with reasoned verdict, checkpoint
12// log, deadline-miss accounting.
13
14import "nx_syscalls.nx"
15import "nx_tier.nx"
16
17// ===== Sealed enum: NxInterruptKind ===============================
18
19const NX_IB_KIND_OPERATOR_INPUT: nx_int = 0 // operator clicked / typed
20const NX_IB_KIND_REALTIME_DEADLINE: nx_int = 1 // audio frame deadline
21const NX_IB_KIND_RESOURCE_PRESSURE: nx_int = 2 // VRAM about to OOM
22const NX_IB_KIND_PEER_REQUEST: nx_int = 3 // mesh peer needs response
23const NX_IB_KIND_HEALTH_SIGNAL: nx_int = 4 // operator health alarm
24const NX_IB_KIND_FAMILY_PRESENCE: nx_int = 5 // child entered room
25const NX_IB_KIND_SCHEDULED_TICK: nx_int = 6 // low-priority cron
26const NX_IB_KIND_N: nx_int = 7
27
28// ===== Sealed enum: NxDeadlineClass ===============================
29
30const NX_IB_DL_INSTANT: nx_int = 0 // <= 10 ms
31const NX_IB_DL_REALTIME: nx_int = 1 // <= 50 ms
32const NX_IB_DL_INTERACTIVE: nx_int = 2 // <= 200 ms
33const NX_IB_DL_RESPONSIVE: nx_int = 3 // <= 1 s
34const NX_IB_DL_BACKGROUND: nx_int = 4 // best-effort
35const NX_IB_DL_N: nx_int = 5
36
37// ===== Sealed enum: NxPreemptVerdict ==============================
38
39const NX_IB_V_YIELD: nx_int = 0 // running op must yield
40const NX_IB_V_CONTINUE: nx_int = 1 // running op may continue
41const NX_IB_V_REFUSED_CRITICAL: nx_int = 2 // running op cannot yield (mid-attention)
42const NX_IB_V_DEADLINE_MISSED: nx_int = 3 // already past deadline
43const NX_IB_V_INVALID: nx_int = 4
44const NX_IB_V_NULL: nx_int = 5
45const NX_IB_V_N: nx_int = 6
46
47// ===== Struct: NxInterruptRequest =================================
48
49struct NxInterruptRequest {
50 request_id: nx_int,
51 kind: nx_int,
52 deadline_class: nx_int,
53 issued_at_us: nx_size,
54 deadline_at_us: nx_size,
55 source_priority: nx_int, // 0..100
56 payload_handle: nx_size,
57}
58
59const NX_IB_R_BYTES: nx_int = 56
60
61// ===== Struct: NxRunningOp ========================================
62
63struct NxRunningOp {
64 op_id: nx_int,
65 op_priority: nx_int, // 0..100
66 started_at_us: nx_size,
67 estimated_remaining_us: nx_size,
68 yieldable_at_us: nx_size, // next safe checkpoint
69 is_yieldable_now: nx_int, // 1 = at checkpoint, 0 = mid-kernel
70}
71
72const NX_IB_OP_BYTES: nx_int = 48
73
74struct NxInterruptBroker {
75 requests: *u8, // pending interrupts
76 n_requests: nx_int,
77 capacity: nx_int,
78 deadline_misses: nx_int,
79 preemptions_granted: nx_int,
80 preemptions_refused: nx_int,
81}
82
83const NX_IB_BYTES: nx_int = 40
84
85// ===== Validators =================================================
86
87func nx_ib_kind_is_valid(k: nx_int) -> nx_int {
88 if k < 0 { return 0 }
89 if k >= NX_IB_KIND_N { return 0 }
90 return 1
91}
92
93func nx_ib_dl_is_valid(d: nx_int) -> nx_int {
94 if d < 0 { return 0 }
95 if d >= NX_IB_DL_N { return 0 }
96 return 1
97}
98
99func nx_ib_v_is_valid(v: nx_int) -> nx_int {
100 if v < 0 { return 0 }
101 if v >= NX_IB_V_N { return 0 }
102 return 1
103}
104
105// Deadline class -> max wait window in microseconds.
106func nx_ib_dl_window_us(d: nx_int) -> nx_size {
107 if d == NX_IB_DL_INSTANT { return 10000 }
108 if d == NX_IB_DL_REALTIME { return 50000 }
109 if d == NX_IB_DL_INTERACTIVE { return 200000 }
110 if d == NX_IB_DL_RESPONSIVE { return 1000000 }
111 if d == NX_IB_DL_BACKGROUND { return 60000000 }
112 return 0
113}
114
115func nx_ib_kind_is_critical(k: nx_int) -> nx_int {
116 if k == NX_IB_KIND_OPERATOR_INPUT { return 1 }
117 if k == NX_IB_KIND_HEALTH_SIGNAL { return 1 }
118 if k == NX_IB_KIND_REALTIME_DEADLINE { return 1 }
119 return 0
120}
121
122// ===== Constructor ================================================
123
124func nx_ib_new(capacity: nx_int) -> *NxInterruptBroker {
125 if capacity <= 0 { return 0 as *NxInterruptBroker }
126 let raw: *u8 = sys_mmap(NX_IB_BYTES)
127 let b: *NxInterruptBroker = raw as *NxInterruptBroker
128 b.requests = sys_mmap(capacity * NX_IB_R_BYTES)
129 b.n_requests = 0
130 b.capacity = capacity
131 b.deadline_misses = 0
132 b.preemptions_granted = 0
133 b.preemptions_refused = 0
134 return b
135}
136
137func _ib_req_at(b: *NxInterruptBroker, idx: nx_int) -> *NxInterruptRequest {
138 if idx < 0 { return 0 as *NxInterruptRequest }
139 if idx >= b.n_requests { return 0 as *NxInterruptRequest }
140 let off: nx_int = idx * NX_IB_R_BYTES
141 return (b.requests + off) as *NxInterruptRequest
142}
143
144// ===== File interrupt =============================================
145
146func nx_ib_file(b: *NxInterruptBroker,
147 request_id: nx_int,
148 kind: nx_int,
149 deadline_class: nx_int,
150 source_priority: nx_int,
151 payload_handle: nx_size,
152 now_us: nx_size) -> nx_int {
153 if (b as i64) == 0 { return NX_IB_V_NULL }
154 if nx_ib_kind_is_valid(kind) == 0 { return NX_IB_V_INVALID }
155 if nx_ib_dl_is_valid(deadline_class) == 0 { return NX_IB_V_INVALID }
156 if b.n_requests >= b.capacity { return NX_IB_V_INVALID }
157 let off: nx_int = b.n_requests * NX_IB_R_BYTES
158 let r: *NxInterruptRequest = (b.requests + off) as *NxInterruptRequest
159 r.request_id = request_id
160 r.kind = kind
161 r.deadline_class = deadline_class
162 r.issued_at_us = now_us
163 r.deadline_at_us = now_us + nx_ib_dl_window_us(deadline_class)
164 r.source_priority = source_priority
165 r.payload_handle = payload_handle
166 b.n_requests = b.n_requests + 1
167 return NX_IB_V_YIELD
168}
169
170// ===== Arbitrate single request vs single running op ==============
171
172func nx_ib_arbitrate(b: *NxInterruptBroker,
173 request_id: nx_int,
174 op: *NxRunningOp,
175 now_us: nx_size) -> nx_int {
176 if (b as i64) == 0 { return NX_IB_V_NULL }
177 if (op as i64) == 0 { return NX_IB_V_NULL }
178 // Locate the request
179 var i: nx_int = 0
180 var found: *NxInterruptRequest = 0 as *NxInterruptRequest
181 while i < b.n_requests {
182 let r: *NxInterruptRequest = _ib_req_at(b, i)
183 if r.request_id == request_id {
184 found = r
185 i = b.n_requests
186 }
187 i = i + 1
188 }
189 if (found as i64) == 0 { return NX_IB_V_INVALID }
190 // Past deadline?
191 if now_us > found.deadline_at_us {
192 b.deadline_misses = b.deadline_misses + 1
193 return NX_IB_V_DEADLINE_MISSED
194 }
195 // Critical interrupt always yields unless op is mid-non-yieldable kernel
196 let critical: nx_int = nx_ib_kind_is_critical(found.kind)
197 if critical == 1 {
198 if op.is_yieldable_now == 1 {
199 b.preemptions_granted = b.preemptions_granted + 1
200 return NX_IB_V_YIELD
201 }
202 // Not yieldable now: refuse only if checkpoint is past the deadline
203 if op.yieldable_at_us > found.deadline_at_us {
204 b.preemptions_refused = b.preemptions_refused + 1
205 return NX_IB_V_REFUSED_CRITICAL
206 }
207 // Will yield at next checkpoint within budget
208 b.preemptions_granted = b.preemptions_granted + 1
209 return NX_IB_V_YIELD
210 }
211 // Non-critical: compare priorities. Interrupt only wins by clear margin.
212 if found.source_priority > (op.op_priority + 10) {
213 if op.is_yieldable_now == 1 {
214 b.preemptions_granted = b.preemptions_granted + 1
215 return NX_IB_V_YIELD
216 }
217 if op.yieldable_at_us <= found.deadline_at_us {
218 b.preemptions_granted = b.preemptions_granted + 1
219 return NX_IB_V_YIELD
220 }
221 }
222 return NX_IB_V_CONTINUE
223}
224
225// ===== Pop request after handling ==================================
226
227func nx_ib_resolve(b: *NxInterruptBroker, request_id: nx_int) -> nx_int {
228 if (b as i64) == 0 { return NX_IB_V_NULL }
229 var i: nx_int = 0
230 var found_idx: nx_int = -1
231 while i < b.n_requests {
232 let r: *NxInterruptRequest = _ib_req_at(b, i)
233 if r.request_id == request_id {
234 found_idx = i
235 i = b.n_requests
236 }
237 i = i + 1
238 }
239 if found_idx < 0 { return NX_IB_V_INVALID }
240 // Shift remaining requests down
241 var j: nx_int = found_idx
242 while j < (b.n_requests - 1) {
243 let src: *NxInterruptRequest = _ib_req_at(b, j + 1)
244 let dst: *NxInterruptRequest = _ib_req_at(b, j)
245 dst.request_id = src.request_id
246 dst.kind = src.kind
247 dst.deadline_class = src.deadline_class
248 dst.issued_at_us = src.issued_at_us
249 dst.deadline_at_us = src.deadline_at_us
250 dst.source_priority = src.source_priority
251 dst.payload_handle = src.payload_handle
252 j = j + 1
253 }
254 b.n_requests = b.n_requests - 1
255 return NX_IB_V_YIELD
256}
257
258// ===== Op factory =================================================
259
260func nx_ib_op_new(op_id: nx_int,
261 op_priority: nx_int,
262 started_at_us: nx_size,
263 estimated_remaining_us: nx_size,
264 yieldable_at_us: nx_size,
265 is_yieldable_now: nx_int) -> *NxRunningOp {
266 let raw: *u8 = sys_mmap(NX_IB_OP_BYTES)
267 let op: *NxRunningOp = raw as *NxRunningOp
268 op.op_id = op_id
269 op.op_priority = op_priority
270 op.started_at_us = started_at_us
271 op.estimated_remaining_us = estimated_remaining_us
272 op.yieldable_at_us = yieldable_at_us
273 op.is_yieldable_now = is_yieldable_now
274 return op
275}
276
277// ===== Accessors ==================================================
278
279func nx_ib_pending_count(b: *NxInterruptBroker) -> nx_int {
280 if (b as i64) == 0 { return 0 }
281 return b.n_requests
282}
283
284func nx_ib_deadline_misses(b: *NxInterruptBroker) -> nx_int {
285 if (b as i64) == 0 { return 0 }
286 return b.deadline_misses
287}
288
289func nx_ib_grants(b: *NxInterruptBroker) -> nx_int {
290 if (b as i64) == 0 { return 0 }
291 return b.preemptions_granted
292}
293
294func nx_ib_refusals(b: *NxInterruptBroker) -> nx_int {
295 if (b as i64) == 0 { return 0 }
296 return b.preemptions_refused
297}