Skip to main content

onnx_runtime_memory/
view_map.rs

1//! Zero-copy **view → source** aliasing supplied by the executor.
2//!
3//! The executor treats the outputs of layout/movement ops
4//! (`Slice`/`Reshape`/`Squeeze`/`Unsqueeze`/`Transpose`/`Expand`, …) as
5//! *zero-copy views*: they own no buffer and instead borrow (alias) a source
6//! buffer with some strided geometry (see `ValueView` in
7//! `onnx-runtime-session/src/executor.rs`). The source buffer is therefore
8//! *pinned* — it must outlive every view that aliases it, or a reused buffer
9//! would clobber a still-live alias (a use-after-free / silent-corruption bug).
10//!
11//! The planner does **not** hardcode op names. Instead the caller (the
12//! executor, which already computes this) supplies a [`ViewMap`] of
13//! `view → source` edges. The planner folds those edges transitively to a
14//! *root* owner and extends the root's live interval to cover every view's last
15//! use. This is exactly what prevents the greedy allocator from recycling a
16//! buffer that a live view still points into.
17
18use std::collections::HashMap;
19
20use onnx_runtime_ir::ValueId;
21
22/// A set of zero-copy `view → source` aliasing relationships.
23///
24/// Each entry means "`view` owns no activation slot; its bytes live inside
25/// `source`'s buffer". A view of a view is allowed; [`ViewMap::root`] folds a
26/// chain down to the single real buffer owner.
27#[derive(Clone, Debug, Default)]
28pub struct ViewMap {
29    /// `view → immediate source`. The source may itself be a view.
30    edges: HashMap<ValueId, ValueId>,
31}
32
33impl ViewMap {
34    /// An empty map (no views — every value owns its own buffer).
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Build a map from `(view, source)` pairs.
40    pub fn from_pairs(pairs: impl IntoIterator<Item = (ValueId, ValueId)>) -> Self {
41        let mut m = Self::new();
42        for (view, source) in pairs {
43            m.insert(view, source);
44        }
45        m
46    }
47
48    /// Record that `view` aliases `source` (owns no buffer of its own).
49    pub fn insert(&mut self, view: ValueId, source: ValueId) {
50        self.edges.insert(view, source);
51    }
52
53    /// Whether `value` is a zero-copy view (aliases another value's buffer and
54    /// therefore gets no slot of its own).
55    pub fn is_view(&self, value: ValueId) -> bool {
56        self.edges.contains_key(&value)
57    }
58
59    /// The immediate source `value` aliases, if it is a view.
60    pub fn source_of(&self, value: ValueId) -> Option<ValueId> {
61        self.edges.get(&value).copied()
62    }
63
64    /// Fold `value` to the **root** buffer owner by following `view → source`
65    /// edges transitively. Returns `value` itself when it is not a view.
66    ///
67    /// A cycle in the (malformed) view map is broken defensively: the last node
68    /// visited before the cycle closes is returned rather than looping forever.
69    pub fn root(&self, value: ValueId) -> ValueId {
70        let mut seen: Vec<ValueId> = Vec::new();
71        let mut cur = value;
72        while let Some(&next) = self.edges.get(&cur) {
73            if seen.contains(&cur) {
74                break;
75            }
76            seen.push(cur);
77            cur = next;
78        }
79        cur
80    }
81
82    /// Number of recorded view edges.
83    pub fn len(&self) -> usize {
84        self.edges.len()
85    }
86
87    /// Whether there are no view edges.
88    pub fn is_empty(&self) -> bool {
89        self.edges.is_empty()
90    }
91}