Initial implementation of if conversion

* Handles simple cases only
* Identifies phis in blocks with two predecessors and attempts to
convert the phi to an select
 * does not perform code motion currently so the converted values must
 dominate the join point (e.g. can't be defined in the branches)
 * limited for now to two predecessors, but can be extended to handle
 more cases
* Adding if conversion to -O and -Os
This commit is contained in:
Alan Baker 2018-01-16 11:15:06 -05:00
parent b2eb840468
commit 2e93e806e4
18 changed files with 948 additions and 9 deletions

View File

@ -68,6 +68,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/dead_variable_elimination.cpp \ source/opt/dead_variable_elimination.cpp \
source/opt/decoration_manager.cpp \ source/opt/decoration_manager.cpp \
source/opt/def_use_manager.cpp \ source/opt/def_use_manager.cpp \
source/opt/dominator_analysis.cpp \
source/opt/dominator_tree.cpp \ source/opt/dominator_tree.cpp \
source/opt/eliminate_dead_constant_pass.cpp \ source/opt/eliminate_dead_constant_pass.cpp \
source/opt/eliminate_dead_functions_pass.cpp \ source/opt/eliminate_dead_functions_pass.cpp \
@ -77,6 +78,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/fold_spec_constant_op_and_composite_pass.cpp \ source/opt/fold_spec_constant_op_and_composite_pass.cpp \
source/opt/freeze_spec_constant_value_pass.cpp \ source/opt/freeze_spec_constant_value_pass.cpp \
source/opt/function.cpp \ source/opt/function.cpp \
source/opt/if_conversion.cpp \
source/opt/inline_pass.cpp \ source/opt/inline_pass.cpp \
source/opt/inline_exhaustive_pass.cpp \ source/opt/inline_exhaustive_pass.cpp \
source/opt/inline_opaque_pass.cpp \ source/opt/inline_opaque_pass.cpp \

View File

@ -482,6 +482,9 @@ Optimizer::PassToken CreateCCPPass();
// Current workaround: Avoid OpUnreachable instructions in loops. // Current workaround: Avoid OpUnreachable instructions in loops.
Optimizer::PassToken CreateWorkaround1209Pass(); Optimizer::PassToken CreateWorkaround1209Pass();
// Creates a pass that converts if-then-else like assignments into OpSelect.
Optimizer::PassToken CreateIfConversionPass();
} // namespace spvtools } // namespace spvtools
#endif // SPIRV_TOOLS_OPTIMIZER_HPP_ #endif // SPIRV_TOOLS_OPTIMIZER_HPP_

View File

