spirt/qptr/
mod.rs

1//! [`QPtr`](crate::TypeKind::QPtr)-related type definitions and passes.
2//
3// FIXME(eddyb) consider `#[cfg(doc)] use crate::TypeKind::QPtr;` for doc comments.
4// FIXME(eddyb) PR description of https://github.com/EmbarkStudios/spirt/pull/24
5// has more useful docs that could be copied here.
6
7use crate::{AddrSpace, OrdAssertEq, Type};
8use std::collections::BTreeMap;
9use std::num::NonZeroU32;
10use std::ops::Range;
11use std::rc::Rc;
12
13// NOTE(eddyb) all the modules are declared here, but they're documented "inside"
14// (i.e. using inner doc comments).
15pub mod analyze;
16mod layout;
17pub mod lift;
18pub mod lower;
19pub mod shapes;
20
21pub use layout::LayoutConfig;
22
23/// `QPtr`-specific attributes ([`Attr::QPtr`]).
24#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub enum QPtrAttr {
26    /// When applied to a `DataInst` with a `QPtr`-typed `inputs[input_idx]`,
27    /// this describes the original `OpTypePointer` consumed by an unknown
28    /// SPIR-V instruction (which may, or may not, access memory, at all).
29    ///
30    /// Assumes the original SPIR-V `StorageClass` is redundant (i.e. can be
31    /// deduced from the pointer's provenance), and that any accesses performed
32    /// through the pointer (or any pointers derived from it) stay within bounds
33    /// (i.e. logical pointer semantics, unsuited for e.g. `OpPtrAccessChain`).
34    //
35    // FIXME(eddyb) reduce usage by modeling more of SPIR-V inside SPIR-T.
36    ToSpvPtrInput { input_idx: u32, pointee: OrdAssertEq<Type> },
37
38    /// When applied to a `DataInst` with a `QPtr`-typed output value,
39    /// this describes the original `OpTypePointer` produced by an unknown
40    /// SPIR-V instruction (likely creating it, without deriving from an input).
41    ///
42    /// Assumes the original SPIR-V `StorageClass` is significant (e.g. fresh
43    /// provenance being created on the fly via `OpConvertUToPtr`, or derived
44    /// internally by the implementation via `OpImageTexelPointer`).
45    //
46    // FIXME(eddyb) reduce usage by modeling more of SPIR-V inside SPIR-T, or
47    // at least using some kind of bitcast instead of `QPtr` + this attribute.
48    // FIXME(eddyb) `OpConvertUToPtr` creates a physical pointer, could we avoid
49    // dealing with those at all in `QPtr`? (as its focus is logical legalization)
50    FromSpvPtrOutput {
51        // FIXME(eddyb) should this use a special `spv::StorageClass` type?
52        addr_space: OrdAssertEq<AddrSpace>,
53        pointee: OrdAssertEq<Type>,
54    },
55
56    /// When applied to a `QPtr`-typed `GlobalVar`, `DataInst`,
57    /// `ControlRegionInputDecl` or `ControlNodeOutputDecl`, this tracks all the
58    /// ways in which the pointer may be used (see `QPtrUsage`).
59    Usage(OrdAssertEq<QPtrUsage>),
60}
61
62#[derive(Clone, PartialEq, Eq, Hash)]
63pub enum QPtrUsage {
64    /// Used to access one or more handles (i.e. optionally indexed by
65    /// [`QPtrOp::HandleArrayIndex`]), which can be:
66    /// - `Handle::Opaque(handle_type)`: all uses involve [`QPtrOp::Load`] or
67    ///   [`QPtrAttr::ToSpvPtrInput`], with the common type `handle_type`
68    /// - `Handle::Buffer(data_usage)`: carries with it `data_usage`, i.e. the
69    ///   usage of the memory that can be accessed through [`QPtrOp::BufferData`]
70    Handles(shapes::Handle<QPtrMemUsage>),
71
72    // FIXME(eddyb) unify terminology around "concrete"/"memory"/"untyped (data)".
73    Memory(QPtrMemUsage),
74}
75
76#[derive(Clone, PartialEq, Eq, Hash)]
77pub struct QPtrMemUsage {
78    /// If present, this is a worst-case upper bound on memory accesses that may
79    /// be performed through this pointer.
80    //
81    // FIXME(eddyb) use proper newtypes for byte amounts.
82    //
83    // FIXME(eddyb) suboptimal naming choice, but other options are too verbose,
84    // including maybe using `RangeTo<_>` to explicitly indicate "exclusive".
85    //
86    // FIXME(eddyb) consider renaming such information to "extent", but that might
87    // be ambiguous with an offset range (as opposed to min/max of *possible*
88    // `offset_range.end`, i.e. "size").
89    pub max_size: Option<u32>,
90
91    pub kind: QPtrMemUsageKind,
92}
93
94impl QPtrMemUsage {
95    pub const UNUSED: Self = Self { max_size: Some(0), kind: QPtrMemUsageKind::Unused };
96}
97
98#[derive(Clone, PartialEq, Eq, Hash)]
99pub enum QPtrMemUsageKind {
100    /// Not actually used, which could be caused by pointer offsetting operations
101    /// with unused results, or as an intermediary state during analyses.
102    Unused,
103
104    // FIXME(eddyb) replace the two leaves with e.g. `Leaf(Type, QPtrMemLeafUsage)`.
105    //
106    //
107    //
108    /// Used as a typed pointer (e.g. via unknown SPIR-V instructions), requiring
109    /// a specific choice of pointee type which cannot be modified, and has to be
110    /// reused as-is when lifting `QPtr`s back to typed pointers.
111    ///
112    /// Other overlapping uses can be merged into this one as long as they can
113    /// be fully expressed using the (transitive) components of this type.
114    StrictlyTyped(Type),
115
116    /// Used directly to access memory (e.g. [`QPtrOp::Load`], [`QPtrOp::Store`]),
117    /// which can be decomposed as necessary (down to individual scalar leaves),
118    /// to allow maximal merging opportunities.
119    //
120    // FIXME(eddyb) track whether `Load`s and/or `Store`s are used, so that we
121    // can infer `NonWritable`/`NonReadable` annotations as well.
122    DirectAccess(Type),
123
124    /// Used as a common base for (constant) offsetting, which requires it to have
125    /// its own (aggregate) type, when lifting `QPtr`s back to typed pointers.
126    OffsetBase(Rc<BTreeMap<u32, QPtrMemUsage>>),
127
128    /// Used as a common base for (dynamic) offsetting, which requires it to have
129    /// its own (array) type, when lifting `QPtr`s back to typed pointers, with
130    /// one single element type being repeated across the entire size.
131    DynOffsetBase {
132        // FIXME(eddyb) this feels inefficient.
133        element: Rc<QPtrMemUsage>,
134        stride: NonZeroU32,
135    },
136    // FIXME(eddyb) consider adding an `Union` case for driving legalization.
137}
138
139/// `QPtr`-specific operations ([`DataInstKind::QPtr`]).
140#[derive(Clone, PartialEq, Eq, Hash)]
141pub enum QPtrOp {
142    // HACK(eddyb) `OpVariable` replacement, which itself should not be kept as
143    // a `SpvInst` - once fn-local variables are lowered, this should go there.
144    FuncLocalVar(shapes::MemLayout),
145
146    /// Adjust a **handle array** `QPtr` (`inputs[0]`), by selecting the handle
147    /// at the index (`inputs[1]`) from the handle array (i.e. the resulting
148    /// `QPtr` is limited to that one handle and can't be further "moved around").
149    //
150    // FIXME(eddyb) this could maybe use `DynOffset`, if `stride` is changed to
151    // be `enum { Handle, Bytes(u32) }`, but that feels a bit too much?
152    HandleArrayIndex,
153
154    /// Get a **memory** `QPtr` pointing at the contents of the buffer whose
155    /// handle is (implicitly) loaded from a **handle** `QPtr` (`inputs[0]`).
156    //
157    // FIXME(eddyb) should buffers be a `Type` of their own, that can be loaded
158    // from a handle `QPtr`, and then has data pointer / length ops *on that*?
159    BufferData,
160
161    /// Get the length of the buffer whose handle is (implicitly) loaded from a
162    /// **handle** `QPtr` (`inputs[0]`), converted to a count of "dynamic units"
163    /// (as per [`shapes::MaybeDynMemLayout`]) by subtracting `fixed_base_size`,
164    /// then dividing by `dyn_unit_stride`.
165    //
166    // FIXME(eddyb) should this handle _only_ "length in bytes", with additional
167    // integer subtraction+division operations on lowering to `QPtr`, and then
168    // multiplication+addition on lifting back to SPIR-V, followed by simplifying
169    // the redundant `(x * a + b - b) / a` to just `x`?
170    //
171    // FIXME(eddyb) actually lower `OpArrayLength` to this!
172    BufferDynLen {
173        fixed_base_size: u32,
174        dyn_unit_stride: NonZeroU32,
175    },
176
177    /// Adjust a **memory** `QPtr` (`inputs[0]`), by adding a (signed) immediate
178    /// amount of bytes to its "address" (whether physical or conceptual).
179    //
180    // FIXME(eddyb) some kind of `inbounds` would be very useful here, up to and
181    // including "capability slicing" to limit the usable range of the output.
182    Offset(i32),
183
184    /// Adjust a **memory** `QPtr` (`inputs[0]`), by adding a (signed) dynamic
185    /// "index" (`inputs[1]`), multiplied by `stride` (bytes per element),
186    /// to its "address" (whether physical or conceptual).
187    DynOffset {
188        stride: NonZeroU32,
189
190        /// Bounds on the dynamic "index" (`inputs[1]`).
191        //
192        // FIXME(eddyb) should this be an attribute/refinement?
193        index_bounds: Option<Range<i32>>,
194    },
195
196    /// Read a single value from a `QPtr` (`inputs[0]`).
197    //
198    // FIXME(eddyb) limit this to memory, and scalars, maybe vectors at most.
199    Load,
200
201    /// Write a single value (`inputs[1]`) to a `QPtr` (`inputs[0]`).
202    //
203    // FIXME(eddyb) limit this to memory, and scalars, maybe vectors at most.
204    Store,
205    //
206    // FIXME(eddyb) implement more ops! at the very least copying!
207    // (and lowering could ignore pointercasts, I guess?)
208}