@@ -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