1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! SPIR-V support, mainly conversions to/from SPIR-T ([`lower`]/[`lift`]).

// NOTE(eddyb) all the modules are declared here, but they're documented "inside"
// (i.e. using inner doc comments).
pub mod lift;
pub mod lower;
pub mod print;
pub mod read;
pub mod spec;
pub mod write;

use crate::{FxIndexMap, InternedStr};
use smallvec::SmallVec;
use std::collections::{BTreeMap, BTreeSet};
use std::iter;
use std::num::NonZeroU32;
use std::string::FromUtf8Error;

/// Semantic properties of a SPIR-V module (not tied to any IDs).
#[derive(Clone)]
pub struct Dialect {
    pub version_major: u8,
    pub version_minor: u8,

    pub capabilities: BTreeSet<u32>,
    pub extensions: BTreeSet<String>,

    pub addressing_model: u32,
    pub memory_model: u32,
}

/// Non-semantic details (i.e. debuginfo) of a SPIR-V module (not tied to any IDs).
#[derive(Clone)]
pub struct ModuleDebugInfo {
    pub original_generator_magic: Option<NonZeroU32>,

    pub source_languages: BTreeMap<DebugSourceLang, DebugSources>,
    pub source_extensions: Vec<String>,
    pub module_processes: Vec<String>,
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct DebugSourceLang {
    pub lang: u32,
    pub version: u32,
}

#[derive(Clone, Default)]
pub struct DebugSources {
    pub file_contents: FxIndexMap<InternedStr, String>,
}

/// A SPIR-V instruction, in its minimal form (opcode and immediate operands).
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Inst {
    pub opcode: spec::Opcode,

    // FIXME(eddyb) change the inline size of this to fit most instructions.
    // FIXME(eddyb) it might be worth investigating the performance implications
    // of interning "long immediates", compared to the flattened representation.
    // NOTE(eddyb) interning these separately is likely unnecessary in many cases,
    // now that `DataInstForm`s are interned, and `Const`s etc. were already.
    pub imms: SmallVec<[Imm; 2]>,
}

impl From<spec::Opcode> for Inst {
    fn from(opcode: spec::Opcode) -> Self {
        Self { opcode, imms: SmallVec::new() }
    }
}

/// A full SPIR-V instruction (like [`Inst`], but including input/output ID operands).
pub struct InstWithIds {
    pub without_ids: Inst,

    // FIXME(eddyb) consider nesting "Result Type ID" in "Result ID".
    pub result_type_id: Option<Id>,
    pub result_id: Option<Id>,

    // FIXME(eddyb) change the inline size of this to fit most instructions.
    pub ids: SmallVec<[Id; 4]>,
}

// HACK(eddyb) access to `Inst` fields for convenience.
impl std::ops::Deref for InstWithIds {
    type Target = Inst;
    fn deref(&self) -> &Inst {
        &self.without_ids
    }
}
impl std::ops::DerefMut for InstWithIds {
    fn deref_mut(&mut self) -> &mut Inst {
        &mut self.without_ids
    }
}

/// SPIR-V immediate (one word, longer immediates are a sequence of multiple [`Imm`]s).
//
// FIXME(eddyb) consider replacing with a `struct` e.g.:
// `{ first: bool, last: bool, kind: OperandKind, word: u32 }`
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Imm {
    Short(spec::OperandKind, u32),
    LongStart(spec::OperandKind, u32),
    LongCont(spec::OperandKind, u32),
}

/// SPIR-V ID.
pub type Id = NonZeroU32;

// FIXME(eddyb) pick a "small string" crate, and fine-tune its inline size,
// instead of allocating a whole `String`.
//
/// Given a single `LiteralString` (as one [`Imm::Short`] or a [`Imm::LongStart`]
/// followed by some number of [`Imm::LongCont`] - will panic otherwise), returns a
/// Rust [`String`] if the literal is valid UTF-8, or the validation error otherwise.
pub fn extract_literal_string(imms: &[Imm]) -> Result<String, FromUtf8Error> {
    let wk = &spec::Spec::get().well_known;

    let mut words = match *imms {
        [Imm::Short(kind, first_word)] | [Imm::LongStart(kind, first_word), ..] => {
            assert_eq!(kind, wk.LiteralString);
            iter::once(first_word).chain(imms[1..].iter().map(|&imm| match imm {
                Imm::LongCont(kind, word) => {
                    assert_eq!(kind, wk.LiteralString);
                    word
                }
                _ => unreachable!(),
            }))
        }
        _ => unreachable!(),
    };

    let mut bytes = Vec::with_capacity(imms.len() * 4);
    while let Some(word) = words.next() {
        for byte in word.to_le_bytes() {
            if byte == 0 {
                assert!(words.next().is_none());
                return String::from_utf8(bytes);
            }
            bytes.push(byte);
        }
    }
    unreachable!("missing \\0 terminator in LiteralString");
}

// FIXME(eddyb) this shouldn't just panic when `s.contains('\0')`.
pub fn encode_literal_string(s: &str) -> impl Iterator<Item = Imm> + '_ {
    let wk = &spec::Spec::get().well_known;

    let bytes = s.as_bytes();

    // FIXME(eddyb) replace with `array_chunks` once that is stabilized.
    let full_words = bytes.chunks_exact(4).map(|w| <[u8; 4]>::try_from(w).unwrap());

    let leftover_bytes = &bytes[full_words.len() * 4..];
    let mut last_word = [0; 4];
    last_word[..leftover_bytes.len()].copy_from_slice(leftover_bytes);

    let total_words = full_words.len() + 1;

    full_words.chain(iter::once(last_word)).map(u32::from_le_bytes).enumerate().map(
        move |(i, word)| {
            let kind = wk.LiteralString;
            match (i, total_words) {
                (0, 1) => Imm::Short(kind, word),
                (0, _) => Imm::LongStart(kind, word),
                (_, _) => Imm::LongCont(kind, word),
            }
        },
    )
}