spirt/spv/
mod.rs

1//! SPIR-V support, mainly conversions to/from SPIR-T ([`lower`]/[`lift`]).
2
3// NOTE(eddyb) all the modules are declared here, but they're documented "inside"
4// (i.e. using inner doc comments).
5pub mod lift;
6pub mod lower;
7pub mod print;
8pub mod read;
9pub mod spec;
10pub mod write;
11
12use crate::{FxIndexMap, InternedStr};
13use smallvec::SmallVec;
14use std::collections::{BTreeMap, BTreeSet};
15use std::iter;
16use std::num::NonZeroU32;
17use std::string::FromUtf8Error;
18
19/// Semantic properties of a SPIR-V module (not tied to any IDs).
20#[derive(Clone)]
21pub struct Dialect {
22    pub version_major: u8,
23    pub version_minor: u8,
24
25    pub capabilities: BTreeSet<u32>,
26    pub extensions: BTreeSet<String>,
27
28    pub addressing_model: u32,
29    pub memory_model: u32,
30}
31
32/// Non-semantic details (i.e. debuginfo) of a SPIR-V module (not tied to any IDs).
33#[derive(Clone)]
34pub struct ModuleDebugInfo {
35    pub original_generator_magic: Option<NonZeroU32>,
36
37    pub source_languages: BTreeMap<DebugSourceLang, DebugSources>,
38    pub source_extensions: Vec<String>,
39    pub module_processes: Vec<String>,
40}
41
42#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
43pub struct DebugSourceLang {
44    pub lang: u32,
45    pub version: u32,
46}
47
48#[derive(Clone, Default)]
49pub struct DebugSources {
50    pub file_contents: FxIndexMap<InternedStr, String>,
51}
52
53/// A SPIR-V instruction, in its minimal form (opcode and immediate operands).
54#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
55pub struct Inst {
56    pub opcode: spec::Opcode,
57
58    // FIXME(eddyb) change the inline size of this to fit most instructions.
59    // FIXME(eddyb) it might be worth investigating the performance implications
60    // of interning "long immediates", compared to the flattened representation.
61    // NOTE(eddyb) interning these separately is likely unnecessary in many cases,
62    // now that `DataInstForm`s are interned, and `Const`s etc. were already.
63    pub imms: SmallVec<[Imm; 2]>,
64}
65
66impl From<spec::Opcode> for Inst {
67    fn from(opcode: spec::Opcode) -> Self {
68        Self { opcode, imms: SmallVec::new() }
69    }
70}
71
72/// A full SPIR-V instruction (like [`Inst`], but including input/output ID operands).
73pub struct InstWithIds {
74    pub without_ids: Inst,
75
76    // FIXME(eddyb) consider nesting "Result Type ID" in "Result ID".
77    pub result_type_id: Option<Id>,
78    pub result_id: Option<Id>,
79
80    // FIXME(eddyb) change the inline size of this to fit most instructions.
81    pub ids: SmallVec<[Id; 4]>,
82}
83
84// HACK(eddyb) access to `Inst` fields for convenience.
85impl std::ops::Deref for InstWithIds {
86    type Target = Inst;
87    fn deref(&self) -> &Inst {
88        &self.without_ids
89    }
90}
91impl std::ops::DerefMut for InstWithIds {
92    fn deref_mut(&mut self) -> &mut Inst {
93        &mut self.without_ids
94    }
95}
96
97/// SPIR-V immediate (one word, longer immediates are a sequence of multiple [`Imm`]s).
98//
99// FIXME(eddyb) consider replacing with a `struct` e.g.:
100// `{ first: bool, last: bool, kind: OperandKind, word: u32 }`
101#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
102pub enum Imm {
103    Short(spec::OperandKind, u32),
104    LongStart(spec::OperandKind, u32),
105    LongCont(spec::OperandKind, u32),
106}
107
108/// SPIR-V ID.
109pub type Id = NonZeroU32;
110
111// FIXME(eddyb) pick a "small string" crate, and fine-tune its inline size,
112// instead of allocating a whole `String`.
113//
114/// Given a single `LiteralString` (as one [`Imm::Short`] or a [`Imm::LongStart`]
115/// followed by some number of [`Imm::LongCont`] - will panic otherwise), returns a
116/// Rust [`String`] if the literal is valid UTF-8, or the validation error otherwise.
117pub fn extract_literal_string(imms: &[Imm]) -> Result<String, FromUtf8Error> {
118    let wk = &spec::Spec::get().well_known;
119
120    let mut words = match *imms {
121        [Imm::Short(kind, first_word)] | [Imm::LongStart(kind, first_word), ..] => {
122            assert_eq!(kind, wk.LiteralString);
123            iter::once(first_word).chain(imms[1..].iter().map(|&imm| match imm {
124                Imm::LongCont(kind, word) => {
125                    assert_eq!(kind, wk.LiteralString);
126                    word
127                }
128                _ => unreachable!(),
129            }))
130        }
131        _ => unreachable!(),
132    };
133
134    let mut bytes = Vec::with_capacity(imms.len() * 4);
135    while let Some(word) = words.next() {
136        for byte in word.to_le_bytes() {
137            if byte == 0 {
138                assert!(words.next().is_none());
139                return String::from_utf8(bytes);
140            }
141            bytes.push(byte);
142        }
143    }
144    unreachable!("missing \\0 terminator in LiteralString");
145}
146
147// FIXME(eddyb) this shouldn't just panic when `s.contains('\0')`.
148pub fn encode_literal_string(s: &str) -> impl Iterator<Item = Imm> + '_ {
149    let wk = &spec::Spec::get().well_known;
150
151    let bytes = s.as_bytes();
152
153    // FIXME(eddyb) replace with `array_chunks` once that is stabilized.
154    let full_words = bytes.chunks_exact(4).map(|w| <[u8; 4]>::try_from(w).unwrap());
155
156    let leftover_bytes = &bytes[full_words.len() * 4..];
157    let mut last_word = [0; 4];
158    last_word[..leftover_bytes.len()].copy_from_slice(leftover_bytes);
159
160    let total_words = full_words.len() + 1;
161
162    full_words.chain(iter::once(last_word)).map(u32::from_le_bytes).enumerate().map(
163        move |(i, word)| {
164            let kind = wk.LiteralString;
165            match (i, total_words) {
166                (0, 1) => Imm::Short(kind, word),
167                (0, _) => Imm::LongStart(kind, word),
168                (_, _) => Imm::LongCont(kind, word),
169            }
170        },
171    )
172}