mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
First bits of hand-written high-level representation
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
145
src/lib.rs
145
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<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>,
|
||||
}
|
||||
|
||||
@@ -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
60
src/parse.rs
Normal 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
149
src/storage.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user