From aff1ff594e996d3330a09df58e8295dfd4c368b1 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Sun, 16 Jul 2023 23:33:13 +0800 Subject: [PATCH 01/11] create standard abbreviation table --- doc/content/_index.md | 7 +++++++ doc/content/rules/_index.md | 9 +++++++++ doc/content/rules/naming/_index.md | 26 ++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 doc/content/rules/_index.md create mode 100644 doc/content/rules/naming/_index.md diff --git a/doc/content/_index.md b/doc/content/_index.md index ffae98f..9243d63 100644 --- a/doc/content/_index.md +++ b/doc/content/_index.md @@ -18,6 +18,13 @@ section = "docs" url = "/docs/getting-started/introduction/" weight = 10 + +[[extra.menu.main]] +name = "Rules" +section = "rules" +url = "/rules/naming/" +weight = 10 + [[extra.menu.main]] name = "Crate doc" section = "crate-doc" diff --git a/doc/content/rules/_index.md b/doc/content/rules/_index.md new file mode 100644 index 0000000..8ee84d5 --- /dev/null +++ b/doc/content/rules/_index.md @@ -0,0 +1,9 @@ ++++ +title = "Rules" +description = "The rules for developing Come." +date = 2023-07-16T04:19:34.823Z +updated = 2023-07-16T04:19:34.823Z +sort_by = "weight" +weight = 1 +template = "docs/section.html" ++++ diff --git a/doc/content/rules/naming/_index.md b/doc/content/rules/naming/_index.md new file mode 100644 index 0000000..8a6b80b --- /dev/null +++ b/doc/content/rules/naming/_index.md @@ -0,0 +1,26 @@ ++++ +title = "Naming" +description = "Naming rules for Come." +date = 2023-07-16T04:21:20.130Z +updated = 2023-07-16T04:21:20.130Z +template = "docs/section.html" +sort_by = "weight" +weight = 1 +draft = false ++++ + +Except for the rules mentioned on [rust naming guides](https://rust-lang.github.io/api-guidelines/naming.html), +there are some additional rules for Come. + +## Don't use undocumented abbreviations + +We don't use abbreviations unless they are documented in the glossary. + +Here is the glossary: + +| Abbreviation | Origin | In Module | +|--------------|--------|-----------| +| ast | Abstract Syntax Tree | (All) | +| asm | ASseMbly language | (All) | +| ir | Intermediate Representation | (All) | +| bb | Basic Block | `come::ir` | From ae010a1dfd0a26648a867bb8cf9dcac4b0402034 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Tue, 9 Jan 2024 11:50:22 +0100 Subject: [PATCH 02/11] update dependencies --- Cargo.toml | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 34f9b93..e9e55ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,36 +4,35 @@ version = "0.1.0" edition = "2021" build = "build.rs" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] bimap = "0.6.3" bincode = "1.3.3" -bitvec = { version = "1.0.1", features=["serde"] } -clap = { version = "4.2.7", features = ["derive"] } -enum_dispatch = "0.3.11" -indexmap = "1.9.3" -itertools = "0.10.5" +bitvec = { version = "1.0.1", features = ["serde"] } +clap = { version = "4.4.14", features = ["derive"] } +enum_dispatch = "0.3.12" +indexmap = "2.1.0" +itertools = "0.12.0" nom = "7.1.3" -paste = "1.0.12" -petgraph = "0.6.3" -phf = { version = "0.11.1", features = ["macros"] } -serde = { version = "1.0.162", features = ["derive"] } -toml = "0.7.3" -shadow-rs = { version = "0.21.0", optional = true } +paste = "1.0.14" +petgraph = "0.6.4" +phf = { version = "0.11.2", features = ["macros"] } +serde = { version = "1.0.195", features = ["derive"] } +toml = "0.8.8" +delegate = { version = "0.12.0" } +shadow-rs = { version = "0.26.0", optional = true } ezio = { version = "0.1.2", optional = true } [dev-dependencies] cov-mark = "1.1.0" [build-dependencies] -shadow-rs = "0.21.0" +shadow-rs = "0.26.0" [lib] crate-type = ["lib"] [features] -build-binary = [] +build-binary = ["shadow-rs", "ezio"] [[bin]] name = "clefviewer" From 8e7c38d1ef5e8237a2c313bc11b60079dd554c6a Mon Sep 17 00:00:00 2001 From: longfangsong Date: Tue, 16 Jan 2024 12:28:08 +0100 Subject: [PATCH 03/11] new fix irreducible framework --- .gitignore | 1 + doc/content/tools/graph_editor.md | 17 +- doc/templates/.gitkeep | 0 src/backend/wasm/_mod.rs | 410 --------------- src/backend/wasm/mod.rs | 28 +- .../control_flow/control_flow_loop.rs | 54 +- src/ir/editor/analyzer/control_flow/mod.rs | 29 +- .../editor/analyzer/control_flow/scc_new.rs | 162 ++++++ src/ir/editor/analyzer/mod.rs | 7 +- src/ir/editor/mod.rs | 5 + src/ir/function/statement/mod.rs | 24 +- src/ir/optimize/pass/fix_irreducible.rs | 4 +- .../optimize/pass/fix_irreducible_new/mod.rs | 386 ++++++++++++++ .../pass/fix_irreducible_new/tests.rs | 483 ++++++++++++++++++ src/ir/optimize/pass/mod.rs | 1 + src/ir/optimize/pass/topological_sort.rs | 8 +- src/utility/data_type.rs | 9 + src/utility/graph.rs | 13 +- 18 files changed, 1155 insertions(+), 486 deletions(-) create mode 100644 doc/templates/.gitkeep delete mode 100644 src/backend/wasm/_mod.rs create mode 100644 src/ir/editor/analyzer/control_flow/scc_new.rs create mode 100644 src/ir/optimize/pass/fix_irreducible_new/mod.rs create mode 100644 src/ir/optimize/pass/fix_irreducible_new/tests.rs diff --git a/.gitignore b/.gitignore index 36cf8c0..4369801 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,4 @@ Cargo.lock !integration-test/cases/*/expected/*.asm *.clef doc/public +lcov.info diff --git a/doc/content/tools/graph_editor.md b/doc/content/tools/graph_editor.md index 3478307..305b5d0 100644 --- a/doc/content/tools/graph_editor.md +++ b/doc/content/tools/graph_editor.md @@ -336,7 +336,7 @@ function renderComeIR() { parameters: Vec::new(), return_type: data_type::Type::None, }, - content: vec![`; + content: vec![\n`; let nodes = new Set(); for (let edge of edgesInfo) { let { first, second, line } = edge; @@ -346,10 +346,6 @@ function renderComeIR() { nodes.add(secondId); } for (let node of nodes) { - content += ` - BasicBlock { - name: Some("bb${node}".to_string()), - content: vec![`; let to = edgesInfo.filter(({ first }) => { const firstId = first.getAttribute("id").substring("node-".length); return firstId === node; @@ -357,19 +353,20 @@ function renderComeIR() { const secondId = second.getAttribute("id").substring("node-".length); return secondId; }); + content += " "; if (to.length === 0) { - content += `Ret { value: None }.into()`; + content += `ret_block(${node}),`; } else if (to.length === 1) { let target = to[0]; - content += `jump("bb${target}")`; + content += `jump_block(${node}, ${target}),`; } else if (to.length === 2) { let first = to[0]; let second = to[1]; - content += `branch("bb${first}", "bb${second}")`; + content += `branch_block(${node}, ${first}, ${second}),`; } - content += `], - },` + content += "\n"; } + content = content.slice(0, -1); content += ` ], };`; diff --git a/doc/templates/.gitkeep b/doc/templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/wasm/_mod.rs b/src/backend/wasm/_mod.rs deleted file mode 100644 index f5b04a1..0000000 --- a/src/backend/wasm/_mod.rs +++ /dev/null @@ -1,410 +0,0 @@ -use std::fmt::{self, Debug, Display}; - -use itertools::Itertools; -use petgraph::{dot::Dot, Direction}; - -use crate::{ - ir::{ - self, - analyzer::{self, Analyzer, BindedAnalyzer, IsAnalyzer, LoopContent}, - statement::IRStatement, - }, - utility::graph, -}; -#[derive(PartialEq, Clone)] -pub struct ControlFlow { - content: Vec, -} - -impl Debug for ControlFlow { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - self.content - .iter() - .map(|item| write!(f, "{:?}", item)) - .collect() - } -} - -impl ControlFlow { - pub fn node_count(&self) -> usize { - self.content.iter().map(|it| it.node_count()).sum() - } - - pub fn contains(&self, node: usize) -> bool { - for content_item in &self.content { - match content_item { - ControlFlowContent::Block(x) => { - if x.contains(node) { - return true; - } - } - ControlFlowContent::BrIf(x) => { - if *x == node { - return true; - } - } - ControlFlowContent::If(x, y) => { - if x.contains(node) { - return true; - } - if let Some(y) = y { - if y.contains(node) { - return true; - } - } - } - ControlFlowContent::Loop(x) => { - if x.contains(node) { - return true; - } - } - ControlFlowContent::Node(x) => { - if *x == node { - return true; - } - } - } - } - false - } - - pub fn first_node(&self) -> usize { - self.content.first().unwrap().first_node() - } -} - -#[derive(PartialEq, Clone)] -pub enum ControlFlowContent { - Block(Box), - BrIf(usize), - If(Box, Option), - Loop(Box), - Node(usize), -} - -impl Debug for ControlFlowContent { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self { - ControlFlowContent::Block(cf) => { - write!(f, "Block {{ {:?}}} ", cf)?; - } - ControlFlowContent::BrIf(i) => { - write!(f, "BrIf {:?}", i)?; - } - ControlFlowContent::If(cf1, cf2) => { - write!(f, "If {{ {:?}}} ", cf1)?; - if let Some(cf2) = cf2 { - write!(f, "Else {{ {:?}}} ", cf2)?; - } - } - ControlFlowContent::Loop(cf) => { - write!(f, "Loop {{ {:?}}} ", cf)?; - } - ControlFlowContent::Node(i) => { - write!(f, "{:?} ", i)?; - } - } - Ok(()) - } -} - -impl ControlFlowContent { - pub fn node_count(&self) -> usize { - match self { - ControlFlowContent::Block(x) => x.node_count(), - ControlFlowContent::BrIf(_) => 0, - ControlFlowContent::If(x, y) => { - x.node_count() + y.as_ref().map(|it| it.node_count()).unwrap_or(0) - } - ControlFlowContent::Loop(x) => x.node_count(), - ControlFlowContent::Node(_) => 1, - } - } - - pub fn contains(&self, node: usize) -> bool { - match self { - ControlFlowContent::Block(x) => x.contains(node), - ControlFlowContent::BrIf(x) => *x == node, - ControlFlowContent::If(x, y) => { - x.contains(node) || y.as_ref().map(|it| it.contains(node)).unwrap_or(false) - } - ControlFlowContent::Loop(x) => x.contains(node), - ControlFlowContent::Node(x) => *x == node, - } - } - - pub fn first_node(&self) -> usize { - match self { - ControlFlowContent::Block(x) => x.first_node(), - ControlFlowContent::BrIf(_) => panic!(), - ControlFlowContent::If(x, _) => x.first_node(), - ControlFlowContent::Loop(x) => x.first_node(), - ControlFlowContent::Node(x) => *x, - } - } -} - -fn fold_loop(current: &mut Vec, loop_item: &analyzer::Loop) { - let entry = loop_item.entries.first().unwrap(); - let slice_start = current - .iter() - .position(|it| { - if let ControlFlowContent::Node(x) = it { - x == entry - } else { - false - } - }) - .unwrap(); - let slice_end = slice_start + loop_item.node_count(); - let mut removed = current.drain(slice_start..slice_end).collect_vec(); - let sub_loops = loop_item.content.iter().filter_map(|it| { - if let LoopContent::SubLoop(subloop) = it { - Some(subloop) - } else { - None - } - }); - for sub_loop in sub_loops { - fold_loop(&mut removed, &sub_loop); - } - let new_loop_item = ControlFlowContent::Loop(Box::new(ControlFlow { content: removed })); - current.insert(slice_start, new_loop_item); -} - -fn nest_branch(current: &mut Vec, analyzer: &BindedAnalyzer) { - let graph = analyzer.control_flow_graph(); - let mut current_index = 0; - while current_index < current.len() { - let current_item = ¤t[current_index]; - match current_item { - ControlFlowContent::Node(node) => { - let dominates = graph.dominates(*node); - let predecessors = graph - .graph() - .neighbors_directed((*node).into(), Direction::Incoming) - .filter(|it| !dominates.contains(&it.index())) - .collect_vec(); - if predecessors.len() == 1 { - // try nesting `node` and nodes dominated by `node` - // into branch started by `predecessor` - let predecessor = predecessors[0]; - println!("nest {} into {:?}", node, predecessor); - let predecessor_last = - analyzer.bind_on.content[predecessor.index()].content.last(); - if let Some(IRStatement::Branch(branch)) = predecessor_last { - println!("{:?} is already a branch", predecessor); - let predecessor_index = current - .iter() - .position(|it| it.contains(predecessor.index())) - .unwrap(); - let nodes_dominated_by_current_node = graph - .dominates(*node) - .into_iter() - .filter(|it| it != node) - .collect_vec(); - dbg!(&nodes_dominated_by_current_node); - let initial_index = current_index; - current_index += 1; - while nodes_dominated_by_current_node - .iter() - .find(|it| current[current_index].contains(**it)) - .is_some() - { - current_index += 1; - } - let mut content = current.drain(initial_index..current_index).collect(); - nest_branch(&mut content, analyzer); - let after_predecessor = current.get_mut(predecessor_index + 1); - // FIXME: is it in order? - if let Some(ControlFlowContent::If(_, else_part)) = after_predecessor { - *else_part = Some(ControlFlow { content }) - } else { - current.insert( - initial_index, - ControlFlowContent::If(Box::new(ControlFlow { content }), None), - ); - } - } else { - current_index += 1; - } - } else { - current_index += 1; - } - } - ControlFlowContent::Loop(loop_item) => { - let first = loop_item.first_node(); - let dominates = graph.dominates(first); - let predecessors = graph - .graph() - .neighbors_directed(first.into(), Direction::Incoming) - .filter(|it| !dominates.contains(&it.index())) - .collect_vec(); - if predecessors.len() == 1 { - // try nesting `loop_item` and nodes dominated by `loop_item` - // into branch started by `predecessor` - let predecessor = predecessors[0]; - println!("nest {:?} into {:?}", loop_item, predecessor); - let predecessor_last_statement = - analyzer.bind_on.content[predecessor.index()].content.last(); - if let Some(IRStatement::Branch(branch)) = predecessor_last_statement { - let predecessor_index = current - .iter() - .position(|it| it.contains(predecessor.index())) - .unwrap(); - let mut content = vec![current.remove(current_index)]; - nest_branch(&mut content, analyzer); - let after_predecessor = current.get_mut(predecessor_index + 1); - if let Some(ControlFlowContent::If(_, else_part)) = after_predecessor { - *else_part = Some(ControlFlow { content }); - } else { - current.insert( - current_index, - ControlFlowContent::If(Box::new(ControlFlow { content }), None), - ); - } - } else { - current_index += 1; - } - } else { - current_index += 1; - } - } - ControlFlowContent::Block(_) - | ControlFlowContent::BrIf(_) - | ControlFlowContent::If(_, _) => current_index += 1, - } - dbg!(¤t); - } -} - -// function must be reduciable and in correct topo order -fn generate_control_flow( - function: &ir::FunctionDefinition, - analyzer: &Analyzer, -) -> Vec { - let binded_analyzer = analyzer.bind(function); - let mut current = function - .content - .iter() - .enumerate() - .map(|(index, _)| ControlFlowContent::Node(index)) - .collect_vec(); - fold_all_loops(binded_analyzer, &mut current); - let binded_analyzer = analyzer.bind(function); - nest_branch(&mut current, &binded_analyzer); - current -} - -fn fold_all_loops( - binded_analyzer: analyzer::BindedAnalyzer, - initial: &mut Vec, -) { - let analyzed_loops = binded_analyzer.control_flow_graph().loops(); - let sub_loops = analyzed_loops.content.iter().filter_map(|it| { - if let LoopContent::SubLoop(subloop) = it { - Some(subloop) - } else { - None - } - }); - for sub_loop in sub_loops { - fold_loop(initial, &sub_loop); - } -} - -#[test] -fn test_fold() { - use crate::{ - ir::{ - function::{basic_block::BasicBlock, test_util::*}, - statement::Ret, - FunctionDefinition, - }, - utility::data_type, - }; - let function_definition = FunctionDefinition { - header: ir::FunctionHeader { - name: "f".to_string(), - parameters: Vec::new(), - return_type: data_type::Type::None, - }, - content: vec![ - BasicBlock { - name: Some("bb0".to_string()), - content: vec![branch("bb1", "bb7")], - }, - BasicBlock { - name: Some("bb1".to_string()), - content: vec![jump("bb2")], - }, - BasicBlock { - name: Some("bb2".to_string()), - content: vec![jump("bb3")], - }, - BasicBlock { - name: Some("bb3".to_string()), - content: vec![jump("bb4")], - }, - BasicBlock { - name: Some("bb4".to_string()), - content: vec![branch("bb5", "bb6")], - }, - BasicBlock { - name: Some("bb5".to_string()), - content: vec![jump("bb2")], - }, - BasicBlock { - name: Some("bb6".to_string()), - content: vec![branch("bb1", "bb14")], - }, - BasicBlock { - name: Some("bb7".to_string()), - content: vec![branch("bb8", "bb9")], - }, - BasicBlock { - name: Some("bb8".to_string()), - content: vec![jump("bb10")], - }, - BasicBlock { - name: Some("bb10".to_string()), - content: vec![jump("bb11")], - }, - BasicBlock { - name: Some("bb9".to_string()), - content: vec![jump("bb11")], - }, - BasicBlock { - name: Some("bb11".to_string()), - content: vec![branch("bb12", "bb13")], - }, - BasicBlock { - name: Some("bb12".to_string()), - content: vec![jump("bb13")], - }, - BasicBlock { - name: Some("bb13".to_string()), - content: vec![jump("bb14")], - }, - BasicBlock { - name: Some("bb14".to_string()), - content: vec![jump("bb15")], - }, - BasicBlock { - name: Some("bb15".to_string()), - content: vec![Ret { value: None }.into()], - }, - ], - }; - let analyzer = Analyzer::default(); - let graph = analyzer.bind(&function_definition); - let binding = graph.control_flow_graph(); - let graph = binding.graph(); - println!("{:?}", Dot::new(&graph)); - let result = generate_control_flow(&function_definition, &analyzer); - dbg!(result); -} - -// ABC -// --- -// -- \ No newline at end of file diff --git a/src/backend/wasm/mod.rs b/src/backend/wasm/mod.rs index aa0089f..3728a50 100644 --- a/src/backend/wasm/mod.rs +++ b/src/backend/wasm/mod.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use crate::ir::{ - analyzer::{self, BindedControlFlowGraph, LoopContent}, + analyzer::{self, BindedControlFlowGraph, SccContent}, statement::IRStatement, }; @@ -224,7 +224,7 @@ impl Iterator for ControlFlowNodesIter<'_> { } } -fn fold_loop(current: &mut ControlFlowContent, loop_item: &analyzer::Loop) { +fn fold_loop(current: &mut ControlFlowContent, loop_item: &analyzer::Scc) { let (to_remove_indexes, to_remove_items): (Vec<_>, Vec<_>) = current .nodes() .filter(|(_, n)| loop_item.is_node_in((*n).into())) @@ -241,7 +241,7 @@ fn fold_loop(current: &mut ControlFlowContent, loop_item: &analyzer::Loop) { ); *first = new_loop_item; for content in &loop_item.content { - if let LoopContent::SubLoop(subloop) = content { + if let SccContent::SubScc(subloop) = content { fold_loop(first, subloop); } } @@ -463,12 +463,12 @@ mod tests { ControlFlowContent::new_node(3), ControlFlowContent::new_node(4), ]); - let loop_item = analyzer::Loop { + let loop_item = analyzer::Scc { entries: vec![1], content: vec![ - analyzer::LoopContent::Node(1), - analyzer::LoopContent::Node(2), - analyzer::LoopContent::Node(3), + analyzer::SccContent::Node(1), + analyzer::SccContent::Node(2), + analyzer::SccContent::Node(3), ], }; fold_loop(&mut content, &loop_item); @@ -494,17 +494,17 @@ mod tests { ControlFlowContent::new_node(5), ControlFlowContent::new_node(6), ]); - let loop_item = analyzer::Loop { + let loop_item = analyzer::Scc { entries: vec![1], content: vec![ - analyzer::LoopContent::Node(1), - analyzer::LoopContent::Node(2), - analyzer::LoopContent::SubLoop(Box::new(analyzer::Loop { + analyzer::SccContent::Node(1), + analyzer::SccContent::Node(2), + analyzer::SccContent::SubScc(Box::new(analyzer::Scc { entries: vec![3], content: vec![ - analyzer::LoopContent::Node(3), - analyzer::LoopContent::Node(4), - analyzer::LoopContent::Node(5), + analyzer::SccContent::Node(3), + analyzer::SccContent::Node(4), + analyzer::SccContent::Node(5), ], })), ], diff --git a/src/ir/editor/analyzer/control_flow/control_flow_loop.rs b/src/ir/editor/analyzer/control_flow/control_flow_loop.rs index c3716ea..2dd5249 100644 --- a/src/ir/editor/analyzer/control_flow/control_flow_loop.rs +++ b/src/ir/editor/analyzer/control_flow/control_flow_loop.rs @@ -4,27 +4,27 @@ use petgraph::prelude::*; use crate::utility::graph::kosaraju_scc_with_filter; #[derive(Debug, PartialEq)] -pub enum LoopContent { - SubLoop(Box), +pub enum SccContent { + SubScc(Box), Node(usize), } -impl LoopContent { +impl SccContent { pub fn is_node_in(&self, node: NodeIndex) -> bool { match self { - LoopContent::SubLoop(it) => it.is_node_in(node), - LoopContent::Node(it) => node.index() == *it, + SccContent::SubScc(it) => it.is_node_in(node), + SccContent::Node(it) => node.index() == *it, } } } #[derive(Debug, PartialEq)] -pub struct Loop { +pub struct Scc { pub entries: Vec, - pub content: Vec, + pub content: Vec, } -impl Loop { +impl Scc { pub fn new( graph: &DiGraph<(), (), usize>, nodes: &[NodeIndex], @@ -60,7 +60,7 @@ impl Loop { entries: entries.into_iter().map(|it| it.index()).collect(), content: sccs[0] .iter() - .map(|it| LoopContent::Node(it.index())) + .map(|it| SccContent::Node(it.index())) .collect(), }; } @@ -68,16 +68,16 @@ impl Loop { .into_iter() .map(|mut scc| { if scc.len() == 1 { - LoopContent::Node(scc.pop().unwrap().index()) + SccContent::Node(scc.pop().unwrap().index()) } else { - let sub_loop = Loop::new(graph, &scc, &new_backedges); - LoopContent::SubLoop(Box::new(sub_loop)) + let sub_loop = Scc::new(graph, &scc, &new_backedges); + SccContent::SubScc(Box::new(sub_loop)) } }) .collect(); for entry in &entries { if !content.iter().any(|it| it.is_node_in(*entry)) { - content.push(LoopContent::Node(entry.index())); + content.push(SccContent::Node(entry.index())); } } Self { @@ -86,15 +86,15 @@ impl Loop { } } - pub fn first_irreducible_loop(&self) -> Option<&Loop> { + pub fn first_irreducible_loop(&self) -> Option<&Scc> { if self.entries.len() > 1 { Some(self) } else { self.content .iter() .filter_map(|it| match it { - LoopContent::SubLoop(sub_loop) => Some(sub_loop), - LoopContent::Node(_) => None, + SccContent::SubScc(sub_loop) => Some(sub_loop), + SccContent::Node(_) => None, }) .find_map(|it| it.first_irreducible_loop()) } @@ -127,12 +127,12 @@ impl Loop { ) } - pub fn smallest_loop_node_in(&self, node: NodeIndex) -> Option<&Loop> { + pub fn smallest_loop_node_in(&self, node: NodeIndex) -> Option<&Scc> { let found_node = self .content .iter() .filter_map(|it| { - if let LoopContent::Node(node) = it { + if let SccContent::Node(node) = it { Some(*node) } else { None @@ -145,16 +145,16 @@ impl Loop { self.content .iter() .filter_map(|it| match it { - LoopContent::SubLoop(sub_loop) => Some(sub_loop), - LoopContent::Node(_) => None, + SccContent::SubScc(sub_loop) => Some(sub_loop), + SccContent::Node(_) => None, }) .find_map(|it| it.smallest_loop_node_in(node)) } pub fn is_node_in(&self, node: NodeIndex) -> bool { self.content.iter().any(|it| match it { - LoopContent::SubLoop(sub_loop) => sub_loop.is_node_in(node), - LoopContent::Node(n) => node.index() == *n, + SccContent::SubScc(sub_loop) => sub_loop.is_node_in(node), + SccContent::Node(n) => node.index() == *n, }) } @@ -162,8 +162,8 @@ impl Loop { self.content .iter() .map(|it| match it { - LoopContent::SubLoop(sub_loop) => sub_loop.node_count(), - LoopContent::Node(_) => 1, + SccContent::SubScc(sub_loop) => sub_loop.node_count(), + SccContent::Node(_) => 1, }) .sum() } @@ -196,7 +196,7 @@ mod tests { graph.add_edge(node_5, node_4, ()); graph.add_edge(node_3, node_7, ()); graph.add_edge(node_4, node_7, ()); - let result = Loop::new( + let result = Scc::new( &graph, &[ node_0, node_1, node_2, node_3, node_4, node_5, node_6, node_7, @@ -208,8 +208,8 @@ mod tests { .content .iter() .find_map(|it| match it { - LoopContent::SubLoop(sub_loop) => Some(sub_loop), - LoopContent::Node(_) => None, + SccContent::SubScc(sub_loop) => Some(sub_loop), + SccContent::Node(_) => None, }) .unwrap() .as_ref(); diff --git a/src/ir/editor/analyzer/control_flow/mod.rs b/src/ir/editor/analyzer/control_flow/mod.rs index 0ca3663..ad92e1c 100644 --- a/src/ir/editor/analyzer/control_flow/mod.rs +++ b/src/ir/editor/analyzer/control_flow/mod.rs @@ -20,7 +20,10 @@ use crate::{ }; use super::IsAnalyzer; -pub use control_flow_loop::{Loop, LoopContent}; +pub use control_flow_loop::{Scc, SccContent}; + +mod scc_new; +pub use scc_new::BindedScc; /// [`ControlFlowGraph`] is the control flow graph and related infomation of a function. #[derive(Debug)] @@ -173,10 +176,10 @@ impl ControlFlowGraph { self.content(content).dominates(bb_index) } // todo: cache it - fn loops(&self, content: &FunctionDefinition) -> Loop { + fn sccs(&self, content: &FunctionDefinition) -> Scc { let graph = &self.content(content).graph; let nodes: Vec<_> = graph.node_indices().collect(); - Loop::new(graph, &nodes, &[]) + Scc::new(graph, &nodes, &[]) } } @@ -198,8 +201,8 @@ impl<'item, 'bind: 'item> BindedControlFlowGraph<'item, 'bind> { pub fn may_pass_blocks(&self, from: usize, to: usize) -> Ref> { self.item.may_pass_blocks(self.bind_on, from, to) } - pub fn loops(&self) -> Loop { - self.item.loops(self.bind_on) + pub fn sccs(&self) -> Scc { + self.item.sccs(self.bind_on) } pub fn graph(&self) -> &DiGraph<(), (), usize> { &self.item.content(self.bind_on).graph @@ -231,6 +234,12 @@ impl<'item, 'bind: 'item> BindedControlFlowGraph<'item, 'bind> { .filter(|it| !nodes_dominated.contains(it)) .collect() } + + pub fn scc_new(&self) -> BindedScc<'_> { + let graph = &self.item.content(self.bind_on).graph; + let nodes = graph.node_indices().map(|it| it.index()).collect_vec(); + BindedScc::new(graph, nodes.into_iter(), true) + } } impl<'item, 'bind: 'item> IsAnalyzer<'item, 'bind> for ControlFlowGraph { @@ -323,13 +332,13 @@ mod tests { }, ], }; - let loops = control_flow_graph.bind(&function_definition).loops(); - assert!(loops.content.contains(&LoopContent::Node(0))); - assert!(loops.content.contains(&LoopContent::Node(9))); + let loops = control_flow_graph.bind(&function_definition).sccs(); + assert!(loops.content.contains(&SccContent::Node(0))); + assert!(loops.content.contains(&SccContent::Node(9))); assert!(loops .content .iter() - .any(|it| if let LoopContent::SubLoop(subloop) = it { + .any(|it| if let SccContent::SubScc(subloop) = it { subloop.entries.contains(&1) } else { false @@ -337,7 +346,7 @@ mod tests { assert!(loops .content .iter() - .any(|it| if let LoopContent::SubLoop(subloop) = it { + .any(|it| if let SccContent::SubScc(subloop) = it { subloop.entries.contains(&2) } else { false diff --git a/src/ir/editor/analyzer/control_flow/scc_new.rs b/src/ir/editor/analyzer/control_flow/scc_new.rs new file mode 100644 index 0000000..e522406 --- /dev/null +++ b/src/ir/editor/analyzer/control_flow/scc_new.rs @@ -0,0 +1,162 @@ +use itertools::Itertools; +use petgraph::prelude::*; + +use crate::utility::graph::kosaraju_scc_with_filter; + +// todo: This is binded, maybe need an unbinded version +#[derive(Clone, Debug)] +pub struct BindedScc<'a> { + graph: &'a DiGraph<(), (), usize>, + pub nodes: Vec, + pub top_level: bool, +} + +impl<'a> BindedScc<'a> { + pub fn new( + graph: &'a DiGraph<(), (), usize>, + nodes: impl IntoIterator, + top_level: bool, + ) -> Self { + Self { + graph, + nodes: nodes.into_iter().collect(), + top_level, + } + } + + pub fn edges(&self) -> Vec<(usize, usize)> { + self.graph + .edge_references() + .filter(|edge| { + self.nodes.contains(&edge.source().index()) + && self.nodes.contains(&edge.target().index()) + }) + .map(|it| (it.source().index(), it.target().index())) + .collect() + } + + pub fn entry_edges(&self) -> Vec<(usize, usize)> { + self.graph + .edge_references() + .filter(|edge| { + !self.nodes.contains(&edge.source().index()) + && self.nodes.contains(&edge.target().index()) + }) + .map(|it| (it.source().index(), it.target().index())) + .collect() + } + + pub fn entry_nodes(&self) -> Vec { + if self.top_level || self.nodes.len() == 1 { + vec![self.nodes[0]] + } else { + self.entry_edges() + .into_iter() + .map(|(_, to)| to) + .sorted() + .dedup() + .collect() + } + } + + pub fn edges_into_entry_nodes(&self) -> Vec<(usize, usize)> { + self.graph + .edge_references() + .filter(|edge| self.entry_nodes().contains(&edge.target().index())) + .map(|it| (it.source().index(), it.target().index())) + .collect() + } + + pub fn reduciable(&self) -> bool { + self.entry_nodes().len() == 1 + } + + /// Returns all top level sccs for a reducible subgraph. + /// Return None if the subgraph is not reducible. + pub fn top_level_sccs(&self) -> Option>> { + let entry_nodes = self.entry_nodes(); + if entry_nodes.len() != 1 { + None + } else { + let entry_node = entry_nodes[0]; + let backedges: Vec<_> = self + .graph + .edges_directed(entry_node.into(), Incoming) + .map(|it| it.id()) + .collect(); + let sccs = kosaraju_scc_with_filter( + self.graph, + entry_nodes[0].into(), + |node| self.nodes.contains(&node.index()), + |edge| !backedges.contains(&edge), + ); + let result = sccs + .into_iter() + .map(|content| { + Self::new(self.graph, content.into_iter().map(NodeIndex::index), false) + }) + .collect(); + Some(result) + } + } + + pub fn first_irreducible_sub_scc(&self) -> Option> { + if self.nodes.len() == 1 { + return None; + } else if !self.reduciable() { + return Some(self.clone()); + } else { + let sccs = self.top_level_sccs().unwrap(); + for scc in sccs { + if let Some(first_irreducible) = scc.first_irreducible_sub_scc() { + return Some(first_irreducible); + } + } + } + None + } + + pub fn contains(&self, node: usize) -> bool { + self.nodes.contains(&node) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_top_level_scc() { + let mut graph: DiGraph<_, _, usize> = DiGraph::default(); + let node_0 = graph.add_node(()); + let node_1 = graph.add_node(()); + let node_2 = graph.add_node(()); + let node_3 = graph.add_node(()); + let node_4 = graph.add_node(()); + let node_5 = graph.add_node(()); + let node_6 = graph.add_node(()); + let node_7 = graph.add_node(()); + let node_8 = graph.add_node(()); + let node_9 = graph.add_node(()); + let node_10 = graph.add_node(()); + graph.add_edge(node_0, node_1, ()); + graph.add_edge(node_1, node_2, ()); + graph.add_edge(node_2, node_3, ()); + graph.add_edge(node_2, node_7, ()); + graph.add_edge(node_3, node_4, ()); + graph.add_edge(node_3, node_8, ()); + graph.add_edge(node_7, node_5, ()); + graph.add_edge(node_7, node_9, ()); + graph.add_edge(node_9, node_8, ()); + graph.add_edge(node_8, node_9, ()); + graph.add_edge(node_5, node_2, ()); + graph.add_edge(node_4, node_6, ()); + graph.add_edge(node_6, node_2, ()); + graph.add_edge(node_6, node_10, ()); + graph.add_edge(node_10, node_6, ()); + graph.add_edge(node_10, node_4, ()); + let scc = BindedScc::new(&graph, 0..10, true); + dbg!(scc.top_level_sccs()); + dbg!(scc.first_irreducible_sub_scc()); + } +} diff --git a/src/ir/editor/analyzer/mod.rs b/src/ir/editor/analyzer/mod.rs index 9eb3749..a4b6931 100644 --- a/src/ir/editor/analyzer/mod.rs +++ b/src/ir/editor/analyzer/mod.rs @@ -2,7 +2,7 @@ use crate::ir::{self, FunctionDefinition}; use self::register_usage::RegisterUsageAnalyzer; pub use self::{ - control_flow::{BindedControlFlowGraph, ControlFlowGraph, Loop, LoopContent}, + control_flow::{BindedControlFlowGraph, BindedScc, ControlFlowGraph, Scc, SccContent}, memory_usage::{BindedMemoryUsage, MemoryUsage}, register_usage::{BindedRegisterUsage, BindedRegisterUsageAnalyzer}, }; @@ -34,6 +34,11 @@ impl Analyzer { control_flow_graph: ControlFlowGraph::new(), } } + + // todo: remove this when starting to care about performance + pub fn clear_all_cache(&mut self) { + *self = Self::new(); + } } pub struct BindedAnalyzer<'item, 'bind: 'item> { pub bind_on: &'bind FunctionDefinition, diff --git a/src/ir/editor/mod.rs b/src/ir/editor/mod.rs index 119ada9..2422ef7 100644 --- a/src/ir/editor/mod.rs +++ b/src/ir/editor/mod.rs @@ -124,4 +124,9 @@ impl Editor { ); } } + + pub fn direct_edit(&mut self, f: impl FnOnce(&mut super::FunctionDefinition)) { + f(&mut self.content); + self.analyzer.clear_all_cache(); + } } diff --git a/src/ir/function/statement/mod.rs b/src/ir/function/statement/mod.rs index 797fd48..4e88fef 100644 --- a/src/ir/function/statement/mod.rs +++ b/src/ir/function/statement/mod.rs @@ -148,7 +148,8 @@ impl fmt::Display for IRStatement { #[cfg(test)] pub mod test_util { - #![allow(clippy::borrow_interior_mutable_const)] + + use crate::ir::function::basic_block::BasicBlock; use super::*; @@ -193,4 +194,25 @@ pub mod test_util { ) -> IRStatement { phi::test_util::new(target, source1_bb, source1, source2_bb, source2).into() } + + pub fn jump_block(id: usize, to: usize) -> BasicBlock { + BasicBlock { + name: Some(format!("bb{}", id)), + content: vec![jump(&format!("bb{}", to))], + } + } + + pub fn branch_block(id: usize, to1: usize, to2: usize) -> BasicBlock { + BasicBlock { + name: Some(format!("bb{}", id)), + content: vec![branch(&format!("bb{}", to1), &format!("bb{}", to2))], + } + } + + pub fn ret_block(id: usize) -> BasicBlock { + BasicBlock { + name: Some(format!("bb{}", id)), + content: vec![Ret { value: None }.into()], + } + } } diff --git a/src/ir/optimize/pass/fix_irreducible.rs b/src/ir/optimize/pass/fix_irreducible.rs index f3ae4d8..a98c8b1 100644 --- a/src/ir/optimize/pass/fix_irreducible.rs +++ b/src/ir/optimize/pass/fix_irreducible.rs @@ -294,7 +294,7 @@ impl IsPass for FixIrreducible { let binded_analyzer = editor.binded_analyzer(); let control_flow_graph = binded_analyzer.control_flow_graph(); let (mut entries, loop_name) = if let Some(irreducible_loop) = - control_flow_graph.loops().first_irreducible_loop() + control_flow_graph.sccs().first_irreducible_loop() { ( irreducible_loop.entry_info(control_flow_graph.graph()), @@ -387,7 +387,7 @@ mod tests { assert_eq!(editor.content.content.len(), 8); let analyzer = editor.binded_analyzer(); let cfg = analyzer.control_flow_graph(); - assert_eq!(cfg.loops().content.len(), 5); + assert_eq!(cfg.sccs().content.len(), 5); } #[test] diff --git a/src/ir/optimize/pass/fix_irreducible_new/mod.rs b/src/ir/optimize/pass/fix_irreducible_new/mod.rs new file mode 100644 index 0000000..071569d --- /dev/null +++ b/src/ir/optimize/pass/fix_irreducible_new/mod.rs @@ -0,0 +1,386 @@ +use std::{ + collections::{BTreeSet, HashMap, HashSet}, + hash::Hash, +}; + +use itertools::Itertools; + +use crate::{ + ir::{ + analyzer::BindedControlFlowGraph, + editor::Editor, + function::basic_block::BasicBlock, + optimize::pass::{remove_unused_register::RemoveUnusedRegister, TopologicalSort}, + quantity::Quantity, + statement::{branch::BranchType, phi::PhiSource, Branch, IRStatement, Jump, Phi}, + FunctionDefinition, RegisterName, + }, + utility::data_type::{Integer, Type}, +}; + +use super::{IsPass, Pass}; + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct FixIrreducible; + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +enum IntoSccEdgeSource { + /// Jump or branch into one single block + /// Can be jump directly into the scc + /// Or branch into the scc in **one single** condition + One(usize), + /// Branch into two blocks in same Scc + Two { + source_block_index: usize, + on_success: usize, + on_failure: usize, + }, +} + +impl IntoSccEdgeSource { + fn source(&self) -> usize { + match self { + IntoSccEdgeSource::One(source_block_index) => *source_block_index, + IntoSccEdgeSource::Two { + source_block_index, .. + } => *source_block_index, + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +enum FixOtherBlockPlan { + DirectReplace { block: usize, origin_target: String }, + ExtractCondition { block: usize, inverse: bool }, +} + +#[derive(Debug, Clone)] +struct EditPlan { + scc_id: String, + phis: Vec, + branches: Vec, + fix_other_block_plan: HashSet, +} + +fn phi_target(origin_target: &str, scc_id: &str) -> RegisterName { + RegisterName(format!("_should_goto_scc_{}_{}", scc_id, origin_target)) +} + +fn extracted_condition(in_block: &str, scc_id: &str) -> RegisterName { + RegisterName(format!( + "_extracted_branch_condition_scc_{}_at_{}", + scc_id, in_block + )) +} + +fn guard_block_name(origin_target: &str, scc_id: &str) -> String { + format!("_guard_block_scc_{}_for_{}", scc_id, origin_target) +} + +fn generate_edit_plan( + origin_target_to_source_map: &HashMap>, + control_flow_graph: &BindedControlFlowGraph, +) -> EditPlan { + let origin_targets = origin_target_to_source_map + .keys() + .copied() + .sorted() + .collect_vec(); + let scc_id = origin_targets.iter().join("_"); + // We order all the source blocks by their index + let all_sources: BTreeSet = origin_target_to_source_map + .values() + .flatten() + .map(|it| it.source()) + .collect(); + // origin target's index -> the phi node which represents whether we should jump to this target + let mut phis = HashMap::new(); + // first, we construct all the Phi statements + for &origin_target in &origin_targets { + // todo: we construct an invalid Phi node (all value = 0) here and fill the content back later + // MAYBE try avoid this by using a `PhiBuilder`. + let origin_target_name = control_flow_graph.basic_block_name_by_index(origin_target); + let phi = Phi { + to: phi_target(origin_target_name, &scc_id), + data_type: Type::Integer(Integer { + signed: false, + width: 1, + }), + // all phi nodes are interested in all the sources + // we keep them all 0, means "should not branch to that target" for now + from: all_sources + .iter() + .map(|it| PhiSource { + value: Quantity::NumberLiteral(0), + block: control_flow_graph + .basic_block_name_by_index(*it) + .to_string(), + }) + .collect(), + }; + phis.insert(origin_target, phi); + } + // then we consider all sources, and: + // - Generate fix other block plan + // - Fill the Phi node + let mut fix_other_block_plan = HashSet::new(); + for (origin_target, sources) in origin_target_to_source_map { + for source in sources.iter() { + let source_block_name = control_flow_graph.basic_block_name_by_index(source.source()); + // get the `value` field for the Phi node, and generate fix other block plan + let value = match source { + IntoSccEdgeSource::One(source_block_index) => { + // in this case, we can directly replace the jump/branch target of the last statement + // of the source block + fix_other_block_plan.insert(FixOtherBlockPlan::DirectReplace { + block: *source_block_index, + origin_target: control_flow_graph + .basic_block_name_by_index(*origin_target) + .to_string(), + }); + // and we should jump to the origin target whatever happens + Quantity::NumberLiteral(1) + } + IntoSccEdgeSource::Two { + on_success, + on_failure, + source_block_index, + } => { + // in this case, we need to extract the source block's branch condition + + // Whether we need to inverse the condition depends on whether the order of the + // [on_success, on_failure] is the same as the order of the branch targets. + // For making these easier, we keep the branch target in ascending order + // thus, if on_success >= on_failure, we need to inverse the condition + fix_other_block_plan.insert(FixOtherBlockPlan::ExtractCondition { + block: *source_block_index, + inverse: on_success >= on_failure, + }); + // and we should jump to the left target if the extracted condition is true + Quantity::RegisterName(extracted_condition(source_block_name, &scc_id)) + } + }; + // update the Phi node + phis.get_mut(origin_target).unwrap().from[all_sources + .iter() + .position(|&it| it == source.source()) + .unwrap()] = PhiSource { + value, + block: source_block_name.to_string(), + }; + } + } + // we don't need origin target's index part of the phis anymore + let mut phis = phis + .into_iter() + .sorted_by(|(a, _), (b, _)| a.cmp(b)) + .map(|it| it.1) + .collect_vec(); + // last, we generate all the branches + + // the last branch to is special, we don't need to generate a guard block for it + // so we save it here for later use + let last_branch_to = *origin_targets.last().unwrap(); + // Generate all the branches, include the last one + let mut branches = phis + .iter() + .zip(origin_targets.into_iter()) + .tuple_windows() + .map( + |((depend_on_phi_result, target_block_index), (_, next_target_block_index))| Branch { + branch_type: BranchType::NE, + operand1: Quantity::RegisterName(depend_on_phi_result.to.clone()), + operand2: Quantity::NumberLiteral(0), + success_label: control_flow_graph + .basic_block_name_by_index(target_block_index) + .to_string(), + failure_label: guard_block_name( + control_flow_graph.basic_block_name_by_index(next_target_block_index), + &scc_id, + ), + }, + ) + .collect_vec(); + // we don't generate a guard block for the last branch, + // instead, we directly jump to the last target + // so the last branch's failure label needs to be updated + branches.last_mut().unwrap().failure_label = control_flow_graph + .basic_block_name_by_index(last_branch_to) + .to_string(); + // and the last phi is also useless + phis.pop(); + EditPlan { + phis, + branches, + fix_other_block_plan, + scc_id, + } +} + +fn fix_other_block( + function: &mut FunctionDefinition, + first_guard_block_name: &str, + scc_id: &str, + plan: FixOtherBlockPlan, +) { + match plan { + FixOtherBlockPlan::DirectReplace { + block, + origin_target, + } => { + let block = &mut function.content[block]; + let last_stmt = block.content.last_mut().unwrap(); + match last_stmt { + IRStatement::Jump(jump) => { + jump.label = first_guard_block_name.to_string(); + } + IRStatement::Branch(branch) => { + if branch.success_label == origin_target { + branch.success_label = first_guard_block_name.to_string(); + } + if branch.failure_label == origin_target { + branch.failure_label = first_guard_block_name.to_string(); + } + } + _ => unreachable!(), + } + } + FixOtherBlockPlan::ExtractCondition { block, inverse } => { + let block = &mut function.content[block]; + let block_name = block.name.clone().unwrap(); + let last_stmt = block.content.pop().unwrap(); + if let IRStatement::Branch(branch) = last_stmt { + let extracted_register_name = extracted_condition(&block_name, scc_id); + let mut condition = branch.extract_condition(extracted_register_name); + if inverse { + condition.operation = condition.operation.inverse().unwrap(); + } + block.content.push(IRStatement::BinaryCalculate(condition)); + block.content.push(IRStatement::Jump(Jump { + label: first_guard_block_name.to_string(), + })); + } else { + unreachable!() + } + } + } +} + +fn execute_edit_plan(function: &mut FunctionDefinition, plan: EditPlan) { + // first, we create all necessary blocks + let mut guard_blocks = plan + .branches + .iter() + .map(|it| { + let name = guard_block_name(&it.success_label, &plan.scc_id); + BasicBlock::new(name) + }) + .collect_vec(); + // insert all the phis into the first guard block + guard_blocks[0].content = plan.phis.into_iter().map(IRStatement::Phi).collect(); + // insert all the branches into the guard blocks + for (guard_block, branch) in guard_blocks.iter_mut().zip(plan.branches) { + guard_block.content.push(IRStatement::Branch(branch)); + } + // edit the original function + let first_guard_block_name = guard_blocks[0].name.clone().unwrap(); + for fix_plan in plan.fix_other_block_plan { + fix_other_block(function, &first_guard_block_name, &plan.scc_id, fix_plan); + } + function.content.extend(guard_blocks.into_iter()); +} + +fn generate_origin_target_to_source_map( + function_definition: &FunctionDefinition, + mut edges_into_entry_nodes: Vec<(usize, usize)>, +) -> HashMap> { + // first we want to know which "into scc edge sources" should be IntoSccEdgeSource::Two + // to do this, we edges_into_entry_nodes sort by first source, + // if there are two consequent edges_into_entry_node with same .0, + // they belong to the IntoSccEdgeSource::Two category + edges_into_entry_nodes.sort(); + let mut two_nodes = Vec::new(); + let mut one_nodes = Vec::new(); + while !edges_into_entry_nodes.is_empty() { + let last = edges_into_entry_nodes.pop().unwrap(); + if let Some(last_but_one) = edges_into_entry_nodes.pop() { + if last_but_one.0 == last.0 { + two_nodes.push((last, last_but_one)); + } else { + one_nodes.push(last); + edges_into_entry_nodes.push(last_but_one); + } + } else { + one_nodes.push(last); + } + } + let mut result: HashMap> = HashMap::new(); + for ((in_block, target1), (_, target2)) in two_nodes { + let target1_is_success = &function_definition[in_block] + .content + .last() + .unwrap() + .as_branch() + .success_label + == function_definition[target1].name.as_ref().unwrap(); + let on_success = if target1_is_success { target1 } else { target2 }; + let on_failure = if target1_is_success { target2 } else { target1 }; + let into_scc_edge_source = IntoSccEdgeSource::Two { + source_block_index: in_block, + on_success, + on_failure, + }; + result + .entry(target1) + .or_default() + .push(into_scc_edge_source.clone()); + result + .entry(target2) + .or_default() + .push(into_scc_edge_source); + } + for (in_block, target) in one_nodes { + result + .entry(target) + .or_default() + .push(IntoSccEdgeSource::One(in_block)); + } + result +} + +impl IsPass for FixIrreducible { + fn run(&self, editor: &mut Editor) { + while let Some(irreducible_scc) = editor + .binded_analyzer() + .control_flow_graph() + .scc_new() + .first_irreducible_sub_scc() + { + let analyzer = editor.binded_analyzer(); + let graph = analyzer.control_flow_graph(); + let edges_into_entry_nodes = irreducible_scc.edges_into_entry_nodes(); + let origin_target_to_source_map = + generate_origin_target_to_source_map(&editor.content, edges_into_entry_nodes); + let edit_plan = generate_edit_plan(&origin_target_to_source_map, &graph); + drop(graph); + drop(irreducible_scc); + // fixme: don't use direct_edit for performance's sake! + editor.direct_edit(move |f| { + execute_edit_plan(f, edit_plan); + }); + } + } + + /// Which passes this pass requires to be executed before it. + fn need(&self) -> Vec { + Vec::new() + } + + /// Which passes this pass will invalidate. + fn invalidate(&self) -> Vec { + vec![TopologicalSort.into(), RemoveUnusedRegister.into()] + } +} + +#[cfg(test)] +mod tests; diff --git a/src/ir/optimize/pass/fix_irreducible_new/tests.rs b/src/ir/optimize/pass/fix_irreducible_new/tests.rs new file mode 100644 index 0000000..f25ed4a --- /dev/null +++ b/src/ir/optimize/pass/fix_irreducible_new/tests.rs @@ -0,0 +1,483 @@ +use function::statement::{phi, BinaryCalculate}; + +use crate::{ + ir::{ + self, + analyzer::{ControlFlowGraph, IsAnalyzer}, + function::{self, test_util::*}, + statement::branch, + FunctionDefinition, + }, + utility::data_type, +}; + +use super::*; + +#[test] +fn test_generate_edit_plan() { + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + jump_block(0, 1), + jump_block(1, 2), + branch_block(2, 3, 7), + branch_block(3, 4, 8), + branch_block(4, 6, 8), + branch_block(5, 2, 10), + branch_block(6, 2, 10), + branch_block(7, 5, 9), + branch_block(8, 3, 10), + jump_block(9, 8), + branch_block(10, 4, 6), + ], + }; + let control_flow_graph = ControlFlowGraph::new(); + let binded = control_flow_graph.bind(&function_definition); + let mut origin_target_to_source_map = HashMap::new(); + origin_target_to_source_map.insert( + binded.basic_block_index_by_name("bb3"), + vec![ + IntoSccEdgeSource::One(binded.basic_block_index_by_name("bb2")), + IntoSccEdgeSource::Two { + source_block_index: binded.basic_block_index_by_name("bb8"), + on_success: binded.basic_block_index_by_name("bb3"), + on_failure: binded.basic_block_index_by_name("bb10"), + }, + ], + ); + origin_target_to_source_map.insert( + binded.basic_block_index_by_name("bb8"), + vec![ + IntoSccEdgeSource::One(binded.basic_block_index_by_name("bb3")), + IntoSccEdgeSource::One(binded.basic_block_index_by_name("bb4")), + IntoSccEdgeSource::One(binded.basic_block_index_by_name("bb9")), + ], + ); + origin_target_to_source_map.insert( + binded.basic_block_index_by_name("bb10"), + vec![ + IntoSccEdgeSource::Two { + source_block_index: binded.basic_block_index_by_name("bb8"), + on_success: binded.basic_block_index_by_name("bb8"), + on_failure: binded.basic_block_index_by_name("bb10"), + }, + IntoSccEdgeSource::One(binded.basic_block_index_by_name("bb5")), + IntoSccEdgeSource::One(binded.basic_block_index_by_name("bb6")), + ], + ); + let plan = generate_edit_plan(&origin_target_to_source_map, &binded); + assert_eq!(plan.phis.len(), 2); + let phi_for_bb3 = plan + .phis + .iter() + .find(|it| it.to == RegisterName("_should_goto_scc_3_8_10_bb3".to_string())) + .unwrap(); + assert_eq!("%_should_goto_scc_3_8_10_bb3 = phi u1 [bb2, 1], [bb3, 0], [bb4, 0], [bb5, 0], [bb6, 0], [bb8, %_extracted_branch_condition_scc_3_8_10_at_bb8], [bb9, 0]", format!("{}", phi_for_bb3)); + let phi_for_bb8 = plan + .phis + .iter() + .find(|it| it.to == RegisterName("_should_goto_scc_3_8_10_bb8".to_string())) + .unwrap(); + assert_eq!("%_should_goto_scc_3_8_10_bb8 = phi u1 [bb2, 0], [bb3, 1], [bb4, 1], [bb5, 0], [bb6, 0], [bb8, 0], [bb9, 1]", format!("{}", phi_for_bb8)); + assert_eq!(plan.branches.len(), 2); + let branch_for_bb3 = plan + .branches + .iter() + .find(|it| it.success_label == "bb3") + .unwrap(); + assert_eq!( + "bne %_should_goto_scc_3_8_10_bb3, 0, bb3, _guard_block_scc_3_8_10_for_bb8", + format!("{}", branch_for_bb3) + ); + let branch_for_bb8 = plan + .branches + .iter() + .find(|it| it.success_label == "bb8") + .unwrap(); + assert_eq!( + "bne %_should_goto_scc_3_8_10_bb8, 0, bb8, bb10", + format!("{}", branch_for_bb8) + ); + assert_eq!(plan.fix_other_block_plan.len(), 7); + assert!(plan + .fix_other_block_plan + .contains(&FixOtherBlockPlan::DirectReplace { + block: binded.basic_block_index_by_name("bb2"), + origin_target: "bb3".to_string(), + })); + assert!(plan + .fix_other_block_plan + .contains(&FixOtherBlockPlan::ExtractCondition { + block: binded.basic_block_index_by_name("bb8"), + inverse: false + })); +} + +#[test] +fn test_inverse_condition() { + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + branch_block(0, 1, 2), + branch_block(1, 4, 3), + branch_block(2, 3, 4), + jump_block(3, 4), + jump_block(4, 5), + branch_block(5, 3, 6), + ret_block(6), + ], + }; + let control_flow_graph = ControlFlowGraph::new(); + let binded = control_flow_graph.bind(&function_definition); + let mut origin_target_to_source_map = HashMap::new(); + origin_target_to_source_map.insert( + binded.basic_block_index_by_name("bb3"), + vec![ + IntoSccEdgeSource::Two { + source_block_index: binded.basic_block_index_by_name("bb1"), + on_success: binded.basic_block_index_by_name("bb4"), + on_failure: binded.basic_block_index_by_name("bb3"), + }, + IntoSccEdgeSource::Two { + source_block_index: binded.basic_block_index_by_name("bb2"), + on_success: binded.basic_block_index_by_name("bb3"), + on_failure: binded.basic_block_index_by_name("bb4"), + }, + ], + ); + origin_target_to_source_map.insert( + binded.basic_block_index_by_name("bb4"), + vec![ + IntoSccEdgeSource::Two { + source_block_index: binded.basic_block_index_by_name("bb1"), + on_success: binded.basic_block_index_by_name("bb4"), + on_failure: binded.basic_block_index_by_name("bb3"), + }, + IntoSccEdgeSource::Two { + source_block_index: binded.basic_block_index_by_name("bb2"), + on_success: binded.basic_block_index_by_name("bb3"), + on_failure: binded.basic_block_index_by_name("bb4"), + }, + IntoSccEdgeSource::One(binded.basic_block_index_by_name("bb3")), + ], + ); + let plan = generate_edit_plan(&origin_target_to_source_map, &binded); + assert_eq!(plan.fix_other_block_plan.len(), 3); + assert!(plan + .fix_other_block_plan + .contains(&FixOtherBlockPlan::ExtractCondition { + block: binded.basic_block_index_by_name("bb1"), + inverse: true + })); + assert!(plan + .fix_other_block_plan + .contains(&FixOtherBlockPlan::ExtractCondition { + block: binded.basic_block_index_by_name("bb2"), + inverse: false + })); +} + +#[test] +fn test_execute_edit_plan() { + let mut function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + branch_block(0, 1, 3), + jump_block(1, 2), + jump_block(2, 3), + jump_block(3, 1), + ], + }; + let edit_plan = EditPlan { + scc_id: "1_3".to_string(), + phis: vec![phi::parse( + "%_should_goto_scc_1_3_bb1 = phi u1 [%_extracted_branch_condition_scc_1_3_at_bb0, bb0], [1, bb3]", + ).unwrap().1], + branches: vec![branch::parse( + "bne %_should_goto_scc_1_3_bb1, 0, bb1, bb3" + ).unwrap().1], + fix_other_block_plan: [ + FixOtherBlockPlan::ExtractCondition { block: 0, inverse: false }, + FixOtherBlockPlan::DirectReplace { block: 2, origin_target: "bb3".to_string() }, + FixOtherBlockPlan::DirectReplace { block: 3, origin_target: "bb1".to_string() } + ].into_iter().collect(), + }; + execute_edit_plan(&mut function_definition, edit_plan); + let bb0 = &function_definition.content[0]; + let condition_statement = bb0.content.iter().rev().nth(1).unwrap(); + let jump_statement = bb0.content.iter().last().unwrap(); + if let IRStatement::BinaryCalculate(BinaryCalculate { to, .. }) = condition_statement { + assert_eq!( + to, + &RegisterName("_extracted_branch_condition_scc_1_3_at_bb0".to_string()) + ) + } else { + panic!("condition_statement should be a `BinaryCalculate`"); + } + if let IRStatement::Jump(Jump { label }) = jump_statement { + assert_eq!(label, "_guard_block_scc_1_3_for_bb1"); + } else { + panic!("jump_statement should be a `Jump`") + } + let bb2 = &function_definition.content[2]; + assert_eq!( + bb2.content.last().unwrap().as_jump().label, + "_guard_block_scc_1_3_for_bb1" + ); + let bb3 = &function_definition.content[3]; + assert_eq!( + bb3.content.last().unwrap().as_jump().label, + "_guard_block_scc_1_3_for_bb1" + ); + let new_block = &function_definition.content[4]; + assert_eq!( + new_block.name.as_ref().unwrap(), + "_guard_block_scc_1_3_for_bb1" + ); + let phi = new_block.content[0].as_phi(); + assert_eq!(phi.to, RegisterName("_should_goto_scc_1_3_bb1".to_string())); + assert_eq!(phi.from.len(), 2); + assert!(phi.from.contains(&PhiSource { + value: RegisterName("_extracted_branch_condition_scc_1_3_at_bb0".to_string()).into(), + block: "bb0".to_string() + })); + assert!(phi.from.contains(&PhiSource { + value: 1.into(), + block: "bb3".to_string() + })); + let branch = new_block.content[1].as_branch(); + assert_eq!( + branch.operand1, + RegisterName("_should_goto_scc_1_3_bb1".to_string()).into() + ); + assert_eq!(branch.operand2, 0.into()); + assert_eq!(branch.success_label, "bb1"); + assert_eq!(branch.failure_label, "bb3"); +} + +#[test] +fn test_generate_origin_target_to_source_map() { + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + branch_block(0, 1, 3), + jump_block(1, 2), + jump_block(2, 3), + jump_block(3, 1), + ], + }; + let edges_into_entry_nodes = vec![(0, 1), (0, 3), (3, 1), (2, 3)]; + let target_to_source_map = + generate_origin_target_to_source_map(&function_definition, edges_into_entry_nodes); + assert!(target_to_source_map + .get(&1) + .unwrap() + .contains(&IntoSccEdgeSource::One(3))); + assert!(target_to_source_map + .get(&1) + .unwrap() + .contains(&IntoSccEdgeSource::Two { + source_block_index: 0, + on_success: 1, + on_failure: 3 + })); + assert!(target_to_source_map + .get(&3) + .unwrap() + .contains(&IntoSccEdgeSource::One(2))); + assert!(target_to_source_map + .get(&3) + .unwrap() + .contains(&IntoSccEdgeSource::Two { + source_block_index: 0, + on_success: 1, + on_failure: 3 + })); + + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + branch_block(0, 1, 3), + jump_block(1, 2), + jump_block(2, 3), + branch_block(3, 1, 3), + ], + }; + let edges_into_entry_nodes = vec![(0, 1), (0, 3), (3, 3), (3, 1), (2, 3)]; + let target_to_source_map = + generate_origin_target_to_source_map(&function_definition, edges_into_entry_nodes); + assert!(target_to_source_map + .get(&1) + .unwrap() + .contains(&IntoSccEdgeSource::Two { + source_block_index: 3, + on_success: 1, + on_failure: 3 + })); + assert!(target_to_source_map + .get(&1) + .unwrap() + .contains(&IntoSccEdgeSource::Two { + source_block_index: 0, + on_success: 1, + on_failure: 3 + })) +} + +#[test] +fn test_fix_irreducible() { + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + branch_block(0, 1, 2), + branch_block(1, 3, 5), + branch_block(3, 2, 9), + jump_block(2, 1), + branch_block(5, 4, 8), + jump_block(4, 6), + branch_block(8, 6, 3), + jump_block(6, 7), + jump_block(7, 8), + ret_block(9), + ], + }; + let mut editor = Editor::new(function_definition); + let pass = FixIrreducible; + pass.run(&mut editor); + assert_eq!(editor.content.content.len(), 12); + let guard1 = editor + .content + .content + .iter() + .find(|it| it.name.as_ref().unwrap() == "_guard_block_scc_1_3_for_bb1") + .unwrap(); + assert!(guard1.content.len() == 2); + assert!(guard1.content[0].as_phi().from.contains(&PhiSource { + value: RegisterName("_extracted_branch_condition_scc_1_3_at_bb0".to_string()).into(), + block: "bb0".to_string() + })); + assert_eq!( + guard1.content[1].as_branch().operand1, + RegisterName("_should_goto_scc_1_3_bb1".to_string()).into() + ); + + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + jump_block(0, 1), + jump_block(1, 2), + branch_block(2, 3, 7), + branch_block(3, 4, 8), + branch_block(7, 5, 9), + branch_block(4, 6, 8), + branch_block(8, 3, 9), + branch_block(6, 2, 10), + branch_block(10, 6, 4), + branch_block(5, 2, 10), + jump_block(9, 8), + ], + }; + let mut editor = Editor::new(function_definition); + let pass = FixIrreducible; + pass.run(&mut editor); + assert_eq!(editor.content.content.len(), 13); + let guard1 = editor + .content + .content + .iter() + .find(|it| it.name.as_ref().unwrap() == "_guard_block_scc_3_8_10_for_bb3") + .unwrap(); + assert!(guard1.content.len() == 3); + assert!(guard1.content[0].as_phi().from.contains(&PhiSource { + value: RegisterName("_extracted_branch_condition_scc_3_8_10_at_bb8".to_string()).into(), + block: "bb8".to_string() + })); + assert_eq!( + guard1.content[1].as_phi().to, + RegisterName("_should_goto_scc_3_8_10_bb10".to_string()) + ); + + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + jump_block(0, 1), + jump_block(1, 2), + branch_block(2, 3, 7), + branch_block(3, 4, 8), + branch_block(4, 6, 8), + branch_block(8, 3, 10), + branch_block(6, 2, 10), + branch_block(10, 6, 4), + branch_block(7, 5, 9), + branch_block(5, 2, 10), + jump_block(9, 8), + ], + }; + let mut editor = Editor::new(function_definition); + let pass = FixIrreducible; + pass.run(&mut editor); + println!("{}", editor.content); + assert_eq!(editor.content.content.len(), 13); + let guard1 = editor + .content + .content + .iter() + .find(|it| it.name.as_ref().unwrap() == "_guard_block_scc_3_5_7_for_bb3") + .unwrap(); + assert!(guard1.content.len() == 3); + assert!(guard1.content[0].as_phi().from.contains(&PhiSource { + value: RegisterName("_extracted_branch_condition_scc_3_5_7_at_bb8".to_string()).into(), + block: "bb8".to_string() + })); + assert_eq!( + guard1.content[2].as_branch().failure_label, + "_guard_block_scc_3_5_7_for_bb8" + ); + let bb8 = editor + .content + .content + .iter() + .find(|it| it.name.as_ref().unwrap() == "bb8") + .unwrap(); + assert_eq!(bb8.content.len(), 2); + assert_eq!( + bb8.content[0].as_binary_calculate().to, + RegisterName("_extracted_branch_condition_scc_3_5_7_at_bb8".to_string()) + ); + assert_eq!( + bb8.content[1].as_jump().label, + "_guard_block_scc_3_5_7_for_bb3" + ); +} diff --git a/src/ir/optimize/pass/mod.rs b/src/ir/optimize/pass/mod.rs index ba7b319..0ad99f3 100644 --- a/src/ir/optimize/pass/mod.rs +++ b/src/ir/optimize/pass/mod.rs @@ -1,4 +1,5 @@ mod fix_irreducible; +mod fix_irreducible_new; mod memory_to_register; mod remove_load_directly_after_store; mod remove_only_once_store; diff --git a/src/ir/optimize/pass/topological_sort.rs b/src/ir/optimize/pass/topological_sort.rs index 2f9a859..b813304 100644 --- a/src/ir/optimize/pass/topological_sort.rs +++ b/src/ir/optimize/pass/topological_sort.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use petgraph::prelude::*; use crate::ir::{ - analyzer::{BindedControlFlowGraph, IsAnalyzer, Loop}, + analyzer::{BindedControlFlowGraph, IsAnalyzer, Scc}, optimize::pass::fix_irreducible::FixIrreducible, }; @@ -17,7 +17,7 @@ impl IsPass for TopologicalSort { fn run(&self, editor: &mut crate::ir::editor::Editor) { let analyzer = editor.analyzer.bind(&editor.content); let graph = analyzer.control_flow_graph(); - let loops = graph.loops(); + let loops = graph.sccs(); let content: Vec<_> = topological_order(&graph, &loops) .into_iter() .map(|it| mem::take(&mut editor.content.content[it])) @@ -36,7 +36,7 @@ impl IsPass for TopologicalSort { fn topological_order_dfs( graph: &BindedControlFlowGraph, - top_level: &Loop, + top_level: &Scc, current_at: NodeIndex, visited: &mut Vec>, result: &mut Vec>, @@ -82,7 +82,7 @@ fn topological_order_dfs( result.push(current_at); } -pub fn topological_order(graph: &BindedControlFlowGraph, top_level: &Loop) -> Vec { +pub fn topological_order(graph: &BindedControlFlowGraph, top_level: &Scc) -> Vec { let mut order = vec![]; let mut visited = vec![]; topological_order_dfs(graph, top_level, 0.into(), &mut visited, &mut order); diff --git a/src/utility/data_type.rs b/src/utility/data_type.rs index abf6e6d..509c6ba 100644 --- a/src/utility/data_type.rs +++ b/src/utility/data_type.rs @@ -85,6 +85,15 @@ impl fmt::Display for Integer { } } +#[cfg(test)] +#[allow(clippy::declare_interior_mutable_const)] +pub const BOOL: std::cell::LazyCell = std::cell::LazyCell::new(|| { + Type::Integer(Integer { + signed: false, + width: 1, + }) +}); + #[cfg(test)] #[allow(clippy::declare_interior_mutable_const)] pub const I32: std::cell::LazyCell = std::cell::LazyCell::new(|| { diff --git a/src/utility/graph.rs b/src/utility/graph.rs index d78cc29..147ba60 100644 --- a/src/utility/graph.rs +++ b/src/utility/graph.rs @@ -320,21 +320,20 @@ mod tests { let node_10 = graph.add_node(10); graph.add_edge(node_0, node_1, ()); graph.add_edge(node_1, node_2, ()); + graph.add_edge(node_1, node_6, ()); graph.add_edge(node_2, node_3, ()); graph.add_edge(node_3, node_5, ()); - graph.add_edge(node_5, node_4, ()); graph.add_edge(node_4, node_2, ()); + graph.add_edge(node_4, node_9, ()); + graph.add_edge(node_5, node_4, ()); + graph.add_edge(node_5, node_10, ()); graph.add_edge(node_6, node_4, ()); - graph.add_edge(node_1, node_6, ()); + graph.add_edge(node_6, node_7, ()); graph.add_edge(node_7, node_8, ()); + graph.add_edge(node_8, node_3, ()); graph.add_edge(node_8, node_9, ()); graph.add_edge(node_9, node_7, ()); - graph.add_edge(node_6, node_7, ()); - graph.add_edge(node_4, node_9, ()); - graph.add_edge(node_8, node_3, ()); - graph.add_edge(node_5, node_10, ()); let result = kosaraju_scc_with_filter(&graph, node_0, |_| true, |_| true); - assert_eq!(result.len(), 5); let node_2_in_scc = result.iter().find(|scc| scc.contains(&node_2)).unwrap(); assert_eq!(node_2_in_scc.len(), 7); From ef06a5296e94f4557b63d1004428153dd0dcca28 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Tue, 23 Apr 2024 23:42:43 +0200 Subject: [PATCH 04/11] fix fold_if_else --- Cargo.toml | 2 +- src/backend/wasm/control_flow/mod.rs | 344 +++++++ src/backend/wasm/control_flow/selector.rs | 195 ++++ src/backend/wasm/mod.rs | 890 ++++++++---------- .../control_flow/control_flow_loop.rs | 220 ----- src/ir/editor/analyzer/control_flow/mod.rs | 167 ++-- src/ir/editor/analyzer/control_flow/scc.rs | 231 +++++ .../editor/analyzer/control_flow/scc_new.rs | 162 ---- src/ir/editor/analyzer/mod.rs | 2 +- src/ir/function/basic_block.rs | 4 + src/ir/optimize/pass/fix_irreducible.rs | 557 ----------- .../mod.rs | 2 +- .../tests.rs | 3 +- src/ir/optimize/pass/mod.rs | 4 +- src/ir/optimize/pass/topological_sort.rs | 19 +- src/lib.rs | 2 + 16 files changed, 1246 insertions(+), 1558 deletions(-) create mode 100644 src/backend/wasm/control_flow/mod.rs create mode 100644 src/backend/wasm/control_flow/selector.rs delete mode 100644 src/ir/editor/analyzer/control_flow/control_flow_loop.rs create mode 100644 src/ir/editor/analyzer/control_flow/scc.rs delete mode 100644 src/ir/editor/analyzer/control_flow/scc_new.rs delete mode 100644 src/ir/optimize/pass/fix_irreducible.rs rename src/ir/optimize/pass/{fix_irreducible_new => fix_irreducible}/mod.rs (99%) rename src/ir/optimize/pass/{fix_irreducible_new => fix_irreducible}/tests.rs (99%) diff --git a/Cargo.toml b/Cargo.toml index e9e55ea..de4b494 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,9 @@ petgraph = "0.6.4" phf = { version = "0.11.2", features = ["macros"] } serde = { version = "1.0.195", features = ["derive"] } toml = "0.8.8" -delegate = { version = "0.12.0" } shadow-rs = { version = "0.26.0", optional = true } ezio = { version = "0.1.2", optional = true } +delegate = "0.12.0" [dev-dependencies] cov-mark = "1.1.0" diff --git a/src/backend/wasm/control_flow/mod.rs b/src/backend/wasm/control_flow/mod.rs new file mode 100644 index 0000000..1167431 --- /dev/null +++ b/src/backend/wasm/control_flow/mod.rs @@ -0,0 +1,344 @@ +pub use self::selector::{CFSelector, CFSelectorSegment}; +use std::ops::{Index, IndexMut}; + +mod selector; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ControlFlowElement { + Block { + content: Vec, + }, + If { + condition: Box, // must be ControlFlowElement::Node + on_success: Vec, + on_failure: Vec, + }, + Loop { + content: Vec, + }, + BasicBlock { + id: usize, + }, +} + +impl ControlFlowElement { + pub fn new_node(index: usize) -> Self { + Self::BasicBlock { id: index } + } + pub fn new_block(content: Vec) -> Self { + Self::Block { content } + } + pub fn first_basic_block_id(&self) -> usize { + match self { + ControlFlowElement::Block { content } => content[0].first_basic_block_id(), + ControlFlowElement::If { condition, .. } => condition.first_basic_block_id(), + ControlFlowElement::Loop { content } => content[0].first_basic_block_id(), + ControlFlowElement::BasicBlock { id: node_id } => *node_id, + } + } + pub fn first_basic_block_selector(&self) -> CFSelector { + match self { + ControlFlowElement::Block { content } | ControlFlowElement::Loop { content } => { + let mut result = content[0].first_basic_block_selector(); + result.push_front(CFSelectorSegment::ContentAtIndex(0)); + result + } + ControlFlowElement::If { .. } => { + CFSelector::from_segment(CFSelectorSegment::IfCondition) + } + ControlFlowElement::BasicBlock { .. } => CFSelector::new_empty(), + } + } + fn select_mut_by_segment(&mut self, segment: CFSelectorSegment) -> &mut ControlFlowElement { + match (self, segment) { + (ControlFlowElement::Block { content }, CFSelectorSegment::ContentAtIndex(index)) + | (ControlFlowElement::Loop { content }, CFSelectorSegment::ContentAtIndex(index)) => { + &mut content[index] + } + (ControlFlowElement::If { condition, .. }, CFSelectorSegment::IfCondition) => { + condition.as_mut() + } + ( + ControlFlowElement::If { on_success, .. }, + CFSelectorSegment::IndexInSuccess(index), + ) => &mut on_success[index], + ( + ControlFlowElement::If { on_failure, .. }, + CFSelectorSegment::IndexInFailure(index), + ) => &mut on_failure[index], + ( + _, + CFSelectorSegment::IfCondition + | CFSelectorSegment::IndexInFailure(_) + | CFSelectorSegment::IndexInSuccess(_), + ) => unreachable!(), + (ControlFlowElement::If { .. }, CFSelectorSegment::ContentAtIndex(_)) => unreachable!(), + (ControlFlowElement::BasicBlock { .. }, _) => unreachable!(), + } + } + fn select_by_segment(&self, segment: CFSelectorSegment) -> Option<&ControlFlowElement> { + match (self, segment) { + (ControlFlowElement::Block { content }, CFSelectorSegment::ContentAtIndex(index)) + | (ControlFlowElement::Loop { content }, CFSelectorSegment::ContentAtIndex(index)) => { + content.get(index) + } + (ControlFlowElement::If { condition, .. }, CFSelectorSegment::IfCondition) => { + Some(condition.as_ref()) + } + ( + ControlFlowElement::If { on_success, .. }, + CFSelectorSegment::IndexInSuccess(index), + ) => on_success.get(index), + ( + ControlFlowElement::If { on_failure, .. }, + CFSelectorSegment::IndexInFailure(index), + ) => on_failure.get(index), + ( + _, + CFSelectorSegment::IfCondition + | CFSelectorSegment::IndexInFailure(_) + | CFSelectorSegment::IndexInSuccess(_), + ) => unreachable!(), + (ControlFlowElement::If { .. }, CFSelectorSegment::ContentAtIndex(_)) => unreachable!(), + (ControlFlowElement::BasicBlock { .. }, _) => unreachable!(), + } + } + fn unwrap_node(&self) -> usize { + if let Self::BasicBlock { id: node_id } = self { + *node_id + } else { + unreachable!() + } + } + fn exists(&self, element: &CFSelector) -> bool { + if let Some((first, rest)) = element.clone().split_first() { + let subcontent = match (self, first) { + ( + ControlFlowElement::Block { content } | ControlFlowElement::Loop { content }, + CFSelectorSegment::ContentAtIndex(i), + ) => content.get(i), + (ControlFlowElement::BasicBlock { .. }, _) => return false, + (ControlFlowElement::If { .. }, CFSelectorSegment::IfCondition) => { + return rest.is_empty() + } + ( + ControlFlowElement::If { on_success, .. }, + CFSelectorSegment::IndexInSuccess(i), + ) => on_success.get(i), + ( + ControlFlowElement::If { on_failure, .. }, + CFSelectorSegment::IndexInFailure(i), + ) => on_failure.get(i), + _ => unreachable!(), + }; + if let Some(subcontent) = subcontent { + subcontent.exists(&rest) + } else { + false + } + } else { + true + } + } + pub fn next_element_sibling(&self, element: &CFSelector) -> Option { + let mut result = element.clone(); + let back = result.pop_back().unwrap(); + let back = match back { + CFSelectorSegment::ContentAtIndex(i) => CFSelectorSegment::ContentAtIndex(i + 1), + CFSelectorSegment::IndexInSuccess(i) => CFSelectorSegment::IndexInSuccess(i + 1), + CFSelectorSegment::IndexInFailure(i) => CFSelectorSegment::IndexInFailure(i + 1), + CFSelectorSegment::IfCondition => return None, //maybe: return self.next_element_sibling(&element.parent()?), + }; + result.push_back(back); + if self.exists(&result) { + Some(result) + } else { + None + } + } + pub fn find_node(&self, node_id: usize) -> Option { + match self { + ControlFlowElement::BasicBlock { id: self_node_id } if *self_node_id == node_id => { + Some(CFSelector::new_empty()) + } + ControlFlowElement::BasicBlock { .. } => None, + ControlFlowElement::Block { content } | ControlFlowElement::Loop { content } => { + for (i, c) in content.iter().enumerate() { + if let Some(mut subresult) = c.find_node(node_id) { + subresult.push_front(CFSelectorSegment::ContentAtIndex(i)); + return Some(subresult); + } + } + None + } + ControlFlowElement::If { + condition, + on_success, + on_failure, + } => { + if let Some(mut subresult) = condition.find_node(node_id) { + subresult.push_front(CFSelectorSegment::IfCondition); + return Some(subresult); + } + for (i, c) in on_success.iter().enumerate() { + if let Some(mut subresult) = c.find_node(node_id) { + subresult.push_front(CFSelectorSegment::IndexInSuccess(i)); + return Some(subresult); + } + } + for (i, c) in on_failure.iter().enumerate() { + if let Some(mut subresult) = c.find_node(node_id) { + subresult.push_front(CFSelectorSegment::IndexInFailure(i)); + return Some(subresult); + } + } + None + } + } + } + pub fn replace(&mut self, selector: &CFSelector, to_element: ControlFlowElement) { + assert!(!selector.is_empty()); + let (parent_selector, last_segment) = selector.clone().split_last().unwrap(); + let parent = &mut self[&parent_selector]; + match (parent, last_segment) { + ( + ControlFlowElement::Block { content } | ControlFlowElement::Loop { content }, + CFSelectorSegment::ContentAtIndex(index), + ) => { + content[index] = to_element; + } + (ControlFlowElement::If { condition, .. }, CFSelectorSegment::IfCondition) => { + *condition = Box::new(to_element); + } + ( + ControlFlowElement::If { on_success, .. }, + CFSelectorSegment::IndexInSuccess(index), + ) => on_success[index] = to_element, + ( + ControlFlowElement::If { on_failure, .. }, + CFSelectorSegment::IndexInFailure(index), + ) => on_failure[index] = to_element, + (ControlFlowElement::If { .. }, CFSelectorSegment::ContentAtIndex(_)) => unreachable!(), + (ControlFlowElement::Block { .. }, _) => unreachable!(), + (ControlFlowElement::Loop { .. }, _) => unreachable!(), + (ControlFlowElement::BasicBlock { .. }, _) => unreachable!(), + } + } + pub fn remove(&mut self, element: &CFSelector) -> ControlFlowElement { + assert!(!element.is_empty()); + let (parent_selector, last_segment) = element.clone().split_last().unwrap(); + let parent = &mut self[&parent_selector]; + return match (parent, last_segment) { + (ControlFlowElement::Block { content }, CFSelectorSegment::ContentAtIndex(i)) => { + content.remove(i) + } + (ControlFlowElement::If { on_success, .. }, CFSelectorSegment::IndexInSuccess(i)) => { + on_success.remove(i) + } + (ControlFlowElement::If { on_failure, .. }, CFSelectorSegment::IndexInFailure(i)) => { + on_failure.remove(i) + } + (ControlFlowElement::Loop { content }, CFSelectorSegment::ContentAtIndex(i)) => { + content.remove(i) + } + (_, CFSelectorSegment::IfCondition) => panic!("You should not remove the condition of an if statement, maybe try remove the whole if statement instead?"), + (ControlFlowElement::Block { .. }, _) => unreachable!(), + (ControlFlowElement::If { .. }, _) => unreachable!(), + (ControlFlowElement::Loop { .. }, _) => unreachable!(), + (ControlFlowElement::BasicBlock { .. }, _) => unreachable!(), + }; + } + pub fn get(&self, index: &CFSelector) -> Option<&ControlFlowElement> { + if index.is_empty() { + Some(self) + } else { + let (first, rest) = index.clone().split_first()?; + let first_result = self.select_by_segment(first); + first_result?.get(&rest) + } + } +} + +impl IndexMut<&CFSelector> for ControlFlowElement { + fn index_mut(&mut self, index: &CFSelector) -> &mut Self::Output { + if index.is_empty() { + self + } else { + let (first, rest) = index.clone().split_first().unwrap(); + let first_result = self.select_mut_by_segment(first); + first_result.index_mut(&rest) + } + } +} + +impl Index<&CFSelector> for ControlFlowElement { + type Output = ControlFlowElement; + + fn index(&self, index: &CFSelector) -> &Self::Output { + self.get(index).unwrap() + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use itertools::Itertools; + + use super::*; + + #[test] + fn test_find_node() { + let current_result = (0..5) + .map(|it| ControlFlowElement::BasicBlock { id: it }) + .collect_vec(); + + let content = ControlFlowElement::Block { + content: current_result, + }; + let selector = content.find_node(0); + assert_eq!(selector.unwrap(), CFSelector::from_str("0").unwrap()); + let selector = content.find_node(1); + assert_eq!(selector.unwrap(), CFSelector::from_str("1").unwrap()); + let content = ControlFlowElement::Block { + content: vec![ + ControlFlowElement::BasicBlock { id: 0 }, + ControlFlowElement::If { + condition: Box::new(ControlFlowElement::BasicBlock { id: 1 }), + on_success: Vec::new(), + on_failure: Vec::new(), + }, + ControlFlowElement::BasicBlock { id: 2 }, + ], + }; + let selector = content.find_node(1); + assert_eq!( + selector.unwrap(), + CFSelector::from_str("1/if_condition").unwrap() + ); + + let content = ControlFlowElement::Block { + content: vec![ + ControlFlowElement::BasicBlock { id: 0 }, + ControlFlowElement::If { + condition: Box::new(ControlFlowElement::BasicBlock { id: 1 }), + on_success: vec![ControlFlowElement::BasicBlock { id: 2 }], + on_failure: Vec::new(), + }, + ControlFlowElement::BasicBlock { id: 3 }, + ], + }; + let selector = content.find_node(1); + assert_eq!( + selector.unwrap(), + CFSelector::from_str("1/if_condition").unwrap() + ); + let selector = content.find_node(2); + assert_eq!( + selector.unwrap(), + CFSelector::from_str("1/success/0").unwrap() + ); + let selector = content.find_node(3); + assert_eq!(selector.unwrap(), CFSelector::from_str("2").unwrap()); + } +} diff --git a/src/backend/wasm/control_flow/selector.rs b/src/backend/wasm/control_flow/selector.rs new file mode 100644 index 0000000..4346a65 --- /dev/null +++ b/src/backend/wasm/control_flow/selector.rs @@ -0,0 +1,195 @@ +use delegate::delegate; +use std::{ + collections::VecDeque, fmt, iter::zip, num::ParseIntError, ops::RangeBounds, result, + str::FromStr, +}; + +#[derive(Clone, PartialEq, Eq)] +pub enum CFSelectorSegment { + ContentAtIndex(usize), + IfCondition, + IndexInSuccess(usize), + IndexInFailure(usize), +} + +impl fmt::Display for CFSelectorSegment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CFSelectorSegment::ContentAtIndex(index) => write!(f, "{}", index), + CFSelectorSegment::IfCondition => write!(f, "if_condition"), + CFSelectorSegment::IndexInSuccess(index) => write!(f, "success/{}", index), + CFSelectorSegment::IndexInFailure(index) => write!(f, "failure/{}", index), + } + } +} + +impl fmt::Debug for CFSelectorSegment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl FromStr for CFSelectorSegment { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + if s == "if_condition" { + Ok(CFSelectorSegment::IfCondition) + } else if s.starts_with("success/") { + let index_str = s.strip_prefix("success/").unwrap(); + let value = index_str.parse()?; + Ok(CFSelectorSegment::IndexInSuccess(value)) + } else if s.starts_with("failure/") { + let index_str = s.strip_prefix("failure/").unwrap(); + let value = index_str.parse()?; + Ok(CFSelectorSegment::IndexInFailure(value)) + } else { + let value = s.parse()?; + Ok(CFSelectorSegment::ContentAtIndex(value)) + } + } +} + +#[derive(Clone, Default, PartialEq, Eq)] +pub struct CFSelector(VecDeque); + +impl fmt::Display for CFSelector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0.is_empty() { + write!(f, "()") + } else { + write!( + f, + "{}", + self.0 + .iter() + .map(|s| format!("{}", s)) + .collect::>() + .join("/") + ) + } + } +} + +impl fmt::Debug for CFSelector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl FromStr for CFSelector { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + let mut result = VecDeque::new(); + let mut parts = s.split("/"); + while let Some(next_part) = parts.next() { + if next_part.starts_with("success") || next_part.starts_with("failure") { + let next_next_part = parts.next().unwrap(); + let segment = CFSelectorSegment::from_str(&[next_part, next_next_part].join("/"))?; + result.push_back(segment); + } else { + let segment = CFSelectorSegment::from_str(next_part)?; + result.push_back(segment); + } + } + Ok(Self(result)) + } +} + +impl CFSelector { + pub(super) fn new_empty() -> Self { + Self(VecDeque::new()) + } + + pub(super) fn from_segment(segment: CFSelectorSegment) -> Self { + let mut result = VecDeque::new(); + result.push_back(segment); + Self(result) + } + + delegate! { + to self.0 { + pub fn is_empty(&self) -> bool; + pub fn len(&self) -> usize; + pub fn pop_front(&mut self) -> Option; + pub fn pop_back(&mut self) -> Option; + pub fn front(&self) -> Option<&CFSelectorSegment>; + pub fn back(&self) -> Option<&CFSelectorSegment>; + pub fn push_front(&mut self, segment: CFSelectorSegment); + pub fn push_back(&mut self, segment: CFSelectorSegment); + } + } + + pub fn parent(&self) -> Option { + let mut result = self.0.clone(); + if result.is_empty() { + None + } else { + result.pop_back(); + Some(CFSelector(result)) + } + } + + pub(super) fn split_first(mut self) -> Option<(CFSelectorSegment, CFSelector)> { + let front = self.0.pop_front()?; + Some((front, self)) + } + + pub(super) fn split_last(mut self) -> Option<(CFSelector, CFSelectorSegment)> { + let back = self.0.pop_back()?; + Some((self, back)) + } + + fn is_ancestor_of(&self, other: &CFSelector) -> bool { + for (from_self, from_other) in zip(&self.0, &other.0) { + if from_self != from_other { + return false; + } + } + true + } + + pub fn is_parent_of(&self, other: &CFSelector) -> bool { + // different from direct parent relationship, this function consider, for example, `0`.is_parent_of(`0/if_condition/0`) + let lca = self.lowest_common_ancestor(other); + let parent_rest = self.range(lca.len()..); + let mut child_rest = other.range(lca.len()..); + if !parent_rest.is_empty() || child_rest.is_empty() { + return false; + } + while child_rest.len() > 1 { + let front = child_rest.pop_front().unwrap(); + if front == CFSelectorSegment::IfCondition { + return false; + } + } + true + } + + pub(super) fn merge(&self, other: &CFSelector) -> CFSelector { + let mut result = self.clone(); + result.0.extend(other.0.clone()); + result + } + + pub fn is_if_condition(&self) -> bool { + matches!(self.back(), Some(CFSelectorSegment::IfCondition)) + } + + pub fn lowest_common_ancestor(&self, other: &CFSelector) -> CFSelector { + let mut result = Self::new_empty(); + for (from_self, from_other) in zip(&self.0, &other.0) { + if from_self == from_other { + result.0.push_back(from_self.clone()); + } else { + break; + } + } + result + } + + pub fn range>(&self, range: R) -> Self { + Self(self.0.range(range).cloned().collect()) + } +} diff --git a/src/backend/wasm/mod.rs b/src/backend/wasm/mod.rs index 3728a50..5af593e 100644 --- a/src/backend/wasm/mod.rs +++ b/src/backend/wasm/mod.rs @@ -1,535 +1,399 @@ -use itertools::Itertools; +use itertools::{Either, Itertools}; +use std::{ + collections::VecDeque, + fmt, + iter::zip, + ops::{Index, IndexMut}, + path::Display, +}; use crate::ir::{ - analyzer::{self, BindedControlFlowGraph, SccContent}, + analyzer::{BindedControlFlowGraph, BindedScc, ControlFlowGraph, IsAnalyzer}, statement::IRStatement, + FunctionDefinition, }; -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ControlFlowContent { - Block(Vec), - If(Vec, Vec), - Loop(Vec), - Node(usize), -} - -impl ControlFlowContent { - pub fn new_block(content: Vec) -> Self { - Self::Block(content) - } - - pub fn new_if(taken: Vec, untaken: Vec) -> Self { - Self::If(taken, untaken) - } - - pub fn new_loop(content: Vec) -> Self { - Self::Loop(content) - } - - pub fn new_node(node: usize) -> Self { - Self::Node(node) - } - - pub fn first_node(&self) -> usize { - match self { - ControlFlowContent::Block(content) | ControlFlowContent::Loop(content) => { - content.first().unwrap().first_node() - } - ControlFlowContent::If(taken, _untaken) => taken.first().unwrap().first_node(), - ControlFlowContent::Node(n) => *n, - } - } +use self::control_flow::{CFSelector, ControlFlowElement}; - pub fn get + Clone>(&self, index: &[T]) -> Option<&Self> { - let current_index = index.get(0)?.clone(); - let current_index = current_index.into(); - let current = match self { - ControlFlowContent::Block(content) | ControlFlowContent::Loop(content) => { - content.get(current_index) - } - ControlFlowContent::If(taken, untaken) => taken - .get(current_index) - .or_else(|| untaken.get(current_index - taken.len())), - ControlFlowContent::Node(_) => { - if current_index == 0 { - Some(self) - } else { - return None; - } - } - }; - let rest_index = &index[1..]; - if rest_index.is_empty() { - current - } else { - current?.get(rest_index) - } - } +mod control_flow; - pub fn get_mut + Clone>(&mut self, index: &[T]) -> Option<&mut Self> { - let current_index = index.get(0)?.clone(); - let current_index = current_index.into(); - let current = match self { - ControlFlowContent::Block(content) | ControlFlowContent::Loop(content) => { - content.get_mut(current_index) - } - ControlFlowContent::If(taken, untaken) => { - if current_index < taken.len() { - taken.get_mut(current_index) - } else { - untaken.get_mut(current_index - taken.len()) - } - } - ControlFlowContent::Node(_) => { - if current_index == 0 { - Some(self) - } else { - return None; - } - } - }; - let rest_index = &index[1..]; - if rest_index.is_empty() { - current - } else { - current?.get_mut(rest_index) - } - } - - pub fn contains(&self, node: usize) -> bool { - match self { - ControlFlowContent::Block(content) | ControlFlowContent::Loop(content) => { - content.iter().any(|it| it.contains(node)) - } - ControlFlowContent::If(taken, untaken) => taken +// fixme: currently this presumes that we have not folded any if-else or block before +fn fold_loop( + function_content: &FunctionDefinition, + scc: &BindedScc, + current_result: &mut Vec, +) { + if let Some(sub_sccs) = scc.top_level_sccs() { + for sub_scc in sub_sccs + .into_iter() + .filter(|sub_scc: &BindedScc<'_>| !sub_scc.is_trivial()) + { + let sub_scc_start_index = current_result .iter() - .chain(untaken.iter()) - .any(|it| it.contains(node)), - ControlFlowContent::Node(n) => *n == node, - } - } - - pub fn remove + Clone>(&mut self, index: &[T]) -> Option { - if index.is_empty() { - None - } else if index.len() == 1 { - let index = index[0].clone().into(); - match self { - ControlFlowContent::Block(content) | ControlFlowContent::Loop(content) => { - Some(content.remove(index)) - } - ControlFlowContent::If(taken, untaken) => { - if index < taken.len() { - Some(taken.remove(index)) + .position(|it| { + if let &ControlFlowElement::BasicBlock { id: block_id } = it { + sub_scc.contains(block_id) } else { - Some(untaken.remove(index)) - } - } - ControlFlowContent::Node(n) => { - panic!("unable to remove the {index}th element from node {n}") - } - } - } else { - self.get_mut(&[index[0].clone()]) - .unwrap() - .remove(&index[1..]) - } - } - - pub fn position(&self, item: &Self) -> Option> { - match self { - ControlFlowContent::Block(content) | ControlFlowContent::Loop(content) => { - for (i, subblock) in content.iter().enumerate() { - let mut potential_result = vec![i]; - if subblock == item { - return Some(potential_result); - } else if let Some(result) = subblock.position(item) { - potential_result.extend_from_slice(&result); - return Some(potential_result); + false } - } - } - ControlFlowContent::If(taken, untaken) => { - for (i, subblock) in taken.iter().chain(untaken.iter()).enumerate() { - let mut potential_result = vec![i]; - if subblock == item { - return Some(potential_result); - } else if let Some(result) = subblock.position(item) { - potential_result.extend_from_slice(&result); - return Some(potential_result); + }) + .unwrap(); + let sub_scc_end_index = current_result + .iter() + .rposition(|it| { + if let &ControlFlowElement::BasicBlock { id: block_id } = it { + sub_scc.contains(block_id) + } else { + false } - } - } - ControlFlowContent::Node(_) => { - if self == item { - return Some(vec![0]); - } - } - } - None - } - - pub fn nodes(&self) -> ControlFlowNodesIter { - ControlFlowNodesIter::new(self) - } -} - -pub struct ControlFlowNodesIter<'a> { - bind_on: &'a ControlFlowContent, - pub current_index: Vec, -} - -impl<'a> ControlFlowNodesIter<'a> { - pub fn new(bind_on: &'a ControlFlowContent) -> Self { - Self { - bind_on, - current_index: vec![0], - } - } - - pub fn from_index(bind_on: &'a ControlFlowContent, index: &[usize]) -> Self { - Self { - bind_on, - current_index: index.to_vec(), + }) + .unwrap(); + let mut new_result = current_result[sub_scc_start_index..=sub_scc_end_index] + .iter() + .cloned() + .collect_vec(); + fold_loop(function_content, &sub_scc, &mut new_result); + current_result.splice( + sub_scc_start_index..=sub_scc_end_index, + [ControlFlowElement::Loop { + content: new_result.into_iter().collect(), + }], + ); } } } -impl Iterator for ControlFlowNodesIter<'_> { - type Item = (Vec, usize); - - fn next(&mut self) -> Option { - match self.bind_on.get(&self.current_index) { - Some(ControlFlowContent::Block(_)) - | Some(ControlFlowContent::Loop(_)) - | Some(ControlFlowContent::If(_, _)) => { - self.current_index.push(0); - self.next() - } - Some(ControlFlowContent::Node(n)) => { - let result = self.current_index.clone(); - *self.current_index.last_mut().unwrap() += 1; - Some((result, *n)) +fn fold_if_else_once( + content: &mut ControlFlowElement, + control_flow_graph: BindedControlFlowGraph, +) -> bool { + for block_id in 0..control_flow_graph.bind_on.content.len() { + let predecessors = control_flow_graph.predecessor(block_id); + if predecessors.len() == 1 { + let predecessor_block_id = predecessors[0]; + let predecessor_last_instruction = control_flow_graph.bind_on[predecessor_block_id] + .content + .last(); + if !matches!(predecessor_last_instruction, Some(IRStatement::Branch(_))) { + continue; } - None => { - if self.current_index.len() != 1 { - self.current_index.pop(); - *self.current_index.last_mut().unwrap() += 1; - self.next() + let predecessor_selector = content.find_node(predecessor_block_id).unwrap(); + let block_selector = content.find_node(block_id).unwrap(); + let block_element = &content[&block_selector]; + let block_element_id = block_element.first_basic_block_id(); + let if_element_selector = if predecessor_selector.is_if_condition() { + // `predecessor_element` is already an if condition + // in such cases, it's possible that: + // - the block is already folded into either of branch the `predecessor_element` is in + // in such case we don't need to do anything + // - the block is not folded into any of branch the `predecessor_element` is in + // in such case we just fold the block into the `if` element which `predecessor_element` is in + let mut if_element_predecessor_in_selector = predecessor_selector.clone(); + if_element_predecessor_in_selector.pop_back(); + if if_element_predecessor_in_selector.is_parent_of(&block_selector) { + // already folded + continue; + } + if_element_predecessor_in_selector + } else { + // need to promote `predecessor_element` into an if element's condition + content.replace( + &predecessor_selector, + ControlFlowElement::If { + condition: Box::new(ControlFlowElement::BasicBlock { + id: predecessor_block_id, + }), + on_success: Vec::new(), + on_failure: Vec::new(), + }, + ); + predecessor_selector.clone() + }; + let to_move_selectors = collect_to_move(content, &block_selector, &control_flow_graph); + let to_move_items = to_move_selectors + .iter() + .map(|it| content[it].clone()) + .collect_vec(); + let predecessor_element = &mut content[&if_element_selector]; + let predecessor_node_id = predecessor_element.first_basic_block_id(); + let move_to = if control_flow_graph.branch_direction(predecessor_node_id, block_id) { + if let ControlFlowElement::If { on_success, .. } = predecessor_element { + on_success + } else { + unreachable!() + } + } else { + if let ControlFlowElement::If { on_failure, .. } = predecessor_element { + on_failure } else { - None + unreachable!() } + }; + move_to.extend(to_move_items); + for to_move_selector in to_move_selectors.iter().rev() { + content.remove(to_move_selector); } + return true; } } + false } -fn fold_loop(current: &mut ControlFlowContent, loop_item: &analyzer::Scc) { - let (to_remove_indexes, to_remove_items): (Vec<_>, Vec<_>) = current - .nodes() - .filter(|(_, n)| loop_item.is_node_in((*n).into())) - .unzip(); - for to_remove_index in to_remove_indexes[1..].iter().rev() { - current.remove(to_remove_index); - } - let first = current.get_mut(&to_remove_indexes[0]).unwrap(); - let new_loop_item = ControlFlowContent::Loop( - to_remove_items - .into_iter() - .map(ControlFlowContent::Node) - .collect(), - ); - *first = new_loop_item; - for content in &loop_item.content { - if let SccContent::SubScc(subloop) = content { - fold_loop(first, subloop); +fn fold_if_else(fd: &FunctionDefinition, content: &mut ControlFlowElement) { + loop { + let cfg = ControlFlowGraph::new(); + let control_flow_graph = cfg.bind(fd); + if fold_if_else_once(content, control_flow_graph) { + break; } } } -fn fold_if_else_once(current: &mut ControlFlowContent, graph: &BindedControlFlowGraph) -> bool { - // A node is foldable if its only successor is an branch block - // So for each branch block: - // - if the "next" block has only one successor, nest it and nodes dominated by it in an if - // - if (the "next" block after the new nested if)'s only successor is also the block, nest it in the else part - let (node_indexes, nodes): (Vec<_>, Vec<_>) = current.nodes().unzip(); - let mut considering_node_index = 0; - let mut folded = false; - while considering_node_index < nodes.len() - 1 { - let node = nodes[considering_node_index]; - let mut next_node_index = node_indexes[considering_node_index].clone(); - *next_node_index.last_mut().unwrap() += 1; - if matches!( - current.get(&next_node_index), - Some(ControlFlowContent::If(_, _)) - ) { - // already nested, just consider next - considering_node_index += 1; - continue; - } - let block = &graph.bind_on.content[node]; - let last_statement = block.content.last().unwrap(); - if let IRStatement::Branch(_) = last_statement { - let next_node = nodes[considering_node_index + 1]; - if graph.not_dominate_successors(next_node).len() == 1 { - let nodes_dominated_by_next_node = graph.dominates(next_node); - let mut to_nest = Vec::new(); - let mut next_to_nest_index = node_indexes[considering_node_index + 1].clone(); - // the next node is deep nested in loops, so we need to fold all structure the node is in - while next_to_nest_index.len() > node_indexes[considering_node_index].len() { - next_to_nest_index.pop(); - } - while let Some(next_to_nest) = current.get(&next_to_nest_index) && nodes_dominated_by_next_node.contains(&next_to_nest.first_node()) { - to_nest.push(next_to_nest_index.clone()); - *next_to_nest_index.last_mut().unwrap() += 1; - } - let initial_considering_node_index = considering_node_index; - considering_node_index += to_nest - .iter() - .map(|it| current.get(it).unwrap().nodes().count()) - .sum::(); - let (to_replace, to_remove) = to_nest.split_first().unwrap(); - let removed = to_remove - .iter() - .map(|it| current.remove(it).unwrap()) - .collect_vec(); - // nest else part - let node_after_nest = nodes[considering_node_index]; - let node_after_nest_successors = graph.not_dominate_successors(node_after_nest); - let untaken_content = if node_after_nest_successors.len() == 1 { - let nodes_dominated_by_node_after_nest = graph.dominates(node_after_nest); - let mut to_nest = Vec::new(); - let mut next_to_nest_index = node_indexes[considering_node_index].clone(); - // the next node is deep nested in loops, so we need to fold all structure the node is in - while next_to_nest_index.len() - > node_indexes[initial_considering_node_index].len() - { - next_to_nest_index.pop(); - } - while let Some(next_to_nest) = current.get(&next_to_nest_index) && nodes_dominated_by_node_after_nest.contains(&next_to_nest.first_node()) { - to_nest.push(next_to_nest_index.clone()); - *next_to_nest_index.last_mut().unwrap() += 1; - } - considering_node_index += to_nest.len(); - to_nest - .iter() - .map(|it| current.remove(it).unwrap()) - .collect_vec() - } else { - Vec::new() - }; - let replaced_node = current.get_mut(to_replace).unwrap(); - let mut taken_content = vec![replaced_node.clone()]; - taken_content.extend_from_slice(&removed); - let new_if_node = ControlFlowContent::If(taken_content, untaken_content); - *replaced_node = new_if_node; - folded = true; - } else { - considering_node_index += 1; - } +fn collect_to_move( + root_element: &ControlFlowElement, + first_to_move_node_selector: &CFSelector, + control_flow_graph: &BindedControlFlowGraph<'_, '_>, +) -> Vec { + let first_to_move_element = &root_element[first_to_move_node_selector]; + let first_to_move_element_first_bb_id = first_to_move_element.first_basic_block_id(); + let move_to_if_condition_bb_id = + control_flow_graph.predecessor(first_to_move_element_first_bb_id)[0]; + let mut to_move = vec![first_to_move_node_selector.clone()]; + let mut next = root_element.next_element_sibling(&first_to_move_node_selector); + while let Some(current_element_selector) = next { + let current_node_id = root_element[¤t_element_selector].first_basic_block_id(); + if control_flow_graph.is_dominated_by(current_node_id, first_to_move_element_first_bb_id) + && control_flow_graph.is_in_same_branch_side( + move_to_if_condition_bb_id, + first_to_move_element_first_bb_id, + current_node_id, + ) + { + to_move.push(current_element_selector.clone()); } else { - considering_node_index += 1; + break; } + next = root_element.next_element_sibling(¤t_element_selector); } - folded + to_move } -fn fold_if_else(current: &mut ControlFlowContent, graph: &BindedControlFlowGraph) { - let mut folded = true; - while folded { - folded = fold_if_else_once(current, graph); - } -} #[cfg(test)] mod tests { - use super::*; + use std::{assert_matches::assert_matches, str::FromStr}; + + use analyzer::Analyzer; + use crate::{ ir::{ self, - analyzer::{ControlFlowGraph, IsAnalyzer}, + analyzer::{self, IsAnalyzer}, + editor::Editor, function::{basic_block::BasicBlock, test_util::*}, + optimize::pass::{FixIrreducible, IsPass, TopologicalSort}, statement::Ret, - FunctionDefinition, }, utility::data_type, }; - #[test] - fn control_flow_content_get() { - let content = ControlFlowContent::new_block(vec![ - ControlFlowContent::new_node(0), - ControlFlowContent::new_if( - vec![ - ControlFlowContent::new_node(1), - ControlFlowContent::new_node(2), - ], - vec![ControlFlowContent::new_loop(vec![ - ControlFlowContent::new_node(3), - ControlFlowContent::new_node(4), - ])], - ), - ]); - assert_eq!( - content.get(&[0usize]), - Some(&ControlFlowContent::new_node(0)) - ); - assert_eq!( - content.get(&[1usize, 0]), - Some(&ControlFlowContent::new_node(1)) - ); - assert_eq!( - content.get(&[1usize, 2]), - Some(&ControlFlowContent::new_loop(vec![ - ControlFlowContent::new_node(3), - ControlFlowContent::new_node(4), - ])) - ); - assert_eq!( - content.get(&[1usize, 2, 0]), - Some(&ControlFlowContent::new_node(3)) - ); - assert_eq!(content.get(&[2usize, 0, 2]), None); - assert_eq!(content.get(&[3usize]), None); - } + + use super::*; #[test] - fn control_flow_content_position() { - let content = ControlFlowContent::new_block(vec![ - ControlFlowContent::new_node(0), - ControlFlowContent::new_if( - vec![ - ControlFlowContent::new_node(1), - ControlFlowContent::new_node(2), - ], - vec![ControlFlowContent::new_loop(vec![ - ControlFlowContent::new_node(3), - ControlFlowContent::new_node(4), - ])], - ), - ]); - assert_eq!( - content.position(&ControlFlowContent::new_node(0)), - Some(vec![0]) + fn test_fold_if_else_once() { + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + jump_block(0, 1), + branch_block(1, 2, 3), + jump_block(2, 4), + jump_block(3, 4), + ret_block(4), + ], + }; + let mut editor = Editor::new(function_definition); + let pass = FixIrreducible; + pass.run(&mut editor); + let pass = TopologicalSort; + pass.run(&mut editor); + let function_definition = editor.content; + + let analyzer = Analyzer::new(); + let binded = analyzer.bind(&function_definition); + let control_flow_graph = binded.control_flow_graph(); + let current_result = (0..(function_definition.content.len())) + .map(ControlFlowElement::new_node) + .collect_vec(); + + let mut content = ControlFlowElement::new_block(current_result); + fold_if_else_once(&mut content, control_flow_graph); + assert_matches!( + content[&CFSelector::from_str("1").unwrap()], + ControlFlowElement::If { .. } + ); + assert_matches!( + content[&CFSelector::from_str("1/success/0").unwrap()], + ControlFlowElement::BasicBlock { id: 2 } ); assert_eq!( - content.position(&ControlFlowContent::new_node(1)), - Some(vec![1, 0]) + content.get(&CFSelector::from_str("1/failure/0").unwrap()), + None ); + let control_flow_graph = binded.control_flow_graph(); + fold_if_else_once(&mut content, control_flow_graph); assert_eq!( - content.position(&ControlFlowContent::new_loop(vec![ - ControlFlowContent::new_node(3), - ControlFlowContent::new_node(4), - ])), - Some(vec![1, 2]) + content[&CFSelector::from_str("1/failure/0").unwrap()], + ControlFlowElement::BasicBlock { id: 3 } ); assert_eq!( - content.position(&ControlFlowContent::new_node(3)), - Some(vec![1, 2, 0]) + content[&CFSelector::from_str("2").unwrap()], + ControlFlowElement::BasicBlock { id: 4 } ); - assert_eq!(content.position(&ControlFlowContent::new_node(5)), None); - } - - #[test] - fn control_flow_nodes() { - let content = ControlFlowContent::new_block(vec![ - ControlFlowContent::new_node(0), - ControlFlowContent::new_if( - vec![ - ControlFlowContent::new_node(1), - ControlFlowContent::new_node(2), - ], - vec![ControlFlowContent::new_loop(vec![ - ControlFlowContent::new_node(3), - ControlFlowContent::new_node(4), - ])], - ), - ControlFlowContent::new_node(5), - ]); - let mut iter = content.nodes(); - assert_eq!(iter.next(), Some((vec![0], 0))); - assert_eq!(iter.next(), Some((vec![1, 0], 1))); - assert_eq!(iter.next(), Some((vec![1, 1], 2))); - assert_eq!(iter.next(), Some((vec![1, 2, 0], 3))); - assert_eq!(iter.next(), Some((vec![1, 2, 1], 4))); - assert_eq!(iter.next(), Some((vec![2], 5))); - assert_eq!(iter.next(), None); - } - #[test] - fn test_fold_loop() { - let mut content = ControlFlowContent::new_block(vec![ - ControlFlowContent::new_node(0), - ControlFlowContent::new_node(1), - ControlFlowContent::new_node(2), - ControlFlowContent::new_node(3), - ControlFlowContent::new_node(4), - ]); - let loop_item = analyzer::Scc { - entries: vec![1], + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, content: vec![ - analyzer::SccContent::Node(1), - analyzer::SccContent::Node(2), - analyzer::SccContent::Node(3), + jump_block(0, 1), + branch_block(1, 2, 4), + jump_block(2, 3), + jump_block(4, 5), + jump_block(3, 6), + jump_block(5, 6), + jump_block(6, 7), + ret_block(7), ], }; - fold_loop(&mut content, &loop_item); + let mut editor = Editor::new(function_definition); + let pass = FixIrreducible; + pass.run(&mut editor); + let pass = TopologicalSort; + pass.run(&mut editor); + let function_definition = editor.content; + + let analyzer = Analyzer::new(); + let binded = analyzer.bind(&function_definition); + let control_flow_graph = binded.control_flow_graph(); + let current_result = (0..(function_definition.content.len())) + .map(ControlFlowElement::new_node) + .collect_vec(); + + let mut content = ControlFlowElement::new_block(current_result); + fold_if_else_once(&mut content, control_flow_graph); + assert_matches!( + content[&CFSelector::from_str("1").unwrap()], + ControlFlowElement::If { .. } + ); + assert_matches!( + content[&CFSelector::from_str("1/success/0").unwrap()], + ControlFlowElement::BasicBlock { id: 2 } + ); + assert_matches!( + content[&CFSelector::from_str("1/success/1").unwrap()], + ControlFlowElement::BasicBlock { id: 3 } + ); assert_eq!( - content, - ControlFlowContent::new_block(vec![ - ControlFlowContent::new_node(0), - ControlFlowContent::new_loop(vec![ - ControlFlowContent::new_node(1), - ControlFlowContent::new_node(2), - ControlFlowContent::new_node(3), - ]), - ControlFlowContent::new_node(4), - ]) + content.get(&CFSelector::from_str("1/failure/0").unwrap()), + None + ); + let control_flow_graph = binded.control_flow_graph(); + fold_if_else_once(&mut content, control_flow_graph); + assert_matches!( + content[&CFSelector::from_str("1/failure/0").unwrap()], + ControlFlowElement::BasicBlock { id: 4 } + ); + assert_matches!( + content[&CFSelector::from_str("1/failure/1").unwrap()], + ControlFlowElement::BasicBlock { id: 5 } ); - let mut content = ControlFlowContent::new_block(vec![ - ControlFlowContent::new_node(0), - ControlFlowContent::new_node(1), - ControlFlowContent::new_node(2), - ControlFlowContent::new_node(3), - ControlFlowContent::new_node(4), - ControlFlowContent::new_node(5), - ControlFlowContent::new_node(6), - ]); - let loop_item = analyzer::Scc { - entries: vec![1], + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, content: vec![ - analyzer::SccContent::Node(1), - analyzer::SccContent::Node(2), - analyzer::SccContent::SubScc(Box::new(analyzer::Scc { - entries: vec![3], - content: vec![ - analyzer::SccContent::Node(3), - analyzer::SccContent::Node(4), - analyzer::SccContent::Node(5), - ], - })), + branch_block(0, 1, 5), + jump_block(1, 2), + jump_block(2, 4), + branch_block(4, 3, 7), + jump_block(3, 2), + ret_block(7), + jump_block(5, 6), + jump_block(6, 7), ], }; - fold_loop(&mut content, &loop_item); - assert_eq!( - content, - ControlFlowContent::new_block(vec![ - ControlFlowContent::new_node(0), - ControlFlowContent::new_loop(vec![ - ControlFlowContent::new_node(1), - ControlFlowContent::new_node(2), - ControlFlowContent::new_loop(vec![ - ControlFlowContent::new_node(3), - ControlFlowContent::new_node(4), - ControlFlowContent::new_node(5), - ]), - ]), - ControlFlowContent::new_node(6), - ]) + let mut editor = Editor::new(function_definition); + let pass = FixIrreducible; + pass.run(&mut editor); + let pass = TopologicalSort; + pass.run(&mut editor); + let function_definition = editor.content; + let analyzer = Analyzer::new(); + let binded = analyzer.bind(&function_definition); + let control_flow_graph = binded.control_flow_graph(); + let mut content = ControlFlowElement::new_block(vec![ + ControlFlowElement::new_node(0), + ControlFlowElement::new_node(1), + ControlFlowElement::Loop { + content: vec![ + ControlFlowElement::new_node(2), + ControlFlowElement::new_node(4), + ControlFlowElement::new_node(3), + ], + }, + ControlFlowElement::new_node(5), + ControlFlowElement::new_node(6), + ControlFlowElement::new_node(7), + ]); + fold_if_else_once(&mut content, control_flow_graph); + let control_flow_graph = binded.control_flow_graph(); + fold_if_else_once(&mut content, control_flow_graph); + let control_flow_graph = binded.control_flow_graph(); + fold_if_else_once(&mut content, control_flow_graph); + assert_matches!( + &content[&CFSelector::from_str("0").unwrap()], + ControlFlowElement::If { .. } + ); + assert_matches!( + &content[&CFSelector::from_str("0/if_condition").unwrap()], + ControlFlowElement::BasicBlock { id: 0 } + ); + assert_matches!( + &content[&CFSelector::from_str("0/success/0").unwrap()], + ControlFlowElement::BasicBlock { id: 1 } + ); + assert_matches!( + &content[&CFSelector::from_str("0/success/1").unwrap()], + ControlFlowElement::Loop { .. } + ); + assert_matches!( + &content[&CFSelector::from_str("0/success/1/0").unwrap()], + ControlFlowElement::BasicBlock { id: 2 } + ); + assert_matches!( + &content[&CFSelector::from_str("0/success/1/1").unwrap()], + ControlFlowElement::If { .. } + ); + assert_matches!( + &content[&CFSelector::from_str("0/success/1/1/if_condition").unwrap()], + ControlFlowElement::BasicBlock { id: 3 } + ); + assert_matches!( + &content[&CFSelector::from_str("0/success/1/1/success/0").unwrap()], + ControlFlowElement::BasicBlock { id: 4 } ); } #[test] - fn test_fold_if_else() { + fn test_loop() { let function_definition = FunctionDefinition { header: ir::FunctionHeader { name: "f".to_string(), @@ -539,60 +403,90 @@ mod tests { content: vec![ BasicBlock { name: Some("bb0".to_string()), - content: vec![branch("bb1", "bb2")], + content: vec![jump("bb1")], }, BasicBlock { name: Some("bb1".to_string()), + content: vec![branch("bb2", "bb8")], + }, + BasicBlock { + name: Some("bb2".to_string()), content: vec![jump("bb3")], }, BasicBlock { name: Some("bb3".to_string()), - content: vec![branch("bb4", "bb5")], + content: vec![jump("bb4")], }, BasicBlock { name: Some("bb4".to_string()), - content: vec![jump("bb6")], + content: vec![jump("bb5")], }, BasicBlock { name: Some("bb5".to_string()), - content: vec![jump("bb6")], + content: vec![branch("bb6", "bb7")], }, BasicBlock { name: Some("bb6".to_string()), - content: vec![branch("bb1", "bb7")], + content: vec![jump("bb3")], }, BasicBlock { - name: Some("bb2".to_string()), - content: vec![jump("bb7")], + name: Some("bb7".to_string()), + content: vec![branch("bb2", "bb15")], }, BasicBlock { - name: Some("bb7".to_string()), + name: Some("bb8".to_string()), + content: vec![branch("bb9", "bb10")], + }, + BasicBlock { + name: Some("bb9".to_string()), + content: vec![jump("bb11")], + }, + BasicBlock { + name: Some("bb11".to_string()), + content: vec![jump("bb12")], + }, + BasicBlock { + name: Some("bb12".to_string()), + content: vec![jump("bb13")], + }, + BasicBlock { + name: Some("bb10".to_string()), + content: vec![jump("bb12")], + }, + BasicBlock { + name: Some("bb13".to_string()), + content: vec![jump("bb14")], + }, + BasicBlock { + name: Some("bb14".to_string()), + content: vec![branch("bb12", "bb15")], + }, + BasicBlock { + name: Some("bb15".to_string()), + content: vec![jump("bb16")], + }, + BasicBlock { + name: Some("bb16".to_string()), content: vec![Ret { value: None }.into()], }, ], }; - let control_flow_graph = ControlFlowGraph::new(); - let binded = control_flow_graph.bind(&function_definition); - let mut content = ControlFlowContent::new_block(vec![ - ControlFlowContent::new_node(0), - ControlFlowContent::new_loop(vec![ - ControlFlowContent::new_node(1), - ControlFlowContent::new_node(2), - ControlFlowContent::new_node(3), - ControlFlowContent::new_node(4), - ControlFlowContent::new_node(5), - ]), - ControlFlowContent::new_node(6), - ControlFlowContent::new_node(7), - ]); - fold_if_else(&mut content, &binded); - assert!(matches!( - content.get(&[1usize]), - Some(ControlFlowContent::If(_, _)) - )); - assert!(matches!( - content.get(&[1usize, 0, 2]), - Some(ControlFlowContent::If(_, _)) - )); + let mut editor = Editor::new(function_definition); + let pass = FixIrreducible; + pass.run(&mut editor); + let pass = TopologicalSort; + pass.run(&mut editor); + let function_definition = editor.content; + + let analyzer = Analyzer::new(); + let binded = analyzer.bind(&function_definition); + let control_flow_graph = binded.control_flow_graph(); + let scc = control_flow_graph.top_level_scc(); + + let mut current_result = (0..(function_definition.content.len())) + .map(ControlFlowElement::new_node) + .collect_vec(); + fold_loop(&function_definition, &scc, &mut current_result); + dbg!(current_result); } } diff --git a/src/ir/editor/analyzer/control_flow/control_flow_loop.rs b/src/ir/editor/analyzer/control_flow/control_flow_loop.rs deleted file mode 100644 index 2dd5249..0000000 --- a/src/ir/editor/analyzer/control_flow/control_flow_loop.rs +++ /dev/null @@ -1,220 +0,0 @@ -use itertools::Itertools; -use petgraph::prelude::*; - -use crate::utility::graph::kosaraju_scc_with_filter; - -#[derive(Debug, PartialEq)] -pub enum SccContent { - SubScc(Box), - Node(usize), -} - -impl SccContent { - pub fn is_node_in(&self, node: NodeIndex) -> bool { - match self { - SccContent::SubScc(it) => it.is_node_in(node), - SccContent::Node(it) => node.index() == *it, - } - } -} - -#[derive(Debug, PartialEq)] -pub struct Scc { - pub entries: Vec, - pub content: Vec, -} - -impl Scc { - pub fn new( - graph: &DiGraph<(), (), usize>, - nodes: &[NodeIndex], - backedges: &[EdgeIndex], - ) -> Self { - let entries: Vec<_> = nodes - .iter() - .filter(|&&node| { - graph - .neighbors_directed(node, Incoming) - .any(|from| !nodes.contains(&from)) - }) - .cloned() - .collect(); - let mut new_backedges: Vec> = Vec::new(); - for &entry in entries.iter() { - new_backedges.extend( - graph - .edges_directed(entry, Incoming) - .filter(|edge| nodes.contains(&edge.source())) - .map(|it| it.id()), - ); - } - new_backedges.extend_from_slice(backedges); - let sccs = kosaraju_scc_with_filter( - graph, - *entries.first().or(nodes.first()).unwrap(), - |it| nodes.contains(&it), - |edge| !new_backedges.contains(&edge), - ); - if sccs.len() == 1 { - return Self { - entries: entries.into_iter().map(|it| it.index()).collect(), - content: sccs[0] - .iter() - .map(|it| SccContent::Node(it.index())) - .collect(), - }; - } - let mut content: Vec<_> = sccs - .into_iter() - .map(|mut scc| { - if scc.len() == 1 { - SccContent::Node(scc.pop().unwrap().index()) - } else { - let sub_loop = Scc::new(graph, &scc, &new_backedges); - SccContent::SubScc(Box::new(sub_loop)) - } - }) - .collect(); - for entry in &entries { - if !content.iter().any(|it| it.is_node_in(*entry)) { - content.push(SccContent::Node(entry.index())); - } - } - Self { - entries: entries.into_iter().map(|it| it.index()).collect(), - content, - } - } - - pub fn first_irreducible_loop(&self) -> Option<&Scc> { - if self.entries.len() > 1 { - Some(self) - } else { - self.content - .iter() - .filter_map(|it| match it { - SccContent::SubScc(sub_loop) => Some(sub_loop), - SccContent::Node(_) => None, - }) - .find_map(|it| it.first_irreducible_loop()) - } - } - - pub fn entry_info( - &self, - graph: &DiGraph<(), (), usize>, - ) -> Vec<(NodeIndex, Vec>)> { - let mut result: Vec<_> = self - .entries - .iter() - .map(|&entry| { - let mut from = graph - .edges_directed(entry.into(), Direction::Incoming) - .map(|it| it.source()) - .collect_vec(); - from.sort_unstable(); - (entry.into(), from) - }) - .collect(); - result.sort_unstable_by_key(|it| it.0); - result - } - - pub fn name(&self) -> String { - format!( - "_loop_{}", - self.entries.iter().map(ToString::to_string).join("_") - ) - } - - pub fn smallest_loop_node_in(&self, node: NodeIndex) -> Option<&Scc> { - let found_node = self - .content - .iter() - .filter_map(|it| { - if let SccContent::Node(node) = it { - Some(*node) - } else { - None - } - }) - .find(|&it| it == node.index()); - if found_node.is_some() { - return Some(self); - } - self.content - .iter() - .filter_map(|it| match it { - SccContent::SubScc(sub_loop) => Some(sub_loop), - SccContent::Node(_) => None, - }) - .find_map(|it| it.smallest_loop_node_in(node)) - } - - pub fn is_node_in(&self, node: NodeIndex) -> bool { - self.content.iter().any(|it| match it { - SccContent::SubScc(sub_loop) => sub_loop.is_node_in(node), - SccContent::Node(n) => node.index() == *n, - }) - } - - pub fn node_count(&self) -> usize { - self.content - .iter() - .map(|it| match it { - SccContent::SubScc(sub_loop) => sub_loop.node_count(), - SccContent::Node(_) => 1, - }) - .sum() - } -} - -#[cfg(test)] -mod tests { - - use super::*; - #[test] - fn test_new_loop() { - let mut graph: DiGraph<_, _, usize> = DiGraph::default(); - let node_0 = graph.add_node(()); - let node_1 = graph.add_node(()); - let node_2 = graph.add_node(()); - let node_3 = graph.add_node(()); - let node_4 = graph.add_node(()); - let node_5 = graph.add_node(()); - let node_6 = graph.add_node(()); - let node_7 = graph.add_node(()); - graph.add_edge(node_0, node_7, ()); - graph.add_edge(node_7, node_1, ()); - graph.add_edge(node_1, node_7, ()); - graph.add_edge(node_7, node_2, ()); - graph.add_edge(node_2, node_3, ()); - graph.add_edge(node_2, node_6, ()); - graph.add_edge(node_3, node_4, ()); - graph.add_edge(node_4, node_6, ()); - graph.add_edge(node_6, node_5, ()); - graph.add_edge(node_5, node_4, ()); - graph.add_edge(node_3, node_7, ()); - graph.add_edge(node_4, node_7, ()); - let result = Scc::new( - &graph, - &[ - node_0, node_1, node_2, node_3, node_4, node_5, node_6, node_7, - ], - &[], - ); - assert_eq!(result.content.len(), 2); - let inner_loop = result - .content - .iter() - .find_map(|it| match it { - SccContent::SubScc(sub_loop) => Some(sub_loop), - SccContent::Node(_) => None, - }) - .unwrap() - .as_ref(); - assert_eq!(inner_loop.entries.len(), 1); - assert_eq!(inner_loop.entries[0], 7); - assert_eq!(inner_loop.content.len(), 5); - } -} diff --git a/src/ir/editor/analyzer/control_flow/mod.rs b/src/ir/editor/analyzer/control_flow/mod.rs index ad92e1c..6cd76e5 100644 --- a/src/ir/editor/analyzer/control_flow/mod.rs +++ b/src/ir/editor/analyzer/control_flow/mod.rs @@ -1,4 +1,3 @@ -mod control_flow_loop; use std::{ cell::{OnceCell, Ref, RefCell}, collections::HashMap, @@ -19,11 +18,12 @@ use crate::{ utility::{self}, }; +use self::scc::Scc; + use super::IsAnalyzer; -pub use control_flow_loop::{Scc, SccContent}; +pub use scc::BindedScc; -mod scc_new; -pub use scc_new::BindedScc; +mod scc; /// [`ControlFlowGraph`] is the control flow graph and related infomation of a function. #[derive(Debug)] @@ -32,6 +32,7 @@ pub struct ControlFlowGraphContent { frontiers: HashMap>, bb_name_index_map: BiMap, dominators: Dominators>, + top_level_scc: Scc, // fixme: remove this refcell! from_to_may_pass_blocks: RefCell>>, } @@ -83,6 +84,7 @@ impl ControlFlowGraphContent { dominators: dorminators, bb_name_index_map, from_to_may_pass_blocks: RefCell::new(HashMap::new()), + top_level_scc: Scc::new(0..function_definition.content.len(), true), } } @@ -175,14 +177,44 @@ impl ControlFlowGraph { fn dominate(&self, content: &ir::FunctionDefinition, bb_index: usize) -> Vec { self.content(content).dominates(bb_index) } - // todo: cache it - fn sccs(&self, content: &FunctionDefinition) -> Scc { - let graph = &self.content(content).graph; - let nodes: Vec<_> = graph.node_indices().collect(); - Scc::new(graph, &nodes, &[]) + fn top_level_scc(&self, content: &FunctionDefinition) -> Scc { + self.content(content).top_level_scc.clone() + } + fn branch_direction( + &self, + content: &FunctionDefinition, + branch_block_index: usize, + target_block_index: usize, + ) -> bool { + let branch_block = &content[branch_block_index]; + let target_block_name = self.basic_block_name_by_index(content, target_block_index); + let branch_statement = branch_block.content.last().unwrap().as_branch(); + branch_statement.success_label == target_block_name + } + fn is_in_same_branch_side( + &self, + content: &FunctionDefinition, + branch_block_index: usize, + block1_index: usize, + block2_index: usize, + ) -> bool { + let branch_block = &content.content[branch_block_index]; + let success_name = &branch_block + .content + .last() + .unwrap() + .as_branch() + .success_label; + let success_block_id = self.basic_block_index_by_name(content, &success_name); + let block1_under_success = self.dominate(content, success_block_id); + let block2_under_success = self.dominate(content, success_block_id); + let block1_under_success = block1_under_success.contains(&block1_index); + let block2_under_success = block2_under_success.contains(&block2_index); + block1_under_success == block2_under_success } } +#[derive(Debug)] pub struct BindedControlFlowGraph<'item, 'bind: 'item> { pub bind_on: &'bind FunctionDefinition, item: &'item ControlFlowGraph, @@ -201,8 +233,8 @@ impl<'item, 'bind: 'item> BindedControlFlowGraph<'item, 'bind> { pub fn may_pass_blocks(&self, from: usize, to: usize) -> Ref> { self.item.may_pass_blocks(self.bind_on, from, to) } - pub fn sccs(&self) -> Scc { - self.item.sccs(self.bind_on) + pub fn top_level_scc(&self) -> BindedScc<'_> { + self.item.top_level_scc(self.bind_on).bind(self.graph()) } pub fn graph(&self) -> &DiGraph<(), (), usize> { &self.item.content(self.bind_on).graph @@ -210,20 +242,21 @@ impl<'item, 'bind: 'item> BindedControlFlowGraph<'item, 'bind> { pub fn dominates(&self, bb_index: usize) -> Vec { self.item.dominate(self.bind_on, bb_index) } + pub fn is_dominated_by(&self, node: usize, dominator_suspect: usize) -> bool { + self.dominates(dominator_suspect).contains(&node) + } pub fn predecessor(&self, bb_index: usize) -> Vec { self.graph() .neighbors_directed(bb_index.into(), Direction::Incoming) .map(|it| it.index()) .collect() } - pub fn successors(&self, bb_index: usize) -> Vec { self.graph() - .neighbors_directed(bb_index.into(), Direction::Incoming) + .neighbors_directed(bb_index.into(), Direction::Outgoing) .map(|it| it.index()) .collect() } - pub fn not_dominate_successors(&self, bb_index: usize) -> Vec { let successors = self .graph() @@ -234,11 +267,22 @@ impl<'item, 'bind: 'item> BindedControlFlowGraph<'item, 'bind> { .filter(|it| !nodes_dominated.contains(it)) .collect() } - - pub fn scc_new(&self) -> BindedScc<'_> { - let graph = &self.item.content(self.bind_on).graph; - let nodes = graph.node_indices().map(|it| it.index()).collect_vec(); - BindedScc::new(graph, nodes.into_iter(), true) + pub fn branch_direction(&self, branch_block_index: usize, target_block_index: usize) -> bool { + self.item + .branch_direction(&self.bind_on, branch_block_index, target_block_index) + } + pub fn is_in_same_branch_side( + &self, + branch_block_index: usize, + block1_index: usize, + block2_index: usize, + ) -> bool { + self.item.is_in_same_branch_side( + &self.bind_on, + branch_block_index, + block1_index, + block2_index, + ) } } @@ -268,88 +312,3 @@ fn remove_unreachable_nodes(mut graph: DiGraph<(), (), usize>) -> DiGraph<(), () graph.retain_nodes(|_, it| reachable_nodes.contains(&it)); graph } - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - ir::{ - function::{basic_block::BasicBlock, test_util::*}, - statement::Ret, - }, - utility::data_type, - }; - - #[test] - fn test_loop() { - let control_flow_graph = ControlFlowGraph::new(); - let function_definition = FunctionDefinition { - header: ir::FunctionHeader { - name: "f".to_string(), - parameters: Vec::new(), - return_type: data_type::Type::None, - }, - content: vec![ - BasicBlock { - name: Some("bb0".to_string()), - content: vec![branch("bb1", "bb2")], - }, - BasicBlock { - name: Some("bb1".to_string()), - content: vec![jump("bb3")], - }, - BasicBlock { - name: Some("bb2".to_string()), - content: vec![jump("bb6")], - }, - BasicBlock { - name: Some("bb3".to_string()), - content: vec![jump("bb4")], - }, - BasicBlock { - name: Some("bb4".to_string()), - content: vec![branch("bb5", "bb9")], - }, - BasicBlock { - name: Some("bb5".to_string()), - content: vec![branch("bb1", "bb3")], - }, - BasicBlock { - name: Some("bb6".to_string()), - content: vec![branch("bb7", "bb8")], - }, - BasicBlock { - name: Some("bb7".to_string()), - content: vec![jump("bb2")], - }, - BasicBlock { - name: Some("bb8".to_string()), - content: vec![branch("bb7", "bb9")], - }, - BasicBlock { - name: Some("bb9".to_string()), - content: vec![Ret { value: None }.into()], - }, - ], - }; - let loops = control_flow_graph.bind(&function_definition).sccs(); - assert!(loops.content.contains(&SccContent::Node(0))); - assert!(loops.content.contains(&SccContent::Node(9))); - assert!(loops - .content - .iter() - .any(|it| if let SccContent::SubScc(subloop) = it { - subloop.entries.contains(&1) - } else { - false - })); - assert!(loops - .content - .iter() - .any(|it| if let SccContent::SubScc(subloop) = it { - subloop.entries.contains(&2) - } else { - false - })); - } -} diff --git a/src/ir/editor/analyzer/control_flow/scc.rs b/src/ir/editor/analyzer/control_flow/scc.rs new file mode 100644 index 0000000..66ee8ea --- /dev/null +++ b/src/ir/editor/analyzer/control_flow/scc.rs @@ -0,0 +1,231 @@ +use crate::utility::graph::kosaraju_scc_with_filter; +use delegate::delegate; + +use itertools::Itertools; +use petgraph::{graph, prelude::*}; + +#[derive(Clone, Debug)] +pub struct Scc { + pub nodes: Vec, + pub top_level: bool, +} + +impl Scc { + pub fn new(nodes: impl IntoIterator, top_level: bool) -> Self { + Self { + nodes: nodes.into_iter().collect(), + top_level, + } + } + + pub fn is_trivial(&self) -> bool { + self.nodes.len() == 1 + } + + pub fn edges(&self, graph: &DiGraph<(), (), usize>) -> Vec<(usize, usize)> { + graph + .edge_references() + .filter(|edge| { + self.nodes.contains(&edge.source().index()) + && self.nodes.contains(&edge.target().index()) + }) + .map(|it| (it.source().index(), it.target().index())) + .collect() + } + + pub fn entry_edges(&self, graph: &DiGraph<(), (), usize>) -> Vec<(usize, usize)> { + graph + .edge_references() + .filter(|edge| { + !self.nodes.contains(&edge.source().index()) + && self.nodes.contains(&edge.target().index()) + }) + .map(|it| (it.source().index(), it.target().index())) + .collect() + } + + pub fn entry_nodes(&self, graph: &DiGraph<(), (), usize>) -> Vec { + if self.top_level || self.nodes.len() == 1 { + vec![self.nodes[0]] + } else { + self.entry_edges(graph) + .into_iter() + .map(|(_, to)| to) + .sorted() + .dedup() + .collect() + } + } + + pub fn edges_into_entry_nodes(&self, graph: &DiGraph<(), (), usize>) -> Vec<(usize, usize)> { + graph + .edge_references() + .filter(|edge| self.entry_nodes(graph).contains(&edge.target().index())) + .map(|it| (it.source().index(), it.target().index())) + .collect() + } + + pub fn reduciable(&self, graph: &DiGraph<(), (), usize>) -> bool { + self.entry_nodes(graph).len() == 1 + } + + /// Returns all top level sccs for a reducible subgraph. + /// Return None if the subgraph is not reducible. + pub fn top_level_sccs(&self, graph: &DiGraph<(), (), usize>) -> Option> { + let entry_nodes = self.entry_nodes(graph); + if entry_nodes.len() != 1 { + None + } else { + let entry_node = entry_nodes[0]; + let backedges: Vec<_> = graph + .edges_directed(entry_node.into(), Incoming) + .map(|it| it.id()) + .collect(); + let sccs = kosaraju_scc_with_filter( + graph, + entry_nodes[0].into(), + |node| self.nodes.contains(&node.index()), + |edge| !backedges.contains(&edge), + ); + let result = sccs + .into_iter() + .map(|content| Self::new(content.into_iter().map(NodeIndex::index), false)) + .collect(); + Some(result) + } + } + + pub fn first_irreducible_sub_scc(&self, graph: &DiGraph<(), (), usize>) -> Option { + if self.nodes.len() == 1 { + return None; + } else if !self.reduciable(graph) { + return Some(self.clone()); + } else { + let sccs = self.top_level_sccs(graph).unwrap(); + for scc in sccs { + if let Some(first_irreducible) = scc.first_irreducible_sub_scc(graph) { + return Some(first_irreducible); + } + } + } + None + } + + pub fn contains(&self, node: usize) -> bool { + self.nodes.contains(&node) + } + + /// Returns the smallest non trivial (ie. not a single node) scc + /// the node is in. + pub fn smallest_non_trivial_scc_node_in( + &self, + graph: &DiGraph<(), (), usize>, + node: usize, + ) -> Option { + if !self.contains(node) { + None + } else if self.is_trivial() { + None + } else if let Some(sub_sccs) = self.top_level_sccs(graph) { + for sub_scc in sub_sccs { + if sub_scc.is_trivial() && sub_scc.contains(node) { + return Some(self.clone()); + } else if let Some(result) = sub_scc.smallest_non_trivial_scc_node_in(graph, node) { + return Some(result); + } + } + unreachable!() + } else { + debug_assert!(!self.reduciable(graph)); + Some(self.clone()) + } + } +} + +#[derive(Clone, Debug)] +pub struct BindedScc<'bind> { + graph: &'bind DiGraph<(), (), usize>, + item: Scc, +} + +impl<'bind> BindedScc<'bind> { + pub fn new(graph: &'bind DiGraph<(), (), usize>, item: Scc) -> Self { + Self { graph, item } + } + delegate! { + to self.item { + pub fn is_trivial(&self) -> bool; + pub fn edges(&self, [self.graph]) -> Vec<(usize, usize)>; + pub fn entry_edges(&self, [self.graph]) -> Vec<(usize, usize)>; + pub fn entry_nodes(&self, [self.graph]) -> Vec; + pub fn edges_into_entry_nodes(&self, [self.graph]) -> Vec<(usize, usize)>; + pub fn reduciable(&self, [self.graph]) -> bool; + pub fn contains(&self, node: usize) -> bool; + } + } + pub fn top_level_sccs(&self) -> Option> { + self.item + .top_level_sccs(self.graph) + .map(|it| it.into_iter().map(|it| it.bind(self.graph)).collect_vec()) + } + pub fn first_irreducible_sub_scc(&self) -> Option { + self.item + .first_irreducible_sub_scc(self.graph) + .map(|it| it.bind(self.graph)) + } + pub fn smallest_non_trivial_scc_node_in(&self, node: usize) -> Option { + self.item + .smallest_non_trivial_scc_node_in(self.graph, node) + .map(|it| it.bind(self.graph)) + } + pub fn top_level(&self) -> bool { + self.item.top_level + } +} + +impl<'item, 'bind: 'item> Scc { + pub fn bind(&'item self, graph: &'bind DiGraph<(), (), usize>) -> BindedScc<'bind> { + BindedScc::new(graph, self.clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_top_level_scc() { + let mut graph: DiGraph<_, _, usize> = DiGraph::default(); + let node_0 = graph.add_node(()); + let node_1 = graph.add_node(()); + let node_2 = graph.add_node(()); + let node_3 = graph.add_node(()); + let node_4 = graph.add_node(()); + let node_5 = graph.add_node(()); + let node_6 = graph.add_node(()); + let node_7 = graph.add_node(()); + let node_8 = graph.add_node(()); + let node_9 = graph.add_node(()); + let node_10 = graph.add_node(()); + graph.add_edge(node_0, node_1, ()); + graph.add_edge(node_1, node_2, ()); + graph.add_edge(node_2, node_3, ()); + graph.add_edge(node_2, node_7, ()); + graph.add_edge(node_3, node_4, ()); + graph.add_edge(node_3, node_8, ()); + graph.add_edge(node_7, node_5, ()); + graph.add_edge(node_7, node_9, ()); + graph.add_edge(node_9, node_8, ()); + graph.add_edge(node_8, node_9, ()); + graph.add_edge(node_5, node_2, ()); + graph.add_edge(node_4, node_6, ()); + graph.add_edge(node_6, node_2, ()); + graph.add_edge(node_6, node_10, ()); + graph.add_edge(node_10, node_6, ()); + graph.add_edge(node_10, node_4, ()); + let scc = Scc::new(0..10, true); + let scc = BindedScc::new(&graph, scc); + dbg!(scc.top_level_sccs()); + dbg!(scc.first_irreducible_sub_scc()); + } +} diff --git a/src/ir/editor/analyzer/control_flow/scc_new.rs b/src/ir/editor/analyzer/control_flow/scc_new.rs deleted file mode 100644 index e522406..0000000 --- a/src/ir/editor/analyzer/control_flow/scc_new.rs +++ /dev/null @@ -1,162 +0,0 @@ -use itertools::Itertools; -use petgraph::prelude::*; - -use crate::utility::graph::kosaraju_scc_with_filter; - -// todo: This is binded, maybe need an unbinded version -#[derive(Clone, Debug)] -pub struct BindedScc<'a> { - graph: &'a DiGraph<(), (), usize>, - pub nodes: Vec, - pub top_level: bool, -} - -impl<'a> BindedScc<'a> { - pub fn new( - graph: &'a DiGraph<(), (), usize>, - nodes: impl IntoIterator, - top_level: bool, - ) -> Self { - Self { - graph, - nodes: nodes.into_iter().collect(), - top_level, - } - } - - pub fn edges(&self) -> Vec<(usize, usize)> { - self.graph - .edge_references() - .filter(|edge| { - self.nodes.contains(&edge.source().index()) - && self.nodes.contains(&edge.target().index()) - }) - .map(|it| (it.source().index(), it.target().index())) - .collect() - } - - pub fn entry_edges(&self) -> Vec<(usize, usize)> { - self.graph - .edge_references() - .filter(|edge| { - !self.nodes.contains(&edge.source().index()) - && self.nodes.contains(&edge.target().index()) - }) - .map(|it| (it.source().index(), it.target().index())) - .collect() - } - - pub fn entry_nodes(&self) -> Vec { - if self.top_level || self.nodes.len() == 1 { - vec![self.nodes[0]] - } else { - self.entry_edges() - .into_iter() - .map(|(_, to)| to) - .sorted() - .dedup() - .collect() - } - } - - pub fn edges_into_entry_nodes(&self) -> Vec<(usize, usize)> { - self.graph - .edge_references() - .filter(|edge| self.entry_nodes().contains(&edge.target().index())) - .map(|it| (it.source().index(), it.target().index())) - .collect() - } - - pub fn reduciable(&self) -> bool { - self.entry_nodes().len() == 1 - } - - /// Returns all top level sccs for a reducible subgraph. - /// Return None if the subgraph is not reducible. - pub fn top_level_sccs(&self) -> Option>> { - let entry_nodes = self.entry_nodes(); - if entry_nodes.len() != 1 { - None - } else { - let entry_node = entry_nodes[0]; - let backedges: Vec<_> = self - .graph - .edges_directed(entry_node.into(), Incoming) - .map(|it| it.id()) - .collect(); - let sccs = kosaraju_scc_with_filter( - self.graph, - entry_nodes[0].into(), - |node| self.nodes.contains(&node.index()), - |edge| !backedges.contains(&edge), - ); - let result = sccs - .into_iter() - .map(|content| { - Self::new(self.graph, content.into_iter().map(NodeIndex::index), false) - }) - .collect(); - Some(result) - } - } - - pub fn first_irreducible_sub_scc(&self) -> Option> { - if self.nodes.len() == 1 { - return None; - } else if !self.reduciable() { - return Some(self.clone()); - } else { - let sccs = self.top_level_sccs().unwrap(); - for scc in sccs { - if let Some(first_irreducible) = scc.first_irreducible_sub_scc() { - return Some(first_irreducible); - } - } - } - None - } - - pub fn contains(&self, node: usize) -> bool { - self.nodes.contains(&node) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_top_level_scc() { - let mut graph: DiGraph<_, _, usize> = DiGraph::default(); - let node_0 = graph.add_node(()); - let node_1 = graph.add_node(()); - let node_2 = graph.add_node(()); - let node_3 = graph.add_node(()); - let node_4 = graph.add_node(()); - let node_5 = graph.add_node(()); - let node_6 = graph.add_node(()); - let node_7 = graph.add_node(()); - let node_8 = graph.add_node(()); - let node_9 = graph.add_node(()); - let node_10 = graph.add_node(()); - graph.add_edge(node_0, node_1, ()); - graph.add_edge(node_1, node_2, ()); - graph.add_edge(node_2, node_3, ()); - graph.add_edge(node_2, node_7, ()); - graph.add_edge(node_3, node_4, ()); - graph.add_edge(node_3, node_8, ()); - graph.add_edge(node_7, node_5, ()); - graph.add_edge(node_7, node_9, ()); - graph.add_edge(node_9, node_8, ()); - graph.add_edge(node_8, node_9, ()); - graph.add_edge(node_5, node_2, ()); - graph.add_edge(node_4, node_6, ()); - graph.add_edge(node_6, node_2, ()); - graph.add_edge(node_6, node_10, ()); - graph.add_edge(node_10, node_6, ()); - graph.add_edge(node_10, node_4, ()); - let scc = BindedScc::new(&graph, 0..10, true); - dbg!(scc.top_level_sccs()); - dbg!(scc.first_irreducible_sub_scc()); - } -} diff --git a/src/ir/editor/analyzer/mod.rs b/src/ir/editor/analyzer/mod.rs index a4b6931..0b3b0d8 100644 --- a/src/ir/editor/analyzer/mod.rs +++ b/src/ir/editor/analyzer/mod.rs @@ -2,7 +2,7 @@ use crate::ir::{self, FunctionDefinition}; use self::register_usage::RegisterUsageAnalyzer; pub use self::{ - control_flow::{BindedControlFlowGraph, BindedScc, ControlFlowGraph, Scc, SccContent}, + control_flow::{BindedControlFlowGraph, BindedScc, ControlFlowGraph}, memory_usage::{BindedMemoryUsage, MemoryUsage}, register_usage::{BindedRegisterUsage, BindedRegisterUsageAnalyzer}, }; diff --git a/src/ir/function/basic_block.rs b/src/ir/function/basic_block.rs index f3f5575..9234c15 100644 --- a/src/ir/function/basic_block.rs +++ b/src/ir/function/basic_block.rs @@ -42,6 +42,10 @@ impl BasicBlock { pub fn remove(&mut self, index: usize) { self.content.remove(index); } + + pub fn is_branch(&self) -> bool { + matches!(self.content.last(), Some(IRStatement::Branch(_))) + } } impl fmt::Display for BasicBlock { diff --git a/src/ir/optimize/pass/fix_irreducible.rs b/src/ir/optimize/pass/fix_irreducible.rs deleted file mode 100644 index a98c8b1..0000000 --- a/src/ir/optimize/pass/fix_irreducible.rs +++ /dev/null @@ -1,557 +0,0 @@ -use super::IsPass; -use crate::{ - ir::{ - statement::{ - branch::BranchType, phi::PhiSource, BinaryCalculate, Branch, IRStatement, Jump, Phi, - }, - RegisterName, - }, - utility::data_type, -}; - -use itertools::Itertools; -use petgraph::prelude::*; - -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -pub struct FixIrreducible; - -pub enum GoToCondition { - Register(RegisterName), - Always, -} - -// If `condition` is true, the connections from `from` should be redirected -struct ShouldGoTo { - from: usize, - condition: GoToCondition, - dispatcher: usize, - on_success_branch: bool, -} - -fn fold_entries_once( - loop_name: &str, - header: &mut Option>, - generated: &mut Vec>, - entries: &mut Vec<(NodeIndex, Vec>)>, - editor: &mut crate::ir::editor::Editor, -) -> Vec { - let last = generated.last(); - if entries.len() >= 2 { - let new_node_name = format!( - "{}_dispatcher_at_{}", - loop_name, - editor.content.content.len() - ); - let new_node = editor.create_basic_block(new_node_name.clone()); - if let Some(last) = last { - let last_bb_size = editor.content[last.index()].content.len(); - let mut last_bb_last_statement = - editor.content[last.index()].content.last().unwrap().clone(); - editor.remove_statement((last.index(), last_bb_size - 1)); - let IRStatement::Branch(branch) = &mut last_bb_last_statement else { - unreachable!() - }; - branch.failure_label = new_node_name.clone(); - editor.push_back_statement(last.index(), last_bb_last_statement); - } - let (branch_to, blocks_into_branch_to) = entries.pop().unwrap(); - let branch_to_bb_name = editor.content[branch_to.index()].name.clone().unwrap(); - let operand_name = format!("{new_node_name}_var"); - let new_branch_statement = Branch { - branch_type: BranchType::NE, - operand1: RegisterName(operand_name).into(), - operand2: 0.into(), - success_label: branch_to_bb_name, - failure_label: String::new(), - }; - editor.push_back_statement(new_node, new_branch_statement); - let header = header.get_or_insert(new_node.into()); - generated.push(new_node.into()); - fix_branch_into_source( - loop_name, - editor, - header.index(), - branch_to.index(), - &blocks_into_branch_to, - new_node, - ) - } else { - let last = last.unwrap(); - let last_bb_size = editor.content[last.index()].content.len(); - let mut last_bb_last_statement = - editor.content[last.index()].content.last().unwrap().clone(); - editor.remove_statement((last.index(), last_bb_size - 1)); - let IRStatement::Branch(branch) = &mut last_bb_last_statement else { - unreachable!() - }; - let (branch_to, blocks_into_branch_to) = entries.pop().unwrap(); - let branch_to_bb_name = editor.content[branch_to.index()].name.clone().unwrap(); - branch.failure_label = branch_to_bb_name; - editor.push_back_statement(last.index(), last_bb_last_statement); - let header = header.as_mut().unwrap(); - let mut result = fix_branch_into_source( - loop_name, - editor, - header.index(), - branch_to.index(), - &blocks_into_branch_to, - last.index(), - ); - for r in result.iter_mut() { - r.on_success_branch = false; - } - result - } -} - -fn fix_branch_into_source( - loop_name: &str, - editor: &mut crate::ir::editor::Editor, - header: usize, - branch_to: usize, - from_blocks: &[NodeIndex], - dispatcher: usize, -) -> Vec { - let header_block_name = format!("{loop_name}_dispatcher_at_{header}"); - let branch_to_block_name = editor.content[branch_to].name.clone().unwrap(); - let mut should_go_tos = Vec::new(); - for from_block in from_blocks { - let from_block = from_block.index(); - let mut from_block_last_statement = - editor.content[from_block].content.last().unwrap().clone(); - editor.remove_statement((from_block, editor.content[from_block].content.len() - 1)); - - let condition_register: RegisterName = - RegisterName(format!("{loop_name}_goto_{branch_to}_in_{from_block}")); - let should_go_to = match &mut from_block_last_statement { - IRStatement::Branch(branch) => { - if branch.success_label == header_block_name { - let condition_statement = BinaryCalculate { - operation: branch - .branch_type - .corresponding_binary_operation() - .inverse() - .unwrap(), - operand1: branch.operand1.clone(), - operand2: branch.operand2.clone(), - to: condition_register.clone(), - data_type: data_type::Integer { - signed: false, - width: 1, - } - .into(), - }; - editor.push_back_statement(from_block, condition_statement); - from_block_last_statement = Jump { - label: header_block_name.clone(), - } - .into(); - } else if branch.failure_label == header_block_name { - let condition_statement = BinaryCalculate { - operation: branch.branch_type.corresponding_binary_operation(), - operand1: branch.operand1.clone(), - operand2: branch.operand2.clone(), - to: condition_register.clone(), - data_type: data_type::Integer { - signed: false, - width: 1, - } - .into(), - }; - editor.push_back_statement(from_block, condition_statement); - from_block_last_statement = Jump { - label: header_block_name.clone(), - } - .into(); - } else if branch.success_label == branch_to_block_name { - let condition_statement = BinaryCalculate { - operation: branch.branch_type.corresponding_binary_operation(), - operand1: branch.operand1.clone(), - operand2: branch.operand2.clone(), - to: condition_register.clone(), - data_type: data_type::Integer { - signed: false, - width: 1, - } - .into(), - }; - editor.push_back_statement(from_block, condition_statement); - branch.success_label = header_block_name.clone(); - } else { - let condition_statement = BinaryCalculate { - operation: branch - .branch_type - .inverse() - .corresponding_binary_operation(), - operand1: branch.operand1.clone(), - operand2: branch.operand2.clone(), - to: condition_register.clone(), - data_type: data_type::Integer { - signed: false, - width: 1, - } - .into(), - }; - editor.push_back_statement(from_block, condition_statement); - branch.failure_label = header_block_name.clone(); - } - ShouldGoTo { - from: from_block, - condition: GoToCondition::Register(condition_register.clone()), - dispatcher, - on_success_branch: true, - } - } - IRStatement::Jump(jump) => { - jump.label = header_block_name.clone(); - ShouldGoTo { - from: from_block, - condition: GoToCondition::Always, - dispatcher, - on_success_branch: true, - } - } - _ => unreachable!(), - }; - editor.push_back_statement(from_block, from_block_last_statement); - should_go_tos.push(should_go_to); - } - should_go_tos -} - -fn generate_phis( - loop_name: &str, - all_should_go_to: &[ShouldGoTo], - generated: &[NodeIndex], - editor: &mut crate::ir::editor::Editor, -) -> Vec { - let sources = all_should_go_to.iter().map(|it| it.from).collect_vec(); - let binding = all_should_go_to.iter().group_by(|it| it.dispatcher); - let groups = binding - .into_iter() - .sorted_by_cached_key(|(it, _)| { - generated - .iter() - .position(|generated_index| generated_index.index() == *it) - }) - .collect_vec(); - generated - .iter() - .zip(groups.into_iter()) - .map(|(generated, (_, should_gotos))| { - let generated = generated.index(); - let should_go_to = should_gotos - .into_iter() - .filter(|it| it.on_success_branch) - .collect_vec(); - let condition_register: RegisterName = - RegisterName(format!("{loop_name}_dispatcher_at_{generated}_var")); - let not_related_sources = sources - .iter() - .filter(|&&source| !should_go_to.iter().any(|it| it.from == source)) - .collect_vec(); - let from = should_go_to - .iter() - .map(|should_go_to_item| PhiSource { - value: match &should_go_to_item.condition { - GoToCondition::Register(register) => register.clone().into(), - GoToCondition::Always => 1.into(), - }, - block: editor - .binded_analyzer() - .control_flow_graph() - .basic_block_name_by_index(should_go_to_item.from) - .to_string(), - }) - .chain(not_related_sources.into_iter().map(|it| { - PhiSource { - value: 0.into(), - block: editor - .binded_analyzer() - .control_flow_graph() - .basic_block_name_by_index(*it) - .to_string(), - } - })) - .collect(); - Phi { - to: condition_register, - data_type: data_type::Integer { - signed: false, - width: 1, - } - .into(), - from, - } - }) - .collect() -} - -impl IsPass for FixIrreducible { - fn run(&self, editor: &mut crate::ir::editor::Editor) { - loop { - let mut header = None; - let binded_analyzer = editor.binded_analyzer(); - let control_flow_graph = binded_analyzer.control_flow_graph(); - let (mut entries, loop_name) = if let Some(irreducible_loop) = - control_flow_graph.sccs().first_irreducible_loop() - { - ( - irreducible_loop.entry_info(control_flow_graph.graph()), - irreducible_loop.name(), - ) - } else { - break; - }; - let mut generated = Vec::new(); - let mut should_go_to = Vec::new(); - while !entries.is_empty() { - should_go_to.append(&mut fold_entries_once( - &loop_name, - &mut header, - &mut generated, - &mut entries, - editor, - )); - } - let phis = generate_phis(&loop_name, &should_go_to, &generated, editor); - let header = header.unwrap().index(); - for phi in phis.into_iter() { - editor.push_front_statement(header, phi); - } - } - } - - fn need(&self) -> Vec { - Vec::new() - } - - fn invalidate(&self) -> Vec { - Vec::new() - } -} - -#[cfg(test)] -mod tests { - use crate::ir::{ - self, - editor::Editor, - function::{basic_block::BasicBlock, test_util::*}, - statement::{calculate::binary::BinaryOperation, Ret}, - FunctionDefinition, - }; - - use super::*; - #[test] - fn simple() { - let function_definition = FunctionDefinition { - header: ir::FunctionHeader { - name: "f".to_string(), - parameters: Vec::new(), - return_type: data_type::Type::None, - }, - content: vec![ - BasicBlock { - name: Some("bb0".to_string()), - content: vec![branch("bb1", "bb5")], - }, - BasicBlock { - name: Some("bb1".to_string()), - content: vec![jump("bb2")], - }, - BasicBlock { - name: Some("bb2".to_string()), - content: vec![jump("bb3")], - }, - BasicBlock { - name: Some("bb3".to_string()), - content: vec![branch("bb4", "bb6")], - }, - BasicBlock { - name: Some("bb4".to_string()), - content: vec![jump("bb1")], - }, - BasicBlock { - name: Some("bb5".to_string()), - content: vec![branch("bb4", "bb6")], - }, - BasicBlock { - name: Some("bb6".to_string()), - content: vec![Ret { value: None }.into()], - }, - ], - }; - let mut editor = Editor::new(function_definition); - let pass = FixIrreducible; - pass.run(&mut editor); - assert_eq!(editor.content.content.len(), 8); - let analyzer = editor.binded_analyzer(); - let cfg = analyzer.control_flow_graph(); - assert_eq!(cfg.sccs().content.len(), 5); - } - - #[test] - fn double_dispatcher() { - let function_definition = FunctionDefinition { - header: ir::FunctionHeader { - name: "f".to_string(), - parameters: Vec::new(), - return_type: data_type::Type::None, - }, - content: vec![ - BasicBlock { - name: Some("bb0".to_string()), - content: vec![branch("bb1", "bb6")], - }, - BasicBlock { - name: Some("bb1".to_string()), - content: vec![jump("bb2")], - }, - BasicBlock { - name: Some("bb2".to_string()), - content: vec![jump("bb3")], - }, - BasicBlock { - name: Some("bb3".to_string()), - content: vec![branch("bb4", "bb5")], - }, - BasicBlock { - name: Some("bb4".to_string()), - content: vec![branch("bb2", "bb1")], - }, - BasicBlock { - name: Some("bb5".to_string()), - content: vec![Ret { value: None }.into()], - }, - BasicBlock { - name: Some("bb6".to_string()), - content: vec![branch("bb4", "bb3")], - }, - ], - }; - let mut editor = Editor::new(function_definition); - let pass = FixIrreducible; - pass.run(&mut editor); - assert_eq!(editor.content.content.len(), 9); - let bb6 = editor - .content - .content - .iter() - .find(|it| it.name.as_ref().map(|it| it == "bb6").unwrap_or(false)) - .unwrap(); - assert_eq!(bb6.content.len(), 3); - let register_4_to_6_setting = bb6 - .content - .iter() - .filter_map(|it| it.try_as_binary_calculate()) - .find(|it| it.to == RegisterName("_loop_3_4_1_goto_4_in_6".to_string())) - .unwrap(); - assert_eq!(register_4_to_6_setting.operation, BinaryOperation::Equal); - assert_eq!(register_4_to_6_setting.operand1, 0.into()); - assert_eq!(register_4_to_6_setting.operand2, 1.into()); - let register_3_to_6_setting = bb6 - .content - .iter() - .filter_map(|it| it.try_as_binary_calculate()) - .find(|it| it.to == RegisterName("_loop_3_4_1_goto_3_in_6".to_string())) - .unwrap(); - assert_eq!(register_3_to_6_setting.operation, BinaryOperation::NotEqual); - assert_eq!(register_3_to_6_setting.operand1, 0.into()); - assert_eq!(register_3_to_6_setting.operand2, 1.into()); - let dispatcher_0 = editor - .content - .content - .iter() - .find(|it| { - it.name - .as_ref() - .map(|it| it == "_loop_3_4_1_dispatcher_at_7") - .unwrap_or(false) - }) - .unwrap(); - let loop_3_4_1_dispatcher_at_7_var = dispatcher_0 - .content - .iter() - .filter_map(|it| it.try_as_phi()) - .find(|it| it.to == RegisterName("_loop_3_4_1_dispatcher_at_7_var".to_string())) - .unwrap(); - let from_bb3 = loop_3_4_1_dispatcher_at_7_var - .from - .iter() - .find(|it| it.block == "bb3") - .unwrap(); - assert_eq!( - from_bb3.value, - RegisterName("_loop_3_4_1_goto_4_in_3".into()).into() - ); - let from_bb4 = loop_3_4_1_dispatcher_at_7_var - .from - .iter() - .find(|it| it.block == "bb4") - .unwrap(); - assert_eq!(from_bb4.value, 0.into()); - let from_bb2 = loop_3_4_1_dispatcher_at_7_var - .from - .iter() - .find(|it| it.block == "bb2") - .unwrap(); - assert_eq!(from_bb2.value, 0.into()); - let dispatch_statement0 = dispatcher_0.content.last().unwrap().as_branch(); - assert_eq!(dispatch_statement0.branch_type, BranchType::NE); - assert_eq!( - dispatch_statement0.operand1, - RegisterName("_loop_3_4_1_dispatcher_at_7_var".to_string()).into() - ); - assert_eq!(dispatch_statement0.operand2, 0.into()); - assert_eq!(dispatch_statement0.success_label, "bb4"); - assert_eq!( - dispatch_statement0.failure_label, - "_loop_3_4_1_dispatcher_at_8" - ); - } - - #[test] - fn nested_dispatcher() { - let function_definition = FunctionDefinition { - header: ir::FunctionHeader { - name: "f".to_string(), - parameters: Vec::new(), - return_type: data_type::Type::None, - }, - content: vec![ - BasicBlock { - name: Some("bb0".to_string()), - content: vec![branch("bb1", "bb2")], - }, - BasicBlock { - name: Some("bb1".to_string()), - content: vec![jump("bb2")], - }, - BasicBlock { - name: Some("bb2".to_string()), - content: vec![branch("bb3", "bb6")], - }, - BasicBlock { - name: Some("bb3".to_string()), - content: vec![branch("bb1", "bb4")], - }, - BasicBlock { - name: Some("bb4".to_string()), - content: vec![branch("bb1", "bb6")], - }, - BasicBlock { - name: Some("bb6".to_string()), - content: vec![jump("bb5")], - }, - BasicBlock { - name: Some("bb5".to_string()), - content: vec![jump("bb4")], - }, - ], - }; - let mut editor = Editor::new(function_definition); - let pass = FixIrreducible; - pass.run(&mut editor); - assert_eq!(editor.content.content.len(), 9); - } -} diff --git a/src/ir/optimize/pass/fix_irreducible_new/mod.rs b/src/ir/optimize/pass/fix_irreducible/mod.rs similarity index 99% rename from src/ir/optimize/pass/fix_irreducible_new/mod.rs rename to src/ir/optimize/pass/fix_irreducible/mod.rs index 071569d..3966384 100644 --- a/src/ir/optimize/pass/fix_irreducible_new/mod.rs +++ b/src/ir/optimize/pass/fix_irreducible/mod.rs @@ -353,7 +353,7 @@ impl IsPass for FixIrreducible { while let Some(irreducible_scc) = editor .binded_analyzer() .control_flow_graph() - .scc_new() + .top_level_scc() .first_irreducible_sub_scc() { let analyzer = editor.binded_analyzer(); diff --git a/src/ir/optimize/pass/fix_irreducible_new/tests.rs b/src/ir/optimize/pass/fix_irreducible/tests.rs similarity index 99% rename from src/ir/optimize/pass/fix_irreducible_new/tests.rs rename to src/ir/optimize/pass/fix_irreducible/tests.rs index f25ed4a..554832b 100644 --- a/src/ir/optimize/pass/fix_irreducible_new/tests.rs +++ b/src/ir/optimize/pass/fix_irreducible/tests.rs @@ -1,4 +1,4 @@ -use function::statement::{phi, BinaryCalculate}; +use function::statement::{phi, BinaryCalculate, Ret}; use crate::{ ir::{ @@ -448,7 +448,6 @@ fn test_fix_irreducible() { let mut editor = Editor::new(function_definition); let pass = FixIrreducible; pass.run(&mut editor); - println!("{}", editor.content); assert_eq!(editor.content.content.len(), 13); let guard1 = editor .content diff --git a/src/ir/optimize/pass/mod.rs b/src/ir/optimize/pass/mod.rs index 0ad99f3..533dfe2 100644 --- a/src/ir/optimize/pass/mod.rs +++ b/src/ir/optimize/pass/mod.rs @@ -1,5 +1,4 @@ mod fix_irreducible; -mod fix_irreducible_new; mod memory_to_register; mod remove_load_directly_after_store; mod remove_only_once_store; @@ -12,8 +11,9 @@ use memory_to_register::MemoryToRegister; use remove_load_directly_after_store::RemoveLoadDirectlyAfterStore; use remove_only_once_store::RemoveOnlyOnceStore; use remove_unused_register::RemoveUnusedRegister; -use std::str::FromStr; pub use topological_sort::TopologicalSort; + +use std::str::FromStr; /// This trait should be implemented by all passes which can do optimizing on ir function. #[enum_dispatch] pub trait IsPass { diff --git a/src/ir/optimize/pass/topological_sort.rs b/src/ir/optimize/pass/topological_sort.rs index b813304..d73d1cf 100644 --- a/src/ir/optimize/pass/topological_sort.rs +++ b/src/ir/optimize/pass/topological_sort.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use petgraph::prelude::*; use crate::ir::{ - analyzer::{BindedControlFlowGraph, IsAnalyzer, Scc}, + analyzer::{BindedControlFlowGraph, IsAnalyzer}, optimize::pass::fix_irreducible::FixIrreducible, }; @@ -17,8 +17,7 @@ impl IsPass for TopologicalSort { fn run(&self, editor: &mut crate::ir::editor::Editor) { let analyzer = editor.analyzer.bind(&editor.content); let graph = analyzer.control_flow_graph(); - let loops = graph.sccs(); - let content: Vec<_> = topological_order(&graph, &loops) + let content: Vec<_> = topological_order(&graph) .into_iter() .map(|it| mem::take(&mut editor.content.content[it])) .collect(); @@ -36,7 +35,6 @@ impl IsPass for TopologicalSort { fn topological_order_dfs( graph: &BindedControlFlowGraph, - top_level: &Scc, current_at: NodeIndex, visited: &mut Vec>, result: &mut Vec>, @@ -45,7 +43,9 @@ fn topological_order_dfs( return; } visited.push(current_at); - let in_loop = top_level.smallest_loop_node_in(current_at); + let in_loop = graph + .top_level_scc() + .smallest_non_trivial_scc_node_in(current_at.index()); let mut to_visit = graph .graph() .neighbors_directed(current_at, Direction::Outgoing) @@ -71,24 +71,23 @@ fn topological_order_dfs( return 2 + at_index; } // we should visit all nodes in this loop before the others - if let Some(in_loop) = in_loop && in_loop.is_node_in(*to_visit_node) { + if let Some(in_loop) = &in_loop && in_loop.contains(to_visit_node.index()) { return 1; } 0 }); for to_visit_node in to_visit { - topological_order_dfs(graph, top_level, to_visit_node, visited, result); + topological_order_dfs(graph, to_visit_node, visited, result); } result.push(current_at); } -pub fn topological_order(graph: &BindedControlFlowGraph, top_level: &Scc) -> Vec { +pub fn topological_order(graph: &BindedControlFlowGraph) -> Vec { let mut order = vec![]; let mut visited = vec![]; - topological_order_dfs(graph, top_level, 0.into(), &mut visited, &mut order); + topological_order_dfs(graph, 0.into(), &mut visited, &mut order); order.reverse(); let mut order: Vec = order.into_iter().map(NodeIndex::index).collect(); - dbg!(&order); let exit_block_position = order.iter().position_max().unwrap(); order.remove(exit_block_position); order diff --git a/src/lib.rs b/src/lib.rs index d602aa4..f1eb088 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ #![feature(let_chains)] #![feature(hash_drain_filter)] #![feature(exact_size_is_empty)] +// #![feature(arbitrary_self_types)] +#![feature(assert_matches)] /// Definitions of AST nodes and their parser. pub mod ast; /// Functions for generating assembly and binary code from ir. From 6c908029f30b7081caa8712c3ca9a6eb28afa427 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Fri, 26 Apr 2024 10:37:12 +0200 Subject: [PATCH 05/11] nested loop with same header --- Cargo.toml | 1 + src/backend/wasm/control_flow/mod.rs | 10 +- src/backend/wasm/lowering.rs | 248 ++++++++++++++++++ src/backend/wasm/mod.rs | 104 ++++++-- src/ir/editor/analyzer/control_flow/scc.rs | 121 ++++++++- src/ir/function/basic_block.rs | 17 +- src/ir/optimize/pass/fix_irreducible/tests.rs | 22 ++ src/utility/{graph.rs => graph/mod.rs} | 8 +- src/utility/graph/subgraph.rs | 65 +++++ 9 files changed, 559 insertions(+), 37 deletions(-) create mode 100644 src/backend/wasm/lowering.rs rename src/utility/{graph.rs => graph/mod.rs} (97%) create mode 100644 src/utility/graph/subgraph.rs diff --git a/Cargo.toml b/Cargo.toml index de4b494..1a04c50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ toml = "0.8.8" shadow-rs = { version = "0.26.0", optional = true } ezio = { version = "0.1.2", optional = true } delegate = "0.12.0" +wasm-encoder = "0.205.0" [dev-dependencies] cov-mark = "1.1.0" diff --git a/src/backend/wasm/control_flow/mod.rs b/src/backend/wasm/control_flow/mod.rs index 1167431..fb2af7e 100644 --- a/src/backend/wasm/control_flow/mod.rs +++ b/src/backend/wasm/control_flow/mod.rs @@ -9,7 +9,7 @@ pub enum ControlFlowElement { content: Vec, }, If { - condition: Box, // must be ControlFlowElement::Node + condition: Box, on_success: Vec, on_failure: Vec, }, @@ -103,13 +103,19 @@ impl ControlFlowElement { (ControlFlowElement::BasicBlock { .. }, _) => unreachable!(), } } - fn unwrap_node(&self) -> usize { + pub fn unwrap_node(&self) -> usize { if let Self::BasicBlock { id: node_id } = self { *node_id } else { unreachable!() } } + pub fn unwrap_content_mut(&mut self) -> &mut Vec { + match self { + Self::Block { content, .. } | Self::Loop { content, .. } => content, + _ => unreachable!(), + } + } fn exists(&self, element: &CFSelector) -> bool { if let Some((first, rest)) = element.clone().split_first() { let subcontent = match (self, first) { diff --git a/src/backend/wasm/lowering.rs b/src/backend/wasm/lowering.rs new file mode 100644 index 0000000..148aa73 --- /dev/null +++ b/src/backend/wasm/lowering.rs @@ -0,0 +1,248 @@ +use std::collections::HashMap; + +use itertools::Itertools; +use wasm_encoder::{Function, Instruction, ValType}; + +use crate::{ + ir::{ + function::basic_block::BasicBlock, + quantity::Quantity, + statement::{ + calculate::{binary, unary}, + IRStatement, + }, + FunctionHeader, RegisterName, + }, + utility::data_type::{Integer, Type}, +}; + +use super::control_flow::ControlFlowElement; +fn lower_type(t: &Type) -> ValType { + match t { + crate::utility::data_type::Type::Integer(Integer { signed, width }) => { + match (signed, width) { + (true, 32) => ValType::I32, + (true, 64) => ValType::I64, + _ => unimplemented!(), + } + } + _ => unimplemented!(), + } +} + +pub fn lower_function_type(header: &FunctionHeader) -> (Vec, Vec) { + let parameter_types = header + .parameters + .iter() + .map(|p| &p.data_type) + .map(lower_type) + .collect(); + let return_type = if header.return_type == Type::None { + vec![] + } else { + vec![lower_type(&header.return_type)] + }; + (parameter_types, return_type) +} + +fn put_value_onto_stack( + value: &Quantity, + register_name_id_map: &HashMap, + result: &mut Function, + data_type: ValType, +) { + match value { + crate::ir::quantity::Quantity::RegisterName(register) => { + let register_id = register_name_id_map[register]; + result.instruction(&Instruction::LocalGet(register_id)); + } + crate::ir::quantity::Quantity::NumberLiteral(n) => { + match data_type { + ValType::I32 => result.instruction(&Instruction::I32Const(*n as i32)), + ValType::I64 => result.instruction(&Instruction::I64Const(*n)), + _ => unimplemented!(), + }; + } + crate::ir::quantity::Quantity::GlobalVariableName(_) => unimplemented!(), + } +} + +fn lower_unary_calculate( + result: &mut Function, + register_name_id_map: &HashMap, + unary_calculate: &unary::UnaryCalculate, +) { + let data_type = lower_type(&unary_calculate.data_type); + match data_type { + ValType::I32 => result.instruction(&Instruction::I32Const(0)), + ValType::I64 => result.instruction(&Instruction::I64Const(0)), + _ => unimplemented!(), + }; + put_value_onto_stack( + &unary_calculate.operand, + register_name_id_map, + result, + data_type, + ); + match &unary_calculate.operation { + unary::UnaryOperation::Neg => match data_type { + ValType::I32 => result.instruction(&Instruction::I32Sub), + ValType::I64 => result.instruction(&Instruction::I64Sub), + _ => unimplemented!(), + }, + unary::UnaryOperation::Not => unimplemented!(), + }; + let result_register_id = register_name_id_map[&unary_calculate.to]; + result.instruction(&Instruction::LocalSet(result_register_id)); +} + +fn lower_binary_calculate( + result: &mut Function, + register_name_id_map: &HashMap, + binary_calculate: &binary::BinaryCalculate, +) { + let data_type = lower_type(&binary_calculate.data_type); + put_value_onto_stack( + &binary_calculate.operand1, + register_name_id_map, + result, + data_type, + ); + put_value_onto_stack( + &binary_calculate.operand2, + register_name_id_map, + result, + data_type, + ); + match &binary_calculate.operation { + binary::BinaryOperation::Add => match data_type { + ValType::I32 => result.instruction(&Instruction::I32Add), + ValType::I64 => result.instruction(&Instruction::I64Add), + _ => unimplemented!(), + }, + binary::BinaryOperation::LessThan => match data_type { + ValType::I32 => result.instruction(&Instruction::I32LtS), + ValType::I64 => result.instruction(&Instruction::I64LtS), + _ => unimplemented!(), + }, + binary::BinaryOperation::LessOrEqualThan => match data_type { + ValType::I32 => result.instruction(&Instruction::I32LeS), + ValType::I64 => result.instruction(&Instruction::I64LeS), + _ => unimplemented!(), + }, + binary::BinaryOperation::GreaterThan => match data_type { + ValType::I32 => result.instruction(&Instruction::I32GtS), + ValType::I64 => result.instruction(&Instruction::I64GtS), + _ => unimplemented!(), + }, + binary::BinaryOperation::GreaterOrEqualThan => match data_type { + ValType::I32 => result.instruction(&Instruction::I32GeS), + ValType::I64 => result.instruction(&Instruction::I64GeS), + _ => unimplemented!(), + }, + binary::BinaryOperation::Equal => match data_type { + ValType::I32 => result.instruction(&Instruction::I32Eq), + ValType::I64 => result.instruction(&Instruction::I64Eq), + _ => unimplemented!(), + }, + binary::BinaryOperation::NotEqual => match data_type { + ValType::I32 => result.instruction(&Instruction::I32Ne), + ValType::I64 => result.instruction(&Instruction::I64Ne), + _ => unimplemented!(), + }, + binary::BinaryOperation::Sub => match data_type { + ValType::I32 => result.instruction(&Instruction::I32Sub), + ValType::I64 => result.instruction(&Instruction::I64Sub), + _ => unimplemented!(), + }, + binary::BinaryOperation::Or => match data_type { + ValType::I32 => result.instruction(&Instruction::I32Or), + ValType::I64 => result.instruction(&Instruction::I64Or), + _ => unimplemented!(), + }, + binary::BinaryOperation::Xor => match data_type { + ValType::I32 => result.instruction(&Instruction::I32Xor), + ValType::I64 => result.instruction(&Instruction::I64Xor), + _ => unimplemented!(), + }, + binary::BinaryOperation::And => match data_type { + ValType::I32 => result.instruction(&Instruction::I32And), + ValType::I64 => result.instruction(&Instruction::I64And), + _ => unimplemented!(), + }, + binary::BinaryOperation::LogicalShiftLeft => match data_type { + ValType::I32 => result.instruction(&Instruction::I32Shl), + ValType::I64 => result.instruction(&Instruction::I64Shl), + _ => unimplemented!(), + }, + binary::BinaryOperation::LogicalShiftRight => match data_type { + ValType::I32 => result.instruction(&Instruction::I32ShrU), + ValType::I64 => result.instruction(&Instruction::I64ShrU), + _ => unimplemented!(), + }, + binary::BinaryOperation::AthematicShiftRight => match data_type { + ValType::I32 => result.instruction(&Instruction::I32ShrS), + ValType::I64 => result.instruction(&Instruction::I64ShrS), + _ => unimplemented!(), + }, + }; + let result_register_id = register_name_id_map[&binary_calculate.to]; + result.instruction(&Instruction::LocalSet(result_register_id)); +} + +fn lower_statement( + result: &mut Function, + register_name_id_map: &HashMap, + statement: &IRStatement, +) { + match statement { + IRStatement::UnaryCalculate(unary_calculate) => { + lower_unary_calculate(result, register_name_id_map, unary_calculate) + } + IRStatement::BinaryCalculate(binary_calculate) => { + lower_binary_calculate(result, register_name_id_map, binary_calculate) + } + IRStatement::Branch(_) => todo!(), + IRStatement::Jump(_) => todo!(), + IRStatement::Ret(_) => todo!(), + + IRStatement::Phi(_) => unimplemented!(), + IRStatement::Alloca(_) => unimplemented!(), + IRStatement::Call(_) => unimplemented!(), + IRStatement::Load(_) => unimplemented!(), + IRStatement::Store(_) => unimplemented!(), + IRStatement::LoadField(_) => unimplemented!(), + IRStatement::SetField(_) => unimplemented!(), + } +} + +fn lower_basic_block(result: &mut Function, block: &BasicBlock) { + for statement in &block.content {} +} + +fn lower_control_flow_element( + result: &mut Function, + body: &[BasicBlock], + element: &ControlFlowElement, +) { + match element { + ControlFlowElement::Block { content } => todo!(), + ControlFlowElement::If { + condition, + on_success, + on_failure, + } => todo!(), + ControlFlowElement::Loop { content } => todo!(), + ControlFlowElement::BasicBlock { id } => todo!(), + } +} + +pub fn lower_function_body(body: &[BasicBlock], control_flow: &[ControlFlowElement]) -> Function { + let locals = body + .iter() + .flat_map(|block| block.created_registers()) + .collect_vec(); + let mut function = Function::new_with_locals_types(locals.iter().map(|(_, t)| lower_type(t))); + for control_flow_element in control_flow {} + function +} diff --git a/src/backend/wasm/mod.rs b/src/backend/wasm/mod.rs index 5af593e..a831b85 100644 --- a/src/backend/wasm/mod.rs +++ b/src/backend/wasm/mod.rs @@ -3,20 +3,29 @@ use std::{ collections::VecDeque, fmt, iter::zip, + mem, ops::{Index, IndexMut}, path::Display, }; +use wasm_encoder::{CodeSection, ExportKind, ExportSection, FunctionSection, Module, TypeSection}; -use crate::ir::{ - analyzer::{BindedControlFlowGraph, BindedScc, ControlFlowGraph, IsAnalyzer}, - statement::IRStatement, - FunctionDefinition, +use crate::{ + ast::function_definition, + ir::{ + analyzer::{BindedControlFlowGraph, BindedScc, ControlFlowGraph, IsAnalyzer}, + editor::Analyzer, + statement::IRStatement, + FunctionDefinition, + }, }; -use self::control_flow::{CFSelector, ControlFlowElement}; +use self::{ + control_flow::{CFSelector, ControlFlowElement}, + lowering::lower_function_type, +}; mod control_flow; - +mod lowering; // fixme: currently this presumes that we have not folded any if-else or block before fn fold_loop( function_content: &FunctionDefinition, @@ -79,8 +88,6 @@ fn fold_if_else_once( } let predecessor_selector = content.find_node(predecessor_block_id).unwrap(); let block_selector = content.find_node(block_id).unwrap(); - let block_element = &content[&block_selector]; - let block_element_id = block_element.first_basic_block_id(); let if_element_selector = if predecessor_selector.is_if_condition() { // `predecessor_element` is already an if condition // in such cases, it's possible that: @@ -139,16 +146,6 @@ fn fold_if_else_once( false } -fn fold_if_else(fd: &FunctionDefinition, content: &mut ControlFlowElement) { - loop { - let cfg = ControlFlowGraph::new(); - let control_flow_graph = cfg.bind(fd); - if fold_if_else_once(content, control_flow_graph) { - break; - } - } -} - fn collect_to_move( root_element: &ControlFlowElement, first_to_move_node_selector: &CFSelector, @@ -178,11 +175,59 @@ fn collect_to_move( to_move } +fn fold_if_else(function_definition: &FunctionDefinition, content: &mut ControlFlowElement) { + loop { + let cfg = ControlFlowGraph::new(); + let control_flow_graph = cfg.bind(function_definition); + if fold_if_else_once(content, control_flow_graph) { + break; + } + } +} + +fn fold(function_definition: &FunctionDefinition) -> Vec { + let analyzer = Analyzer::new(); + let binded = analyzer.bind(&function_definition); + let control_flow_graph = binded.control_flow_graph(); + let current_result = (0..(function_definition.content.len())) + .map(ControlFlowElement::new_node) + .collect_vec(); + + let mut content = ControlFlowElement::new_block(current_result); + let control_flow_graph = binded.control_flow_graph(); + let root_scc = control_flow_graph.top_level_scc(); + fold_loop(function_definition, &root_scc, content.unwrap_content_mut()); + fold_if_else(function_definition, &mut content); + mem::take(content.unwrap_content_mut()) +} + +fn generate_function( + result: ( + &mut TypeSection, + &mut FunctionSection, + &mut ExportSection, + &mut CodeSection, + ), + function_definition: &FunctionDefinition, + control_flow: &[ControlFlowElement], +) { + let function_index = result.0.len(); + let (param_type, return_type) = lower_function_type(&function_definition.header); + result.0.function(param_type, return_type); + result.1.function(function_index); + result.2.export( + &function_definition.header.name, + ExportKind::Func, + function_index, + ); +} + #[cfg(test)] mod tests { - use std::{assert_matches::assert_matches, str::FromStr}; + use std::{assert_matches::assert_matches, fs, str::FromStr}; use analyzer::Analyzer; + use wasm_encoder::{TypeSection, ValType}; use crate::{ ir::{ @@ -489,4 +534,25 @@ mod tests { fold_loop(&function_definition, &scc, &mut current_result); dbg!(current_result); } + + #[test] + fn test_fold_all() { + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + jump_block(0, 1), + jump_block(1, 2), + jump_block(2, 3), + branch_block(3, 4, 1), + branch_block(4, 1, 5), + ret_block(5), + ], + }; + let result = fold(&function_definition); + dbg!(result); + } } diff --git a/src/ir/editor/analyzer/control_flow/scc.rs b/src/ir/editor/analyzer/control_flow/scc.rs index 66ee8ea..f9079cc 100644 --- a/src/ir/editor/analyzer/control_flow/scc.rs +++ b/src/ir/editor/analyzer/control_flow/scc.rs @@ -1,10 +1,17 @@ -use crate::utility::graph::kosaraju_scc_with_filter; +use std::fmt; + +use crate::utility::graph::{kosaraju_scc_with_filter, subgraph::SubGraph}; use delegate::delegate; use itertools::Itertools; -use petgraph::{graph, prelude::*}; +use petgraph::{ + algo::all_simple_paths, + graph, + prelude::*, + visit::{IntoNeighborsDirected, NodeFiltered, NodeRef}, +}; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Scc { pub nodes: Vec, pub top_level: bool, @@ -77,15 +84,30 @@ impl Scc { None } else { let entry_node = entry_nodes[0]; - let backedges: Vec<_> = graph - .edges_directed(entry_node.into(), Incoming) - .map(|it| it.id()) - .collect(); + let node_filtered = + NodeFiltered::from_fn(graph, |node| self.nodes.contains(&node.index())); + let subgraph = SubGraph(node_filtered); + let largest_simple_loop = subgraph + .neighbors_directed(entry_node.into(), Incoming) + .flat_map(|pred| { + all_simple_paths::, _>(&subgraph, entry_node.into(), pred, 1, None) + .max_by(|a, b| a.len().cmp(&b.len())) + }) + .max_by(|a, b| a.len().cmp(&b.len())); + dbg!(&largest_simple_loop); + let backedge = if let Some(mut largest_simple_loops) = largest_simple_loop { + let last_node = largest_simple_loops.pop().unwrap(); + graph.find_edge(last_node, entry_node.into()) + } else { + None + }; + let backedge_info = backedge.and_then(|e| graph.edge_endpoints(e)); + println!("entry: {:?}, {:?}", entry_node, backedge_info); let sccs = kosaraju_scc_with_filter( graph, entry_nodes[0].into(), |node| self.nodes.contains(&node.index()), - |edge| !backedges.contains(&edge), + |edge| Some(edge) != backedge, ); let result = sccs .into_iter() @@ -142,12 +164,36 @@ impl Scc { } } -#[derive(Clone, Debug)] +impl fmt::Display for Scc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.nodes) + } +} + +impl fmt::Debug for Scc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.nodes) + } +} + +#[derive(Clone)] pub struct BindedScc<'bind> { graph: &'bind DiGraph<(), (), usize>, item: Scc, } +impl fmt::Display for BindedScc<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.item.fmt(f) + } +} + +impl fmt::Debug for BindedScc<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.item.fmt(f) + } +} + impl<'bind> BindedScc<'bind> { pub fn new(graph: &'bind DiGraph<(), (), usize>, item: Scc) -> Self { Self { graph, item } @@ -225,7 +271,60 @@ mod tests { graph.add_edge(node_10, node_4, ()); let scc = Scc::new(0..10, true); let scc = BindedScc::new(&graph, scc); - dbg!(scc.top_level_sccs()); - dbg!(scc.first_irreducible_sub_scc()); + println!("{:?}", scc.top_level_sccs()); + println!("{:?}", scc.first_irreducible_sub_scc()); + } + + #[test] + fn test_top_level_scc_recursive() { + let mut graph: DiGraph<_, _, usize> = DiGraph::default(); + let node_0 = graph.add_node(()); + let node_1 = graph.add_node(()); + let node_2 = graph.add_node(()); + let node_3 = graph.add_node(()); + let node_4 = graph.add_node(()); + let node_5 = graph.add_node(()); + graph.add_edge(node_0, node_1, ()); + graph.add_edge(node_1, node_2, ()); + graph.add_edge(node_2, node_3, ()); + graph.add_edge(node_3, node_4, ()); + graph.add_edge(node_4, node_1, ()); + graph.add_edge(node_3, node_1, ()); + graph.add_edge(node_4, node_5, ()); + let scc = Scc::new(0..6, true); + let scc = BindedScc::new(&graph, scc); + let top_level_sccs = scc.top_level_sccs().unwrap(); + println!("{:?}", &top_level_sccs); + let contains_recursive = &top_level_sccs[1]; + println!("{:?}", contains_recursive.top_level_sccs()); + } + + #[test] + fn test_top_level_scc_recursive2() { + let mut graph: DiGraph<_, _, usize> = DiGraph::default(); + let node_0 = graph.add_node(()); + let node_1 = graph.add_node(()); + let node_2 = graph.add_node(()); + let node_3 = graph.add_node(()); + let node_4 = graph.add_node(()); + let node_5 = graph.add_node(()); + let node_6 = graph.add_node(()); + let node_7 = graph.add_node(()); + graph.add_edge(node_0, node_1, ()); + graph.add_edge(node_1, node_2, ()); + graph.add_edge(node_2, node_3, ()); + graph.add_edge(node_3, node_1, ()); + graph.add_edge(node_1, node_4, ()); + graph.add_edge(node_4, node_5, ()); + graph.add_edge(node_5, node_1, ()); + graph.add_edge(node_3, node_6, ()); + graph.add_edge(node_2, node_4, ()); + graph.add_edge(node_4, node_7, ()); + let scc = Scc::new(0..8, true); + let scc = BindedScc::new(&graph, scc); + let top_level_sccs = scc.top_level_sccs().unwrap(); + println!("{:?}", top_level_sccs); + let contains_recursive = &top_level_sccs[1]; + println!("{:?}", contains_recursive.top_level_sccs()); } } diff --git a/src/ir/function/basic_block.rs b/src/ir/function/basic_block.rs index 9234c15..078b4ba 100644 --- a/src/ir/function/basic_block.rs +++ b/src/ir/function/basic_block.rs @@ -1,4 +1,7 @@ -use crate::utility::parsing; +use crate::{ + ir::RegisterName, + utility::{data_type::Type, parsing}, +}; use nom::{ branch::alt, bytes::complete::tag, @@ -10,7 +13,10 @@ use nom::{ }; use std::fmt; -use super::statement::{self, IRStatement}; +use super::{ + statement::{self, IRStatement}, + IsIRStatement, +}; /// A basic block. #[derive(Debug, Eq, PartialEq, Clone, Hash, Default)] @@ -46,6 +52,13 @@ impl BasicBlock { pub fn is_branch(&self) -> bool { matches!(self.content.last(), Some(IRStatement::Branch(_))) } + + pub fn created_registers(&self) -> Vec<(RegisterName, Type)> { + self.content + .iter() + .flat_map(|it| it.generate_register()) + .collect() + } } impl fmt::Display for BasicBlock { diff --git a/src/ir/optimize/pass/fix_irreducible/tests.rs b/src/ir/optimize/pass/fix_irreducible/tests.rs index 554832b..e456218 100644 --- a/src/ir/optimize/pass/fix_irreducible/tests.rs +++ b/src/ir/optimize/pass/fix_irreducible/tests.rs @@ -480,3 +480,25 @@ fn test_fix_irreducible() { "_guard_block_scc_3_5_7_for_bb3" ); } + +#[test] +fn test_shit() { + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + branch_block(1, 2, 3), + branch_block(2, 3, 1), + branch_block(3, 1, 2), + jump_block(0, 1), + ], + }; + println!("{}", function_definition); + let mut editor = Editor::new(function_definition); + let pass = FixIrreducible; + pass.run(&mut editor); + println!("{}", editor.content); +} diff --git a/src/utility/graph.rs b/src/utility/graph/mod.rs similarity index 97% rename from src/utility/graph.rs rename to src/utility/graph/mod.rs index 147ba60..c23060c 100644 --- a/src/utility/graph.rs +++ b/src/utility/graph/mod.rs @@ -1,14 +1,16 @@ -use std::collections::HashMap; +use std::{collections::HashMap, ops::Sub}; use petgraph::{ algo::dominators::Dominators, + graph::DiGraph, visit::{ - EdgeRef, GraphBase, IntoEdgesDirected, IntoNeighbors, IntoNeighborsDirected, - IntoNodeIdentifiers, VisitMap, Visitable, + EdgeRef, FilterNode, GraphBase, GraphRef, IntoEdgesDirected, IntoNeighbors, + IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount, NodeFiltered, VisitMap, Visitable, }, Direction, }; use std::{fmt::Debug, hash::Hash}; +pub mod subgraph; /// Initially implemented bu @m4b in [petgraph#178](https://github.com/petgraph/petgraph/pull/178). /// /// This function will return dominance frontiers of a graph, diff --git a/src/utility/graph/subgraph.rs b/src/utility/graph/subgraph.rs new file mode 100644 index 0000000..a0f8eeb --- /dev/null +++ b/src/utility/graph/subgraph.rs @@ -0,0 +1,65 @@ +use std::fmt::{self, Debug}; + +use itertools::Itertools; +use petgraph::{ + graph::DiGraph, + visit::{ + FilterNode, GraphBase, GraphRef, IntoNeighbors, IntoNeighborsDirected, IntoNodeIdentifiers, + IntoNodeReferences, NodeCount, NodeFiltered, + }, + Direction, +}; + +type CFGraph = DiGraph<(), (), usize>; + +type FilteredCFGraph<'a, F> = NodeFiltered<&'a CFGraph, F>; + +#[derive(Copy, Clone)] +pub struct SubGraph<'a, F: FilterNode<::NodeId>>(pub FilteredCFGraph<'a, F>); + +impl::NodeId>> fmt::Debug for SubGraph<'_, F> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let nodes = self + .0 + .node_references() + .filter(|(it, _)| self.0 .1.include_node(*it)) + .collect_vec(); + f.debug_tuple("SubGraph").field(&nodes).finish() + } +} + +impl<'a, F: FilterNode<::NodeId>> GraphBase for SubGraph<'a, F> { + type EdgeId = as GraphBase>::EdgeId; + + type NodeId = as GraphBase>::NodeId; +} + +impl<'a, F: FilterNode<::NodeId>> NodeCount for &'a SubGraph<'a, F> { + fn node_count(self: &Self) -> usize { + self.0 + .node_references() + .filter(|(it, _)| self.0 .1.include_node(*it)) + .count() + } +} + +impl<'a, F: Copy + FilterNode<::NodeId>> GraphRef for SubGraph<'a, F> {} + +impl<'a, F: FilterNode<::NodeId>> IntoNeighbors for &'a SubGraph<'a, F> { + type Neighbors = <&'a FilteredCFGraph<'a, F> as IntoNeighbors>::Neighbors; + + fn neighbors(self, a: Self::NodeId) -> Self::Neighbors { + self.0.neighbors(a) + } +} + +impl<'a, F: FilterNode<::NodeId>> IntoNeighborsDirected + for &'a SubGraph<'a, F> +{ + type NeighborsDirected = + <&'a FilteredCFGraph<'a, F> as IntoNeighborsDirected>::NeighborsDirected; + + fn neighbors_directed(self, n: Self::NodeId, d: Direction) -> Self::NeighborsDirected { + self.0.neighbors_directed(n, d) + } +} From 4ec82610007744bc003083aee9f98d29b2c82249 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Wed, 1 May 2024 16:31:15 +0200 Subject: [PATCH 06/11] done generate_function --- src/backend/wasm/control_flow/mod.rs | 7 + src/backend/wasm/control_flow/selector.rs | 104 ++++++- src/backend/wasm/lowering.rs | 287 ++++++++++++++++- src/backend/wasm/mod.rs | 21 +- src/ir/editor/analyzer/control_flow/mod.rs | 18 +- src/ir/editor/analyzer/control_flow/scc.rs | 294 +++++++++--------- src/ir/optimize/pass/fix_irreducible/tests.rs | 2 +- src/utility/graph/subgraph.rs | 166 +++++++--- 8 files changed, 671 insertions(+), 228 deletions(-) diff --git a/src/backend/wasm/control_flow/mod.rs b/src/backend/wasm/control_flow/mod.rs index fb2af7e..539bf4c 100644 --- a/src/backend/wasm/control_flow/mod.rs +++ b/src/backend/wasm/control_flow/mod.rs @@ -263,6 +263,13 @@ impl ControlFlowElement { first_result?.get(&rest) } } + pub fn block_content(&self) -> Option<&Vec> { + if let ControlFlowElement::Block { content } = self { + Some(content) + } else { + None + } + } } impl IndexMut<&CFSelector> for ControlFlowElement { diff --git a/src/backend/wasm/control_flow/selector.rs b/src/backend/wasm/control_flow/selector.rs index 4346a65..239e652 100644 --- a/src/backend/wasm/control_flow/selector.rs +++ b/src/backend/wasm/control_flow/selector.rs @@ -1,7 +1,7 @@ use delegate::delegate; use std::{ - collections::VecDeque, fmt, iter::zip, num::ParseIntError, ops::RangeBounds, result, - str::FromStr, + cmp::Ordering, collections::VecDeque, fmt, iter::zip, num::ParseIntError, ops::RangeBounds, + result, str::FromStr, }; #[derive(Clone, PartialEq, Eq)] @@ -17,8 +17,8 @@ impl fmt::Display for CFSelectorSegment { match self { CFSelectorSegment::ContentAtIndex(index) => write!(f, "{}", index), CFSelectorSegment::IfCondition => write!(f, "if_condition"), - CFSelectorSegment::IndexInSuccess(index) => write!(f, "success/{}", index), - CFSelectorSegment::IndexInFailure(index) => write!(f, "failure/{}", index), + CFSelectorSegment::IndexInSuccess(index) => write!(f, "success->{}", index), + CFSelectorSegment::IndexInFailure(index) => write!(f, "failure->{}", index), } } } @@ -35,12 +35,12 @@ impl FromStr for CFSelectorSegment { fn from_str(s: &str) -> Result { if s == "if_condition" { Ok(CFSelectorSegment::IfCondition) - } else if s.starts_with("success/") { - let index_str = s.strip_prefix("success/").unwrap(); + } else if s.starts_with("success->") { + let index_str = s.strip_prefix("success->").unwrap(); let value = index_str.parse()?; Ok(CFSelectorSegment::IndexInSuccess(value)) - } else if s.starts_with("failure/") { - let index_str = s.strip_prefix("failure/").unwrap(); + } else if s.starts_with("failure->") { + let index_str = s.strip_prefix("failure->").unwrap(); let value = index_str.parse()?; Ok(CFSelectorSegment::IndexInFailure(value)) } else { @@ -50,6 +50,36 @@ impl FromStr for CFSelectorSegment { } } +impl PartialOrd for CFSelectorSegment { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (CFSelectorSegment::ContentAtIndex(i), CFSelectorSegment::ContentAtIndex(j)) + | (CFSelectorSegment::IndexInSuccess(i), CFSelectorSegment::IndexInSuccess(j)) + | (CFSelectorSegment::IndexInFailure(i), CFSelectorSegment::IndexInFailure(j)) => { + i.partial_cmp(j) + } + (CFSelectorSegment::IfCondition, CFSelectorSegment::IfCondition) => { + Some(Ordering::Equal) + } + ( + CFSelectorSegment::IfCondition, + CFSelectorSegment::IndexInSuccess(_) | CFSelectorSegment::IndexInFailure(_), + ) => Some(Ordering::Less), + ( + CFSelectorSegment::IndexInFailure(_) | CFSelectorSegment::IndexInSuccess(_), + CFSelectorSegment::IfCondition, + ) => Some(Ordering::Greater), + (CFSelectorSegment::IndexInSuccess(_), CFSelectorSegment::IndexInFailure(_)) => { + Some(Ordering::Less) + } + (CFSelectorSegment::IndexInFailure(_), CFSelectorSegment::IndexInSuccess(_)) => { + Some(Ordering::Greater) + } + _ => None, + } + } +} + #[derive(Clone, Default, PartialEq, Eq)] pub struct CFSelector(VecDeque); @@ -84,14 +114,8 @@ impl FromStr for CFSelector { let mut result = VecDeque::new(); let mut parts = s.split("/"); while let Some(next_part) = parts.next() { - if next_part.starts_with("success") || next_part.starts_with("failure") { - let next_next_part = parts.next().unwrap(); - let segment = CFSelectorSegment::from_str(&[next_part, next_next_part].join("/"))?; - result.push_back(segment); - } else { - let segment = CFSelectorSegment::from_str(next_part)?; - result.push_back(segment); - } + let segment = CFSelectorSegment::from_str(next_part)?; + result.push_back(segment); } Ok(Self(result)) } @@ -102,7 +126,7 @@ impl CFSelector { Self(VecDeque::new()) } - pub(super) fn from_segment(segment: CFSelectorSegment) -> Self { + pub fn from_segment(segment: CFSelectorSegment) -> Self { let mut result = VecDeque::new(); result.push_back(segment); Self(result) @@ -192,4 +216,50 @@ impl CFSelector { pub fn range>(&self, range: R) -> Self { Self(self.0.range(range).cloned().collect()) } + + pub fn is_sibling(selector: &CFSelector, last_selector: &CFSelector) -> bool { + if selector.len() != last_selector.len() { + false + } else { + let shared_part = Self::lowest_common_ancestor(selector, last_selector); + if shared_part.len() == selector.len() - 1 { + true + } else { + false + } + } + } + + pub fn block_like_count(&self) -> usize { + self.0 + .iter() + .filter(|it| matches!(it, CFSelectorSegment::ContentAtIndex(_))) + .count() + } + + pub fn is_after(&self, other: &CFSelector) -> Option { + for (from_self, from_other) in self.0.iter().zip(other.0.iter()) { + if from_other < from_self { + return Some(false); + } else if from_other > from_self { + return Some(true); + } + } + None + } + + pub fn levels_before(&self, other: &CFSelector) -> Option { + if self.is_after(other).unwrap_or(false) { + return None; + } + let shared_part = Self::lowest_common_ancestor(self, other); + let self_unique_part = self.range(shared_part.len()..); + Some( + self_unique_part + .0 + .into_iter() + .filter(|it| matches!(it, CFSelectorSegment::ContentAtIndex(_))) + .count(), + ) + } } diff --git a/src/backend/wasm/lowering.rs b/src/backend/wasm/lowering.rs index 148aa73..c6c77cd 100644 --- a/src/backend/wasm/lowering.rs +++ b/src/backend/wasm/lowering.rs @@ -1,22 +1,25 @@ use std::collections::HashMap; use itertools::Itertools; -use wasm_encoder::{Function, Instruction, ValType}; +use wasm_encoder::{BlockType, Function, Instruction, ValType}; use crate::{ ir::{ + analyzer::BindedControlFlowGraph, function::basic_block::BasicBlock, quantity::Quantity, statement::{ + branch::BranchType, calculate::{binary, unary}, - IRStatement, + Branch, IRStatement, }, FunctionHeader, RegisterName, }, utility::data_type::{Integer, Type}, }; -use super::control_flow::ControlFlowElement; +use super::control_flow::{CFSelector, CFSelectorSegment, ControlFlowElement}; + fn lower_type(t: &Type) -> ValType { match t { crate::utility::data_type::Type::Integer(Integer { signed, width }) => { @@ -194,7 +197,12 @@ fn lower_statement( result: &mut Function, register_name_id_map: &HashMap, statement: &IRStatement, + bb_id: usize, + cfe_root: &ControlFlowElement, + register_type: &HashMap, + cfg: &BindedControlFlowGraph, ) { + let current = cfe_root.find_node(bb_id).unwrap(); match statement { IRStatement::UnaryCalculate(unary_calculate) => { lower_unary_calculate(result, register_name_id_map, unary_calculate) @@ -202,9 +210,57 @@ fn lower_statement( IRStatement::BinaryCalculate(binary_calculate) => { lower_binary_calculate(result, register_name_id_map, binary_calculate) } - IRStatement::Branch(_) => todo!(), - IRStatement::Jump(_) => todo!(), - IRStatement::Ret(_) => todo!(), + IRStatement::Branch(branch_statement) => { + // currently there exists 3 kinds of branch target: + // 1. if-else: this kind is already folded into `if-else` + // 2. loop, branch to loop header + // 3. loop, branch out of loop= + if current.is_if_condition() { + // in such case, the branch target is already folded into `if-else` + // thus, both branch targets are nested into if-else, and since we generate if-else + // block directly after this statement, we just need to generate the condition and put + // it onto the stack + generate_if_condition( + branch_statement, + register_type, + register_name_id_map, + result, + ); + result.instruction(&Instruction::If(BlockType::Empty)); + } else { + let success_target = cfg.basic_block_index_by_name(&branch_statement.success_label); + let success_target_selector = cfe_root.find_node(success_target).unwrap(); + let failure_target = cfg.basic_block_index_by_name(&branch_statement.failure_label); + let failure_target_selector = cfe_root.find_node(failure_target).unwrap(); + if let Some(levels) = success_target_selector.levels_before(¤t) { + // branch back on success + result.instruction(&Instruction::BrIf(levels as u32)); + if let Some(levels) = failure_target_selector.levels_before(¤t) { + // branch back, maybe into a different loop + result.instruction(&Instruction::BrIf(levels as u32)); + } + // or just fallthrough + } else { + // fallthrough on success + // if failure is branch back, swap success and failure + if let Some(levels) = failure_target_selector.levels_before(¤t) { + result.instruction(&Instruction::I32Eqz); + result.instruction(&Instruction::BrIf(levels as u32)); + } + // or both are fallthrough + } + } + } + IRStatement::Jump(jump_statement) => { + let jump_target = cfg.basic_block_index_by_name(&jump_statement.label); + let jump_target_selector = cfe_root.find_node(jump_target).unwrap(); + if let Some(levels) = jump_target_selector.levels_before(¤t) { + result.instruction(&Instruction::BrIf(levels as u32)); + } + } + IRStatement::Ret(_) => { + result.instruction(&Instruction::Return); + } IRStatement::Phi(_) => unimplemented!(), IRStatement::Alloca(_) => unimplemented!(), @@ -216,33 +272,236 @@ fn lower_statement( } } -fn lower_basic_block(result: &mut Function, block: &BasicBlock) { - for statement in &block.content {} +fn generate_if_condition( + branch_statement: &Branch, + register_type: &HashMap, + register_name_id_map: &HashMap, + result: &mut Function, +) { + let data_type = decide_branch_operation_type(&branch_statement, register_type); + put_value_onto_stack( + &branch_statement.operand1, + register_name_id_map, + result, + data_type, + ); + put_value_onto_stack( + &branch_statement.operand2, + register_name_id_map, + result, + data_type, + ); + match branch_statement.branch_type { + BranchType::EQ => match data_type { + ValType::I32 => result.instruction(&Instruction::I32Eq), + ValType::I64 => result.instruction(&Instruction::I64Eq), + _ => unimplemented!(), + }, + BranchType::NE => match data_type { + ValType::I32 => result.instruction(&Instruction::I32Ne), + ValType::I64 => result.instruction(&Instruction::I64Ne), + _ => unimplemented!(), + }, + BranchType::LT => match data_type { + ValType::I32 => result.instruction(&Instruction::I32LtS), + ValType::I64 => result.instruction(&Instruction::I64LtS), + _ => unimplemented!(), + }, + BranchType::GE => match data_type { + ValType::I32 => result.instruction(&Instruction::I32GeS), + ValType::I64 => result.instruction(&Instruction::I64GeS), + _ => unimplemented!(), + }, + }; +} + +fn decide_branch_operation_type( + branch_statement: &Branch, + register_type: &HashMap, +) -> ValType { + let operand1 = &branch_statement.operand1; + let operand2 = &branch_statement.operand2; + let data_type = match (operand1, operand2) { + (Quantity::RegisterName(register), _) | (_, Quantity::RegisterName(register)) => { + ®ister_type[register] + } + (Quantity::NumberLiteral(_), Quantity::NumberLiteral(_)) => &Type::Integer(Integer { + signed: true, + width: 64, + }), + _ => unimplemented!(), + }; + lower_type(data_type) +} + +fn lower_basic_block( + result: &mut Function, + bb_id: usize, + block: &BasicBlock, + selector: CFSelector, + binded_cfg: &BindedControlFlowGraph, + register_name_id_map: &HashMap, + cfe_root: &ControlFlowElement, + register_type: &HashMap, +) { + for statement in &block.content { + lower_statement( + result, + register_name_id_map, + statement, + bb_id, + cfe_root, + register_type, + binded_cfg, + ) + } } fn lower_control_flow_element( result: &mut Function, body: &[BasicBlock], element: &ControlFlowElement, + current: CFSelector, + binded_cfg: &BindedControlFlowGraph, + register_name_id_map: &HashMap, + cfe_root: &ControlFlowElement, + register_type: &HashMap, ) { match element { - ControlFlowElement::Block { content } => todo!(), + ControlFlowElement::Block { content } => { + result.instruction(&Instruction::Block(BlockType::Empty)); + for (i, block) in content.iter().enumerate() { + let mut new_selector = current.clone(); + new_selector.push_back(CFSelectorSegment::ContentAtIndex(i)); + lower_control_flow_element( + result, + body, + block, + new_selector, + binded_cfg, + register_name_id_map, + cfe_root, + register_type, + ); + } + result.instruction(&Instruction::End); + } ControlFlowElement::If { condition, on_success, on_failure, - } => todo!(), - ControlFlowElement::Loop { content } => todo!(), - ControlFlowElement::BasicBlock { id } => todo!(), + } => { + let mut new_selector = current.clone(); + new_selector.push_back(CFSelectorSegment::IfCondition); + lower_control_flow_element( + result, + body, + &condition, + new_selector, + binded_cfg, + register_name_id_map, + cfe_root, + register_type, + ); + result.instruction(&Instruction::If(BlockType::Empty)); + for (i, success_block) in on_success.iter().enumerate() { + let mut new_selector = current.clone(); + new_selector.push_back(CFSelectorSegment::IndexInSuccess(i)); + lower_control_flow_element( + result, + body, + success_block, + new_selector, + binded_cfg, + register_name_id_map, + cfe_root, + register_type, + ); + } + if on_failure.len() != 0 { + result.instruction(&Instruction::Else); + for (i, failure_block) in on_failure.iter().enumerate() { + let mut new_selector = current.clone(); + new_selector.push_back(CFSelectorSegment::IndexInFailure(i)); + lower_control_flow_element( + result, + body, + failure_block, + new_selector, + binded_cfg, + register_name_id_map, + cfe_root, + register_type, + ); + } + } + result.instruction(&Instruction::End); + } + ControlFlowElement::Loop { content } => { + result.instruction(&Instruction::Loop(BlockType::Empty)); + for (i, block) in content.iter().enumerate() { + let mut new_selector = current.clone(); + new_selector.push_back(CFSelectorSegment::ContentAtIndex(i)); + lower_control_flow_element( + result, + body, + block, + new_selector, + binded_cfg, + register_name_id_map, + cfe_root, + register_type, + ); + } + result.instruction(&Instruction::End); + } + ControlFlowElement::BasicBlock { id } => { + lower_basic_block( + result, + *id, + &body[*id], + current, + binded_cfg, + register_name_id_map, + cfe_root, + register_type, + ); + } } } -pub fn lower_function_body(body: &[BasicBlock], control_flow: &[ControlFlowElement]) -> Function { +pub fn lower_function_body( + body: &[BasicBlock], + control_flow_root: &ControlFlowElement, + binded_cfg: &BindedControlFlowGraph, +) -> Function { let locals = body .iter() .flat_map(|block| block.created_registers()) .collect_vec(); + let locals_name_type_map: HashMap = locals.iter().cloned().collect(); + let register_name_id_map: HashMap = locals + .iter() + .enumerate() + .map(|(a, (b, _))| (b.clone(), a as u32)) + .collect(); let mut function = Function::new_with_locals_types(locals.iter().map(|(_, t)| lower_type(t))); - for control_flow_element in control_flow {} + for (i, control_flow_element) in control_flow_root + .block_content() + .unwrap() + .iter() + .enumerate() + { + lower_control_flow_element( + &mut function, + body, + control_flow_element, + CFSelector::from_segment(CFSelectorSegment::ContentAtIndex(i)), + binded_cfg, + ®ister_name_id_map, + control_flow_root, + &locals_name_type_map, + ); + } function } diff --git a/src/backend/wasm/mod.rs b/src/backend/wasm/mod.rs index a831b85..38585f1 100644 --- a/src/backend/wasm/mod.rs +++ b/src/backend/wasm/mod.rs @@ -21,7 +21,7 @@ use crate::{ use self::{ control_flow::{CFSelector, ControlFlowElement}, - lowering::lower_function_type, + lowering::{lower_function_body, lower_function_type}, }; mod control_flow; @@ -32,6 +32,7 @@ fn fold_loop( scc: &BindedScc, current_result: &mut Vec, ) { + dbg!(scc); if let Some(sub_sccs) = scc.top_level_sccs() { for sub_scc in sub_sccs .into_iter() @@ -78,6 +79,7 @@ fn fold_if_else_once( ) -> bool { for block_id in 0..control_flow_graph.bind_on.content.len() { let predecessors = control_flow_graph.predecessor(block_id); + println!("{block_id}'s predecessors are {:?}", predecessors); if predecessors.len() == 1 { let predecessor_block_id = predecessors[0]; let predecessor_last_instruction = control_flow_graph.bind_on[predecessor_block_id] @@ -129,12 +131,10 @@ fn fold_if_else_once( } else { unreachable!() } + } else if let ControlFlowElement::If { on_failure, .. } = predecessor_element { + on_failure } else { - if let ControlFlowElement::If { on_failure, .. } = predecessor_element { - on_failure - } else { - unreachable!() - } + unreachable!() }; move_to.extend(to_move_items); for to_move_selector in to_move_selectors.iter().rev() { @@ -179,7 +179,7 @@ fn fold_if_else(function_definition: &FunctionDefinition, content: &mut ControlF loop { let cfg = ControlFlowGraph::new(); let control_flow_graph = cfg.bind(function_definition); - if fold_if_else_once(content, control_flow_graph) { + if !fold_if_else_once(content, control_flow_graph) { break; } } @@ -188,7 +188,6 @@ fn fold_if_else(function_definition: &FunctionDefinition, content: &mut ControlF fn fold(function_definition: &FunctionDefinition) -> Vec { let analyzer = Analyzer::new(); let binded = analyzer.bind(&function_definition); - let control_flow_graph = binded.control_flow_graph(); let current_result = (0..(function_definition.content.len())) .map(ControlFlowElement::new_node) .collect_vec(); @@ -209,7 +208,7 @@ fn generate_function( &mut CodeSection, ), function_definition: &FunctionDefinition, - control_flow: &[ControlFlowElement], + control_flow_root: &ControlFlowElement, ) { let function_index = result.0.len(); let (param_type, return_type) = lower_function_type(&function_definition.header); @@ -220,6 +219,10 @@ fn generate_function( ExportKind::Func, function_index, ); + let cfg = ControlFlowGraph::new(); + let cfg = cfg.bind(function_definition); + let function = lower_function_body(&function_definition.content, control_flow_root, &cfg); + result.3.function(&function); } #[cfg(test)] diff --git a/src/ir/editor/analyzer/control_flow/mod.rs b/src/ir/editor/analyzer/control_flow/mod.rs index 6cd76e5..2803dc7 100644 --- a/src/ir/editor/analyzer/control_flow/mod.rs +++ b/src/ir/editor/analyzer/control_flow/mod.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Borrow, cell::{OnceCell, Ref, RefCell}, collections::HashMap, }; @@ -18,10 +19,9 @@ use crate::{ utility::{self}, }; -use self::scc::Scc; +pub use self::scc::BindedScc; use super::IsAnalyzer; -pub use scc::BindedScc; mod scc; @@ -32,7 +32,7 @@ pub struct ControlFlowGraphContent { frontiers: HashMap>, bb_name_index_map: BiMap, dominators: Dominators>, - top_level_scc: Scc, + // top_level_scc: BindedScc, // fixme: remove this refcell! from_to_may_pass_blocks: RefCell>>, } @@ -84,7 +84,7 @@ impl ControlFlowGraphContent { dominators: dorminators, bb_name_index_map, from_to_may_pass_blocks: RefCell::new(HashMap::new()), - top_level_scc: Scc::new(0..function_definition.content.len(), true), + // top_level_scc: Scc::new(0..function_definition.content.len(), true), } } @@ -177,8 +177,9 @@ impl ControlFlowGraph { fn dominate(&self, content: &ir::FunctionDefinition, bb_index: usize) -> Vec { self.content(content).dominates(bb_index) } - fn top_level_scc(&self, content: &FunctionDefinition) -> Scc { - self.content(content).top_level_scc.clone() + fn top_level_scc(&self, content: &FunctionDefinition) -> BindedScc { + // self.content(content).top_level_scc.clone() + todo!() } fn branch_direction( &self, @@ -233,8 +234,9 @@ impl<'item, 'bind: 'item> BindedControlFlowGraph<'item, 'bind> { pub fn may_pass_blocks(&self, from: usize, to: usize) -> Ref> { self.item.may_pass_blocks(self.bind_on, from, to) } - pub fn top_level_scc(&self) -> BindedScc<'_> { - self.item.top_level_scc(self.bind_on).bind(self.graph()) + pub fn top_level_scc(&self) -> BindedScc<'item> { + let content = &self.item.content(&self.bind_on).graph; + BindedScc::new_top_level_from_graph(content) } pub fn graph(&self) -> &DiGraph<(), (), usize> { &self.item.content(self.bind_on).graph diff --git a/src/ir/editor/analyzer/control_flow/scc.rs b/src/ir/editor/analyzer/control_flow/scc.rs index f9079cc..585268e 100644 --- a/src/ir/editor/analyzer/control_flow/scc.rs +++ b/src/ir/editor/analyzer/control_flow/scc.rs @@ -1,61 +1,75 @@ use std::fmt; -use crate::utility::graph::{kosaraju_scc_with_filter, subgraph::SubGraph}; -use delegate::delegate; +use crate::{ + ir::analyzer::control_flow::scc, + utility::graph::{ + kosaraju_scc_with_filter, + subgraph::{CFGraph, CFSubGraph}, + }, +}; use itertools::Itertools; use petgraph::{ algo::all_simple_paths, - graph, prelude::*, - visit::{IntoNeighborsDirected, NodeFiltered, NodeRef}, + visit::{GraphBase, IntoEdgeReferences, IntoNeighborsDirected, NodeFiltered, NodeRef}, }; #[derive(Clone)] -pub struct Scc { - pub nodes: Vec, +pub struct BindedScc<'a> { + pub graph_part: CFSubGraph<'a>, pub top_level: bool, } - -impl Scc { - pub fn new(nodes: impl IntoIterator, top_level: bool) -> Self { +impl<'a> BindedScc<'a> { + pub fn new( + graph: &'a CFGraph, + nodes: impl IntoIterator::NodeId>, + edges: impl IntoIterator::EdgeId>, + top_level: bool, + ) -> Self { Self { - nodes: nodes.into_iter().collect(), + graph_part: CFSubGraph::new(graph, nodes, edges), top_level, } } - + pub fn new_top_level_from_graph(graph: &'a CFGraph) -> Self { + let nodes = graph.node_indices(); + let edges = graph.edge_indices(); + Self::new(graph, nodes, edges, true) + } pub fn is_trivial(&self) -> bool { - self.nodes.len() == 1 + self.graph_part.nodes.len() == 1 } - - pub fn edges(&self, graph: &DiGraph<(), (), usize>) -> Vec<(usize, usize)> { - graph + pub fn contains(&self, node: usize) -> bool { + self.graph_part.nodes.contains(&node.into()) + } + pub fn edges(&self) -> Vec<(usize, usize)> { + self.graph_part .edge_references() .filter(|edge| { - self.nodes.contains(&edge.source().index()) - && self.nodes.contains(&edge.target().index()) + self.graph_part.nodes.contains(&edge.source()) + && self.graph_part.nodes.contains(&edge.target()) }) .map(|it| (it.source().index(), it.target().index())) .collect() } - - pub fn entry_edges(&self, graph: &DiGraph<(), (), usize>) -> Vec<(usize, usize)> { - graph + pub fn entry_edges(&self) -> Vec<(usize, usize)> { + self.graph_part + .graph .edge_references() .filter(|edge| { - !self.nodes.contains(&edge.source().index()) - && self.nodes.contains(&edge.target().index()) + !self.graph_part.nodes.contains(&edge.source()) + && self.graph_part.nodes.contains(&edge.target()) }) .map(|it| (it.source().index(), it.target().index())) .collect() } - pub fn entry_nodes(&self, graph: &DiGraph<(), (), usize>) -> Vec { - if self.top_level || self.nodes.len() == 1 { - vec![self.nodes[0]] + pub fn entry_nodes(&self) -> Vec { + if self.top_level || self.graph_part.nodes.len() == 1 { + vec![self.graph_part.nodes[0].index()] } else { - self.entry_edges(graph) + self.entry_edges() .into_iter() .map(|(_, to)| to) .sorted() @@ -64,68 +78,86 @@ impl Scc { } } - pub fn edges_into_entry_nodes(&self, graph: &DiGraph<(), (), usize>) -> Vec<(usize, usize)> { - graph + pub fn edges_into_entry_nodes(&self) -> Vec<(usize, usize)> { + self.graph_part .edge_references() - .filter(|edge| self.entry_nodes(graph).contains(&edge.target().index())) + .filter(|edge| self.entry_nodes().contains(&edge.target().index())) .map(|it| (it.source().index(), it.target().index())) .collect() } - pub fn reduciable(&self, graph: &DiGraph<(), (), usize>) -> bool { - self.entry_nodes(graph).len() == 1 + pub fn reduciable(&self) -> bool { + self.entry_nodes().len() == 1 } /// Returns all top level sccs for a reducible subgraph. /// Return None if the subgraph is not reducible. - pub fn top_level_sccs(&self, graph: &DiGraph<(), (), usize>) -> Option> { - let entry_nodes = self.entry_nodes(graph); + pub fn top_level_sccs(&self) -> Option> { + let entry_nodes = self.entry_nodes(); if entry_nodes.len() != 1 { None } else { let entry_node = entry_nodes[0]; - let node_filtered = - NodeFiltered::from_fn(graph, |node| self.nodes.contains(&node.index())); - let subgraph = SubGraph(node_filtered); - let largest_simple_loop = subgraph + let largest_simple_loop = self + .graph_part .neighbors_directed(entry_node.into(), Incoming) .flat_map(|pred| { - all_simple_paths::, _>(&subgraph, entry_node.into(), pred, 1, None) - .max_by(|a, b| a.len().cmp(&b.len())) + all_simple_paths::, _>( + &self.graph_part, + entry_node.into(), + pred, + 0, + None, + ) + .max_by(|a, b| a.len().cmp(&b.len())) }) .max_by(|a, b| a.len().cmp(&b.len())); - dbg!(&largest_simple_loop); let backedge = if let Some(mut largest_simple_loops) = largest_simple_loop { let last_node = largest_simple_loops.pop().unwrap(); - graph.find_edge(last_node, entry_node.into()) + self.graph_part.find_edge(last_node, entry_node.into()) } else { None }; - let backedge_info = backedge.and_then(|e| graph.edge_endpoints(e)); - println!("entry: {:?}, {:?}", entry_node, backedge_info); + let edges_without_backedge = if let Some(backedge) = backedge { + self.graph_part + .edges + .iter() + .filter(|&&e| e != backedge) + .cloned() + .collect() + } else { + self.graph_part.edges.clone() + }; let sccs = kosaraju_scc_with_filter( - graph, + &self.graph_part, entry_nodes[0].into(), - |node| self.nodes.contains(&node.index()), - |edge| Some(edge) != backedge, + |_| true, + |e| Some(e) != backedge, ); let result = sccs .into_iter() - .map(|content| Self::new(content.into_iter().map(NodeIndex::index), false)) + .map(|content| { + Self::new( + &self.graph_part.graph, + content, + edges_without_backedge.clone(), + false, + ) + }) .collect(); Some(result) } } - pub fn first_irreducible_sub_scc(&self, graph: &DiGraph<(), (), usize>) -> Option { - if self.nodes.len() == 1 { + pub fn first_irreducible_sub_scc(&self) -> Option { + if self.graph_part.nodes.len() == 1 { return None; - } else if !self.reduciable(graph) { + } else if !self.reduciable() { return Some(self.clone()); } else { - let sccs = self.top_level_sccs(graph).unwrap(); + let sccs = self.top_level_sccs().unwrap(); for scc in sccs { - if let Some(first_irreducible) = scc.first_irreducible_sub_scc(graph) { + if let Some(first_irreducible) = scc.first_irreducible_sub_scc() { return Some(first_irreducible); } } @@ -133,105 +165,46 @@ impl Scc { None } - pub fn contains(&self, node: usize) -> bool { - self.nodes.contains(&node) - } - /// Returns the smallest non trivial (ie. not a single node) scc /// the node is in. - pub fn smallest_non_trivial_scc_node_in( - &self, - graph: &DiGraph<(), (), usize>, - node: usize, - ) -> Option { + pub fn smallest_non_trivial_scc_node_in(&self, node: usize) -> Option { if !self.contains(node) { None } else if self.is_trivial() { None - } else if let Some(sub_sccs) = self.top_level_sccs(graph) { + } else if let Some(sub_sccs) = self.top_level_sccs() { for sub_scc in sub_sccs { if sub_scc.is_trivial() && sub_scc.contains(node) { return Some(self.clone()); - } else if let Some(result) = sub_scc.smallest_non_trivial_scc_node_in(graph, node) { + } else if let Some(result) = sub_scc.smallest_non_trivial_scc_node_in(node) { return Some(result); } } unreachable!() } else { - debug_assert!(!self.reduciable(graph)); + debug_assert!(!self.reduciable()); Some(self.clone()) } } } -impl fmt::Display for Scc { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.nodes) - } -} - -impl fmt::Debug for Scc { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.nodes) - } -} - -#[derive(Clone)] -pub struct BindedScc<'bind> { - graph: &'bind DiGraph<(), (), usize>, - item: Scc, -} - -impl fmt::Display for BindedScc<'_> { +impl<'a> fmt::Display for BindedScc<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.item.fmt(f) + write!( + f, + "Scc{{{:?}, top_level: {}}}", + self.graph_part.nodes, self.top_level + ) } } -impl fmt::Debug for BindedScc<'_> { +impl<'a> fmt::Debug for BindedScc<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.item.fmt(f) - } -} - -impl<'bind> BindedScc<'bind> { - pub fn new(graph: &'bind DiGraph<(), (), usize>, item: Scc) -> Self { - Self { graph, item } - } - delegate! { - to self.item { - pub fn is_trivial(&self) -> bool; - pub fn edges(&self, [self.graph]) -> Vec<(usize, usize)>; - pub fn entry_edges(&self, [self.graph]) -> Vec<(usize, usize)>; - pub fn entry_nodes(&self, [self.graph]) -> Vec; - pub fn edges_into_entry_nodes(&self, [self.graph]) -> Vec<(usize, usize)>; - pub fn reduciable(&self, [self.graph]) -> bool; - pub fn contains(&self, node: usize) -> bool; - } - } - pub fn top_level_sccs(&self) -> Option> { - self.item - .top_level_sccs(self.graph) - .map(|it| it.into_iter().map(|it| it.bind(self.graph)).collect_vec()) - } - pub fn first_irreducible_sub_scc(&self) -> Option { - self.item - .first_irreducible_sub_scc(self.graph) - .map(|it| it.bind(self.graph)) - } - pub fn smallest_non_trivial_scc_node_in(&self, node: usize) -> Option { - self.item - .smallest_non_trivial_scc_node_in(self.graph, node) - .map(|it| it.bind(self.graph)) - } - pub fn top_level(&self) -> bool { - self.item.top_level - } -} - -impl<'item, 'bind: 'item> Scc { - pub fn bind(&'item self, graph: &'bind DiGraph<(), (), usize>) -> BindedScc<'bind> { - BindedScc::new(graph, self.clone()) + write!( + f, + "Scc{{{:?}, top_level: {}}}", + self.graph_part.nodes, self.top_level + ) } } @@ -269,10 +242,13 @@ mod tests { graph.add_edge(node_6, node_10, ()); graph.add_edge(node_10, node_6, ()); graph.add_edge(node_10, node_4, ()); - let scc = Scc::new(0..10, true); - let scc = BindedScc::new(&graph, scc); + + let scc = BindedScc::new_top_level_from_graph(&graph); println!("{:?}", scc.top_level_sccs()); - println!("{:?}", scc.first_irreducible_sub_scc()); + println!( + "first_irreducible_sub_scc={:?}", + scc.first_irreducible_sub_scc() + ); } #[test] @@ -291,12 +267,16 @@ mod tests { graph.add_edge(node_4, node_1, ()); graph.add_edge(node_3, node_1, ()); graph.add_edge(node_4, node_5, ()); - let scc = Scc::new(0..6, true); - let scc = BindedScc::new(&graph, scc); + let scc = BindedScc::new_top_level_from_graph(&graph); + let top_level_sccs = scc.top_level_sccs().unwrap(); + println!("{:?}", &top_level_sccs); + let scc = &top_level_sccs[1]; + println!("{:?}", &scc); + let top_level_sccs = scc.top_level_sccs().unwrap(); + println!("{:?}", &top_level_sccs); + let scc = &top_level_sccs[0]; let top_level_sccs = scc.top_level_sccs().unwrap(); println!("{:?}", &top_level_sccs); - let contains_recursive = &top_level_sccs[1]; - println!("{:?}", contains_recursive.top_level_sccs()); } #[test] @@ -309,7 +289,35 @@ mod tests { let node_4 = graph.add_node(()); let node_5 = graph.add_node(()); let node_6 = graph.add_node(()); - let node_7 = graph.add_node(()); + graph.add_edge(node_0, node_1, ()); + graph.add_edge(node_1, node_2, ()); + graph.add_edge(node_2, node_3, ()); + graph.add_edge(node_3, node_4, ()); + graph.add_edge(node_4, node_5, ()); + graph.add_edge(node_3, node_6, ()); + graph.add_edge(node_6, node_1, ()); + graph.add_edge(node_4, node_6, ()); + + let scc = BindedScc::new_top_level_from_graph(&graph); + let top_level_sccs = scc.top_level_sccs().unwrap(); + println!("{:?}", &top_level_sccs); + let scc = &top_level_sccs[1]; + let top_level_sccs = scc.top_level_sccs().unwrap(); + println!("{:?}", &top_level_sccs); + // let scc = &top_level_sccs[0]; + // let top_level_sccs = scc.top_level_sccs().unwrap(); + // println!("{:?}", &top_level_sccs); + } + #[test] + fn test_top_level_scc_recursive3() { + let mut graph: DiGraph<_, _, usize> = DiGraph::default(); + let node_0 = graph.add_node(()); + let node_1 = graph.add_node(()); + let node_2 = graph.add_node(()); + let node_3 = graph.add_node(()); + let node_4 = graph.add_node(()); + let node_5 = graph.add_node(()); + let node_6 = graph.add_node(()); graph.add_edge(node_0, node_1, ()); graph.add_edge(node_1, node_2, ()); graph.add_edge(node_2, node_3, ()); @@ -318,13 +326,15 @@ mod tests { graph.add_edge(node_4, node_5, ()); graph.add_edge(node_5, node_1, ()); graph.add_edge(node_3, node_6, ()); - graph.add_edge(node_2, node_4, ()); - graph.add_edge(node_4, node_7, ()); - let scc = Scc::new(0..8, true); - let scc = BindedScc::new(&graph, scc); + + let scc = BindedScc::new_top_level_from_graph(&graph); + let top_level_sccs = scc.top_level_sccs().unwrap(); + println!("{:?}", &top_level_sccs); + let scc = &top_level_sccs[1]; let top_level_sccs = scc.top_level_sccs().unwrap(); - println!("{:?}", top_level_sccs); - let contains_recursive = &top_level_sccs[1]; - println!("{:?}", contains_recursive.top_level_sccs()); + println!("{:?}", &top_level_sccs); + // let scc = &top_level_sccs[0]; + // let top_level_sccs = scc.top_level_sccs().unwrap(); + // println!("{:?}", &top_level_sccs); } } diff --git a/src/ir/optimize/pass/fix_irreducible/tests.rs b/src/ir/optimize/pass/fix_irreducible/tests.rs index e456218..1021d66 100644 --- a/src/ir/optimize/pass/fix_irreducible/tests.rs +++ b/src/ir/optimize/pass/fix_irreducible/tests.rs @@ -490,10 +490,10 @@ fn test_shit() { return_type: data_type::Type::None, }, content: vec![ + jump_block(0, 1), branch_block(1, 2, 3), branch_block(2, 3, 1), branch_block(3, 1, 2), - jump_block(0, 1), ], }; println!("{}", function_definition); diff --git a/src/utility/graph/subgraph.rs b/src/utility/graph/subgraph.rs index a0f8eeb..9df3e1e 100644 --- a/src/utility/graph/subgraph.rs +++ b/src/utility/graph/subgraph.rs @@ -1,65 +1,157 @@ -use std::fmt::{self, Debug}; +use std::{ + fmt::{self, Debug}, + vec, +}; use itertools::Itertools; use petgraph::{ - graph::DiGraph, + adj::NodeIndex, + graph::{DiGraph, EdgeIndex}, + graphmap::NeighborsDirected, visit::{ - FilterNode, GraphBase, GraphRef, IntoNeighbors, IntoNeighborsDirected, IntoNodeIdentifiers, - IntoNodeReferences, NodeCount, NodeFiltered, + Data, EdgeFiltered, EdgeFilteredNeighbors, EdgeRef, FilterEdge, FilterNode, GraphBase, + GraphRef, IntoEdgeReferences, IntoEdges, IntoEdgesDirected, IntoNeighbors, + IntoNeighborsDirected, IntoNodeIdentifiers, IntoNodeReferences, NodeCount, NodeFiltered, + Visitable, }, Direction, }; -type CFGraph = DiGraph<(), (), usize>; +pub type CFGraph = DiGraph<(), (), usize>; -type FilteredCFGraph<'a, F> = NodeFiltered<&'a CFGraph, F>; +#[derive(Debug, Clone)] +pub struct CFSubGraph<'a> { + pub graph: &'a CFGraph, + pub nodes: Vec<::NodeId>, + pub edges: Vec<::EdgeId>, +} -#[derive(Copy, Clone)] -pub struct SubGraph<'a, F: FilterNode<::NodeId>>(pub FilteredCFGraph<'a, F>); +impl<'a> CFSubGraph<'a> { + pub fn new( + graph: &'a CFGraph, + nodes: impl IntoIterator::NodeId>, + edges: impl IntoIterator::EdgeId>, + ) -> Self { + Self { + graph, + nodes: nodes.into_iter().collect(), + edges: edges.into_iter().collect(), + } + } -impl::NodeId>> fmt::Debug for SubGraph<'_, F> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let nodes = self - .0 - .node_references() - .filter(|(it, _)| self.0 .1.include_node(*it)) - .collect_vec(); - f.debug_tuple("SubGraph").field(&nodes).finish() + pub fn find_edge( + &self, + a: ::NodeId, + b: ::NodeId, + ) -> Option<::EdgeId> { + self.graph + .find_edge(a, b) + .filter(|it| self.edges.contains(it)) } } -impl<'a, F: FilterNode<::NodeId>> GraphBase for SubGraph<'a, F> { - type EdgeId = as GraphBase>::EdgeId; - - type NodeId = as GraphBase>::NodeId; +impl<'a> GraphBase for CFSubGraph<'a> { + type EdgeId = ::EdgeId; + type NodeId = ::NodeId; } -impl<'a, F: FilterNode<::NodeId>> NodeCount for &'a SubGraph<'a, F> { - fn node_count(self: &Self) -> usize { - self.0 - .node_references() - .filter(|(it, _)| self.0 .1.include_node(*it)) - .count() +impl<'a> NodeCount for &'a CFSubGraph<'a> { + fn node_count(&self) -> usize { + self.nodes.len() } } -impl<'a, F: Copy + FilterNode<::NodeId>> GraphRef for SubGraph<'a, F> {} - -impl<'a, F: FilterNode<::NodeId>> IntoNeighbors for &'a SubGraph<'a, F> { - type Neighbors = <&'a FilteredCFGraph<'a, F> as IntoNeighbors>::Neighbors; +impl<'a> IntoNeighbors for &'a CFSubGraph<'a> { + type Neighbors = vec::IntoIter; fn neighbors(self, a: Self::NodeId) -> Self::Neighbors { - self.0.neighbors(a) + let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n)); + let filtered_edges = + EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id())); + filtered_nodes.neighbors(a).collect_vec().into_iter() } } -impl<'a, F: FilterNode<::NodeId>> IntoNeighborsDirected - for &'a SubGraph<'a, F> -{ - type NeighborsDirected = - <&'a FilteredCFGraph<'a, F> as IntoNeighborsDirected>::NeighborsDirected; +impl<'a> IntoNeighborsDirected for &'a CFSubGraph<'a> { + type NeighborsDirected = vec::IntoIter; fn neighbors_directed(self, n: Self::NodeId, d: Direction) -> Self::NeighborsDirected { - self.0.neighbors_directed(n, d) + let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n)); + let filtered_edges = + EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id())); + filtered_nodes + .neighbors_directed(n, d) + .collect_vec() + .into_iter() + } +} + +impl<'a> Data for &'a CFSubGraph<'a> { + type NodeWeight = <&'a CFGraph as Data>::NodeWeight; + type EdgeWeight = <&'a CFGraph as Data>::EdgeWeight; +} + +impl<'a> IntoEdgeReferences for &'a CFSubGraph<'a> { + type EdgeRef = <&'a CFGraph as IntoEdgeReferences>::EdgeRef; + + type EdgeReferences = vec::IntoIter; + + fn edge_references(self) -> Self::EdgeReferences { + let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n)); + let filtered_edges = + EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id())); + filtered_edges.edge_references().collect_vec().into_iter() + } +} + +impl<'a> IntoNodeIdentifiers for &'a CFSubGraph<'a> { + type NodeIdentifiers = vec::IntoIter; + + fn node_identifiers(self) -> Self::NodeIdentifiers { + let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n)); + filtered_nodes.node_identifiers().collect_vec().into_iter() + } +} + +impl<'a> IntoEdges for &'a CFSubGraph<'a> { + type Edges = vec::IntoIter; + + fn edges(self, a: Self::NodeId) -> Self::Edges { + let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n)); + let filtered_edges = + EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id())); + filtered_edges.edges(a).collect_vec().into_iter() + } +} + +impl<'a> IntoEdgesDirected for &'a CFSubGraph<'a> { + type EdgesDirected = vec::IntoIter; + + fn edges_directed(self, a: Self::NodeId, dir: Direction) -> Self::EdgesDirected { + let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n)); + let filtered_edges = + EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id())); + filtered_edges + .edges_directed(a, dir) + .collect_vec() + .into_iter() + } +} + +impl<'a> Visitable for &'a CFSubGraph<'a> { + type Map = ::Map; + + fn visit_map(&self) -> Self::Map { + let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n)); + let filtered_edges = + EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id())); + filtered_edges.visit_map() + } + + fn reset_map(&self, map: &mut Self::Map) { + let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n)); + let filtered_edges = + EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id())); + filtered_edges.reset_map(map) } } From 0bbef4392f177c1fc11eb631955711bbe7159364 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Thu, 2 May 2024 00:40:49 +0200 Subject: [PATCH 07/11] pass all tests --- src/backend/wasm/control_flow/mod.rs | 6 +- src/backend/wasm/control_flow/selector.rs | 13 +-- src/backend/wasm/lowering.rs | 8 +- src/backend/wasm/mod.rs | 63 ++++++-------- src/ir/editor/analyzer/control_flow/mod.rs | 19 ++--- src/ir/editor/analyzer/control_flow/scc.rs | 84 +++++++++++++++++-- src/ir/optimize/pass/fix_irreducible/mod.rs | 14 ++-- src/ir/optimize/pass/fix_irreducible/tests.rs | 83 +++++++++--------- src/utility/graph/mod.rs | 13 ++- src/utility/graph/subgraph.rs | 18 ++-- 10 files changed, 184 insertions(+), 137 deletions(-) diff --git a/src/backend/wasm/control_flow/mod.rs b/src/backend/wasm/control_flow/mod.rs index 539bf4c..f7421e3 100644 --- a/src/backend/wasm/control_flow/mod.rs +++ b/src/backend/wasm/control_flow/mod.rs @@ -234,7 +234,7 @@ impl ControlFlowElement { assert!(!element.is_empty()); let (parent_selector, last_segment) = element.clone().split_last().unwrap(); let parent = &mut self[&parent_selector]; - return match (parent, last_segment) { + match (parent, last_segment) { (ControlFlowElement::Block { content }, CFSelectorSegment::ContentAtIndex(i)) => { content.remove(i) } @@ -252,7 +252,7 @@ impl ControlFlowElement { (ControlFlowElement::If { .. }, _) => unreachable!(), (ControlFlowElement::Loop { .. }, _) => unreachable!(), (ControlFlowElement::BasicBlock { .. }, _) => unreachable!(), - }; + } } pub fn get(&self, index: &CFSelector) -> Option<&ControlFlowElement> { if index.is_empty() { @@ -349,7 +349,7 @@ mod tests { let selector = content.find_node(2); assert_eq!( selector.unwrap(), - CFSelector::from_str("1/success/0").unwrap() + CFSelector::from_str("1/success->0").unwrap() ); let selector = content.find_node(3); assert_eq!(selector.unwrap(), CFSelector::from_str("2").unwrap()); diff --git a/src/backend/wasm/control_flow/selector.rs b/src/backend/wasm/control_flow/selector.rs index 239e652..72f1829 100644 --- a/src/backend/wasm/control_flow/selector.rs +++ b/src/backend/wasm/control_flow/selector.rs @@ -1,7 +1,6 @@ use delegate::delegate; use std::{ - cmp::Ordering, collections::VecDeque, fmt, iter::zip, num::ParseIntError, ops::RangeBounds, - result, str::FromStr, + cmp::Ordering, collections::VecDeque, fmt, iter::zip, num::ParseIntError, ops::RangeBounds, str::FromStr, }; #[derive(Clone, PartialEq, Eq)] @@ -112,8 +111,8 @@ impl FromStr for CFSelector { fn from_str(s: &str) -> Result { let mut result = VecDeque::new(); - let mut parts = s.split("/"); - while let Some(next_part) = parts.next() { + let parts = s.split('/'); + for next_part in parts { let segment = CFSelectorSegment::from_str(next_part)?; result.push_back(segment); } @@ -222,11 +221,7 @@ impl CFSelector { false } else { let shared_part = Self::lowest_common_ancestor(selector, last_selector); - if shared_part.len() == selector.len() - 1 { - true - } else { - false - } + shared_part.len() == selector.len() - 1 } } diff --git a/src/backend/wasm/lowering.rs b/src/backend/wasm/lowering.rs index c6c77cd..dbf6825 100644 --- a/src/backend/wasm/lowering.rs +++ b/src/backend/wasm/lowering.rs @@ -278,7 +278,7 @@ fn generate_if_condition( register_name_id_map: &HashMap, result: &mut Function, ) { - let data_type = decide_branch_operation_type(&branch_statement, register_type); + let data_type = decide_branch_operation_type(branch_statement, register_type); put_value_onto_stack( &branch_statement.operand1, register_name_id_map, @@ -338,7 +338,7 @@ fn lower_basic_block( result: &mut Function, bb_id: usize, block: &BasicBlock, - selector: CFSelector, + _selector: CFSelector, binded_cfg: &BindedControlFlowGraph, register_name_id_map: &HashMap, cfe_root: &ControlFlowElement, @@ -396,7 +396,7 @@ fn lower_control_flow_element( lower_control_flow_element( result, body, - &condition, + condition, new_selector, binded_cfg, register_name_id_map, @@ -418,7 +418,7 @@ fn lower_control_flow_element( register_type, ); } - if on_failure.len() != 0 { + if !on_failure.is_empty() { result.instruction(&Instruction::Else); for (i, failure_block) in on_failure.iter().enumerate() { let mut new_selector = current.clone(); diff --git a/src/backend/wasm/mod.rs b/src/backend/wasm/mod.rs index 38585f1..86ce871 100644 --- a/src/backend/wasm/mod.rs +++ b/src/backend/wasm/mod.rs @@ -1,22 +1,12 @@ -use itertools::{Either, Itertools}; -use std::{ - collections::VecDeque, - fmt, - iter::zip, - mem, - ops::{Index, IndexMut}, - path::Display, -}; -use wasm_encoder::{CodeSection, ExportKind, ExportSection, FunctionSection, Module, TypeSection}; +use itertools::Itertools; +use std::mem; +use wasm_encoder::{CodeSection, ExportKind, ExportSection, FunctionSection, TypeSection}; -use crate::{ - ast::function_definition, - ir::{ - analyzer::{BindedControlFlowGraph, BindedScc, ControlFlowGraph, IsAnalyzer}, - editor::Analyzer, - statement::IRStatement, - FunctionDefinition, - }, +use crate::ir::{ + analyzer::{BindedControlFlowGraph, BindedScc, ControlFlowGraph, IsAnalyzer}, + editor::Analyzer, + statement::IRStatement, + FunctionDefinition, }; use self::{ @@ -26,13 +16,13 @@ use self::{ mod control_flow; mod lowering; + // fixme: currently this presumes that we have not folded any if-else or block before fn fold_loop( function_content: &FunctionDefinition, scc: &BindedScc, current_result: &mut Vec, ) { - dbg!(scc); if let Some(sub_sccs) = scc.top_level_sccs() { for sub_scc in sub_sccs .into_iter() @@ -156,7 +146,7 @@ fn collect_to_move( let move_to_if_condition_bb_id = control_flow_graph.predecessor(first_to_move_element_first_bb_id)[0]; let mut to_move = vec![first_to_move_node_selector.clone()]; - let mut next = root_element.next_element_sibling(&first_to_move_node_selector); + let mut next = root_element.next_element_sibling(first_to_move_node_selector); while let Some(current_element_selector) = next { let current_node_id = root_element[¤t_element_selector].first_basic_block_id(); if control_flow_graph.is_dominated_by(current_node_id, first_to_move_element_first_bb_id) @@ -187,7 +177,7 @@ fn fold_if_else(function_definition: &FunctionDefinition, content: &mut ControlF fn fold(function_definition: &FunctionDefinition) -> Vec { let analyzer = Analyzer::new(); - let binded = analyzer.bind(&function_definition); + let binded = analyzer.bind(function_definition); let current_result = (0..(function_definition.content.len())) .map(ControlFlowElement::new_node) .collect_vec(); @@ -227,10 +217,9 @@ fn generate_function( #[cfg(test)] mod tests { - use std::{assert_matches::assert_matches, fs, str::FromStr}; + use std::{assert_matches::assert_matches, str::FromStr}; use analyzer::Analyzer; - use wasm_encoder::{TypeSection, ValType}; use crate::{ ir::{ @@ -283,17 +272,17 @@ mod tests { ControlFlowElement::If { .. } ); assert_matches!( - content[&CFSelector::from_str("1/success/0").unwrap()], + content[&CFSelector::from_str("1/success->0").unwrap()], ControlFlowElement::BasicBlock { id: 2 } ); assert_eq!( - content.get(&CFSelector::from_str("1/failure/0").unwrap()), + content.get(&CFSelector::from_str("1/failure->0").unwrap()), None ); let control_flow_graph = binded.control_flow_graph(); fold_if_else_once(&mut content, control_flow_graph); assert_eq!( - content[&CFSelector::from_str("1/failure/0").unwrap()], + content[&CFSelector::from_str("1/failure->0").unwrap()], ControlFlowElement::BasicBlock { id: 3 } ); assert_eq!( @@ -339,25 +328,25 @@ mod tests { ControlFlowElement::If { .. } ); assert_matches!( - content[&CFSelector::from_str("1/success/0").unwrap()], + content[&CFSelector::from_str("1/success->0").unwrap()], ControlFlowElement::BasicBlock { id: 2 } ); assert_matches!( - content[&CFSelector::from_str("1/success/1").unwrap()], + content[&CFSelector::from_str("1/success->1").unwrap()], ControlFlowElement::BasicBlock { id: 3 } ); assert_eq!( - content.get(&CFSelector::from_str("1/failure/0").unwrap()), + content.get(&CFSelector::from_str("1/failure->0").unwrap()), None ); let control_flow_graph = binded.control_flow_graph(); fold_if_else_once(&mut content, control_flow_graph); assert_matches!( - content[&CFSelector::from_str("1/failure/0").unwrap()], + content[&CFSelector::from_str("1/failure->0").unwrap()], ControlFlowElement::BasicBlock { id: 4 } ); assert_matches!( - content[&CFSelector::from_str("1/failure/1").unwrap()], + content[&CFSelector::from_str("1/failure->1").unwrap()], ControlFlowElement::BasicBlock { id: 5 } ); @@ -415,27 +404,27 @@ mod tests { ControlFlowElement::BasicBlock { id: 0 } ); assert_matches!( - &content[&CFSelector::from_str("0/success/0").unwrap()], + &content[&CFSelector::from_str("0/success->0").unwrap()], ControlFlowElement::BasicBlock { id: 1 } ); assert_matches!( - &content[&CFSelector::from_str("0/success/1").unwrap()], + &content[&CFSelector::from_str("0/success->1").unwrap()], ControlFlowElement::Loop { .. } ); assert_matches!( - &content[&CFSelector::from_str("0/success/1/0").unwrap()], + &content[&CFSelector::from_str("0/success->1/0").unwrap()], ControlFlowElement::BasicBlock { id: 2 } ); assert_matches!( - &content[&CFSelector::from_str("0/success/1/1").unwrap()], + &content[&CFSelector::from_str("0/success->1/1").unwrap()], ControlFlowElement::If { .. } ); assert_matches!( - &content[&CFSelector::from_str("0/success/1/1/if_condition").unwrap()], + &content[&CFSelector::from_str("0/success->1/1/if_condition").unwrap()], ControlFlowElement::BasicBlock { id: 3 } ); assert_matches!( - &content[&CFSelector::from_str("0/success/1/1/success/0").unwrap()], + &content[&CFSelector::from_str("0/success->1/1/success->0").unwrap()], ControlFlowElement::BasicBlock { id: 4 } ); } diff --git a/src/ir/editor/analyzer/control_flow/mod.rs b/src/ir/editor/analyzer/control_flow/mod.rs index 2803dc7..5b30b13 100644 --- a/src/ir/editor/analyzer/control_flow/mod.rs +++ b/src/ir/editor/analyzer/control_flow/mod.rs @@ -72,16 +72,16 @@ impl ControlFlowGraphContent { _ => unreachable!(), } } - let dorminators = simple_fast(&graph, 0.into()); + let dominators = simple_fast(&graph, 0.into()); let graph = remove_unreachable_nodes(graph); - let frontiers = utility::graph::dominance_frontiers(&dorminators, &graph) + let frontiers = utility::graph::dominance_frontiers(&dominators, &graph) .into_iter() .map(|(k, v)| (k.index(), v.into_iter().map(NodeIndex::index).collect())) .collect(); Self { graph, frontiers, - dominators: dorminators, + dominators, bb_name_index_map, from_to_may_pass_blocks: RefCell::new(HashMap::new()), // top_level_scc: Scc::new(0..function_definition.content.len(), true), @@ -177,10 +177,7 @@ impl ControlFlowGraph { fn dominate(&self, content: &ir::FunctionDefinition, bb_index: usize) -> Vec { self.content(content).dominates(bb_index) } - fn top_level_scc(&self, content: &FunctionDefinition) -> BindedScc { - // self.content(content).top_level_scc.clone() - todo!() - } + fn branch_direction( &self, content: &FunctionDefinition, @@ -206,7 +203,7 @@ impl ControlFlowGraph { .unwrap() .as_branch() .success_label; - let success_block_id = self.basic_block_index_by_name(content, &success_name); + let success_block_id = self.basic_block_index_by_name(content, success_name); let block1_under_success = self.dominate(content, success_block_id); let block2_under_success = self.dominate(content, success_block_id); let block1_under_success = block1_under_success.contains(&block1_index); @@ -235,7 +232,7 @@ impl<'item, 'bind: 'item> BindedControlFlowGraph<'item, 'bind> { self.item.may_pass_blocks(self.bind_on, from, to) } pub fn top_level_scc(&self) -> BindedScc<'item> { - let content = &self.item.content(&self.bind_on).graph; + let content = &self.item.content(self.bind_on).graph; BindedScc::new_top_level_from_graph(content) } pub fn graph(&self) -> &DiGraph<(), (), usize> { @@ -271,7 +268,7 @@ impl<'item, 'bind: 'item> BindedControlFlowGraph<'item, 'bind> { } pub fn branch_direction(&self, branch_block_index: usize, target_block_index: usize) -> bool { self.item - .branch_direction(&self.bind_on, branch_block_index, target_block_index) + .branch_direction(self.bind_on, branch_block_index, target_block_index) } pub fn is_in_same_branch_side( &self, @@ -280,7 +277,7 @@ impl<'item, 'bind: 'item> BindedControlFlowGraph<'item, 'bind> { block2_index: usize, ) -> bool { self.item.is_in_same_branch_side( - &self.bind_on, + self.bind_on, branch_block_index, block1_index, block2_index, diff --git a/src/ir/editor/analyzer/control_flow/scc.rs b/src/ir/editor/analyzer/control_flow/scc.rs index 585268e..daf7c97 100644 --- a/src/ir/editor/analyzer/control_flow/scc.rs +++ b/src/ir/editor/analyzer/control_flow/scc.rs @@ -1,18 +1,15 @@ use std::fmt; -use crate::{ - ir::analyzer::control_flow::scc, - utility::graph::{ - kosaraju_scc_with_filter, - subgraph::{CFGraph, CFSubGraph}, - }, +use crate::utility::graph::{ + kosaraju_scc_with_filter, + subgraph::{CFGraph, CFSubGraph}, }; use itertools::Itertools; use petgraph::{ algo::all_simple_paths, prelude::*, - visit::{GraphBase, IntoEdgeReferences, IntoNeighborsDirected, NodeFiltered, NodeRef}, + visit::{GraphBase, IntoEdgeReferences, IntoNeighborsDirected}, }; #[derive(Clone)] @@ -20,6 +17,12 @@ pub struct BindedScc<'a> { pub graph_part: CFSubGraph<'a>, pub top_level: bool, } + +impl<'a> PartialEq for BindedScc<'a> { + fn eq(&self, other: &Self) -> bool { + self.graph_part.nodes.eq(&other.graph_part.nodes) + } +} impl<'a> BindedScc<'a> { pub fn new( graph: &'a CFGraph, @@ -86,6 +89,15 @@ impl<'a> BindedScc<'a> { .collect() } + pub fn extern_edges_into_entry_nodes(&self) -> Vec<(usize, usize)> { + self.graph_part + .graph + .edge_references() + .filter(|edge| self.entry_nodes().contains(&edge.target().index())) + .map(|it| (it.source().index(), it.target().index())) + .collect() + } + pub fn reduciable(&self) -> bool { self.entry_nodes().len() == 1 } @@ -98,6 +110,7 @@ impl<'a> BindedScc<'a> { None } else { let entry_node = entry_nodes[0]; + dbg!(entry_node); let largest_simple_loop = self .graph_part .neighbors_directed(entry_node.into(), Incoming) @@ -138,7 +151,7 @@ impl<'a> BindedScc<'a> { .into_iter() .map(|content| { Self::new( - &self.graph_part.graph, + self.graph_part.graph, content, edges_without_backedge.clone(), false, @@ -155,8 +168,19 @@ impl<'a> BindedScc<'a> { } else if !self.reduciable() { return Some(self.clone()); } else { + // dbg!(self.top_level_sccs()); + // if self + // .top_level_sccs() + // .map(|it| it.len() != 2) + // .unwrap_or(false) + // { + // return None; + // } let sccs = self.top_level_sccs().unwrap(); for scc in sccs { + // if &scc == self { + // return Some(scc); + // } if let Some(first_irreducible) = scc.first_irreducible_sub_scc() { return Some(first_irreducible); } @@ -337,4 +361,48 @@ mod tests { // let top_level_sccs = scc.top_level_sccs().unwrap(); // println!("{:?}", &top_level_sccs); } + + #[test] + fn test_top_level_strange() { + let mut graph: DiGraph<_, _, usize> = DiGraph::default(); + let node_0 = graph.add_node(()); + let node_1 = graph.add_node(()); + let node_2 = graph.add_node(()); + graph.add_edge(node_0, node_1, ()); + graph.add_edge(node_1, node_2, ()); + graph.add_edge(node_2, node_0, ()); + graph.add_edge(node_0, node_2, ()); + graph.add_edge(node_2, node_1, ()); + graph.add_edge(node_1, node_0, ()); + + let scc = BindedScc::new_top_level_from_graph(&graph); + println!("{:?}", scc.top_level_sccs()); + println!( + "first_irreducible_sub_scc={:?}", + scc.first_irreducible_sub_scc() + ); + } + + #[test] + fn test_top_level_strange_0() { + let mut graph: DiGraph<_, _, usize> = DiGraph::default(); + let node_0 = graph.add_node(()); + let node_1 = graph.add_node(()); + let node_2 = graph.add_node(()); + let node_3 = graph.add_node(()); + graph.add_edge(node_0, node_1, ()); + graph.add_edge(node_1, node_2, ()); + graph.add_edge(node_1, node_3, ()); + graph.add_edge(node_2, node_3, ()); + graph.add_edge(node_3, node_1, ()); + graph.add_edge(node_2, node_1, ()); + graph.add_edge(node_3, node_2, ()); + + let scc = BindedScc::new_top_level_from_graph(&graph); + + println!( + "first_irreducible_sub_scc={:?}", + scc.first_irreducible_sub_scc() + ); + } } diff --git a/src/ir/optimize/pass/fix_irreducible/mod.rs b/src/ir/optimize/pass/fix_irreducible/mod.rs index 3966384..7a58eef 100644 --- a/src/ir/optimize/pass/fix_irreducible/mod.rs +++ b/src/ir/optimize/pass/fix_irreducible/mod.rs @@ -301,8 +301,7 @@ fn generate_origin_target_to_source_map( edges_into_entry_nodes.sort(); let mut two_nodes = Vec::new(); let mut one_nodes = Vec::new(); - while !edges_into_entry_nodes.is_empty() { - let last = edges_into_entry_nodes.pop().unwrap(); + while let Some(last) = edges_into_entry_nodes.pop() { if let Some(last_but_one) = edges_into_entry_nodes.pop() { if last_but_one.0 == last.0 { two_nodes.push((last, last_but_one)); @@ -350,19 +349,22 @@ fn generate_origin_target_to_source_map( impl IsPass for FixIrreducible { fn run(&self, editor: &mut Editor) { - while let Some(irreducible_scc) = editor + while let Some(irreducible_scc) = dbg!(editor .binded_analyzer() .control_flow_graph() .top_level_scc() - .first_irreducible_sub_scc() + .first_irreducible_sub_scc()) { + println!("{}", &editor.content); + println!("{}", &irreducible_scc); + println!("{:?}", irreducible_scc.entry_nodes()); let analyzer = editor.binded_analyzer(); let graph = analyzer.control_flow_graph(); - let edges_into_entry_nodes = irreducible_scc.edges_into_entry_nodes(); + let edges_into_entry_nodes = irreducible_scc.extern_edges_into_entry_nodes(); + dbg!(&edges_into_entry_nodes); let origin_target_to_source_map = generate_origin_target_to_source_map(&editor.content, edges_into_entry_nodes); let edit_plan = generate_edit_plan(&origin_target_to_source_map, &graph); - drop(graph); drop(irreducible_scc); // fixme: don't use direct_edit for performance's sake! editor.direct_edit(move |f| { diff --git a/src/ir/optimize/pass/fix_irreducible/tests.rs b/src/ir/optimize/pass/fix_irreducible/tests.rs index 1021d66..e698416 100644 --- a/src/ir/optimize/pass/fix_irreducible/tests.rs +++ b/src/ir/optimize/pass/fix_irreducible/tests.rs @@ -1,4 +1,4 @@ -use function::statement::{phi, BinaryCalculate, Ret}; +use function::statement::{phi, BinaryCalculate}; use crate::{ ir::{ @@ -346,45 +346,6 @@ fn test_generate_origin_target_to_source_map() { #[test] fn test_fix_irreducible() { - let function_definition = FunctionDefinition { - header: ir::FunctionHeader { - name: "f".to_string(), - parameters: Vec::new(), - return_type: data_type::Type::None, - }, - content: vec![ - branch_block(0, 1, 2), - branch_block(1, 3, 5), - branch_block(3, 2, 9), - jump_block(2, 1), - branch_block(5, 4, 8), - jump_block(4, 6), - branch_block(8, 6, 3), - jump_block(6, 7), - jump_block(7, 8), - ret_block(9), - ], - }; - let mut editor = Editor::new(function_definition); - let pass = FixIrreducible; - pass.run(&mut editor); - assert_eq!(editor.content.content.len(), 12); - let guard1 = editor - .content - .content - .iter() - .find(|it| it.name.as_ref().unwrap() == "_guard_block_scc_1_3_for_bb1") - .unwrap(); - assert!(guard1.content.len() == 2); - assert!(guard1.content[0].as_phi().from.contains(&PhiSource { - value: RegisterName("_extracted_branch_condition_scc_1_3_at_bb0".to_string()).into(), - block: "bb0".to_string() - })); - assert_eq!( - guard1.content[1].as_branch().operand1, - RegisterName("_should_goto_scc_1_3_bb1".to_string()).into() - ); - let function_definition = FunctionDefinition { header: ir::FunctionHeader { name: "f".to_string(), @@ -481,6 +442,48 @@ fn test_fix_irreducible() { ); } +#[test] +fn test_fix_irreducible_0() { + let function_definition = FunctionDefinition { + header: ir::FunctionHeader { + name: "f".to_string(), + parameters: Vec::new(), + return_type: data_type::Type::None, + }, + content: vec![ + branch_block(0, 1, 2), + branch_block(1, 3, 5), + branch_block(3, 2, 9), + jump_block(2, 1), + branch_block(5, 4, 8), + jump_block(4, 6), + branch_block(8, 6, 3), + jump_block(6, 7), + jump_block(7, 8), + ret_block(9), + ], + }; + let mut editor = Editor::new(function_definition); + let pass = FixIrreducible; + pass.run(&mut editor); + assert_eq!(editor.content.content.len(), 12); + let guard1 = editor + .content + .content + .iter() + .find(|it| it.name.as_ref().unwrap() == "_guard_block_scc_1_3_for_bb1") + .unwrap(); + assert!(guard1.content.len() == 2); + assert!(guard1.content[0].as_phi().from.contains(&PhiSource { + value: RegisterName("_extracted_branch_condition_scc_1_3_at_bb0".to_string()).into(), + block: "bb0".to_string() + })); + assert_eq!( + guard1.content[1].as_branch().operand1, + RegisterName("_should_goto_scc_1_3_bb1".to_string()).into() + ); +} + #[test] fn test_shit() { let function_definition = FunctionDefinition { diff --git a/src/utility/graph/mod.rs b/src/utility/graph/mod.rs index c23060c..2a07554 100644 --- a/src/utility/graph/mod.rs +++ b/src/utility/graph/mod.rs @@ -1,11 +1,10 @@ -use std::{collections::HashMap, ops::Sub}; +use std::collections::HashMap; use petgraph::{ algo::dominators::Dominators, - graph::DiGraph, visit::{ - EdgeRef, FilterNode, GraphBase, GraphRef, IntoEdgesDirected, IntoNeighbors, - IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount, NodeFiltered, VisitMap, Visitable, + EdgeRef, GraphBase, IntoEdgesDirected, IntoNeighbors, IntoNeighborsDirected, + IntoNodeIdentifiers, VisitMap, Visitable, }, Direction, }; @@ -28,7 +27,7 @@ pub mod subgraph; /// /// [0]: http://www.cs.rice.edu/~keith/EMBED/dom.pdf pub fn dominance_frontiers( - dorminators: &Dominators, + dominators: &Dominators, graph: G, ) -> HashMap> where @@ -56,10 +55,10 @@ where if predecessors_len >= 2 { for p in predecessors { let mut runner = p; - if let Some(dominator) = dorminators.immediate_dominator(node) { + if let Some(dominator) = dominators.immediate_dominator(node) { while runner != dominator { frontiers.entry(runner).or_insert(vec![]).push(node); - runner = dorminators.immediate_dominator(runner).unwrap(); + runner = dominators.immediate_dominator(runner).unwrap(); } } } diff --git a/src/utility/graph/subgraph.rs b/src/utility/graph/subgraph.rs index 9df3e1e..3efd4b5 100644 --- a/src/utility/graph/subgraph.rs +++ b/src/utility/graph/subgraph.rs @@ -1,17 +1,11 @@ -use std::{ - fmt::{self, Debug}, - vec, -}; +use std::{fmt::Debug, vec}; use itertools::Itertools; use petgraph::{ - adj::NodeIndex, - graph::{DiGraph, EdgeIndex}, - graphmap::NeighborsDirected, + graph::DiGraph, visit::{ - Data, EdgeFiltered, EdgeFilteredNeighbors, EdgeRef, FilterEdge, FilterNode, GraphBase, - GraphRef, IntoEdgeReferences, IntoEdges, IntoEdgesDirected, IntoNeighbors, - IntoNeighborsDirected, IntoNodeIdentifiers, IntoNodeReferences, NodeCount, NodeFiltered, + Data, EdgeFiltered, EdgeRef, GraphBase, IntoEdgeReferences, IntoEdges, IntoEdgesDirected, + IntoNeighbors, IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount, NodeFiltered, Visitable, }, Direction, @@ -68,7 +62,7 @@ impl<'a> IntoNeighbors for &'a CFSubGraph<'a> { let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n)); let filtered_edges = EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id())); - filtered_nodes.neighbors(a).collect_vec().into_iter() + filtered_edges.neighbors(a).collect_vec().into_iter() } } @@ -79,7 +73,7 @@ impl<'a> IntoNeighborsDirected for &'a CFSubGraph<'a> { let filtered_nodes = NodeFiltered::from_fn(self.graph, |n| self.nodes.contains(&n)); let filtered_edges = EdgeFiltered::from_fn(&filtered_nodes, |e| self.edges.contains(&e.id())); - filtered_nodes + filtered_edges .neighbors_directed(n, d) .collect_vec() .into_iter() From 6c02d00024d6d866263f06f780cc5725ce2af5d7 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Fri, 3 May 2024 00:23:00 +0200 Subject: [PATCH 08/11] fix several bugs --- .gitignore | 1 + src/backend/wasm/lowering.rs | 36 ++++++++++++++++--- src/backend/wasm/mod.rs | 40 ++++++++++++++++++++-- src/ir/editor/analyzer/control_flow/scc.rs | 5 +-- src/ir/function/mod.rs | 6 +++- src/ir/function/parameter.rs | 6 ++-- 6 files changed, 79 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 4369801..b488886 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,4 @@ Cargo.lock *.clef doc/public lcov.info +*.wasm diff --git a/src/backend/wasm/lowering.rs b/src/backend/wasm/lowering.rs index dbf6825..c70bade 100644 --- a/src/backend/wasm/lowering.rs +++ b/src/backend/wasm/lowering.rs @@ -13,7 +13,7 @@ use crate::{ calculate::{binary, unary}, Branch, IRStatement, }, - FunctionHeader, RegisterName, + FunctionDefinition, FunctionHeader, RegisterName, }, utility::data_type::{Integer, Type}, }; @@ -258,7 +258,20 @@ fn lower_statement( result.instruction(&Instruction::BrIf(levels as u32)); } } - IRStatement::Ret(_) => { + IRStatement::Ret(ret) => { + if let Some(return_value) = &ret.value { + match return_value { + Quantity::RegisterName(register) => { + let register_id = register_name_id_map[register]; + result.instruction(&Instruction::LocalGet(register_id)); + } + Quantity::GlobalVariableName(_) => unimplemented!(), + Quantity::NumberLiteral(n) => { + // fixme: `n`'s data type + result.instruction(&Instruction::I32Const(*n as _)); + } + } + } result.instruction(&Instruction::Return); } @@ -471,14 +484,26 @@ fn lower_control_flow_element( } pub fn lower_function_body( - body: &[BasicBlock], + function: &FunctionDefinition, control_flow_root: &ControlFlowElement, binded_cfg: &BindedControlFlowGraph, ) -> Function { - let locals = body + let parameters = function + .header + .parameters .iter() - .flat_map(|block| block.created_registers()) + .map(|parameter| (parameter.name.clone(), parameter.data_type.clone())) + .collect_vec(); + let locals = parameters + .into_iter() + .chain( + function + .content + .iter() + .flat_map(|block| block.created_registers()), + ) .collect_vec(); + let body = &function.content; let locals_name_type_map: HashMap = locals.iter().cloned().collect(); let register_name_id_map: HashMap = locals .iter() @@ -503,5 +528,6 @@ pub fn lower_function_body( &locals_name_type_map, ); } + function.instruction(&Instruction::End); function } diff --git a/src/backend/wasm/mod.rs b/src/backend/wasm/mod.rs index 86ce871..afe666a 100644 --- a/src/backend/wasm/mod.rs +++ b/src/backend/wasm/mod.rs @@ -211,15 +211,16 @@ fn generate_function( ); let cfg = ControlFlowGraph::new(); let cfg = cfg.bind(function_definition); - let function = lower_function_body(&function_definition.content, control_flow_root, &cfg); + let function = lower_function_body(function_definition, control_flow_root, &cfg); result.3.function(&function); } #[cfg(test)] mod tests { - use std::{assert_matches::assert_matches, str::FromStr}; + use std::{assert_matches::assert_matches, fs::File, io::Write, str::FromStr}; use analyzer::Analyzer; + use wasm_encoder::Module; use crate::{ ir::{ @@ -547,4 +548,39 @@ mod tests { let result = fold(&function_definition); dbg!(result); } + + #[test] + fn test_generate_function() { + let function = ir::function::parse( + "fn test_code(i32 %a, i32 %b) -> i32 { + test_code_entry: + %0 = add i32 %a, 2 + %2 = add i32 %b, 1 + %4 = add i32 %0, %2 + ret %4 +} +", + ) + .unwrap() + .1; + let folded = fold(&function); + let root = ControlFlowElement::new_block(folded); + let mut types = TypeSection::new(); + let mut functions = FunctionSection::new(); + let mut exports = ExportSection::new(); + let mut codes = CodeSection::new(); + generate_function( + (&mut types, &mut functions, &mut exports, &mut codes), + &function, + &root, + ); + let mut module = Module::new(); + module.section(&types); + module.section(&functions); + module.section(&exports); + module.section(&codes); + let bytes = module.finish(); + let mut f = File::create("./test.wasm").unwrap(); + f.write_all(&bytes).unwrap(); + } } diff --git a/src/ir/editor/analyzer/control_flow/scc.rs b/src/ir/editor/analyzer/control_flow/scc.rs index daf7c97..1b84b0d 100644 --- a/src/ir/editor/analyzer/control_flow/scc.rs +++ b/src/ir/editor/analyzer/control_flow/scc.rs @@ -110,7 +110,6 @@ impl<'a> BindedScc<'a> { None } else { let entry_node = entry_nodes[0]; - dbg!(entry_node); let largest_simple_loop = self .graph_part .neighbors_directed(entry_node.into(), Incoming) @@ -192,9 +191,7 @@ impl<'a> BindedScc<'a> { /// Returns the smallest non trivial (ie. not a single node) scc /// the node is in. pub fn smallest_non_trivial_scc_node_in(&self, node: usize) -> Option { - if !self.contains(node) { - None - } else if self.is_trivial() { + if !self.contains(node) || self.is_trivial() { None } else if let Some(sub_sccs) = self.top_level_sccs() { for sub_scc in sub_sccs { diff --git a/src/ir/function/mod.rs b/src/ir/function/mod.rs index 59b59d4..6837974 100644 --- a/src/ir/function/mod.rs +++ b/src/ir/function/mod.rs @@ -203,7 +203,11 @@ pub fn parse(code: &str) -> IResult<&str, FunctionDefinition> { multispace0, data_type::parse, multispace0, - delimited(tag("{"), many0(basic_block::parse), tag("}")), + delimited( + tag("{"), + many0(parsing::in_multispace(basic_block::parse)), + tag("}"), + ), )), |(_, _, name, parameters, _, _, _, return_type, _, basic_blocks)| { formalize(FunctionDefinition { diff --git a/src/ir/function/parameter.rs b/src/ir/function/parameter.rs index 9e96957..20f0e73 100644 --- a/src/ir/function/parameter.rs +++ b/src/ir/function/parameter.rs @@ -1,5 +1,5 @@ use nom::{ - bytes::complete::tag, character::complete::space0, combinator::map, sequence::tuple, IResult, + character::complete::space0, combinator::map, sequence::tuple, IResult, }; use crate::{ @@ -19,8 +19,8 @@ pub struct Parameter { pub fn parse(code: &str) -> IResult<&str, Parameter> { map( - tuple((local::parse, space0, tag(":"), space0, data_type::parse)), - |(name, _, _, _, data_type)| Parameter { name, data_type }, + tuple((data_type::parse, space0, local::parse, space0)), + |(data_type, _, name, _)| Parameter { name, data_type }, )(code) } From 25c8edfb55c4723cc6db4f09ea7e5b7e5ceec214 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Fri, 3 May 2024 14:50:59 +0200 Subject: [PATCH 09/11] memory operation --- src/backend/wasm/lowering.rs | 117 ++++++++++++++++-- src/backend/wasm/mod.rs | 93 +++++++++++++- src/ir/function/basic_block.rs | 2 +- src/ir/function/statement/calculate/binary.rs | 7 +- 4 files changed, 206 insertions(+), 13 deletions(-) diff --git a/src/backend/wasm/lowering.rs b/src/backend/wasm/lowering.rs index c70bade..3a91642 100644 --- a/src/backend/wasm/lowering.rs +++ b/src/backend/wasm/lowering.rs @@ -1,17 +1,17 @@ use std::collections::HashMap; use itertools::Itertools; -use wasm_encoder::{BlockType, Function, Instruction, ValType}; +use wasm_encoder::{BlockType, Function, Instruction, MemArg, ValType}; use crate::{ ir::{ - analyzer::BindedControlFlowGraph, + analyzer::{register_usage, BindedControlFlowGraph}, function::basic_block::BasicBlock, quantity::Quantity, statement::{ branch::BranchType, calculate::{binary, unary}, - Branch, IRStatement, + Alloca, Branch, IRStatement, }, FunctionDefinition, FunctionHeader, RegisterName, }, @@ -196,11 +196,13 @@ fn lower_binary_calculate( fn lower_statement( result: &mut Function, register_name_id_map: &HashMap, + offset_table: &HashMap, statement: &IRStatement, bb_id: usize, cfe_root: &ControlFlowElement, register_type: &HashMap, cfg: &BindedControlFlowGraph, + move_stack_pointer: i32, ) { let current = cfe_root.find_node(bb_id).unwrap(); match statement { @@ -259,6 +261,12 @@ fn lower_statement( } } IRStatement::Ret(ret) => { + if move_stack_pointer != 0 { + result.instruction(&Instruction::GlobalGet(0)); + result.instruction(&Instruction::I32Const(move_stack_pointer)); + result.instruction(&Instruction::I32Sub); + result.instruction(&Instruction::GlobalSet(0)); + } if let Some(return_value) = &ret.value { match return_value { Quantity::RegisterName(register) => { @@ -274,12 +282,54 @@ fn lower_statement( } result.instruction(&Instruction::Return); } + IRStatement::Load(load) => { + result.instruction(&Instruction::GlobalGet(0)); + let offset = match &load.from { + Quantity::RegisterName(register) => offset_table.get(®ister).unwrap(), + Quantity::GlobalVariableName(_) => unimplemented!(), + Quantity::NumberLiteral(_) => unimplemented!(), + }; + result.instruction(&Instruction::I32Const(*offset)); + result.instruction(&Instruction::I32Sub); + result.instruction(&Instruction::I32Load(MemArg { + offset: 0, + align: 0, + memory_index: 0, + })); + let load_target = register_name_id_map.get(&load.to).unwrap(); + result.instruction(&Instruction::LocalSet(*load_target)); + } + IRStatement::Store(store) => { + // todo: handle data types + result.instruction(&Instruction::GlobalGet(0)); + let offset = match &store.target { + Quantity::RegisterName(register) => offset_table.get(®ister).unwrap(), + Quantity::GlobalVariableName(_) => unimplemented!(), + Quantity::NumberLiteral(_) => unimplemented!(), + }; + result.instruction(&Instruction::I32Const(*offset)); + result.instruction(&Instruction::I32Sub); + match &store.source { + Quantity::RegisterName(register) => { + // fixme: register maybe another memory Address + let register_id = register_name_id_map.get(®ister).unwrap(); + result.instruction(&Instruction::LocalGet(*register_id)); + } + Quantity::NumberLiteral(n) => { + result.instruction(&Instruction::I32Const(*n as _)); + } + Quantity::GlobalVariableName(_) => unimplemented!(), + } + result.instruction(&Instruction::I32Store(MemArg { + offset: 0, + align: 0, + memory_index: 0, + })); + } + IRStatement::Alloca(_) => (/* already handled in alloca_stack */), IRStatement::Phi(_) => unimplemented!(), - IRStatement::Alloca(_) => unimplemented!(), IRStatement::Call(_) => unimplemented!(), - IRStatement::Load(_) => unimplemented!(), - IRStatement::Store(_) => unimplemented!(), IRStatement::LoadField(_) => unimplemented!(), IRStatement::SetField(_) => unimplemented!(), } @@ -351,21 +401,24 @@ fn lower_basic_block( result: &mut Function, bb_id: usize, block: &BasicBlock, - _selector: CFSelector, binded_cfg: &BindedControlFlowGraph, register_name_id_map: &HashMap, + offset_table: &HashMap, cfe_root: &ControlFlowElement, register_type: &HashMap, + move_stack_pointer: i32, ) { for statement in &block.content { lower_statement( result, register_name_id_map, + offset_table, statement, bb_id, cfe_root, register_type, binded_cfg, + move_stack_pointer, ) } } @@ -377,8 +430,10 @@ fn lower_control_flow_element( current: CFSelector, binded_cfg: &BindedControlFlowGraph, register_name_id_map: &HashMap, + offset_table: &HashMap, cfe_root: &ControlFlowElement, register_type: &HashMap, + move_stack_pointer: i32, ) { match element { ControlFlowElement::Block { content } => { @@ -393,8 +448,10 @@ fn lower_control_flow_element( new_selector, binded_cfg, register_name_id_map, + offset_table, cfe_root, register_type, + move_stack_pointer, ); } result.instruction(&Instruction::End); @@ -413,10 +470,12 @@ fn lower_control_flow_element( new_selector, binded_cfg, register_name_id_map, + offset_table, cfe_root, register_type, + move_stack_pointer, ); - result.instruction(&Instruction::If(BlockType::Empty)); + // result.instruction(&Instruction::If(BlockType::Empty)); for (i, success_block) in on_success.iter().enumerate() { let mut new_selector = current.clone(); new_selector.push_back(CFSelectorSegment::IndexInSuccess(i)); @@ -427,8 +486,10 @@ fn lower_control_flow_element( new_selector, binded_cfg, register_name_id_map, + offset_table, cfe_root, register_type, + move_stack_pointer, ); } if !on_failure.is_empty() { @@ -443,8 +504,10 @@ fn lower_control_flow_element( new_selector, binded_cfg, register_name_id_map, + offset_table, cfe_root, register_type, + move_stack_pointer, ); } } @@ -462,8 +525,10 @@ fn lower_control_flow_element( new_selector, binded_cfg, register_name_id_map, + offset_table, cfe_root, register_type, + move_stack_pointer, ); } result.instruction(&Instruction::End); @@ -473,16 +538,38 @@ fn lower_control_flow_element( result, *id, &body[*id], - current, binded_cfg, register_name_id_map, + offset_table, cfe_root, register_type, + move_stack_pointer, ); } } } +pub fn alloca_stack(function: &FunctionDefinition) -> (HashMap, i32) { + let alloca_statements = function + .content + .iter() + .flat_map(|it| it.content.iter()) + .flat_map(IRStatement::try_as_alloca); + let mut offset_table = HashMap::new(); + let mut current_offset = 0; + for Alloca { to, alloc_type } in alloca_statements { + let bytes = match alloc_type { + Type::Address => 4i32, + Type::Integer(Integer { width, .. }) => *width as i32 / 8, + Type::StructRef(_) => unimplemented!(), + Type::None => unreachable!(), + }; + offset_table.insert(to.clone(), current_offset + bytes); + current_offset += bytes; + } + (offset_table, current_offset) +} + pub fn lower_function_body( function: &FunctionDefinition, control_flow_root: &ControlFlowElement, @@ -494,13 +581,15 @@ pub fn lower_function_body( .iter() .map(|parameter| (parameter.name.clone(), parameter.data_type.clone())) .collect_vec(); + let (offset_table, move_stack_pointer) = alloca_stack(function); let locals = parameters .into_iter() .chain( function .content .iter() - .flat_map(|block| block.created_registers()), + .flat_map(|block| block.created_registers()) + .filter(|(register, _)| !offset_table.contains_key(®ister)), ) .collect_vec(); let body = &function.content; @@ -511,6 +600,12 @@ pub fn lower_function_body( .map(|(a, (b, _))| (b.clone(), a as u32)) .collect(); let mut function = Function::new_with_locals_types(locals.iter().map(|(_, t)| lower_type(t))); + if move_stack_pointer != 0 { + function.instruction(&Instruction::GlobalGet(0)); + function.instruction(&Instruction::I32Const(move_stack_pointer)); + function.instruction(&Instruction::I32Add); + function.instruction(&Instruction::GlobalSet(0)); + } for (i, control_flow_element) in control_flow_root .block_content() .unwrap() @@ -524,8 +619,10 @@ pub fn lower_function_body( CFSelector::from_segment(CFSelectorSegment::ContentAtIndex(i)), binded_cfg, ®ister_name_id_map, + &offset_table, control_flow_root, &locals_name_type_map, + move_stack_pointer, ); } function.instruction(&Instruction::End); diff --git a/src/backend/wasm/mod.rs b/src/backend/wasm/mod.rs index afe666a..322f716 100644 --- a/src/backend/wasm/mod.rs +++ b/src/backend/wasm/mod.rs @@ -220,7 +220,9 @@ mod tests { use std::{assert_matches::assert_matches, fs::File, io::Write, str::FromStr}; use analyzer::Analyzer; - use wasm_encoder::Module; + use wasm_encoder::{ + ConstExpr, GlobalSection, GlobalType, MemorySection, MemoryType, Module, ValType, + }; use crate::{ ir::{ @@ -569,16 +571,105 @@ mod tests { let mut functions = FunctionSection::new(); let mut exports = ExportSection::new(); let mut codes = CodeSection::new(); + let mut global_section = GlobalSection::new(); + let mut memory_section = MemorySection::new(); + generate_function( + (&mut types, &mut functions, &mut exports, &mut codes), + &function, + &root, + ); + let mut module = Module::new(); + // stack pointer + global_section.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + shared: false, + }, + &ConstExpr::i32_const(0), + ); + memory_section.memory(MemoryType { + minimum: 1, + maximum: None, + memory64: false, + shared: false, + page_size_log2: None, + }); + module.section(&types); + module.section(&functions); + module.section(&memory_section); + module.section(&global_section); + module.section(&exports); + module.section(&codes); + + let bytes = module.finish(); + let mut f = File::create("./test.wasm").unwrap(); + f.write_all(&bytes).unwrap(); + } + + #[test] + fn test_generate_function2() { + let function = ir::function::parse( + "fn test_condition(i32 %a, i32 %b) -> i32 { + test_condition_entry: + %a_0_addr = alloca i32 + store i32 %a, address %a_0_addr + %b_0_addr = alloca i32 + store i32 %b, address %b_0_addr + %1 = load i32 %a_0_addr + %2 = load i32 %b_0_addr + %0 = slt i32 %1, %2 + bne %0, 0, if_0_success, if_0_fail + if_0_success: + %3 = load i32 %a_0_addr + ret %3 + if_0_fail: + %4 = load i32 %b_0_addr + ret %4 + if_0_end: + ret 0 +} +", + ) + .unwrap() + .1; + let folded = fold(&function); + let root = ControlFlowElement::new_block(folded); + let mut types = TypeSection::new(); + let mut functions = FunctionSection::new(); + let mut exports = ExportSection::new(); + let mut codes = CodeSection::new(); + let mut global_section = GlobalSection::new(); + let mut memory_section = MemorySection::new(); generate_function( (&mut types, &mut functions, &mut exports, &mut codes), &function, &root, ); let mut module = Module::new(); + // stack pointer + global_section.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + shared: false, + }, + &ConstExpr::i32_const(0), + ); + memory_section.memory(MemoryType { + minimum: 1, + maximum: None, + memory64: false, + shared: false, + page_size_log2: None, + }); module.section(&types); module.section(&functions); + module.section(&memory_section); + module.section(&global_section); module.section(&exports); module.section(&codes); + let bytes = module.finish(); let mut f = File::create("./test.wasm").unwrap(); f.write_all(&bytes).unwrap(); diff --git a/src/ir/function/basic_block.rs b/src/ir/function/basic_block.rs index 078b4ba..e95c009 100644 --- a/src/ir/function/basic_block.rs +++ b/src/ir/function/basic_block.rs @@ -75,7 +75,7 @@ impl fmt::Display for BasicBlock { /// Parse a basic block's name. fn parse_tag(code: &str) -> IResult<&str, String> { - map(pair(parsing::ident, tag(":")), |(_, name)| name.to_string())(code) + map(pair(parsing::ident, tag(":")), |(name, _)| name)(code) } /// Parse the ir code to get a [`BasicBlock`]. diff --git a/src/ir/function/statement/calculate/binary.rs b/src/ir/function/statement/calculate/binary.rs index 981e656..b67a0fb 100644 --- a/src/ir/function/statement/calculate/binary.rs +++ b/src/ir/function/statement/calculate/binary.rs @@ -87,7 +87,12 @@ impl fmt::Display for BinaryOperation { fn binary_operation(code: &str) -> IResult<&str, BinaryOperation> { alt(( map(tag("add"), |_| BinaryOperation::Add), - map(tag("less"), |_| BinaryOperation::LessThan), + map(tag("slt"), |_| BinaryOperation::LessThan), + map(tag("sle"), |_| BinaryOperation::LessOrEqualThan), + map(tag("sgt"), |_| BinaryOperation::GreaterThan), + map(tag("sge"), |_| BinaryOperation::GreaterOrEqualThan), + map(tag("eq"), |_| BinaryOperation::Equal), + map(tag("ne"), |_| BinaryOperation::NotEqual), map(tag("sub"), |_| BinaryOperation::Sub), map(tag("or"), |_| BinaryOperation::Or), map(tag("xor"), |_| BinaryOperation::Xor), From 771de3111c6b9cb7fbc5a4a917244d025e6f6c93 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Fri, 3 May 2024 15:43:49 +0200 Subject: [PATCH 10/11] fix loop --- src/backend/wasm/control_flow/selector.rs | 16 ++-- src/backend/wasm/lowering.rs | 50 ++++++++---- src/backend/wasm/mod.rs | 92 ++++++++++++++++++++--- 3 files changed, 128 insertions(+), 30 deletions(-) diff --git a/src/backend/wasm/control_flow/selector.rs b/src/backend/wasm/control_flow/selector.rs index 72f1829..5bcf6d8 100644 --- a/src/backend/wasm/control_flow/selector.rs +++ b/src/backend/wasm/control_flow/selector.rs @@ -1,6 +1,7 @@ use delegate::delegate; use std::{ - cmp::Ordering, collections::VecDeque, fmt, iter::zip, num::ParseIntError, ops::RangeBounds, str::FromStr, + cmp::Ordering, collections::VecDeque, fmt, iter::zip, num::ParseIntError, ops::RangeBounds, + str::FromStr, }; #[derive(Clone, PartialEq, Eq)] @@ -244,17 +245,18 @@ impl CFSelector { } pub fn levels_before(&self, other: &CFSelector) -> Option { - if self.is_after(other).unwrap_or(false) { + // if self.is_after(other).unwrap_or(false) { + // return None; + // } + let shared_part = Self::lowest_common_ancestor(self, other); + dbg!(&shared_part); + if shared_part.is_empty() { return None; } - let shared_part = Self::lowest_common_ancestor(self, other); let self_unique_part = self.range(shared_part.len()..); Some( self_unique_part - .0 - .into_iter() - .filter(|it| matches!(it, CFSelectorSegment::ContentAtIndex(_))) - .count(), + .0.len(), ) } } diff --git a/src/backend/wasm/lowering.rs b/src/backend/wasm/lowering.rs index 3a91642..1c58ec5 100644 --- a/src/backend/wasm/lowering.rs +++ b/src/backend/wasm/lowering.rs @@ -5,7 +5,7 @@ use wasm_encoder::{BlockType, Function, Instruction, MemArg, ValType}; use crate::{ ir::{ - analyzer::{register_usage, BindedControlFlowGraph}, + analyzer::{BindedControlFlowGraph}, function::basic_block::BasicBlock, quantity::Quantity, statement::{ @@ -256,8 +256,11 @@ fn lower_statement( IRStatement::Jump(jump_statement) => { let jump_target = cfg.basic_block_index_by_name(&jump_statement.label); let jump_target_selector = cfe_root.find_node(jump_target).unwrap(); + println!("{:?}", cfe_root); + println!("{jump_target_selector}, {current}"); if let Some(levels) = jump_target_selector.levels_before(¤t) { - result.instruction(&Instruction::BrIf(levels as u32)); + println!("{levels}"); + result.instruction(&Instruction::Br(levels as u32)); } } IRStatement::Ret(ret) => { @@ -285,7 +288,7 @@ fn lower_statement( IRStatement::Load(load) => { result.instruction(&Instruction::GlobalGet(0)); let offset = match &load.from { - Quantity::RegisterName(register) => offset_table.get(®ister).unwrap(), + Quantity::RegisterName(register) => offset_table.get(register).unwrap(), Quantity::GlobalVariableName(_) => unimplemented!(), Quantity::NumberLiteral(_) => unimplemented!(), }; @@ -303,7 +306,7 @@ fn lower_statement( // todo: handle data types result.instruction(&Instruction::GlobalGet(0)); let offset = match &store.target { - Quantity::RegisterName(register) => offset_table.get(®ister).unwrap(), + Quantity::RegisterName(register) => offset_table.get(register).unwrap(), Quantity::GlobalVariableName(_) => unimplemented!(), Quantity::NumberLiteral(_) => unimplemented!(), }; @@ -312,7 +315,7 @@ fn lower_statement( match &store.source { Quantity::RegisterName(register) => { // fixme: register maybe another memory Address - let register_id = register_name_id_map.get(®ister).unwrap(); + let register_id = register_name_id_map.get(register).unwrap(); result.instruction(&Instruction::LocalGet(*register_id)); } Quantity::NumberLiteral(n) => { @@ -589,7 +592,7 @@ pub fn lower_function_body( .content .iter() .flat_map(|block| block.created_registers()) - .filter(|(register, _)| !offset_table.contains_key(®ister)), + .filter(|(register, _)| !offset_table.contains_key(register)), ) .collect_vec(); let body = &function.content; @@ -599,12 +602,12 @@ pub fn lower_function_body( .enumerate() .map(|(a, (b, _))| (b.clone(), a as u32)) .collect(); - let mut function = Function::new_with_locals_types(locals.iter().map(|(_, t)| lower_type(t))); + let mut result = Function::new_with_locals_types(locals.iter().map(|(_, t)| lower_type(t))); if move_stack_pointer != 0 { - function.instruction(&Instruction::GlobalGet(0)); - function.instruction(&Instruction::I32Const(move_stack_pointer)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::GlobalSet(0)); + result.instruction(&Instruction::GlobalGet(0)); + result.instruction(&Instruction::I32Const(move_stack_pointer)); + result.instruction(&Instruction::I32Add); + result.instruction(&Instruction::GlobalSet(0)); } for (i, control_flow_element) in control_flow_root .block_content() @@ -613,7 +616,7 @@ pub fn lower_function_body( .enumerate() { lower_control_flow_element( - &mut function, + &mut result, body, control_flow_element, CFSelector::from_segment(CFSelectorSegment::ContentAtIndex(i)), @@ -625,6 +628,25 @@ pub fn lower_function_body( move_stack_pointer, ); } - function.instruction(&Instruction::End); - function + if move_stack_pointer != 0 { + result.instruction(&Instruction::GlobalGet(0)); + result.instruction(&Instruction::I32Const(move_stack_pointer)); + result.instruction(&Instruction::I32Sub); + result.instruction(&Instruction::GlobalSet(0)); + } + match function.header.return_type { + Type::Integer(Integer { + signed: true, + width: 32, + }) => { + result.instruction(&Instruction::I32Const(0)); + result.instruction(&Instruction::Return); + } + Type::Integer(Integer { .. }) => unimplemented!(), + Type::StructRef(_) => unimplemented!(), + Type::Address => unimplemented!(), + Type::None => (/* pass */), + } + result.instruction(&Instruction::End); + result } diff --git a/src/backend/wasm/mod.rs b/src/backend/wasm/mod.rs index 322f716..9c736ee 100644 --- a/src/backend/wasm/mod.rs +++ b/src/backend/wasm/mod.rs @@ -18,11 +18,7 @@ mod control_flow; mod lowering; // fixme: currently this presumes that we have not folded any if-else or block before -fn fold_loop( - function_content: &FunctionDefinition, - scc: &BindedScc, - current_result: &mut Vec, -) { +fn fold_loop(scc: &BindedScc, current_result: &mut Vec) { if let Some(sub_sccs) = scc.top_level_sccs() { for sub_scc in sub_sccs .into_iter() @@ -52,7 +48,7 @@ fn fold_loop( .iter() .cloned() .collect_vec(); - fold_loop(function_content, &sub_scc, &mut new_result); + fold_loop(&sub_scc, &mut new_result); current_result.splice( sub_scc_start_index..=sub_scc_end_index, [ControlFlowElement::Loop { @@ -69,7 +65,6 @@ fn fold_if_else_once( ) -> bool { for block_id in 0..control_flow_graph.bind_on.content.len() { let predecessors = control_flow_graph.predecessor(block_id); - println!("{block_id}'s predecessors are {:?}", predecessors); if predecessors.len() == 1 { let predecessor_block_id = predecessors[0]; let predecessor_last_instruction = control_flow_graph.bind_on[predecessor_block_id] @@ -185,7 +180,7 @@ fn fold(function_definition: &FunctionDefinition) -> Vec { let mut content = ControlFlowElement::new_block(current_result); let control_flow_graph = binded.control_flow_graph(); let root_scc = control_flow_graph.top_level_scc(); - fold_loop(function_definition, &root_scc, content.unwrap_content_mut()); + fold_loop(&root_scc, content.unwrap_content_mut()); fold_if_else(function_definition, &mut content); mem::take(content.unwrap_content_mut()) } @@ -526,7 +521,7 @@ mod tests { let mut current_result = (0..(function_definition.content.len())) .map(ControlFlowElement::new_node) .collect_vec(); - fold_loop(&function_definition, &scc, &mut current_result); + fold_loop(&scc, &mut current_result); dbg!(current_result); } @@ -674,4 +669,83 @@ mod tests { let mut f = File::create("./test.wasm").unwrap(); f.write_all(&bytes).unwrap(); } + + #[test] + fn test_generate_function3() { + let function = ir::function::parse( + "fn test_condition(i32 %a, i32 %b) -> i32 { + test_condition_entry: + %a_0_addr = alloca i32 + store i32 %a, address %a_0_addr + %b_0_addr = alloca i32 + store i32 %b, address %b_0_addr + %result_0_addr = alloca i32 + store i32 0, address %result_0_addr + %i_0_addr = alloca i32 + %0 = load i32 %a_0_addr + store i32 %0, address %i_0_addr + j loop_0_condition + loop_0_condition: + %2 = load i32 %i_0_addr + %3 = load i32 %b_0_addr + %1 = slt i32 %2, %3 + bne %1, 0, loop_0_success, loop_0_fail + loop_0_success: + %5 = load i32 %result_0_addr + %6 = load i32 %i_0_addr + %4 = add i32 %5, %6 + store i32 %4, address %result_0_addr + %8 = load i32 %i_0_addr + %7 = add i32 %8, 1 + store i32 %7, address %i_0_addr + j loop_0_condition + loop_0_fail: + %9 = load i32 %result_0_addr + ret %9 +} +", + ) + .unwrap() + .1; + let folded = fold(&function); + let root = ControlFlowElement::new_block(folded); + let mut types = TypeSection::new(); + let mut functions = FunctionSection::new(); + let mut exports = ExportSection::new(); + let mut codes = CodeSection::new(); + let mut global_section = GlobalSection::new(); + let mut memory_section = MemorySection::new(); + generate_function( + (&mut types, &mut functions, &mut exports, &mut codes), + &function, + &root, + ); + let mut module = Module::new(); + // stack pointer + global_section.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + shared: false, + }, + &ConstExpr::i32_const(0), + ); + memory_section.memory(MemoryType { + minimum: 1, + maximum: None, + memory64: false, + shared: false, + page_size_log2: None, + }); + module.section(&types); + module.section(&functions); + module.section(&memory_section); + module.section(&global_section); + module.section(&exports); + module.section(&codes); + + let bytes = module.finish(); + let mut f = File::create("./test.wasm").unwrap(); + f.write_all(&bytes).unwrap(); + } } From e8b541af1a4f9aaf5934823b9ec9be5c7d4a9d3b Mon Sep 17 00:00:00 2001 From: longfangsong Date: Fri, 10 May 2024 19:56:55 +0200 Subject: [PATCH 11/11] come command can generate wasm now --- src/backend/wasm/control_flow/selector.rs | 5 +- src/backend/wasm/lowering.rs | 2 +- src/backend/wasm/mod.rs | 54 +++++++++++++++++++++- src/bin/come.rs | 30 ++++++++++-- src/ir/editor/analyzer/control_flow/mod.rs | 1 - src/ir/function/parameter.rs | 4 +- 6 files changed, 80 insertions(+), 16 deletions(-) diff --git a/src/backend/wasm/control_flow/selector.rs b/src/backend/wasm/control_flow/selector.rs index 5bcf6d8..7cb6f28 100644 --- a/src/backend/wasm/control_flow/selector.rs +++ b/src/backend/wasm/control_flow/selector.rs @@ -254,9 +254,6 @@ impl CFSelector { return None; } let self_unique_part = self.range(shared_part.len()..); - Some( - self_unique_part - .0.len(), - ) + Some(self_unique_part.0.len()) } } diff --git a/src/backend/wasm/lowering.rs b/src/backend/wasm/lowering.rs index 1c58ec5..e56962f 100644 --- a/src/backend/wasm/lowering.rs +++ b/src/backend/wasm/lowering.rs @@ -5,7 +5,7 @@ use wasm_encoder::{BlockType, Function, Instruction, MemArg, ValType}; use crate::{ ir::{ - analyzer::{BindedControlFlowGraph}, + analyzer::BindedControlFlowGraph, function::basic_block::BasicBlock, quantity::Quantity, statement::{ diff --git a/src/backend/wasm/mod.rs b/src/backend/wasm/mod.rs index 9c736ee..aef4b65 100644 --- a/src/backend/wasm/mod.rs +++ b/src/backend/wasm/mod.rs @@ -1,12 +1,15 @@ use itertools::Itertools; use std::mem; -use wasm_encoder::{CodeSection, ExportKind, ExportSection, FunctionSection, TypeSection}; +use wasm_encoder::{ + CodeSection, ConstExpr, ExportKind, ExportSection, FunctionSection, GlobalSection, GlobalType, + MemorySection, MemoryType, Module, TypeSection, ValType, +}; use crate::ir::{ analyzer::{BindedControlFlowGraph, BindedScc, ControlFlowGraph, IsAnalyzer}, editor::Analyzer, statement::IRStatement, - FunctionDefinition, + FunctionDefinition, IR, }; use self::{ @@ -210,6 +213,53 @@ fn generate_function( result.3.function(&function); } +pub fn compile(ir_content: &[IR]) -> Module { + let mut module = Module::new(); + let mut types = TypeSection::new(); + let mut functions = FunctionSection::new(); + let mut exports = ExportSection::new(); + let mut codes = CodeSection::new(); + let mut global_section = GlobalSection::new(); + let mut memory_section = MemorySection::new(); + // stack pointer + global_section.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + shared: false, + }, + &ConstExpr::i32_const(0), + ); + memory_section.memory(MemoryType { + minimum: 1, + maximum: None, + memory64: false, + shared: false, + page_size_log2: None, + }); + + for ir_part in ir_content { + if let IR::FunctionDefinition(function) = ir_part { + let folded = fold(function); + let root = ControlFlowElement::new_block(folded); + generate_function( + (&mut types, &mut functions, &mut exports, &mut codes), + function, + &root, + ); + } + } + + module.section(&types); + module.section(&functions); + module.section(&memory_section); + module.section(&global_section); + module.section(&exports); + module.section(&codes); + + module +} + #[cfg(test)] mod tests { use std::{assert_matches::assert_matches, fs::File, io::Write, str::FromStr}; diff --git a/src/bin/come.rs b/src/bin/come.rs index 8545c39..b3496b9 100644 --- a/src/bin/come.rs +++ b/src/bin/come.rs @@ -1,9 +1,9 @@ -use std::path::PathBuf; +use std::{fs::File, path::PathBuf}; -use clap::Parser; +use clap::{Parser, ValueEnum}; use come::{ ast, - backend::riscv, + backend::{riscv, wasm}, ir::{self, optimize}, }; use ezio::file; @@ -11,6 +11,14 @@ use shadow_rs::shadow; use std::io::Write; shadow!(build); +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, ValueEnum)] +enum Target { + /// riscv backend + RISCV, + /// web assembly backend + WASM, +} + /// Come language compiler. #[derive(Parser, Debug)] #[command(version, long_version = build::CLAP_LONG_VERSION, about, long_about = None)] @@ -29,6 +37,9 @@ struct Args { #[arg(short = 'O', long, value_delimiter = ',')] optimize: Vec, + + #[arg(short = 't', long, value_enum)] + target: Target, } fn main() { @@ -43,6 +54,15 @@ fn main() { writeln!(w, "{ir}").unwrap(); } } - let code = riscv::from_ir::emit_asm(&ir); - file::write(args.output, &code); + match args.target { + Target::RISCV => { + let code = riscv::from_ir::emit_asm(&ir); + file::write(args.output, &code); + } + Target::WASM => { + let module = wasm::compile(&ir); + let mut output_file = File::create(args.output).unwrap(); + output_file.write_all(module.as_slice()).unwrap(); + } + } } diff --git a/src/ir/editor/analyzer/control_flow/mod.rs b/src/ir/editor/analyzer/control_flow/mod.rs index 5b30b13..c99fe9b 100644 --- a/src/ir/editor/analyzer/control_flow/mod.rs +++ b/src/ir/editor/analyzer/control_flow/mod.rs @@ -1,5 +1,4 @@ use std::{ - borrow::Borrow, cell::{OnceCell, Ref, RefCell}, collections::HashMap, }; diff --git a/src/ir/function/parameter.rs b/src/ir/function/parameter.rs index 20f0e73..b5fcd93 100644 --- a/src/ir/function/parameter.rs +++ b/src/ir/function/parameter.rs @@ -1,6 +1,4 @@ -use nom::{ - character::complete::space0, combinator::map, sequence::tuple, IResult, -}; +use nom::{character::complete::space0, combinator::map, sequence::tuple, IResult}; use crate::{ ast,