First bits of hand-written high-level representation

This commit is contained in:
Dzmitry Malyshau
2020-02-18 15:34:16 -05:00
parent ed96c2b8e0
commit 99f160ebce
6 changed files with 306 additions and 63 deletions

View File

@@ -2,10 +2,12 @@
name = "javelin"
version = "0.1.0"
authors = ["Dzmitry Malyshau <kvarkus@gmail.com>"]
edition = "2018"
[dependencies]
rspirv = "0.5"
log = "0.4"
smallvec = "1"
spirv_headers = "1"
[dev-dependencies]
env_logger = "0.5"
env_logger = "0.6"

View File

@@ -9,8 +9,8 @@ fn main() {
let args = env::args().collect::<Vec<_>>();
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();

View File

@@ -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<String, EntryPoint>,
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<StructDeclaration>),
}
#[derive(Debug)]
pub struct Jump {
pub target: Token<Block>,
pub arguments: SmallVec<[Token<Operation>; 1]>,
}
#[derive(Debug)]
pub enum Branch {
Jump(Jump),
JumpIf {
condition: Token<Operation>, //bool
accept: Jump,
reject: Jump,
},
Switch {
selector: Token<Operation>, //int
cases: HashMap<i32, Jump>,
default: Jump,
},
Return {
value: Option<Token<Operation>>,
},
}
#[derive(Debug)]
pub enum Operation {
Arithmetic,
}
#[derive(Debug)]
pub enum Terminator {
Branch(Branch),
Kill,
Unreachable,
}
#[derive(Debug)]
pub struct Block {
pub label: Option<String>,
pub argument_types: Vec<Type>,
pub operations: Storage<Operation>,
pub terminator: Terminator,
}
#[derive(Debug)]
pub struct Function {
pub name: Option<String>,
pub parameter_types: Vec<Type>,
pub return_type: Type,
pub blocks: Storage<Block>,
}
#[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<Function>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum LoadError {
Parsing,
}
impl Transpiler {
pub fn new() -> Self {
Transpiler {
}
}
pub fn load(&mut self, spv: &[u8]) -> Result<Module, LoadError> {
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<String, EntryPoint> {
&self.entry_points
}
#[derive(Debug)]
pub struct Module {
pub header: Header,
pub struct_declarations: Storage<StructDeclaration>,
pub functions: Storage<Function>,
pub entry_points: Vec<EntryPoint>,
}

View File

@@ -1,8 +1,5 @@
use std::fmt::{Error as FmtError, Write};
use Module;
pub struct Options {
}
@@ -17,7 +14,7 @@ impl From<FmtError> for Error {
}
}
impl Module {
impl super::Module {
pub fn to_msl(&self, _options: &Options) -> Result<String, Error> {
let mut out = String::new();

60
src/parse.rs Normal file
View File

@@ -0,0 +1,60 @@
use crate::storage::Storage;
#[derive(Debug)]
pub enum ParseError {
InvalidHeader,
UnknownInstruction,
IncompleteData,
}
pub fn parse(mut data: impl Iterator<Item = u32>) -> Result<super::Module, ParseError> {
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<super::Module, ParseError> {
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();
}
}

149
src/storage.rs Normal file
View File

@@ -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<T> {
index: Index,
marker: PhantomData<T>,
}
impl<T> Clone for Token<T> {
fn clone(&self) -> Self {
Token {
index: self.index,
marker: self.marker,
}
}
}
impl<T> Copy for Token<T> {}
impl<T> PartialEq for Token<T> {
fn eq(&self, other: &Self) -> bool {
self.index == other.index
}
}
impl<T> Eq for Token<T> {}
impl<T> fmt::Debug for Token<T> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "Token({})", self.index)
}
}
impl<T> Token<T> {
#[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<T> {
/// Values of this storage.
data: Vec<T>,
}
impl<T> Storage<T> {
pub fn new() -> Self {
Storage {
data: Vec::new(),
}
}
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (Token<T>, &'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<T> {
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<T> where T: PartialEq {
if let Some(index) = self.data.iter().position(|d| d == &value) {
Token::new(index as Index)
} else {
self.append(value)
}
}
}
impl<T> std::ops::Index<Token<T>> for Storage<T> {
type Output = T;
fn index(&self, token: Token<T>) -> &T {
&self.data[token.index as usize]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn append_non_unique() {
let mut storage: Storage<f64> = 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<f64> = 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<f64> = 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<f64> = 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]);
}
}