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//! > *&mdash;
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>&nbsp;&nbsp;&nbsp;&nbsp;*(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}