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