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