diff options
Diffstat (limited to 'src/shader/spirv.rs')
-rw-r--r-- | src/shader/spirv.rs | 794 |
1 files changed, 794 insertions, 0 deletions
diff --git a/src/shader/spirv.rs b/src/shader/spirv.rs new file mode 100644 index 0000000..14180d3 --- /dev/null +++ b/src/shader/spirv.rs @@ -0,0 +1,794 @@ +// Copyright (c) 2021 The Vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +//! Parsing and analysis utilities for SPIR-V shader binaries. +//! +//! This can be used to inspect and validate a SPIR-V module at runtime. The `Spirv` type does some +//! validation, but you should not assume that code that is read successfully is valid. +//! +//! For more information about SPIR-V modules, instructions and types, see the +//! [SPIR-V specification](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html). + +use crate::Version; +use ahash::{HashMap, HashMapExt}; +use std::{ + error::Error, + fmt::{Display, Error as FmtError, Formatter}, + ops::Range, + string::FromUtf8Error, +}; + +// Generated by build.rs +include!(concat!(env!("OUT_DIR"), "/spirv_parse.rs")); + +/// A parsed and analyzed SPIR-V module. +#[derive(Clone, Debug)] +pub struct Spirv { + version: Version, + bound: u32, + instructions: Vec<Instruction>, + ids: HashMap<Id, IdDataIndices>, + + // Items described in the spec section "Logical Layout of a Module" + range_capability: Range<usize>, + range_extension: Range<usize>, + range_ext_inst_import: Range<usize>, + memory_model: usize, + range_entry_point: Range<usize>, + range_execution_mode: Range<usize>, + range_name: Range<usize>, + range_decoration: Range<usize>, + range_global: Range<usize>, +} + +impl Spirv { + /// Parses a SPIR-V document from a list of words. + pub fn new(words: &[u32]) -> Result<Spirv, SpirvError> { + if words.len() < 5 { + return Err(SpirvError::InvalidHeader); + } + + if words[0] != 0x07230203 { + return Err(SpirvError::InvalidHeader); + } + + let version = Version { + major: (words[1] & 0x00ff0000) >> 16, + minor: (words[1] & 0x0000ff00) >> 8, + patch: words[1] & 0x000000ff, + }; + + let bound = words[3]; + + let instructions = { + let mut ret = Vec::new(); + let mut rest = &words[5..]; + while !rest.is_empty() { + let word_count = (rest[0] >> 16) as usize; + assert!(word_count >= 1); + + if rest.len() < word_count { + return Err(ParseError { + instruction: ret.len(), + word: rest.len(), + error: ParseErrors::UnexpectedEOF, + words: rest.to_owned(), + } + .into()); + } + + let mut reader = InstructionReader::new(&rest[0..word_count], ret.len()); + let instruction = Instruction::parse(&mut reader)?; + + if !reader.is_empty() { + return Err(reader.map_err(ParseErrors::LeftoverOperands).into()); + } + + ret.push(instruction); + rest = &rest[word_count..]; + } + ret + }; + + // It is impossible for a valid SPIR-V file to contain more Ids than instructions, so put + // a sane upper limit on the allocation. This prevents a malicious file from causing huge + // memory allocations. + let mut ids = HashMap::with_capacity(instructions.len().min(bound as usize)); + let mut range_capability: Option<Range<usize>> = None; + let mut range_extension: Option<Range<usize>> = None; + let mut range_ext_inst_import: Option<Range<usize>> = None; + let mut range_memory_model: Option<Range<usize>> = None; + let mut range_entry_point: Option<Range<usize>> = None; + let mut range_execution_mode: Option<Range<usize>> = None; + let mut range_name: Option<Range<usize>> = None; + let mut range_decoration: Option<Range<usize>> = None; + let mut range_global: Option<Range<usize>> = None; + let mut in_function = false; + + fn set_range(range: &mut Option<Range<usize>>, index: usize) -> Result<(), SpirvError> { + if let Some(range) = range { + if range.end != index { + return Err(SpirvError::BadLayout { index }); + } + + range.end = index + 1; + } else { + *range = Some(Range { + start: index, + end: index + 1, + }); + } + + Ok(()) + } + + for (index, instruction) in instructions.iter().enumerate() { + if let Some(id) = instruction.result_id() { + if u32::from(id) >= bound { + return Err(SpirvError::IdOutOfBounds { id, index, bound }); + } + + let members = if let Instruction::TypeStruct { member_types, .. } = instruction { + member_types + .iter() + .map(|_| StructMemberDataIndices::default()) + .collect() + } else { + Vec::new() + }; + let data = IdDataIndices { + index, + names: Vec::new(), + decorations: Vec::new(), + members, + }; + if let Some(first) = ids.insert(id, data) { + return Err(SpirvError::DuplicateId { + id, + first_index: first.index, + second_index: index, + }); + } + } + + match instruction { + Instruction::Capability { .. } => set_range(&mut range_capability, index)?, + Instruction::Extension { .. } => set_range(&mut range_extension, index)?, + Instruction::ExtInstImport { .. } => set_range(&mut range_ext_inst_import, index)?, + Instruction::MemoryModel { .. } => set_range(&mut range_memory_model, index)?, + Instruction::EntryPoint { .. } => set_range(&mut range_entry_point, index)?, + Instruction::ExecutionMode { .. } | Instruction::ExecutionModeId { .. } => { + set_range(&mut range_execution_mode, index)? + } + Instruction::Name { .. } | Instruction::MemberName { .. } => { + set_range(&mut range_name, index)? + } + Instruction::Decorate { .. } + | Instruction::MemberDecorate { .. } + | Instruction::DecorationGroup { .. } + | Instruction::GroupDecorate { .. } + | Instruction::GroupMemberDecorate { .. } + | Instruction::DecorateId { .. } + | Instruction::DecorateString { .. } + | Instruction::MemberDecorateString { .. } => { + set_range(&mut range_decoration, index)? + } + Instruction::TypeVoid { .. } + | Instruction::TypeBool { .. } + | Instruction::TypeInt { .. } + | Instruction::TypeFloat { .. } + | Instruction::TypeVector { .. } + | Instruction::TypeMatrix { .. } + | Instruction::TypeImage { .. } + | Instruction::TypeSampler { .. } + | Instruction::TypeSampledImage { .. } + | Instruction::TypeArray { .. } + | Instruction::TypeRuntimeArray { .. } + | Instruction::TypeStruct { .. } + | Instruction::TypeOpaque { .. } + | Instruction::TypePointer { .. } + | Instruction::TypeFunction { .. } + | Instruction::TypeEvent { .. } + | Instruction::TypeDeviceEvent { .. } + | Instruction::TypeReserveId { .. } + | Instruction::TypeQueue { .. } + | Instruction::TypePipe { .. } + | Instruction::TypeForwardPointer { .. } + | Instruction::TypePipeStorage { .. } + | Instruction::TypeNamedBarrier { .. } + | Instruction::TypeRayQueryKHR { .. } + | Instruction::TypeAccelerationStructureKHR { .. } + | Instruction::TypeCooperativeMatrixNV { .. } + | Instruction::TypeVmeImageINTEL { .. } + | Instruction::TypeAvcImePayloadINTEL { .. } + | Instruction::TypeAvcRefPayloadINTEL { .. } + | Instruction::TypeAvcSicPayloadINTEL { .. } + | Instruction::TypeAvcMcePayloadINTEL { .. } + | Instruction::TypeAvcMceResultINTEL { .. } + | Instruction::TypeAvcImeResultINTEL { .. } + | Instruction::TypeAvcImeResultSingleReferenceStreamoutINTEL { .. } + | Instruction::TypeAvcImeResultDualReferenceStreamoutINTEL { .. } + | Instruction::TypeAvcImeSingleReferenceStreaminINTEL { .. } + | Instruction::TypeAvcImeDualReferenceStreaminINTEL { .. } + | Instruction::TypeAvcRefResultINTEL { .. } + | Instruction::TypeAvcSicResultINTEL { .. } + | Instruction::ConstantTrue { .. } + | Instruction::ConstantFalse { .. } + | Instruction::Constant { .. } + | Instruction::ConstantComposite { .. } + | Instruction::ConstantSampler { .. } + | Instruction::ConstantNull { .. } + | Instruction::ConstantPipeStorage { .. } + | Instruction::SpecConstantTrue { .. } + | Instruction::SpecConstantFalse { .. } + | Instruction::SpecConstant { .. } + | Instruction::SpecConstantComposite { .. } + | Instruction::SpecConstantOp { .. } => set_range(&mut range_global, index)?, + Instruction::Undef { .. } if !in_function => set_range(&mut range_global, index)?, + Instruction::Variable { storage_class, .. } + if *storage_class != StorageClass::Function => + { + set_range(&mut range_global, index)? + } + Instruction::Function { .. } => { + in_function = true; + } + Instruction::Line { .. } | Instruction::NoLine { .. } => { + if !in_function { + set_range(&mut range_global, index)? + } + } + _ => (), + } + } + + let mut spirv = Spirv { + version, + bound, + instructions, + ids, + + range_capability: range_capability.unwrap_or_default(), + range_extension: range_extension.unwrap_or_default(), + range_ext_inst_import: range_ext_inst_import.unwrap_or_default(), + memory_model: if let Some(range) = range_memory_model { + if range.end - range.start != 1 { + return Err(SpirvError::MemoryModelInvalid); + } + + range.start + } else { + return Err(SpirvError::MemoryModelInvalid); + }, + range_entry_point: range_entry_point.unwrap_or_default(), + range_execution_mode: range_execution_mode.unwrap_or_default(), + range_name: range_name.unwrap_or_default(), + range_decoration: range_decoration.unwrap_or_default(), + range_global: range_global.unwrap_or_default(), + }; + + for index in spirv.range_name.clone() { + match &spirv.instructions[index] { + Instruction::Name { target, .. } => { + spirv.ids.get_mut(target).unwrap().names.push(index); + } + Instruction::MemberName { ty, member, .. } => { + spirv.ids.get_mut(ty).unwrap().members[*member as usize] + .names + .push(index); + } + _ => unreachable!(), + } + } + + // First handle all regular decorations, including those targeting decoration groups. + for index in spirv.range_decoration.clone() { + match &spirv.instructions[index] { + Instruction::Decorate { target, .. } + | Instruction::DecorateId { target, .. } + | Instruction::DecorateString { target, .. } => { + spirv.ids.get_mut(target).unwrap().decorations.push(index); + } + Instruction::MemberDecorate { + structure_type: target, + member, + .. + } + | Instruction::MemberDecorateString { + struct_type: target, + member, + .. + } => { + spirv.ids.get_mut(target).unwrap().members[*member as usize] + .decorations + .push(index); + } + _ => (), + } + } + + // Then, with decoration groups having their lists complete, handle group decorates. + for index in spirv.range_decoration.clone() { + match &spirv.instructions[index] { + Instruction::GroupDecorate { + decoration_group, + targets, + .. + } => { + let indices = { + let data = &spirv.ids[decoration_group]; + if !matches!( + spirv.instructions[data.index], + Instruction::DecorationGroup { .. } + ) { + return Err(SpirvError::GroupDecorateNotGroup { index }); + }; + data.decorations.clone() + }; + + for target in targets { + spirv + .ids + .get_mut(target) + .unwrap() + .decorations + .extend(&indices); + } + } + Instruction::GroupMemberDecorate { + decoration_group, + targets, + .. + } => { + let indices = { + let data = &spirv.ids[decoration_group]; + if !matches!( + spirv.instructions[data.index], + Instruction::DecorationGroup { .. } + ) { + return Err(SpirvError::GroupDecorateNotGroup { index }); + }; + data.decorations.clone() + }; + + for (target, member) in targets { + spirv.ids.get_mut(target).unwrap().members[*member as usize] + .decorations + .extend(&indices); + } + } + _ => (), + } + } + + Ok(spirv) + } + + /// Returns a reference to the instructions in the module. + #[inline] + pub fn instructions(&self) -> &[Instruction] { + &self.instructions + } + + /// Returns the SPIR-V version that the module is compiled for. + #[inline] + pub fn version(&self) -> Version { + self.version + } + + /// Returns the upper bound of `Id`s. All `Id`s should have a numeric value strictly less than + /// this value. + #[inline] + pub fn bound(&self) -> u32 { + self.bound + } + + /// Returns information about an `Id`. + /// + /// # Panics + /// + /// - Panics if `id` is not defined in this module. This can in theory only happpen if you are + /// mixing `Id`s from different modules. + #[inline] + pub fn id(&self, id: Id) -> IdInfo<'_> { + IdInfo { + data_indices: &self.ids[&id], + instructions: &self.instructions, + } + } + + /// Returns an iterator over all `Capability` instructions. + #[inline] + pub fn iter_capability(&self) -> impl ExactSizeIterator<Item = &Instruction> { + self.instructions[self.range_capability.clone()].iter() + } + + /// Returns an iterator over all `Extension` instructions. + #[inline] + pub fn iter_extension(&self) -> impl ExactSizeIterator<Item = &Instruction> { + self.instructions[self.range_extension.clone()].iter() + } + + /// Returns an iterator over all `ExtInstImport` instructions. + #[inline] + pub fn iter_ext_inst_import(&self) -> impl ExactSizeIterator<Item = &Instruction> { + self.instructions[self.range_ext_inst_import.clone()].iter() + } + + /// Returns the `MemoryModel` instruction. + #[inline] + pub fn memory_model(&self) -> &Instruction { + &self.instructions[self.memory_model] + } + + /// Returns an iterator over all `EntryPoint` instructions. + #[inline] + pub fn iter_entry_point(&self) -> impl ExactSizeIterator<Item = &Instruction> { + self.instructions[self.range_entry_point.clone()].iter() + } + + /// Returns an iterator over all execution mode instructions. + #[inline] + pub fn iter_execution_mode(&self) -> impl ExactSizeIterator<Item = &Instruction> { + self.instructions[self.range_execution_mode.clone()].iter() + } + + /// Returns an iterator over all name debug instructions. + #[inline] + pub fn iter_name(&self) -> impl ExactSizeIterator<Item = &Instruction> { + self.instructions[self.range_name.clone()].iter() + } + + /// Returns an iterator over all decoration instructions. + #[inline] + pub fn iter_decoration(&self) -> impl ExactSizeIterator<Item = &Instruction> { + self.instructions[self.range_decoration.clone()].iter() + } + + /// Returns an iterator over all global declaration instructions: types, + /// constants and global variables. + /// + /// Note: This can also include `Line` and `NoLine` instructions. + #[inline] + pub fn iter_global(&self) -> impl ExactSizeIterator<Item = &Instruction> { + self.instructions[self.range_global.clone()].iter() + } +} + +#[derive(Clone, Debug)] +struct IdDataIndices { + index: usize, + names: Vec<usize>, + decorations: Vec<usize>, + members: Vec<StructMemberDataIndices>, +} + +#[derive(Clone, Debug, Default)] +struct StructMemberDataIndices { + names: Vec<usize>, + decorations: Vec<usize>, +} + +/// Information associated with an `Id`. +#[derive(Clone, Debug)] +pub struct IdInfo<'a> { + data_indices: &'a IdDataIndices, + instructions: &'a [Instruction], +} + +impl<'a> IdInfo<'a> { + /// Returns the instruction that defines this `Id` with a `result_id` operand. + #[inline] + pub fn instruction(&self) -> &'a Instruction { + &self.instructions[self.data_indices.index] + } + + /// Returns an iterator over all name debug instructions that target this `Id`. + #[inline] + pub fn iter_name(&self) -> impl ExactSizeIterator<Item = &'a Instruction> { + let instructions = self.instructions; + self.data_indices + .names + .iter() + .map(move |&index| &instructions[index]) + } + + /// Returns an iterator over all decorate instructions, that target this `Id`. This includes any + /// decorate instructions that target this `Id` indirectly via a `DecorationGroup`. + #[inline] + pub fn iter_decoration(&self) -> impl ExactSizeIterator<Item = &'a Instruction> { + let instructions = self.instructions; + self.data_indices + .decorations + .iter() + .map(move |&index| &instructions[index]) + } + + /// If this `Id` refers to a `TypeStruct`, returns an iterator of information about each member + /// of the struct. Empty otherwise. + #[inline] + pub fn iter_members(&self) -> impl ExactSizeIterator<Item = StructMemberInfo<'a>> { + let instructions = self.instructions; + self.data_indices + .members + .iter() + .map(move |data_indices| StructMemberInfo { + data_indices, + instructions, + }) + } +} + +/// Information associated with a member of a `TypeStruct` instruction. +#[derive(Clone, Debug)] +pub struct StructMemberInfo<'a> { + data_indices: &'a StructMemberDataIndices, + instructions: &'a [Instruction], +} + +impl<'a> StructMemberInfo<'a> { + /// Returns an iterator over all name debug instructions that target this struct member. + #[inline] + pub fn iter_name(&self) -> impl ExactSizeIterator<Item = &'a Instruction> { + let instructions = self.instructions; + self.data_indices + .names + .iter() + .map(move |&index| &instructions[index]) + } + + /// Returns an iterator over all decorate instructions that target this struct member. This + /// includes any decorate instructions that target this member indirectly via a + /// `DecorationGroup`. + #[inline] + pub fn iter_decoration(&self) -> impl ExactSizeIterator<Item = &'a Instruction> { + let instructions = self.instructions; + self.data_indices + .decorations + .iter() + .map(move |&index| &instructions[index]) + } +} + +/// Used in SPIR-V to refer to the result of another instruction. +/// +/// Ids are global across a module, and are always assigned by exactly one instruction. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct Id(u32); + +impl From<Id> for u32 { + #[inline] + fn from(id: Id) -> u32 { + id.0 + } +} + +impl Display for Id { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!(f, "%{}", self.0) + } +} + +/// Helper type for parsing the words of an instruction. +#[derive(Debug)] +struct InstructionReader<'a> { + words: &'a [u32], + next_word: usize, + instruction: usize, +} + +impl<'a> InstructionReader<'a> { + /// Constructs a new reader from a slice of words for a single instruction, including the opcode + /// word. `instruction` is the number of the instruction currently being read, and is used for + /// error reporting. + fn new(words: &'a [u32], instruction: usize) -> Self { + debug_assert!(!words.is_empty()); + Self { + words, + next_word: 0, + instruction, + } + } + + /// Returns whether the reader has reached the end of the current instruction. + fn is_empty(&self) -> bool { + self.next_word >= self.words.len() + } + + /// Converts the `ParseErrors` enum to the `ParseError` struct, adding contextual information. + fn map_err(&self, error: ParseErrors) -> ParseError { + ParseError { + instruction: self.instruction, + word: self.next_word - 1, // -1 because the word has already been read + error, + words: self.words.to_owned(), + } + } + + /// Returns the next word in the sequence. + fn next_u32(&mut self) -> Result<u32, ParseError> { + let word = *self.words.get(self.next_word).ok_or(ParseError { + instruction: self.instruction, + word: self.next_word, // No -1 because we didn't advance yet + error: ParseErrors::MissingOperands, + words: self.words.to_owned(), + })?; + self.next_word += 1; + + Ok(word) + } + + /* + /// Returns the next two words as a single `u64`. + #[inline] + fn next_u64(&mut self) -> Result<u64, ParseError> { + Ok(self.next_u32()? as u64 | (self.next_u32()? as u64) << 32) + } + */ + + /// Reads a nul-terminated string. + fn next_string(&mut self) -> Result<String, ParseError> { + let mut bytes = Vec::new(); + loop { + let word = self.next_u32()?.to_le_bytes(); + + if let Some(nul) = word.iter().position(|&b| b == 0) { + bytes.extend(&word[0..nul]); + break; + } else { + bytes.extend(word); + } + } + String::from_utf8(bytes).map_err(|err| self.map_err(ParseErrors::FromUtf8Error(err))) + } + + /// Reads all remaining words. + fn remainder(&mut self) -> Vec<u32> { + let vec = self.words[self.next_word..].to_owned(); + self.next_word = self.words.len(); + vec + } +} + +/// Error that can happen when reading a SPIR-V module. +#[derive(Clone, Debug)] +pub enum SpirvError { + BadLayout { + index: usize, + }, + DuplicateId { + id: Id, + first_index: usize, + second_index: usize, + }, + GroupDecorateNotGroup { + index: usize, + }, + IdOutOfBounds { + id: Id, + index: usize, + bound: u32, + }, + InvalidHeader, + MemoryModelInvalid, + ParseError(ParseError), +} + +impl Display for SpirvError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + match self { + Self::BadLayout { index } => write!( + f, + "the instruction at index {} does not follow the logical layout of a module", + index, + ), + Self::DuplicateId { + id, + first_index, + second_index, + } => write!( + f, + "id {} is assigned more than once, by instructions {} and {}", + id, first_index, second_index, + ), + Self::GroupDecorateNotGroup { index } => write!( + f, + "a GroupDecorate or GroupMemberDecorate instruction at index {} referred to an Id \ + that was not a DecorationGroup", + index, + ), + Self::IdOutOfBounds { id, bound, index } => write!( + f, + "id {}, assigned at instruction {}, is not below the maximum bound {}", + id, index, bound, + ), + Self::InvalidHeader => write!(f, "the SPIR-V module header is invalid"), + Self::MemoryModelInvalid => { + write!(f, "the MemoryModel instruction is not present exactly once") + } + Self::ParseError(_) => write!(f, "parse error"), + } + } +} + +impl Error for SpirvError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::ParseError(err) => Some(err), + _ => None, + } + } +} + +impl From<ParseError> for SpirvError { + fn from(err: ParseError) -> Self { + Self::ParseError(err) + } +} + +/// Error that can happen when parsing SPIR-V instructions into Rust data structures. +#[derive(Clone, Debug)] +pub struct ParseError { + /// The instruction number the error happened at, starting from 0. + pub instruction: usize, + /// The word from the start of the instruction that the error happened at, starting from 0. + pub word: usize, + /// The error. + pub error: ParseErrors, + /// The words of the instruction. + pub words: Vec<u32>, +} + +impl Display for ParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!( + f, + "at instruction {}, word {}: {}", + self.instruction, self.word, self.error, + ) + } +} + +impl Error for ParseError {} + +/// Individual types of parse error that can happen. +#[derive(Clone, Debug)] +pub enum ParseErrors { + FromUtf8Error(FromUtf8Error), + LeftoverOperands, + MissingOperands, + UnexpectedEOF, + UnknownEnumerant(&'static str, u32), + UnknownOpcode(u16), + UnknownSpecConstantOpcode(u16), +} + +impl Display for ParseErrors { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + match self { + Self::FromUtf8Error(_) => write!(f, "invalid UTF-8 in string literal"), + Self::LeftoverOperands => write!(f, "unparsed operands remaining"), + Self::MissingOperands => write!( + f, + "the instruction and its operands require more words than are present in the \ + instruction", + ), + Self::UnexpectedEOF => write!(f, "encountered unexpected end of file"), + Self::UnknownEnumerant(ty, enumerant) => { + write!(f, "invalid enumerant {} for enum {}", enumerant, ty) + } + Self::UnknownOpcode(opcode) => write!(f, "invalid instruction opcode {}", opcode), + Self::UnknownSpecConstantOpcode(opcode) => { + write!(f, "invalid spec constant instruction opcode {}", opcode) + } + } + } +} |