// 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);
}
}