From f60cc91fb955de919a0702efafcfa45b7e05befa Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Tue, 9 Oct 2018 23:11:09 -0400 Subject: [PATCH 01/16] Add simple constant folding and folding tests --- filetests/folding/branch.clif | 53 ++++++++++ filetests/folding/numerical.clif | 35 +++++++ lib/codegen/meta-python/base/settings.py | 4 + lib/codegen/src/constant_folding.rs | 118 +++++++++++++++++++++++ lib/codegen/src/context.rs | 14 +++ lib/codegen/src/lib.rs | 1 + lib/codegen/src/settings.rs | 1 + lib/codegen/src/timing.rs | 1 + lib/filetests/src/lib.rs | 2 + lib/filetests/src/test_folding.rs | 45 +++++++++ 10 files changed, 274 insertions(+) create mode 100644 filetests/folding/branch.clif create mode 100644 filetests/folding/numerical.clif create mode 100644 lib/codegen/src/constant_folding.rs create mode 100644 lib/filetests/src/test_folding.rs diff --git a/filetests/folding/branch.clif b/filetests/folding/branch.clif new file mode 100644 index 000000000..98303d81e --- /dev/null +++ b/filetests/folding/branch.clif @@ -0,0 +1,53 @@ +test folding + +function %brz_fold() -> i32 { +ebb0: + v0 = bconst.b1 false + brz v0, ebb2 + jump ebb1 +ebb1: + v1 = iconst.i32 42 + return v1 +ebb2: + v2 = iconst.i32 24 + return v2 +} +; sameln: function %brz_fold +; nextln: ebb0: +; nextln: v0 = bconst.b1 false +; nextln: jump ebb2 +; nextln: +; nextln: ebb1: +; nextln: v1 = iconst.i32 42 +; nextln: return v1 +; nextln: +; nextln: ebb2: +; nextln: v2 = iconst.i32 24 +; nextln: return v2 +; nextln: } + +function %brnz_fold() -> i32 { +ebb0: + v0 = bconst.b1 true + brnz v0, ebb2 + jump ebb1 +ebb1: + v1 = iconst.i32 42 + return v1 +ebb2: + v2 = iconst.i32 24 + return v2 +} +; sameln: function %brnz_fold +; nextln: ebb0: +; nextln: v0 = bconst.b1 true +; nextln: jump ebb2 +; nextln: +; nextln: ebb1: +; nextln: v1 = iconst.i32 42 +; nextln: return v1 +; nextln: +; nextln: ebb2: +; nextln: v2 = iconst.i32 24 +; nextln: return v2 +; nextln: } \ No newline at end of file diff --git a/filetests/folding/numerical.clif b/filetests/folding/numerical.clif new file mode 100644 index 000000000..c7703cf87 --- /dev/null +++ b/filetests/folding/numerical.clif @@ -0,0 +1,35 @@ +test folding + +function %iadd_fold() -> i32 { +ebb0: + v0 = iconst.i32 37 + v1 = iconst.i32 5 + v2 = iadd v0, v1 + v3 = iconst.i32 8 + v4 = iadd v2, v3 + return v4 +} +; sameln: function %iadd_fold +; nextln: ebb0: +; nextln: v0 = iconst.i32 37 +; nextln: v1 = iconst.i32 5 +; nextln: v2 = iconst.i32 42 +; nextln: v3 = iconst.i32 8 +; nextln: v4 = iconst.i32 50 +; nextln: return v4 +; nextln: } + +function %isub_fold() -> i32 { +ebb0: + v0 = iconst.i32 42 + v1 = iconst.i32 1 + v2 = isub v0, v1 + return v2 +} +; sameln: function %isub_fold +; nextln: ebb0: +; nextln: v0 = iconst.i32 42 +; nextln: v1 = iconst.i32 1 +; nextln: v2 = iconst.i32 41 +; nextln: return v2 +; nextln: } \ No newline at end of file diff --git a/lib/codegen/meta-python/base/settings.py b/lib/codegen/meta-python/base/settings.py index 4eb754184..4782572e2 100644 --- a/lib/codegen/meta-python/base/settings.py +++ b/lib/codegen/meta-python/base/settings.py @@ -103,6 +103,10 @@ """Enable the use of atomic instructions""", default=True) +enable_constant_folding = BoolSetting( + """Enable the constant folding pass. This may be resource intensive""", + default=False) + # # Settings specific to the `baldrdash` calling convention. # diff --git a/lib/codegen/src/constant_folding.rs b/lib/codegen/src/constant_folding.rs new file mode 100644 index 000000000..3fc3681f4 --- /dev/null +++ b/lib/codegen/src/constant_folding.rs @@ -0,0 +1,118 @@ +//! Fold operations on constants at compile time. + +use cursor::{Cursor, FuncCursor}; +use ir::{self, InstBuilder}; +// use timing; + +/// Fold operations on constants. +/// +/// It's important to note that this will not remove unused constants. It's +/// assumed that the DCE pass will take care of them. +pub fn fold_constants(func: &mut ir::Function) { + // let _tt = timing::constant_folding(); + let mut pos = FuncCursor::new(func); + + while let Some(_ebb) = pos.next_ebb() { + while let Some(inst) = pos.next_inst() { + use ir::instructions::Opcode::*; + match pos.func.dfg[inst].opcode() { + Iadd => { + fold_numerical_binary(&mut pos.func.dfg, inst, |v0_imm, v1_imm| v0_imm + v1_imm) + } + Isub => { + fold_numerical_binary(&mut pos.func.dfg, inst, |v0_imm, v1_imm| v0_imm - v1_imm) + } + Imul => { + fold_numerical_binary(&mut pos.func.dfg, inst, |v0_imm, v1_imm| v0_imm * v1_imm) + } + Brz | Brnz => fold_simple_branch(&mut pos.func, inst), + _ => {} + } + } + } +} + +/// Collapse a numerical binary function. +fn fold_numerical_binary(dfg: &mut ir::DataFlowGraph, inst: ir::Inst, f: F) +where + F: Fn(i64, i64) -> i64, +{ + let typevar = dfg.ctrl_typevar(inst); + let (v0, v1) = { + let values = dfg.inst_args(inst); + assert!(values.len() == 2); + ( + dfg.resolve_aliases(values[0]), + dfg.resolve_aliases(values[1]), + ) + }; + + if let (ir::ValueDef::Result(v0_inst, _), ir::ValueDef::Result(v1_inst, _)) = + (dfg.value_def(v0), dfg.value_def(v1)) + { + let v0_imm: i64 = if let ir::InstructionData::UnaryImm { + opcode: ir::Opcode::Iconst, + imm, + } = dfg[v0_inst] + { + imm.into() + } else { + return; + }; + + let v1_imm: i64 = if let ir::InstructionData::UnaryImm { + opcode: ir::Opcode::Iconst, + imm, + } = dfg[v1_inst] + { + imm.into() + } else { + return; + }; + + dfg.replace(inst).iconst(typevar, f(v0_imm, v1_imm)); + } +} + +/// Collapse a simple branch instructions. +fn fold_simple_branch(func: &mut ir::Function, inst: ir::Inst) { + let (inst_opcode, cond, ebb, args) = { + let values = func.dfg.inst_args(inst); + let inst_data = &func.dfg[inst]; + assert!(values.len() >= 1); + ( + inst_data.opcode(), + func.dfg.resolve_aliases(values[0]), + inst_data.branch_destination().unwrap(), + values[1..].to_vec(), + ) + }; + + if let ir::ValueDef::Result(cond_inst, _) = func.dfg.value_def(cond) { + match func.dfg[cond_inst] { + ir::InstructionData::UnaryBool { + opcode: ir::Opcode::Bconst, + imm, + } => { + let branch_if_zero = match inst_opcode { + ir::Opcode::Brz => true, + ir::Opcode::Brnz => false, + _ => panic!("Invalid state found"), + }; + + if (branch_if_zero && !imm) || (!branch_if_zero && imm) { + func.dfg.replace(inst).jump(ebb, &args); + // remove the rest of the ebb to avoid verifier errors + while let Some(next_inst) = func.layout.next_inst(inst) { + func.layout.remove_inst(next_inst); + } + } else { + // we can't remove the current instruction, so replace + // it with a `nop`. DCE will get rid of it for us. + func.dfg.replace(inst).nop(); + } + } + _ => return, + } + } +} diff --git a/lib/codegen/src/context.rs b/lib/codegen/src/context.rs index e5930cd57..111167c79 100644 --- a/lib/codegen/src/context.rs +++ b/lib/codegen/src/context.rs @@ -12,6 +12,7 @@ use binemit::{ relax_branches, shrink_instructions, CodeOffset, MemoryCodeSink, RelocSink, TrapSink, }; +use constant_folding::fold_constants; use dce::do_dce; use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; @@ -123,6 +124,10 @@ impl Context { let _tt = timing::compile(); self.verify_if(isa)?; + if isa.flags().enable_constant_folding() { + self.fold_constants(isa)?; + } + self.compute_cfg(); if isa.flags().opt_level() != OptLevel::Fastest { self.preopt(isa)?; @@ -331,4 +336,13 @@ impl Context { Ok(code_size) } + + /// Fold constants + pub fn fold_constants<'a, FOI>(&mut self, fisa: FOI) -> CodegenResult<()> + where + FOI: Into>, + { + fold_constants(&mut self.func); + self.verify_if(fisa) + } } diff --git a/lib/codegen/src/lib.rs b/lib/codegen/src/lib.rs index 24c381a6e..7ce170c85 100644 --- a/lib/codegen/src/lib.rs +++ b/lib/codegen/src/lib.rs @@ -94,6 +94,7 @@ pub use entity::packed_option; mod abi; mod bitset; +mod constant_folding; mod constant_hash; mod context; mod dce; diff --git a/lib/codegen/src/settings.rs b/lib/codegen/src/settings.rs index cd135eba4..71bc18eb4 100644 --- a/lib/codegen/src/settings.rs +++ b/lib/codegen/src/settings.rs @@ -380,6 +380,7 @@ mod tests { enable_nan_canonicalization = false\n\ enable_simd = true\n\ enable_atomics = true\n\ + enable_constant_folding = false\n\ baldrdash_prologue_words = 0\n\ allones_funcaddrs = false\n\ probestack_enabled = true\n\ diff --git a/lib/codegen/src/timing.rs b/lib/codegen/src/timing.rs index e5327071c..197bf39b2 100644 --- a/lib/codegen/src/timing.rs +++ b/lib/codegen/src/timing.rs @@ -62,6 +62,7 @@ define_passes!{ gvn: "Global value numbering", licm: "Loop invariant code motion", unreachable_code: "Remove unreachable blocks", + // constant_folding: "Fold constant expressions", regalloc: "Register allocation", ra_liveness: "RA liveness analysis", diff --git a/lib/filetests/src/lib.rs b/lib/filetests/src/lib.rs index 6d48eb963..0ebdc1ba5 100644 --- a/lib/filetests/src/lib.rs +++ b/lib/filetests/src/lib.rs @@ -51,6 +51,7 @@ mod test_cat; mod test_compile; mod test_dce; mod test_domtree; +mod test_folding; mod test_legalizer; mod test_licm; mod test_postopt; @@ -126,6 +127,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult test_shrink::subtest(parsed), "simple-gvn" => test_simple_gvn::subtest(parsed), "verifier" => test_verifier::subtest(parsed), + "folding" => test_folding::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/lib/filetests/src/test_folding.rs b/lib/filetests/src/test_folding.rs new file mode 100644 index 000000000..665e6e6c3 --- /dev/null +++ b/lib/filetests/src/test_folding.rs @@ -0,0 +1,45 @@ +//! Test command for testing the constant folding pass. +//! +//! The `dce` test command runs each function through the constant folding pass after ensuring +//! that all instructions are legal for the target. +//! +//! The resulting function is sent to `filecheck`. + +use cranelift_codegen; +use cranelift_codegen::ir::Function; +use cranelift_codegen::print_errors::pretty_error; +use cranelift_reader::TestCommand; +use std::borrow::Cow; +use subtest::{run_filecheck, Context, SubTest, SubtestResult}; + +struct TestFolding; + +pub fn subtest(parsed: &TestCommand) -> SubtestResult> { + assert_eq!(parsed.command, "folding"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestFolding)) + } +} + +impl SubTest for TestFolding { + fn name(&self) -> &'static str { + "folding" + } + + fn is_mutating(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> SubtestResult<()> { + let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); + + comp_ctx + .fold_constants(context.flags_or_isa()) + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; + + let text = comp_ctx.func.display(context.isa).to_string(); + run_filecheck(&text, context) + } +} From 4b6f468bdb6371d45298c5759a8054435d5ec99b Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Wed, 10 Oct 2018 15:27:00 -0400 Subject: [PATCH 02/16] Add missing newlines --- filetests/folding/branch.clif | 2 +- filetests/folding/numerical.clif | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/filetests/folding/branch.clif b/filetests/folding/branch.clif index 98303d81e..4133a4eb5 100644 --- a/filetests/folding/branch.clif +++ b/filetests/folding/branch.clif @@ -50,4 +50,4 @@ ebb2: ; nextln: ebb2: ; nextln: v2 = iconst.i32 24 ; nextln: return v2 -; nextln: } \ No newline at end of file +; nextln: } diff --git a/filetests/folding/numerical.clif b/filetests/folding/numerical.clif index c7703cf87..f34ad38e3 100644 --- a/filetests/folding/numerical.clif +++ b/filetests/folding/numerical.clif @@ -32,4 +32,4 @@ ebb0: ; nextln: v1 = iconst.i32 1 ; nextln: v2 = iconst.i32 41 ; nextln: return v2 -; nextln: } \ No newline at end of file +; nextln: } From 7d21712c7c934110d15af4c1c1a15f9ac2fde58e Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Wed, 10 Oct 2018 15:40:52 -0400 Subject: [PATCH 03/16] Fix constant size array compile error --- lib/codegen/src/constant_folding.rs | 4 ++-- lib/codegen/src/timing.rs | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/codegen/src/constant_folding.rs b/lib/codegen/src/constant_folding.rs index 3fc3681f4..06086e261 100644 --- a/lib/codegen/src/constant_folding.rs +++ b/lib/codegen/src/constant_folding.rs @@ -2,14 +2,14 @@ use cursor::{Cursor, FuncCursor}; use ir::{self, InstBuilder}; -// use timing; +use timing; /// Fold operations on constants. /// /// It's important to note that this will not remove unused constants. It's /// assumed that the DCE pass will take care of them. pub fn fold_constants(func: &mut ir::Function) { - // let _tt = timing::constant_folding(); + let _tt = timing::constant_folding(); let mut pos = FuncCursor::new(func); while let Some(_ebb) = pos.next_ebb() { diff --git a/lib/codegen/src/timing.rs b/lib/codegen/src/timing.rs index 197bf39b2..88430dc21 100644 --- a/lib/codegen/src/timing.rs +++ b/lib/codegen/src/timing.rs @@ -62,7 +62,7 @@ define_passes!{ gvn: "Global value numbering", licm: "Loop invariant code motion", unreachable_code: "Remove unreachable blocks", - // constant_folding: "Fold constant expressions", + constant_folding: "Fold constant expressions", regalloc: "Register allocation", ra_liveness: "RA liveness analysis", @@ -125,7 +125,7 @@ mod details { } /// Accumulated timing information for a single pass. - #[derive(Default)] + #[derive(Default, Copy, Clone)] struct PassTime { /// Total time spent running this pass including children. total: Duration, @@ -135,17 +135,25 @@ mod details { } /// Accumulated timing for all passes. - #[derive(Default)] + // #[derive(Default)] pub struct PassTimes { pass: [PassTime; NUM_PASSES], } + impl Default for PassTimes { + fn default() -> Self { + PassTimes { + pass: [Default::default(); NUM_PASSES], + } + } + } + impl fmt::Display for PassTimes { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "======== ======== ==================================")?; writeln!(f, " Total Self Pass")?; writeln!(f, "-------- -------- ----------------------------------")?; - for (time, desc) in self.pass.iter().zip(&DESCRIPTIONS) { + for (time, desc) in self.pass.iter().zip(&DESCRIPTIONS[..]) { // Omit passes that haven't run. if time.total == Duration::default() { continue; @@ -213,7 +221,7 @@ mod details { /// Add `timings` to the accumulated timings for the current thread. pub fn add_to_current(times: &PassTimes) { PASS_TIME.with(|rc| { - for (a, b) in rc.borrow_mut().pass.iter_mut().zip(×.pass) { + for (a, b) in rc.borrow_mut().pass.iter_mut().zip(×.pass[..]) { a.total += b.total; a.child += b.child; } From eda762115f5ef71e853707998c865b240bb27b98 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Wed, 10 Oct 2018 22:56:39 -0400 Subject: [PATCH 04/16] Redesign binary operation constant folding --- lib/codegen/src/constant_folding.rs | 211 ++++++++++++++++++++++------ 1 file changed, 166 insertions(+), 45 deletions(-) diff --git a/lib/codegen/src/constant_folding.rs b/lib/codegen/src/constant_folding.rs index 06086e261..403ceb6d9 100644 --- a/lib/codegen/src/constant_folding.rs +++ b/lib/codegen/src/constant_folding.rs @@ -2,8 +2,36 @@ use cursor::{Cursor, FuncCursor}; use ir::{self, InstBuilder}; +use std::mem; use timing; +enum ConstImm { + Bool(bool), + I64(i64), + Ieee32(f32), + Ieee64(f64), +} + +impl ConstImm { + fn unwrap_i64(self) -> i64 { + if let ConstImm::I64(imm) = self { + imm + } else { + panic!("self did not contain an `i64`.") + } + } + + fn evaluate_truthiness(self) -> bool { + match self { + ConstImm::Bool(b) => b, + ConstImm::I64(imm) => imm != 0, + _ => panic!( + "Only a `ConstImm::Bool` and `ConstImm::I64` can be evaluated for \"truthiness\"" + ), + } + } +} + /// Fold operations on constants. /// /// It's important to note that this will not remove unused constants. It's @@ -14,66 +42,159 @@ pub fn fold_constants(func: &mut ir::Function) { while let Some(_ebb) = pos.next_ebb() { while let Some(inst) = pos.next_inst() { - use ir::instructions::Opcode::*; - match pos.func.dfg[inst].opcode() { - Iadd => { - fold_numerical_binary(&mut pos.func.dfg, inst, |v0_imm, v1_imm| v0_imm + v1_imm) - } - Isub => { - fold_numerical_binary(&mut pos.func.dfg, inst, |v0_imm, v1_imm| v0_imm - v1_imm) + use ir::InstructionData::*; + match pos.func.dfg[inst] { + Binary { opcode, args } => { + fold_numerical_binary(&mut pos.func.dfg, inst, opcode, args); } - Imul => { - fold_numerical_binary(&mut pos.func.dfg, inst, |v0_imm, v1_imm| v0_imm * v1_imm) + Branch { + opcode: _, + args: _, + destination: _, + } => { + fold_simple_branch(&mut pos.func, inst); } - Brz | Brnz => fold_simple_branch(&mut pos.func, inst), _ => {} } } } } -/// Collapse a numerical binary function. -fn fold_numerical_binary(dfg: &mut ir::DataFlowGraph, inst: ir::Inst, f: F) -where - F: Fn(i64, i64) -> i64, -{ - let typevar = dfg.ctrl_typevar(inst); - let (v0, v1) = { - let values = dfg.inst_args(inst); - assert!(values.len() == 2); - ( - dfg.resolve_aliases(values[0]), - dfg.resolve_aliases(values[1]), - ) - }; +fn resolve_value_to_imm(dfg: &ir::DataFlowGraph, value: ir::Value) -> Option { + let original = dfg.resolve_aliases(value); - if let (ir::ValueDef::Result(v0_inst, _), ir::ValueDef::Result(v1_inst, _)) = - (dfg.value_def(v0), dfg.value_def(v1)) - { - let v0_imm: i64 = if let ir::InstructionData::UnaryImm { - opcode: ir::Opcode::Iconst, - imm, - } = dfg[v0_inst] - { - imm.into() - } else { - return; - }; + let inst = dfg.value_def(original).unwrap_inst(); - let v1_imm: i64 = if let ir::InstructionData::UnaryImm { - opcode: ir::Opcode::Iconst, + use ir::{InstructionData::*, Opcode::*}; + match dfg[inst] { + UnaryImm { + opcode: Iconst, imm, - } = dfg[v1_inst] - { - imm.into() - } else { - return; - }; + } => Some(ConstImm::I64(imm.into())), + UnaryIeee32 { + opcode: F32const, + imm, + } => { + let imm_as_f32: f32 = unsafe { mem::transmute(imm.bits()) }; + Some(ConstImm::Ieee32(imm_as_f32)) + } + UnaryIeee64 { + opcode: F64const, + imm, + } => { + let imm_as_f64: f64 = unsafe { mem::transmute(imm.bits()) }; + Some(ConstImm::Ieee64(imm_as_f64)) + } + UnaryBool { + opcode: Bconst, + imm, + } => Some(ConstImm::Bool(imm)), + _ => None, + } +} + +fn evaluate_numerical_binary( + dfg: &ir::DataFlowGraph, + opcode: ir::Opcode, + args: [ir::Value; 2], +) -> Option { + use std::num::Wrapping; + + let (imm0, imm1) = ( + resolve_value_to_imm(dfg, args[0])?, + resolve_value_to_imm(dfg, args[1])?, + ); + + match opcode { + ir::Opcode::Iadd => { + let imm0 = Wrapping(imm0.unwrap_i64()); + let imm1 = Wrapping(imm1.unwrap_i64()); + Some(ConstImm::I64((imm0 + imm1).0)) + } + ir::Opcode::Isub => { + let imm0 = Wrapping(imm0.unwrap_i64()); + let imm1 = Wrapping(imm1.unwrap_i64()); + Some(ConstImm::I64((imm0 - imm1).0)) + } + ir::Opcode::Imul => { + let imm0 = Wrapping(imm0.unwrap_i64()); + let imm1 = Wrapping(imm1.unwrap_i64()); + Some(ConstImm::I64((imm0 * imm1).0)) + } + ir::Opcode::Udiv => { + let imm0 = Wrapping(imm0.unwrap_i64()); + let imm1 = Wrapping(imm1.unwrap_i64()); + if imm1.0 == 0 { + panic!("Cannot divide by a zero.") + } + Some(ConstImm::I64((imm0 / imm1).0)) + } + ir::Opcode::Fadd => match (imm0, imm1) { + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => Some(ConstImm::Ieee32(imm0 + imm1)), + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => Some(ConstImm::Ieee64(imm0 + imm1)), + _ => unreachable!(), + }, + ir::Opcode::Fsub => match (imm0, imm1) { + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => Some(ConstImm::Ieee32(imm0 - imm1)), + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => Some(ConstImm::Ieee64(imm0 - imm1)), + _ => unreachable!(), + }, + ir::Opcode::Fmul => match (imm0, imm1) { + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => Some(ConstImm::Ieee32(imm0 * imm1)), + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => Some(ConstImm::Ieee64(imm0 * imm1)), + _ => unreachable!(), + }, + ir::Opcode::Fdiv => match (imm0, imm1) { + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { + if imm1 == 0.0 { + panic!("Cannot divide by a zero.") + } + Some(ConstImm::Ieee32(imm0 / imm1)) + } + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { + if imm1 == 0.0 { + panic!("Cannot divide by a zero.") + } + Some(ConstImm::Ieee64(imm0 / imm1)) + } + _ => unreachable!(), + }, + // ir::Opcode::Fadd => Some(imm0.unwrap) + _ => None, + } +} - dfg.replace(inst).iconst(typevar, f(v0_imm, v1_imm)); +/// Fold a numerical binary function. +fn fold_numerical_binary( + dfg: &mut ir::DataFlowGraph, + inst: ir::Inst, + opcode: ir::Opcode, + args: [ir::Value; 2], +) { + if let Some(const_imm) = evaluate_numerical_binary(dfg, opcode, args) { + use self::ConstImm::*; + match const_imm { + I64(imm) => { + let typevar = dfg.ctrl_typevar(inst); + dfg.replace(inst).iconst(typevar, imm); + } + Ieee32(imm) => { + dfg.replace(inst) + .f32const(ir::immediates::Ieee32::with_float(imm)); + } + Ieee64(imm) => { + dfg.replace(inst) + .f64const(ir::immediates::Ieee64::with_float(imm)); + } + _ => unreachable!(), + } } } +// fn evaulate_simple_branch(dfg: &ir::DataFlowGraph, opcode: ir::Opcode, args: &ir::ValueList) {} + +// fn fold_simple_branch2(dfg: &mut ir::DataFlowGraph, inst: ir::Inst, opcode: ir::Opcode) {} + /// Collapse a simple branch instructions. fn fold_simple_branch(func: &mut ir::Function, inst: ir::Inst) { let (inst_opcode, cond, ebb, args) = { From 13e52f95ccb75572db4cededbfcfd344da83f542 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Wed, 10 Oct 2018 23:01:53 -0400 Subject: [PATCH 05/16] Remove 'enable_constant_folding' setting and move into optimize method --- lib/codegen/meta-python/base/settings.py | 4 ---- lib/codegen/src/context.rs | 14 ++++++++++---- lib/codegen/src/settings.rs | 1 - 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/codegen/meta-python/base/settings.py b/lib/codegen/meta-python/base/settings.py index 4782572e2..4eb754184 100644 --- a/lib/codegen/meta-python/base/settings.py +++ b/lib/codegen/meta-python/base/settings.py @@ -103,10 +103,6 @@ """Enable the use of atomic instructions""", default=True) -enable_constant_folding = BoolSetting( - """Enable the constant folding pass. This may be resource intensive""", - default=False) - # # Settings specific to the `baldrdash` calling convention. # diff --git a/lib/codegen/src/context.rs b/lib/codegen/src/context.rs index 111167c79..7b4c36f1b 100644 --- a/lib/codegen/src/context.rs +++ b/lib/codegen/src/context.rs @@ -113,6 +113,16 @@ impl Context { Ok(()) } + /// Optimize the function with available optimizations. + /// + /// Since this can be resource intensive (and code-size inflating), + /// it is separated from `Context::compile` to allow DCE to remove it + /// if it's not used. + pub fn optimize(&mut self, isa: &TargetIsa) -> CodegenResult<()> { + self.verify_if(isa)?; + self.fold_constants(isa) + } + /// Compile the function. /// /// Run the function through all the passes necessary to generate code for the target ISA @@ -124,10 +134,6 @@ impl Context { let _tt = timing::compile(); self.verify_if(isa)?; - if isa.flags().enable_constant_folding() { - self.fold_constants(isa)?; - } - self.compute_cfg(); if isa.flags().opt_level() != OptLevel::Fastest { self.preopt(isa)?; diff --git a/lib/codegen/src/settings.rs b/lib/codegen/src/settings.rs index 71bc18eb4..cd135eba4 100644 --- a/lib/codegen/src/settings.rs +++ b/lib/codegen/src/settings.rs @@ -380,7 +380,6 @@ mod tests { enable_nan_canonicalization = false\n\ enable_simd = true\n\ enable_atomics = true\n\ - enable_constant_folding = false\n\ baldrdash_prologue_words = 0\n\ allones_funcaddrs = false\n\ probestack_enabled = true\n\ From c051993b2afe080d1deb1e91ecc9a88685e0786b Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Thu, 11 Oct 2018 00:19:05 -0400 Subject: [PATCH 06/16] Remove unsafe code --- lib/codegen/src/constant_folding.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/codegen/src/constant_folding.rs b/lib/codegen/src/constant_folding.rs index 403ceb6d9..2ffba1dc2 100644 --- a/lib/codegen/src/constant_folding.rs +++ b/lib/codegen/src/constant_folding.rs @@ -2,7 +2,6 @@ use cursor::{Cursor, FuncCursor}; use ir::{self, InstBuilder}; -use std::mem; use timing; enum ConstImm { @@ -75,14 +74,14 @@ fn resolve_value_to_imm(dfg: &ir::DataFlowGraph, value: ir::Value) -> Option { - let imm_as_f32: f32 = unsafe { mem::transmute(imm.bits()) }; + let imm_as_f32 = f32::from_bits(imm.bits()); // see https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits for caveats Some(ConstImm::Ieee32(imm_as_f32)) } UnaryIeee64 { opcode: F64const, imm, } => { - let imm_as_f64: f64 = unsafe { mem::transmute(imm.bits()) }; + let imm_as_f64 = f64::from_bits(imm.bits()); // see https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits for caveats Some(ConstImm::Ieee64(imm_as_f64)) } UnaryBool { @@ -191,7 +190,9 @@ fn fold_numerical_binary( } } -// fn evaulate_simple_branch(dfg: &ir::DataFlowGraph, opcode: ir::Opcode, args: &ir::ValueList) {} +// fn evaulate_simple_branch(dfg: &ir::DataFlowGraph, opcode: ir::Opcode, args: &ir::ValueList) -> Option { + +// } // fn fold_simple_branch2(dfg: &mut ir::DataFlowGraph, inst: ir::Inst, opcode: ir::Opcode) {} From 80d8b4e0147f93238c5558b80076c16b0d6df450 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Thu, 11 Oct 2018 15:27:19 -0400 Subject: [PATCH 07/16] Add software float support to the constant folding pass --- lib/codegen/Cargo.toml | 3 +- lib/codegen/src/constant_folding.rs | 57 ++++++++++++++++++----------- lib/codegen/src/lib.rs | 2 + 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/lib/codegen/Cargo.toml b/lib/codegen/Cargo.toml index 7c1d2d0e9..b6d8cb67e 100644 --- a/lib/codegen/Cargo.toml +++ b/lib/codegen/Cargo.toml @@ -19,6 +19,7 @@ failure_derive = { version = "0.1.1", default-features = false } hashmap_core = { version = "0.1.9", optional = true } target-lexicon = { version = "0.0.3", default-features = false } log = { version = "0.4.4", default-features = false } +rustc_apfloat = { version = "0.1.1", default-features = false } # It is a goal of the cranelift-codegen crate to have minimal external dependencies. # Please don't add any unless they are essential to the task of creating binary # machine code. Integration tests that need external dependencies can be @@ -32,7 +33,7 @@ cranelift-codegen-meta = { path = "meta", version = "0.22.0" } # of some minimal std-like replacement libraries. At least one of these two # features need to be enabled. default = ["std"] -std = ["cranelift-entity/std", "cranelift-bforest/std", "target-lexicon/std"] +std = ["cranelift-entity/std", "cranelift-bforest/std", "target-lexicon/std", "rustc_apfloat/std"] core = ["hashmap_core"] # This enables some additional functions useful for writing tests, but which # can significantly increase the size of the library. diff --git a/lib/codegen/src/constant_folding.rs b/lib/codegen/src/constant_folding.rs index 2ffba1dc2..0e94a97e5 100644 --- a/lib/codegen/src/constant_folding.rs +++ b/lib/codegen/src/constant_folding.rs @@ -2,13 +2,17 @@ use cursor::{Cursor, FuncCursor}; use ir::{self, InstBuilder}; +use rustc_apfloat::{ + ieee::{Double, Single}, + Float, +}; use timing; enum ConstImm { Bool(bool), I64(i64), - Ieee32(f32), - Ieee64(f64), + Ieee32(Single), + Ieee64(Double), } impl ConstImm { @@ -74,15 +78,15 @@ fn resolve_value_to_imm(dfg: &ir::DataFlowGraph, value: ir::Value) -> Option { - let imm_as_f32 = f32::from_bits(imm.bits()); // see https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits for caveats - Some(ConstImm::Ieee32(imm_as_f32)) + let ieee_f32 = Single::from_bits(imm.bits() as _); + Some(ConstImm::Ieee32(ieee_f32)) } UnaryIeee64 { opcode: F64const, imm, } => { - let imm_as_f64 = f64::from_bits(imm.bits()); // see https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits for caveats - Some(ConstImm::Ieee64(imm_as_f64)) + let ieee_f64 = Double::from_bits(imm.bits() as _); + Some(ConstImm::Ieee64(ieee_f64)) } UnaryBool { opcode: Bconst, @@ -129,36 +133,47 @@ fn evaluate_numerical_binary( Some(ConstImm::I64((imm0 / imm1).0)) } ir::Opcode::Fadd => match (imm0, imm1) { - (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => Some(ConstImm::Ieee32(imm0 + imm1)), - (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => Some(ConstImm::Ieee64(imm0 + imm1)), + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { + Some(ConstImm::Ieee32((imm0 + imm1).value)) + } + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { + Some(ConstImm::Ieee64((imm0 + imm1).value)) + } _ => unreachable!(), }, ir::Opcode::Fsub => match (imm0, imm1) { - (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => Some(ConstImm::Ieee32(imm0 - imm1)), - (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => Some(ConstImm::Ieee64(imm0 - imm1)), + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { + Some(ConstImm::Ieee32((imm0 - imm1).value)) + } + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { + Some(ConstImm::Ieee64((imm0 - imm1).value)) + } _ => unreachable!(), }, ir::Opcode::Fmul => match (imm0, imm1) { - (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => Some(ConstImm::Ieee32(imm0 * imm1)), - (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => Some(ConstImm::Ieee64(imm0 * imm1)), + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { + Some(ConstImm::Ieee32((imm0 * imm1).value)) + } + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { + Some(ConstImm::Ieee64((imm0 * imm1).value)) + } _ => unreachable!(), }, ir::Opcode::Fdiv => match (imm0, imm1) { (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { - if imm1 == 0.0 { - panic!("Cannot divide by a zero.") + if imm1.is_zero() { + return None; } - Some(ConstImm::Ieee32(imm0 / imm1)) + Some(ConstImm::Ieee32((imm0 / imm1).value)) } (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { - if imm1 == 0.0 { - panic!("Cannot divide by a zero.") + if imm1.is_zero() { + return None; } - Some(ConstImm::Ieee64(imm0 / imm1)) + Some(ConstImm::Ieee64((imm0 / imm1).value)) } _ => unreachable!(), }, - // ir::Opcode::Fadd => Some(imm0.unwrap) _ => None, } } @@ -179,11 +194,11 @@ fn fold_numerical_binary( } Ieee32(imm) => { dfg.replace(inst) - .f32const(ir::immediates::Ieee32::with_float(imm)); + .f32const(ir::immediates::Ieee32::with_bits(imm.to_bits() as u32)); } Ieee64(imm) => { dfg.replace(inst) - .f64const(ir::immediates::Ieee64::with_float(imm)); + .f64const(ir::immediates::Ieee64::with_bits(imm.to_bits() as u64)); } _ => unreachable!(), } diff --git a/lib/codegen/src/lib.rs b/lib/codegen/src/lib.rs index 7ce170c85..a97ca0b68 100644 --- a/lib/codegen/src/lib.rs +++ b/lib/codegen/src/lib.rs @@ -63,6 +63,8 @@ extern crate target_lexicon; #[macro_use] extern crate log; +extern crate rustc_apfloat; + pub use context::Context; pub use legalizer::legalize_function; pub use verifier::verify_function; From 87ad3f3e513464e2b0a04a14fe531ae5b09e9e19 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Thu, 11 Oct 2018 16:25:31 -0400 Subject: [PATCH 08/16] Update to non-broken apfloat version --- lib/codegen/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codegen/Cargo.toml b/lib/codegen/Cargo.toml index b6d8cb67e..e51cbb532 100644 --- a/lib/codegen/Cargo.toml +++ b/lib/codegen/Cargo.toml @@ -19,7 +19,7 @@ failure_derive = { version = "0.1.1", default-features = false } hashmap_core = { version = "0.1.9", optional = true } target-lexicon = { version = "0.0.3", default-features = false } log = { version = "0.4.4", default-features = false } -rustc_apfloat = { version = "0.1.1", default-features = false } +rustc_apfloat = { version = "0.1.2", default-features = false } # It is a goal of the cranelift-codegen crate to have minimal external dependencies. # Please don't add any unless they are essential to the task of creating binary # machine code. Integration tests that need external dependencies can be From 34f2b8402973723f28cc6dbfbf668efae88cf3cd Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Fri, 12 Oct 2018 11:04:25 -0400 Subject: [PATCH 09/16] Move constant folding to lib/preopt --- Cargo.toml | 1 + lib/codegen/Cargo.toml | 3 +- lib/codegen/src/context.rs | 22 +- lib/codegen/src/lib.rs | 5 +- .../src/{preopt.rs => simple_preopt.rs} | 0 lib/codegen/src/timing.rs | 1 - lib/filetests/Cargo.toml | 1 + lib/filetests/src/lib.rs | 1 + lib/filetests/src/test_folding.rs | 4 +- lib/preopt/Cargo.toml | 24 ++ lib/preopt/LICENSE | 219 ++++++++++++++++++ lib/preopt/README.md | 1 + .../src/constant_folding.rs | 12 +- lib/preopt/src/lib.rs | 59 +++++ 14 files changed, 317 insertions(+), 36 deletions(-) rename lib/codegen/src/{preopt.rs => simple_preopt.rs} (100%) create mode 100644 lib/preopt/Cargo.toml create mode 100644 lib/preopt/LICENSE create mode 100644 lib/preopt/README.md rename lib/{codegen => preopt}/src/constant_folding.rs (97%) create mode 100644 lib/preopt/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index e97cbbc00..f3b70149f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ cranelift-filetests = { path = "lib/filetests", version = "0.22.0" } cranelift-module = { path = "lib/module", version = "0.22.0" } cranelift-faerie = { path = "lib/faerie", version = "0.22.0" } cranelift-simplejit = { path = "lib/simplejit", version = "0.22.0" } +cranelift-preopt = { path = "lib/preopt", version = "0.22.0" } cranelift = { path = "lib/umbrella", version = "0.22.0" } filecheck = "0.4.0" clap = "2.32.0" diff --git a/lib/codegen/Cargo.toml b/lib/codegen/Cargo.toml index e51cbb532..7c1d2d0e9 100644 --- a/lib/codegen/Cargo.toml +++ b/lib/codegen/Cargo.toml @@ -19,7 +19,6 @@ failure_derive = { version = "0.1.1", default-features = false } hashmap_core = { version = "0.1.9", optional = true } target-lexicon = { version = "0.0.3", default-features = false } log = { version = "0.4.4", default-features = false } -rustc_apfloat = { version = "0.1.2", default-features = false } # It is a goal of the cranelift-codegen crate to have minimal external dependencies. # Please don't add any unless they are essential to the task of creating binary # machine code. Integration tests that need external dependencies can be @@ -33,7 +32,7 @@ cranelift-codegen-meta = { path = "meta", version = "0.22.0" } # of some minimal std-like replacement libraries. At least one of these two # features need to be enabled. default = ["std"] -std = ["cranelift-entity/std", "cranelift-bforest/std", "target-lexicon/std", "rustc_apfloat/std"] +std = ["cranelift-entity/std", "cranelift-bforest/std", "target-lexicon/std"] core = ["hashmap_core"] # This enables some additional functions useful for writing tests, but which # can significantly increase the size of the library. diff --git a/lib/codegen/src/context.rs b/lib/codegen/src/context.rs index 7b4c36f1b..e4056409a 100644 --- a/lib/codegen/src/context.rs +++ b/lib/codegen/src/context.rs @@ -12,7 +12,6 @@ use binemit::{ relax_branches, shrink_instructions, CodeOffset, MemoryCodeSink, RelocSink, TrapSink, }; -use constant_folding::fold_constants; use dce::do_dce; use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; @@ -23,11 +22,11 @@ use licm::do_licm; use loop_analysis::LoopAnalysis; use nan_canonicalization::do_nan_canonicalization; use postopt::do_postopt; -use preopt::do_preopt; use regalloc; use result::CodegenResult; use settings::{FlagsOrIsa, OptLevel}; use simple_gvn::do_simple_gvn; +use simple_preopt::do_preopt; use std::vec::Vec; use timing; use unreachable_code::eliminate_unreachable_code; @@ -113,16 +112,6 @@ impl Context { Ok(()) } - /// Optimize the function with available optimizations. - /// - /// Since this can be resource intensive (and code-size inflating), - /// it is separated from `Context::compile` to allow DCE to remove it - /// if it's not used. - pub fn optimize(&mut self, isa: &TargetIsa) -> CodegenResult<()> { - self.verify_if(isa)?; - self.fold_constants(isa) - } - /// Compile the function. /// /// Run the function through all the passes necessary to generate code for the target ISA @@ -342,13 +331,4 @@ impl Context { Ok(code_size) } - - /// Fold constants - pub fn fold_constants<'a, FOI>(&mut self, fisa: FOI) -> CodegenResult<()> - where - FOI: Into>, - { - fold_constants(&mut self.func); - self.verify_if(fisa) - } } diff --git a/lib/codegen/src/lib.rs b/lib/codegen/src/lib.rs index a97ca0b68..0f8f637e6 100644 --- a/lib/codegen/src/lib.rs +++ b/lib/codegen/src/lib.rs @@ -63,8 +63,6 @@ extern crate target_lexicon; #[macro_use] extern crate log; -extern crate rustc_apfloat; - pub use context::Context; pub use legalizer::legalize_function; pub use verifier::verify_function; @@ -96,7 +94,6 @@ pub use entity::packed_option; mod abi; mod bitset; -mod constant_folding; mod constant_hash; mod context; mod dce; @@ -109,12 +106,12 @@ mod nan_canonicalization; mod partition_slice; mod postopt; mod predicates; -mod preopt; mod ref_slice; mod regalloc; mod result; mod scoped_hash_map; mod simple_gvn; +mod simple_preopt; mod stack_layout; mod topo_order; mod unreachable_code; diff --git a/lib/codegen/src/preopt.rs b/lib/codegen/src/simple_preopt.rs similarity index 100% rename from lib/codegen/src/preopt.rs rename to lib/codegen/src/simple_preopt.rs diff --git a/lib/codegen/src/timing.rs b/lib/codegen/src/timing.rs index 88430dc21..3bc528e33 100644 --- a/lib/codegen/src/timing.rs +++ b/lib/codegen/src/timing.rs @@ -62,7 +62,6 @@ define_passes!{ gvn: "Global value numbering", licm: "Loop invariant code motion", unreachable_code: "Remove unreachable blocks", - constant_folding: "Fold constant expressions", regalloc: "Register allocation", ra_liveness: "RA liveness analysis", diff --git a/lib/filetests/Cargo.toml b/lib/filetests/Cargo.toml index 1ed2daf9e..c3f587e56 100644 --- a/lib/filetests/Cargo.toml +++ b/lib/filetests/Cargo.toml @@ -11,6 +11,7 @@ publish = false [dependencies] cranelift-codegen = { path = "../codegen", version = "0.22.0", features = ["testing_hooks"] } cranelift-reader = { path = "../reader", version = "0.22.0" } +cranelift-preopt = { path = "../preopt", version = "0.22.0" } file-per-thread-logger = "0.1.1" filecheck = "0.4.0" num_cpus = "1.8.0" diff --git a/lib/filetests/src/lib.rs b/lib/filetests/src/lib.rs index 0ebdc1ba5..f64e603e9 100644 --- a/lib/filetests/src/lib.rs +++ b/lib/filetests/src/lib.rs @@ -28,6 +28,7 @@ )] extern crate cranelift_codegen; +extern crate cranelift_preopt; extern crate cranelift_reader; extern crate file_per_thread_logger; extern crate filecheck; diff --git a/lib/filetests/src/test_folding.rs b/lib/filetests/src/test_folding.rs index 665e6e6c3..0129de551 100644 --- a/lib/filetests/src/test_folding.rs +++ b/lib/filetests/src/test_folding.rs @@ -8,6 +8,7 @@ use cranelift_codegen; use cranelift_codegen::ir::Function; use cranelift_codegen::print_errors::pretty_error; +use cranelift_preopt::fold_constants; use cranelift_reader::TestCommand; use std::borrow::Cow; use subtest::{run_filecheck, Context, SubTest, SubtestResult}; @@ -35,8 +36,7 @@ impl SubTest for TestFolding { fn run(&self, func: Cow, context: &Context) -> SubtestResult<()> { let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); - comp_ctx - .fold_constants(context.flags_or_isa()) + fold_constants(&mut comp_ctx, context.flags_or_isa()) .map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; let text = comp_ctx.func.display(context.isa).to_string(); diff --git a/lib/preopt/Cargo.toml b/lib/preopt/Cargo.toml new file mode 100644 index 000000000..c7d1d5b02 --- /dev/null +++ b/lib/preopt/Cargo.toml @@ -0,0 +1,24 @@ +[package] +authors = ["The Cranelift Project Developers"] +name = "cranelift-preopt" +version = "0.22.0" +description = "Support for optimizations in Cranelift" +license = "Apache-2.0 WITH LLVM-exception" +documentation = "https://cranelift.readthedocs.io/" +repository = "https://github.com/CraneStation/cranelift" +categories = ["no_std"] +readme = "README.md" +keywords = ["optimize", "compile", "compiler", "jit"] + +[dependencies] +cranelift-codegen = { path = "../codegen", version = "0.22.0", default-features = false } +rustc_apfloat = { version = "0.1.2", default-features = false } + +[features] +default = ["std"] +std = ["cranelift-codegen/std", "rustc_apfloat/std"] +core = ["cranelift-codegen/core"] + +[badges] +maintenance = { status = "experimental" } +travis-ci = { repository = "CraneStation/cranelift" } \ No newline at end of file diff --git a/lib/preopt/LICENSE b/lib/preopt/LICENSE new file mode 100644 index 000000000..be1d7c438 --- /dev/null +++ b/lib/preopt/LICENSE @@ -0,0 +1,219 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. diff --git a/lib/preopt/README.md b/lib/preopt/README.md new file mode 100644 index 000000000..12608b7ad --- /dev/null +++ b/lib/preopt/README.md @@ -0,0 +1 @@ +This crate performes early-stage optimizations on [Cranelift](https://crates.io/crates/cranelift) IR. \ No newline at end of file diff --git a/lib/codegen/src/constant_folding.rs b/lib/preopt/src/constant_folding.rs similarity index 97% rename from lib/codegen/src/constant_folding.rs rename to lib/preopt/src/constant_folding.rs index 0e94a97e5..3e81b53e6 100644 --- a/lib/codegen/src/constant_folding.rs +++ b/lib/preopt/src/constant_folding.rs @@ -1,12 +1,13 @@ //! Fold operations on constants at compile time. -use cursor::{Cursor, FuncCursor}; -use ir::{self, InstBuilder}; +use cranelift_codegen::{ + cursor::{Cursor, FuncCursor}, + ir::{self, InstBuilder}, +}; use rustc_apfloat::{ ieee::{Double, Single}, Float, }; -use timing; enum ConstImm { Bool(bool), @@ -40,12 +41,11 @@ impl ConstImm { /// It's important to note that this will not remove unused constants. It's /// assumed that the DCE pass will take care of them. pub fn fold_constants(func: &mut ir::Function) { - let _tt = timing::constant_folding(); let mut pos = FuncCursor::new(func); while let Some(_ebb) = pos.next_ebb() { while let Some(inst) = pos.next_inst() { - use ir::InstructionData::*; + use self::ir::InstructionData::*; match pos.func.dfg[inst] { Binary { opcode, args } => { fold_numerical_binary(&mut pos.func.dfg, inst, opcode, args); @@ -68,7 +68,7 @@ fn resolve_value_to_imm(dfg: &ir::DataFlowGraph, value: ir::Value) -> Option CodegenResult<()> { + ctx.verify_if(isa)?; + fold_constants(ctx, isa)?; + + Ok(()) +} + +/// Fold constants +pub fn fold_constants<'a, FOI>(ctx: &mut Context, fisa: FOI) -> CodegenResult<()> +where + FOI: Into>, +{ + constant_folding::fold_constants(&mut ctx.func); + ctx.verify_if(fisa) +} From 6d91dd63dfc9f91e55a7b71af5ef4b83e8b2c4a2 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Thu, 18 Oct 2018 20:11:19 -0400 Subject: [PATCH 10/16] Save spot commit --- filetests/{folding => optimize}/branch.clif | 3 +- .../{folding => optimize}/numerical.clif | 3 +- lib/filetests/src/lib.rs | 4 +- .../src/{test_folding.rs => test_optimize.rs} | 19 ++-- lib/preopt/src/constant_folding.rs | 103 +++++++++++------- lib/preopt/src/dce.rs | 72 ++++++++++++ lib/preopt/src/lib.rs | 27 ++++- 7 files changed, 180 insertions(+), 51 deletions(-) rename filetests/{folding => optimize}/branch.clif (97%) rename filetests/{folding => optimize}/numerical.clif (96%) rename lib/filetests/src/{test_folding.rs => test_optimize.rs} (77%) create mode 100644 lib/preopt/src/dce.rs diff --git a/filetests/folding/branch.clif b/filetests/optimize/branch.clif similarity index 97% rename from filetests/folding/branch.clif rename to filetests/optimize/branch.clif index 4133a4eb5..c1275ed53 100644 --- a/filetests/folding/branch.clif +++ b/filetests/optimize/branch.clif @@ -1,4 +1,5 @@ -test folding +test optimize +target x86_64 function %brz_fold() -> i32 { ebb0: diff --git a/filetests/folding/numerical.clif b/filetests/optimize/numerical.clif similarity index 96% rename from filetests/folding/numerical.clif rename to filetests/optimize/numerical.clif index f34ad38e3..6c4f1dc9d 100644 --- a/filetests/folding/numerical.clif +++ b/filetests/optimize/numerical.clif @@ -1,4 +1,5 @@ -test folding +test optimize +target x86_64 function %iadd_fold() -> i32 { ebb0: diff --git a/lib/filetests/src/lib.rs b/lib/filetests/src/lib.rs index f64e603e9..429aa87b7 100644 --- a/lib/filetests/src/lib.rs +++ b/lib/filetests/src/lib.rs @@ -52,9 +52,9 @@ mod test_cat; mod test_compile; mod test_dce; mod test_domtree; -mod test_folding; mod test_legalizer; mod test_licm; +mod test_optimize; mod test_postopt; mod test_preopt; mod test_print_cfg; @@ -128,7 +128,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult test_shrink::subtest(parsed), "simple-gvn" => test_simple_gvn::subtest(parsed), "verifier" => test_verifier::subtest(parsed), - "folding" => test_folding::subtest(parsed), + "optimize" => test_optimize::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/lib/filetests/src/test_folding.rs b/lib/filetests/src/test_optimize.rs similarity index 77% rename from lib/filetests/src/test_folding.rs rename to lib/filetests/src/test_optimize.rs index 0129de551..ff7bc0a16 100644 --- a/lib/filetests/src/test_folding.rs +++ b/lib/filetests/src/test_optimize.rs @@ -8,35 +8,40 @@ use cranelift_codegen; use cranelift_codegen::ir::Function; use cranelift_codegen::print_errors::pretty_error; -use cranelift_preopt::fold_constants; +use cranelift_preopt::optimize; use cranelift_reader::TestCommand; use std::borrow::Cow; use subtest::{run_filecheck, Context, SubTest, SubtestResult}; -struct TestFolding; +struct TestOptimize; pub fn subtest(parsed: &TestCommand) -> SubtestResult> { - assert_eq!(parsed.command, "folding"); + assert_eq!(parsed.command, "optimize"); if !parsed.options.is_empty() { Err(format!("No options allowed on {}", parsed)) } else { - Ok(Box::new(TestFolding)) + Ok(Box::new(TestOptimize)) } } -impl SubTest for TestFolding { +impl SubTest for TestOptimize { fn name(&self) -> &'static str { - "folding" + "optimize" } fn is_mutating(&self) -> bool { true } + fn needs_isa(&self) -> bool { + true + } + fn run(&self, func: Cow, context: &Context) -> SubtestResult<()> { + let isa = context.isa.expect("compile needs an ISA"); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); - fold_constants(&mut comp_ctx, context.flags_or_isa()) + optimize(&mut comp_ctx, isa) .map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; let text = comp_ctx.func.display(context.isa).to_string(); diff --git a/lib/preopt/src/constant_folding.rs b/lib/preopt/src/constant_folding.rs index 3e81b53e6..ed9741606 100644 --- a/lib/preopt/src/constant_folding.rs +++ b/lib/preopt/src/constant_folding.rs @@ -51,11 +51,11 @@ pub fn fold_constants(func: &mut ir::Function) { fold_numerical_binary(&mut pos.func.dfg, inst, opcode, args); } Branch { - opcode: _, + opcode, args: _, destination: _, } => { - fold_simple_branch(&mut pos.func, inst); + fold_simple_branch(&mut pos.func, inst, opcode); } _ => {} } @@ -205,51 +205,76 @@ fn fold_numerical_binary( } } -// fn evaulate_simple_branch(dfg: &ir::DataFlowGraph, opcode: ir::Opcode, args: &ir::ValueList) -> Option { - -// } - -// fn fold_simple_branch2(dfg: &mut ir::DataFlowGraph, inst: ir::Inst, opcode: ir::Opcode) {} - -/// Collapse a simple branch instructions. -fn fold_simple_branch(func: &mut ir::Function, inst: ir::Inst) { - let (inst_opcode, cond, ebb, args) = { +fn fold_simple_branch(func: &mut ir::Function, inst: ir::Inst, opcode: ir::Opcode) { + let (cond, ebb, args) = { let values = func.dfg.inst_args(inst); let inst_data = &func.dfg[inst]; - assert!(values.len() >= 1); ( - inst_data.opcode(), - func.dfg.resolve_aliases(values[0]), + resolve_value_to_imm(&func.dfg, values[0]).unwrap(), inst_data.branch_destination().unwrap(), values[1..].to_vec(), ) }; - if let ir::ValueDef::Result(cond_inst, _) = func.dfg.value_def(cond) { - match func.dfg[cond_inst] { - ir::InstructionData::UnaryBool { - opcode: ir::Opcode::Bconst, - imm, - } => { - let branch_if_zero = match inst_opcode { - ir::Opcode::Brz => true, - ir::Opcode::Brnz => false, - _ => panic!("Invalid state found"), - }; - - if (branch_if_zero && !imm) || (!branch_if_zero && imm) { - func.dfg.replace(inst).jump(ebb, &args); - // remove the rest of the ebb to avoid verifier errors - while let Some(next_inst) = func.layout.next_inst(inst) { - func.layout.remove_inst(next_inst); - } - } else { - // we can't remove the current instruction, so replace - // it with a `nop`. DCE will get rid of it for us. - func.dfg.replace(inst).nop(); - } - } - _ => return, + let truthiness = cond.evaluate_truthiness(); + let branch_if_zero = match opcode { + ir::Opcode::Brz => true, + ir::Opcode::Brnz => false, + _ => unreachable!(), + }; + + if (branch_if_zero && !truthiness) || (!branch_if_zero && truthiness) { + func.dfg.replace(inst).jump(ebb, &args); + // remove the rest of the ebb to avoid verifier errors + while let Some(next_inst) = func.layout.next_inst(inst) { + func.layout.remove_inst(next_inst); } + } else { + // we can't remove the current instruction, so replace + // it with a `nop`. DCE will get rid of it for us. + func.dfg.replace(inst).nop(); } } + +// /// Collapse a simple branch instructions. +// fn fold_simple_branch(func: &mut ir::Function, inst: ir::Inst) { +// let (inst_opcode, cond, ebb, args) = { +// let values = func.dfg.inst_args(inst); +// let inst_data = &func.dfg[inst]; +// assert!(values.len() >= 1); +// ( +// inst_data.opcode(), +// func.dfg.resolve_aliases(values[0]), +// inst_data.branch_destination().unwrap(), +// values[1..].to_vec(), +// ) +// }; + +// if let ir::ValueDef::Result(cond_inst, _) = func.dfg.value_def(cond) { +// match func.dfg[cond_inst] { +// ir::InstructionData::UnaryBool { +// opcode: ir::Opcode::Bconst, +// imm, +// } => { +// let branch_if_zero = match inst_opcode { +// ir::Opcode::Brz => true, +// ir::Opcode::Brnz => false, +// _ => panic!("Invalid state found"), +// }; + +// if (branch_if_zero && !imm) || (!branch_if_zero && imm) { +// func.dfg.replace(inst).jump(ebb, &args); +// // remove the rest of the ebb to avoid verifier errors +// while let Some(next_inst) = func.layout.next_inst(inst) { +// func.layout.remove_inst(next_inst); +// } +// } else { +// // we can't remove the current instruction, so replace +// // it with a `nop`. DCE will get rid of it for us. +// func.dfg.replace(inst).nop(); +// } +// } +// _ => return, +// } +// } +// } diff --git a/lib/preopt/src/dce.rs b/lib/preopt/src/dce.rs new file mode 100644 index 000000000..a55600486 --- /dev/null +++ b/lib/preopt/src/dce.rs @@ -0,0 +1,72 @@ +//! A Dead-Code Elimination (DCE) pass. +//! +//! Dead code here means instructions that have no side effects and have no +//! result values used by other instructions. + +use cranelift_codegen::{ + cursor::{Cursor, FuncCursor}, + dominator_tree::DominatorTree, + entity::EntityRef, + ir::instructions::InstructionData, + ir::{DataFlowGraph, Function, Inst, Opcode}, +}; +use std::vec::Vec; + +/// Test whether the given opcode is unsafe to even consider for DCE. +fn trivially_unsafe_for_dce(opcode: Opcode) -> bool { + opcode.is_call() + || opcode.is_branch() + || opcode.is_terminator() + || opcode.is_return() + || opcode.can_trap() + || opcode.other_side_effects() + || opcode.can_store() +} + +/// Preserve instructions with used result values. +fn any_inst_results_used(inst: Inst, live: &[bool], dfg: &DataFlowGraph) -> bool { + dfg.inst_results(inst).iter().any(|v| live[v.index()]) +} + +/// Load instructions without the `notrap` flag are defined to trap when +/// operating on inaccessible memory, so we can't DCE them even if the +/// loaded value is unused. +fn is_load_with_defined_trapping(opcode: Opcode, data: &InstructionData) -> bool { + if !opcode.can_load() { + return false; + } + match *data { + InstructionData::StackLoad { .. } => false, + InstructionData::Load { flags, .. } => !flags.notrap(), + _ => true, + } +} + +/// Perform DCE on `func`. +pub fn do_dce(func: &mut Function, domtree: &mut DominatorTree) { + debug_assert!(domtree.is_valid()); + + let mut live = Vec::with_capacity(func.dfg.num_values()); + live.resize(func.dfg.num_values(), false); + + for &ebb in domtree.cfg_postorder() { + let mut pos = FuncCursor::new(func).at_bottom(ebb); + while let Some(inst) = pos.prev_inst() { + { + let data = &pos.func.dfg[inst]; + let opcode = data.opcode(); + if trivially_unsafe_for_dce(opcode) + || is_load_with_defined_trapping(opcode, &data) + || any_inst_results_used(inst, &live, &pos.func.dfg) + { + for arg in pos.func.dfg.inst_args(inst) { + let v = pos.func.dfg.resolve_aliases(*arg); + live[v.index()] = true; + } + continue; + } + } + pos.remove_inst(); + } + } +} diff --git a/lib/preopt/src/lib.rs b/lib/preopt/src/lib.rs index 279ffd81e..4f4c91e33 100644 --- a/lib/preopt/src/lib.rs +++ b/lib/preopt/src/lib.rs @@ -29,11 +29,16 @@ ) )] #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), alloc)] + +#[cfg(not(feature = "std"))] +extern crate alloc; extern crate cranelift_codegen; extern crate rustc_apfloat; mod constant_folding; +mod dce; use cranelift_codegen::{isa::TargetIsa, settings::FlagsOrIsa, CodegenResult, Context}; @@ -46,6 +51,8 @@ pub fn optimize(ctx: &mut Context, isa: &TargetIsa) -> CodegenResult<()> { ctx.verify_if(isa)?; fold_constants(ctx, isa)?; + dce(ctx, isa)?; + Ok(()) } @@ -55,5 +62,23 @@ where FOI: Into>, { constant_folding::fold_constants(&mut ctx.func); - ctx.verify_if(fisa) + ctx.verify_if(fisa)?; + Ok(()) +} + +/// Perform dead-code elimination on the function. +pub fn dce<'a, FOI>(ctx: &mut Context, fisa: FOI) -> CodegenResult<()> +where + FOI: Into>, +{ + dce::do_dce(&mut ctx.func, &mut ctx.domtree); + ctx.verify_if(fisa)?; + Ok(()) +} + +/// This replaces `std` in builds with `core`. +#[cfg(not(feature = "std"))] +mod std { + pub use alloc::{boxed, slice, string, vec}; + pub use core::*; } From 055662aaa5f68d21c2c71a01f6dc6bc3fbef5747 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Fri, 19 Oct 2018 13:10:24 -0400 Subject: [PATCH 11/16] Remove rustc_apfloats for now --- lib/preopt/Cargo.toml | 4 +- lib/preopt/src/constant_folding.rs | 59 ++++++++++++------------------ lib/preopt/src/lib.rs | 2 +- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/lib/preopt/Cargo.toml b/lib/preopt/Cargo.toml index c7d1d5b02..673ec425d 100644 --- a/lib/preopt/Cargo.toml +++ b/lib/preopt/Cargo.toml @@ -12,11 +12,11 @@ keywords = ["optimize", "compile", "compiler", "jit"] [dependencies] cranelift-codegen = { path = "../codegen", version = "0.22.0", default-features = false } -rustc_apfloat = { version = "0.1.2", default-features = false } +# rustc_apfloat = { version = "0.1.2", default-features = false } [features] default = ["std"] -std = ["cranelift-codegen/std", "rustc_apfloat/std"] +std = ["cranelift-codegen/std"] core = ["cranelift-codegen/core"] [badges] diff --git a/lib/preopt/src/constant_folding.rs b/lib/preopt/src/constant_folding.rs index ed9741606..6c2e27141 100644 --- a/lib/preopt/src/constant_folding.rs +++ b/lib/preopt/src/constant_folding.rs @@ -4,16 +4,16 @@ use cranelift_codegen::{ cursor::{Cursor, FuncCursor}, ir::{self, InstBuilder}, }; -use rustc_apfloat::{ - ieee::{Double, Single}, - Float, -}; +// use rustc_apfloat::{ +// ieee::{Double, Single}, +// Float, +// }; enum ConstImm { Bool(bool), I64(i64), - Ieee32(Single), - Ieee64(Double), + Ieee32(f32), + Ieee64(f64), } impl ConstImm { @@ -78,14 +78,16 @@ fn resolve_value_to_imm(dfg: &ir::DataFlowGraph, value: ir::Value) -> Option { - let ieee_f32 = Single::from_bits(imm.bits() as _); + // see https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits for caveats + let ieee_f32 = f32::from_bits(imm.bits()); Some(ConstImm::Ieee32(ieee_f32)) } UnaryIeee64 { opcode: F64const, imm, } => { - let ieee_f64 = Double::from_bits(imm.bits() as _); + // see https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits for caveats + let ieee_f64 = f64::from_bits(imm.bits()); Some(ConstImm::Ieee64(ieee_f64)) } UnaryBool { @@ -133,44 +135,32 @@ fn evaluate_numerical_binary( Some(ConstImm::I64((imm0 / imm1).0)) } ir::Opcode::Fadd => match (imm0, imm1) { - (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { - Some(ConstImm::Ieee32((imm0 + imm1).value)) - } - (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { - Some(ConstImm::Ieee64((imm0 + imm1).value)) - } + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => Some(ConstImm::Ieee32(imm0 + imm1)), + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => Some(ConstImm::Ieee64(imm0 + imm1)), _ => unreachable!(), }, ir::Opcode::Fsub => match (imm0, imm1) { - (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { - Some(ConstImm::Ieee32((imm0 - imm1).value)) - } - (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { - Some(ConstImm::Ieee64((imm0 - imm1).value)) - } + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => Some(ConstImm::Ieee32(imm0 - imm1)), + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => Some(ConstImm::Ieee64(imm0 - imm1)), _ => unreachable!(), }, ir::Opcode::Fmul => match (imm0, imm1) { - (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { - Some(ConstImm::Ieee32((imm0 * imm1).value)) - } - (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { - Some(ConstImm::Ieee64((imm0 * imm1).value)) - } + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => Some(ConstImm::Ieee32(imm0 * imm1)), + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => Some(ConstImm::Ieee64(imm0 * imm1)), _ => unreachable!(), }, ir::Opcode::Fdiv => match (imm0, imm1) { (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { - if imm1.is_zero() { + if imm1 == 0.0 { return None; } - Some(ConstImm::Ieee32((imm0 / imm1).value)) + Some(ConstImm::Ieee32(imm0 / imm1)) } (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { - if imm1.is_zero() { + if imm1 == 0.0 { return None; } - Some(ConstImm::Ieee64((imm0 / imm1).value)) + Some(ConstImm::Ieee64(imm0 / imm1)) } _ => unreachable!(), }, @@ -194,11 +184,11 @@ fn fold_numerical_binary( } Ieee32(imm) => { dfg.replace(inst) - .f32const(ir::immediates::Ieee32::with_bits(imm.to_bits() as u32)); + .f32const(ir::immediates::Ieee32::with_bits(imm.to_bits())); } Ieee64(imm) => { dfg.replace(inst) - .f64const(ir::immediates::Ieee64::with_bits(imm.to_bits() as u64)); + .f64const(ir::immediates::Ieee64::with_bits(imm.to_bits())); } _ => unreachable!(), } @@ -230,9 +220,8 @@ fn fold_simple_branch(func: &mut ir::Function, inst: ir::Inst, opcode: ir::Opcod func.layout.remove_inst(next_inst); } } else { - // we can't remove the current instruction, so replace - // it with a `nop`. DCE will get rid of it for us. - func.dfg.replace(inst).nop(); + let mut pos = FuncCursor::new(func).at_inst(inst); + pos.remove_inst_and_step_back(); } } diff --git a/lib/preopt/src/lib.rs b/lib/preopt/src/lib.rs index 4f4c91e33..cceb1114f 100644 --- a/lib/preopt/src/lib.rs +++ b/lib/preopt/src/lib.rs @@ -35,7 +35,7 @@ extern crate alloc; extern crate cranelift_codegen; -extern crate rustc_apfloat; +// extern crate rustc_apfloat; mod constant_folding; mod dce; From 072d8ff1359680039442c01eed08372898866c50 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Fri, 19 Oct 2018 15:45:32 -0400 Subject: [PATCH 12/16] Remove redundant dce pass --- lib/preopt/src/dce.rs | 72 ------------------------------------------- lib/preopt/src/lib.rs | 13 -------- 2 files changed, 85 deletions(-) delete mode 100644 lib/preopt/src/dce.rs diff --git a/lib/preopt/src/dce.rs b/lib/preopt/src/dce.rs deleted file mode 100644 index a55600486..000000000 --- a/lib/preopt/src/dce.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! A Dead-Code Elimination (DCE) pass. -//! -//! Dead code here means instructions that have no side effects and have no -//! result values used by other instructions. - -use cranelift_codegen::{ - cursor::{Cursor, FuncCursor}, - dominator_tree::DominatorTree, - entity::EntityRef, - ir::instructions::InstructionData, - ir::{DataFlowGraph, Function, Inst, Opcode}, -}; -use std::vec::Vec; - -/// Test whether the given opcode is unsafe to even consider for DCE. -fn trivially_unsafe_for_dce(opcode: Opcode) -> bool { - opcode.is_call() - || opcode.is_branch() - || opcode.is_terminator() - || opcode.is_return() - || opcode.can_trap() - || opcode.other_side_effects() - || opcode.can_store() -} - -/// Preserve instructions with used result values. -fn any_inst_results_used(inst: Inst, live: &[bool], dfg: &DataFlowGraph) -> bool { - dfg.inst_results(inst).iter().any(|v| live[v.index()]) -} - -/// Load instructions without the `notrap` flag are defined to trap when -/// operating on inaccessible memory, so we can't DCE them even if the -/// loaded value is unused. -fn is_load_with_defined_trapping(opcode: Opcode, data: &InstructionData) -> bool { - if !opcode.can_load() { - return false; - } - match *data { - InstructionData::StackLoad { .. } => false, - InstructionData::Load { flags, .. } => !flags.notrap(), - _ => true, - } -} - -/// Perform DCE on `func`. -pub fn do_dce(func: &mut Function, domtree: &mut DominatorTree) { - debug_assert!(domtree.is_valid()); - - let mut live = Vec::with_capacity(func.dfg.num_values()); - live.resize(func.dfg.num_values(), false); - - for &ebb in domtree.cfg_postorder() { - let mut pos = FuncCursor::new(func).at_bottom(ebb); - while let Some(inst) = pos.prev_inst() { - { - let data = &pos.func.dfg[inst]; - let opcode = data.opcode(); - if trivially_unsafe_for_dce(opcode) - || is_load_with_defined_trapping(opcode, &data) - || any_inst_results_used(inst, &live, &pos.func.dfg) - { - for arg in pos.func.dfg.inst_args(inst) { - let v = pos.func.dfg.resolve_aliases(*arg); - live[v.index()] = true; - } - continue; - } - } - pos.remove_inst(); - } - } -} diff --git a/lib/preopt/src/lib.rs b/lib/preopt/src/lib.rs index cceb1114f..bf2ffc1a8 100644 --- a/lib/preopt/src/lib.rs +++ b/lib/preopt/src/lib.rs @@ -38,7 +38,6 @@ extern crate cranelift_codegen; // extern crate rustc_apfloat; mod constant_folding; -mod dce; use cranelift_codegen::{isa::TargetIsa, settings::FlagsOrIsa, CodegenResult, Context}; @@ -51,8 +50,6 @@ pub fn optimize(ctx: &mut Context, isa: &TargetIsa) -> CodegenResult<()> { ctx.verify_if(isa)?; fold_constants(ctx, isa)?; - dce(ctx, isa)?; - Ok(()) } @@ -66,16 +63,6 @@ where Ok(()) } -/// Perform dead-code elimination on the function. -pub fn dce<'a, FOI>(ctx: &mut Context, fisa: FOI) -> CodegenResult<()> -where - FOI: Into>, -{ - dce::do_dce(&mut ctx.func, &mut ctx.domtree); - ctx.verify_if(fisa)?; - Ok(()) -} - /// This replaces `std` in builds with `core`. #[cfg(not(feature = "std"))] mod std { From 642e71645852ff86810a881d6091d2327fda2faa Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Mon, 29 Oct 2018 10:10:24 -0400 Subject: [PATCH 13/16] Fix more suggestions --- lib/codegen/src/timing.rs | 1 - lib/preopt/Cargo.toml | 5 +- lib/preopt/src/constant_folding.rs | 103 +++++++++-------------------- lib/preopt/src/lib.rs | 2 + 4 files changed, 36 insertions(+), 75 deletions(-) diff --git a/lib/codegen/src/timing.rs b/lib/codegen/src/timing.rs index 3bc528e33..52105b906 100644 --- a/lib/codegen/src/timing.rs +++ b/lib/codegen/src/timing.rs @@ -134,7 +134,6 @@ mod details { } /// Accumulated timing for all passes. - // #[derive(Default)] pub struct PassTimes { pass: [PassTime; NUM_PASSES], } diff --git a/lib/preopt/Cargo.toml b/lib/preopt/Cargo.toml index 673ec425d..d88ce942b 100644 --- a/lib/preopt/Cargo.toml +++ b/lib/preopt/Cargo.toml @@ -12,11 +12,14 @@ keywords = ["optimize", "compile", "compiler", "jit"] [dependencies] cranelift-codegen = { path = "../codegen", version = "0.22.0", default-features = false } +cranelift-entity = { path = "../entity", version = "0.22.0", default-features = false } +# This is commented out because it doesn't build on Rust 1.25.0, which +# cranelift currently supports. # rustc_apfloat = { version = "0.1.2", default-features = false } [features] default = ["std"] -std = ["cranelift-codegen/std"] +std = ["cranelift-codegen/std", "cranelift-entity/std"] core = ["cranelift-codegen/core"] [badges] diff --git a/lib/preopt/src/constant_folding.rs b/lib/preopt/src/constant_folding.rs index 6c2e27141..4ea33820e 100644 --- a/lib/preopt/src/constant_folding.rs +++ b/lib/preopt/src/constant_folding.rs @@ -12,7 +12,7 @@ use cranelift_codegen::{ enum ConstImm { Bool(bool), I64(i64), - Ieee32(f32), + Ieee32(f32), // Ieee32 and Ieee64 will be replaced with `Single` and `Double` from the rust_apfloat library eventually. Ieee64(f64), } @@ -48,14 +48,14 @@ pub fn fold_constants(func: &mut ir::Function) { use self::ir::InstructionData::*; match pos.func.dfg[inst] { Binary { opcode, args } => { - fold_numerical_binary(&mut pos.func.dfg, inst, opcode, args); + fold_binary(&mut pos.func.dfg, inst, opcode, args); } Branch { opcode, args: _, destination: _, } => { - fold_simple_branch(&mut pos.func, inst, opcode); + fold_branch(&mut pos, inst, opcode); } _ => {} } @@ -78,7 +78,7 @@ fn resolve_value_to_imm(dfg: &ir::DataFlowGraph, value: ir::Value) -> Option { - // see https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits for caveats + // See https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits for caveats. let ieee_f32 = f32::from_bits(imm.bits()); Some(ConstImm::Ieee32(ieee_f32)) } @@ -86,7 +86,7 @@ fn resolve_value_to_imm(dfg: &ir::DataFlowGraph, value: ir::Value) -> Option { - // see https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits for caveats + // See https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits for caveats. let ieee_f64 = f64::from_bits(imm.bits()); Some(ConstImm::Ieee64(ieee_f64)) } @@ -98,18 +98,13 @@ fn resolve_value_to_imm(dfg: &ir::DataFlowGraph, value: ir::Value) -> Option Option { use std::num::Wrapping; - let (imm0, imm1) = ( - resolve_value_to_imm(dfg, args[0])?, - resolve_value_to_imm(dfg, args[1])?, - ); - match opcode { ir::Opcode::Iadd => { let imm0 = Wrapping(imm0.unwrap_i64()); @@ -151,15 +146,9 @@ fn evaluate_numerical_binary( }, ir::Opcode::Fdiv => match (imm0, imm1) { (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { - if imm1 == 0.0 { - return None; - } Some(ConstImm::Ieee32(imm0 / imm1)) } (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { - if imm1 == 0.0 { - return None; - } Some(ConstImm::Ieee64(imm0 / imm1)) } _ => unreachable!(), @@ -169,13 +158,22 @@ fn evaluate_numerical_binary( } /// Fold a numerical binary function. -fn fold_numerical_binary( +fn fold_binary( dfg: &mut ir::DataFlowGraph, inst: ir::Inst, opcode: ir::Opcode, args: [ir::Value; 2], ) { - if let Some(const_imm) = evaluate_numerical_binary(dfg, opcode, args) { + let (imm0, imm1) = if let (Some(imm0), Some(imm1)) = ( + resolve_value_to_imm(dfg, args[0]), + resolve_value_to_imm(dfg, args[1]) + ) { + (imm0, imm1) + } else { + return + }; + + if let Some(const_imm) = evaluate_binary(opcode, imm0, imm1) { use self::ConstImm::*; match const_imm { I64(imm) => { @@ -190,17 +188,20 @@ fn fold_numerical_binary( dfg.replace(inst) .f64const(ir::immediates::Ieee64::with_bits(imm.to_bits())); } - _ => unreachable!(), + Bool(imm) => { + let typevar = dfg.ctrl_typevar(inst); + dfg.replace(inst).bconst(typevar, imm); + } } } } -fn fold_simple_branch(func: &mut ir::Function, inst: ir::Inst, opcode: ir::Opcode) { +fn fold_branch(pos: &mut FuncCursor, inst: ir::Inst, opcode: ir::Opcode) { let (cond, ebb, args) = { - let values = func.dfg.inst_args(inst); - let inst_data = &func.dfg[inst]; + let values = pos.func.dfg.inst_args(inst); + let inst_data = &pos.func.dfg[inst]; ( - resolve_value_to_imm(&func.dfg, values[0]).unwrap(), + resolve_value_to_imm(&pos.func.dfg, values[0]).unwrap(), inst_data.branch_destination().unwrap(), values[1..].to_vec(), ) @@ -214,56 +215,12 @@ fn fold_simple_branch(func: &mut ir::Function, inst: ir::Inst, opcode: ir::Opcod }; if (branch_if_zero && !truthiness) || (!branch_if_zero && truthiness) { - func.dfg.replace(inst).jump(ebb, &args); + pos.func.dfg.replace(inst).jump(ebb, &args); // remove the rest of the ebb to avoid verifier errors - while let Some(next_inst) = func.layout.next_inst(inst) { - func.layout.remove_inst(next_inst); + while let Some(next_inst) = pos.func.layout.next_inst(inst) { + pos.func.layout.remove_inst(next_inst); } } else { - let mut pos = FuncCursor::new(func).at_inst(inst); pos.remove_inst_and_step_back(); } } - -// /// Collapse a simple branch instructions. -// fn fold_simple_branch(func: &mut ir::Function, inst: ir::Inst) { -// let (inst_opcode, cond, ebb, args) = { -// let values = func.dfg.inst_args(inst); -// let inst_data = &func.dfg[inst]; -// assert!(values.len() >= 1); -// ( -// inst_data.opcode(), -// func.dfg.resolve_aliases(values[0]), -// inst_data.branch_destination().unwrap(), -// values[1..].to_vec(), -// ) -// }; - -// if let ir::ValueDef::Result(cond_inst, _) = func.dfg.value_def(cond) { -// match func.dfg[cond_inst] { -// ir::InstructionData::UnaryBool { -// opcode: ir::Opcode::Bconst, -// imm, -// } => { -// let branch_if_zero = match inst_opcode { -// ir::Opcode::Brz => true, -// ir::Opcode::Brnz => false, -// _ => panic!("Invalid state found"), -// }; - -// if (branch_if_zero && !imm) || (!branch_if_zero && imm) { -// func.dfg.replace(inst).jump(ebb, &args); -// // remove the rest of the ebb to avoid verifier errors -// while let Some(next_inst) = func.layout.next_inst(inst) { -// func.layout.remove_inst(next_inst); -// } -// } else { -// // we can't remove the current instruction, so replace -// // it with a `nop`. DCE will get rid of it for us. -// func.dfg.replace(inst).nop(); -// } -// } -// _ => return, -// } -// } -// } diff --git a/lib/preopt/src/lib.rs b/lib/preopt/src/lib.rs index bf2ffc1a8..75e9bf7a4 100644 --- a/lib/preopt/src/lib.rs +++ b/lib/preopt/src/lib.rs @@ -35,6 +35,8 @@ extern crate alloc; extern crate cranelift_codegen; +#[macro_use] +extern crate cranelift_entity; // extern crate rustc_apfloat; mod constant_folding; From a08e72684ed41f8fa015e8e1f634fa3cada8ee97 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Mon, 29 Oct 2018 10:23:06 -0400 Subject: [PATCH 14/16] Update to new preopt pass name --- filetests/{optimize => preopt}/branch.clif | 2 +- filetests/{optimize => preopt}/numerical.clif | 2 +- .../div_by_const_indirect.clif | 2 +- .../div_by_const_non_power_of_2.clif | 2 +- .../div_by_const_power_of_2.clif | 2 +- .../rem_by_const_non_power_of_2.clif | 2 +- .../rem_by_const_power_of_2.clif | 2 +- .../{preopt => simple_preopt}/simplify.clif | 2 +- lib/filetests/src/lib.rs | 6 ++-- lib/filetests/src/test_preopt.rs | 18 ++++++++---- ...test_optimize.rs => test_simple_preopt.rs} | 28 ++++++++----------- lib/preopt/src/constant_folding.rs | 18 ++++-------- lib/preopt/src/lib.rs | 2 -- 13 files changed, 39 insertions(+), 49 deletions(-) rename filetests/{optimize => preopt}/branch.clif (98%) rename filetests/{optimize => preopt}/numerical.clif (98%) rename filetests/{preopt => simple_preopt}/div_by_const_indirect.clif (98%) rename filetests/{preopt => simple_preopt}/div_by_const_non_power_of_2.clif (99%) rename filetests/{preopt => simple_preopt}/div_by_const_power_of_2.clif (99%) rename filetests/{preopt => simple_preopt}/rem_by_const_non_power_of_2.clif (99%) rename filetests/{preopt => simple_preopt}/rem_by_const_power_of_2.clif (99%) rename filetests/{preopt => simple_preopt}/simplify.clif (98%) rename lib/filetests/src/{test_optimize.rs => test_simple_preopt.rs} (58%) diff --git a/filetests/optimize/branch.clif b/filetests/preopt/branch.clif similarity index 98% rename from filetests/optimize/branch.clif rename to filetests/preopt/branch.clif index c1275ed53..2b696fa2e 100644 --- a/filetests/optimize/branch.clif +++ b/filetests/preopt/branch.clif @@ -1,4 +1,4 @@ -test optimize +test preopt target x86_64 function %brz_fold() -> i32 { diff --git a/filetests/optimize/numerical.clif b/filetests/preopt/numerical.clif similarity index 98% rename from filetests/optimize/numerical.clif rename to filetests/preopt/numerical.clif index 6c4f1dc9d..9e1dfdc19 100644 --- a/filetests/optimize/numerical.clif +++ b/filetests/preopt/numerical.clif @@ -1,4 +1,4 @@ -test optimize +test preopt target x86_64 function %iadd_fold() -> i32 { diff --git a/filetests/preopt/div_by_const_indirect.clif b/filetests/simple_preopt/div_by_const_indirect.clif similarity index 98% rename from filetests/preopt/div_by_const_indirect.clif rename to filetests/simple_preopt/div_by_const_indirect.clif index 5d3266d26..fa66337fc 100644 --- a/filetests/preopt/div_by_const_indirect.clif +++ b/filetests/simple_preopt/div_by_const_indirect.clif @@ -1,4 +1,4 @@ -test preopt +test simple_preopt target i686 baseline ; Cases where the denominator is created by an iconst diff --git a/filetests/preopt/div_by_const_non_power_of_2.clif b/filetests/simple_preopt/div_by_const_non_power_of_2.clif similarity index 99% rename from filetests/preopt/div_by_const_non_power_of_2.clif rename to filetests/simple_preopt/div_by_const_non_power_of_2.clif index 1d05d4a53..fa0ac41bf 100644 --- a/filetests/preopt/div_by_const_non_power_of_2.clif +++ b/filetests/simple_preopt/div_by_const_non_power_of_2.clif @@ -1,4 +1,4 @@ -test preopt +test simple_preopt target i686 baseline ; -------- U32 -------- diff --git a/filetests/preopt/div_by_const_power_of_2.clif b/filetests/simple_preopt/div_by_const_power_of_2.clif similarity index 99% rename from filetests/preopt/div_by_const_power_of_2.clif rename to filetests/simple_preopt/div_by_const_power_of_2.clif index a047107c2..5a959750b 100644 --- a/filetests/preopt/div_by_const_power_of_2.clif +++ b/filetests/simple_preopt/div_by_const_power_of_2.clif @@ -1,4 +1,4 @@ -test preopt +test simple_preopt target i686 baseline ; -------- U32 -------- diff --git a/filetests/preopt/rem_by_const_non_power_of_2.clif b/filetests/simple_preopt/rem_by_const_non_power_of_2.clif similarity index 99% rename from filetests/preopt/rem_by_const_non_power_of_2.clif rename to filetests/simple_preopt/rem_by_const_non_power_of_2.clif index f440aa4a6..40c5e1d82 100644 --- a/filetests/preopt/rem_by_const_non_power_of_2.clif +++ b/filetests/simple_preopt/rem_by_const_non_power_of_2.clif @@ -1,4 +1,4 @@ -test preopt +test simple_preopt target i686 baseline ; -------- U32 -------- diff --git a/filetests/preopt/rem_by_const_power_of_2.clif b/filetests/simple_preopt/rem_by_const_power_of_2.clif similarity index 99% rename from filetests/preopt/rem_by_const_power_of_2.clif rename to filetests/simple_preopt/rem_by_const_power_of_2.clif index 70bd1bbd4..09eebfa68 100644 --- a/filetests/preopt/rem_by_const_power_of_2.clif +++ b/filetests/simple_preopt/rem_by_const_power_of_2.clif @@ -1,4 +1,4 @@ -test preopt +test simple_preopt target i686 baseline ; -------- U32 -------- diff --git a/filetests/preopt/simplify.clif b/filetests/simple_preopt/simplify.clif similarity index 98% rename from filetests/preopt/simplify.clif rename to filetests/simple_preopt/simplify.clif index 4426b9818..3df0f3355 100644 --- a/filetests/preopt/simplify.clif +++ b/filetests/simple_preopt/simplify.clif @@ -1,4 +1,4 @@ -test preopt +test simple_preopt target i686 function %iadd_imm(i32) -> i32 { diff --git a/lib/filetests/src/lib.rs b/lib/filetests/src/lib.rs index 429aa87b7..58378a148 100644 --- a/lib/filetests/src/lib.rs +++ b/lib/filetests/src/lib.rs @@ -54,13 +54,13 @@ mod test_dce; mod test_domtree; mod test_legalizer; mod test_licm; -mod test_optimize; mod test_postopt; mod test_preopt; mod test_print_cfg; mod test_regalloc; mod test_shrink; mod test_simple_gvn; +mod test_simple_preopt; mod test_verifier; /// The result of running the test in a file. @@ -122,13 +122,13 @@ fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult test_legalizer::subtest(parsed), "licm" => test_licm::subtest(parsed), "postopt" => test_postopt::subtest(parsed), - "preopt" => test_preopt::subtest(parsed), + "simple_preopt" => test_simple_preopt::subtest(parsed), "print-cfg" => test_print_cfg::subtest(parsed), "regalloc" => test_regalloc::subtest(parsed), "shrink" => test_shrink::subtest(parsed), "simple-gvn" => test_simple_gvn::subtest(parsed), "verifier" => test_verifier::subtest(parsed), - "optimize" => test_optimize::subtest(parsed), + "preopt" => test_preopt::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/lib/filetests/src/test_preopt.rs b/lib/filetests/src/test_preopt.rs index 86f8f87de..43566bdfa 100644 --- a/lib/filetests/src/test_preopt.rs +++ b/lib/filetests/src/test_preopt.rs @@ -1,10 +1,14 @@ -//! Test command for testing the preopt pass. +//! Test command for testing the constant folding pass. +//! +//! The `dce` test command runs each function through the constant folding pass after ensuring +//! that all instructions are legal for the target. //! //! The resulting function is sent to `filecheck`. use cranelift_codegen; use cranelift_codegen::ir::Function; use cranelift_codegen::print_errors::pretty_error; +use cranelift_preopt::optimize; use cranelift_reader::TestCommand; use std::borrow::Cow; use subtest::{run_filecheck, Context, SubTest, SubtestResult}; @@ -29,16 +33,18 @@ impl SubTest for TestPreopt { true } + fn needs_isa(&self) -> bool { + true + } + fn run(&self, func: Cow, context: &Context) -> SubtestResult<()> { + let isa = context.isa.expect("compile needs an ISA"); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); - let isa = context.isa.expect("preopt needs an ISA"); - comp_ctx.flowgraph(); - comp_ctx - .preopt(isa) + optimize(&mut comp_ctx, isa) .map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; - let text = &comp_ctx.func.display(isa).to_string(); + let text = comp_ctx.func.display(context.isa).to_string(); run_filecheck(&text, context) } } diff --git a/lib/filetests/src/test_optimize.rs b/lib/filetests/src/test_simple_preopt.rs similarity index 58% rename from lib/filetests/src/test_optimize.rs rename to lib/filetests/src/test_simple_preopt.rs index ff7bc0a16..803aa944e 100644 --- a/lib/filetests/src/test_optimize.rs +++ b/lib/filetests/src/test_simple_preopt.rs @@ -1,50 +1,44 @@ -//! Test command for testing the constant folding pass. -//! -//! The `dce` test command runs each function through the constant folding pass after ensuring -//! that all instructions are legal for the target. +//! Test command for testing the preopt pass. //! //! The resulting function is sent to `filecheck`. use cranelift_codegen; use cranelift_codegen::ir::Function; use cranelift_codegen::print_errors::pretty_error; -use cranelift_preopt::optimize; use cranelift_reader::TestCommand; use std::borrow::Cow; use subtest::{run_filecheck, Context, SubTest, SubtestResult}; -struct TestOptimize; +struct TestSimplePreopt; pub fn subtest(parsed: &TestCommand) -> SubtestResult> { - assert_eq!(parsed.command, "optimize"); + assert_eq!(parsed.command, "simple_preopt"); if !parsed.options.is_empty() { Err(format!("No options allowed on {}", parsed)) } else { - Ok(Box::new(TestOptimize)) + Ok(Box::new(TestSimplePreopt)) } } -impl SubTest for TestOptimize { +impl SubTest for TestSimplePreopt { fn name(&self) -> &'static str { - "optimize" + "simple_preopt" } fn is_mutating(&self) -> bool { true } - fn needs_isa(&self) -> bool { - true - } - fn run(&self, func: Cow, context: &Context) -> SubtestResult<()> { - let isa = context.isa.expect("compile needs an ISA"); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); + let isa = context.isa.expect("preopt needs an ISA"); - optimize(&mut comp_ctx, isa) + comp_ctx.flowgraph(); + comp_ctx + .preopt(isa) .map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; - let text = comp_ctx.func.display(context.isa).to_string(); + let text = &comp_ctx.func.display(isa).to_string(); run_filecheck(&text, context) } } diff --git a/lib/preopt/src/constant_folding.rs b/lib/preopt/src/constant_folding.rs index 4ea33820e..a54e371a4 100644 --- a/lib/preopt/src/constant_folding.rs +++ b/lib/preopt/src/constant_folding.rs @@ -98,11 +98,7 @@ fn resolve_value_to_imm(dfg: &ir::DataFlowGraph, value: ir::Value) -> Option Option { +fn evaluate_binary(opcode: ir::Opcode, imm0: ConstImm, imm1: ConstImm) -> Option { use std::num::Wrapping; match opcode { @@ -145,12 +141,8 @@ fn evaluate_binary( _ => unreachable!(), }, ir::Opcode::Fdiv => match (imm0, imm1) { - (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => { - Some(ConstImm::Ieee32(imm0 / imm1)) - } - (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => { - Some(ConstImm::Ieee64(imm0 / imm1)) - } + (ConstImm::Ieee32(imm0), ConstImm::Ieee32(imm1)) => Some(ConstImm::Ieee32(imm0 / imm1)), + (ConstImm::Ieee64(imm0), ConstImm::Ieee64(imm1)) => Some(ConstImm::Ieee64(imm0 / imm1)), _ => unreachable!(), }, _ => None, @@ -166,11 +158,11 @@ fn fold_binary( ) { let (imm0, imm1) = if let (Some(imm0), Some(imm1)) = ( resolve_value_to_imm(dfg, args[0]), - resolve_value_to_imm(dfg, args[1]) + resolve_value_to_imm(dfg, args[1]), ) { (imm0, imm1) } else { - return + return; }; if let Some(const_imm) = evaluate_binary(opcode, imm0, imm1) { diff --git a/lib/preopt/src/lib.rs b/lib/preopt/src/lib.rs index 75e9bf7a4..bf2ffc1a8 100644 --- a/lib/preopt/src/lib.rs +++ b/lib/preopt/src/lib.rs @@ -35,8 +35,6 @@ extern crate alloc; extern crate cranelift_codegen; -#[macro_use] -extern crate cranelift_entity; // extern crate rustc_apfloat; mod constant_folding; From 6afb3bb2a5b417e63dae4aed3348ab9f816eb270 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Mon, 29 Oct 2018 10:40:29 -0400 Subject: [PATCH 15/16] Add unary folding pass --- lib/preopt/src/constant_folding.rs | 77 ++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/lib/preopt/src/constant_folding.rs b/lib/preopt/src/constant_folding.rs index a54e371a4..a9c52ee64 100644 --- a/lib/preopt/src/constant_folding.rs +++ b/lib/preopt/src/constant_folding.rs @@ -50,6 +50,9 @@ pub fn fold_constants(func: &mut ir::Function) { Binary { opcode, args } => { fold_binary(&mut pos.func.dfg, inst, opcode, args); } + Unary { opcode, arg } => { + fold_unary(&mut pos.func.dfg, inst, opcode, arg); + } Branch { opcode, args: _, @@ -149,7 +152,45 @@ fn evaluate_binary(opcode: ir::Opcode, imm0: ConstImm, imm1: ConstImm) -> Option } } -/// Fold a numerical binary function. +fn evaluate_unary(opcode: ir::Opcode, imm: ConstImm) -> Option { + match opcode { + ir::Opcode::Fneg => match imm { + ConstImm::Ieee32(imm) => Some(ConstImm::Ieee32(-imm)), + ConstImm::Ieee64(imm) => Some(ConstImm::Ieee64(-imm)), + _ => unreachable!(), + } + ir::Opcode::Fabs => match imm { + ConstImm::Ieee32(imm) => Some(ConstImm::Ieee32(imm.abs())), + ConstImm::Ieee64(imm) => Some(ConstImm::Ieee64(imm.abs())), + _ => unreachable!(), + } + _ => None, + } +} + +fn replace_inst(dfg: &mut ir::DataFlowGraph, inst: ir::Inst, const_imm: ConstImm) { + use self::ConstImm::*; + match const_imm { + I64(imm) => { + let typevar = dfg.ctrl_typevar(inst); + dfg.replace(inst).iconst(typevar, imm); + } + Ieee32(imm) => { + dfg.replace(inst) + .f32const(ir::immediates::Ieee32::with_bits(imm.to_bits())); + } + Ieee64(imm) => { + dfg.replace(inst) + .f64const(ir::immediates::Ieee64::with_bits(imm.to_bits())); + } + Bool(imm) => { + let typevar = dfg.ctrl_typevar(inst); + dfg.replace(inst).bconst(typevar, imm); + } + } +} + +/// Fold a binary instruction. fn fold_binary( dfg: &mut ir::DataFlowGraph, inst: ir::Inst, @@ -166,25 +207,21 @@ fn fold_binary( }; if let Some(const_imm) = evaluate_binary(opcode, imm0, imm1) { - use self::ConstImm::*; - match const_imm { - I64(imm) => { - let typevar = dfg.ctrl_typevar(inst); - dfg.replace(inst).iconst(typevar, imm); - } - Ieee32(imm) => { - dfg.replace(inst) - .f32const(ir::immediates::Ieee32::with_bits(imm.to_bits())); - } - Ieee64(imm) => { - dfg.replace(inst) - .f64const(ir::immediates::Ieee64::with_bits(imm.to_bits())); - } - Bool(imm) => { - let typevar = dfg.ctrl_typevar(inst); - dfg.replace(inst).bconst(typevar, imm); - } - } + replace_inst(dfg, inst, const_imm); + } +} + +/// Fold a unary instruction. +fn fold_unary( + dfg: &mut ir::DataFlowGraph, + inst: ir::Inst, + opcode: ir::Opcode, + arg: ir::Value, +) { + let imm = if let Some(imm) = resolve_value_to_imm(dfg, arg) { imm } else { return }; + + if let Some(const_imm) = evaluate_unary(opcode, imm) { + replace_inst(dfg, inst, const_imm); } } From 03c0f30bc4d2a0b722e0951332311330c9447a67 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Mon, 29 Oct 2018 10:48:52 -0400 Subject: [PATCH 16/16] Formatting fixes --- filetests/preopt/numerical.clif | 2 +- lib/preopt/src/constant_folding.rs | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/filetests/preopt/numerical.clif b/filetests/preopt/numerical.clif index 9e1dfdc19..27fdaec0f 100644 --- a/filetests/preopt/numerical.clif +++ b/filetests/preopt/numerical.clif @@ -33,4 +33,4 @@ ebb0: ; nextln: v1 = iconst.i32 1 ; nextln: v2 = iconst.i32 41 ; nextln: return v2 -; nextln: } +; nextln: } \ No newline at end of file diff --git a/lib/preopt/src/constant_folding.rs b/lib/preopt/src/constant_folding.rs index a9c52ee64..1fb5d116e 100644 --- a/lib/preopt/src/constant_folding.rs +++ b/lib/preopt/src/constant_folding.rs @@ -158,12 +158,12 @@ fn evaluate_unary(opcode: ir::Opcode, imm: ConstImm) -> Option { ConstImm::Ieee32(imm) => Some(ConstImm::Ieee32(-imm)), ConstImm::Ieee64(imm) => Some(ConstImm::Ieee64(-imm)), _ => unreachable!(), - } + }, ir::Opcode::Fabs => match imm { ConstImm::Ieee32(imm) => Some(ConstImm::Ieee32(imm.abs())), ConstImm::Ieee64(imm) => Some(ConstImm::Ieee64(imm.abs())), _ => unreachable!(), - } + }, _ => None, } } @@ -212,13 +212,12 @@ fn fold_binary( } /// Fold a unary instruction. -fn fold_unary( - dfg: &mut ir::DataFlowGraph, - inst: ir::Inst, - opcode: ir::Opcode, - arg: ir::Value, -) { - let imm = if let Some(imm) = resolve_value_to_imm(dfg, arg) { imm } else { return }; +fn fold_unary(dfg: &mut ir::DataFlowGraph, inst: ir::Inst, opcode: ir::Opcode, arg: ir::Value) { + let imm = if let Some(imm) = resolve_value_to_imm(dfg, arg) { + imm + } else { + return; + }; if let Some(const_imm) = evaluate_unary(opcode, imm) { replace_inst(dfg, inst, const_imm);