├── .gitignore ├── .gitmodules ├── Cargo.toml ├── Conformance ├── Cargo.toml └── tests │ ├── common │ ├── loader.rs │ ├── mod.rs │ └── runner.rs │ ├── harness.luau │ └── luau.rs ├── Control Flow ├── Builder │ ├── Cargo.toml │ └── src │ │ ├── basic_block_builder.rs │ │ ├── code_builder.rs │ │ ├── lib.rs │ │ ├── sorter.rs │ │ ├── stack_builder.rs │ │ └── types.rs ├── Graph │ ├── Cargo.toml │ └── src │ │ ├── basic_block.rs │ │ ├── dot.rs │ │ ├── instruction.rs │ │ └── lib.rs ├── Liveness │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── locals.rs │ │ └── references.rs └── Structurer │ ├── Cargo.toml │ └── src │ ├── branch │ ├── bulk.rs │ ├── continuation_finder.rs │ ├── mod.rs │ └── single.rs │ ├── lib.rs │ ├── repeat │ ├── bulk.rs │ ├── mod.rs │ ├── single.rs │ └── strongly_connected_finder.rs │ └── single_exit_patcher.rs ├── Data Flow ├── Builder │ ├── Cargo.toml │ └── src │ │ ├── control_flow_converter │ │ ├── basic_block_converter.rs │ │ ├── dependency_map.rs │ │ ├── mod.rs │ │ └── region_stack.rs │ │ ├── function_builder.rs │ │ ├── global_state.rs │ │ ├── lib.rs │ │ └── sections.rs ├── Graph │ ├── Cargo.toml │ └── src │ │ ├── dot.rs │ │ ├── lib.rs │ │ └── node │ │ ├── base.rs │ │ ├── control.rs │ │ ├── mod.rs │ │ └── sources.rs └── Visitor │ ├── Cargo.toml │ ├── build.rs │ ├── isle │ ├── fNN.isle │ ├── global.isle │ ├── iNN.isle │ ├── table.isle │ ├── types.fNN.isle │ ├── types.global.isle │ ├── types.iNN.isle │ ├── types.rust.isle │ └── types.table.isle │ └── src │ ├── control │ ├── dead_port_eliminator.rs │ ├── invariant_port_mover.rs │ ├── mod.rs │ └── region_identity.rs │ ├── isle │ ├── context.rs │ ├── internal.rs │ └── mod.rs │ ├── lib.rs │ ├── successor_finder.rs │ └── topological_normalizer.rs ├── LICENSE.txt ├── README.md ├── Targets └── Luau │ ├── Builder │ ├── Cargo.toml │ └── src │ │ ├── code_handler.rs │ │ ├── data_handler.rs │ │ ├── lib.rs │ │ └── local_allocator │ │ ├── argument_finder.rs │ │ ├── index_provider.rs │ │ ├── local_provider.rs │ │ ├── mod.rs │ │ ├── reference_finder.rs │ │ └── scalar_finder.rs │ ├── Printer │ ├── Cargo.toml │ ├── runtime │ │ ├── LICENSE.txt │ │ ├── base.luau │ │ ├── f32.luau │ │ ├── f64.luau │ │ ├── i32.luau │ │ ├── i64.luau │ │ ├── memory.luau │ │ └── table.luau │ └── src │ │ ├── expression.rs │ │ ├── lib.rs │ │ ├── library │ │ ├── mod.rs │ │ ├── names_finder.rs │ │ ├── printer.rs │ │ └── sections.rs │ │ ├── main.rs │ │ ├── print.rs │ │ └── statement.rs │ └── Tree │ ├── Cargo.toml │ └── src │ ├── expression.rs │ ├── lib.rs │ ├── statement.rs │ └── visitor.rs └── rustfmt.toml /.gitignore: -------------------------------------------------------------------------------- 1 | /Cargo.lock 2 | /target 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Conformance/Suite"] 2 | path = Conformance/Suite 3 | url = https://github.com/WebAssembly/testsuite 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["Conformance", "Targets/Luau/*"] 4 | 5 | [workspace.dependencies] 6 | list = { git = "https://github.com/Rerumu/List.git", rev = "57d0ca8" } 7 | set = { git = "https://github.com/Rerumu/Set.git", rev = "d10ce45" } 8 | 9 | clap = { version = "4.5.23", default-features = false, features = [ 10 | "color", 11 | "derive", 12 | "error-context", 13 | "help", 14 | "std", 15 | "suggestions", 16 | "usage", 17 | ] } 18 | 19 | cranelift-isle = { version = "0.124.1", default-features = false } 20 | 21 | hashbrown = { version = "0.16.0", default-features = false, features = [ 22 | "default-hasher", 23 | "equivalent", 24 | "inline-more", 25 | ] } 26 | 27 | wasmparser = { version = "0.235.0", default-features = false } 28 | -------------------------------------------------------------------------------- /Conformance/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "conformance" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dev-dependencies] 7 | data-flow-graph = { path = "../Data Flow/Graph" } 8 | data-flow-builder = { path = "../Data Flow/Builder" } 9 | data-flow-visitor = { path = "../Data Flow/Visitor" } 10 | luau-tree = { path = "../Targets/Luau/Tree" } 11 | luau-builder = { path = "../Targets/Luau/Builder" } 12 | luau-printer = { path = "../Targets/Luau/Printer" } 13 | datatest-stable = "0.3.2" 14 | pretty_assertions = "1.4.1" 15 | wast = "222.0.0" 16 | 17 | [[test]] 18 | name = "luau" 19 | harness = false 20 | -------------------------------------------------------------------------------- /Conformance/tests/common/loader.rs: -------------------------------------------------------------------------------- 1 | use data_flow_builder::DataFlowBuilder; 2 | use data_flow_graph::{DataFlowGraph, Link}; 3 | use data_flow_visitor::{ 4 | control::{ 5 | dead_port_eliminator::DeadPortEliminator, invariant_port_mover::InvariantPortMover, 6 | region_identity, 7 | }, 8 | isle, 9 | topological_normalizer::TopologicalNormalizer, 10 | }; 11 | 12 | struct Optimizer { 13 | invariant_port_mover: InvariantPortMover, 14 | dead_port_eliminator: DeadPortEliminator, 15 | topological_normalizer: TopologicalNormalizer, 16 | } 17 | 18 | impl Optimizer { 19 | fn new() -> Self { 20 | Self { 21 | invariant_port_mover: InvariantPortMover::new(), 22 | dead_port_eliminator: DeadPortEliminator::new(), 23 | topological_normalizer: TopologicalNormalizer::new(), 24 | } 25 | } 26 | 27 | fn apply_isle(graph: &mut DataFlowGraph) -> bool { 28 | let mut applied = false; 29 | let len = graph.len(); 30 | 31 | for id in (0..len.try_into().unwrap()).rev() { 32 | while isle::simplify_i32(graph, id) 33 | // || isle::simplify_global(graph, id) 34 | // || isle::simplify_table(graph, id) 35 | { 36 | applied = true; 37 | } 38 | } 39 | 40 | applied 41 | } 42 | 43 | fn apply(&mut self, graph: &mut DataFlowGraph, mut omega: u32) -> u32 { 44 | loop { 45 | omega = self.topological_normalizer.run(graph, omega); 46 | 47 | self.invariant_port_mover.run(graph); 48 | self.dead_port_eliminator.run(graph, Link(omega, 0)); 49 | 50 | if !Self::apply_isle(graph) { 51 | break; 52 | } 53 | 54 | region_identity::remove(graph); 55 | } 56 | 57 | omega 58 | } 59 | 60 | fn finalize(&mut self, graph: &mut DataFlowGraph, omega: u32) { 61 | region_identity::insert(graph); 62 | 63 | self.topological_normalizer.run(graph, omega); 64 | } 65 | } 66 | 67 | pub struct Loader { 68 | data_flow_builder: DataFlowBuilder, 69 | optimizer: Optimizer, 70 | } 71 | 72 | impl Loader { 73 | pub fn new() -> Self { 74 | Self { 75 | data_flow_builder: DataFlowBuilder::new(), 76 | optimizer: Optimizer::new(), 77 | } 78 | } 79 | 80 | pub fn run(&mut self, data: &[u8]) -> DataFlowGraph { 81 | let mut graph = DataFlowGraph::new(); 82 | 83 | let omega = self.data_flow_builder.run(&mut graph, data); 84 | let omega = self.optimizer.apply(&mut graph, omega); 85 | 86 | self.optimizer.finalize(&mut graph, omega); 87 | 88 | graph 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Conformance/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod loader; 2 | pub mod runner; 3 | -------------------------------------------------------------------------------- /Conformance/tests/common/runner.rs: -------------------------------------------------------------------------------- 1 | use datatest_stable::Result; 2 | use wast::{ 3 | QuoteWat, Wast, WastDirective, WastExecute, WastInvoke, WastRet, WastThread, Wat, 4 | lexer::Lexer, 5 | parser::ParseBuffer, 6 | token::{Id, Span}, 7 | }; 8 | 9 | pub trait Runner { 10 | fn on_module(&mut self, quote_wat: QuoteWat) -> Result<()>; 11 | 12 | fn on_module_definition(&mut self, quote_wat: QuoteWat) -> Result<()>; 13 | 14 | fn on_module_instance( 15 | &mut self, 16 | span: Span, 17 | instance: Option, 18 | module: Option, 19 | ) -> Result<()>; 20 | 21 | fn on_assert_malformed(&mut self, span: Span, module: QuoteWat, message: &str) -> Result<()>; 22 | 23 | fn on_assert_invalid(&mut self, span: Span, module: QuoteWat, message: &str) -> Result<()>; 24 | 25 | fn on_register(&mut self, span: Span, name: &str, module: Option) -> Result<()>; 26 | 27 | fn on_invoke(&mut self, wast_invoke: WastInvoke) -> Result<()>; 28 | 29 | fn on_assert_trap(&mut self, span: Span, exec: WastExecute, message: &str) -> Result<()>; 30 | 31 | fn on_assert_return( 32 | &mut self, 33 | span: Span, 34 | exec: WastExecute, 35 | results: Vec, 36 | ) -> Result<()>; 37 | 38 | fn on_assert_exhaustion(&mut self, span: Span, call: WastInvoke, message: &str) -> Result<()>; 39 | 40 | fn on_assert_unlinkable(&mut self, span: Span, module: Wat, message: &str) -> Result<()>; 41 | 42 | fn on_assert_exception(&mut self, span: Span, exec: WastExecute) -> Result<()>; 43 | 44 | fn on_assert_suspension(&mut self, span: Span, exec: WastExecute, message: &str) -> Result<()>; 45 | 46 | fn on_thread(&mut self, wast_thread: WastThread) -> Result<()>; 47 | 48 | fn on_wait(&mut self, span: Span, thread: Id) -> Result<()>; 49 | 50 | fn on_directive(&mut self, directive: WastDirective) -> Result<()> { 51 | match directive { 52 | WastDirective::Module(quote_wat) => self.on_module(quote_wat), 53 | WastDirective::ModuleDefinition(quote_wat) => self.on_module_definition(quote_wat), 54 | WastDirective::ModuleInstance { 55 | span, 56 | instance, 57 | module, 58 | } => self.on_module_instance(span, instance, module), 59 | WastDirective::AssertMalformed { 60 | span, 61 | module, 62 | message, 63 | } => self.on_assert_malformed(span, module, message), 64 | WastDirective::AssertInvalid { 65 | span, 66 | module, 67 | message, 68 | } => self.on_assert_invalid(span, module, message), 69 | WastDirective::Register { span, name, module } => self.on_register(span, name, module), 70 | WastDirective::Invoke(wast_invoke) => self.on_invoke(wast_invoke), 71 | WastDirective::AssertTrap { 72 | span, 73 | exec, 74 | message, 75 | } => self.on_assert_trap(span, exec, message), 76 | WastDirective::AssertReturn { 77 | span, 78 | exec, 79 | results, 80 | } => self.on_assert_return(span, exec, results), 81 | WastDirective::AssertExhaustion { 82 | span, 83 | call, 84 | message, 85 | } => self.on_assert_exhaustion(span, call, message), 86 | WastDirective::AssertUnlinkable { 87 | span, 88 | module, 89 | message, 90 | } => self.on_assert_unlinkable(span, module, message), 91 | WastDirective::AssertException { span, exec } => self.on_assert_exception(span, exec), 92 | WastDirective::AssertSuspension { 93 | span, 94 | exec, 95 | message, 96 | } => self.on_assert_suspension(span, exec, message), 97 | WastDirective::Thread(wast_thread) => self.on_thread(wast_thread), 98 | WastDirective::Wait { span, thread } => self.on_wait(span, thread), 99 | } 100 | } 101 | 102 | fn run(&mut self, content: &str) -> Result<()> { 103 | let mut lexer = Lexer::new(content); 104 | 105 | lexer.allow_confusing_unicode(true); 106 | 107 | let buffer = ParseBuffer::new_with_lexer(lexer)?; 108 | let wast = wast::parser::parse::(&buffer)?; 109 | 110 | wast.directives 111 | .into_iter() 112 | .try_for_each(|directive| self.on_directive(directive)) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Conformance/tests/harness.luau: -------------------------------------------------------------------------------- 1 | -- SECTION environment 2 | -- NEEDS create_i64_from_u32 3 | -- NEEDS create_u32_from_i64 4 | -- NEEDS memory_new 5 | -- NEEDS table_new 6 | -- NEEDS vector_create 7 | local environment = {} 8 | local named = {} 9 | local selected = nil 10 | 11 | do 12 | local spectest = { 13 | global_i32 = { 666 }, 14 | global_i64 = { rt_create_i64_from_u32(666, 0) }, 15 | global_f32 = { vector_create(666.6, 0, 0) }, 16 | global_f64 = { 666.6 }, 17 | 18 | table = rt_table_new(10, 10), 19 | memory = rt_memory_new(1, 2), 20 | } 21 | 22 | spectest.print = print 23 | 24 | function spectest.print_i32(argument: number) 25 | print(string.format("I32 `0x%08X`", argument)) 26 | end 27 | 28 | function spectest.print_i64(argument: number) 29 | local argument_1, argument_2 = rt_create_u32_from_i64(argument) 30 | 31 | print(string.format("I64 `0x%08X%08X`", argument_2, argument_1)) 32 | end 33 | 34 | function spectest.print_f32(argument: vector) 35 | print(string.format("F32 `%g`", argument.x)) 36 | end 37 | 38 | function spectest.print_f64(argument: number) 39 | print(string.format("F64 `%g`", argument)) 40 | end 41 | 42 | function spectest.print_i32_f32(argument_1: number, argument_2: vector) 43 | print(string.format("I32 `0x%08X`, F32 `%g`", argument_1, argument_2.x)) 44 | end 45 | 46 | function spectest.print_f64_f64(argument_1: number, argument_2: number) 47 | print(string.format("F64 `%g`, F64 `%g`", argument_1, argument_2)) 48 | end 49 | 50 | environment.spectest = spectest 51 | end 52 | 53 | -- SECTION assert_trap 54 | local function hn_assert_trap(message: string, callback: () -> ()) 55 | if not pcall(callback) then 56 | return 57 | end 58 | 59 | error("should trap: " .. message, 2) 60 | end 61 | 62 | -- SECTION assert_ref_null 63 | local function hn_assert_ref_null(source: unknown) 64 | if source ~= nil then 65 | error(string.format("`%s` should be null", source), 2) 66 | end 67 | end 68 | 69 | -- SECTION assert_ref_extern 70 | local function hn_assert_ref_extern(source: unknown) 71 | if source == nil then 72 | error("source should be non null", 2) 73 | end 74 | end 75 | 76 | -- SECTION assert_equal_i32 77 | local function hn_assert_equal_i32(target: number): (unknown) -> () 78 | return function(source: unknown) 79 | if type(source) ~= "number" then 80 | error(string.format("`%*` should be type `i32`", source), 2) 81 | end 82 | 83 | if source ~= target then 84 | error(string.format("`%08X` (%*) should equal `%08X` (%*)", source, source, target, target), 2) 85 | end 86 | end 87 | end 88 | 89 | -- SECTION assert_equal_i64 90 | -- NEEDS convert_u64_to_f64 91 | -- NEEDS create_i64_from_u32 92 | -- NEEDS create_u32_from_i64 93 | local function hn_assert_equal_i64(target_1: number, target_2: number): (unknown) -> () 94 | local target = rt_create_i64_from_u32(target_1, target_2) 95 | 96 | return function(source: unknown) 97 | if type(source) ~= "number" then 98 | error(string.format("`%*` should be type `i64`", source), 2) 99 | end 100 | 101 | local source_1, source_2 = rt_create_u32_from_i64(source) 102 | 103 | if source_1 ~= target_1 or source_2 ~= target_2 then 104 | local source = rt_convert_u64_to_f64(source) 105 | local target = rt_convert_u64_to_f64(target) 106 | 107 | error( 108 | string.format( 109 | "`%08X%08X` (%*) should equal `%08X%08X` (%*)", 110 | source_2, 111 | source_1, 112 | source, 113 | target_2, 114 | target_1, 115 | target 116 | ), 117 | 2 118 | ) 119 | end 120 | end 121 | end 122 | 123 | -- SECTION is_f32_nan_canonical 124 | -- NEEDS is_f64_nan_canonical 125 | function hn_is_f32_nan_canonical(source: vector): boolean 126 | return hn_is_f64_nan_canonical(source.x) 127 | end 128 | 129 | -- SECTION is_f32_nan_arithmetic 130 | -- NEEDS is_f64_nan_arithmetic 131 | function hn_is_f32_nan_arithmetic(source: vector): boolean 132 | return hn_is_f64_nan_arithmetic(source.x) 133 | end 134 | 135 | -- SECTION assert_equal_f32 136 | -- NEEDS transmute_f32_to_i32 137 | -- NEEDS transmute_i32_to_f32 138 | local function hn_assert_equal_f32(target: number): (unknown) -> () 139 | local target = rt_transmute_i32_to_f32(target) 140 | 141 | return function(source: unknown) 142 | if type(source) ~= "vector" then 143 | error(string.format("`%*` should type `f32`", source), 2) 144 | end 145 | 146 | local source_1 = rt_transmute_f32_to_i32(source) 147 | local target_1 = rt_transmute_f32_to_i32(target) 148 | 149 | if source_1 ~= target_1 then 150 | local source = source.x 151 | local target = target.x 152 | 153 | error(string.format("`%08X` (%*) should equal `%08X` (%*)", source_1, source, target_1, target), 2) 154 | end 155 | end 156 | end 157 | 158 | -- SECTION is_f64_nan_canonical 159 | -- NEEDS create_u32_from_i64 160 | function hn_is_f64_nan_canonical(source: number): boolean 161 | local source_1, source_2 = rt_create_u32_from_i64(source) 162 | 163 | return source_1 == 0 and (source_2 == 0x7FF80000 or source_2 == 0xFFF80000) 164 | end 165 | 166 | -- SECTION is_f64_nan_arithmetic 167 | -- NEEDS is_f64_nan_canonical 168 | function hn_is_f64_nan_arithmetic(source: number): boolean 169 | return source ~= source and not hn_is_f64_nan_canonical(source) 170 | end 171 | 172 | -- SECTION assert_equal_f64 173 | -- NEEDS create_f64_from_u32 174 | -- NEEDS create_u32_from_i64 175 | -- NEEDS transmute_f64_to_i64 176 | local function hn_assert_equal_f64(target_1: number, target_2: number): (unknown) -> () 177 | local target = rt_create_f64_from_u32(target_1, target_2) 178 | 179 | return function(source: unknown) 180 | if type(source) ~= "number" then 181 | error(string.format("`%*` should type `f64`", source), 2) 182 | end 183 | 184 | local source_1, source_2 = rt_create_u32_from_i64(rt_transmute_f64_to_i64(source)) 185 | 186 | if source_1 ~= target_1 or source_2 ~= target_2 then 187 | error( 188 | string.format( 189 | "`%08X%08X` (%*) should equal `%08X%08X` (%*)", 190 | source_2, 191 | source_1, 192 | source, 193 | target_2, 194 | target_1, 195 | target 196 | ), 197 | 2 198 | ) 199 | end 200 | end 201 | end 202 | -------------------------------------------------------------------------------- /Control Flow/Builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "control-flow-builder" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | control-flow-graph = { path = "../Graph" } 8 | control-flow-structurer = { path = "../Structurer" } 9 | 10 | list.workspace = true 11 | set.workspace = true 12 | wasmparser.workspace = true 13 | -------------------------------------------------------------------------------- /Control Flow/Builder/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![expect(clippy::missing_panics_doc)] 3 | 4 | extern crate alloc; 5 | 6 | mod basic_block_builder; 7 | mod code_builder; 8 | mod sorter; 9 | mod stack_builder; 10 | mod types; 11 | 12 | use control_flow_graph::ControlFlowGraph; 13 | use control_flow_structurer::Structurer; 14 | use wasmparser::{BlockType, OperatorsReader}; 15 | 16 | use self::{basic_block_builder::BasicBlockBuilder, sorter::Sorter}; 17 | 18 | pub use self::types::Types; 19 | 20 | pub struct ControlFlowBuilder { 21 | basic_block_builder: BasicBlockBuilder, 22 | structurer: Structurer, 23 | sorter: Sorter, 24 | } 25 | 26 | impl ControlFlowBuilder { 27 | #[must_use] 28 | pub const fn new() -> Self { 29 | Self { 30 | basic_block_builder: BasicBlockBuilder::new(), 31 | structurer: Structurer::new(), 32 | sorter: Sorter::new(), 33 | } 34 | } 35 | 36 | pub fn run( 37 | &mut self, 38 | graph: &mut ControlFlowGraph, 39 | types: &Types, 40 | function_type: BlockType, 41 | locals: u16, 42 | operators: OperatorsReader, 43 | ) { 44 | self.basic_block_builder 45 | .run(graph, types, function_type, locals, operators); 46 | 47 | self.sorter.run(&mut graph.basic_blocks, 0); 48 | 49 | let exit = graph.add_no_operation(); 50 | 51 | self.structurer.run(graph, 0, exit); 52 | self.sorter.run(&mut graph.basic_blocks, 0); 53 | } 54 | } 55 | 56 | impl Default for ControlFlowBuilder { 57 | fn default() -> Self { 58 | Self::new() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Control Flow/Builder/src/sorter.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_graph::BasicBlock; 3 | use set::Set; 4 | 5 | struct DepthFirstSearcher { 6 | seen: Set, 7 | stack: Vec<(u16, bool)>, 8 | } 9 | 10 | impl DepthFirstSearcher { 11 | const fn new() -> Self { 12 | Self { 13 | seen: Set::new(), 14 | stack: Vec::new(), 15 | } 16 | } 17 | 18 | fn add_successor(&mut self, id: u16) { 19 | if self.seen.contains(id.into()) { 20 | return; 21 | } 22 | 23 | self.stack.push((id, false)); 24 | } 25 | 26 | fn run(&mut self, basic_blocks: &mut [BasicBlock], start: u16, mut handler: H) 27 | where 28 | H: FnMut(&mut [BasicBlock], u16), 29 | { 30 | self.seen.clear(); 31 | 32 | self.add_successor(start); 33 | 34 | while let Some((id, post)) = self.stack.pop() { 35 | let id_usize = id.into(); 36 | 37 | if self.seen.grow_insert(id_usize) { 38 | if post { 39 | handler(basic_blocks, id); 40 | } 41 | } else { 42 | let BasicBlock { successors, .. } = &basic_blocks[id_usize]; 43 | 44 | self.stack.push((id, true)); 45 | 46 | for &id in successors { 47 | self.add_successor(id); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | pub struct Sorter { 55 | basic_blocks: Vec, 56 | id_to_post: Vec, 57 | 58 | depth_first_searcher: DepthFirstSearcher, 59 | } 60 | 61 | impl Sorter { 62 | pub const fn new() -> Self { 63 | Self { 64 | basic_blocks: Vec::new(), 65 | id_to_post: Vec::new(), 66 | 67 | depth_first_searcher: DepthFirstSearcher::new(), 68 | } 69 | } 70 | 71 | fn find_basic_blocks(&mut self, basic_blocks: &mut [BasicBlock], entry: u16) -> u16 { 72 | let mut post = 0; 73 | 74 | self.basic_blocks.clear(); 75 | self.id_to_post.clear(); 76 | self.id_to_post.resize(basic_blocks.len(), u16::MAX); 77 | 78 | self.depth_first_searcher 79 | .run(basic_blocks, entry, |basic_blocks, id| { 80 | let id = usize::from(id); 81 | let basic_block = core::mem::take(&mut basic_blocks[id]); 82 | 83 | self.basic_blocks.push(basic_block); 84 | self.id_to_post[id] = post; 85 | 86 | post += 1; 87 | }); 88 | 89 | post 90 | } 91 | 92 | fn patch_post(&mut self, post: u16) { 93 | self.id_to_post 94 | .iter_mut() 95 | .filter(|post| **post != u16::MAX) 96 | .for_each(|old| *old = post - 1 - *old); 97 | } 98 | 99 | fn patch_references(&mut self, basic_blocks: &mut Vec) { 100 | basic_blocks.clear(); 101 | basic_blocks.extend(self.basic_blocks.drain(..).rev()); 102 | 103 | for basic_block in basic_blocks { 104 | basic_block.replace_ids(|id| self.id_to_post[usize::from(id)]); 105 | } 106 | } 107 | 108 | pub fn run(&mut self, basic_blocks: &mut Vec, entry: u16) { 109 | let post = self.find_basic_blocks(basic_blocks, entry); 110 | 111 | self.patch_post(post); 112 | self.patch_references(basic_blocks); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Control Flow/Builder/src/stack_builder.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_graph::instruction::Name; 3 | use list::resizable::Resizable; 4 | use wasmparser::{BlockType, FuncType}; 5 | 6 | use crate::types::Types; 7 | 8 | pub const SHARED_LOCAL: u16 = Name::D as u16; 9 | pub const LOCAL_BASE: u16 = Name::COUNT; 10 | 11 | pub struct Jump { 12 | pub stack: u16, 13 | pub source: u16, 14 | pub branch: u16, 15 | } 16 | 17 | pub struct Level { 18 | pub parameters: u16, 19 | pub results: u16, 20 | pub base: u16, 21 | 22 | pub destination: Option, 23 | pub jumps: Resizable, 24 | } 25 | 26 | pub struct StackBuilder { 27 | levels: Vec, 28 | 29 | top: u16, 30 | } 31 | 32 | impl StackBuilder { 33 | pub const fn new() -> Self { 34 | Self { 35 | levels: Vec::new(), 36 | 37 | top: 0, 38 | } 39 | } 40 | 41 | pub fn set_function_data(&mut self, types: &Types, function_type: BlockType, locals: u16) { 42 | let parameters = types.get_parameter_count(function_type).try_into().unwrap(); 43 | let results = types.get_result_count(function_type).try_into().unwrap(); 44 | 45 | self.top = LOCAL_BASE; 46 | 47 | self.levels.push(Level { 48 | parameters, 49 | results, 50 | base: self.top, 51 | 52 | destination: None, 53 | jumps: Resizable::new(), 54 | }); 55 | 56 | self.top += parameters + locals; 57 | } 58 | 59 | pub fn push_level(&mut self, types: &Types, block_type: BlockType, destination: Option) { 60 | let parameters = types.get_parameter_count(block_type).try_into().unwrap(); 61 | let results = types.get_result_count(block_type).try_into().unwrap(); 62 | 63 | self.levels.push(Level { 64 | parameters, 65 | results, 66 | base: self.top - parameters, 67 | 68 | destination, 69 | jumps: Resizable::new(), 70 | }); 71 | } 72 | 73 | pub fn pull_level(&mut self) -> Level { 74 | let level @ Level { base, results, .. } = self.levels.pop().unwrap(); 75 | 76 | self.top = base + results; 77 | 78 | level 79 | } 80 | 81 | pub fn peek_level_mut(&mut self) -> &mut Level { 82 | self.levels.last_mut().unwrap() 83 | } 84 | 85 | const fn push_locals(&mut self, count: u16) -> (u16, u16) { 86 | let top = self.top; 87 | 88 | self.top = top.wrapping_add(count); 89 | 90 | (top, self.top) 91 | } 92 | 93 | pub const fn push_local(&mut self) -> u16 { 94 | self.push_locals(1).0 95 | } 96 | 97 | const fn pull_locals(&mut self, count: u16) -> (u16, u16) { 98 | let top = self.top; 99 | 100 | self.top = top.wrapping_sub(count); 101 | 102 | (self.top, top) 103 | } 104 | 105 | pub const fn pull_local(&mut self) -> u16 { 106 | self.pull_locals(1).0 107 | } 108 | 109 | pub fn load_function_type(&mut self, r#type: &FuncType) -> ((u16, u16), (u16, u16)) { 110 | let sources = self.pull_locals(r#type.params().len().try_into().unwrap()); 111 | let destinations = self.push_locals(r#type.results().len().try_into().unwrap()); 112 | 113 | (destinations, sources) 114 | } 115 | 116 | pub const fn get_top(&self) -> u16 { 117 | self.top 118 | } 119 | 120 | pub const fn set_top(&mut self, top: u16) { 121 | self.top = top; 122 | } 123 | 124 | pub fn jump_to_level(&mut self, source: u16, branch: usize, destination: usize) { 125 | let branch = branch.try_into().unwrap(); 126 | 127 | self.levels[destination].jumps.push(Jump { 128 | stack: self.top, 129 | source, 130 | branch, 131 | }); 132 | } 133 | 134 | pub fn jump_to_depth(&mut self, source: u16, branch: usize, depth: u32) { 135 | let depth = usize::try_from(depth).unwrap(); 136 | 137 | self.jump_to_level(source, branch, self.levels.len() - depth - 1); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Control Flow/Builder/src/types.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use wasmparser::{BlockType, FuncType, RecGroup, SectionLimited, SubType}; 3 | 4 | pub struct Types { 5 | sub_types: Vec, 6 | functions: Vec, 7 | } 8 | 9 | impl Types { 10 | #[must_use] 11 | pub const fn new() -> Self { 12 | Self { 13 | sub_types: Vec::new(), 14 | functions: Vec::new(), 15 | } 16 | } 17 | 18 | pub fn clear(&mut self) { 19 | self.sub_types.clear(); 20 | self.functions.clear(); 21 | } 22 | 23 | pub fn add_sub_types(&mut self, section: SectionLimited) { 24 | for group in section.into_iter().map(Result::unwrap) { 25 | self.sub_types.extend(group.into_types()); 26 | } 27 | } 28 | 29 | pub fn add_function(&mut self, function: u32) { 30 | self.functions.push(function); 31 | } 32 | 33 | pub fn add_functions(&mut self, section: SectionLimited) { 34 | self.functions 35 | .extend(section.into_iter().map(Result::unwrap)); 36 | } 37 | 38 | #[must_use] 39 | pub fn get_function_index(&self, function: u32) -> u32 { 40 | self.functions[usize::try_from(function).unwrap()] 41 | } 42 | 43 | #[must_use] 44 | pub fn get_type(&self, r#type: u32) -> &SubType { 45 | &self.sub_types[usize::try_from(r#type).unwrap()] 46 | } 47 | 48 | #[must_use] 49 | pub fn get_function_type(&self, function: u32) -> &FuncType { 50 | let function = self.get_function_index(function); 51 | 52 | self.get_type(function).unwrap_func() 53 | } 54 | 55 | #[must_use] 56 | pub fn get_parameter_count(&self, block_type: BlockType) -> usize { 57 | match block_type { 58 | BlockType::Empty | BlockType::Type(_) => 0, 59 | BlockType::FuncType(r#type) => self.get_type(r#type).unwrap_func().params().len(), 60 | } 61 | } 62 | 63 | #[must_use] 64 | pub fn get_result_count(&self, block_type: BlockType) -> usize { 65 | match block_type { 66 | BlockType::Empty => 0, 67 | BlockType::Type(_) => 1, 68 | BlockType::FuncType(r#type) => self.get_type(r#type).unwrap_func().results().len(), 69 | } 70 | } 71 | } 72 | 73 | impl Default for Types { 74 | fn default() -> Self { 75 | Self::new() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Control Flow/Graph/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "control-flow-graph" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | list.workspace = true 8 | -------------------------------------------------------------------------------- /Control Flow/Graph/src/basic_block.rs: -------------------------------------------------------------------------------- 1 | use list::resizable::Resizable; 2 | 3 | pub struct BasicBlock { 4 | pub predecessors: Resizable, 5 | pub successors: Resizable, 6 | 7 | pub start: u32, 8 | pub end: u32, 9 | } 10 | 11 | impl BasicBlock { 12 | #[must_use] 13 | pub const fn from_range(start: u32, end: u32) -> Self { 14 | Self { 15 | predecessors: Resizable::new(), 16 | successors: Resizable::new(), 17 | 18 | start, 19 | end, 20 | } 21 | } 22 | 23 | pub(crate) fn range(&self) -> core::ops::Range { 24 | self.start.try_into().unwrap()..self.end.try_into().unwrap() 25 | } 26 | 27 | pub(crate) fn is_source(&self) -> bool { 28 | self.predecessors.is_empty() 29 | } 30 | 31 | pub fn replace_ids u16>(&mut self, map: M) { 32 | for id in &mut self.predecessors { 33 | *id = map(*id); 34 | } 35 | 36 | for id in &mut self.successors { 37 | *id = map(*id); 38 | } 39 | 40 | self.predecessors.retain(|&id| id != u16::MAX); 41 | } 42 | } 43 | 44 | impl Default for BasicBlock { 45 | fn default() -> Self { 46 | Self::from_range(0, 0) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Control Flow/Graph/src/dot.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Display, Formatter, Result}; 2 | 3 | use crate::{ControlFlowGraph, Instruction}; 4 | 5 | #[derive(PartialEq, Eq, Clone, Copy)] 6 | enum Vertex { 7 | Assignment, 8 | Selection, 9 | Empty, 10 | Instructions, 11 | } 12 | 13 | impl Vertex { 14 | const fn from_instructions(instructions: &[Instruction]) -> Self { 15 | match instructions { 16 | [Instruction::I32Constant(_)] => Self::Assignment, 17 | [Instruction::LocalBranch(_)] => Self::Selection, 18 | [] => Self::Empty, 19 | _ => Self::Instructions, 20 | } 21 | } 22 | 23 | const fn group(self) -> &'static str { 24 | match self { 25 | Self::Assignment => "A", 26 | Self::Selection => "B", 27 | Self::Empty => "C", 28 | Self::Instructions => "D", 29 | } 30 | } 31 | 32 | const fn color(self) -> &'static str { 33 | match self { 34 | Self::Assignment | Self::Selection => "#EF8784", 35 | Self::Empty => "#C2C5FA", 36 | Self::Instructions => "#FBE78E", 37 | } 38 | } 39 | } 40 | 41 | impl Display for Vertex { 42 | fn fmt(&self, f: &mut Formatter) -> Result { 43 | writeln!( 44 | f, 45 | "\tnode [fillcolor = \"{}\", group = {}];", 46 | self.color(), 47 | self.group() 48 | ) 49 | } 50 | } 51 | 52 | fn fmt_instruction(instruction: Instruction, f: &mut Formatter) -> Result { 53 | use core::fmt::Debug; 54 | 55 | match instruction { 56 | Instruction::LocalSet(instruction) => Debug::fmt(&instruction, f), 57 | Instruction::LocalBranch(instruction) => Debug::fmt(&instruction, f), 58 | Instruction::I32Constant(instruction) => Debug::fmt(&instruction, f), 59 | Instruction::I64Constant(instruction) => Debug::fmt(&instruction, f), 60 | Instruction::F32Constant(instruction) => Debug::fmt(&instruction, f), 61 | Instruction::F64Constant(instruction) => Debug::fmt(&instruction, f), 62 | Instruction::RefIsNull(instruction) => Debug::fmt(&instruction, f), 63 | Instruction::RefNull(instruction) => Debug::fmt(&instruction, f), 64 | Instruction::RefFunction(instruction) => Debug::fmt(&instruction, f), 65 | Instruction::Call(instruction) => Debug::fmt(&instruction, f), 66 | Instruction::Unreachable => write!(f, "Unreachable"), 67 | Instruction::IntegerUnaryOperation(instruction) => Debug::fmt(&instruction, f), 68 | Instruction::IntegerBinaryOperation(instruction) => Debug::fmt(&instruction, f), 69 | Instruction::IntegerCompareOperation(instruction) => Debug::fmt(&instruction, f), 70 | Instruction::IntegerNarrow(instruction) => Debug::fmt(&instruction, f), 71 | Instruction::IntegerWiden(instruction) => Debug::fmt(&instruction, f), 72 | Instruction::IntegerExtend(instruction) => Debug::fmt(&instruction, f), 73 | Instruction::IntegerConvertToNumber(instruction) => Debug::fmt(&instruction, f), 74 | Instruction::IntegerTransmuteToNumber(instruction) => Debug::fmt(&instruction, f), 75 | Instruction::NumberUnaryOperation(instruction) => Debug::fmt(&instruction, f), 76 | Instruction::NumberBinaryOperation(instruction) => Debug::fmt(&instruction, f), 77 | Instruction::NumberCompareOperation(instruction) => Debug::fmt(&instruction, f), 78 | Instruction::NumberNarrow(instruction) => Debug::fmt(&instruction, f), 79 | Instruction::NumberWiden(instruction) => Debug::fmt(&instruction, f), 80 | Instruction::NumberTruncateToInteger(instruction) => Debug::fmt(&instruction, f), 81 | Instruction::NumberTransmuteToInteger(instruction) => Debug::fmt(&instruction, f), 82 | Instruction::GlobalGet(instruction) => Debug::fmt(&instruction, f), 83 | Instruction::GlobalSet(instruction) => Debug::fmt(&instruction, f), 84 | Instruction::TableGet(instruction) => Debug::fmt(&instruction, f), 85 | Instruction::TableSet(instruction) => Debug::fmt(&instruction, f), 86 | Instruction::TableSize(instruction) => Debug::fmt(&instruction, f), 87 | Instruction::TableGrow(instruction) => Debug::fmt(&instruction, f), 88 | Instruction::TableFill(instruction) => Debug::fmt(&instruction, f), 89 | Instruction::TableCopy(instruction) => Debug::fmt(&instruction, f), 90 | Instruction::TableInit(instruction) => Debug::fmt(&instruction, f), 91 | Instruction::ElementsDrop(instruction) => Debug::fmt(&instruction, f), 92 | Instruction::MemoryLoad(instruction) => Debug::fmt(&instruction, f), 93 | Instruction::MemoryStore(instruction) => Debug::fmt(&instruction, f), 94 | Instruction::MemorySize(instruction) => Debug::fmt(&instruction, f), 95 | Instruction::MemoryGrow(instruction) => Debug::fmt(&instruction, f), 96 | Instruction::MemoryFill(instruction) => Debug::fmt(&instruction, f), 97 | Instruction::MemoryCopy(instruction) => Debug::fmt(&instruction, f), 98 | Instruction::MemoryInit(instruction) => Debug::fmt(&instruction, f), 99 | Instruction::DataDrop(instruction) => Debug::fmt(&instruction, f), 100 | } 101 | } 102 | 103 | pub struct Dot<'inner> { 104 | inner: &'inner ControlFlowGraph, 105 | } 106 | 107 | impl<'inner> Dot<'inner> { 108 | #[must_use] 109 | pub const fn new(inner: &'inner ControlFlowGraph) -> Self { 110 | Self { inner } 111 | } 112 | 113 | fn fmt_nodes(&self, f: &mut Formatter) -> Result { 114 | writeln!(f, "\tnode [shape = box, style = filled, ordering = out];")?; 115 | 116 | let mut last_vertex = Vertex::Instructions; 117 | 118 | last_vertex.fmt(f)?; 119 | 120 | self.inner.block_ids().try_for_each(|id| { 121 | let instructions = self.inner.instructions(id); 122 | let vertex = Vertex::from_instructions(instructions); 123 | 124 | if vertex != last_vertex { 125 | last_vertex = vertex; 126 | 127 | last_vertex.fmt(f)?; 128 | } 129 | 130 | write!(f, "\tN{id} [xlabel = {id}, label = \"")?; 131 | 132 | instructions.iter().try_for_each(|&instruction| { 133 | fmt_instruction(instruction, f)?; 134 | 135 | write!(f, "\\l") 136 | })?; 137 | 138 | writeln!(f, "\"];") 139 | }) 140 | } 141 | 142 | fn fmt_edges(&self, f: &mut Formatter) -> Result { 143 | writeln!(f, "\tedge [color = \"#444477\"];")?; 144 | 145 | self.inner.block_ids().try_for_each(|id| { 146 | self.inner.successors(id).try_for_each(|successor| { 147 | let style = if successor <= id { 148 | " [style = dashed]" 149 | } else { 150 | "" 151 | }; 152 | 153 | writeln!(f, "\tN{id} -> N{successor}{style};") 154 | }) 155 | }) 156 | } 157 | } 158 | 159 | impl Display for Dot<'_> { 160 | fn fmt(&self, f: &mut Formatter) -> Result { 161 | writeln!(f, "digraph {{")?; 162 | 163 | self.fmt_nodes(f)?; 164 | self.fmt_edges(f)?; 165 | 166 | writeln!(f, "}}") 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Control Flow/Graph/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![expect(clippy::missing_panics_doc)] 3 | 4 | extern crate alloc; 5 | 6 | mod basic_block; 7 | mod dot; 8 | 9 | pub mod instruction; 10 | 11 | use alloc::vec::Vec; 12 | 13 | use self::instruction::{I32Constant, Instruction, LocalBranch, Name}; 14 | 15 | pub use self::{basic_block::BasicBlock, dot::Dot}; 16 | 17 | /// A directed graph of basic blocks containing instructions. 18 | /// 19 | /// Note that after construction, it is expected that the following invariants hold: 20 | /// 21 | /// * All branches are diamond shaped 22 | /// * All loops are tail controlled 23 | /// * Without back-edges, the graph is in topological order 24 | pub struct ControlFlowGraph { 25 | pub instructions: Vec, 26 | pub basic_blocks: Vec, 27 | } 28 | 29 | impl ControlFlowGraph { 30 | #[must_use] 31 | pub const fn new() -> Self { 32 | Self { 33 | instructions: Vec::new(), 34 | basic_blocks: Vec::new(), 35 | } 36 | } 37 | 38 | #[must_use] 39 | pub fn block_ids(&self) -> core::ops::Range { 40 | 0..self.basic_blocks.len().try_into().unwrap() 41 | } 42 | 43 | #[must_use] 44 | pub fn offsets(&self, id: u16) -> core::ops::Range { 45 | self.basic_blocks[usize::from(id)].range() 46 | } 47 | 48 | #[must_use] 49 | pub fn instructions(&self, id: u16) -> &[Instruction] { 50 | let offsets = self.offsets(id); 51 | 52 | &self.instructions[offsets] 53 | } 54 | 55 | pub fn predecessors(&self, id: u16) -> impl Iterator + '_ { 56 | self.basic_blocks[usize::from(id)] 57 | .predecessors 58 | .iter() 59 | .copied() 60 | } 61 | 62 | pub fn predecessors_acyclic(&self, id: u16) -> impl Iterator + '_ { 63 | self.predecessors(id).filter(move |&id_2| id > id_2) 64 | } 65 | 66 | pub fn successors(&self, id: u16) -> impl Iterator + '_ { 67 | self.basic_blocks[usize::from(id)] 68 | .successors 69 | .iter() 70 | .copied() 71 | } 72 | 73 | pub fn successors_acyclic(&self, id: u16) -> impl Iterator + '_ { 74 | self.successors(id).filter(move |&id_2| id < id_2) 75 | } 76 | 77 | #[must_use] 78 | pub fn is_branch_start(&self, id: u16) -> bool { 79 | let mut successors = self.successors_acyclic(id); 80 | 81 | successors.next().is_some() && successors.next().is_some() 82 | } 83 | 84 | #[must_use] 85 | pub fn is_branch_end(&self, id: u16) -> bool { 86 | let mut predecessors = self.predecessors_acyclic(id); 87 | 88 | predecessors.next().is_some() && predecessors.next().is_some() 89 | } 90 | 91 | #[must_use] 92 | pub fn find_branch_start(&self, id: u16) -> Option { 93 | self.predecessors_acyclic(id) 94 | .find(|&id_2| self.is_branch_start(id_2)) 95 | } 96 | 97 | #[must_use] 98 | pub fn find_branch_end(&self, id: u16) -> Option { 99 | self.successors_acyclic(id) 100 | .find(|&id_2| self.is_branch_end(id_2)) 101 | } 102 | 103 | #[must_use] 104 | pub fn find_repeat_start(&self, id: u16) -> Option { 105 | self.successors(id).find(|&id_2| id >= id_2) 106 | } 107 | 108 | #[must_use] 109 | pub fn find_repeat_end(&self, id: u16) -> Option { 110 | self.predecessors(id).find(|&id_2| id <= id_2) 111 | } 112 | 113 | #[must_use] 114 | pub fn has_single_entry(&self) -> bool { 115 | let mut basic_blocks = self.basic_blocks.iter(); 116 | 117 | basic_blocks.any(BasicBlock::is_source) && !basic_blocks.any(BasicBlock::is_source) 118 | } 119 | 120 | #[must_use] 121 | pub fn has_repeats(&self) -> bool { 122 | self.block_ids() 123 | .any(|id| self.find_repeat_end(id).is_some()) 124 | } 125 | 126 | pub fn add_edge(&mut self, from: u16, to: u16) { 127 | let from_usize = usize::from(from); 128 | let to_usize = usize::from(to); 129 | 130 | self.basic_blocks[to_usize].predecessors.push(from); 131 | self.basic_blocks[from_usize].successors.push(to); 132 | } 133 | 134 | pub fn replace_edge(&mut self, from: u16, to: u16, new: u16) { 135 | let from_usize = usize::from(from); 136 | let to_usize = usize::from(to); 137 | let new_usize = usize::from(new); 138 | 139 | let successor = self.successors(from).position(|id| id == to).unwrap(); 140 | 141 | self.basic_blocks[from_usize].successors[successor] = new; 142 | self.basic_blocks[new_usize].predecessors.push(from); 143 | 144 | let predecessor = self.predecessors(to).position(|id| id == from).unwrap(); 145 | 146 | self.basic_blocks[to_usize].predecessors.remove(predecessor); 147 | } 148 | 149 | fn add_instruction(&mut self, instruction: Instruction) -> u16 { 150 | let id = self.basic_blocks.len().try_into().unwrap(); 151 | let position = self.instructions.len().try_into().unwrap(); 152 | 153 | self.instructions.push(instruction); 154 | 155 | self.basic_blocks 156 | .push(BasicBlock::from_range(position, position + 1)); 157 | 158 | id 159 | } 160 | 161 | #[must_use] 162 | pub fn has_assignment(&self, id: u16, name: Name) -> bool { 163 | matches!(self.instructions(id), &[Instruction::I32Constant(I32Constant { destination, .. })] if destination == name as u16) 164 | } 165 | 166 | pub fn add_no_operation(&mut self) -> u16 { 167 | let id = self.basic_blocks.len().try_into().unwrap(); 168 | let position = self.instructions.len().try_into().unwrap(); 169 | 170 | self.basic_blocks 171 | .push(BasicBlock::from_range(position, position)); 172 | 173 | id 174 | } 175 | 176 | pub fn add_selection(&mut self, name: Name) -> u16 { 177 | let local_branch = Instruction::LocalBranch(LocalBranch { 178 | source: name as u16, 179 | }); 180 | 181 | self.add_instruction(local_branch) 182 | } 183 | 184 | pub fn add_assignment(&mut self, name: Name, value: u16) -> u16 { 185 | let i32_constant = Instruction::I32Constant(I32Constant { 186 | destination: name as u16, 187 | data: value.into(), 188 | }); 189 | 190 | self.add_instruction(i32_constant) 191 | } 192 | } 193 | 194 | impl Default for ControlFlowGraph { 195 | fn default() -> Self { 196 | Self::new() 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /Control Flow/Liveness/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "control-flow-liveness" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | control-flow-graph = { path = "../Graph" } 8 | 9 | set.workspace = true 10 | -------------------------------------------------------------------------------- /Control Flow/Liveness/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![expect(clippy::missing_panics_doc)] 3 | 4 | extern crate alloc; 5 | 6 | pub mod locals; 7 | pub mod references; 8 | -------------------------------------------------------------------------------- /Control Flow/Liveness/src/references.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_graph::instruction::{ 3 | DataDrop, ElementsDrop, GlobalGet, GlobalSet, Instruction, MemoryCopy, MemoryFill, MemoryGrow, 4 | MemoryInit, MemoryLoad, MemorySize, MemoryStore, RefFunction, TableCopy, TableFill, TableGet, 5 | TableGrow, TableInit, TableSet, TableSize, 6 | }; 7 | 8 | #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] 9 | pub enum ReferenceType { 10 | Function, 11 | 12 | Global, 13 | Table, 14 | Elements, 15 | Memory, 16 | Data, 17 | } 18 | 19 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 20 | pub struct Reference { 21 | pub r#type: ReferenceType, 22 | pub id: u16, 23 | } 24 | 25 | fn read_function(references: &mut Vec, function: u16) { 26 | references.push(Reference { 27 | r#type: ReferenceType::Function, 28 | id: function, 29 | }); 30 | } 31 | 32 | fn read_global(references: &mut Vec, global: u16) { 33 | references.push(Reference { 34 | r#type: ReferenceType::Global, 35 | id: global, 36 | }); 37 | } 38 | 39 | fn write_global(references: &mut Vec, global: u16) { 40 | read_global(references, global); 41 | } 42 | 43 | fn read_table(references: &mut Vec, table: u16) { 44 | references.push(Reference { 45 | r#type: ReferenceType::Table, 46 | id: table, 47 | }); 48 | } 49 | 50 | fn write_table(references: &mut Vec, table: u16) { 51 | read_table(references, table); 52 | } 53 | 54 | fn read_elements(references: &mut Vec, elements: u16) { 55 | references.push(Reference { 56 | r#type: ReferenceType::Elements, 57 | id: elements, 58 | }); 59 | } 60 | 61 | fn write_elements(references: &mut Vec, elements: u16) { 62 | read_elements(references, elements); 63 | } 64 | 65 | fn read_memory(references: &mut Vec, memory: u16) { 66 | references.push(Reference { 67 | r#type: ReferenceType::Memory, 68 | id: memory, 69 | }); 70 | } 71 | 72 | fn write_memory(references: &mut Vec, memory: u16) { 73 | read_memory(references, memory); 74 | } 75 | 76 | fn read_data(references: &mut Vec, data: u16) { 77 | references.push(Reference { 78 | r#type: ReferenceType::Data, 79 | id: data, 80 | }); 81 | } 82 | 83 | fn write_data(references: &mut Vec, data: u16) { 84 | read_data(references, data); 85 | } 86 | 87 | fn handle_ref_function(references: &mut Vec, instruction: RefFunction) { 88 | let RefFunction { function, .. } = instruction; 89 | 90 | read_function(references, function); 91 | } 92 | 93 | fn handle_global_get(references: &mut Vec, instruction: GlobalGet) { 94 | let GlobalGet { source, .. } = instruction; 95 | 96 | read_global(references, source); 97 | } 98 | 99 | fn handle_global_set(references: &mut Vec, instruction: GlobalSet) { 100 | let GlobalSet { destination, .. } = instruction; 101 | 102 | write_global(references, destination); 103 | } 104 | 105 | fn handle_table_get(references: &mut Vec, instruction: TableGet) { 106 | let TableGet { source, .. } = instruction; 107 | 108 | read_table(references, source.reference); 109 | } 110 | 111 | fn handle_table_set(references: &mut Vec, instruction: TableSet) { 112 | let TableSet { destination, .. } = instruction; 113 | 114 | write_table(references, destination.reference); 115 | } 116 | 117 | fn handle_table_size(references: &mut Vec, instruction: TableSize) { 118 | let TableSize { table, .. } = instruction; 119 | 120 | read_table(references, table); 121 | } 122 | 123 | fn handle_table_grow(references: &mut Vec, instruction: TableGrow) { 124 | let TableGrow { table, .. } = instruction; 125 | 126 | write_table(references, table); 127 | } 128 | 129 | fn handle_table_fill(references: &mut Vec, instruction: TableFill) { 130 | let TableFill { destination, .. } = instruction; 131 | 132 | write_table(references, destination.reference); 133 | } 134 | 135 | fn handle_table_copy(references: &mut Vec, instruction: TableCopy) { 136 | let TableCopy { 137 | destination, 138 | source, 139 | .. 140 | } = instruction; 141 | 142 | write_table(references, destination.reference); 143 | read_table(references, source.reference); 144 | } 145 | 146 | fn handle_table_init(references: &mut Vec, instruction: TableInit) { 147 | let TableInit { 148 | destination, 149 | source, 150 | .. 151 | } = instruction; 152 | 153 | write_table(references, destination.reference); 154 | read_elements(references, source.reference); 155 | } 156 | 157 | fn handle_elements_drop(references: &mut Vec, instruction: ElementsDrop) { 158 | let ElementsDrop { source } = instruction; 159 | 160 | write_elements(references, source); 161 | } 162 | 163 | fn handle_memory_load(references: &mut Vec, instruction: MemoryLoad) { 164 | let MemoryLoad { source, .. } = instruction; 165 | 166 | read_memory(references, source.reference); 167 | } 168 | 169 | fn handle_memory_store(references: &mut Vec, instruction: MemoryStore) { 170 | let MemoryStore { destination, .. } = instruction; 171 | 172 | write_memory(references, destination.reference); 173 | } 174 | 175 | fn handle_memory_size(references: &mut Vec, instruction: MemorySize) { 176 | let MemorySize { memory, .. } = instruction; 177 | 178 | read_memory(references, memory); 179 | } 180 | 181 | fn handle_memory_grow(references: &mut Vec, instruction: MemoryGrow) { 182 | let MemoryGrow { memory, .. } = instruction; 183 | 184 | write_memory(references, memory); 185 | } 186 | 187 | fn handle_memory_fill(references: &mut Vec, instruction: MemoryFill) { 188 | let MemoryFill { destination, .. } = instruction; 189 | 190 | write_memory(references, destination.reference); 191 | } 192 | 193 | fn handle_memory_copy(references: &mut Vec, instruction: MemoryCopy) { 194 | let MemoryCopy { 195 | destination, 196 | source, 197 | .. 198 | } = instruction; 199 | 200 | write_memory(references, destination.reference); 201 | read_memory(references, source.reference); 202 | } 203 | 204 | fn handle_memory_init(references: &mut Vec, instruction: MemoryInit) { 205 | let MemoryInit { 206 | destination, 207 | source, 208 | .. 209 | } = instruction; 210 | 211 | write_memory(references, destination.reference); 212 | read_data(references, source.reference); 213 | } 214 | 215 | fn handle_data_drop(references: &mut Vec, instruction: DataDrop) { 216 | let DataDrop { source } = instruction; 217 | 218 | write_data(references, source); 219 | } 220 | 221 | fn handle_instruction(references: &mut Vec, instruction: Instruction) { 222 | match instruction { 223 | Instruction::LocalSet(_) 224 | | Instruction::LocalBranch(_) 225 | | Instruction::I32Constant(_) 226 | | Instruction::I64Constant(_) 227 | | Instruction::F32Constant(_) 228 | | Instruction::F64Constant(_) 229 | | Instruction::RefIsNull(_) 230 | | Instruction::RefNull(_) 231 | | Instruction::Call(_) 232 | | Instruction::Unreachable 233 | | Instruction::IntegerUnaryOperation(_) 234 | | Instruction::IntegerBinaryOperation(_) 235 | | Instruction::IntegerCompareOperation(_) 236 | | Instruction::IntegerNarrow(_) 237 | | Instruction::IntegerWiden(_) 238 | | Instruction::IntegerExtend(_) 239 | | Instruction::IntegerConvertToNumber(_) 240 | | Instruction::IntegerTransmuteToNumber(_) 241 | | Instruction::NumberUnaryOperation(_) 242 | | Instruction::NumberBinaryOperation(_) 243 | | Instruction::NumberCompareOperation(_) 244 | | Instruction::NumberNarrow(_) 245 | | Instruction::NumberWiden(_) 246 | | Instruction::NumberTruncateToInteger(_) 247 | | Instruction::NumberTransmuteToInteger(_) => {} 248 | 249 | Instruction::RefFunction(instruction) => handle_ref_function(references, instruction), 250 | Instruction::GlobalGet(instruction) => handle_global_get(references, instruction), 251 | Instruction::GlobalSet(instruction) => handle_global_set(references, instruction), 252 | Instruction::TableGet(instruction) => handle_table_get(references, instruction), 253 | Instruction::TableSet(instruction) => handle_table_set(references, instruction), 254 | Instruction::TableSize(instruction) => handle_table_size(references, instruction), 255 | Instruction::TableGrow(instruction) => handle_table_grow(references, instruction), 256 | Instruction::TableFill(instruction) => handle_table_fill(references, instruction), 257 | Instruction::TableCopy(instruction) => handle_table_copy(references, instruction), 258 | Instruction::TableInit(instruction) => handle_table_init(references, instruction), 259 | Instruction::ElementsDrop(instruction) => handle_elements_drop(references, instruction), 260 | Instruction::MemoryLoad(instruction) => handle_memory_load(references, instruction), 261 | Instruction::MemoryStore(instruction) => handle_memory_store(references, instruction), 262 | Instruction::MemorySize(instruction) => handle_memory_size(references, instruction), 263 | Instruction::MemoryGrow(instruction) => handle_memory_grow(references, instruction), 264 | Instruction::MemoryFill(instruction) => handle_memory_fill(references, instruction), 265 | Instruction::MemoryCopy(instruction) => handle_memory_copy(references, instruction), 266 | Instruction::MemoryInit(instruction) => handle_memory_init(references, instruction), 267 | Instruction::DataDrop(instruction) => handle_data_drop(references, instruction), 268 | } 269 | } 270 | 271 | pub fn track(references: &mut Vec, instructions: &[Instruction]) { 272 | references.clear(); 273 | 274 | for &instruction in instructions { 275 | handle_instruction(references, instruction); 276 | } 277 | 278 | references.sort_unstable(); 279 | references.dedup(); 280 | } 281 | -------------------------------------------------------------------------------- /Control Flow/Structurer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "control-flow-structurer" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | control-flow-graph = { path = "../Graph" } 8 | 9 | set.workspace = true 10 | -------------------------------------------------------------------------------- /Control Flow/Structurer/src/branch/bulk.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_graph::ControlFlowGraph; 3 | 4 | use super::single::Single; 5 | 6 | pub struct Bulk { 7 | single: Single, 8 | 9 | infos: Vec<(u16, u16)>, 10 | } 11 | 12 | impl Bulk { 13 | #[must_use] 14 | pub const fn new() -> Self { 15 | Self { 16 | single: Single::new(), 17 | 18 | infos: Vec::new(), 19 | } 20 | } 21 | 22 | fn find_next_branch(graph: &ControlFlowGraph, mut entry: u16, exit: u16) -> Option { 23 | while entry != exit { 24 | let mut successors = graph.successors(entry).filter(|&id| id != entry); 25 | let successor = successors.next().unwrap(); 26 | 27 | if successors.next().is_none() { 28 | entry = successor; 29 | } else { 30 | return Some(entry); 31 | } 32 | } 33 | 34 | None 35 | } 36 | 37 | fn add_branch(&mut self, graph: &ControlFlowGraph, entry: u16, exit: u16) { 38 | if let Some(entry) = Self::find_next_branch(graph, entry, exit) { 39 | self.infos.push((entry, exit)); 40 | } 41 | } 42 | 43 | fn handle_region(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) { 44 | let point = self.single.run(graph, entry); 45 | 46 | self.add_branch(graph, point, exit); 47 | 48 | for successor in graph.successors(entry) { 49 | self.add_branch(graph, successor, point); 50 | } 51 | } 52 | 53 | pub fn run(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) { 54 | self.add_branch(graph, entry, exit); 55 | 56 | while let Some((entry, exit)) = self.infos.pop() { 57 | self.handle_region(graph, entry, exit); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Control Flow/Structurer/src/branch/continuation_finder.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_graph::ControlFlowGraph; 3 | use set::Set; 4 | 5 | pub struct ContinuationFinder { 6 | points: Vec, 7 | excluded: Vec, 8 | 9 | expanded: Set, 10 | seen: Set, 11 | stack: Vec, 12 | } 13 | 14 | impl ContinuationFinder { 15 | pub const fn new() -> Self { 16 | Self { 17 | points: Vec::new(), 18 | excluded: Vec::new(), 19 | 20 | expanded: Set::new(), 21 | seen: Set::new(), 22 | stack: Vec::new(), 23 | } 24 | } 25 | 26 | pub fn set_excluded>(&mut self, iter: I) { 27 | self.excluded.clear(); 28 | self.excluded.extend(iter); 29 | self.excluded.sort_unstable(); 30 | self.excluded.dedup(); 31 | } 32 | 33 | pub fn edges_into(&self, graph: &ControlFlowGraph, container: &mut Vec<(u16, u16)>) { 34 | for &point in &self.points { 35 | for predecessor in graph.predecessors(point) { 36 | if self.expanded.contains(predecessor.into()) { 37 | container.push((predecessor, point)); 38 | } 39 | } 40 | } 41 | } 42 | 43 | fn dominates(&self, graph: &ControlFlowGraph, id: u16) -> bool { 44 | self.excluded.binary_search(&id).is_err() 45 | && graph 46 | .predecessors(id) 47 | .all(|predecessor| id == predecessor || self.expanded.contains(predecessor.into())) 48 | } 49 | 50 | fn add_successor(&mut self, id: u16) { 51 | if self.seen.grow_insert(id.into()) { 52 | return; 53 | } 54 | 55 | self.stack.push(id); 56 | } 57 | 58 | fn set_entry(&mut self, graph: &ControlFlowGraph, entry: u16) { 59 | if self.excluded.binary_search(&entry).is_ok() { 60 | let predecessor = graph.predecessors(entry).next().unwrap(); 61 | 62 | self.points.push(entry); 63 | self.expanded.grow_insert(predecessor.into()); 64 | } else { 65 | let entry_usize = entry.into(); 66 | 67 | self.seen.clear(); 68 | self.seen.grow_insert(entry_usize); 69 | self.expanded.grow_insert(entry_usize); 70 | 71 | for successor in graph.successors(entry) { 72 | self.add_successor(successor); 73 | } 74 | } 75 | } 76 | 77 | fn handle_stack(&mut self, graph: &ControlFlowGraph) -> bool { 78 | let mut changed = false; 79 | 80 | while let Some(id) = self.stack.pop() { 81 | if self.dominates(graph, id) { 82 | self.expanded.grow_insert(id.into()); 83 | 84 | for successor in graph.successors(id) { 85 | self.add_successor(successor); 86 | } 87 | 88 | changed = true; 89 | } else { 90 | self.points.push(id); 91 | } 92 | } 93 | 94 | changed 95 | } 96 | 97 | pub fn run(&mut self, graph: &ControlFlowGraph, entry: u16) { 98 | self.points.clear(); 99 | self.expanded.clear(); 100 | 101 | self.set_entry(graph, entry); 102 | 103 | while self.handle_stack(graph) { 104 | core::mem::swap(&mut self.points, &mut self.stack); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Control Flow/Structurer/src/branch/mod.rs: -------------------------------------------------------------------------------- 1 | mod continuation_finder; 2 | mod single; 3 | 4 | pub mod bulk; 5 | -------------------------------------------------------------------------------- /Control Flow/Structurer/src/branch/single.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_graph::{ControlFlowGraph, instruction::Name}; 3 | 4 | use super::continuation_finder::ContinuationFinder; 5 | 6 | pub struct Single { 7 | edges: Vec<(u16, u16)>, 8 | points: Vec, 9 | separators: Vec, 10 | 11 | continuation_finder: ContinuationFinder, 12 | } 13 | 14 | impl Single { 15 | pub const fn new() -> Self { 16 | Self { 17 | edges: Vec::new(), 18 | points: Vec::new(), 19 | separators: Vec::new(), 20 | 21 | continuation_finder: ContinuationFinder::new(), 22 | } 23 | } 24 | 25 | fn has_assignment_in_branch(&self, graph: &ControlFlowGraph) -> bool { 26 | self.points.iter().any(|&id| { 27 | graph.predecessors(id).any(|id| { 28 | graph.has_assignment(id, Name::A) && self.edges.iter().any(|edge| edge.0 == id) 29 | }) 30 | }) 31 | } 32 | 33 | fn has_assignment_in_tail(&self, graph: &ControlFlowGraph) -> bool { 34 | self.points.iter().any(|&id| { 35 | graph.predecessors(id).any(|id| { 36 | graph.has_assignment(id, Name::A) && self.edges.iter().all(|edge| edge.0 != id) 37 | }) 38 | }) 39 | } 40 | 41 | fn exclude_last_assignments(&mut self, graph: &ControlFlowGraph) { 42 | let excluded = self.points.iter().flat_map(|&id| { 43 | graph.predecessors(id).filter_map(|id| { 44 | if graph.has_assignment(id, Name::A) { 45 | let mut predecessors = graph.predecessors(id); 46 | 47 | let id = if let Some(id) = predecessors.next() 48 | && predecessors.next().is_none() 49 | && graph.has_assignment(id, Name::C) 50 | { 51 | id 52 | } else { 53 | id 54 | }; 55 | 56 | Some(id) 57 | } else { 58 | None 59 | } 60 | }) 61 | }); 62 | 63 | self.continuation_finder.set_excluded(excluded); 64 | } 65 | 66 | fn find_continuations(&mut self, graph: &ControlFlowGraph, entry: u16, id: u16) { 67 | let mut predecessors = graph.predecessors(id).filter(|&other| other != id); 68 | 69 | if predecessors.next().is_some() && predecessors.next().is_some() { 70 | self.edges.push((entry, id)); 71 | } else { 72 | self.continuation_finder.run(graph, id); 73 | self.continuation_finder.edges_into(graph, &mut self.edges); 74 | } 75 | 76 | self.separators.push(self.edges.len()); 77 | } 78 | 79 | fn find_all_continuations(&mut self, graph: &ControlFlowGraph, entry: u16) { 80 | self.edges.clear(); 81 | self.separators.clear(); 82 | 83 | for successor in graph.successors(entry) { 84 | self.find_continuations(graph, entry, successor); 85 | } 86 | 87 | self.points.clear(); 88 | self.points.extend(self.edges.iter().map(|edge| edge.1)); 89 | self.points.sort_unstable(); 90 | self.points.dedup(); 91 | } 92 | 93 | fn set_new_continuation(&mut self, graph: &mut ControlFlowGraph) -> u16 { 94 | let selection = graph.add_selection(Name::A); 95 | 96 | for (from, to) in &mut self.edges { 97 | let point = self.points.binary_search(to).unwrap().try_into().unwrap(); 98 | let assignment = graph.add_assignment(Name::A, point); 99 | 100 | graph.replace_edge(*from, *to, assignment); 101 | graph.add_edge(assignment, selection); 102 | 103 | *from = assignment; 104 | *to = selection; 105 | } 106 | 107 | for &point in &self.points { 108 | graph.add_edge(selection, point); 109 | } 110 | 111 | selection 112 | } 113 | 114 | fn find_or_set_continuation(&mut self, graph: &mut ControlFlowGraph, entry: u16) -> u16 { 115 | if let &[point] = self.points.as_slice() { 116 | point 117 | } else { 118 | if self.has_assignment_in_tail(graph) && self.has_assignment_in_branch(graph) { 119 | self.exclude_last_assignments(graph); 120 | self.find_all_continuations(graph, entry); 121 | } 122 | 123 | self.set_new_continuation(graph) 124 | } 125 | } 126 | 127 | fn set_continuation_merges(&self, graph: &mut ControlFlowGraph, point: u16) { 128 | let mut start = 0; 129 | 130 | for &end in &self.separators { 131 | let continuations = &self.edges[start..end]; 132 | 133 | start = end; 134 | 135 | if continuations.len() > 1 { 136 | let dummy = graph.add_no_operation(); 137 | 138 | for &(from, to) in continuations { 139 | debug_assert_eq!(point, to, "only one continuation should remain"); 140 | 141 | graph.replace_edge(from, to, dummy); 142 | } 143 | 144 | graph.add_edge(dummy, point); 145 | } 146 | } 147 | } 148 | 149 | // We add dummy nodes to empty branches to ensure symmetry. This is done 150 | // last as we don't always know which branches are empty at the start. 151 | fn fill_empty_branches(graph: &mut ControlFlowGraph, entry: u16, point: u16) { 152 | let count = graph.successors(entry).filter(|&id| id == point).count(); 153 | 154 | for _ in 0..count { 155 | let dummy = graph.add_no_operation(); 156 | 157 | graph.replace_edge(entry, point, dummy); 158 | graph.add_edge(dummy, point); 159 | } 160 | } 161 | 162 | pub fn run(&mut self, graph: &mut ControlFlowGraph, entry: u16) -> u16 { 163 | self.continuation_finder.set_excluded(core::iter::empty()); 164 | 165 | self.find_all_continuations(graph, entry); 166 | 167 | let point = self.find_or_set_continuation(graph, entry); 168 | 169 | self.set_continuation_merges(graph, point); 170 | Self::fill_empty_branches(graph, entry, point); 171 | 172 | point 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Control Flow/Structurer/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Resources: 2 | // "Efficient Control Flow Restructuring for GPUs", 3 | // by Nico Reissmann, Thomas L. Falch, Benjamin A. Bjørnseth, 4 | // Helge Bahmann, Jan Christian Meyer, and Magnus Jahre. 5 | #![no_std] 6 | 7 | extern crate alloc; 8 | 9 | mod branch; 10 | mod repeat; 11 | mod single_exit_patcher; 12 | 13 | use control_flow_graph::ControlFlowGraph; 14 | 15 | use self::{ 16 | branch::bulk::Bulk as Branch, repeat::bulk::Bulk as Repeat, 17 | single_exit_patcher::SingleExitPatcher, 18 | }; 19 | 20 | pub struct Structurer { 21 | repeat: Repeat, 22 | branch: Branch, 23 | 24 | single_exit_patcher: SingleExitPatcher, 25 | } 26 | 27 | impl Structurer { 28 | #[must_use] 29 | pub const fn new() -> Self { 30 | Self { 31 | repeat: Repeat::new(), 32 | branch: Branch::new(), 33 | 34 | single_exit_patcher: SingleExitPatcher::new(), 35 | } 36 | } 37 | 38 | pub fn handle_repeats(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) { 39 | self.repeat.run(graph, entry, exit); 40 | } 41 | 42 | pub fn handle_exits(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) { 43 | self.single_exit_patcher.run(graph, entry, exit); 44 | } 45 | 46 | pub fn disable_repeats(&self, graph: &mut ControlFlowGraph) { 47 | for &(entry, latch) in self.repeat.infos() { 48 | graph.replace_edge(latch, entry, latch); 49 | } 50 | } 51 | 52 | pub fn enable_repeats(&self, graph: &mut ControlFlowGraph) { 53 | for &(entry, latch) in self.repeat.infos() { 54 | graph.replace_edge(latch, latch, entry); 55 | } 56 | } 57 | 58 | pub fn handle_branches(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) { 59 | self.branch.run(graph, entry, exit); 60 | } 61 | 62 | pub fn run(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) { 63 | self.handle_repeats(graph, entry, exit); 64 | self.handle_exits(graph, entry, exit); 65 | self.disable_repeats(graph); 66 | self.handle_branches(graph, entry, exit); 67 | self.enable_repeats(graph); 68 | } 69 | } 70 | 71 | impl Default for Structurer { 72 | fn default() -> Self { 73 | Self::new() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Control Flow/Structurer/src/repeat/bulk.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_graph::ControlFlowGraph; 3 | 4 | use super::{single::Single, strongly_connected_finder::StronglyConnectedFinder}; 5 | 6 | pub struct Bulk { 7 | single: Single, 8 | 9 | infos: Vec<(u16, u16)>, 10 | strongly_connected_finder: StronglyConnectedFinder, 11 | } 12 | 13 | impl Bulk { 14 | #[must_use] 15 | pub const fn new() -> Self { 16 | Self { 17 | single: Single::new(), 18 | infos: Vec::new(), 19 | 20 | strongly_connected_finder: StronglyConnectedFinder::new(), 21 | } 22 | } 23 | 24 | #[must_use] 25 | pub fn infos(&self) -> &[(u16, u16)] { 26 | &self.infos 27 | } 28 | 29 | fn handle_region(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) { 30 | self.strongly_connected_finder.run(graph, entry, exit); 31 | self.strongly_connected_finder.for_each(|region| { 32 | let item = self.single.run(graph, region); 33 | 34 | self.infos.push(item); 35 | }); 36 | } 37 | 38 | pub fn run(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) { 39 | let mut index = 0; 40 | 41 | self.infos.clear(); 42 | 43 | self.handle_region(graph, entry, exit); 44 | 45 | while index < self.infos.len() { 46 | let (entry, latch) = self.infos[index]; 47 | 48 | index += 1; 49 | 50 | self.handle_region(graph, entry, latch); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Control Flow/Structurer/src/repeat/mod.rs: -------------------------------------------------------------------------------- 1 | mod single; 2 | mod strongly_connected_finder; 3 | 4 | pub mod bulk; 5 | -------------------------------------------------------------------------------- /Control Flow/Structurer/src/repeat/single.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_graph::{ControlFlowGraph, instruction::Name}; 3 | use set::{Set, Slice}; 4 | 5 | pub struct Single { 6 | entries: Vec, 7 | exits: Vec, 8 | 9 | region: Set, 10 | temporary: Vec, 11 | } 12 | 13 | impl Single { 14 | pub const fn new() -> Self { 15 | Self { 16 | entries: Vec::new(), 17 | exits: Vec::new(), 18 | 19 | region: Set::new(), 20 | temporary: Vec::new(), 21 | } 22 | } 23 | 24 | fn set_region_contents(&mut self, region: &[u16]) { 25 | let region = region.iter().copied().map(usize::from); 26 | 27 | self.region.clear(); 28 | self.region.extend(region); 29 | } 30 | 31 | fn find_entries_and_exits(&mut self, graph: &ControlFlowGraph) { 32 | self.entries.clear(); 33 | self.exits.clear(); 34 | 35 | for id in self.region.ascending() { 36 | let id = id.try_into().unwrap(); 37 | 38 | if graph 39 | .predecessors(id) 40 | .any(|id| !self.region.contains(id.into())) 41 | { 42 | self.entries.push(id); 43 | } 44 | 45 | self.exits.extend( 46 | graph 47 | .successors(id) 48 | .filter(|&id| !self.region.contains(id.into())), 49 | ); 50 | } 51 | 52 | self.exits.sort_unstable(); 53 | self.exits.dedup(); 54 | } 55 | 56 | fn set_new_entry(&mut self, graph: &mut ControlFlowGraph) -> u16 { 57 | let selection = graph.add_selection(Name::C); 58 | 59 | for (&entry, index) in self.entries.iter().zip(0..) { 60 | self.temporary.clear(); 61 | self.temporary.extend(graph.predecessors(entry)); 62 | 63 | for &predecessor in &self.temporary { 64 | let assignment = graph.add_assignment(Name::C, index); 65 | 66 | graph.replace_edge(predecessor, entry, assignment); 67 | graph.add_edge(assignment, selection); 68 | } 69 | 70 | graph.add_edge(selection, entry); 71 | } 72 | 73 | selection 74 | } 75 | 76 | fn find_or_set_entry(&mut self, graph: &mut ControlFlowGraph) -> u16 { 77 | if let &[entry] = self.entries.as_slice() { 78 | entry 79 | } else { 80 | self.set_new_entry(graph) 81 | } 82 | } 83 | 84 | fn set_new_exit(&mut self, graph: &mut ControlFlowGraph) -> u16 { 85 | let selection = graph.add_selection(Name::C); 86 | 87 | for (&exit, index) in self.exits.iter().zip(0..) { 88 | self.temporary.clear(); 89 | self.temporary.extend( 90 | graph 91 | .predecessors(exit) 92 | .filter(|&id| self.region.contains(id.into())), 93 | ); 94 | 95 | for &predecessor in &self.temporary { 96 | let assignment = graph.add_assignment(Name::C, index); 97 | 98 | graph.replace_edge(predecessor, exit, assignment); 99 | graph.add_edge(assignment, selection); 100 | } 101 | 102 | graph.add_edge(selection, exit); 103 | } 104 | 105 | selection 106 | } 107 | 108 | fn find_or_set_exit(&mut self, graph: &mut ControlFlowGraph) -> u16 { 109 | match self.exits.as_slice() { 110 | [exit] => *exit, 111 | [] => graph.add_no_operation(), 112 | [..] => self.set_new_exit(graph), 113 | } 114 | } 115 | 116 | // Either the `target` is in the `region`, or one of its predecessors which was added during this pass is. 117 | fn in_region(graph: &ControlFlowGraph, region: Slice, target: u16) -> bool { 118 | region.contains(target.into()) 119 | || graph 120 | .predecessors(target) 121 | .any(|id| region.contains(id.into())) 122 | } 123 | 124 | fn in_region_acyclic(graph: &ControlFlowGraph, region: Slice, target: u16, exit: u16) -> bool { 125 | target != exit && Self::in_region(graph, region, target) 126 | } 127 | 128 | fn find_latch(&self, graph: &ControlFlowGraph, entry: u16, exit: u16) -> Option { 129 | let mut repetitions = graph 130 | .predecessors(entry) 131 | .filter(|&id| Self::in_region(graph, self.region.as_slice(), id)); 132 | 133 | if let Some(repetition) = repetitions.next() { 134 | let mut escapes = graph 135 | .predecessors(exit) 136 | .filter(|&id| Self::in_region_acyclic(graph, self.region.as_slice(), id, exit)); 137 | 138 | if let Some(escape) = escapes.next() 139 | && repetition == escape 140 | && repetitions.next().is_none() 141 | && escapes.next().is_none() 142 | { 143 | let mut edges = graph.successors(repetition); 144 | 145 | if edges.next().is_some() && edges.next().is_some() && edges.next().is_none() { 146 | return Some(repetition); 147 | } 148 | } 149 | } 150 | 151 | None 152 | } 153 | 154 | fn set_break(&mut self, graph: &mut ControlFlowGraph, latch: u16, selection: u16) { 155 | self.temporary.clear(); 156 | self.temporary.extend( 157 | graph.predecessors(selection).filter(|&id| { 158 | Self::in_region_acyclic(graph, self.region.as_slice(), id, selection) 159 | }), 160 | ); 161 | 162 | for &exit in &self.temporary { 163 | let assignment = graph.add_assignment(Name::B, 0); 164 | 165 | graph.replace_edge(exit, selection, assignment); 166 | graph.add_edge(assignment, latch); 167 | } 168 | } 169 | 170 | fn set_continue(&mut self, graph: &mut ControlFlowGraph, latch: u16, selection: u16) { 171 | self.temporary.clear(); 172 | self.temporary.extend( 173 | graph 174 | .predecessors(selection) 175 | .filter(|&id| Self::in_region(graph, self.region.as_slice(), id)), 176 | ); 177 | 178 | for &entry in &self.temporary { 179 | let assignment = graph.add_assignment(Name::B, 1); 180 | 181 | graph.replace_edge(entry, selection, assignment); 182 | graph.add_edge(assignment, latch); 183 | } 184 | } 185 | 186 | fn set_new_latch(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) -> u16 { 187 | let selection = graph.add_selection(Name::B); 188 | 189 | self.set_break(graph, selection, exit); 190 | self.set_continue(graph, selection, entry); 191 | 192 | graph.add_edge(selection, exit); 193 | graph.add_edge(selection, entry); 194 | 195 | selection 196 | } 197 | 198 | fn find_or_set_latch(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) -> u16 { 199 | self.find_latch(graph, entry, exit) 200 | .unwrap_or_else(|| self.set_new_latch(graph, entry, exit)) 201 | } 202 | 203 | pub fn run(&mut self, graph: &mut ControlFlowGraph, region: &[u16]) -> (u16, u16) { 204 | self.set_region_contents(region); 205 | self.find_entries_and_exits(graph); 206 | 207 | let entry = self.find_or_set_entry(graph); 208 | let exit = self.find_or_set_exit(graph); 209 | let latch = self.find_or_set_latch(graph, entry, exit); 210 | 211 | (entry, latch) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Control Flow/Structurer/src/repeat/strongly_connected_finder.rs: -------------------------------------------------------------------------------- 1 | // Resources: 2 | // "Kosaraju's Strongly Connected Components", 3 | // by S. Rao Kosaraju 4 | 5 | use alloc::vec::Vec; 6 | use control_flow_graph::ControlFlowGraph; 7 | use set::Set; 8 | 9 | struct DepthFirstSearcher { 10 | seen: Set, 11 | stack: Vec<(u16, bool)>, 12 | } 13 | 14 | impl DepthFirstSearcher { 15 | const fn new() -> Self { 16 | Self { 17 | seen: Set::new(), 18 | stack: Vec::new(), 19 | } 20 | } 21 | 22 | fn reset(&mut self, graph: &ControlFlowGraph, entry: u16, exit: u16) { 23 | self.seen.clear(); 24 | self.seen.extend(graph.predecessors(entry).map(usize::from)); 25 | 26 | for id in graph.successors(entry) { 27 | self.seen.remove(id.into()); 28 | } 29 | 30 | self.seen.grow_insert(exit.into()); 31 | } 32 | 33 | fn add_successor(&mut self, id: u16) { 34 | if self.seen.contains(id.into()) { 35 | return; 36 | } 37 | 38 | self.stack.push((id, false)); 39 | } 40 | 41 | fn run(&mut self, result: &mut Vec, entry: u16, successors: H) 42 | where 43 | H: Fn(u16) -> I, 44 | I: IntoIterator, 45 | { 46 | self.add_successor(entry); 47 | 48 | while let Some((id, post)) = self.stack.pop() { 49 | if self.seen.grow_insert(id.into()) { 50 | if post { 51 | result.push(id); 52 | } 53 | } else { 54 | self.stack.push((id, true)); 55 | 56 | for id in successors(id) { 57 | self.add_successor(id); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | pub struct StronglyConnectedFinder { 65 | separators: Vec, 66 | results: Vec, 67 | post: Vec, 68 | 69 | depth_first_searcher: DepthFirstSearcher, 70 | } 71 | 72 | impl StronglyConnectedFinder { 73 | #[must_use] 74 | pub const fn new() -> Self { 75 | Self { 76 | separators: Vec::new(), 77 | results: Vec::new(), 78 | post: Vec::new(), 79 | 80 | depth_first_searcher: DepthFirstSearcher::new(), 81 | } 82 | } 83 | 84 | pub fn for_each(&self, mut handler: H) { 85 | let mut start = 0; 86 | 87 | for &end in &self.separators { 88 | handler(&self.results[start..end]); 89 | 90 | start = end; 91 | } 92 | } 93 | 94 | fn find_successors(&mut self, graph: &ControlFlowGraph, entry: u16, exit: u16) { 95 | self.post.clear(); 96 | 97 | self.depth_first_searcher.reset(graph, entry, exit); 98 | self.depth_first_searcher 99 | .run(&mut self.post, entry, |id| graph.successors(id)); 100 | } 101 | 102 | fn should_store(graph: &ControlFlowGraph, list: &[u16]) -> bool { 103 | if let &[only] = list { 104 | graph.predecessors(only).any(|id| id == only) 105 | } else { 106 | !list.is_empty() 107 | } 108 | } 109 | 110 | fn find_predecessors(&mut self, graph: &ControlFlowGraph, entry: u16, exit: u16) { 111 | self.separators.clear(); 112 | self.results.clear(); 113 | 114 | self.depth_first_searcher.reset(graph, entry, exit); 115 | 116 | let mut start = 0; 117 | 118 | while let Some(id) = self.post.pop() { 119 | self.depth_first_searcher 120 | .run(&mut self.results, id, |id| graph.predecessors(id)); 121 | 122 | if Self::should_store(graph, &self.results[start..]) { 123 | start = self.results.len(); 124 | 125 | self.separators.push(start); 126 | } else { 127 | self.results.truncate(start); 128 | } 129 | } 130 | } 131 | 132 | pub fn run(&mut self, graph: &ControlFlowGraph, entry: u16, exit: u16) { 133 | self.find_successors(graph, entry, exit); 134 | self.find_predecessors(graph, entry, exit); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Control Flow/Structurer/src/single_exit_patcher.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_graph::ControlFlowGraph; 3 | use set::Set; 4 | 5 | pub struct SingleExitPatcher { 6 | seen: Set, 7 | stack: Vec, 8 | } 9 | 10 | impl SingleExitPatcher { 11 | pub const fn new() -> Self { 12 | Self { 13 | seen: Set::new(), 14 | stack: Vec::new(), 15 | } 16 | } 17 | 18 | fn add_successor(&mut self, id: u16) { 19 | if self.seen.grow_insert(id.into()) { 20 | return; 21 | } 22 | 23 | self.stack.push(id); 24 | } 25 | 26 | pub fn run(&mut self, graph: &mut ControlFlowGraph, entry: u16, exit: u16) { 27 | self.seen.clear(); 28 | self.seen.grow_insert(exit.into()); 29 | 30 | self.add_successor(entry); 31 | 32 | while let Some(id) = self.stack.pop() { 33 | let mut sink = true; 34 | 35 | for id in graph.successors(id) { 36 | self.add_successor(id); 37 | 38 | sink = false; 39 | } 40 | 41 | if sink { 42 | graph.add_edge(id, exit); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Data Flow/Builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "data-flow-builder" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | control-flow-graph = { path = "../../Control Flow/Graph" } 8 | control-flow-builder = { path = "../../Control Flow/Builder" } 9 | control-flow-liveness = { path = "../../Control Flow/Liveness" } 10 | data-flow-graph = { path = "../Graph" } 11 | 12 | list.workspace = true 13 | wasmparser.workspace = true 14 | -------------------------------------------------------------------------------- /Data Flow/Builder/src/control_flow_converter/dependency_map.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_liveness::references::{Reference, ReferenceType}; 3 | use data_flow_graph::Link; 4 | 5 | pub struct DependencyMap { 6 | buffer: Vec<(Reference, Link)>, 7 | } 8 | 9 | impl DependencyMap { 10 | pub const fn new() -> Self { 11 | Self { buffer: Vec::new() } 12 | } 13 | 14 | pub fn fill_keys(&mut self, keys: &[Reference]) { 15 | let keys = keys.iter().map(|&key| (key, Link::DANGLING)); 16 | 17 | self.buffer.clear(); 18 | self.buffer.extend(keys); 19 | } 20 | 21 | pub fn fill_values(&mut self, values: I) 22 | where 23 | I: IntoIterator, 24 | { 25 | self.buffer 26 | .iter_mut() 27 | .zip(values) 28 | .for_each(|(item, value)| item.1 = value); 29 | } 30 | 31 | fn position(&self, r#type: ReferenceType, id: u16) -> usize { 32 | self.buffer 33 | .binary_search_by_key(&Reference { r#type, id }, |data| data.0) 34 | .unwrap() 35 | } 36 | 37 | pub fn get(&self, r#type: ReferenceType, id: u16) -> Link { 38 | let position = self.position(r#type, id); 39 | 40 | self.buffer[position].1 41 | } 42 | 43 | pub fn set(&mut self, r#type: ReferenceType, id: u16, value: Link) { 44 | let position = self.position(r#type, id); 45 | 46 | self.buffer[position].1 = value; 47 | } 48 | 49 | pub fn extend_into(&self, links: &mut Vec) { 50 | links.extend(self.buffer.iter().map(|item| item.1)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Data Flow/Builder/src/control_flow_converter/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_graph::ControlFlowGraph; 3 | use control_flow_liveness::{locals::Locals, references::Reference}; 4 | use data_flow_graph::{ 5 | DataFlowGraph, Link, 6 | control::{LambdaIn, ValueType}, 7 | }; 8 | 9 | use self::{basic_block_converter::BasicBlockConverter, region_stack::RegionStack}; 10 | 11 | mod basic_block_converter; 12 | mod dependency_map; 13 | mod region_stack; 14 | 15 | pub struct ControlFlowConverter { 16 | basic_block_converter: BasicBlockConverter, 17 | 18 | region_stack: RegionStack, 19 | successors: Vec, 20 | } 21 | 22 | impl ControlFlowConverter { 23 | #[must_use] 24 | pub const fn new() -> Self { 25 | Self { 26 | basic_block_converter: BasicBlockConverter::new(), 27 | 28 | region_stack: RegionStack::new(), 29 | successors: Vec::new(), 30 | } 31 | } 32 | 33 | fn handle_repeat_start(&mut self, graph: &mut DataFlowGraph, locals: &[u16]) { 34 | let arguments = self.basic_block_converter.get_active_bindings(locals); 35 | 36 | let theta_in = graph.add_theta_in(arguments); 37 | 38 | self.basic_block_converter 39 | .set_active_bindings(theta_in, locals); 40 | 41 | self.region_stack.push(theta_in); 42 | } 43 | 44 | fn handle_repeat_end(&mut self, graph: &mut DataFlowGraph, condition: Link, locals: &[u16]) { 45 | let results = self.basic_block_converter.get_active_bindings(locals); 46 | 47 | let theta_in = self.region_stack.pop(); 48 | let theta_out = graph.add_theta_out(theta_in, results, condition); 49 | 50 | self.basic_block_converter 51 | .set_active_bindings(theta_out, locals); 52 | } 53 | 54 | fn handle_branch_start(&mut self, graph: &mut DataFlowGraph, condition: Link) { 55 | let arguments = self 56 | .basic_block_converter 57 | .get_active_bindings(&self.successors); 58 | 59 | let gamma_in = graph.add_gamma_in(arguments, condition); 60 | 61 | self.region_stack.push_gamma(); 62 | self.region_stack.push(gamma_in); 63 | } 64 | 65 | fn handle_branch_end(&mut self, graph: &mut DataFlowGraph, locals: &[u16]) { 66 | let regions = self.region_stack.pop_gamma(); 67 | 68 | let gamma_in = self.region_stack.pop(); 69 | let gamma_out = graph.add_gamma_out(gamma_in, regions); 70 | 71 | self.basic_block_converter 72 | .set_active_bindings(gamma_out, locals); 73 | } 74 | 75 | fn handle_path_start(&mut self, graph: &mut DataFlowGraph) { 76 | let gamma_in = self.region_stack.peek_gamma(); 77 | let region_in = graph.add_region_in(gamma_in); 78 | 79 | self.region_stack.push(region_in); 80 | 81 | self.basic_block_converter 82 | .set_active_bindings(region_in, &self.successors); 83 | } 84 | 85 | fn handle_path_end(&mut self, graph: &mut DataFlowGraph, locals: &[u16]) { 86 | let results = self.basic_block_converter.get_active_bindings(locals); 87 | 88 | let region_in = self.region_stack.pop(); 89 | let region_out = graph.add_region_out(region_in, results); 90 | 91 | self.region_stack.push(region_out); 92 | } 93 | 94 | fn handle_basic_block( 95 | &mut self, 96 | data_flow_graph: &mut DataFlowGraph, 97 | control_flow_graph: &ControlFlowGraph, 98 | id: u16, 99 | locals: &Locals, 100 | ) { 101 | // We just started down the paths in a branch. 102 | if let Some(start) = control_flow_graph.find_branch_start(id) { 103 | locals.get_union(control_flow_graph.successors(start), &mut self.successors); 104 | 105 | self.handle_path_start(data_flow_graph); 106 | } 107 | // We just finished a branch region. 108 | else if control_flow_graph.is_branch_end(id) { 109 | self.handle_branch_end(data_flow_graph, locals.get(id)); 110 | } 111 | 112 | // We just started a repeat region. 113 | if control_flow_graph.find_repeat_end(id).is_some() { 114 | self.handle_repeat_start(data_flow_graph, locals.get(id)); 115 | } 116 | 117 | self.basic_block_converter 118 | .run(data_flow_graph, control_flow_graph.instructions(id)); 119 | 120 | // We just started a branch region. 121 | if control_flow_graph.is_branch_start(id) { 122 | let condition = self.basic_block_converter.get_condition(); 123 | 124 | locals.get_union(control_flow_graph.successors(id), &mut self.successors); 125 | 126 | self.handle_branch_start(data_flow_graph, condition); 127 | 128 | return; 129 | } 130 | 131 | // We just ended a repeat region. 132 | if let Some(start) = control_flow_graph.find_repeat_start(id) { 133 | let condition = self.basic_block_converter.get_condition(); 134 | 135 | self.handle_repeat_end(data_flow_graph, condition, locals.get(start)); 136 | } 137 | 138 | // We just finished a path in a branch. 139 | if let Some(end) = control_flow_graph.find_branch_end(id) { 140 | self.handle_path_end(data_flow_graph, locals.get(end)); 141 | } 142 | } 143 | 144 | pub fn set_function_data( 145 | &mut self, 146 | graph: &mut DataFlowGraph, 147 | lambda_in: u32, 148 | stack_size: u16, 149 | local_types: &[ValueType], 150 | dependencies: &[Reference], 151 | ) { 152 | let LambdaIn { r#type, .. } = graph.get(lambda_in).as_lambda_in().unwrap(); 153 | 154 | let arguments = r#type.arguments.len(); 155 | 156 | self.basic_block_converter 157 | .set_function_inputs(lambda_in, arguments, dependencies); 158 | 159 | self.basic_block_converter 160 | .set_local_types(graph, local_types); 161 | 162 | self.basic_block_converter.set_stack_size(graph, stack_size); 163 | } 164 | 165 | pub fn run( 166 | &mut self, 167 | data_flow_graph: &mut DataFlowGraph, 168 | control_flow_graph: &ControlFlowGraph, 169 | lambda_in: u32, 170 | locals: &Locals, 171 | ) -> Vec { 172 | let LambdaIn { r#type, .. } = data_flow_graph.get(lambda_in).as_lambda_in().unwrap(); 173 | 174 | let results = r#type.results.len(); 175 | 176 | for id in control_flow_graph.block_ids() { 177 | self.handle_basic_block(data_flow_graph, control_flow_graph, id, locals); 178 | } 179 | 180 | self.basic_block_converter.get_function_outputs(results) 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Data Flow/Builder/src/control_flow_converter/region_stack.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | pub struct RegionStack { 4 | gammas: Vec, 5 | ids: Vec, 6 | } 7 | 8 | impl RegionStack { 9 | pub const fn new() -> Self { 10 | Self { 11 | gammas: Vec::new(), 12 | ids: Vec::new(), 13 | } 14 | } 15 | 16 | pub fn push_gamma(&mut self) { 17 | self.gammas.push(self.ids.len()); 18 | } 19 | 20 | pub fn peek_gamma(&self) -> u32 { 21 | let start = *self.gammas.last().unwrap(); 22 | 23 | self.ids[start] 24 | } 25 | 26 | pub fn pop_gamma(&mut self) -> Vec { 27 | let start = self.gammas.pop().unwrap() + 1; 28 | 29 | self.ids.split_off(start) 30 | } 31 | 32 | pub fn push(&mut self, id: u32) { 33 | self.ids.push(id); 34 | } 35 | 36 | pub fn pop(&mut self) -> u32 { 37 | self.ids.pop().unwrap() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Data Flow/Builder/src/function_builder.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_builder::{ControlFlowBuilder, Types}; 3 | use control_flow_graph::ControlFlowGraph; 4 | use control_flow_liveness::{ 5 | locals::{LocalTracker, Locals}, 6 | references::{self, Reference}, 7 | }; 8 | use data_flow_graph::{ 9 | DataFlowGraph, Link, 10 | control::{FunctionType, ValueType}, 11 | }; 12 | use list::resizable::Resizable; 13 | use wasmparser::{BlockType, FunctionBody, LocalsReader, OperatorsReader, ValType}; 14 | 15 | use crate::{control_flow_converter::ControlFlowConverter, global_state::GlobalState}; 16 | 17 | fn web_type_to_data_type(r#type: ValType) -> ValueType { 18 | match r#type { 19 | ValType::I32 => ValueType::I32, 20 | ValType::I64 => ValueType::I64, 21 | ValType::F32 => ValueType::F32, 22 | ValType::F64 => ValueType::F64, 23 | ValType::Ref(_) => ValueType::Reference, 24 | 25 | ValType::V128 => unimplemented!("`V128` types"), 26 | } 27 | } 28 | 29 | fn load_type_from_function(function: u32, types: &Types) -> FunctionType { 30 | let r#type = types.get_type(function).unwrap_func(); 31 | 32 | FunctionType { 33 | arguments: r#type 34 | .params() 35 | .iter() 36 | .copied() 37 | .map(web_type_to_data_type) 38 | .collect(), 39 | 40 | results: r#type 41 | .results() 42 | .iter() 43 | .copied() 44 | .map(web_type_to_data_type) 45 | .collect(), 46 | } 47 | } 48 | 49 | fn load_type_from_result(result: ValType) -> FunctionType { 50 | let result = web_type_to_data_type(result); 51 | 52 | FunctionType { 53 | arguments: Resizable::new(), 54 | results: core::iter::once(result).collect(), 55 | } 56 | } 57 | 58 | fn read_local_types_into(local_types: &mut Vec, reader: LocalsReader) { 59 | local_types.clear(); 60 | 61 | for (count, r#type) in reader.into_iter().map(Result::unwrap) { 62 | let r#type = web_type_to_data_type(r#type); 63 | let count = count.try_into().unwrap(); 64 | 65 | local_types.extend(core::iter::repeat_n(r#type, count)); 66 | } 67 | } 68 | 69 | pub struct FunctionBuilder { 70 | converter: ControlFlowConverter, 71 | builder: ControlFlowBuilder, 72 | local_tracker: LocalTracker, 73 | 74 | graph: ControlFlowGraph, 75 | 76 | dependencies: Vec, 77 | locals: Locals, 78 | local_types: Vec, 79 | } 80 | 81 | impl FunctionBuilder { 82 | pub const fn new() -> Self { 83 | Self { 84 | converter: ControlFlowConverter::new(), 85 | builder: ControlFlowBuilder::new(), 86 | local_tracker: LocalTracker::new(), 87 | 88 | graph: ControlFlowGraph::new(), 89 | 90 | dependencies: Vec::new(), 91 | locals: Locals::new(), 92 | local_types: Vec::new(), 93 | } 94 | } 95 | 96 | pub fn build_data_flow( 97 | &mut self, 98 | graph: &mut DataFlowGraph, 99 | r#type: FunctionType, 100 | global_state: &GlobalState, 101 | ) -> u32 { 102 | references::track(&mut self.dependencies, &self.graph.instructions); 103 | 104 | let dependencies = global_state.get_dependencies(&self.dependencies); 105 | let stack_size = self.local_tracker.run( 106 | &mut self.locals, 107 | &self.graph, 108 | r#type.results.len().try_into().unwrap(), 109 | ); 110 | 111 | let lambda_in = graph.add_lambda_in(r#type.into(), dependencies); 112 | 113 | self.converter.set_function_data( 114 | graph, 115 | lambda_in, 116 | stack_size, 117 | &self.local_types, 118 | &self.dependencies, 119 | ); 120 | 121 | let results = self 122 | .converter 123 | .run(graph, &self.graph, lambda_in, &self.locals); 124 | 125 | graph.add_lambda_out(lambda_in, results) 126 | } 127 | 128 | pub fn build_function( 129 | &mut self, 130 | graph: &mut DataFlowGraph, 131 | body: &FunctionBody, 132 | function: u32, 133 | types: &Types, 134 | global_state: &GlobalState, 135 | ) -> u32 { 136 | let function = types.get_function_index(function); 137 | 138 | read_local_types_into(&mut self.local_types, body.get_locals_reader().unwrap()); 139 | 140 | self.builder.run( 141 | &mut self.graph, 142 | types, 143 | BlockType::FuncType(function), 144 | self.local_types.len().try_into().unwrap(), 145 | body.get_operators_reader().unwrap(), 146 | ); 147 | 148 | let function_type = load_type_from_function(function, types); 149 | 150 | self.build_data_flow(graph, function_type, global_state) 151 | } 152 | 153 | pub fn build_expression( 154 | &mut self, 155 | graph: &mut DataFlowGraph, 156 | operators: OperatorsReader, 157 | result: ValType, 158 | types: &Types, 159 | global_state: &GlobalState, 160 | ) -> Link { 161 | self.builder.run( 162 | &mut self.graph, 163 | types, 164 | BlockType::Type(result), 165 | 0, 166 | operators, 167 | ); 168 | 169 | self.local_types.clear(); 170 | 171 | let function_type = load_type_from_result(result); 172 | let function = self.build_data_flow(graph, function_type, global_state); 173 | let apply = graph.add_apply(Link(function, 0), Vec::new(), 1, 0); 174 | 175 | Link(apply, 0) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Data Flow/Builder/src/global_state.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use control_flow_liveness::references::{Reference, ReferenceType}; 3 | use data_flow_graph::Link; 4 | use wasmparser::{ExternalKind, TypeRef}; 5 | 6 | pub struct GlobalState { 7 | pub functions: Vec, 8 | pub tables: Vec, 9 | pub memories: Vec, 10 | pub globals: Vec, 11 | 12 | pub elements: Vec, 13 | pub datas: Vec, 14 | } 15 | 16 | impl GlobalState { 17 | #[must_use] 18 | pub const fn new() -> Self { 19 | Self { 20 | functions: Vec::new(), 21 | tables: Vec::new(), 22 | memories: Vec::new(), 23 | globals: Vec::new(), 24 | 25 | elements: Vec::new(), 26 | datas: Vec::new(), 27 | } 28 | } 29 | 30 | pub fn clear(&mut self) { 31 | self.functions.clear(); 32 | self.tables.clear(); 33 | self.memories.clear(); 34 | self.globals.clear(); 35 | 36 | self.elements.clear(); 37 | self.datas.clear(); 38 | } 39 | 40 | pub fn retrieve_all_mutable(&self, results: &mut Vec) { 41 | results.extend_from_slice(&self.functions); 42 | results.extend_from_slice(&self.tables); 43 | results.extend_from_slice(&self.memories); 44 | results.extend_from_slice(&self.globals); 45 | results.extend_from_slice(&self.elements); 46 | results.extend_from_slice(&self.datas); 47 | } 48 | 49 | fn get_dependency(&self, reference: Reference) -> Link { 50 | let list = match reference.r#type { 51 | ReferenceType::Function => &self.functions, 52 | ReferenceType::Global => &self.globals, 53 | ReferenceType::Table => &self.tables, 54 | ReferenceType::Elements => &self.elements, 55 | ReferenceType::Memory => &self.memories, 56 | ReferenceType::Data => &self.datas, 57 | }; 58 | 59 | list[usize::from(reference.id)] 60 | } 61 | 62 | pub fn get_dependencies(&self, references: &[Reference]) -> Vec { 63 | references 64 | .iter() 65 | .map(|&dependency| self.get_dependency(dependency)) 66 | .collect() 67 | } 68 | 69 | pub fn get_external_kind(&self, external_kind: ExternalKind) -> &[Link] { 70 | match external_kind { 71 | ExternalKind::Func => &self.functions, 72 | ExternalKind::Table => &self.tables, 73 | ExternalKind::Memory => &self.memories, 74 | ExternalKind::Global => &self.globals, 75 | ExternalKind::Tag => unimplemented!("`Tag`"), 76 | } 77 | } 78 | 79 | pub fn get_mut_type_ref(&mut self, type_ref: TypeRef) -> &mut Vec { 80 | match type_ref { 81 | TypeRef::Func(_) => &mut self.functions, 82 | TypeRef::Table(_) => &mut self.tables, 83 | TypeRef::Memory(_) => &mut self.memories, 84 | TypeRef::Global(_) => &mut self.globals, 85 | TypeRef::Tag(_) => unimplemented!("`Tag`"), 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Data Flow/Builder/src/sections.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use wasmparser::{ 3 | BinaryReader, Data, Element, Export, FunctionBody, Global, Import, MemoryType, Parser, Payload, 4 | RecGroup, Result, SectionLimited, Table, TagType, 5 | }; 6 | 7 | pub struct Sections<'data> { 8 | pub types: SectionLimited<'data, RecGroup>, 9 | pub imports: SectionLimited<'data, Import<'data>>, 10 | 11 | pub tables: SectionLimited<'data, Table<'data>>, 12 | pub elements: SectionLimited<'data, Element<'data>>, 13 | pub memories: SectionLimited<'data, MemoryType>, 14 | pub datas: SectionLimited<'data, Data<'data>>, 15 | pub globals: SectionLimited<'data, Global<'data>>, 16 | pub tags: SectionLimited<'data, TagType>, 17 | 18 | pub functions: SectionLimited<'data, u32>, 19 | pub code: Vec>, 20 | 21 | pub start: Option, 22 | pub exports: SectionLimited<'data, Export<'data>>, 23 | } 24 | 25 | impl<'data> Sections<'data> { 26 | fn reader_with_empty() -> SectionLimited<'static, T> { 27 | let reader = BinaryReader::new(&[0], 0); 28 | 29 | SectionLimited::new(reader).unwrap() 30 | } 31 | 32 | pub fn load(data: &'data [u8]) -> Self { 33 | let mut types = Self::reader_with_empty(); 34 | let mut imports = Self::reader_with_empty(); 35 | let mut functions = Self::reader_with_empty(); 36 | let mut tables = Self::reader_with_empty(); 37 | let mut memories = Self::reader_with_empty(); 38 | let mut tags = Self::reader_with_empty(); 39 | let mut globals = Self::reader_with_empty(); 40 | let mut exports = Self::reader_with_empty(); 41 | let mut start = None; 42 | let mut elements = Self::reader_with_empty(); 43 | let mut datas = Self::reader_with_empty(); 44 | let mut code = Vec::new(); 45 | 46 | for payload in Parser::new(0).parse_all(data).map(Result::unwrap) { 47 | match payload { 48 | Payload::Version { .. } 49 | | Payload::CustomSection(_) 50 | | Payload::End(_) 51 | | Payload::DataCountSection { .. } => {} 52 | 53 | Payload::TypeSection(section) => types = section, 54 | Payload::ImportSection(section) => imports = section, 55 | Payload::FunctionSection(section) => functions = section, 56 | Payload::TableSection(section) => tables = section, 57 | Payload::MemorySection(section) => memories = section, 58 | Payload::TagSection(section) => tags = section, 59 | Payload::GlobalSection(section) => globals = section, 60 | Payload::ExportSection(section) => exports = section, 61 | Payload::StartSection { func, .. } => start = Some(func), 62 | Payload::ElementSection(section) => elements = section, 63 | Payload::DataSection(section) => datas = section, 64 | 65 | Payload::CodeSectionStart { count, .. } => { 66 | let count = count.try_into().unwrap(); 67 | 68 | code.reserve_exact(count); 69 | } 70 | Payload::CodeSectionEntry(function_body) => code.push(function_body), 71 | 72 | payload => unimplemented!("{payload:?}"), 73 | } 74 | } 75 | 76 | Self { 77 | types, 78 | imports, 79 | tables, 80 | elements, 81 | memories, 82 | datas, 83 | globals, 84 | tags, 85 | functions, 86 | code, 87 | start, 88 | exports, 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Data Flow/Graph/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "data-flow-graph" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | control-flow-graph = { path = "../../Control Flow/Graph" } 8 | 9 | list.workspace = true 10 | -------------------------------------------------------------------------------- /Data Flow/Graph/src/node/base.rs: -------------------------------------------------------------------------------- 1 | use alloc::{sync::Arc, vec::Vec}; 2 | 3 | pub use control_flow_graph::instruction::{ 4 | ExtendType, IntegerBinaryOperator, IntegerCompareOperator, IntegerType, IntegerUnaryOperator, 5 | LoadType, NumberBinaryOperator, NumberCompareOperator, NumberType, NumberUnaryOperator, 6 | StoreType, 7 | }; 8 | 9 | use super::Link; 10 | 11 | pub trait Host { 12 | fn identifier(&self) -> &'static str; 13 | 14 | fn for_each_requirement(&self, handler: &mut dyn FnMut(u32)); 15 | 16 | fn for_each_id(&self, handler: &mut dyn FnMut(u32)); 17 | 18 | fn for_each_mut_id(&mut self, handler: &mut dyn FnMut(&mut u32)); 19 | 20 | fn for_each_argument(&self, handler: &mut dyn FnMut(Link)); 21 | 22 | fn for_each_mut_argument(&mut self, handler: &mut dyn FnMut(&mut Link)); 23 | } 24 | 25 | #[derive(Clone, Copy)] 26 | pub struct Identity { 27 | pub source: Link, 28 | } 29 | 30 | pub struct Merge { 31 | pub states: Vec, 32 | } 33 | 34 | pub struct Apply { 35 | pub function: Link, 36 | pub arguments: Vec, 37 | pub results: u16, 38 | pub states: u16, 39 | } 40 | 41 | #[derive(Clone, Copy)] 42 | pub struct RefIsNull { 43 | pub source: Link, 44 | } 45 | 46 | #[derive(Clone, Copy)] 47 | pub struct IntegerUnaryOperation { 48 | pub source: Link, 49 | pub r#type: IntegerType, 50 | pub operator: IntegerUnaryOperator, 51 | } 52 | 53 | #[derive(Clone, Copy)] 54 | pub struct IntegerBinaryOperation { 55 | pub lhs: Link, 56 | pub rhs: Link, 57 | pub r#type: IntegerType, 58 | pub operator: IntegerBinaryOperator, 59 | } 60 | 61 | #[derive(Clone, Copy)] 62 | pub struct IntegerCompareOperation { 63 | pub lhs: Link, 64 | pub rhs: Link, 65 | pub r#type: IntegerType, 66 | pub operator: IntegerCompareOperator, 67 | } 68 | 69 | #[derive(Clone, Copy)] 70 | pub struct IntegerNarrow { 71 | pub source: Link, 72 | } 73 | 74 | #[derive(Clone, Copy)] 75 | pub struct IntegerWiden { 76 | pub source: Link, 77 | } 78 | 79 | #[derive(Clone, Copy)] 80 | pub struct IntegerExtend { 81 | pub source: Link, 82 | pub r#type: ExtendType, 83 | } 84 | 85 | #[derive(Clone, Copy)] 86 | pub struct IntegerConvertToNumber { 87 | pub source: Link, 88 | pub signed: bool, 89 | pub to: NumberType, 90 | pub from: IntegerType, 91 | } 92 | 93 | #[derive(Clone, Copy)] 94 | pub struct IntegerTransmuteToNumber { 95 | pub source: Link, 96 | pub from: IntegerType, 97 | } 98 | 99 | #[derive(Clone, Copy)] 100 | pub struct NumberUnaryOperation { 101 | pub source: Link, 102 | pub r#type: NumberType, 103 | pub operator: NumberUnaryOperator, 104 | } 105 | 106 | #[derive(Clone, Copy)] 107 | pub struct NumberBinaryOperation { 108 | pub lhs: Link, 109 | pub rhs: Link, 110 | pub r#type: NumberType, 111 | pub operator: NumberBinaryOperator, 112 | } 113 | 114 | #[derive(Clone, Copy)] 115 | pub struct NumberCompareOperation { 116 | pub lhs: Link, 117 | pub rhs: Link, 118 | pub r#type: NumberType, 119 | pub operator: NumberCompareOperator, 120 | } 121 | 122 | #[derive(Clone, Copy)] 123 | pub struct NumberTruncateToInteger { 124 | pub source: Link, 125 | pub signed: bool, 126 | pub saturate: bool, 127 | pub to: IntegerType, 128 | pub from: NumberType, 129 | } 130 | 131 | #[derive(Clone, Copy)] 132 | pub struct NumberTransmuteToInteger { 133 | pub source: Link, 134 | pub from: NumberType, 135 | } 136 | 137 | #[derive(Clone, Copy)] 138 | pub struct NumberNarrow { 139 | pub source: Link, 140 | } 141 | 142 | #[derive(Clone, Copy)] 143 | pub struct NumberWiden { 144 | pub source: Link, 145 | } 146 | 147 | #[derive(Clone, Copy)] 148 | pub struct Location { 149 | pub reference: Link, 150 | pub offset: Link, 151 | } 152 | 153 | #[derive(Clone, Copy)] 154 | pub struct GlobalNew { 155 | pub initializer: Link, 156 | } 157 | 158 | #[derive(Clone, Copy)] 159 | pub struct GlobalGet { 160 | pub source: Link, 161 | } 162 | 163 | #[derive(Clone, Copy)] 164 | pub struct GlobalSet { 165 | pub destination: Link, 166 | pub source: Link, 167 | } 168 | 169 | #[derive(Clone, Copy)] 170 | pub struct TableNew { 171 | pub initializer: Link, 172 | pub minimum: u32, 173 | pub maximum: u32, 174 | } 175 | 176 | #[derive(Clone, Copy)] 177 | pub struct TableGet { 178 | pub source: Location, 179 | } 180 | 181 | #[derive(Clone, Copy)] 182 | pub struct TableSet { 183 | pub destination: Location, 184 | pub source: Link, 185 | } 186 | 187 | #[derive(Clone, Copy)] 188 | pub struct TableSize { 189 | pub source: Link, 190 | } 191 | 192 | #[derive(Clone, Copy)] 193 | pub struct TableGrow { 194 | pub destination: Link, 195 | pub initializer: Link, 196 | pub size: Link, 197 | } 198 | 199 | #[derive(Clone, Copy)] 200 | pub struct TableFill { 201 | pub destination: Location, 202 | pub source: Link, 203 | pub size: Link, 204 | } 205 | 206 | #[derive(Clone, Copy)] 207 | pub struct TableCopy { 208 | pub destination: Location, 209 | pub source: Location, 210 | pub size: Link, 211 | } 212 | 213 | #[derive(Clone, Copy)] 214 | pub struct TableInit { 215 | pub destination: Location, 216 | pub source: Location, 217 | pub size: Link, 218 | } 219 | 220 | pub struct ElementsNew { 221 | pub content: Vec, 222 | } 223 | 224 | #[derive(Clone, Copy)] 225 | pub struct ElementsDrop { 226 | pub source: Link, 227 | } 228 | 229 | #[derive(Clone, Copy)] 230 | pub struct MemoryNew { 231 | pub minimum: u32, 232 | pub maximum: u32, 233 | } 234 | 235 | #[derive(Clone, Copy)] 236 | pub struct MemoryLoad { 237 | pub source: Location, 238 | pub r#type: LoadType, 239 | } 240 | 241 | #[derive(Clone, Copy)] 242 | pub struct MemoryStore { 243 | pub destination: Location, 244 | pub source: Link, 245 | pub r#type: StoreType, 246 | } 247 | 248 | #[derive(Clone, Copy)] 249 | pub struct MemorySize { 250 | pub source: Link, 251 | } 252 | 253 | #[derive(Clone, Copy)] 254 | pub struct MemoryGrow { 255 | pub destination: Link, 256 | pub size: Link, 257 | } 258 | 259 | #[derive(Clone, Copy)] 260 | pub struct MemoryFill { 261 | pub destination: Location, 262 | pub byte: Link, 263 | pub size: Link, 264 | } 265 | 266 | #[derive(Clone, Copy)] 267 | pub struct MemoryCopy { 268 | pub destination: Location, 269 | pub source: Location, 270 | pub size: Link, 271 | } 272 | 273 | #[derive(Clone, Copy)] 274 | pub struct MemoryInit { 275 | pub destination: Location, 276 | pub source: Location, 277 | pub size: Link, 278 | } 279 | 280 | #[derive(Clone)] 281 | pub struct DataNew { 282 | pub content: Arc<[u8]>, 283 | } 284 | 285 | #[derive(Clone, Copy)] 286 | pub struct DataDrop { 287 | pub source: Link, 288 | } 289 | -------------------------------------------------------------------------------- /Data Flow/Graph/src/node/control.rs: -------------------------------------------------------------------------------- 1 | use alloc::{boxed::Box, sync::Arc, vec::Vec}; 2 | use list::resizable::Resizable; 3 | 4 | use super::Link; 5 | 6 | #[derive(Clone, Copy)] 7 | pub enum ValueType { 8 | I32, 9 | I64, 10 | F32, 11 | F64, 12 | 13 | Reference, 14 | } 15 | 16 | #[derive(Clone)] 17 | pub struct FunctionType { 18 | pub arguments: Resizable, 19 | pub results: Resizable, 20 | } 21 | 22 | #[derive(Clone)] 23 | pub struct LambdaIn { 24 | pub output: u32, 25 | pub r#type: Box, 26 | pub dependencies: Vec, 27 | } 28 | 29 | impl LambdaIn { 30 | #[must_use] 31 | pub fn dependency_ports(&self) -> core::ops::Range { 32 | 0..self.dependencies.len().try_into().unwrap() 33 | } 34 | 35 | #[must_use] 36 | pub fn argument_ports(&self) -> core::ops::Range { 37 | let dependencies: u16 = self.dependencies.len().try_into().unwrap(); 38 | let arguments: u16 = self.r#type.arguments.len().try_into().unwrap(); 39 | 40 | dependencies..dependencies + arguments 41 | } 42 | 43 | #[must_use] 44 | pub fn output_ports(&self) -> core::ops::Range { 45 | let dependencies: u16 = self.dependencies.len().try_into().unwrap(); 46 | let arguments: u16 = self.r#type.arguments.len().try_into().unwrap(); 47 | 48 | 0..dependencies + arguments 49 | } 50 | } 51 | 52 | #[derive(Clone)] 53 | pub struct LambdaOut { 54 | pub input: u32, 55 | pub results: Vec, 56 | } 57 | 58 | #[derive(Clone)] 59 | pub struct RegionIn { 60 | pub input: u32, 61 | pub output: u32, 62 | } 63 | 64 | #[derive(Clone)] 65 | pub struct RegionOut { 66 | pub input: u32, 67 | pub output: u32, 68 | pub results: Vec, 69 | } 70 | 71 | #[derive(Clone)] 72 | pub struct GammaIn { 73 | pub output: u32, 74 | pub arguments: Vec, 75 | pub condition: Link, 76 | } 77 | 78 | #[derive(Clone)] 79 | pub struct GammaOut { 80 | pub input: u32, 81 | pub regions: Vec, 82 | } 83 | 84 | #[derive(Clone)] 85 | pub struct ThetaIn { 86 | pub output: u32, 87 | pub arguments: Vec, 88 | } 89 | 90 | #[derive(Clone)] 91 | pub struct ThetaOut { 92 | pub input: u32, 93 | pub results: Vec, 94 | pub condition: Link, 95 | } 96 | 97 | #[derive(Clone)] 98 | pub struct Import { 99 | pub environment: Link, 100 | pub namespace: Arc, 101 | pub identifier: Arc, 102 | } 103 | 104 | #[derive(Clone)] 105 | pub struct OmegaIn { 106 | pub output: u32, 107 | } 108 | 109 | #[derive(Clone)] 110 | pub struct Export { 111 | pub identifier: Arc, 112 | pub reference: Link, 113 | } 114 | 115 | #[derive(Clone)] 116 | pub struct OmegaOut { 117 | pub input: u32, 118 | 119 | pub state: Link, 120 | pub exports: Vec, 121 | } 122 | -------------------------------------------------------------------------------- /Data Flow/Graph/src/node/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | 3 | use self::{ 4 | base::{ 5 | Apply, DataDrop, DataNew, ElementsDrop, ElementsNew, GlobalGet, GlobalNew, GlobalSet, Host, 6 | Identity, IntegerBinaryOperation, IntegerCompareOperation, IntegerConvertToNumber, 7 | IntegerExtend, IntegerNarrow, IntegerTransmuteToNumber, IntegerUnaryOperation, 8 | IntegerWiden, MemoryCopy, MemoryFill, MemoryGrow, MemoryInit, MemoryLoad, MemoryNew, 9 | MemorySize, MemoryStore, Merge, NumberBinaryOperation, NumberCompareOperation, 10 | NumberNarrow, NumberTransmuteToInteger, NumberTruncateToInteger, NumberUnaryOperation, 11 | NumberWiden, RefIsNull, TableCopy, TableFill, TableGet, TableGrow, TableInit, TableNew, 12 | TableSet, TableSize, 13 | }, 14 | control::{ 15 | GammaIn, GammaOut, Import, LambdaIn, LambdaOut, OmegaIn, OmegaOut, RegionIn, RegionOut, 16 | ThetaIn, ThetaOut, 17 | }, 18 | }; 19 | 20 | mod sources; 21 | 22 | pub mod base; 23 | pub mod control; 24 | 25 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] 26 | pub struct Link(pub u32, pub u16); 27 | 28 | impl Link { 29 | #[must_use] 30 | pub const fn into_usize(self) -> usize { 31 | let [id_0, id_1, id_2, id_3] = self.0.to_le_bytes(); 32 | let [port_0, port_1] = self.1.to_le_bytes(); 33 | 34 | usize::from_le_bytes([id_0, id_1, port_0, id_2, port_1, id_3, 0, 0]) 35 | } 36 | 37 | pub const DANGLING: Self = Self(u32::MAX, u16::MAX); 38 | } 39 | 40 | #[derive(Default)] 41 | pub enum Node { 42 | LambdaIn(LambdaIn), 43 | LambdaOut(LambdaOut), 44 | 45 | RegionIn(RegionIn), 46 | RegionOut(RegionOut), 47 | 48 | GammaIn(GammaIn), 49 | GammaOut(GammaOut), 50 | 51 | ThetaIn(ThetaIn), 52 | ThetaOut(ThetaOut), 53 | 54 | OmegaIn(OmegaIn), 55 | OmegaOut(OmegaOut), 56 | 57 | Import(Box), 58 | Host(Box), 59 | 60 | #[default] 61 | Trap, 62 | Null, 63 | 64 | I32(i32), 65 | I64(i64), 66 | F32(f32), 67 | F64(f64), 68 | 69 | Identity(Identity), 70 | Merge(Merge), 71 | 72 | Apply(Apply), 73 | 74 | RefIsNull(RefIsNull), 75 | 76 | IntegerUnaryOperation(IntegerUnaryOperation), 77 | IntegerBinaryOperation(IntegerBinaryOperation), 78 | IntegerCompareOperation(IntegerCompareOperation), 79 | IntegerNarrow(IntegerNarrow), 80 | IntegerWiden(IntegerWiden), 81 | IntegerExtend(IntegerExtend), 82 | IntegerConvertToNumber(IntegerConvertToNumber), 83 | IntegerTransmuteToNumber(IntegerTransmuteToNumber), 84 | 85 | NumberUnaryOperation(NumberUnaryOperation), 86 | NumberBinaryOperation(NumberBinaryOperation), 87 | NumberCompareOperation(NumberCompareOperation), 88 | NumberNarrow(NumberNarrow), 89 | NumberWiden(NumberWiden), 90 | NumberTruncateToInteger(NumberTruncateToInteger), 91 | NumberTransmuteToInteger(NumberTransmuteToInteger), 92 | 93 | GlobalNew(GlobalNew), 94 | GlobalGet(GlobalGet), 95 | GlobalSet(GlobalSet), 96 | 97 | TableNew(TableNew), 98 | TableGet(TableGet), 99 | TableSet(TableSet), 100 | TableSize(TableSize), 101 | TableGrow(TableGrow), 102 | TableFill(TableFill), 103 | TableCopy(TableCopy), 104 | TableInit(TableInit), 105 | 106 | ElementsNew(ElementsNew), 107 | ElementsDrop(ElementsDrop), 108 | 109 | MemoryNew(MemoryNew), 110 | MemoryLoad(MemoryLoad), 111 | MemoryStore(MemoryStore), 112 | MemorySize(MemorySize), 113 | MemoryGrow(MemoryGrow), 114 | MemoryFill(MemoryFill), 115 | MemoryCopy(MemoryCopy), 116 | MemoryInit(MemoryInit), 117 | 118 | DataNew(DataNew), 119 | DataDrop(DataDrop), 120 | } 121 | 122 | macro_rules! as_ref_inner { 123 | ($inner:ident, $name:ident) => { 124 | #[must_use] 125 | pub const fn $name(&self) -> Option<&$inner> { 126 | if let Self::$inner(node) = self { 127 | Some(node) 128 | } else { 129 | None 130 | } 131 | } 132 | }; 133 | } 134 | 135 | macro_rules! as_mut_inner { 136 | ($inner:ident, $name:ident) => { 137 | #[must_use] 138 | pub const fn $name(&mut self) -> Option<&mut $inner> { 139 | if let Self::$inner(node) = self { 140 | Some(node) 141 | } else { 142 | None 143 | } 144 | } 145 | }; 146 | } 147 | 148 | impl Node { 149 | as_ref_inner!(LambdaIn, as_lambda_in); 150 | as_ref_inner!(LambdaOut, as_lambda_out); 151 | as_ref_inner!(RegionIn, as_region_in); 152 | as_ref_inner!(RegionOut, as_region_out); 153 | as_ref_inner!(GammaIn, as_gamma_in); 154 | as_ref_inner!(GammaOut, as_gamma_out); 155 | as_ref_inner!(ThetaIn, as_theta_in); 156 | as_ref_inner!(ThetaOut, as_theta_out); 157 | as_ref_inner!(OmegaIn, as_omega_in); 158 | as_ref_inner!(OmegaOut, as_omega_out); 159 | 160 | as_mut_inner!(LambdaIn, as_mut_lambda_in); 161 | as_mut_inner!(LambdaOut, as_mut_lambda_out); 162 | as_mut_inner!(RegionIn, as_mut_region_in); 163 | as_mut_inner!(RegionOut, as_mut_region_out); 164 | as_mut_inner!(GammaIn, as_mut_gamma_in); 165 | as_mut_inner!(GammaOut, as_mut_gamma_out); 166 | as_mut_inner!(ThetaIn, as_mut_theta_in); 167 | as_mut_inner!(ThetaOut, as_mut_theta_out); 168 | as_mut_inner!(OmegaIn, as_mut_omega_in); 169 | as_mut_inner!(OmegaOut, as_mut_omega_out); 170 | } 171 | -------------------------------------------------------------------------------- /Data Flow/Visitor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "data-flow-visitor" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | data-flow-graph = { path = "../Graph" } 8 | 9 | set.workspace = true 10 | hashbrown.workspace = true 11 | 12 | [build-dependencies] 13 | cranelift-isle.workspace = true 14 | -------------------------------------------------------------------------------- /Data Flow/Visitor/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use cranelift_isle::{codegen::CodegenOptions, compile::from_files}; 4 | 5 | static CODE_OPTIONS: CodegenOptions = CodegenOptions { 6 | exclude_global_allow_pragmas: true, 7 | prefixes: Vec::new(), 8 | }; 9 | 10 | fn read_from_files(path: &Path) -> String { 11 | let files = std::fs::read_dir(path) 12 | .unwrap() 13 | .map(|file| file.unwrap().path()); 14 | 15 | from_files(files, &CODE_OPTIONS).unwrap() 16 | } 17 | 18 | fn write_into_file(path: &Path, code: String) { 19 | let directory = std::env::var("OUT_DIR").unwrap(); 20 | let path = Path::new(&directory).join(path); 21 | 22 | std::fs::write(path, code).unwrap(); 23 | } 24 | 25 | fn main() { 26 | println!("cargo:rerun-if-changed=isle"); 27 | 28 | let code = read_from_files("isle/".as_ref()); 29 | 30 | write_into_file("isle.rs".as_ref(), code); 31 | } 32 | -------------------------------------------------------------------------------- /Data Flow/Visitor/isle/fNN.isle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SovereignSatellite/Spider/fbbed8d5264663d97d46861d28189eeb3ce92c63/Data Flow/Visitor/isle/fNN.isle -------------------------------------------------------------------------------- /Data Flow/Visitor/isle/global.isle: -------------------------------------------------------------------------------- 1 | (decl partial SimplifyGlobal (Link) Link) 2 | 3 | (rule 2 4 | (SimplifyGlobal 5 | (GlobalGet (GlobalNew source)) 6 | ) 7 | 8 | source 9 | ) 10 | 11 | (rule 1 12 | (SimplifyGlobal 13 | (GlobalGet (GlobalSet _ source)) 14 | ) 15 | 16 | source 17 | ) 18 | -------------------------------------------------------------------------------- /Data Flow/Visitor/isle/iNN.isle: -------------------------------------------------------------------------------- 1 | (decl partial SimplifyI32 (Link) Link) 2 | 3 | ;; (N + 0) => N 4 | (rule 3 5 | (SimplifyI32 6 | (I32Add lhs (I32 0)) 7 | ) 8 | 9 | lhs 10 | ) 11 | 12 | ;; (N - 0) => N 13 | (rule 3 14 | (SimplifyI32 15 | (I32Sub lhs (I32 0)) 16 | ) 17 | 18 | lhs 19 | ) 20 | 21 | ;; (K1 + K2) => K3 22 | (rule 2 23 | (SimplifyI32 24 | (I32Add (I32 lhs) (I32 rhs)) 25 | ) 26 | 27 | (I32 (raw_add_i32 lhs rhs)) 28 | ) 29 | 30 | ;; (K1 - K2) => K3 31 | (rule 2 32 | (SimplifyI32 33 | (I32Sub (I32 lhs) (I32 rhs)) 34 | ) 35 | 36 | (I32 (raw_sub_i32 lhs rhs)) 37 | ) 38 | 39 | ;; (N + K1) + K2 => N + (K1 + K2) 40 | (rule 1 41 | (SimplifyI32 42 | (I32Add 43 | (I32Add lhs_1 rhs_1 @ (I32 _)) 44 | rhs_2 @ (I32 _) 45 | ) 46 | ) 47 | 48 | (I32Add lhs_1 (I32Add rhs_1 rhs_2)) 49 | ) 50 | 51 | ;; (N + K1) - K2 => N + (K1 - K2) 52 | (rule 1 53 | (SimplifyI32 54 | (I32Sub 55 | (I32Add lhs_1 rhs_1 @ (I32 _)) 56 | rhs_2 @ (I32 _) 57 | ) 58 | ) 59 | 60 | (I32Add lhs_1 (I32Sub rhs_1 rhs_2)) 61 | ) 62 | 63 | ;; (N - K1) - K2 => N - (K1 + K2) 64 | (rule 1 65 | (SimplifyI32 66 | (I32Sub 67 | (I32Sub lhs_1 rhs_1 @ (I32 _)) 68 | rhs_2 @ (I32 _) 69 | ) 70 | ) 71 | 72 | (I32Sub lhs_1 (I32Add rhs_1 rhs_2)) 73 | ) 74 | -------------------------------------------------------------------------------- /Data Flow/Visitor/isle/table.isle: -------------------------------------------------------------------------------- 1 | (decl partial SimplifyTable (Link) Link) 2 | 3 | (rule 4 | (SimplifyTable 5 | (TableGet (TableSet source offset _) offset) 6 | ) 7 | 8 | source 9 | ) 10 | -------------------------------------------------------------------------------- /Data Flow/Visitor/isle/types.fNN.isle: -------------------------------------------------------------------------------- 1 | ;; Node::F32 2 | (decl F32 (f32) Link) 3 | (extern extractor F32 get_f32) 4 | (extern constructor F32 add_f32) 5 | 6 | ;; Node::F64 7 | (decl F64 (f64) Link) 8 | (extern extractor F64 get_f64) 9 | (extern constructor F64 add_f64) 10 | -------------------------------------------------------------------------------- /Data Flow/Visitor/isle/types.global.isle: -------------------------------------------------------------------------------- 1 | ;; Node::GlobalNew 2 | (decl GlobalNew (Link) Link) 3 | (extern extractor GlobalNew get_global_new) 4 | 5 | ;; Node::GlobalGet 6 | (decl GlobalGet (Link) Link) 7 | (extern extractor GlobalGet get_global_get) 8 | 9 | ;; Node::GlobalSet 10 | (decl GlobalSet (Link Link) Link) 11 | (extern extractor GlobalSet get_global_set) 12 | -------------------------------------------------------------------------------- /Data Flow/Visitor/isle/types.iNN.isle: -------------------------------------------------------------------------------- 1 | ;; Node::I32 2 | (decl I32 (i32) Link) 3 | (extern extractor I32 get_i32) 4 | (extern constructor I32 add_i32) 5 | 6 | ;; Node::I64 7 | (decl I64 (i64) Link) 8 | (extern extractor I64 get_i64) 9 | (extern constructor I64 add_i64) 10 | 11 | ;; Node::IntegerBinaryOperation 12 | (decl IntegerBinaryOperation (Link Link IntegerType IntegerBinaryOperator) Link) 13 | (extern extractor IntegerBinaryOperation get_integer_binary_operation) 14 | (extern constructor IntegerBinaryOperation add_integer_binary_operation) 15 | 16 | ;; Node::IntegerBinaryOperation (IntegerType::I32, IntegerBinaryOperator::Add) 17 | (decl I32Add (Link Link) Link) 18 | 19 | (rule (I32Add lhs rhs) 20 | (IntegerBinaryOperation lhs rhs (IntegerType.I32) (IntegerBinaryOperator.Add)) 21 | ) 22 | 23 | (extractor (I32Add lhs rhs) 24 | (IntegerBinaryOperation lhs rhs (IntegerType.I32) (IntegerBinaryOperator.Add)) 25 | ) 26 | 27 | ;; Node::IntegerBinaryOperation (IntegerType::I32, IntegerBinaryOperator::Subtract) 28 | (decl I32Sub (Link Link) Link) 29 | 30 | (rule (I32Sub lhs rhs) 31 | (IntegerBinaryOperation lhs rhs (IntegerType.I32) (IntegerBinaryOperator.Subtract)) 32 | ) 33 | 34 | (extractor (I32Sub lhs rhs) 35 | (IntegerBinaryOperation lhs rhs (IntegerType.I32) (IntegerBinaryOperator.Subtract)) 36 | ) 37 | 38 | ;; Built-in `+` 39 | (decl pure raw_add_i32 (i32 i32) i32) 40 | (extern constructor raw_add_i32 raw_add_i32) 41 | 42 | ;; Built-in `-` 43 | (decl pure raw_sub_i32 (i32 i32) i32) 44 | (extern constructor raw_sub_i32 raw_sub_i32) 45 | -------------------------------------------------------------------------------- /Data Flow/Visitor/isle/types.rust.isle: -------------------------------------------------------------------------------- 1 | (type Link (primitive Link)) 2 | (type f32 (primitive f32)) 3 | (type f64 (primitive f64)) 4 | 5 | (type IntegerType extern 6 | (enum 7 | (I32) 8 | (I64) 9 | ) 10 | ) 11 | 12 | (type ExtendType extern 13 | (enum 14 | (I32_S8) 15 | (I32_S16) 16 | 17 | (I64_S8) 18 | (I64_S16) 19 | (I64_S32) 20 | ) 21 | ) 22 | 23 | (type IntegerUnaryOperator extern 24 | (enum 25 | (CountOnes) 26 | (LeadingZeroes) 27 | (TrailingZeroes) 28 | ) 29 | ) 30 | 31 | (type IntegerBinaryOperator extern 32 | (enum 33 | (Add) 34 | (Subtract) 35 | (Multiply) 36 | (Divide (signed bool)) 37 | (Remainder (signed bool)) 38 | (And) 39 | (Or) 40 | (ExclusiveOr) 41 | (ShiftLeft) 42 | (ShiftRight (signed bool)) 43 | (RotateLeft) 44 | (RotateRight) 45 | ) 46 | ) 47 | 48 | (type IntegerCompareOperator extern 49 | (enum 50 | (Equal) 51 | (NotEqual) 52 | (LessThan (signed bool)) 53 | (GreaterThan (signed bool)) 54 | (LessThanEqual (signed bool)) 55 | (GreaterThanEqual (signed bool)) 56 | ) 57 | ) 58 | 59 | (type NumberType extern 60 | (enum 61 | (F32) 62 | (F64) 63 | ) 64 | ) 65 | 66 | (type NumberUnaryOperator extern 67 | (enum 68 | (Absolute) 69 | (Negate) 70 | (SquareRoot) 71 | (RoundUp) 72 | (RoundDown) 73 | (Truncate) 74 | (Nearest) 75 | ) 76 | ) 77 | 78 | (type NumberBinaryOperator extern 79 | (enum 80 | (Add) 81 | (Subtract) 82 | (Multiply) 83 | (Divide) 84 | (Minimum) 85 | (Maximum) 86 | (CopySign) 87 | ) 88 | ) 89 | 90 | (type NumberCompareOperator extern 91 | (enum 92 | (Equal) 93 | (NotEqual) 94 | (LessThan) 95 | (GreaterThan) 96 | (LessThanEqual) 97 | (GreaterThanEqual) 98 | ) 99 | ) 100 | 101 | (type LoadType extern 102 | (enum 103 | (I32_S8) 104 | (I32_U8) 105 | (I32_S16) 106 | (I32_U16) 107 | (I32) 108 | 109 | (I64_S8) 110 | (I64_U8) 111 | (I64_S16) 112 | (I64_U16) 113 | (I64_S32) 114 | (I64_U32) 115 | (I64) 116 | 117 | (F32) 118 | (F64) 119 | ) 120 | ) 121 | 122 | (type StoreType extern 123 | (enum 124 | (I32_I8) 125 | (I32_I16) 126 | (I32) 127 | 128 | (I64_I8) 129 | (I64_I16) 130 | (I64_I32) 131 | (I64) 132 | 133 | (F32) 134 | (F64) 135 | ) 136 | ) 137 | -------------------------------------------------------------------------------- /Data Flow/Visitor/isle/types.table.isle: -------------------------------------------------------------------------------- 1 | ;; Node::TableGet 2 | (decl TableGet (Link Link) Link) 3 | (extern extractor TableGet get_table_get) 4 | 5 | ;; Node::TableSet 6 | (decl TableSet (Link Link Link) Link) 7 | (extern extractor TableSet get_table_set) 8 | -------------------------------------------------------------------------------- /Data Flow/Visitor/src/control/dead_port_eliminator.rs: -------------------------------------------------------------------------------- 1 | use data_flow_graph::{ 2 | DataFlowGraph, Link, Node, 3 | control::{GammaIn, GammaOut, LambdaIn, LambdaOut, RegionIn, RegionOut, ThetaIn, ThetaOut}, 4 | }; 5 | use hashbrown::HashMap; 6 | use set::Set; 7 | 8 | pub struct DeadPortEliminator { 9 | map: HashMap, 10 | 11 | seen: Set, 12 | stack: Vec, 13 | } 14 | 15 | impl DeadPortEliminator { 16 | #[must_use] 17 | pub fn new() -> Self { 18 | Self { 19 | map: HashMap::new(), 20 | 21 | seen: Set::new(), 22 | stack: Vec::new(), 23 | } 24 | } 25 | 26 | fn add_predecessor(&mut self, link: Link) { 27 | if self.seen.grow_insert(link.into_usize()) { 28 | return; 29 | } 30 | 31 | self.stack.push(link); 32 | } 33 | 34 | fn add_region_sides(&mut self, links: &[Link], id: u32) { 35 | let len = links.len(); 36 | 37 | self.seen.extend( 38 | (0..) 39 | .map(|port| Link(id, port)) 40 | .map(Link::into_usize) 41 | .take(len), 42 | ); 43 | 44 | for &link in links { 45 | self.add_predecessor(link); 46 | } 47 | } 48 | 49 | fn mark_lambda_in(&mut self, node: &LambdaIn, id: u32) { 50 | let LambdaIn { dependencies, .. } = node; 51 | 52 | self.add_region_sides(dependencies, id); 53 | } 54 | 55 | fn mark_lambda_out(&mut self, node: &LambdaOut, id: u32) { 56 | let LambdaOut { results, .. } = node; 57 | 58 | self.add_region_sides(results, id); 59 | } 60 | 61 | fn mark_region_in(&mut self, node: &RegionIn, port: u16) { 62 | let RegionIn { input, .. } = *node; 63 | 64 | self.add_predecessor(Link(input, port)); 65 | } 66 | 67 | fn mark_region_out(&mut self, node: &RegionOut, port: u16) { 68 | let RegionOut { results, .. } = node; 69 | 70 | self.add_predecessor(results[usize::from(port)]); 71 | } 72 | 73 | fn mark_gamma_in(&mut self, graph: &DataFlowGraph, node: &GammaIn, port: u16) { 74 | let GammaIn { 75 | output, 76 | ref arguments, 77 | .. 78 | } = *node; 79 | let GammaOut { regions, .. } = graph.get(output).as_gamma_out().unwrap(); 80 | 81 | for ®ion in regions { 82 | let RegionOut { input, .. } = *graph.get(region).as_region_out().unwrap(); 83 | 84 | self.add_predecessor(Link(input, port)); 85 | } 86 | 87 | self.add_predecessor(arguments[usize::from(port)]); 88 | } 89 | 90 | fn mark_gamma_out(&mut self, graph: &DataFlowGraph, node: &GammaOut, port: u16) { 91 | let GammaOut { input, ref regions } = *node; 92 | let GammaIn { condition, .. } = *graph.get(input).as_gamma_in().unwrap(); 93 | 94 | self.add_predecessor(condition); 95 | 96 | for ®ion in regions { 97 | self.add_predecessor(Link(region, port)); 98 | } 99 | } 100 | 101 | fn mark_theta_in(&mut self, node: &ThetaIn, port: u16) { 102 | let ThetaIn { 103 | output, 104 | ref arguments, 105 | } = *node; 106 | 107 | self.add_predecessor(Link(output, port)); 108 | self.add_predecessor(arguments[usize::from(port)]); 109 | } 110 | 111 | fn mark_theta_out(&mut self, node: &ThetaOut, port: u16) { 112 | let ThetaOut { 113 | input, 114 | ref results, 115 | condition, 116 | } = *node; 117 | 118 | self.add_predecessor(Link(input, port)); 119 | self.add_predecessor(results[usize::from(port)]); 120 | self.add_predecessor(condition); 121 | } 122 | 123 | fn mark_operation(&mut self, node: &Node) { 124 | node.for_each_argument(|link| self.add_predecessor(link)); 125 | } 126 | 127 | fn mark(&mut self, graph: &DataFlowGraph, result: Link) { 128 | self.seen.clear(); 129 | 130 | self.add_predecessor(result); 131 | 132 | while let Some(Link(id, port)) = self.stack.pop() { 133 | match graph.get(id) { 134 | Node::LambdaIn(node) => self.mark_lambda_in(node, id), 135 | Node::LambdaOut(node) => self.mark_lambda_out(node, id), 136 | Node::RegionIn(node) => self.mark_region_in(node, port), 137 | Node::RegionOut(node) => self.mark_region_out(node, port), 138 | Node::GammaIn(node) => self.mark_gamma_in(graph, node, port), 139 | Node::GammaOut(node) => self.mark_gamma_out(graph, node, port), 140 | Node::ThetaIn(node) => self.mark_theta_in(node, port), 141 | Node::ThetaOut(node) => self.mark_theta_out(node, port), 142 | 143 | node => self.mark_operation(node), 144 | } 145 | } 146 | } 147 | 148 | fn sweep_outputs(&mut self, graph: &DataFlowGraph, id: u32) { 149 | let Some(ports) = graph.get(id).ports_output(graph) else { 150 | return; 151 | }; 152 | 153 | let mut outputs = (0..).map(|port| Link(id, port)); 154 | 155 | for from in outputs.clone().take(ports) { 156 | if !self.seen.contains(from.into_usize()) { 157 | continue; 158 | } 159 | 160 | let to = outputs.next().unwrap(); 161 | 162 | if from.1 != to.1 { 163 | self.map.insert(from, to); 164 | } 165 | } 166 | } 167 | 168 | fn sweep_inputs(&self, graph: &mut DataFlowGraph, id: u32) { 169 | let Some(arguments) = graph.get_mut(id).as_mut_ports() else { 170 | return; 171 | }; 172 | 173 | let mut inputs = (0..).map(|port| Link(id, port)).map(Link::into_usize); 174 | 175 | arguments.retain(|_| self.seen.contains(inputs.next().unwrap())); 176 | } 177 | 178 | fn sweep(&mut self, graph: &mut DataFlowGraph) { 179 | let len = graph.len(); 180 | 181 | self.map.clear(); 182 | 183 | for id in (0..len.try_into().unwrap()).rev() { 184 | self.sweep_outputs(graph, id); 185 | self.sweep_inputs(graph, id); 186 | } 187 | 188 | for node in graph.nodes_mut() { 189 | node.for_each_mut_argument(|old| { 190 | if let Some(new) = self.map.get(old) { 191 | *old = *new; 192 | } 193 | }); 194 | } 195 | } 196 | 197 | pub fn run(&mut self, graph: &mut DataFlowGraph, result: Link) { 198 | self.mark(graph, result); 199 | self.sweep(graph); 200 | } 201 | } 202 | 203 | impl Default for DeadPortEliminator { 204 | fn default() -> Self { 205 | Self::new() 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Data Flow/Visitor/src/control/invariant_port_mover.rs: -------------------------------------------------------------------------------- 1 | use data_flow_graph::{ 2 | DataFlowGraph, Link, Node, 3 | control::{GammaIn, GammaOut, RegionOut, ThetaIn, ThetaOut}, 4 | }; 5 | use hashbrown::HashMap; 6 | 7 | pub struct InvariantPortMover { 8 | map: HashMap, 9 | } 10 | 11 | impl InvariantPortMover { 12 | #[must_use] 13 | pub fn new() -> Self { 14 | Self { 15 | map: HashMap::new(), 16 | } 17 | } 18 | 19 | fn get_reference(&self, link: Link) -> Link { 20 | self.map.get(&link).copied().unwrap_or(link) 21 | } 22 | 23 | fn find_argument(&self, result: Link, input: u32, arguments: &[Link]) -> Option { 24 | let result = self.get_reference(result); 25 | 26 | (result.0 == input).then(|| { 27 | let port = usize::from(result.1); 28 | 29 | self.get_reference(arguments[port]) 30 | }) 31 | } 32 | 33 | fn find_shared_reference( 34 | &self, 35 | graph: &DataFlowGraph, 36 | regions: &[u32], 37 | port: usize, 38 | arguments: &[Link], 39 | ) -> Option { 40 | let mut arguments = regions.iter().map(|®ion| { 41 | let RegionOut { input, results, .. } = graph.get(region).as_region_out().unwrap(); 42 | 43 | self.find_argument(results[port], *input, arguments) 44 | }); 45 | 46 | arguments 47 | .next() 48 | .unwrap() 49 | .filter(|&first| arguments.all(|link| link == Some(first))) 50 | } 51 | 52 | fn handle_simple_reference( 53 | &mut self, 54 | arguments: &[Link], 55 | results: &[Link], 56 | input: u32, 57 | output: u32, 58 | ) { 59 | // Assuming the result of the region is the same value as its argument, 60 | // then we can replace it with a direct link. 61 | for (port, &result) in results.iter().enumerate() { 62 | if let Some(argument) = self.find_argument(result, input, arguments) 63 | && argument == self.get_reference(arguments[port]) 64 | { 65 | let link = Link(output, port.try_into().unwrap()); 66 | 67 | self.map.insert(link, argument); 68 | } 69 | } 70 | } 71 | 72 | fn handle_gamma(&mut self, graph: &DataFlowGraph, gamma_out: &GammaOut) { 73 | let GammaOut { input, regions } = gamma_out; 74 | let GammaIn { 75 | output, arguments, .. 76 | } = graph.get(*input).as_gamma_in().unwrap(); 77 | 78 | for port in 0..gamma_out.ports_output(graph) { 79 | if let Some(argument) = self.find_shared_reference(graph, regions, port, arguments) { 80 | let link = Link(*output, port.try_into().unwrap()); 81 | 82 | self.map.insert(link, argument); 83 | } 84 | } 85 | } 86 | 87 | fn handle_theta(&mut self, graph: &DataFlowGraph, theta_out: &ThetaOut) { 88 | let ThetaOut { input, results, .. } = theta_out; 89 | let ThetaIn { output, arguments } = graph.get(*input).as_theta_in().unwrap(); 90 | 91 | self.handle_simple_reference(arguments, results, *input, *output); 92 | } 93 | 94 | pub fn run(&mut self, graph: &mut DataFlowGraph) { 95 | self.map.clear(); 96 | 97 | for node in graph.nodes() { 98 | match node { 99 | Node::GammaOut(gamma_out) => self.handle_gamma(graph, gamma_out), 100 | Node::ThetaOut(theta_out) => self.handle_theta(graph, theta_out), 101 | 102 | _ => {} 103 | } 104 | } 105 | 106 | for node in graph.nodes_mut() { 107 | node.for_each_mut_argument(|old| { 108 | if let Some(new) = self.map.get(old) { 109 | *old = *new; 110 | } 111 | }); 112 | } 113 | } 114 | } 115 | 116 | impl Default for InvariantPortMover { 117 | fn default() -> Self { 118 | Self::new() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Data Flow/Visitor/src/control/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dead_port_eliminator; 2 | pub mod invariant_port_mover; 3 | pub mod region_identity; 4 | -------------------------------------------------------------------------------- /Data Flow/Visitor/src/control/region_identity.rs: -------------------------------------------------------------------------------- 1 | use data_flow_graph::{ 2 | DataFlowGraph, Link, Node, 3 | base::Identity, 4 | control::{RegionOut, ThetaIn, ThetaOut}, 5 | }; 6 | 7 | fn replace_with_producer(graph: &DataFlowGraph, from: &mut Link) { 8 | let Node::Identity(Identity { source }) = *graph.get(from.0) else { 9 | return; 10 | }; 11 | 12 | *from = source; 13 | } 14 | 15 | fn remove_at(graph: &DataFlowGraph, node: &mut Node) { 16 | if !matches!( 17 | node, 18 | Node::RegionOut(_) | Node::ThetaIn(_) | Node::ThetaOut(_) 19 | ) { 20 | return; 21 | } 22 | 23 | node.for_each_mut_argument(|argument| replace_with_producer(graph, argument)); 24 | } 25 | 26 | pub fn remove(graph: &mut DataFlowGraph) { 27 | let len = graph.len(); 28 | 29 | for id in 0..len.try_into().unwrap() { 30 | let mut node = std::mem::take(graph.get_mut(id)); 31 | 32 | remove_at(graph, &mut node); 33 | 34 | *graph.get_mut(id) = node; 35 | } 36 | } 37 | 38 | fn replace_with_identity(graph: &mut DataFlowGraph, from: &mut Link) { 39 | let identity = graph.add_identity(*from); 40 | 41 | *from = identity; 42 | } 43 | 44 | // We insert at... 45 | // * `RegionOut` arguments, since we need to issue the correct move order. 46 | // * `ThetaIn` arguments always, since they are mutable and must produce new locals. 47 | // * `ThetaOut` arguments and condition, since we need to issue the correct move order. 48 | fn insert_at(graph: &mut DataFlowGraph, node: &mut Node) { 49 | match node { 50 | Node::RegionOut(RegionOut { results, .. }) => { 51 | for result in results { 52 | replace_with_identity(graph, result); 53 | } 54 | } 55 | Node::ThetaIn(ThetaIn { arguments, .. }) => { 56 | for argument in arguments { 57 | replace_with_identity(graph, argument); 58 | } 59 | } 60 | Node::ThetaOut(ThetaOut { 61 | results, condition, .. 62 | }) => { 63 | replace_with_identity(graph, condition); 64 | 65 | for result in results { 66 | replace_with_identity(graph, result); 67 | } 68 | } 69 | 70 | _ => {} 71 | } 72 | } 73 | 74 | pub fn insert(graph: &mut DataFlowGraph) { 75 | let len = graph.len(); 76 | 77 | for id in 0..len.try_into().unwrap() { 78 | let mut node = std::mem::take(graph.get_mut(id)); 79 | 80 | insert_at(graph, &mut node); 81 | 82 | *graph.get_mut(id) = node; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Data Flow/Visitor/src/isle/context.rs: -------------------------------------------------------------------------------- 1 | use data_flow_graph::{ 2 | DataFlowGraph, Link, Node, 3 | base::{ 4 | GlobalGet, GlobalNew, GlobalSet, IntegerBinaryOperation, IntegerBinaryOperator, 5 | IntegerType, Location, TableGet, TableSet, 6 | }, 7 | }; 8 | 9 | use super::internal::Context; 10 | 11 | impl Context for DataFlowGraph { 12 | fn get_i32(&mut self, source: Link) -> Option { 13 | if let Node::I32(value) = *self.get(source.0) { 14 | Some(value) 15 | } else { 16 | None 17 | } 18 | } 19 | 20 | fn add_i32(&mut self, value: i32) -> Link { 21 | self.add_i32(value) 22 | } 23 | 24 | fn get_i64(&mut self, source: Link) -> Option { 25 | if let Node::I64(source) = *self.get(source.0) { 26 | Some(source) 27 | } else { 28 | None 29 | } 30 | } 31 | 32 | fn add_i64(&mut self, value: i64) -> Link { 33 | self.add_i64(value) 34 | } 35 | 36 | fn get_integer_binary_operation( 37 | &mut self, 38 | source: Link, 39 | ) -> Option<(Link, Link, IntegerType, IntegerBinaryOperator)> { 40 | if let Node::IntegerBinaryOperation(IntegerBinaryOperation { 41 | lhs, 42 | rhs, 43 | r#type, 44 | operator, 45 | }) = *self.get(source.0) 46 | { 47 | Some((lhs, rhs, r#type, operator)) 48 | } else { 49 | None 50 | } 51 | } 52 | 53 | fn add_integer_binary_operation( 54 | &mut self, 55 | lhs: Link, 56 | rhs: Link, 57 | r#type: &IntegerType, 58 | operator: &IntegerBinaryOperator, 59 | ) -> Link { 60 | self.add_integer_binary_operation(lhs, rhs, *r#type, *operator) 61 | } 62 | 63 | fn raw_add_i32(&mut self, lhs: i32, rhs: i32) -> i32 { 64 | lhs.wrapping_add(rhs) 65 | } 66 | 67 | fn raw_sub_i32(&mut self, lhs: i32, rhs: i32) -> i32 { 68 | lhs.wrapping_sub(rhs) 69 | } 70 | 71 | fn get_f32(&mut self, source: Link) -> Option { 72 | if let Node::F32(source) = *self.get(source.0) { 73 | Some(source) 74 | } else { 75 | None 76 | } 77 | } 78 | 79 | fn add_f32(&mut self, value: f32) -> Link { 80 | self.add_f32(value) 81 | } 82 | 83 | fn get_f64(&mut self, source: Link) -> Option { 84 | if let Node::F64(source) = *self.get(source.0) { 85 | Some(source) 86 | } else { 87 | None 88 | } 89 | } 90 | 91 | fn add_f64(&mut self, value: f64) -> Link { 92 | self.add_f64(value) 93 | } 94 | 95 | fn get_table_get(&mut self, source: Link) -> Option<(Link, Link)> { 96 | if let Node::TableGet(TableGet { 97 | source: Location { reference, offset }, 98 | }) = *self.get(source.0) 99 | { 100 | Some((reference, offset)) 101 | } else { 102 | None 103 | } 104 | } 105 | 106 | fn get_table_set(&mut self, source: Link) -> Option<(Link, Link, Link)> { 107 | if let Node::TableSet(TableSet { 108 | destination: Location { reference, offset }, 109 | source, 110 | }) = *self.get(source.0) 111 | { 112 | Some((reference, offset, source)) 113 | } else { 114 | None 115 | } 116 | } 117 | 118 | fn get_global_new(&mut self, source: Link) -> Option { 119 | if let Node::GlobalNew(GlobalNew { initializer }) = *self.get(source.0) { 120 | Some(initializer) 121 | } else { 122 | None 123 | } 124 | } 125 | 126 | fn get_global_get(&mut self, source: Link) -> Option { 127 | if let Node::GlobalGet(GlobalGet { source }) = *self.get(source.0) { 128 | Some(source) 129 | } else { 130 | None 131 | } 132 | } 133 | 134 | fn get_global_set(&mut self, source: Link) -> Option<(Link, Link)> { 135 | if let Node::GlobalSet(GlobalSet { 136 | destination, 137 | source, 138 | }) = *self.get(source.0) 139 | { 140 | Some((destination, source)) 141 | } else { 142 | None 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Data Flow/Visitor/src/isle/internal.rs: -------------------------------------------------------------------------------- 1 | #![expect( 2 | clippy::match_ref_pats, 3 | clippy::pedantic, 4 | dead_code, 5 | non_snake_case, 6 | unused_imports, 7 | unused_variables 8 | )] 9 | 10 | use data_flow_graph::{ 11 | Link, 12 | base::{IntegerBinaryOperator, IntegerType}, 13 | }; 14 | 15 | include!(concat!(env!("OUT_DIR"), "/isle.rs")); 16 | -------------------------------------------------------------------------------- /Data Flow/Visitor/src/isle/mod.rs: -------------------------------------------------------------------------------- 1 | mod context; 2 | mod internal; 3 | 4 | use data_flow_graph::{DataFlowGraph, Link, Node, base::Identity}; 5 | 6 | use self::internal::{ 7 | constructor_SimplifyGlobal, constructor_SimplifyI32, constructor_SimplifyTable, 8 | }; 9 | 10 | fn replace_node_content(graph: &mut DataFlowGraph, destination: u32, source: u32) { 11 | let source = if source < destination { 12 | Node::Identity(Identity { 13 | source: Link(source, 0), 14 | }) 15 | } else { 16 | core::mem::take(graph.get_mut(source)) 17 | }; 18 | 19 | *graph.get_mut(destination) = source; 20 | } 21 | 22 | pub fn simplify_i32(graph: &mut DataFlowGraph, id: u32) -> bool { 23 | constructor_SimplifyI32(graph, Link(id, 0)).is_some_and(|source| { 24 | replace_node_content(graph, id, source.0); 25 | 26 | true 27 | }) 28 | } 29 | 30 | pub fn simplify_global(graph: &mut DataFlowGraph, id: u32) -> bool { 31 | constructor_SimplifyGlobal(graph, Link(id, 0)).is_some_and(|source| { 32 | replace_node_content(graph, id, source.0); 33 | 34 | true 35 | }) 36 | } 37 | 38 | pub fn simplify_table(graph: &mut DataFlowGraph, id: u32) -> bool { 39 | constructor_SimplifyTable(graph, Link(id, 0)).is_some_and(|source| { 40 | replace_node_content(graph, id, source.0); 41 | 42 | true 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /Data Flow/Visitor/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![expect(clippy::missing_panics_doc)] 2 | 3 | pub mod control; 4 | pub mod isle; 5 | pub mod successor_finder; 6 | pub mod topological_normalizer; 7 | -------------------------------------------------------------------------------- /Data Flow/Visitor/src/successor_finder.rs: -------------------------------------------------------------------------------- 1 | use data_flow_graph::{DataFlowGraph, Link}; 2 | 3 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 4 | pub struct BiLink { 5 | pub from: u32, 6 | pub port: u16, 7 | pub to: u32, 8 | } 9 | 10 | pub struct SuccessorFinder { 11 | successors: Vec, 12 | } 13 | 14 | impl SuccessorFinder { 15 | #[must_use] 16 | pub const fn new() -> Self { 17 | Self { 18 | successors: Vec::new(), 19 | } 20 | } 21 | 22 | #[must_use] 23 | pub fn by_id(&self, id: u32) -> &[BiLink] { 24 | let start = self.successors.partition_point(|bi| bi.from < id); 25 | let end = self.successors.partition_point(|bi| bi.from <= id); 26 | 27 | &self.successors[start..end] 28 | } 29 | 30 | #[must_use] 31 | pub fn by_link(&self, link: Link) -> &[BiLink] { 32 | let start = self 33 | .successors 34 | .partition_point(|bi| Link(bi.from, bi.port) < link); 35 | 36 | let end = self 37 | .successors 38 | .partition_point(|bi| Link(bi.from, bi.port) <= link); 39 | 40 | &self.successors[start..end] 41 | } 42 | 43 | pub fn run(&mut self, graph: &DataFlowGraph) { 44 | self.successors.clear(); 45 | 46 | for (node, to) in graph.nodes().zip(0..) { 47 | node.for_each_argument(|Link(from, port)| { 48 | self.successors.push(BiLink { from, port, to }); 49 | }); 50 | } 51 | 52 | self.successors.sort_unstable(); 53 | } 54 | } 55 | 56 | impl Default for SuccessorFinder { 57 | fn default() -> Self { 58 | Self::new() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Data Flow/Visitor/src/topological_normalizer.rs: -------------------------------------------------------------------------------- 1 | use data_flow_graph::{DataFlowGraph, Node}; 2 | use set::Set; 3 | 4 | struct DepthFirstSearcher { 5 | seen: Set, 6 | stack: Vec<(u32, bool)>, 7 | } 8 | 9 | impl DepthFirstSearcher { 10 | const fn new() -> Self { 11 | Self { 12 | seen: Set::new(), 13 | stack: Vec::new(), 14 | } 15 | } 16 | 17 | fn add_predecessor(&mut self, id: u32) { 18 | if self.seen.contains(id.try_into().unwrap()) { 19 | return; 20 | } 21 | 22 | self.stack.push((id, false)); 23 | } 24 | 25 | fn add_predecessors(&mut self, node: &Node) { 26 | let start = self.stack.len(); 27 | 28 | node.for_each_requirement(|id| self.add_predecessor(id)); 29 | node.for_each_argument(|link| self.add_predecessor(link.0)); 30 | 31 | self.stack[start..].reverse(); 32 | } 33 | 34 | fn run(&mut self, graph: &mut DataFlowGraph, result: u32, mut handler: H) 35 | where 36 | H: FnMut(&mut DataFlowGraph, u32), 37 | { 38 | self.seen.clear(); 39 | 40 | self.add_predecessor(result); 41 | 42 | while let Some((id, post)) = self.stack.pop() { 43 | if self.seen.grow_insert(id.try_into().unwrap()) { 44 | if post { 45 | handler(graph, id); 46 | } 47 | } else { 48 | self.stack.push((id, true)); 49 | 50 | self.add_predecessors(graph.get(id)); 51 | } 52 | } 53 | } 54 | } 55 | 56 | pub struct TopologicalNormalizer { 57 | nodes: Vec, 58 | id_to_post: Vec, 59 | 60 | depth_first_searcher: DepthFirstSearcher, 61 | } 62 | 63 | impl TopologicalNormalizer { 64 | #[must_use] 65 | pub const fn new() -> Self { 66 | Self { 67 | nodes: Vec::new(), 68 | id_to_post: Vec::new(), 69 | 70 | depth_first_searcher: DepthFirstSearcher::new(), 71 | } 72 | } 73 | 74 | fn handle_nodes(&mut self, graph: &mut DataFlowGraph, result: u32) { 75 | let mut post = 0; 76 | 77 | self.nodes.clear(); 78 | self.id_to_post.clear(); 79 | self.id_to_post.resize(graph.len(), u32::MAX); 80 | 81 | self.depth_first_searcher.run(graph, result, |graph, id| { 82 | let node = std::mem::take(graph.get_mut(id)); 83 | 84 | self.nodes.push(node); 85 | self.id_to_post[usize::try_from(id).unwrap()] = post; 86 | 87 | post += 1; 88 | }); 89 | 90 | std::mem::swap(graph.inner_mut(), &mut self.nodes); 91 | } 92 | 93 | fn handle_edges(&self, graph: &mut DataFlowGraph, result: u32) -> u32 { 94 | for node in graph.nodes_mut() { 95 | node.for_each_mut_id(|id| { 96 | let index = usize::try_from(*id).unwrap(); 97 | 98 | *id = self.id_to_post[index]; 99 | }); 100 | } 101 | 102 | self.id_to_post[usize::try_from(result).unwrap()] 103 | } 104 | 105 | pub fn run(&mut self, graph: &mut DataFlowGraph, result: u32) -> u32 { 106 | self.handle_nodes(graph, result); 107 | self.handle_edges(graph, result) 108 | } 109 | } 110 | 111 | impl Default for TopologicalNormalizer { 112 | fn default() -> Self { 113 | Self::new() 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spider 2 | 3 | Spider is a compiler backend based on WebAssembly semantics. A single target front end is currently supported, which consumes `.wasm` binary modules and produces `.luau` source files. 4 | 5 | ## Install 6 | 7 | You can install the standalone executable through `cargo`, and then run `wasm2luau --help` for usage. 8 | 9 | ```sh 10 | $ cargo install --git "https://github.com/SovereignSatellite/Spider" 11 | $ wasm2luau --help 12 | ``` 13 | -------------------------------------------------------------------------------- /Targets/Luau/Builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "luau-builder" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | data-flow-graph = { path = "../../../Data Flow/Graph" } 8 | data-flow-builder = { path = "../../../Data Flow/Builder" } 9 | luau-tree = { path = "../Tree" } 10 | 11 | hashbrown.workspace = true 12 | set.workspace = true 13 | -------------------------------------------------------------------------------- /Targets/Luau/Builder/src/code_handler.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use data_flow_graph::{Link, base}; 3 | 4 | use hashbrown::HashMap; 5 | use luau_tree::{ 6 | expression::{Expression, Local}, 7 | statement::{ 8 | Assign, AssignAll, Call, DataDrop, ElementsDrop, GlobalSet, Match, MemoryCopy, MemoryFill, 9 | MemoryInit, MemoryStore, Repeat, Sequence, Statement, TableCopy, TableFill, TableInit, 10 | TableSet, 11 | }, 12 | }; 13 | 14 | use super::data_handler::DataHandler; 15 | 16 | pub struct CodeHandler { 17 | scopes: Vec>, 18 | 19 | regions: HashMap, 20 | } 21 | 22 | impl CodeHandler { 23 | pub fn new() -> Self { 24 | Self { 25 | scopes: Vec::new(), 26 | 27 | regions: HashMap::new(), 28 | } 29 | } 30 | 31 | pub fn pop_scope(&mut self) -> Sequence { 32 | let list = self.scopes.pop().unwrap(); 33 | 34 | Sequence { list } 35 | } 36 | 37 | pub fn pop_branch(&mut self, id: u32) { 38 | let code = self.pop_scope(); 39 | 40 | self.regions.insert(id, code); 41 | } 42 | 43 | pub fn push_scope(&mut self) { 44 | self.scopes.push(Vec::new()); 45 | } 46 | 47 | fn do_match_statement(&mut self, condition: Expression, branches: Vec) { 48 | let statement = Statement::Match( 49 | Match { 50 | branches, 51 | condition, 52 | } 53 | .into(), 54 | ); 55 | 56 | self.scopes.last_mut().unwrap().push(statement); 57 | } 58 | 59 | pub fn do_match(&mut self, regions: &[u32], condition: Link, data_handler: &mut DataHandler) { 60 | let condition = data_handler.load(condition); 61 | let condition = if regions.len() == 2 { 62 | condition.into_boolean() 63 | } else { 64 | condition 65 | }; 66 | 67 | let branches: Vec<_> = regions 68 | .iter() 69 | .map(|id| self.regions.remove(id).unwrap()) 70 | .collect(); 71 | 72 | if let Some(destination) = Sequence::as_branch_destination(&branches) { 73 | let source = DataHandler::load_match_expression(condition, branches); 74 | 75 | self.do_assign(destination, source); 76 | } else { 77 | self.do_match_statement(condition, branches); 78 | } 79 | } 80 | 81 | pub fn do_repeat(&mut self, condition: Link, data_handler: &mut DataHandler) { 82 | let condition = data_handler.load(condition).into_boolean(); 83 | let code = self.pop_scope(); 84 | 85 | let statement = Statement::Repeat(Repeat { code, condition }.into()); 86 | 87 | self.scopes.last_mut().unwrap().push(statement); 88 | } 89 | 90 | pub fn do_rename(&mut self, destination: Link, source: Link, data_handler: &DataHandler) { 91 | let Some(destination) = data_handler.get_local(destination) else { 92 | return; 93 | }; 94 | 95 | let source = data_handler.get_local(source).unwrap(); 96 | 97 | self.do_assign(destination, Expression::Local(source)); 98 | } 99 | 100 | pub fn do_assign(&mut self, destination: Local, source: Expression) { 101 | if let Expression::Local(source) = source 102 | && destination == source 103 | { 104 | return; 105 | } 106 | 107 | let statement = Statement::Assign( 108 | Assign { 109 | destination, 110 | source, 111 | } 112 | .into(), 113 | ); 114 | 115 | self.scopes.last_mut().unwrap().push(statement); 116 | } 117 | 118 | pub fn do_assign_all(&mut self, id: u32, sources: &[Link], data_handler: &DataHandler) { 119 | let mut assignments = data_handler.load_assign_all(id, sources); 120 | 121 | if assignments.is_empty() { 122 | return; 123 | } 124 | 125 | assignments.sort_unstable(); 126 | 127 | let statement = Statement::AssignAll(AssignAll { assignments }.into()); 128 | 129 | self.scopes.last_mut().unwrap().push(statement); 130 | } 131 | 132 | pub fn do_call(&mut self, node: &base::Apply, id: u32, data_handler: &mut DataHandler) { 133 | let end = node.arguments.len() - usize::from(node.states); 134 | let statement = Statement::Call( 135 | Call { 136 | function: data_handler.load(node.function), 137 | arguments: data_handler.load_all(&node.arguments[..end]), 138 | results: data_handler.load_local_assignments(id, 0..node.results), 139 | } 140 | .into(), 141 | ); 142 | 143 | self.scopes.last_mut().unwrap().push(statement); 144 | } 145 | 146 | pub fn do_global_set(&mut self, node: base::GlobalSet, data_handler: &mut DataHandler) { 147 | let statement = Statement::GlobalSet( 148 | GlobalSet { 149 | destination: data_handler.load(node.destination), 150 | source: data_handler.load(node.source), 151 | } 152 | .into(), 153 | ); 154 | 155 | self.scopes.last_mut().unwrap().push(statement); 156 | } 157 | 158 | pub fn do_table_set(&mut self, node: base::TableSet, data_handler: &mut DataHandler) { 159 | let statement = Statement::TableSet( 160 | TableSet { 161 | destination: data_handler.load_location(node.destination), 162 | source: data_handler.load(node.source), 163 | } 164 | .into(), 165 | ); 166 | 167 | self.scopes.last_mut().unwrap().push(statement); 168 | } 169 | 170 | pub fn do_table_fill(&mut self, node: base::TableFill, data_handler: &mut DataHandler) { 171 | let statement = Statement::TableFill( 172 | TableFill { 173 | destination: data_handler.load_location(node.destination), 174 | source: data_handler.load(node.source), 175 | size: data_handler.load(node.size), 176 | } 177 | .into(), 178 | ); 179 | 180 | self.scopes.last_mut().unwrap().push(statement); 181 | } 182 | 183 | pub fn do_table_copy(&mut self, node: base::TableCopy, data_handler: &mut DataHandler) { 184 | let statement = Statement::TableCopy( 185 | TableCopy { 186 | destination: data_handler.load_location(node.destination), 187 | source: data_handler.load_location(node.source), 188 | size: data_handler.load(node.size), 189 | } 190 | .into(), 191 | ); 192 | 193 | self.scopes.last_mut().unwrap().push(statement); 194 | } 195 | 196 | pub fn do_table_init(&mut self, node: base::TableInit, data_handler: &mut DataHandler) { 197 | let statement = Statement::TableInit( 198 | TableInit { 199 | destination: data_handler.load_location(node.destination), 200 | source: data_handler.load_location(node.source), 201 | size: data_handler.load(node.size), 202 | } 203 | .into(), 204 | ); 205 | 206 | self.scopes.last_mut().unwrap().push(statement); 207 | } 208 | 209 | pub fn do_elements_drop(&mut self, node: base::ElementsDrop, data_handler: &mut DataHandler) { 210 | let statement = Statement::ElementsDrop( 211 | ElementsDrop { 212 | source: data_handler.load(node.source), 213 | } 214 | .into(), 215 | ); 216 | 217 | self.scopes.last_mut().unwrap().push(statement); 218 | } 219 | 220 | pub fn do_memory_store(&mut self, node: base::MemoryStore, data_handler: &mut DataHandler) { 221 | let statement = Statement::MemoryStore( 222 | MemoryStore { 223 | destination: data_handler.load_location(node.destination), 224 | source: data_handler.load(node.source), 225 | r#type: node.r#type, 226 | } 227 | .into(), 228 | ); 229 | 230 | self.scopes.last_mut().unwrap().push(statement); 231 | } 232 | 233 | pub fn do_memory_fill(&mut self, node: base::MemoryFill, data_handler: &mut DataHandler) { 234 | let statement = Statement::MemoryFill( 235 | MemoryFill { 236 | destination: data_handler.load_location(node.destination), 237 | byte: data_handler.load(node.byte), 238 | size: data_handler.load(node.size), 239 | } 240 | .into(), 241 | ); 242 | 243 | self.scopes.last_mut().unwrap().push(statement); 244 | } 245 | 246 | pub fn do_memory_copy(&mut self, node: base::MemoryCopy, data_handler: &mut DataHandler) { 247 | let statement = Statement::MemoryCopy( 248 | MemoryCopy { 249 | destination: data_handler.load_location(node.destination), 250 | source: data_handler.load_location(node.source), 251 | size: data_handler.load(node.size), 252 | } 253 | .into(), 254 | ); 255 | 256 | self.scopes.last_mut().unwrap().push(statement); 257 | } 258 | 259 | pub fn do_memory_init( 260 | &mut self, 261 | memory_init: base::MemoryInit, 262 | data_handler: &mut DataHandler, 263 | ) { 264 | let statement = Statement::MemoryInit( 265 | MemoryInit { 266 | destination: data_handler.load_location(memory_init.destination), 267 | source: data_handler.load_location(memory_init.source), 268 | size: data_handler.load(memory_init.size), 269 | } 270 | .into(), 271 | ); 272 | 273 | self.scopes.last_mut().unwrap().push(statement); 274 | } 275 | 276 | pub fn do_data_drop(&mut self, node: base::DataDrop, data_handler: &mut DataHandler) { 277 | let statement = Statement::DataDrop( 278 | DataDrop { 279 | source: data_handler.load(node.source), 280 | } 281 | .into(), 282 | ); 283 | 284 | self.scopes.last_mut().unwrap().push(statement); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /Targets/Luau/Builder/src/local_allocator/argument_finder.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use data_flow_graph::{ 3 | DataFlowGraph, Link, Node, 4 | control::{LambdaIn, LambdaOut, OmegaIn, OmegaOut}, 5 | }; 6 | use hashbrown::HashMap; 7 | use set::Set; 8 | 9 | pub fn get_region_range(graph: &DataFlowGraph, node: &Node) -> Option<(u32, u32)> { 10 | let range = match *node { 11 | Node::OmegaOut(OmegaOut { input, .. }) => { 12 | let OmegaIn { output } = *graph.get(input).as_omega_in().unwrap(); 13 | 14 | (input, output) 15 | } 16 | Node::LambdaOut(LambdaOut { input, .. }) => { 17 | let LambdaIn { output, .. } = *graph.get(input).as_lambda_in().unwrap(); 18 | 19 | (input, output) 20 | } 21 | 22 | _ => return None, 23 | }; 24 | 25 | Some(range) 26 | } 27 | 28 | pub struct ArgumentFinder { 29 | seen: Set, 30 | stack: Vec, 31 | } 32 | 33 | impl ArgumentFinder { 34 | pub const fn new() -> Self { 35 | Self { 36 | seen: Set::new(), 37 | stack: Vec::new(), 38 | } 39 | } 40 | 41 | pub fn clear(&mut self) { 42 | self.seen.clear(); 43 | } 44 | 45 | fn handle_arguments( 46 | &mut self, 47 | arguments: &mut Vec<(Link, Link)>, 48 | preferences: &HashMap, 49 | graph: &DataFlowGraph, 50 | id: u32, 51 | ) { 52 | if self.seen.grow_insert(id.try_into().unwrap()) { 53 | return; 54 | } 55 | 56 | graph.get(id).for_each_argument(|link| { 57 | if let Some(&preferred) = preferences.get(&link) { 58 | arguments.push((link, preferred)); 59 | } else { 60 | self.stack.push(link.0); 61 | } 62 | }); 63 | } 64 | 65 | pub fn run( 66 | &mut self, 67 | arguments: &mut Vec<(Link, Link)>, 68 | preferences: &HashMap, 69 | graph: &DataFlowGraph, 70 | start: u32, 71 | ) { 72 | arguments.clear(); 73 | 74 | self.handle_arguments(arguments, preferences, graph, start); 75 | 76 | while let Some(id) = self.stack.pop() { 77 | let id = get_region_range(graph, graph.get(id)).map_or(id, |range| range.0); 78 | 79 | self.handle_arguments(arguments, preferences, graph, id); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Targets/Luau/Builder/src/local_allocator/index_provider.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::binary_heap::{BinaryHeap, PeekMut}; 2 | 3 | #[derive(PartialEq, Eq, PartialOrd, Ord)] 4 | struct Hold { 5 | start: u32, 6 | name: u32, 7 | } 8 | 9 | pub struct IndexProvider { 10 | holds: BinaryHeap, 11 | free: BinaryHeap, 12 | 13 | names: u32, 14 | } 15 | 16 | impl IndexProvider { 17 | pub const fn new() -> Self { 18 | Self { 19 | holds: BinaryHeap::new(), 20 | free: BinaryHeap::new(), 21 | 22 | names: 0, 23 | } 24 | } 25 | 26 | pub const fn get_names(&self) -> u32 { 27 | self.names 28 | } 29 | 30 | pub const fn set_names(&mut self, names: u32) { 31 | self.names = names; 32 | } 33 | 34 | pub fn forget_names(&mut self) { 35 | self.holds.clear(); 36 | self.free.clear(); 37 | } 38 | 39 | pub fn should_exceed(&self, count: usize) -> bool { 40 | self.holds.len() >= count 41 | } 42 | 43 | const fn pull_name(&mut self) -> u32 { 44 | let name = self.names; 45 | 46 | self.names = name + 1; 47 | 48 | name 49 | } 50 | 51 | pub fn pull(&mut self, start: u32) -> u32 { 52 | let name = self.free.pop().unwrap_or_else(|| self.pull_name()); 53 | 54 | self.holds.push(Hold { start, name }); 55 | 56 | name 57 | } 58 | 59 | fn remove_free_if_found(&mut self, name: u32) -> bool { 60 | if let Some(position) = self.free.iter().position(|&other| name == other) { 61 | let mut free = core::mem::take(&mut self.free).into_vec(); 62 | 63 | free.swap_remove(position); 64 | 65 | self.free = free.into(); 66 | 67 | true 68 | } else { 69 | false 70 | } 71 | } 72 | 73 | pub fn try_revive(&mut self, name: u32, start: u32) -> bool { 74 | if self.remove_free_if_found(name) { 75 | self.holds.push(Hold { start, name }); 76 | 77 | true 78 | } else { 79 | false 80 | } 81 | } 82 | 83 | pub fn push_until(&mut self, start: u32) { 84 | while let Some(peek) = self.holds.peek_mut() { 85 | if peek.start < start { 86 | break; 87 | } 88 | 89 | let peek = PeekMut::pop(peek); 90 | 91 | self.free.push(peek.name); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Targets/Luau/Builder/src/local_allocator/local_provider.rs: -------------------------------------------------------------------------------- 1 | use data_flow_graph::Link; 2 | use hashbrown::{HashMap, hash_map::Entry}; 3 | use luau_tree::expression::{Local, Name}; 4 | 5 | use super::index_provider::IndexProvider; 6 | 7 | // Luau has a max amount of local variables and no register allocator, so we must 8 | // decide to spill to our own table per function after this count is reached. 9 | const MAX_LOCAL_VARIABLES: usize = 197; 10 | 11 | pub struct LocalProvider { 12 | local_provider: IndexProvider, 13 | table_provider: IndexProvider, 14 | } 15 | 16 | impl LocalProvider { 17 | pub const fn new() -> Self { 18 | Self { 19 | local_provider: IndexProvider::new(), 20 | table_provider: IndexProvider::new(), 21 | } 22 | } 23 | 24 | pub fn clear(&mut self) { 25 | self.local_provider.set_names(0); 26 | self.local_provider.forget_names(); 27 | 28 | self.table_provider.set_names(0); 29 | self.table_provider.forget_names(); 30 | } 31 | 32 | pub fn refresh(&mut self) { 33 | self.local_provider.forget_names(); 34 | 35 | self.table_provider.set_names(0); 36 | self.table_provider.forget_names(); 37 | } 38 | 39 | pub const fn get_names(&self) -> (u32, u32) { 40 | ( 41 | self.local_provider.get_names(), 42 | self.table_provider.get_names(), 43 | ) 44 | } 45 | 46 | fn pull(&mut self, start: u32) -> Local { 47 | if self.local_provider.should_exceed(MAX_LOCAL_VARIABLES) { 48 | let offset = self.table_provider.pull(start).try_into().unwrap(); 49 | 50 | Local::Slow { offset } 51 | } else { 52 | let name = Name { 53 | id: self.local_provider.pull(start), 54 | }; 55 | 56 | Local::Fast { name } 57 | } 58 | } 59 | 60 | fn try_revive(&mut self, local: Local, start: u32) -> bool { 61 | match local { 62 | Local::Fast { name } => self.local_provider.try_revive(name.id, start), 63 | Local::Slow { offset } => self.table_provider.try_revive(offset.into(), start), 64 | } 65 | } 66 | 67 | pub fn try_revive_into( 68 | &mut self, 69 | assignments: &mut HashMap, 70 | destination: Link, 71 | preferred: Link, 72 | ) -> bool { 73 | let Some(&local) = assignments.get(&preferred) else { 74 | return false; 75 | }; 76 | 77 | let Entry::Vacant(entry) = assignments.entry(destination) else { 78 | return true; 79 | }; 80 | 81 | if !self.try_revive(local, destination.0) { 82 | return false; 83 | } 84 | 85 | entry.insert(local); 86 | 87 | true 88 | } 89 | 90 | pub fn try_pull_into(&mut self, assignments: &mut HashMap, link: Link) { 91 | let Entry::Vacant(entry) = assignments.entry(link) else { 92 | return; 93 | }; 94 | 95 | let local = self.pull(link.0); 96 | 97 | entry.insert(local); 98 | } 99 | 100 | pub fn push_until(&mut self, start: u32) { 101 | self.local_provider.push_until(start); 102 | self.table_provider.push_until(start); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Targets/Luau/Builder/src/local_allocator/mod.rs: -------------------------------------------------------------------------------- 1 | use core::ops::Range; 2 | 3 | use alloc::vec::Vec; 4 | use data_flow_graph::{DataFlowGraph, Link}; 5 | use hashbrown::HashMap; 6 | use luau_tree::expression::Local; 7 | 8 | use self::{ 9 | argument_finder::{ArgumentFinder, get_region_range}, 10 | local_provider::LocalProvider, 11 | scalar_finder::ScalarFinder, 12 | }; 13 | 14 | mod argument_finder; 15 | mod index_provider; 16 | mod local_provider; 17 | mod reference_finder; 18 | mod scalar_finder; 19 | 20 | pub struct Declarations { 21 | pub locals: Range, 22 | pub stack: u16, 23 | } 24 | 25 | pub struct LocalAllocator { 26 | preferences: HashMap, 27 | functions: Vec>, 28 | arguments: Vec<(Link, Link)>, 29 | 30 | provider: LocalProvider, 31 | scalar_finder: ScalarFinder, 32 | argument_finder: ArgumentFinder, 33 | } 34 | 35 | impl LocalAllocator { 36 | pub fn new() -> Self { 37 | Self { 38 | preferences: HashMap::new(), 39 | functions: Vec::new(), 40 | arguments: Vec::new(), 41 | 42 | provider: LocalProvider::new(), 43 | scalar_finder: ScalarFinder::new(), 44 | argument_finder: ArgumentFinder::new(), 45 | } 46 | } 47 | 48 | fn find_functions(&mut self, graph: &DataFlowGraph) { 49 | self.functions.extend( 50 | graph.nodes().rev().filter_map(|node| { 51 | get_region_range(graph, node).map(|(start, end)| start..end + 1) 52 | }), 53 | ); 54 | } 55 | 56 | fn handle_arguments( 57 | &mut self, 58 | assignments: &mut HashMap, 59 | graph: &DataFlowGraph, 60 | id: u32, 61 | ) { 62 | let mut arguments = core::mem::take(&mut self.arguments); 63 | 64 | self.argument_finder 65 | .run(&mut arguments, &self.preferences, graph, id); 66 | 67 | // In the first pass we ensure all producers outside the list 68 | // have their variables reused. 69 | arguments.retain(|&(argument, preferred)| { 70 | preferred == Link::DANGLING 71 | || !self 72 | .provider 73 | .try_revive_into(assignments, argument, preferred) 74 | }); 75 | 76 | arguments.sort_unstable(); 77 | 78 | // In the second pass we assign new variables where needed and 79 | // reuse producers within the list. 80 | for &(argument, preferred) in arguments.iter().rev() { 81 | if preferred != Link::DANGLING 82 | && self 83 | .provider 84 | .try_revive_into(assignments, argument, preferred) 85 | { 86 | continue; 87 | } 88 | 89 | self.provider.try_pull_into(assignments, argument); 90 | } 91 | 92 | self.arguments = arguments; 93 | } 94 | 95 | fn handle_definitions(&mut self, assignments: &mut HashMap, id: u32) { 96 | // We might have some locals that require assignment but have no uses, which means we must 97 | // manually declare them with an empty lifetime. 98 | for link in (0..) 99 | .map(|port| Link(id, port)) 100 | .take_while(|link| self.preferences.contains_key(link)) 101 | { 102 | self.provider.try_pull_into(assignments, link); 103 | } 104 | } 105 | 106 | fn handle_node( 107 | &mut self, 108 | assignments: &mut HashMap, 109 | graph: &DataFlowGraph, 110 | id: u32, 111 | ) -> u32 { 112 | let (start, end) = get_region_range(graph, graph.get(id)).unwrap_or((id, id)); 113 | 114 | self.handle_definitions(assignments, end); 115 | 116 | self.provider.push_until(start); 117 | 118 | self.handle_arguments(assignments, graph, start); 119 | 120 | start 121 | } 122 | 123 | fn handle_scope( 124 | &mut self, 125 | assignments: &mut HashMap, 126 | graph: &DataFlowGraph, 127 | mut range: Range, 128 | ) { 129 | self.handle_definitions(assignments, range.next().unwrap()); 130 | self.handle_arguments(assignments, graph, range.next_back().unwrap()); 131 | 132 | while let Some(id) = range.next_back() { 133 | range.end = self.handle_node(assignments, graph, id); 134 | } 135 | } 136 | 137 | fn handle_function( 138 | &mut self, 139 | assignments: &mut HashMap, 140 | graph: &DataFlowGraph, 141 | range: Range, 142 | ) -> Declarations { 143 | let (first, _) = self.provider.get_names(); 144 | 145 | self.handle_scope(assignments, graph, range); 146 | 147 | let (last, table) = self.provider.get_names(); 148 | 149 | self.provider.refresh(); 150 | 151 | Declarations { 152 | locals: first..last, 153 | stack: table.try_into().unwrap(), 154 | } 155 | } 156 | 157 | pub fn run( 158 | &mut self, 159 | declarations: &mut HashMap, 160 | assignments: &mut HashMap, 161 | graph: &DataFlowGraph, 162 | ) { 163 | self.preferences.clear(); 164 | self.provider.clear(); 165 | self.argument_finder.clear(); 166 | 167 | reference_finder::run(&mut self.preferences, graph); 168 | self.scalar_finder.run(&mut self.preferences, graph); 169 | 170 | self.find_functions(graph); 171 | 172 | declarations.clear(); 173 | assignments.clear(); 174 | 175 | while let Some(range) = self.functions.pop() { 176 | let declaration = self.handle_function(assignments, graph, range.clone()); 177 | 178 | declarations.insert(range.start, declaration); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Targets/Luau/Builder/src/local_allocator/scalar_finder.rs: -------------------------------------------------------------------------------- 1 | use data_flow_graph::{DataFlowGraph, Link, Node}; 2 | use hashbrown::{HashMap, hash_map::Entry}; 3 | 4 | pub fn result_count_of(node: &Node) -> u16 { 5 | match node { 6 | Node::LambdaIn(_) 7 | | Node::RegionIn(_) 8 | | Node::RegionOut(_) 9 | | Node::GammaIn(_) 10 | | Node::GammaOut(_) 11 | | Node::ThetaIn(_) 12 | | Node::ThetaOut(_) 13 | | Node::OmegaIn(_) 14 | | Node::Merge(_) 15 | | Node::GlobalSet(_) 16 | | Node::TableSet(_) 17 | | Node::TableFill(_) 18 | | Node::TableCopy(_) 19 | | Node::TableInit(_) 20 | | Node::ElementsDrop(_) 21 | | Node::MemoryStore(_) 22 | | Node::MemoryFill(_) 23 | | Node::MemoryCopy(_) 24 | | Node::MemoryInit(_) 25 | | Node::DataDrop(_) => 0, 26 | 27 | Node::Host(_node) => 0, 28 | 29 | Node::LambdaOut(_) 30 | | Node::OmegaOut(_) 31 | | Node::Import(_) 32 | | Node::Trap 33 | | Node::Null 34 | | Node::I32(_) 35 | | Node::I64(_) 36 | | Node::F32(_) 37 | | Node::F64(_) 38 | | Node::Identity(_) 39 | | Node::RefIsNull(_) 40 | | Node::IntegerUnaryOperation(_) 41 | | Node::IntegerBinaryOperation(_) 42 | | Node::IntegerCompareOperation(_) 43 | | Node::IntegerNarrow(_) 44 | | Node::IntegerWiden(_) 45 | | Node::IntegerExtend(_) 46 | | Node::IntegerConvertToNumber(_) 47 | | Node::IntegerTransmuteToNumber(_) 48 | | Node::NumberUnaryOperation(_) 49 | | Node::NumberBinaryOperation(_) 50 | | Node::NumberCompareOperation(_) 51 | | Node::NumberNarrow(_) 52 | | Node::NumberWiden(_) 53 | | Node::NumberTruncateToInteger(_) 54 | | Node::NumberTransmuteToInteger(_) 55 | | Node::GlobalNew(_) 56 | | Node::GlobalGet(_) 57 | | Node::TableNew(_) 58 | | Node::TableGet(_) 59 | | Node::TableSize(_) 60 | | Node::TableGrow(_) 61 | | Node::ElementsNew(_) 62 | | Node::MemoryNew(_) 63 | | Node::MemoryLoad(_) 64 | | Node::MemorySize(_) 65 | | Node::MemoryGrow(_) 66 | | Node::DataNew(_) => 1, 67 | 68 | Node::Apply(node) => node.results, 69 | } 70 | } 71 | 72 | fn add_value_assignments(assignments: &mut HashMap, graph: &DataFlowGraph, id: u32) { 73 | let results = result_count_of(graph.get(id)); 74 | 75 | for link in (0..results).map(|port| Link(id, port)) { 76 | let _ = assignments.try_insert(link, Link::DANGLING); 77 | } 78 | } 79 | 80 | pub struct ScalarFinder { 81 | handled: HashMap, 82 | } 83 | 84 | impl ScalarFinder { 85 | pub fn new() -> Self { 86 | Self { 87 | handled: HashMap::new(), 88 | } 89 | } 90 | 91 | fn handle_effects( 92 | &mut self, 93 | assignments: &mut HashMap, 94 | graph: &DataFlowGraph, 95 | id: u32, 96 | node: &Node, 97 | ) { 98 | // Without at least one value reference we might discard the side effects 99 | // of these expressions. 100 | if !matches!( 101 | node, 102 | Node::Apply(_) | Node::TableGrow(_) | Node::MemoryGrow(_) 103 | ) { 104 | return; 105 | } 106 | 107 | if let Entry::Vacant(entry) = self.handled.entry(id) { 108 | entry.insert(true); 109 | 110 | add_value_assignments(assignments, graph, id); 111 | } 112 | } 113 | 114 | fn handle_repeat( 115 | &mut self, 116 | assignments: &mut HashMap, 117 | graph: &DataFlowGraph, 118 | id: u32, 119 | ) { 120 | match self.handled.entry(id) { 121 | Entry::Occupied(mut entry) => { 122 | if entry.insert(true) { 123 | return; 124 | } 125 | 126 | add_value_assignments(assignments, graph, id); 127 | } 128 | Entry::Vacant(entry) => { 129 | entry.insert(false); 130 | } 131 | } 132 | } 133 | 134 | fn handle_excess( 135 | &mut self, 136 | assignments: &mut HashMap, 137 | graph: &DataFlowGraph, 138 | id: u32, 139 | ) { 140 | if self.handled.insert(id, true).unwrap_or_default() { 141 | return; 142 | } 143 | 144 | add_value_assignments(assignments, graph, id); 145 | } 146 | 147 | fn handle_uses( 148 | &mut self, 149 | assignments: &mut HashMap, 150 | graph: &DataFlowGraph, 151 | node: &Node, 152 | ) { 153 | node.for_each_argument(|Link(id, port)| { 154 | if port == 0 { 155 | self.handle_repeat(assignments, graph, id); 156 | } else { 157 | self.handle_excess(assignments, graph, id); 158 | } 159 | }); 160 | } 161 | 162 | // We assign locals to all value ports in a node if... 163 | // * Any value port is used out of local order. 164 | // * Any value port has more than one use. 165 | // * Any value port other than the first is in use. 166 | // * No value port is used but it has side effects. 167 | pub fn run(&mut self, assignments: &mut HashMap, graph: &DataFlowGraph) { 168 | self.handled.clear(); 169 | 170 | // All uses are handled first since that contains all base assignments. 171 | for node in graph.nodes() { 172 | self.handle_uses(assignments, graph, node); 173 | } 174 | 175 | // Then, effects are handled from the missing assignments. 176 | for (node, id) in graph.nodes().zip(0..) { 177 | self.handle_effects(assignments, graph, id, node); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "luau-printer" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | data-flow-graph = { path = "../../../Data Flow/Graph" } 8 | data-flow-builder = { path = "../../../Data Flow/Builder" } 9 | data-flow-visitor = { path = "../../../Data Flow/Visitor" } 10 | luau-tree = { path = "../Tree" } 11 | luau-builder = { path = "../Builder" } 12 | 13 | clap.workspace = true 14 | hashbrown.workspace = true 15 | wasmparser = { workspace = true, features = ["validate"] } 16 | 17 | [[bin]] 18 | name = "wasm2luau" 19 | path = "src/main.rs" 20 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/runtime/base.luau: -------------------------------------------------------------------------------- 1 | -- SECTION bit_and 2 | local bit_and = bit32.band 3 | 4 | -- SECTION bit_or 5 | local bit_or = bit32.bor 6 | 7 | -- SECTION bit_xor 8 | local bit_xor = bit32.bxor 9 | 10 | -- SECTION bit_lshift 11 | local bit_lshift = bit32.lshift 12 | 13 | -- SECTION bit_rshift 14 | local bit_rshift = bit32.rshift 15 | 16 | -- SECTION bit_arshift 17 | local bit_arshift = bit32.arshift 18 | 19 | -- SECTION bit_lrotate 20 | local bit_lrotate = bit32.lrotate 21 | 22 | -- SECTION bit_rrotate 23 | local bit_rrotate = bit32.rrotate 24 | 25 | -- SECTION bit_not 26 | local bit_not = bit32.bnot 27 | 28 | -- SECTION bit_replace 29 | local bit_replace = bit32.replace 30 | 31 | -- SECTION bit_countlz 32 | local bit_countlz = bit32.countlz 33 | 34 | -- SECTION bit_countrz 35 | local bit_countrz = bit32.countrz 36 | 37 | -- SECTION math_abs 38 | local math_abs = math.abs 39 | 40 | -- SECTION math_sqrt 41 | local math_sqrt = math.sqrt 42 | 43 | -- SECTION math_ceil 44 | local math_ceil = math.ceil 45 | 46 | -- SECTION math_floor 47 | local math_floor = math.floor 48 | 49 | -- SECTION math_round 50 | local math_round = math.round 51 | 52 | -- SECTION math_min 53 | local math_min = math.min 54 | 55 | -- SECTION math_max 56 | local math_max = math.max 57 | 58 | -- SECTION math_fmod 59 | local math_fmod = math.fmod 60 | 61 | -- SECTION vector_create 62 | local vector_create = vector.create 63 | 64 | -- SECTION vector_abs 65 | local vector_abs = vector.abs 66 | 67 | -- SECTION vector_min 68 | local vector_min = vector.min 69 | 70 | -- SECTION vector_max 71 | local vector_max = vector.max 72 | 73 | -- SECTION raw_memory 74 | local RAW_MEMORY = buffer.create(8) 75 | 76 | -- SECTION buffer_read_i8 77 | local buffer_read_i8 = buffer.readi8 78 | 79 | -- SECTION buffer_read_u8 80 | local buffer_read_u8 = buffer.readu8 81 | 82 | -- SECTION buffer_read_i16 83 | local buffer_read_i16 = buffer.readi16 84 | 85 | -- SECTION buffer_read_u16 86 | local buffer_read_u16 = buffer.readu16 87 | 88 | -- SECTION buffer_read_i32 89 | local buffer_read_i32 = buffer.readi32 90 | 91 | -- SECTION buffer_read_u32 92 | local buffer_read_u32 = buffer.readu32 93 | 94 | -- SECTION buffer_read_f32 95 | local buffer_read_f32 = buffer.readf32 96 | 97 | -- SECTION buffer_read_f64 98 | local buffer_read_f64 = buffer.readf64 99 | 100 | -- SECTION buffer_write_u8 101 | local buffer_write_u8 = buffer.writeu8 102 | 103 | -- SECTION buffer_write_u16 104 | local buffer_write_u16 = buffer.writeu16 105 | 106 | -- SECTION buffer_write_u32 107 | local buffer_write_u32 = buffer.writeu32 108 | 109 | -- SECTION buffer_write_f32 110 | local buffer_write_f32 = buffer.writef32 111 | 112 | -- SECTION buffer_write_f64 113 | local buffer_write_f64 = buffer.writef64 114 | 115 | -- SECTION is_positive 116 | -- NEEDS buffer_read_i8 117 | -- NEEDS buffer_write_f64 118 | -- NEEDS raw_memory 119 | local function is_positive(source: number): boolean 120 | buffer_write_f64(RAW_MEMORY, 0, source) 121 | 122 | return buffer_read_i8(RAW_MEMORY, 7) >= 0 123 | end 124 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/runtime/f32.luau: -------------------------------------------------------------------------------- 1 | -- SECTION absolute_f32 2 | local rt_absolute_f32 = vector.abs 3 | 4 | -- SECTION negate_f32 5 | local function rt_negate_f32(source: vector): vector 6 | return -source 7 | end 8 | 9 | -- SECTION square_root_f32 10 | -- NEEDS math_sqrt 11 | -- NEEDS vector_create 12 | local function rt_square_root_f32(source: vector): vector 13 | local source = math_sqrt(source.x) 14 | 15 | return vector_create(source, 0, 0) 16 | end 17 | 18 | -- SECTION round_up_f32 19 | -- NEEDS math_ceil 20 | -- NEEDS vector_create 21 | local function rt_round_up_f32(source: vector): vector 22 | local source = math_ceil(source.x) 23 | 24 | return vector_create(source, 0, 0) 25 | end 26 | 27 | -- SECTION round_down_f32 28 | -- NEEDS math_floor 29 | -- NEEDS vector_create 30 | local function rt_round_down_f32(source: vector): vector 31 | local source = math_floor(source.x) 32 | 33 | return vector_create(source, 0, 0) 34 | end 35 | 36 | -- SECTION truncate_f32 37 | -- NEEDS math_ceil 38 | -- NEEDS math_floor 39 | -- NEEDS vector_create 40 | local function rt_truncate_f32(source: vector): vector 41 | local source = if source.x >= 0 then math_floor(source.x) else math_ceil(source.x) 42 | 43 | return vector_create(source, 0, 0) 44 | end 45 | 46 | -- SECTION nearest_f32 47 | -- NEEDS is_positive 48 | -- NEEDS math_abs 49 | -- NEEDS math_round 50 | -- NEEDS vector_create 51 | local function rt_nearest_f32(source: vector): vector 52 | local positive = is_positive(source.x) 53 | local source = math_abs(source.x) 54 | local rounded = math_round(source) 55 | 56 | if source == rounded - 0.5 and rounded % 2 == 1 then 57 | rounded = rounded - 1 58 | end 59 | 60 | return vector_create(if positive then rounded else -rounded, 0, 0) 61 | end 62 | 63 | -- SECTION add_f32 64 | local function rt_add_f32(lhs: vector, rhs: vector): vector 65 | return lhs + rhs 66 | end 67 | 68 | -- SECTION subtract_f32 69 | local function rt_subtract_f32(lhs: vector, rhs: vector): vector 70 | return lhs - rhs 71 | end 72 | 73 | -- SECTION multiply_f32 74 | local function rt_multiply_f32(lhs: vector, rhs: vector): vector 75 | return lhs * rhs 76 | end 77 | 78 | -- SECTION divide_f32 79 | local function rt_divide_f32(lhs: vector, rhs: vector): vector 80 | return lhs / rhs 81 | end 82 | 83 | -- SECTION minimum_f32 84 | -- NEEDS is_positive 85 | -- NEEDS vector_min 86 | local function rt_minimum_f32(lhs: vector, rhs: vector): vector 87 | return if is_positive(rhs.x) then vector_min(lhs, rhs) else vector_min(rhs, lhs) 88 | end 89 | 90 | -- SECTION maximum_f32 91 | -- NEEDS is_positive 92 | -- NEEDS vector_max 93 | local function rt_maximum_f32(lhs: vector, rhs: vector): vector 94 | return if is_positive(lhs.x) then vector_max(lhs, rhs) else vector_max(rhs, lhs) 95 | end 96 | 97 | -- SECTION copy_sign_f32 98 | -- NEEDS is_positive 99 | -- NEEDS vector_abs 100 | local function rt_copy_sign_f32(lhs: vector, rhs: vector): vector 101 | return if is_positive(rhs.x) then vector_abs(lhs) else -vector_abs(lhs) 102 | end 103 | 104 | -- SECTION equal_f32 105 | local function rt_equal_f32(lhs: vector, rhs: vector): boolean 106 | return lhs.x == rhs.x 107 | end 108 | 109 | -- SECTION not_equal_f32 110 | local function rt_not_equal_f32(lhs: vector, rhs: vector): boolean 111 | return lhs.x ~= rhs.x 112 | end 113 | 114 | -- SECTION less_than_f32 115 | local function rt_less_than_f32(lhs: vector, rhs: vector): boolean 116 | return lhs.x < rhs.x 117 | end 118 | 119 | -- SECTION greater_than_f32 120 | local function rt_greater_than_f32(lhs: vector, rhs: vector): boolean 121 | return lhs.x > rhs.x 122 | end 123 | 124 | -- SECTION less_than_equal_f32 125 | local function rt_less_than_equal_f32(lhs: vector, rhs: vector): boolean 126 | return lhs.x <= rhs.x 127 | end 128 | 129 | -- SECTION greater_than_equal_f32 130 | local function rt_greater_than_equal_f32(lhs: vector, rhs: vector): boolean 131 | return lhs.x >= rhs.x 132 | end 133 | 134 | -- SECTION widen_f32 135 | local function rt_widen_f32(source: vector): number 136 | return source.x 137 | end 138 | 139 | -- SECTION saturate_f32_to_s32 140 | -- NEEDS saturate_f64_to_s32 141 | local function rt_saturate_f32_to_s32(source: vector): vector 142 | return rt_saturate_f64_to_s32(source.x) 143 | end 144 | 145 | -- SECTION truncate_f32_to_s32 146 | -- NEEDS truncate_f64_to_s32 147 | local function rt_truncate_f32_to_s32(source: vector): number 148 | return rt_truncate_f64_to_s32(source.x) 149 | end 150 | 151 | -- SECTION saturate_f32_to_u32 152 | -- NEEDS saturate_f64_to_u32 153 | local function rt_saturate_f32_to_u32(source: vector): vector 154 | return rt_saturate_f64_to_u32(source.x) 155 | end 156 | 157 | -- SECTION truncate_f32_to_u32 158 | -- NEEDS truncate_f64_to_u32 159 | local function rt_truncate_f32_to_u32(source: vector): number 160 | return rt_truncate_f64_to_u32(source.x) 161 | end 162 | 163 | -- SECTION saturate_f32_to_s64 164 | -- NEEDS saturate_f64_to_s64 165 | local function rt_saturate_f32_to_s64(source: vector): number 166 | return rt_saturate_f64_to_s64(source.x) 167 | end 168 | 169 | -- SECTION truncate_f32_to_s64 170 | -- NEEDS truncate_f64_to_s64 171 | local function rt_truncate_f32_to_s64(source: vector): number 172 | return rt_truncate_f64_to_s64(source.x) 173 | end 174 | 175 | -- SECTION saturate_f32_to_u64 176 | -- NEEDS saturate_f64_to_u64 177 | local function rt_saturate_f32_to_u64(source: vector): number 178 | return rt_saturate_f64_to_u64(source.x) 179 | end 180 | 181 | -- SECTION truncate_f32_to_u64 182 | -- NEEDS truncate_f64_to_u64 183 | local function rt_truncate_f32_to_u64(source: vector): number 184 | return rt_truncate_f64_to_u64(source.x) 185 | end 186 | 187 | -- SECTION transmute_f32_to_i32 188 | -- NEEDS buffer_read_u32 189 | -- NEEDS buffer_write_f32 190 | -- NEEDS raw_memory 191 | local function rt_transmute_f32_to_i32(source: vector): number 192 | buffer_write_f32(RAW_MEMORY, 0, source.x) 193 | 194 | return buffer_read_u32(RAW_MEMORY, 0) 195 | end 196 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/runtime/f64.luau: -------------------------------------------------------------------------------- 1 | -- SECTION create_f64_from_u32 2 | -- NEEDS buffer_read_f64 3 | -- NEEDS buffer_write_u32 4 | -- NEEDS raw_memory 5 | local function rt_create_f64_from_u32(source_1: number, source_2: number): number 6 | buffer_write_u32(RAW_MEMORY, 0, source_1) 7 | buffer_write_u32(RAW_MEMORY, 4, source_2) 8 | 9 | return buffer_read_f64(RAW_MEMORY, 0) 10 | end 11 | 12 | -- SECTION absolute_f64 13 | local rt_absolute_f64 = math.abs 14 | 15 | -- SECTION negate_f64 16 | local function rt_negate_f64(source: number): number 17 | return -source 18 | end 19 | 20 | -- SECTION square_root_f64 21 | local rt_square_root_f64 = math.sqrt 22 | 23 | -- SECTION round_up_f64 24 | local rt_round_up_f64 = math.ceil 25 | 26 | -- SECTION round_down_f64 27 | local rt_round_down_f64 = math.floor 28 | 29 | -- SECTION truncate_f64 30 | -- NEEDS math_ceil 31 | -- NEEDS math_floor 32 | local function rt_truncate_f64(source: number): number 33 | return if source >= 0 then math_floor(source) else math_ceil(source) 34 | end 35 | 36 | -- SECTION nearest_f64 37 | -- NEEDS is_positive 38 | -- NEEDS math_abs 39 | -- NEEDS math_round 40 | local function rt_nearest_f64(source: number): number 41 | local positive = is_positive(source) 42 | local source = math_abs(source) 43 | local rounded = math_round(source) 44 | 45 | if source == rounded - 0.5 and rounded % 2 == 1 then 46 | rounded = rounded - 1 47 | end 48 | 49 | return if positive then rounded else -rounded 50 | end 51 | 52 | -- SECTION add_f64 53 | local function rt_add_f64(lhs: number, rhs: number): number 54 | return lhs + rhs 55 | end 56 | 57 | -- SECTION subtract_f64 58 | local function rt_subtract_f64(lhs: number, rhs: number): number 59 | return lhs - rhs 60 | end 61 | 62 | -- SECTION multiply_f64 63 | local function rt_multiply_f64(lhs: number, rhs: number): number 64 | return lhs * rhs 65 | end 66 | 67 | -- SECTION divide_f64 68 | local function rt_divide_f64(lhs: number, rhs: number): number 69 | return lhs / rhs 70 | end 71 | 72 | -- SECTION minimum_f64 73 | -- NEEDS is_positive 74 | -- NEEDS math_min 75 | local function rt_minimum_f64(lhs: number, rhs: number): number 76 | return if is_positive(rhs) then math_min(lhs, rhs) else math_min(rhs, lhs) 77 | end 78 | 79 | -- SECTION maximum_f64 80 | -- NEEDS is_positive 81 | -- NEEDS math_max 82 | local function rt_maximum_f64(lhs: number, rhs: number): number 83 | return if is_positive(lhs) then math_max(lhs, rhs) else math_max(rhs, lhs) 84 | end 85 | 86 | -- SECTION copy_sign_f64 87 | -- NEEDS is_positive 88 | -- NEEDS math_abs 89 | local function rt_copy_sign_f64(lhs: number, rhs: number): number 90 | return if is_positive(rhs) then math_abs(lhs) else -math_abs(lhs) 91 | end 92 | 93 | -- SECTION equal_f64 94 | local function rt_equal_f64(lhs: number, rhs: number): boolean 95 | return lhs == rhs 96 | end 97 | 98 | -- SECTION not_equal_f64 99 | local function rt_not_equal_f64(lhs: number, rhs: number): boolean 100 | return lhs ~= rhs 101 | end 102 | 103 | -- SECTION less_than_f64 104 | local function rt_less_than_f64(lhs: number, rhs: number): boolean 105 | return lhs < rhs 106 | end 107 | 108 | -- SECTION greater_than_f64 109 | local function rt_greater_than_f64(lhs: number, rhs: number): boolean 110 | return lhs > rhs 111 | end 112 | 113 | -- SECTION less_than_equal_f64 114 | local function rt_less_than_equal_f64(lhs: number, rhs: number): boolean 115 | return lhs <= rhs 116 | end 117 | 118 | -- SECTION greater_than_equal_f64 119 | local function rt_greater_than_equal_f64(lhs: number, rhs: number): boolean 120 | return lhs >= rhs 121 | end 122 | 123 | -- SECTION narrow_f64 124 | -- NEEDS vector_create 125 | local function rt_narrow_f64(source: number): vector 126 | return vector_create(source, 0, 0) 127 | end 128 | 129 | -- SECTION saturate_f64_to_s32 130 | -- NEEDS bit_or 131 | -- NEEDS math_ceil 132 | -- NEEDS math_floor 133 | local function rt_saturate_f64_to_s32(source: number): number 134 | if source ~= source then 135 | return 0 136 | end 137 | 138 | if source >= 0 then 139 | source = math_floor(source) 140 | 141 | if source >= 0x8000_0000 then 142 | return 0x7FFF_FFFF 143 | end 144 | else 145 | source = math_ceil(source) 146 | 147 | if source < -0x8000_0000 then 148 | return 0x8000_0000 149 | end 150 | end 151 | 152 | return bit_or(source, 0) 153 | end 154 | 155 | -- SECTION truncate_f64_to_s32 156 | -- NEEDS bit_or 157 | -- NEEDS math_ceil 158 | -- NEEDS math_floor 159 | local function rt_truncate_f64_to_s32(source: number): number 160 | if source ~= source then 161 | error("invalid conversion to integer", 2) 162 | end 163 | 164 | if source >= 0 then 165 | source = math_floor(source) 166 | 167 | if source >= 0x8000_0000 then 168 | error("integer overflow", 2) 169 | end 170 | else 171 | source = math_ceil(source) 172 | 173 | if source < -0x8000_0000 then 174 | error("integer overflow", 2) 175 | end 176 | end 177 | 178 | return bit_or(source, 0) 179 | end 180 | 181 | -- SECTION saturate_f64_to_u32 182 | -- NEEDS bit_or 183 | -- NEEDS math_ceil 184 | -- NEEDS math_floor 185 | local function rt_saturate_f64_to_u32(source: number): number 186 | if source ~= source then 187 | return 0 188 | end 189 | 190 | if source >= 0 then 191 | source = math_floor(source) 192 | 193 | if source >= 0x1_0000_0000 then 194 | return 0xFFFF_FFFF 195 | end 196 | else 197 | source = math_ceil(source) 198 | 199 | if source < 0 then 200 | return 0 201 | end 202 | end 203 | 204 | return bit_or(source, 0) 205 | end 206 | 207 | -- SECTION truncate_f64_to_u32 208 | -- NEEDS bit_or 209 | -- NEEDS math_ceil 210 | -- NEEDS math_floor 211 | local function rt_truncate_f64_to_u32(source: number): number 212 | if source ~= source then 213 | error("invalid conversion to integer", 2) 214 | end 215 | 216 | if source >= 0 then 217 | source = math_floor(source) 218 | 219 | if source >= 0x1_0000_0000 then 220 | error("integer overflow", 2) 221 | end 222 | else 223 | source = math_ceil(source) 224 | 225 | if source < 0 then 226 | error("integer overflow", 2) 227 | end 228 | end 229 | 230 | return bit_or(source, 0) 231 | end 232 | 233 | -- SECTION saturate_f64_to_s64 234 | -- NEEDS constant_maximum_s64 235 | -- NEEDS constant_minimum_s64 236 | -- NEEDS constant_zero_u64 237 | -- NEEDS math_ceil 238 | -- NEEDS math_floor 239 | -- NEEDS truncate_f64_to_u64_unchecked 240 | local function rt_saturate_f64_to_s64(source: number): number 241 | if source ~= source then 242 | return ZERO_U64 243 | end 244 | 245 | if source >= 0 then 246 | local source = math_floor(source) 247 | 248 | if source >= 0x8000_0000_0000_0000 then 249 | return MAXIMUM_S64 250 | end 251 | 252 | return rt_truncate_f64_to_u64_unchecked(source) 253 | else 254 | local source = math_ceil(source) 255 | 256 | if source < -0x8000_0000_0000_0000 then 257 | return MINIMUM_S64 258 | end 259 | 260 | local source = rt_truncate_f64_to_u64_unchecked(-source) 261 | 262 | return rt_negate_i64(source) 263 | end 264 | end 265 | 266 | -- SECTION truncate_f64_to_s64 267 | -- NEEDS math_ceil 268 | -- NEEDS math_floor 269 | -- NEEDS negate_i64 270 | -- NEEDS truncate_f64_to_u64_unchecked 271 | local function rt_truncate_f64_to_s64(source: number): number 272 | if source ~= source then 273 | error("invalid conversion to integer", 2) 274 | end 275 | 276 | if source >= 0 then 277 | local source = math_floor(source) 278 | 279 | if source >= 0x8000_0000_0000_0000 then 280 | error("integer overflow", 2) 281 | end 282 | 283 | return rt_truncate_f64_to_u64_unchecked(source) 284 | else 285 | local source = math_ceil(source) 286 | 287 | if source < -0x8000_0000_0000_0000 then 288 | error("integer overflow", 2) 289 | end 290 | 291 | local source = rt_truncate_f64_to_u64_unchecked(-source) 292 | 293 | return rt_negate_i64(source) 294 | end 295 | end 296 | 297 | -- SECTION saturate_f64_to_u64 298 | -- NEEDS constant_all_u64 299 | -- NEEDS constant_zero_u64 300 | -- NEEDS math_ceil 301 | -- NEEDS math_floor 302 | -- NEEDS truncate_f64_to_u64_unchecked 303 | local function rt_saturate_f64_to_u64(source: number): number 304 | if source ~= source then 305 | return ZERO_U64 306 | end 307 | 308 | if source >= 0 then 309 | source = math_floor(source) 310 | 311 | if source >= 0x1_0000_0000_0000_0000 then 312 | return ALL_U64 313 | end 314 | else 315 | source = math_ceil(source) 316 | 317 | if source < 0 then 318 | return ZERO_U64 319 | end 320 | end 321 | 322 | return rt_truncate_f64_to_u64_unchecked(source) 323 | end 324 | 325 | -- SECTION truncate_f64_to_u64_unchecked 326 | -- NEEDS bit_or 327 | -- NEEDS create_i64_from_u32 328 | local function rt_truncate_f64_to_u64_unchecked(source: number): number 329 | local source_1 = bit_or(source % 0x1_0000_0000, 0) 330 | local source_2 = bit_or(source / 0x1_0000_0000, 0) 331 | 332 | return rt_create_i64_from_u32(source_1, source_2) 333 | end 334 | 335 | -- SECTION truncate_f64_to_u64 336 | -- NEEDS math_ceil 337 | -- NEEDS math_floor 338 | -- NEEDS truncate_f64_to_u64_unchecked 339 | local function rt_truncate_f64_to_u64(source: number): number 340 | if source ~= source then 341 | error("invalid conversion to integer", 2) 342 | end 343 | 344 | if source >= 0 then 345 | source = math_floor(source) 346 | 347 | if source >= 0x1_0000_0000_0000_0000 then 348 | error("integer overflow", 2) 349 | end 350 | else 351 | source = math_ceil(source) 352 | 353 | if source < 0 then 354 | error("integer overflow", 2) 355 | end 356 | end 357 | 358 | return rt_truncate_f64_to_u64_unchecked(source) 359 | end 360 | 361 | -- SECTION transmute_f64_to_i64 362 | local function rt_transmute_f64_to_i64(source: number): number 363 | return source 364 | end 365 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/runtime/i32.luau: -------------------------------------------------------------------------------- 1 | -- SECTION count_ones_i32 2 | -- NEEDS bit_and 3 | -- NEEDS bit_rshift 4 | local function rt_count_ones_i32(source: number): number 5 | local source = source - bit_and(bit_rshift(source, 1), 0x5555_5555) 6 | local source = bit_and(source, 0x3333_3333) + bit_and(bit_rshift(source, 2), 0x3333_3333) 7 | local source = bit_and(source + bit_rshift(source, 4), 0x0F0F_0F0F) 8 | local source = source + bit_rshift(source, 8) 9 | local source = source + bit_rshift(source, 16) 10 | 11 | return bit_and(source, 0x0000_003F) 12 | end 13 | 14 | -- SECTION leading_zeroes_i32 15 | local rt_leading_zeroes_i32 = bit32.countlz 16 | 17 | -- SECTION trailing_zeroes_i32 18 | local rt_trailing_zeroes_i32 = bit32.countrz 19 | 20 | -- SECTION add_i32 21 | -- NEEDS bit_or 22 | local function rt_add_i32(lhs: number, rhs: number): number 23 | return bit_or(lhs + rhs, 0) 24 | end 25 | 26 | -- SECTION subtract_i32 27 | -- NEEDS bit_or 28 | local function rt_subtract_i32(lhs: number, rhs: number): number 29 | return bit_or(lhs - rhs, 0) 30 | end 31 | 32 | -- SECTION multiply_i32 33 | -- NEEDS bit_and 34 | -- NEEDS bit_lshift 35 | -- NEEDS bit_or 36 | -- NEEDS bit_rshift 37 | local function rt_multiply_i32(lhs: number, rhs: number): number 38 | if (lhs + rhs) < 0x800_0000 then 39 | return bit_or(lhs * rhs, 0) 40 | else 41 | local a16 = bit_rshift(lhs, 16) 42 | local a00 = bit_and(lhs, 0xFFFF) 43 | local b16 = bit_rshift(rhs, 16) 44 | local b00 = bit_and(rhs, 0xFFFF) 45 | 46 | local c00 = a00 * b00 47 | local c16 = a16 * b00 + a00 * b16 48 | 49 | return bit_or(c00 + bit_lshift(c16, 16), 0) 50 | end 51 | end 52 | 53 | -- SECTION divide_s32 54 | -- NEEDS bit_or 55 | -- NEEDS bit_xor 56 | local function rt_divide_s32(lhs: number, rhs: number): number 57 | if rhs == 0 then 58 | error("integer divide by zero", 2) 59 | elseif lhs == 0x80000000 and rhs == 0xFFFFFFFF then 60 | error("integer overflow", 2) 61 | end 62 | 63 | local lhs = bit_xor(lhs, 0x8000_0000) - 0x8000_0000 64 | local rhs = bit_xor(rhs, 0x8000_0000) - 0x8000_0000 65 | 66 | return bit_or(lhs / rhs, 0) 67 | end 68 | 69 | -- SECTION divide_u32 70 | -- NEEDS bit_or 71 | local function rt_divide_u32(lhs: number, rhs: number): number 72 | if rhs == 0 then 73 | error("integer divide by zero", 2) 74 | end 75 | 76 | return bit_or(lhs / rhs, 0) 77 | end 78 | 79 | -- SECTION remainder_s32 80 | -- NEEDS bit_or 81 | -- NEEDS bit_xor 82 | -- NEEDS math_fmod 83 | local function rt_remainder_s32(lhs: number, rhs: number): number 84 | if rhs == 0 then 85 | error("integer divide by zero", 2) 86 | end 87 | 88 | local lhs = bit_xor(lhs, 0x8000_0000) - 0x8000_0000 89 | local rhs = bit_xor(rhs, 0x8000_0000) - 0x8000_0000 90 | 91 | return bit_or(math_fmod(lhs, rhs), 0) 92 | end 93 | 94 | -- SECTION remainder_u32 95 | -- NEEDS bit_or 96 | local function rt_remainder_u32(lhs: number, rhs: number): number 97 | if rhs == 0 then 98 | error("integer divide by zero", 2) 99 | end 100 | 101 | return bit_or(lhs % rhs, 0) 102 | end 103 | 104 | -- SECTION and_i32 105 | local rt_and_i32 = bit32.band 106 | 107 | -- SECTION or_i32 108 | local rt_or_i32 = bit32.bor 109 | 110 | -- SECTION exclusive_or_i32 111 | local rt_exclusive_or_i32 = bit32.bxor 112 | 113 | -- SECTION shift_left_i32 114 | -- NEEDS bit_and 115 | -- NEEDS bit_lshift 116 | local function rt_shift_left_i32(lhs: number, rhs: number): number 117 | return bit_lshift(lhs, bit_and(rhs, 0x1F)) 118 | end 119 | 120 | -- SECTION shift_right_s32 121 | -- NEEDS bit_and 122 | -- NEEDS bit_arshift 123 | local function rt_shift_right_s32(lhs: number, rhs: number): number 124 | return bit_arshift(lhs, bit_and(rhs, 0x1F)) 125 | end 126 | 127 | -- SECTION shift_right_u32 128 | -- NEEDS bit_and 129 | -- NEEDS bit_rshift 130 | local function rt_shift_right_u32(lhs: number, rhs: number): number 131 | return bit_rshift(lhs, bit_and(rhs, 0x1F)) 132 | end 133 | 134 | -- SECTION rotate_left_i32 135 | -- NEEDS bit_and 136 | -- NEEDS bit_lrotate 137 | local function rt_rotate_left_i32(lhs: number, rhs: number): number 138 | return bit_lrotate(lhs, bit_and(rhs, 0x1F)) 139 | end 140 | 141 | -- SECTION rotate_right_i32 142 | -- NEEDS bit_and 143 | -- NEEDS bit_rrotate 144 | local function rt_rotate_right_i32(lhs: number, rhs: number): number 145 | return bit_rrotate(lhs, bit_and(rhs, 0x1F)) 146 | end 147 | 148 | -- SECTION equal_i32 149 | local function rt_equal_i32(lhs: number, rhs: number): boolean 150 | return lhs == rhs 151 | end 152 | 153 | -- SECTION not_equal_i32 154 | local function rt_not_equal_i32(lhs: number, rhs: number): boolean 155 | return lhs ~= rhs 156 | end 157 | 158 | -- SECTION less_than_s32 159 | -- NEEDS bit_xor 160 | local function rt_less_than_s32(lhs: number, rhs: number): boolean 161 | return bit_xor(lhs, 0x8000_0000) < bit_xor(rhs, 0x8000_0000) 162 | end 163 | 164 | -- SECTION less_than_u32 165 | local function rt_less_than_u32(lhs: number, rhs: number): boolean 166 | return lhs < rhs 167 | end 168 | 169 | -- SECTION greater_than_s32 170 | -- NEEDS bit_xor 171 | local function rt_greater_than_s32(lhs: number, rhs: number): boolean 172 | return bit_xor(lhs, 0x8000_0000) > bit_xor(rhs, 0x8000_0000) 173 | end 174 | 175 | -- SECTION greater_than_u32 176 | local function rt_greater_than_u32(lhs: number, rhs: number): boolean 177 | return lhs > rhs 178 | end 179 | 180 | -- SECTION less_than_equal_s32 181 | -- NEEDS bit_xor 182 | local function rt_less_than_equal_s32(lhs: number, rhs: number): boolean 183 | return bit_xor(lhs, 0x8000_0000) <= bit_xor(rhs, 0x8000_0000) 184 | end 185 | 186 | -- SECTION less_than_equal_u32 187 | local function rt_less_than_equal_u32(lhs: number, rhs: number): boolean 188 | return lhs <= rhs 189 | end 190 | 191 | -- SECTION greater_than_equal_s32 192 | -- NEEDS bit_xor 193 | local function rt_greater_than_equal_s32(lhs: number, rhs: number): boolean 194 | return bit_xor(lhs, 0x8000_0000) >= bit_xor(rhs, 0x8000_0000) 195 | end 196 | 197 | -- SECTION greater_than_equal_u32 198 | local function rt_greater_than_equal_u32(lhs: number, rhs: number): boolean 199 | return lhs >= rhs 200 | end 201 | 202 | -- SECTION widen_i32 203 | -- NEEDS create_i64_from_u32 204 | local function rt_widen_i32(source: number): number 205 | return rt_create_i64_from_u32(source, 0) 206 | end 207 | 208 | -- SECTION extend_s8_to_i32 209 | -- NEEDS bit_and 210 | -- NEEDS bit_or 211 | local function rt_extend_s8_to_i32(source: number): number 212 | local source = bit_and(source, 0xFF) 213 | 214 | return if source < 0x80 then source else bit_or(source - 0x100, 0) 215 | end 216 | 217 | -- SECTION extend_s16_to_i32 218 | -- NEEDS bit_and 219 | -- NEEDS bit_or 220 | local function rt_extend_s16_to_i32(source: number): number 221 | local source = bit_and(source, 0xFFFF) 222 | 223 | return if source < 0x8000 then source else bit_or(source - 0x1_0000, 0) 224 | end 225 | 226 | -- SECTION convert_s32_to_f32 227 | -- NEEDS bit_xor 228 | -- NEEDS vector_create 229 | local function rt_convert_s32_to_f32(source: number): vector 230 | local source = bit_xor(source, 0x8000_0000) - 0x8000_0000 231 | 232 | return vector_create(source, 0, 0) 233 | end 234 | 235 | -- SECTION convert_u32_to_f32 236 | -- NEEDS vector_create 237 | local function rt_convert_u32_to_f32(source: number): vector 238 | return vector_create(source, 0, 0) 239 | end 240 | 241 | -- SECTION convert_s32_to_f64 242 | -- NEEDS bit_xor 243 | local function rt_convert_s32_to_f64(source: number): number 244 | local source = bit_xor(source, 0x8000_0000) - 0x8000_0000 245 | 246 | return source 247 | end 248 | 249 | -- SECTION convert_u32_to_f64 250 | local function rt_convert_u32_to_f64(source: number): number 251 | return source 252 | end 253 | 254 | -- SECTION transmute_i32_to_f32 255 | -- NEEDS buffer_read_f32 256 | -- NEEDS buffer_write_u32 257 | -- NEEDS raw_memory 258 | -- NEEDS vector_create 259 | local function rt_transmute_i32_to_f32(source: number): vector 260 | buffer_write_u32(RAW_MEMORY, 0, source) 261 | 262 | return vector_create(buffer_read_f32(RAW_MEMORY, 0), 0, 0) 263 | end 264 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/runtime/memory.luau: -------------------------------------------------------------------------------- 1 | -- SECTION memory_type 2 | type Memory = { 3 | [number]: buffer, 4 | maximum: number, 5 | } 6 | 7 | -- SECTION data_type 8 | type Data = { 9 | [number]: buffer, 10 | } 11 | 12 | -- SECTION memory_new 13 | -- NEEDS memory_type 14 | local function rt_memory_new(minimum: number, maximum: number): Memory 15 | return { 16 | buffer.create(minimum * 0x1_0000), 17 | maximum = maximum, 18 | } 19 | end 20 | 21 | -- SECTION load_i32_from_s8 22 | -- NEEDS bit_or 23 | -- NEEDS buffer_read_i8 24 | -- NEEDS memory_type 25 | local function rt_load_i32_from_s8(source: Memory, offset: number): number 26 | return bit_or(buffer_read_i8(source[1], offset), 0) 27 | end 28 | 29 | -- SECTION load_i32_from_u8 30 | -- NEEDS buffer_read_u8 31 | -- NEEDS memory_type 32 | local function rt_load_i32_from_u8(source: Memory, offset: number): number 33 | return buffer_read_u8(source[1], offset) 34 | end 35 | 36 | -- SECTION load_i32_from_s16 37 | -- NEEDS bit_or 38 | -- NEEDS buffer_read_i16 39 | -- NEEDS memory_type 40 | local function rt_load_i32_from_s16(source: Memory, offset: number): number 41 | return bit_or(buffer_read_i16(source[1], offset), 0) 42 | end 43 | 44 | -- SECTION load_i32_from_u16 45 | -- NEEDS buffer_read_u16 46 | -- NEEDS memory_type 47 | local function rt_load_i32_from_u16(source: Memory, offset: number): number 48 | return buffer_read_u16(source[1], offset) 49 | end 50 | 51 | -- SECTION load_i32 52 | -- NEEDS buffer_read_u32 53 | -- NEEDS memory_type 54 | local function rt_load_i32(source: Memory, offset: number): number 55 | return buffer_read_u32(source[1], offset) 56 | end 57 | 58 | -- SECTION load_i64_from_s8 59 | -- NEEDS buffer_read_i8 60 | -- NEEDS create_i64_from_u32 61 | -- NEEDS memory_type 62 | local function rt_load_i64_from_s8(source: Memory, offset: number): number 63 | local source = buffer_read_i8(source[1], offset) 64 | 65 | return if source >= 0 66 | then rt_create_i64_from_u32(source, 0) 67 | else rt_create_i64_from_u32(source + 0x1_0000_0000, 0xFFFF_FFFF) 68 | end 69 | 70 | -- SECTION load_i64_from_u8 71 | -- NEEDS buffer_read_u8 72 | -- NEEDS create_i64_from_u32 73 | -- NEEDS memory_type 74 | local function rt_load_i64_from_u8(source: Memory, offset: number): number 75 | local source = buffer_read_u8(source[1], offset) 76 | 77 | return rt_create_i64_from_u32(source, 0) 78 | end 79 | 80 | -- SECTION load_i64_from_s16 81 | -- NEEDS buffer_read_i16 82 | -- NEEDS create_i64_from_u32 83 | -- NEEDS memory_type 84 | local function rt_load_i64_from_s16(source: Memory, offset: number): number 85 | local source = buffer_read_i16(source[1], offset) 86 | 87 | return if source >= 0 88 | then rt_create_i64_from_u32(source, 0) 89 | else rt_create_i64_from_u32(source + 0x1_0000_0000, 0xFFFF_FFFF) 90 | end 91 | 92 | -- SECTION load_i64_from_u16 93 | -- NEEDS buffer_read_u16 94 | -- NEEDS create_i64_from_u32 95 | -- NEEDS memory_type 96 | local function rt_load_i64_from_u16(source: Memory, offset: number): number 97 | local source = buffer_read_u16(source[1], offset) 98 | 99 | return rt_create_i64_from_u32(source, 0) 100 | end 101 | 102 | -- SECTION load_i64_from_s32 103 | -- NEEDS buffer_read_i32 104 | -- NEEDS create_i64_from_u32 105 | -- NEEDS memory_type 106 | local function rt_load_i64_from_s32(source: Memory, offset: number): number 107 | local source = buffer_read_i32(source[1], offset) 108 | 109 | return if source >= 0 110 | then rt_create_i64_from_u32(source, 0) 111 | else rt_create_i64_from_u32(source + 0x1_0000_0000, 0xFFFF_FFFF) 112 | end 113 | 114 | -- SECTION load_i64_from_u32 115 | -- NEEDS buffer_read_u32 116 | -- NEEDS create_i64_from_u32 117 | -- NEEDS memory_type 118 | local function rt_load_i64_from_u32(source: Memory, offset: number): number 119 | local source = buffer_read_u32(source[1], offset) 120 | 121 | return rt_create_i64_from_u32(source, 0) 122 | end 123 | 124 | -- SECTION load_i64 125 | -- NEEDS buffer_read_u32 126 | -- NEEDS create_i64_from_u32 127 | -- NEEDS memory_type 128 | local function rt_load_i64(source: Memory, offset: number): number 129 | local memory = source[1] 130 | local source_1 = buffer_read_u32(memory, offset) 131 | local source_2 = buffer_read_u32(memory, offset + 4) 132 | 133 | return rt_create_i64_from_u32(source_1, source_2) 134 | end 135 | 136 | -- SECTION load_f32 137 | -- NEEDS buffer_read_f32 138 | -- NEEDS memory_type 139 | -- NEEDS vector_create 140 | local function rt_load_f32(source: Memory, offset: number): vector 141 | local source = buffer_read_f32(source[1], offset) 142 | 143 | return vector_create(source, 0, 0) 144 | end 145 | 146 | -- SECTION load_f64 147 | -- NEEDS buffer_read_f64 148 | -- NEEDS memory_type 149 | local function rt_load_f64(source: Memory, offset: number): number 150 | return buffer_read_f64(source[1], offset) 151 | end 152 | 153 | -- SECTION store_i32_into_i8 154 | -- NEEDS buffer_write_u8 155 | -- NEEDS memory_type 156 | local function rt_store_i32_into_i8(destination: Memory, offset: number, source: number) 157 | buffer_write_u8(destination[1], offset, source) 158 | end 159 | 160 | -- SECTION store_i32_into_i16 161 | -- NEEDS buffer_write_u16 162 | -- NEEDS memory_type 163 | local function rt_store_i32_into_i16(destination: Memory, offset: number, source: number) 164 | buffer_write_u16(destination[1], offset, source) 165 | end 166 | 167 | -- SECTION store_i32 168 | -- NEEDS buffer_write_u32 169 | -- NEEDS memory_type 170 | local function rt_store_i32(destination: Memory, offset: number, source: number) 171 | buffer_write_u32(destination[1], offset, source) 172 | end 173 | 174 | -- SECTION store_i64_into_i8 175 | -- NEEDS buffer_write_u8 176 | -- NEEDS create_u32_from_i64 177 | -- NEEDS memory_type 178 | local function rt_store_i64_into_i8(destination: Memory, offset: number, source: number) 179 | local source_1, _ = rt_create_u32_from_i64(source) 180 | 181 | buffer_write_u8(destination[1], offset, source_1) 182 | end 183 | 184 | -- SECTION store_i64_into_i16 185 | -- NEEDS buffer_write_u16 186 | -- NEEDS create_u32_from_i64 187 | -- NEEDS memory_type 188 | local function rt_store_i64_into_i16(destination: Memory, offset: number, source: number) 189 | local source_1, _ = rt_create_u32_from_i64(source) 190 | 191 | buffer_write_u16(destination[1], offset, source_1) 192 | end 193 | 194 | -- SECTION store_i64_into_i32 195 | -- NEEDS buffer_write_u32 196 | -- NEEDS create_u32_from_i64 197 | -- NEEDS memory_type 198 | local function rt_store_i64_into_i32(destination: Memory, offset: number, source: number) 199 | local source_1, _ = rt_create_u32_from_i64(source) 200 | 201 | buffer_write_u32(destination[1], offset, source_1) 202 | end 203 | 204 | -- SECTION store_i64 205 | -- NEEDS buffer_write_u32 206 | -- NEEDS create_u32_from_i64 207 | -- NEEDS memory_type 208 | local function rt_store_i64(destination: Memory, offset: number, source: number) 209 | local source_1, source_2 = rt_create_u32_from_i64(source) 210 | 211 | buffer_write_u32(destination[1], offset + 4, source_2) 212 | buffer_write_u32(destination[1], offset, source_1) 213 | end 214 | 215 | -- SECTION store_f32 216 | -- NEEDS buffer_write_f32 217 | -- NEEDS memory_type 218 | local function rt_store_f32(destination: Memory, offset: number, source: vector) 219 | buffer_write_f32(destination[1], offset, source.x) 220 | end 221 | 222 | -- SECTION store_f64 223 | -- NEEDS buffer_write_f64 224 | -- NEEDS memory_type 225 | local function rt_store_f64(destination: Memory, offset: number, source: number) 226 | buffer_write_f64(destination[1], offset, source) 227 | end 228 | 229 | -- SECTION memory_size 230 | -- NEEDS memory_type 231 | local function rt_memory_size(source: Memory): number 232 | return buffer.len(source[1]) // 0x1_0000 233 | end 234 | 235 | -- SECTION memory_grow 236 | -- NEEDS memory_type 237 | local function rt_memory_grow(destination: Memory, size: number): number 238 | local old = buffer.len(destination[1]) // 0x1_0000 239 | local new = old + size 240 | 241 | if new > destination.maximum then 242 | return 0xFFFF_FFFF 243 | end 244 | 245 | local ok, result = pcall(buffer.create, new * 0x1_0000) 246 | 247 | if not ok then 248 | return 0xFFFF_FFFF 249 | end 250 | 251 | buffer.copy(result, 0, destination[1]) 252 | 253 | destination[1] = result 254 | 255 | return old 256 | end 257 | 258 | -- SECTION memory_fill 259 | -- NEEDS memory_type 260 | local function rt_memory_fill(destination: Memory, offset: number, source: number, size: number) 261 | buffer.fill(destination[1], offset, source, size) 262 | end 263 | 264 | -- SECTION memory_copy 265 | -- NEEDS memory_type 266 | local function rt_memory_copy(destination: Memory, offset_1: number, source: Memory, offset_2: number, size: number) 267 | buffer.copy(destination[1], offset_1, source[1], offset_2, size) 268 | end 269 | 270 | -- SECTION memory_init 271 | -- NEEDS data_type 272 | -- NEEDS memory_type 273 | local function rt_memory_init(destination: Memory, offset_1: number, source: Data, offset_2: number, size: number) 274 | buffer.copy(destination[1], offset_1, source[1], offset_2, size) 275 | end 276 | 277 | -- SECTION data_drop 278 | -- NEEDS data_type 279 | local function rt_data_drop(data: Data) 280 | if table.isfrozen(data) then 281 | return 282 | end 283 | 284 | table.clear(data) 285 | 286 | data[1] = buffer.create(0) 287 | 288 | table.freeze(data) 289 | end 290 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/runtime/table.luau: -------------------------------------------------------------------------------- 1 | -- SECTION elements_type 2 | type Elements = { 3 | [number]: T, 4 | count: number, 5 | } 6 | 7 | -- SECTION table_type 8 | type Table = { 9 | [number]: T, 10 | minimum: number, 11 | maximum: number, 12 | } 13 | 14 | -- SECTION table_new 15 | -- NEEDS table_type 16 | local function rt_table_new(source: T, minimum: number, maximum: number): Table 17 | local result = table.create(minimum, source) 18 | 19 | result.minimum = minimum 20 | result.maximum = maximum 21 | 22 | return result 23 | end 24 | 25 | -- SECTION table_get 26 | -- NEEDS table_type 27 | local function rt_table_get(source: Table, offset: number): T 28 | assert(offset < source.minimum, "out of bounds table access") 29 | 30 | return source[offset] 31 | end 32 | 33 | -- SECTION table_set 34 | -- NEEDS table_type 35 | local function rt_table_set(destination: Table, offset: number, source: T) 36 | assert(offset < destination.minimum, "out of bounds table access") 37 | 38 | destination[offset] = source 39 | end 40 | 41 | -- SECTION table_size 42 | -- NEEDS table_type 43 | local function rt_table_size(source: Table): number 44 | return source.minimum 45 | end 46 | 47 | -- SECTION table_grow 48 | -- NEEDS table_type 49 | local function rt_table_grow(destination: Table, source: T, size: number): number 50 | local old = destination.minimum 51 | local new = old + size 52 | 53 | if new > destination.maximum then 54 | return 0xFFFF_FFFF 55 | end 56 | 57 | for offset = old, new do 58 | destination[offset] = source 59 | end 60 | 61 | destination.minimum = new 62 | 63 | return old 64 | end 65 | 66 | -- SECTION table_fill 67 | -- NEEDS table_type 68 | local function rt_table_fill(destination: Table, offset: number, source: T, size: number) 69 | assert(offset + size <= destination.minimum, "out of bounds table access") 70 | 71 | for offset = offset, offset + size - 1 do 72 | destination[offset] = source 73 | end 74 | end 75 | 76 | -- SECTION table_copy 77 | -- NEEDS table_type 78 | local function rt_table_copy(destination: Table, offset_1: number, source: Table, offset_2: number, size: number) 79 | assert(offset_1 + size <= destination.minimum, "out of bounds table access") 80 | assert(offset_2 + size <= source.minimum, "out of bounds table access") 81 | 82 | table.move(source, offset_2, offset_2 + size - 1, offset_1, destination) 83 | end 84 | 85 | -- SECTION table_init 86 | -- NEEDS elements_type 87 | -- NEEDS table_type 88 | local function rt_table_init( 89 | destination: Table, 90 | offset_1: number, 91 | source: Elements, 92 | offset_2: number, 93 | size: number 94 | ) 95 | assert(offset_1 + size <= destination.minimum, "out of bounds table access") 96 | assert(offset_2 + size <= source.count, "out of bounds elements access") 97 | 98 | table.move(source, offset_2 + 1, offset_2 + size, offset_1, destination) 99 | end 100 | 101 | -- SECTION elements_drop 102 | -- NEEDS elements_type 103 | local function rt_elements_drop(elements: Elements) 104 | if table.isfrozen(elements) then 105 | return 106 | end 107 | 108 | table.clear(elements) 109 | 110 | elements.count = 0 111 | 112 | table.freeze(elements) 113 | end 114 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![expect(clippy::missing_panics_doc)] 2 | 3 | mod expression; 4 | mod print; 5 | mod statement; 6 | 7 | pub mod library; 8 | 9 | use std::{ 10 | io::{Result, Write}, 11 | sync::Arc, 12 | }; 13 | 14 | use hashbrown::HashMap; 15 | use luau_tree::{LuauTree, expression::Name}; 16 | 17 | use self::print::Print; 18 | 19 | pub struct LuauPrinter { 20 | names: HashMap>, 21 | depth: u16, 22 | } 23 | 24 | impl LuauPrinter { 25 | #[must_use] 26 | pub fn new() -> Self { 27 | Self { 28 | names: HashMap::new(), 29 | depth: 0, 30 | } 31 | } 32 | 33 | pub(crate) fn tab(&self, out: &mut dyn Write) -> Result<()> { 34 | (0..self.depth).try_for_each(|_| write!(out, "\t")) 35 | } 36 | 37 | pub fn get_name(&self, name: Name) -> Option<&str> { 38 | self.names.get(&name).map(Arc::as_ref) 39 | } 40 | 41 | pub const fn indent(&mut self) { 42 | self.depth = self.depth.wrapping_add(1); 43 | } 44 | 45 | pub const fn outdent(&mut self) { 46 | self.depth = self.depth.wrapping_sub(1); 47 | } 48 | 49 | /// # Errors 50 | /// 51 | /// Returns any IO errors that the `out` produces during the process. 52 | pub fn print(&mut self, tree: &LuauTree, out: &mut dyn Write) -> Result<()> { 53 | tree.print(self, out) 54 | } 55 | } 56 | 57 | impl Default for LuauPrinter { 58 | fn default() -> Self { 59 | Self::new() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/src/library/mod.rs: -------------------------------------------------------------------------------- 1 | mod names_finder; 2 | mod printer; 3 | mod sections; 4 | 5 | pub use self::{ 6 | names_finder::{NamesFinder, NeedsName}, 7 | printer::Printer as LibraryPrinter, 8 | sections::Sections as LibrarySections, 9 | }; 10 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/src/library/printer.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Result, Write}; 2 | 3 | use hashbrown::HashSet; 4 | 5 | use super::sections::{Section, Sections}; 6 | 7 | pub struct Printer { 8 | references: Vec<&'static str>, 9 | expanded: HashSet<&'static str>, 10 | } 11 | 12 | impl Printer { 13 | #[must_use] 14 | pub fn new() -> Self { 15 | Self { 16 | references: Vec::new(), 17 | expanded: HashSet::new(), 18 | } 19 | } 20 | 21 | fn recursively_expand(&mut self, name: &'static str, sections: &Sections) { 22 | if !self.expanded.insert(name) { 23 | return; 24 | } 25 | 26 | for name in §ions.find(name).references { 27 | self.recursively_expand(name, sections); 28 | } 29 | 30 | self.references.push(name); 31 | } 32 | 33 | pub fn resolve(&mut self, names: &[&'static str], sections: &Sections) { 34 | self.references.clear(); 35 | self.expanded.clear(); 36 | 37 | for &name in names { 38 | self.recursively_expand(name, sections); 39 | } 40 | } 41 | 42 | /// # Errors 43 | /// 44 | /// Returns any IO errors that the `out` produces during the process. 45 | pub fn print(&self, sections: &Sections, out: &mut dyn Write) -> Result<()> { 46 | self.references.iter().try_for_each(|&name| { 47 | let Section { contents, .. } = sections.find(name); 48 | 49 | writeln!(out, "{contents}\n") 50 | }) 51 | } 52 | } 53 | 54 | impl Default for Printer { 55 | fn default() -> Self { 56 | Self::new() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/src/library/sections.rs: -------------------------------------------------------------------------------- 1 | pub struct Section { 2 | pub references: Box<[&'static str]>, 3 | pub name: &'static str, 4 | pub contents: &'static str, 5 | } 6 | 7 | impl Section { 8 | const SECTION_HEADER: &str = "-- SECTION "; 9 | const NEEDS_HEADER: &str = "-- NEEDS "; 10 | 11 | fn try_parse_header( 12 | source: &'static str, 13 | header: &'static str, 14 | ) -> Option<(&'static str, &'static str)> { 15 | if !source.starts_with(header) { 16 | return None; 17 | } 18 | 19 | let source = &source[header.len()..]; 20 | let end = source.find('\n')?; 21 | let (data, source) = source.split_at(end); 22 | 23 | Some((data.trim_end(), source.trim_start())) 24 | } 25 | 26 | fn parse_references(mut source: &'static str) -> (Box<[&'static str]>, &'static str) { 27 | let mut dependencies = Vec::new(); 28 | 29 | while let Some((name, next)) = Self::try_parse_header(source, Self::NEEDS_HEADER) { 30 | dependencies.push(name); 31 | 32 | source = next; 33 | } 34 | 35 | (dependencies.into(), source) 36 | } 37 | 38 | fn parse_contents(source: &'static str) -> (&'static str, &'static str) { 39 | let end = source.find(Self::SECTION_HEADER).unwrap_or(source.len()); 40 | let (content, source) = source.split_at(end); 41 | 42 | (content.trim(), source) 43 | } 44 | 45 | pub fn try_parse(source: &'static str) -> Option<(Self, &'static str)> { 46 | let (name, source) = Self::try_parse_header(source, Self::SECTION_HEADER)?; 47 | let (references, source) = Self::parse_references(source); 48 | let (contents, source) = Self::parse_contents(source); 49 | 50 | assert!( 51 | references.is_sorted(), 52 | "references for `{name}` should be sorted" 53 | ); 54 | 55 | Some(( 56 | Self { 57 | references, 58 | name, 59 | contents, 60 | }, 61 | source, 62 | )) 63 | } 64 | } 65 | 66 | pub struct Sections { 67 | list: Vec
, 68 | } 69 | 70 | impl Sections { 71 | pub const BASE_SOURCE: &str = include_str!("../../runtime/base.luau"); 72 | pub const I32_SOURCE: &str = include_str!("../../runtime/i32.luau"); 73 | pub const I64_SOURCE: &str = include_str!("../../runtime/i64.luau"); 74 | pub const F32_SOURCE: &str = include_str!("../../runtime/f32.luau"); 75 | pub const F64_SOURCE: &str = include_str!("../../runtime/f64.luau"); 76 | pub const TABLE_SOURCE: &str = include_str!("../../runtime/table.luau"); 77 | pub const MEMORY_SOURCE: &str = include_str!("../../runtime/memory.luau"); 78 | 79 | #[must_use] 80 | pub fn with_built_ins() -> Self { 81 | let mut sections = Self { list: Vec::new() }; 82 | 83 | sections.parse_from(Self::BASE_SOURCE); 84 | sections.parse_from(Self::I32_SOURCE); 85 | sections.parse_from(Self::I64_SOURCE); 86 | sections.parse_from(Self::F32_SOURCE); 87 | sections.parse_from(Self::F64_SOURCE); 88 | sections.parse_from(Self::TABLE_SOURCE); 89 | sections.parse_from(Self::MEMORY_SOURCE); 90 | sections.resolve(); 91 | 92 | sections 93 | } 94 | 95 | pub fn parse_from(&mut self, mut source: &'static str) { 96 | while let Some((section, next)) = Section::try_parse(source) { 97 | self.list.push(section); 98 | 99 | source = next; 100 | } 101 | 102 | assert!(source.is_empty(), "trailing data in source\n{source}"); 103 | } 104 | 105 | pub fn resolve(&mut self) { 106 | self.list.sort_unstable_by_key(|&Section { name, .. }| name); 107 | 108 | for window in self.list.windows(2) { 109 | let Section { name: lhs, .. } = window[0]; 110 | let Section { name: rhs, .. } = window[1]; 111 | 112 | assert_ne!(lhs, rhs, "`{lhs}` section was duplicated"); 113 | } 114 | } 115 | 116 | #[must_use] 117 | pub fn find(&self, name: &'static str) -> &Section { 118 | let position = self 119 | .list 120 | .binary_search_by_key(&name, |&Section { name, .. }| name) 121 | .unwrap_or_else(|_| panic!("`{name}` is not a section")); 122 | 123 | &self.list[position] 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufWriter, StdoutLock, Write}; 2 | 3 | use clap::Parser; 4 | use data_flow_builder::DataFlowBuilder; 5 | use data_flow_graph::{DataFlowGraph, Link}; 6 | use data_flow_visitor::{ 7 | control::{ 8 | dead_port_eliminator::DeadPortEliminator, invariant_port_mover::InvariantPortMover, 9 | region_identity, 10 | }, 11 | isle, 12 | topological_normalizer::TopologicalNormalizer, 13 | }; 14 | use luau_builder::LuauBuilder; 15 | use luau_printer::{ 16 | LuauPrinter, 17 | library::{LibraryPrinter, LibrarySections, NamesFinder}, 18 | }; 19 | use luau_tree::LuauTree; 20 | use wasmparser::Validator; 21 | 22 | #[derive(Parser)] 23 | #[command(version)] 24 | struct Arguments { 25 | /// The WebAssembly file for processing 26 | file: String, 27 | 28 | /// Embed debug information if present 29 | #[arg(long, short)] 30 | debug: bool, 31 | 32 | /// Run all optimization passes on code 33 | #[arg(long, short)] 34 | optimize: bool, 35 | } 36 | 37 | fn run_isle_optimizations(graph: &mut DataFlowGraph) -> bool { 38 | let mut applied = false; 39 | let len = graph.len(); 40 | 41 | for id in (0..len.try_into().unwrap()).rev() { 42 | while isle::simplify_i32(graph, id) 43 | // || isle::simplify_global(graph, id) 44 | // || isle::simplify_table(graph, id) 45 | { 46 | applied = true; 47 | } 48 | } 49 | 50 | applied 51 | } 52 | 53 | fn run_all_optimizations(graph: &mut DataFlowGraph, mut omega: u32) -> u32 { 54 | let mut topological_normalizer = TopologicalNormalizer::new(); 55 | let mut invariant_port_mover = InvariantPortMover::new(); 56 | let mut dead_port_eliminator = DeadPortEliminator::new(); 57 | 58 | loop { 59 | omega = topological_normalizer.run(graph, omega); 60 | 61 | invariant_port_mover.run(graph); 62 | dead_port_eliminator.run(graph, Link(omega, 0)); 63 | 64 | if !run_isle_optimizations(graph) { 65 | break; 66 | } 67 | 68 | region_identity::remove(graph); 69 | } 70 | 71 | omega 72 | } 73 | 74 | fn run_post_process(graph: &mut DataFlowGraph, omega: u32) { 75 | let mut topological_normalizer = TopologicalNormalizer::new(); 76 | 77 | region_identity::insert(graph); 78 | 79 | topological_normalizer.run(graph, omega); 80 | } 81 | 82 | fn build_data_flow_graph(data: &[u8], optimize: bool) -> DataFlowGraph { 83 | let mut graph = DataFlowGraph::new(); 84 | let mut builder = DataFlowBuilder::new(); 85 | 86 | let omega = builder.run(&mut graph, data); 87 | let omega = if optimize { 88 | run_all_optimizations(&mut graph, omega) 89 | } else { 90 | omega 91 | }; 92 | 93 | run_post_process(&mut graph, omega); 94 | 95 | graph 96 | } 97 | 98 | fn build_luau_tree(graph: &DataFlowGraph) -> LuauTree { 99 | let mut builder = LuauBuilder::new(); 100 | 101 | builder.run(graph) 102 | } 103 | 104 | fn lock_standard_output() -> BufWriter> { 105 | const DEFAULT_BUF_SIZE: usize = 1024 * 1024; 106 | 107 | BufWriter::with_capacity(DEFAULT_BUF_SIZE, std::io::stdout().lock()) 108 | } 109 | 110 | fn print_luau_library(tree: &LuauTree) -> std::io::Result<()> { 111 | let sections = LibrarySections::with_built_ins(); 112 | let mut printer = LibraryPrinter::new(); 113 | let mut references = Vec::new(); 114 | 115 | NamesFinder::new(&mut references).run(tree); 116 | 117 | printer.resolve(&references, §ions); 118 | 119 | let mut output = lock_standard_output(); 120 | 121 | printer.print(§ions, &mut output)?; 122 | output.flush() 123 | } 124 | 125 | fn print_luau_tree(tree: &LuauTree) -> std::io::Result<()> { 126 | let mut printer = LuauPrinter::new(); 127 | let mut output = lock_standard_output(); 128 | 129 | printer.print(tree, &mut output)?; 130 | output.flush() 131 | } 132 | 133 | fn main() { 134 | let arguments = Arguments::parse(); 135 | let data = std::fs::read(arguments.file).unwrap(); 136 | 137 | Validator::new() 138 | .validate_all(&data) 139 | .expect("`file` should be a WebAssembly binary"); 140 | 141 | let graph = build_data_flow_graph(&data, arguments.optimize); 142 | let tree = build_luau_tree(&graph); 143 | 144 | print_luau_library(&tree).expect("library should print"); 145 | print_luau_tree(&tree).expect("source should print"); 146 | } 147 | -------------------------------------------------------------------------------- /Targets/Luau/Printer/src/print.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Result, Write}; 2 | 3 | use crate::LuauPrinter; 4 | 5 | pub trait Print { 6 | fn print(&self, printer: &mut LuauPrinter, out: &mut dyn Write) -> Result<()>; 7 | } 8 | 9 | impl Print for &T { 10 | fn print(&self, printer: &mut LuauPrinter, out: &mut dyn Write) -> Result<()> { 11 | (*self).print(printer, out) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Targets/Luau/Tree/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "luau-tree" 3 | version = "1.0.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | data-flow-graph = { path = "../../../Data Flow/Graph" } 8 | -------------------------------------------------------------------------------- /Targets/Luau/Tree/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![expect(clippy::missing_panics_doc)] 3 | 4 | extern crate alloc; 5 | 6 | pub mod expression; 7 | pub mod statement; 8 | pub mod visitor; 9 | 10 | use alloc::vec::Vec; 11 | 12 | use self::{ 13 | expression::Name, 14 | statement::{Export, Sequence}, 15 | }; 16 | 17 | pub struct LuauTree { 18 | pub environment: Name, 19 | pub locals: Vec, 20 | pub stack: u16, 21 | 22 | pub code: Sequence, 23 | pub exports: Vec, 24 | } 25 | -------------------------------------------------------------------------------- /Targets/Luau/Tree/src/statement.rs: -------------------------------------------------------------------------------- 1 | use alloc::{boxed::Box, sync::Arc, vec::Vec}; 2 | 3 | use crate::expression::{Expression, Local, Location}; 4 | 5 | pub use data_flow_graph::base::StoreType; 6 | 7 | pub struct Sequence { 8 | pub list: Vec, 9 | } 10 | 11 | impl Sequence { 12 | fn as_assign_destination(&self) -> Option { 13 | match self.list.as_slice() { 14 | [Statement::AssignAll(assign_all)] => assign_all.as_assign_destination(), 15 | [Statement::Assign(assign)] => Some(assign.destination), 16 | _ => None, 17 | } 18 | } 19 | 20 | #[must_use] 21 | pub fn as_branch_destination(branches: &[Self]) -> Option { 22 | let mut locals = branches.iter().map(Self::as_assign_destination); 23 | 24 | locals 25 | .next() 26 | .flatten() 27 | .filter(|&local| locals.all(|other| other == Some(local))) 28 | } 29 | 30 | #[must_use] 31 | pub fn into_assign_source(mut self) -> Expression { 32 | let source = match self.list.pop().unwrap() { 33 | Statement::AssignAll(assign_all) => Expression::Local(assign_all.into_assign_source()), 34 | Statement::Assign(assign) => assign.source, 35 | _ => panic!("should be an assignment"), 36 | }; 37 | 38 | assert!(self.list.is_empty(), "should be only statement"); 39 | 40 | source 41 | } 42 | } 43 | 44 | pub struct Match { 45 | pub branches: Vec, 46 | pub condition: Expression, 47 | } 48 | 49 | pub struct Repeat { 50 | pub code: Sequence, 51 | pub condition: Expression, 52 | } 53 | 54 | pub struct Assign { 55 | pub destination: Local, 56 | pub source: Expression, 57 | } 58 | 59 | pub struct AssignAll { 60 | pub assignments: Vec<(Local, Local)>, 61 | } 62 | 63 | impl AssignAll { 64 | const fn as_assign_destination(&self) -> Option { 65 | if let &[(destination, _)] = self.assignments.as_slice() { 66 | Some(destination) 67 | } else { 68 | None 69 | } 70 | } 71 | 72 | fn into_assign_source(mut self) -> Local { 73 | let (_, source) = self.assignments.pop().unwrap(); 74 | 75 | assert!(self.assignments.is_empty(), "should be only statement"); 76 | 77 | source 78 | } 79 | } 80 | 81 | pub struct Call { 82 | pub function: Expression, 83 | pub results: Vec, 84 | pub arguments: Vec, 85 | } 86 | 87 | pub struct GlobalSet { 88 | pub destination: Expression, 89 | pub source: Expression, 90 | } 91 | 92 | pub struct TableSet { 93 | pub destination: Location, 94 | pub source: Expression, 95 | } 96 | 97 | pub struct TableFill { 98 | pub destination: Location, 99 | pub source: Expression, 100 | pub size: Expression, 101 | } 102 | 103 | pub struct TableCopy { 104 | pub destination: Location, 105 | pub source: Location, 106 | pub size: Expression, 107 | } 108 | 109 | pub struct TableInit { 110 | pub destination: Location, 111 | pub source: Location, 112 | pub size: Expression, 113 | } 114 | 115 | pub struct ElementsDrop { 116 | pub source: Expression, 117 | } 118 | 119 | pub struct MemoryStore { 120 | pub destination: Location, 121 | pub source: Expression, 122 | pub r#type: StoreType, 123 | } 124 | 125 | pub struct MemoryFill { 126 | pub destination: Location, 127 | pub byte: Expression, 128 | pub size: Expression, 129 | } 130 | 131 | pub struct MemoryCopy { 132 | pub destination: Location, 133 | pub source: Location, 134 | pub size: Expression, 135 | } 136 | 137 | pub struct MemoryInit { 138 | pub destination: Location, 139 | pub source: Location, 140 | pub size: Expression, 141 | } 142 | 143 | pub struct DataDrop { 144 | pub source: Expression, 145 | } 146 | 147 | pub enum Statement { 148 | Match(Box), 149 | Repeat(Box), 150 | 151 | Assign(Box), 152 | AssignAll(Box), 153 | 154 | Call(Box), 155 | 156 | GlobalSet(Box), 157 | 158 | TableSet(Box), 159 | TableFill(Box), 160 | TableCopy(Box), 161 | TableInit(Box), 162 | 163 | ElementsDrop(Box), 164 | 165 | MemoryStore(Box), 166 | MemoryFill(Box), 167 | MemoryCopy(Box), 168 | MemoryInit(Box), 169 | 170 | DataDrop(Box), 171 | } 172 | 173 | pub struct Export { 174 | pub identifier: Arc, 175 | pub source: Expression, 176 | } 177 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | --------------------------------------------------------------------------------