├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── etc └── cir-vscode │ ├── cir.tmLanguage.json │ ├── language-configuration.json │ └── package.json ├── examples ├── add.bf ├── beer.bf ├── bittest.bf ├── bounds_checking.bf ├── bubble_sort.bf ├── collatz.bf ├── duplicate_cell.bf ├── e.bf ├── empty_loop.bf ├── factor.bf ├── factorial2.bf ├── golden.bf ├── h.bf ├── hanoi.bf ├── hello_world.bf ├── infinite_loop.bf ├── langton.bf ├── long.bf ├── lost_kingdom.bf ├── lost_kingdom_start.bf ├── mandelbrot.bf ├── mandelbrot_beginning.bf ├── mul.bf ├── noop.bf ├── report_30k.bf ├── rot13.bf ├── shift.bf ├── sierpinski.bf ├── square.bf ├── squares.bf ├── sub.bf ├── xmastree.bf └── zero_cell.bf ├── rust-toolchain.toml └── src ├── args.rs ├── driver.rs ├── graph ├── building.rs ├── edge.rs ├── mod.rs ├── nodes │ ├── cmp.rs │ ├── gamma.rs │ ├── inherent.rs │ ├── io.rs │ ├── memory.rs │ ├── mod.rs │ ├── node.rs │ ├── node_ext.rs │ ├── node_id.rs │ ├── node_macros.rs │ ├── ops.rs │ ├── scan.rs │ ├── theta.rs │ └── values.rs ├── ports.rs ├── remove.rs ├── stats.rs ├── structural_eq.rs └── subgraph.rs ├── interpreter.rs ├── ir ├── builder.rs ├── lifetime.rs ├── mod.rs ├── parse.rs └── pretty_print.rs ├── jit ├── basic_block.rs ├── block_builder.rs ├── block_visitor.rs ├── cir_jit.rs ├── cir_to_bb.rs ├── codegen │ ├── instruction.rs │ ├── mod.rs │ └── terminator.rs ├── disassemble.rs ├── ffi.rs ├── memory.rs └── mod.rs ├── lower_tokens.rs ├── main.rs ├── parse.rs ├── passes ├── add_sub_loop.rs ├── associative_ops.rs ├── canonicalize.rs ├── const_folding.rs ├── copy_cell.rs ├── dataflow │ ├── arithmetic.rs │ ├── domain │ │ ├── bitmap_u16 │ │ │ ├── arch.rs │ │ │ ├── iter.rs │ │ │ └── mod.rs │ │ ├── byte_set.rs │ │ ├── int_set.rs │ │ ├── mod.rs │ │ └── utils.rs │ ├── equality.rs │ ├── gamma.rs │ ├── memory.rs │ ├── mod.rs │ ├── pass.rs │ └── theta.rs ├── dce.rs ├── eliminate_const_gamma.rs ├── equality.rs ├── expr_dedup.rs ├── fold_arithmetic.rs ├── fuse_io.rs ├── licm.rs ├── mem2reg.rs ├── mod.rs ├── move_cell.rs ├── scan_loops.rs ├── square_cell.rs ├── symbolic_eval.rs ├── unobserved_store.rs ├── utils │ ├── binary_op.rs │ ├── changes.rs │ ├── constant_store.rs │ ├── memory_tape.rs │ ├── mod.rs │ └── unary_op.rs └── zero_loop.rs ├── patterns ├── mod.rs └── sexpr.rs ├── tests ├── corpus │ └── beer.out └── mod.rs ├── utils.rs └── values ├── cell.rs ├── mod.rs └── ptr.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vscode 3 | /dumps 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cranial-coitus" 3 | authors = ["Chase Wilson "] 4 | version = "0.1.0" 5 | license = "MIT OR Apache-2.0" 6 | edition = "2021" 7 | resolver = "2" 8 | 9 | [dependencies] 10 | atty = "0.2.14" 11 | paste = "1.0.5" 12 | anyhow = "1.0.45" 13 | pretty = "0.11.2" 14 | similar = "2.1.0" 15 | petgraph = "0.6.0" 16 | union-find = "0.3.3" 17 | # features = ["enable"] 18 | superluminal-perf = { version = "0.1.1" } 19 | clap = { version = "3.1.15", features = ["derive"] } 20 | roaring = { version = "0.9.0", features = ["simd"] } 21 | im-rc = { version = "15.0.0", features = ["pool"] } 22 | tinyvec = { version = "1.5.0", features = ["alloc"] } 23 | xxhash-rust = { version = "0.8.2", features = ["xxh3"] } 24 | tracing-tree = { version = "0.2.0", default-features = false } 25 | tracing = { version = "0.1.29", features = ["release_max_level_debug"] } 26 | 27 | # Cranelift 28 | cranelift = "0.82.1" 29 | cranelift-jit = "0.82.1" 30 | cranelift-module = "0.82.1" 31 | cranelift-native = "0.82.1" 32 | 33 | [dependencies.iced-x86] 34 | version = "1.15.0" 35 | default-features = false 36 | git = "https://github.com/icedland/iced" 37 | features = ["std", "masm", "decoder", "encoder", "code_asm", "instr_info"] 38 | 39 | [dependencies.tracing-subscriber] 40 | version = "0.3.3" 41 | features = ["env-filter", "registry"] 42 | 43 | [dev-dependencies] 44 | expect-test = "1.1.0" 45 | 46 | [profile.release] 47 | debug = 2 48 | lto = "thin" 49 | 50 | [profile.release.package."*"] 51 | debug = 2 52 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2021 Chase Wilson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 4 | (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 5 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 6 | so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 12 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 13 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cranial Coitus 2 | 3 | An experimental [RVSDG] optimizer for [brainfuck] code 4 | 5 | [RVSDG]: https://www.sjalander.com/research/pdf/sjalander-tecs2020.pdf 6 | [brainfuck]: https://en.wikipedia.org/wiki/Brainfuck 7 | -------------------------------------------------------------------------------- /etc/cir-vscode/cir.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "Coitus Intermediate Representation", 4 | "scopeName": "source.cir", 5 | "patterns": [ 6 | { 7 | "include": "#comments" 8 | }, 9 | { 10 | "include": "#keywords" 11 | }, 12 | { 13 | "include": "#variables" 14 | }, 15 | { 16 | "include": "#literals" 17 | } 18 | ], 19 | "repository": { 20 | "comments": { 21 | "patterns": [ 22 | { 23 | "comment": "line comments", 24 | "name": "comment.line.double-slash.cir", 25 | "match": "\\s*//.*" 26 | } 27 | ] 28 | }, 29 | "literals": { 30 | "patterns": [ 31 | { 32 | "comment": "decimal integers and floats", 33 | "name": "constant.numeric.decimal.cir", 34 | "match": "\\b(-)?\\d+\\b" 35 | }, 36 | { 37 | "comment": "booleans", 38 | "name": "constant.language.bool.cir", 39 | "match": "\\b(true|false)\\b" 40 | } 41 | ] 42 | }, 43 | "variables": { 44 | "patterns": [ 45 | { 46 | "comment": "variables", 47 | "name": "variable.other.cir", 48 | "match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b" 49 | } 50 | ] 51 | }, 52 | "keywords": { 53 | "patterns": [ 54 | { 55 | "comment": "control flow keywords", 56 | "name": "keyword.control.cir", 57 | "match": "\\b(do|while|if|else)\\b" 58 | }, 59 | { 60 | "comment": "instruction keywords.cir", 61 | "name": "keyword.operator", 62 | "match": "\\b(add|sub|mul|div|store|load|neg|not|eq|call|lifetime_end)\\b" 63 | }, 64 | { 65 | "comment": "literal and param types", 66 | "name": "storage.type.cir", 67 | "match": "\\b(int|bool|in|out)\\b" 68 | }, 69 | { 70 | "comment": "assignment operator", 71 | "name": "keyword.operator.cir", 72 | "match": "\\b(:=)\\b" 73 | } 74 | ] 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /etc/cir-vscode/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//", 4 | }, 5 | "brackets": [ 6 | [ 7 | "{", 8 | "}" 9 | ] 10 | ], 11 | "autoClosingPairs": [ 12 | [ 13 | "{", 14 | "}" 15 | ] 16 | ], 17 | "surroundingPairs": [ 18 | [ 19 | "{", 20 | "}" 21 | ] 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /etc/cir-vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Coitus Intermediate Representation", 3 | "version": "0.1.0", 4 | "engines": { 5 | "vscode": "*" 6 | }, 7 | "publisher": "Chase Wilson", 8 | "license": "MIT OR Apache-2.0", 9 | "contributes": { 10 | "languages": [ 11 | { 12 | "id": "cir", 13 | "aliases": [ 14 | "Coitus Intermediate Representation", 15 | "Coitus IR", 16 | "CIR" 17 | ], 18 | "extensions": [ 19 | ".cir" 20 | ], 21 | "configuration": "./language-configuration.json" 22 | } 23 | ], 24 | "grammars": [ 25 | { 26 | "language": "cir", 27 | "scopeName": "source.cir", 28 | "path": "./cir.tmLanguage.json" 29 | } 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/add.bf: -------------------------------------------------------------------------------- 1 | ,>,< 2 | [->+<]> 3 | . 4 | -------------------------------------------------------------------------------- /examples/bittest.bf: -------------------------------------------------------------------------------- 1 | ++++++++[>++++++++<-]>[<++++>-] 2 | +<[>-< 3 | [>++++<-]>[<++++++++>-]<[>++++++++<-] 4 | +>[> 5 | ++++++++++[>+++++<-]>+.-.[-]< 6 | <[-]<->] <[>> 7 | +++++++[>+++++++<-]>.+++++.[-]< 8 | <<-]] >[> 9 | ++++++++[>+++++++<-]>.[-]< 10 | <-]< 11 | +++++++++++[>+++>+++++++++>+++++++++>+<<<<-]>-.>-.+++++++.+++++++++++.<. 12 | >>.++.+++++++..<-.>>- 13 | . 14 | [[-]<] 15 | -------------------------------------------------------------------------------- /examples/bounds_checking.bf: -------------------------------------------------------------------------------- 1 | These two programs measure the "real" size of the array in some 2 | sense in cells left and right of the initial cell respectively 3 | They output the result in unary; the easiest thing is to direct them to a file 4 | and measure its size or (on Unix) pipe the output to wc 5 | If bounds checking is present and working the left should measure 0 and the right 6 | should be the array size minus one 7 | 8 | +[<+++++++++++++++++++++++++++++++++.] 9 | 10 | +[>+++++++++++++++++++++++++++++++++.] 11 | -------------------------------------------------------------------------------- /examples/bubble_sort.bf: -------------------------------------------------------------------------------- 1 | # Sourced from http://www(dot)brainfuck(dot)org/bsort(dot)b 2 | # 3 | # (c) 2016 Daniel B(dot) Cristofani 4 | # http://brainfuck(dot)org/ 5 | 6 | >>,------------------------------------------------ 7 | [>>,------------------------------------------------]<< 8 | [ 9 | [<<]>>>> 10 | [ 11 | <<[>+<<+>-] 12 | >>[>+<<<<[->]>[<]>>-] 13 | <<<[[-]>>[>+<-]>>[<<<+>>>-]] 14 | >>[[<+>-]>>]< 15 | ] 16 | <<[>>+<<-]<< 17 | ] 18 | >>>> 19 | [++++++++++++++++++++++++++++++++++++++++++++++++.>>] 20 | -------------------------------------------------------------------------------- /examples/collatz.bf: -------------------------------------------------------------------------------- 1 | # Sourced from http://www(dot)brainfuck(dot)org/collatz(dot)b 2 | # 3 | # Daniel B Cristofani (cristofdathevanetdotcom) 4 | # http://www(dot)hevanet(dot)com/cristofd/brainfuck/ 5 | 6 | >,[ 7 | [ 8 | ----------[ 9 | >>>[>>>>]+[[-]+<[->>>>++>>>>+[>>>>]++[->+<<<<<]]<<<] 10 | ++++++[>------<-]>--[>>[->>>>]+>+[<<<<]>-],< 11 | ]> 12 | ]>>>++>+>>[ 13 | <<[>>>>[-]+++++++++<[>-<-]+++++++++>[-[<->-]+[<<<<]]<[>+<-]>] 14 | >[>[>>>>]+[[-]<[+[->>>>]>+<]>[<+>[<<<<]]+<<<<]>>>[->>>>]+>+[<<<<]] 15 | >[[>+>>[<<<<+>>>>-]>]<<<<[-]>[-<<<<]]>>>>>>> 16 | ]>>+[[-]++++++>>>>]<<<<[[<++++++++>-]<.[-]<[-]<[-]<]<, 17 | ] 18 | -------------------------------------------------------------------------------- /examples/duplicate_cell.bf: -------------------------------------------------------------------------------- 1 | ,[>+>+<<-].>.>. 2 | -------------------------------------------------------------------------------- /examples/e.bf: -------------------------------------------------------------------------------- 1 | # Sourced from http://www(dot)brainfuck(dot)org/e(dot)b 2 | # 3 | # (c) 2016 Daniel B(dot) Cristofani 4 | # http://brainfuck(dot)org/ 5 | 6 | >>>>++>+>++>+>>++<+[ 7 | [>[>>[>>>>]<<<<[[>>>>+<<<<-]<<<<]>>>>>>]+<]>- 8 | >>--[+[+++<<<<--]++>>>>--]+[>>>>]<<<<[<<+<+<]<<[ 9 | >>>>>>[[<<<<+>>>>-]>>>>]<<<<<<<<[<<<<] 10 | >>-[<<+>>-]+<<[->>>>[-[+>>>>-]-<<-[>>>>-]++>>+[-<<<<+]+>>>>]<<<<[<<<<]] 11 | >[-[<+>-]]+<[->>>>[-[+>>>>-]-<<<-[>>>>-]++>>>+[-<<<<+]+>>>>]<<<<[<<<<]]<< 12 | ]>>>+[>>>>]-[+<<<<--]++[<<<<]>>>+[ 13 | >-[ 14 | >>[--[++>>+>>--]-<[-[-[+++<<<<-]+>>>>-]]++>+[-<<<<+]++>>+>>] 15 | <<[>[<-<<<]+<]>->>> 16 | ]+>[>>>>]-[+<<<<--]++<[ 17 | [>>>>]<<<<[ 18 | -[+>[<->-]++<[[>-<-]++[<<<<]+>>+>>-]++<<<<-] 19 | >-[+[<+[<<<<]>]<+>]+<[->->>>[-]]+<<<< 20 | ] 21 | ]>[<<<<]>[ 22 | -[ 23 | -[ 24 | +++++[>++++++++<-]>-.>>>-[<<<----.<]<[<<]>>[-]>->>+[ 25 | [>>>>]+[-[->>>>+>>>>>>>>-[-[+++<<<<[-]]+>>>>-]++[<<<<]]+<<<<]>>> 26 | ]+<+<< 27 | ]>[ 28 | -[ 29 | ->[--[++>>>>--]->[-[-[+++<<<<-]+>>>>-]]++<+[-<<<<+]++>>>>] 30 | <<<<[>[<<<<]+<]>->> 31 | ]< 32 | ]>>>>[--[++>>>>--]-<--[+++>>>>--]+>+[-<<<<+]++>>>>]<<<<<[<<<<]< 33 | ]>[>+<<++<]< 34 | ]>[+>[--[++>>>>--]->--[+++>>>>--]+<+[-<<<<+]++>>>>]<<<[<<<<]]>> 35 | ]> 36 | ] 37 | -------------------------------------------------------------------------------- /examples/empty_loop.bf: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /examples/factor.bf: -------------------------------------------------------------------------------- 1 | * factor an arbitrarily large positive integer 2 | * 3 | * Copyright (C) 1999 by Brian Raiter 4 | * under the GNU General Public License 5 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>- 6 | * 7 | * read in the number 8 | * 9 | <<<<<<<<<+ 10 | [-[>>>>>>>>>>][-]<<<<<<<<<<[[->>>>>>>>>>+<<<<<<<<<<]<<<<<<<<<<] 11 | >>>>>>>>>>,----------] 12 | >>>>>>>>>>[------------------------------------->>>>>>>>>->] 13 | <[+>[>>>>>>>>>+>]<-<<<<<<<<<<]- 14 | * 15 | * display the number and initialize the loop variable to two 16 | * 17 | [>++++++++++++++++++++++++++++++++++++++++++++++++. 18 | ------------------------------------------------<<<<<<<<<<<] 19 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. 20 | --------------------------.[-] 21 | >>>>>>>>>>>>++<<<<+ 22 | * 23 | * the main loop 24 | * 25 | [ [-]>> 26 | * 27 | * make copies of the number and the loop variable 28 | * 29 | [>>>>[-]>[-]>[-]>[-] 30 | >[-]>[-] 31 | <<<<<<<[->>>+>+<<<<]>>>>>>>>] 32 | <<<<<<<<<<[>>>>>>[-<<<<+>>>>]<<<<<<<<<<<<<<<<]>>>>>>>>>> 33 | [>[->>>+>>+<<<<<]>>>>>>>>>] 34 | <<<<<<<<<<[>>>>>>[-<<<<<+>>>>>]<<<<<<<<<<<<<<<<]>>>>>>>>>> 35 | * 36 | * divide the number by the loop variable 37 | * 38 | [>>>[-]>>>[-]>[-]>>>] initialize 39 | <<<<<<<<<<[<<<<<<<<<<] 40 | >>>>>>>>>[-]>>>>>>>+<<<<<<<<[+]+ 41 | [ ->> double divisor until above dividend 42 | [>>>>>>[->++<]>>>>]<<<<<<<<<< 43 | [>>>>>>>>[-]>[-] 44 | <<<<[->>>++<<<]<<<<<<<<<<<<<<<]>>>>>>>>>> 45 | [>>>>>>>>[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+< 46 | [->--------->>>>>>>>>+<<<<<<<<<<[->+<]]]]]]]]]]]>>] 47 | <<<<<<<<<<[>>>>>>>>>[-<+<<<+>>>>]<<<<<<<<<<<<<<<<<<<]>>>>>>>>>> 48 | [>>>>>>>[-<+>[-<+>[-<+>[-<+>[-<+>[-<+>[-<+>[-<+>[-<+> 49 | [-<--------->>>>>>>>>>>+<<<<<<<<<<[-<+>]]]]]]]]]]]>>>] 50 | <<<<<<<<<< 51 | [>>>>[->>>+>>+<<<<<]<<<<<<<<<<<<<<] 52 | >>>>>>>>>>[>>>>>>>[-<<<+>>>]>>>]<<<<<<<<<< 53 | [>>>>>>>>[->-<]> 54 | [<<<<<<<<<[<[-]>>>>>>>>>>[-<<<<<<<<<<+>>>>>>>>>>]<<<<<<<<<<<<<<<<<<<] 55 | >>>>>>>>>>>>>>>>>>>] 56 | <<<<<<<<<<<<<<<<<<<] 57 | >>>>>>>>>[+[+[+[+[+[+[+[+[+[+[[-]<+>]]]]]]]]]]]< 58 | ] 59 | >>>>>>>> 60 | [ subtract divisor from dividend 61 | <<<<<< 62 | [>>>>>>>>[-]>[-]<<<<<[->>>+>+<<<<]>>>>>>]<<<<<<<<<< 63 | [>>>>>>>>[-<<<<+>>>>]<<<[->>>+>+<<<<]<<<<<<<<<<<<<<<]>>>>>>>>>> 64 | [>>>>>>>>>[-<<<<+>>>>]>]<<<<<<<<<< 65 | [>>>>>>>>[-<->]<<<<<<<<<<<<<<<<<<]>>>>>>>>>> 66 | [>>>>>>>[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+< 67 | [++++++++++[+>-<]>>>>>>>>>>-<<<<<<<<<<]]]]]]]]]]]>>>] 68 | >>>>>>>+ 69 | [ if difference is nonnegative then 70 | [-]<<<<<<<<<<<<<<<<< replace dividend and increment quotient 71 | [>>>>[-]>>>>[-<<<<+>>>>]<<[->>+<<]<<<<<<<<<<<<<<<<]>>>>>>>>>> 72 | [>>>>>>>>[->+<<<+>>]>>]<<<<<<<<<< 73 | [>>>[->>>>>>+<<<<<<]<<<<<<<<<<<<<]>>>>>>>>>> 74 | [>>>>>>>>>[-<<<<<<+>>>>>>[-<<<<<<+>>>>>> 75 | [-<<<<<<+>>>>>>[-<<<<<<+>>>>>> 76 | [-<<<<<<+>>>>>>[-<<<<<<+>>>>>> 77 | [-<<<<<<+>>>>>>[-<<<<<<+>>>>>> 78 | [-<<<<<<+>>>>>>[-<<<<<<--------->>>>>>>>>>>>>>>>+<<<<<<<<<< 79 | [-<<<<<<+>>>>>>]]]]]]]]]]]>] 80 | >>>>>>> 81 | ] halve divisor and loop until zero 82 | <<<<<<<<<<<<<<<<<[<<<<<<<<<<]>>>>>>>>>> 83 | [>>>>>>>>[-]<<[->+<]<[->>>+<<<]>>>>>]<<<<<<<<<< 84 | [+>>>>>>>[-<<<<<<<+>>>>>>>[-<<<<<<<->>>>>>+> 85 | [-<<<<<<<+>>>>>>>[-<<<<<<<->>>>>>+> 86 | [-<<<<<<<+>>>>>>>[-<<<<<<<->>>>>>+> 87 | [-<<<<<<<+>>>>>>>[-<<<<<<<->>>>>>+> 88 | [-<<<<<<<+>>>>>>>]]]]]]]]]<<<<<<< 89 | [->>>>>>>+<<<<<<<]-<<<<<<<<<<] 90 | >>>>>>> 91 | [-<<<<<<<<<<<+>>>>>>>>>>>] 92 | >>>[>>>>>>>[-<<<<<<<<<<<+++++>>>>>>>>>>>]>>>]<<<<<<<<<< 93 | [+>>>>>>>>[-<<<<<<<<+>>>>>>>>[-<<<<<<<<->>>>>+>>> 94 | [-<<<<<<<<+>>>>>>>>[-<<<<<<<<->>>>>+>>> 95 | [-<<<<<<<<+>>>>>>>>[-<<<<<<<<->>>>>+>>> 96 | [-<<<<<<<<+>>>>>>>>[-<<<<<<<<->>>>>+>>> 97 | [-<<<<<<<<+>>>>>>>>]]]]]]]]]<<<<<<<< 98 | [->>>>>>>>+<<<<<<<<]-<<<<<<<<<<] 99 | >>>>>>>>[-<<<<<<<<<<<<<+>>>>>>>>>>>>>]>> 100 | [>>>>>>>>[-<<<<<<<<<<<<<+++++>>>>>>>>>>>>>]>>]<<<<<<<<<< 101 | [<<<<<<<<<<]>>>>>>>>>> 102 | >>>>>> 103 | ] 104 | <<<<<< 105 | * 106 | * make copies of the loop variable and the quotient 107 | * 108 | [>>>[->>>>+>+<<<<<]>>>>>>>] 109 | <<<<<<<<<< 110 | [>>>>>>>[-<<<<+>>>>]<<<<<[->>>>>+>>+<<<<<<<]<<<<<<<<<<<<] 111 | >>>>>>>>>>[>>>>>>>[-<<<<<+>>>>>]>>>]<<<<<<<<<< 112 | * 113 | * break out of the loop if the quotient is larger than the loop variable 114 | * 115 | [>>>>>>>>>[-<->]< 116 | [<<<<<<<< 117 | [<<[-]>>>>>>>>>>[-<<<<<<<<<<+>>>>>>>>>>]<<<<<<<<<<<<<<<<<<] 118 | >>>>>>>>>>>>>>>>>>]<<<<<<<<<<<<<<<<<<] 119 | >>>>>>>>[>-<[+[+[+[+[+[+[+[+[+[[-]>+<]]]]]]]]]]]>+ 120 | [ [-] 121 | * 122 | * partially increment the loop variable 123 | * 124 | <[-]+>>>>+>>>>>>>>[>>>>>>>>>>]<<<<<<<<<< 125 | * 126 | * examine the remainder for nonzero digits 127 | * 128 | [<<<<<<[<<<<[<<<<<<<<<<]>>>>+<<<<<<<<<<]<<<<] 129 | >>>>>>>>>>>>>>>>>>>>[>>>>>>>>>>]<<<<<<<<<<[<<<<<<<<<<] 130 | >>>>- 131 | [ [+] 132 | * 133 | * decrement the loop variable and replace the number with the quotient 134 | * 135 | >>>>>>>>-<<[>[-]>>[-<<+>>]>>>>>>>]<<<<<<<<<< 136 | * 137 | * display the loop variable 138 | * 139 | [+>>[>>>>>>>>+>>]<<-<<<<<<<<<<]- 140 | [>>++++++++++++++++++++++++++++++++++++++++++++++++. 141 | ------------------------------------------------<<<<<<<<<<<<] 142 | ++++++++++++++++++++++++++++++++.[-]>>>> 143 | ] 144 | * 145 | * normalize the loop variable 146 | * 147 | >>>>>> 148 | [>>[->>>>>+<<<<<[->>>>>+<<<<< 149 | [->>>>>+<<<<<[->>>>>+<<<<< 150 | [->>>>>+<<<<<[->>>>>+<<<<< 151 | [->>>>>+<<<<<[->>>>>+<<<<< 152 | [->>>>>+<<<<<[->>>>>--------->>>>>+<<<<<<<<<< 153 | [->>>>>+<<<<<]]]]]]]]]]]>>>>>>>>] 154 | <<<<<<<<<<[>>>>>>>[-<<<<<+>>>>>]<<<<<<<<<<<<<<<<<] 155 | >>>>>>>>> 156 | ]< 157 | ]>> 158 | * 159 | * display the number and end 160 | * 161 | [>>>>>>>>>>]<<<<<<<<<<[+>[>>>>>>>>>+>]<-<<<<<<<<<<]- 162 | [>++++++++++++++++++++++++++++++++++++++++++++++++.<<<<<<<<<<<] 163 | ++++++++++. 164 | -------------------------------------------------------------------------------- /examples/factorial2.bf: -------------------------------------------------------------------------------- 1 | # Sourced from http://www(dot)brainfuck(dot)org/factorial2(dot)b 2 | # 3 | # (c) 2019 Daniel B(dot) Cristofani 4 | # http://www(dot)hevanet(dot)com/cristofd/brainfuck/ 5 | 6 | >>>>++>+[ 7 | [ 8 | >[>>]<[>+>]<<[>->>+<<<-]>+[ 9 | [+>>[<<+>>-]>]+[-<<+<]>-[ 10 | -[<+>>+<-]++++++[>++++++++<-]+>.[-]<<[ 11 | >>>[[<<+>+>-]>>>]<<<<[[>+<-]<-<<]>- 12 | ]>>>[ 13 | <<-[<<+>>-]<+++++++++<[ 14 | >[->+>]>>>[<<[<+>-]>>>+>>[-<]<[>]>+<]<<<<<<- 15 | ]>[-]>+>>[<<<+>>>-]>>> 16 | ]<<<+[-[+>>]<<<]>[<<<]> 17 | ]>>>[<[>>>]<<<[[>>>+<<<-]<<<]>>>>>>>-[<]>>>[<<]<<[>+>]<]<< 18 | ]++>> 19 | ]<<++++++++.+ 20 | ] 21 | -------------------------------------------------------------------------------- /examples/golden.bf: -------------------------------------------------------------------------------- 1 | GOLDEN RATIO IN BRAINFUCK 2 | WRITTEN BY KEYMAKER (2012) 3 | COMPUTES TO INFINITE PRECISION (NEVER TERMINATES) 4 | :::::::::::::::::::::::::::::::::::::::::::::::::: 5 | 6 | ++++++[>++++++++<-]>+.---.>>>+>>+>++>+>+>+>>+>+++> 7 | +>>+>+>+>>+>>>>+>>>>+>++>>>>+>+<[[>>>]<<<[>>>>>>>> 8 | >+<<<<<<<<[>>>>>>>>>+<<<<<<<<<-]<-<<<]>>>>>>+>>>+[ 9 | <<<]<<<<<[>>>+<<[>>>+<<<-]<-<<<]>>>+<<<<<<<<[<<]>> 10 | >>[->[<<<+>>>>[>>]>+<<<[<<]>-]<<<-[>>>+<<<-]+>>>>[ 11 | >>]+>[-[-[-<->>>>>>[[>>>]>>]<<<<<[>+<-<<<]<<[<<<]> 12 | >>[->>>[>>>]>>[>>>]+[<<<]<<[<<<]+>>>]>>>[<[-]+>->> 13 | >]<[>>>]<<<[>+++++++++<<<<]<<[<<<]>>>[->[<<+>+>-]< 14 | [>+<-]>[>>[>>>]>>>>[>>>]<-<<[<<<]<<<<[<<<]>-]>>[>> 15 | >]>>>>[>>>]+[<<<]<<<<[<<<]<[>>+<<-]>+>>>]>>>>[->>> 16 | ]<<[>>>]<<<[>[-[-[-[-[-[-[-[-[-<->[<+>[-]>+<]]]]]] 17 | ]]]]<[[>>[<<<+>>>-]<[-]<-<<<]>>>]<<<]>+>[-<-<<<[[< 18 | <<]<<]<<<[<<]+>>->>[>>]>>>>>>>-<[[>>>]>>]<]<[-<<<[ 19 | [<<<]<<]<<<[<<]+[<<]>>->>[>>]>>>>>>[[>>>]>>]<<]<<< 20 | [[<<<]<<]]<[->>>>>>>+[<<+>+>-]<-[>+<-]+<[-[-[-[-[- 21 | [-[-[-[-[->>-<<<<<<<<<[<<]+>>>>>>>>->>[>>]>>>>>]]] 22 | ]]]]]]]<<<<<]>]<[->>>>>>>[<<<+>+>>-]<<[>>+<<-]+<[> 23 | -<[>>[>>>]>>[>>>]>>[>>>]<<<[-<<<]<<[<<<]<<[<<<]>>> 24 | [->[<<+>+>-]<[>+<-]<[>>>>[>>>]>>[>>>]>>[>>>]>++<<< 25 | <[<<<]<<[<<<]<<[<<<]<-]>>>>[>>>]>>[>>>]>>[>>>]+[<< 26 | <]<<[<<<]<<[<<<]+>>>]<<<[<<<]>-]>>[>>>]>>[>>>]>>[- 27 | >[<+>>+<-]<[[<+>>+<-]<[>+<-]>->-[<->-[<->-[<->-[<- 28 | >-[<->-[<->-[<->-[<->-[<->[-]>---------->[-]+>+<<< 29 | ]]]]]]]]]<]+>>[<+>-]>]<<<[[<<<]<<]>>>>>>[<<+>>>+<- 30 | ]>[<+>-]<<<[>[[>>>]>>]<+<<<<[[<<<]<<]>>>>-]>[[>>>] 31 | >>]>+<<-[>+<-]>[>+++<-[>+++++<-[>--->>+>+<<<<-[>-> 32 | >>+<<<<-[>+>>>+<<<<-[>+++>>>+<<<<-[>----->>>++<<<< 33 | -[>--->>>++<<<<-]]]]]]]]+[[<<<]<<]>>>]>[->[[>>>]>> 34 | ]+>>>>>+[[<<<]<<]>>>>]<<<<<]>]<[->>>>>>[[>>>]>>]<< 35 | <<<[<<<]>>>[-<<<[<<<]<<[<<<]>>[>>>]+>[>>>]>>[>>>]> 36 | [<<<<[<<<]<<[<<<]>>[>>>]<<<+>[>>>]>>[>>>]>-]<+>>>] 37 | <<<[-<<<]<<[<<<]>>[->>>]<<[<<<]>>>[++++++++<[>-<-] 38 | >[>+<-]+>>>]<<<[<<<]>>>>+<[->[<<+>+>-]<[-[-[-[-[-[ 39 | -[-[-[-[<---------->>>>[-]+>+<<<<[-]]]]]]]]]]]<[>> 40 | +<<-]>+>>>]<<[-<]<<[<<<]>>>->[<<+>>-]>>[>>>]<<<[>> 41 | +<[>-<<[<<<]>]>[-<<-<]<<]+<[>>+<<-]>[[<<<]<<]<]<<[ 42 | <<]+>>]>+>[>+++++[>++>++<<-]<-]>>>>>[<<<+>+>+>-]<- 43 | [>+<-]+<<[<[<+>-]<<[>>+<<-]>[<+>-]>>-]<<<[-]>>[<+> 44 | >>-<<-]++++++[>++++++++<-]>>[<+>--]<.[-]>>[>>>]>>] 45 | -------------------------------------------------------------------------------- /examples/h.bf: -------------------------------------------------------------------------------- 1 | Tests for several obscure problems and should output an H 2 | 3 | []++++++++++[>>+>+>++++++[<<+<+++>>>-]<<<<-] 4 | "A*$";?@![#>>+<<]>[>>]<<<<[>++<[-]]>.>. 5 | -------------------------------------------------------------------------------- /examples/hello_world.bf: -------------------------------------------------------------------------------- 1 | ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>. 2 | -------------------------------------------------------------------------------- /examples/infinite_loop.bf: -------------------------------------------------------------------------------- 1 | +[] 2 | -------------------------------------------------------------------------------- /examples/langton.bf: -------------------------------------------------------------------------------- 1 | ;; langton's ant random generator 2 | ;; written by Keymaker 3 | ;; this program does not terminate by itself 4 | 5 | >+>>++++++++>++>>>+>+<<<<<[[->>>>-[<+>>>+<<-]<[>+ 6 | <-]>+>-[<<+>>>++++++++++++++++<-]<<[>>+<<-]>>+>[> 7 | >>[>>]+[<<]<-]>>>[>>]>>+<<<[-<[<<]<+>>>[>>]>>-<<< 8 | ]>>>[<<<+>>>-]<<<<[-<<]<[->+<<<<<<<<<[<+>>+<-]>[< 9 | +>-]>>>>>>>]<<<<<<[<<[>+<-]>[<++>-]>[<+>-]]<[>+<- 10 | ]>>>>>>>+>[-<-<<<<<+>>>>>>]<[-<<<<<->>>>>]<<<<<[> 11 | +>+<<-]>[<+>-]+>[<->-[-[-[-[-<<---->>]]]]]<[-<+++ 12 | +>]<[>+>+<<-]>>[<<+>>-]+<-[-[-[->>-<<]>[->>+<<]<] 13 | >[->+<]<]>[->>-<<]>[<<+>+>-]<[>+<-]+<[>-<-[-[-[-[ 14 | -[-[-[-[-[-[-[-[-[-[-[-[->>----------------<<]]]] 15 | ]]]]]]]]]]]]]>[->++++++++++++++++<]>>[<<<+>+>>-]< 16 | <[>>+<<-]+<[>-<-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-> 17 | >>----------------<<<]]]]]]]]]]]]]]]]]>[->>++++++ 18 | ++++++++++<<]<<<]<<[-]+<.[-]>>>++++++++] 19 | -------------------------------------------------------------------------------- /examples/long.bf: -------------------------------------------------------------------------------- 1 | # A long running program for benchmarking 2 | 3 | >+>+>+>+>++<[>[<+++>- 4 | >>>>> 5 | >+>+>+>+>++<[>[<+++>- 6 | >>>>> 7 | >+>+>+>+>++<[>[<+++>- 8 | >>>>> 9 | >+>+>+>+>++<[>[<+++>- 10 | >>>>> 11 | +++[->+++++<]>[-]< 12 | <<<<< 13 | ]<<]>[-] 14 | <<<<< 15 | ]<<]>[-] 16 | <<<<< 17 | ]<<]>[-] 18 | <<<<< 19 | ]<<]>. 20 | -------------------------------------------------------------------------------- /examples/lost_kingdom_start.bf: -------------------------------------------------------------------------------- 1 | [-][.]>+<+ 2 | -------------------------------------------------------------------------------- /examples/mandelbrot_beginning.bf: -------------------------------------------------------------------------------- 1 | +++++++++++++[->++>>>+++++>++>+<<<<<<] 2 | -------------------------------------------------------------------------------- /examples/mul.bf: -------------------------------------------------------------------------------- 1 | ,>,<[>[>+>+<<-]>>[<<+>>-]<<<-]>>. 2 | -------------------------------------------------------------------------------- /examples/noop.bf: -------------------------------------------------------------------------------- 1 | ,+<<>>->><<-<><>+. 2 | -------------------------------------------------------------------------------- /examples/report_30k.bf: -------------------------------------------------------------------------------- 1 | Goes to cell 30000 and reports from there with a # (Verifies that the array is big enough) 2 | 3 | ++++[>++++++<-]>[>+++++>+++++++<<-]>>++++<[[>[[>>+<<-]<]>>>-]>-[>+>+<<-]>] 4 | +++++[>+++++++<<++>-]>.<<. 5 | -------------------------------------------------------------------------------- /examples/rot13.bf: -------------------------------------------------------------------------------- 1 | # Daniel B Cristofani cristofdathevanet(dot)com 2 | # http://www(dot)hevanet(dot)com/cristofd/brainfuck/ 3 | 4 | , 5 | [>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- 6 | [>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- 7 | [>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- 8 | [>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- 9 | [>++++++++++++++<- 10 | [>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- 11 | [>>+++++[<----->-]<<- 12 | [>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- 13 | [>++++++++++++++<- 14 | [>+<-[>+<-[>+<-[>+<-[>+<- 15 | [>++++++++++++++<- 16 | [>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- 17 | [>>+++++[<----->-]<<- 18 | [>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- 19 | [>++++++++++++++<- 20 | [>+<-]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] 21 | ]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]>.[-]<,] 22 | -------------------------------------------------------------------------------- /examples/shift.bf: -------------------------------------------------------------------------------- 1 | # Shifting a value from one cell to another 2 | ,[>>+<<-].>>. 3 | -------------------------------------------------------------------------------- /examples/sierpinski.bf: -------------------------------------------------------------------------------- 1 | # sierpinski(dot)b display Sierpinski triangle 2 | # (c) 2016 Daniel B(dot) Cristofani 3 | # http://brainfuck(dot)org/ 4 | 5 | ++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[ 6 | -<<<[ 7 | ->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<< 8 | ]>.>+[>>]>+ 9 | ] 10 | -------------------------------------------------------------------------------- /examples/square.bf: -------------------------------------------------------------------------------- 1 | # Cell 5 is the input value 2 | # Cell 8 is temp0 3 | # Cell 10 is temp1 4 | 5 | >>>>>, 6 | [>>>+<<<-] 7 | >>> 8 | [ 9 | - 10 | [>>+<<<<<++>>>-] 11 | <<<+>>>>> 12 | [<<+>>-] 13 | << 14 | ] 15 | <<<. 16 | -------------------------------------------------------------------------------- /examples/squares.bf: -------------------------------------------------------------------------------- 1 | # Sourced from http://www(dot)brainfuck(dot)org/squares(dot)b 2 | # 3 | # Daniel B Cristofani (cristofdathevanetdotcom) 4 | # http://www(dot)hevanet(dot)com/cristofd/brainfuck/ 5 | 6 | ++++[>+++++<-]>[<+++++>-]+<+[ 7 | >[>+>+<<-]++>>[<<+>>-]>>>[-]++>[-]+ 8 | >>>+[[-]++++++>>>]<<<[[<++++++++<++>>-]+<.<[>----<-]<] 9 | <<[>>>>>[>>>[-]+++++++++<[>-<-]+++++++++>[-[<->-]+[<<<]]<[>+<-]>]<<-]<<- 10 | ] 11 | -------------------------------------------------------------------------------- /examples/sub.bf: -------------------------------------------------------------------------------- 1 | # Load two values from stdin 2 | ,>, 3 | # Subtract them 4 | [-<->]< 5 | # Output the difference 6 | . 7 | -------------------------------------------------------------------------------- /examples/xmastree.bf: -------------------------------------------------------------------------------- 1 | # Sourced from http://www(dot)brainfuck(dot)org/xmastree(dot)b 2 | # 3 | # Daniel B Cristofani (cristofdathevanetdotcom) 4 | # http://www(dot)hevanet(dot)com/cristofd/brainfuck/ 5 | 6 | >>>--------<,[<[>++++++++++<-]>>[<------>>-<+],]++>>++<--[<++[+>]>+<<+++<]< 7 | <[>>+[[>>+<<-]<<]>>>>[[<<+>.>-]>>]<.<<<+<<-]>>[<.>--]>.>>. 8 | -------------------------------------------------------------------------------- /examples/zero_cell.bf: -------------------------------------------------------------------------------- 1 | # Get an unknown input value and store it at cell zero 2 | , 3 | # Zero out cell zero 4 | [-] 5 | # Output the value of cell zero which should be zero 6 | . 7 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use std::{num::NonZeroU16, path::PathBuf}; 3 | 4 | use crate::passes::PassConfig; 5 | 6 | #[derive(Debug, Parser)] 7 | #[clap(rename_all = "kebab-case")] 8 | pub enum Args { 9 | /// Optimize and execute a brainfuck file 10 | Run { 11 | /// The file to run 12 | file: PathBuf, 13 | 14 | #[clap(flatten)] 15 | settings: Settings, 16 | }, 17 | 18 | /// Optimize and run a brainfuck file along with all intermediate steps 19 | Debug { 20 | /// The file to debug 21 | file: PathBuf, 22 | 23 | #[clap(flatten)] 24 | settings: Settings, 25 | }, 26 | } 27 | 28 | #[derive(Debug, Parser)] 29 | #[clap(rename_all = "kebab-case")] 30 | pub struct Settings { 31 | /// The length of the program tape 32 | #[clap(long, default_value = "30000")] 33 | pub tape_len: NonZeroU16, 34 | 35 | /// The maximum number of optimization iterations 36 | #[clap(long)] 37 | pub iteration_limit: Option, 38 | 39 | /// The interpreter's step limit 40 | #[clap(long)] 41 | pub step_limit: Option, 42 | 43 | /// Prints the final optimized IR 44 | #[clap(long)] 45 | pub print_output_ir: bool, 46 | 47 | /// Whether to inline constant values or give them their own 48 | /// assignments in the output IR 49 | #[clap(long)] 50 | pub dont_inline_constants: bool, 51 | 52 | /// If this is set, then tape wrapping is disabled. This allows 53 | /// greater optimizations and forbids programs that overflow or underflow 54 | /// the program tape pointer, meaning that instead of wrapping around to 55 | /// opposite side of the tape when the tape pointer is above the tape's length 56 | /// or below zero the operation will instead be UB 57 | #[clap(long)] 58 | pub tape_wrapping_ub: bool, 59 | 60 | /// If this is set, then cell wrapping is disabled. This allows 61 | /// greater optimizations and forbids programs that overflow 62 | /// or underflow within cells 63 | #[clap(long)] 64 | pub cell_wrapping_ub: bool, 65 | 66 | /// Disables all optimizations 67 | #[clap(long)] 68 | pub disable_optimizations: bool, 69 | 70 | /// Run the unoptimized program before optimizing and running it again 71 | #[clap(long)] 72 | pub run_unoptimized_program: bool, 73 | 74 | /// Remove the interpreter's step limit 75 | #[clap(long)] 76 | pub no_step_limit: bool, 77 | 78 | #[clap(long)] 79 | pub no_run: bool, 80 | 81 | #[clap(long)] 82 | pub interpreter: bool, 83 | } 84 | 85 | impl Settings { 86 | pub fn step_limit(&self) -> usize { 87 | if self.no_step_limit { 88 | usize::MAX 89 | } else { 90 | self.step_limit.unwrap_or(usize::MAX) 91 | } 92 | } 93 | 94 | pub fn pass_config(&self) -> PassConfig { 95 | PassConfig::new( 96 | self.tape_len.get(), 97 | !self.tape_wrapping_ub, 98 | !self.cell_wrapping_ub, 99 | ) 100 | } 101 | } 102 | 103 | impl Default for Settings { 104 | fn default() -> Self { 105 | Self { 106 | tape_len: NonZeroU16::new(30_000).unwrap(), 107 | iteration_limit: None, 108 | step_limit: None, 109 | print_output_ir: false, 110 | dont_inline_constants: false, 111 | tape_wrapping_ub: false, 112 | cell_wrapping_ub: false, 113 | disable_optimizations: false, 114 | run_unoptimized_program: false, 115 | no_step_limit: false, 116 | no_run: false, 117 | interpreter: false, 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/graph/edge.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | 3 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 4 | pub enum EdgeKind { 5 | Effect, 6 | Value, 7 | } 8 | 9 | impl EdgeKind { 10 | /// Returns `true` if the edge kind is [`Effect`]. 11 | /// 12 | /// [`Effect`]: EdgeKind::Effect 13 | pub const fn is_effect(&self) -> bool { 14 | matches!(self, Self::Effect) 15 | } 16 | 17 | /// Returns `true` if the edge kind is [`Value`]. 18 | /// 19 | /// [`Value`]: EdgeKind::Value 20 | pub const fn is_value(&self) -> bool { 21 | matches!(self, Self::Value) 22 | } 23 | } 24 | 25 | impl Default for EdgeKind { 26 | fn default() -> Self { 27 | Self::Value 28 | } 29 | } 30 | 31 | impl Display for EdgeKind { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | match self { 34 | Self::Effect => f.write_str("effect"), 35 | Self::Value => f.write_str("value"), 36 | } 37 | } 38 | } 39 | 40 | #[derive(Debug, Clone, Copy)] 41 | pub struct EdgeDescriptor { 42 | effect: EdgeCount, 43 | value: EdgeCount, 44 | } 45 | 46 | impl EdgeDescriptor { 47 | pub const fn new(effect: EdgeCount, value: EdgeCount) -> Self { 48 | Self { effect, value } 49 | } 50 | 51 | pub const fn effect(&self) -> EdgeCount { 52 | self.effect 53 | } 54 | 55 | pub const fn value(&self) -> EdgeCount { 56 | self.value 57 | } 58 | 59 | pub const fn zero() -> Self { 60 | Self::new(EdgeCount::zero(), EdgeCount::zero()) 61 | } 62 | 63 | pub const fn from_values(values: usize) -> Self { 64 | Self::new(EdgeCount::zero(), EdgeCount::exact(values)) 65 | } 66 | 67 | pub const fn from_effects(effects: usize) -> Self { 68 | Self::new(EdgeCount::exact(effects), EdgeCount::zero()) 69 | } 70 | } 71 | 72 | #[derive(Debug, Clone, Copy)] 73 | pub struct EdgeCount { 74 | min: Option, 75 | max: Option, 76 | } 77 | 78 | impl EdgeCount { 79 | pub const fn new(min: Option, max: Option) -> Self { 80 | Self { min, max } 81 | } 82 | 83 | pub const fn unlimited() -> Self { 84 | Self::new(None, None) 85 | } 86 | 87 | pub const fn exact(count: usize) -> Self { 88 | Self::new(Some(count), Some(count)) 89 | } 90 | 91 | pub const fn at_least(minimum: usize) -> Self { 92 | Self::new(Some(minimum), None) 93 | } 94 | 95 | pub const fn zero() -> Self { 96 | Self::exact(0) 97 | } 98 | 99 | pub const fn one() -> Self { 100 | Self::exact(1) 101 | } 102 | 103 | pub const fn two() -> Self { 104 | Self::exact(2) 105 | } 106 | 107 | #[allow(dead_code)] 108 | pub const fn three() -> Self { 109 | Self::exact(3) 110 | } 111 | 112 | pub const fn contains(&self, value: usize) -> bool { 113 | match (self.min, self.max) { 114 | (Some(min), None) => value >= min, 115 | (None, Some(max)) => value <= max, 116 | (Some(min), Some(max)) => min <= value && value <= max, 117 | (None, None) => true, 118 | } 119 | } 120 | 121 | pub const fn min(&self) -> Option { 122 | self.min 123 | } 124 | 125 | pub const fn max(&self) -> Option { 126 | self.max 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/graph/nodes/cmp.rs: -------------------------------------------------------------------------------- 1 | use crate::graph::{ 2 | nodes::node_ext::{InputPortKinds, InputPorts, OutputPortKinds, OutputPorts}, 3 | EdgeDescriptor, EdgeKind, InputPort, Node, NodeExt, NodeId, OutputPort, Rvsdg, 4 | }; 5 | use tinyvec::tiny_vec; 6 | 7 | // TODO: Replace Eq and Neq with Cmp node generic over all comparison types 8 | 9 | #[derive(Debug, Clone, Copy, PartialEq)] 10 | pub struct Eq { 11 | node: NodeId, 12 | lhs: InputPort, 13 | rhs: InputPort, 14 | value: OutputPort, 15 | } 16 | 17 | impl Eq { 18 | pub(in crate::graph) const fn new( 19 | node: NodeId, 20 | lhs: InputPort, 21 | rhs: InputPort, 22 | value: OutputPort, 23 | ) -> Self { 24 | Self { 25 | node, 26 | lhs, 27 | rhs, 28 | value, 29 | } 30 | } 31 | 32 | /// Get the eq's left hand side 33 | pub const fn lhs(&self) -> InputPort { 34 | self.lhs 35 | } 36 | 37 | /// Get the eq's right hand side 38 | pub const fn rhs(&self) -> InputPort { 39 | self.rhs 40 | } 41 | 42 | pub const fn value(&self) -> OutputPort { 43 | self.value 44 | } 45 | } 46 | 47 | impl NodeExt for Eq { 48 | fn node(&self) -> NodeId { 49 | self.node 50 | } 51 | 52 | fn input_desc(&self) -> EdgeDescriptor { 53 | EdgeDescriptor::from_values(2) 54 | } 55 | 56 | fn all_input_ports(&self) -> InputPorts { 57 | tiny_vec![self.lhs, self.rhs] 58 | } 59 | 60 | fn all_input_port_kinds(&self) -> InputPortKinds { 61 | tiny_vec! { 62 | [_; 4] => 63 | (self.lhs, EdgeKind::Value), 64 | (self.rhs, EdgeKind::Value), 65 | } 66 | } 67 | 68 | fn update_input(&mut self, from: InputPort, to: InputPort) { 69 | if self.lhs == from { 70 | self.lhs = to; 71 | } 72 | 73 | if self.rhs == from { 74 | self.rhs = to; 75 | } 76 | } 77 | 78 | fn output_desc(&self) -> EdgeDescriptor { 79 | EdgeDescriptor::from_values(1) 80 | } 81 | 82 | fn all_output_ports(&self) -> OutputPorts { 83 | tiny_vec![self.value] 84 | } 85 | 86 | fn all_output_port_kinds(&self) -> OutputPortKinds { 87 | tiny_vec! { 88 | [_; 4] => (self.value, EdgeKind::Value), 89 | } 90 | } 91 | 92 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 93 | if self.value == from { 94 | self.value = to; 95 | } 96 | } 97 | } 98 | 99 | #[derive(Debug, Clone, Copy, PartialEq)] 100 | pub struct Neq { 101 | node: NodeId, 102 | lhs: InputPort, 103 | rhs: InputPort, 104 | value: OutputPort, 105 | } 106 | 107 | impl Neq { 108 | pub(in crate::graph) const fn new( 109 | node: NodeId, 110 | lhs: InputPort, 111 | rhs: InputPort, 112 | value: OutputPort, 113 | ) -> Self { 114 | Self { 115 | node, 116 | lhs, 117 | rhs, 118 | value, 119 | } 120 | } 121 | 122 | /// Get the not-eq's left hand side 123 | pub const fn lhs(&self) -> InputPort { 124 | self.lhs 125 | } 126 | 127 | /// Get the not-eq's right hand side 128 | pub const fn rhs(&self) -> InputPort { 129 | self.rhs 130 | } 131 | 132 | pub const fn value(&self) -> OutputPort { 133 | self.value 134 | } 135 | } 136 | 137 | impl NodeExt for Neq { 138 | fn node(&self) -> NodeId { 139 | self.node 140 | } 141 | 142 | fn input_desc(&self) -> EdgeDescriptor { 143 | EdgeDescriptor::from_values(2) 144 | } 145 | 146 | fn all_input_ports(&self) -> InputPorts { 147 | tiny_vec![self.lhs, self.rhs] 148 | } 149 | 150 | fn all_input_port_kinds(&self) -> InputPortKinds { 151 | tiny_vec! { 152 | [_; 4] => 153 | (self.lhs, EdgeKind::Value), 154 | (self.rhs, EdgeKind::Value), 155 | } 156 | } 157 | 158 | fn update_input(&mut self, from: InputPort, to: InputPort) { 159 | if self.lhs == from { 160 | self.lhs = to; 161 | } 162 | 163 | if self.rhs == from { 164 | self.rhs = to; 165 | } 166 | } 167 | 168 | fn output_desc(&self) -> EdgeDescriptor { 169 | EdgeDescriptor::from_values(1) 170 | } 171 | 172 | fn all_output_ports(&self) -> OutputPorts { 173 | tiny_vec![self.value] 174 | } 175 | 176 | fn all_output_port_kinds(&self) -> OutputPortKinds { 177 | tiny_vec! { 178 | [_; 4] => (self.value, EdgeKind::Value), 179 | } 180 | } 181 | 182 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 183 | if self.value == from { 184 | self.value = to; 185 | } 186 | } 187 | } 188 | 189 | #[derive(Debug, Clone, Copy, PartialEq)] 190 | pub enum EqOrNeq { 191 | Eq(Eq), 192 | Neq(Neq), 193 | } 194 | 195 | impl EqOrNeq { 196 | pub const fn lhs(&self) -> InputPort { 197 | match self { 198 | Self::Eq(eq) => eq.lhs(), 199 | Self::Neq(neq) => neq.lhs(), 200 | } 201 | } 202 | 203 | pub const fn rhs(&self) -> InputPort { 204 | match self { 205 | Self::Eq(eq) => eq.rhs(), 206 | Self::Neq(neq) => neq.rhs(), 207 | } 208 | } 209 | 210 | pub const fn value(&self) -> OutputPort { 211 | match self { 212 | Self::Eq(eq) => eq.value(), 213 | Self::Neq(neq) => neq.value(), 214 | } 215 | } 216 | 217 | pub fn cast_output_dest(graph: &Rvsdg, output: OutputPort) -> Option { 218 | graph.output_dest_node(output)?.try_into().ok() 219 | } 220 | 221 | pub fn cast_input_source(graph: &Rvsdg, input: InputPort) -> Option { 222 | graph.input_source_node(input).try_into().ok() 223 | } 224 | } 225 | 226 | impl TryFrom for EqOrNeq { 227 | type Error = Node; 228 | 229 | fn try_from(node: Node) -> Result { 230 | match node { 231 | Node::Eq(eq) => Ok(Self::Eq(eq)), 232 | Node::Neq(neq) => Ok(Self::Neq(neq)), 233 | node => Err(node), 234 | } 235 | } 236 | } 237 | 238 | impl<'a> TryFrom<&'a Node> for EqOrNeq { 239 | type Error = &'a Node; 240 | 241 | fn try_from(node: &'a Node) -> Result { 242 | match node { 243 | &Node::Eq(eq) => Ok(Self::Eq(eq)), 244 | &Node::Neq(neq) => Ok(Self::Neq(neq)), 245 | node => Err(node), 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/graph/nodes/io.rs: -------------------------------------------------------------------------------- 1 | //! IO related functions 2 | 3 | use crate::graph::{ 4 | nodes::node_ext::{InputPortKinds, InputPorts, OutputPortKinds, OutputPorts}, 5 | EdgeCount, EdgeDescriptor, EdgeKind, InputPort, NodeExt, NodeId, OutputPort, 6 | }; 7 | use tinyvec::{tiny_vec, TinyVec}; 8 | 9 | #[derive(Debug, Clone, Copy, PartialEq)] 10 | pub struct Input { 11 | node: NodeId, 12 | input_effect: InputPort, 13 | output_value: OutputPort, 14 | output_effect: OutputPort, 15 | } 16 | 17 | impl Input { 18 | pub(in crate::graph) const fn new( 19 | node: NodeId, 20 | input_effect: InputPort, 21 | output_value: OutputPort, 22 | output_effect: OutputPort, 23 | ) -> Self { 24 | Self { 25 | node, 26 | input_effect, 27 | output_value, 28 | output_effect, 29 | } 30 | } 31 | 32 | pub const fn input_effect(&self) -> InputPort { 33 | self.input_effect 34 | } 35 | 36 | pub const fn output_value(&self) -> OutputPort { 37 | self.output_value 38 | } 39 | 40 | pub const fn output_effect(&self) -> OutputPort { 41 | self.output_effect 42 | } 43 | } 44 | 45 | impl NodeExt for Input { 46 | fn node(&self) -> NodeId { 47 | self.node 48 | } 49 | 50 | fn input_desc(&self) -> EdgeDescriptor { 51 | EdgeDescriptor::from_effects(1) 52 | } 53 | 54 | fn all_input_ports(&self) -> InputPorts { 55 | tiny_vec![self.input_effect] 56 | } 57 | 58 | fn all_input_port_kinds(&self) -> InputPortKinds { 59 | tiny_vec! { 60 | [_; 4] => (self.input_effect, EdgeKind::Effect), 61 | } 62 | } 63 | 64 | fn update_input(&mut self, from: InputPort, to: InputPort) { 65 | if from == self.input_effect { 66 | self.input_effect = to; 67 | } 68 | } 69 | 70 | fn output_desc(&self) -> EdgeDescriptor { 71 | EdgeDescriptor::new(EdgeCount::one(), EdgeCount::one()) 72 | } 73 | 74 | fn all_output_ports(&self) -> OutputPorts { 75 | tiny_vec![self.output_value, self.output_effect] 76 | } 77 | 78 | fn all_output_port_kinds(&self) -> OutputPortKinds { 79 | tiny_vec! { 80 | [_; 4] => 81 | (self.output_value, EdgeKind::Value), 82 | (self.output_effect, EdgeKind::Effect), 83 | } 84 | } 85 | 86 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 87 | if self.output_value == from { 88 | self.output_value = to; 89 | } 90 | 91 | if self.output_effect == from { 92 | self.output_effect = to; 93 | } 94 | } 95 | } 96 | 97 | pub type OutputValues = TinyVec<[InputPort; 1]>; 98 | 99 | #[derive(Debug, Clone, PartialEq)] 100 | pub struct Output { 101 | node: NodeId, 102 | values: OutputValues, 103 | input_effect: InputPort, 104 | output_effect: OutputPort, 105 | } 106 | 107 | impl Output { 108 | pub(in crate::graph) const fn new( 109 | node: NodeId, 110 | values: OutputValues, 111 | input_effect: InputPort, 112 | output_effect: OutputPort, 113 | ) -> Self { 114 | Self { 115 | node, 116 | values, 117 | input_effect, 118 | output_effect, 119 | } 120 | } 121 | 122 | pub fn values(&self) -> &[InputPort] { 123 | &*self.values 124 | } 125 | 126 | pub fn values_mut(&mut self) -> &mut OutputValues { 127 | &mut self.values 128 | } 129 | 130 | pub const fn input_effect(&self) -> InputPort { 131 | self.input_effect 132 | } 133 | 134 | pub const fn output_effect(&self) -> OutputPort { 135 | self.output_effect 136 | } 137 | } 138 | 139 | impl NodeExt for Output { 140 | fn node(&self) -> NodeId { 141 | self.node 142 | } 143 | 144 | fn input_desc(&self) -> EdgeDescriptor { 145 | EdgeDescriptor::new(EdgeCount::one(), EdgeCount::at_least(1)) 146 | } 147 | 148 | fn all_input_ports(&self) -> InputPorts { 149 | let mut ports = TinyVec::with_capacity(self.values.len() + 1); 150 | ports.extend_from_slice(&self.values); 151 | ports.push(self.input_effect); 152 | ports 153 | } 154 | 155 | fn all_input_port_kinds(&self) -> InputPortKinds { 156 | let mut ports = TinyVec::with_capacity(self.values.len() + 1); 157 | ports.extend(self.values.iter().map(|&value| (value, EdgeKind::Value))); 158 | ports.push((self.input_effect, EdgeKind::Effect)); 159 | ports 160 | } 161 | 162 | fn update_input(&mut self, from: InputPort, to: InputPort) { 163 | for value in &mut self.values { 164 | if from == *value { 165 | *value = to; 166 | } 167 | } 168 | 169 | if from == self.input_effect { 170 | self.input_effect = to; 171 | } 172 | } 173 | 174 | fn output_desc(&self) -> EdgeDescriptor { 175 | EdgeDescriptor::from_effects(1) 176 | } 177 | 178 | fn all_output_ports(&self) -> OutputPorts { 179 | tiny_vec![self.output_effect] 180 | } 181 | 182 | fn all_output_port_kinds(&self) -> OutputPortKinds { 183 | tiny_vec! { 184 | [_; 4] => (self.output_effect, EdgeKind::Effect), 185 | } 186 | } 187 | 188 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 189 | if self.output_effect == from { 190 | self.output_effect = to; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/graph/nodes/memory.rs: -------------------------------------------------------------------------------- 1 | //! Operations on program memory 2 | 3 | use crate::graph::{ 4 | nodes::node_ext::{InputPortKinds, InputPorts, OutputPortKinds, OutputPorts}, 5 | EdgeCount, EdgeDescriptor, EdgeKind, InputPort, NodeExt, NodeId, OutputPort, 6 | }; 7 | use tinyvec::tiny_vec; 8 | 9 | #[derive(Debug, Clone, Copy, PartialEq)] 10 | pub struct Load { 11 | node: NodeId, 12 | ptr: InputPort, 13 | input_effect: InputPort, 14 | output_value: OutputPort, 15 | output_effect: OutputPort, 16 | } 17 | 18 | impl Load { 19 | pub(in crate::graph) const fn new( 20 | node: NodeId, 21 | ptr: InputPort, 22 | input_effect: InputPort, 23 | output_value: OutputPort, 24 | output_effect: OutputPort, 25 | ) -> Self { 26 | Self { 27 | node, 28 | ptr, 29 | input_effect, 30 | output_value, 31 | output_effect, 32 | } 33 | } 34 | 35 | pub const fn ptr(&self) -> InputPort { 36 | self.ptr 37 | } 38 | 39 | pub const fn output_value(&self) -> OutputPort { 40 | self.output_value 41 | } 42 | 43 | pub const fn input_effect(&self) -> InputPort { 44 | self.input_effect 45 | } 46 | 47 | pub const fn output_effect(&self) -> OutputPort { 48 | self.output_effect 49 | } 50 | } 51 | 52 | impl NodeExt for Load { 53 | fn node(&self) -> NodeId { 54 | self.node 55 | } 56 | 57 | fn input_desc(&self) -> EdgeDescriptor { 58 | EdgeDescriptor::new(EdgeCount::one(), EdgeCount::one()) 59 | } 60 | 61 | fn all_input_ports(&self) -> InputPorts { 62 | tiny_vec![self.ptr, self.input_effect] 63 | } 64 | 65 | fn all_input_port_kinds(&self) -> InputPortKinds { 66 | tiny_vec! { 67 | [_; 4] => 68 | (self.ptr, EdgeKind::Value), 69 | (self.input_effect, EdgeKind::Effect), 70 | } 71 | } 72 | 73 | fn update_input(&mut self, from: InputPort, to: InputPort) { 74 | if from == self.ptr { 75 | self.ptr = to; 76 | } 77 | 78 | if from == self.input_effect { 79 | self.input_effect = to; 80 | } 81 | } 82 | 83 | fn output_desc(&self) -> EdgeDescriptor { 84 | EdgeDescriptor::new(EdgeCount::one(), EdgeCount::one()) 85 | } 86 | 87 | fn all_output_ports(&self) -> OutputPorts { 88 | tiny_vec![self.output_value, self.output_effect] 89 | } 90 | 91 | fn all_output_port_kinds(&self) -> OutputPortKinds { 92 | tiny_vec! { 93 | [_; 4] => 94 | (self.output_value, EdgeKind::Value), 95 | (self.output_effect, EdgeKind::Effect), 96 | } 97 | } 98 | 99 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 100 | if self.output_value == from { 101 | self.output_value = to; 102 | } 103 | 104 | if self.output_effect == from { 105 | self.output_effect = to; 106 | } 107 | } 108 | } 109 | 110 | #[derive(Debug, Clone, Copy, PartialEq)] 111 | pub struct Store { 112 | node: NodeId, 113 | ptr: InputPort, 114 | value: InputPort, 115 | input_effect: InputPort, 116 | output_effect: OutputPort, 117 | } 118 | 119 | impl Store { 120 | pub(in crate::graph) const fn new( 121 | node: NodeId, 122 | ptr: InputPort, 123 | value: InputPort, 124 | input_effect: InputPort, 125 | output_effect: OutputPort, 126 | ) -> Self { 127 | Self { 128 | node, 129 | ptr, 130 | value, 131 | input_effect, 132 | output_effect, 133 | } 134 | } 135 | 136 | pub const fn ptr(&self) -> InputPort { 137 | self.ptr 138 | } 139 | 140 | pub const fn value(&self) -> InputPort { 141 | self.value 142 | } 143 | 144 | pub const fn effect_in(&self) -> InputPort { 145 | self.input_effect 146 | } 147 | 148 | pub const fn output_effect(&self) -> OutputPort { 149 | self.output_effect 150 | } 151 | } 152 | 153 | impl NodeExt for Store { 154 | fn node(&self) -> NodeId { 155 | self.node 156 | } 157 | 158 | fn input_desc(&self) -> EdgeDescriptor { 159 | EdgeDescriptor::new(EdgeCount::one(), EdgeCount::two()) 160 | } 161 | 162 | fn all_input_ports(&self) -> InputPorts { 163 | tiny_vec![self.ptr, self.value, self.input_effect] 164 | } 165 | 166 | fn all_input_port_kinds(&self) -> InputPortKinds { 167 | tiny_vec! { 168 | [_; 4] => 169 | (self.ptr, EdgeKind::Value), 170 | (self.value, EdgeKind::Value), 171 | (self.input_effect, EdgeKind::Effect), 172 | } 173 | } 174 | 175 | fn update_input(&mut self, from: InputPort, to: InputPort) { 176 | if self.ptr == from { 177 | self.ptr = to; 178 | } 179 | 180 | if self.value == from { 181 | self.value = to; 182 | } 183 | 184 | if self.input_effect == from { 185 | self.input_effect = to; 186 | } 187 | } 188 | 189 | fn output_desc(&self) -> EdgeDescriptor { 190 | EdgeDescriptor::from_effects(1) 191 | } 192 | 193 | fn all_output_ports(&self) -> OutputPorts { 194 | tiny_vec![self.output_effect] 195 | } 196 | 197 | fn all_output_port_kinds(&self) -> OutputPortKinds { 198 | tiny_vec! { 199 | [_; 4] => (self.output_effect, EdgeKind::Effect), 200 | } 201 | } 202 | 203 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 204 | if self.output_effect == from { 205 | self.output_effect = to; 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/graph/nodes/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod node_macros; 3 | mod cmp; 4 | mod gamma; 5 | mod inherent; 6 | mod io; 7 | mod memory; 8 | mod node; 9 | mod node_id; 10 | mod ops; 11 | mod scan; 12 | mod theta; 13 | mod values; 14 | 15 | pub mod node_ext; 16 | 17 | pub use cmp::{Eq, EqOrNeq, Neq}; 18 | pub use gamma::{Gamma, GammaData}; 19 | pub use inherent::{End, InputParam, OutputParam, Start}; 20 | pub use io::{Input, Output}; 21 | pub use memory::{Load, Store}; 22 | pub use node::Node; 23 | pub use node_ext::NodeExt; 24 | pub use node_id::NodeId; 25 | pub use ops::{Add, AddOrSub, Mul, Neg, Not, Sub}; 26 | pub use scan::{Scan, ScanDirection}; 27 | pub use theta::{Theta, ThetaData}; 28 | pub use values::{Bool, Byte, Int}; 29 | 30 | pub(in crate::graph) use theta::ThetaEffects; 31 | -------------------------------------------------------------------------------- /src/graph/nodes/node.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{ 3 | nodes::{ 4 | node_ext::{InputPortKinds, InputPorts, OutputPortKinds, OutputPorts}, 5 | Scan, 6 | }, 7 | Add, Bool, Byte, EdgeDescriptor, End, Eq, Gamma, Input, InputParam, InputPort, Int, Load, 8 | Mul, Neg, Neq, NodeExt, NodeId, Not, Output, OutputParam, OutputPort, Start, Store, Sub, 9 | Theta, 10 | }, 11 | values::{Cell, Ptr}, 12 | }; 13 | 14 | macro_rules! Node { 15 | ($($variant:ident($($ty:ty),+ $(,)?)),* $(,)?) => { 16 | #[derive(Debug, Clone, PartialEq)] 17 | pub enum Node { 18 | $($variant($($ty),+),)* 19 | } 20 | 21 | impl Node { 22 | // TODO: Make this part of the `NodeExt` trait? 23 | #[inline] 24 | pub const fn kind(&self) -> NodeKind { 25 | match self { 26 | $(Self::$variant(..) => NodeKind::$variant,)* 27 | } 28 | } 29 | } 30 | 31 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 32 | pub enum NodeKind { 33 | $($variant,)* 34 | } 35 | }; 36 | } 37 | 38 | // TODO: derive_more? 39 | // TODO: Type casting node(s) 40 | // TODO: Byte node? 41 | Node! { 42 | Int(Int, Ptr), 43 | Byte(Byte, Cell), 44 | Bool(Bool, bool), 45 | 46 | Add(Add), 47 | Sub(Sub), 48 | Mul(Mul), 49 | 50 | Eq(Eq), 51 | Neq(Neq), 52 | 53 | Not(Not), 54 | Neg(Neg), 55 | 56 | Load(Load), 57 | Store(Store), 58 | Scan(Scan), 59 | 60 | Input(Input), 61 | Output(Output), 62 | 63 | Theta(Box), 64 | Gamma(Box), 65 | 66 | InputParam(InputParam), 67 | OutputParam(OutputParam), 68 | 69 | Start(Start), 70 | End(End), 71 | } 72 | 73 | node_ext! { 74 | Int, 75 | Byte, 76 | Bool, 77 | Add, 78 | Sub, 79 | Mul, 80 | Eq, 81 | Neq, 82 | Not, 83 | Neg, 84 | Load, 85 | Store, 86 | Scan, 87 | Input, 88 | Output, 89 | Theta, 90 | Gamma, 91 | InputParam, 92 | OutputParam, 93 | Start, 94 | End, 95 | } 96 | 97 | impl Node { 98 | pub fn as_byte_value(&self) -> Option { 99 | if let Self::Byte(_, byte) = *self { 100 | Some(byte) 101 | } else { 102 | None 103 | } 104 | } 105 | 106 | pub fn as_int_value(&self) -> Option { 107 | if let Self::Int(_, int) = *self { 108 | Some(int) 109 | } else { 110 | None 111 | } 112 | } 113 | 114 | #[track_caller] 115 | #[allow(dead_code)] 116 | pub fn to_int_value(&self) -> Ptr { 117 | if let Self::Int(_, int) = *self { 118 | int 119 | } else { 120 | panic!("attempted to get int, got {:?}", self); 121 | } 122 | } 123 | 124 | pub fn as_bool_value(&self) -> Option { 125 | if let Self::Bool(_, bool) = *self { 126 | Some(bool) 127 | } else { 128 | None 129 | } 130 | } 131 | 132 | #[track_caller] 133 | #[allow(dead_code)] 134 | pub fn to_bool_value(&self) -> bool { 135 | if let Self::Bool(_, bool) = *self { 136 | bool 137 | } else { 138 | panic!("attempted to get bool, got {:?}", self); 139 | } 140 | } 141 | 142 | pub const fn is_const_number(&self) -> bool { 143 | matches!(self, Self::Byte(..) | Self::Int(..)) 144 | } 145 | 146 | pub(crate) fn is_constant(&self) -> bool { 147 | matches!(self, Self::Byte(..) | Self::Int(..) | Self::Bool(..)) 148 | } 149 | } 150 | 151 | node_methods! { 152 | Int, 153 | Byte, 154 | Bool, 155 | Add, 156 | Sub, 157 | Mul, 158 | Load, 159 | Store, 160 | Scan, 161 | Start, 162 | End, 163 | Input, 164 | Output, 165 | Eq, 166 | Neq, 167 | Not, 168 | Neg, 169 | InputParam, 170 | OutputParam, 171 | Gamma, 172 | Theta, 173 | } 174 | 175 | node_conversions! { 176 | Add, 177 | Sub, 178 | Mul, 179 | Load, 180 | Store, 181 | Scan, 182 | Start, 183 | End, 184 | Input, 185 | Output, 186 | Eq, 187 | Neq, 188 | Not, 189 | Neg, 190 | InputParam, 191 | OutputParam, 192 | } 193 | 194 | impl From for Node { 195 | fn from(node: Gamma) -> Self { 196 | Self::Gamma(Box::new(node)) 197 | } 198 | } 199 | 200 | impl From for Node { 201 | fn from(node: Theta) -> Self { 202 | Self::Theta(Box::new(node)) 203 | } 204 | } 205 | 206 | impl TryInto for Node { 207 | type Error = Self; 208 | 209 | fn try_into(self) -> Result { 210 | if let Self::Gamma(node) = self { 211 | Ok(*node) 212 | } else { 213 | Err(self) 214 | } 215 | } 216 | } 217 | 218 | impl<'a> TryInto<&'a Gamma> for &'a Node { 219 | type Error = Self; 220 | 221 | fn try_into(self) -> Result<&'a Gamma, Self::Error> { 222 | if let Node::Gamma(node) = self { 223 | Ok(node) 224 | } else { 225 | Err(self) 226 | } 227 | } 228 | } 229 | 230 | impl TryInto for Node { 231 | type Error = Self; 232 | 233 | fn try_into(self) -> Result { 234 | if let Self::Theta(node) = self { 235 | Ok(*node) 236 | } else { 237 | Err(self) 238 | } 239 | } 240 | } 241 | 242 | impl<'a> TryInto<&'a Theta> for &'a Node { 243 | type Error = Self; 244 | 245 | fn try_into(self) -> Result<&'a Theta, Self::Error> { 246 | if let Node::Theta(node) = self { 247 | Ok(node) 248 | } else { 249 | Err(self) 250 | } 251 | } 252 | } 253 | 254 | impl<'a> TryInto<&'a Bool> for &'a Node { 255 | type Error = Self; 256 | 257 | fn try_into(self) -> Result<&'a Bool, Self::Error> { 258 | if let Node::Bool(node, _) = self { 259 | Ok(node) 260 | } else { 261 | Err(self) 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/graph/nodes/node_ext.rs: -------------------------------------------------------------------------------- 1 | //! The [`NodeExt`] trait for behavior shared between nodes 2 | 3 | use crate::graph::{EdgeDescriptor, EdgeKind, InputPort, NodeId, OutputPort}; 4 | use tinyvec::TinyVec; 5 | 6 | pub type InputPorts = TinyVec<[InputPort; 4]>; 7 | pub type InputPortKinds = TinyVec<[(InputPort, EdgeKind); 4]>; 8 | 9 | pub type OutputPorts = TinyVec<[OutputPort; 4]>; 10 | pub type OutputPortKinds = TinyVec<[(OutputPort, EdgeKind); 4]>; 11 | 12 | // TODO: 13 | // - .effect_inputs() 14 | // - .value_inputs() 15 | // - .effect_outputs() 16 | // - .value_outputs() 17 | // - .has_effect_inputs() 18 | // - .has_value_inputs() 19 | // - .has_effect_outputs() 20 | // - .has_value_outputs() 21 | // - .update_output() 22 | // - .update_outputs() 23 | // - .num_input_ports() 24 | // - .num_input_effect_ports() 25 | // - .num_input_value_ports() 26 | // - .num_output_ports() 27 | // - .num_output_effect_ports() 28 | // - .num_output_value_ports() 29 | // - .has_input(&self, InputPort) -> bool 30 | // - .has_output(&self, OutputPort) -> bool 31 | pub trait NodeExt { 32 | fn node(&self) -> NodeId; 33 | 34 | fn input_desc(&self) -> EdgeDescriptor; 35 | 36 | fn all_input_ports(&self) -> InputPorts; 37 | 38 | fn all_input_port_kinds(&self) -> InputPortKinds; 39 | 40 | fn update_inputs(&mut self, mut update: F) -> bool 41 | where 42 | F: FnMut(InputPort, EdgeKind) -> Option, 43 | { 44 | let mut changed = false; 45 | for (input, kind) in self.all_input_port_kinds() { 46 | if let Some(new_input) = update(input, kind) { 47 | self.update_input(input, new_input); 48 | changed = true; 49 | } 50 | } 51 | 52 | changed 53 | } 54 | 55 | // TODO: Should this return a bool? 56 | fn update_input(&mut self, from: InputPort, to: InputPort); 57 | 58 | fn output_desc(&self) -> EdgeDescriptor; 59 | 60 | fn all_output_ports(&self) -> OutputPorts; 61 | 62 | fn all_output_port_kinds(&self) -> OutputPortKinds; 63 | 64 | fn update_outputs(&mut self, mut update: F) -> bool 65 | where 66 | F: FnMut(OutputPort, EdgeKind) -> Option, 67 | { 68 | let mut changed = false; 69 | for (output, kind) in self.all_output_port_kinds() { 70 | if let Some(new_input) = update(output, kind) { 71 | self.update_output(output, new_input); 72 | changed = true; 73 | } 74 | } 75 | 76 | changed 77 | } 78 | 79 | // TODO: Should this return a bool? 80 | fn update_output(&mut self, from: OutputPort, to: OutputPort); 81 | 82 | // fn kind(&self) -> NodeKind; 83 | } 84 | -------------------------------------------------------------------------------- /src/graph/nodes/node_id.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Debug, Display, Write}; 2 | 3 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 4 | #[repr(transparent)] 5 | pub struct NodeId(pub u32); 6 | 7 | impl NodeId { 8 | pub(in crate::graph) const fn new(id: u32) -> Self { 9 | Self(id) 10 | } 11 | } 12 | 13 | impl Debug for NodeId { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | f.write_str("NodeId(")?; 16 | Debug::fmt(&self.0, f)?; 17 | f.write_char(')') 18 | } 19 | } 20 | 21 | impl Display for NodeId { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | f.write_char('n')?; 24 | Display::fmt(&self.0, f) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/graph/nodes/node_macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! node_ext { 2 | ($($variant:ident),+ $(,)?) => { 3 | impl NodeExt for Node { 4 | fn node(&self) -> NodeId { 5 | match self { 6 | $(Self::$variant(node, ..) => node.node(),)+ 7 | } 8 | } 9 | 10 | fn input_desc(&self) -> EdgeDescriptor { 11 | match self { 12 | $(Self::$variant(node, ..) => node.input_desc(),)+ 13 | } 14 | } 15 | 16 | fn all_input_ports(&self) -> InputPorts { 17 | match self { 18 | $(Self::$variant(node, ..) => node.all_input_ports(),)+ 19 | } 20 | } 21 | 22 | fn all_input_port_kinds(&self) -> InputPortKinds { 23 | match self { 24 | $(Self::$variant(node, ..) => node.all_input_port_kinds(),)+ 25 | } 26 | } 27 | 28 | fn update_input(&mut self, from: InputPort, to: InputPort) { 29 | match self { 30 | $(Self::$variant(node, ..) => node.update_input(from, to),)+ 31 | } 32 | } 33 | 34 | fn output_desc(&self) -> EdgeDescriptor { 35 | match self { 36 | $(Self::$variant(node, ..) => node.output_desc(),)+ 37 | } 38 | } 39 | 40 | fn all_output_ports(&self) -> OutputPorts { 41 | match self { 42 | $(Self::$variant(node, ..) => node.all_output_ports(),)+ 43 | } 44 | } 45 | 46 | fn all_output_port_kinds(&self) -> OutputPortKinds { 47 | match self { 48 | $(Self::$variant(node, ..) => node.all_output_port_kinds(),)+ 49 | } 50 | } 51 | 52 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 53 | match self { 54 | $(Self::$variant(node, ..) => node.update_output(from, to),)+ 55 | } 56 | } 57 | } 58 | }; 59 | } 60 | 61 | macro_rules! node_methods { 62 | ($($type:ident $(as $name:ident)?),* $(,)?) => { 63 | use paste::paste; 64 | 65 | $( 66 | impl Node { 67 | paste! { 68 | pub const fn [](&self) -> bool { 69 | matches!(self, node_methods!(@pat $type, $($name)?)) 70 | } 71 | 72 | pub fn [](&self) -> Option<&$type> { 73 | if let node_methods!(@variant node, $type, $($name)?) = self { 74 | Some(node) 75 | } else { 76 | None 77 | } 78 | } 79 | 80 | pub fn [](&mut self) -> Option<&mut $type> { 81 | if let node_methods!(@variant node, $type, $($name)?) = self { 82 | Some(node) 83 | } else { 84 | None 85 | } 86 | } 87 | 88 | #[track_caller] 89 | pub fn [](&self) -> &$type { 90 | if let node_methods!(@variant node, $type, $($name)?) = self { 91 | node 92 | } else { 93 | panic!( 94 | concat!("attempted to get", stringify!($type), " got {:?}"), 95 | self, 96 | ); 97 | } 98 | } 99 | 100 | #[track_caller] 101 | pub fn [](&mut self) -> &mut $type { 102 | if let node_methods!(@variant node, $type, $($name)?) = self { 103 | node 104 | } else { 105 | panic!( 106 | concat!("attempted to get", stringify!($type), " got {:?}"), 107 | self, 108 | ); 109 | } 110 | } 111 | } 112 | } 113 | )* 114 | }; 115 | 116 | (@variant $inner:ident, $variant_type:ident, $variant_name:ident $(,)?) => { Node::$variant_name($inner, ..) }; 117 | (@variant $inner:ident, $variant_type:ident $(,)?) => { Node::$variant_type($inner, ..) }; 118 | 119 | (@pat $variant_type:ident, $variant_name:ident $(,)?) => { Node::$variant_name(..) }; 120 | (@pat $variant_type:ident $(,)?) => { Node::$variant_type(..) }; 121 | } 122 | 123 | macro_rules! node_conversions { 124 | ($($type:ident $(as $name:ident)?),* $(,)?) => { 125 | $( 126 | impl From<$type> for Node { 127 | fn from(node: $type) -> Self { 128 | node_conversions!(@variant node, $type, $($name)?) 129 | } 130 | } 131 | 132 | impl TryInto<$type> for Node { 133 | type Error = Self; 134 | 135 | fn try_into(self) -> Result<$type, Self::Error> { 136 | if let node_conversions!(@variant node, $type, $($name)?) = self { 137 | Ok(node) 138 | } else { 139 | Err(self) 140 | } 141 | } 142 | } 143 | 144 | impl TryInto<$type> for &Node { 145 | type Error = Self; 146 | 147 | fn try_into(self) -> Result<$type, Self::Error> { 148 | if let node_conversions!(@variant node, $type, $($name)?) = self { 149 | Ok(node.clone()) 150 | } else { 151 | Err(self) 152 | } 153 | } 154 | } 155 | 156 | impl<'a> TryInto<&'a $type> for &'a Node { 157 | type Error = Self; 158 | 159 | fn try_into(self) -> Result<&'a $type, Self::Error> { 160 | if let node_conversions!(@variant node, $type, $($name)?) = self { 161 | Ok(node) 162 | } else { 163 | Err(self) 164 | } 165 | } 166 | } 167 | )* 168 | }; 169 | 170 | (@variant $inner:ident, $variant_type:ident, $variant_name:ident $(,)?) => { Node::$variant_name($inner, ..) }; 171 | (@variant $inner:ident, $variant_type:ident $(,)?) => { Node::$variant_type($inner) }; 172 | 173 | (@pat $variant_type:ident, $variant_name:ident $(,)?) => { Node::$variant_name(..) }; 174 | (@pat $variant_type:ident $(,)?) => { Node::$variant_type(..) }; 175 | } 176 | -------------------------------------------------------------------------------- /src/graph/nodes/scan.rs: -------------------------------------------------------------------------------- 1 | use crate::graph::{ 2 | nodes::node_ext::{InputPortKinds, InputPorts, OutputPortKinds, OutputPorts}, 3 | EdgeCount, EdgeDescriptor, EdgeKind, InputPort, NodeExt, NodeId, OutputPort, 4 | }; 5 | use tinyvec::tiny_vec; 6 | 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 8 | pub struct Scan { 9 | /// The id of the scan node 10 | node: NodeId, 11 | 12 | /// The direction the scan goes in, forwards or backwards 13 | direction: ScanDirection, 14 | 15 | /// The pointer to start scanning at 16 | ptr: InputPort, 17 | 18 | /// The distance the scan steps by 19 | step: InputPort, 20 | 21 | /// The byte values being scanned for 22 | needle: InputPort, 23 | 24 | /// A pointer to a cell containing one of the requested needles, 25 | /// effectively `ptr ± offset` where `offset` is the offset to a cell 26 | /// with one of the requested needles and `ptr` is `self.ptr`. 27 | /// 28 | /// The relative offset can be either positive or negative (that is, 29 | /// the output pointer can be greater or less than the input pointer) 30 | /// no matter the scan direction because of the wrapping semantics of 31 | /// the program tape. 32 | /// 33 | /// Loops infinitely if no cells with the proper needle value can be found 34 | output_ptr: OutputPort, 35 | 36 | /// Input effect stream 37 | input_effect: InputPort, 38 | 39 | /// Output effect stream 40 | output_effect: OutputPort, 41 | } 42 | 43 | impl Scan { 44 | #[must_use] 45 | #[allow(clippy::too_many_arguments)] 46 | pub(in crate::graph) const fn new( 47 | node: NodeId, 48 | direction: ScanDirection, 49 | ptr: InputPort, 50 | step: InputPort, 51 | needle: InputPort, 52 | output_ptr: OutputPort, 53 | input_effect: InputPort, 54 | output_effect: OutputPort, 55 | ) -> Self { 56 | Self { 57 | node, 58 | direction, 59 | ptr, 60 | step, 61 | needle, 62 | output_ptr, 63 | input_effect, 64 | output_effect, 65 | } 66 | } 67 | 68 | /// Get the scan's direction 69 | pub const fn direction(&self) -> ScanDirection { 70 | self.direction 71 | } 72 | 73 | /// Get the scan's pointer 74 | pub const fn ptr(&self) -> InputPort { 75 | self.ptr 76 | } 77 | 78 | /// Get the scan's step 79 | pub const fn step(&self) -> InputPort { 80 | self.step 81 | } 82 | 83 | /// Get the scan's output pointer 84 | pub const fn output_ptr(&self) -> OutputPort { 85 | self.output_ptr 86 | } 87 | 88 | /// Get the scan's needle 89 | pub const fn needle(&self) -> InputPort { 90 | self.needle 91 | } 92 | 93 | /// Get the scan's input effect 94 | pub const fn input_effect(&self) -> InputPort { 95 | self.input_effect 96 | } 97 | 98 | /// Get the scan's output effect 99 | pub const fn output_effect(&self) -> OutputPort { 100 | self.output_effect 101 | } 102 | } 103 | 104 | impl NodeExt for Scan { 105 | fn node(&self) -> NodeId { 106 | self.node 107 | } 108 | 109 | fn input_desc(&self) -> EdgeDescriptor { 110 | EdgeDescriptor::new(EdgeCount::one(), EdgeCount::exact(3)) 111 | } 112 | 113 | fn all_input_ports(&self) -> InputPorts { 114 | tiny_vec![self.ptr, self.step, self.needle, self.input_effect] 115 | } 116 | 117 | fn all_input_port_kinds(&self) -> InputPortKinds { 118 | tiny_vec![[_; 4] => 119 | (self.ptr, EdgeKind::Value), 120 | (self.step, EdgeKind::Value), 121 | (self.needle, EdgeKind::Value), 122 | (self.input_effect, EdgeKind::Effect), 123 | ] 124 | } 125 | 126 | fn update_input(&mut self, from: InputPort, to: InputPort) { 127 | if from == self.ptr { 128 | self.ptr = to; 129 | } 130 | 131 | if from == self.step { 132 | self.step = to; 133 | } 134 | 135 | if from == self.needle { 136 | self.needle = to; 137 | } 138 | 139 | if from == self.input_effect { 140 | self.input_effect = to; 141 | } 142 | } 143 | 144 | fn output_desc(&self) -> EdgeDescriptor { 145 | EdgeDescriptor::new(EdgeCount::one(), EdgeCount::one()) 146 | } 147 | 148 | fn all_output_ports(&self) -> OutputPorts { 149 | tiny_vec![self.output_ptr, self.output_effect] 150 | } 151 | 152 | fn all_output_port_kinds(&self) -> OutputPortKinds { 153 | tiny_vec![[_; 4] => 154 | (self.output_ptr, EdgeKind::Value), 155 | (self.output_effect, EdgeKind::Effect), 156 | ] 157 | } 158 | 159 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 160 | if self.output_ptr == from { 161 | self.output_ptr = to; 162 | } 163 | 164 | if self.output_effect == from { 165 | self.output_effect = to; 166 | } 167 | } 168 | } 169 | 170 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 171 | pub enum ScanDirection { 172 | Forward, 173 | Backward, 174 | } 175 | 176 | impl ScanDirection { 177 | /// Returns `true` if the scan direction is [`Forward`]. 178 | /// 179 | /// [`Forward`]: ScanDirection::Forward 180 | #[must_use] 181 | pub const fn is_forward(&self) -> bool { 182 | matches!(self, Self::Forward) 183 | } 184 | 185 | /// Returns `true` if the scan direction is [`Backward`]. 186 | /// 187 | /// [`Backward`]: ScanDirection::Backward 188 | #[must_use] 189 | pub const fn is_backward(&self) -> bool { 190 | matches!(self, Self::Backward) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/graph/nodes/values.rs: -------------------------------------------------------------------------------- 1 | use crate::graph::{ 2 | nodes::node_ext::{InputPortKinds, InputPorts, OutputPortKinds, OutputPorts}, 3 | EdgeDescriptor, EdgeKind, InputPort, NodeExt, NodeId, OutputPort, 4 | }; 5 | use tinyvec::{tiny_vec, TinyVec}; 6 | 7 | // TODO: Does there need to be a distinction between pointers and 8 | // larger-than-byte integers? 9 | #[derive(Debug, Clone, Copy, PartialEq)] 10 | pub struct Int { 11 | node: NodeId, 12 | value: OutputPort, 13 | } 14 | 15 | impl Int { 16 | pub(in crate::graph) const fn new(node: NodeId, value: OutputPort) -> Self { 17 | Self { node, value } 18 | } 19 | 20 | pub const fn value(&self) -> OutputPort { 21 | self.value 22 | } 23 | } 24 | 25 | impl NodeExt for Int { 26 | fn node(&self) -> NodeId { 27 | self.node 28 | } 29 | 30 | fn input_desc(&self) -> EdgeDescriptor { 31 | EdgeDescriptor::zero() 32 | } 33 | 34 | fn all_input_ports(&self) -> InputPorts { 35 | TinyVec::new() 36 | } 37 | 38 | fn all_input_port_kinds(&self) -> InputPortKinds { 39 | TinyVec::new() 40 | } 41 | 42 | fn update_input(&mut self, _from: InputPort, _to: InputPort) { 43 | // TODO: Should this be a panic or warning? 44 | } 45 | 46 | fn output_desc(&self) -> EdgeDescriptor { 47 | EdgeDescriptor::from_values(1) 48 | } 49 | 50 | fn all_output_ports(&self) -> OutputPorts { 51 | tiny_vec![self.value] 52 | } 53 | 54 | fn all_output_port_kinds(&self) -> OutputPortKinds { 55 | tiny_vec! { 56 | [_; 4] => (self.value, EdgeKind::Value), 57 | } 58 | } 59 | 60 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 61 | if self.value == from { 62 | self.value = to; 63 | } 64 | } 65 | } 66 | 67 | #[derive(Debug, Clone, Copy, PartialEq)] 68 | pub struct Byte { 69 | node: NodeId, 70 | value: OutputPort, 71 | } 72 | 73 | impl Byte { 74 | pub(in crate::graph) const fn new(node: NodeId, value: OutputPort) -> Self { 75 | Self { node, value } 76 | } 77 | 78 | pub const fn value(&self) -> OutputPort { 79 | self.value 80 | } 81 | } 82 | 83 | impl NodeExt for Byte { 84 | fn node(&self) -> NodeId { 85 | self.node 86 | } 87 | 88 | fn input_desc(&self) -> EdgeDescriptor { 89 | EdgeDescriptor::zero() 90 | } 91 | 92 | fn all_input_ports(&self) -> InputPorts { 93 | TinyVec::new() 94 | } 95 | 96 | fn all_input_port_kinds(&self) -> InputPortKinds { 97 | TinyVec::new() 98 | } 99 | 100 | fn update_input(&mut self, _from: InputPort, _to: InputPort) { 101 | // TODO: Should this be a panic or warning? 102 | } 103 | 104 | fn output_desc(&self) -> EdgeDescriptor { 105 | EdgeDescriptor::from_values(1) 106 | } 107 | 108 | fn all_output_ports(&self) -> OutputPorts { 109 | tiny_vec![self.value] 110 | } 111 | 112 | fn all_output_port_kinds(&self) -> OutputPortKinds { 113 | tiny_vec! { 114 | [_; 4] => (self.value, EdgeKind::Value), 115 | } 116 | } 117 | 118 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 119 | if self.value == from { 120 | self.value = to; 121 | } 122 | } 123 | } 124 | 125 | #[derive(Debug, Clone, Copy, PartialEq)] 126 | pub struct Bool { 127 | node: NodeId, 128 | value: OutputPort, 129 | } 130 | 131 | impl Bool { 132 | pub(in crate::graph) const fn new(node: NodeId, value: OutputPort) -> Self { 133 | Self { node, value } 134 | } 135 | 136 | pub const fn value(&self) -> OutputPort { 137 | self.value 138 | } 139 | } 140 | 141 | impl NodeExt for Bool { 142 | fn node(&self) -> NodeId { 143 | self.node 144 | } 145 | 146 | fn input_desc(&self) -> EdgeDescriptor { 147 | EdgeDescriptor::zero() 148 | } 149 | 150 | fn all_input_ports(&self) -> InputPorts { 151 | TinyVec::new() 152 | } 153 | 154 | fn all_input_port_kinds(&self) -> InputPortKinds { 155 | TinyVec::new() 156 | } 157 | 158 | fn update_input(&mut self, _from: InputPort, _to: InputPort) { 159 | // TODO: Should this be a panic or warning? 160 | } 161 | 162 | fn output_desc(&self) -> EdgeDescriptor { 163 | EdgeDescriptor::from_values(1) 164 | } 165 | 166 | fn all_output_ports(&self) -> OutputPorts { 167 | tiny_vec![self.value] 168 | } 169 | 170 | fn all_output_port_kinds(&self) -> OutputPortKinds { 171 | tiny_vec! { 172 | [_; 4] => (self.value, EdgeKind::Value), 173 | } 174 | } 175 | 176 | fn update_output(&mut self, from: OutputPort, to: OutputPort) { 177 | if self.value == from { 178 | self.value = to; 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/graph/ports.rs: -------------------------------------------------------------------------------- 1 | use crate::graph::{EdgeKind, NodeId}; 2 | use std::{ 3 | cmp::Eq, 4 | fmt::{self, Debug, Display, Write}, 5 | hash::Hash, 6 | }; 7 | 8 | pub trait Port: Debug + Clone + Copy + PartialEq + Eq + Hash { 9 | fn port(&self) -> PortId; 10 | 11 | fn raw(&self) -> u32 { 12 | self.port().0 13 | } 14 | } 15 | 16 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 17 | #[repr(transparent)] 18 | pub struct PortId(u32); 19 | 20 | impl PortId { 21 | pub const fn new(id: u32) -> Self { 22 | Self(id) 23 | } 24 | 25 | const fn inner(&self) -> u32 { 26 | self.0 27 | } 28 | } 29 | 30 | impl Port for PortId { 31 | fn port(&self) -> PortId { 32 | *self 33 | } 34 | } 35 | 36 | impl Debug for PortId { 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | f.write_str("PortId(")?; 39 | Debug::fmt(&self.0, f)?; 40 | f.write_char(')') 41 | } 42 | } 43 | 44 | impl Display for PortId { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | write!(f, "p{}", self.0) 47 | } 48 | } 49 | 50 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 51 | #[repr(transparent)] 52 | pub struct InputPort(PortId); 53 | 54 | impl InputPort { 55 | pub(super) const fn new(id: PortId) -> Self { 56 | Self(id) 57 | } 58 | } 59 | 60 | impl Port for InputPort { 61 | fn port(&self) -> PortId { 62 | self.0 63 | } 64 | } 65 | 66 | impl Debug for InputPort { 67 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 68 | f.write_str("InputPort(")?; 69 | Debug::fmt(&self.0.inner(), f)?; 70 | f.write_char(')') 71 | } 72 | } 73 | 74 | impl Display for InputPort { 75 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 76 | write!(f, "i{}", self.0.inner()) 77 | } 78 | } 79 | 80 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] 81 | #[repr(transparent)] 82 | pub struct OutputPort(PortId); 83 | 84 | impl OutputPort { 85 | pub const fn new(id: PortId) -> Self { 86 | Self(id) 87 | } 88 | } 89 | 90 | impl Port for OutputPort { 91 | fn port(&self) -> PortId { 92 | self.0 93 | } 94 | } 95 | 96 | impl Debug for OutputPort { 97 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 98 | f.write_str("OutputPort(")?; 99 | Debug::fmt(&self.0.inner(), f)?; 100 | f.write_char(')') 101 | } 102 | } 103 | 104 | impl Display for OutputPort { 105 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 106 | write!(f, "o{}", self.0.inner()) 107 | } 108 | } 109 | 110 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 111 | pub struct PortData { 112 | pub kind: PortKind, 113 | pub edge: EdgeKind, 114 | pub parent: NodeId, 115 | } 116 | 117 | impl PortData { 118 | pub const fn new(kind: PortKind, edge: EdgeKind, node: NodeId) -> Self { 119 | Self { 120 | kind, 121 | edge, 122 | parent: node, 123 | } 124 | } 125 | 126 | pub const fn input(node: NodeId, edge: EdgeKind) -> Self { 127 | Self::new(PortKind::Input, edge, node) 128 | } 129 | 130 | pub const fn output(node: NodeId, edge: EdgeKind) -> Self { 131 | Self::new(PortKind::Output, edge, node) 132 | } 133 | } 134 | 135 | impl Display for PortData { 136 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 137 | write!(f, "{} port for {}", self.kind, self.parent) 138 | } 139 | } 140 | 141 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 142 | pub enum PortKind { 143 | Input, 144 | Output, 145 | } 146 | 147 | impl PortKind { 148 | /// Returns `true` if the port kind is [`Input`]. 149 | /// 150 | /// [`Input`]: PortKind::Input 151 | pub const fn is_input(&self) -> bool { 152 | matches!(self, Self::Input) 153 | } 154 | 155 | /// Returns `true` if the port kind is [`Output`]. 156 | /// 157 | /// [`Output`]: PortKind::Output 158 | pub const fn is_output(&self) -> bool { 159 | matches!(self, Self::Output) 160 | } 161 | } 162 | 163 | impl Display for PortKind { 164 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 165 | match self { 166 | Self::Input => f.write_str("input"), 167 | Self::Output => f.write_str("output"), 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/graph/stats.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{Node, Rvsdg}, 3 | utils::PerfEvent, 4 | }; 5 | use std::ops::Neg; 6 | 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 8 | pub struct Stats { 9 | pub branches: usize, 10 | pub loops: usize, 11 | pub loads: usize, 12 | pub stores: usize, 13 | pub constants: usize, 14 | pub instructions: usize, 15 | pub io_ops: usize, 16 | pub scans: usize, 17 | } 18 | 19 | impl Stats { 20 | pub fn new() -> Self { 21 | Self { 22 | branches: 0, 23 | loops: 0, 24 | loads: 0, 25 | stores: 0, 26 | constants: 0, 27 | instructions: 0, 28 | io_ops: 0, 29 | scans: 0, 30 | } 31 | } 32 | 33 | pub fn difference(&self, new: Self) -> StatsChange { 34 | let diff = |old, new| { 35 | let diff = (((old as f64 - new as f64) / old as f64) * 100.0).neg(); 36 | 37 | if diff.is_nan() || diff == -0.0 { 38 | 0.0 39 | } else if diff.is_infinite() { 40 | if diff.is_sign_positive() { 41 | 100.0 42 | } else { 43 | -100.0 44 | } 45 | } else { 46 | diff 47 | } 48 | }; 49 | 50 | StatsChange { 51 | branches: diff(self.branches, new.branches), 52 | loops: diff(self.loops, new.loops), 53 | loads: diff(self.loads, new.loads), 54 | stores: diff(self.stores, new.stores), 55 | constants: diff(self.constants, new.constants), 56 | instructions: diff(self.instructions, new.instructions), 57 | io_ops: diff(self.io_ops, new.io_ops), 58 | scans: diff(self.scans, new.scans), 59 | } 60 | } 61 | } 62 | 63 | impl Default for Stats { 64 | fn default() -> Self { 65 | Self::new() 66 | } 67 | } 68 | 69 | #[derive(Debug, Clone, Copy, PartialEq)] 70 | pub struct StatsChange { 71 | pub branches: f64, 72 | pub loops: f64, 73 | pub loads: f64, 74 | pub stores: f64, 75 | pub constants: f64, 76 | pub instructions: f64, 77 | pub io_ops: f64, 78 | pub scans: f64, 79 | } 80 | 81 | impl Rvsdg { 82 | pub fn stats(&self) -> Stats { 83 | let _event = PerfEvent::new("collect-graph-stats"); 84 | 85 | let mut stats = Stats::new(); 86 | self.for_each_transitive_node(|_node_id, node| match node { 87 | Node::Int(_, _) | Node::Byte(_, _) | Node::Bool(_, _) => stats.constants += 1, 88 | Node::Add(_) 89 | | Node::Sub(_) 90 | | Node::Mul(_) 91 | | Node::Eq(_) 92 | | Node::Neq(_) 93 | | Node::Not(_) 94 | | Node::Neg(_) => { 95 | stats.instructions += 1; 96 | } 97 | Node::Load(_) => { 98 | stats.instructions += 1; 99 | stats.loads += 1; 100 | } 101 | Node::Store(_) => { 102 | stats.instructions += 1; 103 | stats.stores += 1; 104 | } 105 | Node::Scan(_) => { 106 | stats.instructions += 1; 107 | stats.scans += 1; 108 | } 109 | Node::Input(_) | Node::Output(_) => { 110 | stats.instructions += 1; 111 | stats.io_ops += 1; 112 | } 113 | Node::Theta(_) => { 114 | stats.instructions += 1; 115 | stats.loops += 1; 116 | } 117 | Node::Gamma(_) => { 118 | stats.instructions += 1; 119 | stats.branches += 1; 120 | } 121 | 122 | Node::Start(_) | Node::End(_) | Node::InputParam(_) | Node::OutputParam(_) => {} 123 | }); 124 | 125 | stats 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/graph/structural_eq.rs: -------------------------------------------------------------------------------- 1 | use crate::graph::{NodeExt, Rvsdg}; 2 | use std::{ 3 | cmp::min, 4 | collections::{BTreeSet, VecDeque}, 5 | }; 6 | 7 | impl Rvsdg { 8 | pub fn structural_eq(&self, other: &Self) -> bool { 9 | if self.start_nodes.len() != other.start_nodes.len() 10 | || self.end_nodes.len() != other.end_nodes.len() 11 | { 12 | return false; 13 | } else if self.start_nodes.len() != 1 || self.end_nodes.len() != 1 { 14 | panic!( 15 | "encountered graphs with {} start and {} end nodes", 16 | self.start_nodes.len(), 17 | self.end_nodes.len(), 18 | ) 19 | } 20 | 21 | let (mut queue, mut equal) = ( 22 | VecDeque::with_capacity(min(self.nodes.len(), other.nodes.len()) / 2), 23 | BTreeSet::new(), 24 | ); 25 | queue.push_back((self.end_nodes[0], other.end_nodes[0])); 26 | 27 | while let Some((lhs, rhs)) = queue.pop_back() { 28 | equal.insert((lhs, rhs)); 29 | let (lhs, rhs) = (&self.nodes[&lhs], &other.nodes[&rhs]); 30 | 31 | if lhs.kind() != rhs.kind() 32 | || matches!(lhs.as_int_value().zip(rhs.as_int_value()), Some((lhs, rhs)) if lhs != rhs) 33 | || matches!(lhs.as_byte_value().zip(rhs.as_byte_value()), Some((lhs, rhs)) if lhs != rhs) 34 | || matches!(lhs.as_bool_value().zip(rhs.as_bool_value()), Some((lhs, rhs)) if lhs != rhs) 35 | { 36 | return false; 37 | } else if let Some((lhs, rhs)) = lhs.as_theta().zip(rhs.as_theta()) { 38 | // TODO: Theta subgraphs 39 | } else if let Some((lhs, rhs)) = lhs.as_gamma().zip(rhs.as_gamma()) { 40 | // TODO: Gamma subgraphs 41 | } 42 | 43 | // Input edges 44 | for ((lhs, lhs_kind), (rhs, rhs_kind)) in lhs 45 | .all_input_port_kinds() 46 | .into_iter() 47 | .zip(rhs.all_input_port_kinds()) 48 | { 49 | if lhs_kind != rhs_kind { 50 | return false; 51 | } 52 | 53 | let pair = (self.input_source_id(lhs), other.input_source_id(rhs)); 54 | if !equal.contains(&pair) { 55 | queue.push_back(pair); 56 | } 57 | } 58 | 59 | // Output edges 60 | for ((lhs, lhs_kind), (rhs, rhs_kind)) in lhs 61 | .all_output_port_kinds() 62 | .into_iter() 63 | .zip(rhs.all_output_port_kinds()) 64 | { 65 | if lhs_kind != rhs_kind { 66 | return false; 67 | } 68 | 69 | let pair = ( 70 | self.output_dest_id(lhs).unwrap(), 71 | other.output_dest_id(rhs).unwrap(), 72 | ); 73 | if !equal.contains(&pair) { 74 | queue.push_back(pair); 75 | } 76 | } 77 | } 78 | 79 | true 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/graph/subgraph.rs: -------------------------------------------------------------------------------- 1 | use crate::graph::{End, NodeId, Rvsdg, Start}; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | #[derive(Debug, Clone, PartialEq)] 5 | pub struct Subgraph { 6 | pub(super) graph: Rvsdg, 7 | pub(super) start: NodeId, 8 | pub(super) end: NodeId, 9 | } 10 | 11 | impl Subgraph { 12 | pub(super) fn new(graph: Rvsdg, start: NodeId, end: NodeId) -> Self { 13 | Self { graph, start, end } 14 | } 15 | 16 | /// Get the [`Start`] of the subgraph 17 | pub fn start_node(&self) -> Start { 18 | *self.graph.to_node(self.start) 19 | } 20 | 21 | /// Get the [`End`] of the subgraph 22 | pub fn end_node(&self) -> End { 23 | *self.graph.to_node(self.end) 24 | } 25 | } 26 | 27 | impl Deref for Subgraph { 28 | type Target = Rvsdg; 29 | 30 | fn deref(&self) -> &Self::Target { 31 | &self.graph 32 | } 33 | } 34 | 35 | impl DerefMut for Subgraph { 36 | fn deref_mut(&mut self) -> &mut Self::Target { 37 | &mut self.graph 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ir/lifetime.rs: -------------------------------------------------------------------------------- 1 | use crate::ir::{ 2 | Add, Assign, AssignTag, Call, Cmp, Expr, Gamma, Instruction, LifetimeEnd, Load, Mul, Neg, Not, 3 | Store, Sub, Theta, Value, VarId, 4 | }; 5 | use std::collections::{BTreeMap, BTreeSet}; 6 | 7 | pub(super) fn analyze_block(instructions: &mut Vec, mut skip_lifetime: F) 8 | where 9 | F: FnMut(VarId) -> bool, 10 | { 11 | let mut last_usage = BTreeMap::new(); 12 | for (idx, inst) in instructions.iter_mut().enumerate() { 13 | analyze_instruction(&mut last_usage, idx, inst); 14 | } 15 | 16 | let mut final_uses = BTreeMap::new(); 17 | for (var, index) in last_usage { 18 | final_uses 19 | .entry(index) 20 | .or_insert_with(|| Vec::with_capacity(1)) 21 | .push(var); 22 | } 23 | 24 | let (mut idx, mut offset) = (0, 0); 25 | while idx < instructions.len() { 26 | if let Some(vars) = final_uses.get(&(idx - offset)) { 27 | for &var in vars { 28 | if !skip_lifetime(var) { 29 | instructions.insert(idx + 1, Instruction::LifetimeEnd(LifetimeEnd::new(var))); 30 | 31 | offset += 1; 32 | idx += 1; 33 | } 34 | } 35 | } 36 | 37 | idx += 1; 38 | } 39 | } 40 | 41 | fn analyze_instruction( 42 | last_usage: &mut BTreeMap, 43 | idx: usize, 44 | inst: &mut Instruction, 45 | ) { 46 | match inst { 47 | Instruction::Call(Call { args, .. }) => { 48 | for arg in args { 49 | if let Value::Var(var) = *arg { 50 | last_usage.insert(var, idx); 51 | } 52 | } 53 | } 54 | 55 | &mut Instruction::Assign(Assign { 56 | var, 57 | ref value, 58 | tag, 59 | .. 60 | }) => { 61 | last_usage.insert(var, idx); 62 | 63 | if !tag.is_input_param() { 64 | match *value { 65 | Expr::Cmp(Cmp { lhs, rhs, .. }) => { 66 | if let Value::Var(var) = lhs { 67 | last_usage.insert(var, idx); 68 | } 69 | 70 | if let Value::Var(var) = rhs { 71 | last_usage.insert(var, idx); 72 | } 73 | } 74 | 75 | Expr::Add(Add { lhs, rhs }) => { 76 | if let Value::Var(var) = lhs { 77 | last_usage.insert(var, idx); 78 | } 79 | 80 | if let Value::Var(var) = rhs { 81 | last_usage.insert(var, idx); 82 | } 83 | } 84 | 85 | Expr::Sub(Sub { lhs, rhs }) => { 86 | if let Value::Var(var) = lhs { 87 | last_usage.insert(var, idx); 88 | } 89 | 90 | if let Value::Var(var) = rhs { 91 | last_usage.insert(var, idx); 92 | } 93 | } 94 | 95 | Expr::Mul(Mul { lhs, rhs }) => { 96 | if let Value::Var(var) = lhs { 97 | last_usage.insert(var, idx); 98 | } 99 | 100 | if let Value::Var(var) = rhs { 101 | last_usage.insert(var, idx); 102 | } 103 | } 104 | 105 | Expr::Not(Not { value }) => { 106 | if let Value::Var(var) = value { 107 | last_usage.insert(var, idx); 108 | } 109 | } 110 | 111 | Expr::Neg(Neg { value }) => { 112 | if let Value::Var(var) = value { 113 | last_usage.insert(var, idx); 114 | } 115 | } 116 | 117 | Expr::Load(Load { ptr, .. }) => { 118 | if let Value::Var(var) = ptr { 119 | last_usage.insert(var, idx); 120 | } 121 | } 122 | 123 | Expr::Call(Call { ref args, .. }) => { 124 | for &arg in args { 125 | if let Value::Var(var) = arg { 126 | last_usage.insert(var, idx); 127 | } 128 | } 129 | } 130 | 131 | Expr::Value(value) => { 132 | if let Value::Var(var) = value { 133 | last_usage.insert(var, idx); 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | Instruction::Theta(Theta { 141 | body, 142 | inputs, 143 | outputs, 144 | cond, 145 | .. 146 | }) => { 147 | // Collect all the keys we need to preserve 148 | let mut saved_keys = BTreeSet::new(); 149 | if let Some(Value::Var(cond)) = *cond { 150 | saved_keys.insert(cond); 151 | } 152 | saved_keys.extend(outputs.values().filter_map(Value::as_var)); 153 | 154 | for var in inputs 155 | .values() 156 | .filter_map(Value::as_var) 157 | .chain(outputs.keys().copied()) 158 | { 159 | last_usage.insert(var, idx); 160 | saved_keys.insert(var); 161 | } 162 | 163 | analyze_block(body, |var| saved_keys.contains(&var)); 164 | 165 | last_usage.extend(body.iter().filter_map(|inst| { 166 | if let &Instruction::Assign(Assign { 167 | tag: AssignTag::InputParam(_), 168 | // TODO: Do we need to analyze sub-expressions? 169 | value: Expr::Value(Value::Var(var)), 170 | .. 171 | }) = inst 172 | { 173 | Some((var, idx)) 174 | } else { 175 | None 176 | } 177 | })); 178 | } 179 | 180 | Instruction::Gamma(Gamma { 181 | cond, 182 | true_branch, 183 | true_outputs, 184 | false_branch, 185 | false_outputs, 186 | .. 187 | }) => { 188 | if let Value::Var(var) = *cond { 189 | last_usage.insert(var, idx); 190 | } 191 | 192 | last_usage.extend( 193 | true_outputs 194 | .keys() 195 | .chain(false_outputs.keys()) 196 | .copied() 197 | .map(|var| (var, idx)), 198 | ); 199 | 200 | let input_vars = true_branch 201 | .iter() 202 | .chain(false_branch.iter()) 203 | .filter_map(|inst| { 204 | if let Instruction::Assign(Assign { 205 | tag: AssignTag::InputParam(_), 206 | // TODO: Do we need to analyze sub-expressions? 207 | value: Expr::Value(Value::Var(var)), 208 | .. 209 | }) = inst 210 | { 211 | Some(*var) 212 | } else { 213 | None 214 | } 215 | }); 216 | last_usage.extend(input_vars.clone().map(|var| (var, idx))); 217 | 218 | let mut saved_keys = BTreeSet::new(); 219 | saved_keys.extend(true_outputs.keys().chain(false_outputs.keys()).copied()); 220 | saved_keys.extend(input_vars); 221 | 222 | analyze_block(true_branch, |var| saved_keys.contains(&var)); 223 | analyze_block(false_branch, |var| saved_keys.contains(&var)); 224 | } 225 | 226 | &mut Instruction::Store(Store { ptr, value, .. }) => { 227 | if let Value::Var(var) = ptr { 228 | last_usage.insert(var, idx); 229 | } 230 | 231 | if let Value::Var(var) = value { 232 | last_usage.insert(var, idx); 233 | } 234 | } 235 | 236 | Instruction::LifetimeEnd(_) => unreachable!(), 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/ir/parse.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/ir/pretty_print.rs: -------------------------------------------------------------------------------- 1 | use pretty::{Arena, DocAllocator, DocBuilder}; 2 | use std::{fmt::Write, time::Instant}; 3 | use tracing::{level_filters::STATIC_MAX_LEVEL, metadata::LevelFilter}; 4 | 5 | const RENDER_WIDTH: usize = 80; 6 | pub(super) const INDENT_WIDTH: usize = 4; 7 | pub(super) const COMMENT_ALIGNMENT_OFFSET: usize = 25; 8 | 9 | // TODO: Colored output for the terminal 10 | // https://docs.rs/pretty/latest/pretty/trait.RenderAnnotated.html#impl-RenderAnnotated%3C%27_%2C%20ColorSpec%3E 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | pub struct PrettyConfig { 14 | pub display_effects: bool, 15 | pub display_invocations: bool, 16 | pub total_instructions: Option, 17 | /// Log the time spent on pretty printing 18 | pub duration_logging: bool, 19 | /// If printed to an ansi-capable source, this will allow coloration 20 | pub colored: bool, 21 | pub show_lifetimes: bool, 22 | pub hide_const_assignments: bool, 23 | } 24 | 25 | impl PrettyConfig { 26 | pub fn minimal() -> Self { 27 | Self { 28 | display_effects: false, 29 | display_invocations: true, 30 | total_instructions: None, 31 | duration_logging: false, 32 | colored: true, 33 | show_lifetimes: false, 34 | hide_const_assignments: true, 35 | } 36 | } 37 | 38 | pub fn with_hide_const_assignments(mut self, hide_const_assignments: bool) -> Self { 39 | self.hide_const_assignments = hide_const_assignments; 40 | self 41 | } 42 | 43 | pub fn with_instrumented(mut self, total_instructions: usize) -> Self { 44 | self.total_instructions = Some(total_instructions); 45 | self 46 | } 47 | 48 | // pub const fn with_duration_logging(self, duration_logging: bool) -> Self { 49 | // Self { 50 | // duration_logging, 51 | // ..self 52 | // } 53 | // } 54 | 55 | // pub const fn with_color(self, colored: bool) -> Self { 56 | // Self { colored, ..self } 57 | // } 58 | } 59 | 60 | pub trait Pretty { 61 | fn pretty_print(&self, config: PrettyConfig) -> String { 62 | let construction_start = Instant::now(); 63 | 64 | let arena = Arena::<()>::new(); 65 | let pretty = self.pretty(&arena, config); 66 | 67 | if STATIC_MAX_LEVEL >= LevelFilter::DEBUG && config.duration_logging { 68 | let elapsed = construction_start.elapsed(); 69 | tracing::debug!( 70 | target: "timings", 71 | "took {:#?} to construct pretty printed ir", 72 | elapsed, 73 | ); 74 | } 75 | 76 | let format_start = Instant::now(); 77 | 78 | let mut output = String::with_capacity(4096); 79 | write!(output, "{}", pretty.1.pretty(RENDER_WIDTH)) 80 | .expect("writing to a string should never fail"); 81 | 82 | if STATIC_MAX_LEVEL >= LevelFilter::DEBUG && config.duration_logging { 83 | let elapsed = format_start.elapsed(); 84 | tracing::debug!( 85 | target: "timings", 86 | "took {:#?} to construct format pretty printed ir", 87 | elapsed, 88 | ); 89 | } 90 | 91 | output 92 | } 93 | 94 | fn pretty<'a, D, A>(&'a self, allocator: &'a D, config: PrettyConfig) -> DocBuilder<'a, D, A> 95 | where 96 | D: DocAllocator<'a, A>, 97 | D::Doc: Clone, 98 | A: Clone; 99 | } 100 | 101 | pub mod pretty_utils { 102 | use crate::ir::{pretty_print::INDENT_WIDTH, Instruction, Pretty, PrettyConfig}; 103 | use pretty::{DocAllocator, DocBuilder}; 104 | 105 | pub fn binary<'a, D, A, L, R>( 106 | op: &'static str, 107 | lhs: &'a L, 108 | rhs: &'a R, 109 | allocator: &'a D, 110 | config: PrettyConfig, 111 | ) -> DocBuilder<'a, D, A> 112 | where 113 | D: DocAllocator<'a, A>, 114 | D::Doc: Clone, 115 | A: Clone, 116 | L: Pretty, 117 | R: Pretty, 118 | { 119 | allocator 120 | .text(op) 121 | .append(allocator.space()) 122 | .append(lhs.pretty(allocator, config)) 123 | .append(allocator.text(",")) 124 | .append(allocator.space()) 125 | .append(rhs.pretty(allocator, config)) 126 | } 127 | 128 | pub fn unary<'a, D, A, U>( 129 | op: &'static str, 130 | arg: &'a U, 131 | allocator: &'a D, 132 | config: PrettyConfig, 133 | ) -> DocBuilder<'a, D, A> 134 | where 135 | D: DocAllocator<'a, A>, 136 | D::Doc: Clone, 137 | A: Clone, 138 | U: Pretty + 'static, 139 | { 140 | allocator 141 | .text(op) 142 | .append(allocator.space()) 143 | .append(arg.pretty(allocator, config)) 144 | } 145 | 146 | pub fn body_block<'a, D, A>( 147 | allocator: &'a D, 148 | config: PrettyConfig, 149 | indent: bool, 150 | block: &'a [Instruction], 151 | ) -> DocBuilder<'a, D, A> 152 | where 153 | D: DocAllocator<'a, A>, 154 | D::Doc: Clone, 155 | A: Clone, 156 | { 157 | let indent_width = if indent { INDENT_WIDTH } else { 0 }; 158 | 159 | let instructions = block.iter().filter(|inst| { 160 | (config.show_lifetimes || !inst.is_lifetime_end()) 161 | && !(config.hide_const_assignments 162 | && inst 163 | .as_assign() 164 | .map(|assign| assign.value.is_const()) 165 | .unwrap_or(false)) 166 | }); 167 | let total_instructions = instructions.clone().count(); 168 | 169 | if total_instructions == 0 { 170 | allocator.nil() 171 | } else { 172 | if indent { 173 | allocator.hardline() 174 | } else { 175 | allocator.nil() 176 | } 177 | .append( 178 | allocator 179 | .intersperse( 180 | instructions.map(|inst| inst.pretty(allocator, config)), 181 | allocator.hardline(), 182 | ) 183 | .indent(indent_width), 184 | ) 185 | .append(allocator.hardline()) 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/jit/block_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ir::{Const, Value as CirValue, VarId}, 3 | jit::basic_block::{BasicBlock, BlockId, Instruction, Terminator, ValId, Value}, 4 | utils::AssertNone, 5 | }; 6 | use std::{ 7 | collections::{BTreeMap, BTreeSet}, 8 | mem, 9 | }; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct BlockBuilder { 13 | entry: BlockId, 14 | current: Option, 15 | pub(super) blocks: BTreeMap, 16 | allocated_blocks: BTreeSet, 17 | values: BTreeMap, 18 | block_counter: u32, 19 | val_counter: u32, 20 | } 21 | 22 | /// Public API 23 | impl BlockBuilder { 24 | /// Creates a new basic block builder 25 | pub fn new() -> Self { 26 | let entry = BlockId::new(0); 27 | 28 | Self { 29 | entry, 30 | current: Some(BasicBlock::new(entry, Vec::new(), Terminator::Error)), 31 | blocks: BTreeMap::new(), 32 | allocated_blocks: BTreeSet::new(), 33 | values: BTreeMap::new(), 34 | block_counter: 1, 35 | val_counter: 0, 36 | } 37 | } 38 | 39 | pub fn finalize(&mut self) { 40 | if let Some(current) = self.current.take() { 41 | self.blocks 42 | .insert(current.id(), current) 43 | .debug_unwrap_none(); 44 | } 45 | } 46 | 47 | pub fn into_blocks(mut self) -> Vec { 48 | self.finalize(); 49 | 50 | debug_assert!(self.allocated_blocks.is_empty()); 51 | debug_assert!(self.blocks.contains_key(&self.entry)); 52 | 53 | self.blocks.into_values().collect() 54 | } 55 | 56 | /// Allocates a basic block to be created in in the future 57 | pub fn allocate(&mut self) -> AllocatedBlock { 58 | let block = self.next_block(); 59 | self.allocated_blocks.insert(block); 60 | 61 | AllocatedBlock::new(block) 62 | } 63 | 64 | /// Allocates multiple blocks to be created in the future 65 | pub fn allocate_blocks(&mut self) -> [AllocatedBlock; N] { 66 | [(); N].map(|()| self.allocate()) 67 | } 68 | 69 | /// Creates a block for the allocated block and sets the current block to it 70 | pub fn create_block(&mut self, block: AllocatedBlock) -> &mut BasicBlock { 71 | let was_allocated = self.allocated_blocks.remove(&block.block); 72 | debug_assert!(was_allocated); 73 | 74 | let block = BasicBlock::new(block.block, Vec::new(), Terminator::Error); 75 | self.replace_current(block); 76 | 77 | self.current.as_mut().unwrap() 78 | } 79 | 80 | /// Move to the given block 81 | #[allow(dead_code)] 82 | pub fn move_to(&mut self, block: BlockId) -> &mut BasicBlock { 83 | if self.current.as_ref().unwrap().id() != block { 84 | if let Some(block) = self.blocks.remove(&block) { 85 | self.replace_current(block); 86 | } else { 87 | panic!("tried to move to {} but it doesn't exist", block); 88 | } 89 | } 90 | 91 | self.current.as_mut().unwrap() 92 | } 93 | 94 | /// Pushes an instruction to the current block 95 | pub fn push(&mut self, inst: I) 96 | where 97 | I: Into, 98 | { 99 | self.current().push(inst.into()); 100 | } 101 | 102 | /// Assigns a SSA value to a variable id 103 | pub fn assign(&mut self, var: VarId, value: V) 104 | where 105 | V: Into, 106 | { 107 | let value = value.into(); 108 | self.values.insert(var, value).debug_unwrap_none(); 109 | } 110 | 111 | /// Gets the current block 112 | #[track_caller] 113 | pub fn current(&mut self) -> &mut BasicBlock { 114 | self.current.as_mut().unwrap() 115 | } 116 | 117 | /// Gets the id of the current block 118 | #[track_caller] 119 | pub fn current_block_id(&mut self) -> BlockId { 120 | self.current().id() 121 | } 122 | 123 | /// Gets the SSA value associated with an input value 124 | #[track_caller] 125 | pub fn get(&self, value: CirValue) -> Value { 126 | match value { 127 | CirValue::Var(var) => *self.values.get(&var).unwrap(), 128 | CirValue::Const(constant) => match constant { 129 | Const::Ptr(uint) => Value::TapePtr(uint), 130 | Const::Cell(byte) => Value::U8(byte), 131 | Const::Bool(bool) => Value::Bool(bool), 132 | }, 133 | CirValue::Missing => unreachable!(), 134 | } 135 | } 136 | 137 | /// Generates a unique value id 138 | pub fn create_val(&mut self) -> ValId { 139 | let val = self.val_counter; 140 | self.val_counter += 1; 141 | 142 | ValId::new(val) 143 | } 144 | 145 | pub const fn entry(&self) -> BlockId { 146 | self.entry 147 | } 148 | 149 | pub fn set_entry(&mut self, entry: BlockId) { 150 | self.entry = entry; 151 | } 152 | } 153 | 154 | /// Internal utility functions 155 | impl BlockBuilder { 156 | /// Creates a new block with a unique id 157 | #[allow(dead_code)] 158 | fn new_block(&mut self) -> BasicBlock { 159 | BasicBlock::new(self.next_block(), Vec::new(), Terminator::Error) 160 | } 161 | 162 | /// Replaces the current block with the given one, putting the previous 163 | /// block into `self.blocks` 164 | fn replace_current(&mut self, block: BasicBlock) { 165 | let previous = mem::replace(self.current.as_mut().unwrap(), block); 166 | self.blocks 167 | .insert(previous.id(), previous) 168 | .debug_unwrap_none(); 169 | } 170 | 171 | /// Generates a unique block id 172 | fn next_block(&mut self) -> BlockId { 173 | let block = self.block_counter; 174 | self.block_counter += 1; 175 | 176 | BlockId::new(block) 177 | } 178 | } 179 | 180 | #[derive(Debug)] 181 | pub struct AllocatedBlock { 182 | block: BlockId, 183 | } 184 | 185 | impl AllocatedBlock { 186 | const fn new(block: BlockId) -> Self { 187 | Self { block } 188 | } 189 | 190 | pub const fn id(&self) -> BlockId { 191 | self.block 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/jit/block_visitor.rs: -------------------------------------------------------------------------------- 1 | use crate::jit::basic_block::{ 2 | Add, Assign, BasicBlock, BitNot, BlockId, Blocks, Branch, Cmp, Input, Instruction, Load, Mul, 3 | Neg, Not, Output, Phi, RValue, Scanl, Scanr, Store, Sub, Terminator, ValId, Value, 4 | }; 5 | 6 | pub trait BasicBlockVisitor { 7 | fn visit_blocks(&mut self, blocks: &Blocks) { 8 | for block in blocks { 9 | self.before_visit_block(block); 10 | self.visit_block(block); 11 | } 12 | } 13 | 14 | /// Called before a block is visited 15 | fn before_visit_block(&mut self, _block: &BasicBlock) {} 16 | 17 | fn visit_block(&mut self, block: &BasicBlock) { 18 | for inst in block { 19 | self.before_visit_inst(inst); 20 | self.visit_inst(inst); 21 | } 22 | 23 | self.before_visit_term(block.terminator()); 24 | self.visit_term(block.terminator()); 25 | } 26 | 27 | /// Called before an instruction is visited 28 | fn before_visit_inst(&mut self, _inst: &Instruction) {} 29 | 30 | fn visit_inst(&mut self, inst: &Instruction) { 31 | match inst { 32 | Instruction::Store(store) => self.visit_store(store), 33 | Instruction::Assign(assign) => self.visit_assign(assign), 34 | Instruction::Output(output) => self.visit_output(output), 35 | } 36 | } 37 | 38 | fn visit_store(&mut self, store: &Store) { 39 | self.visit_value(store.ptr()); 40 | self.visit_value(store.value()); 41 | } 42 | 43 | fn visit_assign(&mut self, assign: &Assign) { 44 | self.visit_rval(assign.rval(), assign.value()); 45 | } 46 | 47 | fn visit_rval(&mut self, rval: &RValue, assigned_to: ValId) { 48 | match rval { 49 | RValue::Cmp(eq) => self.visit_eq(eq, assigned_to), 50 | RValue::Phi(phi) => self.visit_phi(phi, assigned_to), 51 | RValue::Neg(neg) => self.visit_neg(neg, assigned_to), 52 | RValue::Not(not) => self.visit_not(not, assigned_to), 53 | RValue::Add(add) => self.visit_add(add, assigned_to), 54 | RValue::Sub(sub) => self.visit_sub(sub, assigned_to), 55 | RValue::Mul(mul) => self.visit_mul(mul, assigned_to), 56 | RValue::Load(load) => self.visit_load(load, assigned_to), 57 | RValue::Input(input) => self.visit_input(input, assigned_to), 58 | RValue::BitNot(bit_not) => self.visit_bit_not(bit_not, assigned_to), 59 | RValue::Scanr(scanr) => self.visit_scanr(scanr, assigned_to), 60 | RValue::Scanl(scanl) => self.visit_scanl(scanl, assigned_to), 61 | } 62 | } 63 | 64 | fn visit_eq(&mut self, eq: &Cmp, _assigned_to: ValId) { 65 | self.visit_value(eq.lhs()); 66 | self.visit_value(eq.rhs()); 67 | } 68 | 69 | fn visit_phi(&mut self, phi: &Phi, _assigned_to: ValId) { 70 | self.visit_value(phi.lhs()); 71 | self.visit_value(phi.rhs()); 72 | } 73 | 74 | fn visit_neg(&mut self, neg: &Neg, _assigned_to: ValId) { 75 | self.visit_value(neg.value()); 76 | } 77 | 78 | fn visit_not(&mut self, not: &Not, _assigned_to: ValId) { 79 | self.visit_value(not.value()); 80 | } 81 | 82 | fn visit_add(&mut self, add: &Add, _assigned_to: ValId) { 83 | self.visit_value(add.lhs()); 84 | self.visit_value(add.rhs()); 85 | } 86 | 87 | fn visit_sub(&mut self, sub: &Sub, _assigned_to: ValId) { 88 | self.visit_value(sub.lhs()); 89 | self.visit_value(sub.rhs()); 90 | } 91 | 92 | fn visit_mul(&mut self, mul: &Mul, _assigned_to: ValId) { 93 | self.visit_value(mul.lhs()); 94 | self.visit_value(mul.rhs()); 95 | } 96 | 97 | fn visit_load(&mut self, load: &Load, _assigned_to: ValId) { 98 | self.visit_value(load.ptr()); 99 | } 100 | 101 | fn visit_input(&mut self, _input: &Input, _assigned_to: ValId) {} 102 | 103 | fn visit_bit_not(&mut self, bit_not: &BitNot, _assigned_to: ValId) { 104 | self.visit_value(bit_not.value()); 105 | } 106 | 107 | fn visit_scanr(&mut self, scanr: &Scanr, _assigned_to: ValId) { 108 | self.visit_value(scanr.ptr()); 109 | self.visit_value(scanr.step()); 110 | self.visit_value(scanr.needle()); 111 | } 112 | 113 | fn visit_scanl(&mut self, scanl: &Scanl, _assigned_to: ValId) { 114 | self.visit_value(scanl.ptr()); 115 | self.visit_value(scanl.step()); 116 | self.visit_value(scanl.needle()); 117 | } 118 | 119 | fn visit_output(&mut self, output: &Output) { 120 | for &value in output.values() { 121 | self.visit_value(value); 122 | } 123 | } 124 | 125 | /// Called before a terminator is visited 126 | fn before_visit_term(&mut self, _term: &Terminator) {} 127 | 128 | fn visit_term(&mut self, term: &Terminator) { 129 | match term { 130 | Terminator::Unreachable => self.visit_unreachable(), 131 | Terminator::Jump(target) => self.visit_jump(target), 132 | Terminator::Return(value) => self.visit_return(value), 133 | Terminator::Branch(branch) => self.visit_branch(branch), 134 | Terminator::Error => self.visit_error(), 135 | } 136 | } 137 | 138 | fn visit_unreachable(&mut self) {} 139 | 140 | fn visit_jump(&mut self, _target: &BlockId) {} 141 | 142 | fn visit_return(&mut self, &value: &Value) { 143 | self.visit_value(value); 144 | } 145 | 146 | fn visit_branch(&mut self, branch: &Branch) { 147 | self.visit_value(branch.condition()); 148 | } 149 | 150 | fn visit_error(&mut self) { 151 | panic!("visited an error terminator"); 152 | } 153 | 154 | fn visit_value(&mut self, _value: Value) {} 155 | } 156 | -------------------------------------------------------------------------------- /src/jit/codegen/terminator.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | jit::{ 3 | basic_block::{BasicBlock, BlockId, Terminator, ValId, Value}, 4 | codegen::Codegen, 5 | }, 6 | utils::HashMap, 7 | }; 8 | use cranelift::{ 9 | frontend::FunctionBuilder, 10 | prelude::{ 11 | types::{B1, I16, I32, I8}, 12 | InstBuilder, TrapCode, Type, Value as ClifValue, 13 | }, 14 | }; 15 | 16 | impl<'a> Codegen<'a> { 17 | pub(super) fn terminator(&mut self, current_block: &BasicBlock, terminator: &Terminator) { 18 | match terminator { 19 | // FIXME: Block arguments 20 | &Terminator::Jump(target) => { 21 | let params = collect_block_params( 22 | current_block.id(), 23 | target, 24 | &mut self.builder, 25 | &self.values, 26 | self.ssa, 27 | &mut self.param_buffer, 28 | ); 29 | self.builder.ins().jump(self.blocks[&target], params); 30 | } 31 | 32 | // FIXME: Block arguments 33 | Terminator::Branch(branch) => { 34 | let cond = match branch.condition() { 35 | Value::U8(byte) => self.builder.ins().iconst(I8, byte), 36 | Value::U16(int) => self.builder.ins().iconst(I16, int.0 as i64), 37 | Value::TapePtr(uint) => self.builder.ins().iconst(I32, uint), 38 | Value::Bool(bool) => self.builder.ins().bconst(B1, bool), 39 | Value::Val(value, _) => self.values[&value].0, 40 | }; 41 | 42 | let true_params = collect_block_params( 43 | current_block.id(), 44 | branch.true_jump(), 45 | &mut self.builder, 46 | &self.values, 47 | self.ssa, 48 | &mut self.param_buffer, 49 | ); 50 | 51 | // Jump to the true branch if the condition is true (non-zero) 52 | self.builder 53 | .ins() 54 | .brnz(cond, self.blocks[&branch.true_jump()], true_params); 55 | 56 | let false_params = collect_block_params( 57 | current_block.id(), 58 | branch.false_jump(), 59 | &mut self.builder, 60 | &self.values, 61 | self.ssa, 62 | &mut self.param_buffer, 63 | ); 64 | 65 | // Otherwise jump to the false branch 66 | self.builder 67 | .ins() 68 | .jump(self.blocks[&branch.false_jump()], false_params); 69 | } 70 | 71 | &Terminator::Return(value) => { 72 | // FIXME: return should only accept u8 values 73 | let value = match value { 74 | Value::U8(byte) => self.builder.ins().iconst(I8, byte), 75 | Value::U16(int) => self.builder.ins().iconst(I8, int.0 as i64), 76 | Value::TapePtr(uint) => self.builder.ins().iconst(I8, uint), 77 | Value::Bool(bool) => self.builder.ins().iconst(I8, bool as i64), 78 | Value::Val(value, _) => { 79 | let (value, ty) = self.values[&value]; 80 | if ty != I8 { 81 | self.builder.ins().ireduce(I8, value) 82 | } else { 83 | value 84 | } 85 | } 86 | }; 87 | 88 | self.builder.ins().return_(&[value]); 89 | } 90 | 91 | // Unreachable code can, by definition, never be reached 92 | Terminator::Unreachable => { 93 | self.builder.ins().trap(TrapCode::UnreachableCodeReached); 94 | } 95 | 96 | // We shouldn't have errors by this point in compilation 97 | Terminator::Error => unreachable!(), 98 | } 99 | } 100 | } 101 | 102 | // TODO: Refactor this 103 | fn collect_block_params<'a>( 104 | current: BlockId, 105 | target: BlockId, 106 | builder: &mut FunctionBuilder, 107 | values: &HashMap, 108 | ssa: &[BasicBlock], 109 | param_buffer: &'a mut Vec, 110 | ) -> &'a [ClifValue] { 111 | param_buffer.clear(); 112 | param_buffer.extend( 113 | ssa.iter() 114 | .find(|block| block.id() == target) 115 | .unwrap() 116 | .instructions() 117 | .iter() 118 | .filter_map(|inst| { 119 | inst.as_assign() 120 | .and_then(|assign| assign.rval().as_phi()) 121 | .and_then(|phi| { 122 | if phi.lhs_src() == current { 123 | Some(phi.lhs()) 124 | } else if phi.rhs_src() == current { 125 | Some(phi.rhs()) 126 | } else { 127 | None 128 | } 129 | }) 130 | .and_then(|value| match value { 131 | Value::U8(byte) => Some(builder.ins().iconst(I8, byte)), 132 | Value::U16(int) => Some(builder.ins().iconst(I8, int.0 as i64)), 133 | Value::TapePtr(uint) => Some(builder.ins().iconst(I32, uint)), 134 | Value::Bool(bool) => Some(builder.ins().bconst(B1, bool)), 135 | // FIXME: Sometimes the value doesn't exist??? 136 | Value::Val(value, _) => values.get(&value).map(|&(value, _)| value), 137 | }) 138 | }), 139 | ); 140 | 141 | &*param_buffer 142 | } 143 | -------------------------------------------------------------------------------- /src/jit/disassemble.rs: -------------------------------------------------------------------------------- 1 | use crate::jit::ffi; 2 | use anyhow::Result; 3 | use iced_x86::{ 4 | Decoder, DecoderOptions, FlowControl, Formatter, FormatterOptions, FormatterTextKind, 5 | Instruction, MasmFormatter, SymbolResolver, SymbolResult, 6 | }; 7 | use std::collections::BTreeMap; 8 | 9 | pub fn disassemble(code: &[u8]) -> Result { 10 | let mut formatter = MasmFormatter::with_options(Some(Box::new(Resolver)), None); 11 | set_formatter_options(formatter.options_mut()); 12 | 13 | let bitness = if cfg!(target_pointer_width = "64") { 14 | 64 15 | } else if cfg!(target_pointer_width = "32") { 16 | 32 17 | } else { 18 | anyhow::bail!("unrecognized target pointer width, only 64 and 32 bit arches are accepted"); 19 | }; 20 | 21 | // Decode all instructions 22 | let instructions: Vec<_> = 23 | Decoder::with_ip(bitness, code, code.as_ptr() as u64, DecoderOptions::NONE) 24 | .into_iter() 25 | .collect(); 26 | 27 | // Collect all jump targets and label them 28 | let labels = collect_jump_labels(&instructions); 29 | 30 | // Format all instructions 31 | let mut output = String::with_capacity(1024); 32 | let (mut is_indented, mut in_jump_block) = (false, false); 33 | 34 | for (idx, inst) in instructions.iter().enumerate() { 35 | let address = inst.ip(); 36 | let is_control_flow = matches!( 37 | inst.flow_control(), 38 | FlowControl::ConditionalBranch | FlowControl::UnconditionalBranch, 39 | ); 40 | 41 | if !is_control_flow && in_jump_block { 42 | in_jump_block = false; 43 | output.push('\n'); 44 | } 45 | 46 | // If the current address is jumped to, add a label to the output text 47 | if let Some(label) = labels.get(&address) { 48 | is_indented = true; 49 | 50 | if idx != 0 { 51 | output.push('\n'); 52 | } 53 | 54 | debug_assert!(is_valid_label_name(label)); 55 | output.push_str(label); 56 | output.push_str(":\n"); 57 | } 58 | 59 | // Indent the line if needed 60 | if is_indented { 61 | output.push_str(" "); 62 | } 63 | 64 | // If this is a branch instruction we want to replace the branch address with 65 | // our human readable label 66 | if is_control_flow { 67 | in_jump_block = true; 68 | 69 | // Use the label name if we can find it 70 | if let Some(label) = labels.get(&inst.near_branch_target()) { 71 | let mnemonic = format!("{:?}", inst.mnemonic()).to_lowercase(); 72 | output.push_str(&mnemonic); 73 | output.push(' '); 74 | 75 | debug_assert!(is_valid_label_name(label)); 76 | output.push_str(label); 77 | 78 | // Otherwise fall back to normal formatting 79 | } else { 80 | tracing::warn!( 81 | "failed to get branch label for {} (address: {:#x})", 82 | inst, 83 | inst.near_branch_target(), 84 | ); 85 | 86 | formatter.format(inst, &mut output); 87 | } 88 | 89 | // Otherwise format the instruction into the output buffer 90 | } else { 91 | formatter.format(inst, &mut output); 92 | } 93 | 94 | // Add a newline between each instruction (and a trailing one) 95 | output.push('\n'); 96 | } 97 | 98 | Ok(output) 99 | } 100 | 101 | fn collect_jump_labels(instructions: &[Instruction]) -> BTreeMap { 102 | // Collect all jump targets 103 | let mut jump_targets = Vec::new(); 104 | for inst in instructions { 105 | if matches!( 106 | inst.flow_control(), 107 | FlowControl::ConditionalBranch | FlowControl::UnconditionalBranch, 108 | ) { 109 | jump_targets.push(inst.near_branch_target()); 110 | } 111 | } 112 | 113 | // Sort and deduplicate the jump targets 114 | jump_targets.sort_unstable(); 115 | jump_targets.dedup(); 116 | 117 | // Name each jump target in increasing order 118 | jump_targets 119 | .into_iter() 120 | .enumerate() 121 | .map(|(idx, address)| (address, format!(".LBL_{}", idx))) 122 | .collect() 123 | } 124 | 125 | struct Resolver; 126 | 127 | // FIXME: The code generated by cranelift doesn't use functions 128 | // that match up with our known function addresses 129 | impl SymbolResolver for Resolver { 130 | #[allow(clippy::fn_to_numeric_cast)] 131 | fn symbol( 132 | &mut self, 133 | _instruction: &Instruction, 134 | _operand: u32, 135 | _instruction_operand: Option, 136 | address: u64, 137 | _address_size: u32, 138 | ) -> Option> { 139 | let function = if address == ffi::io_error as u64 { 140 | "io_error" 141 | } else if address == ffi::input as u64 { 142 | "input" 143 | } else if address == ffi::output as u64 { 144 | "output" 145 | } else if address == ffi::scanr_wrapping as u64 { 146 | "scanr_wrapping" 147 | } else if address == ffi::scanl_wrapping as u64 { 148 | "scanl_wrapping" 149 | } else if address == ffi::scanr_non_wrapping as u64 { 150 | "scanr_non_wrapping" 151 | } else if address == ffi::scanl_non_wrapping as u64 { 152 | "scanl_non_wrapping" 153 | } else { 154 | return None; 155 | }; 156 | 157 | Some(SymbolResult::with_str_kind( 158 | address, 159 | function, 160 | FormatterTextKind::FunctionAddress, 161 | )) 162 | } 163 | } 164 | 165 | /// Set up the formatter's options 166 | fn set_formatter_options(options: &mut FormatterOptions) { 167 | // Set up hex formatting 168 | options.set_uppercase_hex(true); 169 | options.set_hex_prefix("0x"); 170 | options.set_hex_suffix(""); 171 | 172 | // Make operand formatting pretty 173 | options.set_space_after_operand_separator(true); 174 | options.set_space_between_memory_add_operators(true); 175 | options.set_space_between_memory_mul_operators(true); 176 | options.set_scale_before_index(false); 177 | } 178 | 179 | /// Returns `true` if the label matches `\.[a-zA-Z0-9_]+ 180 | fn is_valid_label_name(label: &str) -> bool { 181 | label.starts_with('.') 182 | && label.len() >= 2 183 | && label 184 | .chars() 185 | .skip(1) 186 | .all(|char| char.is_alphanumeric() || char == '_') 187 | } 188 | -------------------------------------------------------------------------------- /src/jit/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::jit::{ffi::State, JitFunction}; 2 | use anyhow::{Context, Result}; 3 | use cranelift_jit::JITModule; 4 | use std::{ 5 | io::{self, Read, Write}, 6 | panic::{self, AssertUnwindSafe}, 7 | time::Instant, 8 | }; 9 | 10 | pub struct Executable { 11 | func: JitFunction, 12 | module: Option, 13 | } 14 | 15 | impl Executable { 16 | pub(super) fn new(func: JitFunction, module: JITModule) -> Self { 17 | Self { 18 | func, 19 | module: Some(module), 20 | } 21 | } 22 | 23 | pub unsafe fn call(&self, state: *mut State, start: *mut u8, end: *mut u8) -> u8 { 24 | unsafe { (self.func)(state, start, end) } 25 | } 26 | 27 | pub unsafe fn execute(&self, tape: &mut [u8]) -> Result { 28 | let tape_len = tape.len(); 29 | 30 | let (stdin, stdout, mut utf8_buffer) = 31 | (io::stdin(), io::stdout(), String::with_capacity(512)); 32 | 33 | let start_ptr = tape.as_mut_ptr(); 34 | let end_ptr = unsafe { start_ptr.add(tape_len) }; 35 | 36 | let (mut stdin, mut stdout) = (stdin.lock(), stdout.lock()); 37 | stdout 38 | .flush() 39 | .context("failed to flush stdout before jitted function execution")?; 40 | 41 | let mut state = State::new( 42 | &mut stdin, 43 | &mut stdout, 44 | &mut utf8_buffer, 45 | start_ptr.cast(), 46 | end_ptr.cast(), 47 | ); 48 | 49 | let jit_start = Instant::now(); 50 | let jit_return = panic::catch_unwind(AssertUnwindSafe(|| unsafe { 51 | self.call(&mut state, start_ptr, end_ptr) 52 | })) 53 | .map_err(|err| anyhow::anyhow!("jitted function panicked: {:?}", err)); 54 | let elapsed = jit_start.elapsed(); 55 | drop(stdin); 56 | 57 | stdout 58 | .flush() 59 | .context("failed to flush stdout after jitted function execution")?; 60 | drop(stdout); 61 | 62 | tracing::debug!( 63 | "jitted function returned {:?} in {:#?}", 64 | jit_return, 65 | elapsed, 66 | ); 67 | 68 | jit_return 69 | } 70 | 71 | pub unsafe fn execute_into( 72 | &self, 73 | tape: &mut [u8], 74 | stdin: &mut dyn Read, 75 | stdout: &mut dyn Write, 76 | ) -> Result { 77 | let tape_len = tape.len(); 78 | 79 | let mut utf8_buffer = String::with_capacity(512); 80 | 81 | let start_ptr = tape.as_mut_ptr(); 82 | let end_ptr = unsafe { start_ptr.add(tape_len) }; 83 | 84 | let mut state = State::new( 85 | stdin, 86 | stdout, 87 | &mut utf8_buffer, 88 | start_ptr.cast(), 89 | end_ptr.cast(), 90 | ); 91 | 92 | let jit_start = Instant::now(); 93 | let jit_return = panic::catch_unwind(AssertUnwindSafe(|| unsafe { 94 | self.call(&mut state, start_ptr, end_ptr) 95 | })) 96 | .map_err(|err| anyhow::anyhow!("jitted function panicked: {:?}", err)); 97 | let elapsed = jit_start.elapsed(); 98 | 99 | tracing::debug!( 100 | "jitted function returned {:?} in {:#?}", 101 | jit_return, 102 | elapsed, 103 | ); 104 | 105 | jit_return 106 | } 107 | } 108 | 109 | impl Drop for Executable { 110 | fn drop(&mut self) { 111 | if let Some(module) = self.module.take() { 112 | unsafe { module.free_memory() }; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/jit/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod ffi; 3 | mod basic_block; 4 | mod block_builder; 5 | mod block_visitor; 6 | // mod cir_jit; 7 | mod cir_to_bb; 8 | mod codegen; 9 | mod disassemble; 10 | mod memory; 11 | 12 | pub use codegen::JitReturnCode; 13 | pub use ffi::State; 14 | pub use memory::Executable; 15 | 16 | use crate::{ 17 | args::Settings, 18 | ir::{Block, Pretty, PrettyConfig}, 19 | jit::codegen::Codegen, 20 | }; 21 | use anyhow::{Context as _, Result}; 22 | use cranelift::{ 23 | codegen::Context, 24 | frontend::FunctionBuilderContext, 25 | prelude::{ 26 | isa::TargetIsa, 27 | settings::{self, Flags}, 28 | Configurable, 29 | }, 30 | }; 31 | use cranelift_jit::{JITBuilder, JITModule}; 32 | use cranelift_module::{DataContext, Linkage, Module}; 33 | use std::{fs, mem::transmute, path::Path, slice}; 34 | 35 | /// The function produced by jit code 36 | pub type JitFunction = unsafe extern "fastcall" fn(*mut State, *mut u8, *const u8) -> u8; 37 | 38 | const SCAN_ERROR_MESSAGE: &str = "A scanr or scanl call failed"; 39 | 40 | pub struct Jit<'a> { 41 | /// The function builder context, which is reused across multiple 42 | /// `FunctionBuilder` instances 43 | builder_ctx: FunctionBuilderContext, 44 | 45 | /// The main Cranelift context, which holds the state for codegen. Cranelift 46 | /// separates this from `Module` to allow for parallel compilation, with a 47 | /// context per thread, though this isn't in the simple demo here 48 | ctx: Context, 49 | 50 | data_ctx: DataContext, 51 | 52 | /// The module, with the jit backend, which manages the JIT'd 53 | /// functions 54 | module: JITModule, 55 | 56 | /// The length of the (program/turing) tape we're targeting 57 | tape_len: u16, 58 | 59 | dump_dir: Option<&'a Path>, 60 | file_name: Option<&'a str>, 61 | } 62 | 63 | impl<'a> Jit<'a> { 64 | /// Create a new jit 65 | pub fn new( 66 | settings: &Settings, 67 | dump_dir: Option<&'a Path>, 68 | file_name: Option<&'a str>, 69 | ) -> Result { 70 | let isa = build_isa()?; 71 | let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names()); 72 | 73 | // Add external functions to the module so they're accessible within 74 | // generated code 75 | builder.symbols([ 76 | ("input", ffi::input as *const u8), 77 | ("output", ffi::output as *const u8), 78 | ("io_error", ffi::io_error as *const u8), 79 | ]); 80 | 81 | // If tape wrapping is UB, we can use our functions that take advantage of it 82 | if settings.tape_wrapping_ub { 83 | builder.symbols([ 84 | ("scanr", ffi::scanr_non_wrapping as *const u8), 85 | ("scanl", ffi::scanl_non_wrapping as *const u8), 86 | ]); 87 | } else { 88 | builder.symbols([ 89 | ("scanr", ffi::scanr_wrapping as *const u8), 90 | ("scanl", ffi::scanl_wrapping as *const u8), 91 | ]); 92 | } 93 | 94 | let mut module = JITModule::new(builder); 95 | let ctx = module.make_context(); 96 | let builder_ctx = FunctionBuilderContext::new(); 97 | 98 | let mut data_ctx = DataContext::new(); 99 | data_ctx.define(Box::from(SCAN_ERROR_MESSAGE.as_bytes())); 100 | let message_id = 101 | module.declare_data("scan_error_message", Linkage::Export, false, false)?; 102 | module.define_data(message_id, &data_ctx)?; 103 | 104 | Ok(Self { 105 | builder_ctx, 106 | ctx, 107 | data_ctx, 108 | module, 109 | tape_len: settings.tape_len.get(), 110 | dump_dir, 111 | file_name, 112 | }) 113 | } 114 | 115 | /// Compile a block of CIR into an executable buffer 116 | pub fn compile(mut self, block: &Block) -> Result { 117 | // Translate CIR into SSA form 118 | let blocks = cir_to_bb::translate(block); 119 | 120 | // Create a formatted version of the ssa ir we just generated 121 | if let (Some(dump_dir), Some(file_name)) = (self.dump_dir, self.file_name) { 122 | let ssa_ir = blocks.pretty_print(PrettyConfig::minimal()); 123 | fs::write(dump_dir.join(file_name).with_extension("ssa"), ssa_ir) 124 | .context("failed to write ssa ir file")?; 125 | } 126 | 127 | let mut codegen = Codegen::new( 128 | &blocks, 129 | &mut self.ctx, 130 | &mut self.module, 131 | &mut self.builder_ctx, 132 | &mut self.data_ctx, 133 | self.tape_len, 134 | )?; 135 | let function = codegen.run()?; 136 | 137 | if let (Some(dump_dir), Some(file_name)) = (self.dump_dir, self.file_name) { 138 | fs::write( 139 | dump_dir.join(file_name).with_extension("clif"), 140 | &function.to_string(), 141 | ) 142 | .context("failed to write clif ir file")?; 143 | } 144 | 145 | codegen.finish(); 146 | 147 | // Next, declare the function to jit. Functions must be declared 148 | // before they can be called, or defined 149 | let function_id = self.module.declare_function( 150 | "coitus_jit", 151 | Linkage::Export, 152 | &self.ctx.func.signature, 153 | )?; 154 | 155 | // Define the function within the jit 156 | let code_len = self.module.define_function(function_id, &mut self.ctx)?; 157 | 158 | // Now that compilation is finished, we can clear out the context state. 159 | self.module.clear_context(&mut self.ctx); 160 | 161 | // Finalize the functions which we just defined, which resolves any 162 | // outstanding relocations (patching in addresses, now that they're 163 | // available). 164 | self.module.finalize_definitions(); 165 | 166 | // We can now retrieve a pointer to the machine code. 167 | let code = self.module.get_finalized_function(function_id); 168 | 169 | // Disassemble the generated instructions 170 | if let (Some(dump_dir), Some(file_name)) = (self.dump_dir, self.file_name) { 171 | let code_bytes = unsafe { slice::from_raw_parts(code, code_len.size as usize) }; 172 | let disassembly = disassemble::disassemble(code_bytes)?; 173 | 174 | fs::write(dump_dir.join(file_name).with_extension("asm"), disassembly) 175 | .context("failed to write asm file")?; 176 | } 177 | 178 | // TODO: Assembly statistics 179 | 180 | let function = unsafe { transmute::<*const u8, JitFunction>(code) }; 181 | let executable = Executable::new(function, self.module); 182 | 183 | Ok(executable) 184 | } 185 | } 186 | 187 | /// Produces the isa description for the current machine so that we can jit for it 188 | fn build_isa() -> Result> { 189 | let mut flag_builder = settings::builder(); 190 | 191 | // On at least AArch64, "colocated" calls use shorter-range relocations, 192 | // which might not reach all definitions; we can't handle that here, so 193 | // we require long-range relocation types. 194 | flag_builder 195 | .set("use_colocated_libcalls", "false") 196 | .context("failed to use colocated library calls")?; 197 | 198 | // Position independent code screws with our disassembly 199 | // flag_builder 200 | // .set("is_pic", "true") 201 | // .context("failed to set position independent code")?; 202 | 203 | cranelift_native::builder() 204 | .map_err(|msg| anyhow::anyhow!("host machine is not supported: {}", msg))? 205 | .finish(Flags::new(flag_builder)) 206 | .context("failed to build target isa") 207 | } 208 | -------------------------------------------------------------------------------- /src/lower_tokens.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{GammaData, OutputPort, Rvsdg, ThetaData}, 3 | parse::Token, 4 | }; 5 | 6 | pub fn lower_tokens( 7 | graph: &mut Rvsdg, 8 | mut ptr: OutputPort, 9 | mut effect: OutputPort, 10 | tokens: &[Token], 11 | ) -> (OutputPort, OutputPort) { 12 | // FIXME: Byte node 13 | let (zero, one) = (graph.byte(0).value(), graph.byte(1).value()); 14 | 15 | for token in tokens { 16 | match token { 17 | Token::IncPtr => ptr = graph.add(ptr, one).value(), 18 | Token::DecPtr => ptr = graph.sub(ptr, one).value(), 19 | 20 | Token::Inc => { 21 | // Load the pointed-to cell's current value 22 | let load = graph.load(ptr, effect); 23 | effect = load.output_effect(); 24 | 25 | // Increment the loaded cell's value 26 | let inc = graph.add(load.output_value(), one).value(); 27 | 28 | // Store the incremented value into the pointed-to cell 29 | let store = graph.store(ptr, inc, effect); 30 | effect = store.output_effect(); 31 | } 32 | Token::Dec => { 33 | // Load the pointed-to cell's current value 34 | let load = graph.load(ptr, effect); 35 | effect = load.output_effect(); 36 | 37 | // Decrement the loaded cell's value 38 | let dec = graph.sub(load.output_value(), one).value(); 39 | 40 | // Store the decremented value into the pointed-to cell 41 | let store = graph.store(ptr, dec, effect); 42 | effect = store.output_effect(); 43 | } 44 | 45 | Token::Output => { 46 | // Load the pointed-to cell's current value 47 | let load = graph.load(ptr, effect); 48 | effect = load.output_effect(); 49 | 50 | // Output the value of the loaded cell 51 | let output = graph.output(load.output_value(), effect); 52 | effect = output.output_effect(); 53 | } 54 | Token::Input => { 55 | // Get user input 56 | let input = graph.input(effect); 57 | effect = input.output_effect(); 58 | 59 | // Store the input's result to the currently pointed-to cell 60 | let store = graph.store(ptr, input.output_value(), effect); 61 | effect = store.output_effect(); 62 | } 63 | 64 | Token::Loop(body) => { 65 | // Load the current cell's value 66 | let load = graph.load(ptr, effect); 67 | effect = load.output_effect(); 68 | 69 | // Compare the cell's value to zero 70 | let cmp = graph.eq(load.output_value(), zero); 71 | 72 | // Create a gamma node to decide whether or not to drop into the loop 73 | // Brainfuck loops are equivalent to this general structure: 74 | // 75 | // ```rust 76 | // if *ptr != 0 { 77 | // do { ... } while *ptr != 0; 78 | // } 79 | // ``` 80 | // 81 | // So we translate that into our node structure using a gamma 82 | // node as the outer `if` and a theta as the inner tail controlled loop 83 | let gamma = graph.gamma( 84 | [ptr], 85 | effect, 86 | cmp.value(), 87 | // The truthy branch (`*ptr == 0`) is empty, we skip the loop entirely 88 | // if the cell's value is already zero 89 | |_graph, effect, inputs| { 90 | let ptr = inputs[0]; 91 | GammaData::new([ptr], effect) 92 | }, 93 | // The falsy branch where `*ptr != 0`, this is where we run the loop's actual body! 94 | |graph, mut effect, inputs| { 95 | let mut ptr = inputs[0]; 96 | 97 | // Create the inner theta node 98 | // FIXME: Pass in invariant constants 99 | let theta = graph.theta( 100 | [], 101 | [ptr], 102 | effect, 103 | |graph, effect, _invariant_inputs, variant_inputs| { 104 | let [ptr]: [OutputPort; 1] = variant_inputs.try_into().unwrap(); 105 | let (ptr, effect) = lower_tokens(graph, ptr, effect, body); 106 | 107 | let zero = graph.byte(0); 108 | let load = graph.load(ptr, effect); 109 | let condition = graph.neq(load.output_value(), zero.value()); 110 | 111 | ThetaData::new([ptr], condition.value(), load.output_effect()) 112 | }, 113 | ); 114 | 115 | ptr = theta.output_ports().next().unwrap(); 116 | effect = theta 117 | .output_effect() 118 | .expect("all thetas are effectful right now"); 119 | 120 | GammaData::new([ptr], effect) 121 | }, 122 | ); 123 | 124 | ptr = gamma.outputs()[0]; 125 | effect = gamma.output_effect(); 126 | } 127 | } 128 | } 129 | 130 | (ptr, effect) 131 | } 132 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::max; 2 | 3 | #[derive(Debug)] 4 | pub enum Token { 5 | IncPtr, 6 | DecPtr, 7 | Inc, 8 | Dec, 9 | Output, 10 | Input, 11 | Loop(Box<[Self]>), 12 | } 13 | 14 | enum RawToken { 15 | IncPtr, 16 | DecPtr, 17 | Inc, 18 | Dec, 19 | Output, 20 | Input, 21 | JumpStart, 22 | JumpEnd, 23 | } 24 | 25 | #[derive(Debug)] 26 | pub struct Parsed { 27 | pub tokens: Box<[Token]>, 28 | pub source_len: usize, 29 | pub total_tokens: usize, 30 | pub deepest_nesting: usize, 31 | } 32 | 33 | pub fn parse(source: &str) -> Parsed { 34 | let tokens = source.chars().flat_map(|token| { 35 | Some(match token { 36 | '>' => RawToken::IncPtr, 37 | '<' => RawToken::DecPtr, 38 | '+' => RawToken::Inc, 39 | '-' => RawToken::Dec, 40 | '.' => RawToken::Output, 41 | ',' => RawToken::Input, 42 | '[' => RawToken::JumpStart, 43 | ']' => RawToken::JumpEnd, 44 | _ => return None, 45 | }) 46 | }); 47 | 48 | let (mut scopes, mut total_tokens, mut deepest_nesting) = (vec![Vec::new()], 0, 0); 49 | for token in tokens { 50 | match token { 51 | RawToken::IncPtr => scopes.last_mut().unwrap().push(Token::IncPtr), 52 | RawToken::DecPtr => scopes.last_mut().unwrap().push(Token::DecPtr), 53 | RawToken::Inc => scopes.last_mut().unwrap().push(Token::Inc), 54 | RawToken::Dec => scopes.last_mut().unwrap().push(Token::Dec), 55 | RawToken::Output => scopes.last_mut().unwrap().push(Token::Output), 56 | RawToken::Input => scopes.last_mut().unwrap().push(Token::Input), 57 | RawToken::JumpStart => { 58 | scopes.push(Vec::new()); 59 | deepest_nesting = max(deepest_nesting, scopes.len()); 60 | } 61 | RawToken::JumpEnd => { 62 | let body = scopes.pop().unwrap(); 63 | scopes 64 | .last_mut() 65 | .unwrap() 66 | .push(Token::Loop(body.into_boxed_slice())); 67 | } 68 | } 69 | 70 | total_tokens += 1; 71 | } 72 | 73 | assert_eq!(scopes.len(), 1); 74 | let tokens = scopes.remove(0).into_boxed_slice(); 75 | 76 | Parsed { 77 | tokens, 78 | source_len: source.len(), 79 | total_tokens, 80 | deepest_nesting, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/passes/canonicalize.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{Add, Bool, Byte, Eq, Gamma, Int, Mul, Neq, NodeExt, Not, OutputPort, Rvsdg, Theta}, 3 | passes::{ 4 | utils::{ChangeReport, Changes}, 5 | Pass, 6 | }, 7 | values::{Cell, Ptr}, 8 | }; 9 | use std::{collections::BTreeSet, mem::swap}; 10 | use tinyvec::TinyVec; 11 | 12 | #[derive(Debug)] 13 | pub struct Canonicalize { 14 | constants: BTreeSet, 15 | changes: Changes<3>, 16 | } 17 | 18 | impl Canonicalize { 19 | pub fn new() -> Self { 20 | Self { 21 | constants: BTreeSet::new(), 22 | changes: Changes::new(["canonicalizations", "eq-to-neq", "neq-to-eq"]), 23 | } 24 | } 25 | } 26 | 27 | impl Pass for Canonicalize { 28 | fn pass_name(&self) -> &'static str { 29 | "canonicalize" 30 | } 31 | 32 | fn did_change(&self) -> bool { 33 | self.changes.did_change() 34 | } 35 | 36 | fn reset(&mut self) { 37 | self.constants.clear(); 38 | self.changes.reset(); 39 | } 40 | 41 | fn report(&self) -> ChangeReport { 42 | self.changes.as_report() 43 | } 44 | 45 | fn visit_int(&mut self, _graph: &mut Rvsdg, int: Int, _: Ptr) { 46 | self.constants.insert(int.value()); 47 | } 48 | 49 | fn visit_byte(&mut self, _graph: &mut Rvsdg, byte: Byte, _: Cell) { 50 | self.constants.insert(byte.value()); 51 | } 52 | 53 | fn visit_bool(&mut self, _graph: &mut Rvsdg, bool: Bool, _: bool) { 54 | self.constants.insert(bool.value()); 55 | } 56 | 57 | fn visit_add(&mut self, graph: &mut Rvsdg, add: Add) { 58 | let (lhs_src, rhs_src) = (graph.input_source(add.lhs()), graph.input_source(add.rhs())); 59 | 60 | if self.constants.contains(&lhs_src) && !self.constants.contains(&rhs_src) { 61 | tracing::debug!( 62 | ?add, 63 | ?lhs_src, 64 | ?rhs_src, 65 | "swapping add inputs {} and {}", 66 | add.lhs(), 67 | add.rhs(), 68 | ); 69 | 70 | graph.remove_input_edges(add.lhs()); 71 | graph.remove_input_edges(add.rhs()); 72 | 73 | graph.add_value_edge(rhs_src, add.lhs()); 74 | graph.add_value_edge(lhs_src, add.rhs()); 75 | 76 | self.changes.inc::<"canonicalizations">(); 77 | } 78 | } 79 | 80 | // Note: We can't canonicalize subtraction 81 | 82 | fn visit_mul(&mut self, graph: &mut Rvsdg, mul: Mul) { 83 | let (lhs_src, rhs_src) = (graph.input_source(mul.lhs()), graph.input_source(mul.rhs())); 84 | 85 | if self.constants.contains(&lhs_src) && !self.constants.contains(&rhs_src) { 86 | tracing::debug!( 87 | ?mul, 88 | ?lhs_src, 89 | ?rhs_src, 90 | "swapping mul inputs {} and {}", 91 | mul.lhs(), 92 | mul.rhs(), 93 | ); 94 | 95 | graph.remove_input_edges(mul.lhs()); 96 | graph.remove_input_edges(mul.rhs()); 97 | 98 | graph.add_value_edge(rhs_src, mul.lhs()); 99 | graph.add_value_edge(lhs_src, mul.rhs()); 100 | 101 | self.changes.inc::<"canonicalizations">(); 102 | } 103 | } 104 | 105 | fn visit_eq(&mut self, graph: &mut Rvsdg, eq: Eq) { 106 | let (mut lhs_src, mut rhs_src) = 107 | (graph.input_source(eq.lhs()), graph.input_source(eq.rhs())); 108 | 109 | // If the lhs is a constant and the rhs isn't, swap the two inputs 110 | // to turn `eq 10, x` into `eq x, 10`. This doesn't affect eqs 111 | // with two constant or two non-constant inputs 112 | if self.constants.contains(&lhs_src) && !self.constants.contains(&rhs_src) { 113 | tracing::debug!( 114 | ?eq, 115 | ?lhs_src, 116 | ?rhs_src, 117 | "swapping eq inputs {} and {}", 118 | eq.lhs(), 119 | eq.rhs(), 120 | ); 121 | 122 | graph.remove_input_edges(eq.lhs()); 123 | graph.remove_input_edges(eq.rhs()); 124 | 125 | graph.add_value_edge(rhs_src, eq.lhs()); 126 | graph.add_value_edge(lhs_src, eq.rhs()); 127 | 128 | // Swap the two inputs for when we do eq => neq canonicalization 129 | swap(&mut lhs_src, &mut rhs_src); 130 | 131 | self.changes.inc::<"canonicalizations">(); 132 | } 133 | 134 | // Find all consumers of this eq that are `not` nodes. 135 | // This will find `not (eq x, y)` 136 | let consuming_nots: TinyVec<[_; 2]> = graph 137 | .cast_output_consumers::(eq.value()) 138 | .copied() 139 | .collect(); 140 | 141 | // If any of the consumers are nots, create a neq node and rewire all consumers of the `not` 142 | // to the `neq`, transforming 143 | // 144 | // ``` 145 | // x = eq a, b 146 | // y = not x 147 | // ``` 148 | // 149 | // Into this 150 | // 151 | // ``` 152 | // y = neq a, b 153 | // ``` 154 | let mut neq = None; 155 | for not in consuming_nots { 156 | let neq = *neq.get_or_insert_with(|| graph.neq(lhs_src, rhs_src).value()); 157 | graph.rewire_dependents(not.value(), neq); 158 | 159 | self.changes.inc::<"eq-to-neq">(); 160 | } 161 | } 162 | 163 | fn visit_neq(&mut self, graph: &mut Rvsdg, neq: Neq) { 164 | let (mut lhs_src, mut rhs_src) = 165 | (graph.input_source(neq.lhs()), graph.input_source(neq.rhs())); 166 | 167 | if self.constants.contains(&lhs_src) && !self.constants.contains(&rhs_src) { 168 | tracing::debug!( 169 | ?neq, 170 | ?lhs_src, 171 | ?rhs_src, 172 | "swapping neq inputs {} and {}", 173 | neq.lhs(), 174 | neq.rhs(), 175 | ); 176 | 177 | graph.remove_input_edges(neq.lhs()); 178 | graph.remove_input_edges(neq.rhs()); 179 | 180 | graph.add_value_edge(rhs_src, neq.lhs()); 181 | graph.add_value_edge(lhs_src, neq.rhs()); 182 | 183 | // Swap the two inputs for when we do neq => eq canonicalization 184 | swap(&mut lhs_src, &mut rhs_src); 185 | 186 | self.changes.inc::<"canonicalizations">(); 187 | } 188 | 189 | // Find all consumers of this eq that are `not` nodes. 190 | // This will find `not (neq x, y)` 191 | let consuming_nots: TinyVec<[_; 2]> = graph 192 | .cast_output_consumers::(neq.value()) 193 | .copied() 194 | .collect(); 195 | 196 | // If any of the consumers are nots, create an eq node and rewire all consumers of the `not` 197 | // to the `eq`, transforming 198 | // 199 | // ``` 200 | // x = neq a, b 201 | // y = not x 202 | // ``` 203 | // 204 | // Into this 205 | // 206 | // ``` 207 | // y = eq a, b 208 | // ``` 209 | let mut eq = None; 210 | for not in consuming_nots { 211 | let eq = *eq.get_or_insert_with(|| graph.eq(lhs_src, rhs_src).value()); 212 | graph.rewire_dependents(not.value(), eq); 213 | 214 | self.changes.inc::<"neq-to-eq">(); 215 | } 216 | } 217 | 218 | // FIXME: Remove the added constants from the inner scopes 219 | fn visit_gamma(&mut self, graph: &mut Rvsdg, mut gamma: Gamma) { 220 | let mut changed = false; 221 | changed |= self.visit_graph(gamma.true_mut()); 222 | changed |= self.visit_graph(gamma.false_mut()); 223 | 224 | if changed { 225 | graph.replace_node(gamma.node(), gamma); 226 | } 227 | } 228 | 229 | // FIXME: Remove the added constants from the inner scopes 230 | fn visit_theta(&mut self, graph: &mut Rvsdg, mut theta: Theta) { 231 | if self.visit_graph(theta.body_mut()) { 232 | graph.replace_node(theta.node(), theta); 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/passes/dataflow/domain/utils.rs: -------------------------------------------------------------------------------- 1 | #[inline] 2 | pub fn lsb(chunk: u64) -> Option { 3 | if chunk == 0 { 4 | None 5 | } else { 6 | Some(chunk.trailing_zeros() as u8) 7 | } 8 | } 9 | 10 | /// Returns the last (most significant) bit of `chunk`, or `None` if `chunk` is 11 | /// 0. 12 | #[inline] 13 | pub fn msb(chunk: u64) -> Option { 14 | if chunk == 0 { 15 | None 16 | } else { 17 | let bits = u64::BITS - 1; 18 | Some((bits as u8) ^ chunk.leading_zeros() as u8) 19 | } 20 | } 21 | 22 | /// Removes the first (least significant) bit from `chunk` and returns it, or 23 | /// `None` if `chunk` is 0. 24 | #[inline] 25 | pub fn pop_lsb(chunk: &mut u64) -> Option { 26 | let lsb = lsb(*chunk)?; 27 | *chunk ^= 1 << lsb; 28 | Some(lsb) 29 | } 30 | 31 | /// Removes the last (most significant) bit from `chunk` and returns it, or 32 | /// `None` if `chunk` is 0. 33 | #[inline] 34 | pub fn pop_msb(chunk: &mut u64) -> Option { 35 | let msb = msb(*chunk)?; 36 | *chunk ^= 1 << msb; 37 | Some(msb) 38 | } 39 | -------------------------------------------------------------------------------- /src/passes/dataflow/equality.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{Eq, Neq, Rvsdg}, 3 | passes::dataflow::{ 4 | domain::{differential_product, BoolSet}, 5 | Dataflow, 6 | }, 7 | }; 8 | 9 | impl Dataflow { 10 | pub(super) fn compute_eq(&mut self, graph: &mut Rvsdg, eq: Eq) { 11 | let (lhs_src, rhs_src) = (graph.input_source(eq.lhs()), graph.input_source(eq.rhs())); 12 | 13 | let mut eq_domain = BoolSet::full(); 14 | if let (Some(lhs), Some(rhs)) = (self.domain(lhs_src), self.domain(rhs_src)) { 15 | if let Some((lhs, rhs)) = lhs.as_singleton().zip(rhs.as_singleton()) { 16 | // lhs ≡ rhs ⟹ ¬(lhs ≠ rhs) 17 | if lhs == rhs { 18 | // Only the true branch is reachable 19 | eq_domain.remove(false); 20 | 21 | // lhs ≠ rhs ⟹ ¬(lhs ≡ rhs) 22 | } else { 23 | // Only the false branch is reachable 24 | eq_domain.remove(true); 25 | } 26 | 27 | // If it's possible that the two operands can have the same value, 28 | // then it's possible for the eq to return true 29 | } else if lhs.intersects(rhs) { 30 | // Both branches are reachable right now 31 | 32 | let intersection = lhs.intersect(rhs); 33 | let (lhs_true_domain, rhs_true_domain) = differential_product(lhs, rhs); 34 | debug_assert!(!intersection.is_empty()); 35 | 36 | // Apply constraints to the operands within branches on this condition 37 | // TODO: Warn if we get empty sets here 38 | if !lhs_true_domain.is_empty() { 39 | self.add_constraints( 40 | eq.value(), 41 | lhs_src, 42 | intersection.clone(), 43 | lhs_true_domain, 44 | ); 45 | } 46 | if !rhs_true_domain.is_empty() { 47 | self.add_constraints(eq.value(), rhs_src, intersection, rhs_true_domain); 48 | } 49 | 50 | // If there's zero overlap between the two operands (lhs ∩ rhs = Ø) 51 | // then the eq can't possibly return true 52 | } else { 53 | // Only the false branch is reachable 54 | eq_domain.remove(true); 55 | } 56 | } 57 | 58 | // If the eq's result is statically known, replace the eq node 59 | // with a constant value 60 | if self.can_mutate { 61 | if let Some(result) = eq_domain.as_singleton() { 62 | let node = graph.bool(result); 63 | self.add_domain(node.value(), result); 64 | graph.rewire_dependents(eq.value(), node.value()); 65 | self.changes.inc::<"const-eq">(); 66 | } 67 | } 68 | 69 | self.add_domain(eq.value(), eq_domain); 70 | } 71 | 72 | pub(super) fn compute_neq(&mut self, graph: &mut Rvsdg, neq: Neq) { 73 | let (lhs_src, rhs_src) = (graph.input_source(neq.lhs()), graph.input_source(neq.rhs())); 74 | 75 | let mut neq_domain = BoolSet::full(); 76 | if let (Some(lhs), Some(rhs)) = (self.domain(lhs_src), self.domain(rhs_src)) { 77 | if let Some((lhs, rhs)) = lhs.as_singleton().zip(rhs.as_singleton()) { 78 | // lhs ≡ rhs ⟹ ¬(lhs ≠ rhs) 79 | if lhs == rhs { 80 | // Only the false branch is reachable 81 | neq_domain.remove(true); 82 | 83 | // lhs ≠ rhs ⟹ ¬(lhs ≡ rhs) 84 | } else { 85 | // Only the true branch is reachable 86 | neq_domain.remove(false); 87 | } 88 | 89 | // If it's possible that the two operands can have the same value, 90 | // then it's possible for the neq to return false 91 | } else if lhs.intersects(rhs) { 92 | // Both branches are reachable right now 93 | 94 | let intersection = lhs.intersect(rhs); 95 | let (lhs_false_domain, rhs_false_domain) = differential_product(lhs, rhs); 96 | debug_assert!(!intersection.is_empty()); 97 | 98 | // Apply constraints to the operands within branches on this condition 99 | // TODO: Warn if we get empty sets here 100 | if !lhs_false_domain.is_empty() { 101 | self.add_constraints( 102 | neq.value(), 103 | lhs_src, 104 | lhs_false_domain, 105 | intersection.clone(), 106 | ); 107 | } 108 | if !rhs_false_domain.is_empty() { 109 | self.add_constraints(neq.value(), rhs_src, rhs_false_domain, intersection); 110 | } 111 | 112 | // If there's zero overlap between the two operands (lhs ∩ rhs = Ø) 113 | // then the neq can't possibly return false 114 | } else { 115 | // Only the true branch is reachable 116 | neq_domain.remove(false); 117 | } 118 | } 119 | 120 | // If the neq's result is statically known, replace the neq node 121 | // with a constant value 122 | if self.can_mutate { 123 | if let Some(result) = neq_domain.as_singleton() { 124 | let node = graph.bool(result); 125 | self.add_domain(node.value(), result); 126 | graph.rewire_dependents(neq.value(), node.value()); 127 | self.changes.inc::<"const-neq">(); 128 | } 129 | } 130 | 131 | self.add_domain(neq.value(), neq_domain); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/passes/dataflow/mod.rs: -------------------------------------------------------------------------------- 1 | mod arithmetic; 2 | mod domain; 3 | mod equality; 4 | mod gamma; 5 | mod memory; 6 | mod pass; 7 | mod theta; 8 | 9 | use crate::{ 10 | graph::{InputParam, InputPort, NodeId, OutputPort, Rvsdg}, 11 | passes::{dataflow::domain::ProgramTape, utils::Changes}, 12 | utils::{AssertNone, ImHashMap}, 13 | }; 14 | use domain::{ByteSet, Domain}; 15 | use im_rc::hashmap::HashMapPool; 16 | use std::{fmt::Debug, panic::Location, rc::Rc}; 17 | 18 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 19 | pub struct DataflowSettings { 20 | /// The length of the program tape 21 | tape_len: u16, 22 | 23 | /// Whether or not the tape pointer can overflow and underflow 24 | tape_operations_wrap: bool, 25 | 26 | /// Whether or not cells can overflow and underflow 27 | cell_operations_wrap: bool, 28 | } 29 | 30 | impl DataflowSettings { 31 | pub const fn new( 32 | tape_len: u16, 33 | tape_operations_wrap: bool, 34 | cell_operations_wrap: bool, 35 | ) -> Self { 36 | Self { 37 | tape_len, 38 | tape_operations_wrap, 39 | cell_operations_wrap, 40 | } 41 | } 42 | } 43 | 44 | #[derive(Debug)] 45 | pub struct Dataflow { 46 | changes: Changes<7>, 47 | values: ImHashMap, 48 | port_provenance: ImHashMap, 49 | constraints: ImHashMap<(OutputPort, OutputPort), (Domain, Domain)>, 50 | // TODO: Memory location constraints, constraints that pointers have on 51 | // the cell they point to 52 | tape: ProgramTape, 53 | settings: DataflowSettings, 54 | can_mutate: bool, 55 | } 56 | 57 | impl Dataflow { 58 | pub fn new(settings: DataflowSettings) -> Self { 59 | let (value_pool, provenance_pool, constraint_pool) = ( 60 | HashMapPool::new(1024), 61 | HashMapPool::new(256), 62 | HashMapPool::new(256), 63 | ); 64 | let (values, port_provenance, constraints) = ( 65 | ImHashMap::with_pool_hasher(&value_pool, Rc::new(Default::default())), 66 | ImHashMap::with_pool_hasher(&provenance_pool, Rc::new(Default::default())), 67 | ImHashMap::with_pool_hasher(&constraint_pool, Rc::new(Default::default())), 68 | ); 69 | 70 | Self { 71 | changes: Self::new_changes(), 72 | values, 73 | port_provenance, 74 | constraints, 75 | tape: ProgramTape::zeroed(settings.tape_len), 76 | settings, 77 | can_mutate: true, 78 | } 79 | } 80 | 81 | fn clone_for_subscope(&self, values: ImHashMap) -> Self { 82 | let mut port_provenance = ImHashMap::with_pool_hasher( 83 | self.port_provenance.pool(), 84 | self.port_provenance.hasher().clone(), 85 | ); 86 | port_provenance.extend(values.keys().filter_map(|output| { 87 | self.port_provenance 88 | .get(output) 89 | .cloned() 90 | .map(|domain| (*output, domain)) 91 | })); 92 | 93 | Self { 94 | changes: Self::new_changes(), 95 | values, 96 | port_provenance, 97 | tape: self.tape.clone(), 98 | constraints: self.constraints.clone(), 99 | settings: self.settings, 100 | can_mutate: false, 101 | } 102 | } 103 | 104 | fn allow_mutation(&mut self) { 105 | self.can_mutate = true; 106 | } 107 | 108 | fn with_mutation(mut self, can_mutate: bool) -> Self { 109 | self.can_mutate = can_mutate; 110 | self 111 | } 112 | 113 | fn new_changes() -> Changes<7> { 114 | Changes::new([ 115 | "const-eq", 116 | "const-neq", 117 | "const-add", 118 | "const-sub", 119 | "const-load", 120 | "const-theta-cond", 121 | "gamma-branch-elision", 122 | ]) 123 | } 124 | 125 | const fn tape_len(&self) -> u16 { 126 | self.settings.tape_len 127 | } 128 | 129 | #[track_caller] 130 | fn add_domain(&mut self, port: OutputPort, value: C) 131 | where 132 | C: Into, 133 | { 134 | let mut value = value.into(); 135 | if value.is_empty() { 136 | let caller = Location::caller(); 137 | tracing::warn!( 138 | file = caller.file(), 139 | line = caller.line(), 140 | column = caller.column(), 141 | "domain given for port {port} is empty", 142 | ); 143 | 144 | return; 145 | } 146 | 147 | self.values 148 | .entry(port) 149 | .and_modify(|domain| { 150 | domain.union_mut(&mut value); 151 | }) 152 | .or_insert(value); 153 | } 154 | 155 | fn domain(&self, port: OutputPort) -> Option<&Domain> { 156 | self.values.get(&port).filter(|domain| !domain.is_empty()) 157 | } 158 | 159 | #[allow(clippy::too_many_arguments)] 160 | fn add_constraints( 161 | &mut self, 162 | comparison: OutputPort, 163 | value: OutputPort, 164 | mut true_domain: Domain, 165 | mut false_domain: Domain, 166 | ) { 167 | self.constraints 168 | .entry((comparison, value)) 169 | .and_modify(|(first, second)| { 170 | first.union_mut(&mut true_domain); 171 | second.union_mut(&mut false_domain); 172 | }) 173 | .or_insert((true_domain, false_domain)); 174 | } 175 | 176 | fn add_provenance(&mut self, output: OutputPort, value: ByteSet) { 177 | if value.is_empty() { 178 | let caller = Location::caller(); 179 | tracing::warn!( 180 | file = caller.file(), 181 | line = caller.line(), 182 | column = caller.column(), 183 | "provenance given for port {output} is empty", 184 | ); 185 | 186 | return; 187 | } 188 | 189 | tracing::debug!( 190 | target: "dataflow-provenance", 191 | "setting provenance for {output} to {value}", 192 | ); 193 | 194 | self.port_provenance 195 | .entry(output) 196 | .and_modify(|domain| { 197 | domain.union(value); 198 | }) 199 | .or_insert(value); 200 | } 201 | 202 | fn provenance(&self, port: OutputPort) -> Option<&ByteSet> { 203 | self.port_provenance 204 | .get(&port) 205 | .filter(|domain| !domain.is_empty()) 206 | } 207 | 208 | fn collect_subgraph_inputs( 209 | &mut self, 210 | graph: &Rvsdg, 211 | branch_graph: &Rvsdg, 212 | input_pairs: impl Iterator, 213 | ) -> ImHashMap { 214 | let mut values = 215 | ImHashMap::with_pool_hasher(self.values.pool(), Rc::clone(self.values.hasher())); 216 | 217 | for (input, param) in input_pairs { 218 | let input_src = graph.input_source(input); 219 | 220 | if let Some(domain) = self.domain(input_src) { 221 | let param = branch_graph.cast_node::(param).unwrap(); 222 | values 223 | .insert(param.output(), domain.clone()) 224 | .debug_unwrap_none(); 225 | } 226 | } 227 | 228 | values 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/passes/dataflow/pass.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{ 3 | Add, Bool, Byte, Eq, Gamma, Input, Int, Load, Mul, Neg, Neq, Not, Output, Rvsdg, Scan, 4 | Store, Sub, Theta, 5 | }, 6 | passes::{ 7 | dataflow::{domain::ByteSet, Dataflow}, 8 | utils::ChangeReport, 9 | Pass, 10 | }, 11 | values::{Cell, Ptr}, 12 | }; 13 | 14 | impl Pass for Dataflow { 15 | fn pass_name(&self) -> &'static str { 16 | "dataflow" 17 | } 18 | 19 | fn did_change(&self) -> bool { 20 | self.changes.did_change() 21 | } 22 | 23 | fn reset(&mut self) { 24 | self.values.clear(); 25 | self.changes.reset(); 26 | } 27 | 28 | fn report(&self) -> ChangeReport { 29 | self.changes.as_report() 30 | } 31 | 32 | fn visit_int(&mut self, _graph: &mut Rvsdg, int: Int, value: Ptr) { 33 | self.add_domain(int.value(), value); 34 | } 35 | 36 | fn visit_byte(&mut self, _graph: &mut Rvsdg, byte: Byte, value: Cell) { 37 | self.add_domain(byte.value(), value); 38 | } 39 | 40 | fn visit_bool(&mut self, _graph: &mut Rvsdg, bool: Bool, value: bool) { 41 | self.add_domain(bool.value(), value); 42 | } 43 | 44 | fn visit_add(&mut self, graph: &mut Rvsdg, add: Add) { 45 | self.compute_add(graph, add); 46 | } 47 | 48 | fn visit_sub(&mut self, graph: &mut Rvsdg, sub: Sub) { 49 | self.compute_sub(graph, sub); 50 | } 51 | 52 | fn visit_mul(&mut self, _graph: &mut Rvsdg, _mul: Mul) {} 53 | 54 | fn visit_not(&mut self, _graph: &mut Rvsdg, _not: Not) {} 55 | 56 | fn visit_neg(&mut self, _graph: &mut Rvsdg, _neg: Neg) {} 57 | 58 | fn visit_eq(&mut self, graph: &mut Rvsdg, eq: Eq) { 59 | self.compute_eq(graph, eq); 60 | } 61 | 62 | fn visit_neq(&mut self, graph: &mut Rvsdg, neq: Neq) { 63 | self.compute_neq(graph, neq); 64 | } 65 | 66 | fn visit_load(&mut self, graph: &mut Rvsdg, load: Load) { 67 | self.compute_load(graph, load); 68 | } 69 | 70 | fn visit_store(&mut self, graph: &mut Rvsdg, store: Store) { 71 | self.compute_store(graph, store); 72 | } 73 | 74 | fn visit_scan(&mut self, graph: &mut Rvsdg, scan: Scan) { 75 | self.compute_scan(graph, scan); 76 | } 77 | 78 | fn visit_input(&mut self, _graph: &mut Rvsdg, input: Input) { 79 | // Input calls are wildcards that can produce any value 80 | self.add_domain(input.output_value(), ByteSet::full()); 81 | } 82 | 83 | fn visit_output(&mut self, _graph: &mut Rvsdg, _output: Output) {} 84 | 85 | fn visit_theta(&mut self, graph: &mut Rvsdg, theta: Theta) { 86 | self.compute_theta(graph, theta); 87 | } 88 | 89 | fn visit_gamma(&mut self, graph: &mut Rvsdg, gamma: Gamma) { 90 | self.compute_gamma(graph, gamma); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/passes/dataflow/theta.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{Bool, NodeExt, Rvsdg, Theta}, 3 | passes::{ 4 | dataflow::{domain::Domain, Dataflow}, 5 | Pass, 6 | }, 7 | }; 8 | 9 | impl Dataflow { 10 | pub(super) fn compute_theta(&mut self, graph: &mut Rvsdg, mut theta: Theta) { 11 | let mut theta_changed = false; 12 | 13 | // TODO: Perform induction on loop variables where we can 14 | let inputs = self.collect_subgraph_inputs(graph, theta.body(), theta.input_pair_ids()); 15 | 16 | // The visitor we create for the theta's body cannot mutate, as we need to 17 | // iterate until a fixpoint is reached and *then* optimize based off of that fixpoint state 18 | // TODO: Propagate constraints into the visitor 19 | let mut visitor = self.clone_for_subscope(inputs).with_mutation(false); 20 | 21 | // We can try to do induction on top-level thetas 22 | // (thetas without any thetas in their bodies) 23 | if !theta.has_child_thetas() {} 24 | 25 | // Visit the theta's body once before starting the fixpoint 26 | visitor.visit_graph(theta.body_mut()); 27 | 28 | // Apply feedback parameters 29 | for (input, output) in theta.variant_inputs_loopback() { 30 | let output_source = theta.body().input_source(output.input()); 31 | if let Some(output_domain) = visitor.domain(output_source).cloned() { 32 | visitor.add_domain(input.output(), output_domain); 33 | } 34 | } 35 | 36 | // Check if the theta can possibly iterate more than once 37 | let condition_src = theta.body().input_source(theta.condition().input()); 38 | let can_iterate = visitor 39 | .domain(condition_src) 40 | .and_then(Domain::as_bool_set) 41 | // If it's possible for the theta's condition to be true, we must 42 | // iterate on it 43 | .map_or(true, |domain| domain.contains(true)); 44 | 45 | // Note that we don't apply any constraints to the theta's initial inputs, this 46 | // is because the theta's condition is checked *after* each iteration 47 | 48 | // If the theta can iterate any, run its body to fixpoint 49 | if can_iterate { 50 | let (mut accrued_tape, mut accrued_values, mut fixpoint_iters) = 51 | (visitor.tape.clone(), visitor.values.clone(), 1usize); 52 | 53 | loop { 54 | // Visit the theta's body 55 | visitor.visit_graph(theta.body_mut()); 56 | 57 | let mut did_change = false; 58 | 59 | // Union the accrued tape and this iteration's tape 60 | did_change |= accrued_tape.union(&visitor.tape); 61 | 62 | // Apply feedback parameters 63 | for (input, output) in theta.variant_inputs_loopback() { 64 | let output_source = theta.body().input_source(output.input()); 65 | if let Some(output_domain) = visitor.domain(output_source).cloned() { 66 | visitor.add_domain(input.output(), output_domain); 67 | } 68 | } 69 | 70 | // Union all of the values 71 | accrued_values = 72 | accrued_values.union_with(visitor.values.clone(), |mut accrued, mut domain| { 73 | did_change |= accrued.union_mut(&mut domain); 74 | accrued 75 | }); 76 | 77 | // Changes occurred, continue iterating 78 | if did_change { 79 | fixpoint_iters += 1; 80 | 81 | // Otherwise we've hit a fixpoint! 82 | } else { 83 | tracing::debug!( 84 | "theta {} hit fixpoint in {fixpoint_iters} iteration{}", 85 | theta.node(), 86 | if fixpoint_iters == 1 { "" } else { "s" }, 87 | ); 88 | break; 89 | } 90 | } 91 | 92 | // If the theta's body can't ever run more than once, 93 | // change its condition to false to indicate that. 94 | // Additionally we want to make sure the theta's condition 95 | // isn't already a boolean literal so that we don't cause 96 | // more churn than we have to 97 | } else if self.can_mutate && theta.body().cast_parent::<_, Bool>(condition_src).is_none() { 98 | let cond = theta.body_mut().bool(false); 99 | theta 100 | .body_mut() 101 | .rewire_dependents(condition_src, cond.value()); 102 | 103 | self.changes.inc::<"const-theta-cond">(); 104 | theta_changed = true; 105 | } 106 | 107 | if self.can_mutate { 108 | // Finally, after running the body to a fixpoint we can optimize the innards with 109 | // the fixpoint-ed values 110 | visitor.allow_mutation(); 111 | theta_changed |= visitor.visit_graph(theta.body_mut()); 112 | self.changes.combine(&visitor.changes); 113 | } 114 | 115 | // TODO: Pull out fixpoint-ed constraints from the body 116 | 117 | // Pull output parameters into the outer scope 118 | for (output, param) in theta.output_pairs() { 119 | let param_source = theta.body().input_source(param.input()); 120 | if let Some(domain) = visitor.domain(param_source).cloned() { 121 | self.add_domain(output, domain); 122 | } 123 | } 124 | 125 | // Use the body's fixpoint-ed tape as our tape 126 | self.tape = visitor.tape; 127 | 128 | // If we've changed anything, replace our node within the graph 129 | if theta_changed && self.can_mutate { 130 | graph.replace_node(theta.node(), theta); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/passes/equality.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{Eq, Gamma, InputPort, Neq, Node, NodeExt, Rvsdg, Sub, Theta}, 3 | passes::{ 4 | utils::{ChangeReport, Changes}, 5 | Pass, 6 | }, 7 | }; 8 | 9 | pub struct Equality { 10 | changes: Changes<2>, 11 | } 12 | 13 | impl Equality { 14 | pub fn new() -> Self { 15 | Self { 16 | changes: Changes::new(["eq-rewrites", "neq-rewrites"]), 17 | } 18 | } 19 | 20 | fn input_is_zero(&self, graph: &Rvsdg, input: InputPort) -> bool { 21 | match graph.input_source_node(input) { 22 | Node::Byte(_, byte) => byte.is_zero(), 23 | Node::Int(_, int) => int.is_zero(), 24 | _ => false, 25 | } 26 | } 27 | 28 | // We only want to perform these opts when there's exactly one consumer of the 29 | // input expression and that consumer is us, otherwise we duplicate expressions 30 | // that otherwise wouldn't need to be duplicated 31 | fn input_is_exclusive(&self, graph: &mut Rvsdg, input: InputPort) -> bool { 32 | graph.total_output_consumers(graph.input_source(input)) == 1 33 | } 34 | } 35 | 36 | impl Pass for Equality { 37 | fn pass_name(&self) -> &'static str { 38 | "equality" 39 | } 40 | 41 | fn did_change(&self) -> bool { 42 | self.changes.did_change() 43 | } 44 | 45 | fn reset(&mut self) { 46 | self.changes.reset(); 47 | } 48 | 49 | fn report(&self) -> ChangeReport { 50 | self.changes.as_report() 51 | } 52 | 53 | // Matches the motif of 54 | // ``` 55 | // y_minus_z = sub y, z 56 | // y_eq_z = eq y_minus_z, 0 57 | // ``` 58 | // and turns it into 59 | // ``` 60 | // y_eq_z = eq y, z 61 | // ``` 62 | // 63 | // ```py 64 | // s = z3.Solver(); 65 | // s.add((x - y) == 0, x != y) 66 | // print(s.check()) # unsat 67 | // ``` 68 | fn visit_eq(&mut self, graph: &mut Rvsdg, eq: Eq) { 69 | if self.input_is_zero(graph, eq.rhs()) && self.input_is_exclusive(graph, eq.lhs()) { 70 | if let Some(&sub) = graph.cast_input_source::(eq.lhs()) { 71 | let (lhs_src, rhs_src) = 72 | (graph.input_source(sub.lhs()), graph.input_source(sub.rhs())); 73 | 74 | let new_eq = graph.eq(lhs_src, rhs_src); 75 | graph.rewire_dependents(eq.value(), new_eq.value()); 76 | 77 | self.changes.inc::<"eq-rewrites">(); 78 | } 79 | } else if self.input_is_zero(graph, eq.lhs()) && self.input_is_exclusive(graph, eq.rhs()) { 80 | if let Some(&sub) = graph.cast_input_source::(eq.rhs()) { 81 | let (lhs_src, rhs_src) = 82 | (graph.input_source(sub.lhs()), graph.input_source(sub.rhs())); 83 | 84 | let new_eq = graph.eq(lhs_src, rhs_src); 85 | graph.rewire_dependents(eq.value(), new_eq.value()); 86 | 87 | self.changes.inc::<"eq-rewrites">(); 88 | } 89 | } 90 | } 91 | 92 | // Matches the motif of 93 | // ``` 94 | // y_minus_z = sub y, z 95 | // y_neq_z = neq y_minus_z, 0 96 | // ``` 97 | // and turns it into 98 | // ``` 99 | // y_neq_z = neq y, z 100 | // ``` 101 | // 102 | // ```py 103 | // s = z3.Solver(); 104 | // s.add((x - y) != 0, x == y) 105 | // print(s.check()) # unsat 106 | // ``` 107 | fn visit_neq(&mut self, graph: &mut Rvsdg, neq: Neq) { 108 | if self.input_is_zero(graph, neq.rhs()) && self.input_is_exclusive(graph, neq.lhs()) { 109 | if let Some(&sub) = graph.cast_input_source::(neq.lhs()) { 110 | let (lhs_src, rhs_src) = 111 | (graph.input_source(sub.lhs()), graph.input_source(sub.rhs())); 112 | 113 | let new_neq = graph.neq(lhs_src, rhs_src); 114 | graph.rewire_dependents(neq.value(), new_neq.value()); 115 | 116 | self.changes.inc::<"neq-rewrites">(); 117 | } 118 | } else if self.input_is_zero(graph, neq.lhs()) && self.input_is_exclusive(graph, neq.rhs()) 119 | { 120 | if let Some(&sub) = graph.cast_input_source::(neq.rhs()) { 121 | let (lhs_src, rhs_src) = 122 | (graph.input_source(sub.lhs()), graph.input_source(sub.rhs())); 123 | 124 | let new_eq = graph.eq(lhs_src, rhs_src); 125 | graph.rewire_dependents(neq.value(), new_eq.value()); 126 | 127 | self.changes.inc::<"neq-rewrites">(); 128 | } 129 | } 130 | } 131 | 132 | fn visit_gamma(&mut self, graph: &mut Rvsdg, mut gamma: Gamma) { 133 | if self.visit_graph(gamma.true_mut()) | self.visit_graph(gamma.false_mut()) { 134 | graph.replace_node(gamma.node(), gamma); 135 | } 136 | } 137 | 138 | fn visit_theta(&mut self, graph: &mut Rvsdg, mut theta: Theta) { 139 | if self.visit_graph(theta.body_mut()) { 140 | graph.replace_node(theta.node(), theta); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/passes/fuse_io.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{EdgeKind, Gamma, Input, NodeExt, Output, Rvsdg, Theta}, 3 | passes::{ 4 | utils::{ChangeReport, Changes}, 5 | Pass, 6 | }, 7 | }; 8 | 9 | pub struct FuseIO { 10 | changes: Changes<2>, 11 | } 12 | 13 | impl FuseIO { 14 | pub fn new() -> Self { 15 | Self { 16 | changes: Changes::new(["inputs-fused", "outputs-fused"]), 17 | } 18 | } 19 | } 20 | 21 | impl Pass for FuseIO { 22 | fn pass_name(&self) -> &'static str { 23 | "fuse-io" 24 | } 25 | 26 | fn did_change(&self) -> bool { 27 | self.changes.did_change() 28 | } 29 | 30 | fn reset(&mut self) { 31 | self.changes.reset(); 32 | } 33 | 34 | fn report(&self) -> ChangeReport { 35 | self.changes.as_report() 36 | } 37 | 38 | // FIXME: Would need to return some sort of aggregate type from the input 39 | // FIXME: Loads and stores interspersed between input calls stops them fusing, e.g. 40 | // ``` 41 | // a := input() 42 | // store y, z 43 | // b := input() 44 | // ``` 45 | // even though the actual effects are unrelated to each other, maybe this should 46 | // be addressed by separating the io and memory effect streams 47 | fn visit_input(&mut self, _graph: &mut Rvsdg, mut _input: Input) { 48 | // let mut changed = false; 49 | // // If the node is an output, fuse 'em 50 | // while let Some(consumer) = graph.cast_target::(input.output_effect()).cloned() { 51 | // // Add each value from the fused output to the current one 52 | // output.values_mut().reserve(consumer.values().len()); 53 | // for &value in consumer.values() { 54 | // let source = graph.input_source(value); 55 | // let port = graph.create_input_port(output.node(), EdgeKind::Value); 56 | // graph.add_value_edge(source, port); 57 | // output.values_mut().push(port); 58 | // } 59 | // 60 | // // Rewire the effects from the consumer to the current output 61 | // graph.rewire_dependents(consumer.output_effect(), output.output_effect()); 62 | // graph.remove_node(consumer.node()); 63 | // 64 | // changed = true; 65 | // self.changes.inc::<"outputs-fused">(); 66 | // } 67 | // 68 | // // If we fused any output calls together, replace the node 69 | // if changed { 70 | // graph.replace_node(output.node(), output); 71 | // } 72 | } 73 | 74 | // FIXME: Loads and stores interspersed between output calls stops them fusing, e.g. 75 | // ``` 76 | // output(x) 77 | // store y, z 78 | // output(x) 79 | // ``` 80 | // even though the actual effects are unrelated to each other, maybe this should 81 | // be addressed by separating the io and memory effect streams 82 | fn visit_output(&mut self, graph: &mut Rvsdg, mut output: Output) { 83 | let mut changed = false; 84 | // If the node is an output, fuse 'em 85 | while let Some(consumer) = graph.cast_target::(output.output_effect()).cloned() { 86 | // Add each value from the fused output to the current one 87 | output.values_mut().reserve(consumer.values().len()); 88 | for &value in consumer.values() { 89 | let source = graph.input_source(value); 90 | let port = graph.create_input_port(output.node(), EdgeKind::Value); 91 | graph.add_value_edge(source, port); 92 | output.values_mut().push(port); 93 | } 94 | 95 | // Rewire the effects from the consumer to the current output 96 | graph.rewire_dependents(consumer.output_effect(), output.output_effect()); 97 | graph.remove_node(consumer.node()); 98 | 99 | changed = true; 100 | self.changes.inc::<"outputs-fused">(); 101 | } 102 | 103 | // If we fused any output calls together, replace the node 104 | if changed { 105 | graph.replace_node(output.node(), output); 106 | } 107 | } 108 | 109 | fn visit_gamma(&mut self, graph: &mut Rvsdg, mut gamma: Gamma) { 110 | let mut changed = false; 111 | changed |= self.visit_graph(gamma.true_mut()); 112 | changed |= self.visit_graph(gamma.false_mut()); 113 | 114 | if changed { 115 | graph.replace_node(gamma.node(), gamma); 116 | } 117 | } 118 | 119 | fn visit_theta(&mut self, graph: &mut Rvsdg, mut theta: Theta) { 120 | if self.visit_graph(theta.body_mut()) { 121 | graph.replace_node(theta.node(), theta); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/passes/utils/binary_op.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{Add, Eq, InputPort, Mul, Neq, NodeExt, OutputPort, Rvsdg, Sub}, 3 | ir::Const, 4 | }; 5 | 6 | pub trait BinaryOp: NodeExt { 7 | fn name() -> &'static str; 8 | 9 | fn symbol() -> &'static str; 10 | 11 | fn make_in_graph(graph: &mut Rvsdg, lhs: OutputPort, rhs: OutputPort) -> Self; 12 | 13 | fn apply(lhs: Const, rhs: Const) -> Const; 14 | 15 | fn lhs(&self) -> InputPort; 16 | 17 | fn rhs(&self) -> InputPort; 18 | 19 | fn value(&self) -> OutputPort; 20 | 21 | fn is_associative() -> bool; 22 | 23 | fn is_commutative() -> bool; 24 | } 25 | 26 | impl BinaryOp for Add { 27 | fn name() -> &'static str { 28 | "add" 29 | } 30 | 31 | fn symbol() -> &'static str { 32 | "+" 33 | } 34 | 35 | fn make_in_graph(graph: &mut Rvsdg, lhs: OutputPort, rhs: OutputPort) -> Self { 36 | graph.add(lhs, rhs) 37 | } 38 | 39 | fn apply(lhs: Const, rhs: Const) -> Const { 40 | lhs + rhs 41 | } 42 | 43 | fn lhs(&self) -> InputPort { 44 | Add::lhs(self) 45 | } 46 | 47 | fn rhs(&self) -> InputPort { 48 | Add::rhs(self) 49 | } 50 | 51 | fn value(&self) -> OutputPort { 52 | Add::value(self) 53 | } 54 | 55 | fn is_associative() -> bool { 56 | true 57 | } 58 | 59 | fn is_commutative() -> bool { 60 | true 61 | } 62 | } 63 | 64 | impl BinaryOp for Sub { 65 | fn name() -> &'static str { 66 | "sub" 67 | } 68 | 69 | fn symbol() -> &'static str { 70 | "-" 71 | } 72 | 73 | fn make_in_graph(graph: &mut Rvsdg, lhs: OutputPort, rhs: OutputPort) -> Self { 74 | graph.sub(lhs, rhs) 75 | } 76 | 77 | fn apply(lhs: Const, rhs: Const) -> Const { 78 | lhs - rhs 79 | } 80 | 81 | fn lhs(&self) -> InputPort { 82 | Sub::lhs(self) 83 | } 84 | 85 | fn rhs(&self) -> InputPort { 86 | Sub::rhs(self) 87 | } 88 | 89 | fn value(&self) -> OutputPort { 90 | Sub::value(self) 91 | } 92 | 93 | fn is_associative() -> bool { 94 | false 95 | } 96 | 97 | fn is_commutative() -> bool { 98 | false 99 | } 100 | } 101 | 102 | impl BinaryOp for Mul { 103 | fn name() -> &'static str { 104 | "mul" 105 | } 106 | 107 | fn symbol() -> &'static str { 108 | "*" 109 | } 110 | 111 | fn make_in_graph(graph: &mut Rvsdg, lhs: OutputPort, rhs: OutputPort) -> Self { 112 | graph.mul(lhs, rhs) 113 | } 114 | 115 | fn apply(lhs: Const, rhs: Const) -> Const { 116 | lhs * rhs 117 | } 118 | 119 | fn lhs(&self) -> InputPort { 120 | Mul::lhs(self) 121 | } 122 | 123 | fn rhs(&self) -> InputPort { 124 | Mul::rhs(self) 125 | } 126 | 127 | fn value(&self) -> OutputPort { 128 | Mul::value(self) 129 | } 130 | 131 | fn is_associative() -> bool { 132 | true 133 | } 134 | 135 | fn is_commutative() -> bool { 136 | true 137 | } 138 | } 139 | 140 | impl BinaryOp for Eq { 141 | fn name() -> &'static str { 142 | "eq" 143 | } 144 | 145 | fn symbol() -> &'static str { 146 | "==" 147 | } 148 | 149 | fn make_in_graph(graph: &mut Rvsdg, lhs: OutputPort, rhs: OutputPort) -> Self { 150 | graph.eq(lhs, rhs) 151 | } 152 | 153 | fn apply(lhs: Const, rhs: Const) -> Const { 154 | Const::Bool(lhs == rhs) 155 | } 156 | 157 | fn lhs(&self) -> InputPort { 158 | Eq::lhs(self) 159 | } 160 | 161 | fn rhs(&self) -> InputPort { 162 | Eq::rhs(self) 163 | } 164 | 165 | fn value(&self) -> OutputPort { 166 | Eq::value(self) 167 | } 168 | 169 | fn is_associative() -> bool { 170 | false 171 | } 172 | 173 | fn is_commutative() -> bool { 174 | true 175 | } 176 | } 177 | 178 | impl BinaryOp for Neq { 179 | fn name() -> &'static str { 180 | "neq" 181 | } 182 | 183 | fn symbol() -> &'static str { 184 | "!=" 185 | } 186 | 187 | fn make_in_graph(graph: &mut Rvsdg, lhs: OutputPort, rhs: OutputPort) -> Self { 188 | graph.neq(lhs, rhs) 189 | } 190 | 191 | fn apply(lhs: Const, rhs: Const) -> Const { 192 | Const::Bool(lhs != rhs) 193 | } 194 | 195 | fn lhs(&self) -> InputPort { 196 | Neq::lhs(self) 197 | } 198 | 199 | fn rhs(&self) -> InputPort { 200 | Neq::rhs(self) 201 | } 202 | 203 | fn value(&self) -> OutputPort { 204 | Neq::value(self) 205 | } 206 | 207 | fn is_associative() -> bool { 208 | false 209 | } 210 | 211 | fn is_commutative() -> bool { 212 | true 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/passes/utils/changes.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{AssertNone, HashMap}; 2 | 3 | pub type ChangeReport = HashMap<&'static str, usize>; 4 | 5 | #[derive(Debug)] 6 | pub struct Changes { 7 | changes: [(&'static str, usize); LEN], 8 | has_changed: bool, 9 | } 10 | 11 | impl Changes { 12 | #[must_use] 13 | pub fn new(changes: [&'static str; LEN]) -> Self { 14 | Self { 15 | changes: changes.map(|name| (name, 0)), 16 | has_changed: false, 17 | } 18 | } 19 | 20 | pub fn did_change(&self) -> bool { 21 | self.has_changed 22 | } 23 | 24 | pub fn reset(&mut self) { 25 | self.has_changed = false; 26 | } 27 | 28 | pub fn combine(&mut self, other: &Self) { 29 | self.has_changed |= other.has_changed; 30 | 31 | debug_assert_eq!( 32 | self.changes.map(|(name, _)| name), 33 | other.changes.map(|(name, _)| name), 34 | "tried to combine unequal changes", 35 | ); 36 | 37 | // Sum up all changes 38 | for ((_, total), &(_, additional)) in self.changes.iter_mut().zip(&other.changes) { 39 | *total += additional; 40 | } 41 | } 42 | 43 | pub fn inc(&mut self) { 44 | self.has_changed = true; 45 | for (name, total) in &mut self.changes { 46 | if *name == NAME { 47 | *total += 1; 48 | break; 49 | } 50 | } 51 | } 52 | 53 | pub fn as_report(&self) -> ChangeReport { 54 | let mut report = HashMap::with_capacity_and_hasher(LEN, Default::default()); 55 | for (name, total) in self.changes { 56 | report.insert(name, total).debug_unwrap_none(); 57 | } 58 | 59 | report 60 | } 61 | } 62 | 63 | /* An attempt to make the names checked at compile time, currently ICEs 64 | 65 | mod const_checked { 66 | use crate::{ 67 | passes::utils::ChangeReport, 68 | utils::{AssertNone, HashMap}, 69 | }; 70 | 71 | pub struct Changes 72 | where 73 | [(); CHANGES.len()]:, 74 | { 75 | changes: [usize; CHANGES.len()], 76 | has_changed: bool, 77 | } 78 | 79 | impl Changes 80 | where 81 | [(); CHANGES.len()]:, 82 | { 83 | pub const fn new() -> Self { 84 | Self { 85 | changes: [0; CHANGES.len()], 86 | has_changed: false, 87 | } 88 | } 89 | 90 | pub const fn did_change(&self) -> bool { 91 | self.has_changed 92 | } 93 | 94 | pub fn reset(&mut self) { 95 | self.has_changed = false; 96 | } 97 | 98 | pub fn combine(&mut self, other: &Self) { 99 | self.has_changed |= other.has_changed; 100 | 101 | // Sum up all changes 102 | self.changes 103 | .iter_mut() 104 | .zip(&other.changes) 105 | .for_each(|(total, &additional)| *total += additional); 106 | } 107 | 108 | pub fn inc(&mut self) 109 | where 110 | Assert<{ contains(CHANGES, NAME) }>: True, 111 | { 112 | self.has_changed = true; 113 | 114 | let index = index_of(CHANGES, NAME); 115 | self.changes[index] += 1; 116 | } 117 | 118 | pub fn as_report(&self) -> ChangeReport { 119 | let mut report = HashMap::with_capacity_and_hasher(CHANGES.len(), Default::default()); 120 | for (idx, &total) in self.changes.iter().enumerate() { 121 | report.insert(CHANGES[idx], total).debug_unwrap_none(); 122 | } 123 | 124 | report 125 | } 126 | } 127 | 128 | pub struct Assert {} 129 | 130 | pub trait True {} 131 | 132 | impl True for Assert {} 133 | 134 | pub const fn contains(changes: &[&str], name: &str) -> bool { 135 | let mut idx = 0; 136 | while idx < changes.len() { 137 | if str_eq(changes[idx], name) { 138 | return true; 139 | } 140 | 141 | idx += 1; 142 | } 143 | 144 | false 145 | } 146 | 147 | const fn index_of(changes: &[&str], name: &str) -> usize { 148 | let mut idx = 0; 149 | while idx < changes.len() { 150 | if str_eq(changes[idx], name) { 151 | return idx; 152 | } 153 | 154 | idx += 1; 155 | } 156 | 157 | panic!() 158 | } 159 | 160 | const fn str_eq(lhs: &str, rhs: &str) -> bool { 161 | let (lhs, rhs) = (lhs.as_bytes(), rhs.as_bytes()); 162 | 163 | if lhs.len() != rhs.len() { 164 | return false; 165 | } 166 | 167 | let mut idx = 0; 168 | while idx < lhs.len() { 169 | if lhs[idx] != rhs[idx] { 170 | return false; 171 | } 172 | 173 | idx += 1; 174 | } 175 | 176 | true 177 | } 178 | } 179 | */ 180 | -------------------------------------------------------------------------------- /src/passes/utils/constant_store.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{Gamma, InputParam, OutputPort, Rvsdg, Theta}, 3 | ir::Const, 4 | utils::HashMap, 5 | values::{Cell, Ptr}, 6 | }; 7 | use std::{cell::RefCell, mem::take, thread}; 8 | 9 | thread_local! { 10 | // FIXME: https://github.com/rust-lang/rust-clippy/issues/8493 11 | #[allow(clippy::declare_interior_mutable_const)] 12 | static VALUE_BUFFERS: RefCell>> 13 | = const { RefCell::new(Vec::new()) }; 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct ConstantStore { 18 | values: HashMap, 19 | tape_len: u16, 20 | } 21 | 22 | impl ConstantStore { 23 | pub fn new(tape_len: u16) -> Self { 24 | let values = VALUE_BUFFERS 25 | .with_borrow_mut(|buffers| buffers.pop()) 26 | .unwrap_or_default(); 27 | debug_assert!(values.is_empty()); 28 | 29 | Self { values, tape_len } 30 | } 31 | 32 | pub fn clear(&mut self) { 33 | self.values.clear(); 34 | } 35 | 36 | pub fn add(&mut self, source: OutputPort, constant: C) 37 | where 38 | C: Into, 39 | { 40 | let constant = constant.into(); 41 | let replaced = self.values.insert(source, constant); 42 | debug_assert!(replaced == None || replaced == Some(constant)); 43 | } 44 | 45 | pub fn remove(&mut self, source: OutputPort) { 46 | self.values.remove(&source); 47 | } 48 | 49 | pub fn get(&self, source: OutputPort) -> Option { 50 | self.values.get(&source).copied() 51 | } 52 | 53 | pub fn ptr(&self, source: OutputPort) -> Option { 54 | self.values 55 | .get(&source) 56 | .map(|value| value.into_ptr(self.tape_len)) 57 | } 58 | 59 | pub fn ptr_is_zero(&self, source: OutputPort) -> bool { 60 | self.ptr(source).map_or(false, Ptr::is_zero) 61 | } 62 | 63 | pub fn cell(&self, source: OutputPort) -> Option { 64 | self.values.get(&source).copied().map(Const::into_cell) 65 | } 66 | 67 | pub fn bool(&self, source: OutputPort) -> Option { 68 | self.values.get(&source).and_then(Const::as_bool) 69 | } 70 | 71 | pub fn theta_invariant_inputs_into( 72 | &self, 73 | theta: &Theta, 74 | graph: &Rvsdg, 75 | destination: &mut Self, 76 | ) { 77 | for (input, param) in theta.invariant_input_pairs() { 78 | if let Some(constant) = self.get(graph.input_source(input)) { 79 | destination.add(param.output(), constant); 80 | } 81 | } 82 | } 83 | 84 | #[track_caller] 85 | pub fn gamma_inputs_into( 86 | &mut self, 87 | gamma: &Gamma, 88 | graph: &Rvsdg, 89 | true_branch: &mut Self, 90 | false_brach: &mut Self, 91 | ) { 92 | for (&input, &[true_param, false_param]) in gamma.inputs().iter().zip(gamma.input_params()) 93 | { 94 | let source = graph.input_source(input); 95 | 96 | if let Some(constant) = self.get(source) { 97 | let true_param = gamma.true_branch().to_node::(true_param); 98 | true_branch.add(true_param.output(), constant); 99 | 100 | let false_param = gamma.false_branch().to_node::(false_param); 101 | false_brach.add(false_param.output(), constant); 102 | } 103 | } 104 | } 105 | 106 | /// Get the constant store's tape len 107 | pub fn tape_len(&self) -> u16 { 108 | self.tape_len 109 | } 110 | 111 | pub fn reserve(&mut self, additional: usize) { 112 | self.values.reserve(additional); 113 | } 114 | } 115 | 116 | impl Clone for ConstantStore { 117 | fn clone(&self) -> Self { 118 | let values = 119 | if let Some(mut values) = VALUE_BUFFERS.with_borrow_mut(|buffers| buffers.pop()) { 120 | values.clone_from(&self.values); 121 | values 122 | } else { 123 | self.values.clone() 124 | }; 125 | 126 | Self { 127 | values, 128 | tape_len: self.tape_len, 129 | } 130 | } 131 | 132 | fn clone_from(&mut self, source: &Self) { 133 | self.values.clone_from(&source.values); 134 | self.tape_len = source.tape_len; 135 | } 136 | } 137 | 138 | impl Drop for ConstantStore { 139 | fn drop(&mut self) { 140 | if !thread::panicking() && self.values.capacity() != 0 { 141 | let mut values = take(&mut self.values); 142 | values.clear(); 143 | 144 | VALUE_BUFFERS.with_borrow_mut(|buffers| buffers.push(values)); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/passes/utils/memory_tape.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::OutputPort, 3 | values::{Cell, Ptr}, 4 | }; 5 | use std::{ 6 | cell::RefCell, 7 | mem::take, 8 | ops::{Index, IndexMut}, 9 | thread, 10 | }; 11 | 12 | thread_local! { 13 | // FIXME: https://github.com/rust-lang/rust-clippy/issues/8493 14 | #[allow(clippy::declare_interior_mutable_const)] 15 | static TAPE_BUFFERS: RefCell>> 16 | = const { RefCell::new(Vec::new()) }; 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct MemoryTape { 21 | tape: Vec, 22 | } 23 | 24 | impl MemoryTape { 25 | pub fn zeroed(tape_len: u16) -> Self { 26 | Self::new_with(tape_len, MemoryCell::Cell(Cell::zero())) 27 | } 28 | 29 | pub fn unknown(tape_len: u16) -> Self { 30 | Self::new_with(tape_len, MemoryCell::Unknown) 31 | } 32 | 33 | fn new_with(tape_len: u16, value: MemoryCell) -> Self { 34 | let tape = TAPE_BUFFERS 35 | .with_borrow_mut(|buffers| { 36 | let mut tape = buffers.pop(); 37 | if let Some(tape) = tape.as_mut() { 38 | debug_assert!(tape.is_empty()); 39 | tape.resize(tape_len as usize, value) 40 | } 41 | 42 | tape 43 | }) 44 | .unwrap_or_else(|| vec![value; tape_len as usize]); 45 | 46 | Self { tape } 47 | } 48 | 49 | pub fn zero(&mut self) { 50 | self.tape.fill(MemoryCell::Cell(Cell::zero())); 51 | } 52 | 53 | pub fn mystify(&mut self) { 54 | self.tape.fill(MemoryCell::Unknown); 55 | } 56 | 57 | pub fn mapped(&self, mut map: F) -> Self 58 | where 59 | F: FnMut(MemoryCell) -> MemoryCell, 60 | { 61 | let mut new = self.clone(); 62 | new.tape.iter_mut().for_each(|value| *value = map(*value)); 63 | 64 | new 65 | } 66 | 67 | pub fn tape_len(&self) -> u16 { 68 | self.tape.len() as u16 69 | } 70 | } 71 | 72 | impl Index for MemoryTape { 73 | type Output = MemoryCell; 74 | 75 | #[inline] 76 | fn index(&self, index: Ptr) -> &Self::Output { 77 | debug_assert_eq!(index.tape_len() as usize, self.tape.len()); 78 | &self.tape[index.value() as usize] 79 | } 80 | } 81 | 82 | impl IndexMut for MemoryTape { 83 | #[inline] 84 | fn index_mut(&mut self, index: Ptr) -> &mut Self::Output { 85 | debug_assert_eq!(index.tape_len() as usize, self.tape.len()); 86 | &mut self.tape[index.value() as usize] 87 | } 88 | } 89 | 90 | impl Clone for MemoryTape { 91 | fn clone(&self) -> Self { 92 | let tape = if let Some(mut tape) = TAPE_BUFFERS.with_borrow_mut(|buffers| buffers.pop()) { 93 | tape.clone_from(&self.tape); 94 | tape 95 | } else { 96 | self.tape.clone() 97 | }; 98 | 99 | Self { tape } 100 | } 101 | 102 | fn clone_from(&mut self, source: &Self) { 103 | self.tape.clone_from(&source.tape); 104 | } 105 | } 106 | 107 | impl Drop for MemoryTape { 108 | fn drop(&mut self) { 109 | if !thread::panicking() { 110 | let mut tape = take(&mut self.tape); 111 | tape.clear(); 112 | 113 | TAPE_BUFFERS.with_borrow_mut(|buffers| buffers.push(tape)); 114 | } 115 | } 116 | } 117 | 118 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 119 | pub enum MemoryCell { 120 | Unknown, 121 | Cell(Cell), 122 | Port(OutputPort), 123 | } 124 | 125 | impl MemoryCell { 126 | pub const fn as_cell(&self) -> Option { 127 | if let Self::Cell(cell) = *self { 128 | Some(cell) 129 | } else { 130 | None 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/passes/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod binary_op; 2 | mod changes; 3 | mod constant_store; 4 | mod memory_tape; 5 | mod unary_op; 6 | 7 | pub use binary_op::BinaryOp; 8 | pub use changes::{ChangeReport, Changes}; 9 | pub use constant_store::ConstantStore; 10 | pub use memory_tape::{MemoryCell, MemoryTape}; 11 | pub use unary_op::UnaryOp; 12 | -------------------------------------------------------------------------------- /src/passes/utils/unary_op.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | graph::{InputPort, Neg, NodeExt, Not, OutputPort, Rvsdg}, 3 | ir::Const, 4 | }; 5 | 6 | pub trait UnaryOp: NodeExt { 7 | fn name() -> &'static str; 8 | 9 | fn symbol() -> &'static str; 10 | 11 | fn make_in_graph(graph: &mut Rvsdg, input: OutputPort) -> Self; 12 | 13 | fn apply(input: Const) -> Const; 14 | 15 | fn input(&self) -> InputPort; 16 | 17 | fn value(&self) -> OutputPort; 18 | } 19 | 20 | impl UnaryOp for Neg { 21 | fn name() -> &'static str { 22 | "neg" 23 | } 24 | 25 | fn symbol() -> &'static str { 26 | "-" 27 | } 28 | 29 | fn make_in_graph(graph: &mut Rvsdg, input: OutputPort) -> Self { 30 | graph.neg(input) 31 | } 32 | 33 | fn apply(input: Const) -> Const { 34 | -input 35 | } 36 | 37 | fn input(&self) -> InputPort { 38 | self.input() 39 | } 40 | 41 | fn value(&self) -> OutputPort { 42 | self.value() 43 | } 44 | } 45 | 46 | impl UnaryOp for Not { 47 | fn name() -> &'static str { 48 | "not" 49 | } 50 | 51 | fn symbol() -> &'static str { 52 | "!" 53 | } 54 | 55 | fn make_in_graph(graph: &mut Rvsdg, input: OutputPort) -> Self { 56 | graph.not(input) 57 | } 58 | 59 | fn apply(input: Const) -> Const { 60 | !input 61 | } 62 | 63 | fn input(&self) -> InputPort { 64 | self.input() 65 | } 66 | 67 | fn value(&self) -> OutputPort { 68 | self.value() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/patterns/mod.rs: -------------------------------------------------------------------------------- 1 | mod sexpr; 2 | 3 | use crate::{ 4 | graph::{Node, NodeExt, NodeId, Rvsdg}, 5 | utils::HashMap, 6 | }; 7 | use sexpr::Sexpr; 8 | 9 | #[test] 10 | fn sexpr_test() { 11 | use crate::ir::{IrBuilder, Pretty, PrettyConfig}; 12 | 13 | let rewrite = dbg!(Rewrite::new("add-zero", "(add ?a 0)", "?a")); 14 | 15 | let mut graph = Rvsdg::new(); 16 | let lhs = graph.byte(1u8).value(); 17 | let rhs = graph.byte(0u8).value(); 18 | let add = graph.add(lhs, rhs).into(); 19 | 20 | println!( 21 | "{}", 22 | IrBuilder::new(true) 23 | .translate(&graph) 24 | .pretty_print(PrettyConfig::minimal()), 25 | ); 26 | rewrite.apply(&mut graph, &add); 27 | 28 | println!( 29 | "{}", 30 | IrBuilder::new(true) 31 | .translate(&graph) 32 | .pretty_print(PrettyConfig::minimal()), 33 | ); 34 | } 35 | 36 | #[derive(Debug)] 37 | pub struct Rewrite<'a> { 38 | name: &'a str, 39 | pattern: Pattern<'a>, 40 | output: Pattern<'a>, 41 | } 42 | 43 | impl<'a> Rewrite<'a> { 44 | pub fn new(name: &'a str, pattern: &'a str, output: &'a str) -> Self { 45 | let pattern = Pattern::from_sexpr(&Sexpr::parse(pattern)); 46 | let output = Pattern::from_sexpr(&Sexpr::parse(output)); 47 | 48 | Self { 49 | name, 50 | pattern, 51 | output, 52 | } 53 | } 54 | 55 | pub fn apply(&self, graph: &mut Rvsdg, node: &Node) { 56 | let mut bindings = HashMap::default(); 57 | if match_pattern(&self.pattern, graph, node, &mut bindings) { 58 | println!("{:?}", bindings); 59 | } 60 | } 61 | } 62 | 63 | fn match_pattern<'a>( 64 | pattern: &Pattern<'a>, 65 | graph: &Rvsdg, 66 | node: &Node, 67 | bindings: &mut HashMap<&'a str, NodeId>, 68 | ) -> bool { 69 | match pattern { 70 | &Pattern::Binding(binding) => { 71 | bindings.insert(binding, node.node()); 72 | true 73 | } 74 | 75 | Pattern::Add(lhs_pat, rhs_pat) => { 76 | if let Some(add) = node.as_add() { 77 | let (lhs, rhs) = (add.lhs(), add.rhs()); 78 | 79 | let lhs_node = graph.input_source_node(lhs); 80 | if !match_pattern(lhs_pat, graph, lhs_node, bindings) { 81 | return false; 82 | } 83 | 84 | let rhs_node = graph.input_source_node(rhs); 85 | match_pattern(rhs_pat, graph, rhs_node, bindings) 86 | } else { 87 | false 88 | } 89 | } 90 | 91 | Pattern::ConstAdd(_, _) => todo!(), 92 | 93 | &Pattern::Int(expected) => node 94 | .as_int_value() 95 | .map(|ptr| ptr.value() == expected) 96 | .or_else(|| { 97 | node.as_byte_value() 98 | // TODO: Convert to Ptr to wrap byte into pointer space 99 | .map(|byte| byte.into_inner() as u16 == expected) 100 | }) 101 | .unwrap_or(false), 102 | 103 | &Pattern::Bool(expected) => node 104 | .as_bool_value() 105 | .map(|bool| bool == expected) 106 | .unwrap_or(false), 107 | 108 | Pattern::WildCard => true, 109 | } 110 | } 111 | 112 | #[derive(Debug)] 113 | pub enum Pattern<'a> { 114 | Binding(&'a str), 115 | Add(Box, Box), 116 | ConstAdd(Box, Box), 117 | Int(u16), 118 | Bool(bool), 119 | WildCard, 120 | } 121 | 122 | impl<'a> Pattern<'a> { 123 | pub(crate) fn from_sexpr(sexpr: &Sexpr<'a>) -> Self { 124 | match sexpr { 125 | &Sexpr::Atom(atom) => { 126 | if atom == "_" { 127 | Self::WildCard 128 | } else if atom.starts_with('?') { 129 | Self::Binding(atom) 130 | } else if matches!(atom, "true" | "false") { 131 | Self::Bool(atom.parse().unwrap()) 132 | } else { 133 | Self::Int(atom.parse().unwrap()) 134 | } 135 | } 136 | 137 | Sexpr::Cons(cons) => match cons[0].to_atom() { 138 | "add" => Self::Add( 139 | Box::new(Self::from_sexpr(&cons[1])), 140 | Box::new(Self::from_sexpr(&cons[2])), 141 | ), 142 | 143 | "+" => Self::ConstAdd( 144 | Box::new(Self::from_sexpr(&cons[1])), 145 | Box::new(Self::from_sexpr(&cons[2])), 146 | ), 147 | 148 | pat => panic!("unrecognized pattern: {:?}", pat), 149 | }, 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/patterns/sexpr.rs: -------------------------------------------------------------------------------- 1 | use std::{iter::Peekable, str::CharIndices}; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub enum Sexpr<'a> { 5 | Atom(&'a str), 6 | Cons(Vec), 7 | } 8 | 9 | impl<'a> Sexpr<'a> { 10 | pub fn parse(source: &'a str) -> Self { 11 | parse_sexpr(source) 12 | } 13 | 14 | #[track_caller] 15 | pub fn to_atom(&self) -> &'a str { 16 | if let Self::Atom(atom) = self { 17 | atom 18 | } else { 19 | panic!("attempted to get an atom out of {:?}", self); 20 | } 21 | } 22 | 23 | #[track_caller] 24 | pub fn to_cons(&self) -> &[Self] { 25 | if let Self::Cons(cons) = self { 26 | cons 27 | } else { 28 | panic!("attempted to get a cons out of {:?}", self); 29 | } 30 | } 31 | } 32 | 33 | fn parse_sexpr(source: &str) -> Sexpr<'_> { 34 | let mut chars = source.char_indices().peekable(); 35 | 36 | parse_sexpr_inner(source, &mut chars) 37 | } 38 | 39 | fn parse_sexpr_inner<'a>(source: &'a str, chars: &mut Peekable) -> Sexpr<'a> { 40 | while let Some((idx, char)) = chars.next() { 41 | match char { 42 | '(' => { 43 | // Comments 44 | if chars.peek().unwrap().1 == '*' { 45 | chars.next().unwrap(); 46 | 47 | while chars.next().unwrap().1 != '*' && chars.peek().unwrap().1 != ')' {} 48 | assert_eq!(chars.next().unwrap().1, ')'); 49 | 50 | // Cons 51 | } else { 52 | let mut cons = Vec::new(); 53 | while chars.peek().unwrap().1 != ')' { 54 | cons.push(parse_sexpr_inner(source, chars)); 55 | 56 | while chars.peek().unwrap().1.is_whitespace() { 57 | chars.next().unwrap(); 58 | } 59 | } 60 | 61 | assert_eq!(chars.next().unwrap().1, ')'); 62 | 63 | return Sexpr::Cons(cons); 64 | } 65 | } 66 | 67 | c if is_ident(c) => { 68 | let start = idx; 69 | let mut end = idx; 70 | 71 | let mut current = *chars.peek().unwrap(); 72 | while is_ident(current.1) { 73 | end = current.0; 74 | chars.next().unwrap(); 75 | if let Some(&peek) = chars.peek() { 76 | current = peek; 77 | } else { 78 | break; 79 | } 80 | } 81 | 82 | return Sexpr::Atom(&source[start..=end]); 83 | } 84 | 85 | c if c.is_whitespace() => {} 86 | 87 | unknown => panic!("unknown char: {}", unknown), 88 | } 89 | } 90 | 91 | Sexpr::Cons(Vec::new()) 92 | } 93 | 94 | fn is_ident(c: char) -> bool { 95 | c.is_alphanumeric() || matches!(c, '_' | '?' | '+' | '-' | '*' | '/') 96 | } 97 | 98 | #[test] 99 | fn sexpr_smoke_test() { 100 | let source = " 101 | (* Comment *) 102 | (cons (cons nil)) 103 | "; 104 | 105 | println!("{:?}", parse_sexpr(source)); 106 | } 107 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use crate::{ 4 | args::Settings, 5 | driver, 6 | graph::Rvsdg, 7 | ir::PrettyConfig, 8 | jit::Jit, 9 | utils::{compile_brainfuck_into, ByteVec}, 10 | values::Ptr, 11 | }; 12 | use std::num::NonZeroU16; 13 | 14 | test_opts! { 15 | beer, 16 | use_default_passes = true, 17 | output = include_bytes!("corpus/beer.out"), 18 | |graph, effect, tape_len| { 19 | let ptr = graph.int(Ptr::zero(tape_len)).value(); 20 | let (_, effect) = compile_brainfuck_into( 21 | include_str!("../../examples/beer.bf"), 22 | graph, 23 | ptr, 24 | effect, 25 | ); 26 | 27 | effect 28 | }, 29 | } 30 | 31 | #[test] 32 | fn rot13() { 33 | crate::utils::set_logger(); 34 | 35 | let tape_len = 30000; 36 | let input = b"~mlk zyx"; 37 | let expected_output = b"~zyx mlk"; 38 | 39 | let settings = Settings { 40 | tape_len: NonZeroU16::new(tape_len).unwrap(), 41 | tape_wrapping_ub: false, 42 | cell_wrapping_ub: false, 43 | ..Default::default() 44 | }; 45 | 46 | let mut graph = Rvsdg::new(); 47 | { 48 | let start = graph.start(); 49 | let ptr = graph.int(Ptr::zero(tape_len)).value(); 50 | let (_, effect) = compile_brainfuck_into( 51 | include_str!("../../examples/rot13.bf"), 52 | &mut graph, 53 | ptr, 54 | start.effect(), 55 | ); 56 | let _end = graph.end(effect); 57 | } 58 | 59 | driver::run_opt_passes(&mut graph, usize::MAX, &settings.pass_config(), None); 60 | 61 | let (program, ir) = 62 | driver::sequentialize_graph(&settings, &graph, None, PrettyConfig::minimal()).unwrap(); 63 | println!("{ir}"); 64 | 65 | let (mut input, mut output, mut tape) = ( 66 | ByteVec::from(input), 67 | Vec::with_capacity(128), 68 | vec![0x00; tape_len as usize], 69 | ); 70 | let jit = Jit::new(&settings, None, None) 71 | .unwrap() 72 | .compile(&program) 73 | .unwrap(); 74 | 75 | // Safety: Decidedly not safe in the slightest 76 | unsafe { 77 | jit.execute_into(&mut tape, &mut input, &mut output) 78 | .unwrap(); 79 | } 80 | 81 | assert_eq!(output, expected_output); 82 | } 83 | 84 | test_opts! { 85 | h, 86 | use_default_passes = true, 87 | output = b"H", 88 | |graph, effect, tape_len| { 89 | let ptr = graph.int(Ptr::zero(tape_len)).value(); 90 | let (_, effect) = compile_brainfuck_into( 91 | include_str!("../../examples/h.bf"), 92 | graph, 93 | ptr, 94 | effect, 95 | ); 96 | 97 | effect 98 | }, 99 | } 100 | 101 | test_opts! { 102 | report_30k, 103 | use_default_passes = true, 104 | tape_len = 30000, 105 | output = b"#", 106 | |graph, effect, tape_len| { 107 | let ptr = graph.int(Ptr::zero(tape_len)).value(); 108 | let (_, effect) = compile_brainfuck_into( 109 | include_str!("../../examples/report_30k.bf"), 110 | graph, 111 | ptr, 112 | effect, 113 | ); 114 | 115 | effect 116 | }, 117 | } 118 | -------------------------------------------------------------------------------- /src/values/mod.rs: -------------------------------------------------------------------------------- 1 | mod cell; 2 | mod ptr; 3 | 4 | pub use cell::Cell; 5 | pub use ptr::Ptr; 6 | --------------------------------------------------------------------------------