Skip to content

Commit b9a2a61

Browse files
committed
Combine DSE and load-store
1 parent 4c912a1 commit b9a2a61

1 file changed

Lines changed: 80 additions & 17 deletions

File tree

zjit/src/hir.rs

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4969,32 +4969,60 @@ impl Function {
49694969
self.infer_types();
49704970
}
49714971

4972+
// TODO: Replace hashmaps with vecs?
4973+
// TODO: Add comments describing load store elimination in the first pass
4974+
// TODO: Link to rails at scale post?
4975+
// TODO: Add comments describing deada store elimination in the second pass
4976+
// TODO: Add master comment about type based alias analysis with specific callouts referencing the overall TBAA comment
4977+
// TODO(Jacob): Spend time with the old noggin to answer the following questions.
4978+
// 1. It is possible to do DSE forwards. Is it possible to do this at the same time as load store?
4979+
// If so, this would allow us to do it in one pass*. We still need to clean up the dead stores afterwards and convert them to
4980+
// NOPs or something. The more I think about it, the more I think it makes sense to have a cleanup pass at the end of all other
4981+
// analysis passes.
4982+
// 2. DSE and load-store-opt seem like duals of each other. If this is true, then they should be symmetric. Why does DSE only handle
4983+
// stores? Can I convince myself that we're not missing some potential dead loads? Is this caught at an SSA level already?
49724984
fn optimize_load_store(&mut self) {
4985+
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
4986+
struct HeapKey {
4987+
object: InsnId,
4988+
offset: i32,
4989+
}
4990+
4991+
// This helper function is used while we don't have type based alias
4992+
// analysis. Matching offsets could result in aliased objects that need
4993+
// to be clear.
4994+
// Any code location that uses the remove_aliasing function needs to be replaced
4995+
// when we implement TBAA
4996+
fn remove_aliasing(map: &mut HashMap<HeapKey, InsnId>, offset: i32) {
4997+
map.retain(|HeapKey { object: _, offset: off }, _| *off != offset);
4998+
}
4999+
49735000
for block in self.rpo() {
4974-
let mut compile_time_heap: HashMap<(InsnId, i32), InsnId> = HashMap::new();
5001+
// First, we eliminate loads and stores
5002+
let mut compile_time_heap: HashMap<HeapKey, InsnId> = HashMap::new();
49755003
let old_insns = std::mem::take(&mut self.blocks[block.0].insns);
49765004
let mut new_insns = vec![];
5005+
49775006
for insn_id in old_insns {
4978-
let replacement_insn: InsnId = match self.find(insn_id) {
5007+
match self.find(insn_id) {
49795008
Insn::StoreField { recv, offset, val, .. } => {
4980-
let key = (self.chase_insn(recv), offset);
5009+
let key = HeapKey { object: self.chase_insn(recv), offset };
49815010
let heap_entry = compile_time_heap.get(&key).copied();
49825011
// TODO(Jacob): Switch from actual to partial equality
49835012
if Some(val) == heap_entry {
49845013
// If the value is already stored, short circuit and don't add an instruction to the block
49855014
continue
49865015
}
4987-
// TODO(Jacob): Add TBAA to avoid removing so many entries
4988-
compile_time_heap.retain(|(_, off), _| *off != offset);
5016+
remove_aliasing(&mut compile_time_heap, offset);
49895017
compile_time_heap.insert(key, val);
4990-
insn_id
49915018
},
49925019
Insn::LoadField { recv, offset, .. } => {
4993-
let key = (self.chase_insn(recv), offset);
5020+
let key = HeapKey { object: self.chase_insn(recv), offset };
49945021
match compile_time_heap.entry(key) {
49955022
std::collections::hash_map::Entry::Occupied(entry) => {
49965023
// If the value is stored already, we should short circuit.
49975024
// However, we need to replace insn_id with its representative in the SSA union.
5025+
// TODO: Show an example of why we need this with p little ascii diagram or something
49985026
self.make_equal_to(insn_id, *entry.get());
49995027
continue
50005028
}
@@ -5003,28 +5031,64 @@ impl Function {
50035031
compile_time_heap.insert(key, insn_id);
50045032
}
50055033
}
5006-
insn_id
50075034
}
50085035
Insn::WriteBarrier { .. } => {
50095036
// Currently, WriteBarrier write effects are Allocator and Memory when we'd really like them to be flags.
50105037
// We don't use LoadField for mark bits so we can ignore them for now.
50115038
// But flags does not exist in our effects abstract heap modeling and we don't want to add special casing to effects.
50125039
// This special casing in this pass here should be removed once we refine our effects system to provide greater granularity for WriteBarrier.
5013-
// TODO: use TBAA
50145040
let offset = RUBY_OFFSET_RBASIC_FLAGS;
5015-
compile_time_heap.retain(|(_, off), _| *off != offset);
5016-
insn_id
5041+
remove_aliasing(&mut compile_time_heap, offset);
50175042
},
50185043
insn => {
50195044
// If an instruction affects memory and we haven't modeled it, the compile_time_heap is invalidated
50205045
if insn.effects_of().includes(Effect::write(abstract_heaps::Memory)) {
50215046
compile_time_heap.clear();
50225047
}
5023-
insn_id
50245048
}
50255049
};
5026-
new_insns.push(replacement_insn);
5050+
new_insns.push(insn_id);
50275051
}
5052+
5053+
// After performing scalar replacement on loads and stores where
5054+
// applicable, we scan in reverse to find an eliminate dead stores
5055+
let mut reversed_insns = std::mem::take(&mut new_insns);
5056+
reversed_insns.reverse();
5057+
compile_time_heap.clear();
5058+
5059+
for insn_id in reversed_insns {
5060+
match self.find(insn_id) {
5061+
Insn::StoreField { recv, offset, .. } => {
5062+
let key = HeapKey { object: self.chase_insn(recv), offset };
5063+
if compile_time_heap.contains_key(&key) {
5064+
// We've found a second store with no uses between. eliminate the earlier one
5065+
continue
5066+
}
5067+
else {
5068+
remove_aliasing(&mut compile_time_heap, offset);
5069+
compile_time_heap.insert(key, insn_id);
5070+
}
5071+
}
5072+
Insn::LoadField{ offset, .. } => {
5073+
remove_aliasing(&mut compile_time_heap, offset);
5074+
}
5075+
Insn::WriteBarrier { .. } => {
5076+
let offset = RUBY_OFFSET_RBASIC_FLAGS;
5077+
remove_aliasing(&mut compile_time_heap, offset);
5078+
},
5079+
insn => {
5080+
// TODO: Add test case for control flow potential bugs
5081+
if insn.effects_of().includes(Effect::read(abstract_heaps::Memory)) {
5082+
compile_time_heap.clear();
5083+
}
5084+
else if insn.effects_of().includes(effects::Control) {
5085+
compile_time_heap.clear();
5086+
}
5087+
}
5088+
}
5089+
new_insns.push(insn_id);
5090+
}
5091+
new_insns.reverse();
50285092
self.blocks[block.0].insns = new_insns;
50295093
}
50305094
}
@@ -5057,9 +5121,7 @@ impl Function {
50575121
let mut active_stores: HashMap<StoreHeap, InsnId> = HashMap::new();
50585122
let insns = std::mem::take(&mut self.blocks[block.0].insns);
50595123
let mut new_insns = vec![];
5060-
for insn_id_ref in insns.iter().rev() {
5061-
// TODO: This is ugly, clean this up
5062-
let insn_id = *insn_id_ref;
5124+
for insn_id in insns.iter().rev().copied() {
50635125
match self.find(insn_id) {
50645126
Insn::StoreField { recv, offset, .. } => {
50655127
let key = StoreHeap { object: self.chase_insn(recv), offset };
@@ -5110,6 +5172,7 @@ impl Function {
51105172
// entries of active_stores are not loaded. This is because we use
51115173
// extended basic blocks and we should probably change this to be
51125174
// standard basic blocks.
5175+
// TODO: Add a test for this case
51135176
if insn.effects_of().includes(Effect::read(abstract_heaps::Memory)) {
51145177
active_stores.clear();
51155178
}
@@ -5796,7 +5859,7 @@ impl Function {
57965859
run_pass!(optimize_getivar);
57975860
run_pass!(optimize_c_calls);
57985861
run_pass!(optimize_load_store);
5799-
run_pass!(eliminate_dead_stores);
5862+
// run_pass!(eliminate_dead_stores);
58005863
run_pass!(fold_constants);
58015864
run_pass!(clean_cfg);
58025865
run_pass!(remove_redundant_patch_points);

0 commit comments

Comments
 (0)