code wiki / (root) / nx_bit.nx

nx_bit.nx source

↩ module page · 200 lines · 7906 B

1// nx_bit.nx -- low-level bit primitives. 2// 3// FOUNDATION LAYER. Every crypto module, every bitmap, every 4// CRC-style checksum, every compressed-encoding parser eventually 5// wants: popcount, count-leading-zeros, count-trailing-zeros, 6// byte-swap, rotate-left/right. Today every module re-implements 7// these via shift+mask loops. Centralising matches: 8// 9// * R1 cache-conscious: one canonical implementation per primitive 10// means one inlining decision the optimiser sees everywhere 11// * R2 attestable: SHA-256 + ChaCha20 + others share the same 12// few primitives -- audit once instead of N copies 13// * Future: lower to RV64 Zbb (bit-manipulation extension) when 14// the target supports it; ~2-5x speedup over scalar fallback 15// 16// Bit primitives shipped: 17// 18// nx_popcount32 / nx_popcount64 Hamming weight 19// nx_clz32 / nx_clz64 leading zero count 20// nx_ctz32 / nx_ctz64 trailing zero count 21// nx_bswap32 / nx_bswap64 byte-reverse 22// nx_rotl32 / nx_rotl64 rotate left 23// nx_rotr32 / nx_rotr64 rotate right 24// nx_log2_floor log2 floor (= 63 - clz) 25// nx_is_pow2 power-of-two check 26// nx_align_up round up to align (must be POT) 27// 28// Algorithm references: 29// 30// * popcount: Hamming weight via parallel-prefix (folklore; 31// Knuth TAoCP Vol 4A 7.1.3). Constant 12 ops for 64-bit. 32// * clz / ctz: branch-free de Bruijn sequence multiply 33// (Leiserson et al, "Using De Bruijn sequences to index a 1 34// in a computer word", 1998 unpublished). Constant 8-10 ops. 35// * bswap: 4-step shuffle (Schneier, Practical Cryptography 2003). 36// 37// Forward path: when nxc2 grows the Zbb intrinsic, lower these to 38// `cpop`, `clz`, `ctz`, `rev8`, `rori`, `rorw` instructions. 39// Estimated 3-5x speedup on hot crypto / hashing paths. 40 41// nx_safety_envelope: 42// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 43// sil_target: SIL1 44// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 45// verdict: NOT_YET_EVALUATED 46 47import "syscalls.nx" 48import "nx_bits.nx" 49 50// ---- popcount ----------------------------------------------------- 51 52// Delegated to nx_bits for substrate-wide consolidation. The nx_bits 53// FAST path dispatches to popcntq (x86_64) / cpop (rv64 Zbb), giving 54// a measured 2x speedup over the SWAR fallback at no correctness cost. 55func nx_popcount32(x: i64) -> i64 { 56 return nx_bits_popcount32(x) 57} 58 59func nx_popcount64(x: i64) -> i64 { 60 return nx_bits_popcount64(x) 61} 62 63// ---- count leading zeros ------------------------------------------ 64// Delegated to nx_bits_clz32/64 (intrinsic dispatch -- bsr+xor on 65// x86_64, clzw/clz on rv64 Zbb). Returns 32 for clz32(0), 64 for 66// clz64(0); intrinsic-correct on both ISAs. 67 68func nx_clz32(x: i64) -> i64 { 69 return nx_bits_clz32(x) 70} 71 72func nx_clz64(x: i64) -> i64 { 73 return nx_bits_clz64(x) 74} 75 76// ---- count trailing zeros ----------------------------------------- 77// Delegated to nx_bits_ctz32/64 (bsf+zero / ctzw). 78 79func nx_ctz32(x: i64) -> i64 { 80 return nx_bits_ctz32(x) 81} 82 83func nx_ctz64(x: i64) -> i64 { 84 return nx_bits_ctz64(x) 85} 86 87// ---- byte-swap (endian flip) -------------------------------------- 88 89// Delegated to nx_bits_bswap32/64 (bswapq / rev8 intrinsic). 1 90// cycle vs the 6-op byte-shuffle / 13-op SWAR. 91func nx_bswap32(x: i64) -> i64 { 92 return nx_bits_bswap32(x) 93} 94 95func nx_bswap64(x: i64) -> i64 { 96 return nx_bits_bswap64(x) 97} 98 99// ---- rotates ------------------------------------------------------ 100 101// Delegated to nx_bits for substrate-wide consolidation. 64-bit 102// rotates now use rolq/rorq (x86_64) or rol/ror (rv64 Zbb) -- 1 103// cycle vs ~5 ops. Also fixes a latent bug in the legacy 64-bit 104// body where (x >> (64-nn)) sign-extends for negative x. 105 106func nx_rotl32(x: i64, n: i64) -> i64 { 107 return nx_bits_rotl32(x, n) 108} 109 110func nx_rotr32(x: i64, n: i64) -> i64 { 111 return nx_bits_rotr32(x, n) 112} 113 114func nx_rotl64(x: i64, n: i64) -> i64 { 115 return nx_bits_rotl64(x, n) 116} 117 118func nx_rotr64(x: i64, n: i64) -> i64 { 119 return nx_bits_rotr64(x, n) 120} 121 122// ---- log2 / power-of-two helpers ---------------------------------- 123 124// Floor of log2(x). Returns -1 if x <= 0. 125func nx_log2_floor(x: i64) -> i64 { 126 if x <= 0 { return -1 } 127 return 63 - nx_clz64(x) 128} 129 130// True (1) if x is a power of two AND > 0. 131func nx_is_pow2(x: i64) -> i64 { 132 if x <= 0 { return 0 } 133 if (x & (x - 1)) == 0 { return 1 } 134 return 0 135} 136 137// Round x up to the nearest multiple of `align`. align MUST be a 138// power-of-two (callers should assert). 139func nx_align_up(x: i64, align: i64) -> i64 { 140 let mask: i64 = align - 1 141 return (x + mask) & (~mask) 142} 143 144// ---- self-test ---------------------------------------------------- 145 146func main() -> i64 { 147 // popcount 148 if nx_popcount32(0) != 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 149 if nx_popcount32(1) != 1 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 150 if nx_popcount32(0xF) != 4 { return __syscall(93, 12, 0, 0, 0, 0, 0) } 151 if nx_popcount32(0xFF) != 8 { return __syscall(93, 13, 0, 0, 0, 0, 0) } 152 if nx_popcount32(0xFFFFFFFF) != 32 { return __syscall(93, 14, 0, 0, 0, 0, 0) } 153 if nx_popcount64(0xFFFFFFFFFFFFFFFF) != 64 { return __syscall(93, 15, 0, 0, 0, 0, 0) } 154 if nx_popcount64(0x8000000000000000) != 1 { return __syscall(93, 16, 0, 0, 0, 0, 0) } 155 156 // clz 157 if nx_clz32(0) != 32 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 158 if nx_clz32(1) != 31 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 159 if nx_clz32(0xFFFFFFFF) != 0 { return __syscall(93, 22, 0, 0, 0, 0, 0) } 160 if nx_clz32(0x80000000) != 0 { return __syscall(93, 23, 0, 0, 0, 0, 0) } 161 if nx_clz32(0x40000000) != 1 { return __syscall(93, 24, 0, 0, 0, 0, 0) } 162 if nx_clz64(0) != 64 { return __syscall(93, 25, 0, 0, 0, 0, 0) } 163 if nx_clz64(1) != 63 { return __syscall(93, 26, 0, 0, 0, 0, 0) } 164 if nx_clz64(0x8000000000000000) != 0 { return __syscall(93, 27, 0, 0, 0, 0, 0) } 165 166 // ctz 167 if nx_ctz32(0) != 32 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 168 if nx_ctz32(1) != 0 { return __syscall(93, 31, 0, 0, 0, 0, 0) } 169 if nx_ctz32(2) != 1 { return __syscall(93, 32, 0, 0, 0, 0, 0) } 170 if nx_ctz32(0x80000000) != 31 { return __syscall(93, 33, 0, 0, 0, 0, 0) } 171 if nx_ctz64(0) != 64 { return __syscall(93, 34, 0, 0, 0, 0, 0) } 172 173 // bswap 174 if nx_bswap32(0x12345678) != 0x78563412 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 175 if nx_bswap32(0xDEADBEEF) != 0xEFBEADDE { return __syscall(93, 41, 0, 0, 0, 0, 0) } 176 177 // rotl / rotr 178 if nx_rotl32(0x80000000, 1) != 1 { return __syscall(93, 50, 0, 0, 0, 0, 0) } 179 if nx_rotl32(1, 31) != 0x80000000 { return __syscall(93, 51, 0, 0, 0, 0, 0) } 180 if nx_rotr32(1, 1) != 0x80000000 { return __syscall(93, 52, 0, 0, 0, 0, 0) } 181 182 // log2 / pow2 183 if nx_log2_floor(0) != -1 { return __syscall(93, 60, 0, 0, 0, 0, 0) } 184 if nx_log2_floor(1) != 0 { return __syscall(93, 61, 0, 0, 0, 0, 0) } 185 if nx_log2_floor(8) != 3 { return __syscall(93, 62, 0, 0, 0, 0, 0) } 186 if nx_log2_floor(0x80000000) != 31 { return __syscall(93, 63, 0, 0, 0, 0, 0) } 187 if nx_is_pow2(0) != 0 { return __syscall(93, 70, 0, 0, 0, 0, 0) } 188 if nx_is_pow2(1) != 1 { return __syscall(93, 71, 0, 0, 0, 0, 0) } 189 if nx_is_pow2(8) != 1 { return __syscall(93, 72, 0, 0, 0, 0, 0) } 190 if nx_is_pow2(7) != 0 { return __syscall(93, 73, 0, 0, 0, 0, 0) } 191 192 // align_up 193 if nx_align_up(0, 8) != 0 { return __syscall(93, 80, 0, 0, 0, 0, 0) } 194 if nx_align_up(1, 8) != 8 { return __syscall(93, 81, 0, 0, 0, 0, 0) } 195 if nx_align_up(7, 8) != 8 { return __syscall(93, 82, 0, 0, 0, 0, 0) } 196 if nx_align_up(8, 8) != 8 { return __syscall(93, 83, 0, 0, 0, 0, 0) } 197 if nx_align_up(9, 8) != 16 { return __syscall(93, 84, 0, 0, 0, 0, 0) } 198 199 return 0 200}