From 7a04fceeddf901be79385c008d3100b51e086ab9 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 09:14:16 +0100 Subject: [PATCH 01/43] Initial commit --- .gitignore | 1 + Cargo.lock | 7 +++ Cargo.toml | 6 +++ src/main.rs | 49 +++++++++++++++++ src/parser/mod.rs | 68 ++++++++++++++++++++++++ src/parser/tests.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 256 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/parser/mod.rs create mode 100644 src/parser/tests.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..dd9815e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "shaderc" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a084462 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "shaderc" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..49c0ece --- /dev/null +++ b/src/main.rs @@ -0,0 +1,49 @@ +use std::fmt::Write; +pub mod parser; + +fn main() { + let mut ops: Vec<(Option, Vec)> = Vec::new(); + + // OpMemoryModel Logical GLSL450 + // OpEntryPoint Fragment %main "main" + // OpExecutionMode %main OriginUpperLeft + // OpSource GLSL 450 + // OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" + // OpSourceExtension "GL_GOOGLE_include_directive" + // OpName %main "main" + //%void = OpTypeVoid + //%3 = OpTypeFunction %void + //%main = OpFunction %void None %3 + //%5 = OpLabel + // OpReturn + // OpFunctionEnd + + ops.push((None, vec!["OpCapability".to_string(), "Shader".to_string()])); + ops.push((Some("%1".to_string()), vec!["OpExtInstImport".to_string(), "\"GLSL.std.450\"".to_string()])); + ops.push((None, vec!["OpMemoryModel".to_string(), "Logical".to_string(), "GLSL450".to_string()])); + ops.push((None, vec!["OpEntryPoint".to_string(), "Fragment".to_string(), "%main".to_string(), "\"main\"".to_string()])); + ops.push((None, vec!["OpExecutionMode".to_string(), "%main".to_string(), "OriginUpperLeft".to_string()])); + ops.push((None, vec!["OpSource".to_string(), "GLSL".to_string(), "450".to_string()])); + ops.push((None, vec!["OpSourceExtension".to_string(), "\"GL_GOOGLE_cpp_style_line_directive\"".to_string()])); + ops.push((None, vec!["OpSourceExtension".to_string(), "\"GL_GOOGLE_include_directive\"".to_string()])); + ops.push((None, vec!["OpName".to_string(), "%main".to_string(), "\"main\"".to_string()])); + ops.push((Some("%void".to_string()), vec!["OpTypeVoid".to_string()])); + ops.push((Some("%3".to_string()), vec!["OpTypeFunction".to_string(), "%void".to_string()])); + ops.push((Some("%main".to_string()), vec!["OpFunction".to_string(), "%void".to_string(), "None".to_string(), "%3".to_string()])); + ops.push((Some("%5".to_string()), vec!["OpLabel".to_string()])); + ops.push((None, vec!["OpReturn".to_string()])); + ops.push((None, vec!["OpFunctionEnd".to_string()])); + + let mut out: String = String::new(); + + for op in ops { + if op.0.is_some() { + write!(out, "{} = ", op.0.unwrap()).unwrap(); + } + for arg in op.1 { + write!(out, "{} ", arg).unwrap(); + } + writeln!(out).unwrap(); + } + println!("{}", out); +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..c90444f --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,68 @@ +#[cfg(test)] +mod tests; + +use std::iter::Peekable; +use std::vec::IntoIter; + +#[derive(Debug, PartialEq)] +pub enum Token { + LeftParen, + RightParen, + Symbol(String), +} + +#[derive(Debug, PartialEq)] +pub enum Ast { + Symbol(String), + List(Vec), + Root(Vec), +} + +pub fn tokenize(input: &str) -> Vec { + let mut tokens = Vec::new(); + let mut chars = input.chars().peekable(); + while let Some(c) = chars.next() { + match c { + '(' => tokens.push(Token::LeftParen), + ')' => tokens.push(Token::RightParen), + _ if c.is_whitespace() => (), + _ => { + let mut symbol = c.to_string(); + while let Some(&c) = chars.peek() { + if c.is_whitespace() || c == '(' || c == ')' { + break; + } + symbol.push(c); + chars.next(); + } + tokens.push(Token::Symbol(symbol)); + } + } + } + tokens +} + +fn parse_expr(tokens: &mut Peekable>) -> Ast { + match tokens.next() { + Some(Token::LeftParen) => { + let mut list = Vec::new(); + while tokens.peek() != Some(&Token::RightParen) { + list.push(parse_expr(tokens)); + } + tokens.next(); + Ast::List(list) + } + Some(Token::RightParen) => panic!("unexpected )"), + Some(Token::Symbol(s)) => Ast::Symbol(s), + None => panic!("unexpected EOF"), + } +} + +pub fn parse(tokens: Vec) -> Ast { + let mut tokens = tokens.into_iter().peekable(); + let mut ast: Vec = Vec::new(); + while tokens.peek().is_some() { + ast.push(parse_expr(&mut tokens)); + } + Ast::Root(ast) +} \ No newline at end of file diff --git a/src/parser/tests.rs b/src/parser/tests.rs new file mode 100644 index 0000000..da40601 --- /dev/null +++ b/src/parser/tests.rs @@ -0,0 +1,125 @@ +use crate::parser::{tokenize, parse, Token, Ast}; + +#[test] +fn test_tokenize() { + let input = "(+ (* 1:u8 2) 2)"; + let expected = vec![ + Token::LeftParen, + Token::Symbol("+".to_string()), + Token::LeftParen, + Token::Symbol("*".to_string()), + Token::Symbol("1:u8".to_string()), + Token::Symbol("2".to_string()), + Token::RightParen, + Token::Symbol("2".to_string()), + Token::RightParen, + ]; + assert_eq!(tokenize(input), expected); +} + +#[test] +fn test_parse() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*vec4fi) (Builtin FragCoord)) +(bind (out-color:*vec4fo) (Location 0)) +(dec frag-coord:*vec4fi Input) +(dec out-color:*vec4fo Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (vec4fi (/ (.xy (load-ptr frag-coord)) + (vec2f 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:?}", ast); + let test_ast: Ast = Ast::Root(vec![ + Ast::List(vec![ + Ast::Symbol("module".to_string()), + Ast::Symbol("Shader".to_string()), + Ast::Symbol("Logical".to_string()), + Ast::Symbol("GLSL450".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("import".to_string()), + Ast::Symbol(":std".to_string()), + Ast::Symbol("GLSL.std.450".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("bind".to_string()), + Ast::List(vec![ + Ast::Symbol("frag-coord:*vec4fi".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("Builtin".to_string()), + Ast::Symbol("FragCoord".to_string()), + ]), + ]), + Ast::List(vec![ + Ast::Symbol("bind".to_string()), + Ast::List(vec![ + Ast::Symbol("out-color:*vec4fo".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("Location".to_string()), + Ast::Symbol("0".to_string()), + ]), + ]), + Ast::List(vec![ + Ast::Symbol("dec".to_string()), + Ast::Symbol("frag-coord:*vec4fi".to_string()), + Ast::Symbol("Input".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("dec".to_string()), + Ast::Symbol("out-color:*vec4fo".to_string()), + Ast::Symbol("Output".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("entry".to_string()), + Ast::Symbol("main".to_string()), + Ast::Symbol("Fragment".to_string()), + Ast::Symbol("OriginUpperLeft".to_string()), + Ast::List(vec![ + Ast::Symbol(":frag-coord".to_string()), + Ast::Symbol(":out-color".to_string()), + ]), + ]), + Ast::List(vec![ + Ast::Symbol("fun".to_string()), + Ast::List(vec![ + Ast::Symbol("main".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("store-ptr".to_string()), + Ast::List(vec![ + Ast::Symbol("out-color".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("vec4fi".to_string()), + Ast::List(vec![ + Ast::Symbol("/".to_string()), + Ast::List(vec![ + Ast::Symbol(".xy".to_string()), + Ast::List(vec![ + Ast::Symbol("load-ptr".to_string()), + Ast::Symbol("frag-coord".to_string()), + ]), + ]), + Ast::List(vec![ + Ast::Symbol("vec2f".to_string()), + Ast::Symbol("1920.0".to_string()), + Ast::Symbol("1080.0".to_string()), + ]), + ]), + Ast::Symbol("1.0".to_string()), + Ast::Symbol("1.0".to_string()), + ]), + ]), + ]), + ]); + assert_eq!(ast, test_ast); +} \ No newline at end of file -- 2.39.1 From da68ab6e737ed008e53360c5d23e5e8ee08b28ce Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 09:18:02 +0100 Subject: [PATCH 02/43] Fix the implicit type generators for vectors Instead of vec4f, we now have v4f32, for vector 4 of float 32 bits --- src/parser/tests.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/parser/tests.rs b/src/parser/tests.rs index da40601..be5484b 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -22,15 +22,15 @@ fn test_parse() { let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) -(bind (frag-coord:*vec4fi) (Builtin FragCoord)) -(bind (out-color:*vec4fo) (Location 0)) -(dec frag-coord:*vec4fi Input) -(dec out-color:*vec4fo Output) +(bind (frag-coord:*v4fi) (Builtin FragCoord)) +(bind (out-color:*v4fo) (Location 0)) +(dec frag-coord:*v4fi Input) +(dec out-color:*v4fo Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) (fun (main) (store-ptr (out-color) - (vec4fi (/ (.xy (load-ptr frag-coord)) - (vec2f 1920.0 1080.0)) + (v4fi (/ (.xy (load-ptr frag-coord)) + (v2f 1920.0 1080.0)) 1.0 1.0))) "; @@ -51,7 +51,7 @@ fn test_parse() { Ast::List(vec![ Ast::Symbol("bind".to_string()), Ast::List(vec![ - Ast::Symbol("frag-coord:*vec4fi".to_string()), + Ast::Symbol("frag-coord:*v4fi".to_string()), ]), Ast::List(vec![ Ast::Symbol("Builtin".to_string()), @@ -61,7 +61,7 @@ fn test_parse() { Ast::List(vec![ Ast::Symbol("bind".to_string()), Ast::List(vec![ - Ast::Symbol("out-color:*vec4fo".to_string()), + Ast::Symbol("out-color:*v4fo".to_string()), ]), Ast::List(vec![ Ast::Symbol("Location".to_string()), @@ -70,12 +70,12 @@ fn test_parse() { ]), Ast::List(vec![ Ast::Symbol("dec".to_string()), - Ast::Symbol("frag-coord:*vec4fi".to_string()), + Ast::Symbol("frag-coord:*v4fi".to_string()), Ast::Symbol("Input".to_string()), ]), Ast::List(vec![ Ast::Symbol("dec".to_string()), - Ast::Symbol("out-color:*vec4fo".to_string()), + Ast::Symbol("out-color:*v4fo".to_string()), Ast::Symbol("Output".to_string()), ]), Ast::List(vec![ @@ -99,7 +99,7 @@ fn test_parse() { Ast::Symbol("out-color".to_string()), ]), Ast::List(vec![ - Ast::Symbol("vec4fi".to_string()), + Ast::Symbol("v4fi".to_string()), Ast::List(vec![ Ast::Symbol("/".to_string()), Ast::List(vec![ @@ -110,7 +110,7 @@ fn test_parse() { ]), ]), Ast::List(vec![ - Ast::Symbol("vec2f".to_string()), + Ast::Symbol("v2f".to_string()), Ast::Symbol("1920.0".to_string()), Ast::Symbol("1080.0".to_string()), ]), -- 2.39.1 From 07dc4d62d9ad1f1fef9d1e0a752662dbde0e5768 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 10:09:56 +0100 Subject: [PATCH 03/43] Initial work on the compiler So far it only generates the metadata --- src/compiler/mod.rs | 297 ++++++++++++++++++++++++++++++++++++++++++ src/compiler/tests.rs | 25 ++++ src/main.rs | 1 + src/parser/mod.rs | 2 +- src/parser/tests.rs | 24 ++-- 5 files changed, 336 insertions(+), 13 deletions(-) create mode 100644 src/compiler/mod.rs create mode 100644 src/compiler/tests.rs diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs new file mode 100644 index 0000000..2bf5e06 --- /dev/null +++ b/src/compiler/mod.rs @@ -0,0 +1,297 @@ +use crate::parser::Ast; + +#[cfg(test)] +mod tests; + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum Capability { + #[default] + Shader +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum ExecutionMode { + #[default] + OriginUpperLeft +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum ExecutionModel { + #[default] + Fragment +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct EntryPoint { + pub execution_model: ExecutionModel, + pub execution_mode: ExecutionMode, + pub name: String, + pub interface: Vec, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum AddressingModel { + #[default] + Logical, + Physical32, + Physical64, + PhysicalStorageBuffer64 +} +#[derive(Debug, PartialEq, Default, Clone)] +pub enum MemoryModel { + #[default] + GLSL450, + OpenCL, + VulkanKHR, + Simple +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Memory { + pub addressing_model: AddressingModel, + pub memory_model: MemoryModel, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum BuiltinDecoration { + #[default] + FragCoord, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Decoration { + Builtin(BuiltinDecoration), + Location(u32), +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum StorageClass { + #[default] + Undefined, + Input, + Output, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct GlobalVariable { + pub name: String, + pub typ: String, + pub storage_class: StorageClass, + pub decorations: Vec, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Instruction { + pub result_id: Option, + pub op: String, + pub operands: Vec, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Function { + pub name: String, + pub return_type: String, + pub arguments: Vec, + pub body: Option>, + pub ast: Option, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Import { + pub name: String, + pub value: String, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Module { + pub capabilities: Vec, + pub entry_points: Vec, + pub globals: Vec, + pub functions: Vec, + pub memory_model: Memory, + pub imports: Vec, +} + +pub fn meta_compile(ast: &mut Ast) -> Module { + let mut module: Module = Module::default(); + match ast { + Ast::Root(root) => { + for node in root { + match node { + Ast::List(list) => { + let keyword = list[0].clone(); + match keyword { + Ast::Symbol(sym) => { + match sym.as_str() { + "module" => { + let cap = list[1].clone(); + let exec = list[2].clone(); + let memory = list[3].clone(); + module.memory_model = Memory { + addressing_model: AddressingModel::Logical, + memory_model: MemoryModel::GLSL450, + }; + module.capabilities.push(Capability::Shader); + assert_eq!(exec, Ast::Symbol("Logical".to_string())); + assert_eq!(memory, Ast::Symbol("GLSL450".to_string())); + assert_eq!(cap, Ast::Symbol("Shader".to_string())); + } + "import" => { + let name = match &list[1] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[1]), + }; + let value = match &list[2] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[2]), + }; + module.imports.push(Import { + name: name.to_string(), + value: value.to_string(), + }); + } + "bind" => { + let name_and_type = match &list[1] { + Ast::List(l) => l, + _ => panic!("Expected list! {:?}", list[1]), + }; + let name_and_type: String = name_and_type.iter().map(|x| { + match x { + Ast::Symbol(s) => s.to_string(), + _ => panic!("Expected symbol! {:?}", x), + } + }).collect(); + // name_and_type is of the name:type format, like foo:f32 + let name: String = name_and_type.split(":").collect::>()[0].to_string(); + let typ: String = name_and_type.split(":").collect::>()[1].to_string(); + let bind = match &list[2] { + Ast::List(l) => l, + _ => panic!("Expected list! {:?}", list[2]), + }; + let bind: Vec = bind.iter().map(|x| { + match x { + Ast::Symbol(s) => s.to_string(), + _ => panic!("Expected symbol! {:?}", x), + } + }).collect(); + let bind_name = match bind[0].as_str() { + "Builtin" => Decoration::Builtin(BuiltinDecoration::FragCoord), + "Location" => Decoration::Location(bind[1].parse::().unwrap()), + _ => panic!("Unknown bind! {:?}", bind), + }; + let mut exists = false; + for var in &module.globals { + if var.name == name { + exists = true; + } + } + if !exists { + module.globals.push(GlobalVariable { + name: name, + typ: typ, + storage_class: StorageClass::Undefined, + decorations: vec![bind_name], + }); + } + } + "dec" => { + let name_and_type = match &list[1] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[1]), + }; + let name: String = name_and_type.split(":").collect::>()[0].to_string(); + let typ: String = name_and_type.split(":").collect::>()[1].to_string(); + let storage_class = match &list[2] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[2]), + }; + let mut exists = false; + for var in &module.globals { + if var.name.as_str() == name.as_str() { + exists = true; + } + } + let storage_class = match storage_class.as_str() { + "Input" => StorageClass::Input, + "Output" => StorageClass::Output, + _ => StorageClass::Undefined, + }; + if !exists { + module.globals.push(GlobalVariable { + name: name.to_string(), + typ: typ.to_string(), + storage_class: storage_class.clone(), + decorations: vec![], + }); + } + if exists { + module.globals.iter_mut() + .find(|x| x.name.as_str() == name.as_str()) + .unwrap().storage_class = storage_class.clone(); + } + } + "entry" => { + let name = match &list[1] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[1]), + }; + let exec_model = match &list[2] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[2]), + }; + let exec_mode = match &list[3] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[3]), + }; + let interface = match &list[4] { + Ast::List(l) => l, + _ => panic!("Expected list! {:?}", list[4]), + }; + let interface: Vec = interface.iter().map(|x| { + match x { + Ast::Symbol(s) => s.to_string(), + _ => panic!("Expected symbol! {:?}", x), + } + }).collect(); + module.entry_points.push(EntryPoint { + execution_model: ExecutionModel::Fragment, + execution_mode: ExecutionMode::OriginUpperLeft, + name: name.to_string(), + interface: interface, + }); + assert_eq!(exec_model, "Fragment"); + assert_eq!(exec_mode, "OriginUpperLeft"); + } + "fun" => { + let name = match &list[1] { + Ast::List(l) => l[0].clone(), + _ => panic!("Expected list! {:?}", list[1]), + }; + let name = match name { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", name), + }; + let body = list[2..].to_vec(); + let fun = Function { + name: name.to_string(), + return_type: "void".to_string(), + arguments: vec![], + body: Some(vec![]), + ast: Some(Ast::List(body)), + }; + module.functions.push(fun); + } + _ => panic!("Unknown keyword! {:?}", sym), + } + } + _ => panic!("List where a keyword was expected! {:?}", keyword), + } + } + _ => panic!("Top-level symbol! {:?}", node), + } + } + } + _ => panic!("Non-root ast") + } + module +} \ No newline at end of file diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs new file mode 100644 index 0000000..c8012a1 --- /dev/null +++ b/src/compiler/tests.rs @@ -0,0 +1,25 @@ +use crate::parser::{tokenize, parse}; +use crate::compiler::meta_compile; + +#[test] +fn test_compile() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (Builtin FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let mut ast = parse(tokenize(src)); + println!("{:?}", ast); + let module = meta_compile(&mut ast); + println!("{:?}", module); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 49c0ece..ebebce3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use std::fmt::Write; pub mod parser; +pub mod compiler; fn main() { let mut ops: Vec<(Option, Vec)> = Vec::new(); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c90444f..c56d16f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11,7 +11,7 @@ pub enum Token { Symbol(String), } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Ast { Symbol(String), List(Vec), diff --git a/src/parser/tests.rs b/src/parser/tests.rs index be5484b..6251515 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -22,15 +22,15 @@ fn test_parse() { let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) -(bind (frag-coord:*v4fi) (Builtin FragCoord)) -(bind (out-color:*v4fo) (Location 0)) -(dec frag-coord:*v4fi Input) -(dec out-color:*v4fo Output) +(bind (frag-coord:*v4f32i) (Builtin FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) (fun (main) (store-ptr (out-color) - (v4fi (/ (.xy (load-ptr frag-coord)) - (v2f 1920.0 1080.0)) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) 1.0 1.0))) "; @@ -51,7 +51,7 @@ fn test_parse() { Ast::List(vec![ Ast::Symbol("bind".to_string()), Ast::List(vec![ - Ast::Symbol("frag-coord:*v4fi".to_string()), + Ast::Symbol("frag-coord:*v4f32i".to_string()), ]), Ast::List(vec![ Ast::Symbol("Builtin".to_string()), @@ -61,7 +61,7 @@ fn test_parse() { Ast::List(vec![ Ast::Symbol("bind".to_string()), Ast::List(vec![ - Ast::Symbol("out-color:*v4fo".to_string()), + Ast::Symbol("out-color:*v4f32o".to_string()), ]), Ast::List(vec![ Ast::Symbol("Location".to_string()), @@ -70,12 +70,12 @@ fn test_parse() { ]), Ast::List(vec![ Ast::Symbol("dec".to_string()), - Ast::Symbol("frag-coord:*v4fi".to_string()), + Ast::Symbol("frag-coord:*v4f32i".to_string()), Ast::Symbol("Input".to_string()), ]), Ast::List(vec![ Ast::Symbol("dec".to_string()), - Ast::Symbol("out-color:*v4fo".to_string()), + Ast::Symbol("out-color:*v4f32o".to_string()), Ast::Symbol("Output".to_string()), ]), Ast::List(vec![ @@ -99,7 +99,7 @@ fn test_parse() { Ast::Symbol("out-color".to_string()), ]), Ast::List(vec![ - Ast::Symbol("v4fi".to_string()), + Ast::Symbol("v4f32i".to_string()), Ast::List(vec![ Ast::Symbol("/".to_string()), Ast::List(vec![ @@ -110,7 +110,7 @@ fn test_parse() { ]), ]), Ast::List(vec![ - Ast::Symbol("v2f".to_string()), + Ast::Symbol("v2f32".to_string()), Ast::Symbol("1920.0".to_string()), Ast::Symbol("1080.0".to_string()), ]), -- 2.39.1 From bc2394bb4e6744e9853f16023ef4a5f19598ea4d Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 10:15:30 +0100 Subject: [PATCH 04/43] Tests Added a regression assert into the compile test --- src/compiler/tests.rs | 64 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index c8012a1..2915262 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -1,8 +1,8 @@ use crate::parser::{tokenize, parse}; -use crate::compiler::meta_compile; #[test] fn test_compile() { + use crate::compiler::*; let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) @@ -19,7 +19,65 @@ fn test_compile() { 1.0))) "; let mut ast = parse(tokenize(src)); - println!("{:?}", ast); + println!("{:#?}", ast); let module = meta_compile(&mut ast); - println!("{:?}", module); + println!("{:#?}", module); + //let test_module: Module = Module { capabilities: [Shader], entry_points: [EntryPoint { execution_model: Fragment, execution_mode: OriginUpperLeft, name: "main", interface: [":frag-coord", ":out-color"] }], globals: [GlobalVariable { name: "frag-coord", typ: "*v4f32i", storage_class: Input, decorations: [Builtin(FragCoord)] }, GlobalVariable { name: "out-color", typ: "*v4f32o", storage_class: Output, decorations: [Location(0)] }], functions: [Function { name: "main", return_type: "void", arguments: [], body: Some([]), ast: Some(List([List([Symbol("store-ptr"), List([Symbol("out-color")]), List([Symbol("v4f32i"), List([Symbol("/"), List([Symbol(".xy"), List([Symbol("load-ptr"), Symbol("frag-coord")])]), List([Symbol("v2f32"), Symbol("1920.0"), Symbol("1080.0")])]), Symbol("1.0"), Symbol("1.0")])])])) }], memory_model: Memory { addressing_model: Logical, memory_model: GLSL450 }, imports: [Import { name: ":std", value: "GLSL.std.450" }] }; + let test_module = Module { + capabilities: vec![Capability::Shader], + entry_points: vec![EntryPoint { + execution_model: ExecutionModel::Fragment, + execution_mode: ExecutionMode::OriginUpperLeft, + name: "main".to_string(), + interface: vec![":frag-coord".to_string(), ":out-color".to_string()], + }], + globals: vec![ + GlobalVariable { + name: "frag-coord".to_string(), + typ: "*v4f32i".to_string(), + storage_class: StorageClass::Input, + decorations: vec![Decoration::Builtin(BuiltinDecoration::FragCoord)], + }, + GlobalVariable { + name: "out-color".to_string(), + typ: "*v4f32o".to_string(), + storage_class: StorageClass::Output, + decorations: vec![Decoration::Location(0)], + }, + ], + functions: vec![Function { + name: "main".to_string(), + return_type: "void".to_string(), + arguments: vec![], + body: Some(vec![]), + ast: Some(Ast::List(vec![ + Ast::List(vec![ + Ast::Symbol("store-ptr".to_string()), + Ast::List(vec![Ast::Symbol("out-color".to_string())]), + Ast::List(vec![ + Ast::Symbol("v4f32i".to_string()), + Ast::List(vec![ + Ast::Symbol("/".to_string()), + Ast::List(vec![ + Ast::Symbol(".xy".to_string()), + Ast::List(vec![Ast::Symbol("load-ptr".to_string()), Ast::Symbol("frag-coord".to_string())]), + ]), + Ast::List(vec![Ast::Symbol("v2f32".to_string()), Ast::Symbol("1920.0".to_string()), Ast::Symbol("1080.0".to_string())]), + ]), + Ast::Symbol("1.0".to_string()), + Ast::Symbol("1.0".to_string()), + ]), + ]), + ])), + }], + memory_model: Memory { + addressing_model: AddressingModel::Logical, + memory_model: MemoryModel::GLSL450, + }, + imports: vec![Import { + name: ":std".to_string(), + value: "GLSL.std.450".to_string(), + }], + }; + assert_eq!(module, test_module); } \ No newline at end of file -- 2.39.1 From bee73acb5e0cb5d01fa4468acfb8af4372c46626 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 10:48:40 +0100 Subject: [PATCH 05/43] Initial work on SPIR-V assembly generation Atm it only handles the basic metadata in modules. Doesn't generate code for functions, or types. Type handling is just a placeholder %undefined_type. --- src/compiler/backend/mod.rs | 87 +++++++++++++++++++++++++++++++++++ src/compiler/backend/tests.rs | 25 ++++++++++ src/compiler/mod.rs | 13 ++++-- src/compiler/tests.rs | 5 +- src/parser/tests.rs | 4 +- 5 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 src/compiler/backend/mod.rs create mode 100644 src/compiler/backend/tests.rs diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs new file mode 100644 index 0000000..77e4b81 --- /dev/null +++ b/src/compiler/backend/mod.rs @@ -0,0 +1,87 @@ +#[cfg(test)] +mod tests; + +use crate::compiler::Module; +use std::{fmt::Write, ops::Add}; + +pub fn fix_name(name: &String) -> String { + format!("%{}", name.clone().replace("-", "_")) +} + +pub fn spirv_meta(module: Module) -> String { + let mut spirv_asm = String::new(); + let mut ops: Vec<(Option, Vec)> = Vec::new(); + + let capabilities: Vec = module.capabilities.iter() + .map(|c| format!("{:?}", c)) + .collect(); + for cap in capabilities { + ops.push((None, vec!["OpCapability".to_string(), cap])); + } + + let memory_model_address = match module.memory_model.addressing_model { + crate::compiler::AddressingModel::Logical => "Logical", + crate::compiler::AddressingModel::Physical32 => "Physical32", + crate::compiler::AddressingModel::Physical64 => "Physical64", + crate::compiler::AddressingModel::PhysicalStorageBuffer64 => "PhysicalStorageBuffer64", + }; + let memory_model_model = match module.memory_model.memory_model { + crate::compiler::MemoryModel::Simple => "Simple", + crate::compiler::MemoryModel::GLSL450 => "GLSL450", + crate::compiler::MemoryModel::OpenCL => "OpenCL", + _ => todo!(), + }; + ops.push((None, vec!["OpMemoryModel".to_string(), memory_model_address.to_string(), memory_model_model.to_string()])); + + for entry in module.entry_points { + let exec_model = match entry.execution_model { + crate::compiler::ExecutionModel::Fragment => "Fragment", + crate::compiler::ExecutionModel::Vertex => "Vertex", + }; + let name = entry.name; + let interface: Vec = entry.interface.iter() + .map(|i| fix_name(&i.to_string())) + .collect(); + let exec_mode = match entry.execution_mode { + crate::compiler::ExecutionMode::OriginUpperLeft => "OriginUpperLeft", + }; + ops.push((None, vec!["OpEntryPoint".to_string(), exec_model.to_string(), fix_name(&name), format!("\"{}\"", name), interface.join(" ")])); + ops.push((None, vec!["OpExecutionMode".to_string(), fix_name(&name), exec_mode.to_string()])); + } + + for global in module.globals { + let name = fix_name(&global.name); + let typ = global.typ; + let storage_class = match global.storage_class { + crate::compiler::StorageClass::Input => "Input", + crate::compiler::StorageClass::Output => "Output", + crate::compiler::StorageClass::Undefined => panic!("Bound a non-declared variable"), + }; + let typ_id = "%undefined_type"; + ops.push((Some(name.clone()), vec!["OpVariable".to_string(), typ_id.to_string(), storage_class.to_string()])); + for dec in global.decorations { + // Decorations have the format Location 0, or Builtin FragCoord + let dec = match dec { + crate::compiler::Decoration::Location(loc) => format!("Location {}", loc), + crate::compiler::Decoration::BuiltIn(builtin) => { + let builtin = match builtin { + crate::compiler::BuiltinDecoration::FragCoord => "FragCoord", + }; + format!("BuiltIn {}", builtin) + }, + }; + ops.push((None, vec!["OpDecorate".to_string(), name.clone(), dec])); + } + } + + for op in ops { + if op.0.is_some() { + write!(spirv_asm, "{} = ", op.0.unwrap()).unwrap(); + } + for arg in op.1 { + write!(spirv_asm, "{} ", arg).unwrap(); + } + writeln!(spirv_asm).unwrap(); + } + spirv_asm +} \ No newline at end of file diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs new file mode 100644 index 0000000..87c258d --- /dev/null +++ b/src/compiler/backend/tests.rs @@ -0,0 +1,25 @@ +#[test] +fn test_emit() { + use crate::compiler::*; + use crate::parser::*; + use crate::compiler::backend::*; + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let mut ast = parse(tokenize(src)); + let module = meta_compile(&mut ast); + let res = spirv_meta(module); + println!("{}", res); +} \ No newline at end of file diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 2bf5e06..9a83957 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,5 +1,7 @@ use crate::parser::Ast; +pub mod backend; + #[cfg(test)] mod tests; @@ -18,7 +20,8 @@ pub enum ExecutionMode { #[derive(Debug, PartialEq, Default, Clone)] pub enum ExecutionModel { #[default] - Fragment + Fragment, + Vertex, } #[derive(Debug, PartialEq, Default, Clone)] @@ -60,7 +63,7 @@ pub enum BuiltinDecoration { #[derive(Debug, PartialEq, Clone)] pub enum Decoration { - Builtin(BuiltinDecoration), + BuiltIn(BuiltinDecoration), Location(u32), } @@ -175,7 +178,7 @@ pub fn meta_compile(ast: &mut Ast) -> Module { } }).collect(); let bind_name = match bind[0].as_str() { - "Builtin" => Decoration::Builtin(BuiltinDecoration::FragCoord), + "BuiltIn" => Decoration::BuiltIn(BuiltinDecoration::FragCoord), "Location" => Decoration::Location(bind[1].parse::().unwrap()), _ => panic!("Unknown bind! {:?}", bind), }; @@ -249,7 +252,9 @@ pub fn meta_compile(ast: &mut Ast) -> Module { }; let interface: Vec = interface.iter().map(|x| { match x { - Ast::Symbol(s) => s.to_string(), + Ast::Symbol(s) => { + s.to_string().replace(":", "") + }, _ => panic!("Expected symbol! {:?}", x), } }).collect(); diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 2915262..08b18e9 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -6,7 +6,7 @@ fn test_compile() { let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) -(bind (frag-coord:*v4f32i) (Builtin FragCoord)) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) (bind (out-color:*v4f32o) (Location 0)) (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) @@ -22,7 +22,6 @@ fn test_compile() { println!("{:#?}", ast); let module = meta_compile(&mut ast); println!("{:#?}", module); - //let test_module: Module = Module { capabilities: [Shader], entry_points: [EntryPoint { execution_model: Fragment, execution_mode: OriginUpperLeft, name: "main", interface: [":frag-coord", ":out-color"] }], globals: [GlobalVariable { name: "frag-coord", typ: "*v4f32i", storage_class: Input, decorations: [Builtin(FragCoord)] }, GlobalVariable { name: "out-color", typ: "*v4f32o", storage_class: Output, decorations: [Location(0)] }], functions: [Function { name: "main", return_type: "void", arguments: [], body: Some([]), ast: Some(List([List([Symbol("store-ptr"), List([Symbol("out-color")]), List([Symbol("v4f32i"), List([Symbol("/"), List([Symbol(".xy"), List([Symbol("load-ptr"), Symbol("frag-coord")])]), List([Symbol("v2f32"), Symbol("1920.0"), Symbol("1080.0")])]), Symbol("1.0"), Symbol("1.0")])])])) }], memory_model: Memory { addressing_model: Logical, memory_model: GLSL450 }, imports: [Import { name: ":std", value: "GLSL.std.450" }] }; let test_module = Module { capabilities: vec![Capability::Shader], entry_points: vec![EntryPoint { @@ -36,7 +35,7 @@ fn test_compile() { name: "frag-coord".to_string(), typ: "*v4f32i".to_string(), storage_class: StorageClass::Input, - decorations: vec![Decoration::Builtin(BuiltinDecoration::FragCoord)], + decorations: vec![Decoration::BuiltIn(BuiltinDecoration::FragCoord)], }, GlobalVariable { name: "out-color".to_string(), diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 6251515..dd6a947 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -22,7 +22,7 @@ fn test_parse() { let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) -(bind (frag-coord:*v4f32i) (Builtin FragCoord)) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) (bind (out-color:*v4f32o) (Location 0)) (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) @@ -54,7 +54,7 @@ fn test_parse() { Ast::Symbol("frag-coord:*v4f32i".to_string()), ]), Ast::List(vec![ - Ast::Symbol("Builtin".to_string()), + Ast::Symbol("BuiltIn".to_string()), Ast::Symbol("FragCoord".to_string()), ]), ]), -- 2.39.1 From 6343fdf1fc4b0ca895b48c90ca1dea15c7d7452f Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 11:25:52 +0100 Subject: [PATCH 06/43] Broken; Rust's Display trait is weird. Help. --- src/compiler/backend/mod.rs | 118 ++++++++++++++++++++++++++++++++-- src/compiler/backend/tests.rs | 14 ++++ src/compiler/mod.rs | 12 ++++ src/compiler/tests.rs | 2 +- 4 files changed, 141 insertions(+), 5 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index 77e4b81..74193c5 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -2,9 +2,114 @@ mod tests; use crate::compiler::Module; -use std::{fmt::Write, ops::Add}; +use std::fmt::Write; +use std::fmt; -pub fn fix_name(name: &String) -> String { +use super::StorageClass; + +#[derive(Debug, PartialEq, Clone)] +pub enum Type { + Pointer(Box, StorageClass), + Vector(Box, u32), + Float(u32), + Int(u32), + Unsigned(u32), +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Type::Pointer(typ, storage_class) => { + match storage_class { + StorageClass::Input => write!(f, "*{}i", typ), + StorageClass::Output => write!(f, "*{}o", typ), + StorageClass::Undefined => panic!("Bound a non-declared variable"), + } + }, + Type::Vector(typ, size) => write!(f, "v{}{}", size, typ), + Type::Float(size) => write!(f, "f{}", size), + Type::Int(size) => write!(f, "s{}", size), + Type::Unsigned(size) => write!(f, "u{}", size), + } + } +} + +pub fn parse_type(typ: String) -> Type { + // pointers have the format *i or *o, for the storage class + // floats have the format f, so f32, f64... + // ints have the format s, so s32, s64... + // unsigned ints have the format u, so u32, u64... + // So, *v4f32i is a pointer to a vector of 4 floats, with the storage class Input. + // *v4f32o is a pointer to a vector of 4 floats, with the storage class Output. + + let c = typ.chars().next().unwrap(); + match c { + '*' => { + let mut chars = typ.chars(); + chars.next(); + let typ = chars.collect::(); + let storage_class = match typ.chars().last().unwrap() { + 'i' => StorageClass::Input, + 'o' => StorageClass::Output, + _ => panic!("Invalid storage class"), + }; + let typ = typ.chars().take(typ.len() - 1).collect::(); + Type::Pointer(Box::new(parse_type(typ)), storage_class) + }, + 'v' => { + let mut chars = typ.chars(); + chars.next(); + let size = chars.next().unwrap().to_digit(10).unwrap(); + let typ = chars.collect::(); + Type::Vector(Box::new(parse_type(typ)), size) + }, + 'f' => { + let size = typ.chars().skip(1).collect::().parse().unwrap(); + Type::Float(size) + }, + 's' => { + let size = typ.chars().skip(1).collect::().parse().unwrap(); + Type::Int(size) + }, + 'u' => { + let size = typ.chars().skip(1).collect::().parse().unwrap(); + Type::Unsigned(size) + }, + _ => panic!("Invalid type"), + } + +} + +fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { + match typ { + Type::Unsigned(size) => { + ops.push((format!("%u{}", size).to_string(), format!("OpTypeInt {}", size))); + }, + Type::Int(size) => { + ops.push((format!("%s{}", size).to_string(), format!("OpTypeInt {}", size))); + }, + Type::Float(size) => { + ops.push((format!("%f{}", size).to_string(), format!("OpTypeFloat {}", size))); + }, + Type::Vector(typ, size) => { + emit_type(*typ.clone(), ops); + let typ_id = format!("%{}", *typ.clone()); + ops.push((format!("%v{}{}", typ_id, size).to_string(), format!("OpTypeVector {} {}", typ_id, size))) + }, + Type::Pointer(typ, storage_class) => { + emit_type(*typ.clone(), ops); + let typ_id = format!("%{}", *typ.clone()); + let storage_class = match storage_class { + StorageClass::Input => "Input", + StorageClass::Output => "Output", + StorageClass::Undefined => panic!("Bound a non-declared variable"), + }; + ops.push((format!("%{}{}", typ_id, storage_class).to_string(), format!("OpTypePointer {} {}", storage_class, typ_id))) + }, + } +} + +fn fix_name(name: &String) -> String { format!("%{}", name.clone().replace("-", "_")) } @@ -51,13 +156,18 @@ pub fn spirv_meta(module: Module) -> String { for global in module.globals { let name = fix_name(&global.name); - let typ = global.typ; + let _typ = global.typ; let storage_class = match global.storage_class { crate::compiler::StorageClass::Input => "Input", crate::compiler::StorageClass::Output => "Output", crate::compiler::StorageClass::Undefined => panic!("Bound a non-declared variable"), }; - let typ_id = "%undefined_type"; + let typ_id = format!("%{}", _typ); + let mut type_ops = Vec::new(); + emit_type(parse_type(_typ), &mut type_ops); + for op in type_ops { + ops.push((Some(op.0), vec![op.1])); + } ops.push((Some(name.clone()), vec!["OpVariable".to_string(), typ_id.to_string(), storage_class.to_string()])); for dec in global.decorations { // Decorations have the format Location 0, or Builtin FragCoord diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 87c258d..e84c06b 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -22,4 +22,18 @@ fn test_emit() { let module = meta_compile(&mut ast); let res = spirv_meta(module); println!("{}", res); +} + +#[test] +fn test_type_parse() { + use crate::compiler::backend::*; + use Type::*; + assert_eq!(parse_type("*v4f32i".to_string()), + Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Input)); + assert_eq!(parse_type("*v4f32o".to_string()), + Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Output)); + assert_eq!(parse_type("v2f32".to_string()), + Vector(Box::new(Float(32)), 2)); + assert_eq!(parse_type("f32".to_string()), Float(32)); + assert_eq!(parse_type("s32".to_string()), Int(32)); } \ No newline at end of file diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 9a83957..140a57a 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -2,6 +2,8 @@ use crate::parser::Ast; pub mod backend; +use std::fmt; + #[cfg(test)] mod tests; @@ -75,6 +77,16 @@ pub enum StorageClass { Output, } +impl fmt::Display for StorageClass { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + StorageClass::Undefined => write!(f, "Undefined"), + StorageClass::Input => write!(f, "Input"), + StorageClass::Output => write!(f, "Output"), + } + } +} + #[derive(Debug, PartialEq, Default, Clone)] pub struct GlobalVariable { pub name: String, diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 08b18e9..33398bd 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -28,7 +28,7 @@ fn test_compile() { execution_model: ExecutionModel::Fragment, execution_mode: ExecutionMode::OriginUpperLeft, name: "main".to_string(), - interface: vec![":frag-coord".to_string(), ":out-color".to_string()], + interface: vec!["frag-coord".to_string(), "out-color".to_string()], }], globals: vec![ GlobalVariable { -- 2.39.1 From 8de6ef1ec3ec1329b8edb93a05b08c47c128c6db Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 11:38:58 +0100 Subject: [PATCH 07/43] Fixed type generation --- src/compiler/backend/mod.rs | 42 ++++++++++++++++++++++------------- src/compiler/backend/tests.rs | 10 ++++----- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index 74193c5..e88ceed 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -34,7 +34,7 @@ impl fmt::Display for Type { } } -pub fn parse_type(typ: String) -> Type { +pub fn parse_type(typ: &String) -> Type { // pointers have the format *i or *o, for the storage class // floats have the format f, so f32, f64... // ints have the format s, so s32, s64... @@ -54,14 +54,14 @@ pub fn parse_type(typ: String) -> Type { _ => panic!("Invalid storage class"), }; let typ = typ.chars().take(typ.len() - 1).collect::(); - Type::Pointer(Box::new(parse_type(typ)), storage_class) + Type::Pointer(Box::new(parse_type(&typ)), storage_class) }, 'v' => { let mut chars = typ.chars(); chars.next(); let size = chars.next().unwrap().to_digit(10).unwrap(); let typ = chars.collect::(); - Type::Vector(Box::new(parse_type(typ)), size) + Type::Vector(Box::new(parse_type(&typ)), size) }, 'f' => { let size = typ.chars().skip(1).collect::().parse().unwrap(); @@ -81,7 +81,7 @@ pub fn parse_type(typ: String) -> Type { } fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { - match typ { + match &typ { Type::Unsigned(size) => { ops.push((format!("%u{}", size).to_string(), format!("OpTypeInt {}", size))); }, @@ -91,26 +91,34 @@ fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { Type::Float(size) => { ops.push((format!("%f{}", size).to_string(), format!("OpTypeFloat {}", size))); }, - Type::Vector(typ, size) => { - emit_type(*typ.clone(), ops); - let typ_id = format!("%{}", *typ.clone()); - ops.push((format!("%v{}{}", typ_id, size).to_string(), format!("OpTypeVector {} {}", typ_id, size))) + Type::Vector(in_typ, size) => { + emit_type(*in_typ.clone(), ops); + ops.push((fix_name(&typ.to_string()), format!("OpTypeVector {} {}", fix_name(&in_typ.clone().to_string()), size))) }, - Type::Pointer(typ, storage_class) => { - emit_type(*typ.clone(), ops); - let typ_id = format!("%{}", *typ.clone()); + Type::Pointer(in_typ, storage_class) => { + emit_type(*in_typ.clone(), ops); + let typ_id = fix_name(&typ.to_string()); let storage_class = match storage_class { StorageClass::Input => "Input", StorageClass::Output => "Output", StorageClass::Undefined => panic!("Bound a non-declared variable"), }; - ops.push((format!("%{}{}", typ_id, storage_class).to_string(), format!("OpTypePointer {} {}", storage_class, typ_id))) + ops.push((typ_id, format!("OpTypePointer {} {}", storage_class, fix_name(&in_typ.to_string())))) }, } } fn fix_name(name: &String) -> String { - format!("%{}", name.clone().replace("-", "_")) + format!("%{}", name.clone().replace("-", "_").replace("*", "p")) +} + +fn has_id(name: String, ops: &Vec<(Option, Vec)>) -> bool { + for op in ops { + if op.0.is_some() && op.0.clone().unwrap() == name { + return true; + } + } + false } pub fn spirv_meta(module: Module) -> String { @@ -162,13 +170,15 @@ pub fn spirv_meta(module: Module) -> String { crate::compiler::StorageClass::Output => "Output", crate::compiler::StorageClass::Undefined => panic!("Bound a non-declared variable"), }; - let typ_id = format!("%{}", _typ); let mut type_ops = Vec::new(); - emit_type(parse_type(_typ), &mut type_ops); + emit_type(parse_type(&_typ), &mut type_ops); for op in type_ops { + if has_id(op.0.clone(), &ops) { + continue; + } ops.push((Some(op.0), vec![op.1])); } - ops.push((Some(name.clone()), vec!["OpVariable".to_string(), typ_id.to_string(), storage_class.to_string()])); + ops.push((Some(name.clone()), vec!["OpVariable".to_string(), fix_name(&_typ), storage_class.to_string()])); for dec in global.decorations { // Decorations have the format Location 0, or Builtin FragCoord let dec = match dec { diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index e84c06b..0f21e2f 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -28,12 +28,12 @@ fn test_emit() { fn test_type_parse() { use crate::compiler::backend::*; use Type::*; - assert_eq!(parse_type("*v4f32i".to_string()), + assert_eq!(parse_type(&"*v4f32i".to_string()), Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Input)); - assert_eq!(parse_type("*v4f32o".to_string()), + assert_eq!(parse_type(&"*v4f32o".to_string()), Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Output)); - assert_eq!(parse_type("v2f32".to_string()), + assert_eq!(parse_type(&"v2f32".to_string()), Vector(Box::new(Float(32)), 2)); - assert_eq!(parse_type("f32".to_string()), Float(32)); - assert_eq!(parse_type("s32".to_string()), Int(32)); + assert_eq!(parse_type(&"f32".to_string()), Float(32)); + assert_eq!(parse_type(&"s32".to_string()), Int(32)); } \ No newline at end of file -- 2.39.1 From 38fc11590bac73bdc8000f5dbab77a146737a664 Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 6 Mar 2025 11:25:39 +0100 Subject: [PATCH 08/43] Add error handling and location tracking to the tokenizer and parser --- src/main.rs | 93 +++++++++++++++++--- src/parser/mod.rs | 190 ++++++++++++++++++++++++++++++++++------ src/parser/tests.rs | 206 ++++++++++++++++++++++++-------------------- 3 files changed, 354 insertions(+), 135 deletions(-) diff --git a/src/main.rs b/src/main.rs index ebebce3..2374721 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ use std::fmt::Write; -pub mod parser; pub mod compiler; +pub mod parser; fn main() { let mut ops: Vec<(Option, Vec)> = Vec::new(); - + // OpMemoryModel Logical GLSL450 // OpEntryPoint Fragment %main "main" // OpExecutionMode %main OriginUpperLeft @@ -18,23 +18,88 @@ fn main() { //%5 = OpLabel // OpReturn // OpFunctionEnd - + ops.push((None, vec!["OpCapability".to_string(), "Shader".to_string()])); - ops.push((Some("%1".to_string()), vec!["OpExtInstImport".to_string(), "\"GLSL.std.450\"".to_string()])); - ops.push((None, vec!["OpMemoryModel".to_string(), "Logical".to_string(), "GLSL450".to_string()])); - ops.push((None, vec!["OpEntryPoint".to_string(), "Fragment".to_string(), "%main".to_string(), "\"main\"".to_string()])); - ops.push((None, vec!["OpExecutionMode".to_string(), "%main".to_string(), "OriginUpperLeft".to_string()])); - ops.push((None, vec!["OpSource".to_string(), "GLSL".to_string(), "450".to_string()])); - ops.push((None, vec!["OpSourceExtension".to_string(), "\"GL_GOOGLE_cpp_style_line_directive\"".to_string()])); - ops.push((None, vec!["OpSourceExtension".to_string(), "\"GL_GOOGLE_include_directive\"".to_string()])); - ops.push((None, vec!["OpName".to_string(), "%main".to_string(), "\"main\"".to_string()])); + ops.push(( + Some("%1".to_string()), + vec![ + "OpExtInstImport".to_string(), + "\"GLSL.std.450\"".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpMemoryModel".to_string(), + "Logical".to_string(), + "GLSL450".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpEntryPoint".to_string(), + "Fragment".to_string(), + "%main".to_string(), + "\"main\"".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpExecutionMode".to_string(), + "%main".to_string(), + "OriginUpperLeft".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpSource".to_string(), + "GLSL".to_string(), + "450".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpSourceExtension".to_string(), + "\"GL_GOOGLE_cpp_style_line_directive\"".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpSourceExtension".to_string(), + "\"GL_GOOGLE_include_directive\"".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpName".to_string(), + "%main".to_string(), + "\"main\"".to_string(), + ], + )); ops.push((Some("%void".to_string()), vec!["OpTypeVoid".to_string()])); - ops.push((Some("%3".to_string()), vec!["OpTypeFunction".to_string(), "%void".to_string()])); - ops.push((Some("%main".to_string()), vec!["OpFunction".to_string(), "%void".to_string(), "None".to_string(), "%3".to_string()])); + ops.push(( + Some("%3".to_string()), + vec!["OpTypeFunction".to_string(), "%void".to_string()], + )); + ops.push(( + Some("%main".to_string()), + vec![ + "OpFunction".to_string(), + "%void".to_string(), + "None".to_string(), + "%3".to_string(), + ], + )); ops.push((Some("%5".to_string()), vec!["OpLabel".to_string()])); ops.push((None, vec!["OpReturn".to_string()])); ops.push((None, vec!["OpFunctionEnd".to_string()])); - + let mut out: String = String::new(); for op in ops { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c56d16f..427f1f6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2,9 +2,94 @@ mod tests; use std::iter::Peekable; +use std::ops::RangeInclusive; use std::vec::IntoIter; -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug)] +enum Location { + Char { + line: usize, + col: usize, + }, + String { + lines: RangeInclusive, + cols: RangeInclusive, + }, +} + +#[derive(Debug)] +pub enum ParseError { + UnexpectedParenClose(Location), + UnexpectedEof, +} + +impl Location { + /// Since [`Localised`] doesn't test for location match, allow for creating simple dummy + /// locations for testing + #[cfg(test)] + pub fn dummy() -> Location { + Self::Char { line: 0, col: 0 } + } + + pub fn range(start: &Location, end: &Location) -> Location { + Location::String { + lines: start.line_start()..=end.line_end(), + cols: start.col_start()..=end.col_end(), + } + } + + pub fn line_start(&self) -> usize { + match self { + Location::Char { line, .. } => *line, + Location::String { lines, .. } => *lines.start(), + } + } + + pub fn col_start(&self) -> usize { + match self { + Location::Char { col, .. } => *col, + Location::String { cols, .. } => *cols.start(), + } + } + + pub fn line_end(&self) -> usize { + match self { + Location::Char { line, .. } => *line, + Location::String { lines, .. } => *lines.end(), + } + } + + pub fn col_end(&self) -> usize { + match self { + Location::Char { col, .. } => *col, + Location::String { cols, .. } => *cols.end(), + } + } +} + +#[derive(Debug, Clone)] +struct Localised { + location: Location, + item: T, +} + +impl Localised { + #[cfg(test)] + pub fn dummy_location(item: T) -> Self { + Self { + location: Location::dummy(), + item, + } + } +} + +impl PartialEq for Localised { + fn eq(&self, other: &Self) -> bool { + self.item.eq(&other.item) + } +} + +#[derive(Debug, PartialEq, Clone)] pub enum Token { LeftParen, RightParen, @@ -13,56 +98,109 @@ pub enum Token { #[derive(Debug, PartialEq, Clone)] pub enum Ast { - Symbol(String), - List(Vec), + Symbol(Localised), + List(Localised>), Root(Vec), } -pub fn tokenize(input: &str) -> Vec { +pub fn tokenize(input: &str) -> Vec> { let mut tokens = Vec::new(); - let mut chars = input.chars().peekable(); - while let Some(c) = chars.next() { + // let mut chars = input.chars().peekable(); + let mut chars = (1..) + .zip(input.split('\n')) + .flat_map(|(l_num, l)| { + (1..).zip(l.chars()).map(move |(c_num, c)| Localised { + location: Location::Char { + line: l_num, + col: c_num, + }, + item: c, + }) + }) + .peekable(); + while let Some(Localised { location, item: c }) = chars.next() { match c { - '(' => tokens.push(Token::LeftParen), - ')' => tokens.push(Token::RightParen), + '(' => tokens.push(Localised { + location, + item: Token::LeftParen, + }), + ')' => tokens.push(Localised { + location, + item: Token::RightParen, + }), _ if c.is_whitespace() => (), _ => { + let start = location.clone(); + let mut end = location; let mut symbol = c.to_string(); - while let Some(&c) = chars.peek() { - if c.is_whitespace() || c == '(' || c == ')' { + while let Some(Localised { item: c, .. }) = chars.peek() { + if c.is_whitespace() || *c == '(' || *c == ')' { break; } - symbol.push(c); - chars.next(); + symbol.push(*c); + let Localised { location, .. } = chars.next().unwrap(); + end = location; } - tokens.push(Token::Symbol(symbol)); + tokens.push(Localised { + location: Location::range(&start, &end), + item: Token::Symbol(symbol), + }); } } } tokens } -fn parse_expr(tokens: &mut Peekable>) -> Ast { +fn parse_expr(tokens: &mut Peekable>>) -> Result { match tokens.next() { - Some(Token::LeftParen) => { + Some(Localised { + location: start, + item: Token::LeftParen, + }) => { let mut list = Vec::new(); - while tokens.peek() != Some(&Token::RightParen) { - list.push(parse_expr(tokens)); + while !matches!( + tokens.peek(), + Some(Localised { + item: Token::RightParen, + .. + }) + ) { + list.push(parse_expr(tokens)?); } - tokens.next(); - Ast::List(list) + let Some(Localised { + location: end, + item: Token::RightParen, + }) = tokens.next() + else { + unreachable!() + }; + Ok(Ast::List(Localised { + location: Location::range(&start, &end), + item: list, + })) } - Some(Token::RightParen) => panic!("unexpected )"), - Some(Token::Symbol(s)) => Ast::Symbol(s), - None => panic!("unexpected EOF"), + Some(Localised { + location, + item: Token::RightParen, + }) => Err(ParseError::UnexpectedParenClose(location)), + Some(Localised { + location, + item: Token::Symbol(s), + }) => Ok(Ast::Symbol(Localised { location, item: s })), + None => Err(ParseError::UnexpectedEof), } } -pub fn parse(tokens: Vec) -> Ast { +pub fn parse(tokens: Vec>) -> Result { let mut tokens = tokens.into_iter().peekable(); let mut ast: Vec = Vec::new(); while tokens.peek().is_some() { - ast.push(parse_expr(&mut tokens)); + ast.push(parse_expr(&mut tokens)?); } - Ast::Root(ast) -} \ No newline at end of file + Ok(Ast::Root(ast)) +} + +pub fn parse_string(src: &str) -> Result { + let tokens = tokenize(src); + parse(tokens) +} diff --git a/src/parser/tests.rs b/src/parser/tests.rs index dd6a947..88f38cf 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -1,22 +1,38 @@ -use crate::parser::{tokenize, parse, Token, Ast}; +use crate::parser::{Ast, Localised, Token, parse, tokenize}; #[test] fn test_tokenize() { let input = "(+ (* 1:u8 2) 2)"; let expected = vec![ - Token::LeftParen, - Token::Symbol("+".to_string()), - Token::LeftParen, - Token::Symbol("*".to_string()), - Token::Symbol("1:u8".to_string()), - Token::Symbol("2".to_string()), - Token::RightParen, - Token::Symbol("2".to_string()), - Token::RightParen, + Localised::dummy_location(Token::LeftParen), + Localised::dummy_location(Token::Symbol("+".to_string())), + Localised::dummy_location(Token::LeftParen), + Localised::dummy_location(Token::Symbol("*".to_string())), + Localised::dummy_location(Token::Symbol("1:u8".to_string())), + Localised::dummy_location(Token::Symbol("2".to_string())), + Localised::dummy_location(Token::RightParen), + Localised::dummy_location(Token::Symbol("2".to_string())), + Localised::dummy_location(Token::RightParen), ]; assert_eq!(tokenize(input), expected); } +#[test] +#[should_panic] +fn test_unexpected_pclose() { + let input = "())"; + let tokens = tokenize(input); + let _ast = parse(tokens).unwrap(); +} + +#[test] +#[should_panic] +fn test_unexpected_eof() { + let input = "(1 2 3 4"; + let tokens = tokenize(input); + let _ast = parse(tokens).unwrap(); +} + #[test] fn test_parse() { let src = " @@ -34,92 +50,92 @@ fn test_parse() { 1.0 1.0))) "; - let ast = parse(tokenize(src)); + let ast = parse(tokenize(src)).unwrap(); println!("{:?}", ast); let test_ast: Ast = Ast::Root(vec![ - Ast::List(vec![ - Ast::Symbol("module".to_string()), - Ast::Symbol("Shader".to_string()), - Ast::Symbol("Logical".to_string()), - Ast::Symbol("GLSL450".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("import".to_string()), - Ast::Symbol(":std".to_string()), - Ast::Symbol("GLSL.std.450".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("bind".to_string()), - Ast::List(vec![ - Ast::Symbol("frag-coord:*v4f32i".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("BuiltIn".to_string()), - Ast::Symbol("FragCoord".to_string()), - ]), - ]), - Ast::List(vec![ - Ast::Symbol("bind".to_string()), - Ast::List(vec![ - Ast::Symbol("out-color:*v4f32o".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("Location".to_string()), - Ast::Symbol("0".to_string()), - ]), - ]), - Ast::List(vec![ - Ast::Symbol("dec".to_string()), - Ast::Symbol("frag-coord:*v4f32i".to_string()), - Ast::Symbol("Input".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("dec".to_string()), - Ast::Symbol("out-color:*v4f32o".to_string()), - Ast::Symbol("Output".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("entry".to_string()), - Ast::Symbol("main".to_string()), - Ast::Symbol("Fragment".to_string()), - Ast::Symbol("OriginUpperLeft".to_string()), - Ast::List(vec![ - Ast::Symbol(":frag-coord".to_string()), - Ast::Symbol(":out-color".to_string()), - ]), - ]), - Ast::List(vec![ - Ast::Symbol("fun".to_string()), - Ast::List(vec![ - Ast::Symbol("main".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("store-ptr".to_string()), - Ast::List(vec![ - Ast::Symbol("out-color".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("v4f32i".to_string()), - Ast::List(vec![ - Ast::Symbol("/".to_string()), - Ast::List(vec![ - Ast::Symbol(".xy".to_string()), - Ast::List(vec![ - Ast::Symbol("load-ptr".to_string()), - Ast::Symbol("frag-coord".to_string()), - ]), - ]), - Ast::List(vec![ - Ast::Symbol("v2f32".to_string()), - Ast::Symbol("1920.0".to_string()), - Ast::Symbol("1080.0".to_string()), - ]), - ]), - Ast::Symbol("1.0".to_string()), - Ast::Symbol("1.0".to_string()), - ]), - ]), - ]), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("module".to_string())), + Ast::Symbol(Localised::dummy_location("Shader".to_string())), + Ast::Symbol(Localised::dummy_location("Logical".to_string())), + Ast::Symbol(Localised::dummy_location("GLSL450".to_string())), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("import".to_string())), + Ast::Symbol(Localised::dummy_location(":std".to_string())), + Ast::Symbol(Localised::dummy_location("GLSL.std.450".to_string())), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("bind".to_string())), + Ast::List(Localised::dummy_location(vec![Ast::Symbol( + Localised::dummy_location("frag-coord:*v4f32i".to_string()), + )])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("Builtin".to_string())), + Ast::Symbol(Localised::dummy_location("FragCoord".to_string())), + ])), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("bind".to_string())), + Ast::List(Localised::dummy_location(vec![Ast::Symbol( + Localised::dummy_location("out-color:*v4f32o".to_string()), + )])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("Location".to_string())), + Ast::Symbol(Localised::dummy_location("0".to_string())), + ])), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("dec".to_string())), + Ast::Symbol(Localised::dummy_location("frag-coord:*v4f32i".to_string())), + Ast::Symbol(Localised::dummy_location("Input".to_string())), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("dec".to_string())), + Ast::Symbol(Localised::dummy_location("out-color:*v4f32o".to_string())), + Ast::Symbol(Localised::dummy_location("Output".to_string())), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("entry".to_string())), + Ast::Symbol(Localised::dummy_location("main".to_string())), + Ast::Symbol(Localised::dummy_location("Fragment".to_string())), + Ast::Symbol(Localised::dummy_location("OriginUpperLeft".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location(":frag-coord".to_string())), + Ast::Symbol(Localised::dummy_location(":out-color".to_string())), + ])), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("fun".to_string())), + Ast::List(Localised::dummy_location(vec![Ast::Symbol( + Localised::dummy_location("main".to_string()), + )])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("store-ptr".to_string())), + Ast::List(Localised::dummy_location(vec![Ast::Symbol( + Localised::dummy_location("out-color".to_string()), + )])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("v4f23i".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("/".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location(".xy".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("load-ptr".to_string())), + Ast::Symbol(Localised::dummy_location("frag-coord".to_string())), + ])), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("v2f32".to_string())), + Ast::Symbol(Localised::dummy_location("1920.0".to_string())), + Ast::Symbol(Localised::dummy_location("1080.0".to_string())), + ])), + ])), + Ast::Symbol(Localised::dummy_location("1.0".to_string())), + Ast::Symbol(Localised::dummy_location("1.0".to_string())), + ])), + ])), + ])), ]); assert_eq!(ast, test_ast); -} \ No newline at end of file +} -- 2.39.1 From 1bb4206a3b6ee8206782f3ddb602b33c437d4cb7 Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 6 Mar 2025 11:57:57 +0100 Subject: [PATCH 09/43] Fix test dealing with `Localized` --- src/compiler/backend/tests.rs | 32 +++++++---- src/compiler/mod.rs | 100 ++++++++++++++++++++++------------ src/compiler/tests.rs | 58 ++++++++++++-------- src/parser/mod.rs | 35 ++++++++++-- src/parser/tests.rs | 7 ++- 5 files changed, 156 insertions(+), 76 deletions(-) diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 0f21e2f..4252995 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -1,8 +1,8 @@ #[test] fn test_emit() { + use crate::compiler::backend::*; use crate::compiler::*; use crate::parser::*; - use crate::compiler::backend::*; let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) @@ -18,8 +18,8 @@ fn test_emit() { 1.0 1.0))) "; - let mut ast = parse(tokenize(src)); - let module = meta_compile(&mut ast); + let ast = parse(tokenize(src)); + let module = meta_compile(&mut ast.unwrap()); let res = spirv_meta(module); println!("{}", res); } @@ -28,12 +28,24 @@ fn test_emit() { fn test_type_parse() { use crate::compiler::backend::*; use Type::*; - assert_eq!(parse_type(&"*v4f32i".to_string()), - Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Input)); - assert_eq!(parse_type(&"*v4f32o".to_string()), - Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Output)); - assert_eq!(parse_type(&"v2f32".to_string()), - Vector(Box::new(Float(32)), 2)); + assert_eq!( + parse_type(&"*v4f32i".to_string()), + Pointer( + Box::new(Vector(Box::new(Float(32)), 4)), + StorageClass::Input + ) + ); + assert_eq!( + parse_type(&"*v4f32o".to_string()), + Pointer( + Box::new(Vector(Box::new(Float(32)), 4)), + StorageClass::Output + ) + ); + assert_eq!( + parse_type(&"v2f32".to_string()), + Vector(Box::new(Float(32)), 2) + ); assert_eq!(parse_type(&"f32".to_string()), Float(32)); assert_eq!(parse_type(&"s32".to_string()), Int(32)); -} \ No newline at end of file +} diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 140a57a..8635d0c 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,4 +1,4 @@ -use crate::parser::Ast; +use crate::parser::{Ast, Localised, Location}; pub mod backend; @@ -10,13 +10,13 @@ mod tests; #[derive(Debug, PartialEq, Default, Clone)] pub enum Capability { #[default] - Shader + Shader, } #[derive(Debug, PartialEq, Default, Clone)] pub enum ExecutionMode { #[default] - OriginUpperLeft + OriginUpperLeft, } #[derive(Debug, PartialEq, Default, Clone)] @@ -40,7 +40,7 @@ pub enum AddressingModel { Logical, Physical32, Physical64, - PhysicalStorageBuffer64 + PhysicalStorageBuffer64, } #[derive(Debug, PartialEq, Default, Clone)] pub enum MemoryModel { @@ -48,7 +48,7 @@ pub enum MemoryModel { GLSL450, OpenCL, VulkanKHR, - Simple + Simple, } #[derive(Debug, PartialEq, Default, Clone)] @@ -147,9 +147,9 @@ pub fn meta_compile(ast: &mut Ast) -> Module { memory_model: MemoryModel::GLSL450, }; module.capabilities.push(Capability::Shader); - assert_eq!(exec, Ast::Symbol("Logical".to_string())); - assert_eq!(memory, Ast::Symbol("GLSL450".to_string())); - assert_eq!(cap, Ast::Symbol("Shader".to_string())); + assert_eq!(exec.symbol(), Some("Logical".to_string())); + assert_eq!(memory.symbol(), Some("GLSL450".to_string())); + assert_eq!(cap.symbol(), Some("Shader".to_string())); } "import" => { let name = match &list[1] { @@ -170,28 +170,38 @@ pub fn meta_compile(ast: &mut Ast) -> Module { Ast::List(l) => l, _ => panic!("Expected list! {:?}", list[1]), }; - let name_and_type: String = name_and_type.iter().map(|x| { - match x { + let name_and_type: String = name_and_type + .iter() + .map(|x| match x { Ast::Symbol(s) => s.to_string(), _ => panic!("Expected symbol! {:?}", x), - } - }).collect(); + }) + .collect(); // name_and_type is of the name:type format, like foo:f32 - let name: String = name_and_type.split(":").collect::>()[0].to_string(); - let typ: String = name_and_type.split(":").collect::>()[1].to_string(); + let name: String = + name_and_type.split(":").collect::>()[0] + .to_string(); + let typ: String = + name_and_type.split(":").collect::>()[1] + .to_string(); let bind = match &list[2] { Ast::List(l) => l, _ => panic!("Expected list! {:?}", list[2]), }; - let bind: Vec = bind.iter().map(|x| { - match x { + let bind: Vec = bind + .iter() + .map(|x| match x { Ast::Symbol(s) => s.to_string(), _ => panic!("Expected symbol! {:?}", x), - } - }).collect(); + }) + .collect(); let bind_name = match bind[0].as_str() { - "BuiltIn" => Decoration::BuiltIn(BuiltinDecoration::FragCoord), - "Location" => Decoration::Location(bind[1].parse::().unwrap()), + "BuiltIn" => { + Decoration::BuiltIn(BuiltinDecoration::FragCoord) + } + "Location" => Decoration::Location( + bind[1].parse::().unwrap(), + ), _ => panic!("Unknown bind! {:?}", bind), }; let mut exists = false; @@ -214,8 +224,12 @@ pub fn meta_compile(ast: &mut Ast) -> Module { Ast::Symbol(s) => s, _ => panic!("Expected symbol! {:?}", list[1]), }; - let name: String = name_and_type.split(":").collect::>()[0].to_string(); - let typ: String = name_and_type.split(":").collect::>()[1].to_string(); + let name: String = + name_and_type.split(":").collect::>()[0] + .to_string(); + let typ: String = + name_and_type.split(":").collect::>()[1] + .to_string(); let storage_class = match &list[2] { Ast::Symbol(s) => s, _ => panic!("Expected symbol! {:?}", list[2]), @@ -240,9 +254,12 @@ pub fn meta_compile(ast: &mut Ast) -> Module { }); } if exists { - module.globals.iter_mut() + module + .globals + .iter_mut() .find(|x| x.name.as_str() == name.as_str()) - .unwrap().storage_class = storage_class.clone(); + .unwrap() + .storage_class = storage_class.clone(); } } "entry" => { @@ -262,22 +279,21 @@ pub fn meta_compile(ast: &mut Ast) -> Module { Ast::List(l) => l, _ => panic!("Expected list! {:?}", list[4]), }; - let interface: Vec = interface.iter().map(|x| { - match x { - Ast::Symbol(s) => { - s.to_string().replace(":", "") - }, + let interface: Vec = interface + .iter() + .map(|x| match x { + Ast::Symbol(s) => s.to_string().replace(":", ""), _ => panic!("Expected symbol! {:?}", x), - } - }).collect(); + }) + .collect(); module.entry_points.push(EntryPoint { execution_model: ExecutionModel::Fragment, execution_mode: ExecutionMode::OriginUpperLeft, name: name.to_string(), interface: interface, }); - assert_eq!(exec_model, "Fragment"); - assert_eq!(exec_mode, "OriginUpperLeft"); + assert_eq!(**exec_model, "Fragment"); + assert_eq!(**exec_mode, "OriginUpperLeft"); } "fun" => { let name = match &list[1] { @@ -289,12 +305,23 @@ pub fn meta_compile(ast: &mut Ast) -> Module { _ => panic!("Expected symbol! {:?}", name), }; let body = list[2..].to_vec(); + let location = if let (Some(s), Some(e)) = ( + body.first().map(|a| a.location()).flatten(), + body.last().map(|a| a.location()).flatten(), + ) { + Location::range(s, e) + } else { + Location::Char { line: 0, col: 0 } + }; let fun = Function { name: name.to_string(), return_type: "void".to_string(), arguments: vec![], body: Some(vec![]), - ast: Some(Ast::List(body)), + ast: Some(Ast::List(Localised { + location, + item: body, + })), }; module.functions.push(fun); } @@ -308,7 +335,8 @@ pub fn meta_compile(ast: &mut Ast) -> Module { } } } - _ => panic!("Non-root ast") + _ => panic!("Non-root ast"), } module -} \ No newline at end of file +} + diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 33398bd..a7c7e13 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -1,4 +1,4 @@ -use crate::parser::{tokenize, parse}; +use crate::parser::{parse, tokenize}; #[test] fn test_compile() { @@ -18,9 +18,9 @@ fn test_compile() { 1.0 1.0))) "; - let mut ast = parse(tokenize(src)); + let ast = parse(tokenize(src)); println!("{:#?}", ast); - let module = meta_compile(&mut ast); + let module = meta_compile(&mut ast.unwrap()); println!("{:#?}", module); let test_module = Module { capabilities: vec![Capability::Shader], @@ -49,25 +49,36 @@ fn test_compile() { return_type: "void".to_string(), arguments: vec![], body: Some(vec![]), - ast: Some(Ast::List(vec![ - Ast::List(vec![ - Ast::Symbol("store-ptr".to_string()), - Ast::List(vec![Ast::Symbol("out-color".to_string())]), - Ast::List(vec![ - Ast::Symbol("v4f32i".to_string()), - Ast::List(vec![ - Ast::Symbol("/".to_string()), - Ast::List(vec![ - Ast::Symbol(".xy".to_string()), - Ast::List(vec![Ast::Symbol("load-ptr".to_string()), Ast::Symbol("frag-coord".to_string())]), - ]), - Ast::List(vec![Ast::Symbol("v2f32".to_string()), Ast::Symbol("1920.0".to_string()), Ast::Symbol("1080.0".to_string())]), - ]), - Ast::Symbol("1.0".to_string()), - Ast::Symbol("1.0".to_string()), - ]), + ast: Some(Ast::List(Localised::dummy_location(vec![Ast::List( + Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("store-ptr".to_string())), + Ast::List(Localised::dummy_location(vec![Ast::Symbol( + Localised::dummy_location("out-color".to_string()), + )])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("v4f32i".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("/".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location(".xy".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("load-ptr".to_string())), + Ast::Symbol(Localised::dummy_location( + "frag-coord".to_string(), + )), + ])), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("v2f32".to_string())), + Ast::Symbol(Localised::dummy_location("1920.0".to_string())), + Ast::Symbol(Localised::dummy_location("1080.0".to_string())), + ])), + ])), + Ast::Symbol(Localised::dummy_location("1.0".to_string())), + Ast::Symbol(Localised::dummy_location("1.0".to_string())), + ])), ]), - ])), + )]))), }], memory_model: Memory { addressing_model: AddressingModel::Logical, @@ -78,5 +89,8 @@ fn test_compile() { value: "GLSL.std.450".to_string(), }], }; + dbg!(&module); + dbg!(&test_module); assert_eq!(module, test_module); -} \ No newline at end of file +} + diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 427f1f6..f7b9a58 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2,11 +2,11 @@ mod tests; use std::iter::Peekable; -use std::ops::RangeInclusive; +use std::ops::{Deref, RangeInclusive}; use std::vec::IntoIter; #[derive(Clone, Debug)] -enum Location { +pub enum Location { Char { line: usize, col: usize, @@ -68,9 +68,9 @@ impl Location { } #[derive(Debug, Clone)] -struct Localised { - location: Location, - item: T, +pub struct Localised { + pub location: Location, + pub item: T, } impl Localised { @@ -83,6 +83,14 @@ impl Localised { } } +impl Deref for Localised { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.item + } +} + impl PartialEq for Localised { fn eq(&self, other: &Self) -> bool { self.item.eq(&other.item) @@ -103,6 +111,23 @@ pub enum Ast { Root(Vec), } +impl Ast { + pub fn symbol(self) -> Option { + match self { + Ast::Symbol(Localised { item, .. }) => Some(item), + _ => None, + } + } + + pub fn location(&self) -> Option<&Location> { + match self { + Ast::Symbol(Localised { location, .. }) => Some(location), + Ast::List(Localised { location, .. }) => Some(location), + Ast::Root(_) => None, + } + } +} + pub fn tokenize(input: &str) -> Vec> { let mut tokens = Vec::new(); // let mut chars = input.chars().peekable(); diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 88f38cf..07bde87 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -51,7 +51,6 @@ fn test_parse() { 1.0))) "; let ast = parse(tokenize(src)).unwrap(); - println!("{:?}", ast); let test_ast: Ast = Ast::Root(vec![ Ast::List(Localised::dummy_location(vec![ Ast::Symbol(Localised::dummy_location("module".to_string())), @@ -70,7 +69,7 @@ fn test_parse() { Localised::dummy_location("frag-coord:*v4f32i".to_string()), )])), Ast::List(Localised::dummy_location(vec![ - Ast::Symbol(Localised::dummy_location("Builtin".to_string())), + Ast::Symbol(Localised::dummy_location("BuiltIn".to_string())), Ast::Symbol(Localised::dummy_location("FragCoord".to_string())), ])), ])), @@ -115,7 +114,7 @@ fn test_parse() { Localised::dummy_location("out-color".to_string()), )])), Ast::List(Localised::dummy_location(vec![ - Ast::Symbol(Localised::dummy_location("v4f23i".to_string())), + Ast::Symbol(Localised::dummy_location("v4f32i".to_string())), Ast::List(Localised::dummy_location(vec![ Ast::Symbol(Localised::dummy_location("/".to_string())), Ast::List(Localised::dummy_location(vec![ @@ -137,5 +136,7 @@ fn test_parse() { ])), ])), ]); + println!("ast = {:#?}", ast); + println!("test_ast = {:#?}", test_ast); assert_eq!(ast, test_ast); } -- 2.39.1 From 5df23394c39fdcb1b046f81ecb4309a35b089ee5 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 12:38:23 +0100 Subject: [PATCH 10/43] Cargo fmt --- src/compiler/backend/mod.rs | 116 ++++++++++++++++++++++++++---------- src/compiler/mod.rs | 1 - src/compiler/tests.rs | 1 - 3 files changed, 85 insertions(+), 33 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index e88ceed..e896a23 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -2,8 +2,8 @@ mod tests; use crate::compiler::Module; -use std::fmt::Write; use std::fmt; +use std::fmt::Write; use super::StorageClass; @@ -19,12 +19,10 @@ pub enum Type { impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Type::Pointer(typ, storage_class) => { - match storage_class { - StorageClass::Input => write!(f, "*{}i", typ), - StorageClass::Output => write!(f, "*{}o", typ), - StorageClass::Undefined => panic!("Bound a non-declared variable"), - } + Type::Pointer(typ, storage_class) => match storage_class { + StorageClass::Input => write!(f, "*{}i", typ), + StorageClass::Output => write!(f, "*{}o", typ), + StorageClass::Undefined => panic!("Bound a non-declared variable"), }, Type::Vector(typ, size) => write!(f, "v{}{}", size, typ), Type::Float(size) => write!(f, "f{}", size), @@ -55,46 +53,61 @@ pub fn parse_type(typ: &String) -> Type { }; let typ = typ.chars().take(typ.len() - 1).collect::(); Type::Pointer(Box::new(parse_type(&typ)), storage_class) - }, + } 'v' => { let mut chars = typ.chars(); chars.next(); let size = chars.next().unwrap().to_digit(10).unwrap(); let typ = chars.collect::(); Type::Vector(Box::new(parse_type(&typ)), size) - }, + } 'f' => { let size = typ.chars().skip(1).collect::().parse().unwrap(); Type::Float(size) - }, + } 's' => { let size = typ.chars().skip(1).collect::().parse().unwrap(); Type::Int(size) - }, + } 'u' => { let size = typ.chars().skip(1).collect::().parse().unwrap(); Type::Unsigned(size) - }, + } _ => panic!("Invalid type"), } - } fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { match &typ { Type::Unsigned(size) => { - ops.push((format!("%u{}", size).to_string(), format!("OpTypeInt {}", size))); - }, + ops.push(( + format!("%u{}", size).to_string(), + format!("OpTypeInt {}", size), + )); + } Type::Int(size) => { - ops.push((format!("%s{}", size).to_string(), format!("OpTypeInt {}", size))); - }, + ops.push(( + format!("%s{}", size).to_string(), + format!("OpTypeInt {}", size), + )); + } Type::Float(size) => { - ops.push((format!("%f{}", size).to_string(), format!("OpTypeFloat {}", size))); - }, + ops.push(( + format!("%f{}", size).to_string(), + format!("OpTypeFloat {}", size), + )); + } Type::Vector(in_typ, size) => { emit_type(*in_typ.clone(), ops); - ops.push((fix_name(&typ.to_string()), format!("OpTypeVector {} {}", fix_name(&in_typ.clone().to_string()), size))) - }, + ops.push(( + fix_name(&typ.to_string()), + format!( + "OpTypeVector {} {}", + fix_name(&in_typ.clone().to_string()), + size + ), + )) + } Type::Pointer(in_typ, storage_class) => { emit_type(*in_typ.clone(), ops); let typ_id = fix_name(&typ.to_string()); @@ -103,8 +116,15 @@ fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { StorageClass::Output => "Output", StorageClass::Undefined => panic!("Bound a non-declared variable"), }; - ops.push((typ_id, format!("OpTypePointer {} {}", storage_class, fix_name(&in_typ.to_string())))) - }, + ops.push(( + typ_id, + format!( + "OpTypePointer {} {}", + storage_class, + fix_name(&in_typ.to_string()) + ), + )) + } } } @@ -125,7 +145,9 @@ pub fn spirv_meta(module: Module) -> String { let mut spirv_asm = String::new(); let mut ops: Vec<(Option, Vec)> = Vec::new(); - let capabilities: Vec = module.capabilities.iter() + let capabilities: Vec = module + .capabilities + .iter() .map(|c| format!("{:?}", c)) .collect(); for cap in capabilities { @@ -144,7 +166,14 @@ pub fn spirv_meta(module: Module) -> String { crate::compiler::MemoryModel::OpenCL => "OpenCL", _ => todo!(), }; - ops.push((None, vec!["OpMemoryModel".to_string(), memory_model_address.to_string(), memory_model_model.to_string()])); + ops.push(( + None, + vec![ + "OpMemoryModel".to_string(), + memory_model_address.to_string(), + memory_model_model.to_string(), + ], + )); for entry in module.entry_points { let exec_model = match entry.execution_model { @@ -152,14 +181,32 @@ pub fn spirv_meta(module: Module) -> String { crate::compiler::ExecutionModel::Vertex => "Vertex", }; let name = entry.name; - let interface: Vec = entry.interface.iter() + let interface: Vec = entry + .interface + .iter() .map(|i| fix_name(&i.to_string())) .collect(); let exec_mode = match entry.execution_mode { crate::compiler::ExecutionMode::OriginUpperLeft => "OriginUpperLeft", }; - ops.push((None, vec!["OpEntryPoint".to_string(), exec_model.to_string(), fix_name(&name), format!("\"{}\"", name), interface.join(" ")])); - ops.push((None, vec!["OpExecutionMode".to_string(), fix_name(&name), exec_mode.to_string()])); + ops.push(( + None, + vec![ + "OpEntryPoint".to_string(), + exec_model.to_string(), + fix_name(&name), + format!("\"{}\"", name), + interface.join(" "), + ], + )); + ops.push(( + None, + vec![ + "OpExecutionMode".to_string(), + fix_name(&name), + exec_mode.to_string(), + ], + )); } for global in module.globals { @@ -178,7 +225,14 @@ pub fn spirv_meta(module: Module) -> String { } ops.push((Some(op.0), vec![op.1])); } - ops.push((Some(name.clone()), vec!["OpVariable".to_string(), fix_name(&_typ), storage_class.to_string()])); + ops.push(( + Some(name.clone()), + vec![ + "OpVariable".to_string(), + fix_name(&_typ), + storage_class.to_string(), + ], + )); for dec in global.decorations { // Decorations have the format Location 0, or Builtin FragCoord let dec = match dec { @@ -188,7 +242,7 @@ pub fn spirv_meta(module: Module) -> String { crate::compiler::BuiltinDecoration::FragCoord => "FragCoord", }; format!("BuiltIn {}", builtin) - }, + } }; ops.push((None, vec!["OpDecorate".to_string(), name.clone(), dec])); } @@ -204,4 +258,4 @@ pub fn spirv_meta(module: Module) -> String { writeln!(spirv_asm).unwrap(); } spirv_asm -} \ No newline at end of file +} diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 8635d0c..17abca9 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -339,4 +339,3 @@ pub fn meta_compile(ast: &mut Ast) -> Module { } module } - diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index a7c7e13..9cbc04c 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -93,4 +93,3 @@ fn test_compile() { dbg!(&test_module); assert_eq!(module, test_module); } - -- 2.39.1 From db7be30a7a1248df9716f6407af718eaa347a430 Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 6 Mar 2025 20:10:11 +0100 Subject: [PATCH 11/43] Refactor and error handling for compiler --- src/compiler/backend/tests.rs | 2 +- src/compiler/meta_compile.rs | 206 ++++++++++++++++++++++ src/compiler/mod.rs | 315 ++++++++++++---------------------- src/compiler/tests.rs | 202 +++++++++++++++++++++- src/parser/mod.rs | 37 +++- 5 files changed, 548 insertions(+), 214 deletions(-) create mode 100644 src/compiler/meta_compile.rs diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 4252995..7319154 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -19,7 +19,7 @@ fn test_emit() { 1.0))) "; let ast = parse(tokenize(src)); - let module = meta_compile(&mut ast.unwrap()); + let module = meta_compile(ast.unwrap()).unwrap(); let res = spirv_meta(module); println!("{}", res); } diff --git a/src/compiler/meta_compile.rs b/src/compiler/meta_compile.rs new file mode 100644 index 0000000..f88f500 --- /dev/null +++ b/src/compiler/meta_compile.rs @@ -0,0 +1,206 @@ +use crate::{ + compiler::{ + AddressingModel, Capability, EntryPoint, ExecutionMode, ExecutionModel, Memory, + MemoryModel, expect_list, expect_one_of, expect_symbol, + }, + parser::{Ast, Localised, Location}, +}; + +use super::{ + BuiltinDecoration, CompilerError, Decoration, Function, GlobalVariable, Import, Module, + StorageClass, expect_empty, expect_empty_ast, +}; + +pub fn compile_module>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let _cap = expect_one_of(&["Shader"], expect_symbol(list.next(), &loc)?)?; + let _exec = expect_one_of(&["Logical"], expect_symbol(list.next(), &loc)?)?; + let _memory = expect_one_of(&["GLSL450"], expect_symbol(list.next(), &loc)?)?; + module.memory_model = Memory { + addressing_model: AddressingModel::Logical, + memory_model: MemoryModel::GLSL450, + }; + module.capabilities.push(Capability::Shader); + expect_empty_ast(list) +} + +pub fn compile_import>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let name = expect_symbol(list.next(), &loc)?.into_inner(); + let value = expect_symbol(list.next(), &loc)?.into_inner(); + module.imports.push(Import { name, value }); + expect_empty_ast(list) +} + +pub fn compile_bind>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let Localised { + location: name_and_type_loc, + item: name_and_type, + } = expect_list(list.next(), &loc)?; + let name_and_type = name_and_type + .into_iter() + .map(|x| expect_symbol(Some(x), &name_and_type_loc).map(Localised::into_inner)) + .collect::>()?; + let mut name_and_type = name_and_type.split(':').map(|s| s.to_string()); + // name_and_type is of the name:type format, like foo:f32 + let name: String = name_and_type + .next() + .ok_or(CompilerError::ExpectedSymbol(name_and_type_loc.clone()))?; + let typ: String = name_and_type + .next() + .ok_or(CompilerError::ExpectedType(name_and_type_loc.tail()))?; + expect_empty(name_and_type, name_and_type_loc)?; + let Localised { + location: bind_loc, + item: bind, + } = expect_list(list.next(), &loc)?; + let bind: Vec = bind + .into_iter() + .map(|x| expect_symbol(Some(x), &bind_loc).map(Localised::into_inner)) + .collect::>()?; + let bind_name = bind + .get(0) + .ok_or(CompilerError::ExpectedSymbol(bind_loc.clone()))?; + + let bind_name = match bind_name.as_str() { + "BuiltIn" => Decoration::BuiltIn(BuiltinDecoration::FragCoord), + "Location" => Decoration::Location(bind.get(1).unwrap().parse::().unwrap()), + b => return Err(CompilerError::UnknownBind(b.to_string(), bind_loc)), + }; + let mut exists = false; + for var in &module.globals { + if var.name == name { + exists = true; + } + } + if !exists { + module.globals.push(GlobalVariable { + name, + typ, + storage_class: StorageClass::Undefined, + decorations: vec![bind_name], + }); + } + expect_empty_ast(list) +} + +pub fn compile_dec>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let Localised { + location: name_and_type_loc, + item: name_and_type, + } = expect_symbol(list.next(), &loc)?; + let mut name_and_type = name_and_type.split(':').map(|s| s.to_string()); + let name: String = name_and_type + .next() + .ok_or(CompilerError::ExpectedSymbol(name_and_type_loc.clone()))?; + let typ: String = name_and_type + .next() + .ok_or(CompilerError::ExpectedType(name_and_type_loc.tail()))?; + expect_empty(name_and_type, name_and_type_loc)?; + + let storage_class = expect_symbol(list.next(), &loc)?; + let mut exists = false; + for var in &module.globals { + if var.name.as_str() == name.as_str() { + exists = true; + } + } + let storage_class = match storage_class.as_str() { + "Input" => StorageClass::Input, + "Output" => StorageClass::Output, + _ => StorageClass::Undefined, + }; + if !exists { + module.globals.push(GlobalVariable { + name: name.to_string(), + typ: typ.to_string(), + storage_class: storage_class.clone(), + decorations: vec![], + }); + } + if exists { + module + .globals + .iter_mut() + .find(|x| x.name.as_str() == name.as_str()) + .unwrap() + .storage_class = storage_class.clone(); + } + expect_empty_ast(list) +} + +pub fn compile_entry>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let name = expect_symbol(list.next(), &loc)?; + let _exec_model = expect_one_of(&["Fragment"], expect_symbol(list.next(), &loc)?)?; + let _exec_mode = expect_one_of(&["OriginUpperLeft"], expect_symbol(list.next(), &loc)?)?; + let Localised { + location: interface_loc, + item: interface, + } = expect_list(list.next(), &loc)?; + let interface: Vec = interface + .into_iter() + .map(|x| expect_symbol(Some(x), &interface_loc).map(|s| s.into_inner().replace(":", ""))) + .collect::>()?; + module.entry_points.push(EntryPoint { + execution_model: ExecutionModel::Fragment, + execution_mode: ExecutionMode::OriginUpperLeft, + name: name.to_string(), + interface, + }); + + expect_empty_ast(list) +} + +pub fn compile_fun>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let Localised { + location: name_loc, + item: name, + } = expect_list(list.next(), &loc)?; + let mut name_it = name.into_iter(); + let name = expect_symbol(name_it.next(), &name_loc)?; + expect_empty(name_it, name_loc)?; + let body = list.collect::>(); + let location = if let (Some(s), Some(e)) = ( + body.first().map(|a| a.location()), + body.last().map(|a| a.location()), + ) { + Location::range(s, e) + } else { + Location::Char { line: 0, col: 0 } + }; + let fun = Function { + name: name.to_string(), + return_type: "void".to_string(), + arguments: vec![], + body: Some(vec![]), + ast: Some(Ast::List(Localised { + location, + item: body, + })), + }; + module.functions.push(fun); + + Ok(()) +} diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 17abca9..0238826 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,6 +1,11 @@ +use meta_compile::{ + compile_bind, compile_dec, compile_entry, compile_fun, compile_import, compile_module, +}; + use crate::parser::{Ast, Localised, Location}; pub mod backend; +mod meta_compile; use std::fmt; @@ -127,215 +132,111 @@ pub struct Module { pub imports: Vec, } -pub fn meta_compile(ast: &mut Ast) -> Module { - let mut module: Module = Module::default(); +#[derive(Debug)] +pub enum CompilerError { + ExpectedSymbol(Location), + ExpectedList(Location), + UnknownKeyword(String, Location), + ExpectedOneOf(Vec, String, Location), + TrailingTokens(Location), + UnknownBind(String, Location), + ExpectedType(Location), +} + +fn expect_symbol(ast: Option, loc: &Location) -> Result, CompilerError> { + match ast { + Some(Ast::Symbol(s)) => Ok(s), + Some(l) => Err(CompilerError::ExpectedSymbol(l.location().clone())), + None => Err(CompilerError::ExpectedSymbol(loc.tail())), + } +} + +fn expect_list(ast: Option, loc: &Location) -> Result>, CompilerError> { match ast { - Ast::Root(root) => { - for node in root { - match node { - Ast::List(list) => { - let keyword = list[0].clone(); - match keyword { - Ast::Symbol(sym) => { - match sym.as_str() { - "module" => { - let cap = list[1].clone(); - let exec = list[2].clone(); - let memory = list[3].clone(); - module.memory_model = Memory { - addressing_model: AddressingModel::Logical, - memory_model: MemoryModel::GLSL450, - }; - module.capabilities.push(Capability::Shader); - assert_eq!(exec.symbol(), Some("Logical".to_string())); - assert_eq!(memory.symbol(), Some("GLSL450".to_string())); - assert_eq!(cap.symbol(), Some("Shader".to_string())); - } - "import" => { - let name = match &list[1] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[1]), - }; - let value = match &list[2] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[2]), - }; - module.imports.push(Import { - name: name.to_string(), - value: value.to_string(), - }); - } - "bind" => { - let name_and_type = match &list[1] { - Ast::List(l) => l, - _ => panic!("Expected list! {:?}", list[1]), - }; - let name_and_type: String = name_and_type - .iter() - .map(|x| match x { - Ast::Symbol(s) => s.to_string(), - _ => panic!("Expected symbol! {:?}", x), - }) - .collect(); - // name_and_type is of the name:type format, like foo:f32 - let name: String = - name_and_type.split(":").collect::>()[0] - .to_string(); - let typ: String = - name_and_type.split(":").collect::>()[1] - .to_string(); - let bind = match &list[2] { - Ast::List(l) => l, - _ => panic!("Expected list! {:?}", list[2]), - }; - let bind: Vec = bind - .iter() - .map(|x| match x { - Ast::Symbol(s) => s.to_string(), - _ => panic!("Expected symbol! {:?}", x), - }) - .collect(); - let bind_name = match bind[0].as_str() { - "BuiltIn" => { - Decoration::BuiltIn(BuiltinDecoration::FragCoord) - } - "Location" => Decoration::Location( - bind[1].parse::().unwrap(), - ), - _ => panic!("Unknown bind! {:?}", bind), - }; - let mut exists = false; - for var in &module.globals { - if var.name == name { - exists = true; - } - } - if !exists { - module.globals.push(GlobalVariable { - name: name, - typ: typ, - storage_class: StorageClass::Undefined, - decorations: vec![bind_name], - }); - } - } - "dec" => { - let name_and_type = match &list[1] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[1]), - }; - let name: String = - name_and_type.split(":").collect::>()[0] - .to_string(); - let typ: String = - name_and_type.split(":").collect::>()[1] - .to_string(); - let storage_class = match &list[2] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[2]), - }; - let mut exists = false; - for var in &module.globals { - if var.name.as_str() == name.as_str() { - exists = true; - } - } - let storage_class = match storage_class.as_str() { - "Input" => StorageClass::Input, - "Output" => StorageClass::Output, - _ => StorageClass::Undefined, - }; - if !exists { - module.globals.push(GlobalVariable { - name: name.to_string(), - typ: typ.to_string(), - storage_class: storage_class.clone(), - decorations: vec![], - }); - } - if exists { - module - .globals - .iter_mut() - .find(|x| x.name.as_str() == name.as_str()) - .unwrap() - .storage_class = storage_class.clone(); - } - } - "entry" => { - let name = match &list[1] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[1]), - }; - let exec_model = match &list[2] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[2]), - }; - let exec_mode = match &list[3] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[3]), - }; - let interface = match &list[4] { - Ast::List(l) => l, - _ => panic!("Expected list! {:?}", list[4]), - }; - let interface: Vec = interface - .iter() - .map(|x| match x { - Ast::Symbol(s) => s.to_string().replace(":", ""), - _ => panic!("Expected symbol! {:?}", x), - }) - .collect(); - module.entry_points.push(EntryPoint { - execution_model: ExecutionModel::Fragment, - execution_mode: ExecutionMode::OriginUpperLeft, - name: name.to_string(), - interface: interface, - }); - assert_eq!(**exec_model, "Fragment"); - assert_eq!(**exec_mode, "OriginUpperLeft"); - } - "fun" => { - let name = match &list[1] { - Ast::List(l) => l[0].clone(), - _ => panic!("Expected list! {:?}", list[1]), - }; - let name = match name { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", name), - }; - let body = list[2..].to_vec(); - let location = if let (Some(s), Some(e)) = ( - body.first().map(|a| a.location()).flatten(), - body.last().map(|a| a.location()).flatten(), - ) { - Location::range(s, e) - } else { - Location::Char { line: 0, col: 0 } - }; - let fun = Function { - name: name.to_string(), - return_type: "void".to_string(), - arguments: vec![], - body: Some(vec![]), - ast: Some(Ast::List(Localised { - location, - item: body, - })), - }; - module.functions.push(fun); - } - _ => panic!("Unknown keyword! {:?}", sym), - } - } - _ => panic!("List where a keyword was expected! {:?}", keyword), - } - } - _ => panic!("Top-level symbol! {:?}", node), + Some(Ast::List(l)) => Ok(l), + Some(l) => Err(CompilerError::ExpectedList(l.location().clone())), + None => Err(CompilerError::ExpectedList(loc.tail())), + } +} + +fn expect_one_of( + options: &[T], + Localised { location, item }: Localised, +) -> Result, CompilerError> +where + T: PartialEq + Into + Clone, +{ + if options.iter().find(|e| **e == item).is_some() { + Ok(Localised { location, item }) + } else { + Err(CompilerError::ExpectedOneOf( + options + .iter() + .map(|t| Into::::into(t.clone())) + .collect(), + item, + location, + )) + } +} + +fn expect_empty_ast>(mut iter: I) -> Result<(), CompilerError> { + match iter.next() { + Some(a) => Err(CompilerError::TrailingTokens(a.location().clone())), + None => Ok(()), + } +} + +fn expect_empty>( + mut iter: I, + location: Location, +) -> Result<(), CompilerError> { + match iter.next() { + Some(_) => Err(CompilerError::TrailingTokens(location)), + None => Ok(()), + } +} + +pub fn meta_compile(ast: Ast) -> Result { + let mut module: Module = Module::default(); + let Ast::Root(root) = ast else { + panic!("Non-root ast"); // compiler error, fine to panic + }; + for node in root { + let Localised { + location, + item: list, + } = expect_list(Some(node), &Location::All)?; + let mut iter = list.into_iter(); + + match iter.next() { + Some(Ast::Symbol(sym)) => match sym.as_str() { + "module" => { + compile_module(&mut module, iter, location)?; + } + "import" => { + compile_import(&mut module, iter, location)?; + } + "bind" => { + compile_bind(&mut module, iter, location)?; + } + "dec" => { + compile_dec(&mut module, iter, location)?; + } + "entry" => { + compile_entry(&mut module, iter, location)?; + } + "fun" => { + compile_fun(&mut module, iter, location)?; + } + _ => { + let Localised { location, item } = sym; + return Err(CompilerError::UnknownKeyword(item, location)); } - } + }, + Some(l) => return Err(CompilerError::ExpectedSymbol(l.location().clone())), + None => return Err(CompilerError::ExpectedSymbol(location)), } - _ => panic!("Non-root ast"), } - module + Ok(module) } diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 9cbc04c..2a3dcee 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -1,4 +1,7 @@ -use crate::parser::{parse, tokenize}; +use crate::{ + compiler::{CompilerError, meta_compile}, + parser::{Location, parse, tokenize}, +}; #[test] fn test_compile() { @@ -20,7 +23,7 @@ fn test_compile() { "; let ast = parse(tokenize(src)); println!("{:#?}", ast); - let module = meta_compile(&mut ast.unwrap()); + let module = meta_compile(ast.unwrap()).unwrap(); println!("{:#?}", module); let test_module = Module { capabilities: vec![Capability::Shader], @@ -93,3 +96,198 @@ fn test_compile() { dbg!(&test_module); assert_eq!(module, test_module); } + +#[test] +fn expected_symbol() { + let src = " +(module Shader Logical ) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + assert!(matches!( + module, + Err(CompilerError::ExpectedSymbol(Location::Char { + line: 2, + col: 24 + })) + )) +} + +#[test] +fn expected_list() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun main + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + let Err(CompilerError::ExpectedList(Location::String { lines, cols })) = module else { + panic!() + }; + assert_eq!(lines, 9..=9); + assert_eq!(cols, 6..=9); +} + +#[test] +fn unknown_keyword() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fum (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + let Err(CompilerError::UnknownKeyword(kw, Location::String { lines, cols })) = module else { + panic!() + }; + assert_eq!(kw, "fum".to_string()); + assert_eq!(lines, 9..=9); + assert_eq!(cols, 2..=4); +} + +#[test] +fn expected_one_of() { + let src = " +(module Shader Loical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + let Err(CompilerError::ExpectedOneOf(l, kw, Location::String { lines, cols })) = module else { + panic!() + }; + assert_eq!(l, vec!["Logical".to_string()]); + assert_eq!(kw, "Loical".to_string()); + assert_eq!(lines, 2..=2); + assert_eq!(cols, 16..=21); +} + +#[test] +fn trailing_tokens() { + let src = " +(module Shader Logical GLSL450 GLSL451) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + let Err(CompilerError::TrailingTokens(Location::String { lines, cols })) = module else { + panic!() + }; + assert_eq!(lines, 2..=2); + assert_eq!(cols, 32..=38); +} + +#[test] +fn unknown_bind() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIm FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + dbg!(&module); + let Err(CompilerError::UnknownBind(b, Location::String { lines, cols })) = module else { + panic!() + }; + assert_eq!(b, "BuiltIm"); + assert_eq!(lines, 4..=4); + assert_eq!(cols, 28..=46); +} + +#[test] +fn expected_type() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + dbg!(&module); + let Err(CompilerError::ExpectedType(Location::Char { line: 4, col: 18 })) = module else { + panic!() + }; +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f7b9a58..550f0ad 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3,6 +3,7 @@ mod tests; use std::iter::Peekable; use std::ops::{Deref, RangeInclusive}; +use std::usize; use std::vec::IntoIter; #[derive(Clone, Debug)] @@ -15,6 +16,7 @@ pub enum Location { lines: RangeInclusive, cols: RangeInclusive, }, + All, } #[derive(Debug)] @@ -42,6 +44,7 @@ impl Location { match self { Location::Char { line, .. } => *line, Location::String { lines, .. } => *lines.start(), + Location::All => 0, } } @@ -49,6 +52,7 @@ impl Location { match self { Location::Char { col, .. } => *col, Location::String { cols, .. } => *cols.start(), + Location::All => 0, } } @@ -56,6 +60,7 @@ impl Location { match self { Location::Char { line, .. } => *line, Location::String { lines, .. } => *lines.end(), + Location::All => usize::MAX, } } @@ -63,6 +68,24 @@ impl Location { match self { Location::Char { col, .. } => *col, Location::String { cols, .. } => *cols.end(), + Location::All => usize::MAX, + } + } + + pub fn tail(&self) -> Self { + match self { + Location::Char { line, col } => Location::Char { + line: *line, + col: *col, + }, + Location::String { lines, cols } => Location::Char { + line: *lines.end(), + col: *cols.end(), + }, + Location::All => Location::Char { + line: usize::MAX, + col: usize::MAX, + }, } } } @@ -73,6 +96,12 @@ pub struct Localised { pub item: T, } +impl Localised { + pub fn into_inner(self) -> T { + self.item + } +} + impl Localised { #[cfg(test)] pub fn dummy_location(item: T) -> Self { @@ -119,11 +148,11 @@ impl Ast { } } - pub fn location(&self) -> Option<&Location> { + pub fn location(&self) -> &Location { match self { - Ast::Symbol(Localised { location, .. }) => Some(location), - Ast::List(Localised { location, .. }) => Some(location), - Ast::Root(_) => None, + Ast::Symbol(Localised { location, .. }) => location, + Ast::List(Localised { location, .. }) => location, + Ast::Root(_) => &Location::All, } } } -- 2.39.1 From 6cf6beda27a1515643b9e097b4aa28d5d0aedce2 Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 6 Mar 2025 23:32:06 +0100 Subject: [PATCH 12/43] Frontend and Error printing --- src/compiler/backend/mod.rs | 38 ++--- src/error.rs | 213 ++++++++++++++++++++++++ src/main.rs | 318 ++++++++++++++++++++++++------------ test.sdr | 14 ++ test.spv | 12 ++ 5 files changed, 469 insertions(+), 126 deletions(-) create mode 100644 src/error.rs create mode 100644 test.sdr create mode 100644 test.spv diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index e896a23..bb2ce4f 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -1,12 +1,12 @@ #[cfg(test)] mod tests; -use crate::compiler::Module; +use crate::compiler::{ + AddressingModel, ExecutionMode, ExecutionModel, MemoryModel, Module, StorageClass, +}; use std::fmt; use std::fmt::Write; -use super::StorageClass; - #[derive(Debug, PartialEq, Clone)] pub enum Type { Pointer(Box, StorageClass), @@ -155,15 +155,15 @@ pub fn spirv_meta(module: Module) -> String { } let memory_model_address = match module.memory_model.addressing_model { - crate::compiler::AddressingModel::Logical => "Logical", - crate::compiler::AddressingModel::Physical32 => "Physical32", - crate::compiler::AddressingModel::Physical64 => "Physical64", - crate::compiler::AddressingModel::PhysicalStorageBuffer64 => "PhysicalStorageBuffer64", + AddressingModel::Logical => "Logical", + AddressingModel::Physical32 => "Physical32", + AddressingModel::Physical64 => "Physical64", + AddressingModel::PhysicalStorageBuffer64 => "PhysicalStorageBuffer64", }; let memory_model_model = match module.memory_model.memory_model { - crate::compiler::MemoryModel::Simple => "Simple", - crate::compiler::MemoryModel::GLSL450 => "GLSL450", - crate::compiler::MemoryModel::OpenCL => "OpenCL", + MemoryModel::Simple => "Simple", + MemoryModel::GLSL450 => "GLSL450", + MemoryModel::OpenCL => "OpenCL", _ => todo!(), }; ops.push(( @@ -177,8 +177,8 @@ pub fn spirv_meta(module: Module) -> String { for entry in module.entry_points { let exec_model = match entry.execution_model { - crate::compiler::ExecutionModel::Fragment => "Fragment", - crate::compiler::ExecutionModel::Vertex => "Vertex", + ExecutionModel::Fragment => "Fragment", + ExecutionModel::Vertex => "Vertex", }; let name = entry.name; let interface: Vec = entry @@ -187,7 +187,7 @@ pub fn spirv_meta(module: Module) -> String { .map(|i| fix_name(&i.to_string())) .collect(); let exec_mode = match entry.execution_mode { - crate::compiler::ExecutionMode::OriginUpperLeft => "OriginUpperLeft", + ExecutionMode::OriginUpperLeft => "OriginUpperLeft", }; ops.push(( None, @@ -211,14 +211,14 @@ pub fn spirv_meta(module: Module) -> String { for global in module.globals { let name = fix_name(&global.name); - let _typ = global.typ; + let typ = global.typ; let storage_class = match global.storage_class { - crate::compiler::StorageClass::Input => "Input", - crate::compiler::StorageClass::Output => "Output", - crate::compiler::StorageClass::Undefined => panic!("Bound a non-declared variable"), + StorageClass::Input => "Input", + StorageClass::Output => "Output", + StorageClass::Undefined => panic!("Bound a non-declared variable"), }; let mut type_ops = Vec::new(); - emit_type(parse_type(&_typ), &mut type_ops); + emit_type(parse_type(&typ), &mut type_ops); for op in type_ops { if has_id(op.0.clone(), &ops) { continue; @@ -229,7 +229,7 @@ pub fn spirv_meta(module: Module) -> String { Some(name.clone()), vec![ "OpVariable".to_string(), - fix_name(&_typ), + fix_name(&typ), storage_class.to_string(), ], )); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..54e99f5 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,213 @@ +use std::fmt::{self, Display, Write}; + +use crate::{ + compiler::CompilerError, + parser::{Location, ParseError}, +}; + +pub fn print_error(err: E, src: Option<&str>) { + let mut buf = String::new(); + match src { + Some(src) => { + err.fmt_with_src(&mut buf, &src).unwrap(); + } + None => err.fmt(&mut buf).unwrap(), + } + print!("{buf}"); +} + +pub trait FormattedLocatedError { + fn fmt(&self, w: &mut W) -> fmt::Result; + fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result; +} + +impl Display for Location { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Location::Char { line, col } => write!(f, "{line}:{col}"), + Location::String { lines, cols } => write!(f, "{}:{}", lines.start(), cols.start()), + Location::All => write!(f, "Entire file"), + } + } +} + +impl FormattedLocatedError for ParseError { + fn fmt(&self, w: &mut W) -> fmt::Result { + match self { + ParseError::UnexpectedParenClose(location) => { + writeln!(w, "Unexpected closing paren at {location}")? + } + ParseError::UnexpectedEof => writeln!(w, "Unexpected end of file")?, + } + + Ok(()) + } + + fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result { + match self { + ParseError::UnexpectedParenClose(location) => { + writeln!(w, "Unexpected closing paren at {location}")?; + here(location, src, Some("Unexpected ')'".to_string()), w)?; + } + ParseError::UnexpectedEof => writeln!(w, "Unexpected end of file")?, + } + + Ok(()) + } +} + +impl FormattedLocatedError for CompilerError { + fn fmt(&self, w: &mut W) -> fmt::Result { + match self { + CompilerError::ExpectedSymbol(location) => { + writeln!( + w, + "Expected a symbol but got a list or nothing at {location}" + ) + } + CompilerError::ExpectedList(location) => { + writeln!( + w, + "Expected a list but got a symbol or nothing at {location}" + ) + } + CompilerError::UnknownKeyword(k, location) => { + writeln!(w, "Got unexpected keyword {k} at {location}")?; + writeln!(w, "Expected one of: module, import, bind, dec, entry, fun") + } + CompilerError::ExpectedOneOf(vec, k, location) => { + writeln!(w, "Got unexpected token {k} at {location}")?; + writeln!(w, "Expected one of: {}", vec.join(", ")) + } + CompilerError::TrailingTokens(location) => { + writeln!(w, "Got unexpected trailing tokens at {location}")?; + writeln!(w, "Expected end of list") + } + CompilerError::UnknownBind(b, location) => { + writeln!(w, "Got unexpected keyword {b} at {location}")?; + writeln!(w, "Expected one of: BuiltIn, Location") + } + CompilerError::ExpectedType(location) => { + writeln!(w, "Expected name:type but got name at {location}") + } + } + } + + fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result { + match self { + CompilerError::ExpectedSymbol(location) => { + writeln!( + w, + "Expected a symbol but got a list or nothing at {location}" + )?; + here(location, src, Some("Expected symbol".to_string()), w) + } + CompilerError::ExpectedList(location) => { + writeln!( + w, + "Expected a list but got a symbol or nothing at {location}" + )?; + here(location, src, Some("Expected List".to_string()), w) + } + CompilerError::UnknownKeyword(k, location) => { + writeln!(w, "Got unexpected keyword {k} at {location}")?; + writeln!(w, "Expected one of: module, import, bind, dec, entry, fun")?; + here(location, src, Some("Unexpected keyword".to_string()), w) + } + CompilerError::ExpectedOneOf(vec, k, location) => { + writeln!(w, "Got unexpected token {k} at {location}")?; + writeln!(w, "Expected one of: {}", vec.join(", "))?; + here(location, src, Some("Unexpected token".to_string()), w) + } + CompilerError::TrailingTokens(location) => { + writeln!(w, "Got unexpected trailing tokens at {location}")?; + writeln!(w, "Expected end of list")?; + here(location, src, Some("Unexpected token".to_string()), w) + } + CompilerError::UnknownBind(b, location) => { + writeln!(w, "Got unexpected keyword {b} at {location}")?; + writeln!(w, "Expected one of: BuiltIn, Location")?; + here(location, src, Some("Unexpected keyword".to_string()), w) + } + CompilerError::ExpectedType(location) => { + writeln!(w, "Expected name:type but got name at {location}")?; + here(location, src, None, w) + } + } + } +} + +fn here( + loc: &Location, + src: &str, + comment: Option, + w: &mut W, +) -> fmt::Result { + writeln!(w, "Here:")?; + writeln!( + w, + "{}", + annotate_src(&loc, src, comment.unwrap_or_default()) + )?; + + Ok(()) +} + +fn annotate_src(loc: &Location, src: &str, comment: String) -> String { + let src_lines = src.split('\n').collect::>(); + + let mut buf = String::new(); + + match loc { + Location::Char { line, col } => { + writeln!(buf, "{}|{}", line_num(*line - 1), src_lines[*line - 2]).unwrap(); + writeln!(buf, "{}|{}", line_num(*line), src_lines[*line - 1]).unwrap(); + writeln!(buf, "{}|{}^ {}", " ", " ".repeat(col - 1), comment).unwrap(); + writeln!(buf, "{}|{}", line_num(*line + 1), src_lines[*line]).unwrap(); + } + Location::String { lines, cols } => { + writeln!( + buf, + "{}|{}", + line_num(*lines.start() - 1), + src_lines[*lines.start() - 2] + ) + .unwrap(); + writeln!( + buf, + "{}", + src_lines[(lines.start() - 1)..*lines.end()] + .iter() + .zip(lines.clone()) + .map(|(l, n)| format!("{}|{}", line_num(n), l)) + .collect::>() + .join("\n") + ) + .unwrap(); + writeln!( + buf, + "{}|{}{} {}", + " ", + " ".repeat(cols.start() - 1), + "^".repeat(cols.end() - cols.start() + 1), + comment + ) + .unwrap(); + writeln!( + buf, + "{}|{}", + line_num(*lines.end() + 1), + src_lines[*lines.end()] + ) + .unwrap(); + } + Location::All => todo!(), + } + + buf +} + +fn line_num(num: usize) -> String { + let num = format!("{num}"); + format!("{num}{}", " ".repeat(6 - num.len())) +} diff --git a/src/main.rs b/src/main.rs index 2374721..9e9b216 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,115 +1,219 @@ -use std::fmt::Write; +use compiler::{backend::spirv_meta, meta_compile}; +use error::print_error; +use std::{env, fs, path::PathBuf}; + +use parser::parse_string; pub mod compiler; +mod error; pub mod parser; +#[derive(Debug, Default)] +struct CompilerArgs { + input: Option, + output: Option, + print_help: bool, + print_version: bool, +} + +const SPIRV_VERSION: &'static str = "1.0"; + fn main() { - let mut ops: Vec<(Option, Vec)> = Vec::new(); - - // OpMemoryModel Logical GLSL450 - // OpEntryPoint Fragment %main "main" - // OpExecutionMode %main OriginUpperLeft - // OpSource GLSL 450 - // OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" - // OpSourceExtension "GL_GOOGLE_include_directive" - // OpName %main "main" - //%void = OpTypeVoid - //%3 = OpTypeFunction %void - //%main = OpFunction %void None %3 - //%5 = OpLabel - // OpReturn - // OpFunctionEnd - - ops.push((None, vec!["OpCapability".to_string(), "Shader".to_string()])); - ops.push(( - Some("%1".to_string()), - vec![ - "OpExtInstImport".to_string(), - "\"GLSL.std.450\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpMemoryModel".to_string(), - "Logical".to_string(), - "GLSL450".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpEntryPoint".to_string(), - "Fragment".to_string(), - "%main".to_string(), - "\"main\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpExecutionMode".to_string(), - "%main".to_string(), - "OriginUpperLeft".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpSource".to_string(), - "GLSL".to_string(), - "450".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpSourceExtension".to_string(), - "\"GL_GOOGLE_cpp_style_line_directive\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpSourceExtension".to_string(), - "\"GL_GOOGLE_include_directive\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpName".to_string(), - "%main".to_string(), - "\"main\"".to_string(), - ], - )); - ops.push((Some("%void".to_string()), vec!["OpTypeVoid".to_string()])); - ops.push(( - Some("%3".to_string()), - vec!["OpTypeFunction".to_string(), "%void".to_string()], - )); - ops.push(( - Some("%main".to_string()), - vec![ - "OpFunction".to_string(), - "%void".to_string(), - "None".to_string(), - "%3".to_string(), - ], - )); - ops.push((Some("%5".to_string()), vec!["OpLabel".to_string()])); - ops.push((None, vec!["OpReturn".to_string()])); - ops.push((None, vec!["OpFunctionEnd".to_string()])); - - let mut out: String = String::new(); - - for op in ops { - if op.0.is_some() { - write!(out, "{} = ", op.0.unwrap()).unwrap(); + let args = parse_args(); + if args.print_version || args.print_help { + if args.print_help { + print_help(); } - for arg in op.1 { - write!(out, "{} ", arg).unwrap(); + if args.print_version { + print_version(); } - writeln!(out).unwrap(); + return; } - println!("{}", out); + + let Some(input) = args.input else { + eprintln!("No input specified"); + return; + }; + + let output = args.output.unwrap_or(PathBuf::from("./out.spv")); + + let src = match fs::read_to_string(input) { + Ok(i) => i, + Err(e) => { + eprintln!("{e}"); + return; + } + }; + + let ast = match parse_string(&src) { + Ok(i) => i, + Err(e) => { + print_error(e, Some(&src)); + return; + } + }; + + let module = match meta_compile(ast) { + Ok(m) => m, + Err(e) => { + print_error(e, Some(&src)); + return; + } + }; + + let res = spirv_meta(module); + + fs::write(output, res).unwrap(); +} + +fn parse_args() -> CompilerArgs { + let mut args = env::args(); + + let _prog = args.next().unwrap(); + + let mut parsed_args = CompilerArgs::default(); + + while let Some(arg) = args.next() { + if let Some(arg) = arg.strip_prefix("--") { + match arg { + "help" => parsed_args.print_help = true, + "version" => parsed_args.print_version = true, + "input" => { + if parsed_args.input.is_none() { + let Some(input) = args.next() else { + eprintln!("input needs a file"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + }; + + parsed_args.input = Some(PathBuf::from(input)); + } else { + eprintln!("input can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + "output" => { + if parsed_args.output.is_none() { + let Some(output) = args.next() else { + parsed_args.print_help = true; + eprintln!("output needs a file"); + eprintln!(); + return parsed_args; + }; + + parsed_args.output = Some(PathBuf::from(output)); + } else { + eprintln!("output can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + a => { + eprintln!("unknown arg --{a}"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + continue; + } + + if let Some(arg) = arg.strip_prefix("-") { + let mut chars = arg.chars(); + while let Some(arg) = chars.next() { + match arg { + 'i' => { + if chars.next().is_some() { + parsed_args.print_help = true; + eprintln!("-i needs to be last in combined args"); + return parsed_args; + } + + if parsed_args.input.is_none() { + let Some(input) = args.next() else { + eprintln!("input needs a file"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + }; + + parsed_args.input = Some(PathBuf::from(input)); + } else { + eprintln!("input can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + 'o' => { + if chars.next().is_some() { + parsed_args.print_help = true; + eprintln!("-o needs to be last in combined args"); + eprintln!(); + return parsed_args; + } + + if parsed_args.output.is_none() { + let Some(output) = args.next() else { + parsed_args.print_help = true; + eprintln!("output needs a file"); + return parsed_args; + }; + + parsed_args.output = Some(PathBuf::from(output)); + } else { + eprintln!("output can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + 'h' => parsed_args.print_help = true, + 'v' => parsed_args.print_version = true, + a => { + eprintln!("unknown arg -{a}"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + } + continue; + } + + eprintln!("unknown arg {arg}"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + + parsed_args +} + +fn print_help() { + println!("-- ShaderC shader compiler --"); + println!(); + println!("Arguments:"); + println!("\t-i --input:"); + println!("\t\tThe shader to be parsed and compiled, manadory argument"); + println!(); + println!("\t-o --output:"); + println!("\t\tWhere to output the compiled spirv assembly to, default: out.spv"); + println!(); + println!("\t-h --help:"); + println!("\t\tPrint this and exit"); + println!("\t-v --version:"); + println!("\t\tPrint the compilers version"); + println!(); +} + +fn print_version() { + println!( + "ShaderC version: {}; SPIR-V version: {}", + env!("CARGO_PKG_VERSION"), + SPIRV_VERSION + ) } diff --git a/test.sdr b/test.sdr new file mode 100644 index 0000000..1f0a4cd --- /dev/null +++ b/test.sdr @@ -0,0 +1,14 @@ +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(decr out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) + diff --git a/test.spv b/test.spv new file mode 100644 index 0000000..105937a --- /dev/null +++ b/test.spv @@ -0,0 +1,12 @@ +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %frag_coord %out_color +OpExecutionMode %main OriginUpperLeft +%f32 = OpTypeFloat 32 +%v4f32 = OpTypeVector %f32 4 +%pv4f32i = OpTypePointer Input %v4f32 +%frag_coord = OpVariable %pv4f32i Input +OpDecorate %frag_coord BuiltIn FragCoord +%pv4f32o = OpTypePointer Output %v4f32 +%out_color = OpVariable %pv4f32o Output +OpDecorate %out_color Location 0 -- 2.39.1 From 8280be8973db27669af6a4cc1a748ffa5b2ee3ec Mon Sep 17 00:00:00 2001 From: itycodes Date: Fri, 7 Mar 2025 11:23:44 +0100 Subject: [PATCH 13/43] Added an explicit :void type param to `fn` declarations Currently only :void is allowed. We still need to emit the OpTypeVoid. --- src/compiler/backend/tests.rs | 2 +- src/compiler/meta_compile.rs | 4 +++- src/compiler/tests.rs | 4 ++-- src/parser/tests.rs | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 7319154..4a90b07 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -11,7 +11,7 @@ fn test_emit() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) +(fun (main) :void (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) diff --git a/src/compiler/meta_compile.rs b/src/compiler/meta_compile.rs index f88f500..7e8f937 100644 --- a/src/compiler/meta_compile.rs +++ b/src/compiler/meta_compile.rs @@ -181,6 +181,8 @@ pub fn compile_fun>( let mut name_it = name.into_iter(); let name = expect_symbol(name_it.next(), &name_loc)?; expect_empty(name_it, name_loc)?; + let _return_type = expect_one_of(&[":void"], expect_symbol(list.next(), &loc)?)?; + let return_type = _return_type.into_inner().replace(":", ""); let body = list.collect::>(); let location = if let (Some(s), Some(e)) = ( body.first().map(|a| a.location()), @@ -192,7 +194,7 @@ pub fn compile_fun>( }; let fun = Function { name: name.to_string(), - return_type: "void".to_string(), + return_type: return_type, arguments: vec![], body: Some(vec![]), ast: Some(Ast::List(Localised { diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 2a3dcee..cb47ba9 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -14,7 +14,7 @@ fn test_compile() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) +(fun (main) :void (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) @@ -107,7 +107,7 @@ fn expected_symbol() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) +(fun (main) :void (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 07bde87..2dff458 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -43,7 +43,7 @@ fn test_parse() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) +(fun (main) :void (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) @@ -108,6 +108,7 @@ fn test_parse() { Ast::List(Localised::dummy_location(vec![Ast::Symbol( Localised::dummy_location("main".to_string()), )])), + Ast::Symbol(Localised::dummy_location(":void".to_string())), Ast::List(Localised::dummy_location(vec![ Ast::Symbol(Localised::dummy_location("store-ptr".to_string())), Ast::List(Localised::dummy_location(vec![Ast::Symbol( -- 2.39.1 From 5c1e3e446ba55b8213f2b0572cec000dd24ef63b Mon Sep 17 00:00:00 2001 From: itycodes Date: Fri, 7 Mar 2025 11:36:07 +0100 Subject: [PATCH 14/43] Replace the :void type expr with :<> `void` collides with the simplistic type parser, which expects single char types. `()` collides with the S-expression parser. Specifically, it hits the `v` for vector case. This could be fixed by rewriting the type parser, but I like to keep the whole compiler as simple as possible for now. --- src/compiler/backend/mod.rs | 35 ++++++++++++++++++++++++++++++++++- src/compiler/backend/tests.rs | 2 +- src/compiler/meta_compile.rs | 2 +- src/compiler/tests.rs | 6 +++--- src/parser/tests.rs | 4 ++-- 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index bb2ce4f..d692d14 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -14,11 +14,13 @@ pub enum Type { Float(u32), Int(u32), Unsigned(u32), + Void, } impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { + Type::Void => write!(f, "<>"), Type::Pointer(typ, storage_class) => match storage_class { StorageClass::Input => write!(f, "*{}i", typ), StorageClass::Output => write!(f, "*{}o", typ), @@ -42,6 +44,10 @@ pub fn parse_type(typ: &String) -> Type { let c = typ.chars().next().unwrap(); match c { + '<' => { + assert_eq!(typ.as_str(), "<>"); + Type::Void + } '*' => { let mut chars = typ.chars(); chars.next(); @@ -79,6 +85,9 @@ pub fn parse_type(typ: &String) -> Type { fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { match &typ { + Type::Void => { + ops.push(("%void".to_string(), "OpTypeVoid".to_string())); + } Type::Unsigned(size) => { ops.push(( format!("%u{}", size).to_string(), @@ -129,7 +138,13 @@ fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { } fn fix_name(name: &String) -> String { - format!("%{}", name.clone().replace("-", "_").replace("*", "p")) + format!( + "%{}", + name.clone() + .replace("-", "_") + .replace("*", "p") + .replace("<>", "void") + ) } fn has_id(name: String, ops: &Vec<(Option, Vec)>) -> bool { @@ -248,6 +263,24 @@ pub fn spirv_meta(module: Module) -> String { } } + for fun in module.functions { + let name = fix_name(&fun.name); + let return_type = fix_name(&fun.return_type); + let mut type_ops = Vec::new(); + emit_type(parse_type(&fun.return_type), &mut type_ops); + for op in type_ops { + if has_id(op.0.clone(), &ops) { + continue; + } + ops.push((Some(op.0), vec![op.1])); + } + // Push OpFunctionType + ops.push(( + Some(name.clone()), + vec!["OpTypeFunction".to_string(), return_type.clone()], + )); + } + for op in ops { if op.0.is_some() { write!(spirv_asm, "{} = ", op.0.unwrap()).unwrap(); diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 4a90b07..2f0cce5 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -11,7 +11,7 @@ fn test_emit() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) :void +(fun (main) :<> (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) diff --git a/src/compiler/meta_compile.rs b/src/compiler/meta_compile.rs index 7e8f937..d305239 100644 --- a/src/compiler/meta_compile.rs +++ b/src/compiler/meta_compile.rs @@ -181,7 +181,7 @@ pub fn compile_fun>( let mut name_it = name.into_iter(); let name = expect_symbol(name_it.next(), &name_loc)?; expect_empty(name_it, name_loc)?; - let _return_type = expect_one_of(&[":void"], expect_symbol(list.next(), &loc)?)?; + let _return_type = expect_one_of(&[":<>"], expect_symbol(list.next(), &loc)?)?; let return_type = _return_type.into_inner().replace(":", ""); let body = list.collect::>(); let location = if let (Some(s), Some(e)) = ( diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index cb47ba9..8064ea8 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -14,7 +14,7 @@ fn test_compile() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) :void +(fun (main) :<> (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) @@ -49,7 +49,7 @@ fn test_compile() { ], functions: vec![Function { name: "main".to_string(), - return_type: "void".to_string(), + return_type: "<>".to_string(), arguments: vec![], body: Some(vec![]), ast: Some(Ast::List(Localised::dummy_location(vec![Ast::List( @@ -107,7 +107,7 @@ fn expected_symbol() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) :void +(fun (main) :<> (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 2dff458..8e94147 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -43,7 +43,7 @@ fn test_parse() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) :void +(fun (main) :<> (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) @@ -108,7 +108,7 @@ fn test_parse() { Ast::List(Localised::dummy_location(vec![Ast::Symbol( Localised::dummy_location("main".to_string()), )])), - Ast::Symbol(Localised::dummy_location(":void".to_string())), + Ast::Symbol(Localised::dummy_location(":<>".to_string())), Ast::List(Localised::dummy_location(vec![ Ast::Symbol(Localised::dummy_location("store-ptr".to_string())), Ast::List(Localised::dummy_location(vec![Ast::Symbol( -- 2.39.1 From 77ae300c2c4271b6519715dc3f41d77cf7007ce8 Mon Sep 17 00:00:00 2001 From: itycodes Date: Fri, 7 Mar 2025 11:42:28 +0100 Subject: [PATCH 15/43] An initial README file :) --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1b6dcf --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +A simplistic low-level compiler for a high-level shader bytecode. + +##### Compiler backend, parser, bad prototype Rust: +*ity* + +##### Compiler frontend (error reporting, validation...), fixing & cleaning up ity's bad Rust: +*Avery* -- 2.39.1 From 6d894b20384ba0979a49e3c4db233cd2c2b7f3b6 Mon Sep 17 00:00:00 2001 From: "Lilith N." Date: Fri, 7 Mar 2025 18:31:45 +0100 Subject: [PATCH 16/43] Add a flag to show the AST/Token list --- src/main.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++- src/parser/mod.rs | 25 +++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 9e9b216..70e44c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,15 +2,23 @@ use compiler::{backend::spirv_meta, meta_compile}; use error::print_error; use std::{env, fs, path::PathBuf}; -use parser::parse_string; +use parser::{parse_string, tokenize, Token}; pub mod compiler; mod error; pub mod parser; +#[derive(Debug, Default, PartialEq)] +enum Trace { + Tokens, + AST, + #[default] Normal +} + #[derive(Debug, Default)] struct CompilerArgs { input: Option, output: Option, + trace: Trace, print_help: bool, print_version: bool, } @@ -43,6 +51,17 @@ fn main() { return; } }; + if args.trace == Trace::Tokens { + let tokens = tokenize(&src); + for tok in tokens { + match tok.item { + Token::LeftParen => println!("{:>3}:{:<3} LeftParen",tok.location.line_start(), tok.location.col_start()), + Token::RightParen => println!("{:>3}:{:<3} RightParen",tok.location.line_start(), tok.location.col_start()), + Token::Symbol( val ) => println!("{:>3}:{:<3} Symbol `{}`",tok.location.line_start(), tok.location.col_start(), val) + } + } + return; + } let ast = match parse_string(&src) { Ok(i) => i, @@ -51,6 +70,11 @@ fn main() { return; } }; + + if args.trace == Trace::AST { + ast.pretty_print(None,None); + return; + } let module = match meta_compile(ast) { Ok(m) => m, @@ -111,6 +135,36 @@ fn parse_args() -> CompilerArgs { return parsed_args; } } + "trace" => { + if parsed_args.trace == Trace::Normal { + let Some(output) = args.next() else { + parsed_args.print_help = true; + eprintln!("trace needs a file"); + eprintln!(); + return parsed_args; + }; + match output.as_str() { + "ast" | "AST" | "Ast" => { + parsed_args.trace = Trace::AST; + } + "token" | "tokens" => { + parsed_args.trace = Trace::Tokens; + } + a => { + eprintln!("unknown trace value {a}"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + + } else { + eprintln!("trace can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } a => { eprintln!("unknown arg --{a}"); eprintln!(); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 550f0ad..75774d0 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -155,6 +155,31 @@ impl Ast { Ast::Root(_) => &Location::All, } } + + pub fn pretty_print(&self, prefix: Option<&String>, body_prefix: Option<&String>) { + let empty_string_to_make_rust_happy = &String::from(""); + let prefix_ = prefix.unwrap_or(&empty_string_to_make_rust_happy); + let body_prefix_ = body_prefix.unwrap_or(&empty_string_to_make_rust_happy); + match self { + Ast::Symbol(Localised { item, location }) => { + println!("{} Symbol `{}` ({}:{})", prefix_, item, location.line_start(), location.col_start()); + }, + Ast::List(Localised { item, location }) => { + println!("{} List ({}:{})", prefix_, location.line_start(), location.col_start()); + for i in 0..item.len()-2 { + item[i].pretty_print(Some(&(body_prefix_.to_owned() + &String::from(" ├─ "))), Some(&(body_prefix_.to_owned() + &String::from(" │ ")))); + } + item[item.len()-1].pretty_print(Some(&(body_prefix_.to_owned() + &String::from(" ╰─ "))), Some(&(body_prefix_.to_owned() + &String::from(" ")))); + }, + Ast::Root( items ) => { + println!("{} Root", prefix_); + for i in 0..items.len()-2 { + items[i].pretty_print(Some(&(body_prefix_.to_owned() + &String::from(" ├─ "))), Some(&(body_prefix_.to_owned() + &String::from(" │ ")))); + } + items[items.len()-1].pretty_print(Some(&(body_prefix_.to_owned() + &String::from(" ╰─ "))), Some(&(body_prefix_.to_owned() + &String::from(" ")))); + }, + } + } } pub fn tokenize(input: &str) -> Vec> { -- 2.39.1 From 25ce9bb5203ff5e53ce237580907693c75dc5925 Mon Sep 17 00:00:00 2001 From: "Lilith N." Date: Fri, 7 Mar 2025 19:31:06 +0100 Subject: [PATCH 18/43] Fix off by one error --- src/parser/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 75774d0..9c7c797 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -166,7 +166,7 @@ impl Ast { }, Ast::List(Localised { item, location }) => { println!("{} List ({}:{})", prefix_, location.line_start(), location.col_start()); - for i in 0..item.len()-2 { + for i in 0..item.len()-1 { item[i].pretty_print(Some(&(body_prefix_.to_owned() + &String::from(" ├─ "))), Some(&(body_prefix_.to_owned() + &String::from(" │ ")))); } item[item.len()-1].pretty_print(Some(&(body_prefix_.to_owned() + &String::from(" ╰─ "))), Some(&(body_prefix_.to_owned() + &String::from(" ")))); -- 2.39.1 From 0902762066c716b86f0e65b34ac6eb70ef2abfca Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 09:14:16 +0100 Subject: [PATCH 19/43] Initial commit --- .gitignore | 1 + Cargo.lock | 7 +++ Cargo.toml | 6 +++ src/main.rs | 49 +++++++++++++++++ src/parser/mod.rs | 68 ++++++++++++++++++++++++ src/parser/tests.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 256 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/parser/mod.rs create mode 100644 src/parser/tests.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..dd9815e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "shaderc" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a084462 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "shaderc" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..49c0ece --- /dev/null +++ b/src/main.rs @@ -0,0 +1,49 @@ +use std::fmt::Write; +pub mod parser; + +fn main() { + let mut ops: Vec<(Option, Vec)> = Vec::new(); + + // OpMemoryModel Logical GLSL450 + // OpEntryPoint Fragment %main "main" + // OpExecutionMode %main OriginUpperLeft + // OpSource GLSL 450 + // OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" + // OpSourceExtension "GL_GOOGLE_include_directive" + // OpName %main "main" + //%void = OpTypeVoid + //%3 = OpTypeFunction %void + //%main = OpFunction %void None %3 + //%5 = OpLabel + // OpReturn + // OpFunctionEnd + + ops.push((None, vec!["OpCapability".to_string(), "Shader".to_string()])); + ops.push((Some("%1".to_string()), vec!["OpExtInstImport".to_string(), "\"GLSL.std.450\"".to_string()])); + ops.push((None, vec!["OpMemoryModel".to_string(), "Logical".to_string(), "GLSL450".to_string()])); + ops.push((None, vec!["OpEntryPoint".to_string(), "Fragment".to_string(), "%main".to_string(), "\"main\"".to_string()])); + ops.push((None, vec!["OpExecutionMode".to_string(), "%main".to_string(), "OriginUpperLeft".to_string()])); + ops.push((None, vec!["OpSource".to_string(), "GLSL".to_string(), "450".to_string()])); + ops.push((None, vec!["OpSourceExtension".to_string(), "\"GL_GOOGLE_cpp_style_line_directive\"".to_string()])); + ops.push((None, vec!["OpSourceExtension".to_string(), "\"GL_GOOGLE_include_directive\"".to_string()])); + ops.push((None, vec!["OpName".to_string(), "%main".to_string(), "\"main\"".to_string()])); + ops.push((Some("%void".to_string()), vec!["OpTypeVoid".to_string()])); + ops.push((Some("%3".to_string()), vec!["OpTypeFunction".to_string(), "%void".to_string()])); + ops.push((Some("%main".to_string()), vec!["OpFunction".to_string(), "%void".to_string(), "None".to_string(), "%3".to_string()])); + ops.push((Some("%5".to_string()), vec!["OpLabel".to_string()])); + ops.push((None, vec!["OpReturn".to_string()])); + ops.push((None, vec!["OpFunctionEnd".to_string()])); + + let mut out: String = String::new(); + + for op in ops { + if op.0.is_some() { + write!(out, "{} = ", op.0.unwrap()).unwrap(); + } + for arg in op.1 { + write!(out, "{} ", arg).unwrap(); + } + writeln!(out).unwrap(); + } + println!("{}", out); +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..c90444f --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,68 @@ +#[cfg(test)] +mod tests; + +use std::iter::Peekable; +use std::vec::IntoIter; + +#[derive(Debug, PartialEq)] +pub enum Token { + LeftParen, + RightParen, + Symbol(String), +} + +#[derive(Debug, PartialEq)] +pub enum Ast { + Symbol(String), + List(Vec), + Root(Vec), +} + +pub fn tokenize(input: &str) -> Vec { + let mut tokens = Vec::new(); + let mut chars = input.chars().peekable(); + while let Some(c) = chars.next() { + match c { + '(' => tokens.push(Token::LeftParen), + ')' => tokens.push(Token::RightParen), + _ if c.is_whitespace() => (), + _ => { + let mut symbol = c.to_string(); + while let Some(&c) = chars.peek() { + if c.is_whitespace() || c == '(' || c == ')' { + break; + } + symbol.push(c); + chars.next(); + } + tokens.push(Token::Symbol(symbol)); + } + } + } + tokens +} + +fn parse_expr(tokens: &mut Peekable>) -> Ast { + match tokens.next() { + Some(Token::LeftParen) => { + let mut list = Vec::new(); + while tokens.peek() != Some(&Token::RightParen) { + list.push(parse_expr(tokens)); + } + tokens.next(); + Ast::List(list) + } + Some(Token::RightParen) => panic!("unexpected )"), + Some(Token::Symbol(s)) => Ast::Symbol(s), + None => panic!("unexpected EOF"), + } +} + +pub fn parse(tokens: Vec) -> Ast { + let mut tokens = tokens.into_iter().peekable(); + let mut ast: Vec = Vec::new(); + while tokens.peek().is_some() { + ast.push(parse_expr(&mut tokens)); + } + Ast::Root(ast) +} \ No newline at end of file diff --git a/src/parser/tests.rs b/src/parser/tests.rs new file mode 100644 index 0000000..da40601 --- /dev/null +++ b/src/parser/tests.rs @@ -0,0 +1,125 @@ +use crate::parser::{tokenize, parse, Token, Ast}; + +#[test] +fn test_tokenize() { + let input = "(+ (* 1:u8 2) 2)"; + let expected = vec![ + Token::LeftParen, + Token::Symbol("+".to_string()), + Token::LeftParen, + Token::Symbol("*".to_string()), + Token::Symbol("1:u8".to_string()), + Token::Symbol("2".to_string()), + Token::RightParen, + Token::Symbol("2".to_string()), + Token::RightParen, + ]; + assert_eq!(tokenize(input), expected); +} + +#[test] +fn test_parse() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*vec4fi) (Builtin FragCoord)) +(bind (out-color:*vec4fo) (Location 0)) +(dec frag-coord:*vec4fi Input) +(dec out-color:*vec4fo Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (vec4fi (/ (.xy (load-ptr frag-coord)) + (vec2f 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:?}", ast); + let test_ast: Ast = Ast::Root(vec![ + Ast::List(vec![ + Ast::Symbol("module".to_string()), + Ast::Symbol("Shader".to_string()), + Ast::Symbol("Logical".to_string()), + Ast::Symbol("GLSL450".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("import".to_string()), + Ast::Symbol(":std".to_string()), + Ast::Symbol("GLSL.std.450".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("bind".to_string()), + Ast::List(vec![ + Ast::Symbol("frag-coord:*vec4fi".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("Builtin".to_string()), + Ast::Symbol("FragCoord".to_string()), + ]), + ]), + Ast::List(vec![ + Ast::Symbol("bind".to_string()), + Ast::List(vec![ + Ast::Symbol("out-color:*vec4fo".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("Location".to_string()), + Ast::Symbol("0".to_string()), + ]), + ]), + Ast::List(vec![ + Ast::Symbol("dec".to_string()), + Ast::Symbol("frag-coord:*vec4fi".to_string()), + Ast::Symbol("Input".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("dec".to_string()), + Ast::Symbol("out-color:*vec4fo".to_string()), + Ast::Symbol("Output".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("entry".to_string()), + Ast::Symbol("main".to_string()), + Ast::Symbol("Fragment".to_string()), + Ast::Symbol("OriginUpperLeft".to_string()), + Ast::List(vec![ + Ast::Symbol(":frag-coord".to_string()), + Ast::Symbol(":out-color".to_string()), + ]), + ]), + Ast::List(vec![ + Ast::Symbol("fun".to_string()), + Ast::List(vec![ + Ast::Symbol("main".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("store-ptr".to_string()), + Ast::List(vec![ + Ast::Symbol("out-color".to_string()), + ]), + Ast::List(vec![ + Ast::Symbol("vec4fi".to_string()), + Ast::List(vec![ + Ast::Symbol("/".to_string()), + Ast::List(vec![ + Ast::Symbol(".xy".to_string()), + Ast::List(vec![ + Ast::Symbol("load-ptr".to_string()), + Ast::Symbol("frag-coord".to_string()), + ]), + ]), + Ast::List(vec![ + Ast::Symbol("vec2f".to_string()), + Ast::Symbol("1920.0".to_string()), + Ast::Symbol("1080.0".to_string()), + ]), + ]), + Ast::Symbol("1.0".to_string()), + Ast::Symbol("1.0".to_string()), + ]), + ]), + ]), + ]); + assert_eq!(ast, test_ast); +} \ No newline at end of file -- 2.39.1 From e2bd50a3a10361df60295e20691bc9c70c210428 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 09:18:02 +0100 Subject: [PATCH 20/43] Fix the implicit type generators for vectors Instead of vec4f, we now have v4f32, for vector 4 of float 32 bits --- src/parser/tests.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/parser/tests.rs b/src/parser/tests.rs index da40601..be5484b 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -22,15 +22,15 @@ fn test_parse() { let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) -(bind (frag-coord:*vec4fi) (Builtin FragCoord)) -(bind (out-color:*vec4fo) (Location 0)) -(dec frag-coord:*vec4fi Input) -(dec out-color:*vec4fo Output) +(bind (frag-coord:*v4fi) (Builtin FragCoord)) +(bind (out-color:*v4fo) (Location 0)) +(dec frag-coord:*v4fi Input) +(dec out-color:*v4fo Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) (fun (main) (store-ptr (out-color) - (vec4fi (/ (.xy (load-ptr frag-coord)) - (vec2f 1920.0 1080.0)) + (v4fi (/ (.xy (load-ptr frag-coord)) + (v2f 1920.0 1080.0)) 1.0 1.0))) "; @@ -51,7 +51,7 @@ fn test_parse() { Ast::List(vec![ Ast::Symbol("bind".to_string()), Ast::List(vec![ - Ast::Symbol("frag-coord:*vec4fi".to_string()), + Ast::Symbol("frag-coord:*v4fi".to_string()), ]), Ast::List(vec![ Ast::Symbol("Builtin".to_string()), @@ -61,7 +61,7 @@ fn test_parse() { Ast::List(vec![ Ast::Symbol("bind".to_string()), Ast::List(vec![ - Ast::Symbol("out-color:*vec4fo".to_string()), + Ast::Symbol("out-color:*v4fo".to_string()), ]), Ast::List(vec![ Ast::Symbol("Location".to_string()), @@ -70,12 +70,12 @@ fn test_parse() { ]), Ast::List(vec![ Ast::Symbol("dec".to_string()), - Ast::Symbol("frag-coord:*vec4fi".to_string()), + Ast::Symbol("frag-coord:*v4fi".to_string()), Ast::Symbol("Input".to_string()), ]), Ast::List(vec![ Ast::Symbol("dec".to_string()), - Ast::Symbol("out-color:*vec4fo".to_string()), + Ast::Symbol("out-color:*v4fo".to_string()), Ast::Symbol("Output".to_string()), ]), Ast::List(vec![ @@ -99,7 +99,7 @@ fn test_parse() { Ast::Symbol("out-color".to_string()), ]), Ast::List(vec![ - Ast::Symbol("vec4fi".to_string()), + Ast::Symbol("v4fi".to_string()), Ast::List(vec![ Ast::Symbol("/".to_string()), Ast::List(vec![ @@ -110,7 +110,7 @@ fn test_parse() { ]), ]), Ast::List(vec![ - Ast::Symbol("vec2f".to_string()), + Ast::Symbol("v2f".to_string()), Ast::Symbol("1920.0".to_string()), Ast::Symbol("1080.0".to_string()), ]), -- 2.39.1 From 0916a1910d113c2a9ecc76d335b1e1d90e2f355b Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 10:09:56 +0100 Subject: [PATCH 21/43] Initial work on the compiler So far it only generates the metadata --- src/compiler/mod.rs | 297 ++++++++++++++++++++++++++++++++++++++++++ src/compiler/tests.rs | 25 ++++ src/main.rs | 1 + src/parser/mod.rs | 2 +- src/parser/tests.rs | 24 ++-- 5 files changed, 336 insertions(+), 13 deletions(-) create mode 100644 src/compiler/mod.rs create mode 100644 src/compiler/tests.rs diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs new file mode 100644 index 0000000..2bf5e06 --- /dev/null +++ b/src/compiler/mod.rs @@ -0,0 +1,297 @@ +use crate::parser::Ast; + +#[cfg(test)] +mod tests; + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum Capability { + #[default] + Shader +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum ExecutionMode { + #[default] + OriginUpperLeft +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum ExecutionModel { + #[default] + Fragment +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct EntryPoint { + pub execution_model: ExecutionModel, + pub execution_mode: ExecutionMode, + pub name: String, + pub interface: Vec, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum AddressingModel { + #[default] + Logical, + Physical32, + Physical64, + PhysicalStorageBuffer64 +} +#[derive(Debug, PartialEq, Default, Clone)] +pub enum MemoryModel { + #[default] + GLSL450, + OpenCL, + VulkanKHR, + Simple +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Memory { + pub addressing_model: AddressingModel, + pub memory_model: MemoryModel, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum BuiltinDecoration { + #[default] + FragCoord, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Decoration { + Builtin(BuiltinDecoration), + Location(u32), +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub enum StorageClass { + #[default] + Undefined, + Input, + Output, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct GlobalVariable { + pub name: String, + pub typ: String, + pub storage_class: StorageClass, + pub decorations: Vec, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Instruction { + pub result_id: Option, + pub op: String, + pub operands: Vec, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Function { + pub name: String, + pub return_type: String, + pub arguments: Vec, + pub body: Option>, + pub ast: Option, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Import { + pub name: String, + pub value: String, +} + +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Module { + pub capabilities: Vec, + pub entry_points: Vec, + pub globals: Vec, + pub functions: Vec, + pub memory_model: Memory, + pub imports: Vec, +} + +pub fn meta_compile(ast: &mut Ast) -> Module { + let mut module: Module = Module::default(); + match ast { + Ast::Root(root) => { + for node in root { + match node { + Ast::List(list) => { + let keyword = list[0].clone(); + match keyword { + Ast::Symbol(sym) => { + match sym.as_str() { + "module" => { + let cap = list[1].clone(); + let exec = list[2].clone(); + let memory = list[3].clone(); + module.memory_model = Memory { + addressing_model: AddressingModel::Logical, + memory_model: MemoryModel::GLSL450, + }; + module.capabilities.push(Capability::Shader); + assert_eq!(exec, Ast::Symbol("Logical".to_string())); + assert_eq!(memory, Ast::Symbol("GLSL450".to_string())); + assert_eq!(cap, Ast::Symbol("Shader".to_string())); + } + "import" => { + let name = match &list[1] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[1]), + }; + let value = match &list[2] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[2]), + }; + module.imports.push(Import { + name: name.to_string(), + value: value.to_string(), + }); + } + "bind" => { + let name_and_type = match &list[1] { + Ast::List(l) => l, + _ => panic!("Expected list! {:?}", list[1]), + }; + let name_and_type: String = name_and_type.iter().map(|x| { + match x { + Ast::Symbol(s) => s.to_string(), + _ => panic!("Expected symbol! {:?}", x), + } + }).collect(); + // name_and_type is of the name:type format, like foo:f32 + let name: String = name_and_type.split(":").collect::>()[0].to_string(); + let typ: String = name_and_type.split(":").collect::>()[1].to_string(); + let bind = match &list[2] { + Ast::List(l) => l, + _ => panic!("Expected list! {:?}", list[2]), + }; + let bind: Vec = bind.iter().map(|x| { + match x { + Ast::Symbol(s) => s.to_string(), + _ => panic!("Expected symbol! {:?}", x), + } + }).collect(); + let bind_name = match bind[0].as_str() { + "Builtin" => Decoration::Builtin(BuiltinDecoration::FragCoord), + "Location" => Decoration::Location(bind[1].parse::().unwrap()), + _ => panic!("Unknown bind! {:?}", bind), + }; + let mut exists = false; + for var in &module.globals { + if var.name == name { + exists = true; + } + } + if !exists { + module.globals.push(GlobalVariable { + name: name, + typ: typ, + storage_class: StorageClass::Undefined, + decorations: vec![bind_name], + }); + } + } + "dec" => { + let name_and_type = match &list[1] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[1]), + }; + let name: String = name_and_type.split(":").collect::>()[0].to_string(); + let typ: String = name_and_type.split(":").collect::>()[1].to_string(); + let storage_class = match &list[2] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[2]), + }; + let mut exists = false; + for var in &module.globals { + if var.name.as_str() == name.as_str() { + exists = true; + } + } + let storage_class = match storage_class.as_str() { + "Input" => StorageClass::Input, + "Output" => StorageClass::Output, + _ => StorageClass::Undefined, + }; + if !exists { + module.globals.push(GlobalVariable { + name: name.to_string(), + typ: typ.to_string(), + storage_class: storage_class.clone(), + decorations: vec![], + }); + } + if exists { + module.globals.iter_mut() + .find(|x| x.name.as_str() == name.as_str()) + .unwrap().storage_class = storage_class.clone(); + } + } + "entry" => { + let name = match &list[1] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[1]), + }; + let exec_model = match &list[2] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[2]), + }; + let exec_mode = match &list[3] { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", list[3]), + }; + let interface = match &list[4] { + Ast::List(l) => l, + _ => panic!("Expected list! {:?}", list[4]), + }; + let interface: Vec = interface.iter().map(|x| { + match x { + Ast::Symbol(s) => s.to_string(), + _ => panic!("Expected symbol! {:?}", x), + } + }).collect(); + module.entry_points.push(EntryPoint { + execution_model: ExecutionModel::Fragment, + execution_mode: ExecutionMode::OriginUpperLeft, + name: name.to_string(), + interface: interface, + }); + assert_eq!(exec_model, "Fragment"); + assert_eq!(exec_mode, "OriginUpperLeft"); + } + "fun" => { + let name = match &list[1] { + Ast::List(l) => l[0].clone(), + _ => panic!("Expected list! {:?}", list[1]), + }; + let name = match name { + Ast::Symbol(s) => s, + _ => panic!("Expected symbol! {:?}", name), + }; + let body = list[2..].to_vec(); + let fun = Function { + name: name.to_string(), + return_type: "void".to_string(), + arguments: vec![], + body: Some(vec![]), + ast: Some(Ast::List(body)), + }; + module.functions.push(fun); + } + _ => panic!("Unknown keyword! {:?}", sym), + } + } + _ => panic!("List where a keyword was expected! {:?}", keyword), + } + } + _ => panic!("Top-level symbol! {:?}", node), + } + } + } + _ => panic!("Non-root ast") + } + module +} \ No newline at end of file diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs new file mode 100644 index 0000000..c8012a1 --- /dev/null +++ b/src/compiler/tests.rs @@ -0,0 +1,25 @@ +use crate::parser::{tokenize, parse}; +use crate::compiler::meta_compile; + +#[test] +fn test_compile() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (Builtin FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let mut ast = parse(tokenize(src)); + println!("{:?}", ast); + let module = meta_compile(&mut ast); + println!("{:?}", module); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 49c0ece..ebebce3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use std::fmt::Write; pub mod parser; +pub mod compiler; fn main() { let mut ops: Vec<(Option, Vec)> = Vec::new(); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c90444f..c56d16f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11,7 +11,7 @@ pub enum Token { Symbol(String), } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Ast { Symbol(String), List(Vec), diff --git a/src/parser/tests.rs b/src/parser/tests.rs index be5484b..6251515 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -22,15 +22,15 @@ fn test_parse() { let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) -(bind (frag-coord:*v4fi) (Builtin FragCoord)) -(bind (out-color:*v4fo) (Location 0)) -(dec frag-coord:*v4fi Input) -(dec out-color:*v4fo Output) +(bind (frag-coord:*v4f32i) (Builtin FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) (fun (main) (store-ptr (out-color) - (v4fi (/ (.xy (load-ptr frag-coord)) - (v2f 1920.0 1080.0)) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) 1.0 1.0))) "; @@ -51,7 +51,7 @@ fn test_parse() { Ast::List(vec![ Ast::Symbol("bind".to_string()), Ast::List(vec![ - Ast::Symbol("frag-coord:*v4fi".to_string()), + Ast::Symbol("frag-coord:*v4f32i".to_string()), ]), Ast::List(vec![ Ast::Symbol("Builtin".to_string()), @@ -61,7 +61,7 @@ fn test_parse() { Ast::List(vec![ Ast::Symbol("bind".to_string()), Ast::List(vec![ - Ast::Symbol("out-color:*v4fo".to_string()), + Ast::Symbol("out-color:*v4f32o".to_string()), ]), Ast::List(vec![ Ast::Symbol("Location".to_string()), @@ -70,12 +70,12 @@ fn test_parse() { ]), Ast::List(vec![ Ast::Symbol("dec".to_string()), - Ast::Symbol("frag-coord:*v4fi".to_string()), + Ast::Symbol("frag-coord:*v4f32i".to_string()), Ast::Symbol("Input".to_string()), ]), Ast::List(vec![ Ast::Symbol("dec".to_string()), - Ast::Symbol("out-color:*v4fo".to_string()), + Ast::Symbol("out-color:*v4f32o".to_string()), Ast::Symbol("Output".to_string()), ]), Ast::List(vec![ @@ -99,7 +99,7 @@ fn test_parse() { Ast::Symbol("out-color".to_string()), ]), Ast::List(vec![ - Ast::Symbol("v4fi".to_string()), + Ast::Symbol("v4f32i".to_string()), Ast::List(vec![ Ast::Symbol("/".to_string()), Ast::List(vec![ @@ -110,7 +110,7 @@ fn test_parse() { ]), ]), Ast::List(vec![ - Ast::Symbol("v2f".to_string()), + Ast::Symbol("v2f32".to_string()), Ast::Symbol("1920.0".to_string()), Ast::Symbol("1080.0".to_string()), ]), -- 2.39.1 From 3a3d9b46f4cdcfeb3c3687aac573b534723f4779 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 10:15:30 +0100 Subject: [PATCH 22/43] Tests Added a regression assert into the compile test --- src/compiler/tests.rs | 64 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index c8012a1..2915262 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -1,8 +1,8 @@ use crate::parser::{tokenize, parse}; -use crate::compiler::meta_compile; #[test] fn test_compile() { + use crate::compiler::*; let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) @@ -19,7 +19,65 @@ fn test_compile() { 1.0))) "; let mut ast = parse(tokenize(src)); - println!("{:?}", ast); + println!("{:#?}", ast); let module = meta_compile(&mut ast); - println!("{:?}", module); + println!("{:#?}", module); + //let test_module: Module = Module { capabilities: [Shader], entry_points: [EntryPoint { execution_model: Fragment, execution_mode: OriginUpperLeft, name: "main", interface: [":frag-coord", ":out-color"] }], globals: [GlobalVariable { name: "frag-coord", typ: "*v4f32i", storage_class: Input, decorations: [Builtin(FragCoord)] }, GlobalVariable { name: "out-color", typ: "*v4f32o", storage_class: Output, decorations: [Location(0)] }], functions: [Function { name: "main", return_type: "void", arguments: [], body: Some([]), ast: Some(List([List([Symbol("store-ptr"), List([Symbol("out-color")]), List([Symbol("v4f32i"), List([Symbol("/"), List([Symbol(".xy"), List([Symbol("load-ptr"), Symbol("frag-coord")])]), List([Symbol("v2f32"), Symbol("1920.0"), Symbol("1080.0")])]), Symbol("1.0"), Symbol("1.0")])])])) }], memory_model: Memory { addressing_model: Logical, memory_model: GLSL450 }, imports: [Import { name: ":std", value: "GLSL.std.450" }] }; + let test_module = Module { + capabilities: vec![Capability::Shader], + entry_points: vec![EntryPoint { + execution_model: ExecutionModel::Fragment, + execution_mode: ExecutionMode::OriginUpperLeft, + name: "main".to_string(), + interface: vec![":frag-coord".to_string(), ":out-color".to_string()], + }], + globals: vec![ + GlobalVariable { + name: "frag-coord".to_string(), + typ: "*v4f32i".to_string(), + storage_class: StorageClass::Input, + decorations: vec![Decoration::Builtin(BuiltinDecoration::FragCoord)], + }, + GlobalVariable { + name: "out-color".to_string(), + typ: "*v4f32o".to_string(), + storage_class: StorageClass::Output, + decorations: vec![Decoration::Location(0)], + }, + ], + functions: vec![Function { + name: "main".to_string(), + return_type: "void".to_string(), + arguments: vec![], + body: Some(vec![]), + ast: Some(Ast::List(vec![ + Ast::List(vec![ + Ast::Symbol("store-ptr".to_string()), + Ast::List(vec![Ast::Symbol("out-color".to_string())]), + Ast::List(vec![ + Ast::Symbol("v4f32i".to_string()), + Ast::List(vec![ + Ast::Symbol("/".to_string()), + Ast::List(vec![ + Ast::Symbol(".xy".to_string()), + Ast::List(vec![Ast::Symbol("load-ptr".to_string()), Ast::Symbol("frag-coord".to_string())]), + ]), + Ast::List(vec![Ast::Symbol("v2f32".to_string()), Ast::Symbol("1920.0".to_string()), Ast::Symbol("1080.0".to_string())]), + ]), + Ast::Symbol("1.0".to_string()), + Ast::Symbol("1.0".to_string()), + ]), + ]), + ])), + }], + memory_model: Memory { + addressing_model: AddressingModel::Logical, + memory_model: MemoryModel::GLSL450, + }, + imports: vec![Import { + name: ":std".to_string(), + value: "GLSL.std.450".to_string(), + }], + }; + assert_eq!(module, test_module); } \ No newline at end of file -- 2.39.1 From 6036a5fe9056015ceb39e85680813944d93855cc Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 10:48:40 +0100 Subject: [PATCH 23/43] Initial work on SPIR-V assembly generation Atm it only handles the basic metadata in modules. Doesn't generate code for functions, or types. Type handling is just a placeholder %undefined_type. --- src/compiler/backend/mod.rs | 87 +++++++++++++++++++++++++++++++++++ src/compiler/backend/tests.rs | 25 ++++++++++ src/compiler/mod.rs | 13 ++++-- src/compiler/tests.rs | 5 +- src/parser/tests.rs | 4 +- 5 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 src/compiler/backend/mod.rs create mode 100644 src/compiler/backend/tests.rs diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs new file mode 100644 index 0000000..77e4b81 --- /dev/null +++ b/src/compiler/backend/mod.rs @@ -0,0 +1,87 @@ +#[cfg(test)] +mod tests; + +use crate::compiler::Module; +use std::{fmt::Write, ops::Add}; + +pub fn fix_name(name: &String) -> String { + format!("%{}", name.clone().replace("-", "_")) +} + +pub fn spirv_meta(module: Module) -> String { + let mut spirv_asm = String::new(); + let mut ops: Vec<(Option, Vec)> = Vec::new(); + + let capabilities: Vec = module.capabilities.iter() + .map(|c| format!("{:?}", c)) + .collect(); + for cap in capabilities { + ops.push((None, vec!["OpCapability".to_string(), cap])); + } + + let memory_model_address = match module.memory_model.addressing_model { + crate::compiler::AddressingModel::Logical => "Logical", + crate::compiler::AddressingModel::Physical32 => "Physical32", + crate::compiler::AddressingModel::Physical64 => "Physical64", + crate::compiler::AddressingModel::PhysicalStorageBuffer64 => "PhysicalStorageBuffer64", + }; + let memory_model_model = match module.memory_model.memory_model { + crate::compiler::MemoryModel::Simple => "Simple", + crate::compiler::MemoryModel::GLSL450 => "GLSL450", + crate::compiler::MemoryModel::OpenCL => "OpenCL", + _ => todo!(), + }; + ops.push((None, vec!["OpMemoryModel".to_string(), memory_model_address.to_string(), memory_model_model.to_string()])); + + for entry in module.entry_points { + let exec_model = match entry.execution_model { + crate::compiler::ExecutionModel::Fragment => "Fragment", + crate::compiler::ExecutionModel::Vertex => "Vertex", + }; + let name = entry.name; + let interface: Vec = entry.interface.iter() + .map(|i| fix_name(&i.to_string())) + .collect(); + let exec_mode = match entry.execution_mode { + crate::compiler::ExecutionMode::OriginUpperLeft => "OriginUpperLeft", + }; + ops.push((None, vec!["OpEntryPoint".to_string(), exec_model.to_string(), fix_name(&name), format!("\"{}\"", name), interface.join(" ")])); + ops.push((None, vec!["OpExecutionMode".to_string(), fix_name(&name), exec_mode.to_string()])); + } + + for global in module.globals { + let name = fix_name(&global.name); + let typ = global.typ; + let storage_class = match global.storage_class { + crate::compiler::StorageClass::Input => "Input", + crate::compiler::StorageClass::Output => "Output", + crate::compiler::StorageClass::Undefined => panic!("Bound a non-declared variable"), + }; + let typ_id = "%undefined_type"; + ops.push((Some(name.clone()), vec!["OpVariable".to_string(), typ_id.to_string(), storage_class.to_string()])); + for dec in global.decorations { + // Decorations have the format Location 0, or Builtin FragCoord + let dec = match dec { + crate::compiler::Decoration::Location(loc) => format!("Location {}", loc), + crate::compiler::Decoration::BuiltIn(builtin) => { + let builtin = match builtin { + crate::compiler::BuiltinDecoration::FragCoord => "FragCoord", + }; + format!("BuiltIn {}", builtin) + }, + }; + ops.push((None, vec!["OpDecorate".to_string(), name.clone(), dec])); + } + } + + for op in ops { + if op.0.is_some() { + write!(spirv_asm, "{} = ", op.0.unwrap()).unwrap(); + } + for arg in op.1 { + write!(spirv_asm, "{} ", arg).unwrap(); + } + writeln!(spirv_asm).unwrap(); + } + spirv_asm +} \ No newline at end of file diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs new file mode 100644 index 0000000..87c258d --- /dev/null +++ b/src/compiler/backend/tests.rs @@ -0,0 +1,25 @@ +#[test] +fn test_emit() { + use crate::compiler::*; + use crate::parser::*; + use crate::compiler::backend::*; + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let mut ast = parse(tokenize(src)); + let module = meta_compile(&mut ast); + let res = spirv_meta(module); + println!("{}", res); +} \ No newline at end of file diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 2bf5e06..9a83957 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,5 +1,7 @@ use crate::parser::Ast; +pub mod backend; + #[cfg(test)] mod tests; @@ -18,7 +20,8 @@ pub enum ExecutionMode { #[derive(Debug, PartialEq, Default, Clone)] pub enum ExecutionModel { #[default] - Fragment + Fragment, + Vertex, } #[derive(Debug, PartialEq, Default, Clone)] @@ -60,7 +63,7 @@ pub enum BuiltinDecoration { #[derive(Debug, PartialEq, Clone)] pub enum Decoration { - Builtin(BuiltinDecoration), + BuiltIn(BuiltinDecoration), Location(u32), } @@ -175,7 +178,7 @@ pub fn meta_compile(ast: &mut Ast) -> Module { } }).collect(); let bind_name = match bind[0].as_str() { - "Builtin" => Decoration::Builtin(BuiltinDecoration::FragCoord), + "BuiltIn" => Decoration::BuiltIn(BuiltinDecoration::FragCoord), "Location" => Decoration::Location(bind[1].parse::().unwrap()), _ => panic!("Unknown bind! {:?}", bind), }; @@ -249,7 +252,9 @@ pub fn meta_compile(ast: &mut Ast) -> Module { }; let interface: Vec = interface.iter().map(|x| { match x { - Ast::Symbol(s) => s.to_string(), + Ast::Symbol(s) => { + s.to_string().replace(":", "") + }, _ => panic!("Expected symbol! {:?}", x), } }).collect(); diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 2915262..08b18e9 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -6,7 +6,7 @@ fn test_compile() { let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) -(bind (frag-coord:*v4f32i) (Builtin FragCoord)) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) (bind (out-color:*v4f32o) (Location 0)) (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) @@ -22,7 +22,6 @@ fn test_compile() { println!("{:#?}", ast); let module = meta_compile(&mut ast); println!("{:#?}", module); - //let test_module: Module = Module { capabilities: [Shader], entry_points: [EntryPoint { execution_model: Fragment, execution_mode: OriginUpperLeft, name: "main", interface: [":frag-coord", ":out-color"] }], globals: [GlobalVariable { name: "frag-coord", typ: "*v4f32i", storage_class: Input, decorations: [Builtin(FragCoord)] }, GlobalVariable { name: "out-color", typ: "*v4f32o", storage_class: Output, decorations: [Location(0)] }], functions: [Function { name: "main", return_type: "void", arguments: [], body: Some([]), ast: Some(List([List([Symbol("store-ptr"), List([Symbol("out-color")]), List([Symbol("v4f32i"), List([Symbol("/"), List([Symbol(".xy"), List([Symbol("load-ptr"), Symbol("frag-coord")])]), List([Symbol("v2f32"), Symbol("1920.0"), Symbol("1080.0")])]), Symbol("1.0"), Symbol("1.0")])])])) }], memory_model: Memory { addressing_model: Logical, memory_model: GLSL450 }, imports: [Import { name: ":std", value: "GLSL.std.450" }] }; let test_module = Module { capabilities: vec![Capability::Shader], entry_points: vec![EntryPoint { @@ -36,7 +35,7 @@ fn test_compile() { name: "frag-coord".to_string(), typ: "*v4f32i".to_string(), storage_class: StorageClass::Input, - decorations: vec![Decoration::Builtin(BuiltinDecoration::FragCoord)], + decorations: vec![Decoration::BuiltIn(BuiltinDecoration::FragCoord)], }, GlobalVariable { name: "out-color".to_string(), diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 6251515..dd6a947 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -22,7 +22,7 @@ fn test_parse() { let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) -(bind (frag-coord:*v4f32i) (Builtin FragCoord)) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) (bind (out-color:*v4f32o) (Location 0)) (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) @@ -54,7 +54,7 @@ fn test_parse() { Ast::Symbol("frag-coord:*v4f32i".to_string()), ]), Ast::List(vec![ - Ast::Symbol("Builtin".to_string()), + Ast::Symbol("BuiltIn".to_string()), Ast::Symbol("FragCoord".to_string()), ]), ]), -- 2.39.1 From 3d5369cc365478b31909138be5750841267ece46 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 11:25:52 +0100 Subject: [PATCH 24/43] Broken; Rust's Display trait is weird. Help. --- src/compiler/backend/mod.rs | 118 ++++++++++++++++++++++++++++++++-- src/compiler/backend/tests.rs | 14 ++++ src/compiler/mod.rs | 12 ++++ src/compiler/tests.rs | 2 +- 4 files changed, 141 insertions(+), 5 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index 77e4b81..74193c5 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -2,9 +2,114 @@ mod tests; use crate::compiler::Module; -use std::{fmt::Write, ops::Add}; +use std::fmt::Write; +use std::fmt; -pub fn fix_name(name: &String) -> String { +use super::StorageClass; + +#[derive(Debug, PartialEq, Clone)] +pub enum Type { + Pointer(Box, StorageClass), + Vector(Box, u32), + Float(u32), + Int(u32), + Unsigned(u32), +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Type::Pointer(typ, storage_class) => { + match storage_class { + StorageClass::Input => write!(f, "*{}i", typ), + StorageClass::Output => write!(f, "*{}o", typ), + StorageClass::Undefined => panic!("Bound a non-declared variable"), + } + }, + Type::Vector(typ, size) => write!(f, "v{}{}", size, typ), + Type::Float(size) => write!(f, "f{}", size), + Type::Int(size) => write!(f, "s{}", size), + Type::Unsigned(size) => write!(f, "u{}", size), + } + } +} + +pub fn parse_type(typ: String) -> Type { + // pointers have the format *i or *o, for the storage class + // floats have the format f, so f32, f64... + // ints have the format s, so s32, s64... + // unsigned ints have the format u, so u32, u64... + // So, *v4f32i is a pointer to a vector of 4 floats, with the storage class Input. + // *v4f32o is a pointer to a vector of 4 floats, with the storage class Output. + + let c = typ.chars().next().unwrap(); + match c { + '*' => { + let mut chars = typ.chars(); + chars.next(); + let typ = chars.collect::(); + let storage_class = match typ.chars().last().unwrap() { + 'i' => StorageClass::Input, + 'o' => StorageClass::Output, + _ => panic!("Invalid storage class"), + }; + let typ = typ.chars().take(typ.len() - 1).collect::(); + Type::Pointer(Box::new(parse_type(typ)), storage_class) + }, + 'v' => { + let mut chars = typ.chars(); + chars.next(); + let size = chars.next().unwrap().to_digit(10).unwrap(); + let typ = chars.collect::(); + Type::Vector(Box::new(parse_type(typ)), size) + }, + 'f' => { + let size = typ.chars().skip(1).collect::().parse().unwrap(); + Type::Float(size) + }, + 's' => { + let size = typ.chars().skip(1).collect::().parse().unwrap(); + Type::Int(size) + }, + 'u' => { + let size = typ.chars().skip(1).collect::().parse().unwrap(); + Type::Unsigned(size) + }, + _ => panic!("Invalid type"), + } + +} + +fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { + match typ { + Type::Unsigned(size) => { + ops.push((format!("%u{}", size).to_string(), format!("OpTypeInt {}", size))); + }, + Type::Int(size) => { + ops.push((format!("%s{}", size).to_string(), format!("OpTypeInt {}", size))); + }, + Type::Float(size) => { + ops.push((format!("%f{}", size).to_string(), format!("OpTypeFloat {}", size))); + }, + Type::Vector(typ, size) => { + emit_type(*typ.clone(), ops); + let typ_id = format!("%{}", *typ.clone()); + ops.push((format!("%v{}{}", typ_id, size).to_string(), format!("OpTypeVector {} {}", typ_id, size))) + }, + Type::Pointer(typ, storage_class) => { + emit_type(*typ.clone(), ops); + let typ_id = format!("%{}", *typ.clone()); + let storage_class = match storage_class { + StorageClass::Input => "Input", + StorageClass::Output => "Output", + StorageClass::Undefined => panic!("Bound a non-declared variable"), + }; + ops.push((format!("%{}{}", typ_id, storage_class).to_string(), format!("OpTypePointer {} {}", storage_class, typ_id))) + }, + } +} + +fn fix_name(name: &String) -> String { format!("%{}", name.clone().replace("-", "_")) } @@ -51,13 +156,18 @@ pub fn spirv_meta(module: Module) -> String { for global in module.globals { let name = fix_name(&global.name); - let typ = global.typ; + let _typ = global.typ; let storage_class = match global.storage_class { crate::compiler::StorageClass::Input => "Input", crate::compiler::StorageClass::Output => "Output", crate::compiler::StorageClass::Undefined => panic!("Bound a non-declared variable"), }; - let typ_id = "%undefined_type"; + let typ_id = format!("%{}", _typ); + let mut type_ops = Vec::new(); + emit_type(parse_type(_typ), &mut type_ops); + for op in type_ops { + ops.push((Some(op.0), vec![op.1])); + } ops.push((Some(name.clone()), vec!["OpVariable".to_string(), typ_id.to_string(), storage_class.to_string()])); for dec in global.decorations { // Decorations have the format Location 0, or Builtin FragCoord diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 87c258d..e84c06b 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -22,4 +22,18 @@ fn test_emit() { let module = meta_compile(&mut ast); let res = spirv_meta(module); println!("{}", res); +} + +#[test] +fn test_type_parse() { + use crate::compiler::backend::*; + use Type::*; + assert_eq!(parse_type("*v4f32i".to_string()), + Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Input)); + assert_eq!(parse_type("*v4f32o".to_string()), + Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Output)); + assert_eq!(parse_type("v2f32".to_string()), + Vector(Box::new(Float(32)), 2)); + assert_eq!(parse_type("f32".to_string()), Float(32)); + assert_eq!(parse_type("s32".to_string()), Int(32)); } \ No newline at end of file diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 9a83957..140a57a 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -2,6 +2,8 @@ use crate::parser::Ast; pub mod backend; +use std::fmt; + #[cfg(test)] mod tests; @@ -75,6 +77,16 @@ pub enum StorageClass { Output, } +impl fmt::Display for StorageClass { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + StorageClass::Undefined => write!(f, "Undefined"), + StorageClass::Input => write!(f, "Input"), + StorageClass::Output => write!(f, "Output"), + } + } +} + #[derive(Debug, PartialEq, Default, Clone)] pub struct GlobalVariable { pub name: String, diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 08b18e9..33398bd 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -28,7 +28,7 @@ fn test_compile() { execution_model: ExecutionModel::Fragment, execution_mode: ExecutionMode::OriginUpperLeft, name: "main".to_string(), - interface: vec![":frag-coord".to_string(), ":out-color".to_string()], + interface: vec!["frag-coord".to_string(), "out-color".to_string()], }], globals: vec![ GlobalVariable { -- 2.39.1 From 5f041f491a74264d1b5039b7c106ac029e982e44 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 11:38:58 +0100 Subject: [PATCH 25/43] Fixed type generation --- src/compiler/backend/mod.rs | 42 ++++++++++++++++++++++------------- src/compiler/backend/tests.rs | 10 ++++----- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index 74193c5..e88ceed 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -34,7 +34,7 @@ impl fmt::Display for Type { } } -pub fn parse_type(typ: String) -> Type { +pub fn parse_type(typ: &String) -> Type { // pointers have the format *i or *o, for the storage class // floats have the format f, so f32, f64... // ints have the format s, so s32, s64... @@ -54,14 +54,14 @@ pub fn parse_type(typ: String) -> Type { _ => panic!("Invalid storage class"), }; let typ = typ.chars().take(typ.len() - 1).collect::(); - Type::Pointer(Box::new(parse_type(typ)), storage_class) + Type::Pointer(Box::new(parse_type(&typ)), storage_class) }, 'v' => { let mut chars = typ.chars(); chars.next(); let size = chars.next().unwrap().to_digit(10).unwrap(); let typ = chars.collect::(); - Type::Vector(Box::new(parse_type(typ)), size) + Type::Vector(Box::new(parse_type(&typ)), size) }, 'f' => { let size = typ.chars().skip(1).collect::().parse().unwrap(); @@ -81,7 +81,7 @@ pub fn parse_type(typ: String) -> Type { } fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { - match typ { + match &typ { Type::Unsigned(size) => { ops.push((format!("%u{}", size).to_string(), format!("OpTypeInt {}", size))); }, @@ -91,26 +91,34 @@ fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { Type::Float(size) => { ops.push((format!("%f{}", size).to_string(), format!("OpTypeFloat {}", size))); }, - Type::Vector(typ, size) => { - emit_type(*typ.clone(), ops); - let typ_id = format!("%{}", *typ.clone()); - ops.push((format!("%v{}{}", typ_id, size).to_string(), format!("OpTypeVector {} {}", typ_id, size))) + Type::Vector(in_typ, size) => { + emit_type(*in_typ.clone(), ops); + ops.push((fix_name(&typ.to_string()), format!("OpTypeVector {} {}", fix_name(&in_typ.clone().to_string()), size))) }, - Type::Pointer(typ, storage_class) => { - emit_type(*typ.clone(), ops); - let typ_id = format!("%{}", *typ.clone()); + Type::Pointer(in_typ, storage_class) => { + emit_type(*in_typ.clone(), ops); + let typ_id = fix_name(&typ.to_string()); let storage_class = match storage_class { StorageClass::Input => "Input", StorageClass::Output => "Output", StorageClass::Undefined => panic!("Bound a non-declared variable"), }; - ops.push((format!("%{}{}", typ_id, storage_class).to_string(), format!("OpTypePointer {} {}", storage_class, typ_id))) + ops.push((typ_id, format!("OpTypePointer {} {}", storage_class, fix_name(&in_typ.to_string())))) }, } } fn fix_name(name: &String) -> String { - format!("%{}", name.clone().replace("-", "_")) + format!("%{}", name.clone().replace("-", "_").replace("*", "p")) +} + +fn has_id(name: String, ops: &Vec<(Option, Vec)>) -> bool { + for op in ops { + if op.0.is_some() && op.0.clone().unwrap() == name { + return true; + } + } + false } pub fn spirv_meta(module: Module) -> String { @@ -162,13 +170,15 @@ pub fn spirv_meta(module: Module) -> String { crate::compiler::StorageClass::Output => "Output", crate::compiler::StorageClass::Undefined => panic!("Bound a non-declared variable"), }; - let typ_id = format!("%{}", _typ); let mut type_ops = Vec::new(); - emit_type(parse_type(_typ), &mut type_ops); + emit_type(parse_type(&_typ), &mut type_ops); for op in type_ops { + if has_id(op.0.clone(), &ops) { + continue; + } ops.push((Some(op.0), vec![op.1])); } - ops.push((Some(name.clone()), vec!["OpVariable".to_string(), typ_id.to_string(), storage_class.to_string()])); + ops.push((Some(name.clone()), vec!["OpVariable".to_string(), fix_name(&_typ), storage_class.to_string()])); for dec in global.decorations { // Decorations have the format Location 0, or Builtin FragCoord let dec = match dec { diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index e84c06b..0f21e2f 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -28,12 +28,12 @@ fn test_emit() { fn test_type_parse() { use crate::compiler::backend::*; use Type::*; - assert_eq!(parse_type("*v4f32i".to_string()), + assert_eq!(parse_type(&"*v4f32i".to_string()), Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Input)); - assert_eq!(parse_type("*v4f32o".to_string()), + assert_eq!(parse_type(&"*v4f32o".to_string()), Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Output)); - assert_eq!(parse_type("v2f32".to_string()), + assert_eq!(parse_type(&"v2f32".to_string()), Vector(Box::new(Float(32)), 2)); - assert_eq!(parse_type("f32".to_string()), Float(32)); - assert_eq!(parse_type("s32".to_string()), Int(32)); + assert_eq!(parse_type(&"f32".to_string()), Float(32)); + assert_eq!(parse_type(&"s32".to_string()), Int(32)); } \ No newline at end of file -- 2.39.1 From b4ab40f10eb5006bc2c8cdcf3a862a1c3f2695ea Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 6 Mar 2025 11:25:39 +0100 Subject: [PATCH 26/43] Add error handling and location tracking to the tokenizer and parser --- src/main.rs | 93 +++++++++++++++++--- src/parser/mod.rs | 190 ++++++++++++++++++++++++++++++++++------ src/parser/tests.rs | 206 ++++++++++++++++++++++++-------------------- 3 files changed, 354 insertions(+), 135 deletions(-) diff --git a/src/main.rs b/src/main.rs index ebebce3..2374721 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ use std::fmt::Write; -pub mod parser; pub mod compiler; +pub mod parser; fn main() { let mut ops: Vec<(Option, Vec)> = Vec::new(); - + // OpMemoryModel Logical GLSL450 // OpEntryPoint Fragment %main "main" // OpExecutionMode %main OriginUpperLeft @@ -18,23 +18,88 @@ fn main() { //%5 = OpLabel // OpReturn // OpFunctionEnd - + ops.push((None, vec!["OpCapability".to_string(), "Shader".to_string()])); - ops.push((Some("%1".to_string()), vec!["OpExtInstImport".to_string(), "\"GLSL.std.450\"".to_string()])); - ops.push((None, vec!["OpMemoryModel".to_string(), "Logical".to_string(), "GLSL450".to_string()])); - ops.push((None, vec!["OpEntryPoint".to_string(), "Fragment".to_string(), "%main".to_string(), "\"main\"".to_string()])); - ops.push((None, vec!["OpExecutionMode".to_string(), "%main".to_string(), "OriginUpperLeft".to_string()])); - ops.push((None, vec!["OpSource".to_string(), "GLSL".to_string(), "450".to_string()])); - ops.push((None, vec!["OpSourceExtension".to_string(), "\"GL_GOOGLE_cpp_style_line_directive\"".to_string()])); - ops.push((None, vec!["OpSourceExtension".to_string(), "\"GL_GOOGLE_include_directive\"".to_string()])); - ops.push((None, vec!["OpName".to_string(), "%main".to_string(), "\"main\"".to_string()])); + ops.push(( + Some("%1".to_string()), + vec![ + "OpExtInstImport".to_string(), + "\"GLSL.std.450\"".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpMemoryModel".to_string(), + "Logical".to_string(), + "GLSL450".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpEntryPoint".to_string(), + "Fragment".to_string(), + "%main".to_string(), + "\"main\"".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpExecutionMode".to_string(), + "%main".to_string(), + "OriginUpperLeft".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpSource".to_string(), + "GLSL".to_string(), + "450".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpSourceExtension".to_string(), + "\"GL_GOOGLE_cpp_style_line_directive\"".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpSourceExtension".to_string(), + "\"GL_GOOGLE_include_directive\"".to_string(), + ], + )); + ops.push(( + None, + vec![ + "OpName".to_string(), + "%main".to_string(), + "\"main\"".to_string(), + ], + )); ops.push((Some("%void".to_string()), vec!["OpTypeVoid".to_string()])); - ops.push((Some("%3".to_string()), vec!["OpTypeFunction".to_string(), "%void".to_string()])); - ops.push((Some("%main".to_string()), vec!["OpFunction".to_string(), "%void".to_string(), "None".to_string(), "%3".to_string()])); + ops.push(( + Some("%3".to_string()), + vec!["OpTypeFunction".to_string(), "%void".to_string()], + )); + ops.push(( + Some("%main".to_string()), + vec![ + "OpFunction".to_string(), + "%void".to_string(), + "None".to_string(), + "%3".to_string(), + ], + )); ops.push((Some("%5".to_string()), vec!["OpLabel".to_string()])); ops.push((None, vec!["OpReturn".to_string()])); ops.push((None, vec!["OpFunctionEnd".to_string()])); - + let mut out: String = String::new(); for op in ops { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c56d16f..427f1f6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2,9 +2,94 @@ mod tests; use std::iter::Peekable; +use std::ops::RangeInclusive; use std::vec::IntoIter; -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug)] +enum Location { + Char { + line: usize, + col: usize, + }, + String { + lines: RangeInclusive, + cols: RangeInclusive, + }, +} + +#[derive(Debug)] +pub enum ParseError { + UnexpectedParenClose(Location), + UnexpectedEof, +} + +impl Location { + /// Since [`Localised`] doesn't test for location match, allow for creating simple dummy + /// locations for testing + #[cfg(test)] + pub fn dummy() -> Location { + Self::Char { line: 0, col: 0 } + } + + pub fn range(start: &Location, end: &Location) -> Location { + Location::String { + lines: start.line_start()..=end.line_end(), + cols: start.col_start()..=end.col_end(), + } + } + + pub fn line_start(&self) -> usize { + match self { + Location::Char { line, .. } => *line, + Location::String { lines, .. } => *lines.start(), + } + } + + pub fn col_start(&self) -> usize { + match self { + Location::Char { col, .. } => *col, + Location::String { cols, .. } => *cols.start(), + } + } + + pub fn line_end(&self) -> usize { + match self { + Location::Char { line, .. } => *line, + Location::String { lines, .. } => *lines.end(), + } + } + + pub fn col_end(&self) -> usize { + match self { + Location::Char { col, .. } => *col, + Location::String { cols, .. } => *cols.end(), + } + } +} + +#[derive(Debug, Clone)] +struct Localised { + location: Location, + item: T, +} + +impl Localised { + #[cfg(test)] + pub fn dummy_location(item: T) -> Self { + Self { + location: Location::dummy(), + item, + } + } +} + +impl PartialEq for Localised { + fn eq(&self, other: &Self) -> bool { + self.item.eq(&other.item) + } +} + +#[derive(Debug, PartialEq, Clone)] pub enum Token { LeftParen, RightParen, @@ -13,56 +98,109 @@ pub enum Token { #[derive(Debug, PartialEq, Clone)] pub enum Ast { - Symbol(String), - List(Vec), + Symbol(Localised), + List(Localised>), Root(Vec), } -pub fn tokenize(input: &str) -> Vec { +pub fn tokenize(input: &str) -> Vec> { let mut tokens = Vec::new(); - let mut chars = input.chars().peekable(); - while let Some(c) = chars.next() { + // let mut chars = input.chars().peekable(); + let mut chars = (1..) + .zip(input.split('\n')) + .flat_map(|(l_num, l)| { + (1..).zip(l.chars()).map(move |(c_num, c)| Localised { + location: Location::Char { + line: l_num, + col: c_num, + }, + item: c, + }) + }) + .peekable(); + while let Some(Localised { location, item: c }) = chars.next() { match c { - '(' => tokens.push(Token::LeftParen), - ')' => tokens.push(Token::RightParen), + '(' => tokens.push(Localised { + location, + item: Token::LeftParen, + }), + ')' => tokens.push(Localised { + location, + item: Token::RightParen, + }), _ if c.is_whitespace() => (), _ => { + let start = location.clone(); + let mut end = location; let mut symbol = c.to_string(); - while let Some(&c) = chars.peek() { - if c.is_whitespace() || c == '(' || c == ')' { + while let Some(Localised { item: c, .. }) = chars.peek() { + if c.is_whitespace() || *c == '(' || *c == ')' { break; } - symbol.push(c); - chars.next(); + symbol.push(*c); + let Localised { location, .. } = chars.next().unwrap(); + end = location; } - tokens.push(Token::Symbol(symbol)); + tokens.push(Localised { + location: Location::range(&start, &end), + item: Token::Symbol(symbol), + }); } } } tokens } -fn parse_expr(tokens: &mut Peekable>) -> Ast { +fn parse_expr(tokens: &mut Peekable>>) -> Result { match tokens.next() { - Some(Token::LeftParen) => { + Some(Localised { + location: start, + item: Token::LeftParen, + }) => { let mut list = Vec::new(); - while tokens.peek() != Some(&Token::RightParen) { - list.push(parse_expr(tokens)); + while !matches!( + tokens.peek(), + Some(Localised { + item: Token::RightParen, + .. + }) + ) { + list.push(parse_expr(tokens)?); } - tokens.next(); - Ast::List(list) + let Some(Localised { + location: end, + item: Token::RightParen, + }) = tokens.next() + else { + unreachable!() + }; + Ok(Ast::List(Localised { + location: Location::range(&start, &end), + item: list, + })) } - Some(Token::RightParen) => panic!("unexpected )"), - Some(Token::Symbol(s)) => Ast::Symbol(s), - None => panic!("unexpected EOF"), + Some(Localised { + location, + item: Token::RightParen, + }) => Err(ParseError::UnexpectedParenClose(location)), + Some(Localised { + location, + item: Token::Symbol(s), + }) => Ok(Ast::Symbol(Localised { location, item: s })), + None => Err(ParseError::UnexpectedEof), } } -pub fn parse(tokens: Vec) -> Ast { +pub fn parse(tokens: Vec>) -> Result { let mut tokens = tokens.into_iter().peekable(); let mut ast: Vec = Vec::new(); while tokens.peek().is_some() { - ast.push(parse_expr(&mut tokens)); + ast.push(parse_expr(&mut tokens)?); } - Ast::Root(ast) -} \ No newline at end of file + Ok(Ast::Root(ast)) +} + +pub fn parse_string(src: &str) -> Result { + let tokens = tokenize(src); + parse(tokens) +} diff --git a/src/parser/tests.rs b/src/parser/tests.rs index dd6a947..88f38cf 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -1,22 +1,38 @@ -use crate::parser::{tokenize, parse, Token, Ast}; +use crate::parser::{Ast, Localised, Token, parse, tokenize}; #[test] fn test_tokenize() { let input = "(+ (* 1:u8 2) 2)"; let expected = vec![ - Token::LeftParen, - Token::Symbol("+".to_string()), - Token::LeftParen, - Token::Symbol("*".to_string()), - Token::Symbol("1:u8".to_string()), - Token::Symbol("2".to_string()), - Token::RightParen, - Token::Symbol("2".to_string()), - Token::RightParen, + Localised::dummy_location(Token::LeftParen), + Localised::dummy_location(Token::Symbol("+".to_string())), + Localised::dummy_location(Token::LeftParen), + Localised::dummy_location(Token::Symbol("*".to_string())), + Localised::dummy_location(Token::Symbol("1:u8".to_string())), + Localised::dummy_location(Token::Symbol("2".to_string())), + Localised::dummy_location(Token::RightParen), + Localised::dummy_location(Token::Symbol("2".to_string())), + Localised::dummy_location(Token::RightParen), ]; assert_eq!(tokenize(input), expected); } +#[test] +#[should_panic] +fn test_unexpected_pclose() { + let input = "())"; + let tokens = tokenize(input); + let _ast = parse(tokens).unwrap(); +} + +#[test] +#[should_panic] +fn test_unexpected_eof() { + let input = "(1 2 3 4"; + let tokens = tokenize(input); + let _ast = parse(tokens).unwrap(); +} + #[test] fn test_parse() { let src = " @@ -34,92 +50,92 @@ fn test_parse() { 1.0 1.0))) "; - let ast = parse(tokenize(src)); + let ast = parse(tokenize(src)).unwrap(); println!("{:?}", ast); let test_ast: Ast = Ast::Root(vec![ - Ast::List(vec![ - Ast::Symbol("module".to_string()), - Ast::Symbol("Shader".to_string()), - Ast::Symbol("Logical".to_string()), - Ast::Symbol("GLSL450".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("import".to_string()), - Ast::Symbol(":std".to_string()), - Ast::Symbol("GLSL.std.450".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("bind".to_string()), - Ast::List(vec![ - Ast::Symbol("frag-coord:*v4f32i".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("BuiltIn".to_string()), - Ast::Symbol("FragCoord".to_string()), - ]), - ]), - Ast::List(vec![ - Ast::Symbol("bind".to_string()), - Ast::List(vec![ - Ast::Symbol("out-color:*v4f32o".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("Location".to_string()), - Ast::Symbol("0".to_string()), - ]), - ]), - Ast::List(vec![ - Ast::Symbol("dec".to_string()), - Ast::Symbol("frag-coord:*v4f32i".to_string()), - Ast::Symbol("Input".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("dec".to_string()), - Ast::Symbol("out-color:*v4f32o".to_string()), - Ast::Symbol("Output".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("entry".to_string()), - Ast::Symbol("main".to_string()), - Ast::Symbol("Fragment".to_string()), - Ast::Symbol("OriginUpperLeft".to_string()), - Ast::List(vec![ - Ast::Symbol(":frag-coord".to_string()), - Ast::Symbol(":out-color".to_string()), - ]), - ]), - Ast::List(vec![ - Ast::Symbol("fun".to_string()), - Ast::List(vec![ - Ast::Symbol("main".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("store-ptr".to_string()), - Ast::List(vec![ - Ast::Symbol("out-color".to_string()), - ]), - Ast::List(vec![ - Ast::Symbol("v4f32i".to_string()), - Ast::List(vec![ - Ast::Symbol("/".to_string()), - Ast::List(vec![ - Ast::Symbol(".xy".to_string()), - Ast::List(vec![ - Ast::Symbol("load-ptr".to_string()), - Ast::Symbol("frag-coord".to_string()), - ]), - ]), - Ast::List(vec![ - Ast::Symbol("v2f32".to_string()), - Ast::Symbol("1920.0".to_string()), - Ast::Symbol("1080.0".to_string()), - ]), - ]), - Ast::Symbol("1.0".to_string()), - Ast::Symbol("1.0".to_string()), - ]), - ]), - ]), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("module".to_string())), + Ast::Symbol(Localised::dummy_location("Shader".to_string())), + Ast::Symbol(Localised::dummy_location("Logical".to_string())), + Ast::Symbol(Localised::dummy_location("GLSL450".to_string())), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("import".to_string())), + Ast::Symbol(Localised::dummy_location(":std".to_string())), + Ast::Symbol(Localised::dummy_location("GLSL.std.450".to_string())), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("bind".to_string())), + Ast::List(Localised::dummy_location(vec![Ast::Symbol( + Localised::dummy_location("frag-coord:*v4f32i".to_string()), + )])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("Builtin".to_string())), + Ast::Symbol(Localised::dummy_location("FragCoord".to_string())), + ])), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("bind".to_string())), + Ast::List(Localised::dummy_location(vec![Ast::Symbol( + Localised::dummy_location("out-color:*v4f32o".to_string()), + )])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("Location".to_string())), + Ast::Symbol(Localised::dummy_location("0".to_string())), + ])), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("dec".to_string())), + Ast::Symbol(Localised::dummy_location("frag-coord:*v4f32i".to_string())), + Ast::Symbol(Localised::dummy_location("Input".to_string())), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("dec".to_string())), + Ast::Symbol(Localised::dummy_location("out-color:*v4f32o".to_string())), + Ast::Symbol(Localised::dummy_location("Output".to_string())), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("entry".to_string())), + Ast::Symbol(Localised::dummy_location("main".to_string())), + Ast::Symbol(Localised::dummy_location("Fragment".to_string())), + Ast::Symbol(Localised::dummy_location("OriginUpperLeft".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location(":frag-coord".to_string())), + Ast::Symbol(Localised::dummy_location(":out-color".to_string())), + ])), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("fun".to_string())), + Ast::List(Localised::dummy_location(vec![Ast::Symbol( + Localised::dummy_location("main".to_string()), + )])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("store-ptr".to_string())), + Ast::List(Localised::dummy_location(vec![Ast::Symbol( + Localised::dummy_location("out-color".to_string()), + )])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("v4f23i".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("/".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location(".xy".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("load-ptr".to_string())), + Ast::Symbol(Localised::dummy_location("frag-coord".to_string())), + ])), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("v2f32".to_string())), + Ast::Symbol(Localised::dummy_location("1920.0".to_string())), + Ast::Symbol(Localised::dummy_location("1080.0".to_string())), + ])), + ])), + Ast::Symbol(Localised::dummy_location("1.0".to_string())), + Ast::Symbol(Localised::dummy_location("1.0".to_string())), + ])), + ])), + ])), ]); assert_eq!(ast, test_ast); -} \ No newline at end of file +} -- 2.39.1 From e80c32419ce8eec519dc39b8e171b1372135b44a Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 6 Mar 2025 11:57:57 +0100 Subject: [PATCH 27/43] Fix test dealing with `Localized` --- src/compiler/backend/tests.rs | 32 +++++++---- src/compiler/mod.rs | 100 ++++++++++++++++++++++------------ src/compiler/tests.rs | 58 ++++++++++++-------- src/parser/mod.rs | 35 ++++++++++-- src/parser/tests.rs | 7 ++- 5 files changed, 156 insertions(+), 76 deletions(-) diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 0f21e2f..4252995 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -1,8 +1,8 @@ #[test] fn test_emit() { + use crate::compiler::backend::*; use crate::compiler::*; use crate::parser::*; - use crate::compiler::backend::*; let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) @@ -18,8 +18,8 @@ fn test_emit() { 1.0 1.0))) "; - let mut ast = parse(tokenize(src)); - let module = meta_compile(&mut ast); + let ast = parse(tokenize(src)); + let module = meta_compile(&mut ast.unwrap()); let res = spirv_meta(module); println!("{}", res); } @@ -28,12 +28,24 @@ fn test_emit() { fn test_type_parse() { use crate::compiler::backend::*; use Type::*; - assert_eq!(parse_type(&"*v4f32i".to_string()), - Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Input)); - assert_eq!(parse_type(&"*v4f32o".to_string()), - Pointer(Box::new(Vector(Box::new(Float(32)), 4)), StorageClass::Output)); - assert_eq!(parse_type(&"v2f32".to_string()), - Vector(Box::new(Float(32)), 2)); + assert_eq!( + parse_type(&"*v4f32i".to_string()), + Pointer( + Box::new(Vector(Box::new(Float(32)), 4)), + StorageClass::Input + ) + ); + assert_eq!( + parse_type(&"*v4f32o".to_string()), + Pointer( + Box::new(Vector(Box::new(Float(32)), 4)), + StorageClass::Output + ) + ); + assert_eq!( + parse_type(&"v2f32".to_string()), + Vector(Box::new(Float(32)), 2) + ); assert_eq!(parse_type(&"f32".to_string()), Float(32)); assert_eq!(parse_type(&"s32".to_string()), Int(32)); -} \ No newline at end of file +} diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 140a57a..8635d0c 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,4 +1,4 @@ -use crate::parser::Ast; +use crate::parser::{Ast, Localised, Location}; pub mod backend; @@ -10,13 +10,13 @@ mod tests; #[derive(Debug, PartialEq, Default, Clone)] pub enum Capability { #[default] - Shader + Shader, } #[derive(Debug, PartialEq, Default, Clone)] pub enum ExecutionMode { #[default] - OriginUpperLeft + OriginUpperLeft, } #[derive(Debug, PartialEq, Default, Clone)] @@ -40,7 +40,7 @@ pub enum AddressingModel { Logical, Physical32, Physical64, - PhysicalStorageBuffer64 + PhysicalStorageBuffer64, } #[derive(Debug, PartialEq, Default, Clone)] pub enum MemoryModel { @@ -48,7 +48,7 @@ pub enum MemoryModel { GLSL450, OpenCL, VulkanKHR, - Simple + Simple, } #[derive(Debug, PartialEq, Default, Clone)] @@ -147,9 +147,9 @@ pub fn meta_compile(ast: &mut Ast) -> Module { memory_model: MemoryModel::GLSL450, }; module.capabilities.push(Capability::Shader); - assert_eq!(exec, Ast::Symbol("Logical".to_string())); - assert_eq!(memory, Ast::Symbol("GLSL450".to_string())); - assert_eq!(cap, Ast::Symbol("Shader".to_string())); + assert_eq!(exec.symbol(), Some("Logical".to_string())); + assert_eq!(memory.symbol(), Some("GLSL450".to_string())); + assert_eq!(cap.symbol(), Some("Shader".to_string())); } "import" => { let name = match &list[1] { @@ -170,28 +170,38 @@ pub fn meta_compile(ast: &mut Ast) -> Module { Ast::List(l) => l, _ => panic!("Expected list! {:?}", list[1]), }; - let name_and_type: String = name_and_type.iter().map(|x| { - match x { + let name_and_type: String = name_and_type + .iter() + .map(|x| match x { Ast::Symbol(s) => s.to_string(), _ => panic!("Expected symbol! {:?}", x), - } - }).collect(); + }) + .collect(); // name_and_type is of the name:type format, like foo:f32 - let name: String = name_and_type.split(":").collect::>()[0].to_string(); - let typ: String = name_and_type.split(":").collect::>()[1].to_string(); + let name: String = + name_and_type.split(":").collect::>()[0] + .to_string(); + let typ: String = + name_and_type.split(":").collect::>()[1] + .to_string(); let bind = match &list[2] { Ast::List(l) => l, _ => panic!("Expected list! {:?}", list[2]), }; - let bind: Vec = bind.iter().map(|x| { - match x { + let bind: Vec = bind + .iter() + .map(|x| match x { Ast::Symbol(s) => s.to_string(), _ => panic!("Expected symbol! {:?}", x), - } - }).collect(); + }) + .collect(); let bind_name = match bind[0].as_str() { - "BuiltIn" => Decoration::BuiltIn(BuiltinDecoration::FragCoord), - "Location" => Decoration::Location(bind[1].parse::().unwrap()), + "BuiltIn" => { + Decoration::BuiltIn(BuiltinDecoration::FragCoord) + } + "Location" => Decoration::Location( + bind[1].parse::().unwrap(), + ), _ => panic!("Unknown bind! {:?}", bind), }; let mut exists = false; @@ -214,8 +224,12 @@ pub fn meta_compile(ast: &mut Ast) -> Module { Ast::Symbol(s) => s, _ => panic!("Expected symbol! {:?}", list[1]), }; - let name: String = name_and_type.split(":").collect::>()[0].to_string(); - let typ: String = name_and_type.split(":").collect::>()[1].to_string(); + let name: String = + name_and_type.split(":").collect::>()[0] + .to_string(); + let typ: String = + name_and_type.split(":").collect::>()[1] + .to_string(); let storage_class = match &list[2] { Ast::Symbol(s) => s, _ => panic!("Expected symbol! {:?}", list[2]), @@ -240,9 +254,12 @@ pub fn meta_compile(ast: &mut Ast) -> Module { }); } if exists { - module.globals.iter_mut() + module + .globals + .iter_mut() .find(|x| x.name.as_str() == name.as_str()) - .unwrap().storage_class = storage_class.clone(); + .unwrap() + .storage_class = storage_class.clone(); } } "entry" => { @@ -262,22 +279,21 @@ pub fn meta_compile(ast: &mut Ast) -> Module { Ast::List(l) => l, _ => panic!("Expected list! {:?}", list[4]), }; - let interface: Vec = interface.iter().map(|x| { - match x { - Ast::Symbol(s) => { - s.to_string().replace(":", "") - }, + let interface: Vec = interface + .iter() + .map(|x| match x { + Ast::Symbol(s) => s.to_string().replace(":", ""), _ => panic!("Expected symbol! {:?}", x), - } - }).collect(); + }) + .collect(); module.entry_points.push(EntryPoint { execution_model: ExecutionModel::Fragment, execution_mode: ExecutionMode::OriginUpperLeft, name: name.to_string(), interface: interface, }); - assert_eq!(exec_model, "Fragment"); - assert_eq!(exec_mode, "OriginUpperLeft"); + assert_eq!(**exec_model, "Fragment"); + assert_eq!(**exec_mode, "OriginUpperLeft"); } "fun" => { let name = match &list[1] { @@ -289,12 +305,23 @@ pub fn meta_compile(ast: &mut Ast) -> Module { _ => panic!("Expected symbol! {:?}", name), }; let body = list[2..].to_vec(); + let location = if let (Some(s), Some(e)) = ( + body.first().map(|a| a.location()).flatten(), + body.last().map(|a| a.location()).flatten(), + ) { + Location::range(s, e) + } else { + Location::Char { line: 0, col: 0 } + }; let fun = Function { name: name.to_string(), return_type: "void".to_string(), arguments: vec![], body: Some(vec![]), - ast: Some(Ast::List(body)), + ast: Some(Ast::List(Localised { + location, + item: body, + })), }; module.functions.push(fun); } @@ -308,7 +335,8 @@ pub fn meta_compile(ast: &mut Ast) -> Module { } } } - _ => panic!("Non-root ast") + _ => panic!("Non-root ast"), } module -} \ No newline at end of file +} + diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 33398bd..a7c7e13 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -1,4 +1,4 @@ -use crate::parser::{tokenize, parse}; +use crate::parser::{parse, tokenize}; #[test] fn test_compile() { @@ -18,9 +18,9 @@ fn test_compile() { 1.0 1.0))) "; - let mut ast = parse(tokenize(src)); + let ast = parse(tokenize(src)); println!("{:#?}", ast); - let module = meta_compile(&mut ast); + let module = meta_compile(&mut ast.unwrap()); println!("{:#?}", module); let test_module = Module { capabilities: vec![Capability::Shader], @@ -49,25 +49,36 @@ fn test_compile() { return_type: "void".to_string(), arguments: vec![], body: Some(vec![]), - ast: Some(Ast::List(vec![ - Ast::List(vec![ - Ast::Symbol("store-ptr".to_string()), - Ast::List(vec![Ast::Symbol("out-color".to_string())]), - Ast::List(vec![ - Ast::Symbol("v4f32i".to_string()), - Ast::List(vec![ - Ast::Symbol("/".to_string()), - Ast::List(vec![ - Ast::Symbol(".xy".to_string()), - Ast::List(vec![Ast::Symbol("load-ptr".to_string()), Ast::Symbol("frag-coord".to_string())]), - ]), - Ast::List(vec![Ast::Symbol("v2f32".to_string()), Ast::Symbol("1920.0".to_string()), Ast::Symbol("1080.0".to_string())]), - ]), - Ast::Symbol("1.0".to_string()), - Ast::Symbol("1.0".to_string()), - ]), + ast: Some(Ast::List(Localised::dummy_location(vec![Ast::List( + Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("store-ptr".to_string())), + Ast::List(Localised::dummy_location(vec![Ast::Symbol( + Localised::dummy_location("out-color".to_string()), + )])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("v4f32i".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("/".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location(".xy".to_string())), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("load-ptr".to_string())), + Ast::Symbol(Localised::dummy_location( + "frag-coord".to_string(), + )), + ])), + ])), + Ast::List(Localised::dummy_location(vec![ + Ast::Symbol(Localised::dummy_location("v2f32".to_string())), + Ast::Symbol(Localised::dummy_location("1920.0".to_string())), + Ast::Symbol(Localised::dummy_location("1080.0".to_string())), + ])), + ])), + Ast::Symbol(Localised::dummy_location("1.0".to_string())), + Ast::Symbol(Localised::dummy_location("1.0".to_string())), + ])), ]), - ])), + )]))), }], memory_model: Memory { addressing_model: AddressingModel::Logical, @@ -78,5 +89,8 @@ fn test_compile() { value: "GLSL.std.450".to_string(), }], }; + dbg!(&module); + dbg!(&test_module); assert_eq!(module, test_module); -} \ No newline at end of file +} + diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 427f1f6..f7b9a58 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2,11 +2,11 @@ mod tests; use std::iter::Peekable; -use std::ops::RangeInclusive; +use std::ops::{Deref, RangeInclusive}; use std::vec::IntoIter; #[derive(Clone, Debug)] -enum Location { +pub enum Location { Char { line: usize, col: usize, @@ -68,9 +68,9 @@ impl Location { } #[derive(Debug, Clone)] -struct Localised { - location: Location, - item: T, +pub struct Localised { + pub location: Location, + pub item: T, } impl Localised { @@ -83,6 +83,14 @@ impl Localised { } } +impl Deref for Localised { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.item + } +} + impl PartialEq for Localised { fn eq(&self, other: &Self) -> bool { self.item.eq(&other.item) @@ -103,6 +111,23 @@ pub enum Ast { Root(Vec), } +impl Ast { + pub fn symbol(self) -> Option { + match self { + Ast::Symbol(Localised { item, .. }) => Some(item), + _ => None, + } + } + + pub fn location(&self) -> Option<&Location> { + match self { + Ast::Symbol(Localised { location, .. }) => Some(location), + Ast::List(Localised { location, .. }) => Some(location), + Ast::Root(_) => None, + } + } +} + pub fn tokenize(input: &str) -> Vec> { let mut tokens = Vec::new(); // let mut chars = input.chars().peekable(); diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 88f38cf..07bde87 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -51,7 +51,6 @@ fn test_parse() { 1.0))) "; let ast = parse(tokenize(src)).unwrap(); - println!("{:?}", ast); let test_ast: Ast = Ast::Root(vec![ Ast::List(Localised::dummy_location(vec![ Ast::Symbol(Localised::dummy_location("module".to_string())), @@ -70,7 +69,7 @@ fn test_parse() { Localised::dummy_location("frag-coord:*v4f32i".to_string()), )])), Ast::List(Localised::dummy_location(vec![ - Ast::Symbol(Localised::dummy_location("Builtin".to_string())), + Ast::Symbol(Localised::dummy_location("BuiltIn".to_string())), Ast::Symbol(Localised::dummy_location("FragCoord".to_string())), ])), ])), @@ -115,7 +114,7 @@ fn test_parse() { Localised::dummy_location("out-color".to_string()), )])), Ast::List(Localised::dummy_location(vec![ - Ast::Symbol(Localised::dummy_location("v4f23i".to_string())), + Ast::Symbol(Localised::dummy_location("v4f32i".to_string())), Ast::List(Localised::dummy_location(vec![ Ast::Symbol(Localised::dummy_location("/".to_string())), Ast::List(Localised::dummy_location(vec![ @@ -137,5 +136,7 @@ fn test_parse() { ])), ])), ]); + println!("ast = {:#?}", ast); + println!("test_ast = {:#?}", test_ast); assert_eq!(ast, test_ast); } -- 2.39.1 From 1e1d0911e4b38d724a5f474635a3c1ff11c6b145 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 6 Mar 2025 12:38:23 +0100 Subject: [PATCH 28/43] Cargo fmt --- src/compiler/backend/mod.rs | 116 ++++++++++++++++++++++++++---------- src/compiler/mod.rs | 1 - src/compiler/tests.rs | 1 - 3 files changed, 85 insertions(+), 33 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index e88ceed..e896a23 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -2,8 +2,8 @@ mod tests; use crate::compiler::Module; -use std::fmt::Write; use std::fmt; +use std::fmt::Write; use super::StorageClass; @@ -19,12 +19,10 @@ pub enum Type { impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Type::Pointer(typ, storage_class) => { - match storage_class { - StorageClass::Input => write!(f, "*{}i", typ), - StorageClass::Output => write!(f, "*{}o", typ), - StorageClass::Undefined => panic!("Bound a non-declared variable"), - } + Type::Pointer(typ, storage_class) => match storage_class { + StorageClass::Input => write!(f, "*{}i", typ), + StorageClass::Output => write!(f, "*{}o", typ), + StorageClass::Undefined => panic!("Bound a non-declared variable"), }, Type::Vector(typ, size) => write!(f, "v{}{}", size, typ), Type::Float(size) => write!(f, "f{}", size), @@ -55,46 +53,61 @@ pub fn parse_type(typ: &String) -> Type { }; let typ = typ.chars().take(typ.len() - 1).collect::(); Type::Pointer(Box::new(parse_type(&typ)), storage_class) - }, + } 'v' => { let mut chars = typ.chars(); chars.next(); let size = chars.next().unwrap().to_digit(10).unwrap(); let typ = chars.collect::(); Type::Vector(Box::new(parse_type(&typ)), size) - }, + } 'f' => { let size = typ.chars().skip(1).collect::().parse().unwrap(); Type::Float(size) - }, + } 's' => { let size = typ.chars().skip(1).collect::().parse().unwrap(); Type::Int(size) - }, + } 'u' => { let size = typ.chars().skip(1).collect::().parse().unwrap(); Type::Unsigned(size) - }, + } _ => panic!("Invalid type"), } - } fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { match &typ { Type::Unsigned(size) => { - ops.push((format!("%u{}", size).to_string(), format!("OpTypeInt {}", size))); - }, + ops.push(( + format!("%u{}", size).to_string(), + format!("OpTypeInt {}", size), + )); + } Type::Int(size) => { - ops.push((format!("%s{}", size).to_string(), format!("OpTypeInt {}", size))); - }, + ops.push(( + format!("%s{}", size).to_string(), + format!("OpTypeInt {}", size), + )); + } Type::Float(size) => { - ops.push((format!("%f{}", size).to_string(), format!("OpTypeFloat {}", size))); - }, + ops.push(( + format!("%f{}", size).to_string(), + format!("OpTypeFloat {}", size), + )); + } Type::Vector(in_typ, size) => { emit_type(*in_typ.clone(), ops); - ops.push((fix_name(&typ.to_string()), format!("OpTypeVector {} {}", fix_name(&in_typ.clone().to_string()), size))) - }, + ops.push(( + fix_name(&typ.to_string()), + format!( + "OpTypeVector {} {}", + fix_name(&in_typ.clone().to_string()), + size + ), + )) + } Type::Pointer(in_typ, storage_class) => { emit_type(*in_typ.clone(), ops); let typ_id = fix_name(&typ.to_string()); @@ -103,8 +116,15 @@ fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { StorageClass::Output => "Output", StorageClass::Undefined => panic!("Bound a non-declared variable"), }; - ops.push((typ_id, format!("OpTypePointer {} {}", storage_class, fix_name(&in_typ.to_string())))) - }, + ops.push(( + typ_id, + format!( + "OpTypePointer {} {}", + storage_class, + fix_name(&in_typ.to_string()) + ), + )) + } } } @@ -125,7 +145,9 @@ pub fn spirv_meta(module: Module) -> String { let mut spirv_asm = String::new(); let mut ops: Vec<(Option, Vec)> = Vec::new(); - let capabilities: Vec = module.capabilities.iter() + let capabilities: Vec = module + .capabilities + .iter() .map(|c| format!("{:?}", c)) .collect(); for cap in capabilities { @@ -144,7 +166,14 @@ pub fn spirv_meta(module: Module) -> String { crate::compiler::MemoryModel::OpenCL => "OpenCL", _ => todo!(), }; - ops.push((None, vec!["OpMemoryModel".to_string(), memory_model_address.to_string(), memory_model_model.to_string()])); + ops.push(( + None, + vec![ + "OpMemoryModel".to_string(), + memory_model_address.to_string(), + memory_model_model.to_string(), + ], + )); for entry in module.entry_points { let exec_model = match entry.execution_model { @@ -152,14 +181,32 @@ pub fn spirv_meta(module: Module) -> String { crate::compiler::ExecutionModel::Vertex => "Vertex", }; let name = entry.name; - let interface: Vec = entry.interface.iter() + let interface: Vec = entry + .interface + .iter() .map(|i| fix_name(&i.to_string())) .collect(); let exec_mode = match entry.execution_mode { crate::compiler::ExecutionMode::OriginUpperLeft => "OriginUpperLeft", }; - ops.push((None, vec!["OpEntryPoint".to_string(), exec_model.to_string(), fix_name(&name), format!("\"{}\"", name), interface.join(" ")])); - ops.push((None, vec!["OpExecutionMode".to_string(), fix_name(&name), exec_mode.to_string()])); + ops.push(( + None, + vec![ + "OpEntryPoint".to_string(), + exec_model.to_string(), + fix_name(&name), + format!("\"{}\"", name), + interface.join(" "), + ], + )); + ops.push(( + None, + vec![ + "OpExecutionMode".to_string(), + fix_name(&name), + exec_mode.to_string(), + ], + )); } for global in module.globals { @@ -178,7 +225,14 @@ pub fn spirv_meta(module: Module) -> String { } ops.push((Some(op.0), vec![op.1])); } - ops.push((Some(name.clone()), vec!["OpVariable".to_string(), fix_name(&_typ), storage_class.to_string()])); + ops.push(( + Some(name.clone()), + vec![ + "OpVariable".to_string(), + fix_name(&_typ), + storage_class.to_string(), + ], + )); for dec in global.decorations { // Decorations have the format Location 0, or Builtin FragCoord let dec = match dec { @@ -188,7 +242,7 @@ pub fn spirv_meta(module: Module) -> String { crate::compiler::BuiltinDecoration::FragCoord => "FragCoord", }; format!("BuiltIn {}", builtin) - }, + } }; ops.push((None, vec!["OpDecorate".to_string(), name.clone(), dec])); } @@ -204,4 +258,4 @@ pub fn spirv_meta(module: Module) -> String { writeln!(spirv_asm).unwrap(); } spirv_asm -} \ No newline at end of file +} diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 8635d0c..17abca9 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -339,4 +339,3 @@ pub fn meta_compile(ast: &mut Ast) -> Module { } module } - diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index a7c7e13..9cbc04c 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -93,4 +93,3 @@ fn test_compile() { dbg!(&test_module); assert_eq!(module, test_module); } - -- 2.39.1 From 9aafb9758f289145e8ca30dfa418615a1242a064 Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 6 Mar 2025 20:10:11 +0100 Subject: [PATCH 29/43] Refactor and error handling for compiler --- src/compiler/backend/tests.rs | 2 +- src/compiler/meta_compile.rs | 206 ++++++++++++++++++++++ src/compiler/mod.rs | 315 ++++++++++++---------------------- src/compiler/tests.rs | 202 +++++++++++++++++++++- src/parser/mod.rs | 37 +++- 5 files changed, 548 insertions(+), 214 deletions(-) create mode 100644 src/compiler/meta_compile.rs diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 4252995..7319154 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -19,7 +19,7 @@ fn test_emit() { 1.0))) "; let ast = parse(tokenize(src)); - let module = meta_compile(&mut ast.unwrap()); + let module = meta_compile(ast.unwrap()).unwrap(); let res = spirv_meta(module); println!("{}", res); } diff --git a/src/compiler/meta_compile.rs b/src/compiler/meta_compile.rs new file mode 100644 index 0000000..f88f500 --- /dev/null +++ b/src/compiler/meta_compile.rs @@ -0,0 +1,206 @@ +use crate::{ + compiler::{ + AddressingModel, Capability, EntryPoint, ExecutionMode, ExecutionModel, Memory, + MemoryModel, expect_list, expect_one_of, expect_symbol, + }, + parser::{Ast, Localised, Location}, +}; + +use super::{ + BuiltinDecoration, CompilerError, Decoration, Function, GlobalVariable, Import, Module, + StorageClass, expect_empty, expect_empty_ast, +}; + +pub fn compile_module>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let _cap = expect_one_of(&["Shader"], expect_symbol(list.next(), &loc)?)?; + let _exec = expect_one_of(&["Logical"], expect_symbol(list.next(), &loc)?)?; + let _memory = expect_one_of(&["GLSL450"], expect_symbol(list.next(), &loc)?)?; + module.memory_model = Memory { + addressing_model: AddressingModel::Logical, + memory_model: MemoryModel::GLSL450, + }; + module.capabilities.push(Capability::Shader); + expect_empty_ast(list) +} + +pub fn compile_import>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let name = expect_symbol(list.next(), &loc)?.into_inner(); + let value = expect_symbol(list.next(), &loc)?.into_inner(); + module.imports.push(Import { name, value }); + expect_empty_ast(list) +} + +pub fn compile_bind>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let Localised { + location: name_and_type_loc, + item: name_and_type, + } = expect_list(list.next(), &loc)?; + let name_and_type = name_and_type + .into_iter() + .map(|x| expect_symbol(Some(x), &name_and_type_loc).map(Localised::into_inner)) + .collect::>()?; + let mut name_and_type = name_and_type.split(':').map(|s| s.to_string()); + // name_and_type is of the name:type format, like foo:f32 + let name: String = name_and_type + .next() + .ok_or(CompilerError::ExpectedSymbol(name_and_type_loc.clone()))?; + let typ: String = name_and_type + .next() + .ok_or(CompilerError::ExpectedType(name_and_type_loc.tail()))?; + expect_empty(name_and_type, name_and_type_loc)?; + let Localised { + location: bind_loc, + item: bind, + } = expect_list(list.next(), &loc)?; + let bind: Vec = bind + .into_iter() + .map(|x| expect_symbol(Some(x), &bind_loc).map(Localised::into_inner)) + .collect::>()?; + let bind_name = bind + .get(0) + .ok_or(CompilerError::ExpectedSymbol(bind_loc.clone()))?; + + let bind_name = match bind_name.as_str() { + "BuiltIn" => Decoration::BuiltIn(BuiltinDecoration::FragCoord), + "Location" => Decoration::Location(bind.get(1).unwrap().parse::().unwrap()), + b => return Err(CompilerError::UnknownBind(b.to_string(), bind_loc)), + }; + let mut exists = false; + for var in &module.globals { + if var.name == name { + exists = true; + } + } + if !exists { + module.globals.push(GlobalVariable { + name, + typ, + storage_class: StorageClass::Undefined, + decorations: vec![bind_name], + }); + } + expect_empty_ast(list) +} + +pub fn compile_dec>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let Localised { + location: name_and_type_loc, + item: name_and_type, + } = expect_symbol(list.next(), &loc)?; + let mut name_and_type = name_and_type.split(':').map(|s| s.to_string()); + let name: String = name_and_type + .next() + .ok_or(CompilerError::ExpectedSymbol(name_and_type_loc.clone()))?; + let typ: String = name_and_type + .next() + .ok_or(CompilerError::ExpectedType(name_and_type_loc.tail()))?; + expect_empty(name_and_type, name_and_type_loc)?; + + let storage_class = expect_symbol(list.next(), &loc)?; + let mut exists = false; + for var in &module.globals { + if var.name.as_str() == name.as_str() { + exists = true; + } + } + let storage_class = match storage_class.as_str() { + "Input" => StorageClass::Input, + "Output" => StorageClass::Output, + _ => StorageClass::Undefined, + }; + if !exists { + module.globals.push(GlobalVariable { + name: name.to_string(), + typ: typ.to_string(), + storage_class: storage_class.clone(), + decorations: vec![], + }); + } + if exists { + module + .globals + .iter_mut() + .find(|x| x.name.as_str() == name.as_str()) + .unwrap() + .storage_class = storage_class.clone(); + } + expect_empty_ast(list) +} + +pub fn compile_entry>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let name = expect_symbol(list.next(), &loc)?; + let _exec_model = expect_one_of(&["Fragment"], expect_symbol(list.next(), &loc)?)?; + let _exec_mode = expect_one_of(&["OriginUpperLeft"], expect_symbol(list.next(), &loc)?)?; + let Localised { + location: interface_loc, + item: interface, + } = expect_list(list.next(), &loc)?; + let interface: Vec = interface + .into_iter() + .map(|x| expect_symbol(Some(x), &interface_loc).map(|s| s.into_inner().replace(":", ""))) + .collect::>()?; + module.entry_points.push(EntryPoint { + execution_model: ExecutionModel::Fragment, + execution_mode: ExecutionMode::OriginUpperLeft, + name: name.to_string(), + interface, + }); + + expect_empty_ast(list) +} + +pub fn compile_fun>( + module: &mut Module, + mut list: I, + loc: Location, +) -> Result<(), CompilerError> { + let Localised { + location: name_loc, + item: name, + } = expect_list(list.next(), &loc)?; + let mut name_it = name.into_iter(); + let name = expect_symbol(name_it.next(), &name_loc)?; + expect_empty(name_it, name_loc)?; + let body = list.collect::>(); + let location = if let (Some(s), Some(e)) = ( + body.first().map(|a| a.location()), + body.last().map(|a| a.location()), + ) { + Location::range(s, e) + } else { + Location::Char { line: 0, col: 0 } + }; + let fun = Function { + name: name.to_string(), + return_type: "void".to_string(), + arguments: vec![], + body: Some(vec![]), + ast: Some(Ast::List(Localised { + location, + item: body, + })), + }; + module.functions.push(fun); + + Ok(()) +} diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 17abca9..0238826 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,6 +1,11 @@ +use meta_compile::{ + compile_bind, compile_dec, compile_entry, compile_fun, compile_import, compile_module, +}; + use crate::parser::{Ast, Localised, Location}; pub mod backend; +mod meta_compile; use std::fmt; @@ -127,215 +132,111 @@ pub struct Module { pub imports: Vec, } -pub fn meta_compile(ast: &mut Ast) -> Module { - let mut module: Module = Module::default(); +#[derive(Debug)] +pub enum CompilerError { + ExpectedSymbol(Location), + ExpectedList(Location), + UnknownKeyword(String, Location), + ExpectedOneOf(Vec, String, Location), + TrailingTokens(Location), + UnknownBind(String, Location), + ExpectedType(Location), +} + +fn expect_symbol(ast: Option, loc: &Location) -> Result, CompilerError> { + match ast { + Some(Ast::Symbol(s)) => Ok(s), + Some(l) => Err(CompilerError::ExpectedSymbol(l.location().clone())), + None => Err(CompilerError::ExpectedSymbol(loc.tail())), + } +} + +fn expect_list(ast: Option, loc: &Location) -> Result>, CompilerError> { match ast { - Ast::Root(root) => { - for node in root { - match node { - Ast::List(list) => { - let keyword = list[0].clone(); - match keyword { - Ast::Symbol(sym) => { - match sym.as_str() { - "module" => { - let cap = list[1].clone(); - let exec = list[2].clone(); - let memory = list[3].clone(); - module.memory_model = Memory { - addressing_model: AddressingModel::Logical, - memory_model: MemoryModel::GLSL450, - }; - module.capabilities.push(Capability::Shader); - assert_eq!(exec.symbol(), Some("Logical".to_string())); - assert_eq!(memory.symbol(), Some("GLSL450".to_string())); - assert_eq!(cap.symbol(), Some("Shader".to_string())); - } - "import" => { - let name = match &list[1] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[1]), - }; - let value = match &list[2] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[2]), - }; - module.imports.push(Import { - name: name.to_string(), - value: value.to_string(), - }); - } - "bind" => { - let name_and_type = match &list[1] { - Ast::List(l) => l, - _ => panic!("Expected list! {:?}", list[1]), - }; - let name_and_type: String = name_and_type - .iter() - .map(|x| match x { - Ast::Symbol(s) => s.to_string(), - _ => panic!("Expected symbol! {:?}", x), - }) - .collect(); - // name_and_type is of the name:type format, like foo:f32 - let name: String = - name_and_type.split(":").collect::>()[0] - .to_string(); - let typ: String = - name_and_type.split(":").collect::>()[1] - .to_string(); - let bind = match &list[2] { - Ast::List(l) => l, - _ => panic!("Expected list! {:?}", list[2]), - }; - let bind: Vec = bind - .iter() - .map(|x| match x { - Ast::Symbol(s) => s.to_string(), - _ => panic!("Expected symbol! {:?}", x), - }) - .collect(); - let bind_name = match bind[0].as_str() { - "BuiltIn" => { - Decoration::BuiltIn(BuiltinDecoration::FragCoord) - } - "Location" => Decoration::Location( - bind[1].parse::().unwrap(), - ), - _ => panic!("Unknown bind! {:?}", bind), - }; - let mut exists = false; - for var in &module.globals { - if var.name == name { - exists = true; - } - } - if !exists { - module.globals.push(GlobalVariable { - name: name, - typ: typ, - storage_class: StorageClass::Undefined, - decorations: vec![bind_name], - }); - } - } - "dec" => { - let name_and_type = match &list[1] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[1]), - }; - let name: String = - name_and_type.split(":").collect::>()[0] - .to_string(); - let typ: String = - name_and_type.split(":").collect::>()[1] - .to_string(); - let storage_class = match &list[2] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[2]), - }; - let mut exists = false; - for var in &module.globals { - if var.name.as_str() == name.as_str() { - exists = true; - } - } - let storage_class = match storage_class.as_str() { - "Input" => StorageClass::Input, - "Output" => StorageClass::Output, - _ => StorageClass::Undefined, - }; - if !exists { - module.globals.push(GlobalVariable { - name: name.to_string(), - typ: typ.to_string(), - storage_class: storage_class.clone(), - decorations: vec![], - }); - } - if exists { - module - .globals - .iter_mut() - .find(|x| x.name.as_str() == name.as_str()) - .unwrap() - .storage_class = storage_class.clone(); - } - } - "entry" => { - let name = match &list[1] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[1]), - }; - let exec_model = match &list[2] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[2]), - }; - let exec_mode = match &list[3] { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", list[3]), - }; - let interface = match &list[4] { - Ast::List(l) => l, - _ => panic!("Expected list! {:?}", list[4]), - }; - let interface: Vec = interface - .iter() - .map(|x| match x { - Ast::Symbol(s) => s.to_string().replace(":", ""), - _ => panic!("Expected symbol! {:?}", x), - }) - .collect(); - module.entry_points.push(EntryPoint { - execution_model: ExecutionModel::Fragment, - execution_mode: ExecutionMode::OriginUpperLeft, - name: name.to_string(), - interface: interface, - }); - assert_eq!(**exec_model, "Fragment"); - assert_eq!(**exec_mode, "OriginUpperLeft"); - } - "fun" => { - let name = match &list[1] { - Ast::List(l) => l[0].clone(), - _ => panic!("Expected list! {:?}", list[1]), - }; - let name = match name { - Ast::Symbol(s) => s, - _ => panic!("Expected symbol! {:?}", name), - }; - let body = list[2..].to_vec(); - let location = if let (Some(s), Some(e)) = ( - body.first().map(|a| a.location()).flatten(), - body.last().map(|a| a.location()).flatten(), - ) { - Location::range(s, e) - } else { - Location::Char { line: 0, col: 0 } - }; - let fun = Function { - name: name.to_string(), - return_type: "void".to_string(), - arguments: vec![], - body: Some(vec![]), - ast: Some(Ast::List(Localised { - location, - item: body, - })), - }; - module.functions.push(fun); - } - _ => panic!("Unknown keyword! {:?}", sym), - } - } - _ => panic!("List where a keyword was expected! {:?}", keyword), - } - } - _ => panic!("Top-level symbol! {:?}", node), + Some(Ast::List(l)) => Ok(l), + Some(l) => Err(CompilerError::ExpectedList(l.location().clone())), + None => Err(CompilerError::ExpectedList(loc.tail())), + } +} + +fn expect_one_of( + options: &[T], + Localised { location, item }: Localised, +) -> Result, CompilerError> +where + T: PartialEq + Into + Clone, +{ + if options.iter().find(|e| **e == item).is_some() { + Ok(Localised { location, item }) + } else { + Err(CompilerError::ExpectedOneOf( + options + .iter() + .map(|t| Into::::into(t.clone())) + .collect(), + item, + location, + )) + } +} + +fn expect_empty_ast>(mut iter: I) -> Result<(), CompilerError> { + match iter.next() { + Some(a) => Err(CompilerError::TrailingTokens(a.location().clone())), + None => Ok(()), + } +} + +fn expect_empty>( + mut iter: I, + location: Location, +) -> Result<(), CompilerError> { + match iter.next() { + Some(_) => Err(CompilerError::TrailingTokens(location)), + None => Ok(()), + } +} + +pub fn meta_compile(ast: Ast) -> Result { + let mut module: Module = Module::default(); + let Ast::Root(root) = ast else { + panic!("Non-root ast"); // compiler error, fine to panic + }; + for node in root { + let Localised { + location, + item: list, + } = expect_list(Some(node), &Location::All)?; + let mut iter = list.into_iter(); + + match iter.next() { + Some(Ast::Symbol(sym)) => match sym.as_str() { + "module" => { + compile_module(&mut module, iter, location)?; + } + "import" => { + compile_import(&mut module, iter, location)?; + } + "bind" => { + compile_bind(&mut module, iter, location)?; + } + "dec" => { + compile_dec(&mut module, iter, location)?; + } + "entry" => { + compile_entry(&mut module, iter, location)?; + } + "fun" => { + compile_fun(&mut module, iter, location)?; + } + _ => { + let Localised { location, item } = sym; + return Err(CompilerError::UnknownKeyword(item, location)); } - } + }, + Some(l) => return Err(CompilerError::ExpectedSymbol(l.location().clone())), + None => return Err(CompilerError::ExpectedSymbol(location)), } - _ => panic!("Non-root ast"), } - module + Ok(module) } diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 9cbc04c..2a3dcee 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -1,4 +1,7 @@ -use crate::parser::{parse, tokenize}; +use crate::{ + compiler::{CompilerError, meta_compile}, + parser::{Location, parse, tokenize}, +}; #[test] fn test_compile() { @@ -20,7 +23,7 @@ fn test_compile() { "; let ast = parse(tokenize(src)); println!("{:#?}", ast); - let module = meta_compile(&mut ast.unwrap()); + let module = meta_compile(ast.unwrap()).unwrap(); println!("{:#?}", module); let test_module = Module { capabilities: vec![Capability::Shader], @@ -93,3 +96,198 @@ fn test_compile() { dbg!(&test_module); assert_eq!(module, test_module); } + +#[test] +fn expected_symbol() { + let src = " +(module Shader Logical ) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + assert!(matches!( + module, + Err(CompilerError::ExpectedSymbol(Location::Char { + line: 2, + col: 24 + })) + )) +} + +#[test] +fn expected_list() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun main + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + let Err(CompilerError::ExpectedList(Location::String { lines, cols })) = module else { + panic!() + }; + assert_eq!(lines, 9..=9); + assert_eq!(cols, 6..=9); +} + +#[test] +fn unknown_keyword() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fum (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + let Err(CompilerError::UnknownKeyword(kw, Location::String { lines, cols })) = module else { + panic!() + }; + assert_eq!(kw, "fum".to_string()); + assert_eq!(lines, 9..=9); + assert_eq!(cols, 2..=4); +} + +#[test] +fn expected_one_of() { + let src = " +(module Shader Loical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + let Err(CompilerError::ExpectedOneOf(l, kw, Location::String { lines, cols })) = module else { + panic!() + }; + assert_eq!(l, vec!["Logical".to_string()]); + assert_eq!(kw, "Loical".to_string()); + assert_eq!(lines, 2..=2); + assert_eq!(cols, 16..=21); +} + +#[test] +fn trailing_tokens() { + let src = " +(module Shader Logical GLSL450 GLSL451) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + let Err(CompilerError::TrailingTokens(Location::String { lines, cols })) = module else { + panic!() + }; + assert_eq!(lines, 2..=2); + assert_eq!(cols, 32..=38); +} + +#[test] +fn unknown_bind() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord:*v4f32i) (BuiltIm FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + dbg!(&module); + let Err(CompilerError::UnknownBind(b, Location::String { lines, cols })) = module else { + panic!() + }; + assert_eq!(b, "BuiltIm"); + assert_eq!(lines, 4..=4); + assert_eq!(cols, 28..=46); +} + +#[test] +fn expected_type() { + let src = " +(module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (frag-coord) (BuiltIn FragCoord)) +(bind (out-color:*v4f32o) (Location 0)) +(dec frag-coord:*v4f32i Input) +(dec out-color:*v4f32o Output) +(entry main Fragment OriginUpperLeft (:frag-coord :out-color)) +(fun (main) + (store-ptr (out-color) + (v4f32i (/ (.xy (load-ptr frag-coord)) + (v2f32 1920.0 1080.0)) + 1.0 + 1.0))) +"; + let ast = parse(tokenize(src)); + println!("{:#?}", ast); + let module = meta_compile(ast.unwrap()); + dbg!(&module); + let Err(CompilerError::ExpectedType(Location::Char { line: 4, col: 18 })) = module else { + panic!() + }; +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f7b9a58..550f0ad 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3,6 +3,7 @@ mod tests; use std::iter::Peekable; use std::ops::{Deref, RangeInclusive}; +use std::usize; use std::vec::IntoIter; #[derive(Clone, Debug)] @@ -15,6 +16,7 @@ pub enum Location { lines: RangeInclusive, cols: RangeInclusive, }, + All, } #[derive(Debug)] @@ -42,6 +44,7 @@ impl Location { match self { Location::Char { line, .. } => *line, Location::String { lines, .. } => *lines.start(), + Location::All => 0, } } @@ -49,6 +52,7 @@ impl Location { match self { Location::Char { col, .. } => *col, Location::String { cols, .. } => *cols.start(), + Location::All => 0, } } @@ -56,6 +60,7 @@ impl Location { match self { Location::Char { line, .. } => *line, Location::String { lines, .. } => *lines.end(), + Location::All => usize::MAX, } } @@ -63,6 +68,24 @@ impl Location { match self { Location::Char { col, .. } => *col, Location::String { cols, .. } => *cols.end(), + Location::All => usize::MAX, + } + } + + pub fn tail(&self) -> Self { + match self { + Location::Char { line, col } => Location::Char { + line: *line, + col: *col, + }, + Location::String { lines, cols } => Location::Char { + line: *lines.end(), + col: *cols.end(), + }, + Location::All => Location::Char { + line: usize::MAX, + col: usize::MAX, + }, } } } @@ -73,6 +96,12 @@ pub struct Localised { pub item: T, } +impl Localised { + pub fn into_inner(self) -> T { + self.item + } +} + impl Localised { #[cfg(test)] pub fn dummy_location(item: T) -> Self { @@ -119,11 +148,11 @@ impl Ast { } } - pub fn location(&self) -> Option<&Location> { + pub fn location(&self) -> &Location { match self { - Ast::Symbol(Localised { location, .. }) => Some(location), - Ast::List(Localised { location, .. }) => Some(location), - Ast::Root(_) => None, + Ast::Symbol(Localised { location, .. }) => location, + Ast::List(Localised { location, .. }) => location, + Ast::Root(_) => &Location::All, } } } -- 2.39.1 From 2c716e7b7d6ab82c82bbdb722e242baf409c9e05 Mon Sep 17 00:00:00 2001 From: itycodes Date: Fri, 7 Mar 2025 11:23:44 +0100 Subject: [PATCH 30/43] Added an explicit :void type param to `fn` declarations Currently only :void is allowed. We still need to emit the OpTypeVoid. --- src/compiler/backend/tests.rs | 2 +- src/compiler/meta_compile.rs | 4 +++- src/compiler/tests.rs | 4 ++-- src/parser/tests.rs | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 7319154..4a90b07 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -11,7 +11,7 @@ fn test_emit() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) +(fun (main) :void (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) diff --git a/src/compiler/meta_compile.rs b/src/compiler/meta_compile.rs index f88f500..7e8f937 100644 --- a/src/compiler/meta_compile.rs +++ b/src/compiler/meta_compile.rs @@ -181,6 +181,8 @@ pub fn compile_fun>( let mut name_it = name.into_iter(); let name = expect_symbol(name_it.next(), &name_loc)?; expect_empty(name_it, name_loc)?; + let _return_type = expect_one_of(&[":void"], expect_symbol(list.next(), &loc)?)?; + let return_type = _return_type.into_inner().replace(":", ""); let body = list.collect::>(); let location = if let (Some(s), Some(e)) = ( body.first().map(|a| a.location()), @@ -192,7 +194,7 @@ pub fn compile_fun>( }; let fun = Function { name: name.to_string(), - return_type: "void".to_string(), + return_type: return_type, arguments: vec![], body: Some(vec![]), ast: Some(Ast::List(Localised { diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 2a3dcee..cb47ba9 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -14,7 +14,7 @@ fn test_compile() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) +(fun (main) :void (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) @@ -107,7 +107,7 @@ fn expected_symbol() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) +(fun (main) :void (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 07bde87..2dff458 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -43,7 +43,7 @@ fn test_parse() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) +(fun (main) :void (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) @@ -108,6 +108,7 @@ fn test_parse() { Ast::List(Localised::dummy_location(vec![Ast::Symbol( Localised::dummy_location("main".to_string()), )])), + Ast::Symbol(Localised::dummy_location(":void".to_string())), Ast::List(Localised::dummy_location(vec![ Ast::Symbol(Localised::dummy_location("store-ptr".to_string())), Ast::List(Localised::dummy_location(vec![Ast::Symbol( -- 2.39.1 From 43b12de5080e43811e1c23523acbeb370f6edc53 Mon Sep 17 00:00:00 2001 From: itycodes Date: Fri, 7 Mar 2025 11:36:07 +0100 Subject: [PATCH 31/43] Replace the :void type expr with :<> `void` collides with the simplistic type parser, which expects single char types. `()` collides with the S-expression parser. Specifically, it hits the `v` for vector case. This could be fixed by rewriting the type parser, but I like to keep the whole compiler as simple as possible for now. --- src/compiler/backend/mod.rs | 35 ++++++++++++++++++++++++++++++++++- src/compiler/backend/tests.rs | 2 +- src/compiler/meta_compile.rs | 2 +- src/compiler/tests.rs | 6 +++--- src/parser/tests.rs | 4 ++-- 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index e896a23..a53e767 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -14,11 +14,13 @@ pub enum Type { Float(u32), Int(u32), Unsigned(u32), + Void, } impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { + Type::Void => write!(f, "<>"), Type::Pointer(typ, storage_class) => match storage_class { StorageClass::Input => write!(f, "*{}i", typ), StorageClass::Output => write!(f, "*{}o", typ), @@ -42,6 +44,10 @@ pub fn parse_type(typ: &String) -> Type { let c = typ.chars().next().unwrap(); match c { + '<' => { + assert_eq!(typ.as_str(), "<>"); + Type::Void + } '*' => { let mut chars = typ.chars(); chars.next(); @@ -79,6 +85,9 @@ pub fn parse_type(typ: &String) -> Type { fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { match &typ { + Type::Void => { + ops.push(("%void".to_string(), "OpTypeVoid".to_string())); + } Type::Unsigned(size) => { ops.push(( format!("%u{}", size).to_string(), @@ -129,7 +138,13 @@ fn emit_type(typ: Type, ops: &mut Vec<(String, String)>) { } fn fix_name(name: &String) -> String { - format!("%{}", name.clone().replace("-", "_").replace("*", "p")) + format!( + "%{}", + name.clone() + .replace("-", "_") + .replace("*", "p") + .replace("<>", "void") + ) } fn has_id(name: String, ops: &Vec<(Option, Vec)>) -> bool { @@ -248,6 +263,24 @@ pub fn spirv_meta(module: Module) -> String { } } + for fun in module.functions { + let name = fix_name(&fun.name); + let return_type = fix_name(&fun.return_type); + let mut type_ops = Vec::new(); + emit_type(parse_type(&fun.return_type), &mut type_ops); + for op in type_ops { + if has_id(op.0.clone(), &ops) { + continue; + } + ops.push((Some(op.0), vec![op.1])); + } + // Push OpFunctionType + ops.push(( + Some(name.clone()), + vec!["OpTypeFunction".to_string(), return_type.clone()], + )); + } + for op in ops { if op.0.is_some() { write!(spirv_asm, "{} = ", op.0.unwrap()).unwrap(); diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 4a90b07..2f0cce5 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -11,7 +11,7 @@ fn test_emit() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) :void +(fun (main) :<> (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) diff --git a/src/compiler/meta_compile.rs b/src/compiler/meta_compile.rs index 7e8f937..d305239 100644 --- a/src/compiler/meta_compile.rs +++ b/src/compiler/meta_compile.rs @@ -181,7 +181,7 @@ pub fn compile_fun>( let mut name_it = name.into_iter(); let name = expect_symbol(name_it.next(), &name_loc)?; expect_empty(name_it, name_loc)?; - let _return_type = expect_one_of(&[":void"], expect_symbol(list.next(), &loc)?)?; + let _return_type = expect_one_of(&[":<>"], expect_symbol(list.next(), &loc)?)?; let return_type = _return_type.into_inner().replace(":", ""); let body = list.collect::>(); let location = if let (Some(s), Some(e)) = ( diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index cb47ba9..8064ea8 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -14,7 +14,7 @@ fn test_compile() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) :void +(fun (main) :<> (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) @@ -49,7 +49,7 @@ fn test_compile() { ], functions: vec![Function { name: "main".to_string(), - return_type: "void".to_string(), + return_type: "<>".to_string(), arguments: vec![], body: Some(vec![]), ast: Some(Ast::List(Localised::dummy_location(vec![Ast::List( @@ -107,7 +107,7 @@ fn expected_symbol() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) :void +(fun (main) :<> (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 2dff458..8e94147 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -43,7 +43,7 @@ fn test_parse() { (dec frag-coord:*v4f32i Input) (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) -(fun (main) :void +(fun (main) :<> (store-ptr (out-color) (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) @@ -108,7 +108,7 @@ fn test_parse() { Ast::List(Localised::dummy_location(vec![Ast::Symbol( Localised::dummy_location("main".to_string()), )])), - Ast::Symbol(Localised::dummy_location(":void".to_string())), + Ast::Symbol(Localised::dummy_location(":<>".to_string())), Ast::List(Localised::dummy_location(vec![ Ast::Symbol(Localised::dummy_location("store-ptr".to_string())), Ast::List(Localised::dummy_location(vec![Ast::Symbol( -- 2.39.1 From c6da0d49021d29ca1bdbb657bcae1348dd8f908a Mon Sep 17 00:00:00 2001 From: itycodes Date: Fri, 7 Mar 2025 11:42:28 +0100 Subject: [PATCH 32/43] An initial README file :) --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1b6dcf --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +A simplistic low-level compiler for a high-level shader bytecode. + +##### Compiler backend, parser, bad prototype Rust: +*ity* + +##### Compiler frontend (error reporting, validation...), fixing & cleaning up ity's bad Rust: +*Avery* -- 2.39.1 From d8d4f0ee8900165a2bb93107ff5883e3006a0947 Mon Sep 17 00:00:00 2001 From: itycodes Date: Mon, 10 Mar 2025 06:36:48 +0100 Subject: [PATCH 33/43] Add an Ast::list function analogue to Ast::symbol --- src/parser/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 550f0ad..d175e81 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -148,6 +148,13 @@ impl Ast { } } + pub fn list(self) -> Option> { + match self { + Ast::List(Localised { item, .. }) => Some(item), + _ => None, + } + } + pub fn location(&self) -> &Location { match self { Ast::Symbol(Localised { location, .. }) => location, -- 2.39.1 From 828d0aa567098cd32c62ed74ee034da4be4d8297 Mon Sep 17 00:00:00 2001 From: itycodes Date: Mon, 10 Mar 2025 06:38:55 +0100 Subject: [PATCH 34/43] Modify the language's store-ptr syntax to make it match the compiler store-ptr now takes a symbol instead of a list containing a symbol to make the compiler easier to write. --- src/compiler/backend/tests.rs | 57 ++++++++++++++++++++++++++++++++++- src/parser/tests.rs | 6 ++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 2f0cce5..62ff63e 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -12,7 +12,7 @@ fn test_emit() { (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) (fun (main) :<> - (store-ptr (out-color) + (store-ptr out-color (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) 1.0 @@ -49,3 +49,58 @@ fn test_type_parse() { assert_eq!(parse_type(&"f32".to_string()), Float(32)); assert_eq!(parse_type(&"s32".to_string()), Int(32)); } + +#[test] +fn test_block_ssa() { + use crate::compiler::backend::*; + use crate::compiler::*; + use crate::parser::*; + let src = " + (module Shader Logical GLSL450) +(import :std GLSL.std.450) +(bind (out-color:*f32) (Location 0)) +(dec out-color:*f32 Output) +(entry main Fragment OriginUpperLeft (:out-color)) +(fun (main) :<> + (store-ptr out-color 1.0)) +"; + let ast = parse(tokenize(src)); + let mut module = meta_compile(ast.unwrap()).unwrap(); + let fun = module.functions.pop().unwrap(); + let block = fun.ast.unwrap(); + let block = block + .list() + .unwrap() + .get(0) + .unwrap() + .clone(); + println!("{:#?}", block); + let mut vars = vec![]; + let mut constants = vec![]; + let mut counter = Box::new(0); + let mut stack = vec![]; + let mut out = vec![]; + for v in &module.globals { + vars.push((v.name.clone(), v.typ.clone())); + } + compile_ast_ssa( + block, + &mut vars, + &mut constants, + &mut counter, + &mut stack, + &mut out, + ); + println!("\n---constants:---\n{:#?}\n------", constants); + println!("\n---vars:---\n{:#?}\n------", vars); + println!("\n---stack:---\n{:#?}\n------", stack); + println!("\n---counter---\n{}\n------", counter); + println!("\n---out---"); + for (a, b) in &out { + match a { + Some(a) => println!("%{} = {}", a, b), + None => println!("{}", b), + } + } + println!("------"); +} diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 8e94147..4a96b68 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -44,7 +44,7 @@ fn test_parse() { (dec out-color:*v4f32o Output) (entry main Fragment OriginUpperLeft (:frag-coord :out-color)) (fun (main) :<> - (store-ptr (out-color) + (store-ptr out-color (v4f32i (/ (.xy (load-ptr frag-coord)) (v2f32 1920.0 1080.0)) 1.0 @@ -111,9 +111,7 @@ fn test_parse() { Ast::Symbol(Localised::dummy_location(":<>".to_string())), Ast::List(Localised::dummy_location(vec![ Ast::Symbol(Localised::dummy_location("store-ptr".to_string())), - Ast::List(Localised::dummy_location(vec![Ast::Symbol( - Localised::dummy_location("out-color".to_string()), - )])), + Ast::Symbol(Localised::dummy_location("out-color".to_string())), Ast::List(Localised::dummy_location(vec![ Ast::Symbol(Localised::dummy_location("v4f32i".to_string())), Ast::List(Localised::dummy_location(vec![ -- 2.39.1 From 27d0e02b86980a1067d1d0688ec3d222f19a2741 Mon Sep 17 00:00:00 2001 From: itycodes Date: Mon, 10 Mar 2025 06:41:04 +0100 Subject: [PATCH 35/43] Forgotten cargo fmt --- src/compiler/mod.rs | 3 ++- src/compiler/tests.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 0238826..4bf12ee 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -7,7 +7,7 @@ use crate::parser::{Ast, Localised, Location}; pub mod backend; mod meta_compile; -use std::fmt; +use std::{fmt, ops::Add, ptr}; #[cfg(test)] mod tests; @@ -240,3 +240,4 @@ pub fn meta_compile(ast: Ast) -> Result { } Ok(module) } + diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 8064ea8..7a835a1 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -291,3 +291,4 @@ fn expected_type() { panic!() }; } + -- 2.39.1 From e33e43e221f4baff583c9a8ed4aeccfd5d24550b Mon Sep 17 00:00:00 2001 From: itycodes Date: Mon, 10 Mar 2025 06:41:21 +0100 Subject: [PATCH 36/43] Initial work on function compilation So far we only got constants and store-ptr. Not supported by the frontend yet, so far only in tests. --- src/compiler/backend/mod.rs | 115 ++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index a53e767..5748cc0 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -7,6 +7,8 @@ use std::fmt::Write; use super::StorageClass; +use crate::parser::Ast; + #[derive(Debug, PartialEq, Clone)] pub enum Type { Pointer(Box, StorageClass), @@ -292,3 +294,116 @@ pub fn spirv_meta(module: Module) -> String { } spirv_asm } + +enum Number { + Int(i32), + Float(f32), + NotANumber, +} + +fn match_number(s: &str) -> Number { + // floats have to be in the format of [0-9]+\.[0-9]* + // integers have to be in the format of [0-9]+ + let mut chars = s.chars(); + let mut has_dot = false; + while let Some(c) = chars.next() { + if c == '.' { + if has_dot { + // only one dot allowed. + return Number::NotANumber; + } + has_dot = true; + } else if !c.is_digit(10) { + // not a number; + // has a character that is not a digit or a dot. + return Number::NotANumber; + } + } + if has_dot { + // we have checked that the whole thing is numbers and dots + // and that there is precisely one dot + // that matches the regex. + Number::Float(s.parse().unwrap()) + } else { + // this is an integer, since no dots. + Number::Int(s.parse().unwrap()) + } +} + +pub fn compile_ast_ssa( + ast: Ast, + vars: &mut Vec<(String, String)>, + mut constants: &mut Vec<(String, String)>, + mut counter: &mut Box, + mut stack: &mut Vec, + mut out: &mut Vec<(Option, String)>, +) { + match ast.clone().list() { + Some(l) => { + let mut lst = l.clone(); + assert!(!lst.is_empty()); + let fun = lst.remove(0); + assert!(true); // no safe remove, thanks Rust + let fun_name = fun.symbol(); + assert!(fun_name.is_some()); + let fun_name = fun_name.unwrap(); + match fun_name.as_str() { + "store-ptr" => { + assert!(lst.len() == 2); + let ptr = lst.pop().unwrap(); + let val = lst.pop().unwrap(); + compile_ast_ssa(ptr, vars, constants, counter, stack, out); + compile_ast_ssa(val, vars, constants, counter, stack, out); + let val_id = stack.pop().unwrap(); + let ptr_id = stack.pop().unwrap(); + out.push((None, format!("OpStore {} {}", fix_name(&val_id), fix_name(&ptr_id)))); + } + s => { + panic!("Unknown function: {} with params {:#?} in context:\n{:#?}", s, lst, ast); + } + } + } + None => { + let sym = ast.clone().symbol(); + assert!(sym.is_some()); + let sym = sym.unwrap(); + match match_number(&sym) { + Number::Int(i) => { + let key = format!("i32_{}", i); + let mut contains = false; + for c in constants.iter() { + if c.0 == key { + contains = true; + } + } + if contains { + constants.push((key.clone(), format!("OpConstant %i32 {}", i.to_string()))); + } + stack.push(key); + } + Number::Float(f) => { + let key = format!("f32_{}", f); + let mut contains = false; + for c in constants.iter() { + if c.0 == key { + contains = true; + } + } + if contains { + constants.push((key.clone(), format!("OpConstant %f32 {}", f.to_string()))); + } + stack.push(key); + } + Number::NotANumber => { + for v in vars.iter() { + if v.0 == sym { + stack.push(v.0.clone()); + return; + } + } + panic!("Unknown variable or constant: {}", sym); + } + } + } + } +} -- 2.39.1 From b67795cdfb956575bc1aab5b002e163ff98865d1 Mon Sep 17 00:00:00 2001 From: itycodes Date: Mon, 10 Mar 2025 06:43:14 +0100 Subject: [PATCH 37/43] Forgotten cargo fmt + R-A warning cleanup --- src/compiler/backend/mod.rs | 18 ++++++++++++------ src/compiler/backend/tests.rs | 7 +------ src/compiler/mod.rs | 3 +-- src/compiler/tests.rs | 1 - 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index 5748cc0..598914c 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -333,10 +333,10 @@ fn match_number(s: &str) -> Number { pub fn compile_ast_ssa( ast: Ast, vars: &mut Vec<(String, String)>, - mut constants: &mut Vec<(String, String)>, - mut counter: &mut Box, - mut stack: &mut Vec, - mut out: &mut Vec<(Option, String)>, + constants: &mut Vec<(String, String)>, + counter: &mut Box, + stack: &mut Vec, + out: &mut Vec<(Option, String)>, ) { match ast.clone().list() { Some(l) => { @@ -356,10 +356,16 @@ pub fn compile_ast_ssa( compile_ast_ssa(val, vars, constants, counter, stack, out); let val_id = stack.pop().unwrap(); let ptr_id = stack.pop().unwrap(); - out.push((None, format!("OpStore {} {}", fix_name(&val_id), fix_name(&ptr_id)))); + out.push(( + None, + format!("OpStore {} {}", fix_name(&val_id), fix_name(&ptr_id)), + )); } s => { - panic!("Unknown function: {} with params {:#?} in context:\n{:#?}", s, lst, ast); + panic!( + "Unknown function: {} with params {:#?} in context:\n{:#?}", + s, lst, ast + ); } } } diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 62ff63e..15164f9 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -68,12 +68,7 @@ fn test_block_ssa() { let mut module = meta_compile(ast.unwrap()).unwrap(); let fun = module.functions.pop().unwrap(); let block = fun.ast.unwrap(); - let block = block - .list() - .unwrap() - .get(0) - .unwrap() - .clone(); + let block = block.list().unwrap().get(0).unwrap().clone(); println!("{:#?}", block); let mut vars = vec![]; let mut constants = vec![]; diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 4bf12ee..0238826 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -7,7 +7,7 @@ use crate::parser::{Ast, Localised, Location}; pub mod backend; mod meta_compile; -use std::{fmt, ops::Add, ptr}; +use std::fmt; #[cfg(test)] mod tests; @@ -240,4 +240,3 @@ pub fn meta_compile(ast: Ast) -> Result { } Ok(module) } - diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index 7a835a1..8064ea8 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -291,4 +291,3 @@ fn expected_type() { panic!() }; } - -- 2.39.1 From 10fb87de09cbbd03768d52cee42fd39fc618e407 Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 6 Mar 2025 23:32:06 +0100 Subject: [PATCH 38/43] Frontend and Error printing --- src/error.rs | 213 ++++++++++++++++++++++++++++++++++ src/main.rs | 318 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 424 insertions(+), 107 deletions(-) create mode 100644 src/error.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..54e99f5 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,213 @@ +use std::fmt::{self, Display, Write}; + +use crate::{ + compiler::CompilerError, + parser::{Location, ParseError}, +}; + +pub fn print_error(err: E, src: Option<&str>) { + let mut buf = String::new(); + match src { + Some(src) => { + err.fmt_with_src(&mut buf, &src).unwrap(); + } + None => err.fmt(&mut buf).unwrap(), + } + print!("{buf}"); +} + +pub trait FormattedLocatedError { + fn fmt(&self, w: &mut W) -> fmt::Result; + fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result; +} + +impl Display for Location { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Location::Char { line, col } => write!(f, "{line}:{col}"), + Location::String { lines, cols } => write!(f, "{}:{}", lines.start(), cols.start()), + Location::All => write!(f, "Entire file"), + } + } +} + +impl FormattedLocatedError for ParseError { + fn fmt(&self, w: &mut W) -> fmt::Result { + match self { + ParseError::UnexpectedParenClose(location) => { + writeln!(w, "Unexpected closing paren at {location}")? + } + ParseError::UnexpectedEof => writeln!(w, "Unexpected end of file")?, + } + + Ok(()) + } + + fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result { + match self { + ParseError::UnexpectedParenClose(location) => { + writeln!(w, "Unexpected closing paren at {location}")?; + here(location, src, Some("Unexpected ')'".to_string()), w)?; + } + ParseError::UnexpectedEof => writeln!(w, "Unexpected end of file")?, + } + + Ok(()) + } +} + +impl FormattedLocatedError for CompilerError { + fn fmt(&self, w: &mut W) -> fmt::Result { + match self { + CompilerError::ExpectedSymbol(location) => { + writeln!( + w, + "Expected a symbol but got a list or nothing at {location}" + ) + } + CompilerError::ExpectedList(location) => { + writeln!( + w, + "Expected a list but got a symbol or nothing at {location}" + ) + } + CompilerError::UnknownKeyword(k, location) => { + writeln!(w, "Got unexpected keyword {k} at {location}")?; + writeln!(w, "Expected one of: module, import, bind, dec, entry, fun") + } + CompilerError::ExpectedOneOf(vec, k, location) => { + writeln!(w, "Got unexpected token {k} at {location}")?; + writeln!(w, "Expected one of: {}", vec.join(", ")) + } + CompilerError::TrailingTokens(location) => { + writeln!(w, "Got unexpected trailing tokens at {location}")?; + writeln!(w, "Expected end of list") + } + CompilerError::UnknownBind(b, location) => { + writeln!(w, "Got unexpected keyword {b} at {location}")?; + writeln!(w, "Expected one of: BuiltIn, Location") + } + CompilerError::ExpectedType(location) => { + writeln!(w, "Expected name:type but got name at {location}") + } + } + } + + fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result { + match self { + CompilerError::ExpectedSymbol(location) => { + writeln!( + w, + "Expected a symbol but got a list or nothing at {location}" + )?; + here(location, src, Some("Expected symbol".to_string()), w) + } + CompilerError::ExpectedList(location) => { + writeln!( + w, + "Expected a list but got a symbol or nothing at {location}" + )?; + here(location, src, Some("Expected List".to_string()), w) + } + CompilerError::UnknownKeyword(k, location) => { + writeln!(w, "Got unexpected keyword {k} at {location}")?; + writeln!(w, "Expected one of: module, import, bind, dec, entry, fun")?; + here(location, src, Some("Unexpected keyword".to_string()), w) + } + CompilerError::ExpectedOneOf(vec, k, location) => { + writeln!(w, "Got unexpected token {k} at {location}")?; + writeln!(w, "Expected one of: {}", vec.join(", "))?; + here(location, src, Some("Unexpected token".to_string()), w) + } + CompilerError::TrailingTokens(location) => { + writeln!(w, "Got unexpected trailing tokens at {location}")?; + writeln!(w, "Expected end of list")?; + here(location, src, Some("Unexpected token".to_string()), w) + } + CompilerError::UnknownBind(b, location) => { + writeln!(w, "Got unexpected keyword {b} at {location}")?; + writeln!(w, "Expected one of: BuiltIn, Location")?; + here(location, src, Some("Unexpected keyword".to_string()), w) + } + CompilerError::ExpectedType(location) => { + writeln!(w, "Expected name:type but got name at {location}")?; + here(location, src, None, w) + } + } + } +} + +fn here( + loc: &Location, + src: &str, + comment: Option, + w: &mut W, +) -> fmt::Result { + writeln!(w, "Here:")?; + writeln!( + w, + "{}", + annotate_src(&loc, src, comment.unwrap_or_default()) + )?; + + Ok(()) +} + +fn annotate_src(loc: &Location, src: &str, comment: String) -> String { + let src_lines = src.split('\n').collect::>(); + + let mut buf = String::new(); + + match loc { + Location::Char { line, col } => { + writeln!(buf, "{}|{}", line_num(*line - 1), src_lines[*line - 2]).unwrap(); + writeln!(buf, "{}|{}", line_num(*line), src_lines[*line - 1]).unwrap(); + writeln!(buf, "{}|{}^ {}", " ", " ".repeat(col - 1), comment).unwrap(); + writeln!(buf, "{}|{}", line_num(*line + 1), src_lines[*line]).unwrap(); + } + Location::String { lines, cols } => { + writeln!( + buf, + "{}|{}", + line_num(*lines.start() - 1), + src_lines[*lines.start() - 2] + ) + .unwrap(); + writeln!( + buf, + "{}", + src_lines[(lines.start() - 1)..*lines.end()] + .iter() + .zip(lines.clone()) + .map(|(l, n)| format!("{}|{}", line_num(n), l)) + .collect::>() + .join("\n") + ) + .unwrap(); + writeln!( + buf, + "{}|{}{} {}", + " ", + " ".repeat(cols.start() - 1), + "^".repeat(cols.end() - cols.start() + 1), + comment + ) + .unwrap(); + writeln!( + buf, + "{}|{}", + line_num(*lines.end() + 1), + src_lines[*lines.end()] + ) + .unwrap(); + } + Location::All => todo!(), + } + + buf +} + +fn line_num(num: usize) -> String { + let num = format!("{num}"); + format!("{num}{}", " ".repeat(6 - num.len())) +} diff --git a/src/main.rs b/src/main.rs index 2374721..9e9b216 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,115 +1,219 @@ -use std::fmt::Write; +use compiler::{backend::spirv_meta, meta_compile}; +use error::print_error; +use std::{env, fs, path::PathBuf}; + +use parser::parse_string; pub mod compiler; +mod error; pub mod parser; +#[derive(Debug, Default)] +struct CompilerArgs { + input: Option, + output: Option, + print_help: bool, + print_version: bool, +} + +const SPIRV_VERSION: &'static str = "1.0"; + fn main() { - let mut ops: Vec<(Option, Vec)> = Vec::new(); - - // OpMemoryModel Logical GLSL450 - // OpEntryPoint Fragment %main "main" - // OpExecutionMode %main OriginUpperLeft - // OpSource GLSL 450 - // OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" - // OpSourceExtension "GL_GOOGLE_include_directive" - // OpName %main "main" - //%void = OpTypeVoid - //%3 = OpTypeFunction %void - //%main = OpFunction %void None %3 - //%5 = OpLabel - // OpReturn - // OpFunctionEnd - - ops.push((None, vec!["OpCapability".to_string(), "Shader".to_string()])); - ops.push(( - Some("%1".to_string()), - vec![ - "OpExtInstImport".to_string(), - "\"GLSL.std.450\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpMemoryModel".to_string(), - "Logical".to_string(), - "GLSL450".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpEntryPoint".to_string(), - "Fragment".to_string(), - "%main".to_string(), - "\"main\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpExecutionMode".to_string(), - "%main".to_string(), - "OriginUpperLeft".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpSource".to_string(), - "GLSL".to_string(), - "450".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpSourceExtension".to_string(), - "\"GL_GOOGLE_cpp_style_line_directive\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpSourceExtension".to_string(), - "\"GL_GOOGLE_include_directive\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpName".to_string(), - "%main".to_string(), - "\"main\"".to_string(), - ], - )); - ops.push((Some("%void".to_string()), vec!["OpTypeVoid".to_string()])); - ops.push(( - Some("%3".to_string()), - vec!["OpTypeFunction".to_string(), "%void".to_string()], - )); - ops.push(( - Some("%main".to_string()), - vec![ - "OpFunction".to_string(), - "%void".to_string(), - "None".to_string(), - "%3".to_string(), - ], - )); - ops.push((Some("%5".to_string()), vec!["OpLabel".to_string()])); - ops.push((None, vec!["OpReturn".to_string()])); - ops.push((None, vec!["OpFunctionEnd".to_string()])); - - let mut out: String = String::new(); - - for op in ops { - if op.0.is_some() { - write!(out, "{} = ", op.0.unwrap()).unwrap(); + let args = parse_args(); + if args.print_version || args.print_help { + if args.print_help { + print_help(); } - for arg in op.1 { - write!(out, "{} ", arg).unwrap(); + if args.print_version { + print_version(); } - writeln!(out).unwrap(); + return; } - println!("{}", out); + + let Some(input) = args.input else { + eprintln!("No input specified"); + return; + }; + + let output = args.output.unwrap_or(PathBuf::from("./out.spv")); + + let src = match fs::read_to_string(input) { + Ok(i) => i, + Err(e) => { + eprintln!("{e}"); + return; + } + }; + + let ast = match parse_string(&src) { + Ok(i) => i, + Err(e) => { + print_error(e, Some(&src)); + return; + } + }; + + let module = match meta_compile(ast) { + Ok(m) => m, + Err(e) => { + print_error(e, Some(&src)); + return; + } + }; + + let res = spirv_meta(module); + + fs::write(output, res).unwrap(); +} + +fn parse_args() -> CompilerArgs { + let mut args = env::args(); + + let _prog = args.next().unwrap(); + + let mut parsed_args = CompilerArgs::default(); + + while let Some(arg) = args.next() { + if let Some(arg) = arg.strip_prefix("--") { + match arg { + "help" => parsed_args.print_help = true, + "version" => parsed_args.print_version = true, + "input" => { + if parsed_args.input.is_none() { + let Some(input) = args.next() else { + eprintln!("input needs a file"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + }; + + parsed_args.input = Some(PathBuf::from(input)); + } else { + eprintln!("input can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + "output" => { + if parsed_args.output.is_none() { + let Some(output) = args.next() else { + parsed_args.print_help = true; + eprintln!("output needs a file"); + eprintln!(); + return parsed_args; + }; + + parsed_args.output = Some(PathBuf::from(output)); + } else { + eprintln!("output can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + a => { + eprintln!("unknown arg --{a}"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + continue; + } + + if let Some(arg) = arg.strip_prefix("-") { + let mut chars = arg.chars(); + while let Some(arg) = chars.next() { + match arg { + 'i' => { + if chars.next().is_some() { + parsed_args.print_help = true; + eprintln!("-i needs to be last in combined args"); + return parsed_args; + } + + if parsed_args.input.is_none() { + let Some(input) = args.next() else { + eprintln!("input needs a file"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + }; + + parsed_args.input = Some(PathBuf::from(input)); + } else { + eprintln!("input can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + 'o' => { + if chars.next().is_some() { + parsed_args.print_help = true; + eprintln!("-o needs to be last in combined args"); + eprintln!(); + return parsed_args; + } + + if parsed_args.output.is_none() { + let Some(output) = args.next() else { + parsed_args.print_help = true; + eprintln!("output needs a file"); + return parsed_args; + }; + + parsed_args.output = Some(PathBuf::from(output)); + } else { + eprintln!("output can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + 'h' => parsed_args.print_help = true, + 'v' => parsed_args.print_version = true, + a => { + eprintln!("unknown arg -{a}"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + } + continue; + } + + eprintln!("unknown arg {arg}"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + + parsed_args +} + +fn print_help() { + println!("-- ShaderC shader compiler --"); + println!(); + println!("Arguments:"); + println!("\t-i --input:"); + println!("\t\tThe shader to be parsed and compiled, manadory argument"); + println!(); + println!("\t-o --output:"); + println!("\t\tWhere to output the compiled spirv assembly to, default: out.spv"); + println!(); + println!("\t-h --help:"); + println!("\t\tPrint this and exit"); + println!("\t-v --version:"); + println!("\t\tPrint the compilers version"); + println!(); +} + +fn print_version() { + println!( + "ShaderC version: {}; SPIR-V version: {}", + env!("CARGO_PKG_VERSION"), + SPIRV_VERSION + ) } -- 2.39.1 From ce03ad8bac8c408907da3603b75143b1db703763 Mon Sep 17 00:00:00 2001 From: Avery Date: Mon, 10 Mar 2025 09:30:24 +0100 Subject: [PATCH 39/43] Exit with non 0 exit code on failure --- src/main.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9e9b216..ef9d46d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use compiler::{backend::spirv_meta, meta_compile}; use error::print_error; -use std::{env, fs, path::PathBuf}; +use std::{env, fs, path::PathBuf, process::ExitCode}; use parser::parse_string; pub mod compiler; @@ -17,7 +17,7 @@ struct CompilerArgs { const SPIRV_VERSION: &'static str = "1.0"; -fn main() { +fn main() -> ExitCode { let args = parse_args(); if args.print_version || args.print_help { if args.print_help { @@ -26,12 +26,12 @@ fn main() { if args.print_version { print_version(); } - return; + return ExitCode::FAILURE; } let Some(input) = args.input else { eprintln!("No input specified"); - return; + return ExitCode::FAILURE; }; let output = args.output.unwrap_or(PathBuf::from("./out.spv")); @@ -40,7 +40,7 @@ fn main() { Ok(i) => i, Err(e) => { eprintln!("{e}"); - return; + return ExitCode::FAILURE; } }; @@ -48,7 +48,7 @@ fn main() { Ok(i) => i, Err(e) => { print_error(e, Some(&src)); - return; + return ExitCode::FAILURE; } }; @@ -56,13 +56,15 @@ fn main() { Ok(m) => m, Err(e) => { print_error(e, Some(&src)); - return; + return ExitCode::FAILURE; } }; let res = spirv_meta(module); fs::write(output, res).unwrap(); + + ExitCode::SUCCESS } fn parse_args() -> CompilerArgs { -- 2.39.1 From ac6ed71482842c83948e8ac4c540b7e9ef8f02af Mon Sep 17 00:00:00 2001 From: itycodes Date: Mon, 10 Mar 2025 09:43:37 +0100 Subject: [PATCH 40/43] Slight compiler cleanup, type tracking --- src/compiler/backend/mod.rs | 63 ++++++++++++++++++++++++++++++++--- src/compiler/backend/tests.rs | 18 ++++++---- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index 598914c..5b6ada9 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -330,11 +330,44 @@ fn match_number(s: &str) -> Number { } } +fn compile_biop( + op: &str, + lst: &mut Vec, + vars: &mut Vec<(String, String)>, + constants: &mut Vec<(String, String)>, + types: &mut Vec, + counter: &mut i32, + stack: &mut Vec, + out: &mut Vec<(Option, String)>, +) { + assert!(lst.len() == 2); + let rhs = lst.pop().unwrap(); + let lhs = lst.pop().unwrap(); + compile_ast_ssa(lhs, vars, constants, types, counter, stack, out); + compile_ast_ssa(rhs, vars, constants, types, counter, stack, out); + let rhs_id = stack.pop().unwrap(); + let lhs_id = stack.pop().unwrap(); + let id = String::from(counter.to_string()); + *counter += 1; + out.push(( + Some(id.clone()), + format!( + "{} {} {} {}", + op, + fix_name(&String::from("f32")), + fix_name(&lhs_id), + fix_name(&rhs_id), + ), + )); + stack.push(id); +} + pub fn compile_ast_ssa( ast: Ast, vars: &mut Vec<(String, String)>, constants: &mut Vec<(String, String)>, - counter: &mut Box, + types: &mut Vec, + counter: &mut i32, stack: &mut Vec, out: &mut Vec<(Option, String)>, ) { @@ -352,8 +385,8 @@ pub fn compile_ast_ssa( assert!(lst.len() == 2); let ptr = lst.pop().unwrap(); let val = lst.pop().unwrap(); - compile_ast_ssa(ptr, vars, constants, counter, stack, out); - compile_ast_ssa(val, vars, constants, counter, stack, out); + compile_ast_ssa(ptr, vars, constants, types, counter, stack, out); + compile_ast_ssa(val, vars, constants, types, counter, stack, out); let val_id = stack.pop().unwrap(); let ptr_id = stack.pop().unwrap(); out.push(( @@ -361,6 +394,18 @@ pub fn compile_ast_ssa( format!("OpStore {} {}", fix_name(&val_id), fix_name(&ptr_id)), )); } + "/" => { + compile_biop("OpFDiv", &mut lst, vars, constants, types, counter, stack, out); + } + "*" => { + compile_biop("OpFMul", &mut lst, vars, constants, types, counter, stack, out); + } + "+" => { + compile_biop("OpFAdd", &mut lst, vars, constants, types, counter, stack, out); + } + "-" => { + compile_biop("OpFSub", &mut lst, vars, constants, types, counter, stack, out); + } s => { panic!( "Unknown function: {} with params {:#?} in context:\n{:#?}", @@ -382,7 +427,7 @@ pub fn compile_ast_ssa( contains = true; } } - if contains { + if !contains { constants.push((key.clone(), format!("OpConstant %i32 {}", i.to_string()))); } stack.push(key); @@ -395,9 +440,17 @@ pub fn compile_ast_ssa( contains = true; } } - if contains { + if !contains { constants.push((key.clone(), format!("OpConstant %f32 {}", f.to_string()))); } + for t in types.iter() { + if t == "f32" { + contains = true; + } + } + if !contains { + types.push("f32".to_string()); + } stack.push(key); } Number::NotANumber => { diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 15164f9..b8e775c 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -62,7 +62,7 @@ fn test_block_ssa() { (dec out-color:*f32 Output) (entry main Fragment OriginUpperLeft (:out-color)) (fun (main) :<> - (store-ptr out-color 1.0)) + (store-ptr out-color (/ 1.0 (+ 1.0 1.0)))) "; let ast = parse(tokenize(src)); let mut module = meta_compile(ast.unwrap()).unwrap(); @@ -72,6 +72,7 @@ fn test_block_ssa() { println!("{:#?}", block); let mut vars = vec![]; let mut constants = vec![]; + let mut types = vec![]; let mut counter = Box::new(0); let mut stack = vec![]; let mut out = vec![]; @@ -82,20 +83,23 @@ fn test_block_ssa() { block, &mut vars, &mut constants, + &mut types, &mut counter, &mut stack, &mut out, ); - println!("\n---constants:---\n{:#?}\n------", constants); - println!("\n---vars:---\n{:#?}\n------", vars); - println!("\n---stack:---\n{:#?}\n------", stack); - println!("\n---counter---\n{}\n------", counter); - println!("\n---out---"); + println!("\n---vars:---\n{:#?}\n-----------", vars); + println!("\n---constants:---\n{:#?}\n----------------", constants); + println!("\n---types:---\n{:#?}\n------------", types); + println!("\n---stack:---\n{:#?}\n------------", stack); + println!("\n---counter:---\n{}\n--------------", counter); + println!("\n---out:---"); for (a, b) in &out { match a { Some(a) => println!("%{} = {}", a, b), None => println!("{}", b), } } - println!("------"); + println!("----------"); + assert!(stack.is_empty()); } -- 2.39.1 From 7382e9185f825bcf5f6f2ce25fa93aa46bcabee8 Mon Sep 17 00:00:00 2001 From: itycodes Date: Mon, 10 Mar 2025 11:08:10 +0100 Subject: [PATCH 41/43] Initial work on function compilation API for the frontend Also one forgotten cargo fmt --- src/compiler/backend/mod.rs | 76 ++++++++++++++++++++++++++++++++--- src/compiler/backend/tests.rs | 55 +++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 5 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index 5b6ada9..88cc01a 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod tests; -use crate::compiler::Module; +use crate::compiler::{Instruction, Module}; use std::fmt; use std::fmt::Write; @@ -395,16 +395,24 @@ pub fn compile_ast_ssa( )); } "/" => { - compile_biop("OpFDiv", &mut lst, vars, constants, types, counter, stack, out); + compile_biop( + "OpFDiv", &mut lst, vars, constants, types, counter, stack, out, + ); } "*" => { - compile_biop("OpFMul", &mut lst, vars, constants, types, counter, stack, out); + compile_biop( + "OpFMul", &mut lst, vars, constants, types, counter, stack, out, + ); } "+" => { - compile_biop("OpFAdd", &mut lst, vars, constants, types, counter, stack, out); + compile_biop( + "OpFAdd", &mut lst, vars, constants, types, counter, stack, out, + ); } "-" => { - compile_biop("OpFSub", &mut lst, vars, constants, types, counter, stack, out); + compile_biop( + "OpFSub", &mut lst, vars, constants, types, counter, stack, out, + ); } s => { panic!( @@ -466,3 +474,61 @@ pub fn compile_ast_ssa( } } } + +pub fn compile_fun_ssa(module: &mut Module) { + for fun in module.functions.iter_mut() { + assert!(fun.ast.is_some()); + let ast = fun.ast.as_mut().unwrap(); + let block = ast.clone().list().unwrap().get(0).unwrap().clone(); + let mut vars = vec![]; + let mut constants = vec![]; + let mut types = vec![]; + let mut counter = Box::new(0); + let mut stack = vec![]; + let mut out_op = vec![]; + for v in &module.globals { + vars.push((v.name.clone(), v.typ.clone())); + } + compile_ast_ssa( + block, + &mut vars, + &mut constants, + &mut types, + &mut counter, + &mut stack, + &mut out_op, + ); + let mut out_pre = vec![]; + for t in &types { + let typ = parse_type(t); + let mut type_ops = vec![]; + emit_type(typ, &mut type_ops); + for type_op in type_ops { + out_pre.push((Some(type_op.0), type_op.1)); + } + } + for c in &constants { + out_pre.push((Some(fix_name(&c.0.clone())), c.1.clone())); + } + let mut out_ops = out_pre.clone(); + for op in out_op { + if op.0.is_some() { + out_ops.push((Some(fix_name(&op.0.unwrap())), op.1)); + } else { + out_ops.push((None, op.1)); + } + } + for op in out_ops { + let split: Vec = op.1.split(" ").map(|s| s.to_string()).collect(); + let op_name: String = (&split[0]).clone(); + let op_args: Vec = split[1..].iter().map(|s| s.clone()).collect(); + let op_id = op.0.clone(); + let ins: Instruction = Instruction { + result_id: op_id, + op: op_name, + operands: op_args, + }; + fun.body.as_mut().unwrap().push(ins); + } + } +} diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index b8e775c..36d5734 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -103,3 +103,58 @@ fn test_block_ssa() { println!("----------"); assert!(stack.is_empty()); } + +#[test] +fn test_fun_ssa() { + use crate::compiler::backend::*; + use crate::compiler::*; + use crate::parser::*; + let src = " + (module Shader Logical GLSL450) + (import :std GLSL.std.450) + (bind (out-color:*f32) (Location 0)) + (dec out-color:*f32 Output) + (entry main Fragment OriginUpperLeft (:out-color)) + (fun (main) :<> + (store-ptr out-color (/ 1.0 (+ 1.0 1.0)))) +"; + let ast = parse(tokenize(src)); + let mut module = meta_compile(ast.unwrap()).unwrap(); + compile_fun_ssa(&mut module); + let res = module.functions.pop().unwrap(); + let res = res.body.unwrap(); + println!("{:#?}", res); + // TODO we need to unify the place where we call fix_name + let res_spv: Vec = vec![ + Instruction { + result_id: Some("%f32".to_string()), + op: "OpTypeFloat".to_string(), + operands: vec!["32".to_string()], + }, + Instruction { + result_id: Some("%f32_1".to_string()), + op: "OpConstant".to_string(), + operands: vec!["%f32".to_string(), "1".to_string()], + }, + Instruction { + result_id: Some("%0".to_string()), + op: "OpFAdd".to_string(), + operands: vec![ + "%f32".to_string(), + "%f32_1".to_string(), + "%f32_1".to_string(), + ], + }, + Instruction { + result_id: Some("%1".to_string()), + op: "OpFDiv".to_string(), + operands: vec!["%f32".to_string(), "%f32_1".to_string(), "%0".to_string()], + }, + Instruction { + result_id: None, + op: "OpStore".to_string(), + operands: vec!["%out_color".to_string(), "%1".to_string()], + }, + ]; + assert_eq!(res, res_spv); +} -- 2.39.1 From 041393fa72d9f4caf15daf953fd759be717a20bc Mon Sep 17 00:00:00 2001 From: itycodes Date: Mon, 10 Mar 2025 12:36:24 +0100 Subject: [PATCH 42/43] Temporary frontend with backend changes Broken: Order of constant ops generated by functions. OpConstant belongs before OpFunction. --- src/compiler/backend/mod.rs | 43 ++++++++++++++++------------- src/compiler/backend/tests.rs | 18 +++++++------ src/main.rs | 51 +++++++++++++++++++++++++++++++---- 3 files changed, 80 insertions(+), 32 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index 88cc01a..c9c2a97 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -3,7 +3,6 @@ mod tests; use crate::compiler::{Instruction, Module}; use std::fmt; -use std::fmt::Write; use super::StorageClass; @@ -149,7 +148,7 @@ fn fix_name(name: &String) -> String { ) } -fn has_id(name: String, ops: &Vec<(Option, Vec)>) -> bool { +fn has_id(name: String, ops: &Vec<(Option, T)>) -> bool { for op in ops { if op.0.is_some() && op.0.clone().unwrap() == name { return true; @@ -158,8 +157,7 @@ fn has_id(name: String, ops: &Vec<(Option, Vec)>) -> bool { false } -pub fn spirv_meta(module: Module) -> String { - let mut spirv_asm = String::new(); +pub fn spirv_meta(module: &mut Module) -> Vec<(Option, Vec)> { let mut ops: Vec<(Option, Vec)> = Vec::new(); let capabilities: Vec = module @@ -192,7 +190,7 @@ pub fn spirv_meta(module: Module) -> String { ], )); - for entry in module.entry_points { + for entry in module.entry_points.clone() { let exec_model = match entry.execution_model { crate::compiler::ExecutionModel::Fragment => "Fragment", crate::compiler::ExecutionModel::Vertex => "Vertex", @@ -226,7 +224,7 @@ pub fn spirv_meta(module: Module) -> String { )); } - for global in module.globals { + for global in module.globals.clone() { let name = fix_name(&global.name); let _typ = global.typ; let storage_class = match global.storage_class { @@ -265,8 +263,8 @@ pub fn spirv_meta(module: Module) -> String { } } - for fun in module.functions { - let name = fix_name(&fun.name); + for fun in module.functions.clone() { + let name = fix_name(&format!("l_{}", fun.name)); let return_type = fix_name(&fun.return_type); let mut type_ops = Vec::new(); emit_type(parse_type(&fun.return_type), &mut type_ops); @@ -283,16 +281,17 @@ pub fn spirv_meta(module: Module) -> String { )); } - for op in ops { - if op.0.is_some() { - write!(spirv_asm, "{} = ", op.0.unwrap()).unwrap(); - } - for arg in op.1 { - write!(spirv_asm, "{} ", arg).unwrap(); - } - writeln!(spirv_asm).unwrap(); - } - spirv_asm + // for op in ops { + // if op.0.is_some() { + // write!(spirv_asm, "{} = ", op.0.unwrap()).unwrap(); + // } + // for arg in op.1 { + // write!(spirv_asm, "{} ", arg).unwrap(); + // } + // writeln!(spirv_asm).unwrap(); + // } + // spirv_asm + ops } enum Number { @@ -475,7 +474,7 @@ pub fn compile_ast_ssa( } } -pub fn compile_fun_ssa(module: &mut Module) { +pub fn compile_fun_ssa(module: &mut Module, ops: &Vec<(Option, String)>) { for fun in module.functions.iter_mut() { assert!(fun.ast.is_some()); let ast = fun.ast.as_mut().unwrap(); @@ -501,6 +500,12 @@ pub fn compile_fun_ssa(module: &mut Module) { let mut out_pre = vec![]; for t in &types { let typ = parse_type(t); + if has_id(fix_name(&typ.to_string()), ops) { + continue; + } + if has_id(fix_name(&typ.to_string()), &out_pre) { + continue; + } let mut type_ops = vec![]; emit_type(typ, &mut type_ops); for type_op in type_ops { diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index 36d5734..acbc86a 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -19,9 +19,10 @@ fn test_emit() { 1.0))) "; let ast = parse(tokenize(src)); - let module = meta_compile(ast.unwrap()).unwrap(); - let res = spirv_meta(module); - println!("{}", res); + let mut module = meta_compile(ast.unwrap()).unwrap(); + let res = spirv_meta(&mut module); + println!("{:#?}", res); + // TODO add an assert } #[test] @@ -58,8 +59,8 @@ fn test_block_ssa() { let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) -(bind (out-color:*f32) (Location 0)) -(dec out-color:*f32 Output) +(bind (out-color:*f32o) (Location 0)) +(dec out-color:*f32o Output) (entry main Fragment OriginUpperLeft (:out-color)) (fun (main) :<> (store-ptr out-color (/ 1.0 (+ 1.0 1.0)))) @@ -112,15 +113,16 @@ fn test_fun_ssa() { let src = " (module Shader Logical GLSL450) (import :std GLSL.std.450) - (bind (out-color:*f32) (Location 0)) - (dec out-color:*f32 Output) + (bind (out-color:*f32o) (Location 0)) + (dec out-color:*f32o Output) (entry main Fragment OriginUpperLeft (:out-color)) (fun (main) :<> (store-ptr out-color (/ 1.0 (+ 1.0 1.0)))) "; let ast = parse(tokenize(src)); let mut module = meta_compile(ast.unwrap()).unwrap(); - compile_fun_ssa(&mut module); + let ops = vec![]; + compile_fun_ssa(&mut module, &ops); let res = module.functions.pop().unwrap(); let res = res.body.unwrap(); println!("{:#?}", res); diff --git a/src/main.rs b/src/main.rs index ef9d46d..d746ffe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,7 @@ -use compiler::{backend::spirv_meta, meta_compile}; +use compiler::{ + backend::{compile_fun_ssa, spirv_meta}, + meta_compile, +}; use error::print_error; use std::{env, fs, path::PathBuf, process::ExitCode}; @@ -34,7 +37,7 @@ fn main() -> ExitCode { return ExitCode::FAILURE; }; - let output = args.output.unwrap_or(PathBuf::from("./out.spv")); + let output = args.output.unwrap_or(PathBuf::from("./out.spvas")); let src = match fs::read_to_string(input) { Ok(i) => i, @@ -52,7 +55,7 @@ fn main() -> ExitCode { } }; - let module = match meta_compile(ast) { + let mut module = match meta_compile(ast) { Ok(m) => m, Err(e) => { print_error(e, Some(&src)); @@ -60,7 +63,45 @@ fn main() -> ExitCode { } }; - let res = spirv_meta(module); + use std::fmt::Write; + + let ops_split_params = spirv_meta(&mut module); + let mut ops_merged_param = vec![]; + for op in ops_split_params.clone() { + ops_merged_param.push((op.0, op.1.join(" "))); + } + compile_fun_ssa(&mut module, &ops_merged_param); + + let mut res = String::new(); + + for op in ops_split_params { + if op.0.is_some() { + write!(res, "{} = ", op.0.unwrap()).unwrap(); + } + for arg in op.1 { + write!(res, "{} ", arg).unwrap(); + } + writeln!(res).unwrap(); + } + + let mut counter: u32 = 0; + for fun in module.functions.iter() { + // TODO non-void type + // TODO this should NOT be in the frontend + writeln!(res, "%{} = OpFunction %void None %l_{}", fun.name, fun.name).unwrap(); + writeln!(res, "%n_{} = OpLabel", counter.to_string()).unwrap(); + for inst in fun.body.clone().unwrap().iter() { + if let Some(id) = inst.result_id.as_ref() { + write!(res, "{} = ", id).unwrap(); + } + write!(res, "{}", inst.op).unwrap(); + for op in inst.operands.iter() { + write!(res, " {}", op).unwrap(); + } + writeln!(res).unwrap(); + counter += 1; + } + } fs::write(output, res).unwrap(); @@ -203,7 +244,7 @@ fn print_help() { println!("\t\tThe shader to be parsed and compiled, manadory argument"); println!(); println!("\t-o --output:"); - println!("\t\tWhere to output the compiled spirv assembly to, default: out.spv"); + println!("\t\tWhere to output the compiled spirv assembly to, default: out.spvas"); println!(); println!("\t-h --help:"); println!("\t\tPrint this and exit"); -- 2.39.1 From 9db2b68e7b3e4bc2e49233930bca5cac5f1f0495 Mon Sep 17 00:00:00 2001 From: itycodes Date: Mon, 10 Mar 2025 17:14:23 +0100 Subject: [PATCH 43/43] Move function preamble to backend --- src/compiler/backend/mod.rs | 13 ++++++++++++- src/compiler/backend/tests.rs | 14 ++++++++++++++ src/main.rs | 5 ----- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/compiler/backend/mod.rs b/src/compiler/backend/mod.rs index c9c2a97..53c59ac 100644 --- a/src/compiler/backend/mod.rs +++ b/src/compiler/backend/mod.rs @@ -274,7 +274,6 @@ pub fn spirv_meta(module: &mut Module) -> Vec<(Option, Vec)> { } ops.push((Some(op.0), vec![op.1])); } - // Push OpFunctionType ops.push(( Some(name.clone()), vec!["OpTypeFunction".to_string(), return_type.clone()], @@ -475,6 +474,7 @@ pub fn compile_ast_ssa( } pub fn compile_fun_ssa(module: &mut Module, ops: &Vec<(Option, String)>) { + let mut label_counter = Box::new(0); for fun in module.functions.iter_mut() { assert!(fun.ast.is_some()); let ast = fun.ast.as_mut().unwrap(); @@ -515,6 +515,17 @@ pub fn compile_fun_ssa(module: &mut Module, ops: &Vec<(Option, String)>) for c in &constants { out_pre.push((Some(fix_name(&c.0.clone())), c.1.clone())); } + + // TODO non-void type + out_pre.push(( + Some(fix_name(&fun.name)), + format!("OpFunction %void None %l_{}", fun.name), + )); + out_pre.push(( + Some(format!("%n_{}", label_counter.to_string())), + "OpLabel".to_string(), + )); + *label_counter += 1; let mut out_ops = out_pre.clone(); for op in out_op { if op.0.is_some() { diff --git a/src/compiler/backend/tests.rs b/src/compiler/backend/tests.rs index acbc86a..6420b62 100644 --- a/src/compiler/backend/tests.rs +++ b/src/compiler/backend/tests.rs @@ -138,6 +138,20 @@ fn test_fun_ssa() { op: "OpConstant".to_string(), operands: vec!["%f32".to_string(), "1".to_string()], }, + Instruction { + result_id: Some("%main".to_string()), + op: "OpFunction".to_string(), + operands: vec![ + "%void".to_string(), + "None".to_string(), + "%l_main".to_string(), + ], + }, + Instruction { + result_id: Some("%n_0".to_string()), + op: "OpLabel".to_string(), + operands: vec![], + }, Instruction { result_id: Some("%0".to_string()), op: "OpFAdd".to_string(), diff --git a/src/main.rs b/src/main.rs index d746ffe..ae97a6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -84,12 +84,8 @@ fn main() -> ExitCode { writeln!(res).unwrap(); } - let mut counter: u32 = 0; for fun in module.functions.iter() { // TODO non-void type - // TODO this should NOT be in the frontend - writeln!(res, "%{} = OpFunction %void None %l_{}", fun.name, fun.name).unwrap(); - writeln!(res, "%n_{} = OpLabel", counter.to_string()).unwrap(); for inst in fun.body.clone().unwrap().iter() { if let Some(id) = inst.result_id.as_ref() { write!(res, "{} = ", id).unwrap(); @@ -99,7 +95,6 @@ fn main() -> ExitCode { write!(res, " {}", op).unwrap(); } writeln!(res).unwrap(); - counter += 1; } } -- 2.39.1