phi-removal pass during basic_opts
This commit is contained in:
parent
c960d728bf
commit
fb9a00978f
|
@ -31,6 +31,8 @@ pub struct CFGInfo {
|
||||||
pub def_block: PerEntity<Value, Block>,
|
pub def_block: PerEntity<Value, Block>,
|
||||||
/// Preds for a given block.
|
/// Preds for a given block.
|
||||||
pub preds: PerEntity<Block, SmallVec<[Block; 4]>>,
|
pub preds: PerEntity<Block, SmallVec<[Block; 4]>>,
|
||||||
|
/// A given block's position in each predecessor's successor list.
|
||||||
|
pub pred_pos: PerEntity<Block, SmallVec<[usize; 4]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -61,22 +63,19 @@ impl CFGInfo {
|
||||||
pub fn new(f: &FunctionBody) -> CFGInfo {
|
pub fn new(f: &FunctionBody) -> CFGInfo {
|
||||||
let mut return_blocks = vec![];
|
let mut return_blocks = vec![];
|
||||||
let mut preds: PerEntity<Block, SmallVec<[Block; 4]>> = PerEntity::default();
|
let mut preds: PerEntity<Block, SmallVec<[Block; 4]>> = PerEntity::default();
|
||||||
|
let mut pred_pos: PerEntity<Block, SmallVec<[usize; 4]>> = PerEntity::default();
|
||||||
for (block_id, block) in f.blocks.entries() {
|
for (block_id, block) in f.blocks.entries() {
|
||||||
if let Terminator::Return { .. } = &block.terminator {
|
if let Terminator::Return { .. } = &block.terminator {
|
||||||
return_blocks.push(block_id);
|
return_blocks.push(block_id);
|
||||||
}
|
}
|
||||||
|
let mut target_idx = 0;
|
||||||
block.terminator.visit_targets(|target| {
|
block.terminator.visit_targets(|target| {
|
||||||
preds[target.block].push(block_id);
|
preds[target.block].push(block_id);
|
||||||
|
pred_pos[target.block].push(target_idx);
|
||||||
|
target_idx += 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dedup preds.
|
|
||||||
for block in f.blocks.iter() {
|
|
||||||
let preds = &mut preds[block];
|
|
||||||
preds.sort_unstable();
|
|
||||||
preds.dedup();
|
|
||||||
}
|
|
||||||
|
|
||||||
let postorder = postorder::calculate(f.entry, |block| &f.blocks[block].succs[..]);
|
let postorder = postorder::calculate(f.entry, |block| &f.blocks[block].succs[..]);
|
||||||
|
|
||||||
let domtree =
|
let domtree =
|
||||||
|
@ -127,6 +126,7 @@ impl CFGInfo {
|
||||||
domtree_children,
|
domtree_children,
|
||||||
def_block,
|
def_block,
|
||||||
preds,
|
preds,
|
||||||
|
pred_pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -398,6 +398,15 @@ impl ConstVal {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn meet(a: Option<ConstVal>, b: Option<ConstVal>) -> Option<ConstVal> {
|
||||||
|
match (a, b) {
|
||||||
|
(None, None) => None,
|
||||||
|
(Some(a), None) | (None, Some(a)) => Some(a),
|
||||||
|
(Some(a), Some(b)) if a == b => Some(a),
|
||||||
|
_ => Some(ConstVal::None),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn const_eval(
|
pub fn const_eval(
|
||||||
|
|
|
@ -674,21 +674,13 @@ impl Terminator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visit_target<F: FnMut(&BlockTarget)>(&self, index: usize, mut f: F) {
|
pub fn visit_target<R, F: FnMut(&BlockTarget) -> R>(&self, index: usize, mut f: F) -> R {
|
||||||
match (index, self) {
|
match (index, self) {
|
||||||
(0, Terminator::Br { ref target, .. }) => f(target),
|
(0, Terminator::Br { ref target, .. }) => f(target),
|
||||||
(0, Terminator::CondBr { ref if_true, .. }) => {
|
(0, Terminator::CondBr { ref if_true, .. }) => f(if_true),
|
||||||
f(if_true);
|
(1, Terminator::CondBr { ref if_false, .. }) => f(if_false),
|
||||||
}
|
(0, Terminator::Select { ref default, .. }) => f(default),
|
||||||
(1, Terminator::CondBr { ref if_false, .. }) => {
|
(i, Terminator::Select { ref targets, .. }) if i <= targets.len() => f(&targets[i - 1]),
|
||||||
f(if_false);
|
|
||||||
}
|
|
||||||
(0, Terminator::Select { ref default, .. }) => {
|
|
||||||
f(default);
|
|
||||||
}
|
|
||||||
(i, Terminator::Select { ref targets, .. }) if i <= targets.len() => {
|
|
||||||
f(&targets[i - 1]);
|
|
||||||
}
|
|
||||||
_ => panic!("out of bounds"),
|
_ => panic!("out of bounds"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::passes::dom_pass::{dom_pass, DomtreePass};
|
||||||
use crate::pool::ListRef;
|
use crate::pool::ListRef;
|
||||||
use crate::scoped_map::ScopedMap;
|
use crate::scoped_map::ScopedMap;
|
||||||
use crate::Operator;
|
use crate::Operator;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
pub fn gvn(body: &mut FunctionBody, cfg: &CFGInfo) {
|
pub fn gvn(body: &mut FunctionBody, cfg: &CFGInfo) {
|
||||||
dom_pass::<GVNPass>(
|
dom_pass::<GVNPass>(
|
||||||
|
@ -14,16 +15,18 @@ pub fn gvn(body: &mut FunctionBody, cfg: &CFGInfo) {
|
||||||
cfg,
|
cfg,
|
||||||
&mut GVNPass {
|
&mut GVNPass {
|
||||||
map: ScopedMap::default(),
|
map: ScopedMap::default(),
|
||||||
|
cfg,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct GVNPass {
|
struct GVNPass<'a> {
|
||||||
map: ScopedMap<ValueDef, Value>,
|
map: ScopedMap<ValueDef, Value>,
|
||||||
|
cfg: &'a CFGInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DomtreePass for GVNPass {
|
impl<'a> DomtreePass for GVNPass<'a> {
|
||||||
fn enter(&mut self, block: Block, body: &mut FunctionBody) {
|
fn enter(&mut self, block: Block, body: &mut FunctionBody) {
|
||||||
self.map.push_level();
|
self.map.push_level();
|
||||||
self.optimize(block, body);
|
self.optimize(block, body);
|
||||||
|
@ -41,8 +44,104 @@ fn value_is_pure(value: Value, body: &FunctionBody) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GVNPass {
|
fn value_is_const(value: Value, body: &FunctionBody) -> ConstVal {
|
||||||
|
match body.values[value] {
|
||||||
|
ValueDef::Operator(Operator::I32Const { value }, _, _) => ConstVal::I32(value),
|
||||||
|
ValueDef::Operator(Operator::I64Const { value }, _, _) => ConstVal::I64(value),
|
||||||
|
ValueDef::Operator(Operator::F32Const { value }, _, _) => ConstVal::F32(value),
|
||||||
|
ValueDef::Operator(Operator::F64Const { value }, _, _) => ConstVal::F64(value),
|
||||||
|
_ => ConstVal::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn const_op(val: ConstVal) -> Operator {
|
||||||
|
match val {
|
||||||
|
ConstVal::I32(value) => Operator::I32Const { value },
|
||||||
|
ConstVal::I64(value) => Operator::I64Const { value },
|
||||||
|
ConstVal::F32(value) => Operator::F32Const { value },
|
||||||
|
ConstVal::F64(value) => Operator::F64Const { value },
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_all_from_vec<T: Clone>(v: &mut Vec<T>, indices: &[usize]) {
|
||||||
|
let mut out = 0;
|
||||||
|
let mut indices_i = 0;
|
||||||
|
for i in 0..v.len() {
|
||||||
|
let keep = indices_i == indices.len() || indices[indices_i] != i;
|
||||||
|
if keep {
|
||||||
|
if out < i {
|
||||||
|
v[out] = v[i].clone();
|
||||||
|
}
|
||||||
|
out += 1;
|
||||||
|
} else {
|
||||||
|
indices_i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.truncate(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GVNPass<'a> {
|
||||||
fn optimize(&mut self, block: Block, body: &mut FunctionBody) {
|
fn optimize(&mut self, block: Block, body: &mut FunctionBody) {
|
||||||
|
if block != body.entry {
|
||||||
|
// Pass over blockparams, checking all inputs. If all inputs
|
||||||
|
// resolve to the same SSA value, remove the blockparam and
|
||||||
|
// make it an alias of that value. If all inputs resolve to
|
||||||
|
// the same constant value, remove the blockparam and insert a
|
||||||
|
// new copy of that constant.
|
||||||
|
let mut blockparams_to_remove: SmallVec<[usize; 4]> = smallvec![];
|
||||||
|
let mut const_insts_to_insert: SmallVec<[Value; 4]> = smallvec![];
|
||||||
|
for (i, &(ty, blockparam)) in body.blocks[block].params.iter().enumerate() {
|
||||||
|
let mut inputs: SmallVec<[Value; 4]> = smallvec![];
|
||||||
|
let mut const_val = None;
|
||||||
|
for (&pred, &pos) in self.cfg.preds[block]
|
||||||
|
.iter()
|
||||||
|
.zip(self.cfg.pred_pos[block].iter())
|
||||||
|
{
|
||||||
|
let input = body.blocks[pred]
|
||||||
|
.terminator
|
||||||
|
.visit_target(pos, |target| target.args[i]);
|
||||||
|
inputs.push(input);
|
||||||
|
const_val = ConstVal::meet(const_val, Some(value_is_const(input, body)));
|
||||||
|
}
|
||||||
|
let const_val = const_val.unwrap();
|
||||||
|
|
||||||
|
assert!(inputs.len() > 0);
|
||||||
|
if inputs.iter().all(|x| *x == inputs[0]) {
|
||||||
|
// All inputs are the same value; remove the
|
||||||
|
// blockparam and rewrite it as an alias of the one
|
||||||
|
// single value.
|
||||||
|
body.values[blockparam] = ValueDef::Alias(inputs[0]);
|
||||||
|
blockparams_to_remove.push(i);
|
||||||
|
} else if const_val != ConstVal::None {
|
||||||
|
// All inputs are the same constant; remove the
|
||||||
|
// blockparam and rewrite it as a new constant
|
||||||
|
// operator.
|
||||||
|
let ty = body.type_pool.single(ty);
|
||||||
|
body.values[blockparam] =
|
||||||
|
ValueDef::Operator(const_op(const_val), ListRef::default(), ty);
|
||||||
|
const_insts_to_insert.push(blockparam);
|
||||||
|
blockparams_to_remove.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for inst in const_insts_to_insert {
|
||||||
|
body.blocks[block].insts.insert(0, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_all_from_vec(&mut body.blocks[block].params, &blockparams_to_remove[..]);
|
||||||
|
for (&pred, &pos) in self.cfg.preds[block]
|
||||||
|
.iter()
|
||||||
|
.zip(self.cfg.pred_pos[block].iter())
|
||||||
|
{
|
||||||
|
body.blocks[pred].terminator.update_target(pos, |target| {
|
||||||
|
remove_all_from_vec(&mut target.args, &blockparams_to_remove[..])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass over instructions, updating in place.
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < body.blocks[block].insts.len() {
|
while i < body.blocks[block].insts.len() {
|
||||||
let inst = body.blocks[block].insts[i];
|
let inst = body.blocks[block].insts[i];
|
||||||
|
@ -50,6 +149,7 @@ impl GVNPass {
|
||||||
if value_is_pure(inst, body) {
|
if value_is_pure(inst, body) {
|
||||||
let mut value = body.values[inst].clone();
|
let mut value = body.values[inst].clone();
|
||||||
|
|
||||||
|
// Resolve aliases in the arg lists.
|
||||||
match &mut value {
|
match &mut value {
|
||||||
&mut ValueDef::Operator(_, args, _) | &mut ValueDef::Trace(_, args) => {
|
&mut ValueDef::Operator(_, args, _) | &mut ValueDef::Trace(_, args) => {
|
||||||
for i in 0..args.len() {
|
for i in 0..args.len() {
|
||||||
|
@ -65,24 +165,11 @@ impl GVNPass {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to constant-propagate.
|
||||||
if let ValueDef::Operator(op, args, ..) = &value {
|
if let ValueDef::Operator(op, args, ..) = &value {
|
||||||
let arg_values = body.arg_pool[*args]
|
let arg_values = body.arg_pool[*args]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&arg| match body.values[arg] {
|
.map(|&arg| value_is_const(arg, body))
|
||||||
ValueDef::Operator(Operator::I32Const { value }, _, _) => {
|
|
||||||
ConstVal::I32(value)
|
|
||||||
}
|
|
||||||
ValueDef::Operator(Operator::I64Const { value }, _, _) => {
|
|
||||||
ConstVal::I64(value)
|
|
||||||
}
|
|
||||||
ValueDef::Operator(Operator::F32Const { value }, _, _) => {
|
|
||||||
ConstVal::F32(value)
|
|
||||||
}
|
|
||||||
ValueDef::Operator(Operator::F64Const { value }, _, _) => {
|
|
||||||
ConstVal::F64(value)
|
|
||||||
}
|
|
||||||
_ => ConstVal::None,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let const_val = const_eval(op, &arg_values[..], None);
|
let const_val = const_eval(op, &arg_values[..], None);
|
||||||
match const_val {
|
match const_val {
|
||||||
|
@ -122,6 +209,8 @@ impl GVNPass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GVN: look for already-existing copies of this
|
||||||
|
// value.
|
||||||
if let Some(value) = self.map.get(&value) {
|
if let Some(value) = self.map.get(&value) {
|
||||||
body.set_alias(inst, *value);
|
body.set_alias(inst, *value);
|
||||||
i -= 1;
|
i -= 1;
|
||||||
|
|
Loading…
Reference in a new issue