naga/back/spv/
mod.rs

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