// limitations under the License.
#include "inline_pass.h"
+#include "cfa.h"
// Indices of operands in SPIR-V instructions
static const int kSpvReturnValueId = 0;
static const int kSpvTypePointerStorageClass = 1;
static const int kSpvTypePointerTypeId = 2;
+static const int kSpvLoopMergeMergeBlockId = 0;
+static const int kSpvSelectionMergeMergeBlockId = 0;
namespace spvtools {
namespace opt {
}
void InlinePass::AddBranch(uint32_t label_id,
- std::unique_ptr<ir::BasicBlock>* block_ptr) {
+ std::unique_ptr<ir::BasicBlock>* block_ptr) {
+ std::unique_ptr<ir::Instruction> newBranch(new ir::Instruction(
+ SpvOpBranch, 0, 0,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
+ (*block_ptr)->AddInstruction(std::move(newBranch));
+}
+
+void InlinePass::AddBranchCond(uint32_t cond_id, uint32_t true_id,
+ uint32_t false_id, std::unique_ptr<ir::BasicBlock>* block_ptr) {
std::unique_ptr<ir::Instruction> newBranch(new ir::Instruction(
- SpvOpBranch, 0, 0,
- {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
+ SpvOpBranchConditional, 0, 0,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {cond_id}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {true_id}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {false_id}}}));
(*block_ptr)->AddInstruction(std::move(newBranch));
}
+void InlinePass::AddLoopMerge(uint32_t merge_id, uint32_t continue_id,
+ std::unique_ptr<ir::BasicBlock>* block_ptr) {
+ std::unique_ptr<ir::Instruction> newLoopMerge(new ir::Instruction(
+ SpvOpLoopMerge, 0, 0,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {continue_id}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_LOOP_CONTROL, {0}}}));
+ (*block_ptr)->AddInstruction(std::move(newLoopMerge));
+}
+
void InlinePass::AddStore(uint32_t ptr_id, uint32_t val_id,
std::unique_ptr<ir::BasicBlock>* block_ptr) {
std::unique_ptr<ir::Instruction> newStore(new ir::Instruction(
return newLabel;
}
+uint32_t InlinePass::GetFalseId() {
+ if (false_id_ != 0)
+ return false_id_;
+ false_id_ = module_->GetGlobalValue(SpvOpConstantFalse);
+ if (false_id_ != 0)
+ return false_id_;
+ uint32_t boolId = module_->GetGlobalValue(SpvOpTypeBool);
+ if (boolId == 0) {
+ boolId = TakeNextId();
+ module_->AddGlobalValue(SpvOpTypeBool, boolId, 0);
+ }
+ false_id_ = TakeNextId();
+ module_->AddGlobalValue(SpvOpConstantFalse, false_id_, boolId);
+ return false_id_;
+}
+
void InlinePass::MapParams(
ir::Function* calleeFn,
ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
const uint32_t pid = cpi->result_id();
(*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
kSpvFunctionCallArgumentId + param_idx);
- param_idx++;
+ ++param_idx;
});
}
var_inst->SetResultId(newId);
(*callee2caller)[callee_var_itr->result_id()] = newId;
new_vars->push_back(std::move(var_inst));
- callee_var_itr++;
+ ++callee_var_itr;
}
}
ir::Function* calleeFn = id2function_[call_inst_itr->GetSingleWordOperand(
kSpvFunctionCallFunctionId)];
+ // Check for early returns
+ auto fi = early_return_.find(calleeFn->result_id());
+ bool earlyReturn = fi != early_return_.end();
+
// Map parameters to actual arguments.
MapParams(calleeFn, call_inst_itr, &callee2caller);
// Clone and map callee code. Copy caller block code to beginning of
// first block and end of last block.
bool prevInstWasReturn = false;
+ uint32_t singleTripLoopHeaderId = 0;
+ uint32_t singleTripLoopContinueId = 0;
uint32_t returnLabelId = 0;
bool multiBlocks = false;
const uint32_t calleeTypeId = calleeFn->type_id();
calleeFn->ForEachInst([&new_blocks, &callee2caller, &call_block_itr,
&call_inst_itr, &new_blk_ptr, &prevInstWasReturn,
&returnLabelId, &returnVarId, &calleeTypeId,
- &multiBlocks, &postCallSB, &preCallSB, this](
+ &multiBlocks, &postCallSB, &preCallSB, &earlyReturn,
+ &singleTripLoopHeaderId, &singleTripLoopContinueId,
+ this](
const ir::Instruction* cpi) {
switch (cpi->opcode()) {
case SpvOpFunction:
} else {
// First block needs to use label of original block
// but map callee label in case of phi reference.
- labelId = call_block_itr->label_id();
+ labelId = call_block_itr->id();
callee2caller[cpi->result_id()] = labelId;
firstBlock = true;
}
if (firstBlock) {
// Copy contents of original caller block up to call instruction.
for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
- cii++) {
+ ++cii) {
std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*cii));
// Remember same-block ops for possible regeneration.
if (IsSameBlockOp(&*cp_inst)) {
}
new_blk_ptr->AddInstruction(std::move(cp_inst));
}
+ // If callee is early return function, insert header block for
+ // one-trip loop that will encompass callee code. Start postheader
+ // block.
+ if (earlyReturn) {
+ singleTripLoopHeaderId = this->TakeNextId();
+ AddBranch(singleTripLoopHeaderId, &new_blk_ptr);
+ new_blocks->push_back(std::move(new_blk_ptr));
+ new_blk_ptr.reset(new ir::BasicBlock(NewLabel(
+ singleTripLoopHeaderId)));
+ returnLabelId = this->TakeNextId();
+ singleTripLoopContinueId = this->TakeNextId();
+ AddLoopMerge(returnLabelId, singleTripLoopContinueId, &new_blk_ptr);
+ uint32_t postHeaderId = this->TakeNextId();
+ AddBranch(postHeaderId, &new_blk_ptr);
+ new_blocks->push_back(std::move(new_blk_ptr));
+ new_blk_ptr.reset(new ir::BasicBlock(NewLabel(postHeaderId)));
+ multiBlocks = true;
+ }
} else {
multiBlocks = true;
}
prevInstWasReturn = true;
} break;
case SpvOpFunctionEnd: {
- // If there was an early return, create return label/block.
+ // If there was an early return, insert continue and return blocks.
// If previous instruction was return, insert branch instruction
// to return block.
if (returnLabelId != 0) {
if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr);
new_blocks->push_back(std::move(new_blk_ptr));
+ new_blk_ptr.reset(new ir::BasicBlock(NewLabel(
+ singleTripLoopContinueId)));
+ AddBranchCond(GetFalseId(), singleTripLoopHeaderId, returnLabelId,
+ &new_blk_ptr);
+ new_blocks->push_back(std::move(new_blk_ptr));
new_blk_ptr.reset(new ir::BasicBlock(NewLabel(returnLabelId)));
multiBlocks = true;
}
}
// Copy remaining instructions from caller block.
auto cii = call_inst_itr;
- for (cii++; cii != call_block_itr->end(); cii++) {
+ for (++cii; cii != call_block_itr->end(); ++cii) {
std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*cii));
// If multiple blocks generated, regenerate any same-block
// instruction that has not been seen in this last block.
const auto mapItr = callee2caller.find(*iid);
if (mapItr != callee2caller.end()) {
*iid = mapItr->second;
- } else if (cpi->has_labels()) {
+ } else if (cpi->HasLabels()) {
const ir::Instruction* inst =
def_use_mgr_->id_to_defs().find(*iid)->second;
if (inst->opcode() == SpvOpLabel) {
});
// Update block map given replacement blocks.
for (auto& blk : *new_blocks) {
- id2block_[blk->label_id()] = &*blk;
+ id2block_[blk->id()] = &*blk;
}
}
bool InlinePass::Inline(ir::Function* func) {
bool modified = false;
// Using block iterators here because of block erasures and insertions.
- for (auto bi = func->begin(); bi != func->end(); bi++) {
+ for (auto bi = func->begin(); bi != func->end(); ++bi) {
for (auto ii = bi->begin(); ii != bi->end();) {
if (IsInlinableFunctionCall(&*ii)) {
// Inline call.
if (newBlocks.size() > 1) {
const auto firstBlk = newBlocks.begin();
const auto lastBlk = newBlocks.end() - 1;
- const uint32_t firstId = (*firstBlk)->label_id();
- const uint32_t lastId = (*lastBlk)->label_id();
+ const uint32_t firstId = (*firstBlk)->id();
+ const uint32_t lastId = (*lastBlk)->id();
(*lastBlk)->ForEachSuccessorLabel(
[&firstId, &lastId, this](uint32_t succ) {
ir::BasicBlock* sbp = this->id2block_[succ];
ii = bi->begin();
modified = true;
} else {
- ii++;
+ ++ii;
}
}
}
return modified;
}
-bool InlinePass::IsInlinableFunction(const ir::Function* func) {
- // We can only inline a function if it has blocks.
- if (func->cbegin() == func->cend())
+bool InlinePass::HasMultipleReturns(ir::Function* func) {
+ bool seenReturn = false;
+ bool multipleReturns = false;
+ for (auto& blk : *func) {
+ auto terminal_ii = blk.cend();
+ --terminal_ii;
+ if (terminal_ii->opcode() == SpvOpReturn ||
+ terminal_ii->opcode() == SpvOpReturnValue) {
+ if (seenReturn) {
+ multipleReturns = true;
+ break;
+ }
+ seenReturn = true;
+ }
+ }
+ return multipleReturns;
+}
+
+uint32_t InlinePass::MergeBlockIdIfAny(const ir::BasicBlock& blk) {
+ auto merge_ii = blk.cend();
+ --merge_ii;
+ uint32_t mbid = 0;
+ if (merge_ii != blk.cbegin()) {
+ --merge_ii;
+ if (merge_ii->opcode() == SpvOpLoopMerge)
+ mbid = merge_ii->GetSingleWordOperand(kSpvLoopMergeMergeBlockId);
+ else if (merge_ii->opcode() == SpvOpSelectionMerge)
+ mbid = merge_ii->GetSingleWordOperand(kSpvSelectionMergeMergeBlockId);
+ }
+ return mbid;
+}
+
+void InlinePass::ComputeStructuredSuccessors(ir::Function* func) {
+ // If header, make merge block first successor.
+ for (auto& blk : *func) {
+ uint32_t mbid = MergeBlockIdIfAny(blk);
+ if (mbid != 0)
+ block2structured_succs_[&blk].push_back(id2block_[mbid]);
+ // add true successors
+ blk.ForEachSuccessorLabel([&blk, this](uint32_t sbid) {
+ block2structured_succs_[&blk].push_back(id2block_[sbid]);
+ });
+ }
+}
+
+InlinePass::GetBlocksFunction InlinePass::StructuredSuccessorsFunction() {
+ return [this](const ir::BasicBlock* block) {
+ return &(block2structured_succs_[block]);
+ };
+}
+
+bool InlinePass::HasNoReturnInLoop(ir::Function* func) {
+ // If control not structured, do not do loop/return analysis
+ // TODO: Analyze returns in non-structured control flow
+ if (!module_->HasCapability(SpvCapabilityShader))
return false;
- // Do not inline functions with multiple returns
- // TODO(greg-lunarg): Enable inlining if no return is in loop
- int returnCnt = 0;
- for (auto bi = func->cbegin(); bi != func->cend(); bi++) {
- auto li = bi->cend();
- li--;
- if (li->opcode() == SpvOpReturn || li->opcode() == SpvOpReturnValue) {
- if (returnCnt > 0)
- return false;
- returnCnt++;
+ // Compute structured block order. This order has the property
+ // that dominators are before all blocks they dominate and merge blocks
+ // are after all blocks that are in the control constructs of their header.
+ ComputeStructuredSuccessors(func);
+ auto ignore_block = [](cbb_ptr) {};
+ auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
+ std::list<const ir::BasicBlock*> structuredOrder;
+ spvtools::CFA<ir::BasicBlock>::DepthFirstTraversal(
+ &*func->begin(), StructuredSuccessorsFunction(), ignore_block,
+ [&](cbb_ptr b) { structuredOrder.push_front(b); }, ignore_edge);
+ // Search for returns in loops. Only need to track outermost loop
+ bool return_in_loop = false;
+ uint32_t outerLoopMergeId = 0;
+ for (auto& blk : structuredOrder) {
+ // Exiting current outer loop
+ if (blk->id() == outerLoopMergeId)
+ outerLoopMergeId = 0;
+ // Return block
+ auto terminal_ii = blk->cend();
+ --terminal_ii;
+ if (terminal_ii->opcode() == SpvOpReturn ||
+ terminal_ii->opcode() == SpvOpReturnValue) {
+ if (outerLoopMergeId != 0) {
+ return_in_loop = true;
+ break;
+ }
+ }
+ else if (terminal_ii != blk->cbegin()) {
+ auto merge_ii = terminal_ii;
+ --merge_ii;
+ // Entering outermost loop
+ if (merge_ii->opcode() == SpvOpLoopMerge && outerLoopMergeId == 0)
+ outerLoopMergeId = merge_ii->GetSingleWordOperand(
+ kSpvLoopMergeMergeBlockId);
}
}
- return true;
+ return !return_in_loop;
+}
+
+void InlinePass::AnalyzeReturns(ir::Function* func) {
+ // Look for multiple returns
+ if (!HasMultipleReturns(func)) {
+ no_return_in_loop_.insert(func->result_id());
+ return;
+ }
+ early_return_.insert(func->result_id());
+ // If multiple returns, see if any are in a loop
+ if (HasNoReturnInLoop(func))
+ no_return_in_loop_.insert(func->result_id());
+}
+
+bool InlinePass::IsInlinableFunction(ir::Function* func) {
+ // We can only inline a function if it has blocks.
+ if (func->cbegin() == func->cend())
+ return false;
+ // Do not inline functions with returns in loops. Currently early return
+ // functions are inlined by wrapping them in a one trip loop and implementing
+ // the returns as a branch to the loop's merge block. However, this can only
+ // done validly if the return was not in a loop in the original function.
+ // Also remember functions with multiple (early) returns.
+ AnalyzeReturns(func);
+ const auto ci = no_return_in_loop_.find(func->result_id());
+ return ci != no_return_in_loop_.cend();
}
void InlinePass::Initialize(ir::Module* module) {
// Save module.
module_ = module;
- // Initialize function and block maps.
+ false_id_ = 0;
+
id2function_.clear();
id2block_.clear();
+ block2structured_succs_.clear();
inlinable_.clear();
for (auto& fn : *module_) {
+ // Initialize function and block maps.
id2function_[fn.result_id()] = &fn;
for (auto& blk : fn) {
- id2block_[blk.label_id()] = &blk;
+ id2block_[blk.id()] = &blk;
}
+ // Compute inlinability
if (IsInlinableFunction(&fn))
inlinable_.insert(fn.result_id());
}
#include <memory>
#include <unordered_map>
#include <vector>
+#include <list>
#include "def_use_manager.h"
#include "module.h"
// See optimizer.hpp for documentation.
class InlinePass : public Pass {
+
+ using cbb_ptr = const ir::BasicBlock*;
+
public:
+ using GetBlocksFunction =
+ std::function<std::vector<ir::BasicBlock*>*(const ir::BasicBlock*)>;
+
InlinePass();
Status Process(ir::Module*) override;
// Add unconditional branch to labelId to end of block block_ptr.
void AddBranch(uint32_t labelId, std::unique_ptr<ir::BasicBlock>* block_ptr);
+ // Add conditional branch to end of block |block_ptr|.
+ void AddBranchCond(uint32_t cond_id, uint32_t true_id,
+ uint32_t false_id, std::unique_ptr<ir::BasicBlock>* block_ptr);
+
+ // Add unconditional branch to labelId to end of block block_ptr.
+ void AddLoopMerge(uint32_t merge_id, uint32_t continue_id,
+ std::unique_ptr<ir::BasicBlock>* block_ptr);
+
// Add store of valId to ptrId to end of block block_ptr.
void AddStore(uint32_t ptrId, uint32_t valId,
std::unique_ptr<ir::BasicBlock>* block_ptr);
// Return new label.
std::unique_ptr<ir::Instruction> NewLabel(uint32_t label_id);
+ // Returns the id for the boolean false value. Looks in the module first
+ // and creates it if not found. Remembers it for future calls.
+ uint32_t GetFalseId();
+
// Map callee params to caller args
void MapParams(ir::Function* calleeFn,
ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
ir::UptrVectorIterator<ir::BasicBlock> call_block_itr);
- // Returns true if |inst| is a function call that can be inlined.
+ // Return true if |inst| is a function call that can be inlined.
bool IsInlinableFunctionCall(const ir::Instruction* inst);
- // Returns true if |func| is a function that can be inlined.
- bool IsInlinableFunction(const ir::Function* func);
+ // Returns the id of the merge block declared by a merge instruction in
+ // this block, if any. If none, returns zero.
+ uint32_t MergeBlockIdIfAny(const ir::BasicBlock& blk);
+
+ // Compute structured successors for function |func|.
+ // A block's structured successors are the blocks it branches to
+ // together with its declared merge block if it has one.
+ // When order matters, the merge block always appears first.
+ // This assures correct depth first search in the presence of early
+ // returns and kills. If the successor vector contain duplicates
+ // if the merge block, they are safely ignored by DFS.
+ void ComputeStructuredSuccessors(ir::Function* func);
+
+ // Return function to return ordered structure successors for a given block
+ // Assumes ComputeStructuredSuccessors() has been called.
+ GetBlocksFunction StructuredSuccessorsFunction();
+
+ // Return true if |func| has multiple returns
+ bool HasMultipleReturns(ir::Function* func);
+
+ // Return true if |func| has no return in a loop. The current analysis
+ // requires structured control flow, so return false if control flow not
+ // structured ie. module is not a shader.
+ bool HasNoReturnInLoop(ir::Function* func);
+
+ // Find all functions with multiple returns and no returns in loops
+ void AnalyzeReturns(ir::Function* func);
+
+ // Return true if |func| is a function that can be inlined.
+ bool IsInlinableFunction(ir::Function* func);
// Exhaustively inline all function calls in func as well as in
// all code that is inlined into func. Return true if func is modified.
// Map from block's label id to block.
std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
- // Set of ids of inlinable function
+ // Set of ids of functions with early returns
+ std::set<uint32_t> early_return_;
+
+ // Set of ids of functions with no returns in loop
+ std::set<uint32_t> no_return_in_loop_;
+
+ // Set of ids of inlinable functions
std::set<uint32_t> inlinable_;
+ // Map from block to its structured successor blocks. See
+ // ComputeStructuredSuccessors() for definition.
+ std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
+ block2structured_succs_;
+
+ // result id for OpConstantFalse
+ uint32_t false_id_;
+
// Next unused ID
uint32_t next_id_;
};
/* skip_nop = */ false, /* do_validate = */ true);
}
-TEST_F(InlineTest, EarlyReturnFunctionIsNotInlined) {
+TEST_F(InlineTest, EarlyReturnFunctionInlined) {
// #version 140
//
// in vec4 BaseColor;
// gl_FragColor = color;
// }
- const std::string assembly =
+ const std::string predefs =
R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
%BaseColor = OpVariable %_ptr_Input_v4float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-%main = OpFunction %void None %10
+)";
+
+ const std::string nonEntryFuncs =
+ R"(%foo_vf4_ = OpFunction %float None %14
+%bar = OpFunctionParameter %_ptr_Function_v4float
+%27 = OpLabel
+%28 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%29 = OpLoad %float %28
+%30 = OpFOrdLessThan %bool %29 %float_0
+OpSelectionMerge %31 None
+OpBranchConditional %30 %32 %31
+%32 = OpLabel
+OpReturnValue %float_0
+%31 = OpLabel
+%33 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%34 = OpLoad %float %33
+OpReturnValue %34
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %10
%22 = OpLabel
%color = OpVariable %_ptr_Function_v4float Function
%param = OpVariable %_ptr_Function_v4float Function
OpStore %gl_FragColor %26
OpReturn
OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%false = OpConstantFalse %bool
+%main = OpFunction %void None %10
+%22 = OpLabel
+%35 = OpVariable %_ptr_Function_float Function
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%23 = OpLoad %v4float %BaseColor
+OpStore %param %23
+OpBranch %36
+%36 = OpLabel
+OpLoopMerge %37 %38 None
+OpBranch %39
+%39 = OpLabel
+%40 = OpAccessChain %_ptr_Function_float %param %uint_0
+%41 = OpLoad %float %40
+%42 = OpFOrdLessThan %bool %41 %float_0
+OpSelectionMerge %43 None
+OpBranchConditional %42 %44 %43
+%44 = OpLabel
+OpStore %35 %float_0
+OpBranch %37
+%43 = OpLabel
+%45 = OpAccessChain %_ptr_Function_float %param %uint_0
+%46 = OpLoad %float %45
+OpStore %35 %46
+OpBranch %37
+%38 = OpLabel
+OpBranchConditional %false %36 %37
+%37 = OpLabel
+%24 = OpLoad %float %35
+%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %color %25
+%26 = OpLoad %v4float %color
+OpStore %gl_FragColor %26
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<opt::InlinePass>(predefs + before + nonEntryFuncs,
+ predefs + after + nonEntryFuncs, false, true);
+}
+TEST_F(InlineTest, EarlyReturnInLoopIsNotInlined) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // while (true) {
+ // if (bar.x < 0.0)
+ // return 0.0;
+ // return bar.x;
+ // }
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color = vec4(foo(BaseColor));
+ // gl_FragColor = color;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %foo_vf4_ "foo(vf4;"
+OpName %bar "bar"
+OpName %color "color"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%14 = OpTypeFunction %float %_ptr_Function_v4float
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %10
+%23 = OpLabel
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%24 = OpLoad %v4float %BaseColor
+OpStore %param %24
+%25 = OpFunctionCall %float %foo_vf4_ %param
+%26 = OpCompositeConstruct %v4float %25 %25 %25 %25
+OpStore %color %26
+%27 = OpLoad %v4float %color
+OpStore %gl_FragColor %27
+OpReturn
+OpFunctionEnd
%foo_vf4_ = OpFunction %float None %14
%bar = OpFunctionParameter %_ptr_Function_v4float
-%27 = OpLabel
-%28 = OpAccessChain %_ptr_Function_float %bar %uint_0
-%29 = OpLoad %float %28
-%30 = OpFOrdLessThan %bool %29 %float_0
-OpSelectionMerge %31 None
-OpBranchConditional %30 %32 %31
+%28 = OpLabel
+OpBranch %29
+%29 = OpLabel
+OpLoopMerge %30 %31 None
+OpBranch %32
%32 = OpLabel
+OpBranchConditional %true %33 %30
+%33 = OpLabel
+%34 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%35 = OpLoad %float %34
+%36 = OpFOrdLessThan %bool %35 %float_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %37
+%38 = OpLabel
OpReturnValue %float_0
+%37 = OpLabel
+%39 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%40 = OpLoad %float %39
+OpReturnValue %40
%31 = OpLabel
-%33 = OpAccessChain %_ptr_Function_float %bar %uint_0
-%34 = OpLoad %float %33
-OpReturnValue %34
+OpBranch %29
+%30 = OpLabel
+%41 = OpUndef %float
+OpReturnValue %41
OpFunctionEnd
)";