From b9bd57b97b7c81fb25ca5fcabd4f7e678761d072 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 29 Aug 2026 13:14:34 +1000 Subject: [PATCH 1/7] Avoid an intermediate `GrowableBitSet` in `rustc_mir_transform::sroa` This code only uses `GrowableBitSet` as a way to resize a `DenseBitSet` while retaining its values. --- compiler/rustc_index/src/bit_set.rs | 12 ++++++++++++ compiler/rustc_mir_transform/src/sroa.rs | 8 ++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index aa3c759a6adbd..0d03b48983c08 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -107,6 +107,18 @@ impl DenseBitSet { result } + /// Replaces this bitset with one having the same elements, but a larger domain size. + #[inline] + pub fn enlarge(self, new_domain_size: usize) -> DenseBitSet { + // We could also support shrinking, but it's hard to imagine a real use-case for it. + assert!(self.domain_size <= new_domain_size); + let new_num_words = num_words(new_domain_size); + + let DenseBitSet { domain_size: _, mut words, marker } = self; + words.resize(new_num_words, 0); + DenseBitSet { domain_size: new_domain_size, words, marker } + } + /// Clear all elements. #[inline] pub fn clear(&mut self) { diff --git a/compiler/rustc_mir_transform/src/sroa.rs b/compiler/rustc_mir_transform/src/sroa.rs index c115889205878..88056de13b6d1 100644 --- a/compiler/rustc_mir_transform/src/sroa.rs +++ b/compiler/rustc_mir_transform/src/sroa.rs @@ -2,7 +2,7 @@ use rustc_abi::FieldIdx; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; use rustc_hir::attrs::lang_items::LangItem; use rustc_index::IndexVec; -use rustc_index::bit_set::{DenseBitSet, GrowableBitSet}; +use rustc_index::bit_set::DenseBitSet; use rustc_middle::bug; use rustc_middle::mir::visit::*; use rustc_middle::mir::*; @@ -40,11 +40,7 @@ impl<'tcx> crate::MirPass<'tcx> for ScalarReplacementOfAggregates { let all_dead_locals = replace_flattened_locals(tcx, body, replacements); if !all_dead_locals.is_empty() { excluded.union(&all_dead_locals); - excluded = { - let mut growable = GrowableBitSet::from(excluded); - growable.ensure(body.local_decls.len()); - growable.into() - }; + excluded = excluded.enlarge(body.local_decls.len()); } else { break; } From 8b2647695bc0dd108b6abee8e760cb420548c5d2 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 29 Aug 2026 13:38:08 +1000 Subject: [PATCH 2/7] Avoid an unnecessary `GrowableBitSet` in `rustc_mir_transform::coroutine` This code appears to only need a variation of `DenseBitSet::contains` that doesn't panic on out-of-domain values. --- compiler/rustc_index/src/bit_set.rs | 24 +++++++++++++++---- compiler/rustc_index/src/bit_set/tests.rs | 11 +++++++++ .../rustc_mir_transform/src/coroutine/mod.rs | 10 ++++---- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index 0d03b48983c08..2cfd534a9a92e 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -135,11 +135,27 @@ impl DenseBitSet { count_ones(&self.words) } - /// Returns `true` if `self` contains `elem`. + /// Returns `true` if this bitset contains `value`. + /// + /// Unlike [`DenseBitSet::contains`], this method does not panic if the value + /// is outside this bitset's domain, and simply returns `false` instead. #[inline] - pub fn contains(&self, elem: T) -> bool { - assert!(elem.index() < self.domain_size); - let (word_index, mask) = word_index_and_mask(elem); + pub fn contains_loose(&self, value: T) -> bool { + (value.index() < self.domain_size) && self.contains(value) + } + + /// Returns `true` if this bitset contains `value`. + /// + /// # Panics + /// If `value` is outside this bitset's domain. + /// + /// # See also + /// To allow out-of-domain values without panicking, use [`DenseBitSet::contains_loose`] + /// instead. + #[inline] + pub fn contains(&self, value: T) -> bool { + assert!(value.index() < self.domain_size); + let (word_index, mask) = word_index_and_mask(value); (self.words[word_index] & mask) != 0 } diff --git a/compiler/rustc_index/src/bit_set/tests.rs b/compiler/rustc_index/src/bit_set/tests.rs index 1834be2edbac2..871216b553651 100644 --- a/compiler/rustc_index/src/bit_set/tests.rs +++ b/compiler/rustc_index/src/bit_set/tests.rs @@ -15,6 +15,17 @@ fn test_new_filled() { } } +/// [`DenseBitSet::contains_loose`] should not panic when given an out-of-domain value. +#[test] +fn contains_loose() { + let mut bitset = DenseBitSet::new_empty(100); + bitset.insert(77u32); + + for i in 0..256 { + assert_eq!(bitset.contains_loose(i), i == 77); + } +} + #[test] fn bitset_iter_works() { let mut bitset: DenseBitSet = DenseBitSet::new_empty(100); diff --git a/compiler/rustc_mir_transform/src/coroutine/mod.rs b/compiler/rustc_mir_transform/src/coroutine/mod.rs index e4fa76b0c8aaf..e3b600e8ae778 100644 --- a/compiler/rustc_mir_transform/src/coroutine/mod.rs +++ b/compiler/rustc_mir_transform/src/coroutine/mod.rs @@ -66,7 +66,7 @@ use rustc_abi::{FieldIdx, VariantIdx}; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir::attrs::lang_items::LangItem; use rustc_hir::{self as hir, CoroutineDesugaring, CoroutineKind}; -use rustc_index::bit_set::{BitMatrix, DenseBitSet, GrowableBitSet}; +use rustc_index::bit_set::{BitMatrix, DenseBitSet}; use rustc_index::{Idx, IndexVec, indexvec}; use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; @@ -172,7 +172,7 @@ struct SuspensionPoint<'tcx> { /// Which block to jump to if the coroutine is dropped in this state. drop: Option, /// Set of locals that have live storage while at this suspension point. - storage_liveness: GrowableBitSet, + storage_liveness: DenseBitSet, } struct TransformVisitor<'tcx> { @@ -510,8 +510,8 @@ impl<'tcx> MutVisitor<'tcx> for TransformVisitor<'tcx> { replace_base(&mut resume_arg, self.make_field(variant, idx, ty), self.tcx); } - let storage_liveness: GrowableBitSet = - self.storage_liveness[block].clone().unwrap().into(); + let storage_liveness: DenseBitSet = + self.storage_liveness[block].clone().unwrap(); for i in 0..self.always_live_locals.domain_size() { let l = Local::new(i); @@ -991,7 +991,7 @@ fn create_cases<'tcx>( // Create StorageLive instructions for locals with live storage for l in body.local_decls.indices() { - let needs_storage_live = point.storage_liveness.contains(l) + let needs_storage_live = point.storage_liveness.contains_loose(l) && !transform.remap.contains(l) && !transform.always_live_locals.contains(l); if needs_storage_live { From 0d6f9734d25ce12b7221df534c1e71d012c99404 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 29 Aug 2026 13:40:09 +1000 Subject: [PATCH 3/7] Remove interconversions between `DenseBitSet` and `GrowableBitSet` --- compiler/rustc_index/src/bit_set.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index 2cfd534a9a92e..ac9b26c0dcb18 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -345,12 +345,6 @@ impl DenseBitSet { } } -impl From> for DenseBitSet { - fn from(bit_set: GrowableBitSet) -> Self { - bit_set.bit_set - } -} - impl Clone for DenseBitSet { fn clone(&self) -> Self { DenseBitSet { @@ -1333,12 +1327,6 @@ impl GrowableBitSet { } } -impl From> for GrowableBitSet { - fn from(bit_set: DenseBitSet) -> Self { - Self { bit_set } - } -} - /// A fixed-size 2D bit matrix type with a dense representation. /// /// `R` and `C` are index types used to identify rows and columns respectively; From 9c6abcf3d52825c46b0bd02297a18ff998c7eacd Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 29 Aug 2026 13:47:23 +1000 Subject: [PATCH 4/7] Replace calls to `GrowableBitSet::len` with `.count()` --- compiler/rustc_builtin_macros/src/asm.rs | 2 +- compiler/rustc_index/src/bit_set.rs | 5 ----- compiler/rustc_passes/src/hir_id_validator.rs | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index ae9aa743a75f8..5039d27a46fb4 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -437,7 +437,7 @@ fn expand_preparsed_asm( let positional_args = args.operands.len() - args.named_args.len() - - args.reg_args.len(); + - args.reg_args.count(); let positional = if positional_args != args.operands.len() { "positional " } else { diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index ac9b26c0dcb18..dd19facc9c138 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -1320,11 +1320,6 @@ impl GrowableBitSet { pub fn iter(&self) -> BitIter<'_, T> { self.bit_set.iter() } - - #[inline] - pub fn len(&self) -> usize { - self.bit_set.count() - } } /// A fixed-size 2D bit matrix type with a dense representation. diff --git a/compiler/rustc_passes/src/hir_id_validator.rs b/compiler/rustc_passes/src/hir_id_validator.rs index 84b92d49f24c2..c0a8ec210769e 100644 --- a/compiler/rustc_passes/src/hir_id_validator.rs +++ b/compiler/rustc_passes/src/hir_id_validator.rs @@ -59,7 +59,7 @@ impl<'a, 'hir> HirIdValidator<'a, 'hir> { .max() .expect("owning item has no entry"); - if max != self.hir_ids_seen.len() - 1 { + if max != self.hir_ids_seen.count() - 1 { let pretty_owner = self.tcx.hir_def_path(owner.def_id).to_string_no_crate_verbose(); let missing_items: Vec<_> = (0..=max as u32) From 63f5671ca7617a500d43269842b4cc7c39ef35aa Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 29 Aug 2026 13:50:32 +1000 Subject: [PATCH 5/7] Remove some unused `GrowableBitSet` methods --- compiler/rustc_index/src/bit_set.rs | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index dd19facc9c138..62640e16e21b5 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -1221,9 +1221,6 @@ impl<'a, T: Idx> Iterator for MixedBitIter<'a, T> { /// /// `T` is an index type, typically a newtyped `usize` wrapper, but it can also /// just be `usize`. -/// -/// All operations that involve an element will panic if the element is equal -/// to or greater than the domain size. #[derive(Debug, PartialEq)] pub struct GrowableBitSet { bit_set: DenseBitSet, @@ -1274,24 +1271,6 @@ impl GrowableBitSet { self.bit_set.insert(elem) } - #[inline] - pub fn insert_range(&mut self, elems: Range) { - self.ensure(elems.end.index()); - self.bit_set.insert_range(elems); - } - - /// Returns `true` if the set has changed. - #[inline] - pub fn remove(&mut self, elem: T) -> bool { - self.ensure(elem.index() + 1); - self.bit_set.remove(elem) - } - - #[inline] - pub fn clear(&mut self) { - self.bit_set.clear(); - } - #[inline] pub fn count(&self) -> usize { self.bit_set.count() @@ -1308,14 +1287,6 @@ impl GrowableBitSet { self.bit_set.words.get(word_index).is_some_and(|word| (word & mask) != 0) } - #[inline] - pub fn contains_any(&self, elems: Range) -> bool { - elems.start.index() < self.bit_set.domain_size - && self - .bit_set - .contains_any(elems.start..T::new(elems.end.index().min(self.bit_set.domain_size))) - } - #[inline] pub fn iter(&self) -> BitIter<'_, T> { self.bit_set.iter() From 12cd8ea605574f0639b5bbd7806cb2d46ebff0fb Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 29 Aug 2026 14:09:18 +1000 Subject: [PATCH 6/7] Disconnect `GrowableBitSet` from `DenseBitSet` --- compiler/rustc_index/src/bit_set.rs | 67 ++++++++++++++++++----------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index 62640e16e21b5..df060c9983f1b 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -174,19 +174,14 @@ impl DenseBitSet { /// Insert `elem`. Returns whether the set has changed. #[inline] - pub fn insert(&mut self, elem: T) -> bool { + pub fn insert(&mut self, value: T) -> bool { assert!( - elem.index() < self.domain_size, + value.index() < self.domain_size, "inserting element at index {} but domain size is {}", - elem.index(), + value.index(), self.domain_size, ); - let (word_index, mask) = word_index_and_mask(elem); - let word_ref = &mut self.words[word_index]; - let word = *word_ref; - let new_word = word | mask; - *word_ref = new_word; - new_word != word + insert(&mut self.words, value) } #[inline] @@ -1223,17 +1218,23 @@ impl<'a, T: Idx> Iterator for MixedBitIter<'a, T> { /// just be `usize`. #[derive(Debug, PartialEq)] pub struct GrowableBitSet { - bit_set: DenseBitSet, + domain_size: usize, + words: Vec, + marker: PhantomData, } -// Manually implemented to forward `clone_from`, and to avoid the `T: Clone` bound. +// Manually implemented to provide `clone_from`. impl Clone for GrowableBitSet { fn clone(&self) -> Self { - Self { bit_set: self.bit_set.clone() } + let &GrowableBitSet { domain_size, ref words, marker } = self; + GrowableBitSet { domain_size, words: words.clone(), marker } } fn clone_from(&mut self, source: &Self) { - self.bit_set.clone_from(&source.bit_set); + let GrowableBitSet { domain_size, words, marker } = source; + self.domain_size.clone_from(domain_size); + self.words.clone_from(words); + self.marker.clone_from(marker); } } @@ -1246,50 +1247,54 @@ impl Default for GrowableBitSet { impl GrowableBitSet { /// Ensure that the set can hold at least `min_domain_size` elements. pub fn ensure(&mut self, min_domain_size: usize) { - if self.bit_set.domain_size < min_domain_size { - self.bit_set.domain_size = min_domain_size; + if self.domain_size < min_domain_size { + self.domain_size = min_domain_size; } let min_num_words = num_words(min_domain_size); - if self.bit_set.words.len() < min_num_words { - self.bit_set.words.resize(min_num_words, 0) + if self.words.len() < min_num_words { + self.words.resize(min_num_words, 0) } } pub fn new_empty() -> GrowableBitSet { - GrowableBitSet { bit_set: DenseBitSet::new_empty(0) } + GrowableBitSet { domain_size: 0, words: vec![], marker: PhantomData } } pub fn with_capacity(capacity: usize) -> GrowableBitSet { - GrowableBitSet { bit_set: DenseBitSet::new_empty(capacity) } + GrowableBitSet { + domain_size: capacity, + words: vec![0; num_words(capacity)], + marker: PhantomData, + } } /// Returns `true` if the set has changed. #[inline] - pub fn insert(&mut self, elem: T) -> bool { - self.ensure(elem.index() + 1); - self.bit_set.insert(elem) + pub fn insert(&mut self, value: T) -> bool { + self.ensure(value.index() + 1); + insert(&mut self.words, value) } #[inline] pub fn count(&self) -> usize { - self.bit_set.count() + count_ones(&self.words) } #[inline] pub fn is_empty(&self) -> bool { - self.bit_set.is_empty() + self.words.iter().all(|&w| w == 0) } #[inline] pub fn contains(&self, elem: T) -> bool { let (word_index, mask) = word_index_and_mask(elem); - self.bit_set.words.get(word_index).is_some_and(|word| (word & mask) != 0) + self.words.get(word_index).is_some_and(|word| (word & mask) != 0) } #[inline] pub fn iter(&self) -> BitIter<'_, T> { - self.bit_set.iter() + BitIter::new(&self.words) } } @@ -1626,3 +1631,13 @@ fn max_bit(word: Word) -> usize { fn count_ones(words: &[Word]) -> usize { words.iter().map(|word| word.count_ones() as usize).sum() } + +#[inline] +fn insert(words: &mut [Word], value: T) -> bool { + let (word_index, mask) = word_index_and_mask(value); + let word_ref = &mut words[word_index]; + let word = *word_ref; + let new_word = word | mask; + *word_ref = new_word; + new_word != word +} From 4935ae013136d06a813b9f2e797c48cf01368d03 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 29 Aug 2026 14:16:50 +1000 Subject: [PATCH 7/7] Use `Box<[Word]>` for word storage in `DenseBitSet` --- compiler/rustc_index/src/bit_set.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index df060c9983f1b..ff66c33fe2236 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -78,7 +78,7 @@ fn inclusive_start_end( #[derive(Eq, PartialEq, Hash)] pub struct DenseBitSet { domain_size: usize, - words: Vec, + words: Box<[Word]>, marker: PhantomData, } @@ -94,15 +94,22 @@ impl DenseBitSet { #[inline] pub fn new_empty(domain_size: usize) -> DenseBitSet { let num_words = num_words(domain_size); - DenseBitSet { domain_size, words: vec![0; num_words], marker: PhantomData } + DenseBitSet { + domain_size, + words: vec![0; num_words].into_boxed_slice(), + marker: PhantomData, + } } /// Creates a new, filled bitset with a given `domain_size`. #[inline] pub fn new_filled(domain_size: usize) -> DenseBitSet { let num_words = num_words(domain_size); - let mut result = - DenseBitSet { domain_size, words: vec![!0; num_words], marker: PhantomData }; + let mut result = DenseBitSet { + domain_size, + words: vec![!0; num_words].into_boxed_slice(), + marker: PhantomData, + }; result.clear_excess_bits(); result } @@ -115,7 +122,13 @@ impl DenseBitSet { let new_num_words = num_words(new_domain_size); let DenseBitSet { domain_size: _, mut words, marker } = self; - words.resize(new_num_words, 0); + + if new_num_words != words.len() { + let mut words_vec = words.into_vec(); + words_vec.resize(new_num_words, 0); + words = words_vec.into_boxed_slice() + } + DenseBitSet { domain_size: new_domain_size, words, marker } }