Skip to content

Commit

Permalink
Simple dead-code elimination (FuelLabs#2687)
Browse files Browse the repository at this point in the history
  • Loading branch information
vaivaswatha authored Sep 1, 2022
1 parent 0a5d0c0 commit f70c428
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 1 deletion.
23 changes: 23 additions & 0 deletions sway-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,14 @@ pub(crate) fn compile_ast_to_ir_to_asm(
errors
);

// Remove dead definitions.
check!(
dce(&mut ir, &entry_point_functions),
return err(warnings, errors),
warnings,
errors
);

if build_config.print_ir {
tracing::info!("{}", ir);
}
Expand Down Expand Up @@ -547,6 +555,21 @@ fn combine_constants(ir: &mut Context, functions: &[Function]) -> CompileResult<
ok((), Vec::new(), Vec::new())
}

fn dce(ir: &mut Context, functions: &[Function]) -> CompileResult<()> {
for function in functions {
if let Err(ir_error) = sway_ir::optimize::dce(ir, function) {
return err(
Vec::new(),
vec![CompileError::InternalOwned(
ir_error.to_string(),
span::Span::dummy(),
)],
);
}
}
ok((), Vec::new(), Vec::new())
}

fn simplify_cfg(ir: &mut Context, functions: &[Function]) -> CompileResult<()> {
for function in functions {
if let Err(ir_error) = sway_ir::optimize::simplify_cfg(ir, function) {
Expand Down
19 changes: 19 additions & 0 deletions sway-ir/src/bin/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ fn main() -> Result<(), anyhow::Error> {
pass_mgr.register::<ConstCombinePass>();
pass_mgr.register::<InlinePass>();
pass_mgr.register::<SimplifyCfgPass>();
pass_mgr.register::<DCEPass>();

// Build the config from the command line.
let config = ConfigBuilder::build(&pass_mgr, std::env::args())?;
Expand Down Expand Up @@ -179,6 +180,24 @@ impl NamedPass for SimplifyCfgPass {
}
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

struct DCEPass;

impl NamedPass for DCEPass {
fn name() -> &'static str {
"dce"
}

fn descr() -> &'static str {
"Dead code elimination."
}

fn run(ir: &mut Context) -> Result<bool, IrError> {
Self::run_on_all_fns(ir, optimize::dce)
}
}

// -------------------------------------------------------------------------------------------------
// Using a bespoke CLI parser since the order in which passes are specified is important.

Expand Down
24 changes: 24 additions & 0 deletions sway-ir/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,30 @@ impl Instruction {
}
}
}

pub fn may_have_side_effect(&self) -> bool {
matches!(
self,
Instruction::AsmBlock(_, _)
| Instruction::Call(_, _)
| Instruction::ContractCall { .. }
| Instruction::StateLoadQuadWord { .. }
| Instruction::StateStoreQuadWord { .. }
| Instruction::StateStoreWord { .. }
| Instruction::Store { .. }
// Insert(Element/Value), unlike those in LLVM
// do not have SSA semantics. They are like stores.
| Instruction::InsertElement { .. }
| Instruction::InsertValue { .. }
)
}

pub fn is_terminator(&self) -> bool {
matches!(
self,
Instruction::Branch(_) | Instruction::ConditionalBranch { .. } | Instruction::Ret(..)
)
}
}

/// Iterate over all [`Instruction`]s in a specific [`Block`].
Expand Down
2 changes: 2 additions & 0 deletions sway-ir/src/optimize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ pub mod inline;
pub use inline::*;
pub mod simplify_cfg;
pub use simplify_cfg::*;
pub mod dce;
pub use dce::*;
156 changes: 156 additions & 0 deletions sway-ir/src/optimize/dce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//! ## Dead Code Elimination
//!
//! This optimization removes unused definitions. The pass is a combination of
//! 1. A liveness analysis that keeps track of the uses of a definition,
//! 2. At the time of inspecting a definition, if it has no uses, it is removed.
//! This pass does not do CFG transformations. That is handled by simplify_cfg.
use crate::{
context::Context, error::IrError, function::Function, instruction::Instruction,
value::ValueDatum, Block, Value,
};

use std::collections::HashMap;

fn can_eliminate_instruction(context: &Context, val: Value) -> bool {
let inst = val.get_instruction(context).unwrap();
!inst.is_terminator() && !inst.may_have_side_effect()
}

