Skip to content

Commit 15cae6a

Browse files
committed
Combine DSE and load-store
1 parent d5110e4 commit 15cae6a

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
@@ -5073,32 +5073,60 @@ impl Function {
50735073
}
50745074
}
50755075

5076+
// TODO: Replace hashmaps with vecs?
5077+
// TODO: Add comments describing load store elimination in the first pass
5078+
// TODO: Link to rails at scale post?
5079+
// TODO: Add comments describing deada store elimination in the second pass
5080+
// TODO: Add master comment about type based alias analysis with specific callouts referencing the overall TBAA comment
5081+
// TODO(Jacob): Spend time with the old noggin to answer the following questions.
5082+
// 1. It is possible to do DSE forwards. Is it possible to do this at the same time as load store?
5083+
// 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
5084+
// 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
5085+
// analysis passes.
5086+
// 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
5087+
// stores? Can I convince myself that we're not missing some potential dead loads? Is this caught at an SSA level already?
50765088
fn optimize_load_store(&mut self) {
5089+
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
5090+
struct HeapKey {
5091+
object: InsnId,
5092+
offset: i32,
5093+
}
5094+
5095+
// This helper function is used while we don't have type based alias
5096+
// analysis. Matching offsets could result in aliased objects that need
5097+
// to be clear.
5098+
// Any code location that uses the remove_aliasing function needs to be replaced
5099+
// when we implement TBAA
5100+
fn remove_aliasing(map: &mut HashMap<HeapKey, InsnId>, offset: i32) {
5101+
map.retain(|HeapKey { object: _, offset: off }, _| *off != offset);
5102+
}
5103+
50775104
for block in self.rpo() {
5078-
let mut compile_time_heap: HashMap<(InsnId, i32), InsnId> = HashMap::new();
5105+
// First, we eliminate loads and stores
5106+
let mut compile_time_heap: HashMap<HeapKey, InsnId> = HashMap::new();
50795107
let old_insns = std::mem::take(&mut self.blocks[block.0].insns);
50805108
let mut new_insns = vec![];
5109+
50815110
for insn_id in old_insns {
5082-
let replacement_insn: InsnId = match self.find(insn_id) {
5111+
match self.find(insn_id) {
50835112
Insn::StoreField { recv, offset, val, .. } => {
5084-
let key = (self.chase_insn(recv), offset);
5113+
let key = HeapKey { object: self.chase_insn(recv), offset };
50855114
let heap_entry = compile_time_heap.get(&key).copied();
50865115
// TODO(Jacob): Switch from actual to partial equality
50875116
if Some(val) == heap_entry {
50885117
// If the value is already stored, short circuit and don't add an instruction to the block
50895118
continue
50905119
}
5091-
// TODO(Jacob): Add TBAA to avoid removing so many entries
5092-
compile_time_heap.retain(|(_, off), _| *off != offset);
5120+
remove_aliasing(&mut compile_time_heap, offset);
50935121
compile_time_heap.insert(key, val);
5094-
insn_id
50955122
},
50965123
Insn::LoadField { recv, offset, .. } => {
5097-
let key = (self.chase_insn(recv), offset);
5124+
let key = HeapKey { object: self.chase_insn(recv), offset };
50985125
match compile_time_heap.entry(key) {
50995126
std::collections::hash_map::Entry::Occupied(entry) => {
51005127
// If the value is stored already, we should short circuit.
51015128
// However, we need to replace insn_id with its representative in the SSA union.
5129+
// TODO: Show an example of why we need this with p little ascii diagram or something
51025130
self.make_equal_to(insn_id, *entry.get());
51035131
continue
51045132
}
@@ -5107,28 +5135,64 @@ impl Function {
51075135
compile_time_heap.insert(key, insn_id);
51085136
}
51095137
}
5110-
insn_id
51115138
}
51125139
Insn::WriteBarrier { .. } => {
51135140
// Currently, WriteBarrier write effects are Allocator and Memory when we'd really like them to be flags.
51145141
// We don't use LoadField for mark bits so we can ignore them for now.
51155142
// But flags does not exist in our effects abstract heap modeling and we don't want to add special casing to effects.
51165143
// This special casing in this pass here should be removed once we refine our effects system to provide greater granularity for WriteBarrier.
5117-
// TODO: use TBAA
51185144
let offset = RUBY_OFFSET_RBASIC_FLAGS;
5119-
compile_time_heap.retain(|(_, off), _| *off != offset);
5120-
insn_id
5145+
remove_aliasing(&mut compile_time_heap, offset);
51215146
},
51225147
insn => {
51235148
// If an instruction affects memory and we haven't modeled it, the compile_time_heap is invalidated
51245149
if insn.effects_of().includes(Effect::write(abstract_heaps::Memory)) {
51255150
compile_time_heap.clear();
51265151
}
5127-
insn_id
51285152
}
51295153
};
5130-
new_insns.push(replacement_insn);
5154+
new_insns.push(insn_id);
51315155
}
5156+
5157+
// After performing scalar replacement on loads and stores where
5158+
// applicable, we scan in reverse to find an eliminate dead stores
5159+
let mut reversed_insns = std::mem::take(&mut new_insns);
5160+
reversed_insns.reverse();
5161+
compile_time_heap.clear();
5162+
5163+
for insn_id in reversed_insns {
5164+
match self.find(insn_id) {
5165+
Insn::StoreField { recv, offset, .. } => {
5166+
let key = HeapKey { object: self.chase_insn(recv), offset };
5167+
if compile_time_heap.contains_key(&key) {
5168+
// We've found a second store with no uses between. eliminate the earlier one
5169+
continue
5170+
}
5171+
else {
5172+
remove_aliasing(&mut compile_time_heap, offset);
5173+
compile_time_heap.insert(key, insn_id);
5174+
}
5175+
}
5176+
Insn::LoadField{ offset, .. } => {
5177+
remove_aliasing(&mut compile_time_heap, offset);
5178+
}
5179+
Insn::WriteBarrier { .. } => {
5180+
let offset = RUBY_OFFSET_RBASIC_FLAGS;
5181+
remove_aliasing(&mut compile_time_heap, offset);
5182+
},
5183+
insn => {
5184+
// TODO: Add test case for control flow potential bugs
5185+
if insn.effects_of().includes(Effect::read(abstract_heaps::Memory)) {
5186+
compile_time_heap.clear();
5187+
}
5188+
else if insn.effects_of().includes(effects::Control) {
5189+
compile_time_heap.clear();
5190+
}
5191+
}
5192+
}
5193+
new_insns.push(insn_id);
5194+
}
5195+
new_insns.reverse();
51325196
self.blocks[block.0].insns = new_insns;
51335197
}
51345198
}
@@ -5161,9 +5225,7 @@ impl Function {
51615225
let mut active_stores: HashMap<StoreHeap, InsnId> = HashMap::new();
51625226
let insns = std::mem::take(&mut self.blocks[block.0].insns);
51635227
let mut new_insns = vec![];
5164-
for insn_id_ref in insns.iter().rev() {
5165-
// TODO: This is ugly, clean this up
5166-
let insn_id = *insn_id_ref;
5228+
for insn_id in insns.iter().rev().copied() {
51675229
match self.find(insn_id) {
51685230
Insn::StoreField { recv, offset, .. } => {
51695231
let key = StoreHeap { object: self.chase_insn(recv), offset };
@@ -5214,6 +5276,7 @@ impl Function {
52145276
// entries of active_stores are not loaded. This is because we use
52155277
// extended basic blocks and we should probably change this to be
52165278
// standard basic blocks.
5279+
// TODO: Add a test for this case
52175280
if insn.effects_of().includes(Effect::read(abstract_heaps::Memory)) {
52185281
active_stores.clear();
52195282
}
@@ -5933,7 +5996,7 @@ impl Function {
59335996
run_pass!(optimize_c_calls);
59345997
run_pass!(convert_no_profile_sends);
59355998
run_pass!(optimize_load_store);
5936-
run_pass!(eliminate_dead_stores);
5999+
// run_pass!(eliminate_dead_stores);
59376000
run_pass!(fold_constants);
59386001
run_pass!(clean_cfg);
59396002
run_pass!(remove_redundant_patch_points);

0 commit comments

Comments
 (0)