naga/back/spv/
mod.rs

1/*!
2Backend for [SPIR-V][spv] (Standard Portable Intermediate Representation).
3
4# Layout of values in `uniform` buffers
5
6WGSL's ["Internal Layout of Values"][ilov] rules specify the memory layout of
7each WGSL type. The memory layout is important for data stored in `uniform` and
8`storage` buffers, especially when exchanging data with CPU code.
9
10Both WGSL and Vulkan specify some conditions that a type's memory layout
11must satisfy in order to use that type in a `uniform` or `storage` buffer.
12For `storage` buffers, the WGSL and Vulkan restrictions are compatible, but
13for `uniform` buffers, WGSL allows some types that Vulkan does not, requiring
14adjustments when emitting SPIR-V for `uniform` buffers.
15
16## Padding in two-row matrices
17
18SPIR-V provides detailed control over the layout of matrix types, and is
19capable of describing the WGSL memory layout. However, Vulkan imposes
20additional restrictions.
21
22Vulkan's ["extended layout"][extended-layout] (also known as std140) rules
23apply to types used in `uniform` buffers. Under these rules, matrices are
24defined in terms of arrays of their vector type, and arrays are defined to have
25an alignment equal to the alignment of their element type rounded up to a
26multiple of 16. This means that each column of the matrix has a minimum
27alignment of 16. WGSL, and consequently Naga IR, on the other hand specifies
28column alignment equal to the alignment of the vector type, without being
29rounded up to 16.
30
31To compensate for this, for any `struct` used as a `uniform` buffer which
32contains a two-row matrix, we declare an additional "std140 compatible" type
33in which each column of the matrix has been decomposed into the containing
34struct. For example, the following WGSL struct type:
35
36```ignore
37struct Baz {
38    m: mat3x2<f32>,
39}
40```
41
42is rendered as the SPIR-V struct type:
43
44```ignore
45OpTypeStruct %v2float %v2float %v2float
46```
47
48This has the effect that struct indices in Naga IR for such types do not
49correspond to the struct indices used in SPIR-V. A mapping of struct indices
50for these types is maintained in [`Std140CompatTypeInfo`].
51
52Additionally, any two-row matrices that are declared directly as uniform
53buffers without being wrapped in a struct are declared as a struct containing a
54vector member for each column. Any array of a two-row matrix in a uniform
55buffer is declared as an array of a struct containing a vector member for each
56column. Any struct or array within a uniform buffer which contains a member or
57whose base type requires a std140 compatible type declaration, itself requires a
58std140 compatible type declaration.
59
60Whenever a value of such a type is [`loaded`] we insert code to convert the
61loaded value from the std140 compatible type to the regular type. This occurs
62in `BlockContext::write_checked_load`, making use of the wrapper function
63defined by `Writer::write_wrapped_convert_from_std140_compat_type`. For matrices
64that have been decomposed as separate columns in the containing struct, we load
65each column separately then composite the matrix type in
66`BlockContext::maybe_write_load_uniform_matcx2_struct_member`.
67
68Whenever a column of a matrix that has been decomposed into its containing
69struct is [`accessed`] with a constant index we adjust the emitted access chain
70to access from the containing struct instead, in `BlockContext::write_access_chain`.
71
72Whenever a column of a uniform buffer two-row matrix is [`dynamically accessed`]
73we must first load the matrix type, converting it from its std140 compatible
74type as described above, then access the column using the wrapper function
75defined by `Writer::write_wrapped_matcx2_get_column`. This is handled by
76`BlockContext::maybe_write_uniform_matcx2_dynamic_access`.
77
78Note that this approach differs somewhat from the equivalent code in the HLSL
79backend. For HLSL all structs containing two-row matrices (or arrays of such)
80have their declarations modified, not just those used as uniform buffers.
81Two-row matrices and arrays of such only use modified type declarations when
82used as uniform buffers, or additionally when used as struct member in any
83context. This avoids the need to convert struct values when loading from uniform
84buffers, but when loading arrays and matrices from uniform buffers or from any
85struct the conversion is still required. In contrast, the approach used here
86always requires converting *any* affected type when loading from a uniform
87buffer, but consistently *only* when loading from a uniform buffer. As a result
88this also means we only have to handle loads and not stores, as uniform buffers
89are read-only.
90
91[spv]: https://www.khronos.org/registry/SPIR-V/
92[ilov]: https://gpuweb.github.io/gpuweb/wgsl/#internal-value-layout
93[extended-layout]: https://docs.vulkan.org/spec/latest/chapters/interfaces.html#interfaces-resources-layout
94[`loaded`]: crate::Expression::Load
95[`accessed`]: crate::Expression::AccessIndex
96[`dynamically accessed`]: crate::Expression::Access
97*/
98
99mod block;
100mod f16_polyfill;
101mod helpers;
102mod image;
103mod index;
104mod instructions;
105mod layout;
106mod mesh_shader;
107mod ray;
108mod recyclable;
109mod selection;
110mod subgroup;
111mod writer;
112
113pub use mesh_shader::{MeshReturnInfo, MeshReturnMember};
114pub use spirv::{Capability, SourceLanguage};
115
116use alloc::{string::String, vec::Vec};
117use core::ops;
118
119use spirv::Word;
120use thiserror::Error;
121
122use crate::arena::{Handle, HandleVec};
123use crate::proc::{BoundsCheckPolicies, TypeResolution};
124
125#[derive(Clone)]
126struct PhysicalLayout {
127    magic_number: Word,
128    version: Word,
129    generator: Word,
130    bound: Word,
131    instruction_schema: Word,
132}
133
134#[derive(Default)]
135struct LogicalLayout {
136    capabilities: Vec<Word>,
137    extensions: Vec<Word>,
138    ext_inst_imports: Vec<Word>,
139    memory_model: Vec<Word>,
140    entry_points: Vec<Word>,
141    execution_modes: Vec<Word>,
142    debugs: Vec<Word>,
143    annotations: Vec<Word>,
144    declarations: Vec<Word>,
145    function_declarations: Vec<Word>,
146    function_definitions: Vec<Word>,
147}
148
149#[derive(Clone)]
150struct Instruction {
151    op: spirv::Op,
152    wc: u32,
153    type_id: Option<Word>,
154    result_id: Option<Word>,
155    operands: Vec<Word>,
156}
157
158const BITS_PER_BYTE: crate::Bytes = 8;
159
160#[derive(Clone, Debug, Error)]
161pub enum Error {
162    #[error("The requested entry point couldn't be found")]
163    EntryPointNotFound,
164    #[error("target SPIRV-{0}.{1} is not supported")]
165    UnsupportedVersion(u8, u8),
166    #[error("using {0} requires at least one of the capabilities {1:?}, but none are available")]
167    MissingCapabilities(&'static str, Vec<Capability>),
168    #[error("unimplemented {0}")]
169    FeatureNotImplemented(&'static str),
170    #[error("module is not validated properly: {0}")]
171    Validation(&'static str),
172    #[error("overrides should not be present at this stage")]
173    Override,
174    #[error(transparent)]
175    ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError),
176    #[error("module requires SPIRV-{0}.{1}, which isn't supported")]
177    SpirvVersionTooLow(u8, u8),
178    #[error("mapping of {0:?} is missing")]
179    MissingBinding(crate::ResourceBinding),
180}
181
182#[derive(Default)]
183struct IdGenerator(Word);
184
185impl IdGenerator {
186    fn next(&mut self) -> Word {
187        self.0 += 1;
188        self.0
189    }
190}
191
192#[derive(Debug, Clone)]
193pub struct DebugInfo<'a> {
194    pub source_code: &'a str,
195    pub file_name: &'a str,
196    pub language: SourceLanguage,
197}
198
199/// A SPIR-V block to which we are still adding instructions.
200///
201/// A `Block` represents a SPIR-V block that does not yet have a termination
202/// instruction like `OpBranch` or `OpReturn`.
203///
204/// The `OpLabel` that starts the block is implicit. It will be emitted based on
205/// `label_id` when we write the block to a `LogicalLayout`.
206///
207/// To terminate a `Block`, pass the block and the termination instruction to
208/// `Function::consume`. This takes ownership of the `Block` and transforms it
209/// into a `TerminatedBlock`.
210struct Block {
211    label_id: Word,
212    body: Vec<Instruction>,
213}
214
215/// A SPIR-V block that ends with a termination instruction.
216struct TerminatedBlock {
217    label_id: Word,
218    body: Vec<Instruction>,
219}
220
221impl Block {
222    const fn new(label_id: Word) -> Self {
223        Block {
224            label_id,
225            body: Vec::new(),
226        }
227    }
228}
229
230struct LocalVariable {
231    id: Word,
232    instruction: Instruction,
233}
234
235struct ResultMember {
236    id: Word,
237    type_id: Word,
238    built_in: Option<crate::BuiltIn>,
239}
240
241struct EntryPointContext {
242    argument_ids: Vec<Word>,
243    results: Vec<ResultMember>,
244    task_payload_variable_id: Option<Word>,
245    mesh_state: Option<MeshReturnInfo>,
246}
247
248#[derive(Default)]
249struct Function {
250    signature: Option<Instruction>,
251    parameters: Vec<FunctionArgument>,
252    variables: crate::FastHashMap<Handle<crate::LocalVariable>, LocalVariable>,
253    /// Map from a local variable that is a ray query to its u32 tracker.
254    ray_query_initialization_tracker_variables:
255        crate::FastHashMap<Handle<crate::LocalVariable>, LocalVariable>,
256    /// Map from a local variable that is a ray query to its tracker for the t max.
257    ray_query_t_max_tracker_variables:
258        crate::FastHashMap<Handle<crate::LocalVariable>, LocalVariable>,
259    /// List of local variables used as a counters to ensure that all loops are bounded.
260    force_loop_bounding_vars: Vec<LocalVariable>,
261
262    /// A map from a Naga expression to the temporary SPIR-V variable we have
263    /// spilled its value to, if any.
264    ///
265    /// Naga IR lets us apply [`Access`] expressions to expressions whose value
266    /// is an array or matrix---not a pointer to such---but SPIR-V doesn't have
267    /// instructions that can do the same. So when we encounter such code, we
268    /// spill the expression's value to a generated temporary variable. That, we
269    /// can obtain a pointer to, and then use an `OpAccessChain` instruction to
270    /// do whatever series of [`Access`] and [`AccessIndex`] operations we need
271    /// (with bounds checks). Finally, we generate an `OpLoad` to get the final
272    /// value.
273    ///
274    /// [`Access`]: crate::Expression::Access
275    /// [`AccessIndex`]: crate::Expression::AccessIndex
276    spilled_composites: crate::FastIndexMap<Handle<crate::Expression>, LocalVariable>,
277
278    /// A set of expressions that are either in [`spilled_composites`] or refer
279    /// to some component/element of such.
280    ///
281    /// [`spilled_composites`]: Function::spilled_composites
282    spilled_accesses: crate::arena::HandleSet<crate::Expression>,
283
284    /// A map taking each expression to the number of [`Access`] and
285    /// [`AccessIndex`] expressions that uses it as a base value. If an
286    /// expression has no entry, its count is zero: it is never used as a
287    /// [`Access`] or [`AccessIndex`] base.
288    ///
289    /// We use this, together with [`ExpressionInfo::ref_count`], to recognize
290    /// the tips of chains of [`Access`] and [`AccessIndex`] expressions that
291    /// access spilled values --- expressions in [`spilled_composites`]. We
292    /// defer generating code for the chain until we reach its tip, so we can
293    /// handle it with a single instruction.
294    ///
295    /// [`Access`]: crate::Expression::Access
296    /// [`AccessIndex`]: crate::Expression::AccessIndex
297    /// [`ExpressionInfo::ref_count`]: crate::valid::ExpressionInfo
298    /// [`spilled_composites`]: Function::spilled_composites
299    access_uses: crate::FastHashMap<Handle<crate::Expression>, usize>,
300
301    blocks: Vec<TerminatedBlock>,
302    entry_point_context: Option<EntryPointContext>,
303}
304
305impl Function {
306    fn consume(&mut self, mut block: Block, termination: Instruction) {
307        block.body.push(termination);
308        self.blocks.push(TerminatedBlock {
309            label_id: block.label_id,
310            body: block.body,
311        })
312    }
313
314    fn parameter_id(&self, index: u32) -> Word {
315        match self.entry_point_context {
316            Some(ref context) => context.argument_ids[index as usize],
317            None => self.parameters[index as usize]
318                .instruction
319                .result_id
320                .unwrap(),
321        }
322    }
323}
324
325/// Characteristics of a SPIR-V `OpTypeImage` type.
326///
327/// SPIR-V requires non-composite types to be unique, including images. Since we
328/// use `LocalType` for this deduplication, it's essential that `LocalImageType`
329/// be equal whenever the corresponding `OpTypeImage`s would be. To reduce the
330/// likelihood of mistakes, we use fields that correspond exactly to the
331/// operands of an `OpTypeImage` instruction, using the actual SPIR-V types
332/// where practical.
333#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
334struct LocalImageType {
335    sampled_type: crate::Scalar,
336    dim: spirv::Dim,
337    flags: ImageTypeFlags,
338    image_format: spirv::ImageFormat,
339}
340
341bitflags::bitflags! {
342    /// Flags corresponding to the boolean(-ish) parameters to OpTypeImage.
343    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
344    pub struct ImageTypeFlags: u8 {
345        const DEPTH = 0x1;
346        const ARRAYED = 0x2;
347        const MULTISAMPLED = 0x4;
348        const SAMPLED = 0x8;
349    }
350}
351
352impl LocalImageType {
353    /// Construct a `LocalImageType` from the fields of a `TypeInner::Image`.
354    fn from_inner(dim: crate::ImageDimension, arrayed: bool, class: crate::ImageClass) -> Self {
355        let make_flags = |multi: bool, other: ImageTypeFlags| -> ImageTypeFlags {
356            let mut flags = other;
357            flags.set(ImageTypeFlags::ARRAYED, arrayed);
358            flags.set(ImageTypeFlags::MULTISAMPLED, multi);
359            flags
360        };
361
362        let dim = spirv::Dim::from(dim);
363
364        match class {
365            crate::ImageClass::Sampled { kind, multi } => LocalImageType {
366                sampled_type: crate::Scalar { kind, width: 4 },
367                dim,
368                flags: make_flags(multi, ImageTypeFlags::SAMPLED),
369                image_format: spirv::ImageFormat::Unknown,
370            },
371            crate::ImageClass::Depth { multi } => LocalImageType {
372                sampled_type: crate::Scalar {
373                    kind: crate::ScalarKind::Float,
374                    width: 4,
375                },
376                dim,
377                flags: make_flags(multi, ImageTypeFlags::DEPTH | ImageTypeFlags::SAMPLED),
378                image_format: spirv::ImageFormat::Unknown,
379            },
380            crate::ImageClass::Storage { format, access: _ } => LocalImageType {
381                sampled_type: format.into(),
382                dim,
383                flags: make_flags(false, ImageTypeFlags::empty()),
384                image_format: format.into(),
385            },
386            crate::ImageClass::External => unimplemented!(),
387        }
388    }
389}
390
391/// A numeric type, for use in [`LocalType`].
392#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
393enum NumericType {
394    Scalar(crate::Scalar),
395    Vector {
396        size: crate::VectorSize,
397        scalar: crate::Scalar,
398    },
399    Matrix {
400        columns: crate::VectorSize,
401        rows: crate::VectorSize,
402        scalar: crate::Scalar,
403    },
404}
405
406impl NumericType {
407    const fn from_inner(inner: &crate::TypeInner) -> Option<Self> {
408        match *inner {
409            crate::TypeInner::Scalar(scalar) | crate::TypeInner::Atomic(scalar) => {
410                Some(NumericType::Scalar(scalar))
411            }
412            crate::TypeInner::Vector { size, scalar } => Some(NumericType::Vector { size, scalar }),
413            crate::TypeInner::Matrix {
414                columns,
415                rows,
416                scalar,
417            } => Some(NumericType::Matrix {
418                columns,
419                rows,
420                scalar,
421            }),
422            _ => None,
423        }
424    }
425
426    const fn scalar(self) -> crate::Scalar {
427        match self {
428            NumericType::Scalar(scalar)
429            | NumericType::Vector { scalar, .. }
430            | NumericType::Matrix { scalar, .. } => scalar,
431        }
432    }
433
434    const fn with_scalar(self, scalar: crate::Scalar) -> Self {
435        match self {
436            NumericType::Scalar(_) => NumericType::Scalar(scalar),
437            NumericType::Vector { size, .. } => NumericType::Vector { size, scalar },
438            NumericType::Matrix { columns, rows, .. } => NumericType::Matrix {
439                columns,
440                rows,
441                scalar,
442            },
443        }
444    }
445}
446
447/// A cooperative type, for use in [`LocalType`].
448#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
449enum CooperativeType {
450    Matrix {
451        columns: crate::CooperativeSize,
452        rows: crate::CooperativeSize,
453        scalar: crate::Scalar,
454        role: crate::CooperativeRole,
455    },
456}
457
458impl CooperativeType {
459    const fn from_inner(inner: &crate::TypeInner) -> Option<Self> {
460        match *inner {
461            crate::TypeInner::CooperativeMatrix {
462                columns,
463                rows,
464                scalar,
465                role,
466            } => Some(Self::Matrix {
467                columns,
468                rows,
469                scalar,
470                role,
471            }),
472            _ => None,
473        }
474    }
475}
476
477/// A SPIR-V type constructed during code generation.
478///
479/// This is the variant of [`LookupType`] used to represent types that might not
480/// be available in the arena. Variants are present here for one of two reasons:
481///
482/// -   They represent types synthesized during code generation, as explained
483///     in the documentation for [`LookupType`].
484///
485/// -   They represent types for which SPIR-V forbids duplicate `OpType...`
486///     instructions, requiring deduplication.
487///
488/// This is not a complete copy of [`TypeInner`]: for example, SPIR-V generation
489/// never synthesizes new struct types, so `LocalType` has nothing for that.
490///
491/// Each `LocalType` variant should be handled identically to its analogous
492/// `TypeInner` variant. You can use the [`Writer::localtype_from_inner`]
493/// function to help with this, by converting everything possible to a
494/// `LocalType` before inspecting it.
495///
496/// ## `LocalType` equality and SPIR-V `OpType` uniqueness
497///
498/// The definition of `Eq` on `LocalType` is carefully chosen to help us follow
499/// certain SPIR-V rules. SPIR-V ยง2.8 requires some classes of `OpType...`
500/// instructions to be unique; for example, you can't have two `OpTypeInt 32 1`
501/// instructions in the same module. All 32-bit signed integers must use the
502/// same type id.
503///
504/// All SPIR-V types that must be unique can be represented as a `LocalType`,
505/// and two `LocalType`s are always `Eq` if SPIR-V would require them to use the
506/// same `OpType...` instruction. This lets us avoid duplicates by recording the
507/// ids of the type instructions we've already generated in a hash table,
508/// [`Writer::lookup_type`], keyed by `LocalType`.
509///
510/// As another example, [`LocalImageType`], stored in the `LocalType::Image`
511/// variant, is designed to help us deduplicate `OpTypeImage` instructions. See
512/// its documentation for details.
513///
514/// SPIR-V does not require pointer types to be unique - but different
515/// SPIR-V ids are considered to be distinct pointer types. Since Naga
516/// uses structural type equality, we need to represent each Naga
517/// equivalence class with a single SPIR-V `OpTypePointer`.
518///
519/// As it always must, the `Hash` implementation respects the `Eq` relation.
520///
521/// [`TypeInner`]: crate::TypeInner
522#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
523enum LocalType {
524    /// A numeric type.
525    Numeric(NumericType),
526    Cooperative(CooperativeType),
527    Pointer {
528        base: Word,
529        class: spirv::StorageClass,
530    },
531    Image(LocalImageType),
532    SampledImage {
533        image_type_id: Word,
534    },
535    Sampler,
536    BindingArray {
537        base: Handle<crate::Type>,
538        size: u32,
539    },
540    AccelerationStructure,
541    RayQuery,
542}
543
544/// A type encountered during SPIR-V generation.
545///
546/// In the process of writing SPIR-V, we need to synthesize various types for
547/// intermediate results and such: pointer types, vector/matrix component types,
548/// or even booleans, which usually appear in SPIR-V code even when they're not
549/// used by the module source.
550///
551/// However, we can't use `crate::Type` or `crate::TypeInner` for these, as the
552/// type arena may not contain what we need (it only contains types used
553/// directly by other parts of the IR), and the IR module is immutable, so we
554/// can't add anything to it.
555///
556/// So for local use in the SPIR-V writer, we use this type, which holds either
557/// a handle into the arena, or a [`LocalType`] containing something synthesized
558/// locally.
559///
560/// This is very similar to the [`proc::TypeResolution`] enum, with `LocalType`
561/// playing the role of `TypeInner`. However, `LocalType` also has other
562/// properties needed for SPIR-V generation; see the description of
563/// [`LocalType`] for details.
564///
565/// [`proc::TypeResolution`]: crate::proc::TypeResolution
566#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
567enum LookupType {
568    Handle(Handle<crate::Type>),
569    Local(LocalType),
570}
571
572impl From<LocalType> for LookupType {
573    fn from(local: LocalType) -> Self {
574        Self::Local(local)
575    }
576}
577
578#[derive(Debug, PartialEq, Clone, Hash, Eq)]
579struct LookupFunctionType {
580    parameter_type_ids: Vec<Word>,
581    return_type_id: Word,
582}
583
584#[derive(Debug, PartialEq, Clone, Hash, Eq)]
585enum LookupRayQueryFunction {
586    Initialize,
587    Proceed,
588    GenerateIntersection,
589    ConfirmIntersection,
590    GetVertexPositions { committed: bool },
591    GetIntersection { committed: bool },
592    Terminate,
593}
594
595#[derive(Debug)]
596enum Dimension {
597    Scalar,
598    Vector,
599    Matrix,
600    CooperativeMatrix,
601}
602
603/// Key used to look up an operation which we have wrapped in a helper
604/// function, which should be called instead of directly emitting code
605/// for the expression. See [`Writer::wrapped_functions`].
606#[derive(Debug, Eq, PartialEq, Hash)]
607enum WrappedFunction {
608    BinaryOp {
609        op: crate::BinaryOperator,
610        left_type_id: Word,
611        right_type_id: Word,
612    },
613    ConvertFromStd140CompatType {
614        r#type: Handle<crate::Type>,
615    },
616    MatCx2GetColumn {
617        r#type: Handle<crate::Type>,
618    },
619}
620
621/// A map from evaluated [`Expression`](crate::Expression)s to their SPIR-V ids.
622///
623/// When we emit code to evaluate a given `Expression`, we record the
624/// SPIR-V id of its value here, under its `Handle<Expression>` index.
625///
626/// A `CachedExpressions` value can be indexed by a `Handle<Expression>` value.
627///
628/// [emit]: index.html#expression-evaluation-time-and-scope
629#[derive(Default)]
630struct CachedExpressions {
631    ids: HandleVec<crate::Expression, Word>,
632}
633impl CachedExpressions {
634    fn reset(&mut self, length: usize) {
635        self.ids.clear();
636        self.ids.resize(length, 0);
637    }
638}
639impl ops::Index<Handle<crate::Expression>> for CachedExpressions {
640    type Output = Word;
641    fn index(&self, h: Handle<crate::Expression>) -> &Word {
642        let id = &self.ids[h];
643        if *id == 0 {
644            unreachable!("Expression {:?} is not cached!", h);
645        }
646        id
647    }
648}
649impl ops::IndexMut<Handle<crate::Expression>> for CachedExpressions {
650    fn index_mut(&mut self, h: Handle<crate::Expression>) -> &mut Word {
651        let id = &mut self.ids[h];
652        if *id != 0 {
653            unreachable!("Expression {:?} is already cached!", h);
654        }
655        id
656    }
657}
658impl recyclable::Recyclable for CachedExpressions {
659    fn recycle(self) -> Self {
660        CachedExpressions {
661            ids: self.ids.recycle(),
662        }
663    }
664}
665
666#[derive(Eq, Hash, PartialEq)]
667enum CachedConstant {
668    Literal(crate::proc::HashableLiteral),
669    Composite {
670        ty: LookupType,
671        constituent_ids: Vec<Word>,
672    },
673    ZeroValue(Word),
674}
675
676/// The SPIR-V representation of a [`crate::GlobalVariable`].
677///
678/// In the Vulkan spec 1.3.296, the section [Descriptor Set Interface][dsi] says:
679///
680/// > Variables identified with the `Uniform` storage class are used to access
681/// > transparent buffer backed resources. Such variables *must* be:
682/// >
683/// > -   typed as `OpTypeStruct`, or an array of this type,
684/// >
685/// > -   identified with a `Block` or `BufferBlock` decoration, and
686/// >
687/// > -   laid out explicitly using the `Offset`, `ArrayStride`, and `MatrixStride`
688/// >     decorations as specified in "Offset and Stride Assignment".
689///
690/// This is followed by identical language for the `StorageBuffer`,
691/// except that a `BufferBlock` decoration is not allowed.
692///
693/// When we encounter a global variable in the [`Storage`] or [`Uniform`]
694/// address spaces whose type is not already [`Struct`], this backend implicitly
695/// wraps the global variable in a struct: we generate a SPIR-V global variable
696/// holding an `OpTypeStruct` with a single member, whose type is what the Naga
697/// global's type would suggest, decorated as required above.
698///
699/// The [`helpers::global_needs_wrapper`] function determines whether a given
700/// [`crate::GlobalVariable`] needs to be wrapped.
701///
702/// [dsi]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#interfaces-resources-descset
703/// [`Storage`]: crate::AddressSpace::Storage
704/// [`Uniform`]: crate::AddressSpace::Uniform
705/// [`Struct`]: crate::TypeInner::Struct
706#[derive(Clone)]
707struct GlobalVariable {
708    /// The SPIR-V id of the `OpVariable` that declares the global.
709    ///
710    /// If this global has been implicitly wrapped in an `OpTypeStruct`, this id
711    /// refers to the wrapper, not the original Naga value it contains. If you
712    /// need the Naga value, use [`access_id`] instead of this field.
713    ///
714    /// If this global is not implicitly wrapped, this is the same as
715    /// [`access_id`].
716    ///
717    /// This is used to compute the `access_id` pointer in function prologues,
718    /// and used for `ArrayLength` expressions, which need to pass the wrapper
719    /// struct.
720    ///
721    /// [`access_id`]: GlobalVariable::access_id
722    var_id: Word,
723
724    /// The loaded value of a `AddressSpace::Handle` global variable.
725    ///
726    /// If the current function uses this global variable, this is the id of an
727    /// `OpLoad` instruction in the function's prologue that loads its value.
728    /// (This value is assigned as we write the prologue code of each function.)
729    /// It is then used for all operations on the global, such as `OpImageSample`.
730    handle_id: Word,
731
732    /// The SPIR-V id of a pointer to this variable's Naga IR value.
733    ///
734    /// If the current function uses this global variable, and it has been
735    /// implicitly wrapped in an `OpTypeStruct`, this is the id of an
736    /// `OpAccessChain` instruction in the function's prologue that refers to
737    /// the wrapped value inside the struct. (This value is assigned as we write
738    /// the prologue code of each function.) If you need the wrapper struct
739    /// itself, use [`var_id`] instead of this field.
740    ///
741    /// If this global is not implicitly wrapped, this is the same as
742    /// [`var_id`].
743    ///
744    /// [`var_id`]: GlobalVariable::var_id
745    access_id: Word,
746}
747
748impl GlobalVariable {
749    const fn dummy() -> Self {
750        Self {
751            var_id: 0,
752            handle_id: 0,
753            access_id: 0,
754        }
755    }
756
757    const fn new(id: Word) -> Self {
758        Self {
759            var_id: id,
760            handle_id: 0,
761            access_id: 0,
762        }
763    }
764
765    /// Prepare `self` for use within a single function.
766    fn reset_for_function(&mut self) {
767        self.handle_id = 0;
768        self.access_id = 0;
769    }
770}
771
772struct FunctionArgument {
773    /// Actual instruction of the argument.
774    instruction: Instruction,
775    handle_id: Word,
776}
777
778/// Tracks the expressions for which the backend emits the following instructions:
779/// - OpConstantTrue
780/// - OpConstantFalse
781/// - OpConstant
782/// - OpConstantComposite
783/// - OpConstantNull
784struct ExpressionConstnessTracker {
785    inner: crate::arena::HandleSet<crate::Expression>,
786}
787
788impl ExpressionConstnessTracker {
789    fn from_arena(arena: &crate::Arena<crate::Expression>) -> Self {
790        let mut inner = crate::arena::HandleSet::for_arena(arena);
791        for (handle, expr) in arena.iter() {
792            let insert = match *expr {
793                crate::Expression::Literal(_)
794                | crate::Expression::ZeroValue(_)
795                | crate::Expression::Constant(_) => true,
796                crate::Expression::Compose { ref components, .. } => {
797                    components.iter().all(|&h| inner.contains(h))
798                }
799                crate::Expression::Splat { value, .. } => inner.contains(value),
800                _ => false,
801            };
802            if insert {
803                inner.insert(handle);
804            }
805        }
806        Self { inner }
807    }
808
809    fn is_const(&self, value: Handle<crate::Expression>) -> bool {
810        self.inner.contains(value)
811    }
812}
813
814/// General information needed to emit SPIR-V for Naga statements.
815struct BlockContext<'w> {
816    /// The writer handling the module to which this code belongs.
817    writer: &'w mut Writer,
818
819    /// The [`Module`](crate::Module) for which we're generating code.
820    ir_module: &'w crate::Module,
821
822    /// The [`Function`](crate::Function) for which we're generating code.
823    ir_function: &'w crate::Function,
824
825    /// Information module validation produced about
826    /// [`ir_function`](BlockContext::ir_function).
827    fun_info: &'w crate::valid::FunctionInfo,
828
829    /// The [`spv::Function`](Function) to which we are contributing SPIR-V instructions.
830    function: &'w mut Function,
831
832    /// SPIR-V ids for expressions we've evaluated.
833    cached: CachedExpressions,
834
835    /// The `Writer`'s temporary vector, for convenience.
836    temp_list: Vec<Word>,
837
838    /// Tracks the constness of `Expression`s residing in `self.ir_function.expressions`
839    expression_constness: ExpressionConstnessTracker,
840
841    force_loop_bounding: bool,
842
843    /// Hash from an expression whose type is a ray query / pointer to a ray query to its tracker.
844    /// Note: this is sparse, so can't be a handle vec
845    ray_query_tracker_expr: crate::FastHashMap<Handle<crate::Expression>, RayQueryTrackers>,
846}
847
848#[derive(Clone, Copy)]
849struct RayQueryTrackers {
850    // Initialization tracker
851    initialized_tracker: Word,
852    // Tracks the t max from ray query initialize.
853    // Unlike HLSL, spir-v's equivalent getter for the current committed t has UB (instead of just
854    // returning t_max) if there was no previous hit (though in some places it treats the behaviour as
855    // defined), therefore we must track the tmax inputted into ray query initialize.
856    t_max_tracker: Word,
857}
858
859impl BlockContext<'_> {
860    fn gen_id(&mut self) -> Word {
861        self.writer.id_gen.next()
862    }
863
864    fn get_type_id(&mut self, lookup_type: LookupType) -> Word {
865        self.writer.get_type_id(lookup_type)
866    }
867
868    fn get_handle_type_id(&mut self, handle: Handle<crate::Type>) -> Word {
869        self.writer.get_handle_type_id(handle)
870    }
871
872    fn get_expression_type_id(&mut self, tr: &TypeResolution) -> Word {
873        self.writer.get_expression_type_id(tr)
874    }
875
876    fn get_index_constant(&mut self, index: Word) -> Word {
877        self.writer.get_constant_scalar(crate::Literal::U32(index))
878    }
879
880    fn get_scope_constant(&mut self, scope: Word) -> Word {
881        self.writer
882            .get_constant_scalar(crate::Literal::I32(scope as _))
883    }
884
885    fn get_pointer_type_id(&mut self, base: Word, class: spirv::StorageClass) -> Word {
886        self.writer.get_pointer_type_id(base, class)
887    }
888
889    fn get_numeric_type_id(&mut self, numeric: NumericType) -> Word {
890        self.writer.get_numeric_type_id(numeric)
891    }
892}
893
894/// Information about a type for which we have declared a std140 layout
895/// compatible variant, because the type is used in a uniform but does not
896/// adhere to std140 requirements. The uniform will be declared using the
897/// type `type_id`, and the result of any `Load` will be immediately converted
898/// to the base type. This is used for matrices with 2 rows, as well as any
899/// arrays or structs containing such matrices.
900pub struct Std140CompatTypeInfo {
901    /// ID of the std140 compatible type declaration.
902    type_id: Word,
903    /// For structs, a mapping of Naga IR struct member indices to the indices
904    /// used in the generated SPIR-V. For non-struct types this will be empty.
905    member_indices: Vec<u32>,
906}
907
908pub struct Writer {
909    physical_layout: PhysicalLayout,
910    logical_layout: LogicalLayout,
911    id_gen: IdGenerator,
912
913    /// The set of capabilities modules are permitted to use.
914    ///
915    /// This is initialized from `Options::capabilities`.
916    capabilities_available: Option<crate::FastHashSet<Capability>>,
917
918    /// The set of capabilities used by this module.
919    ///
920    /// If `capabilities_available` is `Some`, then this is always a subset of
921    /// that.
922    capabilities_used: crate::FastIndexSet<Capability>,
923
924    /// The set of spirv extensions used.
925    extensions_used: crate::FastIndexSet<&'static str>,
926
927    debug_strings: Vec<Instruction>,
928    debugs: Vec<Instruction>,
929    annotations: Vec<Instruction>,
930    flags: WriterFlags,
931    bounds_check_policies: BoundsCheckPolicies,
932    zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode,
933    force_loop_bounding: bool,
934    use_storage_input_output_16: bool,
935    void_type: Word,
936    //TODO: convert most of these into vectors, addressable by handle indices
937    lookup_type: crate::FastHashMap<LookupType, Word>,
938    lookup_function: crate::FastHashMap<Handle<crate::Function>, Word>,
939    lookup_function_type: crate::FastHashMap<LookupFunctionType, Word>,
940    /// Operations which have been wrapped in a helper function. The value is
941    /// the ID of the function, which should be called instead of emitting code
942    /// for the operation directly.
943    wrapped_functions: crate::FastHashMap<WrappedFunction, Word>,
944    /// Indexed by const-expression handle indexes
945    constant_ids: HandleVec<crate::Expression, Word>,
946    cached_constants: crate::FastHashMap<CachedConstant, Word>,
947    global_variables: HandleVec<crate::GlobalVariable, GlobalVariable>,
948    std140_compat_uniform_types: crate::FastHashMap<Handle<crate::Type>, Std140CompatTypeInfo>,
949    fake_missing_bindings: bool,
950    binding_map: BindingMap,
951
952    // Cached expressions are only meaningful within a BlockContext, but we
953    // retain the table here between functions to save heap allocations.
954    saved_cached: CachedExpressions,
955
956    gl450_ext_inst_id: Word,
957
958    // Just a temporary list of SPIR-V ids
959    temp_list: Vec<Word>,
960
961    ray_query_functions: crate::FastHashMap<LookupRayQueryFunction, Word>,
962
963    /// F16 I/O polyfill manager for handling `f16` input/output variables
964    /// when `StorageInputOutput16` capability is not available.
965    io_f16_polyfills: f16_polyfill::F16IoPolyfill,
966
967    /// Non semantic debug printf extension `OpExtInstImport`
968    debug_printf: Option<Word>,
969    pub(crate) ray_query_initialization_tracking: bool,
970}
971
972bitflags::bitflags! {
973    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
974    pub struct WriterFlags: u32 {
975        /// Include debug labels for everything.
976        const DEBUG = 0x1;
977
978        /// Flip Y coordinate of [`BuiltIn::Position`] output.
979        ///
980        /// [`BuiltIn::Position`]: crate::BuiltIn::Position
981        const ADJUST_COORDINATE_SPACE = 0x2;
982
983        /// Emit [`OpName`][op] for input/output locations.
984        ///
985        /// Contrary to spec, some drivers treat it as semantic, not allowing
986        /// any conflicts.
987        ///
988        /// [op]: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpName
989        const LABEL_VARYINGS = 0x4;
990
991        /// Emit [`PointSize`] output builtin to vertex shaders, which is
992        /// required for drawing with `PointList` topology.
993        ///
994        /// [`PointSize`]: crate::BuiltIn::PointSize
995        const FORCE_POINT_SIZE = 0x8;
996
997        /// Clamp [`BuiltIn::FragDepth`] output between 0 and 1.
998        ///
999        /// [`BuiltIn::FragDepth`]: crate::BuiltIn::FragDepth
1000        const CLAMP_FRAG_DEPTH = 0x10;
1001
1002        /// Instead of silently failing if the arguments to generate a ray query are
1003        /// invalid, uses debug printf extension to print to the command line
1004        ///
1005        /// Note: VK_KHR_shader_non_semantic_info must be enabled. This will have no
1006        /// effect if `options.ray_query_initialization_tracking` is set to false.
1007        const PRINT_ON_RAY_QUERY_INITIALIZATION_FAIL = 0x20;
1008    }
1009}
1010
1011#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
1012#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
1013#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
1014pub struct BindingInfo {
1015    pub descriptor_set: u32,
1016    pub binding: u32,
1017    /// If the binding is an unsized binding array, this overrides the size.
1018    pub binding_array_size: Option<u32>,
1019}
1020
1021// Using `BTreeMap` instead of `HashMap` so that we can hash itself.
1022pub type BindingMap = alloc::collections::BTreeMap<crate::ResourceBinding, BindingInfo>;
1023
1024#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1025pub enum ZeroInitializeWorkgroupMemoryMode {
1026    /// Via `VK_KHR_zero_initialize_workgroup_memory` or Vulkan 1.3
1027    Native,
1028    /// Via assignments + barrier
1029    Polyfill,
1030    None,
1031}
1032
1033#[derive(Debug, Clone)]
1034pub struct Options<'a> {
1035    /// (Major, Minor) target version of the SPIR-V.
1036    pub lang_version: (u8, u8),
1037
1038    /// Configuration flags for the writer.
1039    pub flags: WriterFlags,
1040
1041    /// Don't panic on missing bindings. Instead use fake values for `Binding`
1042    /// and `DescriptorSet` decorations. This may result in invalid SPIR-V.
1043    pub fake_missing_bindings: bool,
1044
1045    /// Map of resources to information about the binding.
1046    pub binding_map: BindingMap,
1047
1048    /// If given, the set of capabilities modules are allowed to use. Code that
1049    /// requires capabilities beyond these is rejected with an error.
1050    ///
1051    /// If this is `None`, all capabilities are permitted.
1052    pub capabilities: Option<crate::FastHashSet<Capability>>,
1053
1054    /// How should generate code handle array, vector, matrix, or image texel
1055    /// indices that are out of range?
1056    pub bounds_check_policies: BoundsCheckPolicies,
1057
1058    /// Dictates the way workgroup variables should be zero initialized
1059    pub zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode,
1060
1061    /// If set, loops will have code injected into them, forcing the compiler
1062    /// to think the number of iterations is bounded.
1063    pub force_loop_bounding: bool,
1064
1065    /// if set, ray queries will get a variable to track their state to prevent
1066    /// misuse.
1067    pub ray_query_initialization_tracking: bool,
1068
1069    /// Whether to use the `StorageInputOutput16` capability for `f16` shader I/O.
1070    /// When false, `f16` I/O is polyfilled using `f32` types with conversions.
1071    pub use_storage_input_output_16: bool,
1072
1073    pub debug_info: Option<DebugInfo<'a>>,
1074}
1075
1076impl Default for Options<'_> {
1077    fn default() -> Self {
1078        let mut flags = WriterFlags::ADJUST_COORDINATE_SPACE
1079            | WriterFlags::LABEL_VARYINGS
1080            | WriterFlags::CLAMP_FRAG_DEPTH;
1081        if cfg!(debug_assertions) {
1082            flags |= WriterFlags::DEBUG;
1083        }
1084        Options {
1085            lang_version: (1, 0),
1086            flags,
1087            fake_missing_bindings: true,
1088            binding_map: BindingMap::default(),
1089            capabilities: None,
1090            bounds_check_policies: BoundsCheckPolicies::default(),
1091            zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode::Polyfill,
1092            force_loop_bounding: true,
1093            ray_query_initialization_tracking: true,
1094            use_storage_input_output_16: true,
1095            debug_info: None,
1096        }
1097    }
1098}
1099
1100// A subset of options meant to be changed per pipeline.
1101#[derive(Debug, Clone)]
1102#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
1103#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
1104pub struct PipelineOptions {
1105    /// The stage of the entry point.
1106    pub shader_stage: crate::ShaderStage,
1107    /// The name of the entry point.
1108    ///
1109    /// If no entry point that matches is found while creating a [`Writer`], a error will be thrown.
1110    pub entry_point: String,
1111}
1112
1113pub fn write_vec(
1114    module: &crate::Module,
1115    info: &crate::valid::ModuleInfo,
1116    options: &Options,
1117    pipeline_options: Option<&PipelineOptions>,
1118) -> Result<Vec<u32>, Error> {
1119    let mut words: Vec<u32> = Vec::new();
1120    let mut w = Writer::new(options)?;
1121
1122    w.write(
1123        module,
1124        info,
1125        pipeline_options,
1126        &options.debug_info,
1127        &mut words,
1128    )?;
1129    Ok(words)
1130}