Skip to content

Commit fa72cb8

Browse files
committed
Combine DSE and load-store
1 parent f5b48c5 commit fa72cb8

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
@@ -5151,32 +5151,60 @@ impl Function {
51515151
}
51525152
}
51535153

5154+
// TODO: Replace hashmaps with vecs?
5155+
// TODO: Add comments describing load store elimination in the first pass
5156+
// TODO: Link to rails at scale post?
5157+
// TODO: Add comments describing deada store elimination in the second pass
5158+
// TODO: Add master comment about type based alias analysis with specific callouts referencing the overall TBAA comment
5159+
// TODO(Jacob): Spend time with the old noggin to answer the following questions.
5160+
// 1. It is possible to do DSE forwards. Is it possible to do this at the same time as load store?
5161+
// 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
5162+
// 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
5163+
// analysis passes.
5164+
// 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
5165+
// stores? Can I convince myself that we're not missing some potential dead loads? Is this caught at an SSA level already?
51545166
fn optimize_load_store(&mut self) {
5167+
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
5168+
struct HeapKey {
5169+
object: InsnId,
5170+
offset: i32,
5171+
}
5172+
5173+
// This helper function is used while we don't have type based alias
5174+
// analysis. Matching offsets could result in aliased objects that need
5175+
// to be clear.
5176+
// Any code location that uses the remove_aliasing function needs to be replaced
5177+
// when we implement TBAA
5178+
fn remove_aliasing(map: &mut HashMap<HeapKey, InsnId>, offset: i32) {
5179+
map.retain(|HeapKey { object: _, offset: off }, _| *off != offset);
5180+
}
5181+
51555182
for block in self.rpo() {
5156-
let mut compile_time_heap: HashMap<(InsnId, i32), InsnId> = HashMap::new();
5183+
// First, we eliminate loads and stores
5184+
let mut compile_time_heap: HashMap<HeapKey, InsnId> = HashMap::new();
51575185
let old_insns = std::mem::take(&mut self.blocks[block.0].insns);
51585186
let mut new_insns = vec![];
5187+
51595188
for insn_id in old_insns {
5160-
let replacement_insn: InsnId = match self.find(insn_id) {
5189+
match self.find(insn_id) {
51615190
Insn::StoreField { recv, offset, val, .. } => {
5162-
let key = (self.chase_insn(recv), offset);
5191+
let key = HeapKey { object: self.chase_insn(recv), offset };
51635192
let heap_entry = compile_time_heap.get(&key).copied();
51645193
// TODO(Jacob): Switch from actual to partial equality
51655194
if Some(val) == heap_entry {
51665195
// If the value is already stored, short circuit and don't add an instruction to the block
51675196
continue
51685197
}
5169-
// TODO(Jacob): Add TBAA to avoid removing so many entries
5170-
compile_time_heap.retain(|(_, off), _| *off != offset);
5198+
remove_aliasing(&mut compile_time_heap, offset);
51715199
compile_time_heap.insert(key, val);
5172-
insn_id
51735200
},
51745201
Insn::LoadField { recv, offset, .. } => {
5175-
let key = (self.chase_insn(recv), offset);
5202+
let key = HeapKey { object: self.chase_insn(recv), offset };
51765203
match compile_time_heap.entry(key) {
51775204
std::collections::hash_map::Entry::Occupied(entry) => {
51785205
// If the value is stored already, we should short circuit.
51795206
// However, we need to replace insn_id with its representative in the SSA union.
5207+
// TODO: Show an example of why we need this with p little ascii diagram or something
51805208
self.make_equal_to(insn_id, *entry.get());
51815209
continue
51825210
}
@@ -5185,28 +5213,64 @@ impl Function {
51855213
compile_time_heap.insert(key, insn_id);
51865214
}
51875215
}
5188-
insn_id
51895216
}
51905217
Insn::WriteBarrier { .. } => {
51915218
// Currently, WriteBarrier write effects are Allocator and Memory when we'd really like them to be flags.
51925219
// We don't use LoadField for mark bits so we can ignore them for now.
51935220
// But flags does not exist in our effects abstract heap modeling and we don't want to add special casing to effects.
51945221
// This special casing in this pass here should be removed once we refine our effects system to provide greater granularity for WriteBarrier.
5195-
// TODO: use TBAA
51965222
let offset = RUBY_OFFSET_RBASIC_FLAGS;
5197-
compile_time_heap.retain(|(_, off), _| *off != offset);
5198-
insn_id
5223+
remove_aliasing(&mut compile_time_heap, offset);
51995224
},
52005225
insn => {
52015226
// If an instruction affects memory and we haven't modeled it, the compile_time_heap is invalidated
52025227
if insn.effects_of().includes(Effect::write(abstract_heaps::Memory)) {
52035228
compile_time_heap.clear();
52045229
}
5205-
insn_id
52065230
}
52075231
};
5208-
new_insns.push(replacement_insn);
5232+
new_insns.push(insn_id);
52095233
}
5234+
5235+
// After performing scalar replacement on loads and stores where
5236+
// applicable, we scan in reverse to find an eliminate dead stores
5237+
let mut reversed_insns = std::mem::take(&mut new_insns);
5238+
reversed_insns.reverse();
5239+
compile_time_heap.clear();
5240+
5241+
for insn_id in reversed_insns {
5242+
match self.find(insn_id) {
5243+
Insn::StoreField { recv, offset, .. } => {
5244+
let key = HeapKey { object: self.chase_insn(recv), offset };
5245+
if compile_time_heap.contains_key(&key) {
5246+
// We've found a second store with no uses between. eliminate the earlier one
5247+
continue
5248+
}
5249+
else {
5250+
remove_aliasing(&mut compile_time_heap, offset);
5251+
compile_time_heap.insert(key, insn_id);
5252+
}
5253+
}
5254+
Insn::LoadField{ offset, .. } => {
5255+
remove_aliasing(&mut compile_time_heap, offset);
5256+
}
5257+
Insn::WriteBarrier { .. } => {
5258+
let offset = RUBY_OFFSET_RBASIC_FLAGS;
5259+
remove_aliasing(&mut compile_time_heap, offset);
5260+
},
5261+
insn => {
5262+
// TODO: Add test case for control flow potential bugs
5263+
if insn.effects_of().includes(Effect::read(abstract_heaps::Memory)) {
5264+
compile_time_heap.clear();
5265+
}
5266+
else if insn.effects_of().includes(effects::Control) {
5267+
compile_time_heap.clear();
5268+
}
5269+
}
5270+
}
5271+
new_insns.push(insn_id);
5272+
}
5273+
new_insns.reverse();
52105274
self.blocks[block.0].insns = new_insns;
52115275
}
52125276
}
@@ -5239,9 +5303,7 @@ impl Function {
52395303
let mut active_stores: HashMap<StoreHeap, InsnId> = HashMap::new();
52405304
let insns = std::mem::take(&mut self.blocks[block.0].insns);
52415305
let mut new_insns = vec![];
5242-
for insn_id_ref in insns.iter().rev() {
5243-
// TODO: This is ugly, clean this up
5244-
let insn_id = *insn_id_ref;
5306+
for insn_id in insns.iter().rev().copied() {
52455307
match self.find(insn_id) {
52465308
Insn::StoreField { recv, offset, .. } => {
52475309
let key = StoreHeap { object: self.chase_insn(recv), offset };
@@ -5292,6 +5354,7 @@ impl Function {
52925354
// entries of active_stores are not loaded. This is because we use
52935355
// extended basic blocks and we should probably change this to be
52945356
// standard basic blocks.
5357+
// TODO: Add a test for this case
52955358
if insn.effects_of().includes(Effect::read(abstract_heaps::Memory)) {
52965359
active_stores.clear();
52975360
}
@@ -5990,7 +6053,7 @@ impl Function {
59906053
run_pass!(optimize_c_calls);
59916054
run_pass!(convert_no_profile_sends);
59926055
run_pass!(optimize_load_store);
5993-
run_pass!(eliminate_dead_stores);
6056+
// run_pass!(eliminate_dead_stores);
59946057
run_pass!(fold_constants);
59956058
run_pass!(clean_cfg);
59966059
run_pass!(remove_redundant_patch_points);

0 commit comments

Comments
 (0)