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/decoration_manager.cpp \
source/opt/def_use_manager.cpp \
source/opt/dominator_analysis.cpp \
source/opt/dominator_tree.cpp \
source/opt/eliminate_dead_constant_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/freeze_spec_constant_value_pass.cpp \
source/opt/function.cpp \
source/opt/if_conversion.cpp \
source/opt/inline_pass.cpp \
source/opt/inline_exhaustive_pass.cpp \
source/opt/inline_opaque_pass.cpp \

View File

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

View File

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

View File

@ -123,6 +123,12 @@ class BasicBlock {
inline void ForEachPhiInst(const std::function<void(Instruction*)>& f,
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
void ForEachSuccessorLabel(
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.
bool HasPhiInstructions() {
int count = 0;
ForEachPhiInst([&count](ir::Instruction*) {
++count;
return;
});
return count > 0;
return !WhileEachPhiInst([](ir::Instruction*) { return false; });
}
// Return true if this block is a loop header block.
@ -244,12 +245,23 @@ inline void BasicBlock::ForEachInst(
run_on_debug_line_insts);
}
inline void BasicBlock::ForEachPhiInst(
const std::function<void(Instruction*)>& f, bool run_on_debug_line_insts) {
inline bool BasicBlock::WhileEachPhiInst(
const std::function<bool(Instruction*)>& f, bool run_on_debug_line_insts) {
for (auto& inst : insts_) {
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

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);
}
// 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:
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));
}
// 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.
ir::Instruction* AddInstruction(std::unique_ptr<ir::Instruction>&& insn) {
ir::Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));

View File

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

View File

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

View File

@ -267,6 +267,11 @@ add_spvtools_unittest(TARGET pass_workaround1209
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
SRCS ir_builder.cpp
LIBS SPIRV-Tools-opt

View File

@ -72,3 +72,8 @@ add_spvtools_unittest(TARGET dominator_generated
generated.cpp
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
} // anonymous namespace

View File

@ -148,6 +148,8 @@ Options (in lexicographical order):
--freeze-spec-const
Freeze the values of specialization constants to their default
values.
--if-conversion
Convert if-then-else like assignments into OpSelect.
--inline-entry-points-exhaustive
Exhaustively inline all function calls in entry point call tree
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.");
return {OPT_STOP, 1};
}
} else if (0 == strcmp(cur_arg, "--if-conversion")) {
optimizer->RegisterPass(CreateIfConversionPass());
} else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
optimizer->RegisterPass(CreateFreezeSpecConstantValuePass());
} else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) {

View File

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