phi-removal pass during basic_opts

This commit is contained in:
Chris Fallin 2024-05-07 16:41:59 -07:00 committed by Graham Kelly
parent c960d728bf
commit fb9a00978f
4 changed files with 128 additions and 38 deletions

View file

@ -31,6 +31,8 @@ pub struct CFGInfo {
pub def_block: PerEntity<Value, Block>,
/// Preds for a given block.
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)]
@ -61,22 +63,19 @@ impl CFGInfo {
pub fn new(f: &FunctionBody) -> CFGInfo {
let mut return_blocks = vec![];
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() {
if let Terminator::Return { .. } = &block.terminator {
return_blocks.push(block_id);
}
let mut target_idx = 0;
block.terminator.visit_targets(|target| {
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 domtree =
@ -127,6 +126,7 @@ impl CFGInfo {
domtree_children,
def_block,
preds,
pred_pos,
}
}

View file

@ -398,6 +398,15 @@ impl ConstVal {
_ => 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(

View file

@ -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) {
(0, Terminator::Br { ref target, .. }) => f(target),
(0, Terminator::CondBr { ref if_true, .. }) => {
f(if_true);
}
(1, Terminator::CondBr { ref if_false, .. }) => {
f(if_false);
}
(0, Terminator::Select { ref default, .. }) => {
f(default);
}
(i, Terminator::Select { ref targets, .. }) if i <= targets.len() => {
f(&targets[i - 1]);
}
(0, Terminator::CondBr { ref if_true, .. }) => f(if_true),
(1, Terminator::CondBr { ref if_false, .. }) => 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"),
}
}

View file

@ -7,6 +7,7 @@ use crate::passes::dom_pass::{dom_pass, DomtreePass};
use crate::pool::ListRef;
use crate::scoped_map::ScopedMap;
use crate::Operator;
use smallvec::{smallvec, SmallVec};
pub fn gvn(body: &mut FunctionBody, cfg: &CFGInfo) {
dom_pass::<GVNPass>(
@ -14,16 +15,18 @@ pub fn gvn(body: &mut FunctionBody, cfg: &CFGInfo) {
cfg,
&mut GVNPass {
map: ScopedMap::default(),
cfg,
},
);
}
#[derive(Debug)]
struct GVNPass {
struct GVNPass<'a> {
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) {
self.map.push_level();
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) {
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;
while i < body.blocks[block].insts.len() {
let inst = body.blocks[block].insts[i];
@ -50,6 +149,7 @@ impl GVNPass {
if value_is_pure(inst, body) {
let mut value = body.values[inst].clone();
// Resolve aliases in the arg lists.
match &mut value {
&mut ValueDef::Operator(_, args, _) | &mut ValueDef::Trace(_, args) => {
for i in 0..args.len() {
@ -65,24 +165,11 @@ impl GVNPass {
_ => {}
}
// Try to constant-propagate.
if let ValueDef::Operator(op, args, ..) = &value {
let arg_values = body.arg_pool[*args]
.iter()
.map(|&arg| match body.values[arg] {
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,
})
.map(|&arg| value_is_const(arg, body))
.collect::<Vec<_>>();
let const_val = const_eval(op, &arg_values[..], None);
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) {
body.set_alias(inst, *value);
i -= 1;