Do not inline OpKill Instructions (#2713)
It is illegal to inline an OpKill instruction into a continue construct because the continue header will no longer dominate the backedge.
This commit adds a check for this, and does not inline.
If we still want to be able to inline a function that contains an OpKill, we can add a new pass that will wrap OpKill instructions into its own function with just the single instruction.
I do not believe that this is a common case right now, so I will not do that yet.
Fixes #2433.
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index f348bbe..01ed5b8 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -629,12 +629,39 @@
return true;
}
-bool InlinePass::IsInlinableFunctionCall(const Instruction* inst) {
+bool InlinePass::IsInlinableFunctionCall(Instruction* inst) {
if (inst->opcode() != SpvOp::SpvOpFunctionCall) return false;
const uint32_t calleeFnId =
inst->GetSingleWordOperand(kSpvFunctionCallFunctionId);
const auto ci = inlinable_.find(calleeFnId);
- return ci != inlinable_.cend();
+ if (ci == inlinable_.cend()) {
+ return false;
+ }
+
+ if (funcs_with_opkill_.count(calleeFnId) == 0) {
+ return true;
+ }
+
+ // We cannot inline into a continue construct if the function has an OpKill.
+ auto* cfg_analysis = context()->GetStructuredCFGAnalysis();
+ BasicBlock* bb = context()->get_instr_block(inst);
+ uint32_t loop_header_id = cfg_analysis->ContainingLoop(bb->id());
+ if (loop_header_id == 0) {
+ // Not in a loop, so we can inline.
+ return true;
+ }
+ BasicBlock* loop_header_bb = context()->get_instr_block(loop_header_id);
+ uint32_t loop_continue =
+ loop_header_bb->GetLoopMergeInst()->GetSingleWordOperand(1);
+
+ Function* caller_func = bb->GetParent();
+ DominatorAnalysis* dom = context()->GetDominatorAnalysis(caller_func);
+ if (dom->Dominates(loop_continue, bb->id())) {
+ // The function call is the continue construct and the callee contains an
+ // OpKill.
+ return false;
+ }
+ return true;
}
void InlinePass::UpdateSucceedingPhis(
@@ -711,6 +738,9 @@
// 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.
+
+ // Do not inline functions with an OpKill because they may be inlined into a
+ // continue construct.
AnalyzeReturns(func);
if (no_return_in_loop_.find(func->result_id()) == no_return_in_loop_.cend()) {
return false;
@@ -741,6 +771,13 @@
}
// Compute inlinability
if (IsInlinableFunction(&fn)) inlinable_.insert(fn.result_id());
+
+ bool has_opkill = !fn.WhileEachInst(
+ [](Instruction* inst) { return inst->opcode() != SpvOpKill; });
+
+ if (has_opkill) {
+ funcs_with_opkill_.insert(fn.result_id());
+ }
}
}
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h
index ecfe964..e17dddb 100644
--- a/source/opt/inline_pass.h
+++ b/source/opt/inline_pass.h
@@ -122,7 +122,7 @@
UptrVectorIterator<BasicBlock> call_block_itr);
// Return true if |inst| is a function call that can be inlined.
- bool IsInlinableFunctionCall(const Instruction* inst);
+ bool IsInlinableFunctionCall(Instruction* inst);
// Return true if |func| does not have a return that is
// nested in a structured if, switch or loop.
@@ -159,6 +159,9 @@
// Set of ids of functions with no returns in loop
std::set<uint32_t> no_return_in_loop_;
+ // Set of ids of functions with no returns in loop
+ std::unordered_set<uint32_t> funcs_with_opkill_;
+
// Set of ids of inlinable functions
std::set<uint32_t> inlinable_;
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index 7170812..fd38e47 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -3112,6 +3112,121 @@
SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
}
+TEST_F(InlineTest, DontInlineFuncWithOpKill) {
+ const std::string test =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 330
+OpName %main "main"
+OpName %kill_ "kill("
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpLoopMerge %11 %12 None
+OpBranch %13
+%13 = OpLabel
+OpBranchConditional %true %10 %11
+%10 = OpLabel
+OpBranch %12
+%12 = OpLabel
+%16 = OpFunctionCall %void %kill_
+OpBranch %9
+%11 = OpLabel
+OpReturn
+OpFunctionEnd
+%kill_ = OpFunction %void None %3
+%7 = OpLabel
+OpKill
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
+}
+
+TEST_F(InlineTest, InlineFuncWithOpKill) {
+ const std::string before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 330
+OpName %main "main"
+OpName %kill_ "kill("
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpLoopMerge %11 %12 None
+OpBranch %13
+%13 = OpLabel
+OpBranchConditional %true %10 %11
+%10 = OpLabel
+%16 = OpFunctionCall %void %kill_
+OpBranch %12
+%12 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpReturn
+OpFunctionEnd
+%kill_ = OpFunction %void None %3
+%7 = OpLabel
+OpKill
+OpFunctionEnd
+)";
+ const std::string after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 330
+OpName %main "main"
+OpName %kill_ "kill("
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpLoopMerge %11 %12 None
+OpBranch %13
+%13 = OpLabel
+OpBranchConditional %true %10 %11
+%10 = OpLabel
+OpKill
+%17 = OpLabel
+OpBranch %12
+%12 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpReturn
+OpFunctionEnd
+%kill_ = OpFunction %void None %3
+%7 = OpLabel
+OpKill
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InlineExhaustivePass>(before, after, false, true);
+}
+
// TODO(greg-lunarg): Add tests to verify handling of these cases:
//
// Empty modules