From 2a870404fd3e2292955478875336217a27939202 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 12 Mar 2026 12:50:16 +1100 Subject: [PATCH] Add a suite of ChunkedBitSet union/subtract/intersect test scenarios --- compiler/rustc_index/src/bit_set/tests.rs | 110 ++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/compiler/rustc_index/src/bit_set/tests.rs b/compiler/rustc_index/src/bit_set/tests.rs index 341e0622df75..08704863ec3d 100644 --- a/compiler/rustc_index/src/bit_set/tests.rs +++ b/compiler/rustc_index/src/bit_set/tests.rs @@ -298,6 +298,116 @@ fn chunked_bitset() { b10000b.assert_valid(); } +/// Additional helper methods for testing. +impl ChunkedBitSet { + /// Creates a new `ChunkedBitSet` containing all `i` for which `fill_fn(i)` is true. + fn fill_with(domain_size: usize, fill_fn: impl Fn(usize) -> bool) -> Self { + let mut this = ChunkedBitSet::new_empty(domain_size); + for i in 0..domain_size { + if fill_fn(i) { + this.insert(i); + } + } + this + } + + /// Asserts that for each `i` in `0..self.domain_size()`, `self.contains(i) == expected_fn(i)`. + #[track_caller] + fn assert_filled_with(&self, expected_fn: impl Fn(usize) -> bool) { + for i in 0..self.domain_size() { + let expected = expected_fn(i); + assert_eq!(self.contains(i), expected, "i = {i}"); + } + } +} + +#[test] +fn chunked_bulk_ops() { + struct ChunkedBulkOp { + name: &'static str, + op_fn: fn(&mut ChunkedBitSet, &ChunkedBitSet) -> bool, + spec_fn: fn(fn(usize) -> bool, fn(usize) -> bool, usize) -> bool, + } + let ops = &[ + ChunkedBulkOp { + name: "union", + op_fn: ChunkedBitSet::union, + spec_fn: |fizz, buzz, i| fizz(i) || buzz(i), + }, + ChunkedBulkOp { + name: "subtract", + op_fn: ChunkedBitSet::subtract, + spec_fn: |fizz, buzz, i| fizz(i) && !buzz(i), + }, + ChunkedBulkOp { + name: "intersect", + op_fn: ChunkedBitSet::intersect, + spec_fn: |fizz, buzz, i| fizz(i) && buzz(i), + }, + ]; + + let domain_sizes = [ + CHUNK_BITS / 7, // Smaller than a full chunk. + CHUNK_BITS, + (CHUNK_BITS + CHUNK_BITS / 7), // Larger than a full chunk. + ]; + + for ChunkedBulkOp { name, op_fn, spec_fn } in ops { + for domain_size in domain_sizes { + // If false, use different values for LHS and RHS, to test "fizz op buzz". + // If true, use identical values, to test "fizz op fizz". + for identical in [false, true] { + // If false, make a clone of LHS before doing the op. + // This covers optimizations that depend on whether chunk words are shared or not. + for unique in [false, true] { + // Print the current test case, so that we can see which one failed. + println!( + "Testing op={name}, domain_size={domain_size}, identical={identical}, unique={unique} ..." + ); + + let fizz_fn = |i| i % 3 == 0; + let buzz_fn = if identical { fizz_fn } else { |i| i % 5 == 0 }; + + // Check that `fizz op buzz` gives the expected results. + chunked_bulk_ops_test_inner( + domain_size, + unique, + fizz_fn, + buzz_fn, + op_fn, + |i| spec_fn(fizz_fn, buzz_fn, i), + ); + } + } + } + } +} + +fn chunked_bulk_ops_test_inner( + domain_size: usize, + unique: bool, + fizz_fn: impl Fn(usize) -> bool + Copy, + buzz_fn: impl Fn(usize) -> bool + Copy, + op_fn: impl Fn(&mut ChunkedBitSet, &ChunkedBitSet) -> bool, + expected_fn: impl Fn(usize) -> bool + Copy, +) { + // Create two bitsets, "fizz" (LHS) and "buzz" (RHS). + let mut fizz = ChunkedBitSet::fill_with(domain_size, fizz_fn); + let buzz = ChunkedBitSet::fill_with(domain_size, buzz_fn); + + // If requested, clone `fizz` so that its word Rcs are not uniquely-owned. + let _cloned = (!unique).then(|| fizz.clone()); + + // Perform the op (e.g. union/subtract/intersect), and verify that the + // mutated LHS contains exactly the expected values. + let changed = op_fn(&mut fizz, &buzz); + fizz.assert_filled_with(expected_fn); + + // Verify that the "changed" return value is correct. + let should_change = (0..domain_size).any(|i| fizz_fn(i) != expected_fn(i)); + assert_eq!(changed, should_change); +} + fn with_elements_chunked(elements: &[usize], domain_size: usize) -> ChunkedBitSet { let mut s = ChunkedBitSet::new_empty(domain_size); for &e in elements {