mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-25 21:10:04 +00:00
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:
parent
b2eb840468
commit
2e93e806e4
@ -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 \
|
||||
|
@ -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_
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
41
source/opt/dominator_analysis.cpp
Normal file
41
source/opt/dominator_analysis.cpp
Normal 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
|
@ -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_;
|
||||
};
|
||||
|
188
source/opt/if_conversion.cpp
Normal file
188
source/opt/if_conversion.cpp
Normal 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
|
78
source/opt/if_conversion.h
Normal file
78
source/opt/if_conversion.h
Normal 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_
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
151
test/opt/dominator_tree/common_dominators.cpp
Normal file
151
test/opt/dominator_tree/common_dominators.cpp
Normal 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
|
332
test/opt/if_conversion_test.cpp
Normal file
332
test/opt/if_conversion_test.cpp
Normal 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
|
@ -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
|
||||
|
@ -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")) {
|
||||
|
@ -30,6 +30,7 @@ import sys
|
||||
AUTHORS = ['The Khronos Group Inc.',
|
||||
'LunarG Inc.',
|
||||
'Google Inc.',
|
||||
'Google LLC',
|
||||
'Pierre Moreau']
|
||||
CURRENT_YEAR='2018'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user