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