miri/concurrency/
range_object_map.rs

1//! Implements a map from allocation ranges to data. This is somewhat similar to RangeMap, but the
2//! ranges and data are discrete and non-splittable -- they represent distinct "objects". An
3//! allocation in the map will always have the same range until explicitly removed
4
5use std::ops::{Index, IndexMut, Range};
6
7use rustc_abi::Size;
8use rustc_const_eval::interpret::AllocRange;
9
10#[derive(Clone, Debug)]
11struct Elem<T> {
12    /// The range covered by this element; never empty.
13    range: AllocRange,
14    /// The data stored for this element.
15    data: T,
16}
17
18/// Index of an allocation within the map
19type Position = usize;
20
21#[derive(Clone, Debug)]
22pub struct RangeObjectMap<T> {
23    v: Vec<Elem<T>>,
24}
25
26#[derive(Clone, Debug, PartialEq)]
27pub enum AccessType {
28    /// The access perfectly overlaps (same offset and range) with the existing allocation
29    PerfectlyOverlapping(Position),
30    /// The access does not touch any existing allocation
31    Empty(Position),
32    /// The access overlaps with one or more existing allocations
33    ImperfectlyOverlapping(Range<Position>),
34}
35
36impl<T> RangeObjectMap<T> {
37    pub fn new() -> Self {
38        Self { v: Vec::new() }
39    }
40
41    /// Finds the position of the allocation containing the given offset. If the offset is not
42    /// in an existing allocation, then returns Err containing the position
43    /// where such allocation should be inserted
44    fn find_offset(&self, offset: Size) -> Result<Position, Position> {
45        self.v.binary_search_by(|elem| -> std::cmp::Ordering {
46            if offset < elem.range.start {
47                // We are too far right (offset is further left).
48                // (`Greater` means that `elem` is greater than the desired target.)
49                std::cmp::Ordering::Greater
50            } else if offset >= elem.range.end() {
51                // We are too far left (offset is further right).
52                std::cmp::Ordering::Less
53            } else {
54                // This is it!
55                std::cmp::Ordering::Equal
56            }
57        })
58    }
59
60    /// Determines whether a given access on `range` overlaps with
61    /// an existing allocation
62    pub fn access_type(&self, range: AllocRange) -> AccessType {
63        match self.find_offset(range.start) {
64            Ok(pos) => {
65                // Start of the range belongs to an existing object, now let's check the overlapping situation
66                let elem = &self.v[pos];
67                // FIXME: derive Eq for AllocRange in rustc
68                if elem.range.start == range.start && elem.range.size == range.size {
69                    // Happy case: perfectly overlapping access
70                    AccessType::PerfectlyOverlapping(pos)
71                } else {
72                    // FIXME: add a last() method to AllocRange that returns the last inclusive offset (end() is exclusive)
73                    let end_pos = match self.find_offset(range.end() - Size::from_bytes(1)) {
74                        // If the end lands in an existing object, add one to get the exclusive position
75                        Ok(inclusive_pos) => inclusive_pos + 1,
76                        Err(exclusive_pos) => exclusive_pos,
77                    };
78
79                    AccessType::ImperfectlyOverlapping(pos..end_pos)
80                }
81            }
82            Err(pos) => {
83                // Start of the range doesn't belong to an existing object
84                match self.find_offset(range.end() - Size::from_bytes(1)) {
85                    // Neither does the end
86                    Err(end_pos) =>
87                        if pos == end_pos {
88                            // There's nothing between the start and the end, so the range thing is empty
89                            AccessType::Empty(pos)
90                        } else {
91                            // Otherwise we have entirely covered an existing object
92                            AccessType::ImperfectlyOverlapping(pos..end_pos)
93                        },
94                    // Otherwise at least part of it overlaps with something else
95                    Ok(end_pos) => AccessType::ImperfectlyOverlapping(pos..end_pos + 1),
96                }
97            }
98        }
99    }
100
101    /// Inserts an object and its occupied range at given position
102    // The Position can be calculated from AllocRange, but the only user of AllocationMap
103    // always calls access_type before calling insert/index/index_mut, and we don't
104    // want to repeat the binary search on each time, so we ask the caller to supply Position
105    pub fn insert_at_pos(&mut self, pos: Position, range: AllocRange, data: T) {
106        self.v.insert(pos, Elem { range, data });
107        // If we aren't the first element, then our start must be greater than the previous element's end
108        if pos > 0 {
109            assert!(self.v[pos - 1].range.end() <= range.start);
110        }
111        // If we aren't the last element, then our end must be smaller than next element's start
112        if pos < self.v.len() - 1 {
113            assert!(range.end() <= self.v[pos + 1].range.start);
114        }
115    }
116
117    pub fn remove_pos_range(&mut self, pos_range: Range<Position>) {
118        self.v.drain(pos_range);
119    }
120
121    pub fn remove_from_pos(&mut self, pos: Position) {
122        self.v.remove(pos);
123    }
124
125    pub fn iter(&self) -> impl Iterator<Item = &T> {
126        self.v.iter().map(|e| &e.data)
127    }
128}
129
130impl<T> Index<Position> for RangeObjectMap<T> {
131    type Output = T;
132
133    fn index(&self, pos: Position) -> &Self::Output {
134        &self.v[pos].data
135    }
136}
137
138impl<T> IndexMut<Position> for RangeObjectMap<T> {
139    fn index_mut(&mut self, pos: Position) -> &mut Self::Output {
140        &mut self.v[pos].data
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use rustc_const_eval::interpret::alloc_range;
147
148    use super::*;
149
150    #[test]
151    fn empty_map() {
152        // FIXME: make Size::from_bytes const
153        let four = Size::from_bytes(4);
154        let map = RangeObjectMap::<()>::new();
155
156        // Correctly tells where we should insert the first element (at position 0)
157        assert_eq!(map.find_offset(Size::from_bytes(3)), Err(0));
158
159        // Correctly tells the access type along with the supposed position
160        assert_eq!(map.access_type(alloc_range(Size::ZERO, four)), AccessType::Empty(0));
161    }
162
163    #[test]
164    #[should_panic]
165    fn no_overlapping_inserts() {
166        let four = Size::from_bytes(4);
167
168        let mut map = RangeObjectMap::<&str>::new();
169
170        // |_|_|_|_|#|#|#|#|_|_|_|_|...
171        //  0 1 2 3 4 5 6 7 8 9 a b c d
172        map.insert_at_pos(0, alloc_range(four, four), "#");
173        // |_|_|_|_|#|#|#|#|_|_|_|_|...
174        //  0 ^ ^ ^ ^ 5 6 7 8 9 a b c d
175        map.insert_at_pos(0, alloc_range(Size::from_bytes(1), four), "@");
176    }
177
178    #[test]
179    fn boundaries() {
180        let four = Size::from_bytes(4);
181
182        let mut map = RangeObjectMap::<&str>::new();
183
184        // |#|#|#|#|_|_|...
185        //  0 1 2 3 4 5
186        map.insert_at_pos(0, alloc_range(Size::ZERO, four), "#");
187        // |#|#|#|#|_|_|...
188        //  0 1 2 3 ^ 5
189        assert_eq!(map.find_offset(four), Err(1));
190        // |#|#|#|#|_|_|_|_|_|...
191        //  0 1 2 3 ^ ^ ^ ^ 8
192        assert_eq!(map.access_type(alloc_range(four, four)), AccessType::Empty(1));
193
194        let eight = Size::from_bytes(8);
195        // |#|#|#|#|_|_|_|_|@|@|@|@|_|_|...
196        //  0 1 2 3 4 5 6 7 8 9 a b c d
197        map.insert_at_pos(1, alloc_range(eight, four), "@");
198        // |#|#|#|#|_|_|_|_|@|@|@|@|_|_|...
199        //  0 1 2 3 4 5 6 ^ 8 9 a b c d
200        assert_eq!(map.find_offset(Size::from_bytes(7)), Err(1));
201        // |#|#|#|#|_|_|_|_|@|@|@|@|_|_|...
202        //  0 1 2 3 ^ ^ ^ ^ 8 9 a b c d
203        assert_eq!(map.access_type(alloc_range(four, four)), AccessType::Empty(1));
204    }
205
206    #[test]
207    fn perfectly_overlapping() {
208        let four = Size::from_bytes(4);
209
210        let mut map = RangeObjectMap::<&str>::new();
211
212        // |#|#|#|#|_|_|...
213        //  0 1 2 3 4 5
214        map.insert_at_pos(0, alloc_range(Size::ZERO, four), "#");
215        // |#|#|#|#|_|_|...
216        //  ^ ^ ^ ^ 4 5
217        assert_eq!(map.find_offset(Size::ZERO), Ok(0));
218        assert_eq!(
219            map.access_type(alloc_range(Size::ZERO, four)),
220            AccessType::PerfectlyOverlapping(0)
221        );
222
223        // |#|#|#|#|@|@|@|@|_|...
224        //  0 1 2 3 4 5 6 7 8
225        map.insert_at_pos(1, alloc_range(four, four), "@");
226        // |#|#|#|#|@|@|@|@|_|...
227        //  0 1 2 3 ^ ^ ^ ^ 8
228        assert_eq!(map.find_offset(four), Ok(1));
229        assert_eq!(map.access_type(alloc_range(four, four)), AccessType::PerfectlyOverlapping(1));
230    }
231
232    #[test]
233    fn straddling() {
234        let four = Size::from_bytes(4);
235
236        let mut map = RangeObjectMap::<&str>::new();
237
238        // |_|_|_|_|#|#|#|#|_|_|_|_|...
239        //  0 1 2 3 4 5 6 7 8 9 a b c d
240        map.insert_at_pos(0, alloc_range(four, four), "#");
241        // |_|_|_|_|#|#|#|#|_|_|_|_|...
242        //  0 1 ^ ^ ^ ^ 6 7 8 9 a b c d
243        assert_eq!(
244            map.access_type(alloc_range(Size::from_bytes(2), four)),
245            AccessType::ImperfectlyOverlapping(0..1)
246        );
247        // |_|_|_|_|#|#|#|#|_|_|_|_|...
248        //  0 1 2 3 4 5 ^ ^ ^ ^ a b c d
249        assert_eq!(
250            map.access_type(alloc_range(Size::from_bytes(6), four)),
251            AccessType::ImperfectlyOverlapping(0..1)
252        );
253        // |_|_|_|_|#|#|#|#|_|_|_|_|...
254        //  0 1 ^ ^ ^ ^ ^ ^ ^ ^ a b c d
255        assert_eq!(
256            map.access_type(alloc_range(Size::from_bytes(2), Size::from_bytes(8))),
257            AccessType::ImperfectlyOverlapping(0..1)
258        );
259
260        // |_|_|_|_|#|#|#|#|_|_|@|@|_|_|...
261        //  0 1 2 3 4 5 6 7 8 9 a b c d
262        map.insert_at_pos(1, alloc_range(Size::from_bytes(10), Size::from_bytes(2)), "@");
263        // |_|_|_|_|#|#|#|#|_|_|@|@|_|_|...
264        //  0 1 2 3 4 5 ^ ^ ^ ^ ^ ^ ^ ^
265        assert_eq!(
266            map.access_type(alloc_range(Size::from_bytes(6), Size::from_bytes(8))),
267            AccessType::ImperfectlyOverlapping(0..2)
268        );
269    }
270}