1 /*! Standard Portable Intermediate Representation (SPIR-V) backend
2 !*/
3 
4 mod block;
5 mod helpers;
6 mod image;
7 mod index;
8 mod instructions;
9 mod layout;
10 mod recyclable;
11 mod selection;
12 mod writer;
13 
14 pub use spirv::Capability;
15 
16 use crate::arena::Handle;
17 use crate::proc::{BoundsCheckPolicies, TypeResolution};
18 
19 use spirv::Word;
20 use std::ops;
21 use thiserror::Error;
22 
23 #[derive(Clone)]
24 struct PhysicalLayout {
25     magic_number: Word,
26     version: Word,
27     generator: Word,
28     bound: Word,
29     instruction_schema: Word,
30 }
31 
32 #[derive(Default)]
33 struct LogicalLayout {
34     capabilities: Vec<Word>,
35     extensions: Vec<Word>,
36     ext_inst_imports: Vec<Word>,
37     memory_model: Vec<Word>,
38     entry_points: Vec<Word>,
39     execution_modes: Vec<Word>,
40     debugs: Vec<Word>,
41     annotations: Vec<Word>,
42     declarations: Vec<Word>,
43     function_declarations: Vec<Word>,
44     function_definitions: Vec<Word>,
45 }
46 
47 struct Instruction {
48     op: spirv::Op,
49     wc: u32,
50     type_id: Option<Word>,
51     result_id: Option<Word>,
52     operands: Vec<Word>,
53 }
54 
55 const BITS_PER_BYTE: crate::Bytes = 8;
56 
57 #[derive(Clone, Debug, Error)]
58 pub enum Error {
59     #[error("The requested entry point couldn't be found")]
60     EntryPointNotFound,
61     #[error("target SPIRV-{0}.{1} is not supported")]
62     UnsupportedVersion(u8, u8),
63     #[error("using {0} requires at least one of the capabilities {1:?}, but none are available")]
64     MissingCapabilities(&'static str, Vec<Capability>),
65     #[error("unimplemented {0}")]
66     FeatureNotImplemented(&'static str),
67     #[error("module is not validated properly: {0}")]
68     Validation(&'static str),
69     #[error(transparent)]
70     Proc(#[from] crate::proc::ProcError),
71 }
72 
73 #[derive(Default)]
74 struct IdGenerator(Word);
75 
76 impl IdGenerator {
next(&mut self) -> Word77     fn next(&mut self) -> Word {
78         self.0 += 1;
79         self.0
80     }
81 }
82 
83 /// A SPIR-V block to which we are still adding instructions.
84 ///
85 /// A `Block` represents a SPIR-V block that does not yet have a termination
86 /// instruction like `OpBranch` or `OpReturn`.
87 ///
88 /// The `OpLabel` that starts the block is implicit. It will be emitted based on
89 /// `label_id` when we write the block to a `LogicalLayout`.
90 ///
91 /// To terminate a `Block`, pass the block and the termination instruction to
92 /// `Function::consume`. This takes ownership of the `Block` and transforms it
93 /// into a `TerminatedBlock`.
94 struct Block {
95     label_id: Word,
96     body: Vec<Instruction>,
97 }
98 
99 /// A SPIR-V block that ends with a termination instruction.
100 struct TerminatedBlock {
101     label_id: Word,
102     body: Vec<Instruction>,
103 }
104 
105 impl Block {
new(label_id: Word) -> Self106     fn new(label_id: Word) -> Self {
107         Block {
108             label_id,
109             body: Vec::new(),
110         }
111     }
112 }
113 
114 struct LocalVariable {
115     id: Word,
116     instruction: Instruction,
117 }
118 
119 struct ResultMember {
120     id: Word,
121     type_id: Word,
122     built_in: Option<crate::BuiltIn>,
123 }
124 
125 struct EntryPointContext {
126     argument_ids: Vec<Word>,
127     results: Vec<ResultMember>,
128 }
129 
130 #[derive(Default)]
131 struct Function {
132     signature: Option<Instruction>,
133     parameters: Vec<FunctionArgument>,
134     variables: crate::FastHashMap<Handle<crate::LocalVariable>, LocalVariable>,
135     blocks: Vec<TerminatedBlock>,
136     entry_point_context: Option<EntryPointContext>,
137 }
138 
139 impl Function {
consume(&mut self, mut block: Block, termination: Instruction)140     fn consume(&mut self, mut block: Block, termination: Instruction) {
141         block.body.push(termination);
142         self.blocks.push(TerminatedBlock {
143             label_id: block.label_id,
144             body: block.body,
145         })
146     }
147 
parameter_id(&self, index: u32) -> Word148     fn parameter_id(&self, index: u32) -> Word {
149         match self.entry_point_context {
150             Some(ref context) => context.argument_ids[index as usize],
151             None => self.parameters[index as usize]
152                 .instruction
153                 .result_id
154                 .unwrap(),
155         }
156     }
157 }
158 
159 /// Characteristics of a SPIR-V `OpTypeImage` type.
160 ///
161 /// SPIR-V requires non-composite types to be unique, including images. Since we
162 /// use `LocalType` for this deduplication, it's essential that `LocalImageType`
163 /// be equal whenever the corresponding `OpTypeImage`s would be. To reduce the
164 /// likelihood of mistakes, we use fields that correspond exactly to the
165 /// operands of an `OpTypeImage` instruction, using the actual SPIR-V types
166 /// where practical.
167 #[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
168 struct LocalImageType {
169     sampled_type: crate::ScalarKind,
170     dim: spirv::Dim,
171     flags: ImageTypeFlags,
172     image_format: spirv::ImageFormat,
173 }
174 
175 bitflags::bitflags! {
176     /// Flags corresponding to the boolean(-ish) parameters to OpTypeImage.
177     pub struct ImageTypeFlags: u8 {
178         const DEPTH = 0x1;
179         const ARRAYED = 0x2;
180         const MULTISAMPLED = 0x4;
181         const SAMPLED = 0x8;
182     }
183 }
184 
185 impl LocalImageType {
186     /// Construct a `LocalImageType` from the fields of a `TypeInner::Image`.
from_inner(dim: crate::ImageDimension, arrayed: bool, class: crate::ImageClass) -> Self187     fn from_inner(dim: crate::ImageDimension, arrayed: bool, class: crate::ImageClass) -> Self {
188         let make_flags = |multi: bool, other: ImageTypeFlags| -> ImageTypeFlags {
189             let mut flags = other;
190             flags.set(ImageTypeFlags::ARRAYED, arrayed);
191             flags.set(ImageTypeFlags::MULTISAMPLED, multi);
192             flags
193         };
194 
195         let dim = spirv::Dim::from(dim);
196 
197         match class {
198             crate::ImageClass::Sampled { kind, multi } => LocalImageType {
199                 sampled_type: kind,
200                 dim,
201                 flags: make_flags(multi, ImageTypeFlags::SAMPLED),
202                 image_format: spirv::ImageFormat::Unknown,
203             },
204             crate::ImageClass::Depth { multi } => LocalImageType {
205                 sampled_type: crate::ScalarKind::Float,
206                 dim,
207                 flags: make_flags(multi, ImageTypeFlags::DEPTH | ImageTypeFlags::SAMPLED),
208                 image_format: spirv::ImageFormat::Unknown,
209             },
210             crate::ImageClass::Storage { format, access: _ } => LocalImageType {
211                 sampled_type: crate::ScalarKind::from(format),
212                 dim,
213                 flags: make_flags(false, ImageTypeFlags::empty()),
214                 image_format: format.into(),
215             },
216         }
217     }
218 }
219 
220 /// A SPIR-V type constructed during code generation.
221 ///
222 /// This is the variant of [`LookupType`] used to represent types that might not
223 /// be available in the arena. Variants are present here for one of two reasons:
224 ///
225 /// -   They represent types synthesized during code generation, as explained
226 ///     in the documentation for [`LookupType`].
227 ///
228 /// -   They represent types for which SPIR-V forbids duplicate `OpType...`
229 ///     instructions, requiring deduplication.
230 ///
231 /// This is not a complete copy of [`TypeInner`]: for example, SPIR-V generation
232 /// never synthesizes new struct types, so `LocalType` has nothing for that.
233 ///
234 /// Each `LocalType` variant should be handled identically to its analogous
235 /// `TypeInner` variant. You can use the [`make_local`] function to help with
236 /// this, by converting everything possible to a `LocalType` before inspecting
237 /// it.
238 ///
239 /// ## `Localtype` equality and SPIR-V `OpType` uniqueness
240 ///
241 /// The definition of `Eq` on `LocalType` is carefully chosen to help us follow
242 /// certain SPIR-V rules. SPIR-V §2.8 requires some classes of `OpType...`
243 /// instructions to be unique; for example, you can't have two `OpTypeInt 32 1`
244 /// instructions in the same module. All 32-bit signed integers must use the
245 /// same type id.
246 ///
247 /// All SPIR-V types that must be unique can be represented as a `LocalType`,
248 /// and two `LocalType`s are always `Eq` if SPIR-V would require them to use the
249 /// same `OpType...` instruction. This lets us avoid duplicates by recording the
250 /// ids of the type instructions we've already generated in a hash table,
251 /// [`Writer::lookup_type`], keyed by `LocalType`.
252 ///
253 /// As another example, [`LocalImageType`], stored in the `LocalType::Image`
254 /// variant, is designed to help us deduplicate `OpTypeImage` instructions. See
255 /// its documentation for details.
256 ///
257 /// `LocalType` also includes variants like `Pointer` that do not need to be
258 /// unique - but it is harmless to avoid the duplication.
259 ///
260 /// As it always must, the `Hash` implementation respects the `Eq` relation.
261 ///
262 /// [`TypeInner`]: crate::TypeInner
263 #[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
264 enum LocalType {
265     /// A scalar, vector, or pointer to one of those.
266     Value {
267         /// If `None`, this represents a scalar type. If `Some`, this represents
268         /// a vector type of the given size.
269         vector_size: Option<crate::VectorSize>,
270         kind: crate::ScalarKind,
271         width: crate::Bytes,
272         pointer_class: Option<spirv::StorageClass>,
273     },
274     /// A matrix of floating-point values.
275     Matrix {
276         columns: crate::VectorSize,
277         rows: crate::VectorSize,
278         width: crate::Bytes,
279     },
280     Pointer {
281         base: Handle<crate::Type>,
282         class: spirv::StorageClass,
283     },
284     Image(LocalImageType),
285     SampledImage {
286         image_type_id: Word,
287     },
288     Sampler,
289 }
290 
291 /// A type encountered during SPIR-V generation.
292 ///
293 /// In the process of writing SPIR-V, we need to synthesize various types for
294 /// intermediate results and such: pointer types, vector/matrix component types,
295 /// or even booleans, which usually appear in SPIR-V code even when they're not
296 /// used by the module source.
297 ///
298 /// However, we can't use `crate::Type` or `crate::TypeInner` for these, as the
299 /// type arena may not contain what we need (it only contains types used
300 /// directly by other parts of the IR), and the IR module is immutable, so we
301 /// can't add anything to it.
302 ///
303 /// So for local use in the SPIR-V writer, we use this type, which holds either
304 /// a handle into the arena, or a [`LocalType`] containing something synthesized
305 /// locally.
306 ///
307 /// This is very similar to the [`proc::TypeResolution`] enum, with `LocalType`
308 /// playing the role of `TypeInner`. However, `LocalType` also has other
309 /// properties needed for SPIR-V generation; see the description of
310 /// [`LocalType`] for details.
311 ///
312 /// [`proc::TypeResolution`]: crate::proc::TypeResolution
313 #[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
314 enum LookupType {
315     Handle(Handle<crate::Type>),
316     Local(LocalType),
317 }
318 
319 impl From<LocalType> for LookupType {
from(local: LocalType) -> Self320     fn from(local: LocalType) -> Self {
321         Self::Local(local)
322     }
323 }
324 
325 #[derive(Debug, PartialEq, Clone, Hash, Eq)]
326 struct LookupFunctionType {
327     parameter_type_ids: Vec<Word>,
328     return_type_id: Word,
329 }
330 
make_local(inner: &crate::TypeInner) -> Option<LocalType>331 fn make_local(inner: &crate::TypeInner) -> Option<LocalType> {
332     Some(match *inner {
333         crate::TypeInner::Scalar { kind, width } | crate::TypeInner::Atomic { kind, width } => {
334             LocalType::Value {
335                 vector_size: None,
336                 kind,
337                 width,
338                 pointer_class: None,
339             }
340         }
341         crate::TypeInner::Vector { size, kind, width } => LocalType::Value {
342             vector_size: Some(size),
343             kind,
344             width,
345             pointer_class: None,
346         },
347         crate::TypeInner::Matrix {
348             columns,
349             rows,
350             width,
351         } => LocalType::Matrix {
352             columns,
353             rows,
354             width,
355         },
356         crate::TypeInner::Pointer { base, class } => LocalType::Pointer {
357             base,
358             class: helpers::map_storage_class(class),
359         },
360         crate::TypeInner::ValuePointer {
361             size,
362             kind,
363             width,
364             class,
365         } => LocalType::Value {
366             vector_size: size,
367             kind,
368             width,
369             pointer_class: Some(helpers::map_storage_class(class)),
370         },
371         crate::TypeInner::Image {
372             dim,
373             arrayed,
374             class,
375         } => LocalType::Image(LocalImageType::from_inner(dim, arrayed, class)),
376         crate::TypeInner::Sampler { comparison: _ } => LocalType::Sampler,
377         _ => return None,
378     })
379 }
380 
381 #[derive(Debug)]
382 enum Dimension {
383     Scalar,
384     Vector,
385     Matrix,
386 }
387 
388 /// A map from evaluated [`Expression`](crate::Expression)s to their SPIR-V ids.
389 ///
390 /// When we emit code to evaluate a given `Expression`, we record the
391 /// SPIR-V id of its value here, under its `Handle<Expression>` index.
392 ///
393 /// A `CachedExpressions` value can be indexed by a `Handle<Expression>` value.
394 ///
395 /// [emit]: index.html#expression-evaluation-time-and-scope
396 #[derive(Default)]
397 struct CachedExpressions {
398     ids: Vec<Word>,
399 }
400 impl CachedExpressions {
reset(&mut self, length: usize)401     fn reset(&mut self, length: usize) {
402         self.ids.clear();
403         self.ids.resize(length, 0);
404     }
405 }
406 impl ops::Index<Handle<crate::Expression>> for CachedExpressions {
407     type Output = Word;
index(&self, h: Handle<crate::Expression>) -> &Word408     fn index(&self, h: Handle<crate::Expression>) -> &Word {
409         let id = &self.ids[h.index()];
410         if *id == 0 {
411             unreachable!("Expression {:?} is not cached!", h);
412         }
413         id
414     }
415 }
416 impl ops::IndexMut<Handle<crate::Expression>> for CachedExpressions {
index_mut(&mut self, h: Handle<crate::Expression>) -> &mut Word417     fn index_mut(&mut self, h: Handle<crate::Expression>) -> &mut Word {
418         let id = &mut self.ids[h.index()];
419         if *id != 0 {
420             unreachable!("Expression {:?} is already cached!", h);
421         }
422         id
423     }
424 }
425 impl recyclable::Recyclable for CachedExpressions {
recycle(self) -> Self426     fn recycle(self) -> Self {
427         CachedExpressions {
428             ids: self.ids.recycle(),
429         }
430     }
431 }
432 
433 #[derive(Clone)]
434 struct GlobalVariable {
435     /// ID of the variable. Not really used.
436     var_id: Word,
437     /// For `StorageClass::Handle` variables, this ID is recorded in the function
438     /// prelude block (and reset before every function) as `OpLoad` of the variable.
439     /// It is then used for all the global ops, such as `OpImageSample`.
440     handle_id: Word,
441     /// Actual ID used to access this variable.
442     /// For wrapped buffer variables, this ID is `OpAccessChain` into the
443     /// wrapper. Otherwise, the same as `var_id`.
444     ///
445     /// Vulkan requires that globals in the `StorageBuffer` and `Uniform` storage
446     /// classes must be structs with the `Block` decoration, but WGSL and Naga IR
447     /// make no such requirement. So for such variables, we generate a wrapper struct
448     /// type with a single element of the type given by Naga, generate an
449     /// `OpAccessChain` for that member in the function prelude, and use that pointer
450     /// to refer to the global in the function body. This is the id of that access,
451     /// updated for each function in `write_function`.
452     access_id: Word,
453 }
454 
455 impl GlobalVariable {
dummy() -> Self456     fn dummy() -> Self {
457         Self {
458             var_id: 0,
459             handle_id: 0,
460             access_id: 0,
461         }
462     }
463 
new(id: Word) -> Self464     fn new(id: Word) -> Self {
465         Self {
466             var_id: id,
467             handle_id: 0,
468             access_id: 0,
469         }
470     }
471 
472     /// Prepare `self` for use within a single function.
reset_for_function(&mut self)473     fn reset_for_function(&mut self) {
474         self.handle_id = 0;
475         self.access_id = 0;
476     }
477 }
478 
479 struct FunctionArgument {
480     /// Actual instruction of the argument.
481     instruction: Instruction,
482     handle_id: Word,
483 }
484 
485 /// General information needed to emit SPIR-V for Naga statements.
486 struct BlockContext<'w> {
487     /// The writer handling the module to which this code belongs.
488     writer: &'w mut Writer,
489 
490     /// The [`Module`](crate::Module) for which we're generating code.
491     ir_module: &'w crate::Module,
492 
493     /// The [`Function`](crate::Function) for which we're generating code.
494     ir_function: &'w crate::Function,
495 
496     /// Information module validation produced about
497     /// [`ir_function`](BlockContext::ir_function).
498     fun_info: &'w crate::valid::FunctionInfo,
499 
500     /// The [`spv::Function`](Function) to which we are contributing SPIR-V instructions.
501     function: &'w mut Function,
502 
503     /// SPIR-V ids for expressions we've evaluated.
504     cached: CachedExpressions,
505 
506     /// The `Writer`'s temporary vector, for convenience.
507     temp_list: Vec<Word>,
508 }
509 
510 impl BlockContext<'_> {
gen_id(&mut self) -> Word511     fn gen_id(&mut self) -> Word {
512         self.writer.id_gen.next()
513     }
514 
get_type_id(&mut self, lookup_type: LookupType) -> Word515     fn get_type_id(&mut self, lookup_type: LookupType) -> Word {
516         self.writer.get_type_id(lookup_type)
517     }
518 
get_expression_type_id(&mut self, tr: &TypeResolution) -> Word519     fn get_expression_type_id(&mut self, tr: &TypeResolution) -> Word {
520         self.writer.get_expression_type_id(tr)
521     }
522 
get_index_constant(&mut self, index: Word) -> Word523     fn get_index_constant(&mut self, index: Word) -> Word {
524         self.writer
525             .get_constant_scalar(crate::ScalarValue::Uint(index as _), 4)
526     }
527 
get_scope_constant(&mut self, scope: Word) -> Word528     fn get_scope_constant(&mut self, scope: Word) -> Word {
529         self.writer
530             .get_constant_scalar(crate::ScalarValue::Sint(scope as _), 4)
531     }
532 }
533 
534 #[derive(Clone, Copy, Default)]
535 struct LoopContext {
536     continuing_id: Option<Word>,
537     break_id: Option<Word>,
538 }
539 
540 pub struct Writer {
541     physical_layout: PhysicalLayout,
542     logical_layout: LogicalLayout,
543     id_gen: IdGenerator,
544 
545     /// The set of capabilities modules are permitted to use.
546     ///
547     /// This is initialized from `Options::capabilities`.
548     capabilities_available: Option<crate::FastHashSet<Capability>>,
549 
550     /// The set of capabilities used by this module.
551     ///
552     /// If `capabilities_available` is `Some`, then this is always a subset of
553     /// that.
554     capabilities_used: crate::FastHashSet<Capability>,
555 
556     debugs: Vec<Instruction>,
557     annotations: Vec<Instruction>,
558     flags: WriterFlags,
559     bounds_check_policies: BoundsCheckPolicies,
560     void_type: Word,
561     //TODO: convert most of these into vectors, addressable by handle indices
562     lookup_type: crate::FastHashMap<LookupType, Word>,
563     lookup_function: crate::FastHashMap<Handle<crate::Function>, Word>,
564     lookup_function_type: crate::FastHashMap<LookupFunctionType, Word>,
565     constant_ids: Vec<Word>,
566     cached_constants: crate::FastHashMap<(crate::ScalarValue, crate::Bytes), Word>,
567     global_variables: Vec<GlobalVariable>,
568 
569     // Cached expressions are only meaningful within a BlockContext, but we
570     // retain the table here between functions to save heap allocations.
571     saved_cached: CachedExpressions,
572 
573     gl450_ext_inst_id: Word,
574     // Just a temporary list of SPIR-V ids
575     temp_list: Vec<Word>,
576 }
577 
578 bitflags::bitflags! {
579     pub struct WriterFlags: u32 {
580         /// Include debug labels for everything.
581         const DEBUG = 0x1;
582         /// Flip Y coordinate of `BuiltIn::Position` output.
583         const ADJUST_COORDINATE_SPACE = 0x2;
584         /// Emit `OpName` for input/output locations.
585         /// Contrary to spec, some drivers treat it as semantic, not allowing
586         /// any conflicts.
587         const LABEL_VARYINGS = 0x4;
588         /// Emit `PointSize` output builtin to vertex shaders, which is
589         /// required for drawing with `PointList` topology.
590         const FORCE_POINT_SIZE = 0x8;
591         /// Clamp `BuiltIn::FragDepth` output between 0 and 1.
592         const CLAMP_FRAG_DEPTH = 0x10;
593     }
594 }
595 
596 #[derive(Debug, Clone)]
597 pub struct Options {
598     /// (Major, Minor) target version of the SPIR-V.
599     pub lang_version: (u8, u8),
600 
601     /// Configuration flags for the writer.
602     pub flags: WriterFlags,
603 
604     /// If given, the set of capabilities modules are allowed to use. Code that
605     /// requires capabilities beyond these is rejected with an error.
606     ///
607     /// If this is `None`, all capabilities are permitted.
608     pub capabilities: Option<crate::FastHashSet<Capability>>,
609 
610     /// How should generate code handle array, vector, matrix, or image texel
611     /// indices that are out of range?
612     pub bounds_check_policies: BoundsCheckPolicies,
613 }
614 
615 impl Default for Options {
default() -> Self616     fn default() -> Self {
617         let mut flags = WriterFlags::ADJUST_COORDINATE_SPACE
618             | WriterFlags::LABEL_VARYINGS
619             | WriterFlags::CLAMP_FRAG_DEPTH;
620         if cfg!(debug_assertions) {
621             flags |= WriterFlags::DEBUG;
622         }
623         Options {
624             lang_version: (1, 0),
625             flags,
626             capabilities: None,
627             bounds_check_policies: crate::proc::BoundsCheckPolicies::default(),
628         }
629     }
630 }
631 
632 // A subset of options that are meant to be changed per pipeline.
633 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
634 #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
635 #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
636 pub struct PipelineOptions {
637     /// The stage of the entry point
638     pub shader_stage: crate::ShaderStage,
639     /// The name of the entry point
640     ///
641     /// If no entry point that matches is found a error will be thrown while creating a new instance
642     /// of [`Writer`](struct.Writer.html)
643     pub entry_point: String,
644 }
645 
write_vec( module: &crate::Module, info: &crate::valid::ModuleInfo, options: &Options, pipeline_options: Option<&PipelineOptions>, ) -> Result<Vec<u32>, Error>646 pub fn write_vec(
647     module: &crate::Module,
648     info: &crate::valid::ModuleInfo,
649     options: &Options,
650     pipeline_options: Option<&PipelineOptions>,
651 ) -> Result<Vec<u32>, Error> {
652     let mut words = Vec::new();
653     let mut w = Writer::new(options)?;
654     w.write(module, info, pipeline_options, &mut words)?;
655     Ok(words)
656 }
657