diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt index 1c78dade..a51d08e5 100644 --- a/source/opt/CMakeLists.txt +++ b/source/opt/CMakeLists.txt @@ -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 ) diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h index 0c464001..5e226c12 100644 --- a/source/opt/basic_block.h +++ b/source/opt/basic_block.h @@ -113,6 +113,16 @@ class BasicBlock { void ForMergeAndContinueLabel( const std::function& 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_; diff --git a/source/opt/cfg_cleanup_pass.cpp b/source/opt/cfg_cleanup_pass.cpp index de6a4136..406cbef8 100644 --- a/source/opt/cfg_cleanup_pass.cpp +++ b/source/opt/cfg_cleanup_pass.cpp @@ -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 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 reachable_blocks) { + std::vector 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 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; } diff --git a/source/opt/cfg_cleanup_pass.h b/source/opt/cfg_cleanup_pass.h index 27a6ba37..810bc35a 100644 --- a/source/opt/cfg_cleanup_pass.h +++ b/source/opt/cfg_cleanup_pass.h @@ -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 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 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 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 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 diff --git a/source/opt/fold_spec_constant_op_and_composite_pass.cpp b/source/opt/fold_spec_constant_op_and_composite_pass.cpp index 9d4d7907..84248146 100644 --- a/source/opt/fold_spec_constant_op_and_composite_pass.cpp +++ b/source/opt/fold_spec_constant_op_and_composite_pass.cpp @@ -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(operand); + case SpvOp::SpvOpNot: + return ~operand; + case SpvOp::SpvOpLogicalNot: + return !static_cast(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(a)) / (static_cast(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(a) % static_cast(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(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(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(a)) == (static_cast(b)); + case SpvOp::SpvOpLogicalNotEqual: + return (static_cast(a)) != (static_cast(b)); + case SpvOp::SpvOpLogicalOr: + return (static_cast(a)) || (static_cast(b)); + case SpvOp::SpvOpLogicalAnd: + return (static_cast(a)) && (static_cast(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(a)) < (static_cast(b)); + case SpvOp::SpvOpUGreaterThan: + return a > b; + case SpvOp::SpvOpSGreaterThan: + return (static_cast(a)) > (static_cast(b)); + case SpvOp::SpvOpULessThanEqual: + return a <= b; + case SpvOp::SpvOpSLessThanEqual: + return (static_cast(a)) <= (static_cast(b)); + case SpvOp::SpvOpUGreaterThanEqual: + return a >= b; + case SpvOp::SpvOpSGreaterThanEqual: + return (static_cast(a)) >= (static_cast(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(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& 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& operands) { + std::vector 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 OperateVectors( + SpvOp opcode, uint32_t num_dims, + const std::vector& operands) { + std::vector result; + for (uint32_t d = 0; d < num_dims; d++) { + std::vector 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 result_vec = - FoldVectors(spec_opcode, num_dims, operands); + OperateVectors(spec_opcode, num_dims, operands); std::vector result_vector_components; for (uint32_t r : result_vec) { if (auto rc = CreateConst(element_type, {r})) { diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index 462519ea..2022f9cb 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp @@ -230,11 +230,6 @@ Optimizer::PassToken CreateCompactIdsPass() { MakeUnique()); } -Optimizer::PassToken CreateCFGCleanupPass() { - return MakeUnique( - MakeUnique()); -} - std::vector Optimizer::GetPassNames() const { std::vector v; for (uint32_t i = 0; i < impl_->pass_manager.NumPasses(); i++) { @@ -243,4 +238,9 @@ std::vector Optimizer::GetPassNames() const { return v; } +Optimizer::PassToken CreateCFGCleanupPass() { + return MakeUnique( + MakeUnique()); +} + } // namespace spvtools diff --git a/test/opt/cfg_cleanup_test.cpp b/test/opt/cfg_cleanup_test.cpp index d4933ee0..9699ee47 100644 --- a/test/opt/cfg_cleanup_test.cpp +++ b/test/opt/cfg_cleanup_test.cpp @@ -233,4 +233,216 @@ OpFunctionEnd SinglePassRunAndCheck(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(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(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(before, after, true, true); +} } // anonymous namespace