1 // Copyright (c) 2018 Google LLC. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef SOURCE_OPT_COPY_PROP_ARRAYS_H_ 16 #define SOURCE_OPT_COPY_PROP_ARRAYS_H_ 17 18 #include <memory> 19 #include <vector> 20 21 #include "source/opt/mem_pass.h" 22 23 namespace spvtools { 24 namespace opt { 25 26 // This pass implements a simple array copy propagation. It does not do a full 27 // array data flow. It looks for simple cases that meet the following 28 // conditions: 29 // 30 // 1) The source must never be stored to. 31 // 2) The target must be stored to exactly once. 32 // 3) The store to the target must be a store to the entire array, and be a 33 // copy of the entire source. 34 // 4) All loads of the target must be dominated by the store. 35 // 36 // The hard part is keeping all of the types correct. We do not want to 37 // have to do too large a search to update everything, which may not be 38 // possible, do we give up if we see any instruction that might be hard to 39 // update. 40 41 class CopyPropagateArrays : public MemPass { 42 public: name()43 const char* name() const override { return "copy-propagate-arrays"; } 44 Status Process() override; 45 GetPreservedAnalyses()46 IRContext::Analysis GetPreservedAnalyses() override { 47 return IRContext::kAnalysisDefUse | IRContext::kAnalysisCFG | 48 IRContext::kAnalysisInstrToBlockMapping | 49 IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisDecorations | 50 IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap; 51 } 52 53 private: 54 // The class used to identify a particular memory object. This memory object 55 // will be owned by a particular variable, meaning that the memory is part of 56 // that variable. It could be the entire variable or a member of the 57 // variable. 58 class MemoryObject { 59 public: 60 // Construction a memory object that is owned by |var_inst|. The iterator 61 // |begin| and |end| traverse a container of integers that identify which 62 // member of |var_inst| this memory object will represent. These integers 63 // are interpreted the same way they would be in an |OpAccessChain| 64 // instruction. 65 template <class iterator> 66 MemoryObject(Instruction* var_inst, iterator begin, iterator end); 67 68 // Change |this| to now point to the member identified by |access_chain| 69 // (starting from the current member). The elements in |access_chain| are 70 // interpreted the same as the indices in the |OpAccessChain| 71 // instruction. 72 void GetMember(const std::vector<uint32_t>& access_chain); 73 74 // Change |this| to now represent the first enclosing object to which it 75 // belongs. (Remove the last element off the access_chain). It is invalid 76 // to call this function if |this| does not represent a member of its owner. GetParent()77 void GetParent() { 78 assert(IsMember()); 79 access_chain_.pop_back(); 80 } 81 82 // Returns true if |this| represents a member of its owner, and not the 83 // entire variable. IsMember()84 bool IsMember() const { return !access_chain_.empty(); } 85 86 // Returns the number of members in the object represented by |this|. If 87 // |this| does not represent a composite type, the return value will be 0. 88 uint32_t GetNumberOfMembers(); 89 90 // Returns the owning variable that the memory object is contained in. GetVariable()91 Instruction* GetVariable() const { return variable_inst_; } 92 93 // Returns a vector of integers that can be used to access the specific 94 // member that |this| represents starting from the owning variable. These 95 // values are to be interpreted the same way the indices are in an 96 // |OpAccessChain| instruction. AccessChain()97 const std::vector<uint32_t>& AccessChain() const { return access_chain_; } 98 99 // Returns the type id of the pointer type that can be used to point to this 100 // memory object. GetPointerTypeId(const CopyPropagateArrays * pass)101 uint32_t GetPointerTypeId(const CopyPropagateArrays* pass) const { 102 analysis::DefUseManager* def_use_mgr = 103 GetVariable()->context()->get_def_use_mgr(); 104 analysis::TypeManager* type_mgr = 105 GetVariable()->context()->get_type_mgr(); 106 107 Instruction* var_pointer_inst = 108 def_use_mgr->GetDef(GetVariable()->type_id()); 109 110 uint32_t member_type_id = pass->GetMemberTypeId( 111 var_pointer_inst->GetSingleWordInOperand(1), GetAccessIds()); 112 113 uint32_t member_pointer_type_id = type_mgr->FindPointerToType( 114 member_type_id, static_cast<SpvStorageClass>( 115 var_pointer_inst->GetSingleWordInOperand(0))); 116 return member_pointer_type_id; 117 } 118 119 // Returns the storage class of the memory object. GetStorageClass()120 SpvStorageClass GetStorageClass() const { 121 analysis::TypeManager* type_mgr = 122 GetVariable()->context()->get_type_mgr(); 123 const analysis::Pointer* pointer_type = 124 type_mgr->GetType(GetVariable()->type_id())->AsPointer(); 125 return pointer_type->storage_class(); 126 } 127 128 // Returns true if |other| represents memory that is contains inside of the 129 // memory represented by |this|. 130 bool Contains(MemoryObject* other); 131 132 private: 133 // The variable that owns this memory object. 134 Instruction* variable_inst_; 135 136 // The access chain to reach the particular member the memory object 137 // represents. It should be interpreted the same way the indices in an 138 // |OpAccessChain| are interpreted. 139 std::vector<uint32_t> access_chain_; 140 std::vector<uint32_t> GetAccessIds() const; 141 }; 142 143 // Returns the memory object being stored to |var_inst| in the store 144 // instruction |store_inst|, if one exists, that can be used in place of 145 // |var_inst| in all of the loads of |var_inst|. This code is conservative 146 // and only identifies very simple cases. If no such memory object can be 147 // found, the return value is |nullptr|. 148 std::unique_ptr<CopyPropagateArrays::MemoryObject> FindSourceObjectIfPossible( 149 Instruction* var_inst, Instruction* store_inst); 150 151 // Replaces all loads of |var_inst| with a load from |source| instead. 152 // |insertion_pos| is a position where it is possible to construct the 153 // address of |source| and also dominates all of the loads of |var_inst|. 154 void PropagateObject(Instruction* var_inst, MemoryObject* source, 155 Instruction* insertion_pos); 156 157 // Returns true if all of the references to |ptr_inst| can be rewritten and 158 // are dominated by |store_inst|. 159 bool HasValidReferencesOnly(Instruction* ptr_inst, Instruction* store_inst); 160 161 // Returns a memory object that at one time was equivalent to the value in 162 // |result|. If no such memory object exists, the return value is |nullptr|. 163 std::unique_ptr<MemoryObject> GetSourceObjectIfAny(uint32_t result); 164 165 // Returns the memory object that is loaded by |load_inst|. If a memory 166 // object cannot be identified, the return value is |nullptr|. The opcode of 167 // |load_inst| must be |OpLoad|. 168 std::unique_ptr<MemoryObject> BuildMemoryObjectFromLoad( 169 Instruction* load_inst); 170 171 // Returns the memory object that at some point was equivalent to the result 172 // of |extract_inst|. If a memory object cannot be identified, the return 173 // value is |nullptr|. The opcode of |extract_inst| must be 174 // |OpCompositeExtract|. 175 std::unique_ptr<MemoryObject> BuildMemoryObjectFromExtract( 176 Instruction* extract_inst); 177 178 // Returns the memory object that at some point was equivalent to the result 179 // of |construct_inst|. If a memory object cannot be identified, the return 180 // value is |nullptr|. The opcode of |constuct_inst| must be 181 // |OpCompositeConstruct|. 182 std::unique_ptr<MemoryObject> BuildMemoryObjectFromCompositeConstruct( 183 Instruction* conststruct_inst); 184 185 // Returns the memory object that at some point was equivalent to the result 186 // of |insert_inst|. If a memory object cannot be identified, the return 187 // value is |nullptr\. The opcode of |insert_inst| must be 188 // |OpCompositeInsert|. This function looks for a series of 189 // |OpCompositeInsert| instructions that insert the elements one at a time in 190 // order from beginning to end. 191 std::unique_ptr<MemoryObject> BuildMemoryObjectFromInsert( 192 Instruction* insert_inst); 193 194 // Return true if |type_id| is a pointer type whose pointee type is an array. 195 bool IsPointerToArrayType(uint32_t type_id); 196 197 // Returns true of there are not stores using |ptr_inst| or something derived 198 // from it. 199 bool HasNoStores(Instruction* ptr_inst); 200 201 // Creates an |OpAccessChain| instruction whose result is a pointer the memory 202 // represented by |source|. The new instruction will be placed before 203 // |insertion_point|. |insertion_point| must be part of a function. Returns 204 // the new instruction. 205 Instruction* BuildNewAccessChain(Instruction* insertion_point, 206 MemoryObject* source) const; 207 208 // Rewrites all uses of |original_ptr| to use |new_pointer_inst| updating 209 // types of other instructions as needed. This function should not be called 210 // if |CanUpdateUses(original_ptr_inst, new_pointer_inst->type_id())| returns 211 // false. 212 void UpdateUses(Instruction* original_ptr_inst, 213 Instruction* new_pointer_inst); 214 215 // Return true if |UpdateUses| is able to change all of the uses of 216 // |original_ptr_inst| to |type_id| and still have valid code. 217 bool CanUpdateUses(Instruction* original_ptr_inst, uint32_t type_id); 218 219 // Returns the id whose value is the same as |object_to_copy| except its type 220 // is |new_type_id|. Any instructions need to generate this value will be 221 // inserted before |insertion_position|. 222 uint32_t GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id, 223 Instruction* insertion_position); 224 225 // Returns a store to |var_inst| that writes to the entire variable, and is 226 // the only store that does so. Note it does not look through OpAccessChain 227 // instruction, so partial stores are not considered. 228 Instruction* FindStoreInstruction(const Instruction* var_inst) const; 229 230 // Return the type id of the member of the type |id| access using 231 // |access_chain|. The elements of |access_chain| are to be interpreted the 232 // same way the indexes are used in an |OpCompositeExtract| instruction. 233 uint32_t GetMemberTypeId(uint32_t id, 234 const std::vector<uint32_t>& access_chain) const; 235 }; 236 237 } // namespace opt 238 } // namespace spvtools 239 240 #endif // SOURCE_OPT_COPY_PROP_ARRAYS_H_ 241