| // Copyright 2016 The SwiftShader Authors. All Rights Reserved. | |
| // | |
| // 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 "Optimizer.hpp" | |
| #include "src/IceCfg.h" | |
| #include "src/IceCfgNode.h" | |
| #include <vector> | |
| namespace | |
| { | |
| class Optimizer | |
| { | |
| public: | |
| void run(Ice::Cfg *function); | |
| private: | |
| void analyzeUses(Ice::Cfg *function); | |
| void eliminateDeadCode(); | |
| void eliminateUnitializedLoads(); | |
| void eliminateLoadsFollowingSingleStore(); | |
| void optimizeStoresInSingleBasicBlock(); | |
| void replace(Ice::Inst *instruction, Ice::Operand *newValue); | |
| void deleteInstruction(Ice::Inst *instruction); | |
| bool isDead(Ice::Inst *instruction); | |
| static const Ice::InstIntrinsicCall *asLoadSubVector(const Ice::Inst *instruction); | |
| static const Ice::InstIntrinsicCall *asStoreSubVector(const Ice::Inst *instruction); | |
| static bool isLoad(const Ice::Inst &instruction); | |
| static bool isStore(const Ice::Inst &instruction); | |
| static Ice::Operand *storeAddress(const Ice::Inst *instruction); | |
| static Ice::Operand *loadAddress(const Ice::Inst *instruction); | |
| static Ice::Operand *storeData(const Ice::Inst *instruction); | |
| static std::size_t storeSize(const Ice::Inst *instruction); | |
| static bool loadTypeMatchesStore(const Ice::Inst *load, const Ice::Inst *store); | |
| Ice::Cfg *function; | |
| Ice::GlobalContext *context; | |
| struct Uses : std::vector<Ice::Inst*> | |
| { | |
| bool areOnlyLoadStore() const; | |
| void insert(Ice::Operand *value, Ice::Inst *instruction); | |
| void erase(Ice::Inst *instruction); | |
| std::vector<Ice::Inst*> loads; | |
| std::vector<Ice::Inst*> stores; | |
| }; | |
| struct LoadStoreInst | |
| { | |
| LoadStoreInst(Ice::Inst* inst, bool isStore) | |
| : inst(inst), | |
| address(isStore ? storeAddress(inst) : loadAddress(inst)), | |
| isStore(isStore) | |
| { | |
| } | |
| Ice::Inst* inst; | |
| Ice::Operand *address; | |
| bool isStore; | |
| }; | |
| Optimizer::Uses* getUses(Ice::Operand*); | |
| void setUses(Ice::Operand*, Optimizer::Uses*); | |
| bool hasUses(Ice::Operand*) const; | |
| Ice::CfgNode* getNode(Ice::Inst*); | |
| void setNode(Ice::Inst*, Ice::CfgNode*); | |
| Ice::Inst* getDefinition(Ice::Variable*); | |
| void setDefinition(Ice::Variable*, Ice::Inst*); | |
| const std::vector<LoadStoreInst>& getLoadStoreInsts(Ice::CfgNode*); | |
| void setLoadStoreInsts(Ice::CfgNode*, std::vector<LoadStoreInst>*); | |
| bool hasLoadStoreInsts(Ice::CfgNode* node) const; | |
| std::vector<Optimizer::Uses*> allocatedUses; | |
| }; | |
| void Optimizer::run(Ice::Cfg *function) | |
| { | |
| this->function = function; | |
| this->context = function->getContext(); | |
| analyzeUses(function); | |
| eliminateDeadCode(); | |
| eliminateUnitializedLoads(); | |
| eliminateLoadsFollowingSingleStore(); | |
| optimizeStoresInSingleBasicBlock(); | |
| eliminateDeadCode(); | |
| for(auto uses : allocatedUses) | |
| { | |
| delete uses; | |
| } | |
| allocatedUses.clear(); | |
| } | |
| void Optimizer::eliminateDeadCode() | |
| { | |
| bool modified; | |
| do | |
| { | |
| modified = false; | |
| for(Ice::CfgNode *basicBlock : function->getNodes()) | |
| { | |
| for(Ice::Inst &inst : Ice::reverse_range(basicBlock->getInsts())) | |
| { | |
| if(inst.isDeleted()) | |
| { | |
| continue; | |
| } | |
| if(isDead(&inst)) | |
| { | |
| deleteInstruction(&inst); | |
| modified = true; | |
| } | |
| } | |
| } | |
| } | |
| while(modified); | |
| } | |
| void Optimizer::eliminateUnitializedLoads() | |
| { | |
| Ice::CfgNode *entryBlock = function->getEntryNode(); | |
| for(Ice::Inst &alloca : entryBlock->getInsts()) | |
| { | |
| if(alloca.isDeleted()) | |
| { | |
| continue; | |
| } | |
| if(!llvm::isa<Ice::InstAlloca>(alloca)) | |
| { | |
| break; // Allocas are all at the top | |
| } | |
| Ice::Operand *address = alloca.getDest(); | |
| if(!hasUses(address)) | |
| { | |
| continue; | |
| } | |
| const auto &addressUses = *getUses(address); | |
| if(!addressUses.areOnlyLoadStore()) | |
| { | |
| continue; | |
| } | |
| if(addressUses.stores.empty()) | |
| { | |
| for(Ice::Inst *load : addressUses.loads) | |
| { | |
| Ice::Variable *loadData = load->getDest(); | |
| if(hasUses(loadData)) | |
| { | |
| for(Ice::Inst *use : *getUses(loadData)) | |
| { | |
| for(Ice::SizeT i = 0; i < use->getSrcSize(); i++) | |
| { | |
| if(use->getSrc(i) == loadData) | |
| { | |
| auto *undef = context->getConstantUndef(loadData->getType()); | |
| use->replaceSource(i, undef); | |
| } | |
| } | |
| } | |
| setUses(loadData, nullptr); | |
| } | |
| load->setDeleted(); | |
| } | |
| alloca.setDeleted(); | |
| setUses(address, nullptr); | |
| } | |
| } | |
| } | |
| void Optimizer::eliminateLoadsFollowingSingleStore() | |
| { | |
| Ice::CfgNode *entryBlock = function->getEntryNode(); | |
| for(Ice::Inst &alloca : entryBlock->getInsts()) | |
| { | |
| if(alloca.isDeleted()) | |
| { | |
| continue; | |
| } | |
| if(!llvm::isa<Ice::InstAlloca>(alloca)) | |
| { | |
| break; // Allocas are all at the top | |
| } | |
| Ice::Operand *address = alloca.getDest(); | |
| if(!hasUses(address)) | |
| { | |
| continue; | |
| } | |
| auto &addressUses = *getUses(address); | |
| if(!addressUses.areOnlyLoadStore()) | |
| { | |
| continue; | |
| } | |
| if(addressUses.stores.size() == 1) | |
| { | |
| Ice::Inst *store = addressUses.stores[0]; | |
| Ice::Operand *storeValue = storeData(store); | |
| for(Ice::Inst *load = &*++store->getIterator(), *next = nullptr; load != next; next = load, load = &*++store->getIterator()) | |
| { | |
| if(load->isDeleted() || !isLoad(*load)) | |
| { | |
| continue; | |
| } | |
| if(loadAddress(load) != address) | |
| { | |
| continue; | |
| } | |
| if(!loadTypeMatchesStore(load, store)) | |
| { | |
| continue; | |
| } | |
| replace(load, storeValue); | |
| for(size_t i = 0; i < addressUses.loads.size(); i++) | |
| { | |
| if(addressUses.loads[i] == load) | |
| { | |
| addressUses.loads[i] = addressUses.loads.back(); | |
| addressUses.loads.pop_back(); | |
| break; | |
| } | |
| } | |
| for(size_t i = 0; i < addressUses.size(); i++) | |
| { | |
| if(addressUses[i] == load) | |
| { | |
| addressUses[i] = addressUses.back(); | |
| addressUses.pop_back(); | |
| break; | |
| } | |
| } | |
| if(addressUses.size() == 1) | |
| { | |
| assert(addressUses[0] == store); | |
| alloca.setDeleted(); | |
| store->setDeleted(); | |
| setUses(address, nullptr); | |
| if(hasUses(storeValue)) | |
| { | |
| auto &valueUses = *getUses(storeValue); | |
| for(size_t i = 0; i < valueUses.size(); i++) | |
| { | |
| if(valueUses[i] == store) | |
| { | |
| valueUses[i] = valueUses.back(); | |
| valueUses.pop_back(); | |
| break; | |
| } | |
| } | |
| if(valueUses.empty()) | |
| { | |
| setUses(storeValue, nullptr); | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void Optimizer::optimizeStoresInSingleBasicBlock() | |
| { | |
| Ice::CfgNode *entryBlock = function->getEntryNode(); | |
| std::vector<std::vector<LoadStoreInst>* > allocatedVectors; | |
| for(Ice::Inst &alloca : entryBlock->getInsts()) | |
| { | |
| if(alloca.isDeleted()) | |
| { | |
| continue; | |
| } | |
| if(!llvm::isa<Ice::InstAlloca>(alloca)) | |
| { | |
| break; // Allocas are all at the top | |
| } | |
| Ice::Operand *address = alloca.getDest(); | |
| if(!hasUses(address)) | |
| { | |
| continue; | |
| } | |
| const auto &addressUses = *getUses(address); | |
| if(!addressUses.areOnlyLoadStore()) | |
| { | |
| continue; | |
| } | |
| Ice::CfgNode *singleBasicBlock = getNode(addressUses.stores[0]); | |
| for(size_t i = 1; i < addressUses.stores.size(); i++) | |
| { | |
| Ice::Inst *store = addressUses.stores[i]; | |
| if(getNode(store) != singleBasicBlock) | |
| { | |
| singleBasicBlock = nullptr; | |
| break; | |
| } | |
| } | |
| if(singleBasicBlock) | |
| { | |
| if(!hasLoadStoreInsts(singleBasicBlock)) | |
| { | |
| std::vector<LoadStoreInst>* loadStoreInstVector = new std::vector<LoadStoreInst>(); | |
| setLoadStoreInsts(singleBasicBlock, loadStoreInstVector); | |
| allocatedVectors.push_back(loadStoreInstVector); | |
| for(Ice::Inst &inst : singleBasicBlock->getInsts()) | |
| { | |
| if(inst.isDeleted()) | |
| { | |
| continue; | |
| } | |
| bool isStoreInst = isStore(inst); | |
| bool isLoadInst = isLoad(inst); | |
| if(isStoreInst || isLoadInst) | |
| { | |
| loadStoreInstVector->push_back(LoadStoreInst(&inst, isStoreInst)); | |
| } | |
| } | |
| } | |
| Ice::Inst *store = nullptr; | |
| Ice::Operand *storeValue = nullptr; | |
| bool unmatchedLoads = false; | |
| for (auto& loadStoreInst : getLoadStoreInsts(singleBasicBlock)) | |
| { | |
| Ice::Inst* inst = loadStoreInst.inst; | |
| if((loadStoreInst.address != address) || inst->isDeleted()) | |
| { | |
| continue; | |
| } | |
| if(loadStoreInst.isStore) | |
| { | |
| // New store found. If we had a previous one, try to eliminate it. | |
| if(store && !unmatchedLoads) | |
| { | |
| // If the previous store is wider than the new one, we can't eliminate it | |
| // because there could be a wide load reading its non-overwritten data. | |
| if(storeSize(inst) >= storeSize(store)) | |
| { | |
| deleteInstruction(store); | |
| } | |
| } | |
| store = inst; | |
| storeValue = storeData(store); | |
| unmatchedLoads = false; | |
| } | |
| else | |
| { | |
| if(!loadTypeMatchesStore(inst, store)) | |
| { | |
| unmatchedLoads = true; | |
| continue; | |
| } | |
| replace(inst, storeValue); | |
| } | |
| } | |
| } | |
| } | |
| for(auto loadStoreInstVector : allocatedVectors) | |
| { | |
| delete loadStoreInstVector; | |
| } | |
| } | |
| void Optimizer::analyzeUses(Ice::Cfg *function) | |
| { | |
| for(Ice::CfgNode *basicBlock : function->getNodes()) | |
| { | |
| for(Ice::Inst &instruction : basicBlock->getInsts()) | |
| { | |
| if(instruction.isDeleted()) | |
| { | |
| continue; | |
| } | |
| setNode(&instruction, basicBlock); | |
| if(instruction.getDest()) | |
| { | |
| setDefinition(instruction.getDest(), &instruction); | |
| } | |
| for(Ice::SizeT i = 0; i < instruction.getSrcSize(); i++) | |
| { | |
| Ice::SizeT unique = 0; | |
| for(; unique < i; unique++) | |
| { | |
| if(instruction.getSrc(i) == instruction.getSrc(unique)) | |
| { | |
| break; | |
| } | |
| } | |
| if(i == unique) | |
| { | |
| Ice::Operand *src = instruction.getSrc(i); | |
| getUses(src)->insert(src, &instruction); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void Optimizer::replace(Ice::Inst *instruction, Ice::Operand *newValue) | |
| { | |
| Ice::Variable *oldValue = instruction->getDest(); | |
| if(!newValue) | |
| { | |
| newValue = context->getConstantUndef(oldValue->getType()); | |
| } | |
| if(hasUses(oldValue)) | |
| { | |
| for(Ice::Inst *use : *getUses(oldValue)) | |
| { | |
| assert(!use->isDeleted()); // Should have been removed from uses already | |
| for(Ice::SizeT i = 0; i < use->getSrcSize(); i++) | |
| { | |
| if(use->getSrc(i) == oldValue) | |
| { | |
| use->replaceSource(i, newValue); | |
| } | |
| } | |
| getUses(newValue)->insert(newValue, use); | |
| } | |
| setUses(oldValue, nullptr); | |
| } | |
| deleteInstruction(instruction); | |
| } | |
| void Optimizer::deleteInstruction(Ice::Inst *instruction) | |
| { | |
| if(!instruction || instruction->isDeleted()) | |
| { | |
| return; | |
| } | |
| instruction->setDeleted(); | |
| for(Ice::SizeT i = 0; i < instruction->getSrcSize(); i++) | |
| { | |
| Ice::Operand *src = instruction->getSrc(i); | |
| if(hasUses(src)) | |
| { | |
| auto &srcUses = *getUses(src); | |
| srcUses.erase(instruction); | |
| if(srcUses.empty()) | |
| { | |
| setUses(src, nullptr); | |
| if(Ice::Variable *var = llvm::dyn_cast<Ice::Variable>(src)) | |
| { | |
| deleteInstruction(getDefinition(var)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| bool Optimizer::isDead(Ice::Inst *instruction) | |
| { | |
| Ice::Variable *dest = instruction->getDest(); | |
| if(dest) | |
| { | |
| return (!hasUses(dest) || getUses(dest)->empty()) && !instruction->hasSideEffects(); | |
| } | |
| else if(isStore(*instruction)) | |
| { | |
| if(Ice::Variable *address = llvm::dyn_cast<Ice::Variable>(storeAddress(instruction))) | |
| { | |
| Ice::Inst *def = getDefinition(address); | |
| if(def && llvm::isa<Ice::InstAlloca>(def)) | |
| { | |
| if(hasUses(address)) | |
| { | |
| Optimizer::Uses* uses = getUses(address); | |
| return uses->size() == uses->stores.size(); // Dead if all uses are stores | |
| } | |
| else | |
| { | |
| return true; // No uses | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| const Ice::InstIntrinsicCall *Optimizer::asLoadSubVector(const Ice::Inst *instruction) | |
| { | |
| if(auto *instrinsic = llvm::dyn_cast<Ice::InstIntrinsicCall>(instruction)) | |
| { | |
| if(instrinsic->getIntrinsicInfo().ID == Ice::Intrinsics::LoadSubVector) | |
| { | |
| return instrinsic; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| const Ice::InstIntrinsicCall *Optimizer::asStoreSubVector(const Ice::Inst *instruction) | |
| { | |
| if(auto *instrinsic = llvm::dyn_cast<Ice::InstIntrinsicCall>(instruction)) | |
| { | |
| if(instrinsic->getIntrinsicInfo().ID == Ice::Intrinsics::StoreSubVector) | |
| { | |
| return instrinsic; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| bool Optimizer::isLoad(const Ice::Inst &instruction) | |
| { | |
| if(llvm::isa<Ice::InstLoad>(&instruction)) | |
| { | |
| return true; | |
| } | |
| return asLoadSubVector(&instruction) != nullptr; | |
| } | |
| bool Optimizer::isStore(const Ice::Inst &instruction) | |
| { | |
| if(llvm::isa<Ice::InstStore>(&instruction)) | |
| { | |
| return true; | |
| } | |
| return asStoreSubVector(&instruction) != nullptr; | |
| } | |
| Ice::Operand *Optimizer::storeAddress(const Ice::Inst *instruction) | |
| { | |
| assert(isStore(*instruction)); | |
| if(auto *store = llvm::dyn_cast<Ice::InstStore>(instruction)) | |
| { | |
| return store->getAddr(); | |
| } | |
| if(auto *storeSubVector = asStoreSubVector(instruction)) | |
| { | |
| return storeSubVector->getSrc(2); | |
| } | |
| return nullptr; | |
| } | |
| Ice::Operand *Optimizer::loadAddress(const Ice::Inst *instruction) | |
| { | |
| assert(isLoad(*instruction)); | |
| if(auto *load = llvm::dyn_cast<Ice::InstLoad>(instruction)) | |
| { | |
| return load->getSourceAddress(); | |
| } | |
| if(auto *loadSubVector = asLoadSubVector(instruction)) | |
| { | |
| return loadSubVector->getSrc(1); | |
| } | |
| return nullptr; | |
| } | |
| Ice::Operand *Optimizer::storeData(const Ice::Inst *instruction) | |
| { | |
| assert(isStore(*instruction)); | |
| if(auto *store = llvm::dyn_cast<Ice::InstStore>(instruction)) | |
| { | |
| return store->getData(); | |
| } | |
| if(auto *storeSubVector = asStoreSubVector(instruction)) | |
| { | |
| return storeSubVector->getSrc(1); | |
| } | |
| return nullptr; | |
| } | |
| std::size_t Optimizer::storeSize(const Ice::Inst *store) | |
| { | |
| assert(isStore(*store)); | |
| if(auto *instStore = llvm::dyn_cast<Ice::InstStore>(store)) | |
| { | |
| return Ice::typeWidthInBytes(instStore->getData()->getType()); | |
| } | |
| if(auto *storeSubVector = asStoreSubVector(store)) | |
| { | |
| return llvm::cast<Ice::ConstantInteger32>(storeSubVector->getSrc(3))->getValue(); | |
| } | |
| return 0; | |
| } | |
| bool Optimizer::loadTypeMatchesStore(const Ice::Inst *load, const Ice::Inst *store) | |
| { | |
| if(!load || !store) | |
| { | |
| return false; | |
| } | |
| assert(isLoad(*load) && isStore(*store)); | |
| assert(loadAddress(load) == storeAddress(store)); | |
| if(auto *instStore = llvm::dyn_cast<Ice::InstStore>(store)) | |
| { | |
| if(auto *instLoad = llvm::dyn_cast<Ice::InstLoad>(load)) | |
| { | |
| return instStore->getData()->getType() == instLoad->getDest()->getType(); | |
| } | |
| } | |
| if(auto *storeSubVector = asStoreSubVector(store)) | |
| { | |
| if(auto *loadSubVector = asLoadSubVector(load)) | |
| { | |
| // Check for matching type and sub-vector width. | |
| return storeSubVector->getSrc(1)->getType() == loadSubVector->getDest()->getType() && | |
| llvm::cast<Ice::ConstantInteger32>(storeSubVector->getSrc(3))->getValue() == | |
| llvm::cast<Ice::ConstantInteger32>(loadSubVector->getSrc(2))->getValue(); | |
| } | |
| } | |
| return false; | |
| } | |
| Optimizer::Uses* Optimizer::getUses(Ice::Operand* operand) | |
| { | |
| Optimizer::Uses* uses = (Optimizer::Uses*)operand->Ice::Operand::getExternalData(); | |
| if(!uses) | |
| { | |
| uses = new Optimizer::Uses; | |
| setUses(operand, uses); | |
| allocatedUses.push_back(uses); | |
| } | |
| return uses; | |
| } | |
| void Optimizer::setUses(Ice::Operand* operand, Optimizer::Uses* uses) | |
| { | |
| operand->Ice::Operand::setExternalData(uses); | |
| } | |
| bool Optimizer::hasUses(Ice::Operand* operand) const | |
| { | |
| return operand->Ice::Operand::getExternalData() != nullptr; | |
| } | |
| Ice::CfgNode* Optimizer::getNode(Ice::Inst* inst) | |
| { | |
| return (Ice::CfgNode*)inst->Ice::Inst::getExternalData(); | |
| } | |
| void Optimizer::setNode(Ice::Inst* inst, Ice::CfgNode* node) | |
| { | |
| inst->Ice::Inst::setExternalData(node); | |
| } | |
| Ice::Inst* Optimizer::getDefinition(Ice::Variable* var) | |
| { | |
| return (Ice::Inst*)var->Ice::Variable::getExternalData(); | |
| } | |
| void Optimizer::setDefinition(Ice::Variable* var, Ice::Inst* inst) | |
| { | |
| var->Ice::Variable::setExternalData(inst); | |
| } | |
| const std::vector<Optimizer::LoadStoreInst>& Optimizer::getLoadStoreInsts(Ice::CfgNode* node) | |
| { | |
| return *((const std::vector<LoadStoreInst>*)node->Ice::CfgNode::getExternalData()); | |
| } | |
| void Optimizer::setLoadStoreInsts(Ice::CfgNode* node, std::vector<LoadStoreInst>* insts) | |
| { | |
| node->Ice::CfgNode::setExternalData(insts); | |
| } | |
| bool Optimizer::hasLoadStoreInsts(Ice::CfgNode* node) const | |
| { | |
| return node->Ice::CfgNode::getExternalData() != nullptr; | |
| } | |
| bool Optimizer::Uses::areOnlyLoadStore() const | |
| { | |
| return size() == (loads.size() + stores.size()); | |
| } | |
| void Optimizer::Uses::insert(Ice::Operand *value, Ice::Inst *instruction) | |
| { | |
| push_back(instruction); | |
| if(isLoad(*instruction)) | |
| { | |
| if(value == loadAddress(instruction)) | |
| { | |
| loads.push_back(instruction); | |
| } | |
| } | |
| else if(isStore(*instruction)) | |
| { | |
| if(value == storeAddress(instruction)) | |
| { | |
| stores.push_back(instruction); | |
| } | |
| } | |
| } | |
| void Optimizer::Uses::erase(Ice::Inst *instruction) | |
| { | |
| auto &uses = *this; | |
| for(size_t i = 0; i < uses.size(); i++) | |
| { | |
| if(uses[i] == instruction) | |
| { | |
| uses[i] = back(); | |
| pop_back(); | |
| for(size_t i = 0; i < loads.size(); i++) | |
| { | |
| if(loads[i] == instruction) | |
| { | |
| loads[i] = loads.back(); | |
| loads.pop_back(); | |
| break; | |
| } | |
| } | |
| for(size_t i = 0; i < stores.size(); i++) | |
| { | |
| if(stores[i] == instruction) | |
| { | |
| stores[i] = stores.back(); | |
| stores.pop_back(); | |
| break; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| namespace rr | |
| { | |
| void optimize(Ice::Cfg *function) | |
| { | |
| Optimizer optimizer; | |
| optimizer.run(function); | |
| } | |
| } |