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, so 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 IRContext::kAnalysisConstants | IRContext::kAnalysisTypes; 52 } 53 54 private: 55 // Represents one index in the OpAccessChain instruction. It can be either 56 // an instruction's result_id (OpConstant by ex), or a immediate value. 57 // Immediate values are used to prepare the final access chain without 58 // creating OpConstant instructions until done. 59 struct AccessChainEntry { 60 bool is_result_id; 61 union { 62 uint32_t result_id; 63 uint32_t immediate; 64 }; 65 66 bool operator!=(const AccessChainEntry& other) const { 67 return other.is_result_id != is_result_id || other.result_id != result_id; 68 } 69 }; 70 71 // The class used to identify a particular memory object. This memory object 72 // will be owned by a particular variable, meaning that the memory is part of 73 // that variable. It could be the entire variable or a member of the 74 // variable. 75 class MemoryObject { 76 public: 77 // Construction a memory object that is owned by |var_inst|. The iterator 78 // |begin| and |end| traverse a container of integers that identify which 79 // member of |var_inst| this memory object will represent. These integers 80 // are interpreted the same way they would be in an |OpAccessChain| 81 // instruction. 82 template <class iterator> 83 MemoryObject(Instruction* var_inst, iterator begin, iterator end); 84 85 // Change |this| to now point to the member identified by |access_chain| 86 // (starting from the current member). The elements in |access_chain| are 87 // interpreted the same as the indices in the |OpAccessChain| 88 // instruction. 89 void PushIndirection(const std::vector<AccessChainEntry>& access_chain); 90 91 // Change |this| to now represent the first enclosing object to which it 92 // belongs. (Remove the last element off the access_chain). It is invalid 93 // to call this function if |this| does not represent a member of its owner. PopIndirection()94 void PopIndirection() { 95 assert(IsMember()); 96 access_chain_.pop_back(); 97 } 98 99 // Returns true if |this| represents a member of its owner, and not the 100 // entire variable. IsMember()101 bool IsMember() const { return !access_chain_.empty(); } 102 103 // Returns the number of members in the object represented by |this|. If 104 // |this| does not represent a composite type or the number of components is 105 // not known at compile time, the return value will be 0. 106 uint32_t GetNumberOfMembers(); 107 108 // Returns the owning variable that the memory object is contained in. GetVariable()109 Instruction* GetVariable() const { return variable_inst_; } 110 111 // Returns a vector of integers that can be used to access the specific 112 // member that |this| represents starting from the owning variable. These 113 // values are to be interpreted the same way the indices are in an 114 // |OpAccessChain| instruction. AccessChain()115 const std::vector<AccessChainEntry>& AccessChain() const { 116 return access_chain_; 117 } 118 119 // Converts all immediate values in the AccessChain their OpConstant 120 // equivalent. 121 void BuildConstants(); 122 123 // Returns the type id of the pointer type that can be used to point to this 124 // memory object. GetPointerTypeId(const CopyPropagateArrays * pass)125 uint32_t GetPointerTypeId(const CopyPropagateArrays* pass) const { 126 analysis::DefUseManager* def_use_mgr = 127 GetVariable()->context()->get_def_use_mgr(); 128 analysis::TypeManager* type_mgr = 129 GetVariable()->context()->get_type_mgr(); 130 131 Instruction* var_pointer_inst = 132 def_use_mgr->GetDef(GetVariable()->type_id()); 133 134 uint32_t member_type_id = pass->GetMemberTypeId( 135 var_pointer_inst->GetSingleWordInOperand(1), GetAccessIds()); 136 137 uint32_t member_pointer_type_id = type_mgr->FindPointerToType( 138 member_type_id, static_cast<spv::StorageClass>( 139 var_pointer_inst->GetSingleWordInOperand(0))); 140 return member_pointer_type_id; 141 } 142 143 // Returns the storage class of the memory object. GetStorageClass()144 spv::StorageClass GetStorageClass() const { 145 analysis::TypeManager* type_mgr = 146 GetVariable()->context()->get_type_mgr(); 147 const analysis::Pointer* pointer_type = 148 type_mgr->GetType(GetVariable()->type_id())->AsPointer(); 149 return pointer_type->storage_class(); 150 } 151 152 // Returns true if |other| represents memory that is contains inside of the 153 // memory represented by |this|. 154 bool Contains(MemoryObject* other); 155 156 private: 157 // The variable that owns this memory object. 158 Instruction* variable_inst_; 159 160 // The access chain to reach the particular member the memory object 161 // represents. It should be interpreted the same way the indices in an 162 // |OpAccessChain| are interpreted. 163 std::vector<AccessChainEntry> access_chain_; 164 std::vector<uint32_t> GetAccessIds() const; 165 }; 166 167 // Returns the memory object being stored to |var_inst| in the store 168 // instruction |store_inst|, if one exists, that can be used in place of 169 // |var_inst| in all of the loads of |var_inst|. This code is conservative 170 // and only identifies very simple cases. If no such memory object can be 171 // found, the return value is |nullptr|. 172 std::unique_ptr<CopyPropagateArrays::MemoryObject> FindSourceObjectIfPossible( 173 Instruction* var_inst, Instruction* store_inst); 174 175 // Replaces all loads of |var_inst| with a load from |source| instead. 176 // |insertion_pos| is a position where it is possible to construct the 177 // address of |source| and also dominates all of the loads of |var_inst|. 178 void PropagateObject(Instruction* var_inst, MemoryObject* source, 179 Instruction* insertion_pos); 180 181 // Returns true if all of the references to |ptr_inst| can be rewritten and 182 // are dominated by |store_inst|. 183 bool HasValidReferencesOnly(Instruction* ptr_inst, Instruction* store_inst); 184 185 // Returns a memory object that at one time was equivalent to the value in 186 // |result|. If no such memory object exists, the return value is |nullptr|. 187 std::unique_ptr<MemoryObject> GetSourceObjectIfAny(uint32_t result); 188 189 // Returns the memory object that is loaded by |load_inst|. If a memory 190 // object cannot be identified, the return value is |nullptr|. The opcode of 191 // |load_inst| must be |OpLoad|. 192 std::unique_ptr<MemoryObject> BuildMemoryObjectFromLoad( 193 Instruction* load_inst); 194 195 // Returns the memory object that at some point was equivalent to the result 196 // of |extract_inst|. If a memory object cannot be identified, the return 197 // value is |nullptr|. The opcode of |extract_inst| must be 198 // |OpCompositeExtract|. 199 std::unique_ptr<MemoryObject> BuildMemoryObjectFromExtract( 200 Instruction* extract_inst); 201 202 // Returns the memory object that at some point was equivalent to the result 203 // of |construct_inst|. If a memory object cannot be identified, the return 204 // value is |nullptr|. The opcode of |constuct_inst| must be 205 // |OpCompositeConstruct|. 206 std::unique_ptr<MemoryObject> BuildMemoryObjectFromCompositeConstruct( 207 Instruction* conststruct_inst); 208 209 // Returns the memory object that at some point was equivalent to the result 210 // of |insert_inst|. If a memory object cannot be identified, the return 211 // value is |nullptr|. The opcode of |insert_inst| must be 212 // |OpCompositeInsert|. This function looks for a series of 213 // |OpCompositeInsert| instructions that insert the elements one at a time in 214 // order from beginning to end. 215 std::unique_ptr<MemoryObject> BuildMemoryObjectFromInsert( 216 Instruction* insert_inst); 217 218 // Return true if the given entry can represent the given value. 219 bool IsAccessChainIndexValidAndEqualTo(const AccessChainEntry& entry, 220 uint32_t value) const; 221 222 // Return true if |type_id| is a pointer type whose pointee type is an array. 223 bool IsPointerToArrayType(uint32_t type_id); 224 225 // Returns true if there are not stores using |ptr_inst| or something derived 226 // from it. 227 bool HasNoStores(Instruction* ptr_inst); 228 229 // Creates an |OpAccessChain| instruction whose result is a pointer the memory 230 // represented by |source|. The new instruction will be placed before 231 // |insertion_point|. |insertion_point| must be part of a function. Returns 232 // the new instruction. 233 Instruction* BuildNewAccessChain(Instruction* insertion_point, 234 MemoryObject* source) const; 235 236 // Rewrites all uses of |original_ptr| to use |new_pointer_inst| updating 237 // types of other instructions as needed. This function should not be called 238 // if |CanUpdateUses(original_ptr_inst, new_pointer_inst->type_id())| returns 239 // false. 240 void UpdateUses(Instruction* original_ptr_inst, 241 Instruction* new_pointer_inst); 242 243 // Return true if |UpdateUses| is able to change all of the uses of 244 // |original_ptr_inst| to |type_id| and still have valid code. 245 bool CanUpdateUses(Instruction* original_ptr_inst, uint32_t type_id); 246 247 // Returns a store to |var_inst| that writes to the entire variable, and is 248 // the only store that does so. Note it does not look through OpAccessChain 249 // instruction, so partial stores are not considered. 250 Instruction* FindStoreInstruction(const Instruction* var_inst) const; 251 252 // Return the type id of the member of the type |id| access using 253 // |access_chain|. The elements of |access_chain| are to be interpreted the 254 // same way the indexes are used in an |OpCompositeExtract| instruction. 255 uint32_t GetMemberTypeId(uint32_t id, 256 const std::vector<uint32_t>& access_chain) const; 257 }; 258 259 } // namespace opt 260 } // namespace spvtools 261 262 #endif // SOURCE_OPT_COPY_PROP_ARRAYS_H_ 263