Skip to content

Commit d3ae59f

Browse files
committed
Split a new mem module (for "memory" ops/passes/etc.), out of qptr.
1 parent 02cacc0 commit d3ae59f

File tree

14 files changed

+810
-704
lines changed

14 files changed

+810
-704
lines changed

examples/spv-lower-link-qptr-lift.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ fn main() -> std::io::Result<()> {
8080
after_pass("", &module)?;
8181

8282
// HACK(eddyb) this is roughly what Rust-GPU would need.
83-
let layout_config = &spirt::qptr::LayoutConfig {
83+
let layout_config = &spirt::mem::LayoutConfig {
8484
abstract_bool_size_align: (1, 1),
8585
logical_ptr_size_align: (4, 4),
86-
..spirt::qptr::LayoutConfig::VULKAN_SCALAR_LAYOUT
86+
..spirt::mem::LayoutConfig::VULKAN_SCALAR_LAYOUT
8787
};
8888

8989
eprint_duration(|| {
@@ -92,9 +92,11 @@ fn main() -> std::io::Result<()> {
9292
eprintln!("qptr::lower_from_spv_ptrs");
9393
after_pass("qptr::lower_from_spv_ptrs", &module)?;
9494

95-
eprint_duration(|| spirt::passes::qptr::analyze_uses(&mut module, layout_config));
96-
eprintln!("qptr::analyze_uses");
97-
after_pass("qptr::analyze_uses", &module)?;
95+
eprint_duration(|| {
96+
spirt::passes::qptr::analyze_mem_accesses(&mut module, layout_config)
97+
});
98+
eprintln!("mem::analyze_accesses");
99+
after_pass("mem::analyze_accesses", &module)?;
98100

99101
eprint_duration(|| spirt::passes::qptr::lift_to_spv_ptrs(&mut module, layout_config));
100102
eprintln!("qptr::lift_to_spv_ptrs");

src/lib.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ pub mod passes {
169169
pub mod link;
170170
pub mod qptr;
171171
}
172+
pub mod mem;
172173
pub mod qptr;
173174
pub mod spv;
174175

@@ -397,6 +398,10 @@ pub enum Attr {
397398
// of `AttrSetDef::{dbg_src_loc,set_dbg_src_loc}`.
398399
DbgSrcLoc(OrdAssertEq<DbgSrcLoc>),
399400

401+
/// Memory-specific attributes (see [`mem::MemAttr`]).
402+
#[from]
403+
Mem(mem::MemAttr),
404+
400405
/// `QPtr`-specific attributes (see [`qptr::QPtrAttr`]).
401406
#[from]
402407
QPtr(qptr::QPtrAttr),
@@ -489,7 +494,7 @@ pub enum DiagMsgPart {
489494
Attrs(AttrSet),
490495
Type(Type),
491496
Const(Const),
492-
QPtrUsage(qptr::QPtrUsage),
497+
MemAccesses(mem::MemAccesses),
493498
}
494499

495500
/// Wrapper to limit `Ord` for interned index types (e.g. [`InternedStr`])
@@ -638,7 +643,7 @@ pub struct GlobalVarDecl {
638643

639644
/// When `type_of_ptr_to` is `QPtr`, `shape` must be used to describe the
640645
/// global variable (see `GlobalVarShape`'s documentation for more details).
641-
pub shape: Option<qptr::shapes::GlobalVarShape>,
646+
pub shape: Option<mem::shapes::GlobalVarShape>,
642647

643648
/// The address space the global variable will be allocated into.
644649
pub addr_space: AddrSpace,
@@ -947,6 +952,10 @@ pub enum DataInstKind {
947952
// to avoid needing special handling for recursion where it's impossible.
948953
FuncCall(Func),
949954

955+
/// Memory-specific operations (see [`mem::MemOp`]).
956+
#[from]
957+
Mem(mem::MemOp),
958+
950959
/// `QPtr`-specific operations (see [`qptr::QPtrOp`]).
951960
#[from]
952961
QPtr(qptr::QPtrOp),

src/qptr/analyze.rs renamed to src/mem/analyze.rs

Lines changed: 322 additions & 324 deletions
Large diffs are not rendered by default.

src/qptr/layout.rs renamed to src/mem/layout.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// FIXME(eddyb) layouts are a bit tricky: this recomputes them from several passes.
22

3-
use crate::qptr::shapes;
3+
use crate::mem::shapes;
44
use crate::{
55
AddrSpace, Attr, Const, ConstKind, Context, Diag, FxIndexMap, Type, TypeKind, TypeOrConst, spv,
66
};
@@ -61,10 +61,10 @@ impl LayoutConfig {
6161
Self { min_aggregate_legacy_align: 16, ..Self::VULKAN_STANDARD_LAYOUT };
6262
}
6363

64-
pub(super) struct LayoutError(pub(super) Diag);
64+
pub(crate) struct LayoutError(pub(crate) Diag);
6565

6666
#[derive(Clone)]
67-
pub(super) enum TypeLayout {
67+
pub(crate) enum TypeLayout {
6868
Handle(HandleLayout),
6969
HandleArray(HandleLayout, Option<NonZeroU32>),
7070

@@ -73,16 +73,16 @@ pub(super) enum TypeLayout {
7373
}
7474

7575
// NOTE(eddyb) `Handle` is parameterized over the `Buffer` layout.
76-
pub(super) type HandleLayout = shapes::Handle<Rc<MemTypeLayout>>;
76+
pub(crate) type HandleLayout = shapes::Handle<Rc<MemTypeLayout>>;
7777

78-
pub(super) struct MemTypeLayout {
79-
pub(super) original_type: Type,
80-
pub(super) mem_layout: shapes::MaybeDynMemLayout,
81-
pub(super) components: Components,
78+
pub(crate) struct MemTypeLayout {
79+
pub(crate) original_type: Type,
80+
pub(crate) mem_layout: shapes::MaybeDynMemLayout,
81+
pub(crate) components: Components,
8282
}
8383

8484
// FIXME(eddyb) use proper newtypes for byte sizes.
85-
pub(super) enum Components {
85+
pub(crate) enum Components {
8686
Scalar,
8787

8888
/// Vector and array elements (all of them having the same `elem` layout).
@@ -106,7 +106,7 @@ impl Components {
106106
/// this can return multiple components, with at most one ever being non-ZST.
107107
//
108108
// FIXME(eddyb) be more aggressive in pruning ZSTs so this can be simpler.
109-
pub(super) fn find_components_containing(
109+
pub(crate) fn find_components_containing(
110110
&self,
111111
// FIXME(eddyb) consider renaming such offset ranges to "extent".
112112
offset_range: Range<u32>,
@@ -168,7 +168,7 @@ impl Components {
168168
}
169169

170170
/// Context for computing `TypeLayout`s from `Type`s (with caching).
171-
pub(super) struct LayoutCache<'a> {
171+
pub(crate) struct LayoutCache<'a> {
172172
cx: Rc<Context>,
173173
wk: &'static spv::spec::WellKnown,
174174

@@ -178,7 +178,7 @@ pub(super) struct LayoutCache<'a> {
178178
}
179179

180180
impl<'a> LayoutCache<'a> {
181-
pub(super) fn new(cx: Rc<Context>, config: &'a LayoutConfig) -> Self {
181+
pub(crate) fn new(cx: Rc<Context>, config: &'a LayoutConfig) -> Self {
182182
Self { cx, wk: &spv::spec::Spec::get().well_known, config, cache: Default::default() }
183183
}
184184

@@ -197,7 +197,7 @@ impl<'a> LayoutCache<'a> {
197197
}
198198

199199
/// Attempt to compute a `TypeLayout` for a given (SPIR-V) `Type`.
200-
pub(super) fn layout_of(&self, ty: Type) -> Result<TypeLayout, LayoutError> {
200+
pub(crate) fn layout_of(&self, ty: Type) -> Result<TypeLayout, LayoutError> {
201201
if let Some(cached) = self.cache.borrow().get(&ty).cloned() {
202202
return Ok(cached);
203203
}
@@ -348,7 +348,7 @@ impl<'a> LayoutCache<'a> {
348348
// FIXME(eddyb) !!! what if... types had a min/max size and then...
349349
// that would allow surrounding offsets to limit their size... but... ugh...
350350
// ugh this doesn't make any sense. maybe if the front-end specifies
351-
// offsets with "abstract types", it must configure `qptr::layout`?
351+
// offsets with "abstract types", it must configure `mem::layout`?
352352
let layout = if spv_inst.opcode == wk.OpTypeBool {
353353
// FIXME(eddyb) make this properly abstract instead of only configurable.
354354
scalar_with_size_and_align(self.config.abstract_bool_size_align)

src/mem/mod.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//! Memory operations, analyses and transformations.
2+
//
3+
// FIXME(eddyb) document at least these aspects:
4+
// - "memory" = indirect storage of data and/or resources
5+
// - (untyped) "data" = mix of plain bytes and pointers (as per RalfJ blog post)
6+
// (does "non-data memory" actually make sense? could be considered typed?)
7+
//
8+
// FIXME(eddyb) dig into past notes (e.g. `qptr::legalize`, Rust-GPU release notes,
9+
// https://github.com/EmbarkStudios/spirt/pull/24, etc.) for useful docs.
10+
//
11+
// FIXME(eddyb) consider taking this into a more (R)VSDG "state type" direction.
12+
13+
use crate::{OrdAssertEq, Type};
14+
use std::collections::BTreeMap;
15+
use std::num::NonZeroU32;
16+
use std::rc::Rc;
17+
18+
// NOTE(eddyb) all the modules are declared here, but they're documented "inside"
19+
// (i.e. using inner doc comments).
20+
pub mod analyze;
21+
// FIXME(eddyb) make this public?
22+
pub(crate) mod layout;
23+
pub mod shapes;
24+
25+
pub use layout::LayoutConfig;
26+
27+
/// Memory-specific attributes ([`Attr::Mem`]).
28+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
29+
pub enum MemAttr {
30+
/// When applied to a `GlobalVar` or `FuncLocalVar`, this tracks all possible
31+
/// access patterns its memory may be subjected to (see [`MemAccesses`]).
32+
Accesses(OrdAssertEq<MemAccesses>),
33+
}
34+
35+
#[derive(Clone, PartialEq, Eq, Hash)]
36+
pub enum MemAccesses {
37+
/// Accesses to one or more handles (i.e. optionally indexed by
38+
/// [`crate::qptr::QPtrOp::HandleArrayIndex`]), which can be:
39+
/// - `Handle::Opaque(handle_type)`: all accesses involve [`MemOp::Load`] or
40+
/// [`crate::qptr::QPtrAttr::ToSpvPtrInput`], with the common type `handle_type`
41+
/// - `Handle::Buffer(data_happ)`: carries with it `data_happ`,
42+
/// i.e. the access patterns for the memory that is reached through
43+
/// [`crate::qptr::QPtrOp::BufferData`]
44+
Handles(shapes::Handle<DataHapp>),
45+
46+
Data(DataHapp),
47+
}
48+
49+
/// Data HAPP ("Hierarchical Access Pattern Partitioning"): all access patterns
50+
/// for some memory, structured by disjoint offset ranges ("partitions").
51+
///
52+
/// This is the core of "type recovery" (inferring typed memory from untyped),
53+
/// with "struct/"array" equivalents (i.e. as `DataHappKind` variants), but
54+
/// while it can be mapped to an explicitly laid out data type, it also tracks
55+
/// distinctions only needed during merging (e.g. [`DataHappKind::StrictlyTyped`]).
56+
///
57+
/// **Note**: the only reason for the recursive/"hierarchical" aspect is that
58+
/// (array-like) dynamic indexing allows for compact representation of repeated
59+
/// patterns, which can non-trivially nest for 3+ levels without losing the need
60+
/// for efficient representation - in fact, one can construct a worst-case like:
61+
/// ```ignore
62+
/// [(A, [(B, [(C, [(D, [T; N], X); 2], Y); 2], Z); 2], W); 2]
63+
/// ```
64+
/// (with only `N * 2**4` leaf `T`s because of the 4 `[(_, ..., _); 2]` levels,
65+
/// and the potential for dozens of such levels while remaining a plausible size)
66+
//
67+
// FIXME(eddyb) reconsider the name (acronym was picked to avoid harder decisions).
68+
#[derive(Clone, PartialEq, Eq, Hash)]
69+
pub struct DataHapp {
70+
/// If present, this is a worst-case upper bound on the offsets at which
71+
/// accesses may be perfomed.
72+
//
73+
// FIXME(eddyb) use proper newtypes for byte amounts.
74+
//
75+
// FIXME(eddyb) suboptimal naming choice, but other options are too verbose,
76+
// including maybe using `RangeTo<_>` to explicitly indicate "exclusive".
77+
//
78+
// FIXME(eddyb) consider renaming such information to "extent", but that might
79+
// be ambiguous with an offset range (as opposed to using the maximum of all
80+
// *possible* `offset_range.end` values to describe a "maximum size").
81+
pub max_size: Option<u32>,
82+
83+
pub kind: DataHappKind,
84+
}
85+
86+
impl DataHapp {
87+
pub const DEAD: Self = Self { max_size: Some(0), kind: DataHappKind::Dead };
88+
}
89+
90+
#[derive(Clone, PartialEq, Eq, Hash)]
91+
pub enum DataHappKind {
92+
/// Not actually accessed (only an intermediary state, during access analysis).
93+
//
94+
// FIXME(eddyb) use `Option<DataHapp>` instead? or an empty `Disjoint`?
95+
Dead,
96+
97+
// FIXME(eddyb) replace the two leaves with e.g. `Leaf(Type, LeafAccessKind)`.
98+
//
99+
//
100+
/// Accesses through typed pointers (e.g. via unknown SPIR-V instructions),
101+
/// requiring a specific choice of pointee type which cannot be modified,
102+
/// and has to be reused as-is, when lifting to typed memory.
103+
///
104+
/// Other overlapping accesses can be merged into this one as long as they
105+
/// can be fully expressed using the (transitive) components of this type.
106+
StrictlyTyped(Type),
107+
108+
/// Direct accesses (e.g. [`MemOp::Load`], [`MemOp::Store`]), which can be
109+
/// decomposed as necessary (down to individual scalar leaves), to allow
110+
/// maximal merging opportunities.
111+
//
112+
// FIXME(eddyb) track whether accesses are `Load`s and/or `Store`s, to allow
113+
// inferring `NonWritable`/`NonReadable` annotations, as well.
114+
Direct(Type),
115+
116+
/// Partitioning into disjoint offset ranges (the map is keyed by the start
117+
/// of the offset range, while the end is implied by its corresponding value),
118+
/// requiring a "struct" type, when lifting to typed memory.
119+
//
120+
// FIXME(eddyb) make this non-nestable and the fundamental basis of "HAPP".
121+
Disjoint(Rc<BTreeMap<u32, DataHapp>>),
122+
123+
/// `Disjoint` counterpart for dynamic offsetting, requiring an "array" type,
124+
/// when lifting to typed memory, with one single element type being repeated
125+
/// across the entire size, at all offsets that are a multiple of `stride`.
126+
Repeated {
127+
// FIXME(eddyb) this feels inefficient.
128+
element: Rc<DataHapp>,
129+
stride: NonZeroU32,
130+
},
131+
}
132+
133+
/// Memory-specific operations ([`DataInstKind::Mem`]).
134+
#[derive(Clone, PartialEq, Eq, Hash)]
135+
pub enum MemOp {
136+
// HACK(eddyb) `OpVariable` replacement, which itself should not be kept as
137+
// a `SpvInst` - once fn-local variables are lowered, this should go there.
138+
FuncLocalVar(shapes::MemLayout),
139+
140+
/// Read a single value from a pointer (`inputs[0]`).
141+
//
142+
// FIXME(eddyb) limit this to data - and scalars, maybe vectors at most.
143+
Load,
144+
145+
/// Write a single value (`inputs[1]`) to a pointer (`inputs[0]`).
146+
//
147+
// FIXME(eddyb) limit this to data - and scalars, maybe vectors at most.
148+
Store,
149+
//
150+
// FIXME(eddyb) implement more ops (e.g. copies, atomics).
151+
}

src/qptr/shapes.rs renamed to src/mem/shapes.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Variable shapes (untyped memory layouts vs abstract resources).
22
//
33
// FIXME(eddyb) does this need its own module still?
4+
// TODO(eddyb) strongly consider moving these to `mem/mod.rs`.
45

56
use crate::{AddrSpace, Type};
67
use std::num::NonZeroU32;
@@ -22,6 +23,7 @@ pub enum GlobalVarShape {
2223
},
2324

2425
// FIXME(eddyb) unify terminology around "concrete"/"memory"/"untyped (data)".
26+
// TODO(eddyb) strongly consider renaming this to just `Data`.
2527
UntypedData(MemLayout),
2628

2729
/// Non-memory pipeline interface, which must keep the exact original type,
@@ -73,7 +75,7 @@ pub enum Handle<BL = MaybeDynMemLayout> {
7375
// instead of being treated like a buffer?
7476
//
7577
// FIXME(eddyb) should this be a `Type` of its own, that can be loaded from
76-
// a handle `QPtr`, and then has data pointer / length ops *on that*?
78+
// a handle pointer, and then has data pointer / length ops *on that*?
7779
Buffer(AddrSpace, BL),
7880
}
7981

@@ -83,11 +85,12 @@ pub enum Handle<BL = MaybeDynMemLayout> {
8385
/// and are both kept track of to detect ambiguity in implicit layouts, e.g.
8486
/// field offsets when the `Offset` decoration isn't being used.
8587
/// Note, however, that `legacy_align` can be raised to "extended" alignment,
86-
/// or completeley ignored, using [`LayoutConfig`](crate::qptr::LayoutConfig).
88+
/// or completeley ignored, using [`LayoutConfig`](crate::mem::LayoutConfig).
8789
///
8890
/// Only `align` is *required*, that is `size % align == 0` must be always enforced.
8991
//
9092
// FIXME(eddyb) consider supporting specialization-constant-length arrays.
93+
// TODO(eddyb) strongly consider renaming this to `DataLayout`.
9194
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
9295
pub struct MemLayout {
9396
// FIXME(eddyb) use proper newtypes (and log2 for align!).

src/passes/qptr.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::qptr;
44
use crate::visit::{InnerVisit, Visitor};
55
use crate::{AttrSet, Const, Context, Func, FxIndexSet, GlobalVar, Module, Type};
66

7-
pub fn lower_from_spv_ptrs(module: &mut Module, layout_config: &qptr::LayoutConfig) {
7+
pub fn lower_from_spv_ptrs(module: &mut Module, layout_config: &crate::mem::LayoutConfig) {
88
let cx = &module.cx();
99

1010
let (seen_global_vars, seen_funcs) = {
@@ -34,11 +34,13 @@ pub fn lower_from_spv_ptrs(module: &mut Module, layout_config: &qptr::LayoutConf
3434
}
3535
}
3636

37-
pub fn analyze_uses(module: &mut Module, layout_config: &qptr::LayoutConfig) {
38-
qptr::analyze::InferUsage::new(module.cx(), layout_config).infer_usage_in_module(module);
37+
// FIXME(eddyb) this doesn't really belong in `qptr`.
38+
pub fn analyze_mem_accesses(module: &mut Module, layout_config: &crate::mem::LayoutConfig) {
39+
crate::mem::analyze::GatherAccesses::new(module.cx(), layout_config)
40+
.gather_accesses_in_module(module);
3941
}
4042

41-
pub fn lift_to_spv_ptrs(module: &mut Module, layout_config: &qptr::LayoutConfig) {
43+
pub fn lift_to_spv_ptrs(module: &mut Module, layout_config: &crate::mem::LayoutConfig) {
4244
let cx = &module.cx();
4345

4446
let (seen_global_vars, seen_funcs) = {

0 commit comments

Comments
 (0)