1 // Copyright (c) 2017 The Khronos Group Inc.
2 // Copyright (c) 2017 Valve Corporation
3 // Copyright (c) 2017 LunarG Inc.
4 // Copyright (c) 2018 Google LLC
5 //
6 // Licensed under the Apache License, Version 2.0 (the "License");
7 // you may not use this file except in compliance with the License.
8 // You may obtain a copy of the License at
9 //
10 // http://www.apache.org/licenses/LICENSE-2.0
11 //
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17
18 #include "source/opt/aggressive_dead_code_elim_pass.h"
19
20 #include <memory>
21 #include <stack>
22
23 #include "source/cfa.h"
24 #include "source/latest_version_glsl_std_450_header.h"
25 #include "source/opt/iterator.h"
26 #include "source/opt/reflect.h"
27
28 namespace spvtools {
29 namespace opt {
30
31 namespace {
32
33 const uint32_t kTypePointerStorageClassInIdx = 0;
34 const uint32_t kEntryPointFunctionIdInIdx = 1;
35 const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
36 const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
37 const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
38 const uint32_t kCopyMemoryTargetAddrInIdx = 0;
39 const uint32_t kCopyMemorySourceAddrInIdx = 1;
40
41 // Sorting functor to present annotation instructions in an easy-to-process
42 // order. The functor orders by opcode first and falls back on unique id
43 // ordering if both instructions have the same opcode.
44 //
45 // Desired priority:
46 // SpvOpGroupDecorate
47 // SpvOpGroupMemberDecorate
48 // SpvOpDecorate
49 // SpvOpMemberDecorate
50 // SpvOpDecorateId
51 // SpvOpDecorateStringGOOGLE
52 // SpvOpDecorationGroup
53 struct DecorationLess {
operator ()spvtools::opt::__anon7369ce0a0111::DecorationLess54 bool operator()(const Instruction* lhs, const Instruction* rhs) const {
55 assert(lhs && rhs);
56 SpvOp lhsOp = lhs->opcode();
57 SpvOp rhsOp = rhs->opcode();
58 if (lhsOp != rhsOp) {
59 #define PRIORITY_CASE(opcode) \
60 if (lhsOp == opcode && rhsOp != opcode) return true; \
61 if (rhsOp == opcode && lhsOp != opcode) return false;
62 // OpGroupDecorate and OpGroupMember decorate are highest priority to
63 // eliminate dead targets early and simplify subsequent checks.
64 PRIORITY_CASE(SpvOpGroupDecorate)
65 PRIORITY_CASE(SpvOpGroupMemberDecorate)
66 PRIORITY_CASE(SpvOpDecorate)
67 PRIORITY_CASE(SpvOpMemberDecorate)
68 PRIORITY_CASE(SpvOpDecorateId)
69 PRIORITY_CASE(SpvOpDecorateStringGOOGLE)
70 // OpDecorationGroup is lowest priority to ensure use/def chains remain
71 // usable for instructions that target this group.
72 PRIORITY_CASE(SpvOpDecorationGroup)
73 #undef PRIORITY_CASE
74 }
75
76 // Fall back to maintain total ordering (compare unique ids).
77 return *lhs < *rhs;
78 }
79 };
80
81 } // namespace
82
IsVarOfStorage(uint32_t varId,uint32_t storageClass)83 bool AggressiveDCEPass::IsVarOfStorage(uint32_t varId, uint32_t storageClass) {
84 if (varId == 0) return false;
85 const Instruction* varInst = get_def_use_mgr()->GetDef(varId);
86 const SpvOp op = varInst->opcode();
87 if (op != SpvOpVariable) return false;
88 const uint32_t varTypeId = varInst->type_id();
89 const Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId);
90 if (varTypeInst->opcode() != SpvOpTypePointer) return false;
91 return varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) ==
92 storageClass;
93 }
94
IsLocalVar(uint32_t varId)95 bool AggressiveDCEPass::IsLocalVar(uint32_t varId) {
96 if (IsVarOfStorage(varId, SpvStorageClassFunction)) {
97 return true;
98 }
99 if (!private_like_local_) {
100 return false;
101 }
102
103 return IsVarOfStorage(varId, SpvStorageClassPrivate) ||
104 IsVarOfStorage(varId, SpvStorageClassWorkgroup);
105 }
106
AddStores(uint32_t ptrId)107 void AggressiveDCEPass::AddStores(uint32_t ptrId) {
108 get_def_use_mgr()->ForEachUser(ptrId, [this, ptrId](Instruction* user) {
109 switch (user->opcode()) {
110 case SpvOpAccessChain:
111 case SpvOpInBoundsAccessChain:
112 case SpvOpCopyObject:
113 this->AddStores(user->result_id());
114 break;
115 case SpvOpLoad:
116 break;
117 case SpvOpCopyMemory:
118 case SpvOpCopyMemorySized:
119 if (user->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx) == ptrId) {
120 AddToWorklist(user);
121 }
122 break;
123 // If default, assume it stores e.g. frexp, modf, function call
124 case SpvOpStore:
125 default:
126 AddToWorklist(user);
127 break;
128 }
129 });
130 }
131
AllExtensionsSupported() const132 bool AggressiveDCEPass::AllExtensionsSupported() const {
133 // If any extension not in whitelist, return false
134 for (auto& ei : get_module()->extensions()) {
135 const char* extName =
136 reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]);
137 if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
138 return false;
139 }
140 return true;
141 }
142
IsDead(Instruction * inst)143 bool AggressiveDCEPass::IsDead(Instruction* inst) {
144 if (IsLive(inst)) return false;
145 if ((inst->IsBranch() || inst->opcode() == SpvOpUnreachable) &&
146 !IsStructuredHeader(context()->get_instr_block(inst), nullptr, nullptr,
147 nullptr))
148 return false;
149 return true;
150 }
151
IsTargetDead(Instruction * inst)152 bool AggressiveDCEPass::IsTargetDead(Instruction* inst) {
153 const uint32_t tId = inst->GetSingleWordInOperand(0);
154 Instruction* tInst = get_def_use_mgr()->GetDef(tId);
155 if (IsAnnotationInst(tInst->opcode())) {
156 // This must be a decoration group. We go through annotations in a specific
157 // order. So if this is not used by any group or group member decorates, it
158 // is dead.
159 assert(tInst->opcode() == SpvOpDecorationGroup);
160 bool dead = true;
161 get_def_use_mgr()->ForEachUser(tInst, [&dead](Instruction* user) {
162 if (user->opcode() == SpvOpGroupDecorate ||
163 user->opcode() == SpvOpGroupMemberDecorate)
164 dead = false;
165 });
166 return dead;
167 }
168 return IsDead(tInst);
169 }
170
ProcessLoad(uint32_t varId)171 void AggressiveDCEPass::ProcessLoad(uint32_t varId) {
172 // Only process locals
173 if (!IsLocalVar(varId)) return;
174 // Return if already processed
175 if (live_local_vars_.find(varId) != live_local_vars_.end()) return;
176 // Mark all stores to varId as live
177 AddStores(varId);
178 // Cache varId as processed
179 live_local_vars_.insert(varId);
180 }
181
IsStructuredHeader(BasicBlock * bp,Instruction ** mergeInst,Instruction ** branchInst,uint32_t * mergeBlockId)182 bool AggressiveDCEPass::IsStructuredHeader(BasicBlock* bp,
183 Instruction** mergeInst,
184 Instruction** branchInst,
185 uint32_t* mergeBlockId) {
186 if (!bp) return false;
187 Instruction* mi = bp->GetMergeInst();
188 if (mi == nullptr) return false;
189 Instruction* bri = &*bp->tail();
190 if (branchInst != nullptr) *branchInst = bri;
191 if (mergeInst != nullptr) *mergeInst = mi;
192 if (mergeBlockId != nullptr) *mergeBlockId = mi->GetSingleWordInOperand(0);
193 return true;
194 }
195
ComputeBlock2HeaderMaps(std::list<BasicBlock * > & structuredOrder)196 void AggressiveDCEPass::ComputeBlock2HeaderMaps(
197 std::list<BasicBlock*>& structuredOrder) {
198 block2headerBranch_.clear();
199 header2nextHeaderBranch_.clear();
200 branch2merge_.clear();
201 structured_order_index_.clear();
202 std::stack<Instruction*> currentHeaderBranch;
203 currentHeaderBranch.push(nullptr);
204 uint32_t currentMergeBlockId = 0;
205 uint32_t index = 0;
206 for (auto bi = structuredOrder.begin(); bi != structuredOrder.end();
207 ++bi, ++index) {
208 structured_order_index_[*bi] = index;
209 // If this block is the merge block of the current control construct,
210 // we are leaving the current construct so we must update state
211 if ((*bi)->id() == currentMergeBlockId) {
212 currentHeaderBranch.pop();
213 Instruction* chb = currentHeaderBranch.top();
214 if (chb != nullptr)
215 currentMergeBlockId = branch2merge_[chb]->GetSingleWordInOperand(0);
216 }
217 Instruction* mergeInst;
218 Instruction* branchInst;
219 uint32_t mergeBlockId;
220 bool is_header =
221 IsStructuredHeader(*bi, &mergeInst, &branchInst, &mergeBlockId);
222 // Map header block to next enclosing header.
223 if (is_header) header2nextHeaderBranch_[*bi] = currentHeaderBranch.top();
224 // If this is a loop header, update state first so the block will map to
225 // itself.
226 if (is_header && mergeInst->opcode() == SpvOpLoopMerge) {
227 currentHeaderBranch.push(branchInst);
228 branch2merge_[branchInst] = mergeInst;
229 currentMergeBlockId = mergeBlockId;
230 }
231 // Map the block to the current construct.
232 block2headerBranch_[*bi] = currentHeaderBranch.top();
233 // If this is an if header, update state so following blocks map to the if.
234 if (is_header && mergeInst->opcode() == SpvOpSelectionMerge) {
235 currentHeaderBranch.push(branchInst);
236 branch2merge_[branchInst] = mergeInst;
237 currentMergeBlockId = mergeBlockId;
238 }
239 }
240 }
241
AddBranch(uint32_t labelId,BasicBlock * bp)242 void AggressiveDCEPass::AddBranch(uint32_t labelId, BasicBlock* bp) {
243 std::unique_ptr<Instruction> newBranch(
244 new Instruction(context(), SpvOpBranch, 0, 0,
245 {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {labelId}}}));
246 context()->AnalyzeDefUse(&*newBranch);
247 context()->set_instr_block(&*newBranch, bp);
248 bp->AddInstruction(std::move(newBranch));
249 }
250
AddBreaksAndContinuesToWorklist(Instruction * mergeInst)251 void AggressiveDCEPass::AddBreaksAndContinuesToWorklist(
252 Instruction* mergeInst) {
253 assert(mergeInst->opcode() == SpvOpSelectionMerge ||
254 mergeInst->opcode() == SpvOpLoopMerge);
255
256 BasicBlock* header = context()->get_instr_block(mergeInst);
257 uint32_t headerIndex = structured_order_index_[header];
258 const uint32_t mergeId = mergeInst->GetSingleWordInOperand(0);
259 BasicBlock* merge = context()->get_instr_block(mergeId);
260 uint32_t mergeIndex = structured_order_index_[merge];
261 get_def_use_mgr()->ForEachUser(
262 mergeId, [headerIndex, mergeIndex, this](Instruction* user) {
263 if (!user->IsBranch()) return;
264 BasicBlock* block = context()->get_instr_block(user);
265 uint32_t index = structured_order_index_[block];
266 if (headerIndex < index && index < mergeIndex) {
267 // This is a break from the loop.
268 AddToWorklist(user);
269 // Add branch's merge if there is one.
270 Instruction* userMerge = branch2merge_[user];
271 if (userMerge != nullptr) AddToWorklist(userMerge);
272 }
273 });
274
275 if (mergeInst->opcode() != SpvOpLoopMerge) {
276 return;
277 }
278
279 // For loops we need to find the continues as well.
280 const uint32_t contId =
281 mergeInst->GetSingleWordInOperand(kLoopMergeContinueBlockIdInIdx);
282 get_def_use_mgr()->ForEachUser(contId, [&contId, this](Instruction* user) {
283 SpvOp op = user->opcode();
284 if (op == SpvOpBranchConditional || op == SpvOpSwitch) {
285 // A conditional branch or switch can only be a continue if it does not
286 // have a merge instruction or its merge block is not the continue block.
287 Instruction* hdrMerge = branch2merge_[user];
288 if (hdrMerge != nullptr && hdrMerge->opcode() == SpvOpSelectionMerge) {
289 uint32_t hdrMergeId =
290 hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
291 if (hdrMergeId == contId) return;
292 // Need to mark merge instruction too
293 AddToWorklist(hdrMerge);
294 }
295 } else if (op == SpvOpBranch) {
296 // An unconditional branch can only be a continue if it is not
297 // branching to its own merge block.
298 BasicBlock* blk = context()->get_instr_block(user);
299 Instruction* hdrBranch = block2headerBranch_[blk];
300 if (hdrBranch == nullptr) return;
301 Instruction* hdrMerge = branch2merge_[hdrBranch];
302 if (hdrMerge->opcode() == SpvOpLoopMerge) return;
303 uint32_t hdrMergeId =
304 hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
305 if (contId == hdrMergeId) return;
306 } else {
307 return;
308 }
309 AddToWorklist(user);
310 });
311 }
312
AggressiveDCE(Function * func)313 bool AggressiveDCEPass::AggressiveDCE(Function* func) {
314 // Mark function parameters as live.
315 AddToWorklist(&func->DefInst());
316 func->ForEachParam(
317 [this](const Instruction* param) {
318 AddToWorklist(const_cast<Instruction*>(param));
319 },
320 false);
321
322 // Compute map from block to controlling conditional branch
323 std::list<BasicBlock*> structuredOrder;
324 cfg()->ComputeStructuredOrder(func, &*func->begin(), &structuredOrder);
325 ComputeBlock2HeaderMaps(structuredOrder);
326 bool modified = false;
327 // Add instructions with external side effects to worklist. Also add branches
328 // EXCEPT those immediately contained in an "if" selection construct or a loop
329 // or continue construct.
330 // TODO(greg-lunarg): Handle Frexp, Modf more optimally
331 call_in_func_ = false;
332 func_is_entry_point_ = false;
333 private_stores_.clear();
334 // Stacks to keep track of when we are inside an if- or loop-construct.
335 // When immediately inside an if- or loop-construct, we do not initially
336 // mark branches live. All other branches must be marked live.
337 std::stack<bool> assume_branches_live;
338 std::stack<uint32_t> currentMergeBlockId;
339 // Push sentinel values on stack for when outside of any control flow.
340 assume_branches_live.push(true);
341 currentMergeBlockId.push(0);
342 for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
343 // If exiting if or loop, update stacks
344 if ((*bi)->id() == currentMergeBlockId.top()) {
345 assume_branches_live.pop();
346 currentMergeBlockId.pop();
347 }
348 for (auto ii = (*bi)->begin(); ii != (*bi)->end(); ++ii) {
349 SpvOp op = ii->opcode();
350 switch (op) {
351 case SpvOpStore: {
352 uint32_t varId;
353 (void)GetPtr(&*ii, &varId);
354 // Mark stores as live if their variable is not function scope
355 // and is not private scope. Remember private stores for possible
356 // later inclusion. We cannot call IsLocalVar at this point because
357 // private_like_local_ has not been set yet.
358 if (IsVarOfStorage(varId, SpvStorageClassPrivate) ||
359 IsVarOfStorage(varId, SpvStorageClassWorkgroup))
360 private_stores_.push_back(&*ii);
361 else if (!IsVarOfStorage(varId, SpvStorageClassFunction))
362 AddToWorklist(&*ii);
363 } break;
364 case SpvOpCopyMemory:
365 case SpvOpCopyMemorySized: {
366 uint32_t varId;
367 (void)GetPtr(ii->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx),
368 &varId);
369 if (IsVarOfStorage(varId, SpvStorageClassPrivate) ||
370 IsVarOfStorage(varId, SpvStorageClassWorkgroup))
371 private_stores_.push_back(&*ii);
372 else if (!IsVarOfStorage(varId, SpvStorageClassFunction))
373 AddToWorklist(&*ii);
374 } break;
375 case SpvOpLoopMerge: {
376 assume_branches_live.push(false);
377 currentMergeBlockId.push(
378 ii->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx));
379 } break;
380 case SpvOpSelectionMerge: {
381 assume_branches_live.push(false);
382 currentMergeBlockId.push(
383 ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx));
384 } break;
385 case SpvOpSwitch:
386 case SpvOpBranch:
387 case SpvOpBranchConditional:
388 case SpvOpUnreachable: {
389 if (assume_branches_live.top()) {
390 AddToWorklist(&*ii);
391 }
392 } break;
393 default: {
394 // Function calls, atomics, function params, function returns, etc.
395 // TODO(greg-lunarg): function calls live only if write to non-local
396 if (!ii->IsOpcodeSafeToDelete()) {
397 AddToWorklist(&*ii);
398 }
399 // Remember function calls
400 if (op == SpvOpFunctionCall) call_in_func_ = true;
401 } break;
402 }
403 }
404 }
405 // See if current function is an entry point
406 for (auto& ei : get_module()->entry_points()) {
407 if (ei.GetSingleWordInOperand(kEntryPointFunctionIdInIdx) ==
408 func->result_id()) {
409 func_is_entry_point_ = true;
410 break;
411 }
412 }
413 // If the current function is an entry point and has no function calls,
414 // we can optimize private variables as locals
415 private_like_local_ = func_is_entry_point_ && !call_in_func_;
416 // If privates are not like local, add their stores to worklist
417 if (!private_like_local_)
418 for (auto& ps : private_stores_) AddToWorklist(ps);
419 // Perform closure on live instruction set.
420 while (!worklist_.empty()) {
421 Instruction* liveInst = worklist_.front();
422 // Add all operand instructions if not already live
423 liveInst->ForEachInId([&liveInst, this](const uint32_t* iid) {
424 Instruction* inInst = get_def_use_mgr()->GetDef(*iid);
425 // Do not add label if an operand of a branch. This is not needed
426 // as part of live code discovery and can create false live code,
427 // for example, the branch to a header of a loop.
428 if (inInst->opcode() == SpvOpLabel && liveInst->IsBranch()) return;
429 AddToWorklist(inInst);
430 });
431 if (liveInst->type_id() != 0) {
432 AddToWorklist(get_def_use_mgr()->GetDef(liveInst->type_id()));
433 }
434 // If in a structured if or loop construct, add the controlling
435 // conditional branch and its merge.
436 BasicBlock* blk = context()->get_instr_block(liveInst);
437 Instruction* branchInst = block2headerBranch_[blk];
438 if (branchInst != nullptr) {
439 AddToWorklist(branchInst);
440 Instruction* mergeInst = branch2merge_[branchInst];
441 AddToWorklist(mergeInst);
442 }
443 // If the block is a header, add the next outermost controlling
444 // conditional branch and its merge.
445 Instruction* nextBranchInst = header2nextHeaderBranch_[blk];
446 if (nextBranchInst != nullptr) {
447 AddToWorklist(nextBranchInst);
448 Instruction* mergeInst = branch2merge_[nextBranchInst];
449 AddToWorklist(mergeInst);
450 }
451 // If local load, add all variable's stores if variable not already live
452 if (liveInst->opcode() == SpvOpLoad || liveInst->IsAtomicWithLoad()) {
453 uint32_t varId;
454 (void)GetPtr(liveInst, &varId);
455 if (varId != 0) {
456 ProcessLoad(varId);
457 }
458 // Process memory copies like loads
459 } else if (liveInst->opcode() == SpvOpCopyMemory ||
460 liveInst->opcode() == SpvOpCopyMemorySized) {
461 uint32_t varId;
462 (void)GetPtr(liveInst->GetSingleWordInOperand(kCopyMemorySourceAddrInIdx),
463 &varId);
464 if (varId != 0) {
465 ProcessLoad(varId);
466 }
467 // If merge, add other branches that are part of its control structure
468 } else if (liveInst->opcode() == SpvOpLoopMerge ||
469 liveInst->opcode() == SpvOpSelectionMerge) {
470 AddBreaksAndContinuesToWorklist(liveInst);
471 // If function call, treat as if it loads from all pointer arguments
472 } else if (liveInst->opcode() == SpvOpFunctionCall) {
473 liveInst->ForEachInId([this](const uint32_t* iid) {
474 // Skip non-ptr args
475 if (!IsPtr(*iid)) return;
476 uint32_t varId;
477 (void)GetPtr(*iid, &varId);
478 ProcessLoad(varId);
479 });
480 // If function parameter, treat as if it's result id is loaded from
481 } else if (liveInst->opcode() == SpvOpFunctionParameter) {
482 ProcessLoad(liveInst->result_id());
483 // We treat an OpImageTexelPointer as a load of the pointer, and
484 // that value is manipulated to get the result.
485 } else if (liveInst->opcode() == SpvOpImageTexelPointer) {
486 uint32_t varId;
487 (void)GetPtr(liveInst, &varId);
488 if (varId != 0) {
489 ProcessLoad(varId);
490 }
491 }
492 worklist_.pop();
493 }
494
495 // Kill dead instructions and remember dead blocks
496 for (auto bi = structuredOrder.begin(); bi != structuredOrder.end();) {
497 uint32_t mergeBlockId = 0;
498 (*bi)->ForEachInst([this, &modified, &mergeBlockId](Instruction* inst) {
499 if (!IsDead(inst)) return;
500 if (inst->opcode() == SpvOpLabel) return;
501 // If dead instruction is selection merge, remember merge block
502 // for new branch at end of block
503 if (inst->opcode() == SpvOpSelectionMerge ||
504 inst->opcode() == SpvOpLoopMerge)
505 mergeBlockId = inst->GetSingleWordInOperand(0);
506 to_kill_.push_back(inst);
507 modified = true;
508 });
509 // If a structured if or loop was deleted, add a branch to its merge
510 // block, and traverse to the merge block and continue processing there.
511 // We know the block still exists because the label is not deleted.
512 if (mergeBlockId != 0) {
513 AddBranch(mergeBlockId, *bi);
514 for (++bi; (*bi)->id() != mergeBlockId; ++bi) {
515 }
516 } else {
517 ++bi;
518 }
519 }
520
521 return modified;
522 }
523
InitializeModuleScopeLiveInstructions()524 void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() {
525 // Keep all execution modes.
526 for (auto& exec : get_module()->execution_modes()) {
527 AddToWorklist(&exec);
528 }
529 // Keep all entry points.
530 for (auto& entry : get_module()->entry_points()) {
531 AddToWorklist(&entry);
532 }
533 // Keep workgroup size.
534 for (auto& anno : get_module()->annotations()) {
535 if (anno.opcode() == SpvOpDecorate) {
536 if (anno.GetSingleWordInOperand(1u) == SpvDecorationBuiltIn &&
537 anno.GetSingleWordInOperand(2u) == SpvBuiltInWorkgroupSize) {
538 AddToWorklist(&anno);
539 }
540 }
541 }
542 }
543
ProcessImpl()544 Pass::Status AggressiveDCEPass::ProcessImpl() {
545 // Current functionality assumes shader capability
546 // TODO(greg-lunarg): Handle additional capabilities
547 if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
548 return Status::SuccessWithoutChange;
549 // Current functionality assumes relaxed logical addressing (see
550 // instruction.h)
551 // TODO(greg-lunarg): Handle non-logical addressing
552 if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
553 return Status::SuccessWithoutChange;
554 // If any extensions in the module are not explicitly supported,
555 // return unmodified.
556 if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
557
558 // If the decoration manager is kept live then the context will try to keep it
559 // up to date. ADCE deals with group decorations by changing the operands in
560 // |OpGroupDecorate| instruction directly without informing the decoration
561 // manager. This can put it in an invalid state which will cause an error
562 // when the context tries to update it. To avoid this problem invalidate
563 // the decoration manager upfront.
564 context()->InvalidateAnalyses(IRContext::Analysis::kAnalysisDecorations);
565
566 // Eliminate Dead functions.
567 bool modified = EliminateDeadFunctions();
568
569 InitializeModuleScopeLiveInstructions();
570
571 // Process all entry point functions.
572 ProcessFunction pfn = [this](Function* fp) { return AggressiveDCE(fp); };
573 modified |= context()->ProcessEntryPointCallTree(pfn);
574
575 // Process module-level instructions. Now that all live instructions have
576 // been marked, it is safe to remove dead global values.
577 modified |= ProcessGlobalValues();
578
579 // Kill all dead instructions.
580 for (auto inst : to_kill_) {
581 context()->KillInst(inst);
582 }
583
584 // Cleanup all CFG including all unreachable blocks.
585 ProcessFunction cleanup = [this](Function* f) { return CFGCleanup(f); };
586 modified |= context()->ProcessEntryPointCallTree(cleanup);
587
588 return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
589 }
590
EliminateDeadFunctions()591 bool AggressiveDCEPass::EliminateDeadFunctions() {
592 // Identify live functions first. Those that are not live
593 // are dead. ADCE is disabled for non-shaders so we do not check for exported
594 // functions here.
595 std::unordered_set<const Function*> live_function_set;
596 ProcessFunction mark_live = [&live_function_set](Function* fp) {
597 live_function_set.insert(fp);
598 return false;
599 };
600 context()->ProcessEntryPointCallTree(mark_live);
601
602 bool modified = false;
603 for (auto funcIter = get_module()->begin();
604 funcIter != get_module()->end();) {
605 if (live_function_set.count(&*funcIter) == 0) {
606 modified = true;
607 EliminateFunction(&*funcIter);
608 funcIter = funcIter.Erase();
609 } else {
610 ++funcIter;
611 }
612 }
613
614 return modified;
615 }
616
EliminateFunction(Function * func)617 void AggressiveDCEPass::EliminateFunction(Function* func) {
618 // Remove all of the instruction in the function body
619 func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); },
620 true);
621 }
622
ProcessGlobalValues()623 bool AggressiveDCEPass::ProcessGlobalValues() {
624 // Remove debug and annotation statements referencing dead instructions.
625 // This must be done before killing the instructions, otherwise there are
626 // dead objects in the def/use database.
627 bool modified = false;
628 Instruction* instruction = &*get_module()->debug2_begin();
629 while (instruction) {
630 if (instruction->opcode() != SpvOpName) {
631 instruction = instruction->NextNode();
632 continue;
633 }
634
635 if (IsTargetDead(instruction)) {
636 instruction = context()->KillInst(instruction);
637 modified = true;
638 } else {
639 instruction = instruction->NextNode();
640 }
641 }
642
643 // This code removes all unnecessary decorations safely (see #1174). It also
644 // does so in a more efficient manner than deleting them only as the targets
645 // are deleted.
646 std::vector<Instruction*> annotations;
647 for (auto& inst : get_module()->annotations()) annotations.push_back(&inst);
648 std::sort(annotations.begin(), annotations.end(), DecorationLess());
649 for (auto annotation : annotations) {
650 switch (annotation->opcode()) {
651 case SpvOpDecorate:
652 case SpvOpMemberDecorate:
653 case SpvOpDecorateStringGOOGLE:
654 case SpvOpMemberDecorateStringGOOGLE:
655 if (IsTargetDead(annotation)) {
656 context()->KillInst(annotation);
657 modified = true;
658 }
659 break;
660 case SpvOpDecorateId:
661 if (IsTargetDead(annotation)) {
662 context()->KillInst(annotation);
663 modified = true;
664 } else {
665 if (annotation->GetSingleWordInOperand(1) ==
666 SpvDecorationHlslCounterBufferGOOGLE) {
667 // HlslCounterBuffer will reference an id other than the target.
668 // If that id is dead, then the decoration can be removed as well.
669 uint32_t counter_buffer_id = annotation->GetSingleWordInOperand(2);
670 Instruction* counter_buffer_inst =
671 get_def_use_mgr()->GetDef(counter_buffer_id);
672 if (IsDead(counter_buffer_inst)) {
673 context()->KillInst(annotation);
674 modified = true;
675 }
676 }
677 }
678 break;
679 case SpvOpGroupDecorate: {
680 // Go through the targets of this group decorate. Remove each dead
681 // target. If all targets are dead, remove this decoration.
682 bool dead = true;
683 bool removed_operand = false;
684 for (uint32_t i = 1; i < annotation->NumOperands();) {
685 Instruction* opInst =
686 get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i));
687 if (IsDead(opInst)) {
688 // Don't increment |i|.
689 annotation->RemoveOperand(i);
690 modified = true;
691 removed_operand = true;
692 } else {
693 i++;
694 dead = false;
695 }
696 }
697 if (dead) {
698 context()->KillInst(annotation);
699 modified = true;
700 } else if (removed_operand) {
701 context()->UpdateDefUse(annotation);
702 }
703 break;
704 }
705 case SpvOpGroupMemberDecorate: {
706 // Go through the targets of this group member decorate. Remove each
707 // dead target (and member index). If all targets are dead, remove this
708 // decoration.
709 bool dead = true;
710 bool removed_operand = false;
711 for (uint32_t i = 1; i < annotation->NumOperands();) {
712 Instruction* opInst =
713 get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i));
714 if (IsDead(opInst)) {
715 // Don't increment |i|.
716 annotation->RemoveOperand(i + 1);
717 annotation->RemoveOperand(i);
718 modified = true;
719 removed_operand = true;
720 } else {
721 i += 2;
722 dead = false;
723 }
724 }
725 if (dead) {
726 context()->KillInst(annotation);
727 modified = true;
728 } else if (removed_operand) {
729 context()->UpdateDefUse(annotation);
730 }
731 break;
732 }
733 case SpvOpDecorationGroup:
734 // By the time we hit decoration groups we've checked everything that
735 // can target them. So if they have no uses they must be dead.
736 if (get_def_use_mgr()->NumUsers(annotation) == 0) {
737 context()->KillInst(annotation);
738 modified = true;
739 }
740 break;
741 default:
742 assert(false);
743 break;
744 }
745 }
746
747 // Since ADCE is disabled for non-shaders, we don't check for export linkage
748 // attributes here.
749 for (auto& val : get_module()->types_values()) {
750 if (IsDead(&val)) {
751 to_kill_.push_back(&val);
752 }
753 }
754
755 return modified;
756 }
757
758 AggressiveDCEPass::AggressiveDCEPass() = default;
759
Process()760 Pass::Status AggressiveDCEPass::Process() {
761 // Initialize extensions whitelist
762 InitExtensions();
763 return ProcessImpl();
764 }
765
InitExtensions()766 void AggressiveDCEPass::InitExtensions() {
767 extensions_whitelist_.clear();
768 extensions_whitelist_.insert({
769 "SPV_AMD_shader_explicit_vertex_parameter",
770 "SPV_AMD_shader_trinary_minmax",
771 "SPV_AMD_gcn_shader",
772 "SPV_KHR_shader_ballot",
773 "SPV_AMD_shader_ballot",
774 "SPV_AMD_gpu_shader_half_float",
775 "SPV_KHR_shader_draw_parameters",
776 "SPV_KHR_subgroup_vote",
777 "SPV_KHR_16bit_storage",
778 "SPV_KHR_device_group",
779 "SPV_KHR_multiview",
780 "SPV_NVX_multiview_per_view_attributes",
781 "SPV_NV_viewport_array2",
782 "SPV_NV_stereo_view_rendering",
783 "SPV_NV_sample_mask_override_coverage",
784 "SPV_NV_geometry_shader_passthrough",
785 "SPV_AMD_texture_gather_bias_lod",
786 "SPV_KHR_storage_buffer_storage_class",
787 // SPV_KHR_variable_pointers
788 // Currently do not support extended pointer expressions
789 "SPV_AMD_gpu_shader_int16",
790 "SPV_KHR_post_depth_coverage",
791 "SPV_KHR_shader_atomic_counter_ops",
792 "SPV_EXT_shader_stencil_export",
793 "SPV_EXT_shader_viewport_index_layer",
794 "SPV_AMD_shader_image_load_store_lod",
795 "SPV_AMD_shader_fragment_mask",
796 "SPV_EXT_fragment_fully_covered",
797 "SPV_AMD_gpu_shader_half_float_fetch",
798 "SPV_GOOGLE_decorate_string",
799 "SPV_GOOGLE_hlsl_functionality1",
800 "SPV_NV_shader_subgroup_partitioned",
801 "SPV_EXT_descriptor_indexing",
802 "SPV_NV_fragment_shader_barycentric",
803 "SPV_NV_compute_shader_derivatives",
804 "SPV_NV_shader_image_footprint",
805 "SPV_NV_shading_rate",
806 "SPV_NV_mesh_shader",
807 "SPV_NV_ray_tracing",
808 "SPV_EXT_fragment_invocation_density",
809 });
810 }
811
812 } // namespace opt
813 } // namespace spvtools
814