| 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 | |