Skip to main content

naga/back/spv/
layout.rs

1use alloc::{vec, vec::Vec};
2use core::iter;
3
4use spirv::{Op, Word, MAGIC_NUMBER};
5
6use super::{Instruction, LogicalLayout, PhysicalLayout};
7
8#[cfg(test)]
9use alloc::format;
10
11// https://github.com/KhronosGroup/SPIRV-Headers/pull/195
12const GENERATOR: Word = 28;
13
14impl PhysicalLayout {
15    pub(super) const fn new(major_version: u8, minor_version: u8) -> Self {
16        let version = ((major_version as u32) << 16) | ((minor_version as u32) << 8);
17        PhysicalLayout {
18            magic_number: MAGIC_NUMBER,
19            version,
20            generator: GENERATOR,
21            bound: 0,
22            instruction_schema: 0x0u32,
23        }
24    }
25
26    pub(super) fn in_words(&self, sink: &mut impl Extend<Word>) {
27        sink.extend(iter::once(self.magic_number));
28        sink.extend(iter::once(self.version));
29        sink.extend(iter::once(self.generator));
30        sink.extend(iter::once(self.bound));
31        sink.extend(iter::once(self.instruction_schema));
32    }
33
34    /// Returns `(major, minor)`.
35    pub(super) const fn lang_version(&self) -> (u8, u8) {
36        let major = (self.version >> 16) as u8;
37        let minor = (self.version >> 8) as u8;
38        (major, minor)
39    }
40}
41
42impl super::reclaimable::Reclaimable for PhysicalLayout {
43    fn reclaim(self) -> Self {
44        PhysicalLayout {
45            magic_number: self.magic_number,
46            version: self.version,
47            generator: self.generator,
48            instruction_schema: self.instruction_schema,
49            bound: 0,
50        }
51    }
52}
53
54impl LogicalLayout {
55    pub(super) fn in_words(&self, sink: &mut impl Extend<Word>) {
56        sink.extend(self.capabilities.iter().cloned());
57        sink.extend(self.extensions.iter().cloned());
58        sink.extend(self.ext_inst_imports.iter().cloned());
59        sink.extend(self.memory_model.iter().cloned());
60        sink.extend(self.entry_points.iter().cloned());
61        sink.extend(self.execution_modes.iter().cloned());
62        sink.extend(self.debugs.iter().cloned());
63        sink.extend(self.annotations.iter().cloned());
64        sink.extend(self.declarations.iter().cloned());
65        sink.extend(self.function_declarations.iter().cloned());
66        sink.extend(self.function_definitions.iter().cloned());
67    }
68}
69
70impl super::reclaimable::Reclaimable for LogicalLayout {
71    fn reclaim(self) -> Self {
72        Self {
73            capabilities: self.capabilities.reclaim(),
74            extensions: self.extensions.reclaim(),
75            ext_inst_imports: self.ext_inst_imports.reclaim(),
76            memory_model: self.memory_model.reclaim(),
77            entry_points: self.entry_points.reclaim(),
78            execution_modes: self.execution_modes.reclaim(),
79            debugs: self.debugs.reclaim(),
80            annotations: self.annotations.reclaim(),
81            declarations: self.declarations.reclaim(),
82            function_declarations: self.function_declarations.reclaim(),
83            function_definitions: self.function_definitions.reclaim(),
84        }
85    }
86}
87
88impl Instruction {
89    pub(super) const fn new(op: Op) -> Self {
90        Instruction {
91            op,
92            type_id: None,
93            result_id: None,
94            operands: vec![],
95        }
96    }
97
98    pub(super) fn set_type(&mut self, id: Word) {
99        assert!(self.type_id.is_none(), "Type can only be set once");
100        self.type_id = Some(id);
101    }
102
103    pub(super) fn set_result(&mut self, id: Word) {
104        assert!(self.result_id.is_none(), "Result can only be set once");
105        self.result_id = Some(id);
106    }
107
108    pub(super) fn add_operand(&mut self, operand: Word) {
109        self.operands.push(operand);
110    }
111
112    pub(super) fn add_operands(&mut self, operands: Vec<Word>) {
113        for operand in operands.into_iter() {
114            self.add_operand(operand)
115        }
116    }
117
118    fn word_count(&self) -> usize {
119        const OPCODE_WORD_COUNT_FIELDS_SIZE: usize = 1;
120
121        OPCODE_WORD_COUNT_FIELDS_SIZE
122            + usize::from(self.type_id.is_some())
123            + usize::from(self.result_id.is_some())
124            + self.operands.len()
125    }
126
127    pub(super) fn to_words(&self, sink: &mut impl Extend<Word>) {
128        let word_count = self.word_count();
129        assert!(
130            word_count <= usize::from(u16::MAX),
131            "{:?} instruction is {word_count} words long, but SPIR-V instructions are limited to {} words",
132            self.op,
133            u16::MAX
134        );
135        sink.extend(Some(((word_count as u32) << 16) | self.op as u32));
136        sink.extend(self.type_id);
137        sink.extend(self.result_id);
138        sink.extend(self.operands.iter().cloned());
139    }
140}
141
142impl Instruction {
143    #[cfg(test)]
144    fn validate(&self, words: &[Word]) {
145        let mut inst_index = 0;
146        let (wc, op) = ((words[inst_index] >> 16) as u16, words[inst_index] as u16);
147        inst_index += 1;
148
149        assert_eq!(wc, words.len() as u16);
150        assert_eq!(op, self.op as u16);
151
152        if let Some(type_id) = self.type_id {
153            assert_eq!(words[inst_index], type_id);
154            inst_index += 1;
155        }
156
157        if let Some(result_id) = self.result_id {
158            assert_eq!(words[inst_index], result_id);
159            inst_index += 1;
160        }
161
162        for (op_index, i) in (inst_index..wc as usize).enumerate() {
163            assert_eq!(words[i], self.operands[op_index]);
164        }
165    }
166}
167
168#[test]
169fn test_physical_layout_in_words() {
170    let bound = 5;
171
172    // The least and most significant bytes of `version` must both be zero
173    // according to the SPIR-V spec.
174    let version = 0x0001_0200;
175
176    let mut output = vec![];
177    let mut layout = PhysicalLayout::new(1, 2);
178    layout.bound = bound;
179
180    layout.in_words(&mut output);
181
182    assert_eq!(&output, &[MAGIC_NUMBER, version, GENERATOR, bound, 0,]);
183}
184
185#[test]
186fn test_logical_layout_in_words() {
187    let mut output = vec![];
188    let mut layout = LogicalLayout::default();
189    let layout_vectors = 11;
190    let mut instructions = Vec::with_capacity(layout_vectors);
191
192    let vector_names = &[
193        "Capabilities",
194        "Extensions",
195        "External Instruction Imports",
196        "Memory Model",
197        "Entry Points",
198        "Execution Modes",
199        "Debugs",
200        "Annotations",
201        "Declarations",
202        "Function Declarations",
203        "Function Definitions",
204    ];
205
206    for (i, _) in vector_names.iter().enumerate().take(layout_vectors) {
207        let mut dummy_instruction = Instruction::new(Op::Constant);
208        dummy_instruction.set_type((i + 1) as u32);
209        dummy_instruction.set_result((i + 2) as u32);
210        dummy_instruction.add_operand((i + 3) as u32);
211        dummy_instruction.add_operands(super::helpers::string_to_words(
212            format!("This is the vector: {}", vector_names[i]).as_str(),
213        ));
214        instructions.push(dummy_instruction);
215    }
216
217    instructions[0].to_words(&mut layout.capabilities);
218    instructions[1].to_words(&mut layout.extensions);
219    instructions[2].to_words(&mut layout.ext_inst_imports);
220    instructions[3].to_words(&mut layout.memory_model);
221    instructions[4].to_words(&mut layout.entry_points);
222    instructions[5].to_words(&mut layout.execution_modes);
223    instructions[6].to_words(&mut layout.debugs);
224    instructions[7].to_words(&mut layout.annotations);
225    instructions[8].to_words(&mut layout.declarations);
226    instructions[9].to_words(&mut layout.function_declarations);
227    instructions[10].to_words(&mut layout.function_definitions);
228
229    layout.in_words(&mut output);
230
231    let mut index: usize = 0;
232    for instruction in instructions {
233        let wc = instruction.word_count();
234        instruction.validate(&output[index..index + wc]);
235        index += wc;
236    }
237}
238
239#[cfg(test)]
240fn entry_point_of_word_count(word_count: usize) -> Vec<Word> {
241    let mut instruction = Instruction::new(Op::EntryPoint);
242    // `Instruction::new` already accounts for the opcode/word-count word.
243    instruction.add_operands(vec![1; word_count - 1]);
244
245    let mut output = vec![];
246    instruction.to_words(&mut output);
247    output
248}
249
250/// Ensure we allow `u16::MAX` words in an instruction.
251#[test]
252fn test_longest_encodable_instruction() {
253    let output = entry_point_of_word_count(u16::MAX as usize);
254
255    assert_eq!(output.len(), u16::MAX as usize);
256    assert_eq!(output[0] >> 16, u16::MAX as Word);
257}
258
259/// Ensure we panic when we have more than `u16::MAX` words in an instruction.
260#[test]
261#[should_panic(
262    expected = "EntryPoint instruction is 65536 words long, but SPIR-V instructions are limited to 65535 words"
263)]
264fn test_instruction_word_count_overflow() {
265    entry_point_of_word_count(u16::MAX as usize + 1);
266}