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