├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── brainfuck ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── hanoi.bf ├── mandel.bf └── src │ └── main.rs ├── img ├── ir_optimized.png └── ir_unoptimized.png ├── src ├── compiler.rs ├── main.rs ├── parser │ ├── ast.rs │ ├── lexer.rs │ ├── mod.rs │ ├── print.rs │ └── ty.rs └── runtimelib.rs ├── tests ├── main.tc ├── optimize_1.tc ├── optimize_2.tc └── test.turboir └── turbo_ir ├── .gitignore ├── Cargo.toml └── src ├── analysis.rs ├── bin └── main.rs ├── codegen ├── executable_memory.rs ├── mod.rs ├── register_allocation.rs └── x86_backend.rs ├── collections.rs ├── display.rs ├── dot.rs ├── dump.rs ├── graph.rs ├── instruction.rs ├── instruction_builders.rs ├── lib.rs ├── parser ├── lexer.rs └── mod.rs ├── pass_manager.rs ├── passes ├── branch_to_select.rs ├── const_propagate.rs ├── deduplicate.rs ├── global_reorder.rs ├── memory_to_ssa.rs ├── minimize_phis.rs ├── mod.rs ├── optimize_known_bits.rs ├── propagate_block_invariants.rs ├── remove_aliases.rs ├── remove_dead_code.rs ├── remove_dead_stores.rs ├── remove_ineffective_operations.rs ├── remove_known_loads.rs ├── remove_nops.rs ├── reorder.rs ├── rewrite_values.rs ├── simplify_cfg.rs ├── simplify_compares.rs ├── simplify_expressions.rs └── undefined_propagate.rs ├── phi_updater.rs ├── timing.rs ├── ty.rs └── type_inference.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /graphs 3 | /mcode 4 | /.idea 5 | *.bin 6 | *.svg 7 | *.png 8 | perf.data 9 | perf.data.old 10 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "asm" 5 | version = "0.1.0" 6 | source = "git+https://github.com/addrianyy/assembler#12b5ae73788900a5aa4a94c3131d447423fcd2ec" 7 | 8 | [[package]] 9 | name = "compiler" 10 | version = "0.1.0" 11 | dependencies = [ 12 | "turbo_ir", 13 | ] 14 | 15 | [[package]] 16 | name = "smallvec" 17 | version = "1.5.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" 20 | 21 | [[package]] 22 | name = "turbo_ir" 23 | version = "0.1.0" 24 | dependencies = [ 25 | "asm", 26 | "smallvec", 27 | ] 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compiler" 3 | version = "0.1.0" 4 | authors = ["addrianyy "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | turbo_ir = { path = "turbo_ir" } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Adrian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Simple hobby compiler project. Custom language gets converted to intermediate representation and 2 | then machine code is generated. IR is language-independent and as example this project contains 3 | IR-based brainfuck compiler. A lot of features are missing. 4 | 5 | usage: `cargo run [source file]` 6 | 7 | 1. Source code goes through lexer and parser which generates AST. 8 | 9 | ``` 10 | void print_num(u32 num) { 11 | u8 buffer[256]; 12 | i64 i = 255; 13 | 14 | buffer[i] = 0; 15 | i -= 1; 16 | 17 | if (num == 0) { 18 | buffer[i] = 48u8; 19 | i -= 1; 20 | } 21 | 22 | while (num > 0) { 23 | u8 char = (num % 10u8) + 48u8; 24 | 25 | buffer[i] = char; 26 | i -= 1; 27 | 28 | num /= 10u8; 29 | } 30 | 31 | print(&buffer[i + 1]); 32 | } 33 | ``` 34 | 35 | 2. AST gets converted to language-independent intermediate representation. 36 | 37 | ![Unoptimized IR](./img/ir_unoptimized.png) 38 | 39 | 3. Optimization passes are ran on generated IR. 40 | 41 | ![Optimized IR](./img/ir_optimized.png) 42 | 43 | 4. Values are assigned registers and x64 machine code is generated. 44 | 45 | ```asm 46 | 00 push rbp 47 | 02 mov rbp,rsp 48 | 05 sub rsp,0x108 49 | 0C push r13 50 | 0F push rsi 51 | 11 push rdi 52 | 13 push r12 53 | 16 push rbx 54 | 18 mov [rbp+0x10],rcx 55 | 1C lea r13,[rbp-0x100] 56 | 23 mov byte [r13+0xff],0x0 57 | 2B mov r12,0xfe 58 | 32 mov ebx,[rbp+0x10] 59 | 35 cmp dword [rbp+0x10],byte +0x0 60 | 39 jnz near 0x51 61 | 3F mov byte [r13+0xfe],0x30 62 | 47 mov r12,0xfd 63 | 4E mov ebx,[rbp+0x10] 64 | 51 cmp ebx,byte +0x0 65 | 54 jna near 0x7c 66 | 5A mov edi,0xa 67 | 60 mov eax,ebx 68 | 62 xor edx,edx 69 | 64 div edi 70 | 66 mov esi,eax 71 | 68 mov ebx,edx 72 | 6A add ebx,byte +0x30 73 | 6D mov [r13+r12+0x0],bl 74 | 72 dec r12 75 | 75 mov ebx,esi 76 | 77 jmp 0x51 77 | 7C inc r12 78 | 7F lea r13,[r13+r12+0x0] 79 | 84 sub rsp,byte +0x20 80 | 88 mov rcx,r13 81 | 8B mov rax,0x7ff78e441a60 82 | 95 call rax 83 | 97 add rsp,byte +0x20 84 | 9B pop rbx 85 | 9D pop r12 86 | A0 pop rdi 87 | A2 pop rsi 88 | A4 pop r13 89 | A7 mov rsp,rbp 90 | AA pop rbp 91 | AC ret 92 | ``` 93 | -------------------------------------------------------------------------------- /brainfuck/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /graphs 3 | /mcode 4 | /.idea 5 | *.bin 6 | *.svg 7 | *.turboir 8 | -------------------------------------------------------------------------------- /brainfuck/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "asm" 5 | version = "0.1.0" 6 | source = "git+https://github.com/addrianyy/assembler#12b5ae73788900a5aa4a94c3131d447423fcd2ec" 7 | 8 | [[package]] 9 | name = "brainfuck" 10 | version = "0.1.0" 11 | dependencies = [ 12 | "turbo_ir", 13 | ] 14 | 15 | [[package]] 16 | name = "smallvec" 17 | version = "1.5.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" 20 | 21 | [[package]] 22 | name = "turbo_ir" 23 | version = "0.1.0" 24 | dependencies = [ 25 | "asm", 26 | "smallvec", 27 | ] 28 | -------------------------------------------------------------------------------- /brainfuck/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "brainfuck" 3 | version = "0.1.0" 4 | authors = ["addrianyy "] 5 | edition = "2018" 6 | 7 | [profile.release] 8 | debug = true 9 | 10 | [dependencies.turbo_ir] 11 | path = "../turbo_ir" 12 | #features = ["self_profile"] 13 | -------------------------------------------------------------------------------- /brainfuck/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | use std::io::Read; 3 | use std::fs::File; 4 | 5 | use turbo_ir as ir; 6 | 7 | extern "win64" fn read_char() -> u8 { 8 | std::io::stdin() 9 | .bytes() 10 | .next() 11 | .unwrap_or(Ok(0)) 12 | .unwrap_or(0) 13 | } 14 | 15 | extern "win64" fn print_char(ch: u8) { 16 | print!("{}", ch as char); 17 | } 18 | 19 | fn main() { 20 | let input_file = std::env::args().nth(1).unwrap_or_else(|| { 21 | println!("Usage: brainfuck "); 22 | std::process::exit(1); 23 | }); 24 | 25 | let program = std::fs::read_to_string(input_file).unwrap(); 26 | let mut ir = ir::Module::new(); 27 | 28 | let input = unsafe { 29 | ir.create_external_function("read_char", Some(ir::Type::U8), 30 | vec![], read_char as usize) 31 | }; 32 | 33 | let output = unsafe { 34 | ir.create_external_function("print_char", None, 35 | vec![ir::Type::U8], print_char as usize) 36 | }; 37 | 38 | let function = ir.create_function("main", None, vec![ir::Type::U8.ptr()]); 39 | 40 | ir.switch_function(function); 41 | 42 | let buffer = ir.argument(0); 43 | 44 | let pos_one_u8 = ir.iconst(1u8, ir::Type::U8); 45 | let neg_one_u8 = ir.iconst(1u8.wrapping_neg(), ir::Type::U8); 46 | let pos_one_u64 = ir.iconst(1u64, ir::Type::U64); 47 | let neg_one_u64 = ir.iconst(1u64.wrapping_neg(), ir::Type::U64); 48 | let zero = ir.iconst(0u8, ir::Type::U8); 49 | 50 | let index = { 51 | let index = ir.stack_alloc(ir::Type::U64, 1); 52 | let init = ir.iconst(0u32, ir::Type::U64); 53 | 54 | ir.store(index, init); 55 | 56 | index 57 | }; 58 | 59 | macro_rules! get { 60 | () => {{ 61 | let index = ir.load(index); 62 | let ptr = ir.get_element_ptr(buffer, index); 63 | 64 | ir.load(ptr) 65 | }} 66 | } 67 | 68 | macro_rules! set { 69 | ($value: expr) => {{ 70 | let value = $value; 71 | let index = ir.load(index); 72 | let ptr = ir.get_element_ptr(buffer, index); 73 | 74 | ir.store(ptr, value); 75 | }} 76 | } 77 | 78 | let mut loops = Vec::new(); 79 | 80 | for ch in program.chars() { 81 | match ch { 82 | '>' | '<' => { 83 | let value = match ch { 84 | '>' => pos_one_u64, 85 | '<' => neg_one_u64, 86 | _ => unreachable!(), 87 | }; 88 | 89 | let i = ir.load(index); 90 | let i = ir.add(i, value); 91 | 92 | ir.store(index, i); 93 | } 94 | '+' | '-' => { 95 | let value = match ch { 96 | '+' => pos_one_u8, 97 | '-' => neg_one_u8, 98 | _ => unreachable!(), 99 | }; 100 | 101 | let new = get!(); 102 | let new = ir.add(new, value); 103 | 104 | set!(new); 105 | } 106 | ',' => { 107 | let value = ir.call(input, vec![]).unwrap(); 108 | 109 | set!(value); 110 | } 111 | '.' => { 112 | let value = get!(); 113 | 114 | ir.call(output, vec![value]); 115 | } 116 | '[' => { 117 | let header = ir.create_label(); 118 | let body = ir.create_label(); 119 | let after = ir.create_label(); 120 | 121 | ir.branch(header); 122 | ir.switch_label(header); 123 | 124 | let value = get!(); 125 | let cond = ir.compare_ne(value, zero); 126 | 127 | ir.branch_cond(cond, body, after); 128 | ir.switch_label(body); 129 | 130 | loops.push((header, after)); 131 | } 132 | ']' => { 133 | let (header, after) = loops.pop().unwrap(); 134 | 135 | ir.branch(header); 136 | ir.switch_label(after); 137 | } 138 | _ => {}, 139 | } 140 | } 141 | 142 | assert!(loops.is_empty(), "Unmatched loops."); 143 | 144 | ir.ret(None); 145 | 146 | let start = Instant::now(); 147 | 148 | ir.finalize(); 149 | 150 | let finalize_time = start.elapsed().as_secs_f64(); 151 | 152 | let passes = &[ 153 | ir::passes::const_propagate(), 154 | ir::passes::remove_ineffective_operations(), 155 | ir::passes::simplify_cfg(), 156 | ir::passes::simplify_compares(), 157 | ir::passes::simplify_expressions(), 158 | ir::passes::remove_dead_code(), 159 | ir::passes::memory_to_ssa(), 160 | ir::passes::deduplicate_fast(), 161 | ir::passes::remove_known_loads_fast(), 162 | ir::passes::remove_dead_stores_fast(), 163 | ir::passes::undefined_propagate(), 164 | ir::passes::minimize_phis(), 165 | ir::passes::branch_to_select(), 166 | ir::passes::reorder(), 167 | ]; 168 | 169 | let start = Instant::now(); 170 | 171 | ir.optimize(&ir::PassManager::with_passes(passes), false); 172 | 173 | let optimize_time = start.elapsed().as_secs_f64(); 174 | 175 | if true { 176 | ir.dump_function_text(function, &mut File::create("result.turboir").unwrap()).unwrap(); 177 | } 178 | 179 | type Func = unsafe extern "win64" fn(*mut u8); 180 | 181 | let start = Instant::now(); 182 | 183 | let machine_code = ir.generate_machine_code(&ir::backends::X86Backend); 184 | let function_ptr = unsafe { 185 | machine_code.function_ptr::(function) 186 | }; 187 | 188 | let codegen_time = start.elapsed().as_secs_f64(); 189 | 190 | println!(); 191 | println!("Finalization in {}s.", finalize_time); 192 | println!("Optimization in {}s.", optimize_time); 193 | println!("Codegen in {}s.", codegen_time); 194 | println!("Total in {}s.", finalize_time + codegen_time + optimize_time); 195 | println!(); 196 | 197 | let mut buffer = vec![0u8; 30 * 1000]; 198 | 199 | if true { 200 | std::fs::write("asm_dump.bin", machine_code.function_buffer(function)).unwrap(); 201 | } 202 | 203 | println!("Running..."); 204 | 205 | let start = Instant::now(); 206 | 207 | if true { 208 | unsafe { 209 | function_ptr(buffer.as_mut_ptr()); 210 | } 211 | } 212 | 213 | let running_time = start.elapsed().as_secs_f64(); 214 | 215 | println!("Executed in {}s.", running_time); 216 | } 217 | -------------------------------------------------------------------------------- /img/ir_optimized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addrianyy/compiler/7c231d1228cbc54b9498c41582b282127f400561/img/ir_optimized.png -------------------------------------------------------------------------------- /img/ir_unoptimized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addrianyy/compiler/7c231d1228cbc54b9498c41582b282127f400561/img/ir_unoptimized.png -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod parser; 2 | mod compiler; 3 | mod runtimelib; 4 | 5 | use parser::Ty; 6 | 7 | fn recreate_directory(path: &str) { 8 | let _ = std::fs::remove_dir_all(path); 9 | 10 | std::fs::create_dir_all(path).unwrap_or_else(|err| { 11 | panic!("Failed to recreate {}. {:?}", path, err); 12 | }); 13 | } 14 | 15 | fn main() { 16 | let input_file = std::env::args().nth(1).unwrap_or_else(|| { 17 | println!("Usage: compiler "); 18 | std::process::exit(1); 19 | }); 20 | 21 | let no_optimize = std::env::args().nth(2) 22 | .map(|s| s == "-noopt") 23 | .unwrap_or(false); 24 | 25 | let source = std::fs::read_to_string(input_file).unwrap(); 26 | let parsed = parser::parse(&source); 27 | let mut compiled = compiler::compile(&parsed); 28 | 29 | let passes = if no_optimize { 30 | vec![] 31 | } else { 32 | vec![ 33 | turbo_ir::passes::const_propagate(), 34 | turbo_ir::passes::remove_ineffective_operations(), 35 | turbo_ir::passes::simplify_cfg(), 36 | turbo_ir::passes::simplify_compares(), 37 | turbo_ir::passes::simplify_expressions(), 38 | turbo_ir::passes::remove_dead_code(), 39 | turbo_ir::passes::memory_to_ssa(), 40 | turbo_ir::passes::deduplicate_precise(), 41 | turbo_ir::passes::remove_known_loads_precise(), 42 | turbo_ir::passes::remove_dead_stores_precise(), 43 | turbo_ir::passes::undefined_propagate(), 44 | turbo_ir::passes::minimize_phis(), 45 | turbo_ir::passes::optimize_known_bits(), 46 | turbo_ir::passes::propagate_block_invariants(), 47 | turbo_ir::passes::branch_to_select(), 48 | turbo_ir::passes::global_reorder(), 49 | turbo_ir::passes::reorder(), 50 | ] 51 | }; 52 | 53 | let pass_manager = turbo_ir::PassManager::with_passes(&passes); 54 | 55 | compiled.ir.optimize(&pass_manager, false); 56 | 57 | recreate_directory("mcode"); 58 | recreate_directory("graphs"); 59 | 60 | for (prototype, function) in &compiled.functions { 61 | let name = &prototype.name; 62 | 63 | compiled.ir.dump_function_text_stdout(*function); 64 | compiled.ir.dump_function_graph(*function, &format!("graphs/{}.pdf", name)); 65 | 66 | println!(); 67 | } 68 | 69 | let mcode = compiled.ir.generate_machine_code(&turbo_ir::backends::X86Backend); 70 | 71 | for (prototype, function) in &compiled.functions { 72 | let buffer = mcode.function_buffer(*function); 73 | let name = &prototype.name; 74 | 75 | std::fs::write(format!("mcode/{}.bin", name), buffer).unwrap(); 76 | 77 | if name == "main" { 78 | assert!(prototype.return_ty == Ty::U32, "Invalid main return value."); 79 | assert!(prototype.args.is_empty(), "Invalid main arguments."); 80 | 81 | type Func = extern "win64" fn() -> u32; 82 | 83 | let result = unsafe { 84 | let func = mcode.function_ptr::(*function); 85 | 86 | func() 87 | }; 88 | 89 | println!("return value: {}.", result); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/parser/ast.rs: -------------------------------------------------------------------------------- 1 | use super::ty::Ty; 2 | use super::lexer::Token; 3 | 4 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 5 | pub enum UnaryOp { 6 | Neg, 7 | Not, 8 | Ref, 9 | Deref, 10 | } 11 | 12 | impl UnaryOp { 13 | pub(super) fn from_token(token: &Token) -> Option { 14 | Some(match token { 15 | Token::Sub => UnaryOp::Neg, 16 | Token::Not => UnaryOp::Not, 17 | Token::And => UnaryOp::Ref, 18 | Token::Mul => UnaryOp::Deref, 19 | _ => return None, 20 | }) 21 | } 22 | } 23 | 24 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 25 | pub enum BinaryOp { 26 | Add, 27 | Sub, 28 | Mul, 29 | Mod, 30 | Div, 31 | Shr, 32 | Shl, 33 | And, 34 | Or, 35 | Xor, 36 | Equal, 37 | NotEqual, 38 | Gt, 39 | Lt, 40 | Gte, 41 | Lte, 42 | } 43 | 44 | impl BinaryOp { 45 | pub(super) fn from_token(token: &Token) -> Option { 46 | Some(match token { 47 | Token::Add => BinaryOp::Add, 48 | Token::Sub => BinaryOp::Sub, 49 | Token::Mul => BinaryOp::Mul, 50 | Token::Div => BinaryOp::Div, 51 | Token::Mod => BinaryOp::Mod, 52 | Token::Shr => BinaryOp::Shr, 53 | Token::Shl => BinaryOp::Shl, 54 | Token::And => BinaryOp::And, 55 | Token::Or => BinaryOp::Or, 56 | Token::Xor => BinaryOp::Xor, 57 | Token::Equal => BinaryOp::Equal, 58 | Token::NotEqual => BinaryOp::NotEqual, 59 | Token::Gt => BinaryOp::Gt, 60 | Token::Lt => BinaryOp::Lt, 61 | Token::Gte => BinaryOp::Gte, 62 | Token::Lte => BinaryOp::Lte, 63 | _ => return None, 64 | }) 65 | } 66 | 67 | pub(super) fn precedence(&self) -> i32 { 68 | match self { 69 | BinaryOp::Mul | BinaryOp::Mod | BinaryOp::Div => 60, 70 | BinaryOp::Add | BinaryOp::Sub => 50, 71 | BinaryOp::Shl | BinaryOp::Shr => 40, 72 | BinaryOp::Gt | BinaryOp::Lt | BinaryOp::Gte | BinaryOp::Lte => 35, 73 | BinaryOp::Equal | BinaryOp::NotEqual => 33, 74 | BinaryOp::And => 30, 75 | BinaryOp::Xor => 20, 76 | BinaryOp::Or => 10, 77 | } 78 | } 79 | } 80 | 81 | #[derive(Clone, Debug, PartialEq, Eq)] 82 | pub enum Expr { 83 | Variable(String), 84 | Unary { 85 | op: UnaryOp, 86 | value: Box, 87 | }, 88 | Binary { 89 | left: Box, 90 | op: BinaryOp, 91 | right: Box, 92 | }, 93 | Number { 94 | value: u64, 95 | ty: Ty, 96 | }, 97 | Array { 98 | array: Box, 99 | index: Box, 100 | }, 101 | Call { 102 | target: String, 103 | args: Vec, 104 | }, 105 | Cast { 106 | value: Box, 107 | ty: Ty, 108 | }, 109 | } 110 | 111 | #[derive(Clone, Debug, PartialEq, Eq)] 112 | pub enum Stmt { 113 | Assign { 114 | variable: TypedExpr, 115 | value: TypedExpr, 116 | }, 117 | Declare { 118 | /// Actual type. 119 | ty: Ty, 120 | /// Declaration type. 121 | decl_ty: Ty, 122 | name: String, 123 | value: Option, 124 | array: Option, 125 | }, 126 | While { 127 | condition: TypedExpr, 128 | body: Body, 129 | }, 130 | If { 131 | arms: Vec<(TypedExpr, Body)>, 132 | default: Option, 133 | }, 134 | For { 135 | init: Option>, 136 | condition: Option, 137 | step: Option>, 138 | body: Body, 139 | }, 140 | Return(Option), 141 | Expr(TypedExpr), 142 | Break, 143 | Continue, 144 | } 145 | 146 | #[derive(Clone, Debug, PartialEq, Eq)] 147 | pub struct TypedExpr { 148 | pub ty: Option, 149 | pub expr: Expr, 150 | } 151 | 152 | impl TypedExpr { 153 | pub(super) fn new(expr: Expr) -> Self { 154 | Self { 155 | ty: None, 156 | expr, 157 | } 158 | } 159 | } 160 | 161 | impl std::ops::Deref for TypedExpr { 162 | type Target = Expr; 163 | 164 | fn deref(&self) -> &Expr { 165 | &self.expr 166 | } 167 | } 168 | 169 | pub type Body = Vec; 170 | pub type BodyRef<'a> = &'a [Stmt]; 171 | -------------------------------------------------------------------------------- /src/parser/print.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | use super::{Stmt, Expr, BodyRef, Function, FunctionPrototype, ParsedModule}; 4 | 5 | const INDENT_STRING: &str = " "; 6 | const SUBITEM_INDENT: &str = " "; 7 | 8 | fn print_body(body: BodyRef, w: &mut W, indent: usize) -> io::Result<()> { 9 | for stmt in body { 10 | stmt.print(w, indent)?; 11 | } 12 | 13 | Ok(()) 14 | } 15 | 16 | impl Expr { 17 | pub fn print(&self, w: &mut W, indent: usize) -> io::Result<()> { 18 | let mut j = String::new(); 19 | 20 | for _ in 0..indent { 21 | j.push_str(INDENT_STRING); 22 | } 23 | 24 | let i = j.clone() + SUBITEM_INDENT; 25 | 26 | match self { 27 | Expr::Variable(name) => { 28 | writeln!(w, "{}{}", i, name)?; 29 | } 30 | Expr::Cast { value, ty } => { 31 | writeln!(w, "{}CastExpr to {}", i, ty)?; 32 | 33 | writeln!(w, "{}Value:", i)?; 34 | value.print(w, indent + 1)?; 35 | } 36 | Expr::Unary { op, value } => { 37 | writeln!(w, "{}UnaryExpr", j)?; 38 | 39 | writeln!(w, "{}Op: {:?}", i, op)?; 40 | 41 | writeln!(w, "{}Value:", i)?; 42 | value.print(w, indent + 1)?; 43 | } 44 | Expr::Binary { left, op, right } => { 45 | writeln!(w, "{}BinaryExpr", j)?; 46 | 47 | writeln!(w, "{}Op: {:?}", i, op)?; 48 | 49 | writeln!(w, "{}Left:", i)?; 50 | left.print(w, indent + 1)?; 51 | 52 | writeln!(w, "{}Right:", i)?; 53 | right.print(w, indent + 1)?; 54 | } 55 | Expr::Number { value, .. } => { 56 | writeln!(w, "{}{}", i, value)?; 57 | } 58 | Expr::Array { array, index } => { 59 | writeln!(w, "{}ArrayExpr", j)?; 60 | 61 | writeln!(w, "{}Array:", i)?; 62 | array.print(w, indent + 1)?; 63 | 64 | writeln!(w, "{}Index:", i)?; 65 | index.print(w, indent + 1)?; 66 | } 67 | Expr::Call { target, args } => { 68 | writeln!(w, "{}CallExpr", j)?; 69 | 70 | writeln!(w, "{}Target: {}", i, target)?; 71 | 72 | writeln!(w, "{}Args:", i)?; 73 | for arg in args { 74 | arg.print(w, indent + 1)?; 75 | } 76 | } 77 | } 78 | 79 | Ok(()) 80 | } 81 | } 82 | 83 | impl Stmt { 84 | pub fn print(&self, w: &mut W, indent: usize) -> io::Result<()> { 85 | let mut j = String::new(); 86 | 87 | for _ in 0..indent { 88 | j.push_str(INDENT_STRING); 89 | } 90 | 91 | let i = j.clone() + SUBITEM_INDENT; 92 | 93 | match self { 94 | Stmt::Assign { variable, value } => { 95 | writeln!(w, "{}AssignStmt", j)?; 96 | 97 | writeln!(w, "{}Target:", i)?; 98 | variable.print(w, indent + 1)?; 99 | 100 | writeln!(w, "{}Value:", i)?; 101 | value.print(w, indent + 1)?; 102 | } 103 | Stmt::Declare { ty, decl_ty, name, value, array } => { 104 | writeln!(w, "{}DeclareStmt", j)?; 105 | 106 | writeln!(w, "{}Name: {}", i, name)?; 107 | writeln!(w, "{}DeclTy: {}", i, decl_ty)?; 108 | writeln!(w, "{}Ty: {}", i, ty)?; 109 | 110 | if let Some(value) = value { 111 | writeln!(w, "{}Value:", i)?; 112 | value.print(w, indent + 1)?; 113 | } 114 | 115 | if let Some(array) = array { 116 | writeln!(w, "{}ArraySize:", i)?; 117 | array.print(w, indent + 1)?; 118 | } 119 | } 120 | Stmt::While { condition, body } => { 121 | writeln!(w, "{}WhileStmt", j)?; 122 | 123 | writeln!(w, "{}Condition:", i)?; 124 | condition.print(w, indent + 1)?; 125 | 126 | writeln!(w, "{}Body:", i)?; 127 | print_body(body, w, indent + 1)?; 128 | } 129 | Stmt::For { init, condition, step, body } => { 130 | writeln!(w, "{}ForStmt", j)?; 131 | 132 | if let Some(init) = init { 133 | writeln!(w, "{}Init:", i)?; 134 | init.print(w, indent + 1)?; 135 | } 136 | 137 | if let Some(condition) = condition { 138 | writeln!(w, "{}Condition:", i)?; 139 | condition.print(w, indent + 1)?; 140 | } 141 | 142 | if let Some(step) = step { 143 | writeln!(w, "{}Step:", i)?; 144 | step.print(w, indent + 1)?; 145 | } 146 | 147 | writeln!(w, "{}Body:", i)?; 148 | print_body(body, w, indent + 1)?; 149 | } 150 | Stmt::If { arms, default } => { 151 | writeln!(w, "{}IfStmt", j)?; 152 | 153 | for (index, arm) in arms.iter().enumerate() { 154 | writeln!(w, "{}Arm {} condition:", i, index)?; 155 | arm.0.print(w, indent + 1)?; 156 | 157 | writeln!(w, "{}Arm {} body:", i, index)?; 158 | print_body(&arm.1, w, indent + 1)?; 159 | } 160 | 161 | if let Some(default) = default { 162 | writeln!(w, "{}Default arm body:", i)?; 163 | print_body(default, w, indent + 1)?; 164 | } 165 | } 166 | Stmt::Return(value) => { 167 | writeln!(w, "{}ReturnStmt", j)?; 168 | 169 | if let Some(value) = value { 170 | writeln!(w, "{}Value:", i)?; 171 | value.print(w, indent + 1)?; 172 | } else { 173 | writeln!(w, "{}Value: none", i)?; 174 | } 175 | } 176 | Stmt::Expr(value) => { 177 | value.print(w, indent)?; 178 | } 179 | Stmt::Break => writeln!(w, "{}BreakStmt", j)?, 180 | Stmt::Continue => writeln!(w, "{}ContinueStmt", j)?, 181 | } 182 | 183 | Ok(()) 184 | } 185 | } 186 | 187 | impl FunctionPrototype { 188 | pub fn print(&self, w: &mut W) -> io::Result<()> { 189 | writeln!(w, "Name: {}", self.name)?; 190 | writeln!(w, "ReturnTy: {}", self.return_ty)?; 191 | writeln!(w, "Args:")?; 192 | 193 | for (name, ty) in &self.args { 194 | writeln!(w, "{}{} {}", INDENT_STRING, ty, name)?; 195 | } 196 | 197 | Ok(()) 198 | } 199 | } 200 | 201 | impl Function { 202 | pub fn print(&self, w: &mut W) -> io::Result<()> { 203 | self.prototype.print(w)?; 204 | 205 | if let Some(body) = &self.body { 206 | writeln!(w, "Body:")?; 207 | print_body(&body, w, 1)?; 208 | } else { 209 | writeln!(w, "Extern")?; 210 | } 211 | 212 | Ok(()) 213 | } 214 | } 215 | 216 | impl ParsedModule { 217 | #[allow(unused)] 218 | pub fn print(&self, w: &mut W) -> io::Result<()> { 219 | for func in &self.functions { 220 | func.print(w)?; 221 | writeln!(w)?; 222 | } 223 | 224 | Ok(()) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/parser/ty.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use super::lexer::{Token, Keyword}; 4 | 5 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 6 | pub enum TyKind { 7 | U8, 8 | U16, 9 | U32, 10 | U64, 11 | I8, 12 | I16, 13 | I32, 14 | I64, 15 | Void, 16 | } 17 | 18 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 19 | pub struct Ty { 20 | kind: TyKind, 21 | indirection: usize, 22 | } 23 | 24 | impl Ty { 25 | pub const U8: Ty = Ty { kind: TyKind::U8, indirection: 0 }; 26 | pub const U16: Ty = Ty { kind: TyKind::U16, indirection: 0 }; 27 | pub const U32: Ty = Ty { kind: TyKind::U32, indirection: 0 }; 28 | pub const U64: Ty = Ty { kind: TyKind::U64, indirection: 0 }; 29 | pub const I8: Ty = Ty { kind: TyKind::I8, indirection: 0 }; 30 | pub const I16: Ty = Ty { kind: TyKind::I16, indirection: 0 }; 31 | pub const I32: Ty = Ty { kind: TyKind::I32, indirection: 0 }; 32 | pub const I64: Ty = Ty { kind: TyKind::I64, indirection: 0 }; 33 | 34 | #[allow(non_upper_case_globals)] 35 | pub const Void: Ty = Ty { kind: TyKind::Void, indirection: 0 }; 36 | 37 | pub(super) fn from_token(token: &Token) -> Option { 38 | Some(match token { 39 | Token::Keyword(Keyword::U8) => Ty::U8, 40 | Token::Keyword(Keyword::U16) => Ty::U16, 41 | Token::Keyword(Keyword::U32) => Ty::U32, 42 | Token::Keyword(Keyword::U64) => Ty::U64, 43 | Token::Keyword(Keyword::I8) => Ty::I8, 44 | Token::Keyword(Keyword::I16) => Ty::I16, 45 | Token::Keyword(Keyword::I32) => Ty::I32, 46 | Token::Keyword(Keyword::I64) => Ty::I64, 47 | Token::Keyword(Keyword::Void) => Ty::Void, 48 | _ => return None, 49 | }) 50 | } 51 | 52 | pub fn conversion_rank(&self) -> u32 { 53 | assert!(!self.is_pointer(), "Cannot get conversion rank of pointer types."); 54 | 55 | match self.kind { 56 | TyKind::U8 => 1, 57 | TyKind::U16 => 2, 58 | TyKind::U32 => 3, 59 | TyKind::U64 => 4, 60 | TyKind::I8 => 1, 61 | TyKind::I16 => 2, 62 | TyKind::I32 => 3, 63 | TyKind::I64 => 4, 64 | _ => unreachable!(), 65 | } 66 | } 67 | 68 | pub fn ptr(&self) -> Ty { 69 | Ty { 70 | kind: self.kind, 71 | indirection: self.indirection + 1, 72 | } 73 | } 74 | 75 | pub fn strip_pointer(&self) -> Option { 76 | Some(Ty { 77 | kind: self.kind, 78 | indirection: self.indirection.checked_sub(1)?, 79 | }) 80 | } 81 | 82 | pub fn is_arithmetic_type(&self) -> bool { 83 | self.indirection == 0 && self.kind != TyKind::Void 84 | } 85 | 86 | pub fn is_pointer(&self) -> bool { 87 | self.indirection > 0 88 | } 89 | 90 | pub fn is_signed(&self) -> bool { 91 | if self.is_pointer() { 92 | return false; 93 | } 94 | 95 | matches!(self.kind, TyKind::I8 | TyKind::I16 | TyKind::I32 | TyKind::I64) 96 | } 97 | 98 | pub fn is_nonvoid_ptr(&self) -> bool { 99 | if !self.is_pointer() { 100 | return false; 101 | } 102 | 103 | if self.indirection == 1 && self.kind == TyKind::Void { 104 | return false; 105 | } 106 | 107 | true 108 | } 109 | 110 | pub fn size(&self) -> usize { 111 | if self.is_pointer() { 112 | return 8; 113 | } 114 | 115 | match self.kind { 116 | TyKind::I8 | TyKind::U8 => 1, 117 | TyKind::I16 | TyKind::U16 => 2, 118 | TyKind::I32 | TyKind::U32 => 4, 119 | TyKind::I64 | TyKind::U64 => 8, 120 | TyKind::Void => panic!("Cannot get size of void."), 121 | } 122 | } 123 | 124 | pub fn destructure(&self) -> (TyKind, usize) { 125 | (self.kind, self.indirection) 126 | } 127 | } 128 | 129 | impl fmt::Display for Ty { 130 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 131 | match self.kind { 132 | TyKind::U8 => write!(f, "u8")?, 133 | TyKind::U16 => write!(f, "u16")?, 134 | TyKind::U32 => write!(f, "u32")?, 135 | TyKind::U64 => write!(f, "u64")?, 136 | TyKind::I8 => write!(f, "i8")?, 137 | TyKind::I16 => write!(f, "i16")?, 138 | TyKind::I32 => write!(f, "i32")?, 139 | TyKind::I64 => write!(f, "i64")?, 140 | TyKind::Void => write!(f, "void")?, 141 | } 142 | 143 | for _ in 0..self.indirection { 144 | write!(f, "*")?; 145 | } 146 | 147 | Ok(()) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/runtimelib.rs: -------------------------------------------------------------------------------- 1 | pub fn runtime_function(name: &str) -> Option { 2 | Some(match name { 3 | "print" => print as usize, 4 | _ => return None, 5 | }) 6 | } 7 | 8 | unsafe extern "win64" fn print(buffer: *const u8) { 9 | let mut size = 0; 10 | 11 | loop { 12 | if *buffer.add(size) == 0 { 13 | break; 14 | } 15 | 16 | size += 1; 17 | } 18 | 19 | let slice = std::slice::from_raw_parts(buffer, size); 20 | 21 | if let Ok(string) = std::str::from_utf8(slice) { 22 | println!("{}", string); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/main.tc: -------------------------------------------------------------------------------- 1 | extern void print(u8* str); 2 | 3 | void print_num(u32 num) { 4 | u8 buffer[256]; 5 | i64 i = 255; 6 | 7 | buffer[i] = 0; 8 | i -= 1; 9 | 10 | if (num == 0) { 11 | buffer[i] = 48u8; 12 | i -= 1; 13 | } 14 | 15 | while (num > 0) { 16 | u8 char = (num % 10u8) + 48u8; 17 | 18 | buffer[i] = char; 19 | i -= 1; 20 | 21 | num /= 10u8; 22 | } 23 | 24 | print(&buffer[i + 1]); 25 | } 26 | 27 | u32 main() { 28 | u32 count = 8; 29 | u32 a = 0; 30 | u32 b = 1; 31 | 32 | for (u32 i = 0; i < count; i += 1) { 33 | print_num(a); 34 | 35 | u32 t = a + b; 36 | a = b; 37 | b = t; 38 | } 39 | 40 | return 0; 41 | } 42 | 43 | i32 test123(i32 x, i32 y) { 44 | if (x == 4) { 45 | if (x == 8) { 46 | return 44; 47 | } 48 | } 49 | 50 | return 12; 51 | } -------------------------------------------------------------------------------- /tests/optimize_1.tc: -------------------------------------------------------------------------------- 1 | extern void print(u8* str); 2 | 3 | i32 optimize_1(u8* buffer) { 4 | i32 index = 0; 5 | 6 | u8* null = (u8*)0; 7 | if (buffer == null) { 8 | return 0; 9 | } 10 | 11 | while (buffer[index] != 0u8) { 12 | buffer[index] += 1u8; 13 | buffer[index] += 1u8; 14 | if (buffer[index] == 12u8) { 15 | while (1) { 16 | } 17 | } 18 | 19 | index += 1; 20 | } 21 | 22 | return 312; 23 | } 24 | 25 | i32 optimize_2(i32* x) { 26 | *x = 1337; 27 | 28 | while (1) { 29 | } 30 | 31 | return 1; 32 | } 33 | 34 | i32 optimize_3(i32 a) { 35 | if (a != 0) { 36 | return 123; 37 | } else { 38 | return 321; 39 | } 40 | 41 | return 1; 42 | } 43 | 44 | 45 | i32 optimize_4(i32 a) { 46 | if (a == 4) { 47 | a = 555; 48 | } else { 49 | a = 823; 50 | } 51 | 52 | return a; 53 | } 54 | 55 | u32 optimize_5(u32 a) { 56 | return (~(~a & 0xffffffff)) * (1u32 + (a ^ a)); 57 | } 58 | 59 | i32 optimize_6(i32 count) { 60 | i32 total = 0; 61 | 62 | for (i32 i = 0; i < count; i += 1) { 63 | for (i32 j = 0; j < i; j += 1) { 64 | total += j; 65 | } 66 | } 67 | 68 | return total; 69 | } 70 | 71 | void optimize_7() { 72 | i32 ptr = 0; 73 | 74 | *(i8*)&ptr = 13i8; 75 | } 76 | 77 | i32 optimize_8(i32 value) { 78 | i32 result; 79 | 80 | if (value == 22) { 81 | result = 49; 82 | } else { 83 | result += 8; 84 | } 85 | 86 | return result; 87 | } 88 | 89 | i32 optimize_9(i32 value) { 90 | i32 output; 91 | 92 | if (value == 4) { 93 | output = 55; 94 | } 95 | 96 | if (value > 33) { 97 | output += 44; 98 | } 99 | 100 | if (value > 88) { 101 | output -= 4; 102 | } 103 | 104 | return output; 105 | } 106 | 107 | i32 optimize_10(i32 count) { 108 | i32 total = 0; 109 | 110 | for (i32 i = 0; i < count; i += 1) { 111 | total += i; 112 | } 113 | 114 | return total; 115 | } 116 | 117 | i32 optimize_11(i32 x) { 118 | return (1 + 2 + x) * (x + (1+2)); 119 | } 120 | 121 | void call_x() { 122 | } 123 | 124 | void optimize_12(i32 x) { 125 | while (x == 7) { 126 | call_x(); 127 | x = 0; 128 | } 129 | } 130 | 131 | u32 optimize_13(u32 a) { 132 | if (a == 20) { 133 | a += 4; 134 | } else { 135 | a = 1337; 136 | } 137 | 138 | return a & 0xfff; 139 | } 140 | -------------------------------------------------------------------------------- /tests/optimize_2.tc: -------------------------------------------------------------------------------- 1 | u32 optimize_1(i8 x, i8 y) { 2 | i8 r = 0; 3 | 4 | if (x == 0i8) { 5 | r = x | 0x80i8; 6 | } else { 7 | r = y | 0x80i8; 8 | } 9 | 10 | i32 zz = r; 11 | 12 | return zz & 0x8000i32; 13 | } 14 | 15 | u32 optimize_2(i32 x, i32 y) { 16 | x &= ~0x80000000i32; 17 | y &= ~0x80000000i32; 18 | 19 | if (-x < y) { 20 | return 22; 21 | } 22 | 23 | return 33; 24 | } 25 | 26 | u32 optimize_3(i32 x, i32 y) { 27 | x &= ~0xFF000000i32; 28 | y &= ~0xFF000000i32; 29 | 30 | x |= 0xF0000000i32; 31 | y |= 0xFF000000i32; 32 | 33 | if (x < y) { 34 | return 22; 35 | } 36 | 37 | return 33; 38 | } 39 | 40 | u32 optimize_4(u32 x, u32 y) { 41 | i64 x2 = x; 42 | i64 y2 = y; 43 | 44 | x2 ^= (1u64 << 63); 45 | y2 |= 77i64; 46 | 47 | if (x2 > y2) { 48 | return 123; 49 | } 50 | 51 | return y2 & 0b1101i64; 52 | } 53 | 54 | u32 optimize_5(i32 x, i32 y) { 55 | x &= ~0xff; 56 | x |= 0b1011; 57 | 58 | y &= ~0xff; 59 | y |= 0b0101; 60 | 61 | return (x + y) & 0xff; 62 | } 63 | 64 | u32 optimize_6(u32 x, u32 y) { 65 | if (x == (3 + 1 * 2)) { 66 | if (x == 33u32) { 67 | x = x + y; 68 | } else { 69 | x = 0; 70 | x += 1; 71 | x += 1; 72 | x -= 1; 73 | } 74 | } else { 75 | x &= 0x00u32; 76 | 77 | i64 z = y; 78 | if (z > -5) { 79 | x ^= 1; 80 | } 81 | } 82 | 83 | 84 | return (u8)x + 33u8; 85 | } 86 | -------------------------------------------------------------------------------- /tests/test.turboir: -------------------------------------------------------------------------------- 1 | u32 main(u32* ptr, u32 value) { 2 | entry: 3 | g = gep u32* ptr, u32 44 4 | x = add u32 value, 123 5 | read = load u32, u32* g 6 | y = add u32 x, read 7 | 8 | ret u32 y 9 | } -------------------------------------------------------------------------------- /turbo_ir/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /graphs 3 | Cargo.lock 4 | *.svg 5 | *.bin 6 | -------------------------------------------------------------------------------- /turbo_ir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "turbo_ir" 3 | version = "0.1.0" 4 | authors = ["addrianyy "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | asm = { git = "https://github.com/addrianyy/assembler" } 9 | smallvec = "1.5.0" 10 | 11 | [features] 12 | self_profile = [] 13 | -------------------------------------------------------------------------------- /turbo_ir/src/bin/main.rs: -------------------------------------------------------------------------------- 1 | use turbo_ir as ir; 2 | 3 | use ir::passes::IRPass; 4 | 5 | use std::io; 6 | 7 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 8 | struct PassID(usize); 9 | 10 | struct PassRegistry { 11 | passes: Vec<(&'static str, IRPass)>, 12 | } 13 | 14 | impl PassRegistry { 15 | fn new() -> Self { 16 | let passes = vec![ 17 | ("opopt", ir::passes::remove_ineffective_operations()), 18 | ("expr-simplify", ir::passes::simplify_expressions()), 19 | ("undefprop", ir::passes::undefined_propagate()), 20 | ("dse", ir::passes::remove_dead_stores_precise()), 21 | ("rle", ir::passes::remove_known_loads_precise()), 22 | ("dse-fast", ir::passes::remove_dead_stores_fast()), 23 | ("rle-fast", ir::passes::remove_known_loads_fast()), 24 | ("cmp-simplify", ir::passes::simplify_compares()), 25 | ("invprop", ir::passes::propagate_block_invariants()), 26 | ("select", ir::passes::branch_to_select()), 27 | ("dce", ir::passes::remove_dead_code()), 28 | ("constprop", ir::passes::const_propagate()), 29 | ("mem2ssa", ir::passes::memory_to_ssa()), 30 | ("phimin", ir::passes::minimize_phis()), 31 | ("cfg-simplify", ir::passes::simplify_cfg()), 32 | ("dedup", ir::passes::deduplicate_precise()), 33 | ("dedup-fast", ir::passes::deduplicate_fast()), 34 | ("bitopt", ir::passes::optimize_known_bits()), 35 | ("reorder", ir::passes::reorder()), 36 | ("greorder", ir::passes::global_reorder()), 37 | ]; 38 | 39 | Self { passes } 40 | } 41 | 42 | fn pass_by_name(&self, pass_name: &str) -> Option { 43 | for (index, (name, _)) in self.passes.iter().enumerate() { 44 | if *name == pass_name { 45 | return Some(PassID(index)); 46 | } 47 | } 48 | 49 | None 50 | } 51 | 52 | fn all_passes(&self) -> Vec { 53 | (0..self.passes.len()) 54 | .map(PassID) 55 | .collect() 56 | } 57 | 58 | fn pass(&self, pass_id: PassID) -> IRPass { 59 | self.passes[pass_id.0].1 60 | } 61 | } 62 | 63 | #[derive(PartialEq, Eq)] 64 | enum Request { 65 | Add, 66 | Remove, 67 | } 68 | 69 | fn main() -> io::Result<()> { 70 | let args: Vec = std::env::args() 71 | .skip(1) 72 | .collect(); 73 | 74 | if let Some(source_path) = args.get(0) { 75 | let source = std::fs::read_to_string(source_path)?; 76 | let mut module = ir::Module::parse_from_source(&source); 77 | let registry = PassRegistry::new(); 78 | 79 | let mut passes: Vec = Vec::new(); 80 | 81 | macro_rules! remove_pass { 82 | ($pass: expr) => {{ 83 | if let Some(position) = passes.iter().position(|&pass| pass == $pass) { 84 | passes.remove(position); 85 | } 86 | }} 87 | } 88 | 89 | macro_rules! add_pass { 90 | ($pass: expr) => {{ 91 | if !passes.iter().any(|&pass| pass == $pass) { 92 | passes.push($pass); 93 | } 94 | }} 95 | } 96 | 97 | let mut stats = false; 98 | 99 | for argument in &args[1..] { 100 | let request = if argument.starts_with('+') { 101 | Request::Add 102 | } else if argument.starts_with('-') { 103 | Request::Remove 104 | } else { 105 | println!("Unknown argument: {}.", argument); 106 | std::process::exit(-1); 107 | }; 108 | 109 | match &argument[1..] { 110 | "all" => { 111 | for pass in registry.all_passes() { 112 | match request { 113 | Request::Add => add_pass!(pass), 114 | Request::Remove => remove_pass!(pass), 115 | }; 116 | } 117 | } 118 | name => { 119 | if name == "stats" { 120 | stats = request == Request::Add; 121 | } else { 122 | let pass = registry.pass_by_name(name).unwrap_or_else(|| { 123 | println!("Unrecognised IR pass: {}.", name); 124 | std::process::exit(-1); 125 | }); 126 | 127 | match request { 128 | Request::Add => add_pass!(pass), 129 | Request::Remove => remove_pass!(pass), 130 | }; 131 | } 132 | } 133 | } 134 | } 135 | 136 | let passes: Vec = passes.into_iter() 137 | .map(|pass| registry.pass(pass)) 138 | .collect(); 139 | 140 | let pass_manager = ir::PassManager::with_passes(&passes); 141 | 142 | module.optimize(&pass_manager, stats); 143 | 144 | module.for_each_local_function(|_prototype, function| { 145 | module.dump_function_text_stdout(function); 146 | 147 | println!(); 148 | }); 149 | } else { 150 | println!("No TurboIR source file provided."); 151 | } 152 | 153 | Ok(()) 154 | } 155 | -------------------------------------------------------------------------------- /turbo_ir/src/codegen/executable_memory.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | #[cfg(windows)] 4 | fn allocate_rwx(size: usize) -> *mut u8 { 5 | extern "system" { 6 | fn VirtualAlloc(base: *mut u8, size: usize, alloc_type: u32, prot: u32) -> *mut u8; 7 | } 8 | 9 | const MEM_COMMIT_RESERVE: u32 = 0x1000 | 0x2000; 10 | const PAGE_EXECUTE_READWRITE: u32 = 0x40; 11 | 12 | let prot = PAGE_EXECUTE_READWRITE; 13 | 14 | let result = unsafe { 15 | VirtualAlloc(std::ptr::null_mut(), size, MEM_COMMIT_RESERVE, prot) 16 | }; 17 | 18 | let success = !result.is_null(); 19 | 20 | assert!(success, "Allocating memory with size of {} bytes failed.", size); 21 | 22 | result 23 | } 24 | 25 | #[cfg(unix)] 26 | fn allocate_rwx(size: usize) -> *mut u8 { 27 | extern "system" { 28 | fn mmap(base: *mut u8, size: usize, prot: u32, flags: u32, 29 | fd: u32, offset: usize) -> *mut u8; 30 | } 31 | 32 | const MAP_PRIVATE_ANONYMOUS: u32 = 0x02 | 0x20; 33 | 34 | const PROT_READ: u32 = 1; 35 | const PROT_WRITE: u32 = 2; 36 | const PROT_EXEC: u32 = 4; 37 | 38 | let prot = PROT_READ | PROT_WRITE | PROT_EXEC; 39 | 40 | let result = unsafe { 41 | mmap(std::ptr::null_mut(), size, prot, MAP_PRIVATE_ANONYMOUS, 1u32.wrapping_neg(), 0) 42 | }; 43 | 44 | let success = !result.is_null() && result as isize > 0; 45 | 46 | assert!(success, "Allocating memory with size of {} bytes failed.", size); 47 | 48 | result 49 | } 50 | 51 | #[cfg(windows)] 52 | unsafe fn free_rwx(base: *mut u8, _size: usize) { 53 | extern "system" { 54 | fn VirtualFree(base: *mut u8, size: usize, free_type: u32) -> u32; 55 | } 56 | 57 | const MEM_RELEASE: u32 = 0x8000; 58 | 59 | let result = VirtualFree(base, 0, MEM_RELEASE); 60 | 61 | assert!(result != 0, "Freeing memory at addresss {:p} failed.", base); 62 | } 63 | 64 | #[cfg(unix)] 65 | unsafe fn free_rwx(base: *mut u8, size: usize) { 66 | extern "system" { 67 | fn munmap(base: *mut u8, size: usize) -> i32; 68 | } 69 | 70 | let result = munmap(base, size); 71 | 72 | assert_eq!(result, 0, "Freeing memory at addresss {:p} failed.", base); 73 | } 74 | 75 | pub struct ExecutableMemory { 76 | base: *mut u8, 77 | size: usize, 78 | } 79 | 80 | unsafe impl Send for ExecutableMemory {} 81 | unsafe impl Sync for ExecutableMemory {} 82 | 83 | impl ExecutableMemory { 84 | pub fn new(size: usize) -> Self { 85 | let base = allocate_rwx(size); 86 | 87 | Self { 88 | base, 89 | size, 90 | } 91 | } 92 | 93 | pub fn from_buffer(buffer: &[u8]) -> Self { 94 | let mut memory = Self::new(buffer.len()); 95 | 96 | memory.copy_from_slice(buffer); 97 | 98 | memory 99 | } 100 | } 101 | 102 | impl Drop for ExecutableMemory { 103 | fn drop(&mut self) { 104 | unsafe { 105 | free_rwx(self.base, self.size); 106 | } 107 | } 108 | } 109 | 110 | impl Deref for ExecutableMemory { 111 | type Target = [u8]; 112 | 113 | fn deref(&self) -> &Self::Target { 114 | unsafe { 115 | std::slice::from_raw_parts(self.base, self.size) 116 | } 117 | } 118 | } 119 | 120 | impl DerefMut for ExecutableMemory { 121 | fn deref_mut(&mut self) -> &mut Self::Target { 122 | unsafe { 123 | std::slice::from_raw_parts_mut(self.base, self.size) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /turbo_ir/src/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | mod x86_backend; 2 | mod executable_memory; 3 | mod register_allocation; 4 | 5 | use crate::{Function, FunctionData, Module, Map, Instruction, Value}; 6 | use executable_memory::ExecutableMemory; 7 | use register_allocation::RegisterAllocation; 8 | 9 | pub type FunctionMCodeMap = Map; 10 | 11 | pub struct MachineCode { 12 | buffer: ExecutableMemory, 13 | functions: FunctionMCodeMap, 14 | } 15 | 16 | impl MachineCode { 17 | pub(super) fn new(buffer: &[u8], functions: FunctionMCodeMap) -> Self { 18 | let buffer = ExecutableMemory::from_buffer(&buffer); 19 | 20 | Self { 21 | buffer, 22 | functions, 23 | } 24 | } 25 | 26 | pub fn function_buffer(&self, function: Function) -> &[u8] { 27 | let (offset, size) = *self.functions.get(&function) 28 | .expect("Invalid function specified."); 29 | 30 | &self.buffer[offset..][..size] 31 | } 32 | 33 | #[allow(clippy::missing_safety_doc)] 34 | pub unsafe fn function_ptr(&self, function: Function) -> T { 35 | assert_eq!(std::mem::size_of::(), std::mem::size_of::(), 36 | "Function pointer must have pointer size."); 37 | 38 | let ptr = self.function_buffer(function).as_ptr(); 39 | 40 | *(&ptr as *const _ as *const T) 41 | } 42 | } 43 | 44 | pub(super) trait Backend { 45 | fn new(ir: &Module) -> Self where Self: Sized; 46 | fn hardware_registers(&self) -> usize; 47 | fn can_inline_constant(&self, function: &FunctionData, value: Value, constant: i64, 48 | users: &[&Instruction]) -> bool; 49 | fn generate_function(&mut self, function: Function, data: &FunctionData, 50 | register_allocation: RegisterAllocation); 51 | fn finalize(&mut self) -> (Vec, FunctionMCodeMap); 52 | } 53 | 54 | pub(super) fn allocate_registers(function: &mut FunctionData, backend: &dyn Backend) 55 | -> RegisterAllocation 56 | { 57 | function.allocate_registers(backend) 58 | } 59 | 60 | pub mod backends { 61 | use super::Backend; 62 | 63 | pub struct BackendInternal { 64 | inner: Box, 65 | } 66 | 67 | impl BackendInternal { 68 | pub(in super::super) fn get(&self) -> &dyn Backend { 69 | self.inner.as_ref() 70 | } 71 | 72 | pub(in super::super) fn get_mut(&mut self) -> &mut dyn Backend { 73 | self.inner.as_mut() 74 | } 75 | 76 | pub(in super::super) fn finalize(mut self) -> (Vec, super::FunctionMCodeMap) { 77 | self.inner.finalize() 78 | } 79 | } 80 | 81 | pub trait IRBackend { 82 | fn create(&self, ir: &crate::Module) -> BackendInternal; 83 | } 84 | 85 | macro_rules! backend { 86 | ($module: ident, $name: ident) => { 87 | pub struct $name; 88 | 89 | impl IRBackend for $name { 90 | fn create(&self, ir: &crate::Module) -> BackendInternal { 91 | BackendInternal { 92 | inner: Box::new(super::$module::$name::new(ir)), 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | backend!(x86_backend, X86Backend); 100 | } 101 | -------------------------------------------------------------------------------- /turbo_ir/src/collections.rs: -------------------------------------------------------------------------------- 1 | use std::hash::{Hasher, BuildHasherDefault}; 2 | use std::collections::{HashMap, HashSet}; 3 | 4 | pub struct FnvHasher(u64); 5 | 6 | impl Default for FnvHasher { 7 | #[inline] 8 | fn default() -> FnvHasher { 9 | FnvHasher(0xcbf29ce484222325) 10 | } 11 | } 12 | 13 | impl Hasher for FnvHasher { 14 | #[inline] 15 | fn finish(&self) -> u64 { 16 | self.0 17 | } 18 | 19 | #[inline] 20 | fn write(&mut self, bytes: &[u8]) { 21 | for byte in bytes.iter() { 22 | self.0 ^= *byte as u64; 23 | self.0 = self.0.wrapping_mul(0x100000001b3); 24 | } 25 | } 26 | } 27 | 28 | type FnvBuildHasher = BuildHasherDefault; 29 | 30 | pub type Map = HashMap; 31 | pub type Set = HashSet; 32 | pub type LargeKeyMap = HashMap; 33 | 34 | pub trait CapacityExt { 35 | fn new_with_capacity(capacity: usize) -> Self; 36 | } 37 | 38 | impl CapacityExt for Map { 39 | fn new_with_capacity(capacity: usize) -> Self { 40 | Self::with_capacity_and_hasher(capacity, Default::default()) 41 | } 42 | } 43 | 44 | impl CapacityExt for Set { 45 | fn new_with_capacity(capacity: usize) -> Self { 46 | Self::with_capacity_and_hasher(capacity, Default::default()) 47 | } 48 | } 49 | 50 | impl CapacityExt for LargeKeyMap { 51 | fn new_with_capacity(capacity: usize) -> Self { 52 | Self::with_capacity(capacity) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /turbo_ir/src/dot.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::process::{Command, Stdio}; 3 | 4 | pub fn save_graph(graph: &str, output_path: &str) { 5 | let dot_position = output_path.rfind('.').expect("`output_path` doesn't have an extension."); 6 | let format = &output_path[dot_position + 1..]; 7 | 8 | assert!(!format.is_empty(), "Invalid file format."); 9 | 10 | let process = Command::new("dot") 11 | .args(&[&format!("-T{}", format), "-o", output_path]) 12 | .stdin(Stdio::piped()) 13 | .stdout(Stdio::piped()) 14 | .stderr(Stdio::piped()) 15 | .spawn(); 16 | 17 | if let Ok(mut process) = process { 18 | { 19 | let stdin = process.stdin.as_mut() 20 | .expect("Getting `dot` stdin failed."); 21 | 22 | stdin.write_all(graph.as_bytes()) 23 | .expect("Writing to `dot` stdin failed."); 24 | } 25 | 26 | let output = process.wait_with_output() 27 | .expect("Waiting for `dot` failed."); 28 | 29 | let no_output = output.stderr.is_empty() && output.stdout.is_empty(); 30 | if !no_output { 31 | println!("{}", String::from_utf8_lossy(&output.stdout)); 32 | println!("{}", String::from_utf8_lossy(&output.stderr)); 33 | } 34 | 35 | assert!(output.status.success() && no_output, 36 | "`dot` failed to generate graph."); 37 | } else { 38 | println!("WARNING: Failed to run `dot`, it's probably not installed."); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /turbo_ir/src/dump.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | use super::{FunctionData, Instruction, Label, Value}; 4 | use super::display::IRFormatter; 5 | 6 | #[cfg(windows)] 7 | fn stdout_use_colors() -> bool { 8 | // TODO: Check on Windows if stdout is redirected to file. 9 | true 10 | } 11 | 12 | #[cfg(unix)] 13 | fn stdout_use_colors() -> bool { 14 | extern "C" { 15 | fn isatty(fd: i32) -> i32; 16 | } 17 | 18 | unsafe { 19 | isatty(1) == 1 20 | } 21 | } 22 | 23 | struct BlankFormatter; 24 | 25 | impl IRFormatter for BlankFormatter { 26 | fn fmt_value(&self, value: Value) -> String { 27 | format!("{}", value) 28 | } 29 | 30 | fn fmt_type(&self, name: String) -> String { 31 | name 32 | } 33 | 34 | fn fmt_inst(&self, name: String) -> String { 35 | name 36 | } 37 | 38 | fn fmt_label(&self, label: Label) -> String { 39 | format!("{}", label) 40 | } 41 | 42 | fn fmt_literal(&self, literal: String) -> String { 43 | literal 44 | } 45 | 46 | fn fmt_function(&self, name: &str) -> String { 47 | name.to_owned() 48 | } 49 | } 50 | 51 | macro_rules! format_html { 52 | ($value: expr, $color: expr) => { 53 | format!(r##"{}"##, $color, $value) 54 | } 55 | } 56 | 57 | struct GraphvizFormatter; 58 | 59 | impl IRFormatter for GraphvizFormatter { 60 | fn fmt_value(&self, value: Value) -> String { 61 | format_html!(value, "000000") 62 | } 63 | 64 | fn fmt_type(&self, name: String) -> String { 65 | format_html!(name, "000000") 66 | } 67 | 68 | fn fmt_inst(&self, name: String) -> String { 69 | format_html!(name, "000000") 70 | } 71 | 72 | fn fmt_label(&self, label: Label) -> String { 73 | format_html!(label, "000000") 74 | } 75 | 76 | fn fmt_literal(&self, literal: String) -> String { 77 | format_html!(literal, "000000") 78 | } 79 | 80 | fn fmt_function(&self, name: &str) -> String { 81 | name.to_owned() 82 | } 83 | } 84 | 85 | macro_rules! format_console { 86 | ($value: expr, $color: expr) => { 87 | format!("\x1b[1;{}m{}\x1b[0m", $color, $value) 88 | } 89 | } 90 | 91 | struct ConsoleFormatter; 92 | 93 | impl IRFormatter for ConsoleFormatter { 94 | 95 | fn fmt_value(&self, value: Value) -> String { 96 | format_console!(value, 33) 97 | } 98 | 99 | fn fmt_type(&self, name: String) -> String { 100 | format_console!(name, 34) 101 | } 102 | 103 | fn fmt_inst(&self, name: String) -> String { 104 | format_console!(name, 32) 105 | } 106 | 107 | fn fmt_label(&self, label: Label) -> String { 108 | format_console!(label, 37) 109 | } 110 | 111 | fn fmt_literal(&self, literal: String) -> String { 112 | literal 113 | } 114 | 115 | fn fmt_function(&self, name: &str) -> String { 116 | name.to_owned() 117 | } 118 | } 119 | 120 | impl FunctionData { 121 | fn prototype_representation(&self, formatter: &dyn IRFormatter) -> String { 122 | let return_type = match self.prototype.return_type { 123 | Some(ty) => formatter.fmt_type(format!("{}", ty)), 124 | None => formatter.fmt_type(String::from("void")), 125 | }; 126 | 127 | let mut name = format!("{} {}(", return_type, 128 | formatter.fmt_function(&self.prototype.name)); 129 | 130 | for index in 0..self.prototype.arguments.len() { 131 | name.push_str(&format!("{} {}", 132 | formatter.fmt_type(format!("{}", self.prototype.arguments[index])), 133 | formatter.fmt_value(self.argument_values[index]) 134 | )); 135 | 136 | if index != self.prototype.arguments.len() - 1 { 137 | name.push_str(", "); 138 | } 139 | } 140 | 141 | name.push(')'); 142 | 143 | name 144 | } 145 | 146 | fn instruction_string(&self, instruction: &Instruction, 147 | formatter: &dyn IRFormatter) -> String { 148 | let mut buffer = Vec::new(); 149 | 150 | self.print_instruction(&mut buffer, instruction, formatter).unwrap(); 151 | 152 | String::from_utf8(buffer).unwrap() 153 | } 154 | 155 | pub fn dump_graph(&self, path: &str) { 156 | let formatter = &GraphvizFormatter; 157 | let mut dotgraph = String::new(); 158 | 159 | assert!(!path.to_lowercase().ends_with(".svg"), 160 | "Graphviz is buggy and IR graph cannot be created with SVG format."); 161 | 162 | dotgraph.push_str("digraph G {\n"); 163 | 164 | for label in self.reachable_labels() { 165 | let instructions = &self.blocks[&label]; 166 | let targets = self.targets(label); 167 | 168 | let name = if label == self.entry() { 169 | self.prototype_representation(formatter) 170 | } else { 171 | format!("{}:", formatter.fmt_label(label)) 172 | }; 173 | 174 | dotgraph.push_str( 175 | &format!( 176 | r#"{} [shape=box fontname="Consolas" label=<{}
"#, 177 | label, name, 178 | ) 179 | ); 180 | 181 | if label == self.entry() { 182 | dotgraph.push_str(&format!(r#"
{}:
"#, formatter.fmt_label(label))); 183 | } 184 | 185 | for instruction in instructions { 186 | // This space is required here otherwise things break... 187 | dotgraph.push_str(&format!(r#"{}
"#, 188 | self.instruction_string(instruction, formatter))); 189 | } 190 | 191 | dotgraph.push_str(">];\n"); 192 | 193 | let conditional = targets.len() == 2; 194 | 195 | for (i, target) in targets.iter().enumerate() { 196 | let color = match i { 197 | 0 if conditional => "green", 198 | 1 if conditional => "red", 199 | _ => "blue", 200 | }; 201 | 202 | dotgraph.push_str(&format!("{} -> {} [color={}];\n", label, target, color)); 203 | } 204 | } 205 | 206 | dotgraph.push_str("}\n"); 207 | 208 | crate::dot::save_graph(&dotgraph, path); 209 | } 210 | 211 | pub fn dump_text_formatter(&self, w: &mut W, 212 | formatter: &dyn IRFormatter) -> io::Result<()> { 213 | writeln!(w, "{} {{", self.prototype_representation(formatter))?; 214 | 215 | let indent = " "; 216 | let mut first = true; 217 | 218 | for label in self.reachable_labels_dfs() { 219 | if !first { 220 | writeln!(w)?; 221 | } 222 | 223 | first = false; 224 | 225 | writeln!(w, "{}:", formatter.fmt_label(label))?; 226 | 227 | for instruction in &self.blocks[&label] { 228 | writeln!(w, "{}{}", indent, self.instruction_string(instruction, formatter))?; 229 | } 230 | } 231 | 232 | writeln!(w, "}}")?; 233 | 234 | Ok(()) 235 | } 236 | 237 | pub fn dump_text(&self, w: &mut W) -> io::Result<()> { 238 | self.dump_text_formatter(w, &BlankFormatter) 239 | } 240 | 241 | pub fn dump_text_stdout(&self) { 242 | let formatter: &dyn IRFormatter = if stdout_use_colors() { 243 | &ConsoleFormatter 244 | } else { 245 | &BlankFormatter 246 | }; 247 | 248 | self.dump_text_formatter(&mut std::io::stdout(), formatter) 249 | .expect("Failed to write to stdout."); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /turbo_ir/src/graph.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use super::{FunctionData, Label, Map, Set, CapacityExt}; 4 | 5 | pub type FlowGraph = Map>; 6 | pub type Dominators = Map; 7 | 8 | impl FunctionData { 9 | fn preferred_capacity(&self, start: Label) -> usize { 10 | if start == self.entry() { 11 | self.blocks.len() 12 | } else { 13 | 0 14 | } 15 | } 16 | 17 | fn traverse_dfs_postorder(&self, start: Label) -> Vec