nx_select.nx source
↩ module page · 290 lines · 11317 B
1// nx_select.nx -- single-class variant selector engine.
2//
3// SA-4 milestone of NISHI_SELF_ASSEMBLY_ROADMAP.md. Consumes
4// (candidates, probe, calibration, policy) and returns the index of
5// the best-match variant under the policy vector subject to the
6// candidate's requires_* constraints intersecting the device's probe.
7//
8// Selection function (single-class, MVP):
9//
10// S(C, P, K, π) = argmin_{v ∈ candidates}
11// Σ_axis π[axis] · cost_axis(v) · K[axis_norm]
12// s.t. requires_isa(v) ⊆ P.actual_isa_family
13// requires_isa_ext(v) ⊆ P.actual_isa_ext
14// requires_ram_min(v) ≤ P.ram_proxy
15// tier_floor(v) ≤ K.inferred_tier
16// tier_ceiling(v) ≥ K.inferred_tier
17//
18// Joint-DAG cross-primitive selection deferred to SA-5
19// (nx_select_joint). Per-primitive smoke gating deferred to SA-6
20// (nx_verify). Byzantine N-of-M deferred to SA-7.
21//
22// V1 manifest schema is INTEGER-ENCODED (sealed-enum bitmasks for
23// requires_isa, Q10 fixed-point for cost coefficients) so the
24// selector runs without a manifest-header parser. SA-5 lands the
25// parser that lifts authored headers in `runtime/*.nx` into these
26// integer-encoded records.
27//
28// genealogy_id: petabricks_2009 + halide_schedule_search_2019 +
29// cardinal_2026-05-19_self_assembly
30// lineage_id: substrate_select_v1
31//
32// nx_capability_manifest:
33// variant_class: capability_select
34// variant_id: capability_select_v1_single_class
35// requires_isa: [rv64imac, x86_64]
36// requires_syscalls: [mmap]
37// requires_ram_min_b: 4096
38// tier_floor: NX_TIER_MOBILE
39// tier_ceiling: NX_TIER_HPC
40// cost_model:
41// flops_per_n: 1.0 // ~constant per candidate
42// bytes_per_n: 160.0 // NxManifest size per candidate
43// syscalls_per_n: 0.0
44// adversary_class: THREAT_AI_ADVERSARY
45//
46// nx_safety_envelope:
47// intended_use: "Single-class variant selector; pure function of
48// (manifests, probe, calibration, policy)"
49// sil_target: SIL2
50// evidence: [pure_function, deterministic_argmin,
51// filter_then_score, no_side_effects]
52// verdict: NOT_YET_EVALUATED
53
54import "nx_syscalls.nx"
55import "nx_probe.nx"
56import "nx_calibrate.nx"
57const NX_MAGIC_1024: i64 = 1024
58const NX_MAGIC_9223372036854775000: i64 = 9223372036854775000
59
60// ===== ISA bitmask =================================================
61// Maps NX_ISA_* sealed-enum values into bit positions so a single
62// i64 mask can express "this variant runs on RV64 OR x86_64".
63// requires_isa_mask = (1 << NX_ISA_RV64) | (1 << NX_ISA_X86_64)
64func nx_isa_bit(isa: i64) -> i64 {
65 if isa < 0 { return 0 }
66 if isa >= 63 { return 0 }
67 return 1 << isa
68}
69
70// ===== Data layout sealed enum (for joint-DAG composition) =======
71// SA-5 extension. A variant declares which input layout it CAN
72// CONSUME (in_layout) and which output layout it PRODUCES (out_layout).
73// nx_select_joint's edge constraint requires out_layout(producer) to
74// match in_layout(consumer).
75//
76// NX_LAYOUT_OPAQUE_PASS matches anything (used by variants that
77// don't care about layout, e.g., byte-stream consumers).
78const NX_LAYOUT_OPAQUE_PASS: i64 = 0
79const NX_LAYOUT_ROW_MAJOR: i64 = 1
80const NX_LAYOUT_COL_MAJOR: i64 = 2
81const NX_LAYOUT_I64_PACKED: i64 = 3
82const NX_LAYOUT_I32_PACKED: i64 = 4
83const NX_LAYOUT_I8_PACKED: i64 = 5
84const NX_LAYOUT_Q10_FIXED: i64 = 6
85const NX_LAYOUT_F16: i64 = 7
86const NX_LAYOUT_F32: i64 = 8
87const NX_LAYOUT_N: i64 = 9
88
89// Layout compatibility: 1 if producer's out_layout can flow into
90// consumer's in_layout without transform; 0 otherwise. OPAQUE_PASS
91// is a wildcard on either side.
92func nx_layout_compatible(out_layout: i64, in_layout: i64) -> i64 {
93 if out_layout == NX_LAYOUT_OPAQUE_PASS { return 1 }
94 if in_layout == NX_LAYOUT_OPAQUE_PASS { return 1 }
95 if out_layout == in_layout { return 1 }
96 return 0
97}
98
99// ===== ISA-extension bitmask ======================================
100const NX_ISA_EXT_AVX2: i64 = 0
101const NX_ISA_EXT_AVX512: i64 = 1
102const NX_ISA_EXT_BMI2: i64 = 2
103const NX_ISA_EXT_SHA_NI: i64 = 3
104const NX_ISA_EXT_AES_NI: i64 = 4
105const NX_ISA_EXT_NEON: i64 = 5
106const NX_ISA_EXT_SVE: i64 = 6
107const NX_ISA_EXT_RVV: i64 = 7
108const NX_ISA_EXT_N: i64 = 8
109
110// ===== NxManifest (selector-consumable form) ======================
111// Subset of the source-header schema (NISHI_MANIFEST_AUTHOR_GUIDE.md)
112// reduced to integer fields the selector consumes without a parser.
113// Cost coefficients are Q10 fixed-point (multiplied by 1024 at
114// authoring time) so the selector can compute in integer arithmetic.
115
116struct NxManifest {
117 variant_class: i64,
118 variant_id: i64,
119 requires_isa_mask: i64, // OR of (1 << NX_ISA_*)
120 requires_isa_ext_mask: i64, // OR of (1 << NX_ISA_EXT_*)
121 requires_ram_min_b: i64,
122 tier_floor: i64, // NX_TIER_INF_*
123 tier_ceiling: i64, // NX_TIER_INF_*
124 cost_flops_per_n_q10: i64, // Q10 fixed-point
125 cost_bytes_per_n_q10: i64,
126 cost_syscalls_per_n_q10: i64,
127 cost_energy_per_n_q10: i64,
128 in_layout: i64, // NX_LAYOUT_* (joint-DAG SA-5)
129 out_layout: i64, // NX_LAYOUT_*
130}
131
132// ===== NxPolicy ===================================================
133// Policy weights -- non-negative integers, larger = more important.
134// All in Q10 fixed-point so weights compose with cost coefficients
135// without floating-point.
136
137struct NxPolicy {
138 weight_throughput: i64, // weight on flops_per_n
139 weight_bytes: i64, // weight on bytes_per_n
140 weight_syscalls: i64, // weight on syscalls_per_n
141 weight_energy: i64, // weight on energy_per_n
142 weight_ram: i64, // weight on requires_ram_min_b
143}
144
145// ===== Convenience constructors ===================================
146func nx_policy_throughput_default() -> *NxPolicy {
147 let p: *NxPolicy = (sys_mmap(48)) as *NxPolicy
148 p.weight_throughput = NX_MAGIC_1024 // 1.0 in Q10
149 p.weight_bytes = 256 // 0.25
150 p.weight_syscalls = 256
151 p.weight_energy = 128 // 0.125
152 p.weight_ram = 128
153 return p
154}
155
156func nx_policy_energy_default() -> *NxPolicy {
157 let p: *NxPolicy = (sys_mmap(48)) as *NxPolicy
158 p.weight_throughput = 128 // 0.125 (de-prioritized)
159 p.weight_bytes = 256
160 p.weight_syscalls = 256
161 p.weight_energy = NX_MAGIC_1024 // 1.0
162 p.weight_ram = 256
163 return p
164}
165
166func nx_manifest_new(
167 variant_class: i64,
168 variant_id: i64,
169 requires_isa_mask: i64,
170 requires_isa_ext_mask: i64,
171 requires_ram_min_b: i64,
172 tier_floor: i64,
173 tier_ceiling: i64,
174 cost_flops_q10: i64,
175 cost_bytes_q10: i64,
176 cost_syscalls_q10: i64,
177 cost_energy_q10: i64
178) -> *NxManifest {
179 let m: *NxManifest = (sys_mmap(128)) as *NxManifest
180 m.variant_class = variant_class
181 m.variant_id = variant_id
182 m.requires_isa_mask = requires_isa_mask
183 m.requires_isa_ext_mask = requires_isa_ext_mask
184 m.requires_ram_min_b = requires_ram_min_b
185 m.tier_floor = tier_floor
186 m.tier_ceiling = tier_ceiling
187 m.cost_flops_per_n_q10 = cost_flops_q10
188 m.cost_bytes_per_n_q10 = cost_bytes_q10
189 m.cost_syscalls_per_n_q10 = cost_syscalls_q10
190 m.cost_energy_per_n_q10 = cost_energy_q10
191 m.in_layout = NX_LAYOUT_OPAQUE_PASS
192 m.out_layout = NX_LAYOUT_OPAQUE_PASS
193 return m
194}
195
196// Convenience: set layout on an existing manifest (single-class
197// selector ignores these; joint selector consumes them).
198func nx_manifest_set_layout(m: *NxManifest, in_layout: i64, out_layout: i64) -> i64 {
199 m.in_layout = in_layout
200 m.out_layout = out_layout
201 return 0
202}
203
204// ===== Sealed enum: selection verdict =============================
205const NX_SEL_NONE_MATCH: i64 = -1
206const NX_SEL_BAD_INPUT: i64 = -2
207const NX_SEL_OVER_CAPACITY: i64 = -3
208
209// ===== Filter: does this variant fit the device? ==================
210// Returns 1 if all requires-clauses are satisfied; 0 otherwise.
211
212func nx_select_variant_fits(
213 v: *NxManifest,
214 probe: *NxProbeRecord,
215 inferred_tier: i64,
216 probe_ram_proxy: i64,
217 probe_isa_ext_mask: i64
218) -> i64 {
219 // ISA filter.
220 let probe_isa_bit: i64 = nx_isa_bit(probe.actual_isa_family)
221 if (v.requires_isa_mask & probe_isa_bit) == 0 { return 0 }
222 // ISA-extension filter: every requires bit must be present in probe.
223 if (v.requires_isa_ext_mask & probe_isa_ext_mask) != v.requires_isa_ext_mask {
224 return 0
225 }
226 // RAM floor.
227 if probe_ram_proxy < v.requires_ram_min_b { return 0 }
228 // Tier floor + ceiling.
229 if inferred_tier < v.tier_floor { return 0 }
230 if inferred_tier > v.tier_ceiling { return 0 }
231 return 1
232}
233
234// ===== Scoring ====================================================
235// Q10 weighted sum. Lower score = better. All Q10 multiplications
236// stay within i64 range as long as individual factors stay below ~2^30,
237// which they do for any realistic cost-model coefficient.
238
239func nx_select_score(v: *NxManifest, policy: *NxPolicy) -> i64 {
240 let s_t: i64 = policy.weight_throughput * v.cost_flops_per_n_q10
241 let s_b: i64 = policy.weight_bytes * v.cost_bytes_per_n_q10
242 let s_s: i64 = policy.weight_syscalls * v.cost_syscalls_per_n_q10
243 let s_e: i64 = policy.weight_energy * v.cost_energy_per_n_q10
244 let s_r: i64 = policy.weight_ram * v.requires_ram_min_b
245 return s_t + s_b + s_s + s_e + s_r
246}
247
248// ===== Single-class selection ====================================
249// Pure-function MVP: scans candidates, filters by requires, scores
250// the survivors, returns argmin. Returns NX_SEL_NONE_MATCH (-1) if
251// no candidate fits the device.
252//
253// `manifest_ptrs` is a *i64 array where each element holds a
254// (i64-cast) pointer to an NxManifest. Caller pre-populates the
255// array; selector reads the pointers via *i64 indexing (the
256// substrate's clean array convention). Re-entrant; no global state.
257
258func nx_select(
259 manifest_ptrs: *i64, n_candidates: i64,
260 probe: *NxProbeRecord,
261 inferred_tier: i64,
262 probe_ram_proxy: i64,
263 probe_isa_ext_mask: i64,
264 policy: *NxPolicy
265) -> i64 {
266 if n_candidates <= 0 { return NX_SEL_BAD_INPUT }
267 if (probe as i64) == 0 { return NX_SEL_BAD_INPUT }
268 if (policy as i64) == 0 { return NX_SEL_BAD_INPUT }
269 if (manifest_ptrs as i64) == 0 { return NX_SEL_BAD_INPUT }
270
271 var best_idx: i64 = NX_SEL_NONE_MATCH
272 var best_score: i64 = NX_MAGIC_9223372036854775000 // close to MAX_I64
273 var i: i64 = 0
274 while i < n_candidates {
275 let v_addr: i64 = manifest_ptrs[i]
276 if v_addr == 0 { i = i + 1; continue }
277 let v: *NxManifest = v_addr as *NxManifest
278 let fits: i64 = nx_select_variant_fits(v, probe, inferred_tier,
279 probe_ram_proxy, probe_isa_ext_mask)
280 if fits == 1 {
281 let s: i64 = nx_select_score(v, policy)
282 if s < best_score {
283 best_score = s
284 best_idx = i
285 }
286 }
287 i = i + 1
288 }
289 return best_idx
290}