Re-factor Phi insertion code out of LocalMultiStoreElimPass

Including a re-factor of common behaviour into class Pass:

The following functions are now in class Pass:

- IsLoopHeader.
- ComputeStructuredOrder
- ComputeStructuredSuccessors (annoyingly, I could not re-factor all
  instances of this function, the copy in common_uniform_elim_pass.cpp
  is slightly different and fails with the common implementation).
- GetPointeeTypeId
- TakeNextId
- FinalizeNextId
- MergeBlockIdIfAny

This is a NFC (non-functional change)
This commit is contained in:
Diego Novillo 2017-10-25 13:26:25 -04:00 committed by David Neto
parent 94dc66b74d
commit 1040a95b3f
36 changed files with 985 additions and 1316 deletions

View File

@ -33,12 +33,12 @@ const uint32_t kEntryPointFunctionIdInIdx = 1;
bool AggressiveDCEPass::IsVarOfStorage(uint32_t varId,
uint32_t storageClass) {
const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
const ir::Instruction* varInst = get_def_use_mgr()->GetDef(varId);
const SpvOp op = varInst->opcode();
if (op != SpvOpVariable)
return false;
const uint32_t varTypeId = varInst->type_id();
const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
const ir::Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId);
if (varTypeInst->opcode() != SpvOpTypePointer)
return false;
return varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) ==
@ -51,7 +51,7 @@ bool AggressiveDCEPass::IsLocalVar(uint32_t varId) {
}
void AggressiveDCEPass::AddStores(uint32_t ptrId) {
const analysis::UseList* uses = def_use_mgr_->GetUses(ptrId);
const analysis::UseList* uses = get_def_use_mgr()->GetUses(ptrId);
if (uses == nullptr)
return;
for (const auto u : *uses) {
@ -91,7 +91,7 @@ bool AggressiveDCEPass::IsCombinatorExt(ir::Instruction* inst) const {
bool AggressiveDCEPass::AllExtensionsSupported() const {
// If any extension not in whitelist, return false
for (auto& ei : module_->extensions()) {
for (auto& ei : get_module()->extensions()) {
const char* extName = reinterpret_cast<const char*>(
&ei.GetInOperand(0).words[0]);
if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
@ -102,9 +102,9 @@ bool AggressiveDCEPass::AllExtensionsSupported() const {
bool AggressiveDCEPass::KillInstIfTargetDead(ir::Instruction* inst) {
const uint32_t tId = inst->GetSingleWordInOperand(0);
const ir::Instruction* tInst = def_use_mgr_->GetDef(tId);
const ir::Instruction* tInst = get_def_use_mgr()->GetDef(tId);
if (dead_insts_.find(tInst) != dead_insts_.end()) {
def_use_mgr_->KillInst(inst);
get_def_use_mgr()->KillInst(inst);
return true;
}
return false;
@ -165,7 +165,7 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
}
}
// See if current function is an entry point
for (auto& ei : module_->entry_points()) {
for (auto& ei : get_module()->entry_points()) {
if (ei.GetSingleWordInOperand(kEntryPointFunctionIdInIdx) ==
func->result_id()) {
func_is_entry_point_ = true;
@ -182,7 +182,7 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
// Add OpGroupDecorates to worklist because they are a pain to remove
// ids from.
// TODO(greg-lunarg): Handle dead ids in OpGroupDecorate
for (auto& ai : module_->annotations()) {
for (auto& ai : get_module()->annotations()) {
if (ai.opcode() == SpvOpGroupDecorate)
worklist_.push(&ai);
}
@ -192,7 +192,7 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
live_insts_.insert(liveInst);
// Add all operand instructions if not already live
liveInst->ForEachInId([this](const uint32_t* iid) {
ir::Instruction* inInst = def_use_mgr_->GetDef(*iid);
ir::Instruction* inInst = get_def_use_mgr()->GetDef(*iid);
if (live_insts_.find(inInst) == live_insts_.end())
worklist_.push(inInst);
});
@ -229,13 +229,13 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
// Remove debug and annotation statements referencing dead instructions.
// This must be done before killing the instructions, otherwise there are
// dead objects in the def/use database.
for (auto& di : module_->debugs2()) {
for (auto& di : get_module()->debugs2()) {
if (di.opcode() != SpvOpName)
continue;
if (KillInstIfTargetDead(&di))
modified = true;
}
for (auto& ai : module_->annotations()) {
for (auto& ai : get_module()->annotations()) {
if (ai.opcode() != SpvOpDecorate && ai.opcode() != SpvOpDecorateId)
continue;
if (KillInstIfTargetDead(&ai))
@ -246,7 +246,7 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
for (auto& inst : blk) {
if (dead_insts_.find(&inst) == dead_insts_.end())
continue;
def_use_mgr_->KillInst(&inst);
get_def_use_mgr()->KillInst(&inst);
modified = true;
}
}
@ -254,7 +254,7 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
}
void AggressiveDCEPass::Initialize(ir::Module* module) {
module_ = module;
InitializeProcessing(module);
// Clear collections
worklist_ = std::queue<ir::Instruction*>{};
@ -264,9 +264,6 @@ void AggressiveDCEPass::Initialize(ir::Module* module) {
combinator_ops_shader_.clear();
combinator_ops_glsl_std_450_.clear();
// TODO(greg-lunarg): Reuse def/use from previous passes
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
// Initialize extensions whitelist
InitExtensions();
}
@ -274,11 +271,11 @@ void AggressiveDCEPass::Initialize(ir::Module* module) {
Pass::Status AggressiveDCEPass::ProcessImpl() {
// Current functionality assumes shader capability
// TODO(greg-lunarg): Handle additional capabilities
if (!module_->HasCapability(SpvCapabilityShader))
if (!get_module()->HasCapability(SpvCapabilityShader))
return Status::SuccessWithoutChange;
// Current functionality assumes logical addressing only
// TODO(greg-lunarg): Handle non-logical addressing
if (module_->HasCapability(SpvCapabilityAddresses))
if (get_module()->HasCapability(SpvCapabilityAddresses))
return Status::SuccessWithoutChange;
// If any extensions in the module are not explicitly supported,
// return unmodified.
@ -290,7 +287,7 @@ Pass::Status AggressiveDCEPass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return AggressiveDCE(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
@ -441,7 +438,7 @@ void AggressiveDCEPass::InitCombinatorSets() {
};
// Find supported extension instruction set ids
glsl_std_450_id_ = module_->GetExtInstImportId("GLSL.std.450");
glsl_std_450_id_ = get_module()->GetExtInstImportId("GLSL.std.450");
combinator_ops_glsl_std_450_ = {
GLSLstd450Round,

View File

@ -21,16 +21,8 @@
namespace spvtools {
namespace opt {
bool BlockMergePass::IsLoopHeader(ir::BasicBlock* block_ptr) {
auto iItr = block_ptr->tail();
if (iItr == block_ptr->begin())
return false;
--iItr;
return iItr->opcode() == SpvOpLoopMerge;
}
bool BlockMergePass::HasMultipleRefs(uint32_t labId) {
const analysis::UseList* uses = def_use_mgr_->GetUses(labId);
const analysis::UseList* uses = get_def_use_mgr()->GetUses(labId);
int rcnt = 0;
for (const auto u : *uses) {
// Don't count OpName
@ -46,15 +38,15 @@ bool BlockMergePass::HasMultipleRefs(uint32_t labId) {
void BlockMergePass::KillInstAndName(ir::Instruction* inst) {
const uint32_t id = inst->result_id();
if (id != 0) {
analysis::UseList* uses = def_use_mgr_->GetUses(id);
analysis::UseList* uses = get_def_use_mgr()->GetUses(id);
if (uses != nullptr)
for (auto u : *uses)
if (u.inst->opcode() == SpvOpName) {
def_use_mgr_->KillInst(u.inst);
get_def_use_mgr()->KillInst(u.inst);
break;
}
}
def_use_mgr_->KillInst(inst);
get_def_use_mgr()->KillInst(inst);
}
bool BlockMergePass::MergeBlocks(ir::Function* func) {
@ -85,7 +77,7 @@ bool BlockMergePass::MergeBlocks(ir::Function* func) {
continue;
}
// Merge blocks
def_use_mgr_->KillInst(br);
get_def_use_mgr()->KillInst(br);
auto sbi = bi;
for (; sbi != func->end(); ++sbi)
if (sbi->id() == labId)
@ -103,11 +95,7 @@ bool BlockMergePass::MergeBlocks(ir::Function* func) {
}
void BlockMergePass::Initialize(ir::Module* module) {
module_ = module;
// TODO(greg-lunarg): Reuse def/use from previous passes
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
InitializeProcessing(module);
// Initialize extension whitelist
InitExtensions();
@ -115,7 +103,7 @@ void BlockMergePass::Initialize(ir::Module* module) {
bool BlockMergePass::AllExtensionsSupported() const {
// If any extension not in whitelist, return false
for (auto& ei : module_->extensions()) {
for (auto& ei : get_module()->extensions()) {
const char* extName = reinterpret_cast<const char*>(
&ei.GetInOperand(0).words[0]);
if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
@ -132,12 +120,11 @@ Pass::Status BlockMergePass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return MergeBlocks(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
BlockMergePass::BlockMergePass()
: module_(nullptr), def_use_mgr_(nullptr) {}
BlockMergePass::BlockMergePass() {}
Pass::Status BlockMergePass::Process(ir::Module* module) {
Initialize(module);

View File

@ -40,9 +40,6 @@ class BlockMergePass : public Pass {
Status Process(ir::Module*) override;
private:
// Return true if |block_ptr| is loop header block
bool IsLoopHeader(ir::BasicBlock* block_ptr);
// Return true if |labId| has multiple refs. Do not count OpName.
bool HasMultipleRefs(uint32_t labId);
@ -62,12 +59,6 @@ class BlockMergePass : public Pass {
void Initialize(ir::Module* module);
Pass::Status ProcessImpl();
// Module this pass is processing
ir::Module* module_;
// Def-Uses for the module we are processing
std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
// Extensions supported by this pass.
std::unordered_set<std::string> extensions_whitelist_;
};

View File

@ -27,22 +27,6 @@
namespace spvtools {
namespace opt {
uint32_t CFGCleanupPass::TypeToUndef(uint32_t type_id) {
const auto uitr = type2undefs_.find(type_id);
if (uitr != type2undefs_.end()) {
return uitr->second;
}
const uint32_t undefId = TakeNextId();
std::unique_ptr<ir::Instruction> undef_inst(
new ir::Instruction(SpvOpUndef, type_id, undefId, {}));
def_use_mgr_->AnalyzeInstDefUse(&*undef_inst);
module_->AddGlobalValue(std::move(undef_inst));
type2undefs_[type_id] = undefId;
return undefId;
}
// Remove all |phi| operands coming from unreachable blocks (i.e., blocks not in
// |reachable_blocks|). There are two types of removal that this function can
// perform:
@ -127,8 +111,8 @@ void CFGCleanupPass::RemovePhiOperands(
// means that this |phi| argument is no longer defined. Replace it with
// |undef_id|.
if (!undef_id) {
type_id = def_use_mgr_->GetDef(arg_id)->type_id();
undef_id = TypeToUndef(type_id);
type_id = get_def_use_mgr()->GetDef(arg_id)->type_id();
undef_id = Type2Undef(type_id);
}
keep_operands.push_back(
ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, {undef_id}));
@ -157,14 +141,14 @@ void CFGCleanupPass::RemoveBlock(ir::Function::iterator* bi) {
// removal of phi operands.
if (inst != rm_block.GetLabelInst()) {
KillNamesAndDecorates(inst);
def_use_mgr_->KillInst(inst);
get_def_use_mgr()->KillInst(inst);
}
});
// Remove the label instruction last.
auto label = rm_block.GetLabelInst();
KillNamesAndDecorates(label);
def_use_mgr_->KillInst(label);
get_def_use_mgr()->KillInst(label);
*bi = bi->Erase();
}
@ -240,16 +224,9 @@ bool CFGCleanupPass::CFGCleanup(ir::Function* func) {
}
void CFGCleanupPass::Initialize(ir::Module* module) {
// Initialize the DefUse manager. TODO(dnovillo): Re-factor all this into the
// module or some other context class for the optimizer.
module_ = module;
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
InitializeProcessing(module);
FindNamedOrDecoratedIds();
// Initialize next unused Id. TODO(dnovillo): Re-factor into the module or
// some other context class for the optimizer.
next_id_ = module_->id_bound();
// Initialize block lookup map.
label2block_.clear();
for (auto& fn : *module) {
@ -276,7 +253,7 @@ Pass::Status CFGCleanupPass::Process(ir::Module* module) {
// Process all entry point functions.
ProcessFunction pfn = [this](ir::Function* fp) { return CFGCleanup(fp); };
bool modified = ProcessReachableCallTree(pfn, module);
FinalizeNextId(module_);
FinalizeNextId();
return modified ? Pass::Status::SuccessWithChange
: Pass::Status::SuccessWithoutChange;
}

View File

@ -47,22 +47,6 @@ class CFGCleanupPass : public MemPass {
void RemovePhiOperands(ir::Instruction* phi,
std::unordered_set<ir::BasicBlock*> reachable_blocks);
// Return the next available Id and increment it. TODO(dnovillo): Refactor
// into a new type pool manager to be used for all passes.
inline uint32_t TakeNextId() { return next_id_++; }
// Save next available id into |module|. TODO(dnovillo): Refactor
// into a new type pool manager to be used for all passes.
inline void FinalizeNextId(ir::Module* module) {
module->SetIdBound(next_id_);
}
// Return undef in function for type. Create and insert an undef after the
// first non-variable in the function if it doesn't already exist. Add
// undef to function undef map. TODO(dnovillo): Refactor into a new
// type pool manager to be used for all passes.
uint32_t TypeToUndef(uint32_t type_id);
// Map from block's label id to block. TODO(dnovillo): Basic blocks ought to
// have basic blocks in their pred/succ list.
std::unordered_map<uint32_t, ir::BasicBlock*> label2block_;
@ -71,15 +55,6 @@ class CFGCleanupPass : public MemPass {
// TODO(dnovillo): This would be unnecessary if ir::Instruction instances
// knew what basic block they belong to.
std::unordered_map<uint32_t, ir::BasicBlock*> def_block_;
// Map from type to undef values. TODO(dnovillo): This is replicated from
// class LocalMultiStoreElimPass. It should be refactored into a type
// pool manager.
std::unordered_map<uint32_t, uint32_t> type2undefs_;
// Next unused ID. TODO(dnovillo): Refactor this to some common utility class.
// Seems to be implemented in very many passes.
uint32_t next_id_;
};
} // namespace opt

View File

@ -28,9 +28,6 @@ const uint32_t kTypePointerTypeIdInIdx = 1;
const uint32_t kConstantValueInIdx = 0;
const uint32_t kExtractCompositeIdInIdx = 0;
const uint32_t kExtractIdx0InIdx = 1;
const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
const uint32_t kStorePtrIdInIdx = 0;
const uint32_t kLoadPtrIdInIdx = 0;
const uint32_t kCopyObjectOperandInIdx = 0;
@ -57,7 +54,7 @@ bool CommonUniformElimPass::IsSamplerOrImageType(
// Return true if any member is a sampler or image
int samplerOrImageCnt = 0;
typeInst->ForEachInId([&samplerOrImageCnt, this](const uint32_t* tid) {
const ir::Instruction* compTypeInst = def_use_mgr_->GetDef(*tid);
const ir::Instruction* compTypeInst = get_def_use_mgr()->GetDef(*tid);
if (IsSamplerOrImageType(compTypeInst)) ++samplerOrImageCnt;
});
return samplerOrImageCnt > 0;
@ -65,53 +62,26 @@ bool CommonUniformElimPass::IsSamplerOrImageType(
bool CommonUniformElimPass::IsSamplerOrImageVar(
uint32_t varId) const {
const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
const ir::Instruction* varInst = get_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);
const ir::Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId);
const uint32_t varPteTypeId =
varTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
ir::Instruction* varPteTypeInst = def_use_mgr_->GetDef(varPteTypeId);
ir::Instruction* varPteTypeInst = get_def_use_mgr()->GetDef(varPteTypeId);
return IsSamplerOrImageType(varPteTypeInst);
}
bool CommonUniformElimPass::IsLoopHeader(ir::BasicBlock* block_ptr) {
auto iItr = block_ptr->tail();
if (iItr == block_ptr->begin())
return false;
--iItr;
return iItr->opcode() == SpvOpLoopMerge;
}
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;
}
ir::Instruction* CommonUniformElimPass::GetPtr(
ir::Instruction* ip, uint32_t* objId) {
const SpvOp op = ip->opcode();
assert(op == SpvOpStore || op == SpvOpLoad);
*objId = ip->GetSingleWordInOperand(
op == SpvOpStore ? kStorePtrIdInIdx : kLoadPtrIdInIdx);
ir::Instruction* ptrInst = def_use_mgr_->GetDef(*objId);
ir::Instruction* ptrInst = get_def_use_mgr()->GetDef(*objId);
while (ptrInst->opcode() == SpvOpCopyObject) {
*objId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
ptrInst = def_use_mgr_->GetDef(*objId);
ptrInst = get_def_use_mgr()->GetDef(*objId);
}
ir::Instruction* objInst = ptrInst;
while (objInst->opcode() != SpvOpVariable &&
@ -123,13 +93,13 @@ ir::Instruction* CommonUniformElimPass::GetPtr(
assert(objInst->opcode() == SpvOpCopyObject);
*objId = objInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
}
objInst = def_use_mgr_->GetDef(*objId);
objInst = get_def_use_mgr()->GetDef(*objId);
}
return ptrInst;
}
bool CommonUniformElimPass::IsVolatileStruct(uint32_t type_id) {
assert(def_use_mgr_->GetDef(type_id)->opcode() == SpvOpTypeStruct);
assert(get_def_use_mgr()->GetDef(type_id)->opcode() == SpvOpTypeStruct);
bool has_volatile_deco = false;
dec_mgr_->ForEachDecoration(type_id, SpvDecorationVolatile,
[&has_volatile_deco](const ir::Instruction&){ has_volatile_deco = true;});
@ -140,13 +110,13 @@ bool CommonUniformElimPass::IsAccessChainToVolatileStructType(const ir::Instruct
assert(AccessChainInst.opcode() == SpvOpAccessChain);
uint32_t ptr_id = AccessChainInst.GetSingleWordInOperand(0);
const ir::Instruction* ptr_inst = def_use_mgr_->GetDef(ptr_id);
const ir::Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst);
const uint32_t num_operands = AccessChainInst.NumOperands();
// walk the type tree:
for (uint32_t idx = 3; idx < num_operands; ++idx) {
ir::Instruction* pointee_type = def_use_mgr_->GetDef(pointee_type_id);
ir::Instruction* pointee_type = get_def_use_mgr()->GetDef(pointee_type_id);
switch (pointee_type->opcode()) {
case SpvOpTypeMatrix:
@ -161,7 +131,7 @@ bool CommonUniformElimPass::IsAccessChainToVolatileStructType(const ir::Instruct
if (idx < num_operands - 1) {
const uint32_t index_id = AccessChainInst.GetSingleWordOperand(idx);
const ir::Instruction* index_inst = def_use_mgr_->GetDef(index_id);
const ir::Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
uint32_t index_value = index_inst->GetSingleWordOperand(2); // TODO: replace with GetUintValueFromConstant()
pointee_type_id = pointee_type->GetSingleWordInOperand(index_value);
}
@ -184,7 +154,7 @@ bool CommonUniformElimPass::IsVolatileLoad(const ir::Instruction& loadInst) {
// If we load a struct directly (result type is struct),
// check if the struct is decorated volatile
uint32_t type_id = loadInst.type_id();
if (def_use_mgr_->GetDef(type_id)->opcode() == SpvOpTypeStruct)
if (get_def_use_mgr()->GetDef(type_id)->opcode() == SpvOpTypeStruct)
return IsVolatileStruct(type_id);
else
return false;
@ -192,12 +162,12 @@ bool CommonUniformElimPass::IsVolatileLoad(const ir::Instruction& loadInst) {
bool CommonUniformElimPass::IsUniformVar(uint32_t varId) {
const ir::Instruction* varInst =
def_use_mgr_->id_to_defs().find(varId)->second;
get_def_use_mgr()->id_to_defs().find(varId)->second;
if (varInst->opcode() != SpvOpVariable)
return false;
const uint32_t varTypeId = varInst->type_id();
const ir::Instruction* varTypeInst =
def_use_mgr_->id_to_defs().find(varTypeId)->second;
get_def_use_mgr()->id_to_defs().find(varTypeId)->second;
return varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) ==
SpvStorageClassUniform ||
varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) ==
@ -205,7 +175,7 @@ bool CommonUniformElimPass::IsUniformVar(uint32_t varId) {
}
bool CommonUniformElimPass::HasUnsupportedDecorates(uint32_t id) const {
analysis::UseList* uses = def_use_mgr_->GetUses(id);
analysis::UseList* uses = get_def_use_mgr()->GetUses(id);
if (uses == nullptr)
return false;
for (auto u : *uses) {
@ -217,7 +187,7 @@ bool CommonUniformElimPass::HasUnsupportedDecorates(uint32_t id) const {
}
bool CommonUniformElimPass::HasOnlyNamesAndDecorates(uint32_t id) const {
analysis::UseList* uses = def_use_mgr_->GetUses(id);
analysis::UseList* uses = get_def_use_mgr()->GetUses(id);
if (uses == nullptr)
return true;
for (auto u : *uses) {
@ -231,7 +201,7 @@ bool CommonUniformElimPass::HasOnlyNamesAndDecorates(uint32_t id) const {
void CommonUniformElimPass::KillNamesAndDecorates(uint32_t id) {
// TODO(greg-lunarg): Remove id from any OpGroupDecorate and
// kill if no other operands.
analysis::UseList* uses = def_use_mgr_->GetUses(id);
analysis::UseList* uses = get_def_use_mgr()->GetUses(id);
if (uses == nullptr)
return;
std::list<ir::Instruction*> killList;
@ -242,7 +212,7 @@ void CommonUniformElimPass::KillNamesAndDecorates(uint32_t id) {
killList.push_back(u.inst);
}
for (auto kip : killList)
def_use_mgr_->KillInst(kip);
get_def_use_mgr()->KillInst(kip);
}
void CommonUniformElimPass::KillNamesAndDecorates(ir::Instruction* inst) {
@ -259,7 +229,7 @@ void CommonUniformElimPass::DeleteIfUseless(ir::Instruction* inst) {
assert(resId != 0);
if (HasOnlyNamesAndDecorates(resId)) {
KillNamesAndDecorates(resId);
def_use_mgr_->KillInst(inst);
get_def_use_mgr()->KillInst(inst);
}
}
@ -268,20 +238,14 @@ void CommonUniformElimPass::ReplaceAndDeleteLoad(ir::Instruction* loadInst,
ir::Instruction* ptrInst) {
const uint32_t loadId = loadInst->result_id();
KillNamesAndDecorates(loadId);
(void) def_use_mgr_->ReplaceAllUsesWith(loadId, replId);
(void) get_def_use_mgr()->ReplaceAllUsesWith(loadId, replId);
// remove load instruction
def_use_mgr_->KillInst(loadInst);
get_def_use_mgr()->KillInst(loadInst);
// if access chain, see if it can be removed as well
if (IsNonPtrAccessChain(ptrInst->opcode()))
DeleteIfUseless(ptrInst);
}
uint32_t CommonUniformElimPass::GetPointeeTypeId(const ir::Instruction* ptrInst) {
const uint32_t ptrTypeId = ptrInst->type_id();
const ir::Instruction* ptrTypeInst = def_use_mgr_->GetDef(ptrTypeId);
return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
}
void CommonUniformElimPass::GenACLoadRepl(const ir::Instruction* ptrInst,
std::vector<std::unique_ptr<ir::Instruction>>* newInsts,
uint32_t* resultId) {
@ -290,7 +254,7 @@ void CommonUniformElimPass::GenACLoadRepl(const ir::Instruction* ptrInst,
const uint32_t ldResultId = TakeNextId();
const uint32_t varId =
ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
const ir::Instruction* varInst = get_def_use_mgr()->GetDef(varId);
assert(varInst->opcode() == SpvOpVariable);
const uint32_t varPteTypeId = GetPointeeTypeId(varInst);
std::vector<ir::Operand> load_in_operands;
@ -299,7 +263,7 @@ void CommonUniformElimPass::GenACLoadRepl(const ir::Instruction* ptrInst,
std::initializer_list<uint32_t>{varId}));
std::unique_ptr<ir::Instruction> newLoad(new ir::Instruction(SpvOpLoad,
varPteTypeId, ldResultId, load_in_operands));
def_use_mgr_->AnalyzeInstDefUse(&*newLoad);
get_def_use_mgr()->AnalyzeInstDefUse(&*newLoad);
newInsts->emplace_back(std::move(newLoad));
// Build and append Extract
@ -312,7 +276,7 @@ void CommonUniformElimPass::GenACLoadRepl(const ir::Instruction* ptrInst,
uint32_t iidIdx = 0;
ptrInst->ForEachInId([&iidIdx, &ext_in_opnds, this](const uint32_t *iid) {
if (iidIdx > 0) {
const ir::Instruction* cInst = def_use_mgr_->GetDef(*iid);
const ir::Instruction* cInst = get_def_use_mgr()->GetDef(*iid);
uint32_t val = cInst->GetSingleWordInOperand(kConstantValueInIdx);
ext_in_opnds.push_back(
ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
@ -322,7 +286,7 @@ void CommonUniformElimPass::GenACLoadRepl(const ir::Instruction* ptrInst,
});
std::unique_ptr<ir::Instruction> newExt(new ir::Instruction(
SpvOpCompositeExtract, ptrPteTypeId, extResultId, ext_in_opnds));
def_use_mgr_->AnalyzeInstDefUse(&*newExt);
get_def_use_mgr()->AnalyzeInstDefUse(&*newExt);
newInsts->emplace_back(std::move(newExt));
*resultId = extResultId;
}
@ -332,7 +296,7 @@ bool CommonUniformElimPass::IsConstantIndexAccessChain(ir::Instruction* acp) {
uint32_t nonConstCnt = 0;
acp->ForEachInId([&inIdx, &nonConstCnt, this](uint32_t* tid) {
if (inIdx > 0) {
ir::Instruction* opInst = def_use_mgr_->GetDef(*tid);
ir::Instruction* opInst = get_def_use_mgr()->GetDef(*tid);
if (opInst->opcode() != SpvOpConstant) ++nonConstCnt;
}
++inIdx;
@ -468,7 +432,7 @@ bool CommonUniformElimPass::CommonUniformLoadElimination(ir::Function* func) {
replId = TakeNextId();
std::unique_ptr<ir::Instruction> newLoad(new ir::Instruction(SpvOpLoad,
ii->type_id(), replId, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {varId}}}));
def_use_mgr_->AnalyzeInstDefUse(&*newLoad);
get_def_use_mgr()->AnalyzeInstDefUse(&*newLoad);
insertItr = insertItr.InsertBefore(std::move(newLoad));
++insertItr;
uniform2load_id_[varId] = replId;
@ -552,14 +516,14 @@ bool CommonUniformElimPass::CommonExtractElimination(ir::Function* func) {
uint32_t replId = TakeNextId();
std::unique_ptr<ir::Instruction> newExtract(new ir::Instruction(*idxItr.second.front()));
newExtract->SetResultId(replId);
def_use_mgr_->AnalyzeInstDefUse(&*newExtract);
get_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);
(void)get_def_use_mgr()->ReplaceAllUsesWith(resId, replId);
get_def_use_mgr()->KillInst(instItr);
}
modified = true;
}
@ -579,35 +543,19 @@ bool CommonUniformElimPass::EliminateCommonUniform(ir::Function* func) {
}
void CommonUniformElimPass::Initialize(ir::Module* module) {
InitializeProcessing(module);
module_ = module;
// Initialize function and block maps
id2block_.clear();
for (auto& fn : *module_)
for (auto& blk : fn)
id2block_[blk.id()] = &blk;
// Clear collections
block2structured_succs_.clear();
label2preds_.clear();
// Clear collections.
comp2idx2inst_.clear();
// TODO(greg-lunarg): Use def/use from previous pass
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
dec_mgr_.reset(new analysis::DecorationManager(module_));
// Initialize next unused Id.
next_id_ = module->id_bound();
dec_mgr_.reset(new analysis::DecorationManager(get_module()));
// Initialize extension whitelist
InitExtensions();
};
bool CommonUniformElimPass::AllExtensionsSupported() const {
// If any extension not in whitelist, return false
for (auto& ei : module_->extensions()) {
for (auto& ei : get_module()->extensions()) {
const char* extName = reinterpret_cast<const char*>(
&ei.GetInOperand(0).words[0]);
if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
@ -619,11 +567,11 @@ bool CommonUniformElimPass::AllExtensionsSupported() const {
Pass::Status CommonUniformElimPass::ProcessImpl() {
// Assumes all control flow structured.
// TODO(greg-lunarg): Do SSA rewrite for non-structured control flow
if (!module_->HasCapability(SpvCapabilityShader))
if (!get_module()->HasCapability(SpvCapabilityShader))
return Status::SuccessWithoutChange;
// Assumes logical addressing only
// TODO(greg-lunarg): Add support for physical addressing
if (module_->HasCapability(SpvCapabilityAddresses))
if (get_module()->HasCapability(SpvCapabilityAddresses))
return Status::SuccessWithoutChange;
// Do not process if any disallowed extensions are enabled
if (!AllExtensionsSupported())
@ -631,12 +579,12 @@ Pass::Status CommonUniformElimPass::ProcessImpl() {
// Do not process if module contains OpGroupDecorate. Additional
// support required in KillNamesAndDecorates().
// TODO(greg-lunarg): Add support for OpGroupDecorate
for (auto& ai : module_->annotations())
for (auto& ai : get_module()->annotations())
if (ai.opcode() == SpvOpGroupDecorate)
return Status::SuccessWithoutChange;
// If non-32-bit integer type in module, terminate processing
// TODO(): Handle non-32-bit integer constants in access chains
for (const ir::Instruction& inst : module_->types_values())
for (const ir::Instruction& inst : get_module()->types_values())
if (inst.opcode() == SpvOpTypeInt &&
inst.GetSingleWordInOperand(kTypeIntWidthInIdx) != 32)
return Status::SuccessWithoutChange;
@ -644,13 +592,12 @@ Pass::Status CommonUniformElimPass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return EliminateCommonUniform(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
FinalizeNextId(module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
FinalizeNextId();
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
CommonUniformElimPass::CommonUniformElimPass()
: module_(nullptr), def_use_mgr_(nullptr), next_id_(0) {}
CommonUniformElimPass::CommonUniformElimPass() {}
Pass::Status CommonUniformElimPass::Process(ir::Module* module) {
Initialize(module);

View File

@ -56,9 +56,6 @@ class CommonUniformElimPass : public Pass {
// Returns true if |varId| is a variable containing a sampler or image.
bool IsSamplerOrImageVar(uint32_t varId) const;
// Return true if |block_ptr| is loop header block
bool IsLoopHeader(ir::BasicBlock* block_ptr);
// Given a load or store pointed at by |ip|, return the top-most
// non-CopyObj in its pointer operand. Also return the base pointer
// in |objId|.
@ -102,9 +99,6 @@ class CommonUniformElimPass : public Pass {
uint32_t replId,
ir::Instruction* ptrInst);
// Return type id for pointer's pointee
uint32_t GetPointeeTypeId(const ir::Instruction* ptrInst);
// For the (constant index) access chain ptrInst, create an
// equivalent load and extract
void GenACLoadRepl(const ir::Instruction* ptrInst,
@ -117,23 +111,25 @@ class CommonUniformElimPass : public Pass {
// Convert all uniform access chain loads into load/extract.
bool UniformAccessChainConvert(ir::Function* func);
// Returns the id of the merge block declared by a merge instruction in
// this block, if any. If none, returns zero.
uint32_t MergeBlockIdIfAny(const ir::BasicBlock& blk, uint32_t* cbid);
// Compute structured successors for function |func|.
// A block's structured successors are the blocks it branches to
// together with its declared merge block if it has one.
// When order matters, the merge block always appears first.
// This assures correct depth first search in the presence of early
// This assures correct depth first search in the presence of early
// returns and kills. If the successor vector contain duplicates
// if the merge block, they are safely ignored by DFS.
//
// TODO(dnovillo): This pass computes structured successors slightly different
// than the implementation in class Pass. Can this be re-factored?
void ComputeStructuredSuccessors(ir::Function* func);
// Compute structured block order for |func| into |structuredOrder|. This
// order has the property that dominators come before all blocks they
// dominate and merge blocks come after all blocks that are in the control
// constructs of their header.
//
// TODO(dnovillo): This pass computes structured order slightly different
// than the implementation in class Pass. Can this be re-factored?
void ComputeStructuredOrder(ir::Function* func,
std::list<ir::BasicBlock*>* order);
@ -177,37 +173,12 @@ class CommonUniformElimPass : public Pass {
return (op == SpvOpDecorate || op == SpvOpDecorateId);
}
inline void FinalizeNextId(ir::Module* module) {
module->SetIdBound(next_id_);
}
inline uint32_t TakeNextId() {
return next_id_++;
}
void Initialize(ir::Module* module);
Pass::Status ProcessImpl();
// Module this pass is processing
ir::Module* module_;
// Def-Uses for the module we are processing
std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
// Decorations for the module we are processing
std::unique_ptr<analysis::DecorationManager> dec_mgr_;
// Map from block's label id to block.
std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
// Map from block to its structured successor blocks. See
// ComputeStructuredSuccessors() for definition.
std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
block2structured_succs_;
// Map from block's label id to its predecessor blocks ids
std::unordered_map<uint32_t, std::vector<uint32_t>> label2preds_;
// Map from uniform variable id to its common load id
std::unordered_map<uint32_t, uint32_t> uniform2load_id_;
@ -218,9 +189,6 @@ class CommonUniformElimPass : public Pass {
// Extensions supported by this pass.
std::unordered_set<std::string> extensions_whitelist_;
// Next unused ID
uint32_t next_id_;
};
} // namespace opt

View File

@ -24,6 +24,8 @@ using ir::Instruction;
using ir::Operand;
Pass::Status CompactIdsPass::Process(ir::Module* module) {
InitializeProcessing(module);
bool modified = false;
std::unordered_map<uint32_t, uint32_t> result_id_mapping;

View File

@ -28,70 +28,12 @@ const uint32_t kBranchTargetLabIdInIdx = 0;
const uint32_t kBranchCondTrueLabIdInIdx = 1;
const uint32_t kBranchCondFalseLabIdInIdx = 2;
const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
} // anonymous namespace
uint32_t DeadBranchElimPass::MergeBlockIdIfAny(
const ir::BasicBlock& blk, uint32_t* cbid) const {
auto merge_ii = blk.cend();
--merge_ii;
uint32_t mbid = 0;
*cbid = 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;
}
void DeadBranchElimPass::ComputeStructuredSuccessors(ir::Function* func) {
// If header, make merge block first successor. If a loop header, make
// the second successor the continue target.
for (auto& blk : *func) {
uint32_t cbid;
uint32_t mbid = MergeBlockIdIfAny(blk, &cbid);
if (mbid != 0) {
block2structured_succs_[&blk].push_back(id2block_[mbid]);
if (cbid != 0)
block2structured_succs_[&blk].push_back(id2block_[cbid]);
}
// add true successors
blk.ForEachSuccessorLabel([&blk, this](uint32_t sbid) {
block2structured_succs_[&blk].push_back(id2block_[sbid]);
});
}
}
void DeadBranchElimPass::ComputeStructuredOrder(
ir::Function* func, std::list<ir::BasicBlock*>* order) {
// Compute structured successors and do DFS
ComputeStructuredSuccessors(func);
auto ignore_block = [](cbb_ptr) {};
auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
auto get_structured_successors = [this](const ir::BasicBlock* block) {
return &(block2structured_succs_[block]); };
// TODO(greg-lunarg): Get rid of const_cast by making moving const
// out of the cfa.h prototypes and into the invoking code.
auto post_order = [&](cbb_ptr b) {
order->push_front(const_cast<ir::BasicBlock*>(b)); };
spvtools::CFA<ir::BasicBlock>::DepthFirstTraversal(
&*func->begin(), get_structured_successors, ignore_block, post_order,
ignore_edge);
}
bool DeadBranchElimPass::GetConstCondition(uint32_t condId, bool* condVal) {
bool condIsConst;
ir::Instruction* cInst = def_use_mgr_->GetDef(condId);
ir::Instruction* cInst = get_def_use_mgr()->GetDef(condId);
switch (cInst->opcode()) {
case SpvOpConstantFalse: {
*condVal = false;
@ -116,9 +58,9 @@ bool DeadBranchElimPass::GetConstCondition(uint32_t condId, bool* condVal) {
}
bool DeadBranchElimPass::GetConstInteger(uint32_t selId, uint32_t* selVal) {
ir::Instruction* sInst = def_use_mgr_->GetDef(selId);
ir::Instruction* sInst = get_def_use_mgr()->GetDef(selId);
uint32_t typeId = sInst->type_id();
ir::Instruction* typeInst = def_use_mgr_->GetDef(typeId);
ir::Instruction* typeInst = get_def_use_mgr()->GetDef(typeId);
if (!typeInst || (typeInst->opcode() != SpvOpTypeInt)) return false;
// TODO(greg-lunarg): Support non-32 bit ints
if (typeInst->GetSingleWordInOperand(0) != 32)
@ -138,7 +80,7 @@ void DeadBranchElimPass::AddBranch(uint32_t labelId, ir::BasicBlock* bp) {
std::unique_ptr<ir::Instruction> newBranch(
new ir::Instruction(SpvOpBranch, 0, 0,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {labelId}}}));
def_use_mgr_->AnalyzeInstDefUse(&*newBranch);
get_def_use_mgr()->AnalyzeInstDefUse(&*newBranch);
bp->AddInstruction(std::move(newBranch));
}
@ -148,7 +90,7 @@ void DeadBranchElimPass::AddSelectionMerge(uint32_t labelId,
new ir::Instruction(SpvOpSelectionMerge, 0, 0,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {labelId}},
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {0}}}));
def_use_mgr_->AnalyzeInstDefUse(&*newMerge);
get_def_use_mgr()->AnalyzeInstDefUse(&*newMerge);
bp->AddInstruction(std::move(newMerge));
}
@ -159,14 +101,14 @@ void DeadBranchElimPass::AddBranchConditional(uint32_t condId,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {condId}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {trueLabId}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {falseLabId}}}));
def_use_mgr_->AnalyzeInstDefUse(&*newBranchCond);
get_def_use_mgr()->AnalyzeInstDefUse(&*newBranchCond);
bp->AddInstruction(std::move(newBranchCond));
}
void DeadBranchElimPass::KillAllInsts(ir::BasicBlock* bp) {
bp->ForEachInst([this](ir::Instruction* ip) {
KillNamesAndDecorates(ip);
def_use_mgr_->KillInst(ip);
get_def_use_mgr()->KillInst(ip);
});
}
@ -192,7 +134,7 @@ bool DeadBranchElimPass::GetSelectionBranch(ir::BasicBlock* bp,
}
bool DeadBranchElimPass::HasNonPhiNonBackedgeRef(uint32_t labelId) {
analysis::UseList* uses = def_use_mgr_->GetUses(labelId);
analysis::UseList* uses = get_def_use_mgr()->GetUses(labelId);
if (uses == nullptr)
return false;
for (auto u : *uses) {
@ -298,8 +240,8 @@ bool DeadBranchElimPass::EliminateDeadBranches(ir::Function* func) {
const uint32_t mergeLabId =
mergeInst->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
AddBranch(liveLabId, *bi);
def_use_mgr_->KillInst(br);
def_use_mgr_->KillInst(mergeInst);
get_def_use_mgr()->KillInst(br);
get_def_use_mgr()->KillInst(mergeInst);
modified = true;
@ -390,14 +332,14 @@ bool DeadBranchElimPass::EliminateDeadBranches(ir::Function* func) {
});
std::unique_ptr<ir::Instruction> newPhi(new ir::Instruction(
SpvOpPhi, pii->type_id(), replId, phi_in_opnds));
def_use_mgr_->AnalyzeInstDefUse(&*newPhi);
get_def_use_mgr()->AnalyzeInstDefUse(&*newPhi);
pii = pii.InsertBefore(std::move(newPhi));
++pii;
}
const uint32_t phiId = pii->result_id();
KillNamesAndDecorates(phiId);
(void)def_use_mgr_->ReplaceAllUsesWith(phiId, replId);
def_use_mgr_->KillInst(&*pii);
(void)get_def_use_mgr()->ReplaceAllUsesWith(phiId, replId);
get_def_use_mgr()->KillInst(&*pii);
}
}
@ -411,31 +353,24 @@ bool DeadBranchElimPass::EliminateDeadBranches(ir::Function* func) {
}
void DeadBranchElimPass::Initialize(ir::Module* module) {
module_ = module;
InitializeProcessing(module);
// Initialize function and block maps
id2block_.clear();
block2structured_succs_.clear();
// Initialize block map
for (auto& fn : *module_)
for (auto& fn : *get_module())
for (auto& blk : fn)
id2block_[blk.id()] = &blk;
// TODO(greg-lunarg): Reuse def/use from previous passes
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
// Initialize next unused Id.
InitNextId();
// Initialize extension whitelist
InitExtensions();
};
bool DeadBranchElimPass::AllExtensionsSupported() const {
// If any extension not in whitelist, return false
for (auto& ei : module_->extensions()) {
for (auto& ei : get_module()->extensions()) {
const char* extName = reinterpret_cast<const char*>(
&ei.GetInOperand(0).words[0]);
if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
@ -447,12 +382,12 @@ bool DeadBranchElimPass::AllExtensionsSupported() const {
Pass::Status DeadBranchElimPass::ProcessImpl() {
// Current functionality assumes structured control flow.
// TODO(greg-lunarg): Handle non-structured control-flow.
if (!module_->HasCapability(SpvCapabilityShader))
if (!get_module()->HasCapability(SpvCapabilityShader))
return Status::SuccessWithoutChange;
// Do not process if module contains OpGroupDecorate. Additional
// support required in KillNamesAndDecorates().
// TODO(greg-lunarg): Add support for OpGroupDecorate
for (auto& ai : module_->annotations())
for (auto& ai : get_module()->annotations())
if (ai.opcode() == SpvOpGroupDecorate)
return Status::SuccessWithoutChange;
// Do not process if any disallowed extensions are enabled
@ -464,7 +399,7 @@ Pass::Status DeadBranchElimPass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return EliminateDeadBranches(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
FinalizeNextId();
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@ -47,28 +47,6 @@ class DeadBranchElimPass : public MemPass {
Status Process(ir::Module*) override;
private:
// Returns the id of the merge block declared by a merge instruction in
// this block |blk|, if any. If none, returns zero. If loop merge, returns
// the continue target id in |cbid|. Otherwise sets to zero.
uint32_t MergeBlockIdIfAny(const ir::BasicBlock& blk, uint32_t* cbid) const;
// Compute structured successors for function |func|.
// A block's structured successors are the blocks it branches to
// together with its declared merge block if it has one.
// When order matters, the merge block always appears first and if
// a loop merge block, the continue target always appears second.
// This assures correct depth first search in the presence of early
// returns and kills. If the successor vector contain duplicates
// of the merge and continue blocks, they are safely ignored by DFS.
void ComputeStructuredSuccessors(ir::Function* func);
// Compute structured block order |order| for function |func|. This order
// has the property that dominators are before all blocks they dominate and
// merge blocks are after all blocks that are in the control constructs of
// their header.
void ComputeStructuredOrder(
ir::Function* func, std::list<ir::BasicBlock*>* order);
// If |condId| is boolean constant, return conditional value in |condVal| and
// return true, otherwise return false.
bool GetConstCondition(uint32_t condId, bool* condVal);
@ -120,14 +98,6 @@ class DeadBranchElimPass : public MemPass {
void Initialize(ir::Module* module);
Pass::Status ProcessImpl();
// Map from block's label id to block.
std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
// Map from block to its structured successor blocks. See
// ComputeStructuredSuccessors() for definition.
std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
block2structured_succs_;
// All backedge branches in current function
std::unordered_set<ir::Instruction*> backedges_;

View File

@ -26,10 +26,7 @@ Pass::Status DeadVariableElimination::Process(spvtools::ir::Module* module) {
// Anything with a reference count of 0 will then be deleted. For variables
// that might have references that are not explicit in this module, we use the
// value kMustKeep as the reference count.
bool modified = false;
module_ = module;
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
InitializeProcessing(module);
FindNamedOrDecoratedIds();
// Decoration manager to help organize decorations.
@ -61,7 +58,7 @@ Pass::Status DeadVariableElimination::Process(spvtools::ir::Module* module) {
if (count != kMustKeep) {
// If we don't have to keep the instruction for other reasons, then look
// at the uses and count the number of real references.
if (analysis::UseList* uses = def_use_mgr_->GetUses(result_id)) {
if (analysis::UseList* uses = get_def_use_mgr()->GetUses(result_id)) {
count = std::count_if(
uses->begin(), uses->end(), [](const analysis::Use& u) {
return (!ir::IsAnnotationInst(u.inst->opcode()) &&
@ -76,6 +73,7 @@ Pass::Status DeadVariableElimination::Process(spvtools::ir::Module* module) {
}
// Remove all of the variables that have a reference count of 0.
bool modified = false;
if (!ids_to_remove.empty()) {
modified = true;
for (auto result_id : ids_to_remove) {
@ -86,7 +84,7 @@ Pass::Status DeadVariableElimination::Process(spvtools::ir::Module* module) {
}
void DeadVariableElimination::DeleteVariable(uint32_t result_id) {
ir::Instruction* inst = def_use_mgr_->GetDef(result_id);
ir::Instruction* inst = get_def_use_mgr()->GetDef(result_id);
assert(inst->opcode() == SpvOpVariable &&
"Should not be trying to delete anything other than an OpVariable.");
@ -94,7 +92,7 @@ void DeadVariableElimination::DeleteVariable(uint32_t result_id) {
// if that variable can be deleted after the reference is removed.
if (inst->NumOperands() == 4) {
ir::Instruction* initializer =
def_use_mgr_->GetDef(inst->GetSingleWordOperand(3));
get_def_use_mgr()->GetDef(inst->GetSingleWordOperand(3));
// TODO: Handle OpSpecConstantOP which might be defined in terms of other
// variables. Will probably require a unified dead code pass that does all
@ -112,7 +110,7 @@ void DeadVariableElimination::DeleteVariable(uint32_t result_id) {
}
}
this->KillNamesAndDecorates(result_id);
def_use_mgr_->KillDef(result_id);
get_def_use_mgr()->KillDef(result_id);
}
} // namespace opt
} // namespace spvtools

View File

@ -20,8 +20,7 @@ namespace spvtools {
namespace opt {
Pass::Status EliminateDeadFunctionsPass::Process(ir::Module* module) {
bool modified = false;
module_ = module;
InitializeProcessing(module);
// Identify live functions first. Those that are not live
// are dead.
@ -32,8 +31,8 @@ Pass::Status EliminateDeadFunctionsPass::Process(ir::Module* module) {
};
ProcessReachableCallTree(mark_live, module);
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
FindNamedOrDecoratedIds();
bool modified = false;
for (auto funcIter = module->begin(); funcIter != module->end();) {
if (live_function_set.count(&*funcIter) == 0) {
modified = true;
@ -53,7 +52,7 @@ void EliminateDeadFunctionsPass::EliminateFunction(ir::Function* func) {
func->ForEachInst(
[this](ir::Instruction* inst) {
KillNamesAndDecorates(inst);
def_use_mgr_->KillInst(inst);
get_def_use_mgr()->KillInst(inst);
},
true);
}

View File

@ -29,6 +29,8 @@ using Words = std::vector<uint32_t>;
using OrderedUsesMap = std::unordered_map<uint32_t, Words>;
Pass::Status FlattenDecorationPass::Process(ir::Module* module) {
InitializeProcessing(module);
bool modified = false;
// The target Id of OpDecorationGroup instructions.

View File

@ -242,11 +242,7 @@ std::vector<uint32_t> OperateVectors(
} // anonymous namespace
FoldSpecConstantOpAndCompositePass::FoldSpecConstantOpAndCompositePass()
: max_id_(0),
module_(nullptr),
def_use_mgr_(nullptr),
type_mgr_(nullptr),
id_to_const_val_() {}
: max_id_(0), type_mgr_(nullptr), id_to_const_val_() {}
Pass::Status FoldSpecConstantOpAndCompositePass::Process(ir::Module* module) {
Initialize(module);
@ -254,12 +250,11 @@ Pass::Status FoldSpecConstantOpAndCompositePass::Process(ir::Module* module) {
}
void FoldSpecConstantOpAndCompositePass::Initialize(ir::Module* module) {
InitializeProcessing(module);
type_mgr_.reset(new analysis::TypeManager(consumer(), *module));
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
for (const auto& id_def : def_use_mgr_->id_to_defs()) {
for (const auto& id_def : get_def_use_mgr()->id_to_defs()) {
max_id_ = std::max(max_id_, id_def.first);
}
module_ = module;
};
Pass::Status FoldSpecConstantOpAndCompositePass::ProcessImpl(
@ -376,8 +371,8 @@ bool FoldSpecConstantOpAndCompositePass::ProcessOpSpecConstantOp(
// original constant.
uint32_t new_id = folded_inst->result_id();
uint32_t old_id = inst->result_id();
def_use_mgr_->ReplaceAllUsesWith(old_id, new_id);
def_use_mgr_->KillDef(old_id);
get_def_use_mgr()->ReplaceAllUsesWith(old_id, new_id);
get_def_use_mgr()->KillDef(old_id);
return true;
}
@ -585,7 +580,7 @@ FoldSpecConstantOpAndCompositePass::BuildInstructionAndAddToModule(
std::unique_ptr<analysis::Constant> c, ir::Module::inst_iterator* pos) {
analysis::Constant* new_const = c.get();
uint32_t new_id = ++max_id_;
module_->SetIdBound(new_id + 1);
get_module()->SetIdBound(new_id + 1);
const_val_to_id_[new_const] = new_id;
id_to_const_val_[new_id] = std::move(c);
auto new_inst = CreateInstruction(new_id, new_const);
@ -593,7 +588,7 @@ FoldSpecConstantOpAndCompositePass::BuildInstructionAndAddToModule(
auto* new_inst_ptr = new_inst.get();
*pos = pos->InsertBefore(std::move(new_inst));
++(*pos);
def_use_mgr_->AnalyzeInstDefUse(new_inst_ptr);
get_def_use_mgr()->AnalyzeInstDefUse(new_inst_ptr);
return new_inst_ptr;
}

View File

@ -149,10 +149,7 @@ class FoldSpecConstantOpAndCompositePass : public Pass {
// The maximum used ID.
uint32_t max_id_;
// A pointer to the module under process.
ir::Module* module_;
// DefUse manager
std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
// Type manager
std::unique_ptr<analysis::TypeManager> type_mgr_;

View File

@ -58,8 +58,8 @@ Pass::Status InlineExhaustivePass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return InlineExhaustive(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
FinalizeNextId(module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
FinalizeNextId();
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@ -26,7 +26,7 @@ namespace {
} // anonymous namespace
bool InlineOpaquePass::IsOpaqueType(uint32_t typeId) {
const ir::Instruction* typeInst = def_use_mgr_->GetDef(typeId);
const ir::Instruction* typeInst = get_def_use_mgr()->GetDef(typeId);
switch (typeInst->opcode()) {
case SpvOpTypeSampler:
case SpvOpTypeImage:
@ -58,7 +58,7 @@ bool InlineOpaquePass::HasOpaqueArgsOrReturn(const ir::Instruction* callInst) {
int ocnt = 0;
callInst->ForEachInId([&icnt,&ocnt,this](const uint32_t *iid) {
if (icnt > 0) {
const ir::Instruction* argInst = def_use_mgr_->GetDef(*iid);
const ir::Instruction* argInst = get_def_use_mgr()->GetDef(*iid);
if (IsOpaqueType(argInst->type_id()))
++ocnt;
}
@ -106,8 +106,8 @@ Pass::Status InlineOpaquePass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return InlineOpaque(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
FinalizeNextId(module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
FinalizeNextId();
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@ -27,15 +27,14 @@ static const int kSpvTypePointerStorageClass = 1;
static const int kSpvTypePointerTypeId = 2;
static const int kSpvLoopMergeMergeBlockId = 0;
static const int kSpvLoopMergeContinueTargetIdInIdx = 1;
static const int kSpvSelectionMergeMergeBlockId = 0;
namespace spvtools {
namespace opt {
uint32_t InlinePass::FindPointerToType(uint32_t type_id,
SpvStorageClass storage_class) {
ir::Module::inst_iterator type_itr = module_->types_values_begin();
for (; type_itr != module_->types_values_end(); ++type_itr) {
ir::Module::inst_iterator type_itr = get_module()->types_values_begin();
for (; type_itr != get_module()->types_values_end(); ++type_itr) {
const ir::Instruction* type_inst = &*type_itr;
if (type_inst->opcode() == SpvOpTypePointer &&
type_inst->GetSingleWordOperand(kSpvTypePointerTypeId) == type_id &&
@ -54,7 +53,7 @@ uint32_t InlinePass::AddPointerToType(uint32_t type_id,
{{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
{uint32_t(storage_class)}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {type_id}}}));
module_->AddType(std::move(type_inst));
get_module()->AddType(std::move(type_inst));
return resultId;
}
@ -111,16 +110,16 @@ std::unique_ptr<ir::Instruction> InlinePass::NewLabel(uint32_t label_id) {
uint32_t InlinePass::GetFalseId() {
if (false_id_ != 0)
return false_id_;
false_id_ = module_->GetGlobalValue(SpvOpConstantFalse);
false_id_ = get_module()->GetGlobalValue(SpvOpConstantFalse);
if (false_id_ != 0)
return false_id_;
uint32_t boolId = module_->GetGlobalValue(SpvOpTypeBool);
uint32_t boolId = get_module()->GetGlobalValue(SpvOpTypeBool);
if (boolId == 0) {
boolId = TakeNextId();
module_->AddGlobalValue(SpvOpTypeBool, boolId, 0);
get_module()->AddGlobalValue(SpvOpTypeBool, boolId, 0);
}
false_id_ = TakeNextId();
module_->AddGlobalValue(SpvOpConstantFalse, false_id_, boolId);
get_module()->AddGlobalValue(SpvOpConstantFalse, false_id_, boolId);
return false_id_;
}
@ -160,7 +159,7 @@ uint32_t InlinePass::CreateReturnVar(
uint32_t returnVarId = 0;
const uint32_t calleeTypeId = calleeFn->type_id();
const ir::Instruction* calleeType =
def_use_mgr_->id_to_defs().find(calleeTypeId)->second;
get_def_use_mgr()->id_to_defs().find(calleeTypeId)->second;
if (calleeType->opcode() != SpvOpTypeVoid) {
// Find or create ptr to callee return type.
uint32_t returnVarTypeId =
@ -560,24 +559,11 @@ bool InlinePass::HasMultipleReturns(ir::Function* func) {
return multipleReturns;
}
uint32_t InlinePass::MergeBlockIdIfAny(const ir::BasicBlock& blk) {
auto merge_ii = blk.cend();
--merge_ii;
uint32_t mbid = 0;
if (merge_ii != blk.cbegin()) {
--merge_ii;
if (merge_ii->opcode() == SpvOpLoopMerge)
mbid = merge_ii->GetSingleWordOperand(kSpvLoopMergeMergeBlockId);
else if (merge_ii->opcode() == SpvOpSelectionMerge)
mbid = merge_ii->GetSingleWordOperand(kSpvSelectionMergeMergeBlockId);
}
return mbid;
}
void InlinePass::ComputeStructuredSuccessors(ir::Function* func) {
// If header, make merge block first successor.
for (auto& blk : *func) {
uint32_t mbid = MergeBlockIdIfAny(blk);
uint32_t mbid = MergeBlockIdIfAny(blk, nullptr);
if (mbid != 0)
block2structured_succs_[&blk].push_back(id2block_[mbid]);
// add true successors
@ -586,7 +572,6 @@ void InlinePass::ComputeStructuredSuccessors(ir::Function* func) {
});
}
}
InlinePass::GetBlocksFunction InlinePass::StructuredSuccessorsFunction() {
return [this](const ir::BasicBlock* block) {
return &(block2structured_succs_[block]);
@ -596,7 +581,7 @@ InlinePass::GetBlocksFunction InlinePass::StructuredSuccessorsFunction() {
bool InlinePass::HasNoReturnInLoop(ir::Function* func) {
// If control not structured, do not do loop/return analysis
// TODO: Analyze returns in non-structured control flow
if (!module_->HasCapability(SpvCapabilityShader))
if (!get_module()->HasCapability(SpvCapabilityShader))
return false;
// Compute structured block order. This order has the property
// that dominators are before all blocks they dominate and merge blocks
@ -664,13 +649,7 @@ bool InlinePass::IsInlinableFunction(ir::Function* func) {
}
void InlinePass::InitializeInline(ir::Module* module) {
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
// Initialize next unused Id.
next_id_ = module->id_bound();
// Save module.
module_ = module;
InitializeProcessing(module);
false_id_ = 0;
@ -682,7 +661,7 @@ void InlinePass::InitializeInline(ir::Module* module) {
no_return_in_loop_.clear();
multi_return_funcs_.clear();
for (auto& fn : *module_) {
for (auto& fn : *get_module()) {
// Initialize function and block maps.
id2function_[fn.result_id()] = &fn;
for (auto& blk : fn) {
@ -694,9 +673,7 @@ void InlinePass::InitializeInline(ir::Module* module) {
}
};
InlinePass::InlinePass()
: module_(nullptr), def_use_mgr_(nullptr), next_id_(0) {}
InlinePass::InlinePass() {}
} // namespace opt
} // namespace spvtools

View File

@ -43,14 +43,6 @@ class InlinePass : public Pass {
virtual ~InlinePass() = default;
protected:
// Return the next available Id and increment it.
inline uint32_t TakeNextId() { return next_id_++; }
// Write the next available Id back to the module.
inline void FinalizeNextId(ir::Module* module) {
module->SetIdBound(next_id_);
}
// Find pointer to type and storage in module, return its resultId,
// 0 if not found. TODO(greg-lunarg): Move this into type manager.
uint32_t FindPointerToType(uint32_t type_id, SpvStorageClass storage_class);
@ -137,10 +129,6 @@ class InlinePass : public Pass {
// Return true if |inst| is a function call that can be inlined.
bool IsInlinableFunctionCall(const ir::Instruction* inst);
// Returns the id of the merge block declared by a merge instruction in
// this block, if any. If none, returns zero.
uint32_t MergeBlockIdIfAny(const ir::BasicBlock& blk);
// Compute structured successors for function |func|.
// A block's structured successors are the blocks it branches to
// together with its declared merge block if it has one.
@ -175,12 +163,6 @@ class InlinePass : public Pass {
// Initialize state for optimization of |module|
void InitializeInline(ir::Module* module);
// Module being processed by this pass
ir::Module* module_;
// Def/Use database
std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
// Map from function's result id to function.
std::unordered_map<uint32_t, ir::Function*> id2function_;
@ -196,16 +178,8 @@ class InlinePass : public Pass {
// Set of ids of inlinable functions
std::set<uint32_t> inlinable_;
// Map from block to its structured successor blocks. See
// ComputeStructuredSuccessors() for definition.
std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
block2structured_succs_;
// result id for OpConstantFalse
uint32_t false_id_;
// Next unused ID
uint32_t next_id_;
};
} // namespace opt

View File

@ -56,7 +56,7 @@ bool InsertExtractElimPass::ExtInsConflict(const ir::Instruction* extInst,
}
bool InsertExtractElimPass::IsVectorType(uint32_t typeId) {
ir::Instruction* typeInst = def_use_mgr_->GetDef(typeId);
ir::Instruction* typeInst = get_def_use_mgr()->GetDef(typeId);
return typeInst->opcode() == SpvOpTypeVector;
}
@ -67,7 +67,7 @@ bool InsertExtractElimPass::EliminateInsertExtract(ir::Function* func) {
switch (ii->opcode()) {
case SpvOpCompositeExtract: {
uint32_t cid = ii->GetSingleWordInOperand(kExtractCompositeIdInIdx);
ir::Instruction* cinst = def_use_mgr_->GetDef(cid);
ir::Instruction* cinst = get_def_use_mgr()->GetDef(cid);
uint32_t replId = 0;
while (cinst->opcode() == SpvOpCompositeInsert) {
if (ExtInsConflict(&*ii, cinst))
@ -77,7 +77,7 @@ bool InsertExtractElimPass::EliminateInsertExtract(ir::Function* func) {
break;
}
cid = cinst->GetSingleWordInOperand(kInsertCompositeIdInIdx);
cinst = def_use_mgr_->GetDef(cid);
cinst = get_def_use_mgr()->GetDef(cid);
}
// If search ended with CompositeConstruct or ConstantComposite
// and the extract has one index, return the appropriate component.
@ -94,7 +94,7 @@ bool InsertExtractElimPass::EliminateInsertExtract(ir::Function* func) {
uint32_t i = 0;
for (; i <= compIdx; i++) {
uint32_t compId = cinst->GetSingleWordInOperand(i);
ir::Instruction* compInst = def_use_mgr_->GetDef(compId);
ir::Instruction* compInst = get_def_use_mgr()->GetDef(compId);
if (compInst->type_id() != (*ii).type_id())
break;
}
@ -108,8 +108,8 @@ bool InsertExtractElimPass::EliminateInsertExtract(ir::Function* func) {
}
if (replId != 0) {
const uint32_t extId = ii->result_id();
(void)def_use_mgr_->ReplaceAllUsesWith(extId, replId);
def_use_mgr_->KillInst(&*ii);
(void)get_def_use_mgr()->ReplaceAllUsesWith(extId, replId);
get_def_use_mgr()->KillInst(&*ii);
modified = true;
}
} break;
@ -122,11 +122,7 @@ bool InsertExtractElimPass::EliminateInsertExtract(ir::Function* func) {
}
void InsertExtractElimPass::Initialize(ir::Module* module) {
module_ = module;
// Do def/use on whole module
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
InitializeProcessing(module);
// Initialize extension whitelist
InitExtensions();
@ -134,7 +130,7 @@ void InsertExtractElimPass::Initialize(ir::Module* module) {
bool InsertExtractElimPass::AllExtensionsSupported() const {
// If any extension not in whitelist, return false
for (auto& ei : module_->extensions()) {
for (auto& ei : get_module()->extensions()) {
const char* extName = reinterpret_cast<const char*>(
&ei.GetInOperand(0).words[0]);
if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
@ -151,12 +147,11 @@ Pass::Status InsertExtractElimPass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return EliminateInsertExtract(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
InsertExtractElimPass::InsertExtractElimPass()
: module_(nullptr), def_use_mgr_(nullptr) {}
InsertExtractElimPass::InsertExtractElimPass() {}
Pass::Status InsertExtractElimPass::Process(ir::Module* module) {
Initialize(module);

View File

@ -70,12 +70,6 @@ class InsertExtractElimPass : public Pass {
void Initialize(ir::Module* module);
Pass::Status ProcessImpl();
// Module this pass is processing
ir::Module* module_;
// Def-Uses for the module we are processing
std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
// Extensions supported by this pass.
std::unordered_set<std::string> extensions_whitelist_;
};

View File

@ -25,7 +25,6 @@ namespace {
const uint32_t kStoreValIdInIdx = 1;
const uint32_t kAccessChainPtrIdInIdx = 0;
const uint32_t kTypePointerTypeIdInIdx = 1;
const uint32_t kConstantValueInIdx = 0;
const uint32_t kTypeIntWidthInIdx = 0;
@ -36,17 +35,10 @@ void LocalAccessChainConvertPass::DeleteIfUseless(ir::Instruction* inst) {
assert(resId != 0);
if (HasOnlyNamesAndDecorates(resId)) {
KillNamesAndDecorates(resId);
def_use_mgr_->KillInst(inst);
get_def_use_mgr()->KillInst(inst);
}
}
uint32_t LocalAccessChainConvertPass::GetPointeeTypeId(
const ir::Instruction* ptrInst) const {
const uint32_t ptrTypeId = ptrInst->type_id();
const ir::Instruction* ptrTypeInst = def_use_mgr_->GetDef(ptrTypeId);
return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
}
void LocalAccessChainConvertPass::BuildAndAppendInst(
SpvOp opcode,
uint32_t typeId,
@ -55,7 +47,7 @@ void LocalAccessChainConvertPass::BuildAndAppendInst(
std::vector<std::unique_ptr<ir::Instruction>>* newInsts) {
std::unique_ptr<ir::Instruction> newInst(new ir::Instruction(
opcode, typeId, resultId, in_opnds));
def_use_mgr_->AnalyzeInstDefUse(&*newInst);
get_def_use_mgr()->AnalyzeInstDefUse(&*newInst);
newInsts->emplace_back(std::move(newInst));
}
@ -66,7 +58,7 @@ uint32_t LocalAccessChainConvertPass::BuildAndAppendVarLoad(
std::vector<std::unique_ptr<ir::Instruction>>* newInsts) {
const uint32_t ldResultId = TakeNextId();
*varId = ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
const ir::Instruction* varInst = def_use_mgr_->GetDef(*varId);
const ir::Instruction* varInst = get_def_use_mgr()->GetDef(*varId);
assert(varInst->opcode() == SpvOpVariable);
*varPteTypeId = GetPointeeTypeId(varInst);
BuildAndAppendInst(SpvOpLoad, *varPteTypeId, ldResultId,
@ -80,7 +72,7 @@ void LocalAccessChainConvertPass::AppendConstantOperands(
uint32_t iidIdx = 0;
ptrInst->ForEachInId([&iidIdx, &in_opnds, this](const uint32_t *iid) {
if (iidIdx > 0) {
const ir::Instruction* cInst = def_use_mgr_->GetDef(*iid);
const ir::Instruction* cInst = get_def_use_mgr()->GetDef(*iid);
uint32_t val = cInst->GetSingleWordInOperand(kConstantValueInIdx);
in_opnds->push_back(
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {val}});
@ -102,10 +94,10 @@ uint32_t LocalAccessChainConvertPass::GenAccessChainLoadReplacement(
// Build and append Extract
const uint32_t extResultId = TakeNextId();
const uint32_t ptrPteTypeId = GetPointeeTypeId(ptrInst);
std::vector<ir::Operand> ext_in_opnds =
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ldResultId}}};
std::vector<ir::Operand> ext_in_opnds = {
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ldResultId}}};
AppendConstantOperands(ptrInst, &ext_in_opnds);
BuildAndAppendInst(SpvOpCompositeExtract, ptrPteTypeId, extResultId,
BuildAndAppendInst(SpvOpCompositeExtract, ptrPteTypeId, extResultId,
ext_in_opnds, newInsts);
return extResultId;
}
@ -143,7 +135,7 @@ bool LocalAccessChainConvertPass::IsConstantIndexAccessChain(
uint32_t nonConstCnt = 0;
acp->ForEachInId([&inIdx, &nonConstCnt, this](const uint32_t* tid) {
if (inIdx > 0) {
ir::Instruction* opInst = def_use_mgr_->GetDef(*tid);
ir::Instruction* opInst = get_def_use_mgr()->GetDef(*tid);
if (opInst->opcode() != SpvOpConstant) ++nonConstCnt;
}
++inIdx;
@ -154,7 +146,7 @@ bool LocalAccessChainConvertPass::IsConstantIndexAccessChain(
bool LocalAccessChainConvertPass::HasOnlySupportedRefs(uint32_t ptrId) {
if (supported_ref_ptrs_.find(ptrId) != supported_ref_ptrs_.end())
return true;
analysis::UseList* uses = def_use_mgr_->GetUses(ptrId);
analysis::UseList* uses = get_def_use_mgr()->GetUses(ptrId);
assert(uses != nullptr);
for (auto u : *uses) {
SpvOp op = u.inst->opcode();
@ -241,7 +233,7 @@ bool LocalAccessChainConvertPass::ConvertLocalAccessChains(ir::Function* func) {
std::vector<std::unique_ptr<ir::Instruction>> newInsts;
uint32_t valId = ii->GetSingleWordInOperand(kStoreValIdInIdx);
GenAccessChainStoreReplacement(ptrInst, valId, &newInsts);
def_use_mgr_->KillInst(&*ii);
get_def_use_mgr()->KillInst(&*ii);
DeleteIfUseless(ptrInst);
++ii;
ii = ii.InsertBefore(std::move(newInsts));
@ -258,8 +250,7 @@ bool LocalAccessChainConvertPass::ConvertLocalAccessChains(ir::Function* func) {
}
void LocalAccessChainConvertPass::Initialize(ir::Module* module) {
module_ = module;
InitializeProcessing(module);
// Initialize Target Variable Caches
seen_target_vars_.clear();
@ -268,18 +259,13 @@ void LocalAccessChainConvertPass::Initialize(ir::Module* module) {
// Initialize collections
supported_ref_ptrs_.clear();
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
// Initialize next unused Id.
InitNextId();
// Initialize extension whitelist
InitExtensions();
};
bool LocalAccessChainConvertPass::AllExtensionsSupported() const {
// If any extension not in whitelist, return false
for (auto& ei : module_->extensions()) {
for (auto& ei : get_module()->extensions()) {
const char* extName = reinterpret_cast<const char*>(
&ei.GetInOperand(0).words[0]);
if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
@ -291,14 +277,14 @@ bool LocalAccessChainConvertPass::AllExtensionsSupported() const {
Pass::Status LocalAccessChainConvertPass::ProcessImpl() {
// If non-32-bit integer type in module, terminate processing
// TODO(): Handle non-32-bit integer constants in access chains
for (const ir::Instruction& inst : module_->types_values())
for (const ir::Instruction& inst : get_module()->types_values())
if (inst.opcode() == SpvOpTypeInt &&
inst.GetSingleWordInOperand(kTypeIntWidthInIdx) != 32)
return Status::SuccessWithoutChange;
// Do not process if module contains OpGroupDecorate. Additional
// support required in KillNamesAndDecorates().
// TODO(greg-lunarg): Add support for OpGroupDecorate
for (auto& ai : module_->annotations())
for (auto& ai : get_module()->annotations())
if (ai.opcode() == SpvOpGroupDecorate)
return Status::SuccessWithoutChange;
// Do not process if any disallowed extensions are enabled
@ -310,7 +296,7 @@ Pass::Status LocalAccessChainConvertPass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return ConvertLocalAccessChains(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
FinalizeNextId();
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@ -44,7 +44,9 @@ class LocalAccessChainConvertPass : public MemPass {
private:
// Return true if all refs through |ptrId| are only loads or stores and
// cache ptrId in supported_ref_ptrs_.
// cache ptrId in supported_ref_ptrs_. TODO(dnovillo): This function is
// replicated in other passes and it's slightly different in every pass. Is it
// possible to make one common implementation?
bool HasOnlySupportedRefs(uint32_t ptrId);
// Search |func| and cache function scope variables of target type that are
@ -55,9 +57,6 @@ class LocalAccessChainConvertPass : public MemPass {
// Delete |inst| if it has no uses. Assumes |inst| has a non-zero resultId.
void DeleteIfUseless(ir::Instruction* inst);
// Return type id for |ptrInst|'s pointee
uint32_t GetPointeeTypeId(const ir::Instruction* ptrInst) const;
// Build instruction from |opcode|, |typeId|, |resultId|, and |in_opnds|.
// Append to |newInsts|.
void BuildAndAppendInst(SpvOp opcode, uint32_t typeId, uint32_t resultId,

View File

@ -30,7 +30,7 @@ const uint32_t kStoreValIdInIdx = 1;
bool LocalSingleBlockLoadStoreElimPass::HasOnlySupportedRefs(uint32_t ptrId) {
if (supported_ref_ptrs_.find(ptrId) != supported_ref_ptrs_.end())
return true;
analysis::UseList* uses = def_use_mgr_->GetUses(ptrId);
analysis::UseList* uses = get_def_use_mgr()->GetUses(ptrId);
assert(uses != nullptr);
for (auto u : *uses) {
SpvOp op = u.inst->opcode();
@ -68,7 +68,7 @@ bool LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElim(
if (pinned_vars_.find(varId) == pinned_vars_.end()) {
auto si = var2store_.find(varId);
if (si != var2store_.end()) {
def_use_mgr_->KillInst(si->second);
get_def_use_mgr()->KillInst(si->second);
}
}
var2store_[varId] = &*ii;
@ -138,8 +138,7 @@ bool LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElim(
}
void LocalSingleBlockLoadStoreElimPass::Initialize(ir::Module* module) {
module_ = module;
InitializeProcessing(module);
// Initialize Target Type Caches
seen_target_vars_.clear();
@ -148,19 +147,13 @@ void LocalSingleBlockLoadStoreElimPass::Initialize(ir::Module* module) {
// Clear collections
supported_ref_ptrs_.clear();
// TODO(greg-lunarg): Reuse def/use from previous passes
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
// Start new ids with next availablein module
InitNextId();
// Initialize extensions whitelist
InitExtensions();
};
bool LocalSingleBlockLoadStoreElimPass::AllExtensionsSupported() const {
// If any extension not in whitelist, return false
for (auto& ei : module_->extensions()) {
for (auto& ei : get_module()->extensions()) {
const char* extName = reinterpret_cast<const char*>(
&ei.GetInOperand(0).words[0]);
if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
@ -171,12 +164,12 @@ bool LocalSingleBlockLoadStoreElimPass::AllExtensionsSupported() const {
Pass::Status LocalSingleBlockLoadStoreElimPass::ProcessImpl() {
// Assumes logical addressing only
if (module_->HasCapability(SpvCapabilityAddresses))
if (get_module()->HasCapability(SpvCapabilityAddresses))
return Status::SuccessWithoutChange;
// Do not process if module contains OpGroupDecorate. Additional
// support required in KillNamesAndDecorates().
// TODO(greg-lunarg): Add support for OpGroupDecorate
for (auto& ai : module_->annotations())
for (auto& ai : get_module()->annotations())
if (ai.opcode() == SpvOpGroupDecorate)
return Status::SuccessWithoutChange;
// If any extensions in the module are not explicitly supported,
@ -189,7 +182,7 @@ Pass::Status LocalSingleBlockLoadStoreElimPass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return LocalSingleBlockLoadStoreElim(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
FinalizeNextId();
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@ -42,7 +42,10 @@ class LocalSingleBlockLoadStoreElimPass : public MemPass {
private:
// Return true if all uses of |varId| are only through supported reference
// operations ie. loads and store. Also cache in supported_ref_ptrs_;
// operations ie. loads and store. Also cache in supported_ref_ptrs_.
// TODO(dnovillo): This function is replicated in other passes and it's
// slightly different in every pass. Is it possible to make one common
// implementation?
bool HasOnlySupportedRefs(uint32_t varId);
// On all entry point functions, within each basic block, eliminate

View File

@ -20,9 +20,6 @@
#include "iterator.h"
#include "spirv/1.0/GLSL.std.450.h"
// Universal Limit of ResultID + 1
static const int kInvalidId = 0x400000;
namespace spvtools {
namespace opt {
@ -35,7 +32,7 @@ const uint32_t kStoreValIdInIdx = 1;
bool LocalSingleStoreElimPass::HasOnlySupportedRefs(uint32_t ptrId) {
if (supported_ref_ptrs_.find(ptrId) != supported_ref_ptrs_.end())
return true;
analysis::UseList* uses = def_use_mgr_->GetUses(ptrId);
analysis::UseList* uses = get_def_use_mgr()->GetUses(ptrId);
assert(uses != nullptr);
for (auto u : *uses) {
SpvOp op = u.inst->opcode();
@ -236,11 +233,11 @@ bool LocalSingleStoreElimPass::LocalSingleStoreElim(ir::Function* func) {
}
void LocalSingleStoreElimPass::Initialize(ir::Module* module) {
module_ = module;
InitializeProcessing(module);
// Initialize function and block maps
label2block_.clear();
for (auto& fn : *module_) {
for (auto& fn : *get_module()) {
for (auto& blk : fn) {
uint32_t bid = blk.id();
label2block_[bid] = &blk;
@ -254,19 +251,13 @@ void LocalSingleStoreElimPass::Initialize(ir::Module* module) {
// Initialize Supported Ref Pointer Cache
supported_ref_ptrs_.clear();
// TODO: Reuse def/use (and other state) from previous passes
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
// Initialize next unused Id
InitNextId();
// Initialize extension whitelist
InitExtensions();
};
bool LocalSingleStoreElimPass::AllExtensionsSupported() const {
// If any extension not in whitelist, return false
for (auto& ei : module_->extensions()) {
for (auto& ei : get_module()->extensions()) {
const char* extName = reinterpret_cast<const char*>(
&ei.GetInOperand(0).words[0]);
if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
@ -277,12 +268,12 @@ bool LocalSingleStoreElimPass::AllExtensionsSupported() const {
Pass::Status LocalSingleStoreElimPass::ProcessImpl() {
// Assumes logical addressing only
if (module_->HasCapability(SpvCapabilityAddresses))
if (get_module()->HasCapability(SpvCapabilityAddresses))
return Status::SuccessWithoutChange;
// Do not process if module contains OpGroupDecorate. Additional
// support required in KillNamesAndDecorates().
// TODO(greg-lunarg): Add support for OpGroupDecorate
for (auto& ai : module_->annotations())
for (auto& ai : get_module()->annotations())
if (ai.opcode() == SpvOpGroupDecorate)
return Status::SuccessWithoutChange;
// Do not process if any disallowed extensions are enabled
@ -294,16 +285,12 @@ Pass::Status LocalSingleStoreElimPass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return LocalSingleStoreElim(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
FinalizeNextId();
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
LocalSingleStoreElimPass::LocalSingleStoreElimPass()
: pseudo_entry_block_(std::unique_ptr<ir::Instruction>(
new ir::Instruction(SpvOpLabel, 0, 0, {}))),
pseudo_exit_block_(std::unique_ptr<ir::Instruction>(
new ir::Instruction(SpvOpLabel, 0, kInvalidId, {}))) {}
LocalSingleStoreElimPass::LocalSingleStoreElimPass() {}
Pass::Status LocalSingleStoreElimPass::Process(ir::Module* module) {
Initialize(module);

View File

@ -44,7 +44,9 @@ class LocalSingleStoreElimPass : public MemPass {
private:
// Return true if all refs through |ptrId| are only loads or stores and
// cache ptrId in supported_ref_ptrs_.
// cache ptrId in supported_ref_ptrs_. TODO(dnovillo): This function is
// replicated in other passes and it's slightly different in every pass. Is it
// possible to make one common implementation?
bool HasOnlySupportedRefs(uint32_t ptrId);
// Find all function scope variables in |func| that are stored to
@ -118,12 +120,6 @@ class LocalSingleStoreElimPass : public MemPass {
// variable directly or through non-ptr access chains.
std::unordered_set<uint32_t> supported_ref_ptrs_;
// Augmented CFG Entry Block
ir::BasicBlock pseudo_entry_block_;
// Augmented CFG Exit Block
ir::BasicBlock pseudo_exit_block_;
// CFG Predecessors
std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
predecessors_map_;

View File

@ -22,461 +22,13 @@
namespace spvtools {
namespace opt {
namespace {
const uint32_t kStoreValIdInIdx = 1;
const uint32_t kTypePointerTypeIdInIdx = 1;
const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
} // anonymous namespace
bool LocalMultiStoreElimPass::HasOnlySupportedRefs(uint32_t varId) {
if (supported_ref_vars_.find(varId) != supported_ref_vars_.end())
return true;
analysis::UseList* uses = def_use_mgr_->GetUses(varId);
if (uses == nullptr)
return true;
for (auto u : *uses) {
const SpvOp op = u.inst->opcode();
if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName &&
!IsNonTypeDecorate(op))
return false;
}
supported_ref_vars_.insert(varId);
return true;
}
void LocalMultiStoreElimPass::InitSSARewrite(ir::Function& func) {
// Init predecessors
label2preds_.clear();
for (auto& blk : func) {
uint32_t blkId = blk.id();
blk.ForEachSuccessorLabel([&blkId, this](uint32_t sbid) {
label2preds_[sbid].push_back(blkId);
});
}
// Collect target (and non-) variable sets. Remove variables with
// non-load/store refs from target variable set
for (auto& blk : func) {
for (auto& inst : blk) {
switch (inst.opcode()) {
case SpvOpStore:
case SpvOpLoad: {
uint32_t varId;
(void) GetPtr(&inst, &varId);
if (!IsTargetVar(varId))
break;
if (HasOnlySupportedRefs(varId))
break;
seen_non_target_vars_.insert(varId);
seen_target_vars_.erase(varId);
} break;
default:
break;
}
}
}
}
uint32_t LocalMultiStoreElimPass::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;
}
void LocalMultiStoreElimPass::ComputeStructuredSuccessors(ir::Function* func) {
block2structured_succs_.clear();
for (auto& blk : *func) {
// If no predecessors in function, make successor to pseudo entry
if (label2preds_[blk.id()].size() == 0)
block2structured_succs_[&pseudo_entry_block_].push_back(&blk);
// If header, make merge block first successor.
uint32_t cbid;
const uint32_t mbid = MergeBlockIdIfAny(blk, &cbid);
if (mbid != 0) {
block2structured_succs_[&blk].push_back(id2block_[mbid]);
if (cbid != 0)
block2structured_succs_[&blk].push_back(id2block_[cbid]);
}
// add true successors
blk.ForEachSuccessorLabel([&blk, this](uint32_t sbid) {
block2structured_succs_[&blk].push_back(id2block_[sbid]);
});
}
}
void LocalMultiStoreElimPass::ComputeStructuredOrder(
ir::Function* func, std::list<ir::BasicBlock*>* order) {
// Compute structured successors and do DFS
ComputeStructuredSuccessors(func);
auto ignore_block = [](cbb_ptr) {};
auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
auto get_structured_successors = [this](const ir::BasicBlock* block) {
return &(block2structured_succs_[block]); };
// TODO(greg-lunarg): Get rid of const_cast by making moving const
// out of the cfa.h prototypes and into the invoking code.
auto post_order = [&](cbb_ptr b) {
order->push_front(const_cast<ir::BasicBlock*>(b)); };
spvtools::CFA<ir::BasicBlock>::DepthFirstTraversal(
&pseudo_entry_block_, get_structured_successors, ignore_block,
post_order, ignore_edge);
}
void LocalMultiStoreElimPass::SSABlockInitSinglePred(ir::BasicBlock* block_ptr) {
// Copy map entry from single predecessor
const uint32_t label = block_ptr->id();
const uint32_t predLabel = label2preds_[label].front();
assert(visitedBlocks_.find(predLabel) != visitedBlocks_.end());
label2ssa_map_[label] = label2ssa_map_[predLabel];
}
bool LocalMultiStoreElimPass::IsLiveAfter(uint32_t var_id, uint32_t label) const {
// For now, return very conservative result: true. This will result in
// correct, but possibly usused, phi code to be generated. A subsequent
// DCE pass should eliminate this code.
// TODO(greg-lunarg): Return more accurate information
(void) var_id;
(void) label;
return true;
}
uint32_t LocalMultiStoreElimPass::Type2Undef(uint32_t type_id) {
const auto uitr = type2undefs_.find(type_id);
if (uitr != type2undefs_.end())
return uitr->second;
const uint32_t undefId = TakeNextId();
std::unique_ptr<ir::Instruction> undef_inst(
new ir::Instruction(SpvOpUndef, type_id, undefId, {}));
def_use_mgr_->AnalyzeInstDefUse(&*undef_inst);
module_->AddGlobalValue(std::move(undef_inst));
type2undefs_[type_id] = undefId;
return undefId;
}
uint32_t LocalMultiStoreElimPass::GetPointeeTypeId(
const ir::Instruction* ptrInst) const {
const uint32_t ptrTypeId = ptrInst->type_id();
const ir::Instruction* ptrTypeInst = def_use_mgr_->GetDef(ptrTypeId);
return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
}
void LocalMultiStoreElimPass::SSABlockInitLoopHeader(
std::list<ir::BasicBlock*>::iterator block_itr) {
const uint32_t label = (*block_itr)->id();
// Determine backedge label.
uint32_t backLabel = 0;
for (uint32_t predLabel : label2preds_[label])
if (visitedBlocks_.find(predLabel) == visitedBlocks_.end()) {
assert(backLabel == 0);
backLabel = predLabel;
break;
}
assert(backLabel != 0);
// Determine merge block.
auto mergeInst = (*block_itr)->end();
--mergeInst;
--mergeInst;
uint32_t mergeLabel = mergeInst->GetSingleWordInOperand(
kLoopMergeMergeBlockIdInIdx);
// Collect all live variables and a default value for each across all
// non-backedge predecesors. Must be ordered map because phis are
// generated based on order and test results will otherwise vary across
// platforms.
std::map<uint32_t, uint32_t> liveVars;
for (uint32_t predLabel : label2preds_[label]) {
for (auto var_val : label2ssa_map_[predLabel]) {
uint32_t varId = var_val.first;
liveVars[varId] = var_val.second;
}
}
// Add all stored variables in loop. Set their default value id to zero.
for (auto bi = block_itr; (*bi)->id() != mergeLabel; ++bi) {
ir::BasicBlock* bp = *bi;
for (auto ii = bp->begin(); ii != bp->end(); ++ii) {
if (ii->opcode() != SpvOpStore)
continue;
uint32_t varId;
(void) GetPtr(&*ii, &varId);
if (!IsTargetVar(varId))
continue;
liveVars[varId] = 0;
}
}
// Insert phi for all live variables that require them. All variables
// defined in loop require a phi. Otherwise all variables
// with differing predecessor values require a phi.
auto insertItr = (*block_itr)->begin();
for (auto var_val : liveVars) {
const uint32_t varId = var_val.first;
if (!IsLiveAfter(varId, label))
continue;
const uint32_t val0Id = var_val.second;
bool needsPhi = false;
if (val0Id != 0) {
for (uint32_t predLabel : label2preds_[label]) {
// Skip back edge predecessor.
if (predLabel == backLabel)
continue;
const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
// Missing (undef) values always cause difference with (defined) value
if (var_val_itr == label2ssa_map_[predLabel].end()) {
needsPhi = true;
break;
}
if (var_val_itr->second != val0Id) {
needsPhi = true;
break;
}
}
}
else {
needsPhi = true;
}
// If val is the same for all predecessors, enter it in map
if (!needsPhi) {
label2ssa_map_[label].insert(var_val);
continue;
}
// Val differs across predecessors. Add phi op to block and
// add its result id to the map. For back edge predecessor,
// use the variable id. We will patch this after visiting back
// edge predecessor. For predecessors that do not define a value,
// use undef.
std::vector<ir::Operand> phi_in_operands;
uint32_t typeId = GetPointeeTypeId(def_use_mgr_->GetDef(varId));
for (uint32_t predLabel : label2preds_[label]) {
uint32_t valId;
if (predLabel == backLabel) {
valId = varId;
}
else {
const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
if (var_val_itr == label2ssa_map_[predLabel].end())
valId = Type2Undef(typeId);
else
valId = var_val_itr->second;
}
phi_in_operands.push_back(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}});
phi_in_operands.push_back(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {predLabel}});
}
const uint32_t phiId = TakeNextId();
std::unique_ptr<ir::Instruction> newPhi(
new ir::Instruction(SpvOpPhi, typeId, phiId, phi_in_operands));
// The only phis requiring patching are the ones we create.
phis_to_patch_.insert(phiId);
// Only analyze the phi define now; analyze the phi uses after the
// phi backedge predecessor value is patched.
def_use_mgr_->AnalyzeInstDef(&*newPhi);
insertItr = insertItr.InsertBefore(std::move(newPhi));
++insertItr;
label2ssa_map_[label].insert({ varId, phiId });
}
}
void LocalMultiStoreElimPass::SSABlockInitMultiPred(ir::BasicBlock* block_ptr) {
const uint32_t label = block_ptr->id();
// Collect all live variables and a default value for each across all
// predecesors. Must be ordered map because phis are generated based on
// order and test results will otherwise vary across platforms.
std::map<uint32_t, uint32_t> liveVars;
for (uint32_t predLabel : label2preds_[label]) {
assert(visitedBlocks_.find(predLabel) != visitedBlocks_.end());
for (auto var_val : label2ssa_map_[predLabel]) {
const uint32_t varId = var_val.first;
liveVars[varId] = var_val.second;
}
}
// For each live variable, look for a difference in values across
// predecessors that would require a phi and insert one.
auto insertItr = block_ptr->begin();
for (auto var_val : liveVars) {
const uint32_t varId = var_val.first;
if (!IsLiveAfter(varId, label))
continue;
const uint32_t val0Id = var_val.second;
bool differs = false;
for (uint32_t predLabel : label2preds_[label]) {
const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
// Missing values cause a difference because we'll need to create an
// undef for that predecessor.
if (var_val_itr == label2ssa_map_[predLabel].end()) {
differs = true;
break;
}
if (var_val_itr->second != val0Id) {
differs = true;
break;
}
}
// If val is the same for all predecessors, enter it in map
if (!differs) {
label2ssa_map_[label].insert(var_val);
continue;
}
// Val differs across predecessors. Add phi op to block and
// add its result id to the map
std::vector<ir::Operand> phi_in_operands;
const uint32_t typeId = GetPointeeTypeId(def_use_mgr_->GetDef(varId));
for (uint32_t predLabel : label2preds_[label]) {
const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
// If variable not defined on this path, use undef
const uint32_t valId = (var_val_itr != label2ssa_map_[predLabel].end()) ?
var_val_itr->second : Type2Undef(typeId);
phi_in_operands.push_back(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}});
phi_in_operands.push_back(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {predLabel}});
}
const uint32_t phiId = TakeNextId();
std::unique_ptr<ir::Instruction> newPhi(
new ir::Instruction(SpvOpPhi, typeId, phiId, phi_in_operands));
def_use_mgr_->AnalyzeInstDefUse(&*newPhi);
insertItr = insertItr.InsertBefore(std::move(newPhi));
++insertItr;
label2ssa_map_[label].insert({varId, phiId});
}
}
bool LocalMultiStoreElimPass::IsLoopHeader(ir::BasicBlock* block_ptr) const {
auto iItr = block_ptr->end();
--iItr;
if (iItr == block_ptr->begin())
return false;
--iItr;
return iItr->opcode() == SpvOpLoopMerge;
}
void LocalMultiStoreElimPass::SSABlockInit(
std::list<ir::BasicBlock*>::iterator block_itr) {
const size_t numPreds = label2preds_[(*block_itr)->id()].size();
if (numPreds == 0)
return;
if (numPreds == 1)
SSABlockInitSinglePred(*block_itr);
else if (IsLoopHeader(*block_itr))
SSABlockInitLoopHeader(block_itr);
else
SSABlockInitMultiPred(*block_itr);
}
void LocalMultiStoreElimPass::PatchPhis(uint32_t header_id, uint32_t back_id) {
ir::BasicBlock* header = id2block_[header_id];
auto phiItr = header->begin();
for (; phiItr->opcode() == SpvOpPhi; ++phiItr) {
// Only patch phis that we created in a loop header.
// There might be other phis unrelated to our optimizations.
if (0 == phis_to_patch_.count(phiItr->result_id())) continue;
// Find phi operand index for back edge
uint32_t cnt = 0;
uint32_t idx = phiItr->NumInOperands();
phiItr->ForEachInId([&cnt,&back_id,&idx](uint32_t* iid) {
if (cnt % 2 == 1 && *iid == back_id) idx = cnt - 1;
++cnt;
});
assert(idx != phiItr->NumInOperands());
// Replace temporary phi operand with variable's value in backedge block
// map. Use undef if variable not in map.
const uint32_t varId = phiItr->GetSingleWordInOperand(idx);
const auto valItr = label2ssa_map_[back_id].find(varId);
uint32_t valId =
(valItr != label2ssa_map_[back_id].end())
? valItr->second
: Type2Undef(GetPointeeTypeId(def_use_mgr_->GetDef(varId)));
phiItr->SetInOperand(idx, {valId});
// Analyze uses now that they are complete
def_use_mgr_->AnalyzeInstUse(&*phiItr);
}
}
bool LocalMultiStoreElimPass::EliminateMultiStoreLocal(ir::Function* func) {
InitSSARewrite(*func);
// Process all blocks in structured order. This is just one way (the
// simplest?) to make sure all predecessors blocks are processed before
// a block itself.
std::list<ir::BasicBlock*> structuredOrder;
ComputeStructuredOrder(func, &structuredOrder);
bool modified = false;
for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
// Skip pseudo entry block
if (*bi == &pseudo_entry_block_)
continue;
// Initialize this block's label2ssa_map_ entry using predecessor maps.
// Then process all stores and loads of targeted variables.
SSABlockInit(bi);
ir::BasicBlock* bp = *bi;
const uint32_t label = bp->id();
for (auto ii = bp->begin(); ii != bp->end(); ++ii) {
switch (ii->opcode()) {
case SpvOpStore: {
uint32_t varId;
(void) GetPtr(&*ii, &varId);
if (!IsTargetVar(varId))
break;
// Register new stored value for the variable
label2ssa_map_[label][varId] =
ii->GetSingleWordInOperand(kStoreValIdInIdx);
} break;
case SpvOpLoad: {
uint32_t varId;
(void) GetPtr(&*ii, &varId);
if (!IsTargetVar(varId))
break;
uint32_t replId = 0;
const auto ssaItr = label2ssa_map_.find(label);
if (ssaItr != label2ssa_map_.end()) {
const auto valItr = ssaItr->second.find(varId);
if (valItr != ssaItr->second.end())
replId = valItr->second;
}
// If variable is not defined, use undef
if (replId == 0) {
replId = Type2Undef(GetPointeeTypeId(def_use_mgr_->GetDef(varId)));
}
// Replace load's id with the last stored value id for variable
// and delete load. Kill any names or decorates using id before
// replacing to prevent incorrect replacement in those instructions.
const uint32_t loadId = ii->result_id();
KillNamesAndDecorates(loadId);
(void)def_use_mgr_->ReplaceAllUsesWith(loadId, replId);
def_use_mgr_->KillInst(&*ii);
modified = true;
} break;
default: {
} break;
}
}
visitedBlocks_.insert(label);
// Look for successor backedge and patch phis in loop header
// if found.
uint32_t header = 0;
bp->ForEachSuccessorLabel([&header,this](uint32_t succ) {
if (visitedBlocks_.find(succ) == visitedBlocks_.end()) return;
assert(header == 0);
header = succ;
});
if (header != 0)
PatchPhis(header, label);
}
// Add Phi instructions to the function.
if (InsertPhiInstructions(func) == Status::SuccessWithoutChange)
return false;
// Remove all target variable stores.
bool modified = false;
for (auto bi = func->begin(); bi != func->end(); ++bi) {
for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
if (ii->opcode() != SpvOpStore)
@ -490,36 +42,12 @@ bool LocalMultiStoreElimPass::EliminateMultiStoreLocal(ir::Function* func) {
modified = true;
}
}
return modified;
}
void LocalMultiStoreElimPass::Initialize(ir::Module* module) {
module_ = module;
// TODO(greg-lunarg): Reuse def/use from previous passes
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
// Initialize function and block maps
id2block_.clear();
block2structured_succs_.clear();
for (auto& fn : *module_)
for (auto& blk : fn)
id2block_[blk.id()] = &blk;
// Clear collections
seen_target_vars_.clear();
seen_non_target_vars_.clear();
visitedBlocks_.clear();
type2undefs_.clear();
supported_ref_vars_.clear();
block2structured_succs_.clear();
label2preds_.clear();
label2ssa_map_.clear();
phis_to_patch_.clear();
// Start new ids with next availablein module
InitNextId();
InitializeProcessing(module);
// Initialize extension whitelist
InitExtensions();
@ -527,7 +55,7 @@ void LocalMultiStoreElimPass::Initialize(ir::Module* module) {
bool LocalMultiStoreElimPass::AllExtensionsSupported() const {
// If any extension not in whitelist, return false
for (auto& ei : module_->extensions()) {
for (auto& ei : get_module()->extensions()) {
const char* extName = reinterpret_cast<const char*>(
&ei.GetInOperand(0).words[0]);
if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
@ -539,16 +67,16 @@ bool LocalMultiStoreElimPass::AllExtensionsSupported() const {
Pass::Status LocalMultiStoreElimPass::ProcessImpl() {
// Assumes all control flow structured.
// TODO(greg-lunarg): Do SSA rewrite for non-structured control flow
if (!module_->HasCapability(SpvCapabilityShader))
if (!get_module()->HasCapability(SpvCapabilityShader))
return Status::SuccessWithoutChange;
// Assumes logical addressing only
// TODO(greg-lunarg): Add support for physical addressing
if (module_->HasCapability(SpvCapabilityAddresses))
if (get_module()->HasCapability(SpvCapabilityAddresses))
return Status::SuccessWithoutChange;
// Do not process if module contains OpGroupDecorate. Additional
// support required in KillNamesAndDecorates().
// TODO(greg-lunarg): Add support for OpGroupDecorate
for (auto& ai : module_->annotations())
for (auto& ai : get_module()->annotations())
if (ai.opcode() == SpvOpGroupDecorate)
return Status::SuccessWithoutChange;
// Do not process if any disallowed extensions are enabled
@ -560,14 +88,12 @@ Pass::Status LocalMultiStoreElimPass::ProcessImpl() {
ProcessFunction pfn = [this](ir::Function* fp) {
return EliminateMultiStoreLocal(fp);
};
bool modified = ProcessEntryPointCallTree(pfn, module_);
bool modified = ProcessEntryPointCallTree(pfn, get_module());
FinalizeNextId();
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
LocalMultiStoreElimPass::LocalMultiStoreElimPass()
: pseudo_entry_block_(std::unique_ptr<ir::Instruction>(
new ir::Instruction(SpvOpLabel, 0, 0, {}))) {}
LocalMultiStoreElimPass::LocalMultiStoreElimPass() {}
Pass::Status LocalMultiStoreElimPass::Process(ir::Module* module) {
Initialize(module);

View File

@ -46,84 +46,6 @@ class LocalMultiStoreElimPass : public MemPass {
Status Process(ir::Module*) override;
private:
// Return type id for |ptrInst|'s pointee
uint32_t GetPointeeTypeId(const ir::Instruction* ptrInst) const;
// Return true if all uses of |varId| are only through supported reference
// operations ie. loads and store. Also cache in supported_ref_vars_;
bool HasOnlySupportedRefs(uint32_t varId);
// Initialize data structures used by EliminateLocalMultiStore for
// function |func|, specifically block predecessors and target variables.
void InitSSARewrite(ir::Function& func);
// Returns the id of the merge block declared by a merge instruction in
// this block, if any. If none, returns zero.
uint32_t MergeBlockIdIfAny(const ir::BasicBlock& blk, uint32_t* cbid);
// Compute structured successors for function |func|.
// A block's structured successors are the blocks it branches to
// together with its declared merge block if it has one.
// When order matters, the merge block always appears first.
// This assures correct depth first search in the presence of early
// returns and kills. If the successor vector contain duplicates
// if the merge block, they are safely ignored by DFS.
void ComputeStructuredSuccessors(ir::Function* func);
// Compute structured block order for |func| into |structuredOrder|. This
// order has the property that dominators come before all blocks they
// dominate and merge blocks come after all blocks that are in the control
// constructs of their header.
void ComputeStructuredOrder(ir::Function* func,
std::list<ir::BasicBlock*>* order);
// Return true if loop header block
bool IsLoopHeader(ir::BasicBlock* block_ptr) const;
// Initialize label2ssa_map_ entry for block |block_ptr| with single
// predecessor.
void SSABlockInitSinglePred(ir::BasicBlock* block_ptr);
// Return true if variable is loaded in block with |label| or in
// any succeeding block in structured order.
bool IsLiveAfter(uint32_t var_id, uint32_t label) const;
// Initialize label2ssa_map_ entry for loop header block pointed to
// |block_itr| by merging entries from all predecessors. If any value
// ids differ for any variable across predecessors, create a phi function
// in the block and use that value id for the variable in the new map.
// Assumes all predecessors have been visited by EliminateLocalMultiStore
// except the back edge. Use a dummy value in the phi for the back edge
// until the back edge block is visited and patch the phi value then.
void SSABlockInitLoopHeader(std::list<ir::BasicBlock*>::iterator block_itr);
// Initialize label2ssa_map_ entry for multiple predecessor block
// |block_ptr| by merging label2ssa_map_ entries for all predecessors.
// If any value ids differ for any variable across predecessors, create
// a phi function in the block and use that value id for the variable in
// the new map. Assumes all predecessors have been visited by
// EliminateLocalMultiStore.
void SSABlockInitMultiPred(ir::BasicBlock* block_ptr);
// Initialize the label2ssa_map entry for a block pointed to by |block_itr|.
// Insert phi instructions into block when necessary. All predecessor
// blocks must have been visited by EliminateLocalMultiStore except for
// backedges.
void SSABlockInit(std::list<ir::BasicBlock*>::iterator block_itr);
// Return undef in function for type. Create and insert an undef after the
// first non-variable in the function if it doesn't already exist. Add
// undef to function undef map.
uint32_t Type2Undef(uint32_t type_id);
// Patch phis in loop header block |header_id| now that the map is complete
// for the backedge predecessor |back_id|. Specifically, for each phi, find
// the value corresponding to the backedge predecessor. That was temporarily
// set with the variable id that this phi corresponds to. Change this phi
// operand to the the value which corresponds to that variable in the
// predecessor map.
void PatchPhis(uint32_t header_id, uint32_t back_id);
// Initialize extensions whitelist
void InitExtensions();
@ -140,40 +62,6 @@ class LocalMultiStoreElimPass : public MemPass {
void Initialize(ir::Module* module);
Pass::Status ProcessImpl();
// Map from block's label id to block.
std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
// Set of label ids of visited blocks
std::unordered_set<uint32_t> visitedBlocks_;
// Map from type to undef
std::unordered_map<uint32_t, uint32_t> type2undefs_;
// Variables that are only referenced by supported operations for this
// pass ie. loads and stores.
std::unordered_set<uint32_t> supported_ref_vars_;
// Map from block to its structured successor blocks. See
// ComputeStructuredSuccessors() for definition.
std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
block2structured_succs_;
// Map from block's label id to its predecessor blocks ids
std::unordered_map<uint32_t, std::vector<uint32_t>> label2preds_;
// Map from block's label id to a map of a variable to its value at the
// end of the block.
std::unordered_map<uint32_t, std::unordered_map<uint32_t, uint32_t>>
label2ssa_map_;
// The Ids of OpPhi instructions that are in a loop header and which require
// patching of the value for the loop back-edge.
std::unordered_set<uint32_t> phis_to_patch_;
// Extra block whose successors are all blocks with no predecessors
// in function.
ir::BasicBlock pseudo_entry_block_;
// Extensions supported by this pass.
std::unordered_set<std::string> extensions_whitelist_;
};

View File

@ -16,6 +16,7 @@
#include "mem_pass.h"
#include "cfa.h"
#include "iterator.h"
namespace spvtools {
@ -23,47 +24,44 @@ 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 kLoadPtrIdInIdx = 0;
const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
const uint32_t kStorePtrIdInIdx = 0;
const uint32_t kStoreValIdInIdx = 1;
const uint32_t kTypePointerStorageClassInIdx = 0;
const uint32_t kTypePointerTypeIdInIdx = 1;
} // namespace anonymous
} // namespace
bool MemPass::IsBaseTargetType(
const ir::Instruction* typeInst) const {
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;
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;
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;
get_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);
typeInst->ForEachInId([&nonMathComp, this](const uint32_t* tid) {
ir::Instruction* compTypeInst = get_def_use_mgr()->GetDef(*tid);
if (!IsBaseTargetType(compTypeInst)) ++nonMathComp;
});
return nonMathComp == 0;
@ -75,46 +73,41 @@ bool MemPass::IsNonPtrAccessChain(const SpvOp opcode) const {
bool MemPass::IsPtr(uint32_t ptrId) {
uint32_t varId = ptrId;
ir::Instruction* ptrInst = def_use_mgr_->GetDef(varId);
ir::Instruction* ptrInst = get_def_use_mgr()->GetDef(varId);
while (ptrInst->opcode() == SpvOpCopyObject) {
varId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
ptrInst = def_use_mgr_->GetDef(varId);
ptrInst = get_def_use_mgr()->GetDef(varId);
}
const SpvOp op = ptrInst->opcode();
if (op == SpvOpVariable || IsNonPtrAccessChain(op))
return true;
if (op != SpvOpFunctionParameter)
return false;
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);
const ir::Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId);
return varTypeInst->opcode() == SpvOpTypePointer;
}
ir::Instruction* MemPass::GetPtr(
uint32_t ptrId, uint32_t* varId) {
ir::Instruction* MemPass::GetPtr(uint32_t ptrId, uint32_t* varId) {
*varId = ptrId;
ir::Instruction* ptrInst = def_use_mgr_->GetDef(*varId);
ir::Instruction* ptrInst = get_def_use_mgr()->GetDef(*varId);
while (ptrInst->opcode() == SpvOpCopyObject) {
*varId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
ptrInst = def_use_mgr_->GetDef(*varId);
ptrInst = get_def_use_mgr()->GetDef(*varId);
}
ir::Instruction* varInst = ptrInst;
while (varInst->opcode() != SpvOpVariable &&
varInst->opcode() != SpvOpFunctionParameter) {
while (varInst->opcode() != SpvOpVariable &&
varInst->opcode() != SpvOpFunctionParameter) {
if (IsNonPtrAccessChain(varInst->opcode())) {
*varId = varInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
}
else {
} else {
assert(varInst->opcode() == SpvOpCopyObject);
*varId = varInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
}
varInst = def_use_mgr_->GetDef(*varId);
varInst = get_def_use_mgr()->GetDef(*varId);
}
return ptrInst;
}
ir::Instruction* MemPass::GetPtr(
ir::Instruction* ip, uint32_t* varId) {
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(
@ -122,107 +115,70 @@ ir::Instruction* MemPass::GetPtr(
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_->debugs2())
for (auto& di : get_module()->debugs2())
if (di.opcode() == SpvOpName)
named_or_decorated_ids_.insert(di.GetSingleWordInOperand(0));
for (auto& ai : module_->annotations())
for (auto& ai : get_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;
analysis::UseList* uses = get_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;
if (op != SpvOpName && !IsNonTypeDecorate(op)) return false;
}
return true;
}
void MemPass::KillNamesAndDecorates(uint32_t id) {
// TODO(greg-lunarg): Remove id from any OpGroupDecorate and
// 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;
if (named_or_decorated_ids_.find(id) == named_or_decorated_ids_.end()) return;
analysis::UseList* uses = get_def_use_mgr()->GetUses(id);
if (uses == nullptr) return;
std::list<ir::Instruction*> killList;
for (auto u : *uses) {
const SpvOp op = u.inst->opcode();
if (op == SpvOpName || IsNonTypeDecorate(op))
killList.push_back(u.inst);
if (op == SpvOpName || IsNonTypeDecorate(op)) killList.push_back(u.inst);
}
for (auto kip : killList)
def_use_mgr_->KillInst(kip);
for (auto kip : killList) get_def_use_mgr()->KillInst(kip);
}
void MemPass::KillNamesAndDecorates(ir::Instruction* inst) {
const uint32_t rId = inst->result_id();
if (rId == 0)
return;
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;
analysis::UseList* uses = get_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)
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);
const ir::Instruction* varInst = get_def_use_mgr()->GetDef(varId);
// assume live if not a variable eg. function parameter
if (varInst->opcode() != SpvOpVariable)
return true;
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);
const ir::Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId);
if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) !=
SpvStorageClassFunction)
return true;
@ -233,13 +189,12 @@ bool MemPass::IsLiveVar(uint32_t varId) const {
bool MemPass::IsLiveStore(ir::Instruction* storeInst) {
// get store's variable
uint32_t varId;
(void) GetPtr(storeInst, &varId);
(void)GetPtr(storeInst, &varId);
return IsLiveVar(varId);
}
void MemPass::AddStores(
uint32_t ptr_id, std::queue<ir::Instruction*>* insts) {
analysis::UseList* uses = def_use_mgr_->GetUses(ptr_id);
void MemPass::AddStores(uint32_t ptr_id, std::queue<ir::Instruction*>* insts) {
analysis::UseList* uses = get_def_use_mgr()->GetUses(ptr_id);
if (uses != nullptr) {
for (auto u : *uses) {
if (IsNonPtrAccessChain(u.inst->opcode()))
@ -262,37 +217,448 @@ void MemPass::DCEInst(ir::Instruction* inst) {
}
// Remember operands
std::vector<uint32_t> ids;
di->ForEachInId([&ids](uint32_t* iid) {
ids.push_back(*iid);
});
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);
if (di->opcode() == SpvOpLoad) (void)GetPtr(di, &varId);
KillNamesAndDecorates(di);
def_use_mgr_->KillInst(di);
get_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));
deadInsts.push(get_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);
if (varId != 0 && !IsLiveVar(varId)) AddStores(varId, &deadInsts);
deadInsts.pop();
}
}
void MemPass::ReplaceAndDeleteLoad(
ir::Instruction* loadInst, uint32_t replId) {
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);
(void)get_def_use_mgr()->ReplaceAllUsesWith(loadId, replId);
DCEInst(loadInst);
}
MemPass::MemPass() : module_(nullptr), def_use_mgr_(nullptr), next_id_(0) {}
MemPass::MemPass() {}
bool MemPass::HasOnlySupportedRefs(uint32_t varId) {
if (supported_ref_vars_.find(varId) != supported_ref_vars_.end()) return true;
analysis::UseList* uses = get_def_use_mgr()->GetUses(varId);
if (uses == nullptr) return true;
for (auto u : *uses) {
const SpvOp op = u.inst->opcode();
if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName &&
!IsNonTypeDecorate(op))
return false;
}
supported_ref_vars_.insert(varId);
return true;
}
void MemPass::InitSSARewrite(ir::Function* func) {
// Initialize function and block maps.
id2block_.clear();
block2structured_succs_.clear();
for (auto& fn : *get_module())
for (auto& blk : fn) id2block_[blk.id()] = &blk;
// Clear collections.
seen_target_vars_.clear();
seen_non_target_vars_.clear();
visitedBlocks_.clear();
type2undefs_.clear();
supported_ref_vars_.clear();
block2structured_succs_.clear();
label2preds_.clear();
label2ssa_map_.clear();
phis_to_patch_.clear();
// Init predecessors
label2preds_.clear();
for (auto& blk : *func) {
uint32_t blkId = blk.id();
blk.ForEachSuccessorLabel(
[&blkId, this](uint32_t sbid) { label2preds_[sbid].push_back(blkId); });
}
// Collect target (and non-) variable sets. Remove variables with
// non-load/store refs from target variable set
for (auto& blk : *func) {
for (auto& inst : blk) {
switch (inst.opcode()) {
case SpvOpStore:
case SpvOpLoad: {
uint32_t varId;
(void)GetPtr(&inst, &varId);
if (!IsTargetVar(varId)) break;
if (HasOnlySupportedRefs(varId)) break;
seen_non_target_vars_.insert(varId);
seen_target_vars_.erase(varId);
} break;
default:
break;
}
}
}
}
bool MemPass::IsLiveAfter(uint32_t var_id, uint32_t label) const {
// For now, return very conservative result: true. This will result in
// correct, but possibly usused, phi code to be generated. A subsequent
// DCE pass should eliminate this code.
// TODO(greg-lunarg): Return more accurate information
(void)var_id;
(void)label;
return true;
}
void MemPass::SSABlockInitSinglePred(ir::BasicBlock* block_ptr) {
// Copy map entry from single predecessor
const uint32_t label = block_ptr->id();
const uint32_t predLabel = label2preds_[label].front();
assert(visitedBlocks_.find(predLabel) != visitedBlocks_.end());
label2ssa_map_[label] = label2ssa_map_[predLabel];
}
uint32_t MemPass::Type2Undef(uint32_t type_id) {
const auto uitr = type2undefs_.find(type_id);
if (uitr != type2undefs_.end()) return uitr->second;
const uint32_t undefId = TakeNextId();
std::unique_ptr<ir::Instruction> undef_inst(
new ir::Instruction(SpvOpUndef, type_id, undefId, {}));
get_def_use_mgr()->AnalyzeInstDefUse(&*undef_inst);
get_module()->AddGlobalValue(std::move(undef_inst));
type2undefs_[type_id] = undefId;
return undefId;
}
void MemPass::SSABlockInitLoopHeader(
std::list<ir::BasicBlock*>::iterator block_itr) {
const uint32_t label = (*block_itr)->id();
// Determine the back-edge label.
uint32_t backLabel = 0;
for (uint32_t predLabel : label2preds_[label])
if (visitedBlocks_.find(predLabel) == visitedBlocks_.end()) {
assert(backLabel == 0);
backLabel = predLabel;
break;
}
assert(backLabel != 0);
// Determine merge block.
auto mergeInst = (*block_itr)->end();
--mergeInst;
--mergeInst;
uint32_t mergeLabel =
mergeInst->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx);
// Collect all live variables and a default value for each across all
// non-backedge predecesors. Must be ordered map because phis are
// generated based on order and test results will otherwise vary across
// platforms.
std::map<uint32_t, uint32_t> liveVars;
for (uint32_t predLabel : label2preds_[label]) {
for (auto var_val : label2ssa_map_[predLabel]) {
uint32_t varId = var_val.first;
liveVars[varId] = var_val.second;
}
}
// Add all stored variables in loop. Set their default value id to zero.
for (auto bi = block_itr; (*bi)->id() != mergeLabel; ++bi) {
ir::BasicBlock* bp = *bi;
for (auto ii = bp->begin(); ii != bp->end(); ++ii) {
if (ii->opcode() != SpvOpStore) {
continue;
}
uint32_t varId;
(void)GetPtr(&*ii, &varId);
if (!IsTargetVar(varId)) {
continue;
}
liveVars[varId] = 0;
}
}
// Insert phi for all live variables that require them. All variables
// defined in loop require a phi. Otherwise all variables
// with differing predecessor values require a phi.
auto insertItr = (*block_itr)->begin();
for (auto var_val : liveVars) {
const uint32_t varId = var_val.first;
if (!IsLiveAfter(varId, label)) {
continue;
}
const uint32_t val0Id = var_val.second;
bool needsPhi = false;
if (val0Id != 0) {
for (uint32_t predLabel : label2preds_[label]) {
// Skip back edge predecessor.
if (predLabel == backLabel) continue;
const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
// Missing (undef) values always cause difference with (defined) value
if (var_val_itr == label2ssa_map_[predLabel].end()) {
needsPhi = true;
break;
}
if (var_val_itr->second != val0Id) {
needsPhi = true;
break;
}
}
} else {
needsPhi = true;
}
// If val is the same for all predecessors, enter it in map
if (!needsPhi) {
label2ssa_map_[label].insert(var_val);
continue;
}
// Val differs across predecessors. Add phi op to block and
// add its result id to the map. For back edge predecessor,
// use the variable id. We will patch this after visiting back
// edge predecessor. For predecessors that do not define a value,
// use undef.
std::vector<ir::Operand> phi_in_operands;
uint32_t typeId = GetPointeeTypeId(get_def_use_mgr()->GetDef(varId));
for (uint32_t predLabel : label2preds_[label]) {
uint32_t valId;
if (predLabel == backLabel) {
valId = varId;
} else {
const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
if (var_val_itr == label2ssa_map_[predLabel].end())
valId = Type2Undef(typeId);
else
valId = var_val_itr->second;
}
phi_in_operands.push_back(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}});
phi_in_operands.push_back(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {predLabel}});
}
const uint32_t phiId = TakeNextId();
std::unique_ptr<ir::Instruction> newPhi(
new ir::Instruction(SpvOpPhi, typeId, phiId, phi_in_operands));
// The only phis requiring patching are the ones we create.
phis_to_patch_.insert(phiId);
// Only analyze the phi define now; analyze the phi uses after the
// phi backedge predecessor value is patched.
get_def_use_mgr()->AnalyzeInstDef(&*newPhi);
insertItr = insertItr.InsertBefore(std::move(newPhi));
++insertItr;
label2ssa_map_[label].insert({varId, phiId});
}
}
void MemPass::SSABlockInitMultiPred(ir::BasicBlock* block_ptr) {
const uint32_t label = block_ptr->id();
// Collect all live variables and a default value for each across all
// predecesors. Must be ordered map because phis are generated based on
// order and test results will otherwise vary across platforms.
std::map<uint32_t, uint32_t> liveVars;
for (uint32_t predLabel : label2preds_[label]) {
assert(visitedBlocks_.find(predLabel) != visitedBlocks_.end());
for (auto var_val : label2ssa_map_[predLabel]) {
const uint32_t varId = var_val.first;
liveVars[varId] = var_val.second;
}
}
// For each live variable, look for a difference in values across
// predecessors that would require a phi and insert one.
auto insertItr = block_ptr->begin();
for (auto var_val : liveVars) {
const uint32_t varId = var_val.first;
if (!IsLiveAfter(varId, label)) continue;
const uint32_t val0Id = var_val.second;
bool differs = false;
for (uint32_t predLabel : label2preds_[label]) {
const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
// Missing values cause a difference because we'll need to create an
// undef for that predecessor.
if (var_val_itr == label2ssa_map_[predLabel].end()) {
differs = true;
break;
}
if (var_val_itr->second != val0Id) {
differs = true;
break;
}
}
// If val is the same for all predecessors, enter it in map
if (!differs) {
label2ssa_map_[label].insert(var_val);
continue;
}
// Val differs across predecessors. Add phi op to block and add its result
// id to the map.
std::vector<ir::Operand> phi_in_operands;
const uint32_t typeId = GetPointeeTypeId(get_def_use_mgr()->GetDef(varId));
for (uint32_t predLabel : label2preds_[label]) {
const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
// If variable not defined on this path, use undef
const uint32_t valId = (var_val_itr != label2ssa_map_[predLabel].end())
? var_val_itr->second
: Type2Undef(typeId);
phi_in_operands.push_back(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}});
phi_in_operands.push_back(
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {predLabel}});
}
const uint32_t phiId = TakeNextId();
std::unique_ptr<ir::Instruction> newPhi(
new ir::Instruction(SpvOpPhi, typeId, phiId, phi_in_operands));
get_def_use_mgr()->AnalyzeInstDefUse(&*newPhi);
insertItr = insertItr.InsertBefore(std::move(newPhi));
++insertItr;
label2ssa_map_[label].insert({varId, phiId});
}
}
void MemPass::SSABlockInit(std::list<ir::BasicBlock*>::iterator block_itr) {
const size_t numPreds = label2preds_[(*block_itr)->id()].size();
if (numPreds == 0) return;
if (numPreds == 1)
SSABlockInitSinglePred(*block_itr);
else if (IsLoopHeader(*block_itr))
SSABlockInitLoopHeader(block_itr);
else
SSABlockInitMultiPred(*block_itr);
}
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 = get_def_use_mgr()->GetDef(varId);
if (varInst->opcode() != SpvOpVariable) return false;
;
const uint32_t varTypeId = varInst->type_id();
const ir::Instruction* varTypeInst = get_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 = get_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::PatchPhis(uint32_t header_id, uint32_t back_id) {
ir::BasicBlock* header = id2block_[header_id];
auto phiItr = header->begin();
for (; phiItr->opcode() == SpvOpPhi; ++phiItr) {
// Only patch phis that we created in a loop header.
// There might be other phis unrelated to our optimizations.
if (0 == phis_to_patch_.count(phiItr->result_id())) continue;
// Find phi operand index for back edge
uint32_t cnt = 0;
uint32_t idx = phiItr->NumInOperands();
phiItr->ForEachInId([&cnt, &back_id, &idx](uint32_t* iid) {
if (cnt % 2 == 1 && *iid == back_id) idx = cnt - 1;
++cnt;
});
assert(idx != phiItr->NumInOperands());
// Replace temporary phi operand with variable's value in backedge block
// map. Use undef if variable not in map.
const uint32_t varId = phiItr->GetSingleWordInOperand(idx);
const auto valItr = label2ssa_map_[back_id].find(varId);
uint32_t valId =
(valItr != label2ssa_map_[back_id].end())
? valItr->second
: Type2Undef(GetPointeeTypeId(get_def_use_mgr()->GetDef(varId)));
phiItr->SetInOperand(idx, {valId});
// Analyze uses now that they are complete
get_def_use_mgr()->AnalyzeInstUse(&*phiItr);
}
}
Pass::Status MemPass::InsertPhiInstructions(ir::Function* func) {
// TODO(dnovillo) the current Phi placement mechanism assumes structured
// control-flow. This should be generalized
// (https://github.com/KhronosGroup/SPIRV-Tools/issues/893).
if (!get_module()->HasCapability(SpvCapabilityShader))
return Status::SuccessWithoutChange;
// Collect all named and decorated ids.
FindNamedOrDecoratedIds();
// Initialize the data structures used to insert Phi instructions.
InitSSARewrite(func);
// Process all blocks in structured order. This is just one way (the
// simplest?) to make sure all predecessors blocks are processed before
// a block itself.
std::list<ir::BasicBlock*> structuredOrder;
ComputeStructuredOrder(func, &structuredOrder);
for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
// Skip pseudo entry block
if (*bi == &pseudo_entry_block_) continue;
// Initialize this block's label2ssa_map_ entry using predecessor maps.
// Then process all stores and loads of targeted variables.
SSABlockInit(bi);
ir::BasicBlock* bp = *bi;
const uint32_t label = bp->id();
for (auto ii = bp->begin(); ii != bp->end(); ++ii) {
switch (ii->opcode()) {
case SpvOpStore: {
uint32_t varId;
(void)GetPtr(&*ii, &varId);
if (!IsTargetVar(varId)) break;
// Register new stored value for the variable
label2ssa_map_[label][varId] =
ii->GetSingleWordInOperand(kStoreValIdInIdx);
} break;
case SpvOpLoad: {
uint32_t varId;
(void)GetPtr(&*ii, &varId);
if (!IsTargetVar(varId)) break;
uint32_t replId = 0;
const auto ssaItr = label2ssa_map_.find(label);
if (ssaItr != label2ssa_map_.end()) {
const auto valItr = ssaItr->second.find(varId);
if (valItr != ssaItr->second.end()) replId = valItr->second;
}
// If variable is not defined, use undef
if (replId == 0) {
replId = Type2Undef(GetPointeeTypeId(get_def_use_mgr()->GetDef(varId)));
}
// Replace load's id with the last stored value id for variable
// and delete load. Kill any names or decorates using id before
// replacing to prevent incorrect replacement in those instructions.
const uint32_t loadId = ii->result_id();
KillNamesAndDecorates(loadId);
(void)get_def_use_mgr()->ReplaceAllUsesWith(loadId, replId);
get_def_use_mgr()->KillInst(&*ii);
} break;
default: { } break; }
}
visitedBlocks_.insert(label);
// Look for successor backedge and patch phis in loop header
// if found.
uint32_t header = 0;
bp->ForEachSuccessorLabel([&header, this](uint32_t succ) {
if (visitedBlocks_.find(succ) == visitedBlocks_.end()) return;
assert(header == 0);
header = succ;
});
if (header != 0) PatchPhis(header, label);
}
return Status::SuccessWithChange;
}
} // namespace opt
} // namespace spvtools

View File

@ -17,8 +17,8 @@
#ifndef LIBSPIRV_OPT_OPT_PASS_H_
#define LIBSPIRV_OPT_OPT_PASS_H_
#include <algorithm>
#include <list>
#include <map>
#include <queue>
#include <unordered_map>
@ -65,14 +65,6 @@ class MemPass : public Pass {
// Also return the base variable's id in |varId|.
ir::Instruction* GetPtr(ir::Instruction* ip, uint32_t* varId);
// Return true if |varId| is a previously identified target variable.
// Return false if |varId| is a previously identified non-target variable.
// See FindTargetVars() for definition of target variable. If variable is
// not cached, return true if variable is a function scope variable of
// target type, false otherwise. Updates caches of target and non-target
// variables.
bool IsTargetVar(uint32_t varId);
// Return true if all uses of |id| are only name or decorate ops.
bool HasOnlyNamesAndDecorates(uint32_t id) const;
@ -113,26 +105,31 @@ class MemPass : public Pass {
return (op == SpvOpDecorate || op == SpvOpDecorateId);
}
// Initialize next available id from |module|.
void InitNextId() {
next_id_ = module_->IdBound();
}
// Return true if |varId| is a previously identified target variable.
// Return false if |varId| is a previously identified non-target variable.
//
// Non-target variables are variable of function scope of a target type that
// are accessed with constant-index access chains. not accessed with
// non-constant-index access chains. Also cache non-target variables.
//
// If variable is not cached, return true if variable is a function scope
// variable of target type, false otherwise. Updates caches of target and
// non-target variables.
bool IsTargetVar(uint32_t varId);
// Save next available id into |module|.
void FinalizeNextId() {
module_->SetIdBound(next_id_);
}
// Initialize data structures used by EliminateLocalMultiStore for
// function |func|, specifically block predecessors and target variables.
void InitSSARewrite(ir::Function* func);
// Return next available id and calculate next.
inline uint32_t TakeNextId() {
return next_id_++;
}
// Return undef in function for type. Create and insert an undef after the
// first non-variable in the function if it doesn't already exist. Add
// undef to function undef map.
uint32_t Type2Undef(uint32_t type_id);
// Module this pass is processing
ir::Module* module_;
// Def-Uses for the module we are processing
std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
// Insert Phi instructions in the CFG of |func|. This removes extra
// load/store operations to local storage while preserving the SSA form of the
// code.
Pass::Status InsertPhiInstructions(ir::Function* func);
// Cache of verified target vars
std::unordered_set<uint32_t> seen_target_vars_;
@ -140,11 +137,75 @@ class MemPass : public Pass {
// Cache of verified non-target vars
std::unordered_set<uint32_t> seen_non_target_vars_;
private:
// Return true if all uses of |varId| are only through supported reference
// operations ie. loads and store. Also cache in supported_ref_vars_.
// TODO(dnovillo): This function is replicated in other passes and it's
// slightly different in every pass. Is it possible to make one common
// implementation?
bool HasOnlySupportedRefs(uint32_t varId);
// Patch phis in loop header block |header_id| now that the map is complete
// for the backedge predecessor |back_id|. Specifically, for each phi, find
// the value corresponding to the backedge predecessor. That was temporarily
// set with the variable id that this phi corresponds to. Change this phi
// operand to the the value which corresponds to that variable in the
// predecessor map.
void PatchPhis(uint32_t header_id, uint32_t back_id);
// Initialize label2ssa_map_ entry for block |block_ptr| with single
// predecessor.
void SSABlockInitSinglePred(ir::BasicBlock* block_ptr);
// Initialize label2ssa_map_ entry for loop header block pointed to
// |block_itr| by merging entries from all predecessors. If any value
// ids differ for any variable across predecessors, create a phi function
// in the block and use that value id for the variable in the new map.
// Assumes all predecessors have been visited by EliminateLocalMultiStore
// except the back edge. Use a dummy value in the phi for the back edge
// until the back edge block is visited and patch the phi value then.
void SSABlockInitLoopHeader(std::list<ir::BasicBlock*>::iterator block_itr);
// Initialize label2ssa_map_ entry for multiple predecessor block
// |block_ptr| by merging label2ssa_map_ entries for all predecessors.
// If any value ids differ for any variable across predecessors, create
// a phi function in the block and use that value id for the variable in
// the new map. Assumes all predecessors have been visited by
// EliminateLocalMultiStore.
void SSABlockInitMultiPred(ir::BasicBlock* block_ptr);
// Initialize the label2ssa_map entry for a block pointed to by |block_itr|.
// Insert phi instructions into block when necessary. All predecessor
// blocks must have been visited by EliminateLocalMultiStore except for
// backedges.
void SSABlockInit(std::list<ir::BasicBlock*>::iterator block_itr);
// Return true if variable is loaded in block with |label| or in any
// succeeding block in structured order.
bool IsLiveAfter(uint32_t var_id, uint32_t label) const;
// named or decorated ids
std::unordered_set<uint32_t> named_or_decorated_ids_;
// Next unused ID
uint32_t next_id_;
// Map from block's label id to a map of a variable to its value at the
// end of the block.
std::unordered_map<uint32_t, std::unordered_map<uint32_t, uint32_t>>
label2ssa_map_;
// Set of label ids of visited blocks
std::unordered_set<uint32_t> visitedBlocks_;
// Variables that are only referenced by supported operations for this
// pass ie. loads and stores.
std::unordered_set<uint32_t> supported_ref_vars_;
// Map from type to undef
std::unordered_map<uint32_t, uint32_t> type2undefs_;
// The Ids of OpPhi instructions that are in a loop header and which require
// patching of the value for the loop back-edge.
std::unordered_set<uint32_t> phis_to_patch_;
};
} // namespace opt

View File

@ -16,6 +16,7 @@
#include "pass.h"
#include "cfa.h"
#include "iterator.h"
namespace spvtools {
@ -24,9 +25,26 @@ namespace opt {
namespace {
const uint32_t kEntryPointFunctionIdInIdx = 1;
const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
const uint32_t kTypePointerTypeIdInIdx = 1;
// Universal Limit of ResultID + 1
const int kInvalidId = 0x400000;
} // namespace
Pass::Pass()
: pseudo_entry_block_(std::unique_ptr<ir::Instruction>(
new ir::Instruction(SpvOpLabel, 0, 0, {}))),
pseudo_exit_block_(std::unique_ptr<ir::Instruction>(
new ir::Instruction(SpvOpLabel, 0, kInvalidId, {}))),
consumer_(nullptr),
def_use_mgr_(nullptr),
next_id_(0),
module_(nullptr) {}
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)
@ -97,6 +115,85 @@ bool Pass::ProcessCallTreeFromRoots(
}
return modified;
}
bool Pass::IsLoopHeader(ir::BasicBlock* block_ptr) const {
auto iItr = block_ptr->end();
--iItr;
if (iItr == block_ptr->begin())
return false;
--iItr;
return iItr->opcode() == SpvOpLoopMerge;
}
uint32_t Pass::GetPointeeTypeId(const ir::Instruction* ptrInst) const {
const uint32_t ptrTypeId = ptrInst->type_id();
const ir::Instruction* ptrTypeInst = get_def_use_mgr()->GetDef(ptrTypeId);
return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
}
void Pass::ComputeStructuredOrder(ir::Function* func,
std::list<ir::BasicBlock*>* order) {
// Compute structured successors and do DFS
ComputeStructuredSuccessors(func);
auto ignore_block = [](cbb_ptr) {};
auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
auto get_structured_successors = [this](const ir::BasicBlock* block) {
return &(block2structured_succs_[block]);
};
// TODO(greg-lunarg): Get rid of const_cast by making moving const
// out of the cfa.h prototypes and into the invoking code.
auto post_order = [&](cbb_ptr b) {
order->push_front(const_cast<ir::BasicBlock*>(b));
};
spvtools::CFA<ir::BasicBlock>::DepthFirstTraversal(
&pseudo_entry_block_, get_structured_successors, ignore_block, post_order,
ignore_edge);
}
void Pass::ComputeStructuredSuccessors(ir::Function *func) {
block2structured_succs_.clear();
for (auto& blk : *func) {
// If no predecessors in function, make successor to pseudo entry
if (label2preds_[blk.id()].size() == 0)
block2structured_succs_[&pseudo_entry_block_].push_back(&blk);
// If header, make merge block first successor.
uint32_t cbid;
const uint32_t mbid = MergeBlockIdIfAny(blk, &cbid);
if (mbid != 0) {
block2structured_succs_[&blk].push_back(id2block_[mbid]);
if (cbid != 0)
block2structured_succs_[&blk].push_back(id2block_[cbid]);
}
// add true successors
blk.ForEachSuccessorLabel([&blk, this](uint32_t sbid) {
block2structured_succs_[&blk].push_back(id2block_[sbid]);
});
}
}
uint32_t Pass::MergeBlockIdIfAny(const ir::BasicBlock& blk, uint32_t* cbid) {
auto merge_ii = blk.cend();
--merge_ii;
if (cbid != nullptr) {
*cbid = 0;
}
uint32_t mbid = 0;
if (merge_ii != blk.cbegin()) {
--merge_ii;
if (merge_ii->opcode() == SpvOpLoopMerge) {
mbid = merge_ii->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx);
if (cbid != nullptr) {
*cbid =
merge_ii->GetSingleWordInOperand(kLoopMergeContinueBlockIdInIdx);
}
} else if (merge_ii->opcode() == SpvOpSelectionMerge) {
mbid = merge_ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
}
}
return mbid;
}
} // namespace opt
} // namespace spvtools

View File

@ -16,15 +16,17 @@
#define LIBSPIRV_OPT_PASS_H_
#include <algorithm>
#include <list>
#include <map>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "def_use_manager.h"
#include "module.h"
#include "spirv-tools/libspirv.hpp"
#include "basic_block.h"
namespace spvtools {
namespace opt {
@ -50,7 +52,7 @@ class Pass {
// The constructed instance will have an empty message consumer, which just
// ignores all messages from the library. Use SetMessageConsumer() to supply
// one if messages are of concern.
Pass() : consumer_(nullptr) {}
Pass();
// Destructs the pass.
virtual ~Pass() = default;
@ -66,9 +68,19 @@ class Pass {
// Sets the message consumer to the given |consumer|. |consumer| which will be
// invoked every time there is a message to be communicated to the outside.
void SetMessageConsumer(MessageConsumer c) { consumer_ = std::move(c); }
// Returns the reference to the message consumer for this pass.
const MessageConsumer& consumer() const { return consumer_; }
// Returns the def-use manager used for this pass. TODO(dnovillo): This should
// be handled by the pass manager.
analysis::DefUseManager* get_def_use_mgr() const {
return def_use_mgr_.get();
}
// Returns a pointer to the current module for this pass.
ir::Module* get_module() const { return module_; }
// Add to |todo| all ids of functions called in |func|.
void AddCalls(ir::Function* func, std::queue<uint32_t>* todo);
@ -96,8 +108,92 @@ class Pass {
// succesful to indicate whether changes are made to the module.
virtual Status Process(ir::Module* module) = 0;
protected:
// Initialize basic data structures for the pass. This sets up the def-use
// manager, module and other attributes. TODO(dnovillo): Some of this should
// be done during pass instantiation. Other things should be outside the pass
// altogether (e.g., def-use manager).
void InitializeProcessing(ir::Module* module) {
module_ = module;
next_id_ = module_->IdBound();
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), get_module()));
block2structured_succs_.clear();
label2preds_.clear();
id2block_.clear();
for (auto& fn : *module_) {
for (auto& blk : fn) {
id2block_[blk.id()] = &blk;
}
}
}
// Return true if |block_ptr| points to a loop header block. TODO(dnovillo)
// This belongs in a CFG class.
bool IsLoopHeader(ir::BasicBlock* block_ptr) const;
// Compute structured successors for function |func|. A block's structured
// successors are the blocks it branches to together with its declared merge
// block if it has one. When order matters, the merge block always appears
// first. This assures correct depth first search in the presence of early
// returns and kills. If the successor vector contain duplicates if the merge
// block, they are safely ignored by DFS. TODO(dnovillo): This belongs in a
// CFG class.
void ComputeStructuredSuccessors(ir::Function* func);
// Compute structured block order for |func| into |structuredOrder|. This
// order has the property that dominators come before all blocks they
// dominate and merge blocks come after all blocks that are in the control
// constructs of their header. TODO(dnovillo): This belongs in a CFG class.
void ComputeStructuredOrder(ir::Function* func,
std::list<ir::BasicBlock*>* order);
// Return type id for |ptrInst|'s pointee
uint32_t GetPointeeTypeId(const ir::Instruction* ptrInst) const;
// Return the next available Id and increment it.
inline uint32_t TakeNextId() { return next_id_++; }
// Write the next available Id back to the module.
inline void FinalizeNextId() {
assert(module_);
module_->SetIdBound(next_id_);
}
// Returns the id of the merge block declared by a merge instruction in this
// block, if any. If none, returns zero.
uint32_t MergeBlockIdIfAny(const ir::BasicBlock& blk, uint32_t* cbid);
// Map from block to its structured successor blocks. See
// ComputeStructuredSuccessors() for definition.
std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
block2structured_succs_;
// Extra block whose successors are all blocks with no predecessors
// in function.
ir::BasicBlock pseudo_entry_block_;
// Augmented CFG Exit Block.
ir::BasicBlock pseudo_exit_block_;
// Map from block's label id to its predecessor blocks ids
std::unordered_map<uint32_t, std::vector<uint32_t>> label2preds_;
// Map from block's label id to block.
std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
private:
using cbb_ptr = const ir::BasicBlock*;
MessageConsumer consumer_; // Message consumer.
// Def-Uses for the module we are processing
std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
// Next unused ID
uint32_t next_id_;
// The module that the pass is being applied to.
ir::Module* module_;
};
} // namespace opt

View File

@ -53,19 +53,18 @@ namespace spvtools {
namespace opt {
Pass::Status StrengthReductionPass::Process(ir::Module* module) {
InitializeProcessing(module);
// Initialize the member variables on a per module basis.
bool modified = false;
def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
int32_type_id_ = 0;
uint32_type_id_ = 0;
std::memset(constant_ids_, 0, sizeof(constant_ids_));
next_id_ = module->IdBound();
module_ = module;
FindIntTypesAndConstants();
modified = ScanFunctions();
// Have to reset the id bound.
module->SetIdBound(next_id_);
FinalizeNextId();
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
}
@ -84,7 +83,7 @@ bool StrengthReductionPass::ReplaceMultiplyByPowerOf2(
// Check the operands for a constant that is a power of 2.
for (int i = 0; i < 2; i++) {
uint32_t opId = inst->GetSingleWordInOperand(i);
ir::Instruction* opInst = def_use_mgr_->GetDef(opId);
ir::Instruction* opInst = get_def_use_mgr()->GetDef(opId);
if (opInst->opcode() == SpvOp::SpvOpConstant) {
// We found a constant operand.
uint32_t constVal = opInst->GetSingleWordOperand(2);
@ -95,7 +94,7 @@ bool StrengthReductionPass::ReplaceMultiplyByPowerOf2(
uint32_t shiftConstResultId = GetConstantId(shiftAmount);
// Create the new instruction.
uint32_t newResultId = next_id_++;
uint32_t newResultId = TakeNextId();
std::vector<ir::Operand> newOperands;
newOperands.push_back(inst->GetInOperand(1 - i));
ir::Operand shiftOperand(spv_operand_type_t::SPV_OPERAND_TYPE_ID,
@ -106,13 +105,13 @@ bool StrengthReductionPass::ReplaceMultiplyByPowerOf2(
newResultId, newOperands));
// Insert the new instruction and update the data structures.
def_use_mgr_->AnalyzeInstDefUse(&*newInstruction);
get_def_use_mgr()->AnalyzeInstDefUse(&*newInstruction);
inst = inst.InsertBefore(std::move(newInstruction));
++inst;
def_use_mgr_->ReplaceAllUsesWith(inst->result_id(), newResultId);
get_def_use_mgr()->ReplaceAllUsesWith(inst->result_id(), newResultId);
// Remove the old instruction.
def_use_mgr_->KillInst(&*inst);
get_def_use_mgr()->KillInst(&*inst);
// We do not want to replace the instruction twice if both operands
// are constants that are a power of 2. So we break here.
@ -125,8 +124,8 @@ bool StrengthReductionPass::ReplaceMultiplyByPowerOf2(
}
void StrengthReductionPass::FindIntTypesAndConstants() {
for (auto iter = module_->types_values_begin();
iter != module_->types_values_end(); ++iter) {
for (auto iter = get_module()->types_values_begin();
iter != get_module()->types_values_end(); ++iter) {
switch (iter->opcode()) {
case SpvOp::SpvOpTypeInt:
if (iter->GetSingleWordOperand(1) == 32) {
@ -159,12 +158,12 @@ uint32_t StrengthReductionPass::GetConstantId(uint32_t val) {
}
// Construct the constant.
uint32_t resultId = next_id_++;
uint32_t resultId = TakeNextId();
ir::Operand constant(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{val});
std::unique_ptr<ir::Instruction> newConstant(new ir::Instruction(
SpvOp::SpvOpConstant, uint32_type_id_, resultId, {constant}));
module_->AddGlobalValue(std::move(newConstant));
get_module()->AddGlobalValue(std::move(newConstant));
// Store the result id for next time.
constant_ids_[val] = resultId;
@ -178,7 +177,7 @@ bool StrengthReductionPass::ScanFunctions() {
// the instruction gets a pointer to the instruction. We cannot use that to
// insert a new instruction. I want an iterator.
bool modified = false;
for (auto& func : *module_) {
for (auto& func : *get_module()) {
for (auto& bb : func) {
for (auto inst = bb.begin(); inst != bb.end(); ++inst) {
switch (inst->opcode()) {
@ -195,14 +194,14 @@ bool StrengthReductionPass::ScanFunctions() {
}
uint32_t StrengthReductionPass::CreateUint32Type() {
uint32_t type_id = next_id_++;
uint32_t type_id = TakeNextId();
ir::Operand widthOperand(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{32});
ir::Operand signOperand(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{0});
std::unique_ptr<ir::Instruction> newType(new ir::Instruction(
SpvOp::SpvOpTypeInt, type_id, 0, {widthOperand, signOperand}));
module_->AddType(std::move(newType));
get_module()->AddType(std::move(newType));
return type_id;
}

View File

@ -50,9 +50,6 @@ class StrengthReductionPass : public Pass {
// This functions assumes one does not already exist.
uint32_t CreateUint32Type();
// Def-Uses for the module we are processing
std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
// Type ids for the types of interest, or 0 if they do not exist.
uint32_t int32_type_id_;
uint32_t uint32_type_id_;
@ -61,12 +58,6 @@ class StrengthReductionPass : public Pass {
// We set the limit at 32 because a bit shift of a 32-bit integer does not
// need a value larger than 32.
uint32_t constant_ids_[33];
// Next unused ID
uint32_t next_id_;
// The module that the pass is being applied to.
ir::Module* module_;
};
} // namespace opt

View File

@ -103,6 +103,7 @@ class ResultIdTrie {
} // anonymous namespace
Pass::Status UnifyConstantPass::Process(ir::Module* module) {
InitializeProcessing(module);
bool modified = false;
ResultIdTrie defined_constants;
analysis::DefUseManager def_use_mgr(consumer(), module);