/// Perform dead code (if any) elimination and return true if function modified.
pub fn dce(context: &mut Context, function: &Function) -> Result<bool, IrError> {
// Number of uses that an instruction has.
let mut num_uses: HashMap<Value, (Block, u32)> = HashMap::new();

fn get_operands(inst: &Instruction) -> Vec<Value> {
match inst {
Instruction::AddrOf(v) => vec![*v],
Instruction::AsmBlock(_, args) => args.iter().filter_map(|aa| aa.initializer).collect(),
Instruction::BitCast(v, _) => vec![*v],
Instruction::Branch(_) => vec![],
Instruction::Call(_, vs) => vs.clone(),
Instruction::Cmp(_, lhs, rhs) => vec![*lhs, *rhs],
Instruction::ConditionalBranch {
cond_value,
true_block: _,
false_block: _,
} => vec![*cond_value],
Instruction::ContractCall {
return_type: _,
name: _,
params,
coins,
asset_id,
gas,
} => vec![*params, *coins, *asset_id, *gas],
Instruction::ExtractElement {
array,
ty: _,
index_val,
} => vec![*array, *index_val],
Instruction::ExtractValue {
aggregate,
ty: _,
indices: _,
} => vec![*aggregate],
Instruction::GetStorageKey => vec![],
Instruction::Gtf {
index,
tx_field_id: _,
} => vec![*index],
Instruction::GetPointer {
base_ptr: _,
ptr_ty: _,
offset: _,
} =>
// TODO: Not sure.
{
vec![]
}
Instruction::InsertElement {
array,
ty: _,
value,
index_val,
} => vec![*array, *value, *index_val],
Instruction::InsertValue {
aggregate,
ty: _,
value,
indices: _,
} => vec![*aggregate, *value],
Instruction::IntToPtr(v, _) => vec![*v],
Instruction::Load(v) => vec![*v],
Instruction::Nop => vec![],
Instruction::Phi(ins) => ins.iter().map(|v| v.1).collect(),
Instruction::ReadRegister(_) => vec![],
Instruction::Ret(v, _) => vec![*v],
Instruction::StateLoadQuadWord { load_val, key } => vec![*load_val, *key],
Instruction::StateLoadWord(key) => vec![*key],
Instruction::StateStoreQuadWord { stored_val, key } => vec![*stored_val, *key],
Instruction::StateStoreWord { stored_val, key } => vec![*stored_val, *key],
Instruction::Store {
dst_val,
stored_val,
} => {
vec![*dst_val, *stored_val]
}
}
}

// Go through each instruction and update use_count.
for (block, inst) in function.instruction_iter(context) {
let opds = get_operands(inst.get_instruction(context).unwrap());
for v in opds {
match context.values[v.0].value {
ValueDatum::Instruction(_) => {
num_uses
.entry(v)
.and_modify(|(_block, count)| *count += 1)
.or_insert((block, 1));
}
ValueDatum::Constant(_) | ValueDatum::Argument(_) => (),
}
}
}

let mut worklist = function
.instruction_iter(context)
.filter(|(_block, inst)| num_uses.get(inst).is_none())
.collect::<Vec<_>>();

let mut modified = false;
while !worklist.is_empty() {
let (in_block, dead) = worklist.pop().unwrap();
if !can_eliminate_instruction(context, dead) {
continue;
}
// Process dead's operands.
let opds = get_operands(dead.get_instruction(context).unwrap());
for v in opds {
// Reduce the use count of v. If it reaches 0, add it to the worklist.
match context.values[v.0].value {
ValueDatum::Instruction(_) => {
let (block, nu) = num_uses.get_mut(&v).unwrap();
*nu -= 1;
if *nu == 0 {
worklist.push((*block, v));
}
}
ValueDatum::Constant(_) | ValueDatum::Argument(_) => (),
}
}
// Don't remove PHIs, just make them empty.
if matches!(
&context.values[dead.0].value,
ValueDatum::Instruction(Instruction::Phi(_))
) {
dead.replace(context, ValueDatum::Instruction(Instruction::Phi(vec![])));
} else {
in_block.remove_instruction(context, dead);
}
modified = true;
}

Ok(modified)
}
8 changes: 8 additions & 0 deletions sway-ir/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ impl Value {
}
}

pub fn get_instruction<'a>(&self, context: &'a Context) -> Option<&'a Instruction> {
if let ValueDatum::Instruction(instruction) = &context.values.get(self.0).unwrap().value {
Some(instruction)
} else {
None
}
}

/// Get reference to the Constant inside this value, if it's one.
pub fn get_constant<'a>(&self, context: &'a Context) -> Option<&'a Constant> {
if let ValueDatum::Constant(cn) = &context.values.get(self.0).unwrap().value {
Expand Down
16 changes: 16 additions & 0 deletions sway-ir/tests/dce/dce1.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// regex: ID=[[:alpha:]0-9]+

script {
fn main() -> bool {
entry:
v0 = const u64 11
v1 = const u64 0
// not: cmp
v2 = cmp eq v0 v1
br block0

block0:
v9 = const bool false
ret bool v9
}
}
16 changes: 16 additions & 0 deletions sway-ir/tests/dce/dce2.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// regex: ID=[[:alpha:]0-9]+

script {
fn main() -> bool {
entry:
v0 = const u64 11
v1 = const u64 0
br block0

block0:
// not: phi(entry: $(ID))
v10 = phi(entry: v1)
v9 = const bool false
ret bool v9
}
}
13 changes: 13 additions & 0 deletions sway-ir/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,19 @@ fn simplify_cfg() {

// -------------------------------------------------------------------------------------------------

#[allow(clippy::needless_collect)]
#[test]
fn dce() {
run_tests("dce", |_first_line, ir: &mut Context| {
let fn_idcs: Vec<_> = ir.functions.iter().map(|func| func.0).collect();
fn_idcs.into_iter().fold(false, |acc, fn_idx| {
sway_ir::optimize::dce(ir, &sway_ir::function::Function(fn_idx)).unwrap() || acc
})
})
}

// -------------------------------------------------------------------------------------------------

#[test]
fn serialize() {
// This isn't running a pass, it's just confirming that the IR can be loaded and printed, and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use basic_storage_abi::{StoreU64, Quad};
use std::assert::assert;

fn main() -> u64 {
let addr = abi(StoreU64, 0xc46c8efaea3fa7566aa9d5723f360030a59c1fbba3cfbbc2b4141de10cff9886);
let addr = abi(StoreU64, 0xa4283a2b34371f4fd20677090d6fb606f7dd1a500323bdb09f90f0479bc111f7);
let key = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
let value = 4242;

Expand Down

0 comments on commit f70c428

Please sign in to comment.