• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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