1 // Copyright (c) 2017 The Khronos Group Inc.
2 // Copyright (c) 2017 Valve Corporation
3 // Copyright (c) 2017 LunarG Inc.
4 // Copyright (c) 2018-2021 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/eliminate_dead_functions_util.h"
26 #include "source/opt/ir_builder.h"
27 #include "source/opt/iterator.h"
28 #include "source/opt/reflect.h"
29 #include "source/spirv_constant.h"
30 #include "source/util/string_utils.h"
31
32 namespace spvtools {
33 namespace opt {
34
35 namespace {
36
37 const uint32_t kTypePointerStorageClassInIdx = 0;
38 const uint32_t kEntryPointFunctionIdInIdx = 1;
39 const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
40 const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
41 const uint32_t kCopyMemoryTargetAddrInIdx = 0;
42 const uint32_t kCopyMemorySourceAddrInIdx = 1;
43 const uint32_t kLoadSourceAddrInIdx = 0;
44 const uint32_t kDebugDeclareOperandVariableIndex = 5;
45 const uint32_t kGlobalVariableVariableIndex = 12;
46
47 // Sorting functor to present annotation instructions in an easy-to-process
48 // order. The functor orders by opcode first and falls back on unique id
49 // ordering if both instructions have the same opcode.
50 //
51 // Desired priority:
52 // SpvOpGroupDecorate
53 // SpvOpGroupMemberDecorate
54 // SpvOpDecorate
55 // SpvOpMemberDecorate
56 // SpvOpDecorateId
57 // SpvOpDecorateStringGOOGLE
58 // SpvOpDecorationGroup
59 struct DecorationLess {
operator ()spvtools::opt::__anon86022b360111::DecorationLess60 bool operator()(const Instruction* lhs, const Instruction* rhs) const {
61 assert(lhs && rhs);
62 SpvOp lhsOp = lhs->opcode();
63 SpvOp rhsOp = rhs->opcode();
64 if (lhsOp != rhsOp) {
65 #define PRIORITY_CASE(opcode) \
66 if (lhsOp == opcode && rhsOp != opcode) return true; \
67 if (rhsOp == opcode && lhsOp != opcode) return false;
68 // OpGroupDecorate and OpGroupMember decorate are highest priority to
69 // eliminate dead targets early and simplify subsequent checks.
70 PRIORITY_CASE(SpvOpGroupDecorate)
71 PRIORITY_CASE(SpvOpGroupMemberDecorate)
72 PRIORITY_CASE(SpvOpDecorate)
73 PRIORITY_CASE(SpvOpMemberDecorate)
74 PRIORITY_CASE(SpvOpDecorateId)
75 PRIORITY_CASE(SpvOpDecorateStringGOOGLE)
76 // OpDecorationGroup is lowest priority to ensure use/def chains remain
77 // usable for instructions that target this group.
78 PRIORITY_CASE(SpvOpDecorationGroup)
79 #undef PRIORITY_CASE
80 }
81
82 // Fall back to maintain total ordering (compare unique ids).
83 return *lhs < *rhs;
84 }
85 };
86
87 } // namespace
88
IsVarOfStorage(uint32_t varId,uint32_t storageClass)89 bool AggressiveDCEPass::IsVarOfStorage(uint32_t varId, uint32_t storageClass) {
90 if (varId == 0) return false;
91 const Instruction* varInst = get_def_use_mgr()->GetDef(varId);
92 const SpvOp op = varInst->opcode();
93 if (op != SpvOpVariable) return false;
94 const uint32_t varTypeId = varInst->type_id();
95 const Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId);
96 if (varTypeInst->opcode() != SpvOpTypePointer) return false;
97 return varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) ==
98 storageClass;
99 }
100
IsLocalVar(uint32_t varId,Function * func)101 bool AggressiveDCEPass::IsLocalVar(uint32_t varId, Function* func) {
102 if (IsVarOfStorage(varId, SpvStorageClassFunction)) {
103 return true;
104 }
105
106 if (!IsVarOfStorage(varId, SpvStorageClassPrivate) &&
107 !IsVarOfStorage(varId, SpvStorageClassWorkgroup)) {
108 return false;
109 }
110
111 // For a variable in the Private or WorkGroup storage class, the variable will
112 // get a new instance for every call to an entry point. If the entry point
113 // does not have a call, then no other function can read or write to that
114 // instance of the variable.
115 return IsEntryPointWithNoCalls(func);
116 }
117
AddStores(Function * func,uint32_t ptrId)118 void AggressiveDCEPass::AddStores(Function* func, uint32_t ptrId) {
119 get_def_use_mgr()->ForEachUser(ptrId, [this, ptrId, func](Instruction* user) {
120 // If the user is not a part of |func|, skip it.
121 BasicBlock* blk = context()->get_instr_block(user);
122 if (blk && blk->GetParent() != func) return;
123
124 switch (user->opcode()) {
125 case SpvOpAccessChain:
126 case SpvOpInBoundsAccessChain:
127 case SpvOpCopyObject:
128 this->AddStores(func, user->result_id());
129 break;
130 case SpvOpLoad:
131 break;
132 case SpvOpCopyMemory:
133 case SpvOpCopyMemorySized:
134 if (user->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx) == ptrId) {
135 AddToWorklist(user);
136 }
137 break;
138 // If default, assume it stores e.g. frexp, modf, function call
139 case SpvOpStore:
140 default:
141 AddToWorklist(user);
142 break;
143 }
144 });
145 }
146
AllExtensionsSupported() const147 bool AggressiveDCEPass::AllExtensionsSupported() const {
148 // If any extension not in allowlist, return false
149 for (auto& ei : get_module()->extensions()) {
150 const std::string extName = ei.GetInOperand(0).AsString();
151 if (extensions_allowlist_.find(extName) == extensions_allowlist_.end())
152 return false;
153 }
154 // Only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise
155 // around unknown extended instruction sets even if they are non-semantic
156 for (auto& inst : context()->module()->ext_inst_imports()) {
157 assert(inst.opcode() == SpvOpExtInstImport &&
158 "Expecting an import of an extension's instruction set.");
159 const std::string extension_name = inst.GetInOperand(0).AsString();
160 if (spvtools::utils::starts_with(extension_name, "NonSemantic.") &&
161 extension_name != "NonSemantic.Shader.DebugInfo.100") {
162 return false;
163 }
164 }
165 return true;
166 }
167
IsTargetDead(Instruction * inst)168 bool AggressiveDCEPass::IsTargetDead(Instruction* inst) {
169 const uint32_t tId = inst->GetSingleWordInOperand(0);
170 Instruction* tInst = get_def_use_mgr()->GetDef(tId);
171 if (IsAnnotationInst(tInst->opcode())) {
172 // This must be a decoration group. We go through annotations in a specific
173 // order. So if this is not used by any group or group member decorates, it
174 // is dead.
175 assert(tInst->opcode() == SpvOpDecorationGroup);
176 bool dead = true;
177 get_def_use_mgr()->ForEachUser(tInst, [&dead](Instruction* user) {
178 if (user->opcode() == SpvOpGroupDecorate ||
179 user->opcode() == SpvOpGroupMemberDecorate)
180 dead = false;
181 });
182 return dead;
183 }
184 return !IsLive(tInst);
185 }
186
ProcessLoad(Function * func,uint32_t varId)187 void AggressiveDCEPass::ProcessLoad(Function* func, uint32_t varId) {
188 // Only process locals
189 if (!IsLocalVar(varId, func)) return;
190 // Return if already processed
191 if (live_local_vars_.find(varId) != live_local_vars_.end()) return;
192 // Mark all stores to varId as live
193 AddStores(func, varId);
194 // Cache varId as processed
195 live_local_vars_.insert(varId);
196 }
197
AddBranch(uint32_t labelId,BasicBlock * bp)198 void AggressiveDCEPass::AddBranch(uint32_t labelId, BasicBlock* bp) {
199 std::unique_ptr<Instruction> newBranch(
200 new Instruction(context(), SpvOpBranch, 0, 0,
201 {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {labelId}}}));
202 context()->AnalyzeDefUse(&*newBranch);
203 context()->set_instr_block(&*newBranch, bp);
204 bp->AddInstruction(std::move(newBranch));
205 }
206
AddBreaksAndContinuesToWorklist(Instruction * mergeInst)207 void AggressiveDCEPass::AddBreaksAndContinuesToWorklist(
208 Instruction* mergeInst) {
209 assert(mergeInst->opcode() == SpvOpSelectionMerge ||
210 mergeInst->opcode() == SpvOpLoopMerge);
211
212 BasicBlock* header = context()->get_instr_block(mergeInst);
213 const uint32_t mergeId = mergeInst->GetSingleWordInOperand(0);
214 get_def_use_mgr()->ForEachUser(mergeId, [header, this](Instruction* user) {
215 if (!user->IsBranch()) return;
216 BasicBlock* block = context()->get_instr_block(user);
217 if (BlockIsInConstruct(header, block)) {
218 // This is a break from the loop.
219 AddToWorklist(user);
220 // Add branch's merge if there is one.
221 Instruction* userMerge = GetMergeInstruction(user);
222 if (userMerge != nullptr) AddToWorklist(userMerge);
223 }
224 });
225
226 if (mergeInst->opcode() != SpvOpLoopMerge) {
227 return;
228 }
229
230 // For loops we need to find the continues as well.
231 const uint32_t contId =
232 mergeInst->GetSingleWordInOperand(kLoopMergeContinueBlockIdInIdx);
233 get_def_use_mgr()->ForEachUser(contId, [&contId, this](Instruction* user) {
234 SpvOp op = user->opcode();
235 if (op == SpvOpBranchConditional || op == SpvOpSwitch) {
236 // A conditional branch or switch can only be a continue if it does not
237 // have a merge instruction or its merge block is not the continue block.
238 Instruction* hdrMerge = GetMergeInstruction(user);
239 if (hdrMerge != nullptr && hdrMerge->opcode() == SpvOpSelectionMerge) {
240 uint32_t hdrMergeId =
241 hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
242 if (hdrMergeId == contId) return;
243 // Need to mark merge instruction too
244 AddToWorklist(hdrMerge);
245 }
246 } else if (op == SpvOpBranch) {
247 // An unconditional branch can only be a continue if it is not
248 // branching to its own merge block.
249 BasicBlock* blk = context()->get_instr_block(user);
250 Instruction* hdrBranch = GetHeaderBranch(blk);
251 if (hdrBranch == nullptr) return;
252 Instruction* hdrMerge = GetMergeInstruction(hdrBranch);
253 if (hdrMerge->opcode() == SpvOpLoopMerge) return;
254 uint32_t hdrMergeId =
255 hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
256 if (contId == hdrMergeId) return;
257 } else {
258 return;
259 }
260 AddToWorklist(user);
261 });
262 }
263
AggressiveDCE(Function * func)264 bool AggressiveDCEPass::AggressiveDCE(Function* func) {
265 std::list<BasicBlock*> structured_order;
266 cfg()->ComputeStructuredOrder(func, &*func->begin(), &structured_order);
267 live_local_vars_.clear();
268 InitializeWorkList(func, structured_order);
269 ProcessWorkList(func);
270 return KillDeadInstructions(func, structured_order);
271 }
272
KillDeadInstructions(const Function * func,std::list<BasicBlock * > & structured_order)273 bool AggressiveDCEPass::KillDeadInstructions(
274 const Function* func, std::list<BasicBlock*>& structured_order) {
275 bool modified = false;
276 for (auto bi = structured_order.begin(); bi != structured_order.end();) {
277 uint32_t merge_block_id = 0;
278 (*bi)->ForEachInst([this, &modified, &merge_block_id](Instruction* inst) {
279 if (IsLive(inst)) return;
280 if (inst->opcode() == SpvOpLabel) return;
281 // If dead instruction is selection merge, remember merge block
282 // for new branch at end of block
283 if (inst->opcode() == SpvOpSelectionMerge ||
284 inst->opcode() == SpvOpLoopMerge)
285 merge_block_id = inst->GetSingleWordInOperand(0);
286 to_kill_.push_back(inst);
287 modified = true;
288 });
289 // If a structured if or loop was deleted, add a branch to its merge
290 // block, and traverse to the merge block and continue processing there.
291 // We know the block still exists because the label is not deleted.
292 if (merge_block_id != 0) {
293 AddBranch(merge_block_id, *bi);
294 for (++bi; (*bi)->id() != merge_block_id; ++bi) {
295 }
296
297 auto merge_terminator = (*bi)->terminator();
298 if (merge_terminator->opcode() == SpvOpUnreachable) {
299 // The merge was unreachable. This is undefined behaviour so just
300 // return (or return an undef). Then mark the new return as live.
301 auto func_ret_type_inst = get_def_use_mgr()->GetDef(func->type_id());
302 if (func_ret_type_inst->opcode() == SpvOpTypeVoid) {
303 merge_terminator->SetOpcode(SpvOpReturn);
304 } else {
305 // Find an undef for the return value and make sure it gets kept by
306 // the pass.
307 auto undef_id = Type2Undef(func->type_id());
308 auto undef = get_def_use_mgr()->GetDef(undef_id);
309 live_insts_.Set(undef->unique_id());
310 merge_terminator->SetOpcode(SpvOpReturnValue);
311 merge_terminator->SetInOperands({{SPV_OPERAND_TYPE_ID, {undef_id}}});
312 get_def_use_mgr()->AnalyzeInstUse(merge_terminator);
313 }
314 live_insts_.Set(merge_terminator->unique_id());
315 }
316 } else {
317 Instruction* inst = (*bi)->terminator();
318 if (!IsLive(inst)) {
319 // If the terminator is not live, this block has no live instructions,
320 // and it will be unreachable.
321 AddUnreachable(*bi);
322 }
323 ++bi;
324 }
325 }
326 return modified;
327 }
328
ProcessWorkList(Function * func)329 void AggressiveDCEPass::ProcessWorkList(Function* func) {
330 while (!worklist_.empty()) {
331 Instruction* live_inst = worklist_.front();
332 worklist_.pop();
333 AddOperandsToWorkList(live_inst);
334 MarkBlockAsLive(live_inst);
335 MarkLoadedVariablesAsLive(func, live_inst);
336 AddDecorationsToWorkList(live_inst);
337 AddDebugInstructionsToWorkList(live_inst);
338 }
339 }
340
AddDebugInstructionsToWorkList(const Instruction * inst)341 void AggressiveDCEPass::AddDebugInstructionsToWorkList(
342 const Instruction* inst) {
343 for (auto& line_inst : inst->dbg_line_insts()) {
344 if (line_inst.IsDebugLineInst()) {
345 AddOperandsToWorkList(&line_inst);
346 }
347 }
348
349 if (inst->GetDebugScope().GetLexicalScope() != kNoDebugScope) {
350 auto* scope =
351 get_def_use_mgr()->GetDef(inst->GetDebugScope().GetLexicalScope());
352 AddToWorklist(scope);
353 }
354 if (inst->GetDebugInlinedAt() != kNoInlinedAt) {
355 auto* inlined_at = get_def_use_mgr()->GetDef(inst->GetDebugInlinedAt());
356 AddToWorklist(inlined_at);
357 }
358 }
359
AddDecorationsToWorkList(const Instruction * inst)360 void AggressiveDCEPass::AddDecorationsToWorkList(const Instruction* inst) {
361 // Add OpDecorateId instructions that apply to this instruction to the work
362 // list. We use the decoration manager to look through the group
363 // decorations to get to the OpDecorate* instructions themselves.
364 auto decorations =
365 get_decoration_mgr()->GetDecorationsFor(inst->result_id(), false);
366 for (Instruction* dec : decorations) {
367 // We only care about OpDecorateId instructions because the are the only
368 // decorations that will reference an id that will have to be kept live
369 // because of that use.
370 if (dec->opcode() != SpvOpDecorateId) {
371 continue;
372 }
373 if (dec->GetSingleWordInOperand(1) ==
374 SpvDecorationHlslCounterBufferGOOGLE) {
375 // These decorations should not force the use id to be live. It will be
376 // removed if either the target or the in operand are dead.
377 continue;
378 }
379 AddToWorklist(dec);
380 }
381 }
382
MarkLoadedVariablesAsLive(Function * func,Instruction * inst)383 void AggressiveDCEPass::MarkLoadedVariablesAsLive(Function* func,
384 Instruction* inst) {
385 std::vector<uint32_t> live_variables = GetLoadedVariables(inst);
386 for (uint32_t var_id : live_variables) {
387 ProcessLoad(func, var_id);
388 }
389 }
390
GetLoadedVariables(Instruction * inst)391 std::vector<uint32_t> AggressiveDCEPass::GetLoadedVariables(Instruction* inst) {
392 if (inst->opcode() == SpvOpFunctionCall) {
393 return GetLoadedVariablesFromFunctionCall(inst);
394 }
395 uint32_t var_id = GetLoadedVariableFromNonFunctionCalls(inst);
396 if (var_id == 0) {
397 return {};
398 }
399 return {var_id};
400 }
401
GetLoadedVariableFromNonFunctionCalls(Instruction * inst)402 uint32_t AggressiveDCEPass::GetLoadedVariableFromNonFunctionCalls(
403 Instruction* inst) {
404 std::vector<uint32_t> live_variables;
405 if (inst->IsAtomicWithLoad()) {
406 return GetVariableId(inst->GetSingleWordInOperand(kLoadSourceAddrInIdx));
407 }
408
409 switch (inst->opcode()) {
410 case SpvOpLoad:
411 case SpvOpImageTexelPointer:
412 return GetVariableId(inst->GetSingleWordInOperand(kLoadSourceAddrInIdx));
413 case SpvOpCopyMemory:
414 case SpvOpCopyMemorySized:
415 return GetVariableId(
416 inst->GetSingleWordInOperand(kCopyMemorySourceAddrInIdx));
417 default:
418 break;
419 }
420
421 switch (inst->GetCommonDebugOpcode()) {
422 case CommonDebugInfoDebugDeclare:
423 return inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
424 case CommonDebugInfoDebugValue: {
425 analysis::DebugInfoManager* debug_info_mgr =
426 context()->get_debug_info_mgr();
427 return debug_info_mgr->GetVariableIdOfDebugValueUsedForDeclare(inst);
428 }
429 default:
430 break;
431 }
432 return 0;
433 }
434
GetLoadedVariablesFromFunctionCall(const Instruction * inst)435 std::vector<uint32_t> AggressiveDCEPass::GetLoadedVariablesFromFunctionCall(
436 const Instruction* inst) {
437 assert(inst->opcode() == SpvOpFunctionCall);
438 std::vector<uint32_t> live_variables;
439 inst->ForEachInId([this, &live_variables](const uint32_t* operand_id) {
440 if (!IsPtr(*operand_id)) return;
441 uint32_t var_id = GetVariableId(*operand_id);
442 live_variables.push_back(var_id);
443 });
444 return live_variables;
445 }
446
GetVariableId(uint32_t ptr_id)447 uint32_t AggressiveDCEPass::GetVariableId(uint32_t ptr_id) {
448 assert(IsPtr(ptr_id) &&
449 "Cannot get the variable when input is not a pointer.");
450 uint32_t varId = 0;
451 (void)GetPtr(ptr_id, &varId);
452 return varId;
453 }
454
MarkBlockAsLive(Instruction * inst)455 void AggressiveDCEPass::MarkBlockAsLive(Instruction* inst) {
456 BasicBlock* basic_block = context()->get_instr_block(inst);
457 if (basic_block == nullptr) {
458 return;
459 }
460
461 // If we intend to keep this instruction, we need the block label and
462 // block terminator to have a valid block for the instruction.
463 AddToWorklist(basic_block->GetLabelInst());
464
465 // We need to mark the successors blocks that follow as live. If this is
466 // header of the merge construct, the construct may be folded, but we will
467 // definitely need the merge label. If it is not a construct, the terminator
468 // must be live, and the successor blocks will be marked as live when
469 // processing the terminator.
470 uint32_t merge_id = basic_block->MergeBlockIdIfAny();
471 if (merge_id == 0) {
472 AddToWorklist(basic_block->terminator());
473 } else {
474 AddToWorklist(context()->get_def_use_mgr()->GetDef(merge_id));
475 }
476
477 // Mark the structured control flow constructs that contains this block as
478 // live. If |inst| is an instruction in the loop header, then it is part of
479 // the loop, so the loop construct must be live. We exclude the label because
480 // it does not matter how many times it is executed. This could be extended
481 // to more instructions, but we will need it for now.
482 if (inst->opcode() != SpvOpLabel)
483 MarkLoopConstructAsLiveIfLoopHeader(basic_block);
484
485 Instruction* next_branch_inst = GetBranchForNextHeader(basic_block);
486 if (next_branch_inst != nullptr) {
487 AddToWorklist(next_branch_inst);
488 Instruction* mergeInst = GetMergeInstruction(next_branch_inst);
489 AddToWorklist(mergeInst);
490 }
491
492 if (inst->opcode() == SpvOpLoopMerge ||
493 inst->opcode() == SpvOpSelectionMerge) {
494 AddBreaksAndContinuesToWorklist(inst);
495 }
496 }
MarkLoopConstructAsLiveIfLoopHeader(BasicBlock * basic_block)497 void AggressiveDCEPass::MarkLoopConstructAsLiveIfLoopHeader(
498 BasicBlock* basic_block) {
499 // If this is the header for a loop, then loop structure needs to keep as well
500 // because the loop header is also part of the loop.
501 Instruction* merge_inst = basic_block->GetLoopMergeInst();
502 if (merge_inst != nullptr) {
503 AddToWorklist(basic_block->terminator());
504 AddToWorklist(merge_inst);
505 }
506 }
507
AddOperandsToWorkList(const Instruction * inst)508 void AggressiveDCEPass::AddOperandsToWorkList(const Instruction* inst) {
509 inst->ForEachInId([this](const uint32_t* iid) {
510 Instruction* inInst = get_def_use_mgr()->GetDef(*iid);
511 AddToWorklist(inInst);
512 });
513 if (inst->type_id() != 0) {
514 AddToWorklist(get_def_use_mgr()->GetDef(inst->type_id()));
515 }
516 }
517
InitializeWorkList(Function * func,std::list<BasicBlock * > & structured_order)518 void AggressiveDCEPass::InitializeWorkList(
519 Function* func, std::list<BasicBlock*>& structured_order) {
520 AddToWorklist(&func->DefInst());
521 MarkFunctionParameterAsLive(func);
522 MarkFirstBlockAsLive(func);
523
524 // Add instructions with external side effects to the worklist. Also add
525 // branches that are not attached to a structured construct.
526 // TODO(s-perron): The handling of branch seems to be adhoc. This needs to be
527 // cleaned up.
528 for (auto& bi : structured_order) {
529 for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
530 SpvOp op = ii->opcode();
531 if (ii->IsBranch()) {
532 continue;
533 }
534 switch (op) {
535 case SpvOpStore: {
536 uint32_t var_id = 0;
537 (void)GetPtr(&*ii, &var_id);
538 if (!IsLocalVar(var_id, func)) AddToWorklist(&*ii);
539 } break;
540 case SpvOpCopyMemory:
541 case SpvOpCopyMemorySized: {
542 uint32_t var_id = 0;
543 uint32_t target_addr_id =
544 ii->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx);
545 (void)GetPtr(target_addr_id, &var_id);
546 if (!IsLocalVar(var_id, func)) AddToWorklist(&*ii);
547 } break;
548 case SpvOpLoopMerge:
549 case SpvOpSelectionMerge:
550 case SpvOpUnreachable:
551 break;
552 default: {
553 // Function calls, atomics, function params, function returns, etc.
554 if (!ii->IsOpcodeSafeToDelete()) {
555 AddToWorklist(&*ii);
556 }
557 } break;
558 }
559 }
560 }
561 }
562
InitializeModuleScopeLiveInstructions()563 void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() {
564 // Keep all execution modes.
565 for (auto& exec : get_module()->execution_modes()) {
566 AddToWorklist(&exec);
567 }
568 // Keep all entry points.
569 for (auto& entry : get_module()->entry_points()) {
570 if (!preserve_interface_) {
571 live_insts_.Set(entry.unique_id());
572 // The actual function is live always.
573 AddToWorklist(
574 get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(1u)));
575 for (uint32_t i = 3; i < entry.NumInOperands(); ++i) {
576 auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
577 auto storage_class = var->GetSingleWordInOperand(0u);
578 // Vulkan support outputs without an associated input, but not inputs
579 // without an associated output.
580 if (storage_class == SpvStorageClassOutput) {
581 AddToWorklist(var);
582 }
583 }
584 } else {
585 AddToWorklist(&entry);
586 }
587 }
588 for (auto& anno : get_module()->annotations()) {
589 if (anno.opcode() == SpvOpDecorate) {
590 // Keep workgroup size.
591 if (anno.GetSingleWordInOperand(1u) == SpvDecorationBuiltIn &&
592 anno.GetSingleWordInOperand(2u) == SpvBuiltInWorkgroupSize) {
593 AddToWorklist(&anno);
594 }
595
596 if (context()->preserve_bindings()) {
597 // Keep all bindings.
598 if ((anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet) ||
599 (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)) {
600 AddToWorklist(&anno);
601 }
602 }
603
604 if (context()->preserve_spec_constants()) {
605 // Keep all specialization constant instructions
606 if (anno.GetSingleWordInOperand(1u) == SpvDecorationSpecId) {
607 AddToWorklist(&anno);
608 }
609 }
610 }
611 }
612
613 // For each DebugInfo GlobalVariable keep all operands except the Variable.
614 // Later, if the variable is killed with KillInst(), we will set the operand
615 // to DebugInfoNone. Create and save DebugInfoNone now for this possible
616 // later use. This is slightly unoptimal, but it avoids generating it during
617 // instruction killing when the module is not consistent.
618 bool debug_global_seen = false;
619 for (auto& dbg : get_module()->ext_inst_debuginfo()) {
620 if (dbg.GetCommonDebugOpcode() != CommonDebugInfoDebugGlobalVariable)
621 continue;
622 debug_global_seen = true;
623 dbg.ForEachInId([this](const uint32_t* iid) {
624 Instruction* in_inst = get_def_use_mgr()->GetDef(*iid);
625 if (in_inst->opcode() == SpvOpVariable) return;
626 AddToWorklist(in_inst);
627 });
628 }
629 if (debug_global_seen) {
630 auto dbg_none = context()->get_debug_info_mgr()->GetDebugInfoNone();
631 AddToWorklist(dbg_none);
632 }
633 }
634
ProcessImpl()635 Pass::Status AggressiveDCEPass::ProcessImpl() {
636 // Current functionality assumes shader capability
637 // TODO(greg-lunarg): Handle additional capabilities
638 if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
639 return Status::SuccessWithoutChange;
640
641 // Current functionality assumes relaxed logical addressing (see
642 // instruction.h)
643 // TODO(greg-lunarg): Handle non-logical addressing
644 if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
645 return Status::SuccessWithoutChange;
646
647 // The variable pointer extension is no longer needed to use the capability,
648 // so we have to look for the capability.
649 if (context()->get_feature_mgr()->HasCapability(
650 SpvCapabilityVariablePointersStorageBuffer))
651 return Status::SuccessWithoutChange;
652
653 // If any extensions in the module are not explicitly supported,
654 // return unmodified.
655 if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
656
657 // Eliminate Dead functions.
658 bool modified = EliminateDeadFunctions();
659
660 InitializeModuleScopeLiveInstructions();
661
662 // Process all entry point functions.
663 ProcessFunction pfn = [this](Function* fp) { return AggressiveDCE(fp); };
664 modified |= context()->ProcessReachableCallTree(pfn);
665
666 // If the decoration manager is kept live then the context will try to keep it
667 // up to date. ADCE deals with group decorations by changing the operands in
668 // |OpGroupDecorate| instruction directly without informing the decoration
669 // manager. This can put it in an invalid state which will cause an error
670 // when the context tries to update it. To avoid this problem invalidate
671 // the decoration manager upfront.
672 //
673 // We kill it at now because it is used when processing the entry point
674 // functions.
675 context()->InvalidateAnalyses(IRContext::Analysis::kAnalysisDecorations);
676
677 // Process module-level instructions. Now that all live instructions have
678 // been marked, it is safe to remove dead global values.
679 modified |= ProcessGlobalValues();
680
681 assert((to_kill_.empty() || modified) &&
682 "A dead instruction was identified, but no change recorded.");
683
684 // Kill all dead instructions.
685 for (auto inst : to_kill_) {
686 context()->KillInst(inst);
687 }
688
689 // Cleanup all CFG including all unreachable blocks.
690 ProcessFunction cleanup = [this](Function* f) { return CFGCleanup(f); };
691 modified |= context()->ProcessReachableCallTree(cleanup);
692
693 return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
694 }
695
EliminateDeadFunctions()696 bool AggressiveDCEPass::EliminateDeadFunctions() {
697 // Identify live functions first. Those that are not live
698 // are dead.
699 std::unordered_set<const Function*> live_function_set;
700 ProcessFunction mark_live = [&live_function_set](Function* fp) {
701 live_function_set.insert(fp);
702 return false;
703 };
704 context()->ProcessReachableCallTree(mark_live);
705
706 bool modified = false;
707 for (auto funcIter = get_module()->begin();
708 funcIter != get_module()->end();) {
709 if (live_function_set.count(&*funcIter) == 0) {
710 modified = true;
711 funcIter =
712 eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter);
713 } else {
714 ++funcIter;
715 }
716 }
717
718 return modified;
719 }
720
ProcessGlobalValues()721 bool AggressiveDCEPass::ProcessGlobalValues() {
722 // Remove debug and annotation statements referencing dead instructions.
723 // This must be done before killing the instructions, otherwise there are
724 // dead objects in the def/use database.
725 bool modified = false;
726 Instruction* instruction = &*get_module()->debug2_begin();
727 while (instruction) {
728 if (instruction->opcode() != SpvOpName) {
729 instruction = instruction->NextNode();
730 continue;
731 }
732
733 if (IsTargetDead(instruction)) {
734 instruction = context()->KillInst(instruction);
735 modified = true;
736 } else {
737 instruction = instruction->NextNode();
738 }
739 }
740
741 // This code removes all unnecessary decorations safely (see #1174). It also
742 // does so in a more efficient manner than deleting them only as the targets
743 // are deleted.
744 std::vector<Instruction*> annotations;
745 for (auto& inst : get_module()->annotations()) annotations.push_back(&inst);
746 std::sort(annotations.begin(), annotations.end(), DecorationLess());
747 for (auto annotation : annotations) {
748 switch (annotation->opcode()) {
749 case SpvOpDecorate:
750 case SpvOpMemberDecorate:
751 case SpvOpDecorateStringGOOGLE:
752 case SpvOpMemberDecorateStringGOOGLE:
753 if (IsTargetDead(annotation)) {
754 context()->KillInst(annotation);
755 modified = true;
756 }
757 break;
758 case SpvOpDecorateId:
759 if (IsTargetDead(annotation)) {
760 context()->KillInst(annotation);
761 modified = true;
762 } else {
763 if (annotation->GetSingleWordInOperand(1) ==
764 SpvDecorationHlslCounterBufferGOOGLE) {
765 // HlslCounterBuffer will reference an id other than the target.
766 // If that id is dead, then the decoration can be removed as well.
767 uint32_t counter_buffer_id = annotation->GetSingleWordInOperand(2);
768 Instruction* counter_buffer_inst =
769 get_def_use_mgr()->GetDef(counter_buffer_id);
770 if (!IsLive(counter_buffer_inst)) {
771 context()->KillInst(annotation);
772 modified = true;
773 }
774 }
775 }
776 break;
777 case SpvOpGroupDecorate: {
778 // Go through the targets of this group decorate. Remove each dead
779 // target. If all targets are dead, remove this decoration.
780 bool dead = true;
781 bool removed_operand = false;
782 for (uint32_t i = 1; i < annotation->NumOperands();) {
783 Instruction* opInst =
784 get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i));
785 if (!IsLive(opInst)) {
786 // Don't increment |i|.
787 annotation->RemoveOperand(i);
788 modified = true;
789 removed_operand = true;
790 } else {
791 i++;
792 dead = false;
793 }
794 }
795 if (dead) {
796 context()->KillInst(annotation);
797 modified = true;
798 } else if (removed_operand) {
799 context()->UpdateDefUse(annotation);
800 }
801 break;
802 }
803 case SpvOpGroupMemberDecorate: {
804 // Go through the targets of this group member decorate. Remove each
805 // dead target (and member index). If all targets are dead, remove this
806 // decoration.
807 bool dead = true;
808 bool removed_operand = false;
809 for (uint32_t i = 1; i < annotation->NumOperands();) {
810 Instruction* opInst =
811 get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i));
812 if (!IsLive(opInst)) {
813 // Don't increment |i|.
814 annotation->RemoveOperand(i + 1);
815 annotation->RemoveOperand(i);
816 modified = true;
817 removed_operand = true;
818 } else {
819 i += 2;
820 dead = false;
821 }
822 }
823 if (dead) {
824 context()->KillInst(annotation);
825 modified = true;
826 } else if (removed_operand) {
827 context()->UpdateDefUse(annotation);
828 }
829 break;
830 }
831 case SpvOpDecorationGroup:
832 // By the time we hit decoration groups we've checked everything that
833 // can target them. So if they have no uses they must be dead.
834 if (get_def_use_mgr()->NumUsers(annotation) == 0) {
835 context()->KillInst(annotation);
836 modified = true;
837 }
838 break;
839 default:
840 assert(false);
841 break;
842 }
843 }
844
845 for (auto& dbg : get_module()->ext_inst_debuginfo()) {
846 if (IsLive(&dbg)) continue;
847 // Save GlobalVariable if its variable is live, otherwise null out variable
848 // index
849 if (dbg.GetCommonDebugOpcode() == CommonDebugInfoDebugGlobalVariable) {
850 auto var_id = dbg.GetSingleWordOperand(kGlobalVariableVariableIndex);
851 Instruction* var_inst = get_def_use_mgr()->GetDef(var_id);
852 if (IsLive(var_inst)) continue;
853 context()->ForgetUses(&dbg);
854 dbg.SetOperand(
855 kGlobalVariableVariableIndex,
856 {context()->get_debug_info_mgr()->GetDebugInfoNone()->result_id()});
857 context()->AnalyzeUses(&dbg);
858 continue;
859 }
860 to_kill_.push_back(&dbg);
861 modified = true;
862 }
863
864 // Since ADCE is disabled for non-shaders, we don't check for export linkage
865 // attributes here.
866 for (auto& val : get_module()->types_values()) {
867 if (!IsLive(&val)) {
868 // Save forwarded pointer if pointer is live since closure does not mark
869 // this live as it does not have a result id. This is a little too
870 // conservative since it is not known if the structure type that needed
871 // it is still live. TODO(greg-lunarg): Only save if needed.
872 if (val.opcode() == SpvOpTypeForwardPointer) {
873 uint32_t ptr_ty_id = val.GetSingleWordInOperand(0);
874 Instruction* ptr_ty_inst = get_def_use_mgr()->GetDef(ptr_ty_id);
875 if (IsLive(ptr_ty_inst)) continue;
876 }
877 to_kill_.push_back(&val);
878 modified = true;
879 }
880 }
881
882 if (!preserve_interface_) {
883 // Remove the dead interface variables from the entry point interface list.
884 for (auto& entry : get_module()->entry_points()) {
885 std::vector<Operand> new_operands;
886 for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
887 if (i < 3) {
888 // Execution model, function id and name are always valid.
889 new_operands.push_back(entry.GetInOperand(i));
890 } else {
891 auto* var =
892 get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
893 if (IsLive(var)) {
894 new_operands.push_back(entry.GetInOperand(i));
895 }
896 }
897 }
898 if (new_operands.size() != entry.NumInOperands()) {
899 entry.SetInOperands(std::move(new_operands));
900 get_def_use_mgr()->UpdateDefUse(&entry);
901 }
902 }
903 }
904
905 return modified;
906 }
907
Process()908 Pass::Status AggressiveDCEPass::Process() {
909 // Initialize extensions allowlist
910 InitExtensions();
911 return ProcessImpl();
912 }
913
InitExtensions()914 void AggressiveDCEPass::InitExtensions() {
915 extensions_allowlist_.clear();
916 extensions_allowlist_.insert({
917 "SPV_AMD_shader_explicit_vertex_parameter",
918 "SPV_AMD_shader_trinary_minmax",
919 "SPV_AMD_gcn_shader",
920 "SPV_KHR_shader_ballot",
921 "SPV_AMD_shader_ballot",
922 "SPV_AMD_gpu_shader_half_float",
923 "SPV_KHR_shader_draw_parameters",
924 "SPV_KHR_subgroup_vote",
925 "SPV_KHR_8bit_storage",
926 "SPV_KHR_16bit_storage",
927 "SPV_KHR_device_group",
928 "SPV_KHR_multiview",
929 "SPV_NVX_multiview_per_view_attributes",
930 "SPV_NV_viewport_array2",
931 "SPV_NV_stereo_view_rendering",
932 "SPV_NV_sample_mask_override_coverage",
933 "SPV_NV_geometry_shader_passthrough",
934 "SPV_AMD_texture_gather_bias_lod",
935 "SPV_KHR_storage_buffer_storage_class",
936 // SPV_KHR_variable_pointers
937 // Currently do not support extended pointer expressions
938 "SPV_AMD_gpu_shader_int16",
939 "SPV_KHR_post_depth_coverage",
940 "SPV_KHR_shader_atomic_counter_ops",
941 "SPV_EXT_shader_stencil_export",
942 "SPV_EXT_shader_viewport_index_layer",
943 "SPV_AMD_shader_image_load_store_lod",
944 "SPV_AMD_shader_fragment_mask",
945 "SPV_EXT_fragment_fully_covered",
946 "SPV_AMD_gpu_shader_half_float_fetch",
947 "SPV_GOOGLE_decorate_string",
948 "SPV_GOOGLE_hlsl_functionality1",
949 "SPV_GOOGLE_user_type",
950 "SPV_NV_shader_subgroup_partitioned",
951 "SPV_EXT_demote_to_helper_invocation",
952 "SPV_EXT_descriptor_indexing",
953 "SPV_NV_fragment_shader_barycentric",
954 "SPV_NV_compute_shader_derivatives",
955 "SPV_NV_shader_image_footprint",
956 "SPV_NV_shading_rate",
957 "SPV_NV_mesh_shader",
958 "SPV_NV_ray_tracing",
959 "SPV_KHR_ray_tracing",
960 "SPV_KHR_ray_query",
961 "SPV_EXT_fragment_invocation_density",
962 "SPV_EXT_physical_storage_buffer",
963 "SPV_KHR_terminate_invocation",
964 "SPV_KHR_shader_clock",
965 "SPV_KHR_vulkan_memory_model",
966 "SPV_KHR_subgroup_uniform_control_flow",
967 "SPV_KHR_integer_dot_product",
968 "SPV_EXT_shader_image_int64",
969 "SPV_KHR_non_semantic_info",
970 });
971 }
972
GetHeaderBranch(BasicBlock * blk)973 Instruction* AggressiveDCEPass::GetHeaderBranch(BasicBlock* blk) {
974 if (blk == nullptr) {
975 return nullptr;
976 }
977 BasicBlock* header_block = GetHeaderBlock(blk);
978 if (header_block == nullptr) {
979 return nullptr;
980 }
981 return header_block->terminator();
982 }
983
GetHeaderBlock(BasicBlock * blk) const984 BasicBlock* AggressiveDCEPass::GetHeaderBlock(BasicBlock* blk) const {
985 if (blk == nullptr) {
986 return nullptr;
987 }
988
989 BasicBlock* header_block = nullptr;
990 if (blk->IsLoopHeader()) {
991 header_block = blk;
992 } else {
993 uint32_t header =
994 context()->GetStructuredCFGAnalysis()->ContainingConstruct(blk->id());
995 header_block = context()->get_instr_block(header);
996 }
997 return header_block;
998 }
999
GetMergeInstruction(Instruction * inst)1000 Instruction* AggressiveDCEPass::GetMergeInstruction(Instruction* inst) {
1001 BasicBlock* bb = context()->get_instr_block(inst);
1002 if (bb == nullptr) {
1003 return nullptr;
1004 }
1005 return bb->GetMergeInst();
1006 }
1007
GetBranchForNextHeader(BasicBlock * blk)1008 Instruction* AggressiveDCEPass::GetBranchForNextHeader(BasicBlock* blk) {
1009 if (blk == nullptr) {
1010 return nullptr;
1011 }
1012
1013 if (blk->IsLoopHeader()) {
1014 uint32_t header =
1015 context()->GetStructuredCFGAnalysis()->ContainingConstruct(blk->id());
1016 blk = context()->get_instr_block(header);
1017 }
1018 return GetHeaderBranch(blk);
1019 }
1020
MarkFunctionParameterAsLive(const Function * func)1021 void AggressiveDCEPass::MarkFunctionParameterAsLive(const Function* func) {
1022 func->ForEachParam(
1023 [this](const Instruction* param) {
1024 AddToWorklist(const_cast<Instruction*>(param));
1025 },
1026 false);
1027 }
1028
BlockIsInConstruct(BasicBlock * header_block,BasicBlock * bb)1029 bool AggressiveDCEPass::BlockIsInConstruct(BasicBlock* header_block,
1030 BasicBlock* bb) {
1031 if (bb == nullptr || header_block == nullptr) {
1032 return false;
1033 }
1034
1035 uint32_t current_header = bb->id();
1036 while (current_header != 0) {
1037 if (current_header == header_block->id()) return true;
1038 current_header = context()->GetStructuredCFGAnalysis()->ContainingConstruct(
1039 current_header);
1040 }
1041 return false;
1042 }
1043
IsEntryPointWithNoCalls(Function * func)1044 bool AggressiveDCEPass::IsEntryPointWithNoCalls(Function* func) {
1045 auto cached_result = entry_point_with_no_calls_cache_.find(func->result_id());
1046 if (cached_result != entry_point_with_no_calls_cache_.end()) {
1047 return cached_result->second;
1048 }
1049 bool result = IsEntryPoint(func) && !HasCall(func);
1050 entry_point_with_no_calls_cache_[func->result_id()] = result;
1051 return result;
1052 }
1053
IsEntryPoint(Function * func)1054 bool AggressiveDCEPass::IsEntryPoint(Function* func) {
1055 for (const Instruction& entry_point : get_module()->entry_points()) {
1056 uint32_t entry_point_id =
1057 entry_point.GetSingleWordInOperand(kEntryPointFunctionIdInIdx);
1058 if (entry_point_id == func->result_id()) {
1059 return true;
1060 }
1061 }
1062 return false;
1063 }
1064
HasCall(Function * func)1065 bool AggressiveDCEPass::HasCall(Function* func) {
1066 return !func->WhileEachInst(
1067 [](Instruction* inst) { return inst->opcode() != SpvOpFunctionCall; });
1068 }
1069
MarkFirstBlockAsLive(Function * func)1070 void AggressiveDCEPass::MarkFirstBlockAsLive(Function* func) {
1071 BasicBlock* first_block = &*func->begin();
1072 MarkBlockAsLive(first_block->GetLabelInst());
1073 }
1074
AddUnreachable(BasicBlock * & block)1075 void AggressiveDCEPass::AddUnreachable(BasicBlock*& block) {
1076 InstructionBuilder builder(
1077 context(), block,
1078 IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
1079 builder.AddUnreachable();
1080 }
1081
1082 } // namespace opt
1083 } // namespace spvtools
1084