Start a very simple GVN pass (#79)

* Skeleton simple_gvn pass.
* Basic testing infrastructure for simple-gvn.
* Add can_load and can_store flags to instructions.
* Move the replace_values function into the DataFlowGraph.
* Make InstructionData derive from Hash, PartialEq, and Eq.
* Make EntityList's hash and eq functions panic.
* Change Ieee32 and Ieee64 to store u32 and u64, respectively.
This commit is contained in:
Dan Gohman
2017-05-18 18:18:57 -07:00
committed by Jakob Stoklund Olesen
parent 0c7b2c7b68
commit dc809628f4
16 changed files with 256 additions and 57 deletions

View File

@@ -344,3 +344,11 @@ sequences. Instead the test will fail.
Value locations must be present if they are required to compute the binary Value locations must be present if they are required to compute the binary
bits. Missing value locations will cause the test to crash. 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.

View File

@@ -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
}

View File

@@ -201,7 +201,7 @@ load = Instruction(
This is a polymorphic instruction that can load any value type which This is a polymorphic instruction that can load any value type which
has a memory representation. has a memory representation.
""", """,
ins=(Flags, p, Offset), outs=a) ins=(Flags, p, Offset), outs=a, can_load=True)
store = Instruction( store = Instruction(
'store', r""" 'store', r"""
@@ -210,7 +210,7 @@ store = Instruction(
This is a polymorphic instruction that can store any value type with a This is a polymorphic instruction that can store any value type with a
memory representation. memory representation.
""", """,
ins=(Flags, x, p, Offset)) ins=(Flags, x, p, Offset), can_store=True)
iExt8 = TypeVar( iExt8 = TypeVar(
'iExt8', 'An integer type with more than 8 bits', 'iExt8', 'An integer type with more than 8 bits',
@@ -224,7 +224,7 @@ uload8 = Instruction(
This is equivalent to ``load.i8`` followed by ``uextend``. 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 = Instruction(
'sload8', r""" 'sload8', r"""
@@ -232,7 +232,7 @@ sload8 = Instruction(
This is equivalent to ``load.i8`` followed by ``uextend``. 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 = Instruction(
'istore8', r""" 'istore8', r"""
@@ -240,7 +240,7 @@ istore8 = Instruction(
This is equivalent to ``ireduce.i8`` followed by ``store.i8``. 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 = TypeVar(
'iExt16', 'An integer type with more than 16 bits', 'iExt16', 'An integer type with more than 16 bits',
@@ -254,7 +254,7 @@ uload16 = Instruction(
This is equivalent to ``load.i16`` followed by ``uextend``. 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 = Instruction(
'sload16', r""" 'sload16', r"""
@@ -262,7 +262,7 @@ sload16 = Instruction(
This is equivalent to ``load.i16`` followed by ``uextend``. 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 = Instruction(
'istore16', r""" 'istore16', r"""
@@ -270,7 +270,7 @@ istore16 = Instruction(
This is equivalent to ``ireduce.i16`` followed by ``store.i8``. 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 = TypeVar(
'iExt32', 'An integer type with more than 32 bits', 'iExt32', 'An integer type with more than 32 bits',
@@ -284,7 +284,7 @@ uload32 = Instruction(
This is equivalent to ``load.i32`` followed by ``uextend``. 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 = Instruction(
'sload32', r""" 'sload32', r"""
@@ -292,7 +292,7 @@ sload32 = Instruction(
This is equivalent to ``load.i32`` followed by ``uextend``. 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 = Instruction(
'istore32', r""" 'istore32', r"""
@@ -300,7 +300,7 @@ istore32 = Instruction(
This is equivalent to ``ireduce.i32`` followed by ``store.i8``. 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') x = Operand('x', Mem, doc='Value to be stored')
a = Operand('a', Mem, doc='Value loaded') a = Operand('a', Mem, doc='Value loaded')
@@ -316,7 +316,7 @@ stack_load = Instruction(
access cannot go out of bounds, i.e. access cannot go out of bounds, i.e.
:math:`sizeof(a) + Offset <= sizeof(SS)`. :math:`sizeof(a) + Offset <= sizeof(SS)`.
""", """,
ins=(SS, Offset), outs=a) ins=(SS, Offset), outs=a, can_load=True)
stack_store = Instruction( stack_store = Instruction(
'stack_store', r""" 'stack_store', r"""
@@ -329,7 +329,7 @@ stack_store = Instruction(
access cannot go out of bounds, i.e. access cannot go out of bounds, i.e.
:math:`sizeof(a) + Offset <= sizeof(SS)`. :math:`sizeof(a) + Offset <= sizeof(SS)`.
""", """,
ins=(x, SS, Offset)) ins=(x, SS, Offset), can_store=True)
stack_addr = Instruction( stack_addr = Instruction(
'stack_addr', r""" 'stack_addr', r"""
@@ -357,7 +357,7 @@ heap_load = Instruction(
Trap if the heap access would be out of bounds. 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 = Instruction(
'heap_store', r""" 'heap_store', r"""
@@ -365,7 +365,7 @@ heap_store = Instruction(
Trap if the heap access would be out of bounds. 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 = Instruction(
'heap_addr', r""" 'heap_addr', r"""

View File

@@ -85,6 +85,8 @@ class Instruction(object):
:param is_call: This is a call instruction. :param is_call: This is a call instruction.
:param is_return: This is a return instruction. :param is_return: This is a return instruction.
:param can_trap: This instruction can trap. :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 # 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_branch': 'True for all branch or jump instructions.',
'is_call': 'Is this a call instruction?', 'is_call': 'Is this a call instruction?',
'is_return': 'Is this a return 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?', 'can_trap': 'Can this instruction cause a trap?',
} }

View File

@@ -228,7 +228,7 @@ def gen_opcodes(groups, fmt):
fmt.doc_comment('An instruction opcode.') fmt.doc_comment('An instruction opcode.')
fmt.doc_comment('') fmt.doc_comment('')
fmt.doc_comment('All instructions from all supported ISAs are present.') 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 = [] instrs = []
# We explicitly set the discriminant of the first variant to 1, which # We explicitly set the discriminant of the first variant to 1, which

View File

@@ -17,6 +17,7 @@ use legalize_function;
use regalloc; use regalloc;
use result::CtonResult; use result::CtonResult;
use verifier; use verifier;
use simple_gvn::do_simple_gvn;
/// Persistent data structures and compilation pipeline. /// Persistent data structures and compilation pipeline.
pub struct Context { pub struct Context {
@@ -51,16 +52,16 @@ impl Context {
/// ///
/// Also check that the dominator tree and control flow graph are consistent with the function. /// 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. /// check ISA-dependent constraints.
pub fn verify<'a, ISA: Into<Option<&'a TargetIsa>>>(&self, isa: ISA) -> verifier::Result { pub fn verify<'a>(&self, isa: Option<&TargetIsa>) -> verifier::Result {
verifier::verify_context(&self.func, &self.cfg, &self.domtree, isa.into()) verifier::verify_context(&self.func, &self.cfg, &self.domtree, isa)
} }
/// Run the verifier only if the `enable_verifier` setting is true. /// Run the verifier only if the `enable_verifier` setting is true.
pub fn verify_if(&self, isa: &TargetIsa) -> CtonResult { pub fn verify_if(&self, isa: &TargetIsa) -> CtonResult {
if isa.flags().enable_verifier() { if isa.flags().enable_verifier() {
self.verify(isa).map_err(Into::into) self.verify(Some(isa)).map_err(Into::into)
} else { } else {
Ok(()) Ok(())
} }
@@ -78,6 +79,14 @@ impl Context {
self.domtree.compute(&self.func, &self.cfg); 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. /// Run the register allocator.
pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult { pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult {
self.regalloc self.regalloc

View File

@@ -46,6 +46,7 @@
//! The index stored in an `EntityList` points to part 2, the list elements. The value 0 is //! 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. //! reserved for the empty list which isn't allocated in the vector.
use std::hash::{Hash, Hasher};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem; 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 /// 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*. /// function they belong to. *Cloning an entity list does not allocate new memory for the clone*.
/// It creates an alias of the same memory. /// 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)] #[derive(Clone, Debug)]
pub struct EntityList<T: EntityRef> { pub struct EntityList<T: EntityRef> {
index: u32, index: u32,
@@ -75,6 +80,19 @@ impl<T: EntityRef> Default for EntityList<T> {
} }
} }
impl<T: EntityRef> Hash for EntityList<T> {
fn hash<H: Hasher>(&self, _: &mut H) {
panic!("hash called on EntityList");
}
}
impl<T: EntityRef> PartialEq for EntityList<T> {
fn eq(&self, _: &EntityList<T>) -> bool {
panic!("eq called on EntityList");
}
}
impl<T: EntityRef> Eq for EntityList<T> {}
/// A memory pool for storing lists of `T`. /// A memory pool for storing lists of `T`.
#[derive(Clone)] #[derive(Clone)]
pub struct ListPool<T: EntityRef> { pub struct ListPool<T: EntityRef> {

View File

@@ -27,7 +27,7 @@ pub trait CondCode: Copy {
/// This condition code is used by the `icmp` instruction to compare integer values. There are /// 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 /// separate codes for comparing the integers as signed or unsigned numbers where it makes a
/// difference. /// difference.
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum IntCC { pub enum IntCC {
/// `==`. /// `==`.
Equal, Equal,
@@ -139,7 +139,7 @@ impl FromStr for IntCC {
/// The condition codes described here are used to produce a single boolean value from the /// 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 /// 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`. /// 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 { pub enum FloatCC {
/// EQ | LT | GT /// EQ | LT | GT
Ordered, Ordered,

View File

@@ -243,6 +243,46 @@ impl DataFlowGraph {
self.values[dest] = ValueData::Alias { ty, original }; 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. /// Create a new value alias.
/// ///
/// Note that this function should only be called by the parser. /// Note that this function should only be called by the parser.

View File

@@ -14,7 +14,7 @@ use std::str::FromStr;
/// ///
/// An `Imm64` operand can also be used to represent immediate values of smaller integer types by /// An `Imm64` operand can also be used to represent immediate values of smaller integer types by
/// sign-extending to `i64`. /// sign-extending to `i64`.
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct Imm64(i64); pub struct Imm64(i64);
impl Imm64 { 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 /// 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`. /// 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); pub struct Offset32(i32);
impl Offset32 { impl Offset32 {
@@ -220,7 +220,7 @@ impl FromStr for Offset32 {
/// 32-bit unsigned immediate offset. /// 32-bit unsigned immediate offset.
/// ///
/// This is used to encode an immediate offset for WebAssembly heap_load/heap_store instructions. /// 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); pub struct Uoffset32(u32);
impl Uoffset32 { 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. /// All bit patterns are allowed.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Ieee32(f32); 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. /// All bit patterns are allowed.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Ieee64(f64); pub struct Ieee64(u64);
// Format a floating point number in a way that is reasonably human-readable, and that can be // 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 // 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<u64, &'static str> {
impl Ieee32 { impl Ieee32 {
/// Create a new `Ieee32` representing the number `x`. /// Create a new `Ieee32` representing the number `x`.
pub fn new(x: f32) -> Ieee32 { 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) }) Ieee32(unsafe { mem::transmute(x) })
} }
} }
impl Display for Ieee32 { impl Display for Ieee32 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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) format_float(bits as u64, 8, 23, f)
} }
} }
@@ -552,7 +549,7 @@ impl FromStr for Ieee32 {
fn from_str(s: &str) -> Result<Ieee32, &'static str> { fn from_str(s: &str) -> Result<Ieee32, &'static str> {
match parse_float(s, 8, 23) { 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), Err(s) => Err(s),
} }
} }
@@ -561,18 +558,13 @@ impl FromStr for Ieee32 {
impl Ieee64 { impl Ieee64 {
/// Create a new `Ieee64` representing the number `x`. /// Create a new `Ieee64` representing the number `x`.
pub fn new(x: f64) -> Ieee64 { 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) }) Ieee64(unsafe { mem::transmute(x) })
} }
} }
impl Display for Ieee64 { impl Display for Ieee64 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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) format_float(bits, 11, 52, f)
} }
} }
@@ -582,7 +574,7 @@ impl FromStr for Ieee64 {
fn from_str(s: &str) -> Result<Ieee64, &'static str> { fn from_str(s: &str) -> Result<Ieee64, &'static str> {
match parse_float(s, 11, 52) { match parse_float(s, 11, 52) {
Ok(b) => Ok(Ieee64::from_bits(b)), Ok(b) => Ok(Ieee64(b)),
Err(s) => Err(s), 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");
assert_eq!(Ieee32::new(-f32::NAN).to_string(), "-NaN"); assert_eq!(Ieee32::new(-f32::NAN).to_string(), "-NaN");
// Construct some qNaNs with payloads. // Construct some qNaNs with payloads.
assert_eq!(Ieee32::from_bits(0x7fc00001).to_string(), "+NaN:0x1"); assert_eq!(Ieee32(0x7fc00001).to_string(), "+NaN:0x1");
assert_eq!(Ieee32::from_bits(0x7ff00001).to_string(), "+NaN:0x300001"); assert_eq!(Ieee32(0x7ff00001).to_string(), "+NaN:0x300001");
// Signaling NaNs. // Signaling NaNs.
assert_eq!(Ieee32::from_bits(0x7f800001).to_string(), "+sNaN:0x1"); assert_eq!(Ieee32(0x7f800001).to_string(), "+sNaN:0x1");
assert_eq!(Ieee32::from_bits(0x7fa00001).to_string(), "+sNaN:0x200001"); assert_eq!(Ieee32(0x7fa00001).to_string(), "+sNaN:0x200001");
} }
#[test] #[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");
assert_eq!(Ieee64::new(-f64::NAN).to_string(), "-NaN"); assert_eq!(Ieee64::new(-f64::NAN).to_string(), "-NaN");
// Construct some qNaNs with payloads. // Construct some qNaNs with payloads.
assert_eq!(Ieee64::from_bits(0x7ff8000000000001).to_string(), assert_eq!(Ieee64(0x7ff8000000000001).to_string(), "+NaN:0x1");
"+NaN:0x1"); assert_eq!(Ieee64(0x7ffc000000000001).to_string(),
assert_eq!(Ieee64::from_bits(0x7ffc000000000001).to_string(),
"+NaN:0x4000000000001"); "+NaN:0x4000000000001");
// Signaling NaNs. // Signaling NaNs.
assert_eq!(Ieee64::from_bits(0x7ff0000000000001).to_string(), assert_eq!(Ieee64(0x7ff0000000000001).to_string(), "+sNaN:0x1");
"+sNaN:0x1"); assert_eq!(Ieee64(0x7ff4000000000001).to_string(),
assert_eq!(Ieee64::from_bits(0x7ff4000000000001).to_string(),
"+sNaN:0x4000000000001"); "+sNaN:0x4000000000001");
} }

View File

@@ -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 /// 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 /// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a
/// `Box<AuxData>` to store the additional information out of line. /// `Box<AuxData>` to store the additional information out of line.
#[derive(Clone, Debug)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum InstructionData { pub enum InstructionData {
Nullary { opcode: Opcode }, Nullary { opcode: Opcode },

View File

@@ -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 /// 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 /// 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. /// 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 { pub struct MemFlags {
bits: u8, bits: u8,
} }

View File

@@ -35,5 +35,6 @@ mod packed_option;
mod partition_slice; mod partition_slice;
mod predicates; mod predicates;
mod ref_slice; mod ref_slice;
mod simple_gvn;
mod topo_order; mod topo_order;
mod write; mod write;

View File

@@ -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<InstructionData, Inst> = 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);
}
}
}
}
}

View File

@@ -20,6 +20,7 @@ mod legalizer;
mod regalloc; mod regalloc;
mod runner; mod runner;
mod runone; mod runone;
mod simple_gvn;
mod verifier; mod verifier;
/// The result of running the test in a file. /// The result of running the test in a file.
@@ -62,6 +63,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::Result<Box<subtest::SubTest>> {
"legalizer" => legalizer::subtest(parsed), "legalizer" => legalizer::subtest(parsed),
"regalloc" => regalloc::subtest(parsed), "regalloc" => regalloc::subtest(parsed),
"binemit" => binemit::subtest(parsed), "binemit" => binemit::subtest(parsed),
"simple-gvn" => simple_gvn::subtest(parsed),
_ => Err(format!("unknown test command '{}'", parsed.command)), _ => Err(format!("unknown test command '{}'", parsed.command)),
} }
} }

View File

@@ -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<Box<SubTest>> {
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<str> {
Cow::from("simple-gvn")
}
fn is_mutating(&self) -> bool {
true
}
fn run(&self, func: Cow<Function>, 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)
}
}