mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-18 19:20:05 +00:00
The reviewed cfg_cleanup optimize pass
This commit is contained in:
parent
c75704ec08
commit
8ec62deb23
@ -24,15 +24,14 @@ add_library(SPIRV-Tools-opt
|
||||
decoration_manager.h
|
||||
def_use_manager.h
|
||||
eliminate_dead_constant_pass.h
|
||||
eliminate_dead_functions_pass.h
|
||||
flatten_decoration_pass.h
|
||||
function.h
|
||||
fold.h
|
||||
fold_spec_constant_op_and_composite_pass.h
|
||||
freeze_spec_constant_value_pass.h
|
||||
function.h
|
||||
inline_pass.h
|
||||
inline_exhaustive_pass.h
|
||||
inline_opaque_pass.h
|
||||
inline_pass.h
|
||||
insert_extract_elim.h
|
||||
instruction.h
|
||||
ir_loader.h
|
||||
@ -41,19 +40,20 @@ add_library(SPIRV-Tools-opt
|
||||
local_single_store_elim_pass.h
|
||||
local_ssa_elim_pass.h
|
||||
log.h
|
||||
mem_pass.h
|
||||
module.h
|
||||
null_pass.h
|
||||
passes.h
|
||||
pass.h
|
||||
pass_manager.h
|
||||
reflect.h
|
||||
mem_pass.h
|
||||
pass.h
|
||||
passes.h
|
||||
pass_manager.h
|
||||
eliminate_dead_functions_pass.h
|
||||
remove_duplicates_pass.h
|
||||
set_spec_constant_default_value_pass.h
|
||||
strength_reduction_pass.h
|
||||
strip_debug_info_pass.h
|
||||
type_manager.h
|
||||
types.h
|
||||
type_manager.h
|
||||
unify_const_pass.h
|
||||
|
||||
aggressive_dead_code_elim_pass.cpp
|
||||
@ -63,19 +63,18 @@ add_library(SPIRV-Tools-opt
|
||||
cfg_cleanup_pass.cpp
|
||||
common_uniform_elim_pass.cpp
|
||||
compact_ids_pass.cpp
|
||||
dead_branch_elim_pass.cpp
|
||||
decoration_manager.cpp
|
||||
def_use_manager.cpp
|
||||
dead_branch_elim_pass.cpp
|
||||
eliminate_dead_constant_pass.cpp
|
||||
eliminate_dead_functions_pass.cpp
|
||||
flatten_decoration_pass.cpp
|
||||
fold.cpp
|
||||
fold_spec_constant_op_and_composite_pass.cpp
|
||||
freeze_spec_constant_value_pass.cpp
|
||||
function.cpp
|
||||
inline_pass.cpp
|
||||
inline_exhaustive_pass.cpp
|
||||
inline_opaque_pass.cpp
|
||||
inline_pass.cpp
|
||||
insert_extract_elim.cpp
|
||||
instruction.cpp
|
||||
ir_loader.cpp
|
||||
@ -83,17 +82,18 @@ add_library(SPIRV-Tools-opt
|
||||
local_single_block_elim_pass.cpp
|
||||
local_single_store_elim_pass.cpp
|
||||
local_ssa_elim_pass.cpp
|
||||
mem_pass.cpp
|
||||
module.cpp
|
||||
optimizer.cpp
|
||||
pass.cpp
|
||||
pass_manager.cpp
|
||||
eliminate_dead_functions_pass.cpp
|
||||
remove_duplicates_pass.cpp
|
||||
set_spec_constant_default_value_pass.cpp
|
||||
optimizer.cpp
|
||||
mem_pass.cpp
|
||||
pass.cpp
|
||||
pass_manager.cpp
|
||||
strength_reduction_pass.cpp
|
||||
strip_debug_info_pass.cpp
|
||||
type_manager.cpp
|
||||
types.cpp
|
||||
type_manager.cpp
|
||||
unify_const_pass.cpp
|
||||
)
|
||||
|
||||
|
@ -113,6 +113,16 @@ class BasicBlock {
|
||||
void ForMergeAndContinueLabel(
|
||||
const std::function<void(const uint32_t)>& f);
|
||||
|
||||
// Returns true if this basic block has any Phi instructions.
|
||||
bool HasPhiInstructions() {
|
||||
int count = 0;
|
||||
ForEachPhiInst([&count](ir::Instruction*) {
|
||||
++count;
|
||||
return;
|
||||
});
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// The enclosing function.
|
||||
Function* function_;
|
||||
|
@ -27,63 +27,145 @@
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
void CFGCleanupPass::RemoveFromReachedPhiOperands(const ir::BasicBlock& block,
|
||||
ir::Instruction* inst) {
|
||||
uint32_t inst_id = inst->result_id();
|
||||
if (inst_id == 0) {
|
||||
return;
|
||||
uint32_t CFGCleanupPass::TypeToUndef(uint32_t type_id) {
|
||||
const auto uitr = type2undefs_.find(type_id);
|
||||
if (uitr != type2undefs_.end()) {
|
||||
return uitr->second;
|
||||
}
|
||||
|
||||
analysis::UseList* uses = def_use_mgr_->GetUses(inst_id);
|
||||
if (uses == nullptr) {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
|
||||
for (auto u : *uses) {
|
||||
if (u.inst->opcode() != SpvOpPhi) {
|
||||
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:
|
||||
//
|
||||
// 1- Any operand that comes directly from an unreachable block is completely
|
||||
// removed. Since the block is unreachable, the edge between the unreachable
|
||||
// block and the block holding |phi| has been removed.
|
||||
//
|
||||
// 2- Any operand that comes via a live block and was defined at an unreachable
|
||||
// block gets its value replaced with an OpUndef value. Since the argument
|
||||
// was generated in an unreachable block, it no longer exists, so it cannot
|
||||
// be referenced. However, since the value does not reach |phi| directly
|
||||
// from the unreachable block, the operand cannot be removed from |phi|.
|
||||
// Therefore, we replace the argument value with OpUndef.
|
||||
//
|
||||
// For example, in the switch() below, assume that we want to remove the
|
||||
// argument with value %11 coming from block %41.
|
||||
//
|
||||
// [ ... ]
|
||||
// %41 = OpLabel <--- Unreachable block
|
||||
// %11 = OpLoad %int %y
|
||||
// [ ... ]
|
||||
// OpSelectionMerge %16 None
|
||||
// OpSwitch %12 %16 10 %13 13 %14 18 %15
|
||||
// %13 = OpLabel
|
||||
// OpBranch %16
|
||||
// %14 = OpLabel
|
||||
// OpStore %outparm %int_14
|
||||
// OpBranch %16
|
||||
// %15 = OpLabel
|
||||
// OpStore %outparm %int_15
|
||||
// OpBranch %16
|
||||
// %16 = OpLabel
|
||||
// %30 = OpPhi %int %11 %41 %int_42 %13 %11 %14 %11 %15
|
||||
//
|
||||
// Since %41 is now an unreachable block, the first operand of |phi| needs to
|
||||
// be removed completely. But the operands (%11 %14) and (%11 %15) cannot be
|
||||
// removed because %14 and %15 are reachable blocks. Since %11 no longer exist,
|
||||
// in those arguments, we replace all references to %11 with an OpUndef value.
|
||||
// This results in |phi| looking like:
|
||||
//
|
||||
// %50 = OpUndef %int
|
||||
// [ ... ]
|
||||
// %30 = OpPhi %int %int_42 %13 %50 %14 %50 %15
|
||||
void CFGCleanupPass::RemovePhiOperands(
|
||||
ir::Instruction* phi,
|
||||
std::unordered_set<ir::BasicBlock*> reachable_blocks) {
|
||||
std::vector<ir::Operand> keep_operands;
|
||||
uint32_t type_id = 0;
|
||||
uint32_t undef_id = 0;
|
||||
|
||||
// Traverse all the operands in |phi|. Build the new operand vector by adding
|
||||
// all the original operands from |phi| except the unwanted ones.
|
||||
bool undef_created = false;
|
||||
for (uint32_t i = 0; i < phi->NumOperands();) {
|
||||
if (i < 2) {
|
||||
// The first two arguments are always preserved.
|
||||
keep_operands.push_back(phi->GetOperand(i));
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
ir::Instruction* phi_inst = u.inst;
|
||||
std::vector<ir::Operand> keep_operands;
|
||||
for (uint32_t i = 0; i < phi_inst->NumOperands(); i++) {
|
||||
const ir::Operand& var_op = phi_inst->GetOperand(i);
|
||||
if (i >= 2 && i < phi_inst->NumOperands() - 1) {
|
||||
// PHI arguments start at index 2. Each argument consists of two
|
||||
// operands: the variable id and the originating block id.
|
||||
const ir::Operand& block_op = phi_inst->GetOperand(i + 1);
|
||||
assert(var_op.words.size() == 1 && block_op.words.size() == 1 &&
|
||||
"Phi operands should have exactly one word in them.");
|
||||
uint32_t var_id = var_op.words.front();
|
||||
uint32_t block_id = block_op.words.front();
|
||||
if (var_id == inst_id && block_id == block.id()) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// The remaining Phi arguments come in pairs. Index 'i' contains the
|
||||
// variable id, index 'i + 1' is the originating block id.
|
||||
assert(i % 2 == 0 && i < phi->NumOperands() - 1 &&
|
||||
"malformed Phi arguments");
|
||||
|
||||
keep_operands.push_back(var_op);
|
||||
ir::BasicBlock *in_block = label2block_[phi->GetSingleWordOperand(i + 1)];
|
||||
if (reachable_blocks.find(in_block) == reachable_blocks.end()) {
|
||||
// If the incoming block is unreachable, remove both operands as this
|
||||
// means that the |phi| has lost an incoming edge.
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
phi_inst->ReplaceOperands(keep_operands);
|
||||
// In all other cases, the operand must be kept but may need to be changed.
|
||||
uint32_t arg_id = phi->GetSingleWordOperand(i);
|
||||
ir::BasicBlock *def_block = def_block_[arg_id];
|
||||
if (def_block &&
|
||||
reachable_blocks.find(def_block_[arg_id]) == reachable_blocks.end()) {
|
||||
// If the current |phi| argument was defined in an unreachable block, it
|
||||
// means that this |phi| argument is no longer defined. Replace it with
|
||||
// |undef_id|.
|
||||
if (!undef_created) {
|
||||
type_id = def_use_mgr_->GetDef(arg_id)->type_id();
|
||||
undef_id = TypeToUndef(type_id);
|
||||
undef_created = true;
|
||||
}
|
||||
keep_operands.push_back(
|
||||
ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, {undef_id}));
|
||||
} else {
|
||||
// Otherwise, the argument comes from a reachable block or from no block
|
||||
// at all (meaning that it was defined in the global section of the
|
||||
// program). In both cases, keep the argument intact.
|
||||
keep_operands.push_back(phi->GetOperand(i));
|
||||
}
|
||||
|
||||
keep_operands.push_back(phi->GetOperand(i + 1));
|
||||
|
||||
i += 2;
|
||||
}
|
||||
|
||||
phi->ReplaceOperands(keep_operands);
|
||||
}
|
||||
|
||||
void CFGCleanupPass::RemoveBlock(ir::Function::iterator* bi) {
|
||||
auto& block = **bi;
|
||||
block.ForEachInst([&block, this](ir::Instruction* inst) {
|
||||
auto& rm_block = **bi;
|
||||
|
||||
// Remove instructions from the block.
|
||||
rm_block.ForEachInst([&rm_block, this](ir::Instruction* inst) {
|
||||
// Note that we do not kill the block label instruction here. The label
|
||||
// instruction is needed to identify the block, which is needed by the
|
||||
// removal of PHI operands.
|
||||
if (inst != block.GetLabelInst()) {
|
||||
RemoveFromReachedPhiOperands(block, inst);
|
||||
// removal of phi operands.
|
||||
if (inst != rm_block.GetLabelInst()) {
|
||||
KillNamesAndDecorates(inst);
|
||||
def_use_mgr_->KillInst(inst);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the label instruction last.
|
||||
def_use_mgr_->KillInst(block.GetLabelInst());
|
||||
auto label = rm_block.GetLabelInst();
|
||||
KillNamesAndDecorates(label);
|
||||
def_use_mgr_->KillInst(label);
|
||||
|
||||
*bi = bi->Erase();
|
||||
}
|
||||
@ -122,6 +204,24 @@ bool CFGCleanupPass::RemoveUnreachableBlocks(ir::Function* func) {
|
||||
block->ForMergeAndContinueLabel(mark_reachable);
|
||||
}
|
||||
|
||||
// Update operands of Phi nodes that reference unreachable blocks.
|
||||
for (auto& block : *func) {
|
||||
// If the block is about to be removed, don't bother updating its
|
||||
// Phi instructions.
|
||||
if (reachable_blocks.count(&block) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the block is reachable and has Phi instructions, remove all
|
||||
// operands from its Phi instructions that reference unreachable blocks.
|
||||
if (block.HasPhiInstructions()) {
|
||||
block.ForEachPhiInst(
|
||||
[&block, &reachable_blocks, this](ir::Instruction* phi) {
|
||||
RemovePhiOperands(phi, reachable_blocks);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Erase unreachable blocks.
|
||||
for (auto ebi = func->begin(); ebi != func->end();) {
|
||||
if (reachable_blocks.count(&*ebi) == 0) {
|
||||
@ -142,16 +242,32 @@ bool CFGCleanupPass::CFGCleanup(ir::Function* func) {
|
||||
}
|
||||
|
||||
void CFGCleanupPass::Initialize(ir::Module* module) {
|
||||
// Initialize the DefUse manager.
|
||||
// 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));
|
||||
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) {
|
||||
for (auto& block : fn) {
|
||||
label2block_[block.id()] = █
|
||||
|
||||
// Build a map between SSA names to the block they are defined in.
|
||||
// TODO(dnovillo): This is expensive and unnecessary if ir::Instruction
|
||||
// instances could figure out what basic block they belong to. Remove this
|
||||
// once this is possible.
|
||||
block.ForEachInst([this, &block](ir::Instruction* inst) {
|
||||
uint32_t result_id = inst->result_id();
|
||||
if (result_id > 0) {
|
||||
def_block_[result_id] = █
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -162,6 +278,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_);
|
||||
return modified ? Pass::Status::SuccessWithChange
|
||||
: Pass::Status::SuccessWithoutChange;
|
||||
}
|
||||
|
@ -16,8 +16,8 @@
|
||||
#define LIBSPIRV_OPT_CFG_CLEANUP_PASS_H_
|
||||
|
||||
#include "function.h"
|
||||
#include "module.h"
|
||||
#include "mem_pass.h"
|
||||
#include "module.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
@ -37,19 +37,49 @@ class CFGCleanupPass : public MemPass {
|
||||
|
||||
// Remove the block pointed by the iterator |*bi|. This also removes
|
||||
// all the instructions in the pointed-to block.
|
||||
void RemoveBlock(ir::Function::iterator *bi);
|
||||
void RemoveBlock(ir::Function::iterator* bi);
|
||||
|
||||
// Initialize the pass.
|
||||
void Initialize(ir::Module* module);
|
||||
|
||||
// Remove the result from |inst| from every Phi instruction reached by
|
||||
// |inst|. The instruction is coming from basic block |block|.
|
||||
void RemoveFromReachedPhiOperands(const ir::BasicBlock& block,
|
||||
ir::Instruction* inst);
|
||||
// Remove Phi operands in |phi| that are coming from blocks not in
|
||||
// |reachable_blocks|.
|
||||
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_;
|
||||
|
||||
// Map from an instruction result ID to the block that holds it.
|
||||
// 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
|
||||
|
@ -20,11 +20,227 @@
|
||||
|
||||
#include "constants.h"
|
||||
#include "make_unique.h"
|
||||
#include "fold.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
namespace {
|
||||
// Returns the single-word result from performing the given unary operation on
|
||||
// the operand value which is passed in as a 32-bit word.
|
||||
uint32_t UnaryOperate(SpvOp opcode, uint32_t operand) {
|
||||
switch (opcode) {
|
||||
// Arthimetics
|
||||
case SpvOp::SpvOpSNegate:
|
||||
return -static_cast<int32_t>(operand);
|
||||
case SpvOp::SpvOpNot:
|
||||
return ~operand;
|
||||
case SpvOp::SpvOpLogicalNot:
|
||||
return !static_cast<bool>(operand);
|
||||
default:
|
||||
assert(false &&
|
||||
"Unsupported unary operation for OpSpecConstantOp instruction");
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the single-word result from performing the given binary operation on
|
||||
// the operand values which are passed in as two 32-bit word.
|
||||
uint32_t BinaryOperate(SpvOp opcode, uint32_t a, uint32_t b) {
|
||||
switch (opcode) {
|
||||
// Arthimetics
|
||||
case SpvOp::SpvOpIAdd:
|
||||
return a + b;
|
||||
case SpvOp::SpvOpISub:
|
||||
return a - b;
|
||||
case SpvOp::SpvOpIMul:
|
||||
return a * b;
|
||||
case SpvOp::SpvOpUDiv:
|
||||
assert(b != 0);
|
||||
return a / b;
|
||||
case SpvOp::SpvOpSDiv:
|
||||
assert(b != 0u);
|
||||
return (static_cast<int32_t>(a)) / (static_cast<int32_t>(b));
|
||||
case SpvOp::SpvOpSRem: {
|
||||
// The sign of non-zero result comes from the first operand: a. This is
|
||||
// guaranteed by C++11 rules for integer division operator. The division
|
||||
// result is rounded toward zero, so the result of '%' has the sign of
|
||||
// the first operand.
|
||||
assert(b != 0u);
|
||||
return static_cast<int32_t>(a) % static_cast<int32_t>(b);
|
||||
}
|
||||
case SpvOp::SpvOpSMod: {
|
||||
// The sign of non-zero result comes from the second operand: b
|
||||
assert(b != 0u);
|
||||
int32_t rem = BinaryOperate(SpvOp::SpvOpSRem, a, b);
|
||||
int32_t b_prim = static_cast<int32_t>(b);
|
||||
return (rem + b_prim) % b_prim;
|
||||
}
|
||||
case SpvOp::SpvOpUMod:
|
||||
assert(b != 0u);
|
||||
return (a % b);
|
||||
|
||||
// Shifting
|
||||
case SpvOp::SpvOpShiftRightLogical: {
|
||||
return a >> b;
|
||||
}
|
||||
case SpvOp::SpvOpShiftRightArithmetic:
|
||||
return (static_cast<int32_t>(a)) >> b;
|
||||
case SpvOp::SpvOpShiftLeftLogical:
|
||||
return a << b;
|
||||
|
||||
// Bitwise operations
|
||||
case SpvOp::SpvOpBitwiseOr:
|
||||
return a | b;
|
||||
case SpvOp::SpvOpBitwiseAnd:
|
||||
return a & b;
|
||||
case SpvOp::SpvOpBitwiseXor:
|
||||
return a ^ b;
|
||||
|
||||
// Logical
|
||||
case SpvOp::SpvOpLogicalEqual:
|
||||
return (static_cast<bool>(a)) == (static_cast<bool>(b));
|
||||
case SpvOp::SpvOpLogicalNotEqual:
|
||||
return (static_cast<bool>(a)) != (static_cast<bool>(b));
|
||||
case SpvOp::SpvOpLogicalOr:
|
||||
return (static_cast<bool>(a)) || (static_cast<bool>(b));
|
||||
case SpvOp::SpvOpLogicalAnd:
|
||||
return (static_cast<bool>(a)) && (static_cast<bool>(b));
|
||||
|
||||
// Comparison
|
||||
case SpvOp::SpvOpIEqual:
|
||||
return a == b;
|
||||
case SpvOp::SpvOpINotEqual:
|
||||
return a != b;
|
||||
case SpvOp::SpvOpULessThan:
|
||||
return a < b;
|
||||
case SpvOp::SpvOpSLessThan:
|
||||
return (static_cast<int32_t>(a)) < (static_cast<int32_t>(b));
|
||||
case SpvOp::SpvOpUGreaterThan:
|
||||
return a > b;
|
||||
case SpvOp::SpvOpSGreaterThan:
|
||||
return (static_cast<int32_t>(a)) > (static_cast<int32_t>(b));
|
||||
case SpvOp::SpvOpULessThanEqual:
|
||||
return a <= b;
|
||||
case SpvOp::SpvOpSLessThanEqual:
|
||||
return (static_cast<int32_t>(a)) <= (static_cast<int32_t>(b));
|
||||
case SpvOp::SpvOpUGreaterThanEqual:
|
||||
return a >= b;
|
||||
case SpvOp::SpvOpSGreaterThanEqual:
|
||||
return (static_cast<int32_t>(a)) >= (static_cast<int32_t>(b));
|
||||
default:
|
||||
assert(false &&
|
||||
"Unsupported binary operation for OpSpecConstantOp instruction");
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the single-word result from performing the given ternary operation
|
||||
// on the operand values which are passed in as three 32-bit word.
|
||||
uint32_t TernaryOperate(SpvOp opcode, uint32_t a, uint32_t b, uint32_t c) {
|
||||
switch (opcode) {
|
||||
case SpvOp::SpvOpSelect:
|
||||
return (static_cast<bool>(a)) ? b : c;
|
||||
default:
|
||||
assert(false &&
|
||||
"Unsupported ternary operation for OpSpecConstantOp instruction");
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the single-word result from performing the given operation on the
|
||||
// operand words. This only works with 32-bit operations and uses boolean
|
||||
// convention that 0u is false, and anything else is boolean true.
|
||||
// TODO(qining): Support operands other than 32-bit wide.
|
||||
uint32_t OperateWords(SpvOp opcode,
|
||||
const std::vector<uint32_t>& operand_words) {
|
||||
switch (operand_words.size()) {
|
||||
case 1:
|
||||
return UnaryOperate(opcode, operand_words.front());
|
||||
case 2:
|
||||
return BinaryOperate(opcode, operand_words.front(), operand_words.back());
|
||||
case 3:
|
||||
return TernaryOperate(opcode, operand_words[0], operand_words[1],
|
||||
operand_words[2]);
|
||||
default:
|
||||
assert(false && "Invalid number of operands");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the result of performing an operation on scalar constant operands.
|
||||
// This function extracts the operand values as 32 bit words and returns the
|
||||
// result in 32 bit word. Scalar constants with longer than 32-bit width are
|
||||
// not accepted in this function.
|
||||
uint32_t OperateScalars(SpvOp opcode,
|
||||
const std::vector<analysis::Constant*>& operands) {
|
||||
std::vector<uint32_t> operand_values_in_raw_words;
|
||||
for (analysis::Constant* operand : operands) {
|
||||
if (analysis::ScalarConstant* scalar = operand->AsScalarConstant()) {
|
||||
const auto& scalar_words = scalar->words();
|
||||
assert(scalar_words.size() == 1 &&
|
||||
"Scalar constants with longer than 32-bit width are not allowed "
|
||||
"in OperateScalars()");
|
||||
operand_values_in_raw_words.push_back(scalar_words.front());
|
||||
} else if (operand->AsNullConstant()) {
|
||||
operand_values_in_raw_words.push_back(0u);
|
||||
} else {
|
||||
assert(false &&
|
||||
"OperateScalars() only accepts ScalarConst or NullConst type of "
|
||||
"constant");
|
||||
}
|
||||
}
|
||||
return OperateWords(opcode, operand_values_in_raw_words);
|
||||
}
|
||||
|
||||
// Returns the result of performing an operation over constant vectors. This
|
||||
// function iterates through the given vector type constant operands and
|
||||
// calculates the result for each element of the result vector to return.
|
||||
// Vectors with longer than 32-bit scalar components are not accepted in this
|
||||
// function.
|
||||
std::vector<uint32_t> OperateVectors(
|
||||
SpvOp opcode, uint32_t num_dims,
|
||||
const std::vector<analysis::Constant*>& operands) {
|
||||
std::vector<uint32_t> result;
|
||||
for (uint32_t d = 0; d < num_dims; d++) {
|
||||
std::vector<uint32_t> operand_values_for_one_dimension;
|
||||
for (analysis::Constant* operand : operands) {
|
||||
if (analysis::VectorConstant* vector_operand =
|
||||
operand->AsVectorConstant()) {
|
||||
// Extract the raw value of the scalar component constants
|
||||
// in 32-bit words here. The reason of not using OperateScalars() here
|
||||
// is that we do not create temporary null constants as components
|
||||
// when the vector operand is a NullConstant because Constant creation
|
||||
// may need extra checks for the validity and that is not manageed in
|
||||
// here.
|
||||
if (const analysis::ScalarConstant* scalar_component =
|
||||
vector_operand->GetComponents().at(d)->AsScalarConstant()) {
|
||||
const auto& scalar_words = scalar_component->words();
|
||||
assert(
|
||||
scalar_words.size() == 1 &&
|
||||
"Vector components with longer than 32-bit width are not allowed "
|
||||
"in OperateVectors()");
|
||||
operand_values_for_one_dimension.push_back(scalar_words.front());
|
||||
} else if (operand->AsNullConstant()) {
|
||||
operand_values_for_one_dimension.push_back(0u);
|
||||
} else {
|
||||
assert(false &&
|
||||
"VectorConst should only has ScalarConst or NullConst as "
|
||||
"components");
|
||||
}
|
||||
} else if (operand->AsNullConstant()) {
|
||||
operand_values_for_one_dimension.push_back(0u);
|
||||
} else {
|
||||
assert(false &&
|
||||
"OperateVectors() only accepts VectorConst or NullConst type of "
|
||||
"constant");
|
||||
}
|
||||
}
|
||||
result.push_back(OperateWords(opcode, operand_values_for_one_dimension));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
FoldSpecConstantOpAndCompositePass::FoldSpecConstantOpAndCompositePass()
|
||||
: max_id_(0),
|
||||
module_(nullptr),
|
||||
@ -302,7 +518,7 @@ bool IsValidTypeForComponentWiseOperation(const analysis::Type* type) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
}
|
||||
|
||||
ir::Instruction* FoldSpecConstantOpAndCompositePass::DoComponentWiseOperation(
|
||||
ir::Module::inst_iterator* pos) {
|
||||
@ -330,7 +546,7 @@ ir::Instruction* FoldSpecConstantOpAndCompositePass::DoComponentWiseOperation(
|
||||
|
||||
if (result_type->AsInteger() || result_type->AsBool()) {
|
||||
// Scalar operation
|
||||
uint32_t result_val = FoldScalars(spec_opcode, operands);
|
||||
uint32_t result_val = OperateScalars(spec_opcode, operands);
|
||||
auto result_const = CreateConst(result_type, {result_val});
|
||||
return BuildInstructionAndAddToModule(std::move(result_const), pos);
|
||||
} else if (result_type->AsVector()) {
|
||||
@ -339,7 +555,7 @@ ir::Instruction* FoldSpecConstantOpAndCompositePass::DoComponentWiseOperation(
|
||||
result_type->AsVector()->element_type();
|
||||
uint32_t num_dims = result_type->AsVector()->element_count();
|
||||
std::vector<uint32_t> result_vec =
|
||||
FoldVectors(spec_opcode, num_dims, operands);
|
||||
OperateVectors(spec_opcode, num_dims, operands);
|
||||
std::vector<const analysis::Constant*> result_vector_components;
|
||||
for (uint32_t r : result_vec) {
|
||||
if (auto rc = CreateConst(element_type, {r})) {
|
||||
|
@ -230,11 +230,6 @@ Optimizer::PassToken CreateCompactIdsPass() {
|
||||
MakeUnique<opt::CompactIdsPass>());
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateCFGCleanupPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::CFGCleanupPass>());
|
||||
}
|
||||
|
||||
std::vector<const char*> Optimizer::GetPassNames() const {
|
||||
std::vector<const char*> v;
|
||||
for (uint32_t i = 0; i < impl_->pass_manager.NumPasses(); i++) {
|
||||
@ -243,4 +238,9 @@ std::vector<const char*> Optimizer::GetPassNames() const {
|
||||
return v;
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateCFGCleanupPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::CFGCleanupPass>());
|
||||
}
|
||||
|
||||
} // namespace spvtools
|
||||
|
@ -233,4 +233,216 @@ OpFunctionEnd
|
||||
|
||||
SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
|
||||
}
|
||||
|
||||
TEST_F(CFGCleanupTest, RemoveNamedLabels) {
|
||||
const std::string before = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %main "main"
|
||||
OpSource GLSL 430
|
||||
OpName %main "main"
|
||||
OpName %dead "dead"
|
||||
%void = OpTypeVoid
|
||||
%5 = OpTypeFunction %void
|
||||
%main = OpFunction %void None %5
|
||||
%6 = OpLabel
|
||||
OpReturn
|
||||
%dead = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd)";
|
||||
|
||||
const std::string after = R"(OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %main "main"
|
||||
OpSource GLSL 430
|
||||
OpName %main "main"
|
||||
%void = OpTypeVoid
|
||||
%5 = OpTypeFunction %void
|
||||
%main = OpFunction %void None %5
|
||||
%6 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
|
||||
}
|
||||
|
||||
TEST_F(CFGCleanupTest, RemovePhiArgsFromFarBlocks) {
|
||||
const std::string before = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %y %outparm
|
||||
OpName %main "main"
|
||||
OpName %y "y"
|
||||
OpName %outparm "outparm"
|
||||
OpDecorate %y Flat
|
||||
OpDecorate %y Location 0
|
||||
OpDecorate %outparm Location 0
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Function_int = OpTypePointer Function %int
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%y = OpVariable %_ptr_Input_int Input
|
||||
%int_42 = OpConstant %int 42
|
||||
%_ptr_Output_int = OpTypePointer Output %int
|
||||
%outparm = OpVariable %_ptr_Output_int Output
|
||||
%int_14 = OpConstant %int 14
|
||||
%int_15 = OpConstant %int 15
|
||||
%int_5 = OpConstant %int 5
|
||||
%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
OpBranch %40
|
||||
%41 = OpLabel
|
||||
%11 = OpLoad %int %y
|
||||
OpBranch %40
|
||||
%40 = OpLabel
|
||||
%12 = OpLoad %int %y
|
||||
OpSelectionMerge %16 None
|
||||
OpSwitch %12 %16 10 %13 13 %14 18 %15
|
||||
%13 = OpLabel
|
||||
OpBranch %16
|
||||
%14 = OpLabel
|
||||
OpStore %outparm %int_14
|
||||
OpBranch %16
|
||||
%15 = OpLabel
|
||||
OpStore %outparm %int_15
|
||||
OpBranch %16
|
||||
%16 = OpLabel
|
||||
%30 = OpPhi %int %11 %41 %int_42 %13 %11 %14 %11 %15
|
||||
%28 = OpIAdd %int %30 %int_5
|
||||
OpStore %outparm %28
|
||||
OpReturn
|
||||
OpFunctionEnd)";
|
||||
|
||||
const std::string after = R"(OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %y %outparm
|
||||
OpName %main "main"
|
||||
OpName %y "y"
|
||||
OpName %outparm "outparm"
|
||||
OpDecorate %y Flat
|
||||
OpDecorate %y Location 0
|
||||
OpDecorate %outparm Location 0
|
||||
%void = OpTypeVoid
|
||||
%6 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Function_int = OpTypePointer Function %int
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%y = OpVariable %_ptr_Input_int Input
|
||||
%int_42 = OpConstant %int 42
|
||||
%_ptr_Output_int = OpTypePointer Output %int
|
||||
%outparm = OpVariable %_ptr_Output_int Output
|
||||
%int_14 = OpConstant %int 14
|
||||
%int_15 = OpConstant %int 15
|
||||
%int_5 = OpConstant %int 5
|
||||
%26 = OpUndef %int
|
||||
%main = OpFunction %void None %6
|
||||
%15 = OpLabel
|
||||
OpBranch %16
|
||||
%16 = OpLabel
|
||||
%19 = OpLoad %int %y
|
||||
OpSelectionMerge %20 None
|
||||
OpSwitch %19 %20 10 %21 13 %22 18 %23
|
||||
%21 = OpLabel
|
||||
OpBranch %20
|
||||
%22 = OpLabel
|
||||
OpStore %outparm %int_14
|
||||
OpBranch %20
|
||||
%23 = OpLabel
|
||||
OpStore %outparm %int_15
|
||||
OpBranch %20
|
||||
%20 = OpLabel
|
||||
%24 = OpPhi %int %int_42 %21 %26 %22 %26 %23
|
||||
%25 = OpIAdd %int %24 %int_5
|
||||
OpStore %outparm %25
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
|
||||
}
|
||||
|
||||
TEST_F(CFGCleanupTest, RemovePhiConstantArgs) {
|
||||
const std::string before = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %y %outparm
|
||||
OpName %main "main"
|
||||
OpName %y "y"
|
||||
OpName %outparm "outparm"
|
||||
OpDecorate %y Flat
|
||||
OpDecorate %y Location 0
|
||||
OpDecorate %outparm Location 0
|
||||
%void = OpTypeVoid
|
||||
%3 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%y = OpVariable %_ptr_Input_int Input
|
||||
%int_10 = OpConstant %int 10
|
||||
%bool = OpTypeBool
|
||||
%_ptr_Function_int = OpTypePointer Function %int
|
||||
%int_23 = OpConstant %int 23
|
||||
%int_5 = OpConstant %int 5
|
||||
%_ptr_Output_int = OpTypePointer Output %int
|
||||
%outparm = OpVariable %_ptr_Output_int Output
|
||||
%24 = OpUndef %int
|
||||
%main = OpFunction %void None %3
|
||||
%5 = OpLabel
|
||||
OpBranch %14
|
||||
%40 = OpLabel
|
||||
%9 = OpLoad %int %y
|
||||
%12 = OpSGreaterThan %bool %9 %int_10
|
||||
OpSelectionMerge %14 None
|
||||
OpBranchConditional %12 %13 %14
|
||||
%13 = OpLabel
|
||||
OpBranch %14
|
||||
%14 = OpLabel
|
||||
%25 = OpPhi %int %24 %5 %int_23 %13
|
||||
%20 = OpIAdd %int %25 %int_5
|
||||
OpStore %outparm %20
|
||||
OpReturn
|
||||
OpFunctionEnd)";
|
||||
|
||||
const std::string after = R"(OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %y %outparm
|
||||
OpName %main "main"
|
||||
OpName %y "y"
|
||||
OpName %outparm "outparm"
|
||||
OpDecorate %y Flat
|
||||
OpDecorate %y Location 0
|
||||
OpDecorate %outparm Location 0
|
||||
%void = OpTypeVoid
|
||||
%6 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%y = OpVariable %_ptr_Input_int Input
|
||||
%int_10 = OpConstant %int 10
|
||||
%bool = OpTypeBool
|
||||
%_ptr_Function_int = OpTypePointer Function %int
|
||||
%int_23 = OpConstant %int 23
|
||||
%int_5 = OpConstant %int 5
|
||||
%_ptr_Output_int = OpTypePointer Output %int
|
||||
%outparm = OpVariable %_ptr_Output_int Output
|
||||
%15 = OpUndef %int
|
||||
%main = OpFunction %void None %6
|
||||
%16 = OpLabel
|
||||
OpBranch %17
|
||||
%17 = OpLabel
|
||||
%22 = OpPhi %int %15 %16
|
||||
%23 = OpIAdd %int %22 %int_5
|
||||
OpStore %outparm %23
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndCheck<opt::CFGCleanupPass>(before, after, true, true);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
Loading…
Reference in New Issue
Block a user