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, the return value will be 0. 105 uint32_t GetNumberOfMembers(); 106 107 // Returns the owning variable that the memory object is contained in. GetVariable()108 Instruction* GetVariable() const { return variable_inst_; } 109 110 // Returns a vector of integers that can be used to access the specific 111 // member that |this| represents starting from the owning variable. These 112 // values are to be interpreted the same way the indices are in an 113 // |OpAccessChain| instruction. AccessChain()114 const std::vector<AccessChainEntry>& AccessChain() const { 115 return access_chain_; 116 } 117 118 // Converts all immediate values in the AccessChain their OpConstant 119 // equivalent. 120 void BuildConstants(); 121 122 // Returns the type id of the pointer type that can be used to point to this 123 // memory object. GetPointerTypeId(const CopyPropagateArrays * pass)124 uint32_t GetPointerTypeId(const CopyPropagateArrays* pass) const { 125 analysis::DefUseManager* def_use_mgr = 126 GetVariable()->context()->get_def_use_mgr(); 127 analysis::TypeManager* type_mgr = 128 GetVariable()->context()->get_type_mgr(); 129 130 Instruction* var_pointer_inst = 131 def_use_mgr->GetDef(GetVariable()->type_id()); 132 133 uint32_t member_type_id = pass->GetMemberTypeId( 134 var_pointer_inst->GetSingleWordInOperand(1), GetAccessIds()); 135 136 uint32_t member_pointer_type_id = type_mgr->FindPointerToType( 137 member_type_id, static_cast<spv::StorageClass>( 138 var_pointer_inst->GetSingleWordInOperand(0))); 139 return member_pointer_type_id; 140 } 141 142 // Returns the storage class of the memory object. GetStorageClass()143 spv::StorageClass GetStorageClass() const { 144 analysis::TypeManager* type_mgr = 145 GetVariable()->context()->get_type_mgr(); 146 const analysis::Pointer* pointer_type = 147 type_mgr->GetType(GetVariable()->type_id())->AsPointer(); 148 return pointer_type->storage_class(); 149 } 150 151 // Returns true if |other| represents memory that is contains inside of the 152 // memory represented by |this|. 153 bool Contains(MemoryObject* other); 154 155 private: 156 // The variable that owns this memory object. 157 Instruction* variable_inst_; 158 159 // The access chain to reach the particular member the memory object 160 // represents. It should be interpreted the same way the indices in an 161 // |OpAccessChain| are interpreted. 162 std::vector<AccessChainEntry> access_chain_; 163 std::vector<uint32_t> GetAccessIds() const; 164 }; 165 166 // Returns the memory object being stored to |var_inst| in the store 167 // instruction |store_inst|, if one exists, that can be used in place of 168 // |var_inst| in all of the loads of |var_inst|. This code is conservative 169 // and only identifies very simple cases. If no such memory object can be 170 // found, the return value is |nullptr|. 171 std::unique_ptr<CopyPropagateArrays::MemoryObject> FindSourceObjectIfPossible( 172 Instruction* var_inst, Instruction* store_inst); 173 174 // Replaces all loads of |var_inst| with a load from |source| instead. 175 // |insertion_pos| is a position where it is possible to construct the 176 // address of |source| and also dominates all of the loads of |var_inst|. 177 void PropagateObject(Instruction* var_inst, MemoryObject* source, 178 Instruction* insertion_pos); 179 180 // Returns true if all of the references to |ptr_inst| can be rewritten and 181 // are dominated by |store_inst|. 182 bool HasValidReferencesOnly(Instruction* ptr_inst, Instruction* store_inst); 183 184 // Returns a memory object that at one time was equivalent to the value in 185 // |result|. If no such memory object exists, the return value is |nullptr|. 186 std::unique_ptr<MemoryObject> GetSourceObjectIfAny(uint32_t result); 187 188 // Returns the memory object that is loaded by |load_inst|. If a memory 189 // object cannot be identified, the return value is |nullptr|. The opcode of 190 // |load_inst| must be |OpLoad|. 191 std::unique_ptr<MemoryObject> BuildMemoryObjectFromLoad( 192 Instruction* load_inst); 193 194 // Returns the memory object that at some point was equivalent to the result 195 // of |extract_inst|. If a memory object cannot be identified, the return 196 // value is |nullptr|. The opcode of |extract_inst| must be 197 // |OpCompositeExtract|. 198 std::unique_ptr<MemoryObject> BuildMemoryObjectFromExtract( 199 Instruction* extract_inst); 200 201 // Returns the memory object that at some point was equivalent to the result 202 // of |construct_inst|. If a memory object cannot be identified, the return 203 // value is |nullptr|. The opcode of |constuct_inst| must be 204 // |OpCompositeConstruct|. 205 std::unique_ptr<MemoryObject> BuildMemoryObjectFromCompositeConstruct( 206 Instruction* conststruct_inst); 207 208 // Returns the memory object that at some point was equivalent to the result 209 // of |insert_inst|. If a memory object cannot be identified, the return 210 // value is |nullptr\. The opcode of |insert_inst| must be 211 // |OpCompositeInsert|. This function looks for a series of 212 // |OpCompositeInsert| instructions that insert the elements one at a time in 213 // order from beginning to end. 214 std::unique_ptr<MemoryObject> BuildMemoryObjectFromInsert( 215 Instruction* insert_inst); 216 217 // Return true if the given entry can represent the given value. 218 bool IsAccessChainIndexValidAndEqualTo(const AccessChainEntry& entry, 219 uint32_t value) const; 220 221 // Return true if |type_id| is a pointer type whose pointee type is an array. 222 bool IsPointerToArrayType(uint32_t type_id); 223 224 // Returns true if there are not stores using |ptr_inst| or something derived 225 // from it. 226 bool HasNoStores(Instruction* ptr_inst); 227 228 // Creates an |OpAccessChain| instruction whose result is a pointer the memory 229 // represented by |source|. The new instruction will be placed before 230 // |insertion_point|. |insertion_point| must be part of a function. Returns 231 // the new instruction. 232 Instruction* BuildNewAccessChain(Instruction* insertion_point, 233 MemoryObject* source) const; 234 235 // Rewrites all uses of |original_ptr| to use |new_pointer_inst| updating 236 // types of other instructions as needed. This function should not be called 237 // if |CanUpdateUses(original_ptr_inst, new_pointer_inst->type_id())| returns 238 // false. 239 void UpdateUses(Instruction* original_ptr_inst, 240 Instruction* new_pointer_inst); 241 242 // Return true if |UpdateUses| is able to change all of the uses of 243 // |original_ptr_inst| to |type_id| and still have valid code. 244 bool CanUpdateUses(Instruction* original_ptr_inst, uint32_t type_id); 245 246 // Returns a store to |var_inst| that writes to the entire variable, and is 247 // the only store that does so. Note it does not look through OpAccessChain 248 // instruction, so partial stores are not considered. 249 Instruction* FindStoreInstruction(const Instruction* var_inst) const; 250 251 // Return the type id of the member of the type |id| access using 252 // |access_chain|. The elements of |access_chain| are to be interpreted the 253 // same way the indexes are used in an |OpCompositeExtract| instruction. 254 uint32_t GetMemberTypeId(uint32_t id, 255 const std::vector<uint32_t>& access_chain) const; 256 }; 257 258 } // namespace opt 259 } // namespace spvtools 260 261 #endif // SOURCE_OPT_COPY_PROP_ARRAYS_H_ 262