diff --git a/compiler/rustc_abi/src/tests.rs b/compiler/rustc_abi/src/tests.rs index 1ec6a35401068..c9cbd355c5186 100644 --- a/compiler/rustc_abi/src/tests.rs +++ b/compiler/rustc_abi/src/tests.rs @@ -52,6 +52,12 @@ fn wrapping_range_smallest_range_containing() { check([200, 50, 60], 1, 200, 60); check([200, 50, 125], 1, 50, 200); + // A wraparound range is only potentially interesting when + // the non-wraparound range covers over half the range. + check([0, 127], 1, 0, 127); // `0..=127` is smaller + check([0, 128], 1, 0, 128); // both are the same size + check([0, 129], 1, 129, 0); // `(..=0) | (129..)` is smaller + // The mem::Alignment case check((0..64).map(|n| 1 << n), 8, 1, i64::MIN.cast_unsigned().into()); @@ -66,6 +72,9 @@ fn wrapping_range_smallest_range_containing() { // `(..=32) | (96..)`, `(..=96) | (160..)`, `(..=160) | (224..)`, and `32..=224`. // We pick the last one as the only one that doesn't contain zero. check([0xA0, 0xE0, 0x20, 0x60], 1, 0x20, 0xE0); + + // Wraparound can still be needed even in `max - min < values.len()`. + check(std::iter::chain([50; 100], [200; 100]), 1, 200, 50); } #[test] diff --git a/compiler/rustc_abi/src/wrapping_range.rs b/compiler/rustc_abi/src/wrapping_range.rs index 06e1388931988..1e97224899dcf 100644 --- a/compiler/rustc_abi/src/wrapping_range.rs +++ b/compiler/rustc_abi/src/wrapping_range.rs @@ -171,20 +171,36 @@ impl WrappingRange { size: Size, ) -> Option { let mut values: Vec<_> = values.into_iter().collect(); - values.sort_unstable(); - // The simple answer is the non-wraparound range `min..=max`. - let obvious_range = WrappingRange { start: *values.first()?, end: *values.last()? }; + let (min, max) = values + .iter() + .copied() + .map(|x| (x, x)) + .reduce(|a, b| (u128::min(a.0, b.0), u128::max(a.1, b.1)))?; + + // Just checking the max is enough to ensure that everything is in-range. + assert!(max <= size.unsigned_int_max(), "Value {max:?} is too big for {size:?}"); - // Having sorted the inputs, one test is enough to double-check they all fit in `size`. - let max_input = obvious_range.end; - assert!( - max_input <= size.unsigned_int_max(), - "Value {max_input:?} is too big for {size:?}", - ); + // The simple answer is the non-wraparound range `min..=max`. + let obvious_range = WrappingRange { start: min, end: max }; + + // It's very common that the input was only small non-negative integers and the + // obvious range turns out to be the best we can do. + // Every possible wraparound range must include at least `(...=min) | (max..)`, + // so if what we found already is at least that good, just use it. + // WR(min, max).width() ≤ WR(max, min).width() + // (max - min) % 2ⁿ ≤ (min - max) % 2ⁿ + // max - min ≤ min - max + 2ⁿ + // 2×(max - min) ≤ 2ⁿ + // max - min ≤ 2ⁿ⁻¹ + let half_range = 1 << (size.bits() - 1); + if max - min <= half_range { + return Some(obvious_range); + } // But every `[.., end, start, ..]` is also a potential candidate for a wraparound // range `(..=end) | (start..)`, so long as `start` and `end` aren't duplicates. + values.sort_unstable(); let wraparound_ranges = values .array_windows::<2>() .filter_map(|&[end, start]| (start != end).then_some(WrappingRange { start, end }));