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/dead_variable_elimination.cpp \
|
||||||
source/opt/decoration_manager.cpp \
|
source/opt/decoration_manager.cpp \
|
||||||
source/opt/def_use_manager.cpp \
|
source/opt/def_use_manager.cpp \
|
||||||
|
source/opt/dominator_analysis.cpp \
|
||||||
source/opt/dominator_tree.cpp \
|
source/opt/dominator_tree.cpp \
|
||||||
source/opt/eliminate_dead_constant_pass.cpp \
|
source/opt/eliminate_dead_constant_pass.cpp \
|
||||||
source/opt/eliminate_dead_functions_pass.cpp \
|
source/opt/eliminate_dead_functions_pass.cpp \
|
||||||
@ -77,6 +78,7 @@ SPVTOOLS_OPT_SRC_FILES := \
|
|||||||
source/opt/fold_spec_constant_op_and_composite_pass.cpp \
|
source/opt/fold_spec_constant_op_and_composite_pass.cpp \
|
||||||
source/opt/freeze_spec_constant_value_pass.cpp \
|
source/opt/freeze_spec_constant_value_pass.cpp \
|
||||||
source/opt/function.cpp \
|
source/opt/function.cpp \
|
||||||
|
source/opt/if_conversion.cpp \
|
||||||
source/opt/inline_pass.cpp \
|
source/opt/inline_pass.cpp \
|
||||||
source/opt/inline_exhaustive_pass.cpp \
|
source/opt/inline_exhaustive_pass.cpp \
|
||||||
source/opt/inline_opaque_pass.cpp \
|
source/opt/inline_opaque_pass.cpp \
|
||||||
|
@ -482,6 +482,9 @@ Optimizer::PassToken CreateCCPPass();
|
|||||||
// Current workaround: Avoid OpUnreachable instructions in loops.
|
// Current workaround: Avoid OpUnreachable instructions in loops.
|
||||||
Optimizer::PassToken CreateWorkaround1209Pass();
|
Optimizer::PassToken CreateWorkaround1209Pass();
|
||||||
|
|
||||||
|
// Creates a pass that converts if-then-else like assignments into OpSelect.
|
||||||
|
Optimizer::PassToken CreateIfConversionPass();
|
||||||
|
|
||||||
} // namespace spvtools
|
} // namespace spvtools
|
||||||
|
|
||||||
#endif // SPIRV_TOOLS_OPTIMIZER_HPP_
|
#endif // SPIRV_TOOLS_OPTIMIZER_HPP_
|
||||||
|
@ -36,6 +36,7 @@ add_library(SPIRV-Tools-opt
|
|||||||
fold_spec_constant_op_and_composite_pass.h
|
fold_spec_constant_op_and_composite_pass.h
|
||||||
freeze_spec_constant_value_pass.h
|
freeze_spec_constant_value_pass.h
|
||||||
function.h
|
function.h
|
||||||
|
if_conversion.h
|
||||||
inline_exhaustive_pass.h
|
inline_exhaustive_pass.h
|
||||||
inline_opaque_pass.h
|
inline_opaque_pass.h
|
||||||
inline_pass.h
|
inline_pass.h
|
||||||
@ -87,6 +88,7 @@ add_library(SPIRV-Tools-opt
|
|||||||
dead_variable_elimination.cpp
|
dead_variable_elimination.cpp
|
||||||
decoration_manager.cpp
|
decoration_manager.cpp
|
||||||
def_use_manager.cpp
|
def_use_manager.cpp
|
||||||
|
dominator_analysis.cpp
|
||||||
dominator_tree.cpp
|
dominator_tree.cpp
|
||||||
eliminate_dead_constant_pass.cpp
|
eliminate_dead_constant_pass.cpp
|
||||||
eliminate_dead_functions_pass.cpp
|
eliminate_dead_functions_pass.cpp
|
||||||
@ -96,6 +98,7 @@ add_library(SPIRV-Tools-opt
|
|||||||
fold_spec_constant_op_and_composite_pass.cpp
|
fold_spec_constant_op_and_composite_pass.cpp
|
||||||
freeze_spec_constant_value_pass.cpp
|
freeze_spec_constant_value_pass.cpp
|
||||||
function.cpp
|
function.cpp
|
||||||
|
if_conversion.cpp
|
||||||
inline_exhaustive_pass.cpp
|
inline_exhaustive_pass.cpp
|
||||||
inline_opaque_pass.cpp
|
inline_opaque_pass.cpp
|
||||||
inline_pass.cpp
|
inline_pass.cpp
|
||||||
|
@ -123,6 +123,12 @@ class BasicBlock {
|
|||||||
inline void ForEachPhiInst(const std::function<void(Instruction*)>& f,
|
inline void ForEachPhiInst(const std::function<void(Instruction*)>& f,
|
||||||
bool run_on_debug_line_insts = false);
|
bool run_on_debug_line_insts = false);
|
||||||
|
|
||||||
|
// Runs the given function |f| on each Phi instruction in this basic block,
|
||||||
|
// and optionally on the debug line instructions that might precede them. If
|
||||||
|
// |f| returns false, iteration is terminated and this function return false.
|
||||||
|
inline bool WhileEachPhiInst(const std::function<bool(Instruction*)>& f,
|
||||||
|
bool run_on_debug_line_insts = false);
|
||||||
|
|
||||||
// Runs the given function |f| on each label id of each successor block
|
// Runs the given function |f| on each label id of each successor block
|
||||||
void ForEachSuccessorLabel(
|
void ForEachSuccessorLabel(
|
||||||
const std::function<void(const uint32_t)>& f) const;
|
const std::function<void(const uint32_t)>& f) const;
|
||||||
@ -135,12 +141,7 @@ class BasicBlock {
|
|||||||
|
|
||||||
// Returns true if this basic block has any Phi instructions.
|
// Returns true if this basic block has any Phi instructions.
|
||||||
bool HasPhiInstructions() {
|
bool HasPhiInstructions() {
|
||||||
int count = 0;
|
return !WhileEachPhiInst([](ir::Instruction*) { return false; });
|
||||||
ForEachPhiInst([&count](ir::Instruction*) {
|
|
||||||
++count;
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
return count > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true if this block is a loop header block.
|
// Return true if this block is a loop header block.
|
||||||
@ -244,12 +245,23 @@ inline void BasicBlock::ForEachInst(
|
|||||||
run_on_debug_line_insts);
|
run_on_debug_line_insts);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void BasicBlock::ForEachPhiInst(
|
inline bool BasicBlock::WhileEachPhiInst(
|
||||||
const std::function<void(Instruction*)>& f, bool run_on_debug_line_insts) {
|
const std::function<bool(Instruction*)>& f, bool run_on_debug_line_insts) {
|
||||||
for (auto& inst : insts_) {
|
for (auto& inst : insts_) {
|
||||||
if (inst.opcode() != SpvOpPhi) break;
|
if (inst.opcode() != SpvOpPhi) break;
|
||||||
inst.ForEachInst(f, run_on_debug_line_insts);
|
if (!inst.WhileEachInst(f, run_on_debug_line_insts)) return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void BasicBlock::ForEachPhiInst(
|
||||||
|
const std::function<void(Instruction*)>& f, bool run_on_debug_line_insts) {
|
||||||
|
WhileEachPhiInst(
|
||||||
|
[&f](Instruction* inst) {
|
||||||
|
f(inst);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
run_on_debug_line_insts);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ir
|
} // namespace ir
|
||||||
|
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);
|
tree_.Visit(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the most immediate basic block that dominates both |b1| and |b2|.
|
||||||
|
// If there is no such basic block, nullptr is returned.
|
||||||
|
ir::BasicBlock* CommonDominator(ir::BasicBlock* b1, ir::BasicBlock* b2) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DominatorTree tree_;
|
DominatorTree tree_;
|
||||||
};
|
};
|
||||||
|
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));
|
return AddInstruction(std::move(phi_inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a select instruction.
|
||||||
|
// |type| must match the types of |true_value| and |false_value|. It is up to
|
||||||
|
// the caller to ensure that |cond| is a correct type (bool or vector of
|
||||||
|
// bool) for |type|.
|
||||||
|
ir::Instruction* AddSelect(uint32_t type, uint32_t cond, uint32_t true_value,
|
||||||
|
uint32_t false_value) {
|
||||||
|
std::unique_ptr<ir::Instruction> select(new ir::Instruction(
|
||||||
|
GetContext(), SpvOpSelect, type, GetContext()->TakeNextId(),
|
||||||
|
std::initializer_list<ir::Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {cond}},
|
||||||
|
{SPV_OPERAND_TYPE_ID, {true_value}},
|
||||||
|
{SPV_OPERAND_TYPE_ID, {false_value}}}));
|
||||||
|
return AddInstruction(std::move(select));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a composite construct.
|
||||||
|
// |type| should be a composite type and the number of elements it has should
|
||||||
|
// match the size od |ids|.
|
||||||
|
ir::Instruction* AddCompositeConstruct(uint32_t type,
|
||||||
|
const std::vector<uint32_t>& ids) {
|
||||||
|
std::vector<ir::Operand> ops;
|
||||||
|
for (auto id : ids) {
|
||||||
|
ops.emplace_back(SPV_OPERAND_TYPE_ID,
|
||||||
|
std::initializer_list<uint32_t>{id});
|
||||||
|
}
|
||||||
|
std::unique_ptr<ir::Instruction> construct(
|
||||||
|
new ir::Instruction(GetContext(), SpvOpCompositeConstruct, type,
|
||||||
|
GetContext()->TakeNextId(), ops));
|
||||||
|
return AddInstruction(std::move(construct));
|
||||||
|
}
|
||||||
|
|
||||||
// Inserts the new instruction before the insertion point.
|
// Inserts the new instruction before the insertion point.
|
||||||
ir::Instruction* AddInstruction(std::unique_ptr<ir::Instruction>&& insn) {
|
ir::Instruction* AddInstruction(std::unique_ptr<ir::Instruction>&& insn) {
|
||||||
ir::Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
|
ir::Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
|
||||||
|
@ -125,6 +125,8 @@ Optimizer& Optimizer::RegisterPerformancePasses() {
|
|||||||
.RegisterPass(CreateCCPPass())
|
.RegisterPass(CreateCCPPass())
|
||||||
.RegisterPass(CreateAggressiveDCEPass())
|
.RegisterPass(CreateAggressiveDCEPass())
|
||||||
.RegisterPass(CreateDeadBranchElimPass())
|
.RegisterPass(CreateDeadBranchElimPass())
|
||||||
|
.RegisterPass(CreateIfConversionPass())
|
||||||
|
.RegisterPass(CreateAggressiveDCEPass())
|
||||||
.RegisterPass(CreateBlockMergePass())
|
.RegisterPass(CreateBlockMergePass())
|
||||||
.RegisterPass(CreateInsertExtractElimPass())
|
.RegisterPass(CreateInsertExtractElimPass())
|
||||||
.RegisterPass(CreateRedundancyEliminationPass())
|
.RegisterPass(CreateRedundancyEliminationPass())
|
||||||
@ -147,6 +149,8 @@ Optimizer& Optimizer::RegisterSizePasses() {
|
|||||||
.RegisterPass(CreateCCPPass())
|
.RegisterPass(CreateCCPPass())
|
||||||
.RegisterPass(CreateAggressiveDCEPass())
|
.RegisterPass(CreateAggressiveDCEPass())
|
||||||
.RegisterPass(CreateDeadBranchElimPass())
|
.RegisterPass(CreateDeadBranchElimPass())
|
||||||
|
.RegisterPass(CreateIfConversionPass())
|
||||||
|
.RegisterPass(CreateAggressiveDCEPass())
|
||||||
.RegisterPass(CreateBlockMergePass())
|
.RegisterPass(CreateBlockMergePass())
|
||||||
.RegisterPass(CreateInsertExtractElimPass())
|
.RegisterPass(CreateInsertExtractElimPass())
|
||||||
.RegisterPass(CreateRedundancyEliminationPass())
|
.RegisterPass(CreateRedundancyEliminationPass())
|
||||||
@ -354,4 +358,9 @@ Optimizer::PassToken CreateWorkaround1209Pass() {
|
|||||||
MakeUnique<opt::Workaround1209>());
|
MakeUnique<opt::Workaround1209>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optimizer::PassToken CreateIfConversionPass() {
|
||||||
|
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||||
|
MakeUnique<opt::IfConversion>());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace spvtools
|
} // namespace spvtools
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "flatten_decoration_pass.h"
|
#include "flatten_decoration_pass.h"
|
||||||
#include "fold_spec_constant_op_and_composite_pass.h"
|
#include "fold_spec_constant_op_and_composite_pass.h"
|
||||||
#include "freeze_spec_constant_value_pass.h"
|
#include "freeze_spec_constant_value_pass.h"
|
||||||
|
#include "if_conversion.h"
|
||||||
#include "inline_exhaustive_pass.h"
|
#include "inline_exhaustive_pass.h"
|
||||||
#include "inline_opaque_pass.h"
|
#include "inline_opaque_pass.h"
|
||||||
#include "insert_extract_elim.h"
|
#include "insert_extract_elim.h"
|
||||||
|
@ -267,6 +267,11 @@ add_spvtools_unittest(TARGET pass_workaround1209
|
|||||||
LIBS SPIRV-Tools-opt
|
LIBS SPIRV-Tools-opt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_spvtools_unittest(TARGET pass_if_conversion
|
||||||
|
SRCS if_conversion_test.cpp pass_utils.cpp
|
||||||
|
LIBS SPIRV-Tools-opt
|
||||||
|
)
|
||||||
|
|
||||||
add_spvtools_unittest(TARGET ir_builder
|
add_spvtools_unittest(TARGET ir_builder
|
||||||
SRCS ir_builder.cpp
|
SRCS ir_builder.cpp
|
||||||
LIBS SPIRV-Tools-opt
|
LIBS SPIRV-Tools-opt
|
||||||
|
@ -72,3 +72,8 @@ add_spvtools_unittest(TARGET dominator_generated
|
|||||||
generated.cpp
|
generated.cpp
|
||||||
LIBS SPIRV-Tools-opt
|
LIBS SPIRV-Tools-opt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_spvtools_unittest(TARGET dominator_common_dominators
|
||||||
|
SRCS common_dominators.cpp
|
||||||
|
LIBS SPIRV-Tools-opt
|
||||||
|
)
|
||||||
|
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
|
#endif // SPIRV_EFFCEE
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
@ -148,6 +148,8 @@ Options (in lexicographical order):
|
|||||||
--freeze-spec-const
|
--freeze-spec-const
|
||||||
Freeze the values of specialization constants to their default
|
Freeze the values of specialization constants to their default
|
||||||
values.
|
values.
|
||||||
|
--if-conversion
|
||||||
|
Convert if-then-else like assignments into OpSelect.
|
||||||
--inline-entry-points-exhaustive
|
--inline-entry-points-exhaustive
|
||||||
Exhaustively inline all function calls in entry point call tree
|
Exhaustively inline all function calls in entry point call tree
|
||||||
functions. Currently does not inline calls to functions with
|
functions. Currently does not inline calls to functions with
|
||||||
@ -391,6 +393,8 @@ OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
|
|||||||
"error: Expected a string of <spec id>:<default value> pairs.");
|
"error: Expected a string of <spec id>:<default value> pairs.");
|
||||||
return {OPT_STOP, 1};
|
return {OPT_STOP, 1};
|
||||||
}
|
}
|
||||||
|
} else if (0 == strcmp(cur_arg, "--if-conversion")) {
|
||||||
|
optimizer->RegisterPass(CreateIfConversionPass());
|
||||||
} else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
|
} else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
|
||||||
optimizer->RegisterPass(CreateFreezeSpecConstantValuePass());
|
optimizer->RegisterPass(CreateFreezeSpecConstantValuePass());
|
||||||
} else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) {
|
} else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) {
|
||||||
|
@ -30,6 +30,7 @@ import sys
|
|||||||
AUTHORS = ['The Khronos Group Inc.',
|
AUTHORS = ['The Khronos Group Inc.',
|
||||||
'LunarG Inc.',
|
'LunarG Inc.',
|
||||||
'Google Inc.',
|
'Google Inc.',
|
||||||
|
'Google LLC',
|
||||||
'Pierre Moreau']
|
'Pierre Moreau']
|
||||||
CURRENT_YEAR='2018'
|
CURRENT_YEAR='2018'
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user