@ -36,6 +36,7 @@ add_library(SPIRV-Tools-opt
fold_spec_constant_op_and_composite_pass.h fold_spec_constant_op_and_composite_pass.h
freeze_spec_constant_value_pass.h freeze_spec_constant_value_pass.h
function.h function.h
if_conversion.h
inline_exhaustive_pass.h inline_exhaustive_pass.h
inline_opaque_pass.h inline_opaque_pass.h
inline_pass.h inline_pass.h
@ -87,6 +88,7 @@ add_library(SPIRV-Tools-opt
dead_variable_elimination.cpp dead_variable_elimination.cpp
decoration_manager.cpp decoration_manager.cpp
def_use_manager.cpp def_use_manager.cpp
dominator_analysis.cpp
dominator_tree.cpp dominator_tree.cpp
eliminate_dead_constant_pass.cpp eliminate_dead_constant_pass.cpp
eliminate_dead_functions_pass.cpp eliminate_dead_functions_pass.cpp
@ -96,6 +98,7 @@ add_library(SPIRV-Tools-opt
fold_spec_constant_op_and_composite_pass.cpp fold_spec_constant_op_and_composite_pass.cpp
freeze_spec_constant_value_pass.cpp freeze_spec_constant_value_pass.cpp
function.cpp function.cpp
if_conversion.cpp
inline_exhaustive_pass.cpp inline_exhaustive_pass.cpp
inline_opaque_pass.cpp inline_opaque_pass.cpp
inline_pass.cpp inline_pass.cpp

View File

@ -123,6 +123,12 @@ class BasicBlock {
inline void ForEachPhiInst(const std::function<void(Instruction*)>& f, inline void ForEachPhiInst(const std::function<void(Instruction*)>& f,
bool run_on_debug_line_insts = false); bool run_on_debug_line_insts = false);
// Runs the given function |f| on each Phi instruction in this basic block,
// and optionally on the debug line instructions that might precede them. If
// |f| returns false, iteration is terminated and this function return false.
inline bool WhileEachPhiInst(const std::function<bool(Instruction*)>& f,
bool run_on_debug_line_insts = false);
// Runs the given function |f| on each label id of each successor block // Runs the given function |f| on each label id of each successor block
void ForEachSuccessorLabel( void ForEachSuccessorLabel(
const std::function<void(const uint32_t)>& f) const; const std::function<void(const uint32_t)>& f) const;
@ -135,12 +141,7 @@ class BasicBlock {
// Returns true if this basic block has any Phi instructions. // Returns true if this basic block has any Phi instructions.
bool HasPhiInstructions() { bool HasPhiInstructions() {
int count = 0; return !WhileEachPhiInst([](ir::Instruction*) { return false; });
ForEachPhiInst([&count](ir::Instruction*) {
++count;
return;
});
return count > 0;
} }
// Return true if this block is a loop header block. // Return true if this block is a loop header block.
@ -244,12 +245,23 @@ inline void BasicBlock::ForEachInst(
run_on_debug_line_insts); run_on_debug_line_insts);
} }
inline void BasicBlock::ForEachPhiInst( inline bool BasicBlock::WhileEachPhiInst(
const std::function<void(Instruction*)>& f, bool run_on_debug_line_insts) { const std::function<bool(Instruction*)>& f, bool run_on_debug_line_insts) {
for (auto& inst : insts_) { for (auto& inst : insts_) {
if (inst.opcode() != SpvOpPhi) break; if (inst.opcode() != SpvOpPhi) break;
inst.ForEachInst(f, run_on_debug_line_insts); if (!inst.WhileEachInst(f, run_on_debug_line_insts)) return false;
} }
return true;
}
inline void BasicBlock::ForEachPhiInst(
const std::function<void(Instruction*)>& f, bool run_on_debug_line_insts) {
WhileEachPhiInst(
[&f](Instruction* inst) {
f(inst);
return true;
},
run_on_debug_line_insts);
} }
} // namespace ir } // namespace ir

View File

@ -0,0 +1,41 @@
// Copyright (c) 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dominator_analysis.h"
#include <unordered_set>
namespace spvtools {
namespace opt {
ir::BasicBlock* DominatorAnalysisBase::CommonDominator(
ir::BasicBlock* b1, ir::BasicBlock* b2) const {
if (!b1 || !b2) return nullptr;
std::unordered_set<ir::BasicBlock*> seen;
ir::BasicBlock* block = b1;
while (block && seen.insert(block).second) {
block = ImmediateDominator(block);
}
block = b2;
while (block && !seen.count(block)) {
block = ImmediateDominator(block);
}
return block;
}
} // namespace opt
} // namespace spvtools

View File

@ -111,6 +111,10 @@ class DominatorAnalysisBase {
tree_.Visit(func); tree_.Visit(func);
} }
// Returns the most immediate basic block that dominates both |b1| and |b2|.
// If there is no such basic block, nullptr is returned.
ir::BasicBlock* CommonDominator(ir::BasicBlock* b1, ir::BasicBlock* b2) const;
protected: protected:
DominatorTree tree_; DominatorTree tree_;
}; };

View File

