Open and answer this issue on GitHub.
ready · opened 2026-10-27 · updated 2026-10-27 · 0 comments
Nikaia has no i128/u128 (NK1135, ADR-315). The hardware does not offer a general 128-bit integer unit either. What it offers is single instructions with 128-bit results, and today no Nikaia program can reach them:
| Instruction | Where | Use |
|---|---|---|
64×64 → 128 (MUL/MULX, UMULH, MULHU) |
x86-64, ARM64, RISC-V | bigint, a·b mod m, hashes (wyhash), PRNGs (PCG64) |
ADCX/ADOX (two carry chains) |
x86 | bigint multiplication |
128 ÷ 64 → 64 (DIV r64, DLGR) |
x86, IBM z | a·b mod m without a library call |
carry-less 64×64 → 128 (PCLMULQDQ, PMULL, clmul) |
x86, ARM64, RISC-V Zbc | CRC32/64, GHASH, Reed-Solomon |
quadword vector ops (vadduqm, vdivuq, VAQ) |
POWER10, IBM z | decimal and bigint |
The instruction facts come from memory and are not checked; the measurement is what decides.
A native u128 would not reach most of these: LLVM lowers u128 / u64 to __udivti3, carry-less multiply is not integer multiplication at all, and LLVM seldom emits ADCX/ADOX. So the candidate is named operations, in the style of wrapping_/saturating_/checked_, implemented in std with Rust underneath and a software fallback where the CPU lacks the instruction (the ADR-296 kernel pattern).
A sketch (names open, see below):
let (lo, hi) = a.widening_mul(b) // u64 × u64
let (sum, carry) = a.carrying_add(b, carry_in)
let r = lo.wide_rem(hi, m) // 128 ÷ 64
let (lo, hi) = x.carryless_mul(k)
This is not to wait until a program asks for it: find out now whether these operations pay off for known workloads, then decide. Measure before any ADR, following CLAUDE.md Measuring performance: callgrind instruction and branch counts, against tuned Rust, same output byte for byte, release builds on both sides.
Workloads, each as today’s Nikaia (emulated with u32 halves or bit loops) against tuned Rust (u128, core::arch):
mul_mod for a 64-bit modulus, Miller-Rabin over itbenches/wide-ops/: the four workloads in Nikaia and in Rust, plus a script in the pattern of benches/brc/brc.sh (build, compare outputs, count, print the table)decision) covering:
T → T? in Part I (ADR-278), so maybe full_mul/wide_mul(lo, hi) or (hi, lo)u32, u64, the signed oneswide_rem avoids #DE when hi ≥ mNo language change before that decision.