@@ -134,40 +134,52 @@ impl<'hist> MBox<'hist> {
134134 }
135135}
136136
137+
138+ /// LLVM 21 can't reliably optimize out bounds checks - it keeps forgetting the known range
139+ /// of values at any non-trivial if/else.
140+ /// When LLVM fails to optimize out checks, it's way better to have a fallback value than a panic.
141+ /// The fallback doesn't have side effects and allows code reordering.
142+ #[ inline( always) ]
143+ fn mc_sort_value ( base : & [ HistItem ] , idx : usize ) -> Option < u32 > {
144+ debug_assert ! ( base. get( idx) . is_some( ) ) ;
145+ base. get ( idx) . map ( |item| item. mc_sort_value ( ) )
146+ }
147+
137148#[ inline]
138- fn qsort_pivot ( base : & [ HistItem ] ) -> usize {
149+ fn qsort_pivot ( base : & [ HistItem ] ) -> Option < usize > {
139150 let len = base. len ( ) ;
140151 if len < 32 {
141- return len / 2 ;
152+ return Some ( len / 2 ) ;
142153 }
143154 let mut pivots = [ 8 , len / 2 , len - 1 ] ;
144- // LLVM can't see it's in bounds :(
145- pivots. sort_unstable_by_key ( move |& idx| unsafe {
146- debug_assert ! ( base. get( idx) . is_some( ) ) ;
147- base. get_unchecked ( idx)
148- } . mc_sort_value ( ) ) ;
149- pivots[ 1 ]
155+ pivots. sort_unstable_by_key ( move |& idx| mc_sort_value ( base, idx) . unwrap_or_default ( ) ) ;
156+ // this is redundant, but tracking `!base.is_empty()` through `qsort_pivot()` is too much for LLVM
157+ debug_assert ! ( pivots[ 1 ] < base. len( ) ) ;
158+ ( pivots[ 1 ] < base. len ( ) ) . then_some ( pivots[ 1 ] )
150159}
151160
152- fn qsort_partition ( base : & mut [ HistItem ] ) -> usize {
161+ fn qsort_partition ( base : & mut [ HistItem ] ) -> Option < usize > {
153162 let mut r = base. len ( ) ;
154- base. swap ( qsort_pivot ( base) , 0 ) ;
155- let pivot_value = base [ 0 ] . mc_sort_value ( ) ;
163+ base. swap ( qsort_pivot ( base) ? , 0 ) ;
164+ let pivot_value = mc_sort_value ( base , 0 ) ? ;
156165 let mut l = 1 ;
157166 while l < r {
158- if base [ l ] . mc_sort_value ( ) >= pivot_value {
167+ if mc_sort_value ( base , l ) ? >= pivot_value {
159168 l += 1 ;
160169 } else {
161170 r -= 1 ;
162- while l < r && base [ r ] . mc_sort_value ( ) <= pivot_value {
171+ while l < r && mc_sort_value ( base , r ) ? <= pivot_value {
163172 r -= 1 ;
164173 }
174+ debug_assert ! ( l < base. len( ) && r < base. len( ) ) ;
175+ if r >= base. len ( ) { return None ; } // always false, but LLVM needs this ;(
165176 base. swap ( l, r) ;
166177 }
167178 }
168179 l -= 1 ;
180+ if l >= base. len ( ) { return None ; } // always false, but LLVM needs this ;(
169181 base. swap ( l, 0 ) ;
170- l
182+ Some ( l )
171183}
172184
173185/// sorts the slice to make the sum of weights lower than `weight_half_sum` one side,
@@ -179,11 +191,11 @@ fn hist_item_sort_half(mut base: &mut [HistItem], mut weight_half_sum: f64) -> u
179191 return 0 ;
180192 }
181193 loop {
182- let partition = qsort_partition ( base) ;
183- let ( left, right) = base. split_at_mut ( partition + 1 ) ; // +1, because pivot stays on the left side
194+ let Some ( partition) = qsort_partition ( base) else { return base_index ; } ;
195+ let Some ( ( left, right) ) = base. split_at_mut_checked ( partition + 1 ) else { return base_index ; } ; // +1, because pivot stays on the left side
184196 let left_sum = left. iter ( ) . map ( |c| f64:: from ( c. mc_color_weight ) ) . sum :: < f64 > ( ) ;
185197 if left_sum >= weight_half_sum {
186- match left. get_mut ( ..partition) { // trim pivot point, avoid panick branch in []
198+ match left. get_mut ( ..partition) { // trim pivot point, avoid panic branch in [.. ]
187199 Some ( left) if !left. is_empty ( ) => { base = left; continue ; } ,
188200 _ => return base_index,
189201 }
0 commit comments