Skip to main content

nox_spirv/
module.rs

1#![warn(missing_docs)]
2
3use core::{
4    fmt::{Display, self},
5    error::Error,
6    slice,
7};
8
9use crate::{
10    stream::*,
11    op,
12    core::*,
13};
14
15/// SPIR_V version 1.0
16pub const VERSION_1_0: u32 = 0x00010000;
17/// SPIR_V version 1.1
18pub const VERSION_1_1: u32 = 0x00010100;
19/// SPIR_V version 1.2
20pub const VERSION_1_2: u32 = 0x00010200;
21/// SPIR_V version 1.3
22pub const VERSION_1_3: u32 = 0x00010300;
23/// SPIR_V version 1.4
24pub const VERSION_1_4: u32 = 0x00010400;
25/// SPIR_V version 1.5
26pub const VERSION_1_5: u32 = 0x00010500;
27/// SPIR_V version 1.6
28pub const VERSION_1_6: u32 = 0x00010600;
29
30/// Indicates that the SPIR-V code passed to a [`module`][1] is invalid.
31///
32/// [1]: Module
33#[derive(Debug)]
34pub enum ParseError {
35    /// Indicates that the end of the stream was reached prematurely.
36    EndOfStream,
37    /// Indicates a wrong magic number for the module.
38    InvalidMagicNumber(u32),
39    /// Indicates an invalid type.
40    InvalidType {
41        /// The expected type.
42        expected: &'static str,
43        /// The found [`Code`][1].
44        ///
45        /// [1]: op::Code
46        found: op::Code,
47    },
48    /// Indicates an invalid result id.
49    InvalidIdResult(op::IdRef),
50    /// Indicates that a [`context`][1] result type was expected, but [`None`] was found.
51    ///
52    /// [1]: ParseContext
53    ExpectedContextResultType,
54    /// Indicates that an invalid integer literal width was found.
55    InvalidIntLiteralWidth(u32),
56    /// Indicates that an invalid floating point literal width was found.
57    InvalidFloatLiteralWidth(u32),
58    /// Indicates that an id in SPIR-V was out of the set bounds.
59    IdOutOfBounds {
60        /// The set bound.
61        bound: u32,
62        /// The id.
63        id: op::IdResult,
64    },
65    /// Indicates that an unknown variant was found.
66    UnknownVariant {
67        /// The enum type.
68        kind: &'static str,
69        /// The found value.
70        value: u32,
71    },
72    /// Indicates that an invalid pair word count was found (not a multiple of 2).
73    InvalidPairWordCount(u32),
74}
75
76#[derive(Clone)]
77struct ResultStorage<'a> {
78    data: Vec<Option<InstructionStream<'a>>>,
79}
80
81impl<'a> ResultStorage<'a>
82{
83
84    #[inline]
85    fn new(bound: u32) -> Self {
86        Self {
87            data: vec![None; bound as usize],
88        }
89    }
90
91    #[inline]
92    fn insert(&mut self,
93        id: op::IdResult,
94        mut stream: InstructionStream<'a>
95    ) -> ParseResult<()>
96    {
97        stream.reset();
98        let idx = id.0 as usize;
99        if idx >= self.data.len() {
100            return Err(ParseError::IdOutOfBounds {
101                bound: self.data.len() as u32,
102                id
103            })
104        }
105        self.data[idx] = Some(stream);
106        Ok(())
107    }
108
109    #[inline]
110    fn get(&self, id: op::IdRef) -> Option<InstructionStream<'a>> {
111        self.data.get(id.0 as usize)
112            .and_then(|&value| value)
113    }
114}
115
116/// Represents a SPIR-V module.
117#[derive(Clone)]
118pub struct Module<'a> {
119    stream: Stream<'a>,
120    results: ResultStorage<'a>,
121}
122
123/// The [`Result`] from a parsing operation,
124pub type ParseResult<T> = Result<T, ParseError>;
125
126impl<'a> Module<'a> {
127
128    /// Creates a new module from SPIR-V words.
129    #[inline]
130    pub fn new(spirv: &'a [u32]) -> ParseResult<Self> {
131        let stream = Stream::new(spirv)?;
132        Ok(Self {
133            results: ResultStorage::new(stream.bound()),
134            stream,
135        })
136    }
137
138    /// The SPIR-V version of the module.
139    #[inline]
140    pub fn version(&self) -> u32 {
141        self.stream.version()
142    }
143
144    /// Tries to parse the next instruction.
145    #[inline]
146    pub fn parse_next(&mut self) -> ParseResult<bool> {
147        let Some(mut instruction) = self.stream
148            .next()
149        else {
150            return Ok(false)
151        };
152        let inst_info = op::INST_INFOS[instruction.code().0 as usize];
153        if inst_info.has_id_result {
154            if inst_info.has_id_result_type {
155                let _ = instruction.read()?;
156            }
157            let id = op::IdResult(instruction.read()?);
158            self.results.insert(
159                id,
160                instruction,
161            )?;
162        }
163        Ok(true)
164    }
165
166    /// Tries to parse until the end of the inner [`Stream`].
167    #[inline]
168    pub fn parse_full(&mut self) -> ParseResult<()> {
169        while self.parse_next()? {}
170        Ok(())
171    }
172
173    /// Returns an iterator over all [`instruction streams`][1].
174    ///
175    /// [1]: InstructionStream
176    #[inline]
177    pub fn all_instructions(&self) -> impl Iterator<Item = InstructionStream<'a>> {
178        let mut stream = self.stream;
179        stream.reset();
180        stream
181    }
182    
183    /// Returns an iterator over all remaining unparsed [`instruction streams`][1].
184    ///
185    /// [1]: InstructionStream
186    #[inline]
187    pub fn remaining_instructions(&self) -> impl Iterator<Item = InstructionStream<'a>> {
188        self.stream
189    }
190
191    /// Returns an iterator over all cached [`instruction streams`][1] with a result.
192    ///
193    /// [1]: InstructionStream
194    #[inline]
195    pub fn results(&self) -> impl Iterator<Item = (op::IdRef, InstructionStream<'a>)> {
196        self.results.data
197            .iter()
198            .enumerate()
199            .filter_map(|(i, result)| {
200                result
201                    .map(|result| (op::IdRef::from_word(i as u32), result))
202            })
203    }
204
205    /// Gets a result with `id`, returning [`None`] if the result is not present.
206    #[inline]
207    pub fn get_result(&self, id: op::IdRef) -> Option<InstructionStream<'a>> {
208        self.results.get(id)
209    }
210}
211
212/// A parse context used when parsing a [`Literal`].
213#[derive(Default, Clone, Copy)]
214pub struct ParseContext {
215    /// The optional [`result type`][1].
216    ///
217    /// [1]: op::IdResultType
218    pub result_type: Option<op::IdResultType>,
219}
220
221/// An extension auto-trait for [`Word`].
222pub trait WordExt<'a>: Word {
223
224    /// Parses one word.
225    #[inline]
226    fn parse_one(
227        stream: &mut InstructionStream<'a>,
228    ) -> ParseResult<Self> {
229        Ok(Self::from_word(stream.read()?))
230    }
231
232    /// Tries to optionally parse one word.
233    #[inline]
234    fn parse_optional(
235        stream: &mut InstructionStream<'a>,
236    ) -> ParseResult<Option<Self>> {
237        if stream.is_eos() {
238            Ok(None)
239        } else {
240            Ok(Some(Self::from_word(stream.read()?)))
241        }
242    }
243
244    /// Parses words until the end of the stream.
245    #[inline]
246    fn parse_eos(
247        stream: &mut InstructionStream<'a>,
248    ) -> ParseResult<&'a [Self]> {
249        let words = stream.read_words(None)?;
250        Ok(unsafe {
251            slice::from_raw_parts(
252                words.as_ptr().cast(),
253                words.len(),
254            )
255        })
256    }
257}
258
259impl<'a, T: Word> WordExt<'a> for T {}
260
261impl<'a> CompilerStr<'a> {
262
263    /// Parses a string.
264    #[inline]
265    pub fn parse_one(
266        stream: &mut InstructionStream<'a>,
267    ) -> ParseResult<Self> {
268        stream.read_string()
269    }
270
271    /// Tries to optionally parse a string.
272    #[inline]
273    pub fn parse_optional(
274        stream: &mut InstructionStream<'a>,
275    ) -> ParseResult<Option<Self>> {
276        if stream.is_eos() {
277            Ok(None)
278        } else {
279            Ok(Some(stream.read_string()?))
280        }
281    }
282}
283
284impl Display for ParseError {
285
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        match self {
288            Self::EndOfStream => write!(f, "end of stream"),
289            Self::InvalidMagicNumber(num) => write!(f, "invalid magic number {num}, expected 0x07230203"),
290            Self::InvalidType { expected, found } => write!(f,
291                "invalid type; expected {expected}, found {found}",
292            ),
293            Self::InvalidIdResult(id) => write!(f,
294                "invalid id result {id}",
295            ),
296            Self::ExpectedContextResultType => write!(f,
297                "expected context result type, found None",
298            ),
299            Self::InvalidIntLiteralWidth(width) => write!(f,
300                "invalid int literal width {width}",
301            ),
302            Self::InvalidFloatLiteralWidth(width) => write!(f,
303                "invalid float literal width {width}",
304            ),
305            Self::IdOutOfBounds { bound, id } => write!(f,
306                "Id {id} is out of the set bound {bound}",
307            ),
308            Self::UnknownVariant { kind, value } => write!(f,
309                "unknown {kind} variant {value}",
310            ),
311            Self::InvalidPairWordCount(count) => write!(f,
312                "invalid pair word count {count}: count is not a multiple of 2"
313            ),
314        }
315    }
316}
317
318impl Error for ParseError {}