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