code wiki / (root) / nx_interrupt_broker.nx

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}