ruvector/crates/ruvector-collections/build.rs
Ofer Shaal 12bfcefb18 feat(collections): Phase 0 — Miller-Rabin primality kernel + prime tables (PIAL)
Lands the deterministic Sinclair-12 Miller-Rabin u64 kernel and build-time
prime tables under crates/ruvector-collections/, per ADR-151.

Implementation
- src/primality_kernel.rs: shared MR core (mulmod via u128, powmod, witness
  loop, prev/next prime). Single source of truth — include!d from both build.rs
  and src/primality.rs to keep the build script and runtime kernel byte-identical.
- src/primality.rs: public API — is_prime_u32/u64, prev/next_prime_u64,
  prev_prime_below_pow2(k), next_prime_above_pow2(k), ephemeral_prime(seed).
  Probabilistic is_prime_u128 gated behind --feature unstable-u128 with
  Russian-peasant mulmod, mod_add overflow-safe addition, and LCG-seeded
  witness selection.
- build.rs: emits PRIMES_BELOW_2K[57] / PRIMES_ABOVE_2K[57] for k ∈ [8, 64].
  ABOVE[64] is a 0 sentinel (no u64 prime > 2^64); k=64 BELOW special-cases
  via mr_prev_prime_u64(u64::MAX).

Tests (76 pass; cross-check 0.00s)
- tests/primality_pseudoprimes.rs: pinned A014233 strong pseudoprimes
  (entries 4, 5, 11) so any witness-set regression — including dropping
  base-37 — fails loudly. SPP_FIRST_11 = 3_825_123_056_546_413_051 is the
  canary for base-37 detection.
- tests/table_cross_check.rs: re-validates all 114 emitted table entries
  against MR + sweep_odds_strictly_between (iterates the prime gap, not the
  range — so even k=63 finishes instantly).
- Doc tests + 7 inline unit tests including u128 M_89 smoke.

Benches (criterion, M-series)
- is_prime_u64 worst case (u64::MAX − 58): 15.63 µs (3 runs ±2%)
- prev_prime_below_pow2 k=32 shard router: 7.48 ns
- next_prime_u64 ~1e9: 11.44 µs
- next_prime_u64 2^61 − 1 general path: 7.83 µs

Empirical floor finding: re-running with num-prime 0.4.4 in the same binary
on the same hardware measured num_prime::is_prime64(u64::MAX − 58) at 884 ns
vs ours at 15.63 µs — confirming the 50 ns PRD target was structurally
unachievable in safe Rust (~17.7× headroom recoverable via Montgomery in
Phase 0.1, but not 300×). PRD §6 and ADR-151 amended in a follow-up commit.
2026-04-16 14:40:37 -04:00

73 lines
2.8 KiB
Rust

// build.rs — emits PRIMES_BELOW_2K[57] and PRIMES_ABOVE_2K[57] using the
// same Miller-Rabin kernel that ships at runtime. ADR-151 acceptance #2
// requires the table and the runtime to agree on every entry, and this is
// how we guarantee that — one source of truth, included from both sides.
use std::env;
use std::fs;
use std::path::PathBuf;
include!("src/primality_kernel.rs");
fn main() {
println!("cargo:rerun-if-changed=src/primality_kernel.rs");
println!("cargo:rerun-if-changed=build.rs");
let mut out = String::with_capacity(4096);
out.push_str(
"// AUTO-GENERATED by build.rs from primality_kernel.rs.\n\
// Do not edit by hand — regenerated on every build.\n\
//\n\
// Index: table[k - 8] holds the prime for exponent k, k in [8, 64].\n\n",
);
// BELOW: largest prime strictly less than 2^k.
out.push_str(
"/// Largest prime strictly less than 2^k for k in [8, 64], indexed by `k - 8`.\n\
///\n\
/// Generated at build time from the same Miller-Rabin kernel that ships at runtime\n\
/// (ADR-151 acceptance #2). Re-validated under `cargo test`.\n",
);
out.push_str("pub const PRIMES_BELOW_2K: [u64; 57] = [\n");
for k in 8u32..=64 {
let p = if k == 64 {
// 2^64 overflows u64. Largest prime < 2^64 is the largest u64
// prime; u64::MAX itself is composite, so prev_prime(u64::MAX)
// gives the right answer.
mr_prev_prime_u64(u64::MAX)
} else {
mr_prev_prime_u64(1u64 << k)
};
out.push_str(&format!(" {p}, // largest prime < 2^{k}\n"));
}
out.push_str("];\n\n");
// ABOVE: smallest prime strictly greater than 2^k.
out.push_str(
"/// Smallest prime strictly greater than 2^k for k in [8, 64], indexed by `k - 8`.\n\
///\n\
/// Entry at k = 64 is `0` (sentinel) — no u64 prime exists greater than 2^64.\n\
/// Runtime callers must avoid that index.\n",
);
out.push_str("pub const PRIMES_ABOVE_2K: [u64; 57] = [\n");
for k in 8u32..=64 {
let p = if k == 64 {
// No u64 prime exists strictly greater than 2^64. Emit a sentinel
// and forbid this index at the runtime call site (debug_assert
// in next_prime_above_pow2).
0u64
} else {
mr_next_prime_u64(1u64 << k)
};
if p == 0 {
out.push_str(&format!(" 0, // sentinel: no u64 prime > 2^{k}\n"));
} else {
out.push_str(&format!(" {p}, // smallest prime > 2^{k}\n"));
}
}
out.push_str("];\n");
let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR not set"));
let out_path = out_dir.join("prime_tables.rs");
fs::write(&out_path, out).expect("failed to write prime_tables.rs");
}