@ -0,0 +1,188 @@
// Copyright (c) 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "if_conversion.h"
namespace spvtools {
namespace opt {
Pass::Status IfConversion::Process(ir::IRContext* c) {
InitializeProcessing(c);
bool modified = false;
std::vector<ir::Instruction*> to_kill;
for (auto& func : *get_module()) {
DominatorAnalysis* dominators =
context()->GetDominatorAnalysis(&func, *cfg());
for (auto& block : func) {
// Check if it is possible for |block| to have phis that can be
// transformed.
ir::BasicBlock* common = nullptr;
if (!CheckBlock(&block, dominators, &common)) continue;
// Get an insertion point.
auto iter = block.begin();
while (iter != block.end() && iter->opcode() == SpvOpPhi) {
++iter;
}
InstructionBuilder<ir::IRContext::kAnalysisDefUse |
ir::IRContext::kAnalysisInstrToBlockMapping>
builder(context(), &*iter);
block.ForEachPhiInst([this, &builder, &modified, &common, &to_kill,
dominators, &block](ir::Instruction* phi) {
// This phi is not compatible, but subsequent phis might be.
if (!CheckType(phi->type_id())) return;
// We cannot transform cases where the phi is used by another phi in the
// same block due to instruction ordering restrictions.
// TODO(alan-baker): If all inappropriate uses could also be
// transformed, we could still remove this phi.
if (!CheckPhiUsers(phi, &block)) return;
// Identify the incoming values associated with the true and false
// branches. If |then_block| dominates |inc0| or if the true edge
// branches straight to this block and |common| is |inc0|, then |inc0|
// is on the true branch. Otherwise the |inc1| is on the true branch.
ir::BasicBlock* inc0 = GetIncomingBlock(phi, 0u);
ir::Instruction* branch = common->terminator();
uint32_t condition = branch->GetSingleWordInOperand(0u);
ir::BasicBlock* then_block =
GetBlock(branch->GetSingleWordInOperand(1u));
ir::Instruction* true_value = nullptr;
ir::Instruction* false_value = nullptr;
if ((then_block == &block && inc0 == common) ||
dominators->Dominates(then_block, inc0)) {
true_value = GetIncomingValue(phi, 0u);
false_value = GetIncomingValue(phi, 1u);
} else {
true_value = GetIncomingValue(phi, 1u);
false_value = GetIncomingValue(phi, 0u);
}
// If either incoming value is defined in a block that does not dominate
// this phi, then we cannot eliminate the phi with a select.
// TODO(alan-baker): Perform code motion where it makes sense to enable
// the transform in this case.
ir::BasicBlock* true_def_block = context()->get_instr_block(true_value);
if (true_def_block && !dominators->Dominates(true_def_block, &block))
return;
ir::BasicBlock* false_def_block =
context()->get_instr_block(false_value);
if (false_def_block && !dominators->Dominates(false_def_block, &block))
return;
analysis::Type* data_ty =
context()->get_type_mgr()->GetType(true_value->type_id());
if (analysis::Vector* vec_data_ty = data_ty->AsVector()) {
condition = SplatCondition(vec_data_ty, condition, &builder);
}
ir::Instruction* select = builder.AddSelect(phi->type_id(), condition,
true_value->result_id(),
false_value->result_id());
context()->ReplaceAllUsesWith(phi->result_id(), select->result_id());
to_kill.push_back(phi);
modified = true;
return;
});
}
}
for (auto inst : to_kill) {
context()->KillInst(inst);
}
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
bool IfConversion::CheckBlock(ir::BasicBlock* block,
DominatorAnalysis* dominators,
ir::BasicBlock** common) {
const std::vector<uint32_t>& preds = cfg()->preds(block->id());
// TODO(alan-baker): Extend to more than two predecessors
if (preds.size() != 2) return false;
ir::BasicBlock* inc0 = context()->get_instr_block(preds[0]);
if (dominators->Dominates(block, inc0)) return false;
ir::BasicBlock* inc1 = context()->get_instr_block(preds[1]);
if (dominators->Dominates(block, inc1)) return false;
// All phis will have the same common dominator, so cache the result
// for this block. If there is no common dominator, then we cannot transform
// any phi in this basic block.
*common = dominators->CommonDominator(inc0, inc1);
if (!*common) return false;
ir::Instruction* branch = (*common)->terminator();
if (branch->opcode() != SpvOpBranchConditional) return false;
return true;
}
bool IfConversion::CheckPhiUsers(ir::Instruction* phi, ir::BasicBlock* block) {
return get_def_use_mgr()->WhileEachUser(phi, [block,
this](ir::Instruction* user) {
if (user->opcode() == SpvOpPhi && context()->get_instr_block(user) == block)
return false;
return true;
});
}
uint32_t IfConversion::SplatCondition(
analysis::Vector* vec_data_ty, uint32_t cond,
InstructionBuilder<ir::IRContext::kAnalysisDefUse |
ir::IRContext::kAnalysisInstrToBlockMapping>* builder) {
// If the data inputs to OpSelect are vectors, the condition for
// OpSelect must be a boolean vector with the same number of
// components. So splat the condition for the branch into a vector
// type.
analysis::Bool bool_ty;
analysis::Vector bool_vec_ty(&bool_ty, vec_data_ty->element_count());
uint32_t bool_vec_id =
context()->get_type_mgr()->GetTypeInstruction(&bool_vec_ty);
std::vector<uint32_t> ids(vec_data_ty->element_count(), cond);
return builder->AddCompositeConstruct(bool_vec_id, ids)->result_id();
}
bool IfConversion::CheckType(uint32_t id) {
ir::Instruction* type = get_def_use_mgr()->GetDef(id);
SpvOp op = type->opcode();
if (spvOpcodeIsScalarType(op) || op == SpvOpTypePointer ||
op == SpvOpTypeVector)
return true;
return false;
}
ir::BasicBlock* IfConversion::GetBlock(uint32_t id) {
return context()->get_instr_block(get_def_use_mgr()->GetDef(id));
}
ir::BasicBlock* IfConversion::GetIncomingBlock(ir::Instruction* phi,
uint32_t predecessor) {
uint32_t in_index = 2 * predecessor + 1;
return GetBlock(phi->GetSingleWordInOperand(in_index));
}
ir::Instruction* IfConversion::GetIncomingValue(ir::Instruction* phi,
uint32_t predecessor) {
uint32_t in_index = 2 * predecessor;
return get_def_use_mgr()->GetDef(phi->GetSingleWordInOperand(in_index));
}
} // namespace opt
} // namespace spvtools

View File

@ -0,0 +1,78 @@
// Copyright (c) 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef LIBSPIRV_OPT_IF_CONVERSION_H_
#define LIBSPIRV_OPT_IF_CONVERSION_H_
#include "basic_block.h"
#include "ir_builder.h"
#include "pass.h"
#include "types.h"
namespace spvtools {
namespace opt {
// See optimizer.hpp for documentation.
class IfConversion : public Pass {
public:
const char* name() const override { return "if-conversion"; }
Status Process(ir::IRContext* context) override;
ir::IRContext::Analysis GetPreservedAnalyses() override {
return ir::IRContext::kAnalysisDefUse |
ir::IRContext::kAnalysisDominatorAnalysis |
ir::IRContext::kAnalysisInstrToBlockMapping |
ir::IRContext::kAnalysisCFG;
}
private:
// Returns true if |id| is a valid type for use with OpSelect. OpSelect only
// allows scalars, vectors and pointers as valid inputs.
bool CheckType(uint32_t id);
// Returns the basic block containing |id|.
ir::BasicBlock* GetBlock(uint32_t id);
// Returns the basic block for the |predecessor|'th index predecessor of
// |phi|.
ir::BasicBlock* GetIncomingBlock(ir::Instruction* phi, uint32_t predecessor);
// Returns the instruction defining the |predecessor|'th index of |phi|.
ir::Instruction* GetIncomingValue(ir::Instruction* phi, uint32_t predecessor);
// Returns the id of a OpCompositeConstruct boolean vector. The composite has
// the same number of elements as |vec_data_ty| and each member is |cond|.
// |where| indicates the location in |block| to insert the composite
// construct. If necessary, this function will also construct the necessary
// type instructions for the boolean vector.
uint32_t SplatCondition(
analysis::Vector* vec_data_ty, uint32_t cond,
InstructionBuilder<ir::IRContext::kAnalysisDefUse |
ir::IRContext::kAnalysisInstrToBlockMapping>* builder);
// Returns true if none of |phi|'s users are in |block|.
bool CheckPhiUsers(ir::Instruction* phi, ir::BasicBlock* block);
// Returns |false| if |block| is not appropriate to transform. Only
// transforms blocks with two predecessors. Neither incoming block can be
// dominated by |block|. Both predecessors must share a common dominator that
// is terminated by a conditional branch.
bool CheckBlock(ir::BasicBlock* block, DominatorAnalysis* dominators,
ir::BasicBlock** common);
};
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_IF_CONVERSION_H_

View File

@ -123,6 +123,37 @@ class InstructionBuilder {
return AddInstruction(std::move(phi_inst)); return AddInstruction(std::move(phi_inst));
} }
// Creates a select instruction.
// |type| must match the types of |true_value| and |false_value|. It is up to
// the caller to ensure that |cond| is a correct type (bool or vector of
// bool) for |type|.
ir::Instruction* AddSelect(uint32_t type, uint32_t cond, uint32_t true_value,
uint32_t false_value) {
std::unique_ptr<ir::Instruction> select(new ir::Instruction(
GetContext(), SpvOpSelect, type, GetContext()->TakeNextId(),
std::initializer_list<ir::Operand>{
{SPV_OPERAND_TYPE_ID, {cond}},
{SPV_OPERAND_TYPE_ID, {true_value}},
{SPV_OPERAND_TYPE_ID, {false_value}}}));
return AddInstruction(std::move(select));
}
// Create a composite construct.
// |type| should be a composite type and the number of elements it has should
// match the size od |ids|.
ir::Instruction* AddCompositeConstruct(uint32_t type,
const std::vector<uint32_t>& ids) {
std::vector<ir::Operand> ops;
for (auto id : ids) {
ops.emplace_back(SPV_OPERAND_TYPE_ID,
std::initializer_list<uint32_t>{id});
}
std::unique_ptr<ir::Instruction> construct(
new ir::Instruction(GetContext(), SpvOpCompositeConstruct, type,
GetContext()->TakeNextId(), ops));
return AddInstruction(std::move(construct));
}
// Inserts the new instruction before the insertion point. // Inserts the new instruction before the insertion point.
ir::Instruction* AddInstruction(std::unique_ptr<ir::Instruction>&& insn) { ir::Instruction* AddInstruction(std::unique_ptr<ir::Instruction>&& insn) {
ir::Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn)); ir::Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));

