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}