// 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 "mem_pass.h" #include "iterator.h" namespace spvtools { namespace opt { namespace { const uint32_t kStorePtrIdInIdx = 0; const uint32_t kLoadPtrIdInIdx = 0; const uint32_t kAccessChainPtrIdInIdx = 0; const uint32_t kCopyObjectOperandInIdx = 0; const uint32_t kTypePointerStorageClassInIdx = 0; const uint32_t kTypePointerTypeIdInIdx = 1; } // namespace anonymous bool MemPass::IsBaseTargetType( const ir::Instruction* typeInst) const { switch (typeInst->opcode()) { case SpvOpTypeInt: case SpvOpTypeFloat: case SpvOpTypeBool: case SpvOpTypeVector: case SpvOpTypeMatrix: case SpvOpTypeImage: case SpvOpTypeSampler: case SpvOpTypeSampledImage: return true; default: break; } return false; } bool MemPass::IsTargetType( const ir::Instruction* typeInst) const { if (IsBaseTargetType(typeInst)) return true; if (typeInst->opcode() == SpvOpTypeArray) return IsBaseTargetType( 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 (!IsBaseTargetType(compTypeInst)) ++nonMathComp; }); return nonMathComp == 0; } bool MemPass::IsNonPtrAccessChain(const SpvOp opcode) const { return opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain; } bool MemPass::IsPtr(uint32_t ptrId) { uint32_t varId = ptrId; ir::Instruction* ptrInst = def_use_mgr_->GetDef(varId); while (ptrInst->opcode() == SpvOpCopyObject) { varId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx); ptrInst = def_use_mgr_->GetDef(varId); } const SpvOp op = ptrInst->opcode(); if (op == SpvOpVariable || IsNonPtrAccessChain(op)) return true; if (op != SpvOpFunctionParameter) return false; const uint32_t varTypeId = ptrInst->type_id(); const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId); return varTypeInst->opcode() == SpvOpTypePointer; } ir::Instruction* MemPass::GetPtr( uint32_t ptrId, uint32_t* varId) { *varId = ptrId; ir::Instruction* ptrInst = def_use_mgr_->GetDef(*varId); while (ptrInst->opcode() == SpvOpCopyObject) { *varId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx); ptrInst = def_use_mgr_->GetDef(*varId); } ir::Instruction* varInst = ptrInst; while (varInst->opcode() != SpvOpVariable && varInst->opcode() != SpvOpFunctionParameter) { if (IsNonPtrAccessChain(varInst->opcode())) { *varId = varInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx); } else { assert(varInst->opcode() == SpvOpCopyObject); *varId = varInst->GetSingleWordInOperand(kCopyObjectOperandInIdx); } varInst = def_use_mgr_->GetDef(*varId); } return ptrInst; } ir::Instruction* MemPass::GetPtr( ir::Instruction* ip, uint32_t* varId) { const SpvOp op = ip->opcode(); assert(op == SpvOpStore || op == SpvOpLoad); const uint32_t ptrId = ip->GetSingleWordInOperand( op == SpvOpStore ? kStorePtrIdInIdx : kLoadPtrIdInIdx); return GetPtr(ptrId, varId); } bool MemPass::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); if (varInst->opcode() != SpvOpVariable) return false;; const uint32_t varTypeId = varInst->type_id(); const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId); if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) != SpvStorageClassFunction) { seen_non_target_vars_.insert(varId); return false; } const uint32_t varPteTypeId = varTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx); 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 MemPass::FindNamedOrDecoratedIds() { named_or_decorated_ids_.clear(); for (auto& di : module_->debugs()) if (di.opcode() == SpvOpName) named_or_decorated_ids_.insert(di.GetSingleWordInOperand(0)); for (auto& ai : module_->annotations()) if (ai.opcode() == SpvOpDecorate || ai.opcode() == SpvOpDecorateId) named_or_decorated_ids_.insert(ai.GetSingleWordInOperand(0)); } bool MemPass::HasOnlyNamesAndDecorates(uint32_t id) const { analysis::UseList* uses = def_use_mgr_->GetUses(id); if (uses == nullptr) return true; if (named_or_decorated_ids_.find(id) == named_or_decorated_ids_.end()) return false; for (auto u : *uses) { const SpvOp op = u.inst->opcode(); if (op != SpvOpName && !IsNonTypeDecorate(op)) return false; } return true; } void MemPass::KillNamesAndDecorates(uint32_t id) { // TODO(greg-lunarg): Remove id from any OpGroupDecorate and // kill if no other operands. if (named_or_decorated_ids_.find(id) == named_or_decorated_ids_.end()) return; analysis::UseList* uses = def_use_mgr_->GetUses(id); if (uses == nullptr) return; std::list killList; for (auto u : *uses) { const SpvOp op = u.inst->opcode(); if (op == SpvOpName || IsNonTypeDecorate(op)) killList.push_back(u.inst); } for (auto kip : killList) def_use_mgr_->KillInst(kip); } void MemPass::KillNamesAndDecorates(ir::Instruction* inst) { const uint32_t rId = inst->result_id(); if (rId == 0) return; KillNamesAndDecorates(rId); } bool MemPass::HasLoads(uint32_t varId) const { analysis::UseList* uses = def_use_mgr_->GetUses(varId); if (uses == nullptr) return false; for (auto u : *uses) { SpvOp op = u.inst->opcode(); // TODO(): The following is slightly conservative. Could be // better handling of non-store/name. if (IsNonPtrAccessChain(op) || op == SpvOpCopyObject) { if (HasLoads(u.inst->result_id())) return true; } else if (op != SpvOpStore && op != SpvOpName) return true; } return false; } bool MemPass::IsLiveVar(uint32_t varId) const { const ir::Instruction* varInst = def_use_mgr_->GetDef(varId); // assume live if not a variable eg. function parameter if (varInst->opcode() != SpvOpVariable) return true; // non-function scope vars are live const uint32_t varTypeId = varInst->type_id(); const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId); if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) != SpvStorageClassFunction) return true; // test if variable is loaded from return HasLoads(varId); } bool MemPass::IsLiveStore(ir::Instruction* storeInst) { // get store's variable uint32_t varId; (void) GetPtr(storeInst, &varId); return IsLiveVar(varId); } void MemPass::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 MemPass::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); KillNamesAndDecorates(di); def_use_mgr_->KillInst(di); // For all operands with no remaining uses, add their instruction // to the dead instruction queue. for (auto id : ids) if (HasOnlyNamesAndDecorates(id)) 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(); } } void MemPass::ReplaceAndDeleteLoad( ir::Instruction* loadInst, uint32_t replId) { const uint32_t loadId = loadInst->result_id(); KillNamesAndDecorates(loadId); (void) def_use_mgr_->ReplaceAllUsesWith(loadId, replId); DCEInst(loadInst); } MemPass::MemPass() : module_(nullptr), def_use_mgr_(nullptr) {} } // namespace opt } // namespace spvtools