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:
committed by
Jakob Stoklund Olesen
parent
0c7b2c7b68
commit
dc809628f4
@@ -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.
|
||||||
|
|||||||
11
filetests/simple_gvn/basic.cton
Normal file
11
filetests/simple_gvn/basic.cton
Normal 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
|
||||||
|
}
|
||||||
@@ -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"""
|
||||||
|
|||||||
@@ -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?',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
65
lib/cretonne/src/simple_gvn.rs
Normal file
65
lib/cretonne/src/simple_gvn.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/filetest/simple_gvn.rs
Normal file
51
src/filetest/simple_gvn.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user