diff --git a/docs/testing.rst b/docs/testing.rst index f58a413033..d4aa1a881c 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -344,3 +344,11 @@ sequences. Instead the test will fail. Value locations must be present if they are required to compute the binary bits. Missing value locations will cause the test to crash. + +`test simple-gvn` +----------------- + +Test the simple GVN pass. + +The simple GVN pass is run on each function, and then results are run +through filecheck. diff --git a/filetests/simple_gvn/basic.cton b/filetests/simple_gvn/basic.cton new file mode 100644 index 0000000000..20544666fc --- /dev/null +++ b/filetests/simple_gvn/basic.cton @@ -0,0 +1,11 @@ +test simple-gvn + +function simple_redundancy(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = iadd v0, v1 + v3 = iadd v0, v1 +; check: v3 -> v2 + v4 = imul v2, v3 +; check: v4 = imul $v2, $v3 + return v4 +} diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 5777ed76ea..b4458ec585 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -201,7 +201,7 @@ load = Instruction( This is a polymorphic instruction that can load any value type which has a memory representation. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) store = Instruction( 'store', r""" @@ -210,7 +210,7 @@ store = Instruction( This is a polymorphic instruction that can store any value type with a memory representation. """, - ins=(Flags, x, p, Offset)) + ins=(Flags, x, p, Offset), can_store=True) iExt8 = TypeVar( 'iExt8', 'An integer type with more than 8 bits', @@ -224,7 +224,7 @@ uload8 = Instruction( This is equivalent to ``load.i8`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) sload8 = Instruction( 'sload8', r""" @@ -232,7 +232,7 @@ sload8 = Instruction( This is equivalent to ``load.i8`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) istore8 = Instruction( 'istore8', r""" @@ -240,7 +240,7 @@ istore8 = Instruction( This is equivalent to ``ireduce.i8`` followed by ``store.i8``. """, - ins=(Flags, x, p, Offset)) + ins=(Flags, x, p, Offset), can_store=True) iExt16 = TypeVar( 'iExt16', 'An integer type with more than 16 bits', @@ -254,7 +254,7 @@ uload16 = Instruction( This is equivalent to ``load.i16`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) sload16 = Instruction( 'sload16', r""" @@ -262,7 +262,7 @@ sload16 = Instruction( This is equivalent to ``load.i16`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) istore16 = Instruction( 'istore16', r""" @@ -270,7 +270,7 @@ istore16 = Instruction( This is equivalent to ``ireduce.i16`` followed by ``store.i8``. """, - ins=(Flags, x, p, Offset)) + ins=(Flags, x, p, Offset), can_store=True) iExt32 = TypeVar( 'iExt32', 'An integer type with more than 32 bits', @@ -284,7 +284,7 @@ uload32 = Instruction( This is equivalent to ``load.i32`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) sload32 = Instruction( 'sload32', r""" @@ -292,7 +292,7 @@ sload32 = Instruction( This is equivalent to ``load.i32`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) istore32 = Instruction( 'istore32', r""" @@ -300,7 +300,7 @@ istore32 = Instruction( This is equivalent to ``ireduce.i32`` followed by ``store.i8``. """, - ins=(Flags, x, p, Offset)) + ins=(Flags, x, p, Offset), can_store=True) x = Operand('x', Mem, doc='Value to be stored') a = Operand('a', Mem, doc='Value loaded') @@ -316,7 +316,7 @@ stack_load = Instruction( access cannot go out of bounds, i.e. :math:`sizeof(a) + Offset <= sizeof(SS)`. """, - ins=(SS, Offset), outs=a) + ins=(SS, Offset), outs=a, can_load=True) stack_store = Instruction( 'stack_store', r""" @@ -329,7 +329,7 @@ stack_store = Instruction( access cannot go out of bounds, i.e. :math:`sizeof(a) + Offset <= sizeof(SS)`. """, - ins=(x, SS, Offset)) + ins=(x, SS, Offset), can_store=True) stack_addr = Instruction( 'stack_addr', r""" @@ -357,7 +357,7 @@ heap_load = Instruction( Trap if the heap access would be out of bounds. """, - ins=(p, Offset), outs=a) + ins=(p, Offset), outs=a, can_load=True) heap_store = Instruction( 'heap_store', r""" @@ -365,7 +365,7 @@ heap_store = Instruction( Trap if the heap access would be out of bounds. """, - ins=(x, p, Offset)) + ins=(x, p, Offset), can_store=True) heap_addr = Instruction( 'heap_addr', r""" diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 6724b775ea..d8e9d24e5f 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -85,6 +85,8 @@ class Instruction(object): :param is_call: This is a call instruction. :param is_return: This is a return instruction. :param can_trap: This instruction can trap. + :param can_load: This instruction can load from memory. + :param can_store: This instruction can store to memory. """ # Boolean instruction attributes that can be passed as keyword arguments to @@ -95,6 +97,8 @@ class Instruction(object): 'is_branch': 'True for all branch or jump instructions.', 'is_call': 'Is this a call instruction?', 'is_return': 'Is this a return instruction?', + 'can_load': 'Can this instruction read from memory?', + 'can_store': 'Can this instruction write to memory?', 'can_trap': 'Can this instruction cause a trap?', } diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index f3a4343ab2..548cd45a29 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -228,7 +228,7 @@ def gen_opcodes(groups, fmt): fmt.doc_comment('An instruction opcode.') fmt.doc_comment('') fmt.doc_comment('All instructions from all supported ISAs are present.') - fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') + fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]') instrs = [] # We explicitly set the discriminant of the first variant to 1, which diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index d8b6af4d0b..ce4b269574 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -17,6 +17,7 @@ use legalize_function; use regalloc; use result::CtonResult; use verifier; +use simple_gvn::do_simple_gvn; /// Persistent data structures and compilation pipeline. pub struct Context { @@ -51,16 +52,16 @@ impl Context { /// /// Also check that the dominator tree and control flow graph are consistent with the function. /// - /// The `TargetIsa` argument is currently unused, but the verifier will soon be able to also + /// The `isa` argument is currently unused, but the verifier will soon be able to also /// check ISA-dependent constraints. - pub fn verify<'a, ISA: Into>>(&self, isa: ISA) -> verifier::Result { - verifier::verify_context(&self.func, &self.cfg, &self.domtree, isa.into()) + pub fn verify<'a>(&self, isa: Option<&TargetIsa>) -> verifier::Result { + verifier::verify_context(&self.func, &self.cfg, &self.domtree, isa) } /// Run the verifier only if the `enable_verifier` setting is true. pub fn verify_if(&self, isa: &TargetIsa) -> CtonResult { if isa.flags().enable_verifier() { - self.verify(isa).map_err(Into::into) + self.verify(Some(isa)).map_err(Into::into) } else { Ok(()) } @@ -78,6 +79,14 @@ impl Context { self.domtree.compute(&self.func, &self.cfg); } + /// Perform simple GVN on the function. + pub fn simple_gvn(&mut self) -> CtonResult { + do_simple_gvn(&mut self.func, &mut self.cfg); + // TODO: Factor things such that we can get a Flags and test + // enable_verifier(). + self.verify(None).map_err(Into::into) + } + /// Run the register allocator. pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult { self.regalloc diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index b6cf3099eb..5121d8e4cc 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -46,6 +46,7 @@ //! The index stored in an `EntityList` points to part 2, the list elements. The value 0 is //! reserved for the empty list which isn't allocated in the vector. +use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::mem; @@ -59,6 +60,10 @@ use entity_map::EntityRef; /// Entity lists can be cloned, but that operation should only be used as part of cloning the whole /// function they belong to. *Cloning an entity list does not allocate new memory for the clone*. /// It creates an alias of the same memory. +/// +/// Entity lists can also be hashed and compared for equality, but those operations just panic if, +/// they're ever actually called, because it's not possible to compare the contents of the list +/// without the pool reference. #[derive(Clone, Debug)] pub struct EntityList { index: u32, @@ -75,6 +80,19 @@ impl Default for EntityList { } } +impl Hash for EntityList { + fn hash(&self, _: &mut H) { + panic!("hash called on EntityList"); + } +} + +impl PartialEq for EntityList { + fn eq(&self, _: &EntityList) -> bool { + panic!("eq called on EntityList"); + } +} +impl Eq for EntityList {} + /// A memory pool for storing lists of `T`. #[derive(Clone)] pub struct ListPool { diff --git a/lib/cretonne/src/ir/condcodes.rs b/lib/cretonne/src/ir/condcodes.rs index 5117e39d9d..520014471d 100644 --- a/lib/cretonne/src/ir/condcodes.rs +++ b/lib/cretonne/src/ir/condcodes.rs @@ -27,7 +27,7 @@ pub trait CondCode: Copy { /// This condition code is used by the `icmp` instruction to compare integer values. There are /// separate codes for comparing the integers as signed or unsigned numbers where it makes a /// difference. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum IntCC { /// `==`. Equal, @@ -139,7 +139,7 @@ impl FromStr for IntCC { /// The condition codes described here are used to produce a single boolean value from the /// comparison. The 14 condition codes here cover every possible combination of the relation above /// except the impossible `!UN & !EQ & !LT & !GT` and the always true `UN | EQ | LT | GT`. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum FloatCC { /// EQ | LT | GT Ordered, diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index d5eb5ab73d..81c3142680 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -243,6 +243,46 @@ impl DataFlowGraph { self.values[dest] = ValueData::Alias { ty, original }; } + /// Replace the results of one instruction with aliases to the results of another. + /// + /// Change all the results of `dest_inst` to behave as aliases of + /// corresponding results of `src_inst`, as if calling change_to_alias for + /// each. + /// + /// After calling this instruction, `dest_inst` will have had its results + /// cleared, so it likely needs to be removed from the graph. + /// + pub fn replace_with_aliases(&mut self, dest_inst: Inst, src_inst: Inst) { + debug_assert_ne!(dest_inst, + src_inst, + "Replacing {} with itself would create a loop", + dest_inst); + debug_assert_eq!(self.results[dest_inst].len(&self.value_lists), + self.results[src_inst].len(&self.value_lists), + "Replacing {} with {} would produce a different number of results.", + dest_inst, + src_inst); + + for (&dest, &src) in self.results[dest_inst] + .as_slice(&self.value_lists) + .iter() + .zip(self.results[src_inst].as_slice(&self.value_lists)) { + let original = src; + let ty = self.value_type(original); + assert_eq!(self.value_type(dest), + ty, + "Aliasing {} to {} would change its type {} to {}", + dest, + src, + self.value_type(dest), + ty); + + self.values[dest] = ValueData::Alias { ty, original }; + } + + self.clear_results(dest_inst); + } + /// Create a new value alias. /// /// Note that this function should only be called by the parser. diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index e061b88482..44c4cd3fc3 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -14,7 +14,7 @@ use std::str::FromStr; /// /// An `Imm64` operand can also be used to represent immediate values of smaller integer types by /// sign-extending to `i64`. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Imm64(i64); impl Imm64 { @@ -153,7 +153,7 @@ pub type Uimm8 = u8; /// /// This is used to encode an immediate offset for load/store instructions. All supported ISAs have /// a maximum load/store offset that fits in an `i32`. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Offset32(i32); impl Offset32 { @@ -220,7 +220,7 @@ impl FromStr for Offset32 { /// 32-bit unsigned immediate offset. /// /// This is used to encode an immediate offset for WebAssembly heap_load/heap_store instructions. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Uoffset32(u32); impl Uoffset32 { @@ -282,17 +282,19 @@ impl FromStr for Uoffset32 { } } -/// An IEEE binary32 immediate floating point value. +/// An IEEE binary32 immediate floating point value, represented as a u32 +/// containing the bitpattern. /// /// All bit patterns are allowed. -#[derive(Copy, Clone, Debug)] -pub struct Ieee32(f32); +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Ieee32(u32); -/// An IEEE binary64 immediate floating point value. +/// An IEEE binary64 immediate floating point value, represented as a u64 +/// containing the bitpattern. /// /// All bit patterns are allowed. -#[derive(Copy, Clone, Debug)] -pub struct Ieee64(f64); +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Ieee64(u64); // Format a floating point number in a way that is reasonably human-readable, and that can be // converted back to binary without any rounding issues. The hexadecimal formatting of normal and @@ -531,18 +533,13 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result { impl Ieee32 { /// Create a new `Ieee32` representing the number `x`. pub fn new(x: f32) -> Ieee32 { - Ieee32(x) - } - - /// Construct `Ieee32` immediate from raw bits. - pub fn from_bits(x: u32) -> Ieee32 { Ieee32(unsafe { mem::transmute(x) }) } } impl Display for Ieee32 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let bits: u32 = unsafe { mem::transmute(self.0) }; + let bits: u32 = self.0; format_float(bits as u64, 8, 23, f) } } @@ -552,7 +549,7 @@ impl FromStr for Ieee32 { fn from_str(s: &str) -> Result { match parse_float(s, 8, 23) { - Ok(b) => Ok(Ieee32::from_bits(b as u32)), + Ok(b) => Ok(Ieee32(b as u32)), Err(s) => Err(s), } } @@ -561,18 +558,13 @@ impl FromStr for Ieee32 { impl Ieee64 { /// Create a new `Ieee64` representing the number `x`. pub fn new(x: f64) -> Ieee64 { - Ieee64(x) - } - - /// Construct `Ieee64` immediate from raw bits. - pub fn from_bits(x: u64) -> Ieee64 { Ieee64(unsafe { mem::transmute(x) }) } } impl Display for Ieee64 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let bits: u64 = unsafe { mem::transmute(self.0) }; + let bits: u64 = self.0; format_float(bits, 11, 52, f) } } @@ -582,7 +574,7 @@ impl FromStr for Ieee64 { fn from_str(s: &str) -> Result { match parse_float(s, 11, 52) { - Ok(b) => Ok(Ieee64::from_bits(b)), + Ok(b) => Ok(Ieee64(b)), Err(s) => Err(s), } } @@ -743,11 +735,11 @@ mod tests { assert_eq!(Ieee32::new(f32::NAN).to_string(), "+NaN"); assert_eq!(Ieee32::new(-f32::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(Ieee32::from_bits(0x7fc00001).to_string(), "+NaN:0x1"); - assert_eq!(Ieee32::from_bits(0x7ff00001).to_string(), "+NaN:0x300001"); + assert_eq!(Ieee32(0x7fc00001).to_string(), "+NaN:0x1"); + assert_eq!(Ieee32(0x7ff00001).to_string(), "+NaN:0x300001"); // Signaling NaNs. - assert_eq!(Ieee32::from_bits(0x7f800001).to_string(), "+sNaN:0x1"); - assert_eq!(Ieee32::from_bits(0x7fa00001).to_string(), "+sNaN:0x200001"); + assert_eq!(Ieee32(0x7f800001).to_string(), "+sNaN:0x1"); + assert_eq!(Ieee32(0x7fa00001).to_string(), "+sNaN:0x200001"); } #[test] @@ -845,14 +837,12 @@ mod tests { assert_eq!(Ieee64::new(f64::NAN).to_string(), "+NaN"); assert_eq!(Ieee64::new(-f64::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(Ieee64::from_bits(0x7ff8000000000001).to_string(), - "+NaN:0x1"); - assert_eq!(Ieee64::from_bits(0x7ffc000000000001).to_string(), + assert_eq!(Ieee64(0x7ff8000000000001).to_string(), "+NaN:0x1"); + assert_eq!(Ieee64(0x7ffc000000000001).to_string(), "+NaN:0x4000000000001"); // Signaling NaNs. - assert_eq!(Ieee64::from_bits(0x7ff0000000000001).to_string(), - "+sNaN:0x1"); - assert_eq!(Ieee64::from_bits(0x7ff4000000000001).to_string(), + assert_eq!(Ieee64(0x7ff0000000000001).to_string(), "+sNaN:0x1"); + assert_eq!(Ieee64(0x7ff4000000000001).to_string(), "+sNaN:0x4000000000001"); } diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 4301c7796c..66c6d039b0 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -98,7 +98,7 @@ impl FromStr for Opcode { /// value should have its `ty` field set to `VOID`. The size of `InstructionData` should be kept at /// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a /// `Box` to store the additional information out of line. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[allow(missing_docs)] pub enum InstructionData { Nullary { opcode: Opcode }, diff --git a/lib/cretonne/src/ir/memflags.rs b/lib/cretonne/src/ir/memflags.rs index bd2f2bab19..8d0694e1a0 100644 --- a/lib/cretonne/src/ir/memflags.rs +++ b/lib/cretonne/src/ir/memflags.rs @@ -14,7 +14,7 @@ const NAMES: [&str; 2] = ["notrap", "aligned"]; /// Each of these flags introduce a limited form of undefined behavior. The flags each enable /// certain optimizations that need to make additional assumptions. Generally, the semantics of a /// program does not change when a flag is removed, but adding a flag will. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct MemFlags { bits: u8, } diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 1f51b38d3f..264e808cbf 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -35,5 +35,6 @@ mod packed_option; mod partition_slice; mod predicates; mod ref_slice; +mod simple_gvn; mod topo_order; mod write; diff --git a/lib/cretonne/src/simple_gvn.rs b/lib/cretonne/src/simple_gvn.rs new file mode 100644 index 0000000000..cfd13128fe --- /dev/null +++ b/lib/cretonne/src/simple_gvn.rs @@ -0,0 +1,65 @@ +//! A simple GVN pass. + +use flowgraph::ControlFlowGraph; +use dominator_tree::DominatorTree; +use ir::{Cursor, InstructionData, Function, Inst, Opcode}; +use std::collections::HashMap; + +/// Test whether the given opcode is unsafe to even consider for GVN. +fn trivially_unsafe_for_gvn(opcode: Opcode) -> bool { + opcode.is_call() || opcode.is_branch() || opcode.is_terminator() || opcode.is_return() || + opcode.can_trap() +} + +/// Perform simple GVN on `func`. +/// +pub fn do_simple_gvn(func: &mut Function, cfg: &mut ControlFlowGraph) { + let mut visible_values: HashMap = HashMap::new(); + + let domtree = DominatorTree::with_function(func, &cfg); + + // Visit EBBs in a reverse post-order. + let mut postorder = cfg.postorder_ebbs(); + let mut pos = Cursor::new(&mut func.layout); + + while let Some(ebb) = postorder.pop() { + pos.goto_top(ebb); + + while let Some(inst) = pos.next_inst() { + let opcode = func.dfg[inst].opcode(); + + if trivially_unsafe_for_gvn(opcode) { + continue; + } + + // TODO: Implement simple redundant-load elimination. + if opcode.can_store() { + continue; + } + if opcode.can_load() { + continue; + } + + let key = func.dfg[inst].clone(); + let entry = visible_values.entry(key); + use std::collections::hash_map::Entry::*; + match entry { + Occupied(mut entry) => { + if domtree.dominates(*entry.get(), inst, &pos.layout) { + func.dfg.replace_with_aliases(inst, *entry.get()); + pos.remove_inst(); + } else { + // The prior instruction doesn't dominate inst, so it + // won't dominate any subsequent instructions we'll + // visit, so just replace it. + *entry.get_mut() = inst; + continue; + } + } + Vacant(entry) => { + entry.insert(inst); + } + } + } + } +} diff --git a/src/filetest/mod.rs b/src/filetest/mod.rs index 7a379ec0fe..961c3ff2f2 100644 --- a/src/filetest/mod.rs +++ b/src/filetest/mod.rs @@ -20,6 +20,7 @@ mod legalizer; mod regalloc; mod runner; mod runone; +mod simple_gvn; mod verifier; /// The result of running the test in a file. @@ -62,6 +63,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::Result> { "legalizer" => legalizer::subtest(parsed), "regalloc" => regalloc::subtest(parsed), "binemit" => binemit::subtest(parsed), + "simple-gvn" => simple_gvn::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/src/filetest/simple_gvn.rs b/src/filetest/simple_gvn.rs new file mode 100644 index 0000000000..f220cbdde7 --- /dev/null +++ b/src/filetest/simple_gvn.rs @@ -0,0 +1,51 @@ +//! Test command for testing the simple GVN pass. +//! +//! The `simple-gvn` test command runs each function through the simple GVN pass after ensuring +//! that all instructions are legal for the target. +//! +//! The resulting function is sent to `filecheck`. + +use cretonne::ir::Function; +use cretonne; +use cton_reader::TestCommand; +use filetest::subtest::{SubTest, Context, Result, run_filecheck}; +use std::borrow::Cow; +use std::fmt::Write; +use utils::pretty_error; + +struct TestSimpleGVN; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "simple-gvn"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestSimpleGVN)) + } +} + +impl SubTest for TestSimpleGVN { + fn name(&self) -> Cow { + Cow::from("simple-gvn") + } + + fn is_mutating(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + // Create a compilation context, and drop in the function. + let mut comp_ctx = cretonne::Context::new(); + comp_ctx.func = func.into_owned(); + + comp_ctx.flowgraph(); + comp_ctx + .simple_gvn() + .map_err(|e| pretty_error(&comp_ctx.func, e))?; + + let mut text = String::new(); + write!(&mut text, "{}", &comp_ctx.func) + .map_err(|e| e.to_string())?; + run_filecheck(&text, context) + } +}