Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@ fail-fast = false

[profile.ci.junit]
path = "junit.xml"

# Used by scripts/miri-tests.sh. Miri interprets every operation, so a test
# that takes milliseconds natively can take hours. Stop any single test after
# 20 minutes so it fails with its name instead of silently eating the job's
# time limit (the job was cancelled at 6 h on every push before this).
[profile.miri]
fail-fast = false
slow-timeout = { period = "60s", terminate-after = 20 }
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,9 @@ jobs:
if: github.event_name == 'merge_group' || github.event_name == 'push'
runs-on: ubuntu-latest
name: miri
# A healthy run takes well under this; the per-test limit is in the
# `miri` nextest profile (.config/nextest.toml).
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
Expand Down
8 changes: 7 additions & 1 deletion scripts/miri-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export RUSTFLAGS="-Zrandomize-layout"
# The polyfill is no longer the bottleneck — the missing piece is a
# `cfg(miri)` switch in `src/simd.rs` that re-exports from `simd_nightly`
# instead of `simd_avx*` under Miri.
cargo +nightly miri nextest run -v \
cargo +nightly miri nextest run -v --profile miri \
--no-fail-fast \
-p ndarray -p ndarray-rand \
--features approx,serde,nightly-simd \
Expand All @@ -67,6 +67,12 @@ cargo +nightly miri nextest run -v \
and !test(/^cpu_guard::/)
'
#
# Tests that are correct but take hours under interpretation (the exhaustive
# 511 x 511 kernel sweeps, the flush-bound worst-case runs) carry
# `#[cfg_attr(miri, ignore = "...")]` in their own file, and spatial_hash's
# k-NN stops once every entry is visited. The `miri` nextest profile stops any
# test after 20 minutes, so a new slow test fails by name.
#
# Filter rationale (4-clause AND):
#
# 1. `!(test(/^hpc::/) - test(/^hpc::byte_scan/))`
Expand Down
28 changes: 27 additions & 1 deletion src/spatial_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ impl SpatialHash {
let mut ring = 0i32;
let max_ring = 64; // safety cap

// Once every entry in the grid has been visited, no further ring can
// add a candidate. Without this, a query for more neighbours than the
// grid holds walks all 129^3 cells up to `max_ring`.
let total = self.len();
let mut visited = 0usize;

loop {
// Collect candidates from all cells in this ring shell.
for dx in -ring..=ring {
Expand All @@ -165,6 +171,7 @@ impl SpatialHash {
}
let key = (cx + dx, cy + dy, cz + dz);
if let Some(cell) = self.grid.get(&key) {
visited += cell.len();
for &eid in cell {
if let Some(&pos) = positions.get(&eid) {
let d2 = sq_dist_f32(query, pos);
Expand All @@ -176,7 +183,7 @@ impl SpatialHash {
}
}

if ring >= max_ring {
if ring >= max_ring || visited >= total {
break;
}

Expand Down Expand Up @@ -537,6 +544,25 @@ mod tests {
assert_eq!(result.len(), 1);
}

#[test]
fn test_knn_k_larger_than_count_still_reaches_far_rings() {
// One entry in ring 0 and one 10 rings out: the early exit on
// "every entry visited" must not fire before the far one is seen.
let mut sh = SpatialHash::new(1.0);
let pts = vec![(0u32, [0.5f32, 0.5, 0.5]), (1, [10.5, 0.5, 0.5])];
for &(id, pos) in &pts {
sh.insert(id, pos[0], pos[1], pos[2]);
}
let positions = make_positions(&pts);
let result = sh.query_knn(0.5, 0.5, 0.5, 100, &positions);
let ids: Vec<u32> = result.iter().map(|r| r.0).collect();
assert_eq!(ids, vec![0, 1]);
// An empty grid answers empty.
assert!(SpatialHash::new(1.0)
.query_knn(0.0, 0.0, 0.0, 3, &positions)
.is_empty());
}

// -- negative coordinates --

#[test]
Expand Down
14 changes: 13 additions & 1 deletion tests/pof_antennas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ fn check_a2(a: &[Raw], b: &[Raw]) {
}

#[test]
#[cfg_attr(
miri,
ignore = "511 x 511 = 261 121 kernel calls take hours under miri; the native test jobs run it"
)]
fn a2_all_511_by_511_pairs_are_exact() {
let mut n = 0usize;
for va in -255..=255i32 {
Expand Down Expand Up @@ -100,7 +104,12 @@ fn a2_mixed_blocks_and_worst_case_runs_are_exact() {
.wrapping_add(1_442_695_040_888_963_407);
((s >> 33) % 511) as i32 - 255
};
for blocks in [1usize, 3, 64, 300] {
// Under miri: no 300-block case and no flush-bound runs below (hours of
// interpretation); the native test jobs run both.
for blocks in [1usize, 3, 64, 300]
.into_iter()
.filter(|&b| !cfg!(miri) || b < 300)
{
let mut mk = || {
let mut m = [0u8; 64];
let mut p = 0u64;
Expand All @@ -115,6 +124,9 @@ fn a2_mixed_blocks_and_worst_case_runs_are_exact() {
let b: Vec<_> = (0..blocks).map(|_| mk()).collect();
check_a2(&a, &b);
}
if cfg!(miri) {
return;
}
let n = 2 * B_FLUSH_BLOCKS + 3;
for (pa, pb) in [(u64::MAX, u64::MAX), (u64::MAX, 0), (0xF0F0_F0F0_0F0F_0F0F, 0x3333_CCCC_5555_AAAA)] {
check_a2(&vec![([255u8; 64], pa); n], &vec![([255u8; 64], pb); n]);
Expand Down
60 changes: 56 additions & 4 deletions tests/u9_abacus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ fn abacus_carries_through_many_columns() {
}

#[test]
#[cfg_attr(
miri,
ignore = "511 x 511 = 261 121 kernel calls take hours under miri; the native test jobs run it"
)]
fn all_511_by_511_pairs_are_exact() {
let mut n = 0usize;
for va in -255..=255i32 {
Expand Down Expand Up @@ -150,7 +154,11 @@ fn full_blocks_match_the_oracle_and_b() {
.wrapping_add(1_442_695_040_888_963_407);
((s >> 33) % 511) as i32 - 255
};
for blocks in [1usize, 2, 7, 64, 300] {
// 300 blocks take too long under miri; the smaller sizes keep the kernels covered there.
for blocks in [1usize, 2, 7, 64, 300]
.into_iter()
.filter(|&b| !cfg!(miri) || b < 300)
{
let mut mk = || {
let mut m = [0u8; 64];
let mut p = 0u64;
Expand All @@ -170,6 +178,10 @@ fn full_blocks_match_the_oracle_and_b() {

/// +-255 x +-255 in every lane, past B's flush bound, totals beyond `i32`.
#[test]
#[cfg_attr(
miri,
ignore = "tens of thousands of all-255 blocks past the flush bound take hours under miri; the native test jobs run it"
)]
fn worst_case_runs_exceed_i32_exactly() {
let n = 3 * B_FLUSH_BLOCKS + 5;
for (pa, pb) in [(u64::MAX, u64::MAX), (u64::MAX, 0), (0, 0), (0xF0F0_F0F0_0F0F_0F0F, 0x3333_CCCC_5555_AAAA)] {
Expand Down Expand Up @@ -247,6 +259,10 @@ fn carry_rules_match_u32_for_every_low() {
}

#[test]
#[cfg_attr(
miri,
ignore = "511 x 511 = 261 121 kernel calls take hours under miri; the native test jobs run it"
)]
fn d_all_511_by_511_pairs_are_exact() {
let mut n = 0usize;
for va in -255..=255i32 {
Expand Down Expand Up @@ -276,7 +292,11 @@ fn d_full_blocks_match_the_oracle() {
.wrapping_add(1_442_695_040_888_963_407);
((s >> 33) % 511) as i32 - 255
};
for blocks in [1usize, 2, 7, 64, 300] {
// 300 blocks take too long under miri; the smaller sizes keep the kernels covered there.
for blocks in [1usize, 2, 7, 64, 300]
.into_iter()
.filter(|&b| !cfg!(miri) || b < 300)
{
let mut mk = || {
let mut m = [0u8; 64];
let mut p = 0u64;
Expand All @@ -296,6 +316,10 @@ fn d_full_blocks_match_the_oracle() {
/// All-255 runs past D's flush bound: a carry on almost every add, `high`
/// close to its limit, totals far beyond `i32`.
#[test]
#[cfg_attr(
miri,
ignore = "tens of thousands of all-255 blocks past the flush bound take hours under miri; the native test jobs run it"
)]
fn d_worst_case_runs_cross_the_flush_bound() {
let n = 2 * D_FLUSH_BLOCKS + 5;
for (pa, pb) in [(u64::MAX, u64::MAX), (u64::MAX, 0), (0xF0F0_F0F0_0F0F_0F0F, 0x3333_CCCC_5555_AAAA)] {
Expand Down Expand Up @@ -346,6 +370,10 @@ fn anchor_split_and_sign_bytes() {
}

#[test]
#[cfg_attr(
miri,
ignore = "511 x 511 = 261 121 kernel calls take hours under miri; the native test jobs run it"
)]
fn e_all_511_by_511_pairs_are_exact() {
let mut n = 0usize;
for va in -255..=255i32 {
Expand Down Expand Up @@ -375,7 +403,11 @@ fn e_full_blocks_match_the_oracle() {
.wrapping_add(1_442_695_040_888_963_407);
((s >> 33) % 511) as i32 - 255
};
for blocks in [1usize, 2, 7, 64, 300] {
// 300 blocks take too long under miri; the smaller sizes keep the kernels covered there.
for blocks in [1usize, 2, 7, 64, 300]
.into_iter()
.filter(|&b| !cfg!(miri) || b < 300)
{
let mut mk = || {
let mut m = [0u8; 64];
let mut p = 0u64;
Expand All @@ -393,6 +425,10 @@ fn e_full_blocks_match_the_oracle() {
}

#[test]
#[cfg_attr(
miri,
ignore = "tens of thousands of all-255 blocks past the flush bound take hours under miri; the native test jobs run it"
)]
fn e_worst_case_runs_cross_the_flush_bound() {
let n = 3 * B_FLUSH_BLOCKS + 5;
for (pa, pb) in [(u64::MAX, u64::MAX), (u64::MAX, 0), (0, 0), (0xF0F0_F0F0_0F0F_0F0F, 0x3333_CCCC_5555_AAAA)] {
Expand All @@ -417,6 +453,10 @@ fn run_e5(a: &[Raw], b: &[Raw]) -> [i64; 3] {
}

#[test]
#[cfg_attr(
miri,
ignore = "511 x 511 = 261 121 kernel calls take hours under miri; the native test jobs run it"
)]
fn e5_all_511_by_511_pairs_are_exact() {
let mut n = 0usize;
for va in -255..=255i32 {
Expand Down Expand Up @@ -446,7 +486,11 @@ fn e5_full_blocks_with_mixed_signs_match_the_oracle() {
.wrapping_add(1_442_695_040_888_963_407);
((s >> 33) % 511) as i32 - 255
};
for blocks in [1usize, 2, 7, 64, 300] {
// 300 blocks take too long under miri; the smaller sizes keep the kernels covered there.
for blocks in [1usize, 2, 7, 64, 300]
.into_iter()
.filter(|&b| !cfg!(miri) || b < 300)
{
let mut mk = || {
let mut m = [0u8; 64];
let mut p = 0u64;
Expand All @@ -470,6 +514,10 @@ fn e5_full_blocks_with_mixed_signs_match_the_oracle() {

/// All-255 runs past three flush bounds, every sign mix; totals beyond `i32`.
#[test]
#[cfg_attr(
miri,
ignore = "tens of thousands of all-255 blocks past the flush bound take hours under miri; the native test jobs run it"
)]
fn e5_worst_case_runs_cross_the_flush_bound() {
let n = 3 * B_FLUSH_BLOCKS + 5;
for (pa, pb) in [(u64::MAX, u64::MAX), (u64::MAX, 0), (0, 0), (0xF0F0_F0F0_0F0F_0F0F, 0x3333_CCCC_5555_AAAA)] {
Expand All @@ -482,6 +530,10 @@ fn e5_worst_case_runs_cross_the_flush_bound() {
/// The anchor accumulators are reduced once per flush, never per block:
/// `n` blocks give `n / B_FLUSH_BLOCKS` flushes, plus one at `finish`.
#[test]
#[cfg_attr(
miri,
ignore = "tens of thousands of all-255 blocks past the flush bound take hours under miri; the native test jobs run it"
)]
fn e5_reduces_the_anchor_only_at_flush() {
let n = 2 * B_FLUSH_BLOCKS + 7;
let (x, y) = (block(&([200u8; 64], 0x0F0F)), block(&([131u8; 64], u64::MAX)));
Expand Down
4 changes: 4 additions & 0 deletions tests/u9_head_to_head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ fn run_scan(a: &[([u8; 64], u64)], b: &[([u8; 64], u64)]) -> (i64, i64) {
/// other 63 lanes are touched +0), at a lane that rotates with the pair so
/// every lane position is exercised.
#[test]
#[cfg_attr(
miri,
ignore = "511 x 511 = 261 121 kernel calls take hours under miri; the native test jobs run it"
)]
fn all_511_by_511_pairs_are_exact() {
let mut n = 0usize;
for va in -255..=255i32 {
Expand Down
Loading