nx_metabolism.nx source
↩ module page · 176 lines · 7010 B
1// nx_metabolism.nx -- per-call-site profiler + tier promotion.
2//
3// Biological analogue: cells consume more glucose at hot signaling
4// pathways and grow mitochondria to compensate. Nishi metabolism
5// tracks (hit_count, total_cycles, ram_peak) per call site and
6// suggests a target tier for the existing nxc2 codegen layer to
7// emit: interpreted bytecode -> AOT native -> SIMD-vectorized native
8// -> GPU offload -> custom-silicon block.
9//
10// THIS IS THE OPTIMIZATION DECISION LAYER. nxc2 already has thirteen
11// architecture backends compiled in (aarch64.c, armv7a.c, cortex_m.c,
12// gcn.c, loongarch64.c, metal.c, mips64.c, ppc64le.c, ptx.c, riscv.c,
13// riscv32.c, s390x.c, wasm.c, x86_64.c). What was missing is the
14// NishiLang-side decision primitive that says WHICH backend to pick
15// per call site. Metabolism is that primitive.
16//
17// Composes:
18// nx_tier -- NX_TIER_MCU..NX_TIER_HPC target enum
19// nx_budget -- ram_peak per site informs ceiling tuning
20// nx_homeostasis -- repeated hot-site overshoot triggers
21// tropism migration to higher tier
22// nx_attention_class -- foreground sites get higher promotion bias
23// (frame-budget pressure outranks build pressure)
24//
25// V1 ships a flat hash table keyed on site_id. Adaptive thresholds
26// per tier; defaults derived from heuristics rather than learned.
27//
28// Gap list (V1 honest perf verdict):
29// - no online learning (thresholds are static constants)
30// - no decay (a one-time spike at startup never ages out)
31// - no cross-site cost-amortization (each site decides alone)
32// - no silicon target yet (NX_TIER_SOVEREIGN_CHIP returns same as HPC)
33//
34// genealogy_id: nishi_cardinal_2026-05-17_pathway_tropism + cardinal-21_resource_awareness
35// lineage_id: substrate_metabolism_v1
36//
37// nx_safety_envelope:
38// intended_use: "Per-call-site profiling and target-tier
39// promotion suggestions for the codegen layer"
40// sil_target: SIL1
41// evidence: [site_table_bounded, no_unbounded_growth]
42// verdict: NOT_YET_EVALUATED
43
44import "nx_syscalls.nx"
45import "nx_tier.nx"
46
47// ===== Sealed enum: NxMetabVerdict ==============================
48
49const NX_METAB_OK: nx_int = 0
50const NX_METAB_ERR_FULL: nx_int = 1 // site table at capacity
51const NX_METAB_ERR_BAD_SITE: nx_int = 2
52
53// ===== Struct: NxCallSite ========================================
54//
55// One slot per known call site. site_id is the caller-supplied stable
56// hash (typically file:line or function-name hash from the lex layer).
57
58struct NxCallSite {
59 site_id: nx_int,
60 hit_count: nx_size,
61 total_cycles: nx_size,
62 ram_peak: nx_size,
63 current_tier: nx_int,
64 last_class: nx_int, // most recent attention_class observed
65}
66
67// ===== Struct: NxMetabolism ======================================
68
69struct NxMetabolism {
70 sites: *NxCallSite,
71 capacity: nx_size,
72 count: nx_size,
73}
74
75const NX_METAB_DEFAULT_CAPACITY: nx_size = 256
76
77// Promotion thresholds: hit_count above which we suggest promoting
78// from one tier to the next-higher one. Tuned for NX_TIER_WORKSTATION;
79// per Cardinal 11 these belong in svc-config in the longer term, but
80// for substrate-level defaults they live as named constants here.
81const NX_METAB_PROMOTE_TO_NATIVE_AT: nx_size = 64
82const NX_METAB_PROMOTE_TO_VECTOR_AT: nx_size = 4096
83const NX_METAB_PROMOTE_TO_GPU_AT: nx_size = 65536
84const NX_METAB_PROMOTE_TO_SILICON_AT: nx_size = 1048576
85
86// ===== Constructor ===============================================
87
88func nx_metab_new(capacity: nx_size) -> *NxMetabolism {
89 let m: *NxMetabolism = (sys_mmap(24)) as *NxMetabolism
90 let site_bytes: nx_size = capacity * 48
91 m.sites = (sys_mmap(site_bytes)) as *NxCallSite
92 m.capacity = capacity
93 m.count = 0
94 return m
95}
96
97// ===== _metab_lookup =============================================
98//
99// Linear-scan find. Returns NULL if not present. Internal helper;
100// V2 will swap in open-addressing hash, but at 256 sites linear is
101// faster than the cache-misses a hash table costs.
102
103func _metab_lookup(m: *NxMetabolism, site_id: nx_int) -> *NxCallSite {
104 var i: nx_size = 0
105 while i < m.count {
106 let s: *NxCallSite = (m.sites as i64 + (i as i64) * 48) as *NxCallSite
107 if s.site_id == site_id { return s }
108 i = i + 1
109 }
110 return (0 as i64) as *NxCallSite
111}
112
113// ===== nx_metab_record ===========================================
114//
115// Caller records one observation of (cycles, ram_bytes, class) for
116// one site. Creates the site row on first sight; OK to call from any
117// number of cells against the same site_id.
118
119func nx_metab_record(m: *NxMetabolism,
120 site_id: nx_int,
121 cycles: nx_size,
122 ram_bytes: nx_size,
123 attention_class: nx_int) -> nx_int {
124 var s: *NxCallSite = _metab_lookup(m, site_id)
125 if (s as i64) == 0 {
126 if m.count >= m.capacity { return NX_METAB_ERR_FULL }
127 s = (m.sites as i64 + (m.count as i64) * 48) as *NxCallSite
128 s.site_id = site_id
129 s.hit_count = 0
130 s.total_cycles = 0
131 s.ram_peak = 0
132 s.current_tier = NX_TIER_WORKSTATION
133 s.last_class = attention_class
134 m.count = m.count + 1
135 }
136 s.hit_count = s.hit_count + 1
137 s.total_cycles = s.total_cycles + cycles
138 if ram_bytes > s.ram_peak { s.ram_peak = ram_bytes }
139 s.last_class = attention_class
140 return NX_METAB_OK
141}
142
143// ===== nx_metab_suggest_target ===================================
144//
145// The decision function. Returns the target tier the codegen layer
146// should emit for this site on next compile. Thresholds are
147// cumulative: a site that crosses PROMOTE_TO_GPU also crossed the
148// vector + native thresholds, so the lookups are ordered descending.
149
150func nx_metab_suggest_target(m: *NxMetabolism, site_id: nx_int) -> nx_int {
151 let s: *NxCallSite = _metab_lookup(m, site_id)
152 if (s as i64) == 0 { return NX_TIER_WORKSTATION }
153 let hits: nx_size = s.hit_count
154 if hits >= NX_METAB_PROMOTE_TO_SILICON_AT { return NX_TIER_SOVEREIGN_CHIP }
155 if hits >= NX_METAB_PROMOTE_TO_GPU_AT { return NX_TIER_HPC }
156 if hits >= NX_METAB_PROMOTE_TO_VECTOR_AT { return NX_TIER_SERVER }
157 if hits >= NX_METAB_PROMOTE_TO_NATIVE_AT { return NX_TIER_WORKSTATION }
158 return NX_TIER_FAMILY_DEVICE
159}
160
161// ===== nx_metab_avg_cycles =======================================
162
163func nx_metab_avg_cycles(m: *NxMetabolism, site_id: nx_int) -> nx_size {
164 let s: *NxCallSite = _metab_lookup(m, site_id)
165 if (s as i64) == 0 { return 0 }
166 if s.hit_count <= 0 { return 0 }
167 return s.total_cycles / s.hit_count
168}
169
170// ===== nx_metab_hit_count ========================================
171
172func nx_metab_hit_count(m: *NxMetabolism, site_id: nx_int) -> nx_size {
173 let s: *NxCallSite = _metab_lookup(m, site_id)
174 if (s as i64) == 0 { return 0 }
175 return s.hit_count
176}