/src/spirv-tools/source/opt/mem_pass.cpp
Line | Count | Source |
1 | | // Copyright (c) 2017 The Khronos Group Inc. |
2 | | // Copyright (c) 2017 Valve Corporation |
3 | | // Copyright (c) 2017 LunarG Inc. |
4 | | // |
5 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
6 | | // you may not use this file except in compliance with the License. |
7 | | // You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, software |
12 | | // distributed under the License is distributed on an "AS IS" BASIS, |
13 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 | | // See the License for the specific language governing permissions and |
15 | | // limitations under the License. |
16 | | |
17 | | #include "source/opt/mem_pass.h" |
18 | | |
19 | | #include <memory> |
20 | | #include <set> |
21 | | #include <vector> |
22 | | |
23 | | #include "source/cfa.h" |
24 | | #include "source/opt/basic_block.h" |
25 | | #include "source/opt/ir_context.h" |
26 | | |
27 | | namespace spvtools { |
28 | | namespace opt { |
29 | | namespace { |
30 | | constexpr uint32_t kCopyObjectOperandInIdx = 0; |
31 | | constexpr uint32_t kTypePointerStorageClassInIdx = 0; |
32 | | constexpr uint32_t kTypePointerTypeIdInIdx = 1; |
33 | | } // namespace |
34 | | |
35 | 620k | bool MemPass::IsBaseTargetType(const Instruction* typeInst) const { |
36 | 620k | switch (typeInst->opcode()) { |
37 | 394k | case spv::Op::OpTypeInt: |
38 | 431k | case spv::Op::OpTypeFloat: |
39 | 480k | case spv::Op::OpTypeBool: |
40 | 555k | case spv::Op::OpTypeVector: |
41 | 562k | case spv::Op::OpTypeMatrix: |
42 | 562k | case spv::Op::OpTypeImage: |
43 | 562k | case spv::Op::OpTypeSampler: |
44 | 562k | case spv::Op::OpTypeSampledImage: |
45 | 562k | case spv::Op::OpTypePointer: |
46 | 562k | case spv::Op::OpTypeCooperativeMatrixNV: |
47 | 562k | case spv::Op::OpTypeCooperativeMatrixKHR: |
48 | 562k | return true; |
49 | 57.4k | default: |
50 | 57.4k | break; |
51 | 620k | } |
52 | 57.4k | return false; |
53 | 620k | } |
54 | | |
55 | 620k | bool MemPass::IsTargetType(const Instruction* typeInst) const { |
56 | 620k | if (IsBaseTargetType(typeInst)) return true; |
57 | 57.4k | if (typeInst->opcode() == spv::Op::OpTypeArray) { |
58 | 29.0k | if (!IsTargetType( |
59 | 29.0k | get_def_use_mgr()->GetDef(typeInst->GetSingleWordOperand(1)))) { |
60 | 0 | return false; |
61 | 0 | } |
62 | 29.0k | return true; |
63 | 29.0k | } |
64 | 28.3k | if (typeInst->opcode() != spv::Op::OpTypeStruct) return false; |
65 | | // All struct members must be math type |
66 | 80.1k | return typeInst->WhileEachInId([this](const uint32_t* tid) { |
67 | 80.1k | Instruction* compTypeInst = get_def_use_mgr()->GetDef(*tid); |
68 | 80.1k | if (!IsTargetType(compTypeInst)) return false; |
69 | 80.1k | return true; |
70 | 80.1k | }); |
71 | 28.3k | } |
72 | | |
73 | 4.02M | bool MemPass::IsNonPtrAccessChain(const spv::Op opcode) const { |
74 | 4.02M | return opcode == spv::Op::OpAccessChain || |
75 | 2.46M | opcode == spv::Op::OpInBoundsAccessChain || |
76 | 2.45M | opcode == spv::Op::OpUntypedAccessChainKHR; |
77 | 4.02M | } |
78 | | |
79 | 1.45M | bool MemPass::IsPtr(uint32_t ptrId) { |
80 | 1.45M | uint32_t varId = ptrId; |
81 | 1.45M | Instruction* ptrInst = get_def_use_mgr()->GetDef(varId); |
82 | 1.45M | if (ptrInst->opcode() == spv::Op::OpFunction) { |
83 | | // A function is not a pointer, but it's return type could be, which will |
84 | | // erroneously lead to this function returning true later on |
85 | 27.7k | return false; |
86 | 27.7k | } |
87 | 1.43M | while (ptrInst->opcode() == spv::Op::OpCopyObject) { |
88 | 105 | varId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx); |
89 | 105 | ptrInst = get_def_use_mgr()->GetDef(varId); |
90 | 105 | } |
91 | 1.43M | const spv::Op op = ptrInst->opcode(); |
92 | 1.43M | if (op == spv::Op::OpVariable || op == spv::Op::OpUntypedVariableKHR || |
93 | 717k | IsNonPtrAccessChain(op)) |
94 | 1.41M | return true; |
95 | 13.9k | const uint32_t varTypeId = ptrInst->type_id(); |
96 | 13.9k | if (varTypeId == 0) return false; |
97 | 13.9k | const Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId); |
98 | 13.9k | return varTypeInst->opcode() == spv::Op::OpTypePointer || |
99 | 36 | varTypeInst->opcode() == spv::Op::OpTypeUntypedPointerKHR; |
100 | 13.9k | } |
101 | | |
102 | 7.79M | Instruction* MemPass::GetPtr(uint32_t ptrId, uint32_t* varId) { |
103 | 7.79M | *varId = ptrId; |
104 | 7.79M | Instruction* ptrInst = get_def_use_mgr()->GetDef(*varId); |
105 | 7.79M | Instruction* varInst; |
106 | | |
107 | 7.79M | switch (ptrInst->opcode()) { |
108 | 5.44M | case spv::Op::OpVariable: |
109 | 5.44M | case spv::Op::OpUntypedVariableKHR: |
110 | 5.48M | case spv::Op::OpFunctionParameter: |
111 | 5.48M | varInst = ptrInst; |
112 | 5.48M | break; |
113 | 2.28M | case spv::Op::OpAccessChain: |
114 | 2.31M | case spv::Op::OpInBoundsAccessChain: |
115 | 2.31M | case spv::Op::OpUntypedAccessChainKHR: |
116 | 2.31M | case spv::Op::OpPtrAccessChain: |
117 | 2.31M | case spv::Op::OpInBoundsPtrAccessChain: |
118 | 2.31M | case spv::Op::OpImageTexelPointer: |
119 | 2.31M | case spv::Op::OpCopyObject: |
120 | 2.31M | varInst = ptrInst->GetBaseAddress(); |
121 | 2.31M | break; |
122 | 0 | default: |
123 | 0 | *varId = 0; |
124 | 0 | return ptrInst; |
125 | 0 | break; |
126 | 7.79M | } |
127 | | |
128 | 7.79M | if (varInst->opcode() == spv::Op::OpVariable || |
129 | 7.73M | varInst->opcode() == spv::Op::OpUntypedVariableKHR) { |
130 | 7.73M | *varId = varInst->result_id(); |
131 | 7.73M | } else { |
132 | 67.0k | *varId = 0; |
133 | 67.0k | } |
134 | | |
135 | 7.79M | while (ptrInst->opcode() == spv::Op::OpCopyObject) { |
136 | 710 | uint32_t temp = ptrInst->GetSingleWordInOperand(0); |
137 | 710 | ptrInst = get_def_use_mgr()->GetDef(temp); |
138 | 710 | } |
139 | | |
140 | 7.79M | return ptrInst; |
141 | 7.79M | } |
142 | | |
143 | 6.40M | Instruction* MemPass::GetPtr(Instruction* ip, uint32_t* varId) { |
144 | 6.40M | assert(ip->opcode() == spv::Op::OpStore || ip->opcode() == spv::Op::OpLoad || |
145 | 6.40M | ip->opcode() == spv::Op::OpImageTexelPointer || |
146 | 6.40M | ip->IsAtomicWithLoad()); |
147 | | |
148 | | // All of these opcode place the pointer in position 0. |
149 | 6.40M | const uint32_t ptrId = ip->GetSingleWordInOperand(0); |
150 | 6.40M | return GetPtr(ptrId, varId); |
151 | 6.40M | } |
152 | | |
153 | 122k | bool MemPass::HasOnlyNamesAndDecorates(uint32_t id) const { |
154 | 122k | return get_def_use_mgr()->WhileEachUser(id, [this](Instruction* user) { |
155 | 94.9k | spv::Op op = user->opcode(); |
156 | 94.9k | if (op != spv::Op::OpName && !IsNonTypeDecorate(op)) { |
157 | 86.4k | return false; |
158 | 86.4k | } |
159 | 8.43k | return true; |
160 | 94.9k | }); |
161 | 122k | } |
162 | | |
163 | 121k | void MemPass::KillAllInsts(BasicBlock* bp, bool killLabel) { |
164 | 121k | bp->KillAllInsts(killLabel); |
165 | 121k | } |
166 | | |
167 | 314 | bool MemPass::HasLoads(uint32_t varId) const { |
168 | 386 | return !get_def_use_mgr()->WhileEachUser(varId, [this](Instruction* user) { |
169 | 386 | spv::Op op = user->opcode(); |
170 | | // TODO(): The following is slightly conservative. Could be |
171 | | // better handling of non-store/name. |
172 | 386 | if (IsNonPtrAccessChain(op) || op == spv::Op::OpCopyObject) { |
173 | 171 | if (HasLoads(user->result_id())) { |
174 | 111 | return false; |
175 | 111 | } |
176 | 215 | } else if (op != spv::Op::OpStore && op != spv::Op::OpName && |
177 | 114 | !IsNonTypeDecorate(op)) { |
178 | 111 | return false; |
179 | 111 | } |
180 | 164 | return true; |
181 | 386 | }); |
182 | 314 | } |
183 | | |
184 | 184 | bool MemPass::IsLiveVar(uint32_t varId) const { |
185 | 184 | const Instruction* varInst = get_def_use_mgr()->GetDef(varId); |
186 | | // assume live if not a variable eg. function parameter |
187 | 184 | if (varInst->opcode() != spv::Op::OpVariable) return true; |
188 | | // non-function scope vars are live |
189 | 184 | const uint32_t varTypeId = varInst->type_id(); |
190 | 184 | const Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId); |
191 | 184 | if (spv::StorageClass(varTypeInst->GetSingleWordInOperand( |
192 | 184 | kTypePointerStorageClassInIdx)) != spv::StorageClass::Function) |
193 | 41 | return true; |
194 | | // test if variable is loaded from |
195 | 143 | return HasLoads(varId); |
196 | 184 | } |
197 | | |
198 | 72 | void MemPass::AddStores(uint32_t ptr_id, std::queue<Instruction*>* insts) { |
199 | 78 | get_def_use_mgr()->ForEachUser(ptr_id, [this, insts](Instruction* user) { |
200 | 78 | spv::Op op = user->opcode(); |
201 | 78 | if (IsNonPtrAccessChain(op)) { |
202 | 40 | AddStores(user->result_id(), insts); |
203 | 40 | } else if (op == spv::Op::OpStore) { |
204 | 9 | insts->push(user); |
205 | 9 | } |
206 | 78 | }); |
207 | 72 | } |
208 | | |
209 | | void MemPass::DCEInst(Instruction* inst, |
210 | 30.8k | const std::function<void(Instruction*)>& call_back) { |
211 | 30.8k | std::queue<Instruction*> deadInsts; |
212 | 30.8k | deadInsts.push(inst); |
213 | 97.4k | while (!deadInsts.empty()) { |
214 | 66.5k | Instruction* di = deadInsts.front(); |
215 | | // Don't delete labels |
216 | 66.5k | if (di->opcode() == spv::Op::OpLabel) { |
217 | 0 | deadInsts.pop(); |
218 | 0 | continue; |
219 | 0 | } |
220 | | // Remember operands |
221 | 66.5k | std::set<uint32_t> ids; |
222 | 122k | di->ForEachInId([&ids](uint32_t* iid) { ids.insert(*iid); }); |
223 | 66.5k | uint32_t varId = 0; |
224 | | // Remember variable if dead load |
225 | 66.5k | if (di->opcode() == spv::Op::OpLoad) (void)GetPtr(di, &varId); |
226 | 66.5k | if (call_back) { |
227 | 66.5k | call_back(di); |
228 | 66.5k | } |
229 | 66.5k | context()->KillInst(di); |
230 | | // For all operands with no remaining uses, add their instruction |
231 | | // to the dead instruction queue. |
232 | 66.5k | for (auto id : ids) |
233 | 122k | if (HasOnlyNamesAndDecorates(id)) { |
234 | 35.6k | Instruction* odi = get_def_use_mgr()->GetDef(id); |
235 | 35.6k | if (context()->IsCombinatorInstruction(odi)) deadInsts.push(odi); |
236 | 35.6k | } |
237 | | // if a load was deleted and it was the variable's |
238 | | // last load, add all its stores to dead queue |
239 | 66.5k | if (varId != 0 && !IsLiveVar(varId)) AddStores(varId, &deadInsts); |
240 | 66.5k | deadInsts.pop(); |
241 | 66.5k | } |
242 | 30.8k | } |
243 | | |
244 | 653k | MemPass::MemPass() {} |
245 | | |
246 | 599k | bool MemPass::HasOnlySupportedRefs(uint32_t varId) { |
247 | 8.55M | return get_def_use_mgr()->WhileEachUser(varId, [this](Instruction* user) { |
248 | 8.55M | auto dbg_op = user->GetCommonDebugOpcode(); |
249 | 8.55M | if (dbg_op == CommonDebugInfoDebugDeclare || |
250 | 8.55M | dbg_op == CommonDebugInfoDebugValue) { |
251 | 0 | return true; |
252 | 0 | } |
253 | 8.55M | spv::Op op = user->opcode(); |
254 | 8.55M | if (op != spv::Op::OpStore && op != spv::Op::OpLoad && |
255 | 201k | op != spv::Op::OpName && !IsNonTypeDecorate(op)) { |
256 | 38.2k | return false; |
257 | 38.2k | } |
258 | 8.51M | return true; |
259 | 8.55M | }); |
260 | 599k | } |
261 | | |
262 | 372k | uint32_t MemPass::Type2Undef(uint32_t type_id) { |
263 | 372k | const auto uitr = type2undefs_.find(type_id); |
264 | 372k | if (uitr != type2undefs_.end()) return uitr->second; |
265 | 12.4k | const uint32_t undefId = TakeNextId(); |
266 | 12.4k | if (undefId == 0) { |
267 | 0 | return 0; |
268 | 0 | } |
269 | | |
270 | 12.4k | std::unique_ptr<Instruction> undef_inst( |
271 | 12.4k | new Instruction(context(), spv::Op::OpUndef, type_id, undefId, {})); |
272 | 12.4k | get_def_use_mgr()->AnalyzeInstDefUse(&*undef_inst); |
273 | 12.4k | get_module()->AddGlobalValue(std::move(undef_inst)); |
274 | 12.4k | type2undefs_[type_id] = undefId; |
275 | 12.4k | return undefId; |
276 | 12.4k | } |
277 | | |
278 | 4.69M | bool MemPass::IsTargetVar(uint32_t varId) { |
279 | 4.69M | if (varId == 0) { |
280 | 235k | return false; |
281 | 235k | } |
282 | | |
283 | 4.46M | if (seen_non_target_vars_.find(varId) != seen_non_target_vars_.end()) |
284 | 1.66M | return false; |
285 | 2.79M | if (seen_target_vars_.find(varId) != seen_target_vars_.end()) return true; |
286 | 601k | const Instruction* varInst = get_def_use_mgr()->GetDef(varId); |
287 | 601k | if (varInst->opcode() != spv::Op::OpVariable) return false; |
288 | 601k | const uint32_t varTypeId = varInst->type_id(); |
289 | 601k | const Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId); |
290 | 601k | if (spv::StorageClass(varTypeInst->GetSingleWordInOperand( |
291 | 601k | kTypePointerStorageClassInIdx)) != spv::StorageClass::Function) { |
292 | 90.3k | seen_non_target_vars_.insert(varId); |
293 | 90.3k | return false; |
294 | 90.3k | } |
295 | 511k | const uint32_t varPteTypeId = |
296 | 511k | varTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx); |
297 | 511k | Instruction* varPteTypeInst = get_def_use_mgr()->GetDef(varPteTypeId); |
298 | 511k | if (!IsTargetType(varPteTypeInst)) { |
299 | 9 | seen_non_target_vars_.insert(varId); |
300 | 9 | return false; |
301 | 9 | } |
302 | 511k | seen_target_vars_.insert(varId); |
303 | 511k | return true; |
304 | 511k | } |
305 | | |
306 | | // Remove all |phi| operands coming from unreachable blocks (i.e., blocks not in |
307 | | // |reachable_blocks|). There are two types of removal that this function can |
308 | | // perform: |
309 | | // |
310 | | // 1- Any operand that comes directly from an unreachable block is completely |
311 | | // removed. Since the block is unreachable, the edge between the unreachable |
312 | | // block and the block holding |phi| has been removed. |
313 | | // |
314 | | // 2- Any operand that comes via a live block and was defined at an unreachable |
315 | | // block gets its value replaced with an OpUndef value. Since the argument |
316 | | // was generated in an unreachable block, it no longer exists, so it cannot |
317 | | // be referenced. However, since the value does not reach |phi| directly |
318 | | // from the unreachable block, the operand cannot be removed from |phi|. |
319 | | // Therefore, we replace the argument value with OpUndef. |
320 | | // |
321 | | // For example, in the switch() below, assume that we want to remove the |
322 | | // argument with value %11 coming from block %41. |
323 | | // |
324 | | // [ ... ] |
325 | | // %41 = OpLabel <--- Unreachable block |
326 | | // %11 = OpLoad %int %y |
327 | | // [ ... ] |
328 | | // OpSelectionMerge %16 None |
329 | | // OpSwitch %12 %16 10 %13 13 %14 18 %15 |
330 | | // %13 = OpLabel |
331 | | // OpBranch %16 |
332 | | // %14 = OpLabel |
333 | | // OpStore %outparm %int_14 |
334 | | // OpBranch %16 |
335 | | // %15 = OpLabel |
336 | | // OpStore %outparm %int_15 |
337 | | // OpBranch %16 |
338 | | // %16 = OpLabel |
339 | | // %30 = OpPhi %int %11 %41 %int_42 %13 %11 %14 %11 %15 |
340 | | // |
341 | | // Since %41 is now an unreachable block, the first operand of |phi| needs to |
342 | | // be removed completely. But the operands (%11 %14) and (%11 %15) cannot be |
343 | | // removed because %14 and %15 are reachable blocks. Since %11 no longer exist, |
344 | | // in those arguments, we replace all references to %11 with an OpUndef value. |
345 | | // This results in |phi| looking like: |
346 | | // |
347 | | // %50 = OpUndef %int |
348 | | // [ ... ] |
349 | | // %30 = OpPhi %int %int_42 %13 %50 %14 %50 %15 |
350 | | bool MemPass::RemovePhiOperands( |
351 | 474k | Instruction* phi, const std::unordered_set<BasicBlock*>& reachable_blocks) { |
352 | 474k | std::vector<Operand> keep_operands; |
353 | 474k | uint32_t type_id = 0; |
354 | | // The id of an undefined value we've generated. |
355 | 474k | uint32_t undef_id = 0; |
356 | | |
357 | | // Traverse all the operands in |phi|. Build the new operand vector by adding |
358 | | // all the original operands from |phi| except the unwanted ones. |
359 | 2.50M | for (uint32_t i = 0; i < phi->NumOperands();) { |
360 | 2.02M | if (i < 2) { |
361 | | // The first two arguments are always preserved. |
362 | 949k | keep_operands.push_back(phi->GetOperand(i)); |
363 | 949k | ++i; |
364 | 949k | continue; |
365 | 949k | } |
366 | | |
367 | | // The remaining Phi arguments come in pairs. Index 'i' contains the |
368 | | // variable id, index 'i + 1' is the originating block id. |
369 | 2.02M | assert(i % 2 == 0 && i < phi->NumOperands() - 1 && |
370 | 1.07M | "malformed Phi arguments"); |
371 | | |
372 | 1.07M | BasicBlock* in_block = cfg()->block(phi->GetSingleWordOperand(i + 1)); |
373 | 1.07M | if (reachable_blocks.find(in_block) == reachable_blocks.end()) { |
374 | | // If the incoming block is unreachable, remove both operands as this |
375 | | // means that the |phi| has lost an incoming edge. |
376 | 141 | i += 2; |
377 | 141 | continue; |
378 | 141 | } |
379 | | |
380 | | // In all other cases, the operand must be kept but may need to be changed. |
381 | 1.07M | uint32_t arg_id = phi->GetSingleWordOperand(i); |
382 | 1.07M | Instruction* arg_def_instr = get_def_use_mgr()->GetDef(arg_id); |
383 | 1.07M | BasicBlock* def_block = context()->get_instr_block(arg_def_instr); |
384 | 1.07M | if (def_block && |
385 | 635k | reachable_blocks.find(def_block) == reachable_blocks.end()) { |
386 | | // If the current |phi| argument was defined in an unreachable block, it |
387 | | // means that this |phi| argument is no longer defined. Replace it with |
388 | | // |undef_id|. |
389 | 0 | if (!undef_id) { |
390 | 0 | type_id = arg_def_instr->type_id(); |
391 | 0 | undef_id = Type2Undef(type_id); |
392 | 0 | if (undef_id == 0) return false; |
393 | 0 | } |
394 | 0 | keep_operands.push_back( |
395 | 0 | Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, {undef_id})); |
396 | 1.07M | } else { |
397 | | // Otherwise, the argument comes from a reachable block or from no block |
398 | | // at all (meaning that it was defined in the global section of the |
399 | | // program). In both cases, keep the argument intact. |
400 | 1.07M | keep_operands.push_back(phi->GetOperand(i)); |
401 | 1.07M | } |
402 | | |
403 | 1.07M | keep_operands.push_back(phi->GetOperand(i + 1)); |
404 | | |
405 | 1.07M | i += 2; |
406 | 1.07M | } |
407 | | |
408 | 474k | context()->ForgetUses(phi); |
409 | 474k | phi->ReplaceOperands(keep_operands); |
410 | 474k | context()->AnalyzeUses(phi); |
411 | 474k | return true; |
412 | 474k | } |
413 | | |
414 | 131k | void MemPass::RemoveBlock(Function::iterator* bi) { |
415 | 131k | auto& rm_block = **bi; |
416 | | |
417 | | // Remove instructions from the block. |
418 | 556k | rm_block.ForEachInst([&rm_block, this](Instruction* inst) { |
419 | | // Note that we do not kill the block label instruction here. The label |
420 | | // instruction is needed to identify the block, which is needed by the |
421 | | // removal of phi operands. |
422 | 556k | if (inst != rm_block.GetLabelInst()) { |
423 | 424k | context()->KillInst(inst); |
424 | 424k | } |
425 | 556k | }); |
426 | | |
427 | | // Remove the label instruction last. |
428 | 131k | auto label = rm_block.GetLabelInst(); |
429 | 131k | context()->KillInst(label); |
430 | | |
431 | 131k | *bi = bi->Erase(); |
432 | 131k | } |
433 | | |
434 | 77.3k | Pass::Status MemPass::RemoveUnreachableBlocks(Function* func) { |
435 | 77.3k | if (func->IsDeclaration()) return Status::SuccessWithoutChange; |
436 | 77.3k | bool modified = false; |
437 | | |
438 | | // Mark reachable all blocks reachable from the function's entry block. |
439 | 77.3k | std::unordered_set<BasicBlock*> reachable_blocks; |
440 | 77.3k | std::unordered_set<BasicBlock*> visited_blocks; |
441 | 77.3k | std::queue<BasicBlock*> worklist; |
442 | 77.3k | reachable_blocks.insert(func->entry().get()); |
443 | | |
444 | | // Initially mark the function entry point as reachable. |
445 | 77.3k | worklist.push(func->entry().get()); |
446 | | |
447 | 77.3k | auto mark_reachable = [&reachable_blocks, &visited_blocks, &worklist, |
448 | 4.60M | this](uint32_t label_id) { |
449 | 4.60M | auto successor = cfg()->block(label_id); |
450 | 4.60M | if (visited_blocks.count(successor) == 0) { |
451 | 3.15M | reachable_blocks.insert(successor); |
452 | 3.15M | worklist.push(successor); |
453 | 3.15M | visited_blocks.insert(successor); |
454 | 3.15M | } |
455 | 4.60M | }; |
456 | | |
457 | | // Transitively mark all blocks reachable from the entry as reachable. |
458 | 3.31M | while (!worklist.empty()) { |
459 | 3.23M | BasicBlock* block = worklist.front(); |
460 | 3.23M | worklist.pop(); |
461 | | |
462 | | // All the successors of a live block are also live. |
463 | 3.23M | static_cast<const BasicBlock*>(block)->ForEachSuccessorLabel( |
464 | 3.23M | mark_reachable); |
465 | | |
466 | | // All the Merge and ContinueTarget blocks of a live block are also live. |
467 | 3.23M | block->ForMergeAndContinueLabel(mark_reachable); |
468 | 3.23M | } |
469 | | |
470 | | // Update operands of Phi nodes that reference unreachable blocks. |
471 | 3.36M | for (auto& block : *func) { |
472 | | // If the block is about to be removed, don't bother updating its |
473 | | // Phi instructions. |
474 | 3.36M | if (reachable_blocks.count(&block) == 0) { |
475 | 131k | continue; |
476 | 131k | } |
477 | | |
478 | | // If the block is reachable and has Phi instructions, remove all |
479 | | // operands from its Phi instructions that reference unreachable blocks. |
480 | | // If the block has no Phi instructions, this is a no-op. |
481 | 3.23M | bool success = |
482 | 3.23M | block.WhileEachPhiInst([&reachable_blocks, this](Instruction* phi) { |
483 | 474k | return RemovePhiOperands(phi, reachable_blocks); |
484 | 474k | }); |
485 | 3.23M | if (!success) return Status::Failure; |
486 | 3.23M | } |
487 | | |
488 | | // Erase unreachable blocks. |
489 | 3.44M | for (auto ebi = func->begin(); ebi != func->end();) { |
490 | 3.36M | if (reachable_blocks.count(&*ebi) == 0) { |
491 | 131k | RemoveBlock(&ebi); |
492 | 131k | modified = true; |
493 | 3.23M | } else { |
494 | 3.23M | ++ebi; |
495 | 3.23M | } |
496 | 3.36M | } |
497 | | |
498 | 77.3k | return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; |
499 | 77.3k | } |
500 | | |
501 | 77.3k | Pass::Status MemPass::CFGCleanup(Function* func) { |
502 | 77.3k | return RemoveUnreachableBlocks(func); |
503 | 77.3k | } |
504 | | |
505 | 27.5k | void MemPass::CollectTargetVars(Function* func) { |
506 | 27.5k | seen_target_vars_.clear(); |
507 | 27.5k | seen_non_target_vars_.clear(); |
508 | 27.5k | type2undefs_.clear(); |
509 | | |
510 | | // Collect target (and non-) variable sets. Remove variables with |
511 | | // non-load/store refs from target variable set |
512 | 722k | for (auto& blk : *func) { |
513 | 3.82M | for (auto& inst : blk) { |
514 | 3.82M | switch (inst.opcode()) { |
515 | 504k | case spv::Op::OpStore: |
516 | 1.10M | case spv::Op::OpLoad: { |
517 | 1.10M | uint32_t varId; |
518 | 1.10M | (void)GetPtr(&inst, &varId); |
519 | 1.10M | if (!IsTargetVar(varId)) break; |
520 | 599k | if (HasOnlySupportedRefs(varId)) break; |
521 | 38.2k | seen_non_target_vars_.insert(varId); |
522 | 38.2k | seen_target_vars_.erase(varId); |
523 | 38.2k | } break; |
524 | 2.72M | default: |
525 | 2.72M | break; |
526 | 3.82M | } |
527 | 3.82M | } |
528 | 722k | } |
529 | 27.5k | } |
530 | | |
531 | | } // namespace opt |
532 | | } // namespace spvtools |