Skip to content

Commit

Permalink
Add basic tests for SPIR-V back-end writer
Browse files Browse the repository at this point in the history
  • Loading branch information
Timo-DK authored and kvark committed Jun 21, 2020
1 parent a9228d2 commit 9940460
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 43 deletions.
18 changes: 9 additions & 9 deletions src/back/spv/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,7 +17,7 @@ impl PhysicalLayout {
}
}

pub(crate) fn in_words(&self, sink: &mut impl Extend<Word>) {
pub(super) fn in_words(&self, sink: &mut impl Extend<Word>) {
sink.extend(iter::once(self.magic_number));
sink.extend(iter::once(self.version));
sink.extend(iter::once(self.generator));
Expand All @@ -27,7 +27,7 @@ impl PhysicalLayout {
}

impl LogicalLayout {
pub(crate) fn in_words(&self, sink: &mut impl Extend<Word>) {
pub(super) fn in_words(&self, sink: &mut impl Extend<Word>) {
sink.extend(self.capabilities.iter().cloned());
sink.extend(self.extensions.iter().cloned());
sink.extend(self.ext_inst_imports.iter().cloned());
Expand All @@ -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),
Expand All @@ -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<Word>) {
pub(super) fn add_operands(&mut self, operands: Vec<Word>) {
for operand in operands.into_iter() {
self.add_operand(operand)
}
}

pub(crate) fn to_words(&self, sink: &mut impl Extend<Word>) {
pub(super) fn to_words(&self, sink: &mut impl Extend<Word>) {
sink.extend(Some((self.wc << 16 | self.op as u32) as u32));
sink.extend(self.type_id);
sink.extend(self.result_id);
Expand Down
30 changes: 3 additions & 27 deletions src/back/spv/layout_tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::back::spv::test_framework::*;
use crate::back::spv::{helpers, Instruction, LogicalLayout, PhysicalLayout};
use crate::Header;
use spirv::*;
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 5 additions & 2 deletions src/back/spv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ mod helpers;
mod layout;
mod writer;

#[cfg(test)]
mod test_framework;

#[cfg(test)]
mod layout_tests;

Expand All @@ -25,7 +28,7 @@ struct PhysicalLayout {
}

#[derive(Default)]
pub(crate) struct LogicalLayout {
struct LogicalLayout {
capabilities: Vec<Word>,
extensions: Vec<Word>,
ext_inst_imports: Vec<Word>,
Expand All @@ -39,7 +42,7 @@ pub(crate) struct LogicalLayout {
function_definitions: Vec<Word>,
}

struct Instruction {
pub(self) struct Instruction {
op: Op,
wc: u32,
type_id: Option<Word>,
Expand Down
58 changes: 58 additions & 0 deletions src/back/spv/test_framework.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
172 changes: 167 additions & 5 deletions src/back/spv/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
}
}

0 comments on commit 9940460

Please sign in to comment.