@@ -743,6 +743,39 @@ impl<T: Idx> ChunkedBitSet<T> {
743743 bit_relations_inherent_impls ! { }
744744}
745745
746+ /// Behaves like [`Rc::make_mut`], except that if an inner clone would be
747+ /// required but `would_change` returns false, then no inner clone occurs and
748+ /// None is returned instead.
749+ ///
750+ /// This function assumes that `would_change` is cheaper than an inner clone,
751+ /// but expensive enough to be worth avoiding if an inner clone isn't required.
752+ ///
753+ /// In other words, this function returns:
754+ /// - Some (without cloning) if [`Rc::get_mut`] succeeds
755+ /// - None (without cloning) if `would_change` returns false
756+ /// - Some (after cloning) otherwise
757+ fn make_chunk_words_mut_for_change < ' a > (
758+ self_words : & ' a mut Rc < [ Word ; CHUNK_WORDS ] > ,
759+ would_change : impl Fn ( & [ Word ; CHUNK_WORDS ] ) -> bool ,
760+ ) -> Option < & ' a mut [ Word ; CHUNK_WORDS ] > {
761+ // If the Rc is already unique, we can skip the would-change check entirely.
762+ if let Some ( words) = Rc :: get_mut ( self_words) {
763+ // Round-trip through a raw pointer to work around a well-known limitation of the
764+ // non-Polonius borrow checker <https://github.com/rust-lang/rust/issues/54663>.
765+ // SAFETY: Equivalent to `Some(words)`, which is sound but only accepted by Polonius;
766+ // see `tests/ui/nll/polonius/nll-problem-case-3-issue-54663.rs`.
767+ return Some ( unsafe { & mut * ( words as * mut _ ) } ) ;
768+ } ;
769+
770+ // At this point we would need to clone the chunk if we wanted to return `&mut`,
771+ // so first check if the intended operation would actually modify the words.
772+ if !would_change ( self_words) {
773+ return None ;
774+ }
775+
776+ Some ( Rc :: make_mut ( self_words) )
777+ }
778+
746779impl < T : Idx > BitRelations < ChunkedBitSet < T > > for ChunkedBitSet < T > {
747780 fn union ( & mut self , other : & ChunkedBitSet < T > ) -> bool {
748781 assert_eq ! ( self . domain_size, other. domain_size) ;
@@ -780,38 +813,36 @@ impl<T: Idx> BitRelations<ChunkedBitSet<T>> for ChunkedBitSet<T> {
780813 // performance win. Also, we only need to operate on the
781814 // in-use words, hence the slicing.
782815 let num_words = num_words ( chunk_domain_size as usize ) ;
783-
784- // If both sides are the same, nothing will change. This
785- // case is very common and it's a pretty fast check, so
786- // it's a performance win to do it.
787- if self_chunk_words[ 0 ..num_words] == other_chunk_words[ 0 ..num_words] {
788- continue ;
789- }
790-
791- // Do a more precise "will anything change?" test. Also a
792- // performance win.
793816 let op = |a, b| a | b;
794- if !bitwise_changes (
795- & self_chunk_words[ 0 ..num_words] ,
796- & other_chunk_words[ 0 ..num_words] ,
797- op,
798- ) {
817+
818+ // Try to get a mutable reference to our words, but only if doing so is
819+ // cheap, or if we're definitely going to modify the words.
820+ let Some ( self_chunk_words) =
821+ make_chunk_words_mut_for_change ( self_chunk_words, |self_chunk_words| {
822+ let lhs = & self_chunk_words[ 0 ..num_words] ;
823+ let rhs = & other_chunk_words[ 0 ..num_words] ;
824+ // If both sides are the same, nothing will change. That case is very
825+ // common, so it's a performance win to do an extremely fast equality
826+ // check before the full would-change check.
827+ ( lhs != rhs) && bitwise_changes ( lhs, rhs, op)
828+ } )
829+ else {
799830 continue ;
800- }
831+ } ;
801832
802- // If we reach here, `self_chunk_words` is definitely changing.
803- let self_chunk_words = Rc :: make_mut ( self_chunk_words) ;
804833 let has_changed = bitwise (
805834 & mut self_chunk_words[ 0 ..num_words] ,
806835 & other_chunk_words[ 0 ..num_words] ,
807836 op,
808837 ) ;
809- debug_assert ! ( has_changed) ;
810- * self_chunk_count = count_ones ( & self_chunk_words[ 0 ..num_words] ) as ChunkSize ;
811- if * self_chunk_count == chunk_domain_size {
812- * self_chunk = Ones ;
838+ if has_changed {
839+ * self_chunk_count =
840+ count_ones ( & self_chunk_words[ 0 ..num_words] ) as ChunkSize ;
841+ if * self_chunk_count == chunk_domain_size {
842+ * self_chunk = Ones ;
843+ }
844+ changed = true ;
813845 }
814- changed = true ;
815846 }
816847 }
817848 }
@@ -868,26 +899,32 @@ impl<T: Idx> BitRelations<ChunkedBitSet<T>> for ChunkedBitSet<T> {
868899 // See `ChunkedBitSet::union` for details on what is happening here.
869900 let num_words = num_words ( chunk_domain_size as usize ) ;
870901 let op = |a : Word , b : Word | a & !b;
871- if !bitwise_changes (
872- & self_chunk_words[ 0 ..num_words] ,
873- & other_chunk_words[ 0 ..num_words] ,
874- op,
875- ) {
902+
903+ let Some ( self_chunk_words) =
904+ make_chunk_words_mut_for_change ( self_chunk_words, |self_chunk_words| {
905+ bitwise_changes (
906+ & self_chunk_words[ 0 ..num_words] ,
907+ & other_chunk_words[ 0 ..num_words] ,
908+ op,
909+ )
910+ } )
911+ else {
876912 continue ;
877- }
913+ } ;
878914
879- let self_chunk_words = Rc :: make_mut ( self_chunk_words) ;
880915 let has_changed = bitwise (
881916 & mut self_chunk_words[ 0 ..num_words] ,
882917 & other_chunk_words[ 0 ..num_words] ,
883918 op,
884919 ) ;
885- debug_assert ! ( has_changed) ;
886- * self_chunk_count = count_ones ( & self_chunk_words[ 0 ..num_words] ) as ChunkSize ;
887- if * self_chunk_count == 0 {
888- * self_chunk = Zeros ;
920+ if has_changed {
921+ * self_chunk_count =
922+ count_ones ( & self_chunk_words[ 0 ..num_words] ) as ChunkSize ;
923+ if * self_chunk_count == 0 {
924+ * self_chunk = Zeros ;
925+ }
926+ changed = true ;
889927 }
890- changed = true ;
891928 }
892929 }
893930 }
@@ -926,26 +963,32 @@ impl<T: Idx> BitRelations<ChunkedBitSet<T>> for ChunkedBitSet<T> {
926963 // See `ChunkedBitSet::union` for details on what is happening here.
927964 let num_words = num_words ( chunk_domain_size as usize ) ;
928965 let op = |a, b| a & b;
929- if !bitwise_changes (
930- & self_chunk_words[ 0 ..num_words] ,
931- & other_chunk_words[ 0 ..num_words] ,
932- op,
933- ) {
966+
967+ let Some ( self_chunk_words) =
968+ make_chunk_words_mut_for_change ( self_chunk_words, |self_chunk_words| {
969+ bitwise_changes (
970+ & self_chunk_words[ 0 ..num_words] ,
971+ & other_chunk_words[ 0 ..num_words] ,
972+ op,
973+ )
974+ } )
975+ else {
934976 continue ;
935- }
977+ } ;
936978
937- let self_chunk_words = Rc :: make_mut ( self_chunk_words) ;
938979 let has_changed = bitwise (
939980 & mut self_chunk_words[ 0 ..num_words] ,
940981 & other_chunk_words[ 0 ..num_words] ,
941982 op,
942983 ) ;
943- debug_assert ! ( has_changed) ;
944- * self_chunk_count = count_ones ( & self_chunk_words[ 0 ..num_words] ) as ChunkSize ;
945- if * self_chunk_count == 0 {
946- * self_chunk = Zeros ;
984+ if has_changed {
985+ * self_chunk_count =
986+ count_ones ( & self_chunk_words[ 0 ..num_words] ) as ChunkSize ;
987+ if * self_chunk_count == 0 {
988+ * self_chunk = Zeros ;
989+ }
990+ changed = true ;
947991 }
948- changed = true ;
949992 }
950993 }
951994 }
0 commit comments