Skip to main content

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