View File

@ -125,6 +125,8 @@ Optimizer& Optimizer::RegisterPerformancePasses() {
.RegisterPass(CreateCCPPass()) .RegisterPass(CreateCCPPass())
.RegisterPass(CreateAggressiveDCEPass()) .RegisterPass(CreateAggressiveDCEPass())
.RegisterPass(CreateDeadBranchElimPass()) .RegisterPass(CreateDeadBranchElimPass())
.RegisterPass(CreateIfConversionPass())
.RegisterPass(CreateAggressiveDCEPass())
.RegisterPass(CreateBlockMergePass()) .RegisterPass(CreateBlockMergePass())
.RegisterPass(CreateInsertExtractElimPass()) .RegisterPass(CreateInsertExtractElimPass())
.RegisterPass(CreateRedundancyEliminationPass()) .RegisterPass(CreateRedundancyEliminationPass())
@ -147,6 +149,8 @@ Optimizer& Optimizer::RegisterSizePasses() {
.RegisterPass(CreateCCPPass()) .RegisterPass(CreateCCPPass())
.RegisterPass(CreateAggressiveDCEPass()) .RegisterPass(CreateAggressiveDCEPass())
.RegisterPass(CreateDeadBranchElimPass()) .RegisterPass(CreateDeadBranchElimPass())
.RegisterPass(CreateIfConversionPass())
.RegisterPass(CreateAggressiveDCEPass())
.RegisterPass(CreateBlockMergePass()) .RegisterPass(CreateBlockMergePass())
.RegisterPass(CreateInsertExtractElimPass()) .RegisterPass(CreateInsertExtractElimPass())
.RegisterPass(CreateRedundancyEliminationPass()) .RegisterPass(CreateRedundancyEliminationPass())
@ -354,4 +358,9 @@ Optimizer::PassToken CreateWorkaround1209Pass() {
MakeUnique<opt::Workaround1209>()); MakeUnique<opt::Workaround1209>());
} }
Optimizer::PassToken CreateIfConversionPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::IfConversion>());
}
} // namespace spvtools } // namespace spvtools

