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