spirt/lib.rs
1//! > <div style="font-size:small;border:1px solid;padding:1em;padding-top:0">
2//! > <div align="center">
3//! >
4//! > ## `SPIR-🇹`
5//! >
6//! > **⋯🢒 🇹arget 🠆 🇹ransform 🠆 🇹ranslate ⋯🢒**
7//! >
8//! > </div><br>
9//! >
10//! > **SPIR-🇹** is a research project aimed at exploring shader-oriented IR designs
11//! > derived from SPIR-V, and producing a framework around such an IR to facilitate
12//! > advanced compilation pipelines, beyond what existing SPIR-V tooling allows for.
13//! >
14//! > 🚧 *This project is in active design and development, many details can and will change* 🚧
15//! >
16//! > </div>
17//! >
18//! > *—
19#![cfg_attr(
20 docsrs,
21 // NOTE(eddyb) this requires updating `repository` before every release to
22 // end in `/tree/` followed by the tag name, in order to be useful.
23 doc = concat!(
24 "[`", env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"), "`'s `README`]",
25 "(", env!("CARGO_PKG_REPOSITORY"), "#readme)* "
26 )
27)]
28#![cfg_attr(
29 git_main_docs,
30 doc = concat!(
31 "[`", env!("CARGO_PKG_NAME"), " @ ", env!("GIT_MAIN_DESCRIBE"), "`'s `README`]",
32 "(https://github.com/rust-gpu/spirt/tree/", env!("GIT_MAIN_COMMIT"), "#readme)* "
33 )
34)]
35#![cfg_attr(
36 any(docsrs, git_main_docs),
37 doc = "<sup> *(click through for the full version)*</sup>"
38)]
39// HACK(eddyb) this is only relevant for local builds (which don't need a link).
40#![cfg_attr(
41 not(any(docsrs, git_main_docs)),
42 doc = concat!("`", env!("CARGO_PKG_NAME"), "`'s `README`* ")
43)]
44//!
45//! *Check out also [the `rust-gpu/spirt` GitHub repository](https://github.com/rust-gpu/spirt),
46//! for any additional developments.*
47//!
48//! #### Notable types/modules
49//!
50//! ##### IR data types
51// HACK(eddyb) using `(struct.Context.html)` to link `Context`, not `context::Context`.
52//! * [`Context`](struct.Context.html): handles interning ([`Type`]s, [`Const`]s, etc.) and allocating entity handles
53//! * [`Module`]: owns [`Func`]s and [`GlobalVar`]s (rooted by [`exports`](Module::exports))
54//! * [`FuncDefBody`]: owns [`ControlRegion`]s and [DataInst]s (rooted by [`body`](FuncDefBody::body))
55//!
56//! ##### Utilities and passes
57//! * [`print`](mod@print): pretty-printer with (styled and hyperlinked) HTML output
58//! * [`spv::lower`]/[`spv::lift`]: conversion from/to SPIR-V
59//! * [`cfg::Structurizer`]: (re)structurization from arbitrary control-flow
60//!
61
62// BEGIN - Embark standard lints v6 for Rust 1.55+
63// do not change or add/remove here, but one can add exceptions after this section
64// for more info see: <https://github.com/EmbarkStudios/rust-ecosystem/issues/59>
65#![deny(unsafe_code)]
66#![warn(
67 clippy::all,
68 clippy::await_holding_lock,
69 clippy::char_lit_as_u8,
70 clippy::checked_conversions,
71 clippy::dbg_macro,
72 clippy::debug_assert_with_mut_call,
73 clippy::doc_markdown,
74 clippy::empty_enum,
75 clippy::enum_glob_use,
76 clippy::exit,
77 clippy::expl_impl_clone_on_copy,
78 clippy::explicit_deref_methods,
79 clippy::explicit_into_iter_loop,
80 clippy::fallible_impl_from,
81 clippy::filter_map_next,
82 clippy::flat_map_option,
83 clippy::float_cmp_const,
84 clippy::fn_params_excessive_bools,
85 clippy::from_iter_instead_of_collect,
86 clippy::if_let_mutex,
87 clippy::implicit_clone,
88 clippy::imprecise_flops,
89 clippy::inefficient_to_string,
90 clippy::invalid_upcast_comparisons,
91 clippy::large_digit_groups,
92 clippy::large_stack_arrays,
93 clippy::large_types_passed_by_value,
94 clippy::let_unit_value,
95 clippy::linkedlist,
96 clippy::lossy_float_literal,
97 clippy::macro_use_imports,
98 clippy::manual_ok_or,
99 clippy::map_err_ignore,
100 clippy::map_flatten,
101 clippy::map_unwrap_or,
102 clippy::match_on_vec_items,
103 clippy::match_same_arms,
104 clippy::match_wild_err_arm,
105 clippy::match_wildcard_for_single_variants,
106 clippy::mem_forget,
107 clippy::missing_enforced_import_renames,
108 clippy::mut_mut,
109 clippy::mutex_integer,
110 clippy::needless_borrow,
111 clippy::needless_continue,
112 clippy::needless_for_each,
113 clippy::option_option,
114 clippy::path_buf_push_overwrite,
115 clippy::ptr_as_ptr,
116 clippy::rc_mutex,
117 clippy::ref_option_ref,
118 clippy::rest_pat_in_fully_bound_structs,
119 clippy::same_functions_in_if_condition,
120 clippy::semicolon_if_nothing_returned,
121 clippy::single_match_else,
122 clippy::string_add_assign,
123 clippy::string_add,
124 clippy::string_lit_as_bytes,
125 clippy::string_to_string,
126 clippy::todo,
127 clippy::trait_duplication_in_bounds,
128 clippy::unimplemented,
129 clippy::unnested_or_patterns,
130 clippy::unused_self,
131 clippy::useless_transmute,
132 clippy::verbose_file_reads,
133 clippy::zero_sized_map_values,
134 future_incompatible,
135 nonstandard_style,
136 rust_2018_idioms
137)]
138// END - Embark standard lints v6 for Rust 1.55+
139// crate-specific exceptions:
140#![allow(
141 // NOTE(eddyb) ignored for readability (`match` used when `if let` is too long).
142 clippy::single_match_else,
143
144 // NOTE(eddyb) ignored because it's misguided to suggest `let mut s = ...;`
145 // and `s.push_str(...);` when `+` is equivalent and does not require `let`.
146 clippy::string_add,
147
148 // FIXME(eddyb) rework doc comments to conform to linted expectations.
149 clippy::too_long_first_doc_paragraph,
150)]
151// NOTE(eddyb) this is stronger than the "Embark standard lints" above, because
152// we almost never need `unsafe` code and this is a further "speed bump" to it.
153#![forbid(unsafe_code)]
154
155// NOTE(eddyb) all the modules are declared here, but they're documented "inside"
156// (i.e. using inner doc comments).
157pub mod cfg;
158pub mod cfgssa;
159mod context;
160pub mod func_at;
161pub mod print;
162pub mod transform;
163pub mod visit;
164pub mod passes {
165 //! IR transformations (typically whole-[`Module`](crate::Module)).
166 //
167 // NOTE(eddyb) inline `mod` to avoid adding APIs here, it's just namespacing.
168
169 pub mod legalize;
170 pub mod link;
171 pub mod qptr;
172}
173pub mod qptr;
174pub mod spv;
175
176use smallvec::SmallVec;
177use std::borrow::Cow;
178use std::collections::BTreeSet;
179use std::rc::Rc;
180
181// HACK(eddyb) work around the lack of `FxIndex{Map,Set}` type aliases elsewhere.
182#[doc(hidden)]
183type FxIndexMap<K, V> =
184 indexmap::IndexMap<K, V, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
185#[doc(hidden)]
186type FxIndexSet<V> = indexmap::IndexSet<V, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
187
188// NOTE(eddyb) these reexports are all documented inside `context`.
189// FIXME(eddyb) maybe make an `entity` module to move either the definitions,
190// or at least the re-exports - an `ir` module might help too, organizationally?
191pub use context::{
192 Context, EntityDefs, EntityList, EntityListIter, EntityOrientedDenseMap, EntityOrientedMapKey,
193};
194
195/// Interned handle for a [`str`].
196pub use context::InternedStr;
197
198// HACK(eddyb) this only serves to disallow modifying the `cx` field of `Module`.
199#[doc(hidden)]
200mod sealed {
201 use super::*;
202 use std::rc::Rc;
203
204 #[derive(Clone)]
205 pub struct Module {
206 /// Context used for everything interned, in this module.
207 ///
208 /// Notable choices made for this field:
209 /// * private to disallow switching the context of a module
210 /// * [`Rc`] sharing to allow multiple modules to use the same context
211 /// (`Context: !Sync` because of the interners so it can't be `Arc`)
212 cx: Rc<Context>,
213
214 pub dialect: ModuleDialect,
215 pub debug_info: ModuleDebugInfo,
216
217 pub global_vars: EntityDefs<GlobalVar>,
218 pub funcs: EntityDefs<Func>,
219
220 pub exports: FxIndexMap<ExportKey, Exportee>,
221 }
222
223 impl Module {
224 pub fn new(cx: Rc<Context>, dialect: ModuleDialect, debug_info: ModuleDebugInfo) -> Self {
225 Self {
226 cx,
227
228 dialect,
229 debug_info,
230
231 global_vars: Default::default(),
232 funcs: Default::default(),
233
234 exports: Default::default(),
235 }
236 }
237
238 // FIXME(eddyb) `cx_ref` might be the better default in situations where
239 // the module doesn't need to be modified, figure out if that's common.
240 pub fn cx(&self) -> Rc<Context> {
241 self.cx.clone()
242 }
243
244 pub fn cx_ref(&self) -> &Rc<Context> {
245 &self.cx
246 }
247 }
248}
249pub use sealed::Module;
250
251/// Semantic properties of a SPIR-T module (not tied to any declarations/definitions).
252#[derive(Clone)]
253pub enum ModuleDialect {
254 Spv(spv::Dialect),
255}
256
257/// Non-semantic details (i.e. debuginfo) of a SPIR-Y module (not tied to any
258/// declarations/definitions).
259#[derive(Clone)]
260pub enum ModuleDebugInfo {
261 Spv(spv::ModuleDebugInfo),
262}
263
264/// An unique identifier (e.g. a link name, or "symbol") for a module export.
265#[derive(Clone, PartialEq, Eq, Hash)]
266pub enum ExportKey {
267 LinkName(InternedStr),
268
269 SpvEntryPoint {
270 imms: SmallVec<[spv::Imm; 2]>,
271 // FIXME(eddyb) remove this by recomputing the interface vars.
272 interface_global_vars: SmallVec<[GlobalVar; 4]>,
273 },
274}
275
276/// A definition exported out of a module (see also [`ExportKey`]).
277#[derive(Copy, Clone)]
278pub enum Exportee {
279 GlobalVar(GlobalVar),
280 Func(Func),
281}
282
283/// Interned handle for an [`AttrSetDef`](crate::AttrSetDef)
284/// (a set of [`Attr`](crate::Attr)s).
285pub use context::AttrSet;
286
287/// Definition for an [`AttrSet`]: a set of [`Attr`]s.
288#[derive(Default, PartialEq, Eq, Hash)]
289pub struct AttrSetDef {
290 // FIXME(eddyb) use `BTreeMap<Attr, AttrValue>` and split some of the params
291 // between the `Attr` and `AttrValue` based on specified uniquness.
292 // FIXME(eddyb) don't put debuginfo in here, but rather at use sites
293 // (for e.g. types, with component types also having the debuginfo
294 // bundled at the use site of the composite type) in order to allow
295 // deduplicating definitions that only differ in debuginfo, in SPIR-T,
296 // and still lift SPIR-V with duplicate definitions, out of that.
297 pub attrs: BTreeSet<Attr>,
298}
299
300impl AttrSetDef {
301 pub fn push_diag(&mut self, diag: Diag) {
302 // FIXME(eddyb) seriously consider moving to `BTreeMap` (see above).
303 // HACK(eddyb) this assumes `Attr::Diagnostics` is the last of `Attr`!
304 let mut attr = if let Some(Attr::Diagnostics(_)) = self.attrs.last() {
305 self.attrs.pop_last().unwrap()
306 } else {
307 Attr::Diagnostics(OrdAssertEq(vec![]))
308 };
309 match &mut attr {
310 Attr::Diagnostics(OrdAssertEq(diags)) => diags.push(diag),
311 _ => unreachable!(),
312 }
313 self.attrs.insert(attr);
314 }
315
316 // FIXME(eddyb) should this be hidden in favor of `AttrSet::append_diag`?
317 pub fn append_diag(&self, diag: Diag) -> Self {
318 let mut new_attrs = Self { attrs: self.attrs.clone() };
319 new_attrs.push_diag(diag);
320 new_attrs
321 }
322}
323
324// FIXME(eddyb) should these methods be elsewhere?
325impl AttrSet {
326 // FIXME(eddyb) should this be hidden in favor of `push_diag`?
327 // FIXME(eddyb) should these methods always take multiple values?
328 pub fn append_diag(self, cx: &Context, diag: Diag) -> Self {
329 cx.intern(cx[self].append_diag(diag))
330 }
331
332 pub fn push_diag(&mut self, cx: &Context, diag: Diag) {
333 *self = self.append_diag(cx, diag);
334 }
335}
336
337/// Any semantic or non-semantic (debuginfo) decoration/modifier, that can be
338/// *optionally* applied to some declaration/definition.
339///
340/// Always used via [`AttrSetDef`] (interned as [`AttrSet`]).
341//
342// FIXME(eddyb) consider interning individual attrs, not just `AttrSet`s.
343#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::From)]
344pub enum Attr {
345 /// `QPtr`-specific attributes (see [`qptr::QPtrAttr`]).
346 #[from]
347 QPtr(qptr::QPtrAttr),
348
349 SpvAnnotation(spv::Inst),
350
351 SpvDebugLine {
352 file_path: OrdAssertEq<InternedStr>,
353 line: u32,
354 col: u32,
355 },
356
357 /// Some SPIR-V instructions, like `OpFunction`, take a bitflags operand
358 /// that is effectively an optimization over using `OpDecorate`.
359 //
360 // FIXME(eddyb) handle flags having further operands as parameters.
361 SpvBitflagsOperand(spv::Imm),
362
363 /// Can be used anywhere to record [`Diag`]nostics produced during a pass,
364 /// while allowing the pass to continue (and its output to be pretty-printed).
365 //
366 // HACK(eddyb) this is the last variant to control printing order, but also
367 // to make `push_diag`/`append_diag` above work correctly!
368 Diagnostics(OrdAssertEq<Vec<Diag>>),
369}
370
371/// Diagnostics produced by SPIR-T passes, and recorded in [`Attr::Diagnostics`].
372#[derive(Clone, PartialEq, Eq, Hash)]
373pub struct Diag {
374 pub level: DiagLevel,
375 // FIXME(eddyb) this may want to be `SmallVec` and/or `Rc`?
376 pub message: Vec<DiagMsgPart>,
377}
378
379impl Diag {
380 pub fn new(level: DiagLevel, message: impl IntoIterator<Item = DiagMsgPart>) -> Self {
381 Self { level, message: message.into_iter().collect() }
382 }
383
384 // FIMXE(eddyb) make macros more ergonomic than this, for interpolation.
385 #[track_caller]
386 pub fn bug(message: impl IntoIterator<Item = DiagMsgPart>) -> Self {
387 Self::new(DiagLevel::Bug(std::panic::Location::caller()), message)
388 }
389
390 pub fn err(message: impl IntoIterator<Item = DiagMsgPart>) -> Self {
391 Self::new(DiagLevel::Error, message)
392 }
393
394 pub fn warn(message: impl IntoIterator<Item = DiagMsgPart>) -> Self {
395 Self::new(DiagLevel::Warning, message)
396 }
397}
398
399/// The "severity" level of a [`Diag`]nostic.
400///
401/// Note: `Bug` diagnostics track their emission point for easier identification.
402#[derive(Copy, Clone, PartialEq, Eq, Hash)]
403pub enum DiagLevel {
404 Bug(&'static std::panic::Location<'static>),
405 Error,
406 Warning,
407}
408
409/// One part of a [`Diag`]nostic message, allowing rich interpolation.
410///
411/// Note: [`visit::Visitor`] and [`transform::Transformer`] *do not* interact
412/// with any interpolated information, and it's instead treated as "frozen" data.
413#[derive(Clone, PartialEq, Eq, Hash, derive_more::From)]
414// HACK(eddyb) this sets the default as "opt-out", to avoid `#[from(forward)]`
415// on the `Plain` variant from making it "opt-in" for all variants.
416#[from]
417pub enum DiagMsgPart {
418 #[from(forward)]
419 Plain(Cow<'static, str>),
420
421 // FIXME(eddyb) use `dyn Trait` instead of listing out a few cases.
422 Attrs(AttrSet),
423 Type(Type),
424 Const(Const),
425 QPtrUsage(qptr::QPtrUsage),
426}
427
428/// Wrapper to limit `Ord` for interned index types (e.g. [`InternedStr`])
429/// to only situations where the interned index reflects contents (i.e. equality).
430//
431// FIXME(eddyb) this is not ideal, and it might be more useful to replace the
432// `BTreeSet<Attr>` with an `BTreeMap<Attr, AttrValue>`, where only `Attr` needs
433// to be `Ord`, and the details that cannot be `Ord`, can be moved to `AttrValue`.
434#[derive(Copy, Clone, PartialEq, Eq, Hash)]
435pub struct OrdAssertEq<T>(pub T);
436
437impl<T: Eq> PartialOrd for OrdAssertEq<T> {
438 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
439 Some(self.cmp(other))
440 }
441}
442
443impl<T: Eq> Ord for OrdAssertEq<T> {
444 #[track_caller]
445 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
446 assert!(
447 self == other,
448 "OrdAssertEq<{}>::cmp called with unequal values",
449 std::any::type_name::<T>(),
450 );
451 std::cmp::Ordering::Equal
452 }
453}
454
455/// Interned handle for a [`TypeDef`](crate::TypeDef).
456pub use context::Type;
457
458/// Definition for a [`Type`].
459//
460// FIXME(eddyb) maybe special-case some basic types like integers.
461#[derive(PartialEq, Eq, Hash)]
462pub struct TypeDef {
463 pub attrs: AttrSet,
464 pub kind: TypeKind,
465}
466
467#[derive(Clone, PartialEq, Eq, Hash)]
468pub enum TypeKind {
469 /// "Quasi-pointer", an untyped pointer-like abstract scalar that can represent
470 /// both memory locations (in any address space) and other kinds of locations
471 /// (e.g. SPIR-V `OpVariable`s in non-memory "storage classes").
472 ///
473 /// This flexibility can be used to represent pointers from source languages
474 /// that expect/are defined to operate on untyped memory (C, C++, Rust, etc.),
475 /// that can then be legalized away (e.g. via inlining) or even emulated.
476 ///
477 /// Information narrowing down how values of the type may be created/used
478 /// (e.g. "points to variable `x`" or "accessed at offset `y`") can be found
479 /// attached as `Attr`s on those `Value`s (see [`Attr::QPtr`]).
480 //
481 // FIXME(eddyb) a "refinement system" that's orthogonal from types, and kept
482 // separately in e.g. `ControlRegionInputDecl`, might be a better approach?
483 QPtr,
484
485 SpvInst {
486 spv_inst: spv::Inst,
487 // FIXME(eddyb) find a better name.
488 type_and_const_inputs: SmallVec<[TypeOrConst; 2]>,
489 },
490
491 /// The type of a [`ConstKind::SpvStringLiteralForExtInst`] constant, i.e.
492 /// a SPIR-V `OpString` with no actual type in SPIR-V.
493 SpvStringLiteralForExtInst,
494}
495
496// HACK(eddyb) this behaves like an implicit conversion for `cx.intern(...)`.
497impl context::InternInCx<Type> for TypeKind {
498 fn intern_in_cx(self, cx: &Context) -> Type {
499 cx.intern(TypeDef { attrs: Default::default(), kind: self })
500 }
501}
502
503// HACK(eddyb) this is like `Either<Type, Const>`, only used in `TypeKind::SpvInst`,
504// and only because SPIR-V type definitions can references both types and consts.
505#[derive(Copy, Clone, PartialEq, Eq, Hash)]
506pub enum TypeOrConst {
507 Type(Type),
508 Const(Const),
509}
510
511/// Interned handle for a [`ConstDef`](crate::ConstDef) (a constant value).
512pub use context::Const;
513
514/// Definition for a [`Const`]: a constant value.
515//
516// FIXME(eddyb) maybe special-case some basic consts like integer literals.
517#[derive(PartialEq, Eq, Hash)]
518pub struct ConstDef {
519 pub attrs: AttrSet,
520 pub ty: Type,
521 pub kind: ConstKind,
522}
523
524#[derive(Clone, PartialEq, Eq, Hash)]
525pub enum ConstKind {
526 PtrToGlobalVar(GlobalVar),
527
528 // HACK(eddyb) this is a fallback case that should become increasingly rare
529 // (especially wrt recursive consts), `Rc` means it can't bloat `ConstDef`.
530 SpvInst {
531 spv_inst_and_const_inputs: Rc<(spv::Inst, SmallVec<[Const; 4]>)>,
532 },
533
534 /// SPIR-V `OpString`, but only when used as an operand for an `OpExtInst`,
535 /// which can't have literals itself - for non-string literals `OpConstant*`
536 /// are readily usable, but only `OpString` is supported for string literals.
537 SpvStringLiteralForExtInst(InternedStr),
538}
539
540/// Declarations ([`GlobalVarDecl`], [`FuncDecl`]) can contain a full definition,
541/// or only be an import of a definition (e.g. from another module).
542#[derive(Clone)]
543pub enum DeclDef<D> {
544 Imported(Import),
545 Present(D),
546}
547
548/// An identifier (e.g. a link name, or "symbol") for an import declaration.
549#[derive(Copy, Clone, PartialEq, Eq, Hash)]
550pub enum Import {
551 LinkName(InternedStr),
552}
553
554/// Entity handle for a [`GlobalVarDecl`](crate::GlobalVarDecl) (a global variable).
555pub use context::GlobalVar;
556
557/// Declaration/definition for a [`GlobalVar`]: a global variable.
558//
559// FIXME(eddyb) mark any `GlobalVar` not *controlled* by the SPIR-V module
560// (roughly: storage classes that don't allow initializers, i.e. most of them),
561// as an "import" from "the shader interface", and therefore "externally visible",
562// to implicitly distinguish it from `GlobalVar`s internal to the module
563// (such as any constants that may need to be reshaped for legalization).
564#[derive(Clone)]
565pub struct GlobalVarDecl {
566 pub attrs: AttrSet,
567
568 /// The type of a pointer to the global variable (as opposed to the value type).
569 // FIXME(eddyb) try to replace with value type (or at least have that too).
570 pub type_of_ptr_to: Type,
571
572 /// When `type_of_ptr_to` is `QPtr`, `shape` must be used to describe the
573 /// global variable (see `GlobalVarShape`'s documentation for more details).
574 pub shape: Option<qptr::shapes::GlobalVarShape>,
575
576 /// The address space the global variable will be allocated into.
577 pub addr_space: AddrSpace,
578
579 pub def: DeclDef<GlobalVarDefBody>,
580}
581
582#[derive(Copy, Clone, PartialEq, Eq, Hash)]
583pub enum AddrSpace {
584 /// Placeholder for `GlobalVar`s with `GlobalVarShape::Handles`.
585 ///
586 /// In SPIR-V, this corresponds to `UniformConstant` for `Handle::Opaque`,
587 /// or the buffer's storage class for `Handle::Buffer`.
588 Handles,
589
590 SpvStorageClass(u32),
591}
592
593/// The body of a [`GlobalVar`] definition.
594#[derive(Clone)]
595pub struct GlobalVarDefBody {
596 /// If `Some`, the global variable will start out with the specified value.
597 pub initializer: Option<Const>,
598}
599
600/// Entity handle for a [`FuncDecl`](crate::FuncDecl) (a function).
601pub use context::Func;
602
603/// Declaration/definition for a [`Func`]: a function.
604#[derive(Clone)]
605pub struct FuncDecl {
606 pub attrs: AttrSet,
607
608 pub ret_type: Type,
609
610 pub params: SmallVec<[FuncParam; 2]>,
611
612 pub def: DeclDef<FuncDefBody>,
613}
614
615#[derive(Copy, Clone)]
616pub struct FuncParam {
617 pub attrs: AttrSet,
618
619 pub ty: Type,
620}
621
622/// The body of a [`Func`] definition.
623//
624// FIXME(eddyb) `FuncDefBody`/`func_def_body` are too long, find shorter names.
625#[derive(Clone)]
626pub struct FuncDefBody {
627 pub control_regions: EntityDefs<ControlRegion>,
628 pub control_nodes: EntityDefs<ControlNode>,
629 pub data_insts: EntityDefs<DataInst>,
630
631 /// The [`ControlRegion`] representing the whole body of the function.
632 ///
633 /// Function parameters are provided via `body.inputs`, i.e. they can be
634 /// only accessed with `Value::ControlRegionInputs { region: body, idx }`.
635 ///
636 /// When `unstructured_cfg` is `None`, this includes the structured return
637 /// of the function, with `body.outputs` as the returned values.
638 pub body: ControlRegion,
639
640 /// The unstructured (part of the) control-flow graph of the function.
641 ///
642 /// Only present if structurization wasn't attempted, or if was only partial
643 /// (leaving behind a mix of structured and unstructured control-flow).
644 ///
645 /// When present, it starts at `body` (more specifically, its exit),
646 /// effectively replacing the structured return `body` otherwise implies,
647 /// with `body` (or rather, its `children`) always being fully structured.
648 pub unstructured_cfg: Option<cfg::ControlFlowGraph>,
649}
650
651/// Entity handle for a [`ControlRegionDef`](crate::ControlRegionDef)
652/// (a control-flow region).
653///
654/// A [`ControlRegion`] ("control-flow region") is a linear chain of [`ControlNode`]s,
655/// describing a single-entry single-exit (SESE) control-flow "region" (subgraph)
656/// in a function's control-flow graph (CFG).
657///
658/// # Control-flow
659///
660/// In SPIR-T, two forms of control-flow are used:
661/// * "structured": [`ControlRegion`]s and [`ControlNode`]s in a "mutual tree"
662/// * i.e. each such [`ControlRegion`] can only appear in exactly one [`ControlNode`],
663/// and each [`ControlNode`] can only appear in exactly one [`ControlRegion`]
664/// * a region is either the function's body, or used as part of [`ControlNode`]
665/// (e.g. the "then" case of an `if`-`else`), itself part of a larger region
666/// * when inside a region, reaching any other part of the function (or any
667/// other function on call stack) requires leaving through the region's
668/// single exit (also called "merge") point, i.e. its execution is either:
669/// * "convergent": the region completes and continues into its parent
670/// [`ControlNode`], or function (the latter being a "structured return")
671/// * "divergent": execution gets stuck in the region (an infinite loop),
672/// or is aborted (e.g. `OpTerminateInvocation` from SPIR-V)
673/// * "unstructured": [`ControlRegion`]s which connect to other [`ControlRegion`]s
674/// using [`cfg::ControlInst`](crate::cfg::ControlInst)s (as described by a
675/// [`cfg::ControlFlowGraph`](crate::cfg::ControlFlowGraph))
676///
677/// When a function's entire body can be described by a single [`ControlRegion`],
678/// that function is said to have (entirely) "structured control-flow".
679///
680/// Mixing "structured" and "unstructured" control-flow is supported because:
681/// * during structurization, it allows structured subgraphs to remain connected
682/// by the same CFG edges that were connecting smaller [`ControlRegion`]s before
683/// * structurization doesn't have to fail in the cases it doesn't fully support
684/// yet, but can instead result in a "maximally structured" function
685///
686/// Other IRs may use different "structured control-flow" definitions, notably:
687/// * SPIR-V uses a laxer definition, that corresponds more to the constraints
688/// of the GLSL language, and is single-entry multiple-exit (SEME) with
689/// "alternate exits" consisting of `break`s out of `switch`es and loops,
690/// and `return`s (making it non-trivial to inline one function into another)
691/// * RVSDG inspired SPIR-T's design, but its regions are (acyclic) graphs, it
692/// makes no distinction between control-flow and "computational" nodes, and
693/// its execution order is determined by value/state dependencies alone
694/// (SPIR-T may get closer to it in the future, but the initial compromise
695/// was chosen to limit the effort of lowering/lifting from/to SPIR-V)
696///
697/// # Data-flow interactions
698///
699/// SPIR-T [`Value`](crate::Value)s follow "single static assignment" (SSA), just like SPIR-V:
700/// * inside a function, any new value is produced (or "defined") as an output
701/// of [`DataInst`]/[`ControlNode`], and "uses" of that value are [`Value`](crate::Value)s
702/// variants which refer to the defining [`DataInst`]/[`ControlNode`] directly
703/// (guaranteeing the "single" and "static" of "SSA", by construction)
704/// * the definition of a value must "dominate" all of its uses
705/// (i.e. in all possible execution paths, the definition precedes all uses)
706///
707/// But unlike SPIR-V, SPIR-T's structured control-flow has implications for SSA:
708/// * dominance is simpler, so values defined in a [`ControlRegion`](crate::ControlRegion) can be used:
709/// * later in that region, including in the region's `outputs`
710/// (which allows "exporting" values out to the rest of the function)
711/// * outside that region, but *only* if the parent [`ControlNode`](crate::ControlNode)
712/// is a `Loop` (that is, when the region is a loop's body)
713/// * this is an "emergent" property, stemming from the region having to
714/// execute (at least once) before the parent [`ControlNode`](crate::ControlNode)
715/// can complete, but is not is not ideal and should eventually be replaced
716/// with passing all such values through loop (body) `outputs`
717/// * instead of φ ("phi") nodes, SPIR-T uses region `outputs` to merge values
718/// coming from separate control-flow paths (i.e. the cases of a `Select`),
719/// and region `inputs` for passing values back along loop backedges
720/// (additionally, the body's `inputs` are used for function parameters)
721/// * like the "block arguments" alternative to SSA phi nodes (which some
722/// other SSA IRs use), this has the advantage of keeping the uses of the
723/// "source" values in their respective paths (where they're dominated),
724/// instead of in the merge (where phi nodes require special-casing, as
725/// their "uses" of all the "source" values would normally be illegal)
726/// * in unstructured control-flow, region `inputs` are additionally used for
727/// representing phi nodes, as [`cfg::ControlInst`](crate::cfg::ControlInst)s
728/// passing values to their target regions
729/// * all value uses across unstructured control-flow edges (i.e. not in the
730/// same region containing the value definition) *require* explicit passing,
731/// as unstructured control-flow [`ControlRegion`](crate::ControlRegion)s
732/// do *not* themselves get *any* implied dominance relations from the
733/// shape of the control-flow graph (unlike most typical CFG+SSA IRs)
734pub use context::ControlRegion;
735
736/// Definition for a [`ControlRegion`]: a control-flow region.
737#[derive(Clone, Default)]
738pub struct ControlRegionDef {
739 /// Inputs to this [`ControlRegion`]:
740 /// * accessed using [`Value::ControlRegionInput`]
741 /// * values provided by the parent:
742 /// * when this is the function body: the function's parameters
743 pub inputs: SmallVec<[ControlRegionInputDecl; 2]>,
744
745 pub children: EntityList<ControlNode>,
746
747 /// Output values from this [`ControlRegion`], provided to the parent:
748 /// * when this is the function body: these are the structured return values
749 /// * when this is a `Select` case: these are the values for the parent
750 /// [`ControlNode`]'s outputs (accessed using [`Value::ControlNodeOutput`])
751 /// * when this is a `Loop` body: these are the values to be used for the
752 /// next loop iteration's body `inputs`
753 /// * **not** accessible through [`Value::ControlNodeOutput`] on the `Loop`,
754 /// as it's both confusing regarding [`Value::ControlRegionInput`], and
755 /// also there's nothing stopping body-defined values from directly being
756 /// used outside the loop (once that changes, this aspect can be flipped)
757 pub outputs: SmallVec<[Value; 2]>,
758}
759
760#[derive(Copy, Clone)]
761pub struct ControlRegionInputDecl {
762 pub attrs: AttrSet,
763
764 pub ty: Type,
765}
766
767/// Entity handle for a [`ControlNodeDef`](crate::ControlNodeDef)
768/// (a control-flow operator or leaf).
769///
770/// See [`ControlRegion`] docs for more on control-flow in SPIR-T.
771pub use context::ControlNode;
772
773/// Definition for a [`ControlNode`]: a control-flow operator or leaf.
774///
775/// See [`ControlRegion`] docs for more on control-flow in SPIR-T.
776#[derive(Clone)]
777pub struct ControlNodeDef {
778 pub kind: ControlNodeKind,
779
780 /// Outputs from this [`ControlNode`]:
781 /// * accessed using [`Value::ControlNodeOutput`]
782 /// * values provided by `region.outputs`, where `region` is the executed
783 /// child [`ControlRegion`]:
784 /// * when this is a `Select`: the case that was chosen
785 pub outputs: SmallVec<[ControlNodeOutputDecl; 2]>,
786}
787
788#[derive(Copy, Clone)]
789pub struct ControlNodeOutputDecl {
790 pub attrs: AttrSet,
791
792 pub ty: Type,
793}
794
795#[derive(Clone)]
796pub enum ControlNodeKind {
797 /// Linear chain of [`DataInst`]s, executing in sequence.
798 ///
799 /// This is only an optimization over keeping [`DataInst`]s in [`ControlRegion`]
800 /// linear chains directly, or even merging [`DataInst`] with [`ControlNode`].
801 Block {
802 // FIXME(eddyb) should empty blocks be allowed? should `DataInst`s be
803 // linked directly into the `ControlRegion` `children` list?
804 insts: EntityList<DataInst>,
805 },
806
807 /// Choose one [`ControlRegion`] out of `cases` to execute, based on a single
808 /// value input (`scrutinee`) interpreted according to [`SelectionKind`].
809 ///
810 /// This corresponds to "gamma" (`γ`) nodes in (R)VSDG, though those are
811 /// sometimes limited only to a two-way selection on a boolean condition.
812 Select { kind: SelectionKind, scrutinee: Value, cases: SmallVec<[ControlRegion; 2]> },
813
814 /// Execute `body` repeatedly, until `repeat_condition` evaluates to `false`.
815 ///
816 /// To represent "loop state", `body` can take `inputs`, getting values from:
817 /// * on the first iteration: `initial_inputs`
818 /// * on later iterations: `body`'s own `outputs` (from the last iteration)
819 ///
820 /// As the condition is checked only *after* the body, this type of loop is
821 /// sometimes described as "tail-controlled", and is also equivalent to the
822 /// C-like `do { body; } while(repeat_condition)` construct.
823 ///
824 /// This corresponds to "theta" (`θ`) nodes in (R)VSDG.
825 Loop {
826 initial_inputs: SmallVec<[Value; 2]>,
827
828 body: ControlRegion,
829
830 // FIXME(eddyb) should this be kept in `body.outputs`? (that would not
831 // have any ambiguity as to whether it can see `body`-computed values)
832 repeat_condition: Value,
833 },
834
835 /// Leave the current invocation, similar to returning from every function
836 /// call in the stack (up to and including the entry-point), but potentially
837 /// indicating a fatal error as well.
838 //
839 // FIXME(eddyb) make this less shader-controlflow-centric.
840 ExitInvocation {
841 kind: cfg::ExitInvocationKind,
842
843 // FIXME(eddyb) centralize `Value` inputs across `ControlNode`s,
844 // and only use stricter types for building/traversing the IR.
845 inputs: SmallVec<[Value; 2]>,
846 },
847}
848
849#[derive(Clone)]
850pub enum SelectionKind {
851 /// Two-case selection based on boolean condition, i.e. `if`-`else`, with
852 /// the two cases being "then" and "else" (in that order).
853 BoolCond,
854
855 SpvInst(spv::Inst),
856}
857
858/// Entity handle for a [`DataInstDef`](crate::DataInstDef) (a leaf instruction).
859pub use context::DataInst;
860
861/// Definition for a [`DataInst`]: a leaf (non-control-flow) instruction.
862//
863// FIXME(eddyb) `DataInstKind::FuncCall` should probably be a `ControlNodeKind`,
864// but also `DataInst` vs `ControlNode` is a purely artificial distinction.
865#[derive(Clone)]
866pub struct DataInstDef {
867 pub attrs: AttrSet,
868
869 pub form: DataInstForm,
870
871 // FIXME(eddyb) change the inline size of this to fit most instructions.
872 pub inputs: SmallVec<[Value; 2]>,
873}
874
875/// Interned handle for a [`DataInstFormDef`](crate::DataInstFormDef)
876/// (a "form", or "template", for [`DataInstDef`](crate::DataInstDef)s).
877pub use context::DataInstForm;
878
879/// "Form" (or "template") definition for [`DataInstFormDef`]s, which includes
880/// most of their common *static* information (notably excluding `attrs`, as
881/// they vary more often due to handling diagnostics, debuginfo, refinement etc.).
882//
883// FIXME(eddyb) now that this is interned, try to find all the code that was
884// working around needing to borrow `DataInstKind`, just because it was owned
885// by a `FuncDefBody` (instead of interned in the `Context`).
886#[derive(Clone, PartialEq, Eq, Hash)]
887pub struct DataInstFormDef {
888 pub kind: DataInstKind,
889
890 pub output_type: Option<Type>,
891}
892
893#[derive(Clone, PartialEq, Eq, Hash, derive_more::From)]
894pub enum DataInstKind {
895 // FIXME(eddyb) try to split this into recursive and non-recursive calls,
896 // to avoid needing special handling for recursion where it's impossible.
897 FuncCall(Func),
898
899 /// `QPtr`-specific operations (see [`qptr::QPtrOp`]).
900 #[from]
901 QPtr(qptr::QPtrOp),
902
903 // FIXME(eddyb) should this have `#[from]`?
904 SpvInst(spv::Inst),
905 SpvExtInst {
906 ext_set: InternedStr,
907 inst: u32,
908 },
909}
910
911#[derive(Copy, Clone, PartialEq, Eq, Hash)]
912pub enum Value {
913 Const(Const),
914
915 /// One of the inputs to a [`ControlRegion`]:
916 /// * declared by `region.inputs[input_idx]`
917 /// * value provided by the parent of the `region`:
918 /// * when `region` is the function body: `input_idx`th function parameter
919 ControlRegionInput {
920 region: ControlRegion,
921 input_idx: u32,
922 },
923
924 /// One of the outputs produced by a [`ControlNode`]:
925 /// * declared by `control_node.outputs[output_idx]`
926 /// * value provided by `region.outputs[output_idx]`, where `region` is the
927 /// executed child [`ControlRegion`] (of `control_node`):
928 /// * when `control_node` is a `Select`: the case that was chosen
929 ControlNodeOutput {
930 control_node: ControlNode,
931 output_idx: u32,
932 },
933
934 /// The output value of a [`DataInst`].
935 DataInstOutput(DataInst),
936}