Skip to content

Commit daf5985

Browse files
committed
Auto merge of rust-lang#137704 - nnethercote:opt-empty-prov-range-checks, r=oli-obk
Optimize empty provenance range checks. Currently it gets the pointers in the range and checks if the result is empty, but it can be done faster if you combine those two steps. r? `@oli-obk`
2 parents f4a216d + a36d8ac commit daf5985

File tree

2 files changed

+60
-10
lines changed

2 files changed

+60
-10
lines changed

compiler/rustc_data_structures/src/sorted_map.rs

+33
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Borrow;
2+
use std::cmp::Ordering;
23
use std::fmt::Debug;
34
use std::mem;
45
use std::ops::{Bound, Index, IndexMut, RangeBounds};
@@ -156,6 +157,38 @@ impl<K: Ord, V> SortedMap<K, V> {
156157
&self.data[start..end]
157158
}
158159

160+
/// `sm.range_is_empty(r)` == `sm.range(r).is_empty()`, but is faster.
161+
#[inline]
162+
pub fn range_is_empty<R>(&self, range: R) -> bool
163+
where
164+
R: RangeBounds<K>,
165+
{
166+
// `range` must (via `range_slice_indices`) search for the start and
167+
// end separately. But here we can do a single binary search for the
168+
// entire range. If a single `x` matching `range` is found then the
169+
// range is *not* empty.
170+
self.data
171+
.binary_search_by(|(x, _)| {
172+
// Is `x` below `range`?
173+
match range.start_bound() {
174+
Bound::Included(start) if x < start => return Ordering::Less,
175+
Bound::Excluded(start) if x <= start => return Ordering::Less,
176+
_ => {}
177+
};
178+
179+
// Is `x` above `range`?
180+
match range.end_bound() {
181+
Bound::Included(end) if x > end => return Ordering::Greater,
182+
Bound::Excluded(end) if x >= end => return Ordering::Greater,
183+
_ => {}
184+
};
185+
186+
// `x` must be within `range`.
187+
Ordering::Equal
188+
})
189+
.is_err()
190+
}
191+
159192
#[inline]
160193
pub fn remove_range<R>(&mut self, range: R)
161194
where

compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs

+27-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! representation for the common case where PTR_SIZE consecutive bytes have the same provenance.
33
44
use std::cmp;
5+
use std::ops::Range;
56

67
use rustc_abi::{HasDataLayout, Size};
78
use rustc_data_structures::sorted_map::SortedMap;
@@ -66,6 +67,15 @@ impl ProvenanceMap {
6667
}
6768

6869
impl<Prov: Provenance> ProvenanceMap<Prov> {
70+
fn adjusted_range(range: AllocRange, cx: &impl HasDataLayout) -> Range<Size> {
71+
// We have to go back `pointer_size - 1` bytes, as that one would still overlap with
72+
// the beginning of this range.
73+
let adjusted_start = Size::from_bytes(
74+
range.start.bytes().saturating_sub(cx.data_layout().pointer_size.bytes() - 1),
75+
);
76+
adjusted_start..range.end()
77+
}
78+
6979
/// Returns all ptr-sized provenance in the given range.
7080
/// If the range has length 0, returns provenance that crosses the edge between `start-1` and
7181
/// `start`.
@@ -74,12 +84,17 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
7484
range: AllocRange,
7585
cx: &impl HasDataLayout,
7686
) -> &[(Size, Prov)] {
77-
// We have to go back `pointer_size - 1` bytes, as that one would still overlap with
78-
// the beginning of this range.
79-
let adjusted_start = Size::from_bytes(
80-
range.start.bytes().saturating_sub(cx.data_layout().pointer_size.bytes() - 1),
81-
);
82-
self.ptrs.range(adjusted_start..range.end())
87+
self.ptrs.range(Self::adjusted_range(range, cx))
88+
}
89+
90+
/// `pm.range_get_ptrs_is_empty(r, cx)` == `pm.range_get_ptrs(r, cx).is_empty()`, but is
91+
/// faster.
92+
pub(super) fn range_get_ptrs_is_empty(
93+
&self,
94+
range: AllocRange,
95+
cx: &impl HasDataLayout,
96+
) -> bool {
97+
self.ptrs.range_is_empty(Self::adjusted_range(range, cx))
8398
}
8499

85100
/// Returns all byte-wise provenance in the given range.
@@ -117,7 +132,7 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
117132
/// limit access to provenance outside of the `Allocation` abstraction.
118133
///
119134
pub fn range_empty(&self, range: AllocRange, cx: &impl HasDataLayout) -> bool {
120-
self.range_get_ptrs(range, cx).is_empty() && self.range_get_bytes(range).is_empty()
135+
self.range_get_ptrs_is_empty(range, cx) && self.range_get_bytes(range).is_empty()
121136
}
122137

123138
/// Yields all the provenances stored in this map.
@@ -149,12 +164,14 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
149164
// provenance that overlaps with the given range.
150165
let (first, last) = {
151166
// Find all provenance overlapping the given range.
152-
let provenance = self.range_get_ptrs(range, cx);
153-
if provenance.is_empty() {
154-
// No provenance in this range, we are done.
167+
if self.range_get_ptrs_is_empty(range, cx) {
168+
// No provenance in this range, we are done. This is the common case.
155169
return Ok(());
156170
}
157171

172+
// This redoes some of the work of `range_get_ptrs_is_empty`, but this path is much
173+
// colder than the early return above, so it's worth it.
174+
let provenance = self.range_get_ptrs(range, cx);
158175
(
159176
provenance.first().unwrap().0,
160177
provenance.last().unwrap().0 + cx.data_layout().pointer_size,

0 commit comments

Comments
 (0)