mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2025-01-11 09:00:06 +00:00
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:
parent
94dc66b74d
commit
1040a95b3f
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user