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}