From 63064bd9eba297a6c2d0cc45564c753e8757be27 Mon Sep 17 00:00:00 2001 From: GregF Date: Thu, 5 Oct 2017 20:37:00 -0600 Subject: [PATCH] DeadBranchElim: Add dead case elimination Expands dead branch elimination to eliminate dead switch cases. It also changes dbe to eliminate orphaned merge blocks and recursively eliminate any blocks thereby orphaned. --- source/opt/dead_branch_elim_pass.cpp | 226 ++++++++++++++++++-------- source/opt/dead_branch_elim_pass.h | 32 +++- source/opt/instruction.h | 33 ++++ test/opt/dead_branch_elim_test.cpp | 302 +++++++++++++++++++++++++++++++++-- 4 files changed, 505 insertions(+), 88 deletions(-) diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp index ef25705..207b122 100644 --- a/source/opt/dead_branch_elim_pass.cpp +++ b/source/opt/dead_branch_elim_pass.cpp @@ -24,13 +24,9 @@ namespace opt { namespace { -const uint32_t kBranchCondConditionalIdInIdx = 0; const uint32_t kBranchCondTrueLabIdInIdx = 1; const uint32_t kBranchCondFalseLabIdInIdx = 2; const uint32_t kSelectionMergeMergeBlockIdInIdx = 0; -const uint32_t kPhiVal0IdInIdx = 0; -const uint32_t kPhiLab0IdInIdx = 1; -const uint32_t kPhiVal1IdInIdx = 2; const uint32_t kLoopMergeMergeBlockIdInIdx = 0; const uint32_t kLoopMergeContinueBlockIdInIdx = 1; @@ -92,29 +88,48 @@ void DeadBranchElimPass::ComputeStructuredOrder( ignore_edge); } -void DeadBranchElimPass::GetConstCondition( - uint32_t condId, bool* condVal, bool* condIsConst) { +bool DeadBranchElimPass::GetConstCondition(uint32_t condId, bool* condVal) { + bool condIsConst; ir::Instruction* cInst = def_use_mgr_->GetDef(condId); switch (cInst->opcode()) { case SpvOpConstantFalse: { *condVal = false; - *condIsConst = true; + condIsConst = true; } break; case SpvOpConstantTrue: { *condVal = true; - *condIsConst = true; + condIsConst = true; } break; case SpvOpLogicalNot: { bool negVal; - (void)GetConstCondition(cInst->GetSingleWordInOperand(0), - &negVal, condIsConst); - if (*condIsConst) + condIsConst = GetConstCondition(cInst->GetSingleWordInOperand(0), + &negVal); + if (condIsConst) *condVal = !negVal; } break; default: { - *condIsConst = false; + condIsConst = false; } break; } + return condIsConst; +} + +bool DeadBranchElimPass::GetConstInteger(uint32_t selId, uint32_t* selVal) { + ir::Instruction* sInst = def_use_mgr_->GetDef(selId); + uint32_t typeId = sInst->type_id(); + ir::Instruction* typeInst = def_use_mgr_->GetDef(typeId); + // TODO(greg-lunarg): Support non-32 bit ints + if (typeInst->GetSingleWordInOperand(0) != 32) + return false; + if (sInst->opcode() == SpvOpConstant) { + *selVal = sInst->GetSingleWordInOperand(0); + return true; + } + else if (sInst->opcode() == SpvOpConstantNull) { + *selVal = 0; + return true; + } + return false; } void DeadBranchElimPass::AddBranch(uint32_t labelId, ir::BasicBlock* bp) { @@ -153,25 +168,21 @@ void DeadBranchElimPass::KillAllInsts(ir::BasicBlock* bp) { }); } -bool DeadBranchElimPass::GetConstConditionalSelectionBranch(ir::BasicBlock* bp, +bool DeadBranchElimPass::GetSelectionBranch(ir::BasicBlock* bp, ir::Instruction** branchInst, ir::Instruction** mergeInst, - uint32_t *condId, bool *condVal) { + uint32_t *condId) { auto ii = bp->end(); --ii; *branchInst = &*ii; - if ((*branchInst)->opcode() != SpvOpBranchConditional) - return false; if (ii == bp->begin()) return false; --ii; *mergeInst = &*ii; if ((*mergeInst)->opcode() != SpvOpSelectionMerge) return false; - bool condIsConst; - *condId = (*branchInst)->GetSingleWordInOperand( - kBranchCondConditionalIdInIdx); - (void) GetConstCondition(*condId, condVal, &condIsConst); - return condIsConst; + // Both BranchConidtional and Switch have their conditional value at 0. + *condId = (*branchInst)->GetSingleWordInOperand(0); + return true; } bool DeadBranchElimPass::HasNonPhiRef(uint32_t labelId) { @@ -194,29 +205,60 @@ bool DeadBranchElimPass::EliminateDeadBranches(ir::Function* func) { // Skip blocks that are already in the elimination set if (elimBlocks.find(*bi) != elimBlocks.end()) continue; - // Skip blocks that don't have constant conditional branch preceded + // Skip blocks that don't have conditional branch preceded // by OpSelectionMerge ir::Instruction* br; ir::Instruction* mergeInst; uint32_t condId; - bool condVal; - if (!GetConstConditionalSelectionBranch(*bi, &br, &mergeInst, &condId, - &condVal)) + if (!GetSelectionBranch(*bi, &br, &mergeInst, &condId)) continue; - // Replace conditional branch with unconditional branch - const uint32_t trueLabId = - br->GetSingleWordInOperand(kBranchCondTrueLabIdInIdx); - const uint32_t falseLabId = - br->GetSingleWordInOperand(kBranchCondFalseLabIdInIdx); + // If constant condition/selector, replace conditional branch/switch + // with unconditional branch and delete merge + uint32_t liveLabId; + if (br->opcode() == SpvOpBranchConditional) { + bool condVal; + if (!GetConstCondition(condId, &condVal)) + continue; + liveLabId = (condVal == true) ? + br->GetSingleWordInOperand(kBranchCondTrueLabIdInIdx) : + br->GetSingleWordInOperand(kBranchCondFalseLabIdInIdx); + } + else { + // Search switch operands for selector value, set liveLabId to + // corresponding label, use default if not found + uint32_t selVal; + if (!GetConstInteger(condId, &selVal)) + continue; + uint32_t icnt = 0; + uint32_t caseVal; + br->ForEachInOperand( + [&icnt,&caseVal,&selVal,&liveLabId](const uint32_t* idp) { + if (icnt == 1) { + // Start with default label + liveLabId = *idp; + } + else if (icnt > 1) { + if (icnt % 2 == 0) { + caseVal = *idp; + } + else { + if (caseVal == selVal) + liveLabId = *idp; + } + } + ++icnt; + }); + } + const uint32_t mergeLabId = mergeInst->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx); - const uint32_t liveLabId = condVal == true ? trueLabId : falseLabId; - const uint32_t deadLabId = condVal == true ? falseLabId : trueLabId; AddBranch(liveLabId, *bi); def_use_mgr_->KillInst(br); def_use_mgr_->KillInst(mergeInst); + modified = true; + // Initialize live block set to the live label std::unordered_set liveLabIds; liveLabIds.insert(liveLabId); @@ -245,46 +287,88 @@ bool DeadBranchElimPass::EliminateDeadBranches(ir::Function* func) { dLabId = (*dbi)->id(); } - // Process phi instructions in merge block. - // elimBlocks are now blocks which cannot precede merge block. Also, - // if eliminated branch is to merge label, remember the conditional block - // also cannot precede merge block. - uint32_t deadCondLabId = 0; - if (deadLabId == mergeLabId) - deadCondLabId = (*bi)->id(); - (*dbi)->ForEachPhiInst([&elimBlocks, &deadCondLabId, this]( - ir::Instruction* phiInst) { - const uint32_t phiLabId0 = - phiInst->GetSingleWordInOperand(kPhiLab0IdInIdx); - const bool useFirst = - elimBlocks.find(id2block_[phiLabId0]) == elimBlocks.end() && - phiLabId0 != deadCondLabId; - const uint32_t phiValIdx = - useFirst ? kPhiVal0IdInIdx : kPhiVal1IdInIdx; - const uint32_t replId = phiInst->GetSingleWordInOperand(phiValIdx); - const uint32_t phiId = phiInst->result_id(); + // If merge block is unreachable, continue eliminating blocks until + // a live block or last block is reached. + while (!HasNonPhiRef(dLabId)) { + KillAllInsts(*dbi); + elimBlocks.insert(*dbi); + ++dbi; + if (dbi == structuredOrder.end()) + break; + dLabId = (*dbi)->id(); + } + + // If last block reached, look for next dead branch + if (dbi == structuredOrder.end()) + continue; + + // Create set of dead predecessors in preparation for phi update. + // Add the header block if the live branch is not the merge block. + std::unordered_set deadPreds(elimBlocks); + if (liveLabId != dLabId) + deadPreds.insert(*bi); + + // Update phi instructions in terminating block. + for (auto pii = (*dbi)->begin(); ; ++pii) { + // Skip NoOps, break at end of phis + SpvOp op = pii->opcode(); + if (op == SpvOpNop) + continue; + if (op != SpvOpPhi) + break; + // Count phi's live predecessors with lcnt and remember last one + // with lidx. + uint32_t lcnt = 0; + uint32_t lidx = 0; + uint32_t icnt = 0; + pii->ForEachInId( + [&deadPreds,&icnt,&lcnt,&lidx,this](uint32_t* idp) { + if (icnt % 2 == 1) { + if (deadPreds.find(id2block_[*idp]) == deadPreds.end()) { + ++lcnt; + lidx = icnt - 1; + } + } + ++icnt; + }); + // If just one live predecessor, replace resultid with live value id. + uint32_t replId; + if (lcnt == 1) { + replId = pii->GetSingleWordInOperand(lidx); + } + else { + // Otherwise create new phi eliminating dead predecessor entries + assert(lcnt > 1); + replId = TakeNextId(); + std::vector phi_in_opnds; + icnt = 0; + uint32_t lastId; + pii->ForEachInId( + [&deadPreds,&icnt,&phi_in_opnds,&lastId,this](uint32_t* idp) { + if (icnt % 2 == 1) { + if (deadPreds.find(id2block_[*idp]) == deadPreds.end()) { + phi_in_opnds.push_back( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {lastId}}); + phi_in_opnds.push_back( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {*idp}}); + } + } + else { + lastId = *idp; + } + ++icnt; + }); + std::unique_ptr newPhi(new ir::Instruction( + SpvOpPhi, pii->type_id(), replId, phi_in_opnds)); + def_use_mgr_->AnalyzeInstDefUse(&*newPhi); + pii = pii.InsertBefore(std::move(newPhi)); + ++pii; + } + const uint32_t phiId = pii->result_id(); KillNamesAndDecorates(phiId); (void)def_use_mgr_->ReplaceAllUsesWith(phiId, replId); - def_use_mgr_->KillInst(phiInst); - }); - - // If merge block has no predecessors, replace the new branch with - // a MergeSelection/BranchCondition using the original constant condition - // and the mergeblock as the false branch. This is done so the merge block - // is not orphaned, which could cause invalid control flow in certain case. - // TODO(greg-lunarg): Do this only in cases where invalid code is caused. - if (!HasNonPhiRef(mergeLabId)) { - auto eii = (*bi)->end(); - --eii; - ir::Instruction* nbr = &*eii; - AddSelectionMerge(mergeLabId, *bi); - if (condVal == true) - AddBranchConditional(condId, liveLabId, mergeLabId, *bi); - else - AddBranchConditional(condId, mergeLabId, liveLabId, *bi); - def_use_mgr_->KillInst(nbr); + def_use_mgr_->KillInst(&*pii); } - modified = true; } // Erase dead blocks @@ -312,6 +396,9 @@ void DeadBranchElimPass::Initialize(ir::Module* module) { // TODO(greg-lunarg): Reuse def/use from previous passes def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_)); + // Initialize next unused Id. + next_id_ = module->id_bound(); + // Initialize extension whitelist InitExtensions(); }; @@ -348,6 +435,7 @@ Pass::Status DeadBranchElimPass::ProcessImpl() { return EliminateDeadBranches(fp); }; bool modified = ProcessEntryPointCallTree(pfn, module_); + FinalizeNextId(module_); return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } diff --git a/source/opt/dead_branch_elim_pass.h b/source/opt/dead_branch_elim_pass.h index 3a18ddc..9d9326a 100644 --- a/source/opt/dead_branch_elim_pass.h +++ b/source/opt/dead_branch_elim_pass.h @@ -69,9 +69,13 @@ class DeadBranchElimPass : public MemPass { void ComputeStructuredOrder( ir::Function* func, std::list* order); - // If |condId| is boolean constant, return value in |condVal| and - // |condIsConst| as true, otherwise return |condIsConst| as false. - void GetConstCondition(uint32_t condId, bool* condVal, bool* condIsConst); + // If |condId| is boolean constant, return conditional value in |condVal| and + // return true, otherwise return false. + bool GetConstCondition(uint32_t condId, bool* condVal); + + // If |valId| is a 32-bit integer constant, return value via |value| and + // return true, otherwise return false. + bool GetConstInteger(uint32_t valId, uint32_t* value); // Add branch to |labelId| to end of block |bp|. void AddBranch(uint32_t labelId, ir::BasicBlock* bp); @@ -87,12 +91,11 @@ class DeadBranchElimPass : public MemPass { // Kill all instructions in block |bp|. void KillAllInsts(ir::BasicBlock* bp); - // If block |bp| contains constant conditional branch preceeded by an + // If block |bp| contains conditional branch or switch preceeded by an // OpSelctionMerge, return true and return branch and merge instructions - // in |branchInst| and |mergeInst| and the boolean constant in |condVal|. - bool GetConstConditionalSelectionBranch(ir::BasicBlock* bp, - ir::Instruction** branchInst, ir::Instruction** mergeInst, - uint32_t *condId, bool *condVal); + // in |branchInst| and |mergeInst| and the conditional id in |condId|. + bool GetSelectionBranch(ir::BasicBlock* bp, ir::Instruction** branchInst, + ir::Instruction** mergeInst, uint32_t *condId); // Return true if |labelId| has any non-phi references bool HasNonPhiRef(uint32_t labelId); @@ -111,9 +114,22 @@ class DeadBranchElimPass : public MemPass { // Return true if all extensions in this module are allowed by this pass. bool AllExtensionsSupported() const; + // Save next available id into |module|. + inline void FinalizeNextId(ir::Module* module) { + module->SetIdBound(next_id_); + } + + // Return next available id and calculate next. + inline uint32_t TakeNextId() { + return next_id_++; + } + void Initialize(ir::Module* module); Pass::Status ProcessImpl(); + // Next unused ID + uint32_t next_id_; + // Map from block's label id to block. std::unordered_map id2block_; diff --git a/source/opt/instruction.h b/source/opt/instruction.h index 0ded232..9d49550 100644 --- a/source/opt/instruction.h +++ b/source/opt/instruction.h @@ -191,6 +191,11 @@ class Instruction { inline void ForEachInId(const std::function& f); inline void ForEachInId(const std::function& f) const; + // Runs the given function |f| on all "in" operands + inline void ForEachInOperand(const std::function& f); + inline void ForEachInOperand(const std::function& f) + const; + // Returns true if any operands can be labels inline bool HasLabels() const; @@ -308,6 +313,34 @@ inline void Instruction::ForEachInId( } } +inline void Instruction::ForEachInOperand( + const std::function& f) { + for (auto& opnd : operands_) { + switch (opnd.type) { + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + break; + default: + f(&opnd.words[0]); + break; + } + } +} + +inline void Instruction::ForEachInOperand( + const std::function& f) const { + for (const auto& opnd : operands_) { + switch (opnd.type) { + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + break; + default: + f(&opnd.words[0]); + break; + } + } +} + inline bool Instruction::HasLabels() const { switch (opcode_) { case SpvOpSelectionMerge: diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp index 286232e..661f9c7 100644 --- a/test/opt/dead_branch_elim_test.cpp +++ b/test/opt/dead_branch_elim_test.cpp @@ -679,14 +679,9 @@ OpFunctionEnd %v = OpVariable %_ptr_Function_v4float Function %17 = OpLoad %v4float %BaseColor OpStore %v %17 -OpSelectionMerge %18 None -OpBranchConditional %true %19 %18 +OpBranch %19 %19 = OpLabel OpKill -%18 = OpLabel -%23 = OpLoad %v4float %v -OpStore %gl_FragColor %23 -OpReturn OpFunctionEnd )"; @@ -746,13 +741,9 @@ OpFunctionEnd const std::string after = R"(%foo_ = OpFunction %v4float None %9 %19 = OpLabel -OpSelectionMerge %20 None -OpBranchConditional %true %21 %20 +OpBranch %21 %21 = OpLabel OpReturnValue %13 -%20 = OpLabel -%23 = OpUndef %v4float -OpReturnValue %23 OpFunctionEnd )"; @@ -1068,6 +1059,295 @@ OpFunctionEnd predefs + before, predefs + after, true, true); } +TEST_F(DeadBranchElimTest, SwitchLiveCase) { + // #version 450 + // + // layout (location=0) in vec4 BaseColor; + // layout (location=0) out vec4 OutColor; + // + // void main() + // { + // switch (1) { + // case 0: + // OutColor = vec4(0.0,0.0,0.0,0.0); + // break; + // case 1: + // OutColor = vec4(0.1,0.1,0.1,0.1); + // break; + // case 2: + // OutColor = vec4(0.2,0.2,0.2,0.2); + // break; + // default: + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %OutColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %OutColor "OutColor" +OpName %BaseColor "BaseColor" +OpDecorate %OutColor Location 0 +OpDecorate %BaseColor Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_0_1 = OpConstant %float 0.1 +%15 = OpConstantComposite %v4float %float_0_1 %float_0_1 %float_0_1 %float_0_1 +%float_0_2 = OpConstant %float 0.2 +%17 = OpConstantComposite %v4float %float_0_2 %float_0_2 %float_0_2 %float_0_2 +%float_1 = OpConstant %float 1 +%19 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %6 +%21 = OpLabel +OpSelectionMerge %22 None +OpSwitch %int_1 %23 0 %24 1 %25 2 %26 +%23 = OpLabel +OpStore %OutColor %19 +OpBranch %22 +%24 = OpLabel +OpStore %OutColor %13 +OpBranch %22 +%25 = OpLabel +OpStore %OutColor %15 +OpBranch %22 +%26 = OpLabel +OpStore %OutColor %17 +OpBranch %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %6 +%21 = OpLabel +OpBranch %25 +%25 = OpLabel +OpStore %OutColor %15 +OpBranch %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, SwitchLiveDefault) { + // #version 450 + // + // layout (location=0) in vec4 BaseColor; + // layout (location=0) out vec4 OutColor; + // + // void main() + // { + // switch (7) { + // case 0: + // OutColor = vec4(0.0,0.0,0.0,0.0); + // break; + // case 1: + // OutColor = vec4(0.1,0.1,0.1,0.1); + // break; + // case 2: + // OutColor = vec4(0.2,0.2,0.2,0.2); + // break; + // default: + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %OutColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %OutColor "OutColor" +OpName %BaseColor "BaseColor" +OpDecorate %OutColor Location 0 +OpDecorate %BaseColor Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%int_7 = OpConstant %int 7 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_0_1 = OpConstant %float 0.1 +%15 = OpConstantComposite %v4float %float_0_1 %float_0_1 %float_0_1 %float_0_1 +%float_0_2 = OpConstant %float 0.2 +%17 = OpConstantComposite %v4float %float_0_2 %float_0_2 %float_0_2 %float_0_2 +%float_1 = OpConstant %float 1 +%19 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %6 +%21 = OpLabel +OpSelectionMerge %22 None +OpSwitch %int_7 %23 0 %24 1 %25 2 %26 +%23 = OpLabel +OpStore %OutColor %19 +OpBranch %22 +%24 = OpLabel +OpStore %OutColor %13 +OpBranch %22 +%25 = OpLabel +OpStore %OutColor %15 +OpBranch %22 +%26 = OpLabel +OpStore %OutColor %17 +OpBranch %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %6 +%21 = OpLabel +OpBranch %23 +%23 = OpLabel +OpStore %OutColor %19 +OpBranch %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, SwitchLiveCaseBreakFromLoop) { + // This sample does not directly translate to GLSL/HLSL as + // direct breaks from a loop cannot be made from a switch. + // This construct is currently formed by inlining a function + // containing early returns from the cases of a switch. The + // function is wrapped in a one-trip loop and returns are + // translated to branches to the loop's merge block. + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %OutColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %oc "oc" +OpName %OutColor "OutColor" +OpName %BaseColor "BaseColor" +OpDecorate %OutColor Location 0 +OpDecorate %BaseColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%float_0 = OpConstant %float 0 +%17 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_0_1 = OpConstant %float 0.1 +%19 = OpConstantComposite %v4float %float_0_1 %float_0_1 %float_0_1 %float_0_1 +%float_0_2 = OpConstant %float 0.2 +%21 = OpConstantComposite %v4float %float_0_2 %float_0_2 %float_0_2 %float_0_2 +%float_1 = OpConstant %float 1 +%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%26 = OpLabel +%oc = OpVariable %_ptr_Function_v4float Function +OpBranch %27 +%27 = OpLabel +OpLoopMerge %28 %29 None +OpBranch %30 +%30 = OpLabel +OpSelectionMerge %31 None +OpSwitch %int_1 %31 0 %32 1 %33 2 %34 +%32 = OpLabel +OpStore %oc %17 +OpBranch %28 +%33 = OpLabel +OpStore %oc %19 +OpBranch %28 +%34 = OpLabel +OpStore %oc %21 +OpBranch %28 +%31 = OpLabel +OpStore %oc %23 +OpBranch %28 +%29 = OpLabel +OpBranchConditional %false %27 %28 +%28 = OpLabel +%35 = OpLoad %v4float %oc +OpStore %OutColor %35 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%26 = OpLabel +%oc = OpVariable %_ptr_Function_v4float Function +OpBranch %27 +%27 = OpLabel +OpLoopMerge %28 %29 None +OpBranch %30 +%30 = OpLabel +OpBranch %33 +%33 = OpLabel +OpStore %oc %19 +OpBranch %28 +%29 = OpLabel +OpBranchConditional %false %27 %28 +%28 = OpLabel +%35 = OpLoad %v4float %oc +OpStore %OutColor %35 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // More complex control flow -- 2.7.4