code wiki / _hdl_build / nx_cpuid_lib.nx

nx_cpuid_lib.nx source

↩ module page · 135 lines · 6343 B

1// nx_cpuid_lib.nx -- PURE decode of x86-64 cpuid(7,0):EBX, the structured extended-feature register. 2// 3// WHY: nx_prim_gap reported cpu-feature-detect as MISSING across all 256 registry primitives, which read 4// as 'the ecosystem cannot see its own silicon'. THAT WAS A DISCOVERY FAILURE, NOT A CAPABILITY ONE -- 5// runtime/nx_cpu_features.nx already does REAL cpuid via the __cpuid_ebx intrinsic (nx_types OP_CPUID_EBX 6// =133, emitted as raw bytes 0F A2 because sovereign nxasm has no cpuid mnemonic). But it exposes only 7// THREE bits (BMI2/ADX/both) out of a register that ALREADY CARRIES AVX2, the whole AVX-512 family, SHA, 8// BMI1, RDSEED, ADX and CLFLUSHOPT. The data was fetched and thrown away. 9// LAW: a capability that no registry can see is indistinguishable from one that does not exist. 10// 11// EVERYTHING HERE IS PURE (ebx value in -> answer out). That is deliberate and load-bearing: it lets the 12// gate prove AVX-512 decoding CORRECT ON HARDWARE THAT HAS NO AVX-512, by feeding synthetic registers. 13// A detector you can only test on silicon you own is a detector you cannot trust for future silicon -- 14// and future silicon is the entire point of this primitive. 15// license_tier: ORIGINAL Read-only. No hw writes (Rule 26). 16import "nx_syscalls.nx" 17 18// cpuid(7,0):EBX bit positions (Intel SDM Vol.2A Table 3-8 / AMD APM Vol.3 E4.7) 19const CF_BIT_FSGSBASE: i64 = 0 20const CF_BIT_BMI1: i64 = 3 21const CF_BIT_AVX2: i64 = 5 22const CF_BIT_SMEP: i64 = 7 23const CF_BIT_BMI2: i64 = 8 24const CF_BIT_AVX512F: i64 = 16 25const CF_BIT_AVX512DQ: i64 = 17 26const CF_BIT_RDSEED: i64 = 18 27const CF_BIT_ADX: i64 = 19 28const CF_BIT_SMAP: i64 = 20 29const CF_BIT_CLFLUSHOPT: i64 = 23 30const CF_BIT_AVX512CD: i64 = 28 31const CF_BIT_SHA: i64 = 29 32const CF_BIT_AVX512BW: i64 = 30 33const CF_BIT_AVX512VL: i64 = 31 34 35// ---- CPUID leaf 1, EBX: cache line + SMT topology, REACHABLE TODAY (no new intrinsic) ---- 36// WHY THIS EXISTS: nx_hw.nx returns a HARDCODED 64 from nx_hw_cache_line_size() and a hardcoded 4096 37// from nx_hw_page_size(), and its comment defers real probing to "when nxc2 grows raw inline asm". 38// THAT BLOCKER IS STALE: __cpuid_ebx already exists (OP_CPUID_EBX=133, emitted as raw 0F A2) and 39// CPUID.1:EBX carries BOTH answers in the register we can already read: 40// bits 15:8 = CLFLUSH line size in 8-BYTE UNITS -> x8 gives the cache line in bytes 41// bits 23:16 = max addressable LOGICAL PROCESSORS per package -> the SMT/topology fact that 42// nx_hw_envelope lacks (it reports "cores: 8" for what is 8 THREADS on 4 CORES) 43// A constant that cannot fail is not a probe. These REFUSE (-1) on a zero field rather than returning a 44// plausible default, so a caller can tell "measured 64" from "assumed 64" -- which is the entire 45// difference for a spore germinating onto unknown silicon. 46const CF_BYTE_MASK: i64 = 255 47const CF_CLFLUSH_UNIT: i64 = 8 48 49func cf_cache_line(ebx1: i64) -> i64 { 50 if ebx1 < 0 { return 0 - 1 } 51 let f: i64 = (ebx1 >> 8) & CF_BYTE_MASK 52 if f == 0 { return 0 - 1 } 53 return f * CF_CLFLUSH_UNIT 54} 55 56func cf_logical_procs(ebx1: i64) -> i64 { 57 if ebx1 < 0 { return 0 - 1 } 58 let f: i64 = (ebx1 >> 16) & CF_BYTE_MASK 59 if f == 0 { return 0 - 1 } 60 return f 61} 62 63// SIMD dispatch tiers -- the answer other organs actually need: WHICH CODE PATH DO I TAKE? 64const CF_TIER_SCALAR: i64 = 0 65const CF_TIER_AVX2: i64 = 1 66const CF_TIER_AVX512: i64 = 2 67 68// The one primitive. EBX is returned zero-extended (positive, < 2^32) so >> is unambiguous. 69func cf_bit(ebx: i64, b: i64) -> i64 { 70 if ebx < 0 { return 0 - 1 } 71 if b < 0 { return 0 - 1 } 72 if b > 31 { return 0 - 1 } 73 return (ebx >> b) & 1 74} 75 76func cf_avx2(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_AVX2) } 77func cf_bmi1(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_BMI1) } 78func cf_bmi2(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_BMI2) } 79func cf_adx(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_ADX) } 80func cf_sha(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_SHA) } 81func cf_rdseed(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_RDSEED) } 82func cf_clflushopt(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_CLFLUSHOPT) } 83func cf_avx512f(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_AVX512F) } 84func cf_avx512dq(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_AVX512DQ) } 85func cf_avx512cd(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_AVX512CD) } 86func cf_avx512bw(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_AVX512BW) } 87func cf_avx512vl(ebx: i64) -> i64 { return cf_bit(ebx, CF_BIT_AVX512VL) } 88 89// SIMD tier. AVX-512F alone promotes to tier 2 -- the F subset is the dispatch-relevant floor; DQ/BW/VL 90// are refinements a tier-2 kernel queries individually. Monotonic BY CONSTRUCTION: tier 2 implies the 91// tier-1 path is also legal, so a caller may always fall back down but never up. 92func cf_simd_tier(ebx: i64) -> i64 { 93 if ebx < 0 { return 0 - 1 } 94 var t: i64 = CF_TIER_SCALAR 95 if cf_avx2(ebx) == 1 { t = CF_TIER_AVX2 } 96 if cf_avx512f(ebx) == 1 { t = CF_TIER_AVX512 } 97 return t 98} 99 100// ---- INDEPENDENT ORACLE: does /proc/cpuinfo's flags line carry this token? ---- 101// Triangulation, not convenience: the kernel decoded cpuid ITSELF, so agreeing with it is two 102// independent readings of the same silicon. A mismatch means OUR bit map is wrong (or the register was 103// misread) and must be surfaced, never averaged away. 104// Token match is DELIMITER-EXACT so "avx" can never match inside "avx2" -- the classic substring lie. 105func cf_isdelim(c: i64) -> i64 { 106 if c == 32 { return 1 } 107 if c == 10 { return 1 } 108 if c == 9 { return 1 } 109 if c == 58 { return 1 } 110 return 0 111} 112func cf_flag(buf: *u8, n: i64, tok: *u8) -> i64 { 113 var tl: i64 = 0 114 while tok[tl] != (0 as u8) { tl = tl + 1 } 115 if tl == 0 { return 0 - 1 } 116 var i: i64 = 0 117 while i + tl <= n { 118 var lok: i64 = 0 119 if i == 0 { lok = 1 } 120 if i > 0 { lok = cf_isdelim(buf[i - 1] as i64) } 121 if lok == 1 { 122 var j: i64 = 0 123 var m: i64 = 1 124 while j < tl { if buf[i + j] != tok[j] { m = 0; j = tl } else { j = j + 1 } } 125 if m == 1 { 126 var rok: i64 = 0 127 if i + tl >= n { rok = 1 } 128 if i + tl < n { rok = cf_isdelim(buf[i + tl] as i64) } 129 if rok == 1 { return 1 } 130 } 131 } 132 i = i + 1 133 } 134 return 0 135}