Skip to content
Snippets Groups Projects

Proper loop induced clone detection

Merged rarbore2 requested to merge clone_detection6 into main
2 files
+ 175
176
Compare changes
  • Side-by-side
  • Inline
Files
2
@@ -724,199 +724,197 @@ fn materialize_clones(
// Helper to induce a clone when an implicit clone is identified.
let nodes = nodes.clone();
let mut induce_clone =
|object: NodeID, user: NodeID, value: &BTreeMap<NodeID, BTreeSet<NodeID>>| {
// If `user` already used `object` and tries to use it again, then the
// clone is a "loop induced" clone. Otherwise, it's a simple clone.
if !value[&object].contains(&user) {
let success = editor.edit(|mut edit| {
// Create the constant collection object for allocation.
let object_ty = typing[object.idx()];
let object_cons = edit.add_zero_constant(object_ty);
let cons_node = edit.add_node(Node::Constant { id: object_cons });
let mut induce_clone = |object: NodeID,
user: NodeID,
value: &BTreeMap<NodeID, BTreeSet<NodeID>>| {
// If `user` already used `object` and tries to use it again, then the
// clone is a "loop induced" clone. Otherwise, it's a simple clone.
if !value[&object].contains(&user) {
let success = editor.edit(|mut edit| {
// Create the constant collection object for allocation.
let object_ty = typing[object.idx()];
let object_cons = edit.add_zero_constant(object_ty);
let cons_node = edit.add_node(Node::Constant { id: object_cons });
// Create the clone into the new constant collection.
let clone_node = edit.add_node(Node::Write {
collect: cons_node,
data: object,
indices: vec![].into_boxed_slice(),
});
// Make user use the cloned object.
edit.replace_all_uses_where(object, clone_node, |id| *id == user)
});
assert!(success);
} else {
// Figure out where to place that phi. This is the deepest
// loop header where `user` is responsible for making `object` used
// used at the top of the block, and the block dominates the block
// containing `user`. If `user` is a phi, then the region it's
// attached to is excluded from eligibility.
let eligible_blocks = rev_po.iter().map(|bb| *bb).filter(|bb| {
lattice[bb_to_prefix_sum[bb.idx()]]
.get(&object)
.unwrap_or(&BTreeSet::new())
.contains(&user)
&& dom.does_dom(*bb, bbs.0[user.idx()])
&& loops.contains(*bb)
&& loops.is_in_loop(*bb, bbs.0[user.idx()])
&& (!editor.func().nodes[user.idx()].is_phi() || *bb != bbs.0[user.idx()])
// Create the clone into the new constant collection.
let clone_node = edit.add_node(Node::Write {
collect: cons_node,
data: object,
indices: vec![].into_boxed_slice(),
});
let top_block = eligible_blocks
.max_by_key(|bb| loops.nesting(*bb).unwrap())
.unwrap();
assert!(editor.func().nodes[top_block.idx()].is_region());
// Figure out the users of `object` that we need to phi back
// upwards. Assign each user a number indicating how far down the
// user chain it is, higher is farther down. This is used for
// picking the most downstream user later.
let mut users: BTreeMap<NodeID, usize> = BTreeMap::new();
let mut workset: BTreeSet<NodeID> = BTreeSet::new();
workset.insert(object);
let mut chain_ordering = 1;
while let Some(pop) = workset.pop_first() {
let iterated_users: BTreeSet<_> = super_value
.get(&pop)
.map(|users| users.into_iter())
.into_iter()
.flatten()
.map(|id| *id)
.filter(|iterated_user| {
loops.is_in_loop(top_block, bbs.0[iterated_user.idx()])
&& dom.does_dom(bbs.0[pop.idx()], bbs.0[iterated_user.idx()])
})
.collect();
workset.extend(iterated_users.iter().filter(|id| !users.contains_key(id)));
for user in iterated_users {
*users.entry(user).or_default() = chain_ordering;
chain_ordering += 1;
}
// Make user use the cloned object.
edit.replace_all_uses_where(object, clone_node, |id| *id == user)
});
assert!(success);
} else {
// Figure out where to place that phi. This is the deepest
// loop header where `user` is responsible for making `object` used
// used at the top of the block, and the block dominates the block
// containing `user`. If `user` is a phi, then the region it's
// attached to is excluded from eligibility.
let eligible_blocks = rev_po.iter().map(|bb| *bb).filter(|bb| {
lattice[bb_to_prefix_sum[bb.idx()]]
.get(&object)
.unwrap_or(&BTreeSet::new())
.contains(&user)
&& dom.does_dom(*bb, bbs.0[user.idx()])
&& loops.contains(*bb)
&& loops.is_in_loop(*bb, bbs.0[user.idx()])
&& (!editor.func().nodes[user.idx()].is_phi() || *bb != bbs.0[user.idx()])
});
let top_block = eligible_blocks
.max_by_key(|bb| loops.nesting(*bb).unwrap())
.unwrap();
assert!(editor.func().nodes[top_block.idx()].is_region());
// Figure out the users of `object` that we need to phi back
// upwards. Assign each user a number indicating how far down the
// user chain it is, higher is farther down. This is used for
// picking the most downstream user later.
let mut users: BTreeMap<NodeID, usize> = BTreeMap::new();
let mut workset: BTreeSet<NodeID> = BTreeSet::new();
workset.insert(object);
let mut chain_ordering = 1;
while let Some(pop) = workset.pop_first() {
let iterated_users: BTreeSet<_> = super_value
.get(&pop)
.map(|users| users.into_iter())
.into_iter()
.flatten()
.map(|id| *id)
.filter(|iterated_user| loops.is_in_loop(top_block, bbs.0[iterated_user.idx()]))
.collect();
workset.extend(iterated_users.iter().filter(|id| !users.contains_key(id)));
for user in iterated_users {
*users.entry(user).or_default() = chain_ordering;
chain_ordering += 1;
}
}
// The fringe users may not dominate any predecessors of the loop
// header. The following is some Juno code that exposes this:
//
// fn problematic(a : size) -> i32 {
// for i = 0 to a {
// let arr : i32[1];
// for j = 0 to a {
// arr[0] = 1;
// }
// }
// return 0;
// }
//
// Note that `arr` induces a clone each iteration, since its value
// needs to be reset to all zeros. However, it should also be noted
// that the most fringe user of `arr`, the write inside the inner
// loop, does not dominate the bottom of the outer loop. Thus, we
// need to insert a phi in the bottom block of the outer loop to
// retrieve either the write, or `arr` before the inner loop. The
// general version of this problem requires the following solution.
// Our goal is to figure out which downstream user represents
// `object` at each block in the loop. We first assign each block
// containing a user the most downstream user it contains. Then, we
// create a dummy phi for every region (including the header) in the
// loop, which is the downstream user for that block. Then, every
// other block is assigned the downstream user of its single
// predecessor. This basically amounts to recreating SSA for
// `object` inside the loop.
let mut user_per_loop_bb = BTreeMap::new();
let mut added_phis = BTreeMap::new();
let mut top_phi = NodeID::new(0);
// Assign existing users.
for (user, ordering) in users.iter() {
let bb = bbs.0[user.idx()];
if let Some(old_user) = user_per_loop_bb.get(&bb)
&& users[old_user] > *ordering
{
} else {
user_per_loop_bb.insert(bb, *user);
}
// The fringe users may not dominate any predecessors of the loop
// header. The following is some Juno code that exposes this:
//
// fn problematic(a : size) -> i32 {
// for i = 0 to a {
// let arr : i32[1];
// for j = 0 to a {
// arr[0] = 1;
// }
// }
// return 0;
// }
//
// Note that `arr` induces a clone each iteration, since its value
// needs to be reset to all zeros. However, it should also be noted
// that the most fringe user of `arr`, the write inside the inner
// loop, does not dominate the bottom of the outer loop. Thus, we
// need to insert a phi in the bottom block of the outer loop to
// retrieve either the write, or `arr` before the inner loop. The
// general version of this problem requires the following solution.
// Our goal is to figure out which downstream user represents
// `object` at each block in the loop. We first assign each block
// containing a user the most downstream user it contains. Then, we
// create a dummy phi for every region (including the header) in the
// loop, which is the downstream user for that block. Then, every
// other block is assigned the downstream user of its single
// predecessor. This basically amounts to recreating SSA for
// `object` inside the loop.
let mut user_per_loop_bb = BTreeMap::new();
let mut added_phis = BTreeMap::new();
let mut top_phi = NodeID::new(0);
// Assign existing users.
for (user, ordering) in users.iter() {
let bb = bbs.0[user.idx()];
if let Some(old_user) = user_per_loop_bb.get(&bb)
&& users[old_user] > *ordering
{
} else {
user_per_loop_bb.insert(bb, *user);
}
// Assign dummy phis.
for bb in loops.nodes_in_loop(top_block) {
if (!user_per_loop_bb.contains_key(&bb) || bb == top_block)
&& editor.func().nodes[bb.idx()].is_region()
{
let success = editor.edit(|mut edit| {
let phi_node = edit.add_node(Node::Phi {
control: bb,
data: empty().collect(),
});
if bb != top_block || !user_per_loop_bb.contains_key(&bb) {
user_per_loop_bb.insert(bb, phi_node);
}
if bb == top_block {
top_phi = phi_node;
}
added_phis.insert(phi_node, bb);
Ok(edit)
}
// Assign dummy phis.
for bb in loops.nodes_in_loop(top_block) {
if (!user_per_loop_bb.contains_key(&bb) || bb == top_block)
&& editor.func().nodes[bb.idx()].is_region()
{
let success = editor.edit(|mut edit| {
let phi_node = edit.add_node(Node::Phi {
control: bb,
data: empty().collect(),
});
assert!(success);
}
if bb != top_block || !user_per_loop_bb.contains_key(&bb) {
user_per_loop_bb.insert(bb, phi_node);
}
if bb == top_block {
top_phi = phi_node;
}
added_phis.insert(phi_node, bb);
Ok(edit)
});
assert!(success);
}
// Assign users for the rest of the blocks.
for bb in rev_po.iter().filter(|bb| loops.is_in_loop(top_block, **bb)) {
if !user_per_loop_bb.contains_key(&bb) {
assert!(control_subgraph.preds(*bb).count() == 1);
user_per_loop_bb.insert(
*bb,
user_per_loop_bb[&control_subgraph.preds(*bb).next().unwrap()],
);
}
}
// Assign users for the rest of the blocks.
for bb in rev_po.iter().filter(|bb| loops.is_in_loop(top_block, **bb)) {
if !user_per_loop_bb.contains_key(&bb) {
assert!(control_subgraph.preds(*bb).count() == 1);
user_per_loop_bb.insert(
*bb,
user_per_loop_bb[&control_subgraph.preds(*bb).next().unwrap()],
);
}
}
// Induce the clone.
let success = editor.edit(|mut edit| {
// Create the constant collection object for allocation.
let object_ty = typing[object.idx()];
let object_cons = edit.add_zero_constant(object_ty);
let cons_node = edit.add_node(Node::Constant { id: object_cons });
// Induce the clone.
let success = editor.edit(|mut edit| {
// Create the constant collection object for allocation.
let object_ty = typing[object.idx()];
let object_cons = edit.add_zero_constant(object_ty);
let cons_node = edit.add_node(Node::Constant { id: object_cons });
// Create the phis.
let mut real_phis = BTreeMap::new();
for (dummy, bb) in added_phis {
let real = edit.add_node(Node::Phi {
control: bb,
data: control_subgraph
.preds(bb)
.map(|pred| *user_per_loop_bb.get(&pred).unwrap_or(&cons_node))
.collect(),
});
real_phis.insert(dummy, real);
}
// Create the clone into the phi.
let clone_node = edit.add_node(Node::Write {
collect: real_phis[&top_phi],
data: object,
indices: vec![].into_boxed_slice(),
// Create the phis.
let mut real_phis = BTreeMap::new();
for (dummy, bb) in added_phis {
let real = edit.add_node(Node::Phi {
control: bb,
data: control_subgraph
.preds(bb)
.map(|pred| *user_per_loop_bb.get(&pred).unwrap_or(&cons_node))
.collect(),
});
real_phis.insert(dummy, real);
}
// Make user use the cloned object.
edit = edit.replace_all_uses_where(object, clone_node, |id| {
id.idx() < bbs.0.len() && loops.is_in_loop(top_block, bbs.0[id.idx()])
})?;
// Create the clone into the phi.
let clone_node = edit.add_node(Node::Write {
collect: real_phis[&top_phi],
data: object,
indices: vec![].into_boxed_slice(),
});
// Get rid of the dummy phis.
for (dummy, real) in real_phis {
edit = edit.replace_all_uses(dummy, real)?;
edit = edit.delete_node(dummy)?;
}
// Make users use the cloned object.
edit = edit.replace_all_uses_where(object, clone_node, |id| {
id.idx() < bbs.0.len() && loops.is_in_loop(top_block, bbs.0[id.idx()])
})?;
Ok(edit)
});
assert!(success);
// Get rid of the dummy phis.
for (dummy, real) in real_phis {
edit = edit.replace_all_uses(dummy, real)?;
edit = edit.delete_node(dummy)?;
}
// De-duplicate phis.
gvn(editor, false);
Ok(edit)
});
assert!(success);
// Get rid of unused phis.
dce(editor);
}
};
// De-duplicate phis.
gvn(editor, false);
// Get rid of unused phis.
dce(editor);
}
};
// Now that we've computed the used collections dataflow analysis, use the
// results to materialize a clone whenever a node attempts to use an already
Loading