diff --git a/.gitignore b/.gitignore index fe42cc56de..743f7ed7ca 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ **/*.rs.bk Cargo.lock .DS_Store +.idea diff --git a/README.md b/README.md index cd547c5d80..a2a88688dd 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Rust | | | Back-end | Status | Notes | --------------- | ------------------ | ----- | -SPIR-V (binary) | | | +SPIR-V (binary) | :construction: | | WGSL | | | Metal | :construction: | | HLSL | | | diff --git a/examples/convert.rs b/examples/convert.rs index ba6e6a4a1d..b41e0127bb 100644 --- a/examples/convert.rs +++ b/examples/convert.rs @@ -30,7 +30,7 @@ fn main() { return; } else if args[1].ends_with(".spv") { let input = fs::read(&args[1]).unwrap(); - naga::front::spirv::parse_u8_slice(&input).unwrap() + naga::front::spv::parse_u8_slice(&input).unwrap() } else if args[1].ends_with(".wgsl") { let input = fs::read_to_string(&args[1]).unwrap(); naga::front::wgsl::parse_str(&input).unwrap() @@ -71,6 +71,27 @@ fn main() { }; let msl = msl::write_string(&module, options).unwrap(); fs::write(&args[2], msl).unwrap(); + } else if args[2].ends_with(".spv") { + use naga::back::spv; + + let debug_flag = args.get(3).map_or(spv::WriterFlags::DEBUG, |arg| { + if arg.parse().unwrap() { + spv::WriterFlags::DEBUG + } else { + spv::WriterFlags::NONE + } + }); + + let spv = spv::writer::Writer::new(&module.header, debug_flag).write(&module); + + let bytes = spv + .iter() + .fold(Vec::with_capacity(spv.len() * 4), |mut v, w| { + v.extend_from_slice(&w.to_le_bytes()); + v + }); + + fs::write(&args[2], bytes.as_slice()).unwrap(); } else { panic!("Unknown output: {:?}", args[2]); } diff --git a/src/arena.rs b/src/arena.rs index c790ec553f..d6d8dc511c 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -2,7 +2,7 @@ use std::{fmt, hash, marker::PhantomData, num::NonZeroU32}; /// An unique index in the arena array that a handle points to. /// -/// This type is independent of `spirv::Word`. `spirv::Word` is used in data +/// This type is independent of `spv::Word`. `spv::Word` is used in data /// representation. It holds a SPIR-V and refers to that instruction. In /// structured representation, we use Handle to refer to an SPIR-V instruction. /// `Index` is an implementation detail to `Handle`. diff --git a/src/back/mod.rs b/src/back/mod.rs index 4a194befe0..e2b4919f66 100644 --- a/src/back/mod.rs +++ b/src/back/mod.rs @@ -1 +1,2 @@ pub mod msl; +pub mod spv; diff --git a/src/back/spv/layout.rs b/src/back/spv/layout.rs new file mode 100644 index 0000000000..40e3e47e9e --- /dev/null +++ b/src/back/spv/layout.rs @@ -0,0 +1,85 @@ +use crate::back::spv::{Instruction, LogicalLayout, PhysicalLayout}; +use spirv::*; +use std::iter; + +impl PhysicalLayout { + pub(crate) fn new(header: &crate::Header) -> Self { + let version: Word = ((header.version.0 as u32) << 16) + | ((header.version.1 as u32) << 8) + | header.version.2 as u32; + + PhysicalLayout { + magic_number: MAGIC_NUMBER, + version, + generator: header.generator, + bound: 0, + instruction_schema: 0x0u32, + } + } + + pub(crate) fn in_words(&self, sink: &mut impl Extend) { + sink.extend(iter::once(self.magic_number)); + sink.extend(iter::once(self.version)); + sink.extend(iter::once(self.generator)); + sink.extend(iter::once(self.bound)); + sink.extend(iter::once(self.instruction_schema)); + } +} + +impl LogicalLayout { + pub(crate) fn in_words(&self, sink: &mut impl Extend) { + sink.extend(self.capabilities.iter().cloned()); + sink.extend(self.extensions.iter().cloned()); + sink.extend(self.ext_inst_imports.iter().cloned()); + sink.extend(self.memory_model.iter().cloned()); + sink.extend(self.entry_points.iter().cloned()); + sink.extend(self.execution_modes.iter().cloned()); + sink.extend(self.debugs.iter().cloned()); + sink.extend(self.annotations.iter().cloned()); + sink.extend(self.declarations.iter().cloned()); + sink.extend(self.function_declarations.iter().cloned()); + sink.extend(self.function_definitions.iter().cloned()); + } +} + +impl Instruction { + pub(crate) fn new(op: Op) -> Self { + Instruction { + op, + wc: 1, // Always start at 1 for the first word (OP + WC), + type_id: None, + result_id: None, + operands: vec![], + } + } + + pub(crate) fn set_type(&mut self, id: Word) { + assert!(self.type_id.is_none(), "Type can only be set once"); + self.type_id = Some(id); + self.wc += 1; + } + + pub(crate) fn set_result(&mut self, id: Word) { + assert!(self.result_id.is_none(), "Result can only be set once"); + self.result_id = Some(id); + self.wc += 1; + } + + pub(crate) fn add_operand(&mut self, operand: Word) { + self.operands.push(operand); + self.wc += 1; + } + + pub(crate) fn add_operands(&mut self, operands: Vec) { + for operand in operands.into_iter() { + self.add_operand(operand) + } + } + + pub(crate) fn to_words(&self, sink: &mut impl Extend) { + sink.extend(Some((self.wc << 16 | self.op as u32) as u32)); + sink.extend(self.type_id); + sink.extend(self.result_id); + sink.extend(self.operands.iter().cloned()); + } +} diff --git a/src/back/spv/mod.rs b/src/back/spv/mod.rs new file mode 100644 index 0000000000..183f7c008d --- /dev/null +++ b/src/back/spv/mod.rs @@ -0,0 +1,42 @@ +mod layout; +pub mod writer; + +use spirv::*; + +bitflags::bitflags! { + pub struct WriterFlags: u32 { + const NONE = 0x0; + const DEBUG = 0x1; + } +} + +struct PhysicalLayout { + magic_number: Word, + version: Word, + generator: Word, + bound: Word, + instruction_schema: Word, +} + +#[derive(Default)] +pub(crate) struct LogicalLayout { + capabilities: Vec, + extensions: Vec, + ext_inst_imports: Vec, + memory_model: Vec, + entry_points: Vec, + execution_modes: Vec, + debugs: Vec, + annotations: Vec, + declarations: Vec, + function_declarations: Vec, + function_definitions: Vec, +} + +struct Instruction { + op: Op, + wc: u32, + type_id: Option, + result_id: Option, + operands: Vec, +} diff --git a/src/back/spv/writer.rs b/src/back/spv/writer.rs new file mode 100644 index 0000000000..d5a3b70ed4 --- /dev/null +++ b/src/back/spv/writer.rs @@ -0,0 +1,1013 @@ +/*! Standard Portable Intermediate Representation (SPIR-V) backend !*/ +use crate::back::spv::{Instruction, LogicalLayout, PhysicalLayout, WriterFlags}; +use crate::{FastHashMap, FastHashSet}; +use spirv::*; + +trait LookupHelper { + type Target; + fn lookup_id(&self, handle: crate::Handle) -> Option; + fn lookup_handle(&self, word: Word) -> Option>; +} + +impl LookupHelper for FastHashMap> { + type Target = T; + + fn lookup_id(&self, handle: crate::Handle) -> Option { + let mut word = None; + for (k, v) in self.iter() { + if *v == handle { + word = Some(*k); + break; + } + } + word + } + + fn lookup_handle(&self, word: u32) -> Option> { + let mut handle = None; + for (k, v) in self.iter() { + if *k == word { + handle = Some(*v); + break; + } + } + handle + } +} + +#[derive(Debug, PartialEq)] +struct LookupFunctionType { + parameter_type_ids: Vec, + return_type_id: Word, +} + +pub struct Writer { + physical_layout: PhysicalLayout, + logical_layout: LogicalLayout, + id_count: u32, + capabilities: FastHashSet, + debugs: Vec, + annotations: Vec, + writer_flags: WriterFlags, + void_type: Option, + lookup_type: FastHashMap>, + lookup_function: FastHashMap>, + lookup_function_type: FastHashMap, + lookup_constant: FastHashMap>, + lookup_global_variable: FastHashMap>, +} + +impl Writer { + pub fn new(header: &crate::Header, writer_flags: WriterFlags) -> Self { + Writer { + physical_layout: PhysicalLayout::new(header), + logical_layout: LogicalLayout::default(), + id_count: 0, + capabilities: FastHashSet::default(), + debugs: vec![], + annotations: vec![], + writer_flags, + void_type: None, + lookup_type: FastHashMap::default(), + lookup_function: FastHashMap::default(), + lookup_function_type: FastHashMap::default(), + lookup_constant: FastHashMap::default(), + lookup_global_variable: FastHashMap::default(), + } + } + + fn generate_id(&mut self) -> Word { + self.id_count += 1; + self.id_count + } + + fn bytes_to_words(&self, bytes: &[u8]) -> Vec { + bytes + .chunks(4) + .map(|chars| chars.iter().rev().fold(0u32, |u, c| (u << 8) | *c as u32)) + .collect() + } + + fn string_to_words(&self, input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut words = self.bytes_to_words(bytes); + + if bytes.len() % 4 == 0 { + // nul-termination + words.push(0x0u32); + } + + words + } + + fn try_add_capabilities(&mut self, capabilities: &[Capability]) { + for capability in capabilities.iter() { + self.capabilities.insert(*capability); + } + } + + fn instruction_capability(&self, capability: Capability) -> Instruction { + let mut instruction = Instruction::new(Op::Capability); + instruction.add_operand(capability as u32); + instruction + } + + fn instruction_ext_inst_import(&mut self, name: &str) -> Instruction { + let mut instruction = Instruction::new(Op::ExtInstImport); + let id = self.generate_id(); + instruction.set_result(id); + instruction.add_operands(self.string_to_words(name)); + instruction + } + + fn instruction_memory_model(&mut self) -> Instruction { + let mut instruction = Instruction::new(Op::MemoryModel); + let addressing_model = AddressingModel::Logical; + let memory_model = MemoryModel::GLSL450; + self.try_add_capabilities(addressing_model.required_capabilities()); + self.try_add_capabilities(memory_model.required_capabilities()); + + instruction.add_operand(addressing_model as u32); + instruction.add_operand(memory_model as u32); + instruction + } + + fn instruction_entry_point( + &mut self, + entry_point: &crate::EntryPoint, + ir_module: &crate::Module, + ) -> Instruction { + let mut instruction = Instruction::new(Op::EntryPoint); + let function_id = self + .lookup_function + .lookup_id(entry_point.function) + .unwrap(); + + instruction.add_operand(entry_point.exec_model as u32); + instruction.add_operand(function_id); + + if self.writer_flags.contains(WriterFlags::DEBUG) { + let mut debug_instruction = Instruction::new(Op::Name); + debug_instruction.set_result(function_id); + debug_instruction.add_operands(self.string_to_words(entry_point.name.as_str())); + self.debugs.push(debug_instruction); + } + + instruction.add_operands(self.string_to_words(entry_point.name.as_str())); + + let function = &ir_module.functions[entry_point.function]; + for ((handle, _), &usage) in ir_module + .global_variables + .iter() + .zip(&function.global_usage) + { + if usage.contains(crate::GlobalUse::STORE) || usage.contains(crate::GlobalUse::LOAD) { + let id = self.get_global_variable_id( + &ir_module.types, + &ir_module.global_variables, + handle, + ); + instruction.add_operand(id); + } + } + + self.try_add_capabilities(entry_point.exec_model.required_capabilities()); + match entry_point.exec_model { + ExecutionModel::Vertex => {} + ExecutionModel::Fragment => { + let execution_mode = ExecutionMode::OriginUpperLeft; + self.try_add_capabilities(execution_mode.required_capabilities()); + + let mut execution_mode_instruction = Instruction::new(Op::ExecutionMode); + execution_mode_instruction.add_operand(function_id); + execution_mode_instruction.add_operand(execution_mode as u32); + execution_mode_instruction.to_words(&mut self.logical_layout.execution_modes); + } + _ => unimplemented!(), + } + + instruction + } + + fn get_type_id( + &mut self, + arena: &crate::Arena, + handle: crate::Handle, + ) -> Word { + match self.lookup_type.lookup_id(handle) { + Some(word) => word, + None => { + let (instruction, id) = self.instruction_type_declaration(arena, handle); + instruction.to_words(&mut self.logical_layout.declarations); + id + } + } + } + + fn get_constant_id( + &mut self, + handle: crate::Handle, + ir_module: &crate::Module, + ) -> Word { + match self.lookup_constant.lookup_id(handle) { + Some(word) => word, + None => { + let (instruction, id) = self.instruction_constant_type(handle, ir_module); + instruction.to_words(&mut self.logical_layout.declarations); + id + } + } + } + + fn get_global_variable_id( + &mut self, + arena: &crate::Arena, + global_arena: &crate::Arena, + handle: crate::Handle, + ) -> Word { + match self.lookup_global_variable.lookup_id(handle) { + Some(word) => word, + None => { + let global_variable = &global_arena[handle]; + let (instruction, id) = + self.instruction_global_variable(arena, global_variable, handle); + instruction.to_words(&mut self.logical_layout.declarations); + id + } + } + } + + fn get_function_type( + &mut self, + ty: Option>, + arena: &crate::Arena, + ) -> Word { + match ty { + Some(handle) => self.get_type_id(arena, handle), + None => match self.void_type { + Some(id) => id, + None => { + let id = self.generate_id(); + + let mut instruction = Instruction::new(Op::TypeVoid); + instruction.set_result(id); + + self.void_type = Some(id); + instruction.to_words(&mut self.logical_layout.declarations); + id + } + }, + } + } + + fn find_scalar_handle( + &self, + arena: &crate::Arena, + kind: crate::ScalarKind, + width: u8, + ) -> crate::Handle { + let mut scalar_handle = None; + for (handle, ty) in arena.iter() { + match ty.inner { + crate::TypeInner::Scalar { + kind: _kind, + width: _width, + } => { + if kind == _kind && width == _width { + scalar_handle = Some(handle); + break; + } + } + _ => continue, + } + } + scalar_handle.unwrap() + } + + fn instruction_type_declaration( + &mut self, + arena: &crate::Arena, + handle: crate::Handle, + ) -> (Instruction, Word) { + let ty = &arena[handle]; + let id = self.generate_id(); + let mut instruction; + + match ty.inner { + crate::TypeInner::Scalar { kind, width } => { + match kind { + crate::ScalarKind::Sint => { + instruction = Instruction::new(Op::TypeInt); + instruction.set_result(id); + instruction.add_operand(width as u32); + instruction.add_operand(0x1u32); + } + crate::ScalarKind::Uint => { + instruction = Instruction::new(Op::TypeInt); + instruction.set_result(id); + instruction.add_operand(width as u32); + instruction.add_operand(0x0u32); + } + crate::ScalarKind::Float => { + instruction = Instruction::new(Op::TypeFloat); + instruction.set_result(id); + instruction.add_operand(width as u32); + } + crate::ScalarKind::Bool => { + instruction = Instruction::new(Op::TypeBool); + instruction.set_result(id); + } + } + self.lookup_type.insert(id, handle); + } + crate::TypeInner::Vector { size, kind, width } => { + let scalar_handle = self.find_scalar_handle(arena, kind, width); + let scalar_id = self.get_type_id(arena, scalar_handle); + + instruction = Instruction::new(Op::TypeVector); + instruction.set_result(id); + instruction.add_operand(scalar_id); + instruction.add_operand(size as u32); + + self.lookup_type.insert(id, handle); + } + crate::TypeInner::Matrix { + columns, + rows: _, + kind, + width, + } => { + let scalar_handle = self.find_scalar_handle(arena, kind, width); + let scalar_id = self.get_type_id(arena, scalar_handle); + + instruction = Instruction::new(Op::TypeMatrix); + instruction.set_result(id); + instruction.add_operand(scalar_id); + instruction.add_operand(columns as u32); + } + crate::TypeInner::Pointer { base, class } => { + let type_id = self.get_type_id(arena, base); + instruction = Instruction::new(Op::TypePointer); + instruction.set_result(id); + instruction.add_operand(class as u32); + instruction.add_operand(type_id); + + self.lookup_type.insert(id, handle); + } + crate::TypeInner::Array { base, size } => { + let type_id = self.get_type_id(arena, handle); + + instruction = Instruction::new(Op::TypeArray); + instruction.set_result(id); + instruction.add_operand(type_id); + + match size { + crate::ArraySize::Static(word) => { + instruction.add_operand(word); + } + _ => panic!("Array size {:?} unsupported", size), + } + + self.lookup_type.insert(id, base); + } + crate::TypeInner::Struct { ref members } => { + instruction = Instruction::new(Op::TypeStruct); + instruction.set_result(id); + + for member in members { + let type_id = self.get_type_id(arena, member.ty); + instruction.add_operand(type_id); + } + + self.lookup_type.insert(id, handle); + } + crate::TypeInner::Image { base, dim, flags } => { + let type_id = self.get_type_id(arena, base); + self.try_add_capabilities(dim.required_capabilities()); + + instruction = Instruction::new(Op::TypeImage); + instruction.set_result(id); + instruction.add_operand(type_id); + instruction.add_operand(dim as u32); + + // TODO Add Depth, but how to determine? Not yet in the WGSL spec + instruction.add_operand(1); + + instruction.add_operand(if flags.contains(crate::ImageFlags::ARRAYED) { + 1 + } else { + 0 + }); + + instruction.add_operand(if flags.contains(crate::ImageFlags::MULTISAMPLED) { + 1 + } else { + 0 + }); + + if let Dim::DimSubpassData = dim { + instruction.add_operand(2); + instruction.add_operand(ImageFormat::Unknown as u32); + } else { + instruction.add_operand(if flags.contains(crate::ImageFlags::SAMPLED) { + 1 + } else { + 0 + }); + + // TODO Defaults to Unknown, not yet in IR + instruction.add_operand(ImageFormat::Unknown as u32); + }; + + instruction.add_operand( + if flags.contains(crate::ImageFlags::CAN_STORE) + && flags.contains(crate::ImageFlags::CAN_LOAD) + { + 2 + } else if flags.contains(crate::ImageFlags::CAN_STORE) { + 1 + } else { + 0 + }, + ); + + self.lookup_type.insert(id, base); + } + crate::TypeInner::Sampler => { + instruction = Instruction::new(Op::TypeSampler); + instruction.set_result(id); + self.lookup_type.insert(id, handle); + } + } + + (instruction, id) + } + + fn instruction_constant_type( + &mut self, + handle: crate::Handle, + ir_module: &crate::Module, + ) -> (Instruction, Word) { + let id = self.generate_id(); + self.lookup_constant.insert(id, handle); + let constant = &ir_module.constants[handle]; + let arena = &ir_module.types; + + match constant.inner { + crate::ConstantInner::Sint(val) => { + let type_id = self.get_type_id(arena, constant.ty); + + let mut instruction = Instruction::new(Op::Constant); + instruction.set_type(type_id); + instruction.set_result(id); + + let ty = &ir_module.types[constant.ty]; + match ty.inner { + crate::TypeInner::Scalar { kind: _, width } => match width { + 32 => { + instruction.add_operand(val as u32); + } + 64 => { + let (low, high) = ((val >> 32) as u32, val as u32); + instruction.add_operand(low); + instruction.add_operand(high); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + + (instruction, id) + } + crate::ConstantInner::Uint(val) => { + let type_id = self.get_type_id(arena, constant.ty); + + let mut instruction = Instruction::new(Op::Constant); + instruction.set_type(type_id); + instruction.set_result(id); + + let ty = &ir_module.types[constant.ty]; + match ty.inner { + crate::TypeInner::Scalar { kind: _, width } => match width { + 32 => { + instruction.add_operand(val as u32); + } + 64 => { + let (low, high) = ((val >> 32) as u32, val as u32); + instruction.add_operand(low); + instruction.add_operand(high); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + + (instruction, id) + } + crate::ConstantInner::Float(val) => { + let type_id = self.get_type_id(arena, constant.ty); + + let mut instruction = Instruction::new(Op::Constant); + instruction.set_type(type_id); + instruction.set_result(id); + + let ty = &ir_module.types[constant.ty]; + match ty.inner { + crate::TypeInner::Scalar { kind: _, width } => match width { + 32 => { + instruction.add_operand((val as f32).to_bits()); + } + 64 => { + let bits = f64::to_bits(val); + let (low, high) = ((bits >> 32) as u32, bits as u32); + instruction.add_operand(low); + instruction.add_operand(high); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + + (instruction, id) + } + crate::ConstantInner::Bool(val) => { + let type_id = self.get_type_id(arena, constant.ty); + + let mut instruction = Instruction::new(if val { + Op::ConstantTrue + } else { + Op::ConstantFalse + }); + + instruction.set_type(type_id); + instruction.set_result(id); + (instruction, id) + } + crate::ConstantInner::Composite(ref constituents) => { + let type_id = self.get_type_id(arena, constant.ty); + + let mut instruction = Instruction::new(Op::ConstantComposite); + instruction.set_type(type_id); + instruction.set_result(id); + + for constituent in constituents.iter() { + let id = self.get_constant_id(*constituent, &ir_module); + instruction.add_operand(id); + } + + (instruction, id) + } + } + } + + fn get_pointer_id( + &mut self, + arena: &crate::Arena, + handle: crate::Handle, + class: StorageClass, + ) -> Word { + let ty = &arena[handle]; + let type_id = self.get_type_id(arena, handle); + match ty.inner { + crate::TypeInner::Pointer { .. } => type_id, + _ => { + let pointer_id = self.generate_id(); + let mut instruction = Instruction::new(Op::TypePointer); + instruction.set_result(pointer_id); + instruction.add_operand((class) as u32); + instruction.add_operand(type_id); + instruction.to_words(&mut self.logical_layout.declarations); + + /* TODO + Not able to lookup Pointer, because there is no Handle in the IR for it. + Idea would be to not have any handles at all in the lookups, so we aren't bound + to the IR. We can then insert, like here runtime values to the lookups + */ + // self.lookup_type.insert(pointer_id, global_variable.ty); + pointer_id + } + } + } + + fn instruction_global_variable( + &mut self, + arena: &crate::Arena, + global_variable: &crate::GlobalVariable, + handle: crate::Handle, + ) -> (Instruction, Word) { + let mut instruction = Instruction::new(Op::Variable); + let id = self.generate_id(); + + self.try_add_capabilities(global_variable.class.required_capabilities()); + + let pointer_id = self.get_pointer_id(arena, global_variable.ty, global_variable.class); + + instruction.set_type(pointer_id); + instruction.set_result(id); + instruction.add_operand(global_variable.class as u32); + + if self.writer_flags.contains(WriterFlags::DEBUG) { + let mut debug_instruction = Instruction::new(Op::Name); + debug_instruction.set_result(id); + debug_instruction.add_operands( + self.string_to_words(global_variable.name.as_ref().unwrap().as_str()), + ); + self.debugs.push(debug_instruction); + } + + match global_variable.binding.as_ref().unwrap() { + crate::Binding::Location(location) => { + let mut instruction = Instruction::new(Op::Decorate); + instruction.add_operand(id); + instruction.add_operand(Decoration::Location as u32); + instruction.add_operand(*location); + self.annotations.push(instruction); + } + crate::Binding::Descriptor { set, binding } => { + let mut set_instruction = Instruction::new(Op::Decorate); + set_instruction.add_operand(id); + set_instruction.add_operand(Decoration::DescriptorSet as u32); + set_instruction.add_operand(*set); + self.annotations.push(set_instruction); + + let mut binding_instruction = Instruction::new(Op::Decorate); + binding_instruction.add_operand(id); + binding_instruction.add_operand(Decoration::Binding as u32); + binding_instruction.add_operand(*binding); + self.annotations.push(binding_instruction); + } + crate::Binding::BuiltIn(built_in) => { + let built_in_u32: u32 = unsafe { std::mem::transmute(*built_in) }; + + let mut instruction = Instruction::new(Op::Decorate); + instruction.add_operand(id); + instruction.add_operand(Decoration::BuiltIn as u32); + instruction.add_operand(built_in_u32); + self.annotations.push(instruction); + } + } + + // TODO Initializer is optional and not (yet) included in the IR + + self.lookup_global_variable.insert(id, handle); + (instruction, id) + } + + fn write_physical_layout(&mut self) { + self.physical_layout.bound = self.id_count + 1; + } + + fn instruction_source(&self) -> Instruction { + let version = 450u32; + + let mut instruction = Instruction::new(Op::Source); + instruction.add_operand(SourceLanguage::GLSL as u32); + instruction.add_operands(self.bytes_to_words(&version.to_le_bytes())); + instruction + } + + fn instruction_function_type(&mut self, lookup_function_type: LookupFunctionType) -> Word { + let mut id = None; + + for (k, v) in self.lookup_function_type.iter() { + if v.eq(&lookup_function_type) { + id = Some(*k); + break; + } + } + + if id.is_none() { + let _id = self.generate_id(); + id = Some(_id); + + let mut instruction = Instruction::new(Op::TypeFunction); + instruction.set_result(_id); + instruction.add_operand(lookup_function_type.return_type_id); + + for parameter_type_id in lookup_function_type.parameter_type_ids.iter() { + instruction.add_operand(*parameter_type_id); + } + + self.lookup_function_type.insert(_id, lookup_function_type); + instruction.to_words(&mut self.logical_layout.declarations); + } + + id.unwrap() + } + + fn instruction_function( + &mut self, + handle: crate::Handle, + function: &crate::Function, + arena: &crate::Arena, + ) -> Instruction { + let id = self.generate_id(); + + let return_type_id = self.get_function_type(function.return_type, arena); + + let mut instruction = Instruction::new(Op::Function); + instruction.set_type(return_type_id); + instruction.set_result(id); + + let control_u32: Word = unsafe { std::mem::transmute(function.control) }; + + instruction.add_operand(control_u32); + + let mut parameter_type_ids = Vec::with_capacity(function.parameter_types.len()); + for parameter_type in function.parameter_types.iter() { + parameter_type_ids.push(self.get_type_id(arena, *parameter_type)) + } + + let lookup_function_type = LookupFunctionType { + return_type_id, + parameter_type_ids, + }; + + let type_function_id = self.instruction_function_type(lookup_function_type); + + instruction.add_operand(type_function_id); + + self.lookup_function.insert(id, handle); + + instruction + } + + fn parse_expression<'a>( + &mut self, + ir_module: &'a crate::Module, + function: &crate::Function, + expression: &crate::Expression, + output: &mut Vec, + ) -> (Word, &'a crate::TypeInner) { + match expression { + crate::Expression::GlobalVariable(handle) => { + let var = &ir_module.global_variables[*handle]; + let inner = &ir_module.types[var.ty].inner; + let id = self.get_global_variable_id( + &ir_module.types, + &ir_module.global_variables, + *handle, + ); + (id, inner) + } + crate::Expression::Constant(handle) => { + let var = &ir_module.constants[*handle]; + let inner = &ir_module.types[var.ty].inner; + let id = self.get_constant_id(*handle, ir_module); + (id, inner) + } + crate::Expression::Compose { ty, components } => { + let var = &ir_module.types[*ty]; + let inner = &var.inner; + let id = self.generate_id(); + let type_id = self.get_type_id(&ir_module.types, *ty); + + let mut instruction = Instruction::new(Op::CompositeConstruct); + instruction.set_type(type_id); + instruction.set_result(id); + + for component in components { + let expression = &function.expressions[*component]; + let (component_id, _) = + self.parse_expression(ir_module, &function, expression, output); + instruction.add_operand(component_id); + } + + output.push(instruction); + + (id, inner) + } + crate::Expression::Binary { op, left, right } => { + match op { + crate::BinaryOperator::Multiply => { + // TODO OpVectorTimesScalar is only supported + let id = self.generate_id(); + let left_expression = &function.expressions[*left]; + let right_expression = &function.expressions[*right]; + let (left_id, left_inner) = + self.parse_expression(ir_module, function, left_expression, output); + let (right_id, right_inner) = + self.parse_expression(ir_module, function, right_expression, output); + + let mut result_type_id = None; + let mut vector_id = None; + let mut scalar_id = None; + + match left_inner { + crate::TypeInner::Vector { size, kind, width } => { + vector_id = Some(left_id); + for (k, v) in self.lookup_type.iter() { + let ty = &ir_module.types[*v]; + match ty.inner { + crate::TypeInner::Vector { + size: _size, + kind: _kind, + width: _width, + } => { + if size == &_size && kind == &_kind && width == &_width + { + result_type_id = Some(*k); + break; + } + } + _ => continue, + } + } + } + _ => scalar_id = Some(left_id), + } + + match right_inner { + crate::TypeInner::Vector { size, kind, width } => { + vector_id = Some(right_id); + for (k, v) in self.lookup_type.iter() { + let ty = &ir_module.types[*v]; + match ty.inner { + crate::TypeInner::Vector { + size: _size, + kind: _kind, + width: _width, + } => { + if size == &_size && kind == &_kind && width == &_width + { + result_type_id = Some(*k); + break; + } + } + _ => continue, + } + } + } + _ => scalar_id = Some(right_id), + } + + // TODO Quick fix + let load_id = self.generate_id(); + let mut instruction = Instruction::new(Op::Load); + instruction.set_type(result_type_id.unwrap()); + instruction.set_result(load_id); + instruction.add_operand(vector_id.unwrap()); + + output.push(instruction); + + let mut instruction = Instruction::new(Op::VectorTimesScalar); + instruction.set_type(result_type_id.unwrap()); + instruction.set_result(id); + instruction.add_operand(load_id); + instruction.add_operand(scalar_id.unwrap()); + output.push(instruction); + + // TODO Not sure how or what to return + ( + id, + &crate::TypeInner::Scalar { + kind: crate::ScalarKind::Float, + width: 10, + }, + ) + } + _ => unimplemented!("{:?}", op), + } + } + crate::Expression::LocalVariable(variable) => { + let id = self.generate_id(); + let var = &function.local_variables[*variable]; + + let ty = &ir_module.types[var.ty]; + + let pointer_id = + self.get_pointer_id(&ir_module.types, var.ty, StorageClass::Function); + + let mut instruction = Instruction::new(Op::Variable); + instruction.set_type(pointer_id); + instruction.set_result(id); + instruction.add_operand(StorageClass::Function as u32); + (id, &ty.inner) + } + _ => unimplemented!("{:?}", expression), + } + } + + fn instruction_function_block( + &mut self, + ir_module: &crate::Module, + function: &crate::Function, + statement: &crate::Statement, + output: &mut Vec, + ) -> Instruction { + match statement { + crate::Statement::Return { value: _ } => match function.return_type { + Some(_) => unimplemented!(), + None => Instruction::new(Op::Return), + }, + crate::Statement::Store { pointer, value } => { + let mut instruction = Instruction::new(Op::Store); + + let pointer_expression = &function.expressions[*pointer]; + let value_expression = &function.expressions[*value]; + let (pointer_id, _) = + self.parse_expression(ir_module, function, pointer_expression, output); + let (value_id, _) = + self.parse_expression(ir_module, function, value_expression, output); + + instruction.add_operand(pointer_id); + instruction.add_operand(value_id); + + instruction + } + _ => unimplemented!(), + } + } + + fn instruction_label(&mut self) -> Instruction { + let mut instruction = Instruction::new(Op::Label); + instruction.set_result(self.generate_id()); + instruction + } + + fn instruction_function_end(&self) -> Instruction { + Instruction::new(Op::FunctionEnd) + } + + fn write_logical_layout(&mut self, ir_module: &crate::Module) { + self.instruction_ext_inst_import("GLSL.std.450") + .to_words(&mut self.logical_layout.ext_inst_imports); + + if self.writer_flags.contains(WriterFlags::DEBUG) { + self.debugs.push(self.instruction_source()); + } + + for (handle, function) in ir_module.functions.iter() { + let mut function_instructions: Vec = vec![]; + function_instructions.push(self.instruction_function( + handle, + function, + &ir_module.types, + )); + + function_instructions.push(self.instruction_label()); + + for block in function.body.iter() { + let mut output: Vec = vec![]; + let instruction = + self.instruction_function_block(ir_module, function, &block, &mut output); + function_instructions.append(&mut output); + function_instructions.push(instruction); + } + + function_instructions.push(self.instruction_function_end()); + for instruction in function_instructions.iter() { + instruction.to_words(&mut self.logical_layout.function_definitions); + } + } + + for entry_point in ir_module.entry_points.iter() { + let entry_point_instruction = self.instruction_entry_point(entry_point, ir_module); + entry_point_instruction.to_words(&mut self.logical_layout.entry_points); + } + + // Looking through all global variable, types, constants. + // Doing this because we also want to include not used parts of the module + // to be included in the output + for (handle, _) in ir_module.global_variables.iter() { + self.get_global_variable_id(&ir_module.types, &ir_module.global_variables, handle); + } + + for (handle, _) in ir_module.types.iter() { + self.get_type_id(&ir_module.types, handle); + } + + for (handle, _) in ir_module.constants.iter() { + self.get_constant_id(handle, &ir_module); + } + + for annotation in self.annotations.iter() { + annotation.to_words(&mut self.logical_layout.annotations); + } + + for capability in self.capabilities.iter() { + self.instruction_capability(*capability) + .to_words(&mut self.logical_layout.capabilities); + } + + self.instruction_memory_model() + .to_words(&mut self.logical_layout.memory_model); + + if self.writer_flags.contains(WriterFlags::DEBUG) { + for debug in self.debugs.iter() { + debug.to_words(&mut self.logical_layout.debugs); + } + } + } + + pub fn write(&mut self, ir_module: &crate::Module) -> Vec { + let mut words: Vec = vec![]; + + self.write_logical_layout(ir_module); + self.write_physical_layout(); + + self.physical_layout.in_words(&mut words); + self.logical_layout.in_words(&mut words); + words + } +} diff --git a/src/front/mod.rs b/src/front/mod.rs index 0ca2c87c36..36bdd65e9c 100644 --- a/src/front/mod.rs +++ b/src/front/mod.rs @@ -1,6 +1,6 @@ #[cfg(feature = "glsl")] pub mod glsl; -pub mod spirv; +pub mod spv; pub mod wgsl; use crate::arena::Arena; diff --git a/src/front/spirv.rs b/src/front/spv.rs similarity index 99% rename from src/front/spirv.rs rename to src/front/spv.rs index 1ebd1b1f06..ac4a120714 100644 --- a/src/front/spirv.rs +++ b/src/front/spv.rs @@ -4,7 +4,7 @@ Our IR links to everything with `Handle`, while SPIR-V uses IDs. In order to keep track of the associations, the parser has many lookup tables. -There map `spirv::Word` into a specific IR handle, plus potentially a bit of +There map `spv::Word` into a specific IR handle, plus potentially a bit of extra info, such as the related SPIR-V type ID. TODO: would be nice to find ways that avoid looking up as much