Skip to content

Commit cca18f6

Browse files
committed
Work around multiple regressions in LLVM optimizer
1 parent 26edfc4 commit cca18f6

File tree

1 file changed

+29
-17
lines changed

1 file changed

+29
-17
lines changed

src/mediancut.rs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)