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