From e5a524b816344a2a01a3fab73f16fd831a76410e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kostrubiec?= Date: Thu, 12 Sep 2024 22:17:56 +0200 Subject: [PATCH] Added support for removing empty cleanup blocks(exception handlers) --- cilly/src/v2/asm.rs | 4 +- cilly/src/v2/il_exporter/mod.rs | 4 +- cilly/src/v2/opt/mod.rs | 127 ++++++++++++++++--- cilly/src/v2/opt/opt_node.rs | 176 ++++++++++++++++---------- cilly/src/v2/opt/simplify_handlers.rs | 75 ++++------- 5 files changed, 250 insertions(+), 136 deletions(-) diff --git a/cilly/src/v2/asm.rs b/cilly/src/v2/asm.rs index 95538b72..8b2c5ab5 100644 --- a/cilly/src/v2/asm.rs +++ b/cilly/src/v2/asm.rs @@ -59,7 +59,7 @@ impl Assembly { } #[must_use] pub fn default_fuel(&self) -> OptFuel { - OptFuel::new((self.method_defs.len() * 4 + self.roots.len() * 4) as u32) + OptFuel::new((self.method_defs.len() * 4 + self.roots.len() * 8) as u32) } pub(crate) fn borrow_methoddef(&mut self, def_id: MethodDefIdx) -> MethodDef { self.method_defs.remove(&def_id).unwrap() @@ -80,7 +80,7 @@ impl Assembly { if *fuel == prev { break; } - let _pass_min_cost: bool = fuel.consume(1); + //let _pass_min_cost: bool = fuel.consume(1); } } /// Optimizes the assembly, cosuming some fuel. This performs a single optimization pass. diff --git a/cilly/src/v2/il_exporter/mod.rs b/cilly/src/v2/il_exporter/mod.rs index 2ce37c17..9e7dedbb 100644 --- a/cilly/src/v2/il_exporter/mod.rs +++ b/cilly/src/v2/il_exporter/mod.rs @@ -202,6 +202,8 @@ impl ILExporter { writeln!(out,"pop")?; } for hblock in handler{ + //DEBUG REMOVE THIS + writeln!(out,"// is_only_rethrow:{}",hblock.is_only_rethrow(asm))?; writeln!(out," h{}_{}:",block.block_id(),hblock.block_id())?; for root in hblock.roots(){ self.export_root(asm,out,*root,true)?; @@ -482,7 +484,7 @@ impl ILExporter { } super::CILNode::RefToPtr(inner) => { self.export_node(asm, out, *inner)?; - writeln!(out, "conv.u") + writeln!(out, "conv.u//rtp") } super::CILNode::PtrCast(val, _) => self.export_node(asm, out, *val), super::CILNode::LdFieldAdress { addr, field } => { diff --git a/cilly/src/v2/opt/mod.rs b/cilly/src/v2/opt/mod.rs index 0b1f7c7c..4a752b05 100644 --- a/cilly/src/v2/opt/mod.rs +++ b/cilly/src/v2/opt/mod.rs @@ -351,6 +351,34 @@ impl MethodImpl { // Check if each local is ever read or its address is taken let mut local_reads = vec![false; locals.len()]; let mut local_address_of = vec![false; locals.len()]; + /* + let blocks_copy = blocks.clone(); + for block in blocks.iter_mut() { + let Some(root) = block.roots().last() else { + continue; + }; + let CILRoot::Branch(info) = asm.get_root(*root) else { + continue; + }; + let (target, sub_target, None) = info.as_ref() else { + continue; + }; + let id = blockid_from_jump(*target, *sub_target); + if id == block.block_id() { + continue; + } + let Some(target_block) = block_with_id(&blocks_copy, id) else { + continue; + }; + let (None, None) = (block.handler(), target_block.handler()) else { + continue; + }; + if fuel.consume(4) { + block.roots_mut().pop(); + block.roots_mut().extend(target_block.roots()); + } + } */ + if !fuel.consume(8) { return; } @@ -451,7 +479,15 @@ impl MethodDef { } if fuel.consume(5) { if let Some(roots) = self.iter_roots_mut() { - let mut peekable = roots.peekable(); + let roots: Vec<_> = roots + .filter(|root| { + !matches!( + asm.get_root(**root), + CILRoot::Nop | CILRoot::SourceFileInfo { .. } + ) + }) + .collect(); + let mut peekable = roots.into_iter().peekable(); while let Some(curr) = peekable.next() { let Some(peek) = peekable.peek() else { continue; @@ -468,6 +504,19 @@ impl MethodDef { { *curr = nop }*/ + // If we return var a immeditaly after assigining it, we can just return it. + (CILRoot::StLoc(set_loc, tree), CILRoot::Ret(ret_loc)) => { + let CILNode::LdLoc(ret_loc) = asm.get_node(*ret_loc) else { + continue; + }; + if set_loc != ret_loc { + continue; + } + let tree = *tree; + *curr = nop; + let curr = peekable.next().unwrap(); + *curr = asm.alloc_root(CILRoot::Ret(tree)); + } (CILRoot::Branch(info), CILRoot::Branch(info2)) if is_branch_unconditional(info2) => { @@ -738,30 +787,74 @@ impl MethodDef { if fuel.consume(5) { self.implementation_mut().remove_duplicate_sfi(asm); } - if fuel.consume(6) { - if let MethodImpl::MethodBody { blocks, .. } = self.implementation_mut() { - for block in blocks.iter_mut() { - simplify_bbs(block.handler_mut(), asm, fuel, cache); - let Some(handler) = block.handler() else { - return; - }; - // If tall the blocks only rethrow(and don't do anything else!), then this handler nothing, and we can ommit it - if handler.iter().all(|block| block.is_only_rethrow(asm)) { - //panic!("about to remove {handler}"); - block.remove_handler(); - } + if let MethodImpl::MethodBody { blocks, .. } = self.implementation_mut() { + for block in blocks.iter_mut() { + let Some(handler) = block.handler() else { + continue; + }; + // If this handler does nothing besides jumping around, seting locals, and then rethrows, then this handler should optimize away into nothing. + if handler.iter().all(|block| { + block + .meaningfull_roots(asm) + .all(|root| match asm.get_root(root) { + CILRoot::Branch(info) => { + if let Some(cond) = &info.2 { + cond.nodes() + .iter() + .all(|node| !cache.has_side_effects(*node, asm)) + } else { + true + } + } + CILRoot::StLoc(_, tree) => !cache.has_side_effects(*tree, asm), + CILRoot::ReThrow | CILRoot::Nop | CILRoot::SetStaticField { .. } => { + true + } + _ => false, + }) + }) && fuel.consume(6) + { + block.remove_handler(); } - // simplify_bbs(Some(blocks), asm, fuel) - }; - } + } + // simplify_bbs(Some(blocks), asm, fuel) + }; } } #[must_use] pub fn is_branch_unconditional(branch: &(u32, u32, Option)) -> bool { branch.2.is_none() } - +fn blockid_from_jump(target: u32, sub_target: u32) -> u32 { + if sub_target == 0 { + target + } else { + sub_target + } +} +fn block_with_id(blocks: &[BasicBlock], id: u32) -> Option<&BasicBlock> { + blocks.iter().find(|block| block.block_id() == id) +} +#[test] +fn find_block() { + let blocks = vec![]; + assert!(block_with_id(&blocks, 0).is_none()); + let blocks = vec![ + BasicBlock::new(vec![], 0, None), + BasicBlock::new(vec![], 1, None), + ]; + assert!(block_with_id(&blocks, 0).is_some()); + assert!(block_with_id(&blocks, 1).is_some()); + assert!(block_with_id(&blocks, 2).is_none()); +} +#[test] +fn blockid() { + assert_eq!(blockid_from_jump(0, 0), 0); + assert_eq!(blockid_from_jump(2, 1), 1); + assert_eq!(blockid_from_jump(1, 2), 2); + assert_eq!(blockid_from_jump(2, 0), 2); +} #[test] fn opt_mag() { use super::{BinOp, Float}; diff --git a/cilly/src/v2/opt/opt_node.rs b/cilly/src/v2/opt/opt_node.rs index e0aa813c..76df9d46 100644 --- a/cilly/src/v2/opt/opt_node.rs +++ b/cilly/src/v2/opt/opt_node.rs @@ -1,24 +1,38 @@ use crate::v2::{Assembly, CILNode, Const, Int, Type}; use super::OptFuel; - -pub fn opt_node(node: crate::v2::CILNode, asm: &mut Assembly, fuel: &mut OptFuel) -> CILNode { - match node { +pub fn opt_if_fuel(new: CILNode, original: CILNode, fuel: &mut OptFuel) -> CILNode { + if fuel.consume(1) { + new + } else { + original + } +} +pub fn opt_node(original: CILNode, asm: &mut Assembly, fuel: &mut OptFuel) -> CILNode { + match original { CILNode::IntCast { input, target, extend, } => match asm.get_node(input) { CILNode::Const(cst) => match (cst.as_ref(), target) { - (Const::U64(val), Int::USize) => Const::USize(*val).into(), - (Const::I64(val), Int::ISize) => Const::ISize(*val).into(), - (Const::U64(val), Int::U64) => Const::U64(*val).into(), - (Const::I64(val), Int::I64) => Const::I64(*val).into(), - (Const::U32(val), Int::U32) => Const::U32(*val).into(), - (Const::I32(val), Int::I32) => Const::I32(*val).into(), - (Const::I32(val), Int::U32) => Const::U32(*val as u32).into(), - (Const::U64(val), Int::U8) => Const::U8(*val as u8).into(), - _ => node, + (Const::U64(val), Int::USize) => { + opt_if_fuel(Const::USize(*val).into(), original, fuel) + } + (Const::I64(val), Int::ISize) => { + opt_if_fuel(Const::ISize(*val).into(), original, fuel) + } + (Const::U64(val), Int::U64) => opt_if_fuel(Const::U64(*val).into(), original, fuel), + (Const::I64(val), Int::I64) => opt_if_fuel(Const::I64(*val).into(), original, fuel), + (Const::U32(val), Int::U32) => opt_if_fuel(Const::U32(*val).into(), original, fuel), + (Const::I32(val), Int::I32) => opt_if_fuel(Const::I32(*val).into(), original, fuel), + (Const::I32(val), Int::U32) => { + opt_if_fuel(Const::U32(*val as u32).into(), original, fuel) + } + (Const::U64(val), Int::U8) => { + opt_if_fuel(Const::U8(*val as u8).into(), original, fuel) + } + _ => original, }, CILNode::IntCast { input: input2, @@ -26,40 +40,52 @@ pub fn opt_node(node: crate::v2::CILNode, asm: &mut Assembly, fuel: &mut OptFuel extend: extend2, } => { if target == *target2 && extend == *extend2 { - return asm.get_node(input).clone(); + return opt_if_fuel(asm.get_node(input).clone(), original, fuel); } match (target, target2) { (Int::USize | Int::ISize, Int::USize | Int::ISize) => { // A usize to isize cast does nothing, except change the type on the evaulation stack(the bits are unchanged). // So, we can just create a cast like it. - CILNode::IntCast { - input: *input2, - target, - extend: *extend2, - } + opt_if_fuel( + CILNode::IntCast { + input: *input2, + target, + extend: *extend2, + }, + original, + fuel, + ) } (Int::U64 | Int::I64, Int::U64 | Int::I64) => { // A u64 to i64 cast does nothing, except change the type on the evaulation stack(the bits are unchanged). // So, we can just create a cast like it. - CILNode::IntCast { - input: *input2, - target, - extend: *extend2, - } + opt_if_fuel( + CILNode::IntCast { + input: *input2, + target, + extend: *extend2, + }, + original, + fuel, + ) } (Int::U32 | Int::I32, Int::U32 | Int::I32) => { // A u64 to i64 cast does nothing, except change the type on the evaulation stack(the bits are unchanged). // So, we can just create a cast like it. - CILNode::IntCast { - input: *input2, - target, - extend: *extend2, - } + opt_if_fuel( + CILNode::IntCast { + input: *input2, + target, + extend: *extend2, + }, + original, + fuel, + ) } - _ => node, + _ => original, } } - _ => node, + _ => original, }, CILNode::Call(info) => super::inline::trivial_inline_call(info.0, &info.1, fuel, asm), CILNode::LdInd { @@ -67,47 +93,69 @@ pub fn opt_node(node: crate::v2::CILNode, asm: &mut Assembly, fuel: &mut OptFuel tpe, volitale, } => match asm.get_node(addr) { - CILNode::RefToPtr(inner) => CILNode::LdInd { - addr: *inner, - tpe, - volitale, - }, - CILNode::LdLocA(loc) => CILNode::LdLoc(*loc), - CILNode::LdArgA(loc) => CILNode::LdArg(*loc), + CILNode::RefToPtr(inner) => opt_if_fuel( + CILNode::LdInd { + addr: *inner, + tpe, + volitale, + }, + original, + fuel, + ), + CILNode::LdLocA(loc) => opt_if_fuel(CILNode::LdLoc(*loc), original, fuel), + CILNode::LdArgA(loc) => opt_if_fuel(CILNode::LdArg(*loc), original, fuel), CILNode::LdFieldAdress { addr, field } => { let field_desc = asm.get_field(*field); if field_desc.tpe() == *asm.get_type(tpe) { - CILNode::LdField { - addr: *addr, - field: *field, - } + opt_if_fuel( + CILNode::LdField { + addr: *addr, + field: *field, + }, + original, + fuel, + ) } else { - node + original } } - _ => node, + _ => original, }, CILNode::LdField { addr, field } => match asm.get_node(addr) { - CILNode::RefToPtr(addr) => CILNode::LdField { addr: *addr, field }, - CILNode::LdLocA(loc) => CILNode::LdField { - addr: asm.alloc_node(CILNode::LdLoc(*loc)), - field, - }, - CILNode::LdArgA(loc) => CILNode::LdField { - addr: asm.alloc_node(CILNode::LdArg(*loc)), - field, - }, + CILNode::RefToPtr(addr) => { + opt_if_fuel(CILNode::LdField { addr: *addr, field }, original, fuel) + } + CILNode::LdLocA(loc) => opt_if_fuel( + CILNode::LdField { + addr: asm.alloc_node(CILNode::LdLoc(*loc)), + field, + }, + original, + fuel, + ), + CILNode::LdArgA(loc) => opt_if_fuel( + CILNode::LdField { + addr: asm.alloc_node(CILNode::LdArg(*loc)), + field, + }, + original, + fuel, + ), CILNode::LdFieldAdress { addr: addr2, field: field2, - } => CILNode::LdField { - addr: asm.alloc_node(CILNode::LdField { - addr: *addr2, - field: *field2, - }), - field, - }, + } => opt_if_fuel( + CILNode::LdField { + addr: asm.alloc_node(CILNode::LdField { + addr: *addr2, + field: *field2, + }), + field, + }, + original, + fuel, + ), CILNode::LdInd { addr, tpe, @@ -115,16 +163,16 @@ pub fn opt_node(node: crate::v2::CILNode, asm: &mut Assembly, fuel: &mut OptFuel } => { if let Type::ClassRef(tpe) = *asm.get_type(*tpe) { if tpe == asm.get_field(field).owner() { - CILNode::LdField { addr: *addr, field } + opt_if_fuel(CILNode::LdField { addr: *addr, field }, original, fuel) } else { - node + original } } else { - node + original } } - _ => node, + _ => original, }, - _ => node, + _ => original, } } diff --git a/cilly/src/v2/opt/simplify_handlers.rs b/cilly/src/v2/opt/simplify_handlers.rs index f1d664fc..91d1845a 100644 --- a/cilly/src/v2/opt/simplify_handlers.rs +++ b/cilly/src/v2/opt/simplify_handlers.rs @@ -3,10 +3,8 @@ use fxhash::{FxHashMap, FxHashSet}; use crate::v2::{Assembly, BasicBlock, CILRoot}; -use super::{OptFuel, SideEffectInfoCache}; -fn block_with_id(blocks: &[BasicBlock], id: u32) -> Option<&BasicBlock> { - blocks.iter().find(|block| block.block_id() == id) -} +use super::{block_with_id, blockid_from_jump, OptFuel, SideEffectInfoCache}; + fn block_targets<'a, 'asm: 'a>( block: &'a BasicBlock, asm: &'asm Assembly, @@ -55,13 +53,7 @@ fn block_gc(blocks: &mut Vec, asm: &Assembly) { .cloned() .collect(); } -fn blockid_from_jump(target: u32, sub_target: u32) -> u32 { - if sub_target == 0 { - target - } else { - sub_target - } -} + pub fn simplify_bbs( handler: Option<&mut Vec>, asm: &mut Assembly, @@ -87,34 +79,31 @@ pub fn simplify_bbs( }; let (target, sub_target, cond) = info.as_ref(); // Sub target of 0, look up by the target - let jump = direct_jumps.get(&blockid_from_jump(*target, *sub_target)); - let Some(jump) = jump else { - continue; - }; - let Some((target, sub_target)) = jump else { - // Check that this jump is unconditonal, or the next root is a rethrow! - if let Some(cond) = cond { - if root_iter.peek().map(|root| asm.get_root(**root)) != Some(&CILRoot::ReThrow) { - continue; - } - // If some args have side effects, this optimization has to replace this branch with pops. TODO: implement that. - if cond - .nodes() - .into_iter() - .any(|node| cache.has_side_effects(node, asm)) - { - continue; - } + + // Check that this jump is unconditonal, or the next root is a rethrow! + if let Some(cond) = cond { + if root_iter.peek().map(|root| asm.get_root(**root)) != Some(&CILRoot::ReThrow) { + continue; } + // If some args have side effects, this optimization has to replace this branch with pops. TODO: implement that. + if cond + .nodes() + .into_iter() + .any(|node| cache.has_side_effects(node, asm)) + { + continue; + } + } else { // TODO: Correctnesss:Check if this root's tree has no side effects! - let rethrow = rethrows + let rethrow = *rethrows .get(&blockid_from_jump(*target, *sub_target)) .unwrap(); - if *rethrow && fuel.consume(1) { + + if rethrow { *root = asm.alloc_root(CILRoot::ReThrow); } - continue; - }; + } + /* if fuel.consume(1) { *root = asm.alloc_root(CILRoot::Branch(Box::new(( @@ -127,18 +116,7 @@ pub fn simplify_bbs( //block_gc(handler, asm); } -#[test] -fn find_block() { - let blocks = vec![]; - assert!(block_with_id(&blocks, 0).is_none()); - let blocks = vec![ - BasicBlock::new(vec![], 0, None), - BasicBlock::new(vec![], 1, None), - ]; - assert!(block_with_id(&blocks, 0).is_some()); - assert!(block_with_id(&blocks, 1).is_some()); - assert!(block_with_id(&blocks, 2).is_none()); -} + #[test] fn targets() { let mut asm = Assembly::default(); @@ -151,10 +129,3 @@ fn targets() { let block = BasicBlock::new(vec![nop, goto, nop], 0, None); assert_eq!(block_targets(&block, &asm).count(), 1); } -#[test] -fn blockid() { - assert_eq!(blockid_from_jump(0, 0), 0); - assert_eq!(blockid_from_jump(2, 1), 1); - assert_eq!(blockid_from_jump(1, 2), 2); - assert_eq!(blockid_from_jump(2, 0), 2); -}