Coverage Report

Created: 2026-03-31 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
18.8k
  const char* name() const override { return "copy-propagate-arrays"; }
44
  Status Process() override;
45
46
242
  IRContext::Analysis GetPreservedAnalyses() override {
47
242
    return IRContext::kAnalysisDefUse | IRContext::kAnalysisCFG |
48
242
           IRContext::kAnalysisInstrToBlockMapping |
49
242
           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisDecorations |
50
242
           IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap |
51
242
           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
52
242
  }
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
18
    bool operator!=(const AccessChainEntry& other) const {
67
18
      return other.is_result_id != is_result_id || other.result_id != result_id;
68
18
    }
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
280
    void PopIndirection() {
95
280
      assert(IsMember());
96
280
      access_chain_.pop_back();
97
280
    }
98
99
    // Returns true if |this| represents a member of its owner, and not the
100
    // entire variable.
101
769
    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
13.8k
    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
4.38k
    const std::vector<AccessChainEntry>& AccessChain() const {
116
4.38k
      return access_chain_;
117
4.38k
    }
118
119
    // Converts all immediate values in the AccessChain their OpConstant
120
    // equivalent.
121
    // Returns false if the constants could not be created.
122
    bool BuildConstants();
123
124
    // Returns the type id of the pointer type that can be used to point to this
125
    // memory object.
126
642
    uint32_t GetPointerTypeId(const CopyPropagateArrays* pass) const {
127
642
      analysis::DefUseManager* def_use_mgr =
128
642
          GetVariable()->context()->get_def_use_mgr();
129
642
      analysis::TypeManager* type_mgr =
130
642
          GetVariable()->context()->get_type_mgr();
131
132
642
      Instruction* var_pointer_inst =
133
642
          def_use_mgr->GetDef(GetVariable()->type_id());
134
135
642
      uint32_t member_type_id = pass->GetMemberTypeId(
136
642
          var_pointer_inst->GetSingleWordInOperand(1), GetAccessIds());
137
138
642
      uint32_t member_pointer_type_id = type_mgr->FindPointerToType(
139
642
          member_type_id, static_cast<spv::StorageClass>(
140
642
                              var_pointer_inst->GetSingleWordInOperand(0)));
141
642
      return member_pointer_type_id;
142
642
    }
143
144
    // Returns the storage class of the memory object.
145
2.73k
    spv::StorageClass GetStorageClass() const {
146
2.73k
      analysis::TypeManager* type_mgr =
147
2.73k
          GetVariable()->context()->get_type_mgr();
148
2.73k
      const analysis::Pointer* pointer_type =
149
2.73k
          type_mgr->GetType(GetVariable()->type_id())->AsPointer();
150
2.73k
      return pointer_type->storage_class();
151
2.73k
    }
152
153
    // Returns true if |other| represents memory that is contains inside of the
154
    // memory represented by |this|.
155
    bool Contains(MemoryObject* other);
156
157
   private:
158
    // The variable that owns this memory object.
159
    Instruction* variable_inst_;
160
161
    // The access chain to reach the particular member the memory object
162
    // represents.  It should be interpreted the same way the indices in an
163
    // |OpAccessChain| are interpreted.
164
    std::vector<AccessChainEntry> access_chain_;
165
    std::vector<uint32_t> GetAccessIds() const;
166
  };
167
168
  // Returns the memory object being stored to |var_inst| in the store
169
  // instruction |store_inst|, if one exists, that can be used in place of
170
  // |var_inst| in all of the loads of |var_inst|.  This code is conservative
171
  // and only identifies very simple cases.  If no such memory object can be
172
  // found, the return value is |nullptr|.
173
  std::unique_ptr<CopyPropagateArrays::MemoryObject> FindSourceObjectIfPossible(
174
      Instruction* var_inst, Instruction* store_inst);
175
176
  // Replaces all loads of |var_inst| with a load from |source| instead.
177
  // |insertion_pos| is a position where it is possible to construct the
178
  // address of |source| and also dominates all of the loads of |var_inst|.
179
  // Returns false if the propagation failed.
180
  bool PropagateObject(Instruction* var_inst, MemoryObject* source,
181
                       Instruction* insertion_pos);
182
183
  // Returns true if all of the references to |ptr_inst| can be rewritten and
184
  // are dominated by |store_inst|.
185
  bool HasValidReferencesOnly(Instruction* ptr_inst, Instruction* store_inst);
186
187
  // Returns a memory object that at one time was equivalent to the value in
188
  // |result|.  If no such memory object exists, the return value is |nullptr|.
189
  std::unique_ptr<MemoryObject> GetSourceObjectIfAny(uint32_t result);
190
191
  // Returns the memory object that is loaded by |load_inst|.  If a memory
192
  // object cannot be identified, the return value is |nullptr|.  The opcode of
193
  // |load_inst| must be |OpLoad|.
194
  std::unique_ptr<MemoryObject> BuildMemoryObjectFromLoad(
195
      Instruction* load_inst);
196
197
  // Returns the memory object that at some point was equivalent to the result
198
  // of |extract_inst|.  If a memory object cannot be identified, the return
199
  // value is |nullptr|.  The opcode of |extract_inst| must be
200
  // |OpCompositeExtract|.
201
  std::unique_ptr<MemoryObject> BuildMemoryObjectFromExtract(
202
      Instruction* extract_inst);
203
204
  // Returns the memory object that at some point was equivalent to the result
205
  // of |construct_inst|.  If a memory object cannot be identified, the return
206
  // value is |nullptr|.  The opcode of |constuct_inst| must be
207
  // |OpCompositeConstruct|.
208
  std::unique_ptr<MemoryObject> BuildMemoryObjectFromCompositeConstruct(
209
      Instruction* conststruct_inst);
210
211
  // Returns the memory object that at some point was equivalent to the result
212
  // of |insert_inst|.  If a memory object cannot be identified, the return
213
  // value is |nullptr|.  The opcode of |insert_inst| must be
214
  // |OpCompositeInsert|.  This function looks for a series of
215
  // |OpCompositeInsert| instructions that insert the elements one at a time in
216
  // order from beginning to end.
217
  std::unique_ptr<MemoryObject> BuildMemoryObjectFromInsert(
218
      Instruction* insert_inst);
219
220
  // Return true if the given entry can represent the given value.
221
  bool IsAccessChainIndexValidAndEqualTo(const AccessChainEntry& entry,
222
                                         uint32_t value) const;
223
224
  // Return true if |type_id| is a pointer type whose pointee type is an array.
225
  bool IsPointerToArrayType(uint32_t type_id);
226
227
  // Return true if |inst| is one of the InterpolateAt* GLSL.std.450 extended
228
  // instructions.
229
  bool IsInterpolationInstruction(Instruction* inst);
230
231
  // Returns true if there are not stores using |ptr_inst| or something derived
232
  // from it.
233
  bool HasNoStores(Instruction* ptr_inst);
234
235
  // Creates an |OpAccessChain| instruction whose result is a pointer the memory
236
  // represented by |source|.  The new instruction will be placed before
237
  // |insertion_point|.  |insertion_point| must be part of a function.  Returns
238
  // the new instruction.
239
  Instruction* BuildNewAccessChain(Instruction* insertion_point,
240
                                   MemoryObject* source) const;
241
242
  // Rewrites all uses of |original_ptr| to use |new_pointer_inst| updating
243
  // types of other instructions as needed.  This function should not be called
244
  // if |CanUpdateUses(original_ptr_inst, new_pointer_inst->type_id())| returns
245
  // false.
246
  bool UpdateUses(Instruction* original_ptr_inst,
247
                  Instruction* new_pointer_inst);
248
249
  // Return true if |UpdateUses| is able to change all of the uses of
250
  // |original_ptr_inst| to |type_id| and still have valid code.
251
  bool CanUpdateUses(Instruction* original_ptr_inst, uint32_t type_id);
252
253
  // Returns a store to |var_inst| that writes to the entire variable, and is
254
  // the only store that does so.  Note it does not look through OpAccessChain
255
  // instruction, so partial stores are not considered.
256
  Instruction* FindStoreInstruction(const Instruction* var_inst) const;
257
258
  // Return the type id of the member of the type |id| access using
259
  // |access_chain|. The elements of |access_chain| are to be interpreted the
260
  // same way the indexes are used in an |OpCompositeExtract| instruction.
261
  uint32_t GetMemberTypeId(uint32_t id,
262
                           const std::vector<uint32_t>& access_chain) const;
263
264
  // If the result of inst is stored to a variable, add that variable to the
265
  // worklist.
266
  void AddUsesToWorklist(Instruction* inst);
267
268
  // OpVariable worklist. An instruction is added to this list if we would like
269
  // to run copy propagation on it.
270
  std::queue<Instruction*> worklist_;
271
};
272
273
}  // namespace opt
274
}  // namespace spvtools
275
276
#endif  // SOURCE_OPT_COPY_PROP_ARRAYS_H_