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 \
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 \
// 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_
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
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
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
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;
// 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.
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
--- /dev/null
+// 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
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_;
};
--- /dev/null
+// 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
--- /dev/null
+// 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_
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));
.RegisterPass(CreateCCPPass())
.RegisterPass(CreateAggressiveDCEPass())
.RegisterPass(CreateDeadBranchElimPass())
+ .RegisterPass(CreateIfConversionPass())
+ .RegisterPass(CreateAggressiveDCEPass())
.RegisterPass(CreateBlockMergePass())
.RegisterPass(CreateInsertExtractElimPass())
.RegisterPass(CreateRedundancyEliminationPass())
.RegisterPass(CreateCCPPass())
.RegisterPass(CreateAggressiveDCEPass())
.RegisterPass(CreateDeadBranchElimPass())
+ .RegisterPass(CreateIfConversionPass())
+ .RegisterPass(CreateAggressiveDCEPass())
.RegisterPass(CreateBlockMergePass())
.RegisterPass(CreateInsertExtractElimPass())
.RegisterPass(CreateRedundancyEliminationPass())
MakeUnique<opt::Workaround1209>());
}
+Optimizer::PassToken CreateIfConversionPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::IfConversion>());
+}
+
} // namespace spvtools
#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"
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
generated.cpp
LIBS SPIRV-Tools-opt
)
+
+add_spvtools_unittest(TARGET dominator_common_dominators
+ SRCS common_dominators.cpp
+ LIBS SPIRV-Tools-opt
+)
--- /dev/null
+// 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
--- /dev/null
+// 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
}
}
+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
--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
"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")) {
AUTHORS = ['The Khronos Group Inc.',
'LunarG Inc.',
'Google Inc.',
+ 'Google LLC',
'Pierre Moreau']
CURRENT_YEAR='2018'