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