pub const MAGIC: u32 = 0x0723_0203;
pub const VERSION_1_0: u32 = 0x0001_0000;
pub const GENERATOR: u32 = 0;
pub const OP_NAME: u16 = 5;
pub const OP_MEMBER_NAME: u16 = 6;
pub const OP_MEMORY_MODEL: u16 = 14;
pub const OP_ENTRY_POINT: u16 = 15;
pub const OP_EXECUTION_MODE: u16 = 16;
pub const OP_CAPABILITY: u16 = 17;
pub const OP_TYPE_VOID: u16 = 19;
pub const OP_TYPE_BOOL: u16 = 20;
pub const OP_TYPE_INT: u16 = 21;
pub const OP_TYPE_FLOAT: u16 = 22;
pub const OP_TYPE_VECTOR: u16 = 23;
pub const OP_TYPE_RUNTIME_ARRAY: u16 = 29;
pub const OP_TYPE_STRUCT: u16 = 30;
pub const OP_TYPE_POINTER: u16 = 32;
pub const OP_TYPE_FUNCTION: u16 = 33;
pub const OP_CONSTANT: u16 = 43;
pub const OP_FUNCTION: u16 = 54;
pub const OP_FUNCTION_END: u16 = 56;
pub const OP_VARIABLE: u16 = 59;
pub const OP_LOAD: u16 = 61;
pub const OP_STORE: u16 = 62;
pub const OP_ACCESS_CHAIN: u16 = 65;
pub const OP_DECORATE: u16 = 71;
pub const OP_MEMBER_DECORATE: u16 = 72;
pub const OP_COMPOSITE_EXTRACT: u16 = 81;
pub const OP_CONVERT_S_TO_F: u16 = 111;
pub const OP_CONVERT_U_TO_F: u16 = 112;
pub const OP_BITCAST: u16 = 124;
pub const OP_F_NEGATE: u16 = 127;
pub const OP_I_ADD: u16 = 128;
pub const OP_F_ADD: u16 = 129;
pub const OP_I_SUB: u16 = 130;
pub const OP_I_MUL: u16 = 132;
pub const OP_F_MUL: u16 = 133;
pub const OP_SELECT: u16 = 169;
pub const OP_I_EQUAL: u16 = 170;
pub const OP_I_NOT_EQUAL: u16 = 171;
pub const OP_U_LESS_THAN: u16 = 176;
pub const OP_SHIFT_RIGHT_LOGICAL: u16 = 194;
pub const OP_SHIFT_LEFT_LOGICAL: u16 = 196;
pub const OP_BITWISE_OR: u16 = 197;
pub const OP_BITWISE_XOR: u16 = 198;
pub const OP_BITWISE_AND: u16 = 199;
pub const OP_LOOP_MERGE: u16 = 246;
pub const OP_SELECTION_MERGE: u16 = 247;
pub const OP_LABEL: u16 = 248;
pub const OP_BRANCH: u16 = 249;
pub const OP_BRANCH_CONDITIONAL: u16 = 250;
pub const OP_RETURN: u16 = 253;
pub const CAP_SHADER: u32 = 1;
pub const ADDRESSING_LOGICAL: u32 = 0;
pub const MEMORY_MODEL_GLSL450: u32 = 1;
pub const EXEC_MODEL_GL_COMPUTE: u32 = 5;
pub const EXEC_MODE_LOCAL_SIZE: u32 = 17;
pub const SC_INPUT: u32 = 1;
pub const SC_UNIFORM: u32 = 2;
pub const SC_FUNCTION: u32 = 7;
pub const SC_PUSH_CONSTANT: u32 = 9;
pub const DEC_BLOCK: u32 = 2;
pub const DEC_BUFFER_BLOCK: u32 = 3;
pub const DEC_ARRAY_STRIDE: u32 = 6;
pub const DEC_BUILTIN: u32 = 11;
pub const DEC_BINDING: u32 = 33;
pub const DEC_DESCRIPTOR_SET: u32 = 34;
pub const DEC_OFFSET: u32 = 35;
pub const BUILTIN_GLOBAL_INVOCATION_ID: u32 = 28;
pub const NONE: u32 = 0;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Section {
Prelude,
Debug,
Annotations,
Types,
Code,
}
#[derive(Default)]
pub struct Builder {
next_id: u32,
prelude: Vec<u32>,
debug: Vec<u32>,
annotations: Vec<u32>,
types: Vec<u32>,
code: Vec<u32>,
}
impl Builder {
pub fn new() -> Self {
Self {
next_id: 1,
..Default::default()
}
}
pub fn id(&mut self) -> u32 {
let id = self.next_id;
self.next_id += 1;
id
}
fn section(&mut self, s: Section) -> &mut Vec<u32> {
match s {
Section::Prelude => &mut self.prelude,
Section::Debug => &mut self.debug,
Section::Annotations => &mut self.annotations,
Section::Types => &mut self.types,
Section::Code => &mut self.code,
}
}
pub fn inst(&mut self, s: Section, op: u16, operands: &[u32]) {
let out = self.section(s);
push_inst(out, op, operands);
}
pub fn typed(&mut self, s: Section, op: u16, result_type: u32, rest: &[u32]) -> u32 {
let id = self.id();
let mut ops = vec![result_type, id];
ops.extend_from_slice(rest);
self.inst(s, op, &ops);
id
}
pub fn result(&mut self, s: Section, op: u16, rest: &[u32]) -> u32 {
let id = self.id();
let mut ops = vec![id];
ops.extend_from_slice(rest);
self.inst(s, op, &ops);
id
}
pub fn inst_str(&mut self, s: Section, op: u16, head: &[u32], text: &str, tail: &[u32]) {
let mut ops = head.to_vec();
ops.extend(encode_string(text));
ops.extend_from_slice(tail);
self.inst(s, op, &ops);
}
pub fn finish(self) -> Vec<u32> {
let mut out = Vec::with_capacity(
5 + self.prelude.len()
+ self.debug.len()
+ self.annotations.len()
+ self.types.len()
+ self.code.len(),
);
out.push(MAGIC);
out.push(VERSION_1_0);
out.push(GENERATOR);
out.push(self.next_id);
out.push(0); out.extend_from_slice(&self.prelude);
out.extend_from_slice(&self.debug);
out.extend_from_slice(&self.annotations);
out.extend_from_slice(&self.types);
out.extend_from_slice(&self.code);
out
}
}
fn push_inst(out: &mut Vec<u32>, op: u16, operands: &[u32]) {
let word_count = operands.len() + 1;
assert!(
word_count < 0x1_0000,
"SPIR-V instruction longer than the 16-bit word count field"
);
out.push(((word_count as u32) << 16) | op as u32);
out.extend_from_slice(operands);
}
pub fn encode_string(text: &str) -> Vec<u32> {
let mut bytes = text.as_bytes().to_vec();
bytes.push(0);
while !bytes.len().is_multiple_of(4) {
bytes.push(0);
}
let (words, rest) = bytes.as_chunks::<4>();
debug_assert!(rest.is_empty(), "padding loop left a partial word");
words.iter().map(|c| u32::from_le_bytes(*c)).collect()
}
pub fn to_bytes(words: &[u32]) -> Vec<u8> {
let mut out = Vec::with_capacity(words.len() * 4);
for w in words {
out.extend_from_slice(&w.to_le_bytes());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_is_well_formed_and_bound_covers_every_id() {
let mut b = Builder::new();
let a = b.id();
let c = b.id();
assert_eq!((a, c), (1, 2));
let words = b.finish();
assert_eq!(words[0], MAGIC);
assert_eq!(words[1], VERSION_1_0);
assert_eq!(words[3], 3, "bound must be one past the largest id");
assert_eq!(words[4], 0);
assert_eq!(words.len(), 5, "no sections were written");
}
#[test]
fn instruction_word_count_is_operands_plus_one() {
let mut b = Builder::new();
b.inst(Section::Prelude, OP_CAPABILITY, &[CAP_SHADER]);
let words = b.finish();
assert_eq!(words[5], (2 << 16) | OP_CAPABILITY as u32);
assert_eq!(words[6], CAP_SHADER);
}
#[test]
fn sections_are_concatenated_in_layout_order_regardless_of_write_order() {
let mut b = Builder::new();
b.inst(Section::Code, OP_RETURN, &[]);
b.inst(Section::Annotations, OP_DECORATE, &[1, DEC_BLOCK]);
b.inst(Section::Prelude, OP_CAPABILITY, &[CAP_SHADER]);
let words = b.finish();
let ops: Vec<u16> = decode_opcodes(&words[5..]);
assert_eq!(ops, vec![OP_CAPABILITY, OP_DECORATE, OP_RETURN]);
}
#[test]
fn strings_are_nul_terminated_and_word_padded() {
assert_eq!(encode_string("main").len(), 2);
assert_eq!(encode_string("main")[1], 0);
assert_eq!(encode_string("abc").len(), 1);
assert_eq!(encode_string("abc")[0], 0x0063_6261);
assert_eq!(encode_string("").len(), 1);
assert_eq!(encode_string("")[0], 0);
}
#[test]
fn to_bytes_is_little_endian() {
assert_eq!(to_bytes(&[MAGIC]), vec![0x03, 0x02, 0x23, 0x07]);
}
pub(crate) fn decode_opcodes(words: &[u32]) -> Vec<u16> {
let mut ops = Vec::new();
let mut i = 0;
while i < words.len() {
let count = (words[i] >> 16) as usize;
assert!(count > 0, "zero-length instruction at word {i}");
ops.push((words[i] & 0xffff) as u16);
i += count;
}
assert_eq!(i, words.len(), "instruction stream overran the module");
ops
}
}