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