void Pass::AddCalls(ir::Function* func, std::queue<uint32_t>* todo) { for (auto bi = func->begin(); bi != func->end(); ++bi) for (auto ii = bi->begin(); ii != bi->end(); ++ii) if (ii->opcode() == SpvOpFunctionCall) todo->push(ii->GetSingleWordInOperand(0)); }
bool CommonUniformElimPass::CommonExtractElimination(ir::Function* func) { // Find all composite ids with duplicate extracts. for (auto bi = func->begin(); bi != func->end(); ++bi) { for (auto ii = bi->begin(); ii != bi->end(); ++ii) { if (ii->opcode() != SpvOpCompositeExtract) continue; // TODO(greg-lunarg): Support multiple indices if (ii->NumInOperands() > 2) continue; if (HasUnsupportedDecorates(ii->result_id())) continue; uint32_t compId = ii->GetSingleWordInOperand(kExtractCompositeIdInIdx); uint32_t idx = ii->GetSingleWordInOperand(kExtractIdx0InIdx); comp2idx2inst_[compId][idx].push_back(&*ii); } } // For all defs of ids with duplicate extracts, insert new extracts // after def, and replace and delete old extracts bool modified = false; for (auto bi = func->begin(); bi != func->end(); ++bi) { for (auto ii = bi->begin(); ii != bi->end(); ++ii) { const auto cItr = comp2idx2inst_.find(ii->result_id()); if (cItr == comp2idx2inst_.end()) continue; for (auto idxItr : cItr->second) { if (idxItr.second.size() < 2) continue; uint32_t replId = TakeNextId(); std::unique_ptr<ir::Instruction> newExtract(new ir::Instruction(*idxItr.second.front())); newExtract->SetResultId(replId); def_use_mgr_->AnalyzeInstDefUse(&*newExtract); ++ii; ii = ii.InsertBefore(std::move(newExtract)); for (auto instItr : idxItr.second) { uint32_t resId = instItr->result_id(); KillNamesAndDecorates(resId); (void)def_use_mgr_->ReplaceAllUsesWith(resId, replId); def_use_mgr_->KillInst(instItr); } modified = true; } } } return modified; }
uint32_t CommonUniformElimPass::MergeBlockIdIfAny(const ir::BasicBlock& blk, uint32_t* cbid) { auto merge_ii = blk.cend(); --merge_ii; *cbid = 0; uint32_t mbid = 0; if (merge_ii != blk.cbegin()) { --merge_ii; if (merge_ii->opcode() == SpvOpLoopMerge) { mbid = merge_ii->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx); *cbid = merge_ii->GetSingleWordInOperand(kLoopMergeContinueBlockIdInIdx); } else if (merge_ii->opcode() == SpvOpSelectionMerge) { mbid = merge_ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx); } } return mbid; }
bool LocalAccessChainConvertPass::ConvertLocalAccessChains(Function* func) { FindTargetVars(func); // Replace access chains of all targeted variables with equivalent // extract and insert sequences bool modified = false; for (auto bi = func->begin(); bi != func->end(); ++bi) { std::vector<Instruction*> dead_instructions; for (auto ii = bi->begin(); ii != bi->end(); ++ii) { switch (ii->opcode()) { case SpvOpLoad: { uint32_t varId; Instruction* ptrInst = GetPtr(&*ii, &varId); if (!IsNonPtrAccessChain(ptrInst->opcode())) break; if (!IsTargetVar(varId)) break; std::vector<std::unique_ptr<Instruction>> newInsts; ReplaceAccessChainLoad(ptrInst, &*ii); modified = true; } break; case SpvOpStore: { uint32_t varId; Instruction* ptrInst = GetPtr(&*ii, &varId); if (!IsNonPtrAccessChain(ptrInst->opcode())) break; if (!IsTargetVar(varId)) break; std::vector<std::unique_ptr<Instruction>> newInsts; uint32_t valId = ii->GetSingleWordInOperand(kStoreValIdInIdx); GenAccessChainStoreReplacement(ptrInst, valId, &newInsts); dead_instructions.push_back(&*ii); ++ii; ii = ii.InsertBefore(std::move(newInsts)); ++ii; ++ii; modified = true; } break; default: break; } } while (!dead_instructions.empty()) { Instruction* inst = dead_instructions.back(); dead_instructions.pop_back(); DCEInst(inst, [&dead_instructions](Instruction* other_inst) { auto i = std::find(dead_instructions.begin(), dead_instructions.end(), other_inst); if (i != dead_instructions.end()) { dead_instructions.erase(i); } }); } } return modified; }
bool LocalAccessChainConvertPass::ConvertLocalAccessChains(ir::Function* func) { FindTargetVars(func); // Replace access chains of all targeted variables with equivalent // extract and insert sequences bool modified = false; for (auto bi = func->begin(); bi != func->end(); ++bi) { for (auto ii = bi->begin(); ii != bi->end(); ++ii) { switch (ii->opcode()) { case SpvOpLoad: { uint32_t varId; ir::Instruction* ptrInst = GetPtr(&*ii, &varId); if (!IsNonPtrAccessChain(ptrInst->opcode())) break; if (!IsTargetVar(varId)) break; std::vector<std::unique_ptr<ir::Instruction>> newInsts; uint32_t replId = GenAccessChainLoadReplacement(ptrInst, &newInsts); ReplaceAndDeleteLoad(&*ii, replId); ++ii; ii = ii.InsertBefore(&newInsts); ++ii; modified = true; } break; case SpvOpStore: { uint32_t varId; ir::Instruction* ptrInst = GetPtr(&*ii, &varId); if (!IsNonPtrAccessChain(ptrInst->opcode())) break; if (!IsTargetVar(varId)) break; std::vector<std::unique_ptr<ir::Instruction>> newInsts; uint32_t valId = ii->GetSingleWordInOperand(kStoreValIdInIdx); GenAccessChainStoreReplacement(ptrInst, valId, &newInsts); def_use_mgr_->KillInst(&*ii); DeleteIfUseless(ptrInst); ++ii; ii = ii.InsertBefore(&newInsts); ++ii; ++ii; modified = true; } break; default: break; } } } return modified; }
bool LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElim( Function* func) { // Perform local store/load, load/load and store/store elimination // on each block bool modified = false; std::vector<Instruction*> instructions_to_kill; std::unordered_set<Instruction*> instructions_to_save; for (auto bi = func->begin(); bi != func->end(); ++bi) { var2store_.clear(); var2load_.clear(); auto next = bi->begin(); for (auto ii = next; ii != bi->end(); ii = next) { ++next; switch (ii->opcode()) { case SpvOpStore: { // Verify store variable is target type uint32_t varId; Instruction* ptrInst = GetPtr(&*ii, &varId); if (!IsTargetVar(varId)) continue; if (!HasOnlySupportedRefs(varId)) continue; // If a store to the whole variable, remember it for succeeding // loads and stores. Otherwise forget any previous store to that // variable. if (ptrInst->opcode() == SpvOpVariable) { // If a previous store to same variable, mark the store // for deletion if not still used. auto prev_store = var2store_.find(varId); if (prev_store != var2store_.end() && instructions_to_save.count(prev_store->second) == 0) { instructions_to_kill.push_back(prev_store->second); modified = true; } bool kill_store = false; auto li = var2load_.find(varId); if (li != var2load_.end()) { if (ii->GetSingleWordInOperand(kStoreValIdInIdx) == li->second->result_id()) { // We are storing the same value that already exists in the // memory location. The store does nothing. kill_store = true; } } if (!kill_store) { var2store_[varId] = &*ii; var2load_.erase(varId); } else { instructions_to_kill.push_back(&*ii); modified = true; } } else { assert(IsNonPtrAccessChain(ptrInst->opcode())); var2store_.erase(varId); var2load_.erase(varId); } } break; case SpvOpLoad: { // Verify store variable is target type uint32_t varId; Instruction* ptrInst = GetPtr(&*ii, &varId); if (!IsTargetVar(varId)) continue; if (!HasOnlySupportedRefs(varId)) continue; uint32_t replId = 0; if (ptrInst->opcode() == SpvOpVariable) { // If a load from a variable, look for a previous store or // load from that variable and use its value. auto si = var2store_.find(varId); if (si != var2store_.end()) { replId = si->second->GetSingleWordInOperand(kStoreValIdInIdx); } else { auto li = var2load_.find(varId); if (li != var2load_.end()) { replId = li->second->result_id(); } } } else { // If a partial load of a previously seen store, remember // not to delete the store. auto si = var2store_.find(varId); if (si != var2store_.end()) instructions_to_save.insert(si->second); } if (replId != 0) { // replace load's result id and delete load context()->KillNamesAndDecorates(&*ii); context()->ReplaceAllUsesWith(ii->result_id(), replId); instructions_to_kill.push_back(&*ii); modified = true; } else { if (ptrInst->opcode() == SpvOpVariable) var2load_[varId] = &*ii; // register load } } break; case SpvOpFunctionCall: { // Conservatively assume all locals are redefined for now. // TODO(): Handle more optimally var2store_.clear(); var2load_.clear(); } break; default: break; } } } for (Instruction* inst : instructions_to_kill) { context()->KillInst(inst); } return modified; }
bool AggressiveDCEPass::AggressiveDCE(Function* func) { // Mark function parameters as live. AddToWorklist(&func->DefInst()); func->ForEachParam( [this](const Instruction* param) { AddToWorklist(const_cast<Instruction*>(param)); }, false); // Compute map from block to controlling conditional branch std::list<BasicBlock*> structuredOrder; cfg()->ComputeStructuredOrder(func, &*func->begin(), &structuredOrder); ComputeBlock2HeaderMaps(structuredOrder); bool modified = false; // Add instructions with external side effects to worklist. Also add branches // EXCEPT those immediately contained in an "if" selection construct or a loop // or continue construct. // TODO(greg-lunarg): Handle Frexp, Modf more optimally call_in_func_ = false; func_is_entry_point_ = false; private_stores_.clear(); // Stacks to keep track of when we are inside an if- or loop-construct. // When immediately inside an if- or loop-construct, we do not initially // mark branches live. All other branches must be marked live. std::stack<bool> assume_branches_live; std::stack<uint32_t> currentMergeBlockId; // Push sentinel values on stack for when outside of any control flow. assume_branches_live.push(true); currentMergeBlockId.push(0); for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) { // If exiting if or loop, update stacks if ((*bi)->id() == currentMergeBlockId.top()) { assume_branches_live.pop(); currentMergeBlockId.pop(); } for (auto ii = (*bi)->begin(); ii != (*bi)->end(); ++ii) { SpvOp op = ii->opcode(); switch (op) { case SpvOpStore: { uint32_t varId; (void)GetPtr(&*ii, &varId); // Mark stores as live if their variable is not function scope // and is not private scope. Remember private stores for possible // later inclusion. We cannot call IsLocalVar at this point because // private_like_local_ has not been set yet. if (IsVarOfStorage(varId, SpvStorageClassPrivate) || IsVarOfStorage(varId, SpvStorageClassWorkgroup)) private_stores_.push_back(&*ii); else if (!IsVarOfStorage(varId, SpvStorageClassFunction)) AddToWorklist(&*ii); } break; case SpvOpCopyMemory: case SpvOpCopyMemorySized: { uint32_t varId; (void)GetPtr(ii->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx), &varId); if (IsVarOfStorage(varId, SpvStorageClassPrivate) || IsVarOfStorage(varId, SpvStorageClassWorkgroup)) private_stores_.push_back(&*ii); else if (!IsVarOfStorage(varId, SpvStorageClassFunction)) AddToWorklist(&*ii); } break; case SpvOpLoopMerge: { assume_branches_live.push(false); currentMergeBlockId.push( ii->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx)); } break; case SpvOpSelectionMerge: { assume_branches_live.push(false); currentMergeBlockId.push( ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx)); } break; case SpvOpSwitch: case SpvOpBranch: case SpvOpBranchConditional: case SpvOpUnreachable: { if (assume_branches_live.top()) { AddToWorklist(&*ii); } } break; default: { // Function calls, atomics, function params, function returns, etc. // TODO(greg-lunarg): function calls live only if write to non-local if (!ii->IsOpcodeSafeToDelete()) { AddToWorklist(&*ii); } // Remember function calls if (op == SpvOpFunctionCall) call_in_func_ = true; } break; } } } // See if current function is an entry point for (auto& ei : get_module()->entry_points()) { if (ei.GetSingleWordInOperand(kEntryPointFunctionIdInIdx) == func->result_id()) { func_is_entry_point_ = true; break; } } // If the current function is an entry point and has no function calls, // we can optimize private variables as locals private_like_local_ = func_is_entry_point_ && !call_in_func_; // If privates are not like local, add their stores to worklist if (!private_like_local_) for (auto& ps : private_stores_) AddToWorklist(ps); // Perform closure on live instruction set. while (!worklist_.empty()) { Instruction* liveInst = worklist_.front(); // Add all operand instructions if not already live liveInst->ForEachInId([&liveInst, this](const uint32_t* iid) { Instruction* inInst = get_def_use_mgr()->GetDef(*iid); // Do not add label if an operand of a branch. This is not needed // as part of live code discovery and can create false live code, // for example, the branch to a header of a loop. if (inInst->opcode() == SpvOpLabel && liveInst->IsBranch()) return; AddToWorklist(inInst); }); if (liveInst->type_id() != 0) { AddToWorklist(get_def_use_mgr()->GetDef(liveInst->type_id())); } // If in a structured if or loop construct, add the controlling // conditional branch and its merge. BasicBlock* blk = context()->get_instr_block(liveInst); Instruction* branchInst = block2headerBranch_[blk]; if (branchInst != nullptr) { AddToWorklist(branchInst); Instruction* mergeInst = branch2merge_[branchInst]; AddToWorklist(mergeInst); } // If the block is a header, add the next outermost controlling // conditional branch and its merge. Instruction* nextBranchInst = header2nextHeaderBranch_[blk]; if (nextBranchInst != nullptr) { AddToWorklist(nextBranchInst); Instruction* mergeInst = branch2merge_[nextBranchInst]; AddToWorklist(mergeInst); } // If local load, add all variable's stores if variable not already live if (liveInst->opcode() == SpvOpLoad || liveInst->IsAtomicWithLoad()) { uint32_t varId; (void)GetPtr(liveInst, &varId); if (varId != 0) { ProcessLoad(varId); } // Process memory copies like loads } else if (liveInst->opcode() == SpvOpCopyMemory || liveInst->opcode() == SpvOpCopyMemorySized) { uint32_t varId; (void)GetPtr(liveInst->GetSingleWordInOperand(kCopyMemorySourceAddrInIdx), &varId); if (varId != 0) { ProcessLoad(varId); } // If merge, add other branches that are part of its control structure } else if (liveInst->opcode() == SpvOpLoopMerge || liveInst->opcode() == SpvOpSelectionMerge) { AddBreaksAndContinuesToWorklist(liveInst); // If function call, treat as if it loads from all pointer arguments } else if (liveInst->opcode() == SpvOpFunctionCall) { liveInst->ForEachInId([this](const uint32_t* iid) { // Skip non-ptr args if (!IsPtr(*iid)) return; uint32_t varId; (void)GetPtr(*iid, &varId); ProcessLoad(varId); }); // If function parameter, treat as if it's result id is loaded from } else if (liveInst->opcode() == SpvOpFunctionParameter) { ProcessLoad(liveInst->result_id()); // We treat an OpImageTexelPointer as a load of the pointer, and // that value is manipulated to get the result. } else if (liveInst->opcode() == SpvOpImageTexelPointer) { uint32_t varId; (void)GetPtr(liveInst, &varId); if (varId != 0) { ProcessLoad(varId); } } worklist_.pop(); } // Kill dead instructions and remember dead blocks for (auto bi = structuredOrder.begin(); bi != structuredOrder.end();) { uint32_t mergeBlockId = 0; (*bi)->ForEachInst([this, &modified, &mergeBlockId](Instruction* inst) { if (!IsDead(inst)) return; if (inst->opcode() == SpvOpLabel) return; // If dead instruction is selection merge, remember merge block // for new branch at end of block if (inst->opcode() == SpvOpSelectionMerge || inst->opcode() == SpvOpLoopMerge) mergeBlockId = inst->GetSingleWordInOperand(0); to_kill_.push_back(inst); modified = true; }); // If a structured if or loop was deleted, add a branch to its merge // block, and traverse to the merge block and continue processing there. // We know the block still exists because the label is not deleted. if (mergeBlockId != 0) { AddBranch(mergeBlockId, *bi); for (++bi; (*bi)->id() != mergeBlockId; ++bi) { } auto merge_terminator = (*bi)->terminator(); if (merge_terminator->opcode() == SpvOpUnreachable) { // The merge was unreachable. This is undefined behaviour so just // return (or return an undef). Then mark the new return as live. auto func_ret_type_inst = get_def_use_mgr()->GetDef(func->type_id()); if (func_ret_type_inst->opcode() == SpvOpTypeVoid) { merge_terminator->SetOpcode(SpvOpReturn); } else { // Find an undef for the return value and make sure it gets kept by // the pass. auto undef_id = Type2Undef(func->type_id()); auto undef = get_def_use_mgr()->GetDef(undef_id); live_insts_.Set(undef->unique_id()); merge_terminator->SetOpcode(SpvOpReturnValue); merge_terminator->SetInOperands({{SPV_OPERAND_TYPE_ID, {undef_id}}}); get_def_use_mgr()->AnalyzeInstUse(merge_terminator); } live_insts_.Set(merge_terminator->unique_id()); } } else { ++bi; } } return modified; }