diff --git a/Cargo.toml b/Cargo.toml index f4f5514678..9cbe44f15e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,10 +2,12 @@ name = "javelin" version = "0.1.0" authors = ["Dzmitry Malyshau "] +edition = "2018" [dependencies] -rspirv = "0.5" +log = "0.4" +smallvec = "1" spirv_headers = "1" [dev-dependencies] -env_logger = "0.5" +env_logger = "0.6" diff --git a/examples/convert.rs b/examples/convert.rs index e0a4823c89..195d475768 100644 --- a/examples/convert.rs +++ b/examples/convert.rs @@ -9,8 +9,8 @@ fn main() { let args = env::args().collect::>(); let input = fs::read(&args[1]).unwrap(); - let mut transpiler = javelin::Transpiler::new(); - let module = transpiler.load(&input).unwrap(); + let module = javelin::parse_u8_slice(&input).unwrap(); + //println!("{:?}", module); let options = javelin::msl::Options {}; let msl = module.to_msl(&options).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 3ab2132be5..9165bec25e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,69 +1,104 @@ -extern crate rspirv; -extern crate spirv_headers; +extern crate spirv_headers as spirv; pub mod msl; +mod parse; +mod storage; + +pub use parse::{parse, parse_u8_slice, ParseError}; + +use crate::storage::{Storage, Token}; use std::collections::HashMap; +use smallvec::SmallVec; -pub struct Transpiler { + + +#[derive(Debug)] +pub struct Header { + pub version: (u8, u8, u8), + pub generator: u32, } -pub struct Module { - raw: rspirv::mr::Module, - entry_points: HashMap, +pub type Bytes = u8; + +#[derive(Debug)] +pub struct StructDeclaration { + } +#[derive(Debug)] +pub enum Type { + Void, + Int { width: Bytes }, + Uint { width: Bytes }, + Float { width: Bytes }, + Struct(Token), +} + +#[derive(Debug)] +pub struct Jump { + pub target: Token, + pub arguments: SmallVec<[Token; 1]>, +} + +#[derive(Debug)] +pub enum Branch { + Jump(Jump), + JumpIf { + condition: Token, //bool + accept: Jump, + reject: Jump, + }, + Switch { + selector: Token, //int + cases: HashMap, + default: Jump, + }, + Return { + value: Option>, + }, +} + +#[derive(Debug)] +pub enum Operation { + Arithmetic, +} + +#[derive(Debug)] +pub enum Terminator { + Branch(Branch), + Kill, + Unreachable, +} + +#[derive(Debug)] +pub struct Block { + pub label: Option, + pub argument_types: Vec, + pub operations: Storage, + pub terminator: Terminator, +} + +#[derive(Debug)] +pub struct Function { + pub name: Option, + pub parameter_types: Vec, + pub return_type: Type, + pub blocks: Storage, +} + +#[derive(Debug)] pub struct EntryPoint { - pub cleansed_name: String, - pub exec_model: spirv_headers::ExecutionModel, + pub exec_model: spirv::ExecutionModel, + pub name: String, + pub function: Token, } -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum LoadError { - Parsing, -} - -impl Transpiler { - pub fn new() -> Self { - Transpiler { - } - } - - pub fn load(&mut self, spv: &[u8]) -> Result { - let mut loader = rspirv::mr::Loader::new(); - rspirv::binary::Parser::new(spv, &mut loader) - .parse() - .map_err(|_| LoadError::Parsing)?; - let raw = loader.module(); - - let entry_points = raw.entry_points - .iter() - .map(|ep| { - let name = match ep.operands[2] { - rspirv::mr::Operand::LiteralString(ref name) => name.to_string(), - ref other => panic!("Unexpected entry point operand {:?}", other), - }; - let ep = EntryPoint { - cleansed_name: name.clone(), //TODO - exec_model: match ep.operands[0] { - rspirv::mr::Operand::ExecutionModel(model) => model, - ref other => panic!("Unexpected execution model operand {:?}", other), - }, - }; - (name, ep) - }) - .collect(); - - Ok(Module { - raw, - entry_points, - }) - } -} - -impl Module { - pub fn entry_points(&self) -> &HashMap { - &self.entry_points - } +#[derive(Debug)] +pub struct Module { + pub header: Header, + pub struct_declarations: Storage, + pub functions: Storage, + pub entry_points: Vec, } diff --git a/src/msl.rs b/src/msl.rs index b9e86a17c1..3da8c6ce55 100644 --- a/src/msl.rs +++ b/src/msl.rs @@ -1,8 +1,5 @@ use std::fmt::{Error as FmtError, Write}; -use Module; - - pub struct Options { } @@ -17,7 +14,7 @@ impl From for Error { } } -impl Module { +impl super::Module { pub fn to_msl(&self, _options: &Options) -> Result { let mut out = String::new(); diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000000..08e482c180 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,60 @@ +use crate::storage::Storage; + +#[derive(Debug)] +pub enum ParseError { + InvalidHeader, + UnknownInstruction, + IncompleteData, +} + +pub fn parse(mut data: impl Iterator) -> Result { + let header = { + if data.next().ok_or(ParseError::IncompleteData)? != spirv::MAGIC_NUMBER { + return Err(ParseError::InvalidHeader); + } + let version_raw = data.next().ok_or(ParseError::IncompleteData)?.to_le_bytes(); + super::Header { + version: (version_raw[2], version_raw[1], version_raw[0]), + generator: 0, + } + }; + Ok(super::Module { + header, + struct_declarations: Storage::new(), + functions: Storage::new(), + entry_points: Vec::new(), + }) +} + +pub fn parse_u8_slice(data: &[u8]) -> Result { + use std::convert::TryInto; + + if data.len() % 4 != 0 { + return Err(ParseError::IncompleteData); + } + + parse(data + .chunks(4) + .map(|c| u32::from_le_bytes(c.try_into().unwrap())) + ) +} + +#[cfg(test)] +mod test { + #[test] + fn parse() { + let bin = vec![ + // Magic number. Version number: 1.0. + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, + // Generator number: 0. Bound: 0. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Reserved word: 0. + 0x00, 0x00, 0x00, 0x00, + // OpMemoryModel. Logical. + 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + // GLSL450. + 0x01, 0x00, 0x00, 0x00, + ]; + let _ = super::parse_u8_slice(&bin).unwrap(); + } +} diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 0000000000..8142b75cf5 --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,149 @@ +use std::{ + fmt, + marker::PhantomData, +}; + +/// An unique index in the storage array that a token points to. +/// +/// This type is independent of `spirv::Word`. `spirv::Word` is used in data +/// representation. It holds a SPIR-V and refers to that instruction. In +/// structured representation, we use Token to refer to an SPIR-V instruction. +/// Index is an implementation detail to Token. +type Index = u32; + +/// A strongly typed reference to a SPIR-V element. +pub struct Token { + index: Index, + marker: PhantomData, +} + +impl Clone for Token { + fn clone(&self) -> Self { + Token { + index: self.index, + marker: self.marker, + } + } +} +impl Copy for Token {} +impl PartialEq for Token { + fn eq(&self, other: &Self) -> bool { + self.index == other.index + } +} +impl Eq for Token {} +impl fmt::Debug for Token { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "Token({})", self.index) + } +} + +impl Token { + #[cfg(test)] + pub const DUMMY: Self = Token { + index: !0, + marker: PhantomData, + }; + + pub(crate) fn new(index: Index) -> Self { + Token { + index, + marker: PhantomData, + } + } + + pub fn index(&self) -> usize { + self.index as usize + } +} + +/// A structure holding some kind of SPIR-V entity (e.g., type, constant, +/// instruction, etc.) that can be referenced. +#[derive(Debug)] +pub struct Storage { + /// Values of this storage. + data: Vec, +} + +impl Storage { + pub fn new() -> Self { + Storage { + data: Vec::new(), + } + } + + pub fn iter<'a>(&'a self) -> impl Iterator, &'a T)> { + self.data + .iter() + .enumerate() + .map(|(i, v)| (Token::new(i as Index), v)) + } + + /// Adds a new value to the storage, returning a typed token. + /// + /// The value is not linked to any SPIR-V module. + pub fn append(&mut self, value: T) -> Token { + let index = self.data.len() as Index; + self.data.push(value); + Token::new(index) + } + + /// Adds a value with a check for uniqueness: returns a token pointing to + /// an existing element if its value matches the given one, or adds a new + /// element otherwise. + pub fn fetch_or_append(&mut self, value: T) -> Token where T: PartialEq { + if let Some(index) = self.data.iter().position(|d| d == &value) { + Token::new(index as Index) + } else { + self.append(value) + } + } +} + +impl std::ops::Index> for Storage { + type Output = T; + fn index(&self, token: Token) -> &T { + &self.data[token.index as usize] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn append_non_unique() { + let mut storage: Storage = Storage::new(); + let t1 = storage.append(0.0); + let t2 = storage.append(0.0); + assert!(t1 != t2); + assert!(storage[t1] == storage[t2]); + } + + #[test] + fn append_unique() { + let mut storage: Storage = Storage::new(); + let t1 = storage.append(std::f64::NAN); + let t2 = storage.append(std::f64::NAN); + assert!(t1 != t2); + assert!(storage[t1] != storage[t2]); + } + + #[test] + fn fetch_or_append_non_unique() { + let mut storage: Storage = Storage::new(); + let t1 = storage.fetch_or_append(0.0); + let t2 = storage.fetch_or_append(0.0); + assert!(t1 == t2); + assert!(storage[t1] == storage[t2]) + } + + #[test] + fn fetch_or_append_unique() { + let mut storage: Storage = Storage::new(); + let t1 = storage.fetch_or_append(std::f64::NAN); + let t2 = storage.fetch_or_append(std::f64::NAN); + assert!(t1 != t2); + assert!(storage[t1] != storage[t2]); + } +}