// Copyright (c) 2017 The Khronos Group Inc. // Copyright (c) 2017 Valve Corporation // Copyright (c) 2017 LunarG Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "iterator.h" #include "local_single_block_elim_pass.h" static const int kSpvEntryPointFunctionId = 1; static const int kSpvStorePtrId = 0; static const int kSpvStoreValId = 1; static const int kSpvLoadPtrId = 0; static const int kSpvAccessChainPtrId = 0; static const int kSpvTypePointerStorageClass = 0; static const int kSpvTypePointerTypeId = 1; namespace spvtools { namespace opt { bool LocalSingleBlockLoadStoreElimPass::IsNonPtrAccessChain( const SpvOp opcode) const { return opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain; } bool LocalSingleBlockLoadStoreElimPass::IsMathType( const ir::Instruction* typeInst) const { switch (typeInst->opcode()) { case SpvOpTypeInt: case SpvOpTypeFloat: case SpvOpTypeBool: case SpvOpTypeVector: case SpvOpTypeMatrix: return true; default: break; } return false; } bool LocalSingleBlockLoadStoreElimPass::IsTargetType( const ir::Instruction* typeInst) const { if (IsMathType(typeInst)) return true; if (typeInst->opcode() == SpvOpTypeArray) return IsMathType(def_use_mgr_->GetDef(typeInst->GetSingleWordOperand(1))); if (typeInst->opcode() != SpvOpTypeStruct) return false; // All struct members must be math type int nonMathComp = 0; typeInst->ForEachInId([&nonMathComp,this](const uint32_t* tid) { ir::Instruction* compTypeInst = def_use_mgr_->GetDef(*tid); if (!IsMathType(compTypeInst)) ++nonMathComp; }); return nonMathComp == 0; } ir::Instruction* LocalSingleBlockLoadStoreElimPass::GetPtr( ir::Instruction* ip, uint32_t* varId) { *varId = ip->GetSingleWordInOperand( ip->opcode() == SpvOpStore ? kSpvStorePtrId : kSpvLoadPtrId); ir::Instruction* ptrInst = def_use_mgr_->GetDef(*varId); ir::Instruction* varInst = ptrInst; while (IsNonPtrAccessChain(varInst->opcode())) { *varId = varInst->GetSingleWordInOperand(kSpvAccessChainPtrId); varInst = def_use_mgr_->GetDef(*varId); } return ptrInst; } bool LocalSingleBlockLoadStoreElimPass::IsTargetVar(uint32_t varId) { if (seen_non_target_vars_.find(varId) != seen_non_target_vars_.end()) return false; if (seen_target_vars_.find(varId) != seen_target_vars_.end()) return true; const ir::Instruction* varInst = def_use_mgr_->GetDef(varId); assert(varInst->opcode() == SpvOpVariable); const uint32_t varTypeId = varInst->type_id(); const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId); if (varTypeInst->GetSingleWordInOperand(kSpvTypePointerStorageClass) != SpvStorageClassFunction) { seen_non_target_vars_.insert(varId); return false; } const uint32_t varPteTypeId = varTypeInst->GetSingleWordInOperand(kSpvTypePointerTypeId); ir::Instruction* varPteTypeInst = def_use_mgr_->GetDef(varPteTypeId); if (!IsTargetType(varPteTypeInst)) { seen_non_target_vars_.insert(varId); return false; } seen_target_vars_.insert(varId); return true; } void LocalSingleBlockLoadStoreElimPass::ReplaceAndDeleteLoad( ir::Instruction* loadInst, uint32_t replId) { const uint32_t loadId = loadInst->result_id(); (void) def_use_mgr_->ReplaceAllUsesWith(loadId, replId); // TODO(greg-lunarg): Consider moving DCE into separate pass DCEInst(loadInst); } bool LocalSingleBlockLoadStoreElimPass::HasLoads(uint32_t ptrId) const { analysis::UseList* uses = def_use_mgr_->GetUses(ptrId); if (uses == nullptr) return false; for (auto u : *uses) { SpvOp op = u.inst->opcode(); if (IsNonPtrAccessChain(op)) { if (HasLoads(u.inst->result_id())) return true; } else { // Conservatively assume that calls will do a load // TODO(): Improve analysis around function calls if (op == SpvOpLoad || op == SpvOpFunctionCall) return true; } } return false; } bool LocalSingleBlockLoadStoreElimPass::IsLiveVar(uint32_t varId) const { // non-function scope vars are live const ir::Instruction* varInst = def_use_mgr_->GetDef(varId); assert(varInst->opcode() == SpvOpVariable); const uint32_t varTypeId = varInst->type_id(); const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId); if (varTypeInst->GetSingleWordInOperand(kSpvTypePointerStorageClass) != SpvStorageClassFunction) return true; // test if variable is loaded from return HasLoads(varId); } bool LocalSingleBlockLoadStoreElimPass::IsLiveStore( ir::Instruction* storeInst) { // get store's variable uint32_t varId; (void) GetPtr(storeInst, &varId); return IsLiveVar(varId); } void LocalSingleBlockLoadStoreElimPass::AddStores( uint32_t ptr_id, std::queue* insts) { analysis::UseList* uses = def_use_mgr_->GetUses(ptr_id); if (uses != nullptr) { for (auto u : *uses) { if (IsNonPtrAccessChain(u.inst->opcode())) AddStores(u.inst->result_id(), insts); else if (u.inst->opcode() == SpvOpStore) insts->push(u.inst); } } } void LocalSingleBlockLoadStoreElimPass::DCEInst(ir::Instruction* inst) { std::queue deadInsts; deadInsts.push(inst); while (!deadInsts.empty()) { ir::Instruction* di = deadInsts.front(); // Don't delete labels if (di->opcode() == SpvOpLabel) { deadInsts.pop(); continue; } // Remember operands std::vector ids; di->ForEachInId([&ids](uint32_t* iid) { ids.push_back(*iid); }); uint32_t varId = 0; // Remember variable if dead load if (di->opcode() == SpvOpLoad) (void) GetPtr(di, &varId); def_use_mgr_->KillInst(di); // For all operands with no remaining uses, add their instruction // to the dead instruction queue. for (auto id : ids) { analysis::UseList* uses = def_use_mgr_->GetUses(id); if (uses == nullptr) deadInsts.push(def_use_mgr_->GetDef(id)); } // if a load was deleted and it was the variable's // last load, add all its stores to dead queue if (varId != 0 && !IsLiveVar(varId)) AddStores(varId, &deadInsts); deadInsts.pop(); } } bool LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElim( ir::Function* func) { // Verify no CopyObject ops in function. This is a pre-SSA pass and // is generally not useful for code already in CSSA form. for (auto& blk : *func) for (auto& inst : blk) if (inst.opcode() == SpvOpCopyObject) return false; // Perform local store/load and load/load elimination on each block bool modified = false; for (auto bi = func->begin(); bi != func->end(); ++bi) { var2store_.clear(); var2load_.clear(); pinned_vars_.clear(); for (auto ii = bi->begin(); ii != bi->end(); ++ii) { switch (ii->opcode()) { case SpvOpStore: { // Verify store variable is target type uint32_t varId; ir::Instruction* ptrInst = GetPtr(&*ii, &varId); if (!IsTargetVar(varId)) continue; // Register the store if (ptrInst->opcode() == SpvOpVariable) { // if not pinned, look for WAW if (pinned_vars_.find(varId) == pinned_vars_.end()) { auto si = var2store_.find(varId); if (si != var2store_.end()) { def_use_mgr_->KillInst(si->second); } } var2store_[varId] = &*ii; } else { assert(IsNonPtrAccessChain(ptrInst->opcode())); var2store_.erase(varId); } pinned_vars_.erase(varId); var2load_.erase(varId); } break; case SpvOpLoad: { // Verify store variable is target type uint32_t varId; ir::Instruction* ptrInst = GetPtr(&*ii, &varId); if (!IsTargetVar(varId)) continue; // Look for previous store or load uint32_t replId = 0; if (ptrInst->opcode() == SpvOpVariable) { auto si = var2store_.find(varId); if (si != var2store_.end()) { replId = si->second->GetSingleWordInOperand(kSpvStoreValId); } else { auto li = var2load_.find(varId); if (li != var2load_.end()) { replId = li->second->result_id(); } } } if (replId != 0) { // replace load's result id and delete load ReplaceAndDeleteLoad(&*ii, replId); modified = true; } else { if (ptrInst->opcode() == SpvOpVariable) var2load_[varId] = &*ii; // register load pinned_vars_.insert(varId); } } break; case SpvOpFunctionCall: { // Conservatively assume all locals are redefined for now. // TODO(): Handle more optimally var2store_.clear(); var2load_.clear(); pinned_vars_.clear(); } break; default: break; } } // Go back and delete useless stores in block // TODO(greg-lunarg): Consider moving DCE into separate pass for (auto ii = bi->begin(); ii != bi->end(); ++ii) { if (ii->opcode() != SpvOpStore) continue; if (IsLiveStore(&*ii)) continue; DCEInst(&*ii); } } return modified; } void LocalSingleBlockLoadStoreElimPass::Initialize(ir::Module* module) { module_ = module; // Initialize function and block maps id2function_.clear(); for (auto& fn : *module_) id2function_[fn.result_id()] = &fn; // Initialize Target Type Caches seen_target_vars_.clear(); seen_non_target_vars_.clear(); // TODO(): Reuse def/use from previous passes def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_)); // Start new ids with next availablein module next_id_ = module_->id_bound(); }; Pass::Status LocalSingleBlockLoadStoreElimPass::ProcessImpl() { // Assumes logical addressing only if (module_->HasCapability(SpvCapabilityAddresses)) return Status::SuccessWithoutChange; bool modified = false; // Call Mem2Reg on all remaining functions. for (auto& e : module_->entry_points()) { ir::Function* fn = id2function_[e.GetSingleWordOperand(kSpvEntryPointFunctionId)]; modified = LocalSingleBlockLoadStoreElim(fn) || modified; } FinalizeNextId(module_); return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElimPass() : module_(nullptr), def_use_mgr_(nullptr), next_id_(0) {} Pass::Status LocalSingleBlockLoadStoreElimPass::Process(ir::Module* module) { Initialize(module); return ProcessImpl(); } } // namespace opt } // namespace spvtools