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/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/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/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/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/binary/reader.rs b/crates/rspirv2-types/src/binary/reader.rs index 82da5da1..29222531 100644 --- a/crates/rspirv2-types/src/binary/reader.rs +++ b/crates/rspirv2-types/src/binary/reader.rs @@ -2,28 +2,31 @@ 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}; -/// Reader for an entire module -pub struct ModuleReader<'a> { - words: &'a [Word], - offset: usize, +/// 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<'a> ModuleReader<'a> { - pub fn new(words: &'a [Word]) -> Self { - Self { words, offset: 0 } +impl DerefMut for InstOffset { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.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), - } +impl Display for InstOffset { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) } } diff --git a/crates/rspirv2-types/src/dis.rs b/crates/rspirv2-types/src/dis.rs index 82aff00c..38cef186 100644 --- a/crates/rspirv2-types/src/dis.rs +++ b/crates/rspirv2-types/src/dis.rs @@ -1,13 +1,9 @@ //! Module for Disassembly -use crate::Word; -use crate::binary::{DecodeError, ModuleReader}; -use crate::inst::InstEncoding; use crate::operand::LiteralStringEscape; use anstyle::Style; use std::cell::Cell; use std::fmt::{Display, Formatter}; -use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; /// Options for disassembly @@ -100,36 +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 { - 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)?; - 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 ba87ae19..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; @@ -15,6 +18,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>; @@ -47,6 +55,26 @@ pub trait InstEncoding: Sized + Debug + Eq { } } +impl InstEncoding for () { + fn name() -> &'static str { + "()" + } + + 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> { @@ -92,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 aceb847c..f452cba3 100644 --- a/crates/rspirv2-types/src/lib.rs +++ b/crates/rspirv2-types/src/lib.rs @@ -6,6 +6,8 @@ pub mod inst; pub mod meta; pub mod module; pub mod operand; +pub mod slice; +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..6b9791dc 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 crate::{Word, cast_words_to_ne_bytes}; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; +use std::ops::Deref; pub const SPIRV_MAGIC: Word = Word(0x07230203); @@ -128,25 +129,37 @@ 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. + /// + /// 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 { + 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 +174,61 @@ 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) - } + 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 header(&self) -> SpirvHeader { - SpirvHeader::from_slice(self.0.as_slice()) + 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 instructions(&self) -> &[Word] { - &self.0[5..] + /// 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), + } } - pub fn reader(&self) -> ModuleReader<'_> { - ModuleReader::new(self.instructions()) + /// 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), + } } +} - pub fn dis( - &self, - opt: DisOptions, - ) -> Result, DecodeError> { - DisModule::new(self.instructions(), opt) +impl Deref for Module { + type Target = InstVec; + + fn deref(&self) -> &Self::Target { + &self.inst } } @@ -196,6 +239,7 @@ pub enum ParseError { HeaderReservedNotZero(SpirvHeader), BytesTooShort(usize), BytesNotMultipleOfFour(usize), + DecodeError(DecodeError), } impl Display for ParseError { @@ -219,6 +263,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 +276,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 +334,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/slice.rs b/crates/rspirv2-types/src/slice.rs new file mode 100644 index 00000000..def6186f --- /dev/null +++ b/crates/rspirv2-types/src/slice.rs @@ -0,0 +1,444 @@ +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. +#[repr(transparent)] +pub struct InstSlice { + _phantom: PhantomData, + raw: RawInstSlice, +} + +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: &[Word]) -> Result<&Self, DecodeError> { + 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: &[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) -> Result<&Self, DecodeError> { + 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) -> &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 { + &self.raw + } + + /// Iterate over instruction references ([`InstRef`]) without decoding the instruction itself + #[inline] + pub const fn iter_ref(&self) -> InstRefIter<'_, ISA> { + InstRefIter::new(self) + } + + /// Iterate over all instructions + #[inline] + pub const fn iter(&self) -> InstIter<'_, ISA> { + InstIter::new(self) + } + + /// disassemble + #[inline] + pub fn dis(&self, opt: DisOptions) -> Result, DecodeError> { + DisInstSlice::new(self, opt) + } +} + +impl Deref for InstSlice { + type Target = RawInstSlice; + + fn deref(&self) -> &Self::Target { + self.as_raw() + } +} + +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) + .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: &'a InstSlice, + dis: DisContext, +} + +impl<'a, ISA: InstEncoding> DisInstSlice<'a, ISA> { + #[inline] + pub fn new(slice: &'a InstSlice, 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: &'a InstSlice) -> 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: &'a InstSlice) -> 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: &'a InstSlice) -> 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: &'a InstSlice) -> 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. +#[repr(transparent)] +#[derive(Debug)] +pub struct RawInstSlice(pub [Word]); + +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) -> &[Word] { + &self.0 + } + + /// Iterate over instruction references ([`InstRef`]) without decoding the instruction itself + pub const fn iter(&self) -> RawInstRefIter<'_> { + 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<&'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: &'a RawInstSlice, + offset: InstOffset, +} + +impl<'a> RawInstOffsetRefIter<'a> { + /// Create a new iterator + #[inline] + pub const fn new(raw: &'a RawInstSlice) -> 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: &'a RawInstSlice) -> 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 new file mode 100644 index 00000000..dad1ead1 --- /dev/null +++ b/crates/rspirv2-types/src/vec.rs @@ -0,0 +1,243 @@ +use crate::Word; +use crate::binary::{DecodeError, InstOffset, WordWriter}; +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. +/// +/// See [`InstSlice`] for details. +pub struct InstVec { + raw: RawInstVec, + _phantom: PhantomData, +} + +impl InstVec { + /// Create a new empty [`InstVec`] + #[inline] + pub const fn new() -> Self { + Self { + raw: RawInstVec::new(), + _phantom: PhantomData, + } + } + + /// 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 { + 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. + /// + /// 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 { + raw, + _phantom: PhantomData, + } + } + + /// Appends an instruction to the back of the [`InstVec`]. See [`Vec::push`]. + #[inline] + pub fn push(&mut self, inst: impl Into) -> InstOffset { + 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`]. + #[inline] + pub fn append(&mut self, other: &mut Self) { + 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.raw.0 + } + + /// View this [`InstVec`] as a [`InstSlice`] + 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 { + self.raw.as_slice() + } +} + +impl Deref for InstVec { + type Target = InstSlice; + + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl Extend for InstVec { + fn extend>(&mut self, iter: T) { + self.raw.extend(iter); + } +} + +impl FromIterator for InstVec { + fn from_iter>(iter: T) -> Self { + Self::from_raw_unchecked(RawInstVec::from_iter(iter)) + } +} + +impl Clone for InstVec { + #[inline] + fn clone(&self) -> Self { + Self { + raw: self.raw.clone(), + _phantom: PhantomData, + } + } +} + +impl Debug for InstVec { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InstVec") + .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 + } + + /// 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) { + 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()) + } +} + +impl Deref for RawInstVec { + type Target = RawInstSlice; + + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +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); + } +} 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) + ); + } + } +} 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), diff --git a/crates/rspirv2/tests/decode_encode.rs b/crates/rspirv2/tests/decode_encode.rs index 4e157c34..57b4078e 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_words(), 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/rspirv2/tests/inst_encoding.rs b/crates/rspirv2/tests/inst_encoding.rs index 059e2fed..e7665e1c 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::slice::InstSlice; -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 = InstSlice::from_words_unchecked(&spirv).iter(); + 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 = InstSlice::from_words_unchecked(&spirv).iter(); + 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 = 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()); + 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(()) } 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(()) } 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*