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}