code wiki / (root) / nx_yield.nx

nx_yield.nx source

↩ module page · 159 lines · 6821 B

1// nx_yield.nx -- cooperative yield contract between cells. 2// 3// Per cardinal: cooperative-resource-arbitration. Yield is COOPERATIVE, 4// not preemptive. A cell declares a yield-within-N-microseconds 5// contract on instantiation; if a higher-priority class requests the 6// contested resource, the cell receives a yield signal and must 7// release within its declared quantum. Cells that miss their quantum 8// are first demoted (priority dropped one rung) and then throttled 9// (forced sleeps inserted until they catch up). 10// 11// This is the structural EXCEED-axis vs preemptive OS scheduling: 12// preemption causes cache-line invalidation and TLB shootdowns that 13// tank foreground game frame budget. Cooperative yield lets the 14// preempted cell hit a clean checkpoint before releasing, preserving 15// L1/L2 state for the in-coming foreground. 16// 17// Composes: 18// nx_attention_class -- determines who yields to whom 19// nx_budget -- yield can free budget headroom for waiter 20// nx_evict_journal -- DEMOTED / THROTTLED outcomes logged 21// nx_homeostasis -- repeat-misser cells become migration targets 22// 23// V1 ships a single shared resource model. Per-resource arbitration 24// queues (RAM vs VRAM vs CPU) are queued; today all five resources 25// arbitrate via one contention check. 26// 27// Gap list (V1 honest perf verdict): 28// - no priority-inheritance to avoid inversion 29// - no per-resource arbitration queues (single contention model) 30// - no monotonic clock integration (caller supplies tick_us) 31// - no automatic re-promotion after throttle (caller must reset) 32// 33// genealogy_id: nishi_cardinal_2026-05-17_cooperative_resource_arbitration 34// lineage_id: substrate_yield_v1 35// 36// nx_safety_envelope: 37// intended_use: "Cooperative yield contract enforcement; 38// contested resources released within quantum" 39// sil_target: SIL2 40// evidence: [contract_declared_at_instantiation, 41// miss_demotes_then_throttles_never_kills] 42// hazard_register: [bug-tape-yield-priority-inversion, 43// bug-tape-yield-livelock-on-equal-priority] 44// verdict: NOT_YET_EVALUATED 45 46import "nx_syscalls.nx" 47import "nx_tier.nx" 48import "nx_attention_class.nx" 49 50// ===== Sealed enum: NxYieldVerdict ================================ 51 52const NX_YIELD_OK: nx_int = 0 // grant, holder keeps resource 53const NX_YIELD_GRANTED: nx_int = 1 // request succeeded, holder yielded 54const NX_YIELD_DENIED: nx_int = 2 // requester lower prio, no yield 55const NX_YIELD_DEMOTED: nx_int = 3 // holder missed quantum, dropped a rung 56const NX_YIELD_THROTTLED: nx_int = 4 // holder repeatedly missed, throttled 57const NX_YIELD_ERR_BAD_ARGS: nx_int = 5 58 59// ===== Struct: NxYieldContract ==================================== 60// 61// One contract per cell. quantum_us declared at instantiation; misses 62// count tracks consecutive missed yields to decide demote-vs-throttle. 63// last_grant_tick_us is monotonic-clock value supplied by caller. 64 65struct NxYieldContract { 66 cell_id: nx_int, 67 attention_class: nx_int, 68 quantum_us: nx_size, 69 misses: nx_int, 70 last_grant_tick_us: nx_size, 71 throttle_until_us: nx_size, 72} 73 74// Demote-after-N-misses; throttle-after-M-misses. These are the only 75// magic numbers; per Cardinal 11 they live as named constants so 76// future tuning is a one-line edit, not a code-search. 77const NX_YIELD_MISS_DEMOTE_AT: nx_int = 1 78const NX_YIELD_MISS_THROTTLE_AT: nx_int = 3 79const NX_YIELD_THROTTLE_FOR_US: nx_size = 50000 80 81// ===== Constructor =============================================== 82 83func nx_yield_contract_new(cell_id: nx_int, 84 attention_class: nx_int, 85 quantum_us: nx_size, 86 now_us: nx_size) -> *NxYieldContract { 87 let c: *NxYieldContract = (sys_mmap(48)) as *NxYieldContract 88 c.cell_id = cell_id 89 c.attention_class = attention_class 90 c.quantum_us = quantum_us 91 c.misses = 0 92 c.last_grant_tick_us = now_us 93 c.throttle_until_us = 0 94 return c 95} 96 97// ===== nx_yield_request ========================================== 98// 99// Higher-priority requester (class lower numeric value) gets the 100// resource if the holder is currently throttled OR has held past its 101// quantum. Equal-priority sees DENIED to avoid livelock. Lower-prio 102// requester always sees DENIED. 103// 104// Returns one of NX_YIELD_GRANTED / DENIED / DEMOTED / THROTTLED. 105// On GRANTED, caller (the requester) takes the resource; the holder 106// is expected to release at its next yield-checkpoint. 107 108func nx_yield_request(holder: *NxYieldContract, 109 requester_class: nx_int, 110 now_us: nx_size) -> nx_int { 111 if nx_ac_is_valid(requester_class) == 0 { return NX_YIELD_ERR_BAD_ARGS } 112 if nx_ac_is_valid(holder.attention_class) == 0 { return NX_YIELD_ERR_BAD_ARGS } 113 114 // If holder is in active throttle window, anyone (even equal-prio) 115 // takes the resource immediately. 116 if now_us < holder.throttle_until_us { return NX_YIELD_GRANTED } 117 118 // Strictly higher-priority requester always succeeds. 119 let req_prio: nx_int = nx_ac_priority(requester_class) 120 let hld_prio: nx_int = nx_ac_priority(holder.attention_class) 121 if req_prio >= hld_prio { return NX_YIELD_DENIED } 122 123 // Holder has held this resource since last_grant_tick_us. If it 124 // exceeded its quantum, this is a missed yield-checkpoint. 125 let held_us: nx_size = now_us - holder.last_grant_tick_us 126 if held_us > holder.quantum_us { 127 holder.misses = holder.misses + 1 128 if holder.misses >= NX_YIELD_MISS_THROTTLE_AT { 129 holder.throttle_until_us = now_us + NX_YIELD_THROTTLE_FOR_US 130 holder.misses = 0 131 return NX_YIELD_THROTTLED 132 } 133 if holder.misses >= NX_YIELD_MISS_DEMOTE_AT { 134 return NX_YIELD_DEMOTED 135 } 136 } 137 holder.last_grant_tick_us = now_us 138 return NX_YIELD_GRANTED 139} 140 141// ===== nx_yield_checkpoint ======================================= 142// 143// Holder calls this voluntarily at its yield-points (frame boundary, 144// loop iteration, block-of-N-bytes etc). Resets the held timer and 145// decrements miss count on each successful checkpoint, rewarding 146// well-behaved cells with eventual re-promotion to clean state. 147 148func nx_yield_checkpoint(holder: *NxYieldContract, now_us: nx_size) -> nx_int { 149 holder.last_grant_tick_us = now_us 150 if holder.misses > 0 { holder.misses = holder.misses - 1 } 151 return NX_YIELD_OK 152} 153 154// ===== nx_yield_is_throttled ===================================== 155 156func nx_yield_is_throttled(holder: *NxYieldContract, now_us: nx_size) -> nx_int { 157 if now_us < holder.throttle_until_us { return 1 } 158 return 0 159}