/src/spirv-tools/source/opt/copy_prop_arrays.h
Line | Count | Source |
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: |
43 | 14.1k | const char* name() const override { return "copy-propagate-arrays"; } |
44 | | Status Process() override; |
45 | | |
46 | 126 | IRContext::Analysis GetPreservedAnalyses() override { |
47 | 126 | return IRContext::kAnalysisDefUse | IRContext::kAnalysisCFG | |
48 | 126 | IRContext::kAnalysisInstrToBlockMapping | |
49 | 126 | IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisDecorations | |
50 | 126 | IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap | |
51 | 126 | IRContext::kAnalysisConstants | IRContext::kAnalysisTypes; |
52 | 126 | } |
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 | 3 | bool operator!=(const AccessChainEntry& other) const { |
67 | 3 | return other.is_result_id != is_result_id || other.result_id != result_id; |
68 | 3 | } |
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. |
94 | 29 | void PopIndirection() { |
95 | 29 | assert(IsMember()); |
96 | 29 | access_chain_.pop_back(); |
97 | 29 | } |
98 | | |
99 | | // Returns true if |this| represents a member of its owner, and not the |
100 | | // entire variable. |
101 | 130 | 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. |
109 | 9.02k | 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. |
115 | 1.65k | const std::vector<AccessChainEntry>& AccessChain() const { |
116 | 1.65k | return access_chain_; |
117 | 1.65k | } |
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. |
125 | 321 | uint32_t GetPointerTypeId(const CopyPropagateArrays* pass) const { |
126 | 321 | analysis::DefUseManager* def_use_mgr = |
127 | 321 | GetVariable()->context()->get_def_use_mgr(); |
128 | 321 | analysis::TypeManager* type_mgr = |
129 | 321 | GetVariable()->context()->get_type_mgr(); |
130 | | |
131 | 321 | Instruction* var_pointer_inst = |
132 | 321 | def_use_mgr->GetDef(GetVariable()->type_id()); |
133 | | |
134 | 321 | uint32_t member_type_id = pass->GetMemberTypeId( |
135 | 321 | var_pointer_inst->GetSingleWordInOperand(1), GetAccessIds()); |
136 | | |
137 | 321 | uint32_t member_pointer_type_id = type_mgr->FindPointerToType( |
138 | 321 | member_type_id, static_cast<spv::StorageClass>( |
139 | 321 | var_pointer_inst->GetSingleWordInOperand(0))); |
140 | 321 | return member_pointer_type_id; |
141 | 321 | } |
142 | | |
143 | | // Returns the storage class of the memory object. |
144 | 1.83k | spv::StorageClass GetStorageClass() const { |
145 | 1.83k | analysis::TypeManager* type_mgr = |
146 | 1.83k | GetVariable()->context()->get_type_mgr(); |
147 | 1.83k | const analysis::Pointer* pointer_type = |
148 | 1.83k | type_mgr->GetType(GetVariable()->type_id())->AsPointer(); |
149 | 1.83k | return pointer_type->storage_class(); |
150 | 1.83k | } |
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 | | // Return true if |inst| is one of the InterpolateAt* GLSL.std.450 extended |
226 | | // instructions. |
227 | | bool IsInterpolationInstruction(Instruction* inst); |
228 | | |
229 | | // Returns true if there are not stores using |ptr_inst| or something derived |
230 | | // from it. |
231 | | bool HasNoStores(Instruction* ptr_inst); |
232 | | |
233 | | // Creates an |OpAccessChain| instruction whose result is a pointer the memory |
234 | | // represented by |source|. The new instruction will be placed before |
235 | | // |insertion_point|. |insertion_point| must be part of a function. Returns |
236 | | // the new instruction. |
237 | | Instruction* BuildNewAccessChain(Instruction* insertion_point, |
238 | | MemoryObject* source) const; |
239 | | |
240 | | // Rewrites all uses of |original_ptr| to use |new_pointer_inst| updating |
241 | | // types of other instructions as needed. This function should not be called |
242 | | // if |CanUpdateUses(original_ptr_inst, new_pointer_inst->type_id())| returns |
243 | | // false. |
244 | | void UpdateUses(Instruction* original_ptr_inst, |
245 | | Instruction* new_pointer_inst); |
246 | | |
247 | | // Return true if |UpdateUses| is able to change all of the uses of |
248 | | // |original_ptr_inst| to |type_id| and still have valid code. |
249 | | bool CanUpdateUses(Instruction* original_ptr_inst, uint32_t type_id); |
250 | | |
251 | | // Returns a store to |var_inst| that writes to the entire variable, and is |
252 | | // the only store that does so. Note it does not look through OpAccessChain |
253 | | // instruction, so partial stores are not considered. |
254 | | Instruction* FindStoreInstruction(const Instruction* var_inst) const; |
255 | | |
256 | | // Return the type id of the member of the type |id| access using |
257 | | // |access_chain|. The elements of |access_chain| are to be interpreted the |
258 | | // same way the indexes are used in an |OpCompositeExtract| instruction. |
259 | | uint32_t GetMemberTypeId(uint32_t id, |
260 | | const std::vector<uint32_t>& access_chain) const; |
261 | | |
262 | | // If the result of inst is stored to a variable, add that variable to the |
263 | | // worklist. |
264 | | void AddUsesToWorklist(Instruction* inst); |
265 | | |
266 | | // OpVariable worklist. An instruction is added to this list if we would like |
267 | | // to run copy propagation on it. |
268 | | std::queue<Instruction*> worklist_; |
269 | | }; |
270 | | |
271 | | } // namespace opt |
272 | | } // namespace spvtools |
273 | | |
274 | | #endif // SOURCE_OPT_COPY_PROP_ARRAYS_H_ |