View File

@ -30,6 +30,7 @@
#include "flatten_decoration_pass.h" #include "flatten_decoration_pass.h"
#include "fold_spec_constant_op_and_composite_pass.h" #include "fold_spec_constant_op_and_composite_pass.h"
#include "freeze_spec_constant_value_pass.h" #include "freeze_spec_constant_value_pass.h"
#include "if_conversion.h"
#include "inline_exhaustive_pass.h" #include "inline_exhaustive_pass.h"
#include "inline_opaque_pass.h" #include "inline_opaque_pass.h"
#include "insert_extract_elim.h" #include "insert_extract_elim.h"

View File

@ -267,6 +267,11 @@ add_spvtools_unittest(TARGET pass_workaround1209
LIBS SPIRV-Tools-opt LIBS SPIRV-Tools-opt
) )
add_spvtools_unittest(TARGET pass_if_conversion
SRCS if_conversion_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt
)
add_spvtools_unittest(TARGET ir_builder add_spvtools_unittest(TARGET ir_builder
SRCS ir_builder.cpp SRCS ir_builder.cpp
LIBS SPIRV-Tools-opt LIBS SPIRV-Tools-opt

View File

@ -72,3 +72,8 @@ add_spvtools_unittest(TARGET dominator_generated
generated.cpp generated.cpp
LIBS SPIRV-Tools-opt LIBS SPIRV-Tools-opt
) )
add_spvtools_unittest(TARGET dominator_common_dominators
SRCS common_dominators.cpp
LIBS SPIRV-Tools-opt
)

View File

@ -0,0 +1,151 @@
// Copyright (c) 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "opt/build_module.h"
#include "opt/ir_context.h"
namespace {
using namespace spvtools;
using CommonDominatorsTest = ::testing::Test;
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%1 = OpLabel
OpBranch %2
%2 = OpLabel
OpLoopMerge %3 %4 None
OpBranch %5
%5 = OpLabel
OpBranchConditional %true %3 %4
%4 = OpLabel
OpBranch %2
%3 = OpLabel
OpSelectionMerge %6 None
OpBranchConditional %true %7 %8
%7 = OpLabel
OpBranch %6
%8 = OpLabel
OpBranch %9
%9 = OpLabel
OpBranch %6
%6 = OpLabel
OpBranch %10
%11 = OpLabel
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)";
ir::BasicBlock* GetBlock(uint32_t id, std::unique_ptr<ir::IRContext>& context) {
return context->get_instr_block(context->get_def_use_mgr()->GetDef(id));
}
TEST(CommonDominatorsTest, SameBlock) {
std::unique_ptr<ir::IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
EXPECT_NE(nullptr, context);
ir::CFG cfg(context->module());
opt::DominatorAnalysis* analysis =
context->GetDominatorAnalysis(&*context->module()->begin(), cfg);
for (auto& block : *context->module()->begin()) {
EXPECT_EQ(&block, analysis->CommonDominator(&block, &block));
}
}
TEST(CommonDominatorsTest, ParentAndChild) {
std::unique_ptr<ir::IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
EXPECT_NE(nullptr, context);
ir::CFG cfg(context->module());
opt::DominatorAnalysis* analysis =
context->GetDominatorAnalysis(&*context->module()->begin(), cfg);
EXPECT_EQ(
GetBlock(1u, context),
analysis->CommonDominator(GetBlock(1u, context), GetBlock(2u, context)));
EXPECT_EQ(
GetBlock(2u, context),
analysis->CommonDominator(GetBlock(2u, context), GetBlock(5u, context)));
EXPECT_EQ(
GetBlock(1u, context),
analysis->CommonDominator(GetBlock(1u, context), GetBlock(5u, context)));
}
TEST(CommonDominatorsTest, BranchSplit) {
std::unique_ptr<ir::IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
EXPECT_NE(nullptr, context);
ir::CFG cfg(context->module());
opt::DominatorAnalysis* analysis =
context->GetDominatorAnalysis(&*context->module()->begin(), cfg);
EXPECT_EQ(
GetBlock(3u, context),
analysis->CommonDominator(GetBlock(7u, context), GetBlock(8u, context)));
EXPECT_EQ(
GetBlock(3u, context),
analysis->CommonDominator(GetBlock(7u, context), GetBlock(9u, context)));
}
TEST(CommonDominatorsTest, LoopContinueAndMerge) {
std::unique_ptr<ir::IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
EXPECT_NE(nullptr, context);
ir::CFG cfg(context->module());
opt::DominatorAnalysis* analysis =
context->GetDominatorAnalysis(&*context->module()->begin(), cfg);
EXPECT_EQ(
GetBlock(5u, context),
analysis->CommonDominator(GetBlock(3u, context), GetBlock(4u, context)));
}
TEST(CommonDominatorsTest, NoCommonDominator) {
std::unique_ptr<ir::IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
EXPECT_NE(nullptr, context);
ir::CFG cfg(context->module());
opt::DominatorAnalysis* analysis =
context->GetDominatorAnalysis(&*context->module()->begin(), cfg);
EXPECT_EQ(nullptr, analysis->CommonDominator(GetBlock(10u, context),
GetBlock(11u, context)));
EXPECT_EQ(nullptr, analysis->CommonDominator(GetBlock(11u, context),
GetBlock(6u, context)));
}
} // anonymous namespace

View File

@ -0,0 +1,332 @@
// Copyright (c) 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "assembly_builder.h"
#include "gmock/gmock.h"
#include "pass_fixture.h"
#include "pass_utils.h"
namespace {
using namespace spvtools;
using IfConversionTest = PassTest<::testing::Test>;
#ifdef SPIRV_EFFCEE
TEST_F(IfConversionTest, TestSimpleIfThenElse) {
const std::string text = R"(
; CHECK: OpSelectionMerge [[merge:%\w+]]
; CHECK: [[merge]] = OpLabel
; CHECK-NOT: OpPhi
; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
; CHECK OpStore {{%\w+}} [[sel]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "func" %2
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%_ptr_Output_uint = OpTypePointer Output %uint
%2 = OpVariable %_ptr_Output_uint Output
%11 = OpTypeFunction %void
%1 = OpFunction %void None %11
%12 = OpLabel
OpSelectionMerge %14 None
OpBranchConditional %true %15 %16
%15 = OpLabel
OpBranch %14
%16 = OpLabel
OpBranch %14
%14 = OpLabel
%18 = OpPhi %uint %uint_0 %15 %uint_1 %16
OpStore %2 %18
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<opt::IfConversion>(text, true);
}
TEST_F(IfConversionTest, TestSimpleHalfIfTrue) {
const std::string text = R"(
; CHECK: OpSelectionMerge [[merge:%\w+]]
; CHECK: [[merge]] = OpLabel
; CHECK-NOT: OpPhi
; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
; CHECK OpStore {{%\w+}} [[sel]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "func" %2
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%_ptr_Output_uint = OpTypePointer Output %uint
%2 = OpVariable %_ptr_Output_uint Output
%11 = OpTypeFunction %void
%1 = OpFunction %void None %11
%12 = OpLabel
OpSelectionMerge %14 None
OpBranchConditional %true %15 %14
%15 = OpLabel
OpBranch %14
%14 = OpLabel
%18 = OpPhi %uint %uint_0 %15 %uint_1 %12
OpStore %2 %18
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<opt::IfConversion>(text, true);
}
TEST_F(IfConversionTest, TestSimpleHalfIfExtraBlock) {
const std::string text = R"(
; CHECK: OpSelectionMerge [[merge:%\w+]]
; CHECK: [[merge]] = OpLabel
; CHECK-NOT: OpPhi
; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
; CHECK OpStore {{%\w+}} [[sel]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "func" %2
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%_ptr_Output_uint = OpTypePointer Output %uint
%2 = OpVariable %_ptr_Output_uint Output
%11 = OpTypeFunction %void
%1 = OpFunction %void None %11
%12 = OpLabel
OpSelectionMerge %14 None
OpBranchConditional %true %15 %14
%15 = OpLabel
OpBranch %16
%16 = OpLabel
OpBranch %14
%14 = OpLabel
%18 = OpPhi %uint %uint_0 %15 %uint_1 %12
OpStore %2 %18
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<opt::IfConversion>(text, true);
}
TEST_F(IfConversionTest, TestSimpleHalfIfFalse) {
const std::string text = R"(
; CHECK: OpSelectionMerge [[merge:%\w+]]
; CHECK: [[merge]] = OpLabel
; CHECK-NOT: OpPhi
; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
; CHECK OpStore {{%\w+}} [[sel]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "func" %2
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%_ptr_Output_uint = OpTypePointer Output %uint
%2 = OpVariable %_ptr_Output_uint Output
%11 = OpTypeFunction %void
%1 = OpFunction %void None %11
%12 = OpLabel
OpSelectionMerge %14 None
OpBranchConditional %true %14 %15
%15 = OpLabel
OpBranch %14
%14 = OpLabel
%18 = OpPhi %uint %uint_0 %12 %uint_1 %15
OpStore %2 %18
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<opt::IfConversion>(text, true);
}
TEST_F(IfConversionTest, TestVectorSplat) {
const std::string text = R"(
; CHECK: [[bool_vec:%\w+]] = OpTypeVector %bool 2
; CHECK: OpSelectionMerge [[merge:%\w+]]
; CHECK: [[merge]] = OpLabel
; CHECK-NOT: OpPhi
; CHECK: [[comp:%\w+]] = OpCompositeConstruct [[bool_vec]] %true %true
; CHECK: [[sel:%\w+]] = OpSelect {{%\w+}} [[comp]]
; CHECK OpStore {{%\w+}} [[sel]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "func" %2
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_vec2 = OpTypeVector %uint 2
%vec2_01 = OpConstantComposite %uint_vec2 %uint_0 %uint_1
%vec2_10 = OpConstantComposite %uint_vec2 %uint_1 %uint_0
%_ptr_Output_uint = OpTypePointer Output %uint_vec2
%2 = OpVariable %_ptr_Output_uint Output
%11 = OpTypeFunction %void
%1 = OpFunction %void None %11
%12 = OpLabel
OpSelectionMerge %14 None
OpBranchConditional %true %15 %16
%15 = OpLabel
OpBranch %14
%16 = OpLabel
OpBranch %14
%14 = OpLabel
%18 = OpPhi %uint_vec2 %vec2_01 %15 %vec2_10 %16
OpStore %2 %18
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<opt::IfConversion>(text, true);
}
#endif // SPIRV_EFFCEE
TEST_F(IfConversionTest, NoCommonDominator) {
const std::string text = R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "func" %2
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%_ptr_Output_uint = OpTypePointer Output %uint
%2 = OpVariable %_ptr_Output_uint Output
%8 = OpTypeFunction %void
%1 = OpFunction %void None %8
%9 = OpLabel
OpBranch %10
%11 = OpLabel
OpBranch %10
%10 = OpLabel
%12 = OpPhi %uint %uint_0 %9 %uint_1 %11
OpStore %2 %12
OpReturn
OpFunctionEnd
)";
SinglePassRunAndCheck<opt::IfConversion>(text, text, true, true);
}
TEST_F(IfConversionTest, LoopUntouched) {
const std::string text = R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "func" %2
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%_ptr_Output_uint = OpTypePointer Output %uint
%2 = OpVariable %_ptr_Output_uint Output
%8 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%1 = OpFunction %void None %8
%11 = OpLabel
OpBranch %12
%12 = OpLabel
%13 = OpPhi %uint %uint_0 %11 %uint_1 %12
OpLoopMerge %14 %12 None
OpBranchConditional %true %14 %12
%14 = OpLabel
OpStore %2 %13
OpReturn
OpFunctionEnd
)";
SinglePassRunAndCheck<opt::IfConversion>(text, text, true, true);
}
TEST_F(IfConversionTest, TooManyPredecessors) {
const std::string text = R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "func" %2
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%_ptr_Output_uint = OpTypePointer Output %uint
%2 = OpVariable %_ptr_Output_uint Output
%8 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%1 = OpFunction %void None %8
%11 = OpLabel
OpSelectionMerge %12 None
OpBranchConditional %true %13 %12
%13 = OpLabel
OpBranchConditional %true %14 %15
%14 = OpLabel
OpBranch %12
%15 = OpLabel
OpBranch %12
%12 = OpLabel
%16 = OpPhi %uint %uint_0 %11 %uint_0 %14 %uint_1 %15
OpStore %2 %16
OpReturn
OpFunctionEnd
)";
SinglePassRunAndCheck<opt::IfConversion>(text, text, true, true);
}
TEST_F(IfConversionTest, NoCodeMotion) {
const std::string text = R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "func" %2
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%_ptr_Output_uint = OpTypePointer Output %uint
%2 = OpVariable %_ptr_Output_uint Output
%8 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%1 = OpFunction %void None %8
%11 = OpLabel
OpSelectionMerge %12 None
OpBranchConditional %true %13 %12
%13 = OpLabel
%14 = OpIAdd %uint %uint_0 %uint_1
OpBranch %12
%12 = OpLabel
%15 = OpPhi %uint %uint_0 %11 %14 %13
OpStore %2 %15
OpReturn
OpFunctionEnd
)";
SinglePassRunAndCheck<opt::IfConversion>(text, text, true, true);
}
} // anonymous namespace

View File

@ -226,6 +226,75 @@ TEST_F(IRBuilderTest, TestCondBranchAddition) {
} }
} }
TEST_F(IRBuilderTest, AddSelect) {
const std::string text = R"(
; CHECK: [[bool:%\w+]] = OpTypeBool
; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
; CHECK: [[true:%\w+]] = OpConstantTrue [[bool]]
; CHECK: [[u0:%\w+]] = OpConstant [[uint]] 0
; CHECK: [[u1:%\w+]] = OpConstant [[uint]] 1
; CHECK: OpSelect [[uint]] [[true]] [[u0]] [[u1]]
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
%1 = OpTypeVoid
%2 = OpTypeBool
%3 = OpTypeInt 32 0
%4 = OpConstantTrue %2
%5 = OpConstant %3 0
%6 = OpConstant %3 1
%7 = OpTypeFunction %1
%8 = OpFunction %1 None %7
%9 = OpLabel
OpReturn
OpFunctionEnd
)";
std::unique_ptr<ir::IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
EXPECT_NE(nullptr, context);
opt::InstructionBuilder<> builder(
context.get(), &*context->module()->begin()->begin()->begin());
EXPECT_NE(nullptr, builder.AddSelect(3u, 4u, 5u, 6u));
Match(text, context.get());
}
TEST_F(IRBuilderTest, AddCompositeConstruct) {
const std::string text = R"(
; CHECK: [[uint:%\w+]] = OpTypeInt
; CHECK: [[u0:%\w+]] = OpConstant [[uint]] 0
; CHECK: [[u1:%\w+]] = OpConstant [[uint]] 1
; CHECK: [[struct:%\w+]] = OpTypeStruct [[uint]] [[uint]] [[uint]] [[uint]]
; CHECK: OpCompositeConstruct [[struct]] [[u0]] [[u1]] [[u1]] [[u0]]
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
%1 = OpTypeVoid
%2 = OpTypeInt 32 0
%3 = OpConstant %2 0
%4 = OpConstant %2 1
%5 = OpTypeStruct %2 %2 %2 %2
%6 = OpTypeFunction %1
%7 = OpFunction %1 None %6
%8 = OpLabel
OpReturn
OpFunctionEnd
)";
std::unique_ptr<ir::IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
EXPECT_NE(nullptr, context);
opt::InstructionBuilder<> builder(
context.get(), &*context->module()->begin()->begin()->begin());
std::vector<uint32_t> ids = {3u, 4u, 4u, 3u};
EXPECT_NE(nullptr, builder.AddCompositeConstruct(5u, ids));
Match(text, context.get());
}
#endif // SPIRV_EFFCEE #endif // SPIRV_EFFCEE
} // anonymous namespace } // anonymous namespace

View File

@ -148,6 +148,8 @@ Options (in lexicographical order):
--freeze-spec-const --freeze-spec-const
Freeze the values of specialization constants to their default Freeze the values of specialization constants to their default
values. values.
--if-conversion
Convert if-then-else like assignments into OpSelect.
--inline-entry-points-exhaustive --inline-entry-points-exhaustive
Exhaustively inline all function calls in entry point call tree Exhaustively inline all function calls in entry point call tree
functions. Currently does not inline calls to functions with functions. Currently does not inline calls to functions with
@ -391,6 +393,8 @@ OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
"error: Expected a string of <spec id>:<default value> pairs."); "error: Expected a string of <spec id>:<default value> pairs.");
return {OPT_STOP, 1}; return {OPT_STOP, 1};
} }
} else if (0 == strcmp(cur_arg, "--if-conversion")) {
optimizer->RegisterPass(CreateIfConversionPass());
} else if (0 == strcmp(cur_arg, "--freeze-spec-const")) { } else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
optimizer->RegisterPass(CreateFreezeSpecConstantValuePass()); optimizer->RegisterPass(CreateFreezeSpecConstantValuePass());
} else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) { } else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) {

View File

@ -30,6 +30,7 @@ import sys
AUTHORS = ['The Khronos Group Inc.', AUTHORS = ['The Khronos Group Inc.',
'LunarG Inc.', 'LunarG Inc.',
'Google Inc.', 'Google Inc.',
'Google LLC',
'Pierre Moreau'] 'Pierre Moreau']
CURRENT_YEAR='2018' CURRENT_YEAR='2018'