From 9cf6c2d61905832dad367575618b30ec2d3a1753 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 19 Mar 2026 13:37:16 +0100 Subject: [PATCH 01/10] .gitignore: ignore random .spv and .dis* files --- .gitignore | 2 ++ spv/.gitignore | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 spv/.gitignore diff --git a/.gitignore b/.gitignore index d81f12ed..115e1fda 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target /.idea +**/*.spv +**/*.dis* diff --git a/spv/.gitignore b/spv/.gitignore new file mode 100644 index 00000000..2738f9cc --- /dev/null +++ b/spv/.gitignore @@ -0,0 +1,2 @@ +!*.spv +!*.dis* From b784d54136fd11ecd5b14bada2467b3bd795d03f Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 18 Mar 2026 16:22:59 +0100 Subject: [PATCH 02/10] InstVec: add `InstOffset` --- crates/rspirv2-types/src/binary/reader.rs | 38 ++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/rspirv2-types/src/binary/reader.rs b/crates/rspirv2-types/src/binary/reader.rs index 82da5da1..2a14a080 100644 --- a/crates/rspirv2-types/src/binary/reader.rs +++ b/crates/rspirv2-types/src/binary/reader.rs @@ -2,23 +2,53 @@ use crate::Word; use crate::binary::DecodeError; use crate::meta::InstMeta; use std::cmp::Ordering; +use std::fmt::{Display, Formatter}; +use std::ops::{Deref, DerefMut}; + +/// An offset to some instruction, a wrapper around `usize`. +#[repr(transparent)] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct InstOffset(pub usize); + +impl Deref for InstOffset { + type Target = usize; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for InstOffset { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Display for InstOffset { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} /// Reader for an entire module pub struct ModuleReader<'a> { words: &'a [Word], - offset: usize, + offset: InstOffset, } impl<'a> ModuleReader<'a> { pub fn new(words: &'a [Word]) -> Self { - Self { words, offset: 0 } + Self { + words, + offset: InstOffset(0), + } } #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Result>, DecodeError> { - match InstReader::from_words(&self.words[self.offset..]) { + match InstReader::from_words(&self.words[*self.offset..]) { Ok(inst_reader) => { - self.offset += inst_reader.len(); + *self.offset += inst_reader.len(); Ok(Some(inst_reader)) } Err(DecodeError::OutOfInstructions) => Ok(None), From 5aa7e56f40261eff62032f0c009c0119a63ca369 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 18 Mar 2026 16:22:59 +0100 Subject: [PATCH 03/10] InstVec: add `InstVec` to compactly store instructions in their spirv encoded form --- crates/examples/src/bin/debug_dis.rs | 7 +- .../examples/src/bin/print_spec_constants.rs | 15 +- .../rspirv2-types/src/binary/decode_error.rs | 2 +- crates/rspirv2-types/src/inst.rs | 16 + crates/rspirv2-types/src/lib.rs | 1 + crates/rspirv2-types/src/module.rs | 100 +++-- crates/rspirv2-types/src/vec.rs | 361 ++++++++++++++++++ crates/rspirv2/tests/decode_encode.rs | 9 +- crates/rspirv2/tests/dis_color.rs | 7 +- crates/tools-gen/src/dis.rs | 4 +- 10 files changed, 460 insertions(+), 62 deletions(-) create mode 100644 crates/rspirv2-types/src/vec.rs diff --git a/crates/examples/src/bin/debug_dis.rs b/crates/examples/src/bin/debug_dis.rs index f30f3be0..d7bb4b47 100644 --- a/crates/examples/src/bin/debug_dis.rs +++ b/crates/examples/src/bin/debug_dis.rs @@ -1,6 +1,5 @@ use clap::Parser; use rspirv2::core::inst_set::CoreInstSet; -use rspirv2::inst::InstEncoding; use rspirv2::module::Module; use std::path::PathBuf; @@ -13,10 +12,8 @@ pub struct Args { impl Args { pub fn run(&self) -> anyhow::Result<()> { let binary = std::fs::read(&self.path)?; - let module = Module::from_bytes(binary.as_slice())?; - let mut module_reader = module.reader(); - while let Some(inst) = module_reader.next()? { - let inst = CoreInstSet::decode(inst)?; + let module = Module::::from_bytes(binary.as_slice())?; + for inst in module.iter() { println!("{:?}", inst); } Ok(()) diff --git a/crates/examples/src/bin/print_spec_constants.rs b/crates/examples/src/bin/print_spec_constants.rs index 3a448073..47f38296 100644 --- a/crates/examples/src/bin/print_spec_constants.rs +++ b/crates/examples/src/bin/print_spec_constants.rs @@ -1,6 +1,5 @@ use clap::Parser; -use rspirv2::core::inst::{OpName, OpSpecConstant}; -use rspirv2::inst::InstEncoding; +use rspirv2::core::inst_set::CoreInstSet; use rspirv2::module::Module; use rspirv2::operand::IdResult; use std::collections::HashMap; @@ -14,20 +13,18 @@ pub struct Args { impl Args { pub fn run(&self, f: &mut impl std::io::Write) -> anyhow::Result<()> { - let module = Module::from_bytes(std::fs::read(&self.path)?.as_slice())?; + let module = Module::::from_bytes(std::fs::read(&self.path)?.as_slice())?; let mut names = HashMap::::new(); - let mut module_reader = module.reader(); - while let Some(inst) = module_reader.next()? { - if let Some(inst) = OpName::try_decode(inst)? { + for inst in module.iter() { + if let CoreInstSet::Name(inst) = inst { names.insert(inst.target.0, inst.name.0); } } let mut spec_const_name_to_value = HashMap::::new(); - let mut module_reader = module.reader(); - while let Some(inst) = module_reader.next()? { - if let Some(inst) = OpSpecConstant::try_decode(inst)? + for inst in module.iter() { + if let CoreInstSet::SpecConstant(inst) = inst && let Some(name) = names.get(&inst.id_result.unwrap()) { spec_const_name_to_value.insert(name.clone(), inst.value.as_u32()?); diff --git a/crates/rspirv2-types/src/binary/decode_error.rs b/crates/rspirv2-types/src/binary/decode_error.rs index 345c503f..336d1928 100644 --- a/crates/rspirv2-types/src/binary/decode_error.rs +++ b/crates/rspirv2-types/src/binary/decode_error.rs @@ -5,7 +5,7 @@ use std::fmt::{Debug, Display, Formatter}; use std::str::Utf8Error; use std::string::FromUtf8Error; -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub enum DecodeError { UnknownOpCode { opcode: u16, diff --git a/crates/rspirv2-types/src/inst.rs b/crates/rspirv2-types/src/inst.rs index ba87ae19..fd6d60fa 100644 --- a/crates/rspirv2-types/src/inst.rs +++ b/crates/rspirv2-types/src/inst.rs @@ -47,6 +47,22 @@ pub trait InstEncoding: Sized + Debug + Eq { } } +impl InstEncoding for () { + fn encode(&self, _: &mut impl WordWriter) -> Result<(), EncodeError> { + Ok(()) + } + + fn decode(reader: InstReader<'_>) -> Result { + Err(DecodeError::UnknownOpCode { + opcode: reader.opcode(), + }) + } + + fn dis_fmt(&self, _: &mut Formatter<'_>, _: &DisContext) -> std::fmt::Result { + Ok(()) + } +} + pub struct InstDis<'a, T: InstEncoding>(&'a T, &'a DisContext); impl<'a, T: InstEncoding> Display for InstDis<'a, T> { diff --git a/crates/rspirv2-types/src/lib.rs b/crates/rspirv2-types/src/lib.rs index aceb847c..9af9003a 100644 --- a/crates/rspirv2-types/src/lib.rs +++ b/crates/rspirv2-types/src/lib.rs @@ -6,6 +6,7 @@ pub mod inst; pub mod meta; pub mod module; pub mod operand; +pub mod vec; mod word; pub use word::*; diff --git a/crates/rspirv2-types/src/module.rs b/crates/rspirv2-types/src/module.rs index 49501133..b655bb10 100644 --- a/crates/rspirv2-types/src/module.rs +++ b/crates/rspirv2-types/src/module.rs @@ -1,9 +1,10 @@ use crate::Word; -use crate::binary::{DecodeError, ModuleReader}; -use crate::dis::{DisModule, DisOptions}; +use crate::binary::DecodeError; use crate::inst::InstEncoding; +use crate::vec::InstVec; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; +use std::ops::Deref; pub const SPIRV_MAGIC: Word = Word(0x07230203); @@ -128,25 +129,38 @@ impl SpirvHeader { } } -/// A SPIR-V Module with a valid header. +/// A SPIR-V Module is an [`InstVec`] with an optional [`SpirvHeader`]. /// /// See -#[derive(Clone, Debug)] -pub struct Module(Vec); +#[derive(Clone, Debug, Default)] +pub struct Module { + pub header: Option, + pub inst: InstVec, +} -impl Module { - pub fn new(header: SpirvHeader) -> Self { - Self(header.to_array().to_vec()) +impl Module { + /// Parse a SPIR-V module from bytes, endianness is automatically detected and instructions checked for validity. + pub fn from_bytes(bytes: &[u8]) -> Result { + let (header, inst_words) = Self::from_bytes_inner(bytes)?; + Ok(Self { + header: Some(header), + inst: InstVec::from_words(inst_words)?, + }) } - pub fn from_words(words: Vec) -> Result { - let slf = Self(words); - slf.header().validate()?; - Ok(slf) + /// Parse a SPIR-V module from bytes, endianness is automatically detected. However, instructions are not checked + /// for validity, and you may get panics due to invalid instructions later on. + pub fn from_bytes_unchecked(bytes: &[u8]) -> Result { + let (header, inst_words) = Self::from_bytes_inner(bytes)?; + Ok(Self { + header: Some(header), + inst: InstVec::from_words_unchecked(inst_words), + }) } - /// Parse a SPIR-V module from bytes. Endianness is automatically detected. - pub fn from_bytes(bytes: &[u8]) -> Result { + /// Parse a SPIR-V module from bytes, endianness is automatically detected. However, instructions are not checked + /// for validity, and you may get panics due to invalid instructions later on. + fn from_bytes_inner(bytes: &[u8]) -> Result<(SpirvHeader, Vec), ParseError> { let (chunks, remainder) = bytes.as_chunks(); if !remainder.is_empty() { return Err(ParseError::BytesNotMultipleOfFour(chunks.len())); @@ -161,31 +175,35 @@ impl Module { return Err(ParseError::MismatchedMagic(magic)); } - let words = if requires_swap { - chunks.iter().copied().map(Word::from_be_bytes).collect() - } else { - chunks.iter().copied().map(Word::from_le_bytes).collect() - }; - Self::from_words(words) - } - - pub fn header(&self) -> SpirvHeader { - SpirvHeader::from_slice(self.0.as_slice()) - } + let header = SpirvHeader::from_array(core::array::from_fn(|i| { + if requires_swap { + Word::from_be_bytes(chunks[i]) + } else { + Word::from_le_bytes(chunks[i]) + } + })); - pub fn instructions(&self) -> &[Word] { - &self.0[5..] + let inst_words = chunks + .iter() + .skip(HEADER_WORDS) + .copied() + .map(|w| { + if requires_swap { + Word::from_be_bytes(w) + } else { + Word::from_le_bytes(w) + } + }) + .collect::>(); + Ok((header, inst_words)) } +} - pub fn reader(&self) -> ModuleReader<'_> { - ModuleReader::new(self.instructions()) - } +impl Deref for Module { + type Target = InstVec; - pub fn dis( - &self, - opt: DisOptions, - ) -> Result, DecodeError> { - DisModule::new(self.instructions(), opt) + fn deref(&self) -> &Self::Target { + &self.inst } } @@ -196,6 +214,7 @@ pub enum ParseError { HeaderReservedNotZero(SpirvHeader), BytesTooShort(usize), BytesNotMultipleOfFour(usize), + DecodeError(DecodeError), } impl Display for ParseError { @@ -219,6 +238,7 @@ impl Display for ParseError { ParseError::BytesNotMultipleOfFour(len) => { write!(f, "The byte array of length {len} must be a multiple of 4") } + ParseError::DecodeError(e) => write!(f, "{e}"), } } } @@ -231,6 +251,12 @@ impl Debug for ParseError { impl Error for ParseError {} +impl From for ParseError { + fn from(value: DecodeError) -> Self { + Self::DecodeError(value) + } +} + #[cfg(test)] mod tests { use super::*; @@ -283,9 +309,9 @@ mod tests { assert_eq!(valid.validate(), Ok(())); let test = |bytes: &[u8]| { - let module = Module::from_bytes(bytes); + let module = Module::<()>::from_bytes(bytes); assert!(module.is_ok(), "{:?}", module); - assert_eq!(module.unwrap().header(), valid); + assert_eq!(module.unwrap().header, Some(valid)); }; let ne_bytes = valid diff --git a/crates/rspirv2-types/src/vec.rs b/crates/rspirv2-types/src/vec.rs new file mode 100644 index 00000000..26ce335f --- /dev/null +++ b/crates/rspirv2-types/src/vec.rs @@ -0,0 +1,361 @@ +use crate::Word; +use crate::binary::{DecodeError, InstOffset, InstReader}; +use crate::dis::{DisModule, DisOptions}; +use crate::inst::InstEncoding; +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; +use std::ops::Deref; + +/// An `InstVec` works like a `Vec`, but stores instructions in the variable-sized SPIR-V binary form to safe +/// on memory size. +/// +/// Instruction sets are a giant enum of all instructions, their size is determined by largest instruction plus a +/// discriminant. The largest instructions in the core ISA being made from 10-15 operands of 4 bytes each, giving us an +/// estimated size of around 64 bytes *per instruction*. With the most common instructions only need 2-4 operands or +/// 12-20 bytes, storing that enum in a regular `Vec` is wasting a lot of memory. +/// +/// This `InstVec` stores instructions in their binary SPIR-V form, which is variable sized, allowing us to save a lot +/// of memory and improving cache locality. To make it as convenient to use as a `Vec`, instructions are +/// automatically encoded and decoded on the fly. To remain performant, decoding of instructions must be cheap, +/// so operands should not allocate any memory and instead borrow slices from the underlying `Vec` whenever +/// possible. +/// +/// However, storing instructions in a variable-sized way brings the same disadvantages as UTF-8 characters in +/// [`String`]. You can't arbitrarily index into an `InstVec` like you can with a [`Vec`], as only indices pointing to +/// the beginning of an instruction are valid. You also can't easily replace instructions in the middle of the stream, +/// as changing the size of an instruction requires you to move all following instructions. Although, you can remove +/// instructions by filling them with `OpNop`. +/// +/// # Safety +/// The referenced words must be an encoded instruction that is valid within the `ISA` instruction set. Encountering an +/// invalid instruction will panic. +pub struct InstVec { + words: Vec, + _phantom: PhantomData, +} + +impl InstVec { + /// Create a new empty [`InstVec`] + #[inline] + pub const fn new() -> Self { + Self { + words: Vec::new(), + _phantom: PhantomData, + } + } + + /// Create a new [`InstVec`] from a [`Vec`] safely, by verifying the instructions within to be valid for the `ISA` + pub fn from_words(words: Vec) -> Result { + FailableInstOffsetRefIter::::from_words(&words).try_for_each(|v| v.map(|_| ()))?; + Ok(Self::from_words_unchecked(words)) + } + + /// Create a new [`InstVec`] from a [`Vec`] of words without checking whether the words contain valid instructions. + /// + /// See [`InstVec`] # Safety + #[inline] + pub fn from_words_unchecked(words: Vec) -> Self { + Self { + words, + _phantom: PhantomData, + } + } + + /// Appends an instruction to the back of the [`InstVec`]. + #[inline] + pub fn push(&mut self, inst: impl Into) -> InstOffset { + let offset = InstOffset(self.words.len()); + inst.into() + .encode(&mut self.words) + .expect("error while encoding"); + offset + } + + /// Moves all the instruction of `other` into `self`, leaving `other` empty. + #[inline] + pub fn append(&mut self, other: &mut Self) { + self.words.append(&mut other.words); + } + + /// Iterate over [`InstRef`] + #[inline] + pub fn iter_ref(&self) -> InstRefIter<'_, ISA> { + InstRefIter::from_words_unchecked(&self.words) + } + + /// Iterate over instructions + #[inline] + pub fn iter(&self) -> InstIter<'_, ISA> { + InstIter::from_words_unchecked(&self.words) + } + + /// Turn into the inner Vec of words + pub fn into_vec(self) -> Vec { + self.words + } + + /// View this [`InstVec`] as a slice of words + pub const fn as_slice(&self) -> &[Word] { + self.words.as_slice() + } + + pub fn dis(&self, opt: DisOptions) -> Result, DecodeError> { + DisModule::new(self.as_slice(), opt) + } +} + +impl Extend for InstVec { + fn extend>(&mut self, iter: T) { + for inst in iter { + self.push(inst); + } + } +} + +impl FromIterator for InstVec { + fn from_iter>(iter: T) -> Self { + let mut s = Self::new(); + for inst in iter { + s.push(inst); + } + s + } +} + +impl Clone for InstVec { + fn clone(&self) -> Self { + Self { + words: self.words.clone(), + _phantom: PhantomData, + } + } +} + +impl Debug for InstVec { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InstVec") + .field("words", &self.words) + .finish() + } +} + +impl Default for InstVec { + fn default() -> Self { + Self::new() + } +} + +/// A reference to a valid instruction encoded in a slice of [`Word`]s. +/// +/// Call [`Self::get`] to get the underlying instruction, as it unfortunately can't implement [`Deref`]. +/// +/// # Safety +/// The referenced words must be an encoded instruction that is valid within the `ISA` instruction set. Encountering an +/// invalid instruction will panic. +pub struct InstRef<'a, ISA: InstEncoding> { + reader: InstReader<'a>, + _phantom: PhantomData, +} + +impl<'a, ISA: InstEncoding> InstRef<'a, ISA> { + #[inline] + pub fn from_words(words: &'a [Word]) -> Result { + Ok(Self { + reader: InstReader::from_words(words)?, + _phantom: PhantomData, + }) + } + + #[inline] + pub fn get(&self) -> ISA { + ISA::decode(**self).unwrap() + } +} + +impl<'a, ISA: InstEncoding> Deref for InstRef<'a, ISA> { + type Target = InstReader<'a>; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.reader + } +} + +impl Copy for InstRef<'_, ISA> {} + +impl Clone for InstRef<'_, ISA> { + fn clone(&self) -> Self { + *self + } +} + +impl Debug for InstRef<'_, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InstRef") + .field("reader", &self.reader) + .finish() + } +} + +/// An [`Iterator`] of [`Result`] yielding either `(`[`InstOffset`]`, `[`InstRef`]`)` or [`DecodeError`] +pub struct FailableInstOffsetRefIter<'a, ISA: InstEncoding> { + offset: InstOffset, + words: &'a [Word], + _phantom: PhantomData, +} + +impl<'a, ISA: InstEncoding> FailableInstOffsetRefIter<'a, ISA> { + /// Create a new iterator + #[inline] + pub fn from_words(words: &'a [Word]) -> Self { + Self { + offset: InstOffset::default(), + words, + _phantom: PhantomData, + } + } + + fn debug_fmt(&self, f: &mut Formatter<'_>, name: &str) -> std::fmt::Result { + f.debug_struct(name) + .field("offset", &self.offset) + .field("words", &self.words) + .finish() + } +} + +impl<'a, ISA: InstEncoding> Iterator for FailableInstOffsetRefIter<'a, ISA> { + type Item = Result<(InstOffset, InstRef<'a, ISA>), DecodeError>; + + #[inline] + fn next(&mut self) -> Option { + match InstRef::from_words(&self.words[*self.offset..]) { + Ok(inst_reader) => { + let ret = Some(Ok((self.offset, inst_reader))); + *self.offset += inst_reader.len(); + ret + } + Err(DecodeError::OutOfInstructions) => None, + Err(e) => Some(Err(e)), + } + } +} + +impl Clone for FailableInstOffsetRefIter<'_, ISA> { + fn clone(&self) -> Self { + Self { + offset: self.offset, + words: self.words, + _phantom: PhantomData, + } + } +} + +impl Debug for FailableInstOffsetRefIter<'_, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.debug_fmt(f, "FailableInstOffsetRefIter") + } +} + +/// An [`Iterator`] of `(`[`InstOffset`]`, `[`InstRef`]`)` that panics when instructions fail to decode +pub struct InstRefOffsetIter<'a, ISA: InstEncoding>(FailableInstOffsetRefIter<'a, ISA>); + +impl<'a, ISA: InstEncoding> InstRefOffsetIter<'a, ISA> { + /// Create a new iterator, trusting that the supplied Words are valid in the `ISA` + #[inline] + pub fn from_words_unchecked(words: &'a [Word]) -> Self { + Self(FailableInstOffsetRefIter::from_words(words)) + } +} + +impl<'a, ISA: InstEncoding> Iterator for InstRefOffsetIter<'a, ISA> { + type Item = (InstOffset, InstRef<'a, ISA>); + + #[inline] + fn next(&mut self) -> Option { + Some( + self.0 + .next()? + .expect("Instruction decoding failed unexpectingly"), + ) + } +} + +impl Clone for InstRefOffsetIter<'_, ISA> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Debug for InstRefOffsetIter<'_, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.debug_fmt(f, "InstRefOffsetIter") + } +} + +/// An [`Iterator`] of [`InstRef`] that panics when instructions fail to decode +pub struct InstRefIter<'a, ISA: InstEncoding>(InstRefOffsetIter<'a, ISA>); + +impl<'a, ISA: InstEncoding> InstRefIter<'a, ISA> { + /// Create a new iterator, trusting that the supplied Words are valid in the `ISA` + #[inline] + pub fn from_words_unchecked(words: &'a [Word]) -> Self { + Self(InstRefOffsetIter::from_words_unchecked(words)) + } + + pub fn with_offsets(self) -> InstRefOffsetIter<'a, ISA> { + self.0 + } +} + +impl<'a, ISA: InstEncoding> Iterator for InstRefIter<'a, ISA> { + type Item = InstRef<'a, ISA>; + + #[inline] + fn next(&mut self) -> Option { + Some(self.0.next()?.1) + } +} + +impl Clone for InstRefIter<'_, ISA> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Debug for InstRefIter<'_, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.0.debug_fmt(f, "InstRefIter") + } +} + +/// An [`Iterator`] of instructions that panics when instructions fail to decode +pub struct InstIter<'a, ISA: InstEncoding>(InstRefOffsetIter<'a, ISA>); + +impl<'a, ISA: InstEncoding> InstIter<'a, ISA> { + /// Create a new iterator, trusting that the supplied Words are valid in the `ISA` + #[inline] + pub fn from_words_unchecked(words: &'a [Word]) -> Self { + Self(InstRefOffsetIter::from_words_unchecked(words)) + } +} + +impl<'a, ISA: InstEncoding> Iterator for InstIter<'a, ISA> { + type Item = ISA; + + #[inline] + fn next(&mut self) -> Option { + Some(self.0.next()?.1.get()) + } +} + +impl Clone for InstIter<'_, ISA> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Debug for InstIter<'_, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.0.debug_fmt(f, "InstIter") + } +} diff --git a/crates/rspirv2/tests/decode_encode.rs b/crates/rspirv2/tests/decode_encode.rs index 4e157c34..251e6e2d 100644 --- a/crates/rspirv2/tests/decode_encode.rs +++ b/crates/rspirv2/tests/decode_encode.rs @@ -1,3 +1,4 @@ +use rspirv2::core::inst_set::CoreInstSet; use rspirv2::module::Module; use rspirv2_types::inst::InstEncoding; use rspirv2_types::module::SPIRV_MAGIC; @@ -22,13 +23,11 @@ fn test_bla_be() -> anyhow::Result<()> { } fn roundtrip_spv(spv: &[u8]) -> anyhow::Result<()> { - let module = Module::from_bytes(spv)?; - let mut module_reader = module.reader(); + let module = Module::::from_bytes(spv)?; let mut writer = Vec::new(); - while let Some(inst) = module_reader.next()? { - let inst = rspirv2::core::inst_set::CoreInstSet::decode(inst)?; + for inst in module.iter() { inst.encode(&mut writer)?; } - assert_eq!(module.instructions(), writer.as_slice()); + assert_eq!(module.as_slice(), writer.as_slice()); Ok(()) } diff --git a/crates/rspirv2/tests/dis_color.rs b/crates/rspirv2/tests/dis_color.rs index 2bb8ed2d..4a38db84 100644 --- a/crates/rspirv2/tests/dis_color.rs +++ b/crates/rspirv2/tests/dis_color.rs @@ -6,13 +6,14 @@ use std::io::Write; #[test] pub fn test_disabled_color() -> anyhow::Result<()> { - let module = Module::from_bytes(std::fs::read(spv("dis_reference"))?.as_slice())?; + let module = + Module::::from_bytes(std::fs::read(spv("dis_reference"))?.as_slice())?; let mut color_stripped = Vec::new(); write!( anstream::AutoStream::never(&mut color_stripped), "{}", - module.dis::(DisOptions { + module.dis(DisOptions { color: true, ..Default::default() })? @@ -22,7 +23,7 @@ pub fn test_disabled_color() -> anyhow::Result<()> { write!( &mut color_disabled, "{}", - module.dis::(DisOptions { + module.dis(DisOptions { color: false, ..Default::default() })? diff --git a/crates/tools-gen/src/dis.rs b/crates/tools-gen/src/dis.rs index d553db8b..f41e8303 100644 --- a/crates/tools-gen/src/dis.rs +++ b/crates/tools-gen/src/dis.rs @@ -34,8 +34,8 @@ impl Args { } } - let module = Module::from_bytes(slice.as_slice())?; - write!(stdout, "{}", module.dis::(self.to_dis_opts()?)?)?; + let module = Module::::from_bytes(slice.as_slice())?; + write!(stdout, "{}", module.dis(self.to_dis_opts()?)?)?; Ok(()) } From bddd054568d30d9d0ecae42f7e8d4911118493f6 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Wed, 18 Mar 2026 19:56:48 +0100 Subject: [PATCH 04/10] InstVec: remove `ModuleReader`, replaced by `InstIter` --- crates/rspirv2-types/src/binary/reader.rs | 27 ------------ crates/rspirv2-types/src/dis.rs | 7 ++-- crates/rspirv2/tests/inst_encoding.rs | 51 +++++++++++------------ 3 files changed, 28 insertions(+), 57 deletions(-) diff --git a/crates/rspirv2-types/src/binary/reader.rs b/crates/rspirv2-types/src/binary/reader.rs index 2a14a080..29222531 100644 --- a/crates/rspirv2-types/src/binary/reader.rs +++ b/crates/rspirv2-types/src/binary/reader.rs @@ -30,33 +30,6 @@ impl Display for InstOffset { } } -/// Reader for an entire module -pub struct ModuleReader<'a> { - words: &'a [Word], - offset: InstOffset, -} - -impl<'a> ModuleReader<'a> { - pub fn new(words: &'a [Word]) -> Self { - Self { - words, - offset: InstOffset(0), - } - } - - #[allow(clippy::should_implement_trait)] - pub fn next(&mut self) -> Result>, DecodeError> { - match InstReader::from_words(&self.words[*self.offset..]) { - Ok(inst_reader) => { - *self.offset += inst_reader.len(); - Ok(Some(inst_reader)) - } - Err(DecodeError::OutOfInstructions) => Ok(None), - Err(e) => Err(e), - } - } -} - /// Reader for a single instruction #[derive(Copy, Clone, Debug)] pub struct InstReader<'a>(&'a [Word]); diff --git a/crates/rspirv2-types/src/dis.rs b/crates/rspirv2-types/src/dis.rs index 82aff00c..1288f885 100644 --- a/crates/rspirv2-types/src/dis.rs +++ b/crates/rspirv2-types/src/dis.rs @@ -1,9 +1,10 @@ //! Module for Disassembly use crate::Word; -use crate::binary::{DecodeError, ModuleReader}; +use crate::binary::DecodeError; use crate::inst::InstEncoding; use crate::operand::LiteralStringEscape; +use crate::vec::InstIter; use anstyle::Style; use std::cell::Cell; use std::fmt::{Display, Formatter}; @@ -121,9 +122,7 @@ impl<'a, ISA: InstEncoding> DisModule<'a, ISA> { impl<'a, ISA: InstEncoding> Display for DisModule<'a, ISA> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut reader = ModuleReader::new(self.words); - while let Some(inst) = reader.next().map_err(|_| std::fmt::Error)? { - let inst = ISA::decode(inst).map_err(|_| std::fmt::Error)?; + for inst in InstIter::::from_words_unchecked(self.words) { writeln!(f, "{}", inst.dis(&self.dis))?; } Ok(()) diff --git a/crates/rspirv2/tests/inst_encoding.rs b/crates/rspirv2/tests/inst_encoding.rs index 059e2fed..91fce575 100644 --- a/crates/rspirv2/tests/inst_encoding.rs +++ b/crates/rspirv2/tests/inst_encoding.rs @@ -1,24 +1,27 @@ -use anyhow::Context; -use rspirv2::binary::{ModuleReader, VecInstWriter}; +use rspirv2::binary::VecInstWriter; use rspirv2::core::inst::{ OpConstant, OpConvertUToF, OpDecorate, OpIAdd, OpNop, OpStore, OpTypeFloat, OpTypeInt, OpTypePointer, OpVariable, }; +use rspirv2::core::inst_set::CoreInstSet; use rspirv2::core::operands::{Decoration, StorageClass}; use rspirv2::core::preamble::OpAccessChain; use rspirv2::operand::{IdRef, IdResult, IdResultType, LiteralConst, LiteralInteger}; use rspirv2_types::Word; use rspirv2_types::binary::EncodeError; use rspirv2_types::inst::{Inst, InstEncoding}; +use rspirv2_types::vec::InstIter; -fn roundtrip(inst: T) { +fn roundtrip(inst: T) +where + CoreInstSet: From, +{ let mut spirv = Vec::::new(); inst.encode(&mut spirv).unwrap(); - let mut mod_reader = ModuleReader::new(spirv.as_slice()); - let inst_reader = mod_reader.next().unwrap().unwrap(); - assert!(matches!(mod_reader.next(), Ok(None))); - let decoded = T::decode(inst_reader).unwrap(); - assert_eq!(inst, decoded); + let mut iter = InstIter::::from_words_unchecked(&spirv); + let decoded = iter.next().unwrap(); + assert!(iter.next().is_none()); + assert_eq!(CoreInstSet::from(inst), decoded); } #[test] @@ -78,13 +81,10 @@ fn test_op_constant_sequence() -> anyhow::Result<()> { c2.encode(&mut spirv)?; c3.encode(&mut spirv)?; - let mut mod_reader = ModuleReader::new(spirv.as_slice()); - let d1 = OpConstant::decode(mod_reader.next()?.context("No further ops")?)?; - let d2 = OpConstant::decode(mod_reader.next()?.context("No further ops")?)?; - let d3 = OpConstant::decode(mod_reader.next()?.context("No further ops")?)?; - assert_eq!(c1, d1); - assert_eq!(c2, d2); - assert_eq!(c3, d3); + let mut iter = InstIter::::from_words_unchecked(&spirv); + assert_eq!(c1, iter.next().unwrap()); + assert_eq!(c2, iter.next().unwrap()); + assert_eq!(c3, iter.next().unwrap()); Ok(()) } @@ -158,17 +158,16 @@ fn test_non_trivial_code() -> anyhow::Result<()> { }; spirv.push_mut(&mut store_op)?; - let mut mod_reader = ModuleReader::new(spirv.words.as_slice()); - let mut decode = || mod_reader.next()?.context("No further ops"); - assert_eq!(u32_op, OpTypeInt::decode(decode()?)?); - assert_eq!(u32_1_op, OpConstant::decode(decode()?)?); - assert_eq!(add_op, OpIAdd::decode(decode()?)?); - assert_eq!(f32_op, OpTypeFloat::decode(decode()?)?); - assert_eq!(u_to_f_op, OpConvertUToF::decode(decode()?)?); - assert_eq!(f32_ptr_op, OpTypePointer::decode(decode()?)?); - assert_eq!(var_out_op, OpVariable::decode(decode()?)?); - assert_eq!(var_out_location_op, OpDecorate::decode(decode()?)?); - assert_eq!(store_op, OpStore::decode(decode()?)?); + let mut iter = InstIter::::from_words_unchecked(&spirv.words); + assert_eq!(Some(CoreInstSet::from(u32_op)), iter.next()); + assert_eq!(Some(CoreInstSet::from(u32_1_op)), iter.next()); + assert_eq!(Some(CoreInstSet::from(add_op)), iter.next()); + assert_eq!(Some(CoreInstSet::from(f32_op)), iter.next()); + assert_eq!(Some(CoreInstSet::from(u_to_f_op)), iter.next()); + assert_eq!(Some(CoreInstSet::from(f32_ptr_op)), iter.next()); + assert_eq!(Some(CoreInstSet::from(var_out_op)), iter.next()); + assert_eq!(Some(CoreInstSet::from(var_out_location_op)), iter.next()); + assert_eq!(Some(CoreInstSet::from(store_op)), iter.next()); Ok(()) } From 724aa6ec0c7c59bba2fd47d5060c502a445e1fd1 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Tue, 17 Mar 2026 16:28:15 +0100 Subject: [PATCH 05/10] InstVec: add `cast_words_to_ne_bytes(&[Word]) -> &[u8]` --- crates/rspirv2-types/Cargo.toml | 1 + crates/rspirv2-types/src/word.rs | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/crates/rspirv2-types/Cargo.toml b/crates/rspirv2-types/Cargo.toml index 7323da49..020bbc5e 100644 --- a/crates/rspirv2-types/Cargo.toml +++ b/crates/rspirv2-types/Cargo.toml @@ -18,3 +18,4 @@ anstyle.workspace = true [dev-dependencies] anyhow.workspace = true +bytemuck.workspace = true diff --git a/crates/rspirv2-types/src/word.rs b/crates/rspirv2-types/src/word.rs index 2f9d4c65..dab4971e 100644 --- a/crates/rspirv2-types/src/word.rs +++ b/crates/rspirv2-types/src/word.rs @@ -51,3 +51,37 @@ impl Debug for Word { write!(f, "Word({:#010x})", self.0) } } + +/// Convert a slice of [`Word`]s to a slice of `u8`s in native endian. +/// +/// Implementation replicates `bytemuck::cast_slice()`, see `test_cast_words_bytemuck_equivalence`. +pub fn cast_words_to_ne_bytes(words: &[Word]) -> &[u8] { + unsafe { core::slice::from_raw_parts(words.as_ptr().cast::(), words.len() * 4) } +} + +#[cfg(test)] +mod tests { + use super::*; + + const TEST_CORPUS: &[&[u32]] = &[ + &[1, 2, 3, 4], + &[0xDEADBEEF], + &[0, 0xDEADBEEF, 0x12345678, 0xFFFFFFFF], + &[0], + &[], + ]; + + #[test] + pub fn test_cast_words_bytemuck_equivalence() { + for case in TEST_CORPUS { + // convert to Words (potentially changing byte order) + let case = case.iter().map(|v| Word(*v)).collect::>(); + // "transmute" slice of words to slice of u32s, without impl bytemuck::Pod for Word, NO CONVERSIONS HERE! + let case_u32s = case.iter().map(|w| w.0).collect::>(); + assert_eq!( + bytemuck::cast_slice::(&case_u32s), + cast_words_to_ne_bytes(&case) + ); + } + } +} From 7e09981e675fc2a6f31c6159e78f86e2990873af Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 19 Mar 2026 09:59:02 +0100 Subject: [PATCH 06/10] InstVec: add `Module::from_words_maybe_header` for compiletest dis --- crates/rspirv2-types/src/module.rs | 35 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/crates/rspirv2-types/src/module.rs b/crates/rspirv2-types/src/module.rs index b655bb10..6b9791dc 100644 --- a/crates/rspirv2-types/src/module.rs +++ b/crates/rspirv2-types/src/module.rs @@ -1,7 +1,7 @@ -use crate::Word; use crate::binary::DecodeError; use crate::inst::InstEncoding; use crate::vec::InstVec; +use crate::{Word, cast_words_to_ne_bytes}; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use std::ops::Deref; @@ -148,8 +148,9 @@ impl Module { }) } - /// Parse a SPIR-V module from bytes, endianness is automatically detected. However, instructions are not checked - /// for validity, and you may get panics due to invalid instructions later on. + /// Parse a SPIR-V module from bytes, endianness is automatically detected. + /// + /// Instructions are not checked for validity, and you may get panics due to invalid instructions later on. pub fn from_bytes_unchecked(bytes: &[u8]) -> Result { let (header, inst_words) = Self::from_bytes_inner(bytes)?; Ok(Self { @@ -158,8 +159,6 @@ impl Module { }) } - /// Parse a SPIR-V module from bytes, endianness is automatically detected. However, instructions are not checked - /// for validity, and you may get panics due to invalid instructions later on. fn from_bytes_inner(bytes: &[u8]) -> Result<(SpirvHeader, Vec), ParseError> { let (chunks, remainder) = bytes.as_chunks(); if !remainder.is_empty() { @@ -197,6 +196,32 @@ impl Module { .collect::>(); Ok((header, inst_words)) } + + /// Parse a SPIR-V [`Module`] from a word slice that may or may not contain a [`SpirvHeader`]. + pub fn from_words_maybe_header(words: &[Word]) -> Result { + match Self::from_bytes(cast_words_to_ne_bytes(words)) { + Ok(e) => Ok(e), + Err(ParseError::MismatchedMagic(..)) => Ok(Self { + header: None, + inst: InstVec::from_words(words.to_vec())?, + }), + Err(e) => Err(e), + } + } + + /// Parse a SPIR-V [`Module`] from a word slice that may or may not contain a [`SpirvHeader`]. + /// + /// Instructions are not checked for validity, and you may get panics due to invalid instructions later on. + pub fn from_words_maybe_header_unchecked(words: &[Word]) -> Result { + match Self::from_bytes_unchecked(cast_words_to_ne_bytes(words)) { + Ok(e) => Ok(e), + Err(ParseError::MismatchedMagic(..)) => Ok(Self { + header: None, + inst: InstVec::from_words_unchecked(words.to_vec()), + }), + Err(e) => Err(e), + } + } } impl Deref for Module { From eee87aaa1f778ff735c11a223df4c6c1c21c0d03 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Mon, 16 Mar 2026 10:24:52 +0100 Subject: [PATCH 07/10] InstVec: add `InstEncoding::name() -> &'static str` to query instset name --- crates/grammar-parser/src/codegen/instructions.rs | 4 ++++ crates/rspirv2-types/src/inst.rs | 9 +++++++++ crates/rspirv2/src/core/inst_set.rs | 3 +++ crates/rspirv2/src/debug_printf/inst_set.rs | 3 +++ crates/rspirv2/src/glsl_std_450/inst_set.rs | 3 +++ 5 files changed, 22 insertions(+) diff --git a/crates/grammar-parser/src/codegen/instructions.rs b/crates/grammar-parser/src/codegen/instructions.rs index c82a6c57..8c790a5a 100644 --- a/crates/grammar-parser/src/codegen/instructions.rs +++ b/crates/grammar-parser/src/codegen/instructions.rs @@ -182,6 +182,10 @@ pub fn write_inst_enum( } impl InstEncoding for #name { + fn name() -> &'static str { + stringify!(#name) + } + fn encode(&self, writer: &mut impl WordWriter) -> Result<(), EncodeError> { match self { #(#encode_match)* diff --git a/crates/rspirv2-types/src/inst.rs b/crates/rspirv2-types/src/inst.rs index fd6d60fa..d6c66666 100644 --- a/crates/rspirv2-types/src/inst.rs +++ b/crates/rspirv2-types/src/inst.rs @@ -15,6 +15,11 @@ pub trait Inst: InstEncoding { } pub trait InstEncoding: Sized + Debug + Eq { + /// Name of the instruction set, for debug printing + fn name() -> &'static str { + "unknown" + } + /// Encode this instruction to a [`WordWriter`] fn encode(&self, writer: &mut impl WordWriter) -> Result<(), EncodeError>; @@ -48,6 +53,10 @@ pub trait InstEncoding: Sized + Debug + Eq { } impl InstEncoding for () { + fn name() -> &'static str { + "()" + } + fn encode(&self, _: &mut impl WordWriter) -> Result<(), EncodeError> { Ok(()) } diff --git a/crates/rspirv2/src/core/inst_set.rs b/crates/rspirv2/src/core/inst_set.rs index c396db60..6600b06a 100644 --- a/crates/rspirv2/src/core/inst_set.rs +++ b/crates/rspirv2/src/core/inst_set.rs @@ -966,6 +966,9 @@ pub enum CoreInstSet { ConvertHandleToSampledImageINTEL(OpConvertHandleToSampledImageINTEL), } impl InstEncoding for CoreInstSet { + fn name() -> &'static str { + stringify!(CoreInstSet) + } fn encode(&self, writer: &mut impl WordWriter) -> Result<(), EncodeError> { match self { Self::Nop(inst) => InstEncoding::encode(inst, writer), diff --git a/crates/rspirv2/src/debug_printf/inst_set.rs b/crates/rspirv2/src/debug_printf/inst_set.rs index c99b4dc0..970770bd 100644 --- a/crates/rspirv2/src/debug_printf/inst_set.rs +++ b/crates/rspirv2/src/debug_printf/inst_set.rs @@ -4,6 +4,9 @@ pub enum DebugPrintfInstSet { DebugPrintf(DebugPrintf), } impl InstEncoding for DebugPrintfInstSet { + fn name() -> &'static str { + stringify!(DebugPrintfInstSet) + } fn encode(&self, writer: &mut impl WordWriter) -> Result<(), EncodeError> { match self { Self::DebugPrintf(inst) => InstEncoding::encode(inst, writer), diff --git a/crates/rspirv2/src/glsl_std_450/inst_set.rs b/crates/rspirv2/src/glsl_std_450/inst_set.rs index e3dfe4d6..9c423e0f 100644 --- a/crates/rspirv2/src/glsl_std_450/inst_set.rs +++ b/crates/rspirv2/src/glsl_std_450/inst_set.rs @@ -84,6 +84,9 @@ pub enum GlslInstSet { NClamp(NClamp), } impl InstEncoding for GlslInstSet { + fn name() -> &'static str { + stringify!(GlslInstSet) + } fn encode(&self, writer: &mut impl WordWriter) -> Result<(), EncodeError> { match self { Self::Round(inst) => InstEncoding::encode(inst, writer), From 5e74c153d5943fcbd04b096b55a620e40be678ce Mon Sep 17 00:00:00 2001 From: firestar99 Date: Mon, 23 Mar 2026 14:16:32 +0100 Subject: [PATCH 08/10] InstVec: add `InstSlice` and `RawInstSlice` for more flexibility --- crates/rspirv2-types/src/dis.rs | 33 -- crates/rspirv2-types/src/inst.rs | 55 ++++ crates/rspirv2-types/src/lib.rs | 1 + crates/rspirv2-types/src/slice.rs | 448 ++++++++++++++++++++++++++ crates/rspirv2-types/src/vec.rs | 297 +++-------------- crates/rspirv2/tests/decode_encode.rs | 2 +- crates/rspirv2/tests/inst_encoding.rs | 8 +- 7 files changed, 551 insertions(+), 293 deletions(-) create mode 100644 crates/rspirv2-types/src/slice.rs diff --git a/crates/rspirv2-types/src/dis.rs b/crates/rspirv2-types/src/dis.rs index 1288f885..38cef186 100644 --- a/crates/rspirv2-types/src/dis.rs +++ b/crates/rspirv2-types/src/dis.rs @@ -1,14 +1,9 @@ //! Module for Disassembly -use crate::Word; -use crate::binary::DecodeError; -use crate::inst::InstEncoding; use crate::operand::LiteralStringEscape; -use crate::vec::InstIter; use anstyle::Style; use std::cell::Cell; use std::fmt::{Display, Formatter}; -use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; /// Options for disassembly @@ -101,34 +96,6 @@ impl DerefMut for DisContext { } } -/// A sequence of words that has been pre-processed and may be [`Display`]ed. -/// -/// The `ISA: `[`InstEncoding`] generic determines for which instruction set these Words are disassembled. -pub struct DisModule<'a, ISA: InstEncoding> { - words: &'a [Word], - dis: DisContext, - _phantom: PhantomData, -} - -impl<'a, ISA: InstEncoding> DisModule<'a, ISA> { - pub fn new(words: &'a [Word], opt: DisOptions) -> Result { - Ok(Self { - words, - dis: DisContext::new(opt), - _phantom: PhantomData, - }) - } -} - -impl<'a, ISA: InstEncoding> Display for DisModule<'a, ISA> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - for inst in InstIter::::from_words_unchecked(self.words) { - writeln!(f, "{}", inst.dis(&self.dis))?; - } - Ok(()) - } -} - /// Utility struct for inserting separators between variants. Use it like `format!({sep}{value})`, will skip the /// separator on first write. /// diff --git a/crates/rspirv2-types/src/inst.rs b/crates/rspirv2-types/src/inst.rs index d6c66666..f9a5278e 100644 --- a/crates/rspirv2-types/src/inst.rs +++ b/crates/rspirv2-types/src/inst.rs @@ -1,8 +1,11 @@ +use crate::Word; use crate::binary::{DecodeError, EncodeError, IdResultAlloc, InstReader, WordWriter}; use crate::dis::DisContext; use crate::meta::InstMeta; use crate::operand::{IdResult, OptionIdResult}; use std::fmt::{Debug, Display, Formatter}; +use std::marker::PhantomData; +use std::ops::Deref; pub trait Inst: InstEncoding { const META: &InstMeta; @@ -117,3 +120,55 @@ impl MaybeIdResult for OptionIdResult { pub fn make_mut_ref_unit() -> &'static mut () { Box::leak(Box::new(())) } + +/// A reference to a valid instruction encoded in a slice of [`Word`]s. +/// +/// Call [`Self::get`] to get the underlying instruction, as it unfortunately can't implement [`Deref`]. +/// +/// # Safety +/// The referenced words must be an encoded instruction that is valid within the `ISA` instruction set. Encountering an +/// invalid instruction may panic. +pub struct InstRef<'a, ISA: InstEncoding> { + reader: InstReader<'a>, + _phantom: PhantomData, +} + +impl<'a, ISA: InstEncoding> InstRef<'a, ISA> { + #[inline] + pub fn from_words_unchecked(words: &'a [Word]) -> Result { + Ok(Self { + reader: InstReader::from_words(words)?, + _phantom: PhantomData, + }) + } + + #[inline] + pub fn get(&self) -> ISA { + ISA::decode(**self).unwrap() + } +} + +impl<'a, ISA: InstEncoding> Deref for InstRef<'a, ISA> { + type Target = InstReader<'a>; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.reader + } +} + +impl Copy for InstRef<'_, ISA> {} + +impl Clone for InstRef<'_, ISA> { + fn clone(&self) -> Self { + *self + } +} + +impl Debug for InstRef<'_, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InstRef") + .field("reader", &self.reader) + .finish() + } +} diff --git a/crates/rspirv2-types/src/lib.rs b/crates/rspirv2-types/src/lib.rs index 9af9003a..f452cba3 100644 --- a/crates/rspirv2-types/src/lib.rs +++ b/crates/rspirv2-types/src/lib.rs @@ -6,6 +6,7 @@ pub mod inst; pub mod meta; pub mod module; pub mod operand; +pub mod slice; pub mod vec; mod word; diff --git a/crates/rspirv2-types/src/slice.rs b/crates/rspirv2-types/src/slice.rs new file mode 100644 index 00000000..7e423daa --- /dev/null +++ b/crates/rspirv2-types/src/slice.rs @@ -0,0 +1,448 @@ +use crate::Word; +use crate::binary::{DecodeError, InstOffset, InstReader}; +use crate::dis::{DisContext, DisOptions}; +use crate::inst::{InstEncoding, InstRef}; +use std::fmt::{Debug, Display, Formatter}; +use std::marker::PhantomData; +use std::ops::Deref; + +pub fn decode_failed(e: DecodeError) -> ! { + panic!("Decode failed: {e}") +} + +/// An `InstSlice` works like a `&[ISA]`, but stores instructions in the variable-sized SPIR-V binary form to safe +/// on memory. +/// +/// Instruction sets are a giant enum of all instructions, their size is determined by largest instruction plus a +/// discriminant. The largest instructions in the core ISA are made from 10-15 operands, the enum of all instructions is +/// currently 80 bytes large. If you'd put that directly into a `Vec`, you'd be paying 80 bytes *per instruction*, even +/// though the most common instructions only need 2-4 operands or 12-20 bytes, wasting a lot of memory. +/// +/// This `InstVec` stores instructions in their binary SPIR-V form, which is variable sized, allowing us to save a lot +/// of memory and improving cache locality. To make it as convenient to use as a `Vec`, instructions are +/// automatically encoded and decoded on the fly. But to remain performant, decoding of instructions must be cheap, +/// so operands should not allocate any memory and instead should borrow slices from the underlying `Vec` whenever +/// possible. +/// +/// However, storing instructions in a variable-sized way brings the same disadvantages as UTF-8 characters in rust +/// strings. You can't arbitrarily index into an `InstSlice` like you can with a regular slice, as only indices pointing +/// to the beginning of an instruction are valid. You also can't easily replace instructions in the middle of the +/// stream, as changing the size of an instruction requires you to move all following instructions. Although, you can +/// remove instructions by filling them with `OpNop`. +/// +/// # Safety +/// The inner slice of words is assumed to contain valid instructions of the generic `ISA` Instruction Set. May panic if +/// instructions fail to decode, but will not lead to UB, allowing [`Self::from_words_unchecked`] to be safe. +pub struct InstSlice<'a, ISA: InstEncoding> { + raw: RawInstSlice<'a>, + _phantom: PhantomData, +} + +impl<'a, ISA: InstEncoding> InstSlice<'a, ISA> { + /// Create a new [`InstSlice`] from a `&[Word]` safely, by verifying the instructions to be valid in the `ISA`. + #[inline] + pub fn from_words(words: &'a [Word]) -> Result { + Self::from_raw(RawInstSlice::from_words(words)) + } + + /// Create a new [`InstSlice`] from a `&[Word]` without checking whether the words contain valid instructions. + /// + /// See [`crate::vec::InstVec`] # Safety + #[inline] + pub const fn from_words_unchecked(words: &'a [Word]) -> Self { + Self::from_raw_unchecked(RawInstSlice::from_words(words)) + } + + /// Create a new [`InstSlice`] from a [`RawInstSlice`] safely, by verifying the instructions to be valid in the + /// `ISA`. + #[inline] + pub fn from_raw(raw: RawInstSlice<'a>) -> Result { + raw.verify_valid_in_isa::()?; + Ok(Self::from_raw_unchecked(raw)) + } + + /// Create a new [`InstSlice`] from a [`RawInstSlice`] without checking whether the words contain valid + /// instructions. + /// + /// # Safety + /// See [`InstSlice`] #Safety + #[inline] + pub const fn from_raw_unchecked(raw: RawInstSlice<'a>) -> Self { + Self { + raw, + _phantom: PhantomData, + } + } + + /// View self as a [`RawInstSlice`] + #[inline] + pub const fn as_raw(&self) -> RawInstSlice<'a> { + self.raw + } + + /// Iterate over instruction references ([`InstRef`]) without decoding the instruction itself + #[inline] + pub const fn iter_ref(&self) -> InstRefIter<'a, ISA> { + InstRefIter::new(*self) + } + + /// Iterate over all instructions + #[inline] + pub const fn iter(&self) -> InstIter<'a, ISA> { + InstIter::new(*self) + } + + /// disassemble + #[inline] + pub fn dis(&self, opt: DisOptions) -> Result, DecodeError> { + DisInstSlice::new(*self, opt) + } +} + +impl<'a, ISA: InstEncoding> Deref for InstSlice<'a, ISA> { + type Target = RawInstSlice<'a>; + + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl<'a, ISA: InstEncoding> Copy for InstSlice<'a, ISA> {} + +impl<'a, ISA: InstEncoding> Clone for InstSlice<'a, ISA> { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +impl<'a, ISA: InstEncoding> Debug for InstSlice<'a, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InstSlice") + .field("ISA", &ISA::name()) + .field("raw", &self.raw) + .finish() + } +} + +/// A sequence of words that has been pre-processed and may be [`Display`]ed. +/// +/// The `ISA: `[`InstEncoding`] generic determines for which instruction set these Words are disassembled. +pub struct DisInstSlice<'a, ISA: InstEncoding> { + slice: InstSlice<'a, ISA>, + dis: DisContext, +} + +impl<'a, ISA: InstEncoding> DisInstSlice<'a, ISA> { + #[inline] + pub fn new(slice: InstSlice<'a, ISA>, opt: DisOptions) -> Result { + Ok(Self { + slice, + dis: DisContext::new(opt), + }) + } +} + +impl<'a, ISA: InstEncoding> Display for DisInstSlice<'a, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for inst in self.slice.iter() { + writeln!(f, "{}", inst.dis(&self.dis))?; + } + Ok(()) + } +} + +/// An [`Iterator`] yielding a tuple of [`InstOffset`] and [`InstRef`] +pub struct InstOffsetRefIter<'a, ISA: InstEncoding> { + inner: RawInstOffsetRefIter<'a>, + _phantom: PhantomData, +} + +impl<'a, ISA: InstEncoding> InstOffsetRefIter<'a, ISA> { + #[inline] + pub const fn new(slice: InstSlice<'a, ISA>) -> Self { + Self { + inner: RawInstOffsetRefIter::new(slice.as_raw()), + _phantom: PhantomData, + } + } +} + +impl<'a, ISA: InstEncoding> Iterator for InstOffsetRefIter<'a, ISA> { + type Item = (InstOffset, InstRef<'a, ISA>); + + #[inline] + fn next(&mut self) -> Option { + match self.inner.next()? { + Ok((offset, reader)) => Some(( + offset, + match InstRef::from_words_unchecked(reader.to_words()) { + Ok(e) => e, + Err(e) => decode_failed(e), + }, + )), + Err(e) => decode_failed(e), + } + } +} + +impl<'a, ISA: InstEncoding> Clone for InstOffsetRefIter<'a, ISA> { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + _phantom: PhantomData, + } + } +} + +impl<'a, ISA: InstEncoding> Debug for InstOffsetRefIter<'a, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InstOffsetRefIter") + .field("ISA", &ISA::name()) + .field("inner", &self.inner) + .finish() + } +} + +/// An [`Iterator`] yielding [`InstRef`] +pub struct InstRefIter<'a, ISA: InstEncoding>(InstOffsetRefIter<'a, ISA>); + +impl<'a, ISA: InstEncoding> InstRefIter<'a, ISA> { + #[inline] + pub const fn new(slice: InstSlice<'a, ISA>) -> Self { + Self(InstOffsetRefIter::new(slice)) + } +} + +impl<'a, ISA: InstEncoding> Iterator for InstRefIter<'a, ISA> { + type Item = InstRef<'a, ISA>; + + #[inline] + fn next(&mut self) -> Option { + Some(self.0.next()?.1) + } +} + +impl<'a, ISA: InstEncoding> Clone for InstRefIter<'a, ISA> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, ISA: InstEncoding> Debug for InstRefIter<'a, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("InstRefIter").field(&self.0).finish() + } +} + +/// An [`Iterator`] yielding `ISA` +pub struct InstOffsetIter<'a, ISA: InstEncoding>(InstOffsetRefIter<'a, ISA>); + +impl<'a, ISA: InstEncoding> InstOffsetIter<'a, ISA> { + #[inline] + pub const fn new(slice: InstSlice<'a, ISA>) -> Self { + Self(InstOffsetRefIter::new(slice)) + } + + /// Add [`InstOffset`]s to this iterator, akin to `enumerate` + #[inline] + pub const fn with_offsets(self) -> InstOffsetRefIter<'a, ISA> { + self.0 + } +} + +impl<'a, ISA: InstEncoding> Iterator for InstOffsetIter<'a, ISA> { + type Item = (InstOffset, ISA); + + #[inline] + fn next(&mut self) -> Option { + let (offset, inst_ref) = self.0.next()?; + Some((offset, inst_ref.get())) + } +} + +impl<'a, ISA: InstEncoding> Clone for InstOffsetIter<'a, ISA> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, ISA: InstEncoding> Debug for InstOffsetIter<'a, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("InstOffsetIter").field(&self.0).finish() + } +} + +/// An [`Iterator`] yielding `ISA` +pub struct InstIter<'a, ISA: InstEncoding>(InstOffsetIter<'a, ISA>); + +impl<'a, ISA: InstEncoding> InstIter<'a, ISA> { + #[inline] + pub const fn new(slice: InstSlice<'a, ISA>) -> Self { + Self(InstOffsetIter::new(slice)) + } + + /// Add [`InstOffset`]s to this iterator, akin to `enumerate` + #[inline] + pub const fn with_offsets(self) -> InstOffsetIter<'a, ISA> { + self.0 + } +} + +impl<'a, ISA: InstEncoding> Iterator for InstIter<'a, ISA> { + type Item = ISA; + + #[inline] + fn next(&mut self) -> Option { + Some(self.0.next()?.1) + } +} + +impl<'a, ISA: InstEncoding> Clone for InstIter<'a, ISA> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, ISA: InstEncoding> Debug for InstIter<'a, ISA> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("InstIter").field(&self.0).finish() + } +} + +/// A slice of SPIR-V instructions of an arbitrary instruction set. +/// +/// Compared to [`InstSlice`], does not specify the instruction set nor require the inner words to be valid instructions +/// in said instruction set. +#[derive(Copy, Clone, Debug)] +pub struct RawInstSlice<'a>(pub &'a [Word]); + +impl<'a> RawInstSlice<'a> { + pub const fn from_words(words: &'a [Word]) -> Self { + Self(words) + } + + /// Returns the underlying slice of words + pub const fn as_words(&self) -> &'a [Word] { + self.0 + } + + /// Iterate over instruction references ([`InstRef`]) without decoding the instruction itself + pub const fn iter(&self) -> RawInstRefIter<'a> { + RawInstRefIter::new(*self) + } + + /// Verify whether the instructions are valid within the generic `ISA` instruction set + pub fn verify_valid_in_isa(&self) -> Result<(), DecodeError> { + self.iter().try_for_each(|result| { + ISA::decode(result?)?; + Ok(()) + }) + } +} + +impl<'a, ISA: InstEncoding> From> for RawInstSlice<'a> { + fn from(value: InstSlice<'a, ISA>) -> Self { + value.raw + } +} + +/// An [`Iterator`] of [`Result`]s yielding either a tuple of [`InstOffset`] and [`InstReader`], or a [`DecodeError`] +#[derive(Clone, Debug)] +pub struct RawInstOffsetRefIter<'a> { + raw: RawInstSlice<'a>, + offset: InstOffset, +} + +impl<'a> RawInstOffsetRefIter<'a> { + /// Create a new iterator + #[inline] + pub const fn new(raw: RawInstSlice<'a>) -> Self { + Self { + raw, + offset: InstOffset(0), + } + } +} + +impl<'a> Iterator for RawInstOffsetRefIter<'a> { + type Item = Result<(InstOffset, InstReader<'a>), DecodeError>; + + #[inline] + fn next(&mut self) -> Option { + match InstReader::from_words(&self.raw.0[*self.offset..]) { + Ok(inst_reader) => { + let old_offset = self.offset; + *self.offset += inst_reader.len(); + Some(Ok((old_offset, inst_reader))) + } + Err(DecodeError::OutOfInstructions) => None, + Err(e) => Some(Err(e)), + } + } +} + +/// An [`Iterator`] of [`Result`] yielding either an [`InstReader`] or a [`DecodeError`]. +/// +/// Use [`Self::with_offsets`] to also get [`InstOffset`] of the instruction. +#[derive(Clone, Debug)] +pub struct RawInstRefIter<'a>(RawInstOffsetRefIter<'a>); + +impl<'a> RawInstRefIter<'a> { + /// Create a new iterator + #[inline] + pub const fn new(raw: RawInstSlice<'a>) -> Self { + Self(RawInstOffsetRefIter::new(raw)) + } + + /// Add [`InstOffset`]s to this iterator, akin to `enumerate` + pub const fn with_offsets(self) -> RawInstOffsetRefIter<'a> { + self.0 + } +} + +impl<'a> Iterator for RawInstRefIter<'a> { + type Item = Result, DecodeError>; + + #[inline] + fn next(&mut self) -> Option { + match self.0.next() { + Some(Ok((_, inst))) => Some(Ok(inst)), + Some(Err(e)) => Some(Err(e)), + None => None, + } + } +} + +pub trait SkipDecodeErrorIteratorExt { + /// Skip over any decode errors + fn skip_errors(self) -> impl Iterator; +} + +impl>> SkipDecodeErrorIteratorExt for I { + fn skip_errors(self) -> impl Iterator { + self.filter_map(|v| v.ok()) + } +} + +pub trait DecodeIteratorExt { + /// Decode the [`InstReader`]s in this stream into `ISA` Instructions + fn decode(self) -> impl Iterator>; +} + +impl<'a, I: Iterator>> DecodeIteratorExt for I { + fn decode(self) -> impl Iterator> { + self.map(|reader| ISA::decode(reader)) + } +} + +pub trait TryDecodeIteratorExt { + /// Decode the [`InstReader`]s in this stream into `ISA` Instructions, and forward any [`DecodeError`]s + fn try_decode(self) -> impl Iterator>; +} + +impl<'a, I: Iterator, DecodeError>>> TryDecodeIteratorExt for I { + fn try_decode(self) -> impl Iterator> { + self.map(|reader| ISA::decode(reader?)) + } +} diff --git a/crates/rspirv2-types/src/vec.rs b/crates/rspirv2-types/src/vec.rs index 26ce335f..b26a27f5 100644 --- a/crates/rspirv2-types/src/vec.rs +++ b/crates/rspirv2-types/src/vec.rs @@ -1,34 +1,15 @@ use crate::Word; -use crate::binary::{DecodeError, InstOffset, InstReader}; -use crate::dis::{DisModule, DisOptions}; +use crate::binary::{DecodeError, InstOffset}; +use crate::dis::DisOptions; use crate::inst::InstEncoding; +use crate::slice::{DisInstSlice, InstIter, InstRefIter, InstSlice, RawInstSlice}; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; -use std::ops::Deref; /// An `InstVec` works like a `Vec`, but stores instructions in the variable-sized SPIR-V binary form to safe /// on memory size. /// -/// Instruction sets are a giant enum of all instructions, their size is determined by largest instruction plus a -/// discriminant. The largest instructions in the core ISA being made from 10-15 operands of 4 bytes each, giving us an -/// estimated size of around 64 bytes *per instruction*. With the most common instructions only need 2-4 operands or -/// 12-20 bytes, storing that enum in a regular `Vec` is wasting a lot of memory. -/// -/// This `InstVec` stores instructions in their binary SPIR-V form, which is variable sized, allowing us to save a lot -/// of memory and improving cache locality. To make it as convenient to use as a `Vec`, instructions are -/// automatically encoded and decoded on the fly. To remain performant, decoding of instructions must be cheap, -/// so operands should not allocate any memory and instead borrow slices from the underlying `Vec` whenever -/// possible. -/// -/// However, storing instructions in a variable-sized way brings the same disadvantages as UTF-8 characters in -/// [`String`]. You can't arbitrarily index into an `InstVec` like you can with a [`Vec`], as only indices pointing to -/// the beginning of an instruction are valid. You also can't easily replace instructions in the middle of the stream, -/// as changing the size of an instruction requires you to move all following instructions. Although, you can remove -/// instructions by filling them with `OpNop`. -/// -/// # Safety -/// The referenced words must be an encoded instruction that is valid within the `ISA` instruction set. Encountering an -/// invalid instruction will panic. +/// See [`InstSlice`] for details. pub struct InstVec { words: Vec, _phantom: PhantomData, @@ -45,8 +26,9 @@ impl InstVec { } /// Create a new [`InstVec`] from a [`Vec`] safely, by verifying the instructions within to be valid for the `ISA` + #[inline] pub fn from_words(words: Vec) -> Result { - FailableInstOffsetRefIter::::from_words(&words).try_for_each(|v| v.map(|_| ()))?; + RawInstSlice::from_words(words.as_slice()).verify_valid_in_isa::()?; Ok(Self::from_words_unchecked(words)) } @@ -61,7 +43,7 @@ impl InstVec { } } - /// Appends an instruction to the back of the [`InstVec`]. + /// Appends an instruction to the back of the [`InstVec`]. See [`Vec::push`]. #[inline] pub fn push(&mut self, inst: impl Into) -> InstOffset { let offset = InstOffset(self.words.len()); @@ -71,36 +53,56 @@ impl InstVec { offset } - /// Moves all the instruction of `other` into `self`, leaving `other` empty. + /// Moves all the instruction of `other` into `self`, leaving `other` empty. See [`Vec::append`]. #[inline] pub fn append(&mut self, other: &mut Self) { self.words.append(&mut other.words); } - /// Iterate over [`InstRef`] + /// Turn into the inner Vec of words #[inline] - pub fn iter_ref(&self) -> InstRefIter<'_, ISA> { - InstRefIter::from_words_unchecked(&self.words) + pub fn into_vec(self) -> Vec { + self.words } - /// Iterate over instructions - #[inline] - pub fn iter(&self) -> InstIter<'_, ISA> { - InstIter::from_words_unchecked(&self.words) + /// View this [`InstVec`] as a [`InstSlice`] + pub const fn as_slice(&self) -> InstSlice<'_, ISA> { + InstSlice::from_words_unchecked(self.words.as_slice()) } - /// Turn into the inner Vec of words - pub fn into_vec(self) -> Vec { - self.words + /// View this [`InstVec`] as a [`RawInstSlice`] + pub const fn as_raw_slice(&self) -> RawInstSlice<'_> { + RawInstSlice::from_words(self.words.as_slice()) } + // --------------------------------- + // below are equivalent to InstSlice + // --------------------------------- + /// View this [`InstVec`] as a slice of words - pub const fn as_slice(&self) -> &[Word] { - self.words.as_slice() + #[inline] + pub const fn as_words(&self) -> &[Word] { + self.as_slice().as_raw().as_words() } - pub fn dis(&self, opt: DisOptions) -> Result, DecodeError> { - DisModule::new(self.as_slice(), opt) + /// Iterate over instruction references ([`InstRef`]) without decoding the instruction itself + /// + /// [`InstRef`]: crate::inst::InstRef + #[inline] + pub const fn iter_ref(&self) -> InstRefIter<'_, ISA> { + InstRefIter::new(self.as_slice()) + } + + /// Iterate over all instructions + #[inline] + pub const fn iter(&self) -> InstIter<'_, ISA> { + InstIter::new(self.as_slice()) + } + + /// disassemble + #[inline] + pub fn dis(&self, opt: DisOptions) -> Result, DecodeError> { + DisInstSlice::new(self.as_slice(), opt) } } @@ -144,218 +146,3 @@ impl Default for InstVec { Self::new() } } - -/// A reference to a valid instruction encoded in a slice of [`Word`]s. -/// -/// Call [`Self::get`] to get the underlying instruction, as it unfortunately can't implement [`Deref`]. -/// -/// # Safety -/// The referenced words must be an encoded instruction that is valid within the `ISA` instruction set. Encountering an -/// invalid instruction will panic. -pub struct InstRef<'a, ISA: InstEncoding> { - reader: InstReader<'a>, - _phantom: PhantomData, -} - -impl<'a, ISA: InstEncoding> InstRef<'a, ISA> { - #[inline] - pub fn from_words(words: &'a [Word]) -> Result { - Ok(Self { - reader: InstReader::from_words(words)?, - _phantom: PhantomData, - }) - } - - #[inline] - pub fn get(&self) -> ISA { - ISA::decode(**self).unwrap() - } -} - -impl<'a, ISA: InstEncoding> Deref for InstRef<'a, ISA> { - type Target = InstReader<'a>; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.reader - } -} - -impl Copy for InstRef<'_, ISA> {} - -impl Clone for InstRef<'_, ISA> { - fn clone(&self) -> Self { - *self - } -} - -impl Debug for InstRef<'_, ISA> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("InstRef") - .field("reader", &self.reader) - .finish() - } -} - -/// An [`Iterator`] of [`Result`] yielding either `(`[`InstOffset`]`, `[`InstRef`]`)` or [`DecodeError`] -pub struct FailableInstOffsetRefIter<'a, ISA: InstEncoding> { - offset: InstOffset, - words: &'a [Word], - _phantom: PhantomData, -} - -impl<'a, ISA: InstEncoding> FailableInstOffsetRefIter<'a, ISA> { - /// Create a new iterator - #[inline] - pub fn from_words(words: &'a [Word]) -> Self { - Self { - offset: InstOffset::default(), - words, - _phantom: PhantomData, - } - } - - fn debug_fmt(&self, f: &mut Formatter<'_>, name: &str) -> std::fmt::Result { - f.debug_struct(name) - .field("offset", &self.offset) - .field("words", &self.words) - .finish() - } -} - -impl<'a, ISA: InstEncoding> Iterator for FailableInstOffsetRefIter<'a, ISA> { - type Item = Result<(InstOffset, InstRef<'a, ISA>), DecodeError>; - - #[inline] - fn next(&mut self) -> Option { - match InstRef::from_words(&self.words[*self.offset..]) { - Ok(inst_reader) => { - let ret = Some(Ok((self.offset, inst_reader))); - *self.offset += inst_reader.len(); - ret - } - Err(DecodeError::OutOfInstructions) => None, - Err(e) => Some(Err(e)), - } - } -} - -impl Clone for FailableInstOffsetRefIter<'_, ISA> { - fn clone(&self) -> Self { - Self { - offset: self.offset, - words: self.words, - _phantom: PhantomData, - } - } -} - -impl Debug for FailableInstOffsetRefIter<'_, ISA> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.debug_fmt(f, "FailableInstOffsetRefIter") - } -} - -/// An [`Iterator`] of `(`[`InstOffset`]`, `[`InstRef`]`)` that panics when instructions fail to decode -pub struct InstRefOffsetIter<'a, ISA: InstEncoding>(FailableInstOffsetRefIter<'a, ISA>); - -impl<'a, ISA: InstEncoding> InstRefOffsetIter<'a, ISA> { - /// Create a new iterator, trusting that the supplied Words are valid in the `ISA` - #[inline] - pub fn from_words_unchecked(words: &'a [Word]) -> Self { - Self(FailableInstOffsetRefIter::from_words(words)) - } -} - -impl<'a, ISA: InstEncoding> Iterator for InstRefOffsetIter<'a, ISA> { - type Item = (InstOffset, InstRef<'a, ISA>); - - #[inline] - fn next(&mut self) -> Option { - Some( - self.0 - .next()? - .expect("Instruction decoding failed unexpectingly"), - ) - } -} - -impl Clone for InstRefOffsetIter<'_, ISA> { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl Debug for InstRefOffsetIter<'_, ISA> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.0.debug_fmt(f, "InstRefOffsetIter") - } -} - -/// An [`Iterator`] of [`InstRef`] that panics when instructions fail to decode -pub struct InstRefIter<'a, ISA: InstEncoding>(InstRefOffsetIter<'a, ISA>); - -impl<'a, ISA: InstEncoding> InstRefIter<'a, ISA> { - /// Create a new iterator, trusting that the supplied Words are valid in the `ISA` - #[inline] - pub fn from_words_unchecked(words: &'a [Word]) -> Self { - Self(InstRefOffsetIter::from_words_unchecked(words)) - } - - pub fn with_offsets(self) -> InstRefOffsetIter<'a, ISA> { - self.0 - } -} - -impl<'a, ISA: InstEncoding> Iterator for InstRefIter<'a, ISA> { - type Item = InstRef<'a, ISA>; - - #[inline] - fn next(&mut self) -> Option { - Some(self.0.next()?.1) - } -} - -impl Clone for InstRefIter<'_, ISA> { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl Debug for InstRefIter<'_, ISA> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.0.0.debug_fmt(f, "InstRefIter") - } -} - -/// An [`Iterator`] of instructions that panics when instructions fail to decode -pub struct InstIter<'a, ISA: InstEncoding>(InstRefOffsetIter<'a, ISA>); - -impl<'a, ISA: InstEncoding> InstIter<'a, ISA> { - /// Create a new iterator, trusting that the supplied Words are valid in the `ISA` - #[inline] - pub fn from_words_unchecked(words: &'a [Word]) -> Self { - Self(InstRefOffsetIter::from_words_unchecked(words)) - } -} - -impl<'a, ISA: InstEncoding> Iterator for InstIter<'a, ISA> { - type Item = ISA; - - #[inline] - fn next(&mut self) -> Option { - Some(self.0.next()?.1.get()) - } -} - -impl Clone for InstIter<'_, ISA> { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl Debug for InstIter<'_, ISA> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.0.0.debug_fmt(f, "InstIter") - } -} diff --git a/crates/rspirv2/tests/decode_encode.rs b/crates/rspirv2/tests/decode_encode.rs index 251e6e2d..57b4078e 100644 --- a/crates/rspirv2/tests/decode_encode.rs +++ b/crates/rspirv2/tests/decode_encode.rs @@ -28,6 +28,6 @@ fn roundtrip_spv(spv: &[u8]) -> anyhow::Result<()> { for inst in module.iter() { inst.encode(&mut writer)?; } - assert_eq!(module.as_slice(), writer.as_slice()); + assert_eq!(module.as_words(), writer.as_slice()); Ok(()) } diff --git a/crates/rspirv2/tests/inst_encoding.rs b/crates/rspirv2/tests/inst_encoding.rs index 91fce575..e7665e1c 100644 --- a/crates/rspirv2/tests/inst_encoding.rs +++ b/crates/rspirv2/tests/inst_encoding.rs @@ -10,7 +10,7 @@ use rspirv2::operand::{IdRef, IdResult, IdResultType, LiteralConst, LiteralInteg use rspirv2_types::Word; use rspirv2_types::binary::EncodeError; use rspirv2_types::inst::{Inst, InstEncoding}; -use rspirv2_types::vec::InstIter; +use rspirv2_types::slice::InstSlice; fn roundtrip(inst: T) where @@ -18,7 +18,7 @@ where { let mut spirv = Vec::::new(); inst.encode(&mut spirv).unwrap(); - let mut iter = InstIter::::from_words_unchecked(&spirv); + let mut iter = InstSlice::from_words_unchecked(&spirv).iter(); let decoded = iter.next().unwrap(); assert!(iter.next().is_none()); assert_eq!(CoreInstSet::from(inst), decoded); @@ -81,7 +81,7 @@ fn test_op_constant_sequence() -> anyhow::Result<()> { c2.encode(&mut spirv)?; c3.encode(&mut spirv)?; - let mut iter = InstIter::::from_words_unchecked(&spirv); + let mut iter = InstSlice::from_words_unchecked(&spirv).iter(); assert_eq!(c1, iter.next().unwrap()); assert_eq!(c2, iter.next().unwrap()); assert_eq!(c3, iter.next().unwrap()); @@ -158,7 +158,7 @@ fn test_non_trivial_code() -> anyhow::Result<()> { }; spirv.push_mut(&mut store_op)?; - let mut iter = InstIter::::from_words_unchecked(&spirv.words); + let mut iter = InstSlice::from_words_unchecked(&spirv.words).iter(); assert_eq!(Some(CoreInstSet::from(u32_op)), iter.next()); assert_eq!(Some(CoreInstSet::from(u32_1_op)), iter.next()); assert_eq!(Some(CoreInstSet::from(add_op)), iter.next()); From 3bb62fea44192dd92c10a02e91096ad8828bd206 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 26 Mar 2026 15:06:15 +0100 Subject: [PATCH 09/10] InstVec: add `RawInstVec` mirroring `InstVec` and `RawInstSlice` --- crates/rspirv2-types/src/vec.rs | 162 +++++++++++++++++++++++++++----- 1 file changed, 137 insertions(+), 25 deletions(-) diff --git a/crates/rspirv2-types/src/vec.rs b/crates/rspirv2-types/src/vec.rs index b26a27f5..07d3556f 100644 --- a/crates/rspirv2-types/src/vec.rs +++ b/crates/rspirv2-types/src/vec.rs @@ -1,8 +1,8 @@ use crate::Word; -use crate::binary::{DecodeError, InstOffset}; +use crate::binary::{DecodeError, InstOffset, WordWriter}; use crate::dis::DisOptions; use crate::inst::InstEncoding; -use crate::slice::{DisInstSlice, InstIter, InstRefIter, InstSlice, RawInstSlice}; +use crate::slice::{DisInstSlice, InstIter, InstRefIter, InstSlice, RawInstRefIter, RawInstSlice}; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; @@ -11,7 +11,7 @@ use std::marker::PhantomData; /// /// See [`InstSlice`] for details. pub struct InstVec { - words: Vec, + raw: RawInstVec, _phantom: PhantomData, } @@ -20,7 +20,7 @@ impl InstVec { #[inline] pub const fn new() -> Self { Self { - words: Vec::new(), + raw: RawInstVec::new(), _phantom: PhantomData, } } @@ -28,8 +28,15 @@ impl InstVec { /// Create a new [`InstVec`] from a [`Vec`] safely, by verifying the instructions within to be valid for the `ISA` #[inline] pub fn from_words(words: Vec) -> Result { - RawInstSlice::from_words(words.as_slice()).verify_valid_in_isa::()?; - Ok(Self::from_words_unchecked(words)) + Self::from_raw(RawInstVec::from_words(words)) + } + + /// Create a new [`InstVec`] from a [`RawInstVec`] safely, by verifying the instructions within to be valid for the + /// `ISA` + #[inline] + pub fn from_raw(raw: RawInstVec) -> Result { + raw.as_slice().verify_valid_in_isa::()?; + Ok(Self::from_raw_unchecked(raw)) } /// Create a new [`InstVec`] from a [`Vec`] of words without checking whether the words contain valid instructions. @@ -37,8 +44,16 @@ impl InstVec { /// See [`InstVec`] # Safety #[inline] pub fn from_words_unchecked(words: Vec) -> Self { + Self::from_raw_unchecked(RawInstVec::from_words(words)) + } + + /// Create a new [`InstVec`] from a [`RawInstVec`] without checking whether the words contain valid instructions. + /// + /// See [`InstVec`] # Safety + #[inline] + pub fn from_raw_unchecked(raw: RawInstVec) -> Self { Self { - words, + raw, _phantom: PhantomData, } } @@ -46,33 +61,37 @@ impl InstVec { /// Appends an instruction to the back of the [`InstVec`]. See [`Vec::push`]. #[inline] pub fn push(&mut self, inst: impl Into) -> InstOffset { - let offset = InstOffset(self.words.len()); - inst.into() - .encode(&mut self.words) - .expect("error while encoding"); + let offset = InstOffset(self.raw.0.len()); + inst.into().encode(&mut self.raw).expect("error while encoding"); offset } /// Moves all the instruction of `other` into `self`, leaving `other` empty. See [`Vec::append`]. #[inline] pub fn append(&mut self, other: &mut Self) { - self.words.append(&mut other.words); + self.raw.append(&mut other.raw); + } + + /// Turn into the inner Vec of words + #[inline] + pub fn into_raw(self) -> RawInstVec { + self.raw } /// Turn into the inner Vec of words #[inline] pub fn into_vec(self) -> Vec { - self.words + self.raw.0 } /// View this [`InstVec`] as a [`InstSlice`] pub const fn as_slice(&self) -> InstSlice<'_, ISA> { - InstSlice::from_words_unchecked(self.words.as_slice()) + InstSlice::from_raw_unchecked(self.raw.as_slice()) } /// View this [`InstVec`] as a [`RawInstSlice`] pub const fn as_raw_slice(&self) -> RawInstSlice<'_> { - RawInstSlice::from_words(self.words.as_slice()) + self.raw.as_slice() } // --------------------------------- @@ -108,26 +127,21 @@ impl InstVec { impl Extend for InstVec { fn extend>(&mut self, iter: T) { - for inst in iter { - self.push(inst); - } + self.raw.extend(iter); } } impl FromIterator for InstVec { fn from_iter>(iter: T) -> Self { - let mut s = Self::new(); - for inst in iter { - s.push(inst); - } - s + Self::from_raw_unchecked(RawInstVec::from_iter(iter)) } } impl Clone for InstVec { + #[inline] fn clone(&self) -> Self { Self { - words: self.words.clone(), + raw: self.raw.clone(), _phantom: PhantomData, } } @@ -136,13 +150,111 @@ impl Clone for InstVec { impl Debug for InstVec { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("InstVec") - .field("words", &self.words) + .field("ISA", &ISA::name()) + .field("raw", &self.raw) .finish() } } impl Default for InstVec { + #[inline] fn default() -> Self { Self::new() } } + +/// A slice of SPIR-V instructions of an arbitrary instruction set. +/// +/// Compared to [`InstVec`], does not specify the instruction set nor require the inner words to be valid instructions +/// in said instruction set. +#[derive(Clone, Debug, Default)] +pub struct RawInstVec(pub Vec); + +impl RawInstVec { + /// Create a new empty [`RawInstVec`] + #[inline] + pub const fn new() -> Self { + Self(Vec::new()) + } + + /// Create a new [`RawInstVec`] from a [`Vec`] + #[inline] + pub fn from_words(words: Vec) -> Self { + Self(words) + } + + /// Appends an instruction to the back of the [`RawInstVec`], like [`Vec::push`]. + #[inline] + pub fn push(&mut self, inst: impl InstEncoding) -> InstOffset { + let offset = InstOffset(self.0.len()); + inst.encode(&mut self.0).expect("error while encoding"); + offset + } + + /// Moves all the instruction of `other` into `self`, leaving `other` empty. See [`Vec::append`]. + #[inline] + pub fn append(&mut self, other: &mut Self) { + self.0.append(&mut other.0); + } + + /// Turn into the inner Vec of words + #[inline] + pub fn into_vec(self) -> Vec { + self.0 + } + + /// View this [`RawInstVec`] as a [`RawInstSlice`] + pub const fn as_slice(&self) -> RawInstSlice<'_> { + RawInstSlice::from_words(self.0.as_slice()) + } + + // --------------------------------- + // below are equivalent to InstSlice + // --------------------------------- + + /// View this [`RawInstVec`] as a slice of words + #[inline] + pub const fn as_words(&self) -> &[Word] { + self.as_slice().as_words() + } + + /// Iterate over instruction references ([`InstRef`]) without decoding the instruction itself + pub const fn iter(&self) -> RawInstRefIter<'_> { + self.as_slice().iter() + } +} + +impl Extend for RawInstVec { + fn extend>(&mut self, iter: T) { + for inst in iter { + self.push(inst); + } + } +} + +impl FromIterator for RawInstVec { + fn from_iter>(iter: T) -> Self { + let mut s = Self::new(); + for inst in iter { + s.push(inst); + } + s + } +} + +impl WordWriter for RawInstVec { + #[inline] + fn write(&mut self, word: Word) { + self.0.write(word); + } + + #[inline] + fn write_iter(&mut self, iter: impl IntoIterator) { + self.0.write_iter(iter); + } + + #[inline] + fn inst_reserve(&mut self, len: usize) { + self.0.inst_reserve(len); + } +} From 2e8b8713b27b01979ba832835e01b251c50e9a94 Mon Sep 17 00:00:00 2001 From: firestar99 Date: Thu, 26 Mar 2026 15:45:57 +0100 Subject: [PATCH 10/10] InstVec: make `InstSlice` wrap around `[Word]` instead of `&[Word]`, allows `impl Deref for InstVec` --- crates/rspirv2-types/src/slice.rs | 104 ++++++++++++++---------------- crates/rspirv2-types/src/vec.rs | 79 +++++++++-------------- 2 files changed, 81 insertions(+), 102 deletions(-) diff --git a/crates/rspirv2-types/src/slice.rs b/crates/rspirv2-types/src/slice.rs index 7e423daa..def6186f 100644 --- a/crates/rspirv2-types/src/slice.rs +++ b/crates/rspirv2-types/src/slice.rs @@ -33,15 +33,16 @@ pub fn decode_failed(e: DecodeError) -> ! { /// # Safety /// The inner slice of words is assumed to contain valid instructions of the generic `ISA` Instruction Set. May panic if /// instructions fail to decode, but will not lead to UB, allowing [`Self::from_words_unchecked`] to be safe. -pub struct InstSlice<'a, ISA: InstEncoding> { - raw: RawInstSlice<'a>, +#[repr(transparent)] +pub struct InstSlice { _phantom: PhantomData, + raw: RawInstSlice, } -impl<'a, ISA: InstEncoding> InstSlice<'a, ISA> { +impl InstSlice { /// Create a new [`InstSlice`] from a `&[Word]` safely, by verifying the instructions to be valid in the `ISA`. #[inline] - pub fn from_words(words: &'a [Word]) -> Result { + pub fn from_words(words: &[Word]) -> Result<&Self, DecodeError> { Self::from_raw(RawInstSlice::from_words(words)) } @@ -49,14 +50,14 @@ impl<'a, ISA: InstEncoding> InstSlice<'a, ISA> { /// /// See [`crate::vec::InstVec`] # Safety #[inline] - pub const fn from_words_unchecked(words: &'a [Word]) -> Self { + pub const fn from_words_unchecked(words: &[Word]) -> &Self { Self::from_raw_unchecked(RawInstSlice::from_words(words)) } /// Create a new [`InstSlice`] from a [`RawInstSlice`] safely, by verifying the instructions to be valid in the /// `ISA`. #[inline] - pub fn from_raw(raw: RawInstSlice<'a>) -> Result { + pub fn from_raw(raw: &RawInstSlice) -> Result<&Self, DecodeError> { raw.verify_valid_in_isa::()?; Ok(Self::from_raw_unchecked(raw)) } @@ -67,60 +68,51 @@ impl<'a, ISA: InstEncoding> InstSlice<'a, ISA> { /// # Safety /// See [`InstSlice`] #Safety #[inline] - pub const fn from_raw_unchecked(raw: RawInstSlice<'a>) -> Self { - Self { - raw, - _phantom: PhantomData, - } + pub const fn from_raw_unchecked(raw: &RawInstSlice) -> &Self { + // Safety: + // * slices have the same layout + // * Self is `#[repr(transparent)]` to `RawInstSlice` + unsafe { core::mem::transmute(raw) } } /// View self as a [`RawInstSlice`] #[inline] - pub const fn as_raw(&self) -> RawInstSlice<'a> { - self.raw + pub const fn as_raw(&self) -> &RawInstSlice { + &self.raw } /// Iterate over instruction references ([`InstRef`]) without decoding the instruction itself #[inline] - pub const fn iter_ref(&self) -> InstRefIter<'a, ISA> { - InstRefIter::new(*self) + pub const fn iter_ref(&self) -> InstRefIter<'_, ISA> { + InstRefIter::new(self) } /// Iterate over all instructions #[inline] - pub const fn iter(&self) -> InstIter<'a, ISA> { - InstIter::new(*self) + pub const fn iter(&self) -> InstIter<'_, ISA> { + InstIter::new(self) } /// disassemble #[inline] - pub fn dis(&self, opt: DisOptions) -> Result, DecodeError> { - DisInstSlice::new(*self, opt) + pub fn dis(&self, opt: DisOptions) -> Result, DecodeError> { + DisInstSlice::new(self, opt) } } -impl<'a, ISA: InstEncoding> Deref for InstSlice<'a, ISA> { - type Target = RawInstSlice<'a>; +impl Deref for InstSlice { + type Target = RawInstSlice; fn deref(&self) -> &Self::Target { - &self.raw + self.as_raw() } } -impl<'a, ISA: InstEncoding> Copy for InstSlice<'a, ISA> {} - -impl<'a, ISA: InstEncoding> Clone for InstSlice<'a, ISA> { - #[inline] - fn clone(&self) -> Self { - *self - } -} - -impl<'a, ISA: InstEncoding> Debug for InstSlice<'a, ISA> { +impl Debug for InstSlice { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("InstSlice") .field("ISA", &ISA::name()) - .field("raw", &self.raw) + .field("raw", &&self.raw) .finish() } } @@ -129,13 +121,13 @@ impl<'a, ISA: InstEncoding> Debug for InstSlice<'a, ISA> { /// /// The `ISA: `[`InstEncoding`] generic determines for which instruction set these Words are disassembled. pub struct DisInstSlice<'a, ISA: InstEncoding> { - slice: InstSlice<'a, ISA>, + slice: &'a InstSlice, dis: DisContext, } impl<'a, ISA: InstEncoding> DisInstSlice<'a, ISA> { #[inline] - pub fn new(slice: InstSlice<'a, ISA>, opt: DisOptions) -> Result { + pub fn new(slice: &'a InstSlice, opt: DisOptions) -> Result { Ok(Self { slice, dis: DisContext::new(opt), @@ -160,7 +152,7 @@ pub struct InstOffsetRefIter<'a, ISA: InstEncoding> { impl<'a, ISA: InstEncoding> InstOffsetRefIter<'a, ISA> { #[inline] - pub const fn new(slice: InstSlice<'a, ISA>) -> Self { + pub const fn new(slice: &'a InstSlice) -> Self { Self { inner: RawInstOffsetRefIter::new(slice.as_raw()), _phantom: PhantomData, @@ -209,7 +201,7 @@ pub struct InstRefIter<'a, ISA: InstEncoding>(InstOffsetRefIter<'a, ISA>); impl<'a, ISA: InstEncoding> InstRefIter<'a, ISA> { #[inline] - pub const fn new(slice: InstSlice<'a, ISA>) -> Self { + pub const fn new(slice: &'a InstSlice) -> Self { Self(InstOffsetRefIter::new(slice)) } } @@ -240,7 +232,7 @@ pub struct InstOffsetIter<'a, ISA: InstEncoding>(InstOffsetRefIter<'a, ISA>); impl<'a, ISA: InstEncoding> InstOffsetIter<'a, ISA> { #[inline] - pub const fn new(slice: InstSlice<'a, ISA>) -> Self { + pub const fn new(slice: &'a InstSlice) -> Self { Self(InstOffsetRefIter::new(slice)) } @@ -278,7 +270,7 @@ pub struct InstIter<'a, ISA: InstEncoding>(InstOffsetIter<'a, ISA>); impl<'a, ISA: InstEncoding> InstIter<'a, ISA> { #[inline] - pub const fn new(slice: InstSlice<'a, ISA>) -> Self { + pub const fn new(slice: &'a InstSlice) -> Self { Self(InstOffsetIter::new(slice)) } @@ -314,22 +306,26 @@ impl<'a, ISA: InstEncoding> Debug for InstIter<'a, ISA> { /// /// Compared to [`InstSlice`], does not specify the instruction set nor require the inner words to be valid instructions /// in said instruction set. -#[derive(Copy, Clone, Debug)] -pub struct RawInstSlice<'a>(pub &'a [Word]); +#[repr(transparent)] +#[derive(Debug)] +pub struct RawInstSlice(pub [Word]); -impl<'a> RawInstSlice<'a> { - pub const fn from_words(words: &'a [Word]) -> Self { - Self(words) +impl RawInstSlice { + pub const fn from_words(words: &[Word]) -> &Self { + // Safety: + // * slices have the same layout + // * Self is `#[repr(transparent)]` to `[Word]` + unsafe { core::mem::transmute(words) } } /// Returns the underlying slice of words - pub const fn as_words(&self) -> &'a [Word] { - self.0 + pub const fn as_words(&self) -> &[Word] { + &self.0 } /// Iterate over instruction references ([`InstRef`]) without decoding the instruction itself - pub const fn iter(&self) -> RawInstRefIter<'a> { - RawInstRefIter::new(*self) + pub const fn iter(&self) -> RawInstRefIter<'_> { + RawInstRefIter::new(self) } /// Verify whether the instructions are valid within the generic `ISA` instruction set @@ -341,23 +337,23 @@ impl<'a> RawInstSlice<'a> { } } -impl<'a, ISA: InstEncoding> From> for RawInstSlice<'a> { - fn from(value: InstSlice<'a, ISA>) -> Self { - value.raw +impl<'a, ISA: InstEncoding> From<&'a InstSlice> for &'a RawInstSlice { + fn from(value: &'a InstSlice) -> Self { + value.as_raw() } } /// An [`Iterator`] of [`Result`]s yielding either a tuple of [`InstOffset`] and [`InstReader`], or a [`DecodeError`] #[derive(Clone, Debug)] pub struct RawInstOffsetRefIter<'a> { - raw: RawInstSlice<'a>, + raw: &'a RawInstSlice, offset: InstOffset, } impl<'a> RawInstOffsetRefIter<'a> { /// Create a new iterator #[inline] - pub const fn new(raw: RawInstSlice<'a>) -> Self { + pub const fn new(raw: &'a RawInstSlice) -> Self { Self { raw, offset: InstOffset(0), @@ -391,7 +387,7 @@ pub struct RawInstRefIter<'a>(RawInstOffsetRefIter<'a>); impl<'a> RawInstRefIter<'a> { /// Create a new iterator #[inline] - pub const fn new(raw: RawInstSlice<'a>) -> Self { + pub const fn new(raw: &'a RawInstSlice) -> Self { Self(RawInstOffsetRefIter::new(raw)) } diff --git a/crates/rspirv2-types/src/vec.rs b/crates/rspirv2-types/src/vec.rs index 07d3556f..dad1ead1 100644 --- a/crates/rspirv2-types/src/vec.rs +++ b/crates/rspirv2-types/src/vec.rs @@ -1,10 +1,10 @@ use crate::Word; use crate::binary::{DecodeError, InstOffset, WordWriter}; -use crate::dis::DisOptions; -use crate::inst::InstEncoding; -use crate::slice::{DisInstSlice, InstIter, InstRefIter, InstSlice, RawInstRefIter, RawInstSlice}; +use crate::inst::{Inst, InstEncoding}; +use crate::slice::{InstSlice, RawInstSlice}; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; +use std::ops::Deref; /// An `InstVec` works like a `Vec`, but stores instructions in the variable-sized SPIR-V binary form to safe /// on memory size. @@ -61,9 +61,13 @@ impl InstVec { /// Appends an instruction to the back of the [`InstVec`]. See [`Vec::push`]. #[inline] pub fn push(&mut self, inst: impl Into) -> InstOffset { - let offset = InstOffset(self.raw.0.len()); - inst.into().encode(&mut self.raw).expect("error while encoding"); - offset + self.raw.push(inst.into()) + } + + /// Appends an instruction to the back of the [`InstVec`]. See [`Vec::push`]. + #[inline] + pub fn push_inst>(&mut self, inst: I) -> I::MaybeIdResult { + self.raw.push_inst(inst) } /// Moves all the instruction of `other` into `self`, leaving `other` empty. See [`Vec::append`]. @@ -85,43 +89,21 @@ impl InstVec { } /// View this [`InstVec`] as a [`InstSlice`] - pub const fn as_slice(&self) -> InstSlice<'_, ISA> { + pub const fn as_slice(&self) -> &InstSlice { InstSlice::from_raw_unchecked(self.raw.as_slice()) } /// View this [`InstVec`] as a [`RawInstSlice`] - pub const fn as_raw_slice(&self) -> RawInstSlice<'_> { + pub const fn as_raw_slice(&self) -> &RawInstSlice { self.raw.as_slice() } +} - // --------------------------------- - // below are equivalent to InstSlice - // --------------------------------- - - /// View this [`InstVec`] as a slice of words - #[inline] - pub const fn as_words(&self) -> &[Word] { - self.as_slice().as_raw().as_words() - } - - /// Iterate over instruction references ([`InstRef`]) without decoding the instruction itself - /// - /// [`InstRef`]: crate::inst::InstRef - #[inline] - pub const fn iter_ref(&self) -> InstRefIter<'_, ISA> { - InstRefIter::new(self.as_slice()) - } - - /// Iterate over all instructions - #[inline] - pub const fn iter(&self) -> InstIter<'_, ISA> { - InstIter::new(self.as_slice()) - } +impl Deref for InstVec { + type Target = InstSlice; - /// disassemble - #[inline] - pub fn dis(&self, opt: DisOptions) -> Result, DecodeError> { - DisInstSlice::new(self.as_slice(), opt) + fn deref(&self) -> &Self::Target { + self.as_slice() } } @@ -191,6 +173,14 @@ impl RawInstVec { offset } + /// Appends an instruction to the back of the [`RawInstVec`]. See [`Vec::push`]. + #[inline] + pub fn push_inst(&mut self, mut inst: I) -> I::MaybeIdResult { + let id_result = *inst.id_result(); + self.push(inst); + id_result + } + /// Moves all the instruction of `other` into `self`, leaving `other` empty. See [`Vec::append`]. #[inline] pub fn append(&mut self, other: &mut Self) { @@ -204,23 +194,16 @@ impl RawInstVec { } /// View this [`RawInstVec`] as a [`RawInstSlice`] - pub const fn as_slice(&self) -> RawInstSlice<'_> { + pub const fn as_slice(&self) -> &RawInstSlice { RawInstSlice::from_words(self.0.as_slice()) } +} - // --------------------------------- - // below are equivalent to InstSlice - // --------------------------------- - - /// View this [`RawInstVec`] as a slice of words - #[inline] - pub const fn as_words(&self) -> &[Word] { - self.as_slice().as_words() - } +impl Deref for RawInstVec { + type Target = RawInstSlice; - /// Iterate over instruction references ([`InstRef`]) without decoding the instruction itself - pub const fn iter(&self) -> RawInstRefIter<'_> { - self.as_slice().iter() + fn deref(&self) -> &Self::Target { + self.as_slice() } }