mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-25 04:50:04 +00:00
Allow hoisting code in if-conversion.
When doing if-conversion, we do not currently move code out of the side nodes. The reason for this is that it can increase the number of instructions that get executed because both side nods will have to be executed now. In this commit, we add code to move an instruction, and all of the instructions it depends on, out of a side node and into the header of the selection construct. However to keep the cost down, we only do it when the two values in the OpPhi node compute the same value. This way we have to move only one of the instructions and the other becomes unused most of the time. So no real extra cost. Makes the value number table an alalysis in the ir context. Added more opcodes to list of code motion safe opcodes. Fixes #1526.
This commit is contained in:
parent
1c2cbaf569
commit
7d01643132
@ -14,12 +14,15 @@
|
||||
|
||||
#include "if_conversion.h"
|
||||
|
||||
#include "value_number_table.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
Pass::Status IfConversion::Process(ir::IRContext* c) {
|
||||
InitializeProcessing(c);
|
||||
|
||||
const ValueNumberTable& vn_table = *context()->GetValueNumberTable();
|
||||
bool modified = false;
|
||||
std::vector<ir::Instruction*> to_kill;
|
||||
for (auto& func : *get_module()) {
|
||||
@ -41,7 +44,8 @@ Pass::Status IfConversion::Process(ir::IRContext* c) {
|
||||
ir::IRContext::kAnalysisDefUse |
|
||||
ir::IRContext::kAnalysisInstrToBlockMapping);
|
||||
block.ForEachPhiInst([this, &builder, &modified, &common, &to_kill,
|
||||
dominators, &block](ir::Instruction* phi) {
|
||||
dominators, &block,
|
||||
&vn_table](ir::Instruction* phi) {
|
||||
// This phi is not compatible, but subsequent phis might be.
|
||||
if (!CheckType(phi->type_id())) return;
|
||||
|
||||
@ -71,16 +75,47 @@ Pass::Status IfConversion::Process(ir::IRContext* c) {
|
||||
false_value = GetIncomingValue(phi, 0u);
|
||||
}
|
||||
|
||||
ir::BasicBlock* true_def_block = context()->get_instr_block(true_value);
|
||||
ir::BasicBlock* false_def_block =
|
||||
context()->get_instr_block(false_value);
|
||||
|
||||
uint32_t true_vn = vn_table.GetValueNumber(true_value);
|
||||
uint32_t false_vn = vn_table.GetValueNumber(false_value);
|
||||
if (true_vn != 0 && true_vn == false_vn) {
|
||||
ir::Instruction* inst_to_use = nullptr;
|
||||
|
||||
// Try to pick an instruction that is not in a side node. If we can't
|
||||
// pick either the true for false branch as long as they can be
|
||||
// legally moved.
|
||||
if (!true_def_block ||
|
||||
dominators->Dominates(true_def_block, &block)) {
|
||||
inst_to_use = true_value;
|
||||
} else if (!false_def_block ||
|
||||
dominators->Dominates(false_def_block, &block)) {
|
||||
inst_to_use = false_value;
|
||||
} else if (CanHoistInstruction(true_value, common, dominators)) {
|
||||
inst_to_use = true_value;
|
||||
} else if (CanHoistInstruction(false_value, common, dominators)) {
|
||||
inst_to_use = false_value;
|
||||
}
|
||||
|
||||
if (inst_to_use != nullptr) {
|
||||
modified = true;
|
||||
HoistInstruction(inst_to_use, common, dominators);
|
||||
context()->KillNamesAndDecorates(phi);
|
||||
context()->ReplaceAllUsesWith(phi->result_id(),
|
||||
inst_to_use->result_id());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
@ -183,5 +218,66 @@ ir::Instruction* IfConversion::GetIncomingValue(ir::Instruction* phi,
|
||||
return get_def_use_mgr()->GetDef(phi->GetSingleWordInOperand(in_index));
|
||||
}
|
||||
|
||||
void IfConversion::HoistInstruction(ir::Instruction* inst,
|
||||
ir::BasicBlock* target_block,
|
||||
DominatorAnalysis* dominators) {
|
||||
ir::BasicBlock* inst_block = context()->get_instr_block(inst);
|
||||
if (!inst_block) {
|
||||
// This is in the header, and dominates everything.
|
||||
return;
|
||||
}
|
||||
|
||||
if (dominators->Dominates(inst_block, target_block)) {
|
||||
// Already in position. No work to do.
|
||||
return;
|
||||
}
|
||||
|
||||
assert(inst->IsOpcodeCodeMotionSafe() &&
|
||||
"Trying to move an instruction that is not safe to move.");
|
||||
|
||||
// First hoist all instructions it depends on.
|
||||
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
|
||||
inst->ForEachInId(
|
||||
[this, target_block, def_use_mgr, dominators](uint32_t* id) {
|
||||
ir::Instruction* operand_inst = def_use_mgr->GetDef(*id);
|
||||
HoistInstruction(operand_inst, target_block, dominators);
|
||||
});
|
||||
|
||||
ir::Instruction* insertion_pos = target_block->terminator();
|
||||
if ((insertion_pos)->PreviousNode()->opcode() == SpvOpSelectionMerge) {
|
||||
insertion_pos = insertion_pos->PreviousNode();
|
||||
}
|
||||
inst->RemoveFromList();
|
||||
insertion_pos->InsertBefore(std::unique_ptr<ir::Instruction>(inst));
|
||||
context()->set_instr_block(inst, target_block);
|
||||
}
|
||||
|
||||
bool IfConversion::CanHoistInstruction(ir::Instruction* inst,
|
||||
ir::BasicBlock* target_block,
|
||||
DominatorAnalysis* dominators) {
|
||||
ir::BasicBlock* inst_block = context()->get_instr_block(inst);
|
||||
if (!inst_block) {
|
||||
// This is in the header, and dominates everything.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dominators->Dominates(inst_block, target_block)) {
|
||||
// Already in position. No work to do.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!inst->IsOpcodeCodeMotionSafe()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check all instruction |inst| depends on.
|
||||
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
|
||||
return inst->WhileEachInId(
|
||||
[this, target_block, def_use_mgr, dominators](uint32_t* id) {
|
||||
ir::Instruction* operand_inst = def_use_mgr->GetDef(*id);
|
||||
return CanHoistInstruction(operand_inst, target_block, dominators);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
@ -68,6 +68,19 @@ class IfConversion : public Pass {
|
||||
// is terminated by a conditional branch.
|
||||
bool CheckBlock(ir::BasicBlock* block, DominatorAnalysis* dominators,
|
||||
ir::BasicBlock** common);
|
||||
|
||||
// Moves |inst| to |target_block| if it does not already dominate the block.
|
||||
// Any instructions that |inst| depends on are move if necessary. It is
|
||||
// assumed that |inst| can be hoisted to |target_block| as defined by
|
||||
// |CanHoistInstruction|. |dominators| is the dominator analysis for the
|
||||
// function that contains |target_block|.
|
||||
void HoistInstruction(ir::Instruction* inst, ir::BasicBlock* target_block,
|
||||
DominatorAnalysis* dominators);
|
||||
|
||||
// Returns true if it is legal to move |inst| and the instructions it depends
|
||||
// on to |target_block| if they do not already dominate |target_block|.
|
||||
bool CanHoistInstruction(ir::Instruction* inst, ir::BasicBlock* target_block,
|
||||
DominatorAnalysis* dominators);
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
|
@ -523,9 +523,20 @@ std::ostream& operator<<(std::ostream& str, const ir::Instruction& inst) {
|
||||
|
||||
bool Instruction::IsOpcodeCodeMotionSafe() const {
|
||||
switch (opcode_) {
|
||||
case SpvOpNop:
|
||||
case SpvOpUndef:
|
||||
case SpvOpLoad:
|
||||
case SpvOpAccessChain:
|
||||
case SpvOpInBoundsAccessChain:
|
||||
case SpvOpArrayLength:
|
||||
case SpvOpVectorExtractDynamic:
|
||||
case SpvOpVectorInsertDynamic:
|
||||
case SpvOpVectorShuffle:
|
||||
case SpvOpCompositeConstruct:
|
||||
case SpvOpCompositeExtract:
|
||||
case SpvOpCompositeInsert:
|
||||
case SpvOpCopyObject:
|
||||
case SpvOpTranspose:
|
||||
case SpvOpConvertFToU:
|
||||
case SpvOpConvertFToS:
|
||||
case SpvOpConvertSToF:
|
||||
@ -556,11 +567,22 @@ bool Instruction::IsOpcodeCodeMotionSafe() const {
|
||||
case SpvOpVectorTimesMatrix:
|
||||
case SpvOpMatrixTimesVector:
|
||||
case SpvOpMatrixTimesMatrix:
|
||||
case SpvOpOuterProduct:
|
||||
case SpvOpDot:
|
||||
case SpvOpIAddCarry:
|
||||
case SpvOpISubBorrow:
|
||||
case SpvOpUMulExtended:
|
||||
case SpvOpSMulExtended:
|
||||
case SpvOpAny:
|
||||
case SpvOpAll:
|
||||
case SpvOpIsNan:
|
||||
case SpvOpIsInf:
|
||||
case SpvOpLogicalEqual:
|
||||
case SpvOpLogicalNotEqual:
|
||||
case SpvOpLogicalOr:
|
||||
case SpvOpLogicalAnd:
|
||||
case SpvOpLogicalNot:
|
||||
case SpvOpSelect:
|
||||
case SpvOpIEqual:
|
||||
case SpvOpINotEqual:
|
||||
case SpvOpUGreaterThan:
|
||||
@ -590,10 +612,12 @@ bool Instruction::IsOpcodeCodeMotionSafe() const {
|
||||
case SpvOpBitwiseXor:
|
||||
case SpvOpBitwiseAnd:
|
||||
case SpvOpNot:
|
||||
case SpvOpAccessChain:
|
||||
case SpvOpInBoundsAccessChain:
|
||||
case SpvOpPtrAccessChain:
|
||||
case SpvOpInBoundsPtrAccessChain:
|
||||
case SpvOpBitFieldInsert:
|
||||
case SpvOpBitFieldSExtract:
|
||||
case SpvOpBitFieldUExtract:
|
||||
case SpvOpBitReverse:
|
||||
case SpvOpBitCount:
|
||||
case SpvOpSizeOf:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -51,6 +51,9 @@ void IRContext::BuildInvalidAnalyses(IRContext::Analysis set) {
|
||||
if (set & kAnalysisRegisterPressure) {
|
||||
BuildRegPressureAnalysis();
|
||||
}
|
||||
if (set & kAnalysisValueNumberTable) {
|
||||
BuildValueNumberTable();
|
||||
}
|
||||
}
|
||||
|
||||
void IRContext::InvalidateAnalysesExceptFor(
|
||||
@ -82,6 +85,9 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
|
||||
if (analyses_to_invalidate & kAnalysisNameMap) {
|
||||
id_to_name_.reset(nullptr);
|
||||
}
|
||||
if (analyses_to_invalidate & kAnalysisValueNumberTable) {
|
||||
vn_table_.reset(nullptr);
|
||||
}
|
||||
|
||||
valid_analyses_ = Analysis(valid_analyses_ & ~analyses_to_invalidate);
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "register_pressure.h"
|
||||
#include "scalar_analysis.h"
|
||||
#include "type_manager.h"
|
||||
#include "value_number_table.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@ -62,7 +63,8 @@ class IRContext {
|
||||
kAnalysisNameMap = 1 << 7,
|
||||
kAnalysisScalarEvolution = 1 << 8,
|
||||
kAnalysisRegisterPressure = 1 << 9,
|
||||
kAnalysisEnd = 1 << 10
|
||||
kAnalysisValueNumberTable = 1 << 10,
|
||||
kAnalysisEnd = 1 << 11
|
||||
};
|
||||
|
||||
friend inline Analysis operator|(Analysis lhs, Analysis rhs);
|
||||
@ -208,6 +210,15 @@ class IRContext {
|
||||
return def_use_mgr_.get();
|
||||
}
|
||||
|
||||
// Returns a pointer to a value number table. If the liveness analysis is
|
||||
// invalid, it is rebuilt first.
|
||||
opt::ValueNumberTable* GetValueNumberTable() {
|
||||
if (!AreAnalysesValid(kAnalysisValueNumberTable)) {
|
||||
BuildValueNumberTable();
|
||||
}
|
||||
return vn_table_.get();
|
||||
}
|
||||
|
||||
// Returns a pointer to a liveness analysis. If the liveness analysis is
|
||||
// invalid, it is rebuilt first.
|
||||
opt::LivenessAnalysis* GetLivenessAnalysis() {
|
||||
@ -374,7 +385,7 @@ class IRContext {
|
||||
|
||||
// Returns true if |inst| is a combinator in the current context.
|
||||
// |combinator_ops_| is built if it has not been already.
|
||||
inline bool IsCombinatorInstruction(ir::Instruction* inst) {
|
||||
inline bool IsCombinatorInstruction(const Instruction* inst) {
|
||||
if (!AreAnalysesValid(kAnalysisCombinators)) {
|
||||
InitializeCombinators();
|
||||
}
|
||||
@ -475,6 +486,13 @@ class IRContext {
|
||||
valid_analyses_ = valid_analyses_ | kAnalysisRegisterPressure;
|
||||
}
|
||||
|
||||
// Builds the value number table analysis from scratch, even if it was already
|
||||
// valid.
|
||||
void BuildValueNumberTable() {
|
||||
vn_table_.reset(new opt::ValueNumberTable(this));
|
||||
valid_analyses_ = valid_analyses_ | kAnalysisValueNumberTable;
|
||||
}
|
||||
|
||||
// Removes all computed dominator and post-dominator trees. This will force
|
||||
// the context to rebuild the trees on demand.
|
||||
void ResetDominatorAnalysis() {
|
||||
@ -581,6 +599,8 @@ class IRContext {
|
||||
|
||||
// The liveness analysis |module_|.
|
||||
std::unique_ptr<opt::LivenessAnalysis> reg_pressure_;
|
||||
|
||||
std::unique_ptr<opt::ValueNumberTable> vn_table_;
|
||||
};
|
||||
|
||||
inline ir::IRContext::Analysis operator|(ir::IRContext::Analysis lhs,
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "cfg.h"
|
||||
#include "ir_context.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
@ -34,6 +35,10 @@ uint32_t ValueNumberTable::GetValueNumber(
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t ValueNumberTable::GetValueNumber(uint32_t id) const {
|
||||
return GetValueNumber(context()->get_def_use_mgr()->GetDef(id));
|
||||
}
|
||||
|
||||
uint32_t ValueNumberTable::AssignValueNumber(ir::Instruction* inst) {
|
||||
// If it already has a value return that.
|
||||
uint32_t value = GetValueNumber(inst);
|
||||
|
@ -18,7 +18,12 @@
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include "instruction.h"
|
||||
#include "ir_context.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace ir {
|
||||
class IRContext;
|
||||
}
|
||||
} // namespace spvtools
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
@ -60,7 +65,7 @@ class ValueNumberTable {
|
||||
|
||||
// Returns the value number of the value contain in |id|. Returns 0 if it
|
||||
// has not been assigned a value number.
|
||||
inline uint32_t GetValueNumber(uint32_t id) const;
|
||||
uint32_t GetValueNumber(uint32_t id) const;
|
||||
|
||||
ir::IRContext* context() const { return context_; }
|
||||
|
||||
@ -84,10 +89,6 @@ class ValueNumberTable {
|
||||
uint32_t next_value_number_;
|
||||
};
|
||||
|
||||
uint32_t ValueNumberTable::GetValueNumber(uint32_t id) const {
|
||||
return GetValueNumber(context()->get_def_use_mgr()->GetDef(id));
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
|
@ -209,6 +209,97 @@ OpFunctionEnd
|
||||
|
||||
SinglePassRunAndMatch<opt::IfConversion>(text, true);
|
||||
}
|
||||
|
||||
TEST_F(IfConversionTest, CodeMotionSameValue) {
|
||||
const std::string text = R"(
|
||||
; CHECK: [[var:%\w+]] = OpVariable
|
||||
; CHECK: OpFunction
|
||||
; CHECK: OpLabel
|
||||
; CHECK-NOT: OpLabel
|
||||
; CHECK: [[add:%\w+]] = OpIAdd %uint %uint_0 %uint_1
|
||||
; CHECK: OpSelectionMerge [[merge_lab:%\w+]] None
|
||||
; CHECK-NEXT: OpBranchConditional
|
||||
; CHECK: [[merge_lab]] = OpLabel
|
||||
; CHECK-NOT: OpLabel
|
||||
; CHECK: OpStore [[var]] [[add]]
|
||||
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 %15
|
||||
%13 = OpLabel
|
||||
%14 = OpIAdd %uint %uint_0 %uint_1
|
||||
OpBranch %12
|
||||
%15 = OpLabel
|
||||
%16 = OpIAdd %uint %uint_0 %uint_1
|
||||
OpBranch %12
|
||||
%12 = OpLabel
|
||||
%17 = OpPhi %uint %16 %15 %14 %13
|
||||
OpStore %2 %17
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndMatch<opt::IfConversion>(text, true);
|
||||
}
|
||||
|
||||
TEST_F(IfConversionTest, CodeMotionMultipleInstructions) {
|
||||
const std::string text = R"(
|
||||
; CHECK: [[var:%\w+]] = OpVariable
|
||||
; CHECK: OpFunction
|
||||
; CHECK: OpLabel
|
||||
; CHECK-NOT: OpLabel
|
||||
; CHECK: [[a1:%\w+]] = OpIAdd %uint %uint_0 %uint_1
|
||||
; CHECK: [[a2:%\w+]] = OpIAdd %uint [[a1]] %uint_1
|
||||
; CHECK: OpSelectionMerge [[merge_lab:%\w+]] None
|
||||
; CHECK-NEXT: OpBranchConditional
|
||||
; CHECK: [[merge_lab]] = OpLabel
|
||||
; CHECK-NOT: OpLabel
|
||||
; CHECK: OpStore [[var]] [[a2]]
|
||||
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 %15
|
||||
%13 = OpLabel
|
||||
%a1 = OpIAdd %uint %uint_0 %uint_1
|
||||
%a2 = OpIAdd %uint %a1 %uint_1
|
||||
OpBranch %12
|
||||
%15 = OpLabel
|
||||
%b1 = OpIAdd %uint %uint_0 %uint_1
|
||||
%b2 = OpIAdd %uint %b1 %uint_1
|
||||
OpBranch %12
|
||||
%12 = OpLabel
|
||||
%17 = OpPhi %uint %b2 %15 %a2 %13
|
||||
OpStore %2 %17
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndMatch<opt::IfConversion>(text, true);
|
||||
}
|
||||
#endif // SPIRV_EFFCEE
|
||||
|
||||
TEST_F(IfConversionTest, NoCommonDominator) {
|
||||
@ -329,4 +420,51 @@ OpFunctionEnd
|
||||
SinglePassRunAndCheck<opt::IfConversion>(text, text, true, true);
|
||||
}
|
||||
|
||||
TEST_F(IfConversionTest, NoCodeMotionImmovableInst) {
|
||||
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 %14
|
||||
%13 = OpLabel
|
||||
OpSelectionMerge %15 None
|
||||
OpBranchConditional %true %16 %15
|
||||
%16 = OpLabel
|
||||
%17 = OpIAdd %uint %uint_0 %uint_1
|
||||
OpBranch %15
|
||||
%15 = OpLabel
|
||||
%18 = OpPhi %uint %uint_0 %13 %17 %16
|
||||
%19 = OpIAdd %uint %18 %uint_1
|
||||
OpBranch %12
|
||||
%14 = OpLabel
|
||||
OpSelectionMerge %20 None
|
||||
OpBranchConditional %true %21 %20
|
||||
%21 = OpLabel
|
||||
%22 = OpIAdd %uint %uint_0 %uint_1
|
||||
OpBranch %20
|
||||
%20 = OpLabel
|
||||
%23 = OpPhi %uint %uint_0 %14 %22 %21
|
||||
%24 = OpIAdd %uint %23 %uint_1
|
||||
OpBranch %12
|
||||
%12 = OpLabel
|
||||
%25 = OpPhi %uint %24 %20 %19 %15
|
||||
OpStore %2 %25
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndCheck<opt::IfConversion>(text, text, true, true);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
Loading…
Reference in New Issue
Block a user