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