diff --git a/Cargo.lock b/Cargo.lock index 71a91a3ebe..10b4aff613 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -593,7 +593,13 @@ dependencies = [ name = "fe-codegen" version = "0.14.0-alpha" dependencies = [ + "fe-common", "fe-mir", + "fe-new_abi", + "fxhash", + "salsa", + "smol_str", + "yultsur", ] [[package]] diff --git a/crates/bar.dot b/crates/bar.dot new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/codegen/Cargo.toml b/crates/codegen/Cargo.toml index bc618a69ed..31f40afbb3 100644 --- a/crates/codegen/Cargo.toml +++ b/crates/codegen/Cargo.toml @@ -5,4 +5,10 @@ authors = ["The Fe Developers "] edition = "2021" [dependencies] -fe-mir = { path = "../mir", version = "^0.14.0-alpha" } \ No newline at end of file +fe-mir = { path = "../mir", version = "^0.14.0-alpha" } +fe-common = { path = "../common", version = "^0.14.0-alpha" } +fe-new_abi = { path = "../new_abi", version = "^0.14.0-alpha" } +salsa = "0.16.1" +fxhash = "0.2.1" +smol_str = "0.1.21" +yultsur = { git = "https://github.com/g-r-a-n-t/yultsur", rev = "ae85470" } \ No newline at end of file diff --git a/crates/codegen/src/db.rs b/crates/codegen/src/db.rs new file mode 100644 index 0000000000..835697d507 --- /dev/null +++ b/crates/codegen/src/db.rs @@ -0,0 +1,23 @@ +use std::rc::Rc; + +use fe_common::db::{Upcast, UpcastMut}; +use fe_mir::{ + db::MirDb, + ir::{FunctionBody, FunctionId, FunctionSignature, TypeId}, +}; +use fe_new_abi::{function::AbiFunction, types::AbiType}; + +mod queries; + +#[salsa::query_group(CodegenDbStorage)] +pub trait CodegenDb: MirDb + Upcast + UpcastMut { + #[salsa::invoke(queries::function::legalized_signature)] + fn codegen_legalized_signature(&self, function_id: FunctionId) -> Rc; + #[salsa::invoke(queries::function::legalized_body)] + fn codegen_legalized_body(&self, function_id: FunctionId) -> Rc; + + #[salsa::invoke(queries::abi::abi_type)] + fn codegen_abi_type(&self, ty: TypeId) -> AbiType; + #[salsa::invoke(queries::abi::abi_function)] + fn codegen_abi_function(&self, function_id: FunctionId) -> Rc; +} diff --git a/crates/codegen/src/db/queries.rs b/crates/codegen/src/db/queries.rs new file mode 100644 index 0000000000..db68776cbe --- /dev/null +++ b/crates/codegen/src/db/queries.rs @@ -0,0 +1,2 @@ +pub mod abi; +pub mod function; diff --git a/crates/codegen/src/db/queries/abi.rs b/crates/codegen/src/db/queries/abi.rs new file mode 100644 index 0000000000..2720503cc0 --- /dev/null +++ b/crates/codegen/src/db/queries/abi.rs @@ -0,0 +1,85 @@ +use std::rc::Rc; + +use fe_mir::ir::{self, FunctionId, TypeId}; +use fe_new_abi::{ + function::{AbiFunction, AbiFunctionType}, + types::{AbiTupleField, AbiType}, +}; + +use crate::db::CodegenDb; + +pub fn abi_function(db: &dyn CodegenDb, function: FunctionId) -> Rc { + // We use a legalized signature. + let sig = db.codegen_legalized_signature(function); + + let name = function.name(db.upcast()); + let args = sig + .params + .iter() + .map(|param| (param.name.to_string(), db.codegen_abi_type(param.ty))) + .collect(); + let ret_ty = sig.return_type.map(|ty| db.codegen_abi_type(ty)); + + AbiFunction::new(AbiFunctionType::Function, name.to_string(), args, ret_ty).into() +} + +pub fn abi_type(db: &dyn CodegenDb, ty: TypeId) -> AbiType { + if ty.is_zero_sized(db.upcast()) { + unreachable!("zero-sized type must be removed in legalization"); + } + + let ty = ty.data(db.upcast()); + + match ty.as_ref() { + ir::Type::I8 => AbiType::Int(8), + ir::Type::I16 => AbiType::Int(16), + ir::Type::I32 => AbiType::Int(32), + ir::Type::I64 => AbiType::Int(64), + ir::Type::I128 => AbiType::Int(128), + ir::Type::I256 => AbiType::Int(256), + ir::Type::U8 => AbiType::UInt(8), + ir::Type::U16 => AbiType::UInt(16), + ir::Type::U32 => AbiType::UInt(32), + ir::Type::U64 => AbiType::UInt(64), + ir::Type::U128 => AbiType::UInt(128), + ir::Type::U256 => AbiType::UInt(256), + ir::Type::Bool => AbiType::Bool, + ir::Type::Address => AbiType::Address, + ir::Type::Unit => unreachable!("zero-sized type must be removed in legalization"), + ir::Type::Array(def) => { + let elem_ty = db.codegen_abi_type(def.elem_ty); + let len = def.len; + AbiType::Array { + elem_ty: elem_ty.into(), + len, + } + } + ir::Type::Tuple(def) => { + let fields = def + .items + .iter() + .enumerate() + .map(|(i, item)| { + let field_ty = db.codegen_abi_type(*item); + AbiTupleField::new(format!("{}", i), field_ty) + }) + .collect(); + + AbiType::Tuple(fields) + } + ir::Type::Struct(def) => { + let fields = def + .fields + .iter() + .map(|(name, ty)| { + let ty = db.codegen_abi_type(*ty); + AbiTupleField::new(name.to_string(), ty) + }) + .collect(); + + AbiType::Tuple(fields) + } + ir::Type::Event(_) | ir::Type::Contract(_) => unreachable!(), + ir::Type::Map(_) => todo!("map type can't be used in parameter or return type"), + } +} diff --git a/crates/codegen/src/db/queries/function.rs b/crates/codegen/src/db/queries/function.rs new file mode 100644 index 0000000000..4878dd4be8 --- /dev/null +++ b/crates/codegen/src/db/queries/function.rs @@ -0,0 +1,17 @@ +use std::rc::Rc; + +use fe_mir::ir::{FunctionBody, FunctionId, FunctionSignature}; + +use crate::{db::CodegenDb, yul::legalize}; + +pub fn legalized_signature(db: &dyn CodegenDb, function: FunctionId) -> Rc { + let mut sig = function.signature(db.upcast()).as_ref().clone(); + legalize::legalize_func_signature(db, &mut sig); + sig.into() +} + +pub fn legalized_body(db: &dyn CodegenDb, function: FunctionId) -> Rc { + let mut body = function.body(db.upcast()).as_ref().clone(); + legalize::legalize_func_body(db, &mut body); + body.into() +} diff --git a/crates/codegen/src/lib.rs b/crates/codegen/src/lib.rs index a795c81958..37ec962db2 100644 --- a/crates/codegen/src/lib.rs +++ b/crates/codegen/src/lib.rs @@ -1 +1,2 @@ +pub mod db; pub mod yul; diff --git a/crates/codegen/src/yul/inst_order.rs b/crates/codegen/src/yul/inst_order.rs new file mode 100644 index 0000000000..1e599100a4 --- /dev/null +++ b/crates/codegen/src/yul/inst_order.rs @@ -0,0 +1,309 @@ +#![allow(unused)] + +use fe_mir::{ + analysis::{ + domtree::DFSet, loop_tree::LoopId, post_domtree::PostIDom, ControlFlowGraph, DomTree, + LoopTree, PostDomTree, + }, + ir::{inst::BranchInfo, BasicBlockId, FunctionBody, InstId, ValueId}, +}; + +#[derive(Debug, Clone, Default)] +pub struct InstOrder { + pub order: Vec, +} + +#[derive(Debug, Clone)] +pub enum StructuralInst { + Inst(InstId), + If { + cond: ValueId, + then: Vec, + else_: Vec, + }, + For { + body: Vec, + }, + Break, + Continue, +} + +struct InstSerializer<'a> { + body: &'a FunctionBody, + cfg: ControlFlowGraph, + loop_tree: LoopTree, + df: DFSet, + pd_tree: PostDomTree, + scope: Option, +} + +impl<'a> InstSerializer<'a> { + fn new(body: &'a FunctionBody) -> Self { + let cfg = ControlFlowGraph::compute(body); + let domtree = DomTree::compute(&cfg); + let df = domtree.compute_df(&cfg); + let pd_tree = PostDomTree::compute(body); + let loop_tree = LoopTree::compute(&cfg, &domtree); + + Self { + body, + cfg, + loop_tree, + df, + pd_tree, + scope: None, + } + } + + fn serialize_insts(&mut self) -> InstOrder { + self.scope = None; + let entry = self.cfg.entry(); + let mut order = vec![]; + self.analyze_block(entry, &mut order); + InstOrder { order } + } + + fn analyze_block(&mut self, block: BasicBlockId, order: &mut Vec) { + match self.loop_tree.loop_of_block(block) { + Some(lp) + if block == self.loop_tree.loop_header(lp) + && Some(block) != self.scope.as_ref().and_then(Scope::loop_header) => + { + let loop_exit = self.find_loop_exit(lp); + self.enter_loop_scope(block, loop_exit); + let mut body = vec![]; + self.analyze_block(block, &mut body); + self.exit_scope(); + order.push(StructuralInst::For { body }); + + if let Some(exit) = loop_exit { + self.analyze_block(exit, order); + } + return; + } + _ => {} + }; + + for inst in self.body.order.iter_inst(block) { + if self.body.store.is_terminator(inst) { + break; + } + order.push(StructuralInst::Inst(inst)); + } + + let terminator = self.body.order.terminator(&self.body.store, block).unwrap(); + match self.analyze_terminator(terminator) { + TerminatorInfo::If { + cond, + then, + else_, + merge_block, + } => { + let mut then_body = vec![]; + let mut else_body = vec![]; + + self.enter_if_scope(merge_block); + if let Some(merge_block) = merge_block { + if merge_block == else_ { + self.analyze_block(then, &mut then_body) + } else if merge_block == then { + self.analyze_block(else_, &mut else_body); + } else { + self.analyze_block(then, &mut then_body); + self.analyze_block(else_, &mut else_body); + } + order.push(StructuralInst::If { + cond, + then: then_body, + else_: else_body, + }); + self.exit_scope(); + self.analyze_block(merge_block, order) + } else { + self.analyze_block(then, &mut then_body); + self.analyze_block(else_, &mut else_body); + self.exit_scope(); + order.push(StructuralInst::If { + cond, + then: then_body, + else_: else_body, + }); + } + } + TerminatorInfo::ToMergeBlock => {} + TerminatorInfo::Continue => order.push(StructuralInst::Continue), + TerminatorInfo::Break => order.push(StructuralInst::Break), + TerminatorInfo::FallThrough(next) => self.analyze_block(next, order), + TerminatorInfo::NormalInst(inst) => order.push(StructuralInst::Inst(inst)), + } + } + + fn enter_loop_scope(&mut self, header: BasicBlockId, exit: Option) { + let kind = ScopeKind::Loop { header, exit }; + let current_scope = std::mem::take(&mut self.scope); + self.scope = Some(Scope { + kind, + parent: current_scope.map(Into::into), + }); + } + + fn enter_if_scope(&mut self, merge_block: Option) { + let kind = ScopeKind::If { merge_block }; + let current_scope = std::mem::take(&mut self.scope); + self.scope = Some(Scope { + kind, + parent: current_scope.map(Into::into), + }); + } + + fn exit_scope(&mut self) { + let current_scope = std::mem::take(&mut self.scope); + self.scope = current_scope.unwrap().parent.map(|parent| *parent); + } + + // NOTE: We assume loop has at most one canonical loop exit. + fn find_loop_exit(&self, lp: LoopId) -> Option { + let mut exit_candidates = vec![]; + for block_in_loop in self.loop_tree.iter_blocks_post_order(&self.cfg, lp) { + for &succ in self.cfg.succs(block_in_loop) { + if !self.loop_tree.is_block_in_loop(succ, lp) { + exit_candidates.push(succ); + } + } + } + + if exit_candidates.is_empty() { + return None; + } + + for &cand in &exit_candidates { + // `cand` is true loop exit if the `cand` is contained in the dominance frontier + // of all other candidates. and yeset foo + if exit_candidates.iter().all(|&block| { + if block == cand { + true + } else if let Some(mut df) = self.df.frontiers(block) { + df.any(|frontier| frontier == cand) + } else { + true + } + }) { + return Some(cand); + } + } + + None + } + + fn analyze_terminator(&self, inst: InstId) -> TerminatorInfo { + debug_assert!(self.body.store.is_terminator(inst)); + + match self.body.store.branch_info(inst) { + BranchInfo::Jump(dest) => self.analyze_jump(dest), + BranchInfo::Branch(cond, then, else_) => { + self.analyze_branch(self.body.order.inst_block(inst), cond, then, else_) + } + BranchInfo::NotBranch => TerminatorInfo::NormalInst(inst), + } + } + + // NOTE: We remove critical edges in legalization pass, so `break` and + // `continue` never appear in branch info. + fn analyze_branch( + &self, + block: BasicBlockId, + cond: ValueId, + then: BasicBlockId, + else_: BasicBlockId, + ) -> TerminatorInfo { + let merge_block = match self.pd_tree.post_idom(block) { + PostIDom::DummyEntry | PostIDom::DummyExit => None, + PostIDom::Block(block) => Some(block), + }; + + TerminatorInfo::If { + cond, + then, + else_, + merge_block, + } + } + + fn analyze_jump(&self, dest: BasicBlockId) -> TerminatorInfo { + match &self.scope { + Some(scope) => { + if Some(dest) == scope.loop_header_recursive() { + TerminatorInfo::Continue + } else if Some(dest) == scope.loop_exit_recursive() { + TerminatorInfo::Break + } else if Some(dest) == scope.if_merge_block() { + TerminatorInfo::ToMergeBlock + } else { + TerminatorInfo::FallThrough(dest) + } + } + + None => TerminatorInfo::FallThrough(dest), + } + } +} + +struct Scope { + kind: ScopeKind, + parent: Option>, +} + +#[derive(Debug, Clone, Copy)] +enum ScopeKind { + Loop { + header: BasicBlockId, + exit: Option, + }, + If { + merge_block: Option, + }, +} + +impl Scope { + fn loop_header(&self) -> Option { + match self.kind { + ScopeKind::Loop { header, .. } => Some(header), + _ => None, + } + } + fn loop_header_recursive(&self) -> Option { + match self.kind { + ScopeKind::Loop { header, .. } => Some(header), + _ => self.parent.as_ref()?.loop_header_recursive(), + } + } + + fn loop_exit_recursive(&self) -> Option { + match self.kind { + ScopeKind::Loop { exit, .. } => exit, + _ => self.parent.as_ref()?.loop_exit_recursive(), + } + } + + fn if_merge_block(&self) -> Option { + match self.kind { + ScopeKind::If { merge_block } => merge_block, + _ => None, + } + } +} + +#[derive(Debug, Clone)] +enum TerminatorInfo { + If { + cond: ValueId, + then: BasicBlockId, + else_: BasicBlockId, + merge_block: Option, + }, + ToMergeBlock, + Continue, + Break, + FallThrough(BasicBlockId), + NormalInst(InstId), +} diff --git a/crates/codegen/src/yul/isel/mod.rs b/crates/codegen/src/yul/isel/mod.rs new file mode 100644 index 0000000000..e6a246a6fa --- /dev/null +++ b/crates/codegen/src/yul/isel/mod.rs @@ -0,0 +1,25 @@ +#![allow(unused)] +use fe_mir::{ + analysis::ControlFlowGraph, + ir::{FunctionBody, FunctionSignature, ValueId}, +}; +use fxhash::FxHashMap; +use smol_str::SmolStr; + +use crate::db::CodegenDb; + +struct FuncLowerHelper<'db, 'a> { + db: &'db dyn CodegenDb, + value_map: FxHashMap, + sig: &'a FunctionSignature, + body: &'a FunctionBody, + cfg: ControlFlowGraph, + sink: Vec, + ret_value: Option, +} + +impl<'db, 'a> FuncLowerHelper<'db, 'a> { + fn lower_func(self) -> Vec { + todo!() + } +} diff --git a/crates/codegen/src/yul/legalize/body.rs b/crates/codegen/src/yul/legalize/body.rs index 69292db8a2..4683402b59 100644 --- a/crates/codegen/src/yul/legalize/body.rs +++ b/crates/codegen/src/yul/legalize/body.rs @@ -1,15 +1,19 @@ -use fe_mir::{ - db::MirDb, - ir::{ - body_cursor::{BodyCursor, CursorLocation}, - inst::InstKind, - FunctionBody, Inst, InstId, - }, +use fe_mir::ir::{ + body_cursor::{BodyCursor, CursorLocation}, + inst::InstKind, + FunctionBody, Inst, InstId, }; -pub fn legalize_func_body(db: &dyn MirDb, body: &mut FunctionBody) { - let mut cursor = BodyCursor::new_at_entry(body); +use crate::db::CodegenDb; + +use super::critical_edge::CriticalEdgeSplitter; + +pub fn legalize_func_body(db: &dyn CodegenDb, body: &mut FunctionBody) { + // Remove critical edges. + CriticalEdgeSplitter::new().run(body); + // Remove zero-sized types usage. + let mut cursor = BodyCursor::new_at_entry(body); loop { match cursor.loc() { CursorLocation::BlockTop(_) | CursorLocation::BlockBottom(_) => cursor.proceed(), @@ -22,41 +26,40 @@ pub fn legalize_func_body(db: &dyn MirDb, body: &mut FunctionBody) { } } -fn legalize_inst(db: &dyn MirDb, body: &mut FunctionBody, inst: InstId) { - legalize_call(db, body, inst); - legalize_return(db, body, inst); +fn legalize_inst(db: &dyn CodegenDb, body: &mut FunctionBody, inst: InstId) { + legalize_inst_arg(db, body, inst); legalize_inst_result(db, body, inst); } -// Remove zero-sized arguments from call instruction. -fn legalize_call(db: &dyn MirDb, body: &mut FunctionBody, inst: InstId) { +fn legalize_inst_arg(db: &dyn CodegenDb, body: &mut FunctionBody, inst_id: InstId) { + // Replace inst with dummy inst to avoid borrow checker complaining. let dummy_inst = Inst::nop(); - let mut call_inst = body.store.replace_inst(inst, dummy_inst); - if let InstKind::Call { args, .. } = &mut call_inst.kind { - args.retain(|arg| !body.store.value_ty(*arg).is_zero_sized(db)); - } + let mut inst = body.store.replace_inst(inst_id, dummy_inst); - body.store.replace_inst(inst, call_inst); -} + match &mut inst.kind { + InstKind::AggregateConstruct { args, .. } | InstKind::Call { args, .. } => { + args.retain(|arg| !body.store.value_ty(*arg).is_zero_sized(db.upcast())); + } -// Remove return argument if its type is zero sized. -fn legalize_return(db: &dyn MirDb, body: &mut FunctionBody, inst: InstId) { - if let Inst { - kind: InstKind::Return { arg: Some(arg) }, - source, - } = body.store.inst_data(inst) - { - if body.store.value_ty(*arg).is_zero_sized(db) { - let ret_inst = Inst::new(InstKind::Return { arg: None }, source.clone()); - body.store.replace_inst(inst, ret_inst); + InstKind::Return { arg } => { + if arg + .map(|arg| body.store.value_ty(arg).is_zero_sized(db.upcast())) + .unwrap_or(false) + { + *arg = None; + } } + + _ => {} } + + body.store.replace_inst(inst_id, inst); } -/// Remove instruction result if its type is zero sized. -fn legalize_inst_result(db: &dyn MirDb, body: &mut FunctionBody, inst: InstId) { +/// Remove instruction result if its type is zero-sized. +fn legalize_inst_result(db: &dyn CodegenDb, body: &mut FunctionBody, inst: InstId) { if let Some(result) = body.store.inst_result(inst) { - if body.store.value_ty(result).is_zero_sized(db) { + if body.store.value_ty(result).is_zero_sized(db.upcast()) { body.store.remove_inst_result(inst) } } diff --git a/crates/codegen/src/yul/legalize/critical_edge.rs b/crates/codegen/src/yul/legalize/critical_edge.rs new file mode 100644 index 0000000000..40a898c18b --- /dev/null +++ b/crates/codegen/src/yul/legalize/critical_edge.rs @@ -0,0 +1,133 @@ +use fe_mir::{ + analysis::ControlFlowGraph, + ir::{ + body_cursor::{BodyCursor, CursorLocation}, + inst::{BranchInfo, InstKind}, + BasicBlock, BasicBlockId, FunctionBody, Inst, InstId, SourceInfo, + }, +}; + +#[derive(Debug)] +pub struct CriticalEdgeSplitter { + critical_edges: Vec, +} + +impl CriticalEdgeSplitter { + pub fn new() -> Self { + Self { + critical_edges: Vec::default(), + } + } + + pub fn run(&mut self, func: &mut FunctionBody) { + let cfg = ControlFlowGraph::compute(func); + + for block in func.order.iter_block() { + let terminator = func.order.terminator(&func.store, block).unwrap(); + self.add_critical_edges(terminator, func, &cfg); + } + + self.split_edges(func); + } + + fn add_critical_edges( + &mut self, + terminator: InstId, + func: &FunctionBody, + cfg: &ControlFlowGraph, + ) { + match func.store.branch_info(terminator) { + BranchInfo::Branch(_, then, else_) => { + if cfg.preds(then).len() > 1 { + self.critical_edges.push(CriticalEdge { + terminator, + to: then, + }); + } + if cfg.preds(else_).len() > 1 { + self.critical_edges.push(CriticalEdge { + terminator, + to: else_, + }); + } + } + BranchInfo::Jump(_) | BranchInfo::NotBranch => {} + } + } + + fn split_edges(&mut self, func: &mut FunctionBody) { + for edge in std::mem::take(&mut self.critical_edges) { + let terminator = edge.terminator; + let source_block = func.order.inst_block(terminator); + let original_dest = edge.to; + + // Create new block that contains only jump inst. + let new_dest = func.store.store_block(BasicBlock {}); + let mut cursor = BodyCursor::new(func, CursorLocation::BlockTop(source_block)); + cursor.insert_block(new_dest); + cursor.set_loc(CursorLocation::BlockTop(new_dest)); + cursor.store_and_insert_inst(Inst::new( + InstKind::Jump { + dest: original_dest, + }, + SourceInfo::dummy(), + )); + + // Rewrite branch destination to the new dest. + func.store + .rewrite_branch_dest(terminator, original_dest, new_dest); + } + } +} + +#[derive(Debug)] +struct CriticalEdge { + terminator: InstId, + to: BasicBlockId, +} + +#[cfg(test)] +mod tests { + use fe_mir::ir::{body_builder::BodyBuilder, FunctionId, TypeId}; + + use super::*; + + fn body_builder() -> BodyBuilder { + BodyBuilder::new(FunctionId(0), SourceInfo::dummy()) + } + + #[test] + fn critical_edge_remove() { + let mut builder = body_builder(); + let lp_header = builder.make_block(); + let lp_body = builder.make_block(); + let exit = builder.make_block(); + + let dummy_ty = TypeId(0); + let v0 = builder.make_imm_from_bool(false, dummy_ty); + builder.branch(v0, lp_header, exit, SourceInfo::dummy()); + + builder.move_to_block(lp_header); + builder.jump(lp_body, SourceInfo::dummy()); + + builder.move_to_block(lp_body); + builder.branch(v0, lp_header, exit, SourceInfo::dummy()); + + builder.move_to_block(exit); + builder.ret(v0, SourceInfo::dummy()); + + let mut func = builder.build(); + CriticalEdgeSplitter::new().run(&mut func); + let cfg = ControlFlowGraph::compute(&func); + + for &header_pred in cfg.preds(lp_header) { + debug_assert_eq!(cfg.succs(header_pred).len(), 1); + debug_assert_eq!(cfg.succs(header_pred)[0], lp_header); + } + + for &exit_pred in cfg.preds(exit) { + debug_assert_eq!(cfg.succs(exit_pred).len(), 1); + debug_assert_eq!(cfg.succs(exit_pred)[0], exit); + } + } +} diff --git a/crates/codegen/src/yul/legalize/mod.rs b/crates/codegen/src/yul/legalize/mod.rs index 450e825562..62e82f78fe 100644 --- a/crates/codegen/src/yul/legalize/mod.rs +++ b/crates/codegen/src/yul/legalize/mod.rs @@ -1,4 +1,5 @@ mod body; +mod critical_edge; mod signature; pub use body::legalize_func_body; diff --git a/crates/codegen/src/yul/legalize/signature.rs b/crates/codegen/src/yul/legalize/signature.rs index ab7755949f..1354f70f8e 100644 --- a/crates/codegen/src/yul/legalize/signature.rs +++ b/crates/codegen/src/yul/legalize/signature.rs @@ -1,6 +1,8 @@ -use fe_mir::{db::MirDb, ir::FunctionSignature}; +use fe_mir::ir::FunctionSignature; -pub fn legalize_func_signature(_db: &dyn MirDb, _sig: &mut FunctionSignature) { +use crate::db::CodegenDb; + +pub fn legalize_func_signature(_db: &dyn CodegenDb, _sig: &mut FunctionSignature) { // TODO: Remove zero sized types from arguments, also remove return type if // it's zero-sized } diff --git a/crates/codegen/src/yul/mod.rs b/crates/codegen/src/yul/mod.rs index 47c4684630..ba32f5b6eb 100644 --- a/crates/codegen/src/yul/mod.rs +++ b/crates/codegen/src/yul/mod.rs @@ -1 +1,4 @@ +pub mod isel; pub mod legalize; + +mod inst_order; diff --git a/crates/mir/bar.dot b/crates/mir/bar.dot new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/mir/src/analysis/cfg.rs b/crates/mir/src/analysis/cfg.rs index 4f70d9c322..bf1f2a44fa 100644 --- a/crates/mir/src/analysis/cfg.rs +++ b/crates/mir/src/analysis/cfg.rs @@ -67,7 +67,7 @@ impl ControlFlowGraph { self.exits.push(block) } BranchInfo::Jump(dest) => self.add_edge(block, dest), - BranchInfo::Branch((then, else_)) => { + BranchInfo::Branch(_, then, else_) => { self.add_edge(block, then); self.add_edge(block, else_); } diff --git a/crates/mir/src/analysis/domtree.rs b/crates/mir/src/analysis/domtree.rs index 77220401f1..9775db6335 100644 --- a/crates/mir/src/analysis/domtree.rs +++ b/crates/mir/src/analysis/domtree.rs @@ -3,6 +3,8 @@ //! The algorithm is based on Keith D. Cooper., Timothy J. Harvey., and Ken //! Kennedy.: A Simple, Fast Dominance Algorithm: +use std::collections::BTreeSet; + use fxhash::FxHashMap; use crate::ir::BasicBlockId; @@ -120,6 +122,47 @@ impl DomTree { b1 } + + /// Compute dominance frontiers of each blocks. + pub fn compute_df(&self, cfg: &ControlFlowGraph) -> DFSet { + let mut df = DFSet::default(); + + for &block in &self.rpo { + let preds = cfg.preds(block); + if preds.len() < 2 { + continue; + } + + for pred in preds { + let mut runner = *pred; + while self.doms.get(&block) != Some(&runner) && self.is_reachable(runner) { + df.0.entry(runner).or_default().insert(block); + runner = self.doms[&runner]; + } + } + } + + df + } +} + +/// Dominance frontiers of each blocks. +#[derive(Default, Debug)] +pub struct DFSet(FxHashMap>); + +impl DFSet { + /// Returns all dominance frontieres of a `block`. + pub fn frontiers( + &self, + block: BasicBlockId, + ) -> Option + '_> { + self.0.get(&block).map(|set| set.iter().copied()) + } + + /// Returns number of frontier blocks of a `block`. + pub fn frontier_num(&self, block: BasicBlockId) -> usize { + self.0.get(&block).map(BTreeSet::len).unwrap_or(0) + } } #[cfg(test)] @@ -128,9 +171,11 @@ mod tests { use crate::ir::{body_builder::BodyBuilder, FunctionBody, FunctionId, SourceInfo, TypeId}; - fn calc_dom(func: &FunctionBody) -> DomTree { + fn calc_dom(func: &FunctionBody) -> (DomTree, DFSet) { let cfg = ControlFlowGraph::compute(func); - DomTree::compute(&cfg) + let domtree = DomTree::compute(&cfg); + let df = domtree.compute_df(&cfg); + (domtree, df) } fn body_builder() -> BodyBuilder { @@ -161,12 +206,24 @@ mod tests { let func = builder.build(); - let dom_tree = calc_dom(&func); + let (dom_tree, df) = calc_dom(&func); let entry_block = func.order.entry(); assert_eq!(dom_tree.idom(entry_block), None); assert_eq!(dom_tree.idom(then_block), Some(entry_block)); assert_eq!(dom_tree.idom(else_block), Some(entry_block)); assert_eq!(dom_tree.idom(merge_block), Some(entry_block)); + + assert_eq!(df.frontier_num(entry_block), 0); + assert_eq!(df.frontier_num(then_block), 1); + assert_eq!( + df.frontiers(then_block).unwrap().next().unwrap(), + merge_block + ); + assert_eq!( + df.frontiers(else_block).unwrap().next().unwrap(), + merge_block + ); + assert_eq!(df.frontier_num(merge_block), 0); } #[test] @@ -197,7 +254,7 @@ mod tests { let func = builder.build(); - let dom_tree = calc_dom(&func); + let (dom_tree, _) = calc_dom(&func); let entry_block = func.order.entry(); assert_eq!(dom_tree.idom(entry_block), None); assert_eq!(dom_tree.idom(block1), Some(entry_block)); @@ -267,7 +324,7 @@ mod tests { let func = builder.build(); - let dom_tree = calc_dom(&func); + let (dom_tree, _) = calc_dom(&func); let entry_block = func.order.entry(); assert_eq!(dom_tree.idom(entry_block), None); assert_eq!(dom_tree.idom(block1), Some(entry_block)); diff --git a/crates/mir/src/db/queries/function.rs b/crates/mir/src/db/queries/function.rs index 4a5d56baed..f1bba171d1 100644 --- a/crates/mir/src/db/queries/function.rs +++ b/crates/mir/src/db/queries/function.rs @@ -46,6 +46,11 @@ impl ir::FunctionId { self.analyzer_func(db).is_constructor(db.upcast()) } + pub fn name(&self, db: &dyn MirDb) -> SmolStr { + let analyzer_func = self.analyzer_func(db); + analyzer_func.name(db.upcast()) + } + /// Returns `class_name::fn_name` if a function is a method else `fn_name`. pub fn name_with_class(self, db: &dyn MirDb) -> SmolStr { let analyzer_func = self.analyzer_func(db); diff --git a/crates/mir/src/graphviz/block.rs b/crates/mir/src/graphviz/block.rs index f193f38e0c..38d840fc99 100644 --- a/crates/mir/src/graphviz/block.rs +++ b/crates/mir/src/graphviz/block.rs @@ -51,10 +51,10 @@ impl BlockNode { label::Text::HtmlStr(label.into()) } - pub(super) fn preds(self, db: &dyn MirDb) -> Vec { + pub(super) fn succs(self, db: &dyn MirDb) -> Vec { let func_body = self.func.body(db); let cfg = ControlFlowGraph::compute(&func_body); - cfg.preds(self.block) + cfg.succs(self.block) .iter() .map(|block| Self::new(self.func, *block)) .collect() diff --git a/crates/mir/src/graphviz/function.rs b/crates/mir/src/graphviz/function.rs index 4f2636c16a..85840e2c0e 100644 --- a/crates/mir/src/graphviz/function.rs +++ b/crates/mir/src/graphviz/function.rs @@ -2,7 +2,7 @@ use std::fmt::Write; use dot2::{label, Id}; -use crate::{db::MirDb, ir::FunctionId, pretty_print::PrettyPrint}; +use crate::{analysis::ControlFlowGraph, db::MirDb, ir::FunctionId, pretty_print::PrettyPrint}; use super::block::BlockNode; @@ -41,8 +41,9 @@ impl FunctionNode { pub(super) fn blocks(self, db: &dyn MirDb) -> Vec { let body = self.func.body(db); - body.order - .iter_block() + // We use control flow graph to collect reachable blocks. + let cfg = ControlFlowGraph::compute(&body); + cfg.post_order() .map(|block| BlockNode::new(self.func, block)) .collect() } diff --git a/crates/mir/src/graphviz/module.rs b/crates/mir/src/graphviz/module.rs index 8380f55c92..8882d97663 100644 --- a/crates/mir/src/graphviz/module.rs +++ b/crates/mir/src/graphviz/module.rs @@ -41,10 +41,10 @@ impl<'db> GraphWalk<'db> for ModuleGraph<'db> { let mut edges = vec![]; for func in self.db.mir_lower_module_all_functions(self.module).iter() { for block in FunctionNode::new(*func).blocks(self.db) { - for pred in block.preds(self.db) { + for succ in block.succs(self.db) { let edge = ModuleGraphEdge { - from: pred, - to: block, + from: block, + to: succ, }; edges.push(edge); } diff --git a/crates/mir/src/ir/body_order.rs b/crates/mir/src/ir/body_order.rs index 9028296e63..70df3cf76a 100644 --- a/crates/mir/src/ir/body_order.rs +++ b/crates/mir/src/ir/body_order.rs @@ -83,7 +83,6 @@ impl BodyOrder { /// # Panics /// Panics if /// 1. `block` is not inserted yet. - /// 2. No terminator found in a block. pub fn terminator(&self, store: &BodyDataStore, block: BasicBlockId) -> Option { let last_inst = self.last_inst(block)?; if store.is_terminator(last_inst) { diff --git a/crates/mir/src/ir/function.rs b/crates/mir/src/ir/function.rs index f4f02d8608..760dfc854b 100644 --- a/crates/mir/src/ir/function.rs +++ b/crates/mir/src/ir/function.rs @@ -7,7 +7,7 @@ use smol_str::SmolStr; use super::{ basic_block::BasicBlock, body_order::BodyOrder, - inst::{BranchInfo, Inst, InstId}, + inst::{BranchInfo, Inst, InstId, InstKind}, types::TypeId, value::{Immediate, Local, Value, ValueId}, BasicBlockId, SourceInfo, @@ -32,7 +32,7 @@ pub struct FunctionParam { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct FunctionId(pub(crate) u32); +pub struct FunctionId(pub u32); impl_intern_key!(FunctionId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -168,6 +168,25 @@ impl BodyDataStore { self.inst_results.get(&inst).copied() } + pub fn rewrite_branch_dest(&mut self, inst: InstId, from: BasicBlockId, to: BasicBlockId) { + match &mut self.inst_data_mut(inst).kind { + InstKind::Jump { dest } => { + if *dest == from { + *dest = to; + } + } + InstKind::Branch { then, else_, .. } => { + if *then == from { + *then = to; + } + if *else_ == from { + *else_ = to; + } + } + _ => unreachable!("inst is not a branch"), + } + } + pub fn remove_inst_result(&mut self, inst: InstId) { self.inst_results.remove(&inst); } diff --git a/crates/mir/src/ir/inst.rs b/crates/mir/src/ir/inst.rs index ea8b4520aa..e8ad3eed65 100644 --- a/crates/mir/src/ir/inst.rs +++ b/crates/mir/src/ir/inst.rs @@ -175,7 +175,7 @@ impl Inst { pub fn branch_info(&self) -> BranchInfo { match self.kind { InstKind::Jump { dest } => BranchInfo::Jump(dest), - InstKind::Branch { then, else_, .. } => BranchInfo::Branch((then, else_)), + InstKind::Branch { cond, then, else_ } => BranchInfo::Branch(cond, then, else_), _ => BranchInfo::NotBranch, } } @@ -526,5 +526,5 @@ impl From for YulIntrinsicOp { pub enum BranchInfo { NotBranch, Jump(BasicBlockId), - Branch((BasicBlockId, BasicBlockId)), + Branch(ValueId, BasicBlockId, BasicBlockId), } diff --git a/crates/mir/src/ir/types.rs b/crates/mir/src/ir/types.rs index 983e1864a0..c300e0be31 100644 --- a/crates/mir/src/ir/types.rs +++ b/crates/mir/src/ir/types.rs @@ -29,7 +29,7 @@ pub enum Type { /// An interned Id for [`ArrayDef`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TypeId(pub(crate) u32); +pub struct TypeId(pub u32); impl_intern_key!(TypeId); /// A static array type definition. diff --git a/crates/mir/src/lower/function.rs b/crates/mir/src/lower/function.rs index a5678ee95c..cb1c1527c2 100644 --- a/crates/mir/src/lower/function.rs +++ b/crates/mir/src/lower/function.rs @@ -126,7 +126,9 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { } else { self.make_unit() }; - self.builder.ret(value, stmt.into()) + self.builder.ret(value, stmt.into()); + let next_block = self.builder.make_block(); + self.builder.move_to_block(next_block); } ast::FuncStmt::VarDecl { target, value, .. } => { @@ -182,25 +184,23 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { ast::FuncStmt::For { target, iter, body } => self.lower_for_loop(target, iter, body), ast::FuncStmt::While { test, body } => { - let entry_bb = self.builder.make_block(); - let body_bb = self.builder.make_block(); + let header_bb = self.builder.make_block(); let exit_bb = self.builder.make_block(); - self.builder.jump(entry_bb, SourceInfo::dummy()); - - // Lower while entry. - self.builder.move_to_block(entry_bb); let cond = self.lower_expr(test); self.builder - .branch(cond, body_bb, exit_bb, SourceInfo::dummy()); + .branch(cond, header_bb, exit_bb, SourceInfo::dummy()); // Lower while body. - self.builder.move_to_block(body_bb); - self.enter_loop_scope(entry_bb, exit_bb); + self.builder.move_to_block(header_bb); + self.enter_loop_scope(header_bb, exit_bb); for stmt in body { self.lower_stmt(stmt); } - self.builder.jump(entry_bb, SourceInfo::dummy()); + let cond = self.lower_expr(test); + self.builder + .branch(cond, header_bb, exit_bb, SourceInfo::dummy()); + self.exit_scope(); // Move to while exit bb. @@ -257,16 +257,37 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { // TODO: Generate appropriate error message. let arg = self.make_unit(); self.builder.revert(arg, stmt.into()); + let next_block = self.builder.make_block(); + self.builder.move_to_block(next_block); } ast::FuncStmt::Break => { let exit = self.scope().loop_exit(&self.scopes); - self.builder.jump(exit, stmt.into()) + self.builder.jump(exit, stmt.into()); + let next_block = self.builder.make_block(); + self.builder.move_to_block(next_block); } ast::FuncStmt::Continue => { let entry = self.scope().loop_entry(&self.scopes); - self.builder.jump(entry, stmt.into()) + if let Some(loop_idx) = self.scope().loop_idx(&self.scopes) { + let u256_ty = self.u256_ty(); + let imm_one = self.builder.make_imm(1u32.into(), u256_ty); + let inc = self + .builder + .add(loop_idx, imm_one, u256_ty, SourceInfo::dummy()); + self.builder.assign(loop_idx, inc, SourceInfo::dummy()); + let maximum_iter_count = self.scope().maximum_iter_count(&self.scopes).unwrap(); + let cond = self + .builder + .eq(loop_idx, maximum_iter_count, u256_ty, stmt.into()); + let exit = self.scope().loop_exit(&self.scopes); + self.builder.branch(cond, exit, entry, stmt.into()); + } else { + self.builder.jump(entry, stmt.into()) + } + let next_block = self.builder.make_block(); + self.builder.move_to_block(next_block); } ast::FuncStmt::Revert { error } => { @@ -277,6 +298,8 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { }; self.builder.revert(error, stmt.into()); + let next_block = self.builder.make_block(); + self.builder.move_to_block(next_block); } ast::FuncStmt::Unsafe(stmts) => { @@ -342,10 +365,7 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { for stmt in then { self.lower_stmt(stmt); } - let current_block = self.builder.current_block(); - if !self.builder.is_block_terminated(current_block) { - self.builder.jump(merge_bb, SourceInfo::dummy()); - } + self.builder.jump(merge_bb, SourceInfo::dummy()); self.builder.move_to_block(merge_bb); self.exit_scope(); } else { @@ -373,37 +393,17 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { self.exit_scope(); let else_block_end_bb = self.builder.current_block(); - match ( - self.builder.is_block_terminated(then_block_end_bb), - self.builder.is_block_terminated(else_block_end_bb), - ) { - (true, true) => {} - (false, true) => { - let merge_bb = self.builder.make_block(); - self.builder.move_to_block(then_block_end_bb); - self.builder.jump(merge_bb, SourceInfo::dummy()); - self.builder.move_to_block(merge_bb); - } - (true, false) => { - let merge_bb = self.builder.make_block(); - self.builder.move_to_block(else_block_end_bb); - self.builder.jump(merge_bb, SourceInfo::dummy()); - self.builder.move_to_block(merge_bb); - } - (false, false) => { - let merge_bb = self.builder.make_block(); - self.builder.move_to_block(then_block_end_bb); - self.builder.jump(merge_bb, SourceInfo::dummy()); - self.builder.move_to_block(else_block_end_bb); - self.builder.jump(merge_bb, SourceInfo::dummy()); - self.builder.move_to_block(merge_bb); - } - } + let merge_bb = self.builder.make_block(); + self.builder.move_to_block(then_block_end_bb); + self.builder.jump(merge_bb, SourceInfo::dummy()); + self.builder.move_to_block(else_block_end_bb); + self.builder.jump(merge_bb, SourceInfo::dummy()); + self.builder.move_to_block(merge_bb); } } // NOTE: we assume a type of `iter` is array. - // TODO: Desugar to `loop` + `match` like rustc. + // TODO: Desugar to `loop` + `match` like rustc in HIR to generate better MIR. fn lower_for_loop( &mut self, loop_variable: &Node, @@ -412,7 +412,6 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { ) { let preheader_bb = self.builder.make_block(); let entry_bb = self.builder.make_block(); - let body_bb = self.builder.make_block(); let exit_bb = self.builder.make_block(); let iter_elem_ty = self.analyzer_body.var_types[&loop_variable.id].clone(); let iter_elem_ty = self.db.mir_lowered_type(iter_elem_ty); @@ -434,18 +433,15 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { self.scope_mut() .declare_var(&loop_variable.kind, loop_value); - // Creates index that stores loop iteration count. + // Declare and initialize `loop_idx` to 0. let u256_ty = self.u256_ty(); let loop_idx = Local::tmp_local("$loop_idx_tmp".into(), u256_ty); - - // Declare and initialize `loop_idx` to 0. let loop_idx = self.builder.declare(loop_idx); let imm_zero = self.builder.make_imm(0u32.into(), u256_ty); self.builder.assign(loop_idx, imm_zero, SourceInfo::dummy()); // Evaluates loop variable. let iter = self.lower_expr(iter); - self.builder.jump(entry_bb, SourceInfo::dummy()); // Create maximum loop count. let iter_ty = self.builder.value_ty(iter); @@ -454,36 +450,39 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> { _ => unreachable!(), }; let maximum_iter_count = self.builder.make_imm(maximum_iter_count.into(), u256_ty); - - /* Lower entry. */ - self.builder.move_to_block(entry_bb); - // if loop_idx == array length, then jump to loop exit. let cond = self .builder .eq(loop_idx, maximum_iter_count, u256_ty, SourceInfo::dummy()); self.builder - .branch(cond, exit_bb, body_bb, SourceInfo::dummy()); + .branch(cond, exit_bb, entry_bb, SourceInfo::dummy()); + self.scope_mut().loop_idx = Some(loop_idx); + self.scope_mut().maximum_iter_count = Some(maximum_iter_count); /* Lower body. */ - self.builder.move_to_block(body_bb); + self.builder.move_to_block(entry_bb); - // loop_variable = array[ioop_idx] + // loop_variable = array[loop_idx] let iter_elem = self.builder .aggregate_access(iter, vec![loop_idx], iter_elem_ty, SourceInfo::dummy()); self.builder .assign(loop_value, iter_elem, SourceInfo::dummy()); - // loop_idx+= 1 + + for stmt in body { + self.lower_stmt(stmt); + } + + // loop_idx += 1 let imm_one = self.builder.make_imm(1u32.into(), u256_ty); let inc = self .builder .add(loop_idx, imm_one, u256_ty, SourceInfo::dummy()); self.builder.assign(loop_idx, inc, SourceInfo::dummy()); - - for stmt in body { - self.lower_stmt(stmt); - } - self.builder.jump(entry_bb, SourceInfo::dummy()); + let cond = self + .builder + .eq(loop_idx, maximum_iter_count, u256_ty, SourceInfo::dummy()); + self.builder + .branch(cond, exit_bb, entry_bb, SourceInfo::dummy()); /* Move to exit bb */ self.exit_scope(); @@ -921,6 +920,9 @@ struct Scope { loop_entry: Option, loop_exit: Option, variables: FxHashMap, + // TODO: Remove the below two fields when `for` loop desugaring is implemented. + loop_idx: Option, + maximum_iter_count: Option, } impl Scope { @@ -930,6 +932,8 @@ impl Scope { loop_entry: None, loop_exit: None, variables: FxHashMap::default(), + loop_idx: None, + maximum_iter_count: None, }; // Declare function parameters. @@ -948,6 +952,8 @@ impl Scope { loop_entry: None, loop_exit: None, variables: FxHashMap::default(), + loop_idx: None, + maximum_iter_count: None, } } @@ -957,6 +963,8 @@ impl Scope { loop_entry: loop_entry.into(), loop_exit: loop_exit.into(), variables: FxHashMap::default(), + loop_idx: None, + maximum_iter_count: None, } } @@ -974,6 +982,20 @@ impl Scope { } } + fn loop_idx(&self, scopes: &Arena) -> Option { + match self.loop_idx { + Some(idx) => Some(idx), + None => scopes[self.parent?].loop_idx(scopes), + } + } + + fn maximum_iter_count(&self, scopes: &Arena) -> Option { + match self.maximum_iter_count { + Some(count) => Some(count), + None => scopes[self.parent?].maximum_iter_count(scopes), + } + } + fn declare_var(&mut self, name: &SmolStr, value: ValueId) { debug_assert!(!self.variables.contains_key(name)); diff --git a/crates/new_abi/src/function.rs b/crates/new_abi/src/function.rs index 5fa8dbae80..1ff7417d8e 100644 --- a/crates/new_abi/src/function.rs +++ b/crates/new_abi/src/function.rs @@ -4,7 +4,7 @@ use serde::Serialize; use super::types::AbiType; -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct AbiFunction { #[serde(rename = "type")] func_type: AbiFunctionType, @@ -86,7 +86,7 @@ impl AbiFunctionSelector { } } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] struct AbiFunctionParamInner { name: String, #[serde(flatten)] diff --git a/crates/new_abi/src/types.rs b/crates/new_abi/src/types.rs index 8cf60dc60b..78bcdafe39 100644 --- a/crates/new_abi/src/types.rs +++ b/crates/new_abi/src/types.rs @@ -63,9 +63,9 @@ impl Serialize for AbiType { #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct AbiTupleField { - pub(crate) name: String, + name: String, #[serde(flatten)] - pub(crate) ty: AbiType, + ty: AbiType, } impl AbiTupleField {