diff --git a/src/back/spv/layout.rs b/src/back/spv/layout.rs index 40e3e47e9e..5db9a05caa 100644 --- a/src/back/spv/layout.rs +++ b/src/back/spv/layout.rs @@ -3,7 +3,7 @@ use spirv::*; use std::iter; impl PhysicalLayout { - pub(crate) fn new(header: &crate::Header) -> Self { + pub(super) 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; @@ -17,7 +17,7 @@ impl PhysicalLayout { } } - pub(crate) fn in_words(&self, sink: &mut impl Extend) { + pub(super) 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)); @@ -27,7 +27,7 @@ impl PhysicalLayout { } impl LogicalLayout { - pub(crate) fn in_words(&self, sink: &mut impl Extend) { + pub(super) 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()); @@ -43,7 +43,7 @@ impl LogicalLayout { } impl Instruction { - pub(crate) fn new(op: Op) -> Self { + pub(super) fn new(op: Op) -> Self { Instruction { op, wc: 1, // Always start at 1 for the first word (OP + WC), @@ -53,30 +53,30 @@ impl Instruction { } } - pub(crate) fn set_type(&mut self, id: Word) { + pub(super) 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) { + pub(super) 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) { + pub(super) fn add_operand(&mut self, operand: Word) { self.operands.push(operand); self.wc += 1; } - pub(crate) fn add_operands(&mut self, operands: Vec) { + pub(super) 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) { + pub(super) 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); diff --git a/src/back/spv/layout_tests.rs b/src/back/spv/layout_tests.rs index 697b609fac..37024b238f 100644 --- a/src/back/spv/layout_tests.rs +++ b/src/back/spv/layout_tests.rs @@ -1,3 +1,4 @@ +use crate::back::spv::test_framework::*; use crate::back::spv::{helpers, Instruction, LogicalLayout, PhysicalLayout}; use crate::Header; use spirv::*; @@ -76,7 +77,7 @@ fn test_logical_layout_in_words() { for instruction in instructions { let wc = instruction.wc as usize; let instruction_output = &output[index..index + wc]; - validate_instruction(instruction_output, instruction); + validate_instruction(instruction_output, &instruction); index += wc; } } @@ -157,32 +158,7 @@ fn test_instruction_to_words() { let mut output = vec![]; instruction.to_words(&mut output); - validate_instruction(output.as_slice(), instruction); -} - -fn validate_instruction(words: &[Word], instruction: Instruction) { - let mut inst_index = 0; - let (wc, op) = ((words[inst_index] >> 16) as u16, words[inst_index] as u16); - inst_index += 1; - - assert_eq!(wc, words.len() as u16); - assert_eq!(op, instruction.op as u16); - - if instruction.type_id.is_some() { - assert_eq!(words[inst_index], instruction.type_id.unwrap()); - inst_index += 1; - } - - if instruction.result_id.is_some() { - assert_eq!(words[inst_index], instruction.result_id.unwrap()); - inst_index += 1; - } - - let mut op_index = 0; - for i in inst_index..wc as usize { - assert_eq!(words[i as usize], instruction.operands[op_index]); - op_index += 1; - } + validate_instruction(output.as_slice(), &instruction); } fn to_word(bytes: &[u8]) -> Word { diff --git a/src/back/spv/mod.rs b/src/back/spv/mod.rs index 468307f55d..4c3ca33626 100644 --- a/src/back/spv/mod.rs +++ b/src/back/spv/mod.rs @@ -2,6 +2,9 @@ mod helpers; mod layout; mod writer; +#[cfg(test)] +mod test_framework; + #[cfg(test)] mod layout_tests; @@ -25,7 +28,7 @@ struct PhysicalLayout { } #[derive(Default)] -pub(crate) struct LogicalLayout { +struct LogicalLayout { capabilities: Vec, extensions: Vec, ext_inst_imports: Vec, @@ -39,7 +42,7 @@ pub(crate) struct LogicalLayout { function_definitions: Vec, } -struct Instruction { +pub(self) struct Instruction { op: Op, wc: u32, type_id: Option, diff --git a/src/back/spv/test_framework.rs b/src/back/spv/test_framework.rs new file mode 100644 index 0000000000..332d300a4d --- /dev/null +++ b/src/back/spv/test_framework.rs @@ -0,0 +1,58 @@ +use crate::back::spv::Instruction; +use spirv::{Op, Word}; + +pub(super) struct SpecRequirements { + pub(super) op: Op, + pub(super) wc: u32, + pub(super) type_id: bool, + pub(super) result_id: bool, + pub(super) operands: bool, +} + +/// Basic validation for checking if the instruction complies to the spec requirements +pub(super) fn validate_spec_requirements( + requirements: SpecRequirements, + instruction: &Instruction, +) { + assert_eq!(requirements.op, instruction.op); + + // Pass the assert if the minimum referred wc in the spec is met + assert!(instruction.wc >= requirements.wc); + + if requirements.type_id { + assert!(instruction.type_id.is_some()); + } + + if requirements.result_id { + assert!(instruction.result_id.is_some()); + } + + if requirements.operands { + assert!(!instruction.operands.is_empty()); + } +} + +pub(super) fn validate_instruction(words: &[Word], instruction: &Instruction) { + let mut inst_index = 0; + let (wc, op) = ((words[inst_index] >> 16) as u16, words[inst_index] as u16); + inst_index += 1; + + assert_eq!(wc, words.len() as u16); + assert_eq!(op, instruction.op as u16); + + if instruction.type_id.is_some() { + assert_eq!(words[inst_index], instruction.type_id.unwrap()); + inst_index += 1; + } + + if instruction.result_id.is_some() { + assert_eq!(words[inst_index], instruction.result_id.unwrap()); + inst_index += 1; + } + + let mut op_index = 0; + for i in inst_index..wc as usize { + assert_eq!(words[i as usize], instruction.operands[op_index]); + op_index += 1; + } +} diff --git a/src/back/spv/writer.rs b/src/back/spv/writer.rs index 4124280d2d..3757f8fdd6 100644 --- a/src/back/spv/writer.rs +++ b/src/back/spv/writer.rs @@ -646,11 +646,9 @@ impl Writer { self.physical_layout.bound = self.id_count + 1; } - fn instruction_source(&self) -> Instruction { - let version = 450u32; - + fn instruction_source(&self, source_language: SourceLanguage, version: u32) -> Instruction { let mut instruction = Instruction::new(Op::Source); - instruction.add_operand(SourceLanguage::GLSL as u32); + instruction.add_operand(source_language as u32); instruction.add_operands(helpers::bytes_to_words(&version.to_le_bytes())); instruction } @@ -923,7 +921,8 @@ impl Writer { .to_words(&mut self.logical_layout.ext_inst_imports); if self.writer_flags.contains(WriterFlags::DEBUG) { - self.debugs.push(self.instruction_source()); + self.debugs + .push(self.instruction_source(SourceLanguage::GLSL, 450)); } for (handle, function) in ir_module.functions.iter() { @@ -1000,3 +999,166 @@ impl Writer { words } } + +#[cfg(test)] +mod tests { + use crate::back::spv::test_framework::*; + use crate::back::spv::{Writer, WriterFlags}; + use crate::Header; + use spirv::*; + + #[test] + fn test_writer_generate_id() { + let mut writer = create_writer(); + + assert_eq!(writer.id_count, 0); + writer.generate_id(); + assert_eq!(writer.id_count, 1); + } + + #[test] + fn test_try_add_capabilities() { + let mut writer = create_writer(); + + assert_eq!(writer.capabilities.len(), 0); + writer.try_add_capabilities(&[Capability::Shader]); + assert_eq!(writer.capabilities.len(), 1); + + writer.try_add_capabilities(&[Capability::Shader]); + assert_eq!(writer.capabilities.len(), 1); + } + + #[test] + fn test_instruction_capability() { + let writer = create_writer(); + let instruction = writer.instruction_capability(Capability::Shader); + let mut output = vec![]; + + let requirements = SpecRequirements { + op: Op::Capability, + wc: 2, + type_id: false, + result_id: false, + operands: true, + }; + + validate_spec_requirements(requirements, &instruction); + + instruction.to_words(&mut output); + validate_instruction(output.as_slice(), &instruction); + } + + #[test] + fn test_instruction_ext_inst_import() { + let mut writer = create_writer(); + let import_name = "GLSL.std.450"; + let instruction = writer.instruction_ext_inst_import(import_name); + let mut output = vec![]; + + let requirements = SpecRequirements { + op: Op::ExtInstImport, + wc: 2, + type_id: false, + result_id: true, + operands: true, + }; + + validate_spec_requirements(requirements, &instruction); + + instruction.to_words(&mut output); + validate_instruction(output.as_slice(), &instruction); + } + + #[test] + fn test_instruction_memory_model() { + let mut writer = create_writer(); + let instruction = writer.instruction_memory_model(); + let mut output = vec![]; + + let requirements = SpecRequirements { + op: Op::MemoryModel, + wc: 3, + type_id: false, + result_id: false, + operands: true, + }; + validate_spec_requirements(requirements, &instruction); + + instruction.to_words(&mut output); + validate_instruction(output.as_slice(), &instruction); + } + + #[test] + fn test_instruction_source() { + let writer = create_writer(); + let version = 450; + let instruction = writer.instruction_source(SourceLanguage::GLSL, version); + let mut output = vec![]; + + let requirements = SpecRequirements { + op: Op::Source, + wc: 3, + type_id: false, + result_id: false, + operands: true, + }; + validate_spec_requirements(requirements, &instruction); + + instruction.to_words(&mut output); + validate_instruction(output.as_slice(), &instruction); + } + + #[test] + fn test_instruction_label() { + let mut writer = create_writer(); + let instruction = writer.instruction_label(); + let mut output = vec![]; + + let requirements = SpecRequirements { + op: Op::Label, + wc: 2, + type_id: false, + result_id: true, + operands: false, + }; + validate_spec_requirements(requirements, &instruction); + + instruction.to_words(&mut output); + validate_instruction(output.as_slice(), &instruction); + } + + #[test] + fn test_instruction_function_end() { + let writer = create_writer(); + let instruction = writer.instruction_function_end(); + let mut output = vec![]; + + let requirements = SpecRequirements { + op: Op::FunctionEnd, + wc: 1, + type_id: false, + result_id: false, + operands: false, + }; + validate_spec_requirements(requirements, &instruction); + + instruction.to_words(&mut output); + validate_instruction(output.as_slice(), &instruction); + } + + #[test] + fn test_write_physical_layout() { + let mut writer = create_writer(); + assert_eq!(writer.physical_layout.bound, 0); + writer.write_physical_layout(); + assert_eq!(writer.physical_layout.bound, 1); + } + + fn create_writer() -> Writer { + let header = Header { + generator: 0, + version: (1, 0, 0), + }; + Writer::new(&header, WriterFlags::NONE) + } +}