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