code wiki / (root) / nx_metabolism.nx

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}