Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ jobs:
run: RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps
- name: cargo clippy
run: cargo clippy --all-targets -- -D warnings
- name: grammar-parser without codegen feature
run: cargo check -p rspirv2-grammar-parser

defaults:
run:
Expand Down
2 changes: 2 additions & 0 deletions crates/grammar-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ pub mod timer;
#[cfg(feature = "codegen")]
pub mod codegen;

#[cfg(feature = "codegen")]
pub use proc_macro2;
#[cfg(feature = "codegen")]
pub use quote;
2 changes: 1 addition & 1 deletion crates/rspirv2-types/src/operand/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ impl Display for IdResultWriter<'_> {
let style = ctx.color(ID_RESULT_COLOR);
write!(f, "{}{style}%{}{style:#} = ", &ctx.padding[..pad_len], name)
} else {
write!(f, "{}", &ctx.padding)
write!(f, "{}", ctx.padding)
}
}
}
Expand Down
46 changes: 15 additions & 31 deletions crates/rspirv2-types/src/operand/literal_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,11 @@ mod tests {

#[test]
fn test_str() -> anyhow::Result<()> {
roundtrip("abc", &[[b'a', b'b', b'c', 0]])?;
roundtrip("123", &[[b'1', b'2', b'3', 0]])?;
roundtrip("abcd", &[[b'a', b'b', b'c', b'd'], [0, 0, 0, 0]])?;
roundtrip(
"abcdefg",
&[[b'a', b'b', b'c', b'd'], [b'e', b'f', b'g', 0]],
)?;
roundtrip(
"abcdefgh",
&[
[b'a', b'b', b'c', b'd'],
[b'e', b'f', b'g', b'h'],
[0, 0, 0, 0],
],
)?;
roundtrip("abc", &[*b"abc\0"])?;
roundtrip("123", &[*b"123\0"])?;
roundtrip("abcd", &[*b"abcd", *b"\0\0\0\0"])?;
roundtrip("abcdefg", &[*b"abcd", *b"efg\0"])?;
roundtrip("abcdefgh", &[*b"abcd", *b"efgh", *b"\0\0\0\0"])?;
Ok(())
}

Expand All @@ -172,25 +162,19 @@ mod tests {
assert_eq!(read.as_ref().map(|s| s.as_str()), str);
};

test(&[[b'a', 0, 0, 0]], Some("a"));
test(&[[b'a', b'b', 0, 0]], Some("ab"));
test(&[[b'a', b'b', b'c', 0]], Some("abc"));
test(&[[b'a', b'b', b'c', b'd'], [0, 0, 0, 0]], Some("abcd"));
test(&[[b'a', b'b', b'c', b'd'], [b'e', 0, 0, 0]], Some("abcde"));
test(
&[[b'a', b'b', b'c', b'd'], [b'e', b'f', b'g', 0]],
Some("abcdefg"),
);
test(
&[[b'a', b'b', b'c', b'd'], [b'e', b'f', b'g', b'h'], [0; 4]],
Some("abcdefgh"),
);
test(&[*b"a\0\0\0"], Some("a"));
test(&[*b"ab\0\0"], Some("ab"));
test(&[*b"abc\0"], Some("abc"));
test(&[*b"abcd", *b"\0\0\0\0"], Some("abcd"));
test(&[*b"abcd", *b"e\0\0\0"], Some("abcde"));
test(&[*b"abcd", *b"efg\0"], Some("abcdefg"));
test(&[*b"abcd", *b"efgh", *b"\0\0\0\0"], Some("abcdefgh"));

// missing null terminator
test(&[], None);
test(&[[b'a', b'b', b'c', b'd']], None);
test(&[[b'a', b'b', b'c', b'd'], [b'e', b'f', b'g', b'h']], None);
test(&[*b"abcd"], None);
test(&[*b"abcd", *b"efgh"], None);

test(&[[0, 0, 0, 0]], Some(""));
test(&[*b"\0\0\0\0"], Some(""));
}
}
219 changes: 189 additions & 30 deletions crates/rspirv2-types/src/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::dis::{DisInstSlice, InstSetDisCtx, IntoDisContext};
use crate::inst::{InstEncoding, InstRef};
use std::fmt::{Debug, Formatter};
use std::marker::PhantomData;
use std::ops::Deref;
use std::ops::{Bound, Deref, Index, RangeBounds};

pub fn decode_failed(e: DecodeError) -> ! {
panic!("Decode failed: {e}")
Expand Down Expand Up @@ -75,6 +75,12 @@ impl<ISA: InstEncoding> InstSlice<ISA> {
unsafe { core::mem::transmute(raw) }
}

/// An [`InstSlice`] with 0 instructions
#[inline]
pub const fn empty() -> &'static Self {
Self::from_words_unchecked(&[])
}

/// View self as a [`RawInstSlice`]
#[inline]
pub const fn as_raw(&self) -> &RawInstSlice {
Expand Down Expand Up @@ -111,6 +117,55 @@ impl<ISA: InstEncoding> Debug for InstSlice<ISA> {
}
}

impl<ISA: InstEncoding> InstSlice<ISA> {
/// Get instruction at `offset` as an [`InstRef`], return `None` when index is invalid
pub fn get_ref(&self, offset: InstOffset) -> Option<InstRef<'_, ISA>> {
self.iter_ref().with_offsets().advance_to(offset)
}

/// Get instruction at `offset`, return `None` when index is invalid
pub fn get(&self, offset: InstOffset) -> Option<ISA> {
Some(self.get_ref(offset)?.get())
}

/// Get instruction at `offset` as an [`InstRef`], panic when index is invalid
pub fn index_ref(&self, offset: InstOffset) -> InstRef<'_, ISA> {
self.get_ref(offset)
.unwrap_or_else(|| panic!("Offset {offset} invalid for this InstSlice"))
}

/// Get instruction at `offset`, panic when index is invalid
pub fn index(&self, offset: InstOffset) -> ISA {
self.index_ref(offset).get()
}

/// Slice this [`InstSlice`]
pub fn slice<R: RangeBounds<InstOffset>>(&self, index: R) -> Option<&Self> {
let mut iter = self.iter_ref().with_offsets();
let start = iter.advance_to_bound(index.start_bound(), false)?;
// reusing the same iter to not have to advance it twice over `..start` insts
// if end < start, the advance may fail due to already having skipped over the end inst, but that's fine since
// indexing a slice leads to failure anyway.
// Important detail: When you hit the offset, do NOT advance the iterator, otherwise `0..=0` would fail
let end = iter.advance_to_bound(index.end_bound(), true)?;
let slice = match (start, end) {
(Some(start), Some(end)) => &self.0[start..end],
(Some(start), None) => &self.0[start..],
(None, Some(end)) => &self.0[..end],
(None, None) => &self.0[..],
};
Some(InstSlice::from_words_unchecked(slice))
}
}

impl<ISA: InstEncoding, R: RangeBounds<InstOffset>> Index<R> for InstSlice<ISA> {
type Output = InstSlice<ISA>;

fn index(&self, index: R) -> &Self::Output {
self.slice(index).expect("Index out of bounds")
}
}

impl<ISA: InstSetDisCtx> InstSlice<ISA> {
/// disassemble
#[inline]
Expand All @@ -133,23 +188,90 @@ impl<'a, ISA: InstEncoding> InstOffsetRefIter<'a, ISA> {
_phantom: PhantomData,
}
}

pub fn offset(&self) -> InstOffset {
self.inner.offset()
}

pub fn peek(&self) -> Option<(InstOffset, InstRef<'a, ISA>)> {
Some(reader_to_inst_ref_offset(self.inner.peek()?))
}

/// Advance the iterator to this offset and return an [`InstRef`] to the instruction at this offset.
///
/// May return `None` if offset is out of bounds, offset is within an instruction and not at the start of one, or
/// this Iterator has advanced beyond the requested offset already.
pub fn advance_to(&mut self, to: InstOffset) -> Option<InstRef<'a, ISA>> {
while let Some((off, inst)) = self.peek() {
if off == to {
return Some(inst);
} else if off > to {
// jumped over offset -> offset within an inst or iter has advanced too far before calling this
return None;
}
self.next();
}
// eof
None
}

/// outer Option: failure due to eof or in the middle of insts
/// inner Option: Bound or Unbounded
#[expect(clippy::option_option)]
fn advance_to_bound(&mut self, bound: Bound<&InstOffset>, end: bool) -> Option<Option<usize>> {
let to = match bound.cloned() {
Bound::Included(to) | Bound::Excluded(to) => to,
Bound::Unbounded => {
return Some(None);
}
};
let one_further = matches!(bound, Bound::Included(_)) == end;

// handle "one past end"
let total_len = self.inner.raw.as_words().len();
if to.0 == total_len {
return if !one_further {
Some(Some(total_len))
} else {
None
};
}

// handle degenerate RangeInclusive
// `6..=5` needs to return an empty slice, but `6..=4` should fail as normal. Just that the offset is one
// dynamically sized instruction. The best we can do is reset to offset 0 and start iterating again.
// Expensive, but only happens in this degenerate case or when it's oob anyway.
if self.inner.offset > to {
self.inner.offset = InstOffset(0);
}

let inst = self.advance_to(to)?;
let extra = if one_further { inst.len() } else { 0 };
Some(Some(to.0 + extra))
}
}

impl<'a, ISA: InstEncoding> Iterator for InstOffsetRefIter<'a, ISA> {
type Item = (InstOffset, InstRef<'a, ISA>);

#[inline]
fn next(&mut self) -> Option<Self::Item> {
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),
Some(reader_to_inst_ref_offset(self.inner.next()?))
}
}

fn reader_to_inst_ref_offset<ISA: InstEncoding>(
reader: Result<(InstOffset, InstReader<'_>), DecodeError>,
) -> (InstOffset, InstRef<'_, ISA>) {
match reader {
Ok((offset, reader)) => {
let inst_ref = match InstRef::from_words_unchecked(reader.to_words()) {
Ok(e) => e,
Err(e) => decode_failed(e),
};
(offset, inst_ref)
}
Err(e) => decode_failed(e),
}
}

Expand Down Expand Up @@ -179,6 +301,16 @@ impl<'a, ISA: InstEncoding> InstRefIter<'a, ISA> {
pub const fn new(slice: &'a InstSlice<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
}

pub fn peek(&self) -> Option<InstRef<'a, ISA>> {
Some(self.0.peek()?.1)
}
}

impl<'a, ISA: InstEncoding> Iterator for InstRefIter<'a, ISA> {
Expand Down Expand Up @@ -293,6 +425,12 @@ impl RawInstSlice {
unsafe { core::mem::transmute(words) }
}

/// An [`InstSlice`] with 0 instructions
#[inline]
pub const fn empty() -> &'static Self {
Self::from_words(&[])
}

/// Returns the underlying slice of words
pub const fn as_words(&self) -> &[Word] {
&self.0
Expand Down Expand Up @@ -342,35 +480,46 @@ impl<'a> RawInstOffsetRefIter<'a> {
offset: InstOffset(0),
}
}
}

impl<'a> Iterator for RawInstOffsetRefIter<'a> {
type Item = Result<(InstOffset, InstReader<'a>), DecodeError>;
pub fn offset(&self) -> InstOffset {
self.offset
}

#[inline]
fn next(&mut self) -> Option<Self::Item> {
let old_offset = self.offset;
if let Some(words) = self.raw.0.get(*old_offset..) {
pub fn peek(&self) -> Option<Result<(InstOffset, InstReader<'a>), DecodeError>> {
if let Some(words) = self.raw.0.get(*self.offset..) {
match InstReader::from_words(words) {
Ok(inst_reader) => {
*self.offset += inst_reader.len();
Some(Ok((old_offset, inst_reader)))
}
Ok(inst_reader) => Some(Ok((self.offset, inst_reader))),
Err(DecodeError {
kind: DecodeErrorKind::OutOfInstructions,
..
}) => None,
Err(e) => {
self.offset = InstOffset(!0);
Some(Err(e.with_inst_offset(old_offset)))
}
Err(e) => Some(Err(e.with_inst_offset(self.offset))),
}
} else {
None
}
}
}

impl<'a> Iterator for RawInstOffsetRefIter<'a> {
type Item = Result<(InstOffset, InstReader<'a>), DecodeError>;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
let out = self.peek();
match out {
Some(Ok((_, inst_reader))) => {
*self.offset += inst_reader.len();
}
Some(Err(_)) => {
self.offset = InstOffset(!0);
}
None => (),
}
out
}
}

/// An [`Iterator`] of [`Result`] yielding either an [`InstReader`] or a [`DecodeError`].
///
/// Use [`Self::with_offsets`] to also get [`InstOffset`] of the instruction.
Expand All @@ -388,18 +537,28 @@ impl<'a> RawInstRefIter<'a> {
pub const fn with_offsets(self) -> RawInstOffsetRefIter<'a> {
self.0
}

pub fn peek(&self) -> Option<Result<InstReader<'a>, DecodeError>> {
remove_offset_raw(self.0.peek())
}
}

impl<'a> Iterator for RawInstRefIter<'a> {
type Item = Result<InstReader<'a>, DecodeError>;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
match self.0.next() {
Some(Ok((_, inst))) => Some(Ok(inst)),
Some(Err(e)) => Some(Err(e)),
None => None,
}
remove_offset_raw(self.0.next())
}
}

fn remove_offset_raw<T>(
value: Option<Result<(InstOffset, T), DecodeError>>,
) -> Option<Result<T, DecodeError>> {
match value {
Some(Ok((_, inst))) => Some(Ok(inst)),
Some(Err(e)) => Some(Err(e)),
None => None,
}
}

Expand Down
Loading
Loading