diff --git a/crates/codegen/Cargo.toml b/crates/codegen/Cargo.toml index 31f40afbb3..6089268d3b 100644 --- a/crates/codegen/Cargo.toml +++ b/crates/codegen/Cargo.toml @@ -5,6 +5,7 @@ authors = ["The Fe Developers "] edition = "2021" [dependencies] +fe-analyzer = { path = "../analyzer", version = "^0.14.0-alpha"} 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" } diff --git a/crates/codegen/src/db.rs b/crates/codegen/src/db.rs index 835697d507..e8bde2461c 100644 --- a/crates/codegen/src/db.rs +++ b/crates/codegen/src/db.rs @@ -1,8 +1,9 @@ use std::rc::Rc; -use fe_common::db::{Upcast, UpcastMut}; +use fe_analyzer::{db::AnalyzerDbStorage, AnalyzerDb}; +use fe_common::db::{SourceDb, SourceDbStorage, Upcast, UpcastMut}; use fe_mir::{ - db::MirDb, + db::{MirDb, MirDbStorage}, ir::{FunctionBody, FunctionId, FunctionSignature, TypeId}, }; use fe_new_abi::{function::AbiFunction, types::AbiType}; @@ -15,9 +16,55 @@ pub trait CodegenDb: MirDb + Upcast + UpcastMut { 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::function::lower_function)] + fn codegen_lower_function(&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; } + +// TODO: Move this to driver. +#[salsa::database(SourceDbStorage, AnalyzerDbStorage, MirDbStorage, CodegenDbStorage)] +#[derive(Default)] +pub struct NewDb { + storage: salsa::Storage, +} +impl salsa::Database for NewDb {} + +impl Upcast for NewDb { + fn upcast(&self) -> &(dyn MirDb + 'static) { + &*self + } +} + +impl UpcastMut for NewDb { + fn upcast_mut(&mut self) -> &mut (dyn MirDb + 'static) { + &mut *self + } +} + +impl Upcast for NewDb { + fn upcast(&self) -> &(dyn SourceDb + 'static) { + &*self + } +} + +impl UpcastMut for NewDb { + fn upcast_mut(&mut self) -> &mut (dyn SourceDb + 'static) { + &mut *self + } +} + +impl Upcast for NewDb { + fn upcast(&self) -> &(dyn AnalyzerDb + 'static) { + &*self + } +} + +impl UpcastMut for NewDb { + fn upcast_mut(&mut self) -> &mut (dyn AnalyzerDb + 'static) { + &mut *self + } +} diff --git a/crates/codegen/src/db/queries/function.rs b/crates/codegen/src/db/queries/function.rs index 4878dd4be8..814c770cb3 100644 --- a/crates/codegen/src/db/queries/function.rs +++ b/crates/codegen/src/db/queries/function.rs @@ -2,7 +2,10 @@ use std::rc::Rc; use fe_mir::ir::{FunctionBody, FunctionId, FunctionSignature}; -use crate::{db::CodegenDb, yul::legalize}; +use crate::{ + db::CodegenDb, + yul::{isel, legalize}, +}; pub fn legalized_signature(db: &dyn CodegenDb, function: FunctionId) -> Rc { let mut sig = function.signature(db.upcast()).as_ref().clone(); @@ -15,3 +18,7 @@ pub fn legalized_body(db: &dyn CodegenDb, function: FunctionId) -> Rc Rc { + isel::lower_function(db, function).into() +} diff --git a/crates/codegen/src/yul/isel/function.rs b/crates/codegen/src/yul/isel/function.rs new file mode 100644 index 0000000000..a11b2540b1 --- /dev/null +++ b/crates/codegen/src/yul/isel/function.rs @@ -0,0 +1,367 @@ +#![allow(unused)] +use super::inst_order::InstSerializer; + +use fe_mir::ir::{ + constant::ConstantValue, + inst::{BinOp, InstKind, UnOp}, + Constant, FunctionBody, FunctionId, FunctionSignature, InstId, TypeId, Value, ValueId, +}; +use fxhash::FxHashMap; +use smol_str::SmolStr; +use yultsur::{ + yul::{self, Statement}, + *, +}; + +use crate::{db::CodegenDb, yul::isel::inst_order::StructuralInst}; + +// TODO: consider return type. +pub fn lower_function(db: &dyn CodegenDb, function: FunctionId) -> String { + let sig = db.codegen_legalized_signature(function); + let body = db.codegen_legalized_body(function); + + let func = FuncLowerHelper::new(db, &sig, &body).lower_func(); + format!("{}", func) +} + +struct FuncLowerHelper<'db, 'a> { + db: &'db dyn CodegenDb, + value_map: FxHashMap, + sig: &'a FunctionSignature, + body: &'a FunctionBody, + ret_value: Option, +} + +impl<'db, 'a> FuncLowerHelper<'db, 'a> { + fn new(db: &'db dyn CodegenDb, sig: &'a FunctionSignature, body: &'a FunctionBody) -> Self { + let mut value_map = FxHashMap::default(); + // Register arguments to value_map. + for &value in body.store.locals() { + match body.store.value_data(value) { + Value::Local(local) if local.is_arg => { + let ident = identifier! {(local.name.clone())}; + value_map.insert(value, ident); + } + _ => {} + } + } + + let ret_value = if sig.return_type.is_some() { + Some(identifier! {("$ret")}) + } else { + None + }; + + Self { + db, + value_map, + sig, + body, + ret_value, + } + } + + fn lower_func(&mut self) -> yul::FunctionDefinition { + let name = identifier! {(self.sig.analyzer_func_id.name(self.db.upcast()))}; + + let parameters = self + .sig + .params + .iter() + .map(|param| identifier! {(param.name.clone())}) + .collect(); + + let ret = self + .ret_value + .clone() + .map(|value| vec![value]) + .unwrap_or_default(); + + let body = self.lower_body(); + + yul::FunctionDefinition { + name, + parameters, + returns: ret, + block: body, + } + } + + fn lower_body(&mut self) -> yul::Block { + let inst_order = InstSerializer::new(self.body).serialize(); + + let mut sink = vec![]; + for inst in inst_order.order { + self.lower_structural_inst(inst, &mut sink) + } + + yul::Block { statements: sink } + } + + fn lower_structural_inst(&mut self, inst: StructuralInst, sink: &mut Vec) { + let stmt = match inst { + StructuralInst::Inst(inst) => self.lower_inst(inst), + StructuralInst::If { cond, then, else_ } => self.lower_if(cond, then, else_), + StructuralInst::For { body } => self.lower_for(body), + StructuralInst::Break => yul::Statement::Break, + StructuralInst::Continue => yul::Statement::Continue, + }; + + sink.push(stmt) + } + + fn lower_inst(&mut self, inst: InstId) -> yul::Statement { + match &self.body.store.inst_data(inst).kind { + InstKind::Declare { local: value } => { + let (value, ident) = match self.body.store.value_data(*value) { + Value::Local(local) => { + if local.is_tmp { + (*value, format!("${}", local.name)) + } else { + (*value, format!("{}", local.name)) + } + } + _ => unreachable!(), + }; + + let ident = identifier! {(ident)}; + self.value_map.insert(value, ident.clone()); + yul::Statement::VariableDeclaration(yul::VariableDeclaration { + identifiers: vec![ident], + expression: None, + }) + } + InstKind::Assign { lhs, rhs } => { + let lhs = self.value_ident(*lhs); + let rhs = self.value_expr(*rhs); + statement! { [lhs] := [rhs]} + } + InstKind::Unary { op, value } => { + let rhs = self.lower_unary(*op, *value); + self.assign_inst_result(inst, rhs) + } + InstKind::Binary { op, lhs, rhs } => { + let rhs = self.lower_binary(*op, *lhs, *rhs, inst); + self.assign_inst_result(inst, rhs) + } + InstKind::Cast { value, to } => { + todo!() + } + InstKind::AggregateConstruct { ty, args } => { + todo!() + } + InstKind::AggregateAccess { value, indices } => { + todo!() + } + InstKind::MapAccess { value, key } => { + todo!() + } + InstKind::Call { + func, + args, + call_type, + } => { + todo!() + } + + InstKind::Revert { arg } => { + todo!() + } + + InstKind::Emit { arg } => { + todo!() + } + + InstKind::Return { arg } => { + if let Some(arg) = arg { + let arg = self.value_expr(*arg); + let ret_value = self.ret_value.clone().unwrap(); + let mut statements = vec![statement! { [ret_value] := [arg]}]; + statements.push(yul::Statement::Leave); + Statement::Block(yul::Block { statements }) + } else { + yul::Statement::Leave + } + } + + InstKind::Keccak256 { arg } => { + todo!() + } + + InstKind::Clone { arg } => { + todo!() + } + + InstKind::ToMem { arg } => { + todo!() + } + + InstKind::AbiEncode { arg } => { + todo!() + } + + InstKind::Create { value, contract } => { + todo!() + } + + InstKind::Create2 { + value, + salt, + contract, + } => { + todo!() + } + + InstKind::Revert { arg } => todo!(), + InstKind::Emit { arg } => { + todo!() + } + InstKind::Jump { .. } | InstKind::Branch { .. } | InstKind::Nop => { + unreachable!() + } + + InstKind::YulIntrinsic { op, args } => { + todo!() + } + } + } + + fn lower_if( + &mut self, + cond: ValueId, + then: Vec, + else_: Vec, + ) -> yul::Statement { + let cond = self.value_expr(cond); + let mut then_stmts = vec![]; + let mut else_stmts = vec![]; + + for inst in then { + self.lower_structural_inst(inst, &mut then_stmts); + } + for inst in else_ { + self.lower_structural_inst(inst, &mut else_stmts); + } + + switch! { + switch ([cond]) + (case 1 {[then_stmts...]}) + (case 0 {[else_stmts...]}) + } + } + + fn lower_for(&mut self, body: Vec) -> yul::Statement { + let mut body_stmts = vec![]; + for inst in body { + self.lower_structural_inst(inst, &mut body_stmts); + } + + block_statement! {( + for {} (1) {} + { + [body_stmts...] + } + )} + } + + fn lower_unary(&self, op: UnOp, value: ValueId) -> yul::Expression { + let value = self.value_expr(value); + match op { + UnOp::Not => expression! { iszero([value])}, + UnOp::Neg => { + let zero = literal_expression! {0}; + expression! { sub([zero], [value])} + } + UnOp::Inv => expression! { not([value])}, + } + } + + fn lower_binary(&self, op: BinOp, lhs: ValueId, rhs: ValueId, inst: InstId) -> yul::Expression { + let lhs = self.value_expr(lhs); + let rhs = self.value_expr(rhs); + let is_signed = self + .body + .store + .inst_result(inst) + .map(|val| { + let ty = self.body.store.value_ty(val); + ty.is_signed(self.db.upcast()) + }) + .unwrap_or(false); + match op { + BinOp::Add => expression! {add([lhs], [rhs])}, + BinOp::Sub => expression! {sub([lhs], [rhs])}, + BinOp::Mul => expression! {mul([lhs], [rhs])}, + // TODO: zero division check for div and mod. + BinOp::Div if is_signed => expression! {sdiv([lhs], [rhs])}, + BinOp::Div => expression! {div([lhs], [rhs])}, + BinOp::Mod if is_signed => expression! {smod([lhs], [rhs])}, + BinOp::Mod => expression! {mod([lhs], [rhs])}, + BinOp::Pow => expression! {exp([lhs], [rhs])}, + BinOp::Shl => expression! {shl([lhs], [rhs])}, + BinOp::Shr => expression! {shr([lhs], [rhs])}, + BinOp::BitOr | BinOp::LogicalOr => expression! {or([lhs], [rhs])}, + BinOp::BitXor => expression! {xor([lhs], [rhs])}, + BinOp::BitAnd | BinOp::LogicalAnd => expression! {and([lhs], [rhs])}, + BinOp::Eq => expression! {eq([lhs], [rhs])}, + BinOp::Ne => expression! {is_zero((eq([lhs], [rhs])))}, + BinOp::Ge if is_signed => expression! {is_zero((slt([lhs], [rhs])))}, + BinOp::Ge => expression! {is_zero((lt([lhs], [rhs])))}, + BinOp::Gt if is_signed => expression! {sgt([lhs], [rhs])}, + BinOp::Gt => expression! {gt([lhs], [rhs])}, + BinOp::Le if is_signed => expression! {is_zero((sgt([lhs], [rhs])))}, + BinOp::Le => expression! {is_zero((gt([lhs], [rhs])))}, + BinOp::Lt if is_signed => expression! {slt([lhs], [rhs])}, + BinOp::Lt => expression! {lt([lhs], [rhs])}, + } + } + + fn assign_inst_result(&mut self, inst: InstId, rhs: yul::Expression) -> yul::Statement { + if let Some(result) = self.body.store.inst_result(inst) { + let tmp = self.make_tmp(result); + statement! {let [tmp] := [rhs]} + } else { + yul::Statement::Expression(rhs) + } + } + + fn value_expr(&self, value: ValueId) -> yul::Expression { + match self.body.store.value_data(value) { + Value::Local(_) | Value::Temporary(_) => { + let ident = &self.value_map[&value]; + literal_expression! {(ident)} + } + Value::Immediate(imm) => { + let num = format!("{:#x}", imm.value); + literal_expression! {(num)} + } + Value::Constant(constant) => match &constant.id.data(self.db.upcast()).value { + ConstantValue::Immediate(imm) => { + let num = format!("{:#x}", imm); + literal_expression! {(num)} + } + ConstantValue::Str(_) => { + todo!() + } + ConstantValue::Bool(true) => { + literal_expression! {1} + } + ConstantValue::Bool(false) => { + literal_expression! {30} + } + }, + Value::Unit(_) => unreachable!(), + } + } + + fn value_ident(&self, value: ValueId) -> yul::Identifier { + self.value_map[&value].clone() + } + + fn make_tmp(&mut self, tmp: ValueId) -> yul::Identifier { + let tmp_name = format!("$tmp_{}", tmp.index()); + let ident = identifier! {(tmp_name)}; + self.value_map.insert(tmp, ident.clone()); + ident + } +} diff --git a/crates/codegen/src/yul/inst_order.rs b/crates/codegen/src/yul/isel/inst_order.rs similarity index 96% rename from crates/codegen/src/yul/inst_order.rs rename to crates/codegen/src/yul/isel/inst_order.rs index 1e599100a4..38a6a7267b 100644 --- a/crates/codegen/src/yul/inst_order.rs +++ b/crates/codegen/src/yul/isel/inst_order.rs @@ -1,5 +1,3 @@ -#![allow(unused)] - use fe_mir::{ analysis::{ domtree::DFSet, loop_tree::LoopId, post_domtree::PostIDom, ControlFlowGraph, DomTree, @@ -9,12 +7,12 @@ use fe_mir::{ }; #[derive(Debug, Clone, Default)] -pub struct InstOrder { - pub order: Vec, +pub(super) struct InstOrder { + pub(super) order: Vec, } #[derive(Debug, Clone)] -pub enum StructuralInst { +pub(super) enum StructuralInst { Inst(InstId), If { cond: ValueId, @@ -28,7 +26,7 @@ pub enum StructuralInst { Continue, } -struct InstSerializer<'a> { +pub(super) struct InstSerializer<'a> { body: &'a FunctionBody, cfg: ControlFlowGraph, loop_tree: LoopTree, @@ -38,7 +36,7 @@ struct InstSerializer<'a> { } impl<'a> InstSerializer<'a> { - fn new(body: &'a FunctionBody) -> Self { + pub(super) fn new(body: &'a FunctionBody) -> Self { let cfg = ControlFlowGraph::compute(body); let domtree = DomTree::compute(&cfg); let df = domtree.compute_df(&cfg); @@ -55,7 +53,7 @@ impl<'a> InstSerializer<'a> { } } - fn serialize_insts(&mut self) -> InstOrder { + pub(super) fn serialize(&mut self) -> InstOrder { self.scope = None; let entry = self.cfg.entry(); let mut order = vec![]; @@ -178,7 +176,7 @@ impl<'a> InstSerializer<'a> { 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 + // of all other candidates. if exit_candidates.iter().all(|&block| { if block == cand { true diff --git a/crates/codegen/src/yul/isel/mod.rs b/crates/codegen/src/yul/isel/mod.rs index e6a246a6fa..6a99febc02 100644 --- a/crates/codegen/src/yul/isel/mod.rs +++ b/crates/codegen/src/yul/isel/mod.rs @@ -1,25 +1,5 @@ -#![allow(unused)] -use fe_mir::{ - analysis::ControlFlowGraph, - ir::{FunctionBody, FunctionSignature, ValueId}, -}; -use fxhash::FxHashMap; -use smol_str::SmolStr; +mod function; -use crate::db::CodegenDb; +mod inst_order; -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!() - } -} +pub use function::lower_function; diff --git a/crates/codegen/src/yul/mod.rs b/crates/codegen/src/yul/mod.rs index ba32f5b6eb..32d435afaa 100644 --- a/crates/codegen/src/yul/mod.rs +++ b/crates/codegen/src/yul/mod.rs @@ -1,4 +1,2 @@ pub mod isel; pub mod legalize; - -mod inst_order; diff --git a/crates/driver/Cargo.toml b/crates/driver/Cargo.toml index a5234c6b72..463d249bca 100644 --- a/crates/driver/Cargo.toml +++ b/crates/driver/Cargo.toml @@ -17,6 +17,7 @@ fe-analyzer = {path = "../analyzer", version = "^0.14.0-alpha"} fe-common = {path = "../common", version = "^0.14.0-alpha"} fe-lowering = {path = "../lowering", version = "^0.14.0-alpha"} fe-mir = {path = "../mir", version = "^0.14.0-alpha"} +fe-codegen = {path = "../codegen", version = "^0.14.0-alpha"} fe-parser = {path = "../parser", version = "^0.14.0-alpha"} fe-yulgen = {path = "../yulgen", version = "^0.14.0-alpha"} fe-yulc = {path = "../yulc", version = "^0.14.0-alpha", features = ["solc-backend"], optional = true} diff --git a/crates/driver/src/lib.rs b/crates/driver/src/lib.rs index 764bd8ea6c..7aa4063ab7 100644 --- a/crates/driver/src/lib.rs +++ b/crates/driver/src/lib.rs @@ -1,6 +1,6 @@ #![allow(unused_imports, dead_code)] -pub use fe_mir::db::NewDb; +pub use fe_codegen::db::{CodegenDb, NewDb}; pub use fe_yulgen::Db; use fe_analyzer::context::Analysis; @@ -99,6 +99,28 @@ pub fn dump_mir_single_file(db: &mut NewDb, path: &str, src: &str) -> Result Result, CompileError> { + let module = ModuleId::new_standalone(db, path, src); + + let diags = module.diagnostics(db); + if !diags.is_empty() { + return Err(CompileError(diags)); + } + + let mut functions = vec![]; + for &func in db.mir_lower_module_all_functions(module).as_ref() { + let yul_func = db.codegen_lower_function(func); + functions.push(yul_func.as_ref().clone()); + } + + Ok(functions) +} + fn compile_module_id( db: &mut Db, module_id: ModuleId, diff --git a/crates/fe/src/main.rs b/crates/fe/src/main.rs index 75c4608700..e8f569eae1 100644 --- a/crates/fe/src/main.rs +++ b/crates/fe/src/main.rs @@ -77,6 +77,12 @@ pub fn main() { .help("dump mir dot file") .takes_value(false), ) + .arg( + Arg::with_name("codegen") + .long("codegen") + .help("todo") + .takes_value(false), + ) .get_matches(); let input_path = matches.value_of("input").unwrap(); @@ -90,6 +96,11 @@ pub fn main() { if matches.is_present("mir") { return mir_dump(input_path); } + + if matches.is_present("codegen") { + return yul_functions_dump(input_path); + } + #[cfg(not(feature = "solc-backend"))] if with_bytecode { eprintln!("Warning: bytecode output requires 'solc-backend' feature. Try `cargo build --release --features solc-backend`. Skipping."); @@ -296,3 +307,32 @@ fn mir_dump(input_path: &str) { std::process::exit(1) } } + +fn yul_functions_dump(input_path: &str) { + let mut db = fe_driver::NewDb::default(); + if Path::new(input_path).is_file() { + let content = match std::fs::read_to_string(input_path) { + Err(err) => { + eprintln!("Failed to load file: `{}`. Error: {}", input_path, err); + std::process::exit(1) + } + Ok(content) => content, + }; + + match fe_driver::dump_codegen_funcs(&mut db, input_path, &content) { + Ok(functions) => { + for func in functions { + println!("{}", func) + } + } + Err(err) => { + eprintln!("Unable to dump mir `{}", input_path); + print_diagnostics(&db, &err.0); + std::process::exit(1) + } + } + } else { + eprintln!("mir doesn't support ingot yet"); + std::process::exit(1) + } +} diff --git a/crates/mir/src/db/queries/types.rs b/crates/mir/src/db/queries/types.rs index 15365acc3e..9fe706055e 100644 --- a/crates/mir/src/db/queries/types.rs +++ b/crates/mir/src/db/queries/types.rs @@ -117,6 +117,13 @@ impl TypeId { ) } + pub fn is_signed(self, db: &dyn MirDb) -> bool { + matches!( + self.data(db).as_ref(), + Type::I8 | Type::I16 | Type::I32 | Type::I64 | Type::I128 | Type::I256 + ) + } + /// Returns size of the type in bytes. pub fn size_of(self, db: &dyn MirDb, slot_size: usize) -> usize { match self.data(db).as_ref() { diff --git a/crates/mir/src/ir/body_builder.rs b/crates/mir/src/ir/body_builder.rs index 0a18d542e1..8f33f09978 100644 --- a/crates/mir/src/ir/body_builder.rs +++ b/crates/mir/src/ir/body_builder.rs @@ -100,7 +100,7 @@ impl BodyBuilder { pub fn make_constant(&mut self, constant: ConstantId, ty: TypeId) -> ValueId { self.body .store - .store_value(Value::Constant(Constant { constant, ty })) + .store_value(Value::Constant(Constant { id: constant, ty })) } pub fn declare(&mut self, local: Local) -> ValueId { diff --git a/crates/mir/src/ir/value.rs b/crates/mir/src/ir/value.rs index c869dd3949..9c7cb1c78d 100644 --- a/crates/mir/src/ir/value.rs +++ b/crates/mir/src/ir/value.rs @@ -117,7 +117,7 @@ impl Local { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Constant { - pub constant: ConstantId, + pub id: ConstantId, pub ty: TypeId, } diff --git a/crates/mir/src/pretty_print/value.rs b/crates/mir/src/pretty_print/value.rs index a8ef1255ce..ca80fd4df0 100644 --- a/crates/mir/src/pretty_print/value.rs +++ b/crates/mir/src/pretty_print/value.rs @@ -18,7 +18,7 @@ impl PrettyPrint for ValueId { Value::Temporary(_) | Value::Local(_) => write!(w, "_{}", self.index()), Value::Immediate(imm) => write!(w, "{}", imm.value), Value::Constant(constant) => { - let const_value = constant.constant.data(db); + let const_value = constant.id.data(db); write!(w, "const ")?; match &const_value.value { ConstantValue::Immediate(num) => write!(w, "{}", num),