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