Skip to main content

celox_analysis/
dependence.rs

1//! Ordered memory-dependence construction over abstract objects and byte ranges.
2//!
3//! Exact ranges are represented by an interval partition whose size is bounded
4//! by access endpoints, not by the number of bytes in an object. An exact
5//! access touching `k` live segments takes `O((k + 1) log S + D)` time, where
6//! `S` is the number of segments and `D` is the number of dependencies emitted.
7//! Storage is `O(S + R)`, where `R` is the unresolved readers retained for WAR
8//! edges. Unknown-object operations intentionally scan one object; UnknownAll
9//! operations scan all currently represented objects.
10
11use std::collections::{BTreeMap, BTreeSet};
12
13use crate::memory::MemoryEffect;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16struct AccessHistory<I> {
17    last_writer: Option<I>,
18    readers_since_write: Vec<I>,
19}
20
21impl<I> Default for AccessHistory<I> {
22    fn default() -> Self {
23        Self {
24            last_writer: None,
25            readers_since_write: Vec::new(),
26        }
27    }
28}
29
30impl<I: Copy + Eq> AccessHistory<I> {
31    fn record_reader(&mut self, instruction: I) {
32        if self.readers_since_write.last() != Some(&instruction) {
33            self.readers_since_write.push(instruction);
34        }
35    }
36}
37
38#[derive(Debug, Clone)]
39struct MemorySegment<I> {
40    end: i64,
41    history: AccessHistory<I>,
42}
43
44#[derive(Debug)]
45struct RangeMemoryHistory<I> {
46    segments: BTreeMap<i64, MemorySegment<I>>,
47}
48
49impl<I> Default for RangeMemoryHistory<I> {
50    fn default() -> Self {
51        Self {
52            segments: BTreeMap::new(),
53        }
54    }
55}
56
57impl<I: Copy + Ord> RangeMemoryHistory<I> {
58    fn split_at(&mut self, point: i64) {
59        let Some((&start, segment)) = self.segments.range(..=point).next_back() else {
60            return;
61        };
62        if start == point || point >= segment.end {
63            return;
64        }
65        let tail = segment.clone();
66        self.segments
67            .get_mut(&start)
68            .expect("the selected memory segment exists")
69            .end = point;
70        self.segments.insert(point, tail);
71    }
72
73    fn read(&mut self, offset: i64, end: i64, instruction: I, dependencies: &mut BTreeSet<I>) {
74        self.split_at(offset);
75        self.split_at(end);
76
77        let existing = self
78            .segments
79            .range(offset..end)
80            .map(|(&start, segment)| (start, segment.end))
81            .collect::<Vec<_>>();
82        let mut cursor = offset;
83        for &(start, segment_end) in &existing {
84            if cursor < start {
85                self.segments.insert(
86                    cursor,
87                    MemorySegment {
88                        end: start,
89                        history: AccessHistory::default(),
90                    },
91                );
92            }
93            cursor = cursor.max(segment_end);
94        }
95        if cursor < end {
96            self.segments.insert(
97                cursor,
98                MemorySegment {
99                    end,
100                    history: AccessHistory::default(),
101                },
102            );
103        }
104
105        let starts = self
106            .segments
107            .range(offset..end)
108            .map(|(&start, _)| start)
109            .collect::<Vec<_>>();
110        for start in starts {
111            let history = &mut self
112                .segments
113                .get_mut(&start)
114                .expect("the covered memory segment exists")
115                .history;
116            dependencies.extend(history.last_writer);
117            history.record_reader(instruction);
118        }
119    }
120
121    fn write(&mut self, offset: i64, end: i64, instruction: I, dependencies: &mut BTreeSet<I>) {
122        self.split_at(offset);
123        self.split_at(end);
124        let starts = self
125            .segments
126            .range(offset..end)
127            .map(|(&start, _)| start)
128            .collect::<Vec<_>>();
129        for start in &starts {
130            let history = &self
131                .segments
132                .get(start)
133                .expect("the overlapping memory segment exists")
134                .history;
135            dependencies.extend(history.last_writer);
136            dependencies.extend(history.readers_since_write.iter().copied());
137        }
138        for start in starts {
139            self.segments.remove(&start);
140        }
141        self.segments.insert(
142            offset,
143            MemorySegment {
144                end,
145                history: AccessHistory {
146                    last_writer: Some(instruction),
147                    readers_since_write: Vec::new(),
148                },
149            },
150        );
151        self.coalesce_around(offset);
152    }
153
154    fn collect_writers(&self, dependencies: &mut BTreeSet<I>) {
155        dependencies.extend(
156            self.segments
157                .values()
158                .filter_map(|segment| segment.history.last_writer),
159        );
160    }
161
162    fn collect_reads_and_writes(&self, dependencies: &mut BTreeSet<I>) {
163        for segment in self.segments.values() {
164            dependencies.extend(segment.history.last_writer);
165            dependencies.extend(segment.history.readers_since_write.iter().copied());
166        }
167    }
168
169    fn coalesce_around(&mut self, mut start: i64) {
170        let Some(mut current) = self.segments.get(&start).cloned() else {
171            return;
172        };
173        let predecessor = self
174            .segments
175            .range(..start)
176            .next_back()
177            .map(|(&other_start, other)| (other_start, other.clone()));
178        if let Some((other_start, other)) = predecessor
179            && other.end == start
180            && other.history == current.history
181        {
182            self.segments.remove(&start);
183            self.segments
184                .get_mut(&other_start)
185                .expect("the predecessor memory segment exists")
186                .end = current.end;
187            start = other_start;
188            current.end = self.segments[&start].end;
189        }
190        if let Some(successor) = self.segments.get(&current.end).cloned()
191            && successor.history == current.history
192        {
193            self.segments.remove(&current.end);
194            self.segments
195                .get_mut(&start)
196                .expect("the current memory segment exists")
197                .end = successor.end;
198        }
199    }
200}
201
202#[derive(Debug)]
203struct MemoryObjectHistory<I> {
204    exact: RangeMemoryHistory<I>,
205    last_unknown_writer: Option<I>,
206    unknown_readers_since_write: Vec<I>,
207}
208
209impl<I> Default for MemoryObjectHistory<I> {
210    fn default() -> Self {
211        Self {
212            exact: RangeMemoryHistory::default(),
213            last_unknown_writer: None,
214            unknown_readers_since_write: Vec::new(),
215        }
216    }
217}
218
219impl<I: Copy + Ord> MemoryObjectHistory<I> {
220    fn read_exact(
221        &mut self,
222        offset: i64,
223        end: i64,
224        instruction: I,
225        dependencies: &mut BTreeSet<I>,
226    ) {
227        dependencies.extend(self.last_unknown_writer);
228        self.exact.read(offset, end, instruction, dependencies);
229    }
230
231    fn write_exact(
232        &mut self,
233        offset: i64,
234        end: i64,
235        instruction: I,
236        dependencies: &mut BTreeSet<I>,
237    ) {
238        dependencies.extend(self.last_unknown_writer);
239        dependencies.extend(self.unknown_readers_since_write.iter().copied());
240        self.exact.write(offset, end, instruction, dependencies);
241    }
242
243    fn read_unknown(&mut self, instruction: I, dependencies: &mut BTreeSet<I>) {
244        dependencies.extend(self.last_unknown_writer);
245        self.exact.collect_writers(dependencies);
246        if self.unknown_readers_since_write.last() != Some(&instruction) {
247            self.unknown_readers_since_write.push(instruction);
248        }
249    }
250
251    fn write_unknown(&mut self, instruction: I, dependencies: &mut BTreeSet<I>) {
252        self.collect_reads_and_writes(dependencies);
253        self.exact.segments.clear();
254        self.last_unknown_writer = Some(instruction);
255        self.unknown_readers_since_write.clear();
256    }
257
258    fn collect_writers(&self, dependencies: &mut BTreeSet<I>) {
259        dependencies.extend(self.last_unknown_writer);
260        self.exact.collect_writers(dependencies);
261    }
262
263    fn collect_reads_and_writes(&self, dependencies: &mut BTreeSet<I>) {
264        dependencies.extend(self.last_unknown_writer);
265        dependencies.extend(self.unknown_readers_since_write.iter().copied());
266        self.exact.collect_reads_and_writes(dependencies);
267    }
268}
269
270/// Incrementally records the dependencies required to preserve source-order
271/// memory semantics. Returned edges include RAW, WAR, and WAW, but never an
272/// edge solely between two reads.
273#[derive(Debug)]
274pub struct MemoryDependencyTracker<O, I> {
275    objects: BTreeMap<O, MemoryObjectHistory<I>>,
276    last_global_writer: Option<I>,
277    global_readers_since_write: Vec<I>,
278}
279
280impl<O, I> Default for MemoryDependencyTracker<O, I> {
281    fn default() -> Self {
282        Self {
283            objects: BTreeMap::new(),
284            last_global_writer: None,
285            global_readers_since_write: Vec::new(),
286        }
287    }
288}
289
290impl<O: Copy + Ord, I: Copy + Ord> MemoryDependencyTracker<O, I> {
291    /// Add one ordered memory event. Reads are processed before writes, so an
292    /// event may represent a read-modify-write instruction. Any self-edge is
293    /// removed before returning.
294    pub fn add_event<R, W>(
295        &mut self,
296        instruction: I,
297        reads: R,
298        writes: W,
299        dependencies: &mut BTreeSet<I>,
300    ) where
301        R: IntoIterator<Item = MemoryEffect<O>>,
302        W: IntoIterator<Item = MemoryEffect<O>>,
303    {
304        for effect in reads {
305            self.read(effect, instruction, dependencies);
306        }
307        for effect in writes {
308            self.write(effect, instruction, dependencies);
309        }
310        dependencies.remove(&instruction);
311    }
312
313    fn read(&mut self, effect: MemoryEffect<O>, instruction: I, dependencies: &mut BTreeSet<I>) {
314        match effect {
315            MemoryEffect::Exact(location) => {
316                if location.byte_len == 0 {
317                    return;
318                }
319                dependencies.extend(self.last_global_writer);
320                match location.end() {
321                    Some(end) => self.objects.entry(location.object).or_default().read_exact(
322                        location.offset,
323                        end,
324                        instruction,
325                        dependencies,
326                    ),
327                    None => self
328                        .objects
329                        .entry(location.object)
330                        .or_default()
331                        .read_unknown(instruction, dependencies),
332                }
333            }
334            MemoryEffect::UnknownObject(object) => {
335                dependencies.extend(self.last_global_writer);
336                self.objects
337                    .entry(object)
338                    .or_default()
339                    .read_unknown(instruction, dependencies);
340            }
341            MemoryEffect::UnknownAll => {
342                dependencies.extend(self.last_global_writer);
343                for history in self.objects.values() {
344                    history.collect_writers(dependencies);
345                }
346                if self.global_readers_since_write.last() != Some(&instruction) {
347                    self.global_readers_since_write.push(instruction);
348                }
349            }
350        }
351    }
352
353    fn write(&mut self, effect: MemoryEffect<O>, instruction: I, dependencies: &mut BTreeSet<I>) {
354        match effect {
355            MemoryEffect::Exact(location) => {
356                if location.byte_len == 0 {
357                    return;
358                }
359                self.collect_global_history(dependencies);
360                match location.end() {
361                    Some(end) => self
362                        .objects
363                        .entry(location.object)
364                        .or_default()
365                        .write_exact(location.offset, end, instruction, dependencies),
366                    None => self
367                        .objects
368                        .entry(location.object)
369                        .or_default()
370                        .write_unknown(instruction, dependencies),
371                }
372            }
373            MemoryEffect::UnknownObject(object) => {
374                self.collect_global_history(dependencies);
375                self.objects
376                    .entry(object)
377                    .or_default()
378                    .write_unknown(instruction, dependencies);
379            }
380            MemoryEffect::UnknownAll => {
381                self.collect_global_history(dependencies);
382                for history in self.objects.values() {
383                    history.collect_reads_and_writes(dependencies);
384                }
385                self.objects.clear();
386                self.last_global_writer = Some(instruction);
387                self.global_readers_since_write.clear();
388            }
389        }
390    }
391
392    fn collect_global_history(&self, dependencies: &mut BTreeSet<I>) {
393        dependencies.extend(self.last_global_writer);
394        dependencies.extend(self.global_readers_since_write.iter().copied());
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401    use crate::memory::MemoryLocation;
402
403    fn exact(object: u8, offset: i64, byte_len: usize) -> MemoryEffect<u8> {
404        MemoryEffect::Exact(MemoryLocation {
405            object,
406            offset,
407            byte_len,
408        })
409    }
410
411    fn add(
412        tracker: &mut MemoryDependencyTracker<u8, usize>,
413        instruction: usize,
414        reads: Vec<MemoryEffect<u8>>,
415        writes: Vec<MemoryEffect<u8>>,
416    ) -> BTreeSet<usize> {
417        let mut dependencies = BTreeSet::new();
418        tracker.add_event(instruction, reads, writes, &mut dependencies);
419        dependencies
420    }
421
422    #[test]
423    fn emits_raw_war_and_waw_but_no_read_after_read_edge() {
424        let mut tracker = MemoryDependencyTracker::default();
425
426        assert!(add(&mut tracker, 0, vec![exact(0, 0, 8)], vec![]).is_empty());
427        assert!(add(&mut tracker, 1, vec![exact(0, 4, 4)], vec![]).is_empty());
428        assert_eq!(
429            add(&mut tracker, 2, vec![], vec![exact(0, 0, 8)]),
430            BTreeSet::from([0, 1])
431        );
432        assert_eq!(
433            add(&mut tracker, 3, vec![exact(0, 7, 1)], vec![]),
434            BTreeSet::from([2])
435        );
436        assert_eq!(
437            add(&mut tracker, 4, vec![], vec![exact(0, 0, 8)]),
438            BTreeSet::from([2, 3])
439        );
440    }
441
442    #[test]
443    fn disjoint_ranges_and_objects_have_independent_histories() {
444        let mut tracker = MemoryDependencyTracker::default();
445        assert!(add(&mut tracker, 0, vec![], vec![exact(0, 0, 8)]).is_empty());
446        assert!(add(&mut tracker, 1, vec![exact(0, 8, 8)], vec![]).is_empty());
447        assert!(add(&mut tracker, 2, vec![exact(1, 0, 8)], vec![]).is_empty());
448        assert_eq!(
449            add(&mut tracker, 3, vec![exact(0, 7, 2)], vec![]),
450            BTreeSet::from([0])
451        );
452    }
453
454    #[test]
455    fn unknown_object_aliases_only_its_object() {
456        let mut tracker = MemoryDependencyTracker::default();
457        assert!(add(&mut tracker, 0, vec![], vec![exact(0, 0, 8)]).is_empty());
458        assert!(
459            add(
460                &mut tracker,
461                1,
462                vec![MemoryEffect::UnknownObject(1)],
463                vec![]
464            )
465            .is_empty()
466        );
467        assert_eq!(
468            add(
469                &mut tracker,
470                2,
471                vec![MemoryEffect::UnknownObject(0)],
472                vec![]
473            ),
474            BTreeSet::from([0])
475        );
476        assert_eq!(
477            add(&mut tracker, 3, vec![], vec![exact(0, 32, 8)]),
478            BTreeSet::from([2])
479        );
480    }
481
482    #[test]
483    fn unknown_all_orders_every_object_and_future_access() {
484        let mut tracker = MemoryDependencyTracker::default();
485        assert!(add(&mut tracker, 0, vec![], vec![exact(0, 0, 8)]).is_empty());
486        assert!(add(&mut tracker, 1, vec![exact(1, 0, 8)], vec![]).is_empty());
487        assert_eq!(
488            add(&mut tracker, 2, vec![MemoryEffect::UnknownAll], vec![]),
489            BTreeSet::from([0])
490        );
491        assert_eq!(
492            add(&mut tracker, 3, vec![], vec![MemoryEffect::UnknownAll]),
493            BTreeSet::from([0, 1, 2])
494        );
495        assert_eq!(
496            add(&mut tracker, 4, vec![exact(9, 0, 1)], vec![]),
497            BTreeSet::from([3])
498        );
499    }
500
501    #[test]
502    fn read_modify_write_does_not_emit_a_self_edge() {
503        let mut tracker = MemoryDependencyTracker::default();
504        assert!(add(&mut tracker, 0, vec![exact(0, 0, 8)], vec![exact(0, 0, 8)]).is_empty());
505        assert_eq!(
506            add(&mut tracker, 1, vec![exact(0, 0, 8)], vec![]),
507            BTreeSet::from([0])
508        );
509    }
510
511    #[test]
512    fn storage_scales_with_endpoints_instead_of_range_width() {
513        const WRITES: usize = 256;
514        const HUGE_RANGE: usize = 4_000_000;
515        let mut tracker = MemoryDependencyTracker::default();
516        for instruction in 0..WRITES {
517            let mut dependencies = BTreeSet::new();
518            tracker.add_event(
519                instruction,
520                [],
521                [
522                    exact(0, 0, 8),
523                    exact(0, 1024 + instruction as i64, 1),
524                    exact(0, 1_000_000, HUGE_RANGE),
525                ],
526                &mut dependencies,
527            );
528            if instruction != 0 {
529                assert!(dependencies.contains(&(instruction - 1)));
530            }
531        }
532
533        let segments = tracker.objects[&0].exact.segments.len();
534        assert_eq!(segments, WRITES + 2);
535        assert!(segments < HUGE_RANGE / 1000);
536    }
537
538    #[test]
539    fn overflowing_exact_range_falls_back_to_unknown_object() {
540        let mut tracker = MemoryDependencyTracker::default();
541        assert!(add(&mut tracker, 0, vec![], vec![exact(0, 0, 8)]).is_empty());
542        assert_eq!(
543            add(&mut tracker, 1, vec![exact(0, i64::MAX, 2)], vec![]),
544            BTreeSet::from([0])
545        );
546    }
547}