mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-19 03:20:14 +00:00
Fix merge return in the face of breaks (#2466)
Fixes #2453 * Enable addition of OpPhi instructions when the loop has multiple predecessors of the merge due to a break * This can result in some values no longer dominating their uses * Track return blocks in structured flow to produce OpPhis that have multiple undef and non-undef arguments * New tests to catch the bug * When a block is predicated, mark the new body as a return if the old block as already a return
This commit is contained in:
parent
0300a464a4
commit
c9874e5090
@ -197,6 +197,7 @@ void MergeReturnPass::ProcessStructuredBlock(BasicBlock* block) {
|
|||||||
tail_opcode == SpvOpUnreachable) {
|
tail_opcode == SpvOpUnreachable) {
|
||||||
assert(CurrentState().InLoop() && "Should be in the dummy loop.");
|
assert(CurrentState().InLoop() && "Should be in the dummy loop.");
|
||||||
BranchToBlock(block, CurrentState().LoopMergeId());
|
BranchToBlock(block, CurrentState().LoopMergeId());
|
||||||
|
return_blocks_.insert(block->id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,11 +233,19 @@ void MergeReturnPass::UpdatePhiNodes(BasicBlock* new_source,
|
|||||||
const auto& target_pred = cfg()->preds(target->id());
|
const auto& target_pred = cfg()->preds(target->id());
|
||||||
if (target_pred.size() == 1) {
|
if (target_pred.size() == 1) {
|
||||||
MarkForNewPhiNodes(target, context()->get_instr_block(target_pred[0]));
|
MarkForNewPhiNodes(target, context()->get_instr_block(target_pred[0]));
|
||||||
|
} else {
|
||||||
|
// If the loop contained a break and a return, OpPhi instructions may be
|
||||||
|
// required starting from the dominator of the loop merge.
|
||||||
|
DominatorAnalysis* dom_tree =
|
||||||
|
context()->GetDominatorAnalysis(target->GetParent());
|
||||||
|
auto idom = dom_tree->ImmediateDominator(target);
|
||||||
|
if (idom) {
|
||||||
|
MarkForNewPhiNodes(target, idom);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MergeReturnPass::CreatePhiNodesForInst(BasicBlock* merge_block,
|
void MergeReturnPass::CreatePhiNodesForInst(BasicBlock* merge_block,
|
||||||
uint32_t predecessor,
|
|
||||||
Instruction& inst) {
|
Instruction& inst) {
|
||||||
DominatorAnalysis* dom_tree =
|
DominatorAnalysis* dom_tree =
|
||||||
context()->GetDominatorAnalysis(merge_block->GetParent());
|
context()->GetDominatorAnalysis(merge_block->GetParent());
|
||||||
@ -281,17 +290,16 @@ void MergeReturnPass::CreatePhiNodesForInst(BasicBlock* merge_block,
|
|||||||
uint32_t undef_id = Type2Undef(inst.type_id());
|
uint32_t undef_id = Type2Undef(inst.type_id());
|
||||||
std::vector<uint32_t> phi_operands;
|
std::vector<uint32_t> phi_operands;
|
||||||
|
|
||||||
// Add the operands for the defining instructions.
|
// Add the OpPhi operands. If the predecessor is a return block use undef,
|
||||||
phi_operands.push_back(inst.result_id());
|
// otherwise use |inst|'s id.
|
||||||
phi_operands.push_back(predecessor);
|
|
||||||
|
|
||||||
// Add undef from all other blocks.
|
|
||||||
std::vector<uint32_t> preds = cfg()->preds(merge_block->id());
|
std::vector<uint32_t> preds = cfg()->preds(merge_block->id());
|
||||||
for (uint32_t pred_id : preds) {
|
for (uint32_t pred_id : preds) {
|
||||||
if (pred_id != predecessor) {
|
if (return_blocks_.count(pred_id)) {
|
||||||
phi_operands.push_back(undef_id);
|
phi_operands.push_back(undef_id);
|
||||||
phi_operands.push_back(pred_id);
|
} else {
|
||||||
|
phi_operands.push_back(inst.result_id());
|
||||||
}
|
}
|
||||||
|
phi_operands.push_back(pred_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Instruction* new_phi = builder.AddPhi(inst.type_id(), phi_operands);
|
Instruction* new_phi = builder.AddPhi(inst.type_id(), phi_operands);
|
||||||
@ -400,8 +408,14 @@ bool MergeReturnPass::BreakFromConstruct(
|
|||||||
// Forget about the edges leaving block. They will be removed.
|
// Forget about the edges leaving block. They will be removed.
|
||||||
cfg()->RemoveSuccessorEdges(block);
|
cfg()->RemoveSuccessorEdges(block);
|
||||||
|
|
||||||
BasicBlock* old_body = block->SplitBasicBlock(context(), TakeNextId(), iter);
|
auto old_body_id = TakeNextId();
|
||||||
|
BasicBlock* old_body = block->SplitBasicBlock(context(), old_body_id, iter);
|
||||||
predicated->insert(old_body);
|
predicated->insert(old_body);
|
||||||
|
// If a return block is being split, mark the new body block also as a return
|
||||||
|
// block.
|
||||||
|
if (return_blocks_.count(block->id())) {
|
||||||
|
return_blocks_.insert(old_body_id);
|
||||||
|
}
|
||||||
|
|
||||||
// If |block| was a continue target for a loop |old_body| is now the correct
|
// If |block| was a continue target for a loop |old_body| is now the correct
|
||||||
// continue target.
|
// continue target.
|
||||||
@ -660,7 +674,7 @@ void MergeReturnPass::AddNewPhiNodes(BasicBlock* bb, BasicBlock* pred,
|
|||||||
BasicBlock* current_bb = pred;
|
BasicBlock* current_bb = pred;
|
||||||
while (current_bb != nullptr && current_bb->id() != header_id) {
|
while (current_bb != nullptr && current_bb->id() != header_id) {
|
||||||
for (Instruction& inst : *current_bb) {
|
for (Instruction& inst : *current_bb) {
|
||||||
CreatePhiNodesForInst(bb, pred->id(), inst);
|
CreatePhiNodesForInst(bb, inst);
|
||||||
}
|
}
|
||||||
current_bb = dom_tree->ImmediateDominator(current_bb);
|
current_bb = dom_tree->ImmediateDominator(current_bb);
|
||||||
}
|
}
|
||||||
|
@ -240,12 +240,11 @@ class MergeReturnPass : public MemPass {
|
|||||||
// return block at the end of the pass.
|
// return block at the end of the pass.
|
||||||
void CreateReturnBlock();
|
void CreateReturnBlock();
|
||||||
|
|
||||||
// Creates a Phi node in |merge_block| for the result of |inst| coming from
|
// Creates a Phi node in |merge_block| for the result of |inst|.
|
||||||
// |predecessor|. Any uses of the result of |inst| that are no longer
|
// Any uses of the result of |inst| that are no longer
|
||||||
// dominated by |inst|, are replaced with the result of the new |OpPhi|
|
// dominated by |inst|, are replaced with the result of the new |OpPhi|
|
||||||
// instruction.
|
// instruction.
|
||||||
void CreatePhiNodesForInst(BasicBlock* merge_block, uint32_t predecessor,
|
void CreatePhiNodesForInst(BasicBlock* merge_block, Instruction& inst);
|
||||||
Instruction& inst);
|
|
||||||
|
|
||||||
// Traverse the nodes in |new_merge_nodes_|, and adds the OpPhi instructions
|
// Traverse the nodes in |new_merge_nodes_|, and adds the OpPhi instructions
|
||||||
// that are needed to make the code correct. It is assumed that at this point
|
// that are needed to make the code correct. It is assumed that at this point
|
||||||
@ -331,6 +330,11 @@ class MergeReturnPass : public MemPass {
|
|||||||
// values that will need a phi on the new edges.
|
// values that will need a phi on the new edges.
|
||||||
std::unordered_map<BasicBlock*, BasicBlock*> new_merge_nodes_;
|
std::unordered_map<BasicBlock*, BasicBlock*> new_merge_nodes_;
|
||||||
bool HasNontrivialUnreachableBlocks(Function* function);
|
bool HasNontrivialUnreachableBlocks(Function* function);
|
||||||
|
|
||||||
|
// Contains all return blocks that are merged. This is set is populated while
|
||||||
|
// processing structured blocks and used to properly construct OpPhi
|
||||||
|
// instructions.
|
||||||
|
std::unordered_set<uint32_t> return_blocks_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace opt
|
} // namespace opt
|
||||||
|
@ -1206,7 +1206,7 @@ TEST_F(MergeReturnPassTest, StructuredControlFlowPartialReplacePhi) {
|
|||||||
; CHECK: [[bb:%\w+]] = OpLabel
|
; CHECK: [[bb:%\w+]] = OpLabel
|
||||||
; CHECK-NEXT: [[val:%\w+]] = OpUndef %bool
|
; CHECK-NEXT: [[val:%\w+]] = OpUndef %bool
|
||||||
; CHECK: [[merge]] = OpLabel
|
; CHECK: [[merge]] = OpLabel
|
||||||
; CHECK-NEXT: [[phi1:%\w+]] = OpPhi %bool [[val]] [[bb]] {{%\w+}} [[old_ret_block]]
|
; CHECK-NEXT: [[phi1:%\w+]] = OpPhi %bool {{%\w+}} [[old_ret_block]] [[val]] [[bb]]
|
||||||
; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[bb2:%\w+]]
|
; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[bb2:%\w+]]
|
||||||
; CHECK: [[bb2]] = OpLabel
|
; CHECK: [[bb2]] = OpLabel
|
||||||
; CHECK: OpBranch [[header2:%\w+]]
|
; CHECK: OpBranch [[header2:%\w+]]
|
||||||
@ -1263,7 +1263,7 @@ TEST_F(MergeReturnPassTest, GeneratePhiInOuterLoop) {
|
|||||||
; CHECK: [[continue]] = OpLabel
|
; CHECK: [[continue]] = OpLabel
|
||||||
; CHECK-NEXT: [[undef:%\w+]] = OpUndef
|
; CHECK-NEXT: [[undef:%\w+]] = OpUndef
|
||||||
; CHECK: [[merge]] = OpLabel
|
; CHECK: [[merge]] = OpLabel
|
||||||
; CHECK-NEXT: [[phi:%\w+]] = OpPhi %bool [[undef]] [[continue]] {{%\w+}} {{%\w+}}
|
; CHECK-NEXT: [[phi:%\w+]] = OpPhi %bool {{%\w+}} {{%\w+}} [[undef]] [[continue]]
|
||||||
; CHECK: OpCopyObject %bool [[phi]]
|
; CHECK: OpCopyObject %bool [[phi]]
|
||||||
OpCapability Shader
|
OpCapability Shader
|
||||||
%1 = OpExtInstImport "GLSL.std.450"
|
%1 = OpExtInstImport "GLSL.std.450"
|
||||||
@ -1328,7 +1328,7 @@ TEST_F(MergeReturnPassTest, SerialLoopsUpdateBlockMapping) {
|
|||||||
; CHECK: OpLoopMerge [[merge:%\w+]]
|
; CHECK: OpLoopMerge [[merge:%\w+]]
|
||||||
; CHECK: [[def:%\w+]] = OpFOrdLessThan
|
; CHECK: [[def:%\w+]] = OpFOrdLessThan
|
||||||
; CHECK: [[merge]] = OpLabel
|
; CHECK: [[merge]] = OpLabel
|
||||||
; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} [[def]]
|
; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[def]]
|
||||||
; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
|
; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
|
||||||
; CHECK: [[cont]] = OpLabel
|
; CHECK: [[cont]] = OpLabel
|
||||||
; CHECK-NEXT: OpBranchConditional [[phi]]
|
; CHECK-NEXT: OpBranchConditional [[phi]]
|
||||||
@ -1480,6 +1480,198 @@ TEST_F(MergeReturnPassTest, InnerLoopMergeIsOuterLoopContinue) {
|
|||||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||||
SinglePassRunAndMatch<MergeReturnPass>(before, false);
|
SinglePassRunAndMatch<MergeReturnPass>(before, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(MergeReturnPassTest, BreakFromLoopUseNoLongerDominated) {
|
||||||
|
const std::string spirv = R"(
|
||||||
|
; CHECK: [[undef:%\w+]] = OpUndef
|
||||||
|
; CHECK: OpLoopMerge
|
||||||
|
; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
|
||||||
|
; CHECK-NEXT: OpBranch [[body:%\w+]]
|
||||||
|
; CHECK: [[body]] = OpLabel
|
||||||
|
; CHECK-NEXT: OpSelectionMerge [[non_ret:%\w+]]
|
||||||
|
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret:%\w+]] [[non_ret]]
|
||||||
|
; CHECK: [[ret]] = OpLabel
|
||||||
|
; CHECK-NEXT: OpStore
|
||||||
|
; CHECK-NEXT: OpBranch [[merge]]
|
||||||
|
; CHECK: [[non_ret]] = OpLabel
|
||||||
|
; CHECK-NEXT: [[def:%\w+]] = OpLogicalNot
|
||||||
|
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[break:%\w+]] [[cont]]
|
||||||
|
; CHECK: [[break]] = OpLabel
|
||||||
|
; CHECK-NEXT: OpBranch [[merge]]
|
||||||
|
; CHECK: [[cont]] = OpLabel
|
||||||
|
; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]]
|
||||||
|
; CHECK: [[merge]] = OpLabel
|
||||||
|
; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret]] [[def]] [[break]] [[def]] [[cont]]
|
||||||
|
; CHECK: OpLogicalNot {{%\w+}} [[phi]]
|
||||||
|
OpCapability Shader
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint GLCompute %func "func"
|
||||||
|
OpExecutionMode %func LocalSize 1 1 1
|
||||||
|
%void = OpTypeVoid
|
||||||
|
%void_fn = OpTypeFunction %void
|
||||||
|
%bool = OpTypeBool
|
||||||
|
%true = OpConstantTrue %bool
|
||||||
|
%func = OpFunction %void None %void_fn
|
||||||
|
%1 = OpLabel
|
||||||
|
OpBranch %2
|
||||||
|
%2 = OpLabel
|
||||||
|
OpLoopMerge %8 %7 None
|
||||||
|
OpBranch %3
|
||||||
|
%3 = OpLabel
|
||||||
|
OpSelectionMerge %5 None
|
||||||
|
OpBranchConditional %true %4 %5
|
||||||
|
%4 = OpLabel
|
||||||
|
OpReturn
|
||||||
|
%5 = OpLabel
|
||||||
|
%def = OpLogicalNot %bool %true
|
||||||
|
OpBranchConditional %true %6 %7
|
||||||
|
%6 = OpLabel
|
||||||
|
OpBranch %8
|
||||||
|
%7 = OpLabel
|
||||||
|
OpBranchConditional %true %2 %8
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %9
|
||||||
|
%9 = OpLabel
|
||||||
|
%use = OpLogicalNot %bool %def
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||||
|
SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MergeReturnPassTest, TwoBreaksFromLoopUsesNoLongerDominated) {
|
||||||
|
const std::string spirv = R"(
|
||||||
|
; CHECK: [[undef:%\w+]] = OpUndef
|
||||||
|
; CHECK: OpLoopMerge
|
||||||
|
; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
|
||||||
|
; CHECK-NEXT: OpBranch [[body:%\w+]]
|
||||||
|
; CHECK: [[body]] = OpLabel
|
||||||
|
; CHECK-NEXT: OpSelectionMerge [[body2:%\w+]]
|
||||||
|
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret1:%\w+]] [[body2]]
|
||||||
|
; CHECK: [[ret1]] = OpLabel
|
||||||
|
; CHECK-NEXT: OpStore
|
||||||
|
; CHECK-NEXT: OpBranch [[merge]]
|
||||||
|
; CHECK: [[body2]] = OpLabel
|
||||||
|
; CHECK-NEXT: [[def1:%\w+]] = OpLogicalNot
|
||||||
|
; CHECK-NEXT: OpSelectionMerge [[body3:%\w+]]
|
||||||
|
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret2:%\w+]] [[body3:%\w+]]
|
||||||
|
; CHECK: [[ret2]] = OpLabel
|
||||||
|
; CHECK-NEXT: OpStore
|
||||||
|
; CHECK-NEXT: OpBranch [[merge]]
|
||||||
|
; CHECK: [[body3]] = OpLabel
|
||||||
|
; CHECK-NEXT: [[def2:%\w+]] = OpLogicalAnd
|
||||||
|
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[break:%\w+]] [[cont]]
|
||||||
|
; CHECK: [[break]] = OpLabel
|
||||||
|
; CHECK-NEXT: OpBranch [[merge]]
|
||||||
|
; CHECK: [[cont]] = OpLabel
|
||||||
|
; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]]
|
||||||
|
; CHECK: [[merge]] = OpLabel
|
||||||
|
; CHECK-NEXT: [[phi1:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret1]] [[undef]] [[ret2]] [[def1]] [[break]] [[def1]] [[cont]]
|
||||||
|
; CHECK-NEXT: [[phi2:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret1]] [[undef]] [[ret2]] [[def2]] [[break]] [[def2]] [[cont]]
|
||||||
|
; CHECK: OpLogicalNot {{%\w+}} [[phi1]]
|
||||||
|
; CHECK: OpLogicalAnd {{%\w+}} [[phi2]]
|
||||||
|
OpCapability Shader
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint GLCompute %func "func"
|
||||||
|
OpExecutionMode %func LocalSize 1 1 1
|
||||||
|
%void = OpTypeVoid
|
||||||
|
%void_fn = OpTypeFunction %void
|
||||||
|
%bool = OpTypeBool
|
||||||
|
%true = OpConstantTrue %bool
|
||||||
|
%func = OpFunction %void None %void_fn
|
||||||
|
%1 = OpLabel
|
||||||
|
OpBranch %2
|
||||||
|
%2 = OpLabel
|
||||||
|
OpLoopMerge %10 %9 None
|
||||||
|
OpBranch %3
|
||||||
|
%3 = OpLabel
|
||||||
|
OpSelectionMerge %5 None
|
||||||
|
OpBranchConditional %true %4 %5
|
||||||
|
%4 = OpLabel
|
||||||
|
OpReturn
|
||||||
|
%5 = OpLabel
|
||||||
|
%def1 = OpLogicalNot %bool %true
|
||||||
|
OpSelectionMerge %7 None
|
||||||
|
OpBranchConditional %true %6 %7
|
||||||
|
%6 = OpLabel
|
||||||
|
OpReturn
|
||||||
|
%7 = OpLabel
|
||||||
|
%def2 = OpLogicalAnd %bool %true %true
|
||||||
|
OpBranchConditional %true %8 %9
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %10
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranchConditional %true %2 %10
|
||||||
|
%10 = OpLabel
|
||||||
|
OpBranch %11
|
||||||
|
%11 = OpLabel
|
||||||
|
%use1 = OpLogicalNot %bool %def1
|
||||||
|
%use2 = OpLogicalAnd %bool %def2 %true
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||||
|
SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MergeReturnPassTest, PredicateBreakBlock) {
|
||||||
|
const std::string spirv = R"(
|
||||||
|
; IDs are being preserved so we can rely on basic block labels.
|
||||||
|
; CHECK: [[undef:%\w+]] = OpUndef
|
||||||
|
; CHECK: [[undef:%\w+]] = OpUndef
|
||||||
|
; CHECK: %13 = OpLabel
|
||||||
|
; CHECK-NEXT: [[def:%\w+]] = OpLogicalNot
|
||||||
|
; CHECK: %8 = OpLabel
|
||||||
|
; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} [[undef]] {{%\w+}} [[undef]] {{%\w+}} [[def]] %13 [[undef]] {{%\w+}}
|
||||||
|
; CHECK: OpLogicalAnd {{%\w+}} [[phi]]
|
||||||
|
OpCapability Shader
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint GLCompute %1 "func"
|
||||||
|
OpExecutionMode %1 LocalSize 1 1 1
|
||||||
|
%void = OpTypeVoid
|
||||||
|
%3 = OpTypeFunction %void
|
||||||
|
%bool = OpTypeBool
|
||||||
|
%true = OpUndef %bool
|
||||||
|
%1 = OpFunction %void None %3
|
||||||
|
%6 = OpLabel
|
||||||
|
OpBranch %7
|
||||||
|
%7 = OpLabel
|
||||||
|
OpLoopMerge %8 %9 None
|
||||||
|
OpBranch %10
|
||||||
|
%10 = OpLabel
|
||||||
|
OpSelectionMerge %11 None
|
||||||
|
OpBranchConditional %true %12 %13
|
||||||
|
%12 = OpLabel
|
||||||
|
OpLoopMerge %14 %15 None
|
||||||
|
OpBranch %16
|
||||||
|
%16 = OpLabel
|
||||||
|
OpReturn
|
||||||
|
%15 = OpLabel
|
||||||
|
OpBranch %12
|
||||||
|
%14 = OpLabel
|
||||||
|
OpUnreachable
|
||||||
|
%13 = OpLabel
|
||||||
|
%17 = OpLogicalNot %bool %true
|
||||||
|
OpBranch %8
|
||||||
|
%11 = OpLabel
|
||||||
|
OpUnreachable
|
||||||
|
%9 = OpLabel
|
||||||
|
OpBranch %7
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %18
|
||||||
|
%18 = OpLabel
|
||||||
|
%19 = OpLogicalAnd %bool %17 %true
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
)";
|
||||||
|
|
||||||
|
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||||
|
SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace opt
|
} // namespace opt
|
||||||
} // namespace spvtools
|
} // namespace spvtools
|
||||||
|
Loading…
Reference in New Issue
Block a user