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}