Replace OpKill With function call. (#2790)

We are no able to inline OpKill instructions into a continue construct.
See #2433.  However, we have to be able to inline to correctly do
legalization.  This commit creates a pass that will wrap OpKill
instructions into a function of its own.  That way we are able to inline
the rest of the code.

The follow up to this will be to not inline any function that contains
an OpKill.

Fixes #2726
This commit is contained in:
Steven Perron 2019-08-14 09:27:12 -04:00 committed by GitHub
parent f701237f2d
commit 60043edfa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 446 additions and 10 deletions

View File

@ -171,7 +171,8 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/upgrade_memory_model.cpp \
source/opt/value_number_table.cpp \
source/opt/vector_dce.cpp \
source/opt/workaround1209.cpp
source/opt/workaround1209.cpp \
source/opt/wrap_opkill.cpp
# Locations of grammar files.
#

View File

@ -650,6 +650,8 @@ static_library("spvtools_opt") {
"source/opt/vector_dce.h",
"source/opt/workaround1209.cpp",
"source/opt/workaround1209.h",
"source/opt/wrap_opkill.cpp",
"source/opt/wrap_opkill.h",
]
deps = [

View File

@ -795,6 +795,10 @@ Optimizer::PassToken CreateGraphicsRobustAccessPass();
// for the first index.
Optimizer::PassToken CreateDescriptorScalarReplacementPass();
// Create a pass to replace all OpKill instruction with a function call to a
// function that has a single OpKill. This allows more code to be inlined.
Optimizer::PassToken CreateWrapOpKillPass();
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_

View File

@ -113,6 +113,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
value_number_table.h
vector_dce.h
workaround1209.h
wrap_opkill.h
aggressive_dead_code_elim_pass.cpp
basic_block.cpp
@ -212,6 +213,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
value_number_table.cpp
vector_dce.cpp
workaround1209.cpp
wrap_opkill.cpp
)
if(MSVC)

View File

@ -465,6 +465,20 @@ class InstructionBuilder {
return AddInstruction(std::move(new_inst));
}
Instruction* AddFunctionCall(uint32_t result_type, uint32_t function,
const std::vector<uint32_t>& parameters) {
std::vector<Operand> operands;
operands.push_back({SPV_OPERAND_TYPE_ID, {function}});
for (uint32_t id : parameters) {
operands.push_back({SPV_OPERAND_TYPE_ID, {id}});
}
std::unique_ptr<Instruction> new_inst(
new Instruction(GetContext(), SpvOpFunctionCall, result_type,
GetContext()->TakeNextId(), operands));
return AddInstruction(std::move(new_inst));
}
// Inserts the new instruction before the insertion point.
Instruction* AddInstruction(std::unique_ptr<Instruction>&& insn) {
Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
@ -512,6 +526,10 @@ class InstructionBuilder {
// Returns true if the users requested to update |analysis|.
inline bool IsAnalysisUpdateRequested(IRContext::Analysis analysis) const {
if (!GetContext()->AreAnalysesValid(analysis)) {
// Do not try to update something that is not built.
return false;
}
return preserved_analyses_ & analysis;
}

View File

@ -107,8 +107,10 @@ Optimizer& Optimizer::RegisterPass(PassToken&& p) {
// or enable more copy propagation.
Optimizer& Optimizer::RegisterLegalizationPasses() {
return
// Remove unreachable block so that merge return works.
RegisterPass(CreateDeadBranchElimPass())
// Wrap OpKill instructions so all other code can be inlined.
RegisterPass(CreateWrapOpKillPass())
// Remove unreachable block so that merge return works.
.RegisterPass(CreateDeadBranchElimPass())
// Merge the returns so we can inline.
.RegisterPass(CreateMergeReturnPass())
// Make sure uses and definitions are in the same function.
@ -154,7 +156,8 @@ Optimizer& Optimizer::RegisterLegalizationPasses() {
}
Optimizer& Optimizer::RegisterPerformancePasses() {
return RegisterPass(CreateDeadBranchElimPass())
return RegisterPass(CreateWrapOpKillPass())
.RegisterPass(CreateDeadBranchElimPass())
.RegisterPass(CreateMergeReturnPass())
.RegisterPass(CreateInlineExhaustivePass())
.RegisterPass(CreateAggressiveDCEPass())
@ -190,7 +193,8 @@ Optimizer& Optimizer::RegisterPerformancePasses() {
}
Optimizer& Optimizer::RegisterSizePasses() {
return RegisterPass(CreateDeadBranchElimPass())
return RegisterPass(CreateWrapOpKillPass())
.RegisterPass(CreateDeadBranchElimPass())
.RegisterPass(CreateMergeReturnPass())
.RegisterPass(CreateInlineExhaustivePass())
.RegisterPass(CreateAggressiveDCEPass())
@ -477,6 +481,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
RegisterPass(CreateDecomposeInitializedVariablesPass());
} else if (pass_name == "graphics-robust-access") {
RegisterPass(CreateGraphicsRobustAccessPass());
} else if (pass_name == "wrap-opkill") {
RegisterPass(CreateWrapOpKillPass());
} else {
Errorf(consumer(), nullptr, {},
"Unknown flag '--%s'. Use --help for a list of valid flags",
@ -893,4 +899,8 @@ Optimizer::PassToken CreateDescriptorScalarReplacementPass() {
MakeUnique<opt::DescriptorScalarReplacement>());
}
Optimizer::PassToken CreateWrapOpKillPass() {
return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::WrapOpKill>());
}
} // namespace spvtools

View File

@ -76,5 +76,6 @@
#include "source/opt/upgrade_memory_model.h"
#include "source/opt/vector_dce.h"
#include "source/opt/workaround1209.h"
#include "source/opt/wrap_opkill.h"
#endif // SOURCE_OPT_PASSES_H_

View File

@ -571,10 +571,10 @@ void Pointer::GetExtraHashWords(std::vector<uint32_t>* words,
void Pointer::SetPointeeType(const Type* type) { pointee_type_ = type; }
Function::Function(Type* ret_type, const std::vector<const Type*>& params)
Function::Function(const Type* ret_type, const std::vector<const Type*>& params)
: Type(kFunction), return_type_(ret_type), param_types_(params) {}
Function::Function(Type* ret_type, std::vector<const Type*>& params)
Function::Function(const Type* ret_type, std::vector<const Type*>& params)
: Type(kFunction), return_type_(ret_type), param_types_(params) {}
bool Function::IsSameImpl(const Type* that, IsSameCache* seen) const {

View File

@ -520,8 +520,8 @@ class Pointer : public Type {
class Function : public Type {
public:
Function(Type* ret_type, const std::vector<const Type*>& params);
Function(Type* ret_type, std::vector<const Type*>& params);
Function(const Type* ret_type, const std::vector<const Type*>& params);
Function(const Type* ret_type, std::vector<const Type*>& params);
Function(const Function&) = default;
std::string str() const override;

125
source/opt/wrap_opkill.cpp Normal file
View File

@ -0,0 +1,125 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/opt/wrap_opkill.h"
#include "ir_builder.h"
namespace spvtools {
namespace opt {
Pass::Status WrapOpKill::Process() {
bool modified = false;
for (auto& func : *get_module()) {
func.ForEachInst([this, &modified](Instruction* inst) {
if (inst->opcode() == SpvOpKill) {
modified = true;
ReplaceWithFunctionCall(inst);
}
});
}
if (opkill_function_ != nullptr) {
assert(modified &&
"The function should only be generated if something was modified.");
context()->AddFunction(std::move(opkill_function_));
}
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
}
void WrapOpKill::ReplaceWithFunctionCall(Instruction* inst) {
assert(inst->opcode() == SpvOpKill &&
"|inst| must be an OpKill instruction.");
InstructionBuilder ir_builder(
context(), inst,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
ir_builder.AddFunctionCall(GetVoidTypeId(), GetOpKillFuncId(), {});
ir_builder.AddUnreachable();
context()->KillInst(inst);
}
uint32_t WrapOpKill::GetVoidTypeId() {
if (void_type_id_ != 0) {
return void_type_id_;
}
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Void void_type;
void_type_id_ = type_mgr->GetTypeInstruction(&void_type);
return void_type_id_;
}
uint32_t WrapOpKill::GetVoidFunctionTypeId() {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Void void_type;
const analysis::Type* registered_void_type =
type_mgr->GetRegisteredType(&void_type);
analysis::Function func_type(registered_void_type, {});
return type_mgr->GetTypeInstruction(&func_type);
}
uint32_t WrapOpKill::GetOpKillFuncId() {
if (opkill_function_ != nullptr) {
return opkill_function_->result_id();
}
uint32_t opkill_func_id = TakeNextId();
// Generate the function start instruction
std::unique_ptr<Instruction> func_start(new Instruction(
context(), SpvOpFunction, GetVoidTypeId(), opkill_func_id, {}));
func_start->AddOperand({SPV_OPERAND_TYPE_FUNCTION_CONTROL, {0}});
func_start->AddOperand({SPV_OPERAND_TYPE_ID, {GetVoidFunctionTypeId()}});
opkill_function_.reset(new Function(std::move(func_start)));
// Generate the function end instruction
std::unique_ptr<Instruction> func_end(
new Instruction(context(), SpvOpFunctionEnd, 0, 0, {}));
opkill_function_->SetFunctionEnd(std::move(func_end));
// Create the one basic block for the function.
std::unique_ptr<Instruction> label_inst(
new Instruction(context(), SpvOpLabel, 0, TakeNextId(), {}));
std::unique_ptr<BasicBlock> bb(new BasicBlock(std::move(label_inst)));
// Add the OpKill to the basic block
std::unique_ptr<Instruction> kill_inst(
new Instruction(context(), SpvOpKill, 0, 0, {}));
bb->AddInstruction(std::move(kill_inst));
// Add the bb to the function
opkill_function_->AddBasicBlock(std::move(bb));
// Add the function to the module.
if (context()->AreAnalysesValid(IRContext::kAnalysisDefUse)) {
opkill_function_->ForEachInst(
[this](Instruction* inst) { context()->AnalyzeDefUse(inst); });
}
if (context()->AreAnalysesValid(IRContext::kAnalysisInstrToBlockMapping)) {
for (BasicBlock& basic_block : *opkill_function_) {
context()->set_instr_block(basic_block.GetLabelInst(), &basic_block);
for (Instruction& inst : basic_block) {
context()->set_instr_block(&inst, &basic_block);
}
}
}
return opkill_function_->result_id();
}
} // namespace opt
} // namespace spvtools

70
source/opt/wrap_opkill.h Normal file
View File

@ -0,0 +1,70 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_OPT_WRAP_OPKILL_H_
#define SOURCE_OPT_WRAP_OPKILL_H_
#include "source/opt/pass.h"
namespace spvtools {
namespace opt {
// Documented in optimizer.hpp
class WrapOpKill : public Pass {
public:
WrapOpKill() : void_type_id_(0) {}
const char* name() const override { return "wrap-opkill"; }
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
return IRContext::kAnalysisDefUse |
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId |
IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisConstants |
IRContext::kAnalysisTypes;
}
private:
// Replaces the OpKill instruction |inst| with a function call to a function
// that contains a single instruction, which is OpKill. An OpUnreachable
// instruction will be placed after the function call.
void ReplaceWithFunctionCall(Instruction* inst);
// Returns the id of the void type.
uint32_t GetVoidTypeId();
// Returns the id of the function type for a void function with no parameters.
uint32_t GetVoidFunctionTypeId();
// Return the id of a function that has return type void, no no parameters,
// and contains a single instruction, which is an OpKill.
uint32_t GetOpKillFuncId();
// The id of the void type. If its value is 0, then the void type has not
// been found or created yet.
uint32_t void_type_id_;
// The function that is a single instruction, which is an OpKill. The
// function has a void return type and takes no parameters. If the function is
// |nullptr|, then the function has not been generated.
std::unique_ptr<Function> opkill_function_;
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_WRAP_OPKILL_H_

View File

@ -97,6 +97,7 @@ add_spvtools_unittest(TARGET opt
value_table_test.cpp
vector_dce_test.cpp
workaround1209_test.cpp
wrap_opkill_test.cpp
LIBS SPIRV-Tools-opt
PCH_FILE pch_test_opt
)

View File

@ -0,0 +1,198 @@
// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "gmock/gmock.h"
#include "test/opt/assembly_builder.h"
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"
namespace spvtools {
namespace opt {
namespace {
using WrapOpKillTest = PassTest<::testing::Test>;
TEST_F(WrapOpKillTest, SingleOpKill) {
const std::string text = R"(
; CHECK: OpEntryPoint Fragment [[main:%\w+]]
; CHECK: [[main]] = OpFunction
; CHECK: OpFunctionCall %void [[orig_kill:%\w+]]
; CHECK: [[orig_kill]] = OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpFunctionCall %void [[new_kill:%\w+]]
; CHECK-NEXT: OpUnreachable
; CHECK: [[new_kill]] = OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpKill
; CHECK-NEXT: OpFunctionEnd
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 330
OpName %main "main"
%void = OpTypeVoid
%5 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%main = OpFunction %void None %5
%8 = OpLabel
OpBranch %9
%9 = OpLabel
OpLoopMerge %10 %11 None
OpBranch %12
%12 = OpLabel
OpBranchConditional %true %13 %10
%13 = OpLabel
OpBranch %11
%11 = OpLabel
%14 = OpFunctionCall %void %kill_
OpBranch %9
%10 = OpLabel
OpReturn
OpFunctionEnd
%kill_ = OpFunction %void None %5
%15 = OpLabel
OpKill
OpFunctionEnd
)";
SinglePassRunAndMatch<WrapOpKill>(text, true);
}
TEST_F(WrapOpKillTest, MultipleOpKillInSameFunc) {
const std::string text = R"(
; CHECK: OpEntryPoint Fragment [[main:%\w+]]
; CHECK: [[main]] = OpFunction
; CHECK: OpFunctionCall %void [[orig_kill:%\w+]]
; CHECK: [[orig_kill]] = OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpSelectionMerge
; CHECK-NEXT: OpBranchConditional
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpFunctionCall %void [[new_kill:%\w+]]
; CHECK-NEXT: OpUnreachable
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpFunctionCall %void [[new_kill]]
; CHECK-NEXT: OpUnreachable
; CHECK: [[new_kill]] = OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpKill
; CHECK-NEXT: OpFunctionEnd
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 330
OpName %main "main"
%void = OpTypeVoid
%5 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%main = OpFunction %void None %5
%8 = OpLabel
OpBranch %9
%9 = OpLabel
OpLoopMerge %10 %11 None
OpBranch %12
%12 = OpLabel
OpBranchConditional %true %13 %10
%13 = OpLabel
OpBranch %11
%11 = OpLabel
%14 = OpFunctionCall %void %kill_
OpBranch %9
%10 = OpLabel
OpReturn
OpFunctionEnd
%kill_ = OpFunction %void None %5
%15 = OpLabel
OpSelectionMerge %16 None
OpBranchConditional %true %17 %18
%17 = OpLabel
OpKill
%18 = OpLabel
OpKill
%16 = OpLabel
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<WrapOpKill>(text, true);
}
TEST_F(WrapOpKillTest, MultipleOpKillInDifferentFunc) {
const std::string text = R"(
; CHECK: OpEntryPoint Fragment [[main:%\w+]]
; CHECK: [[main]] = OpFunction
; CHECK: OpFunctionCall %void [[orig_kill1:%\w+]]
; CHECK-NEXT: OpFunctionCall %void [[orig_kill2:%\w+]]
; CHECK: [[orig_kill1]] = OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpFunctionCall %void [[new_kill:%\w+]]
; CHECK-NEXT: OpUnreachable
; CHECK: [[orig_kill2]] = OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpFunctionCall %void [[new_kill]]
; CHECK-NEXT: OpUnreachable
; CHECK: [[new_kill]] = OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpKill
; CHECK-NEXT: OpFunctionEnd
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 330
OpName %main "main"
%void = OpTypeVoid
%4 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%main = OpFunction %void None %4
%7 = OpLabel
OpBranch %8
%8 = OpLabel
OpLoopMerge %9 %10 None
OpBranch %11
%11 = OpLabel
OpBranchConditional %true %12 %9
%12 = OpLabel
OpBranch %10
%10 = OpLabel
%13 = OpFunctionCall %void %14
%15 = OpFunctionCall %void %16
OpBranch %8
%9 = OpLabel
OpReturn
OpFunctionEnd
%14 = OpFunction %void None %4
%17 = OpLabel
OpKill
OpFunctionEnd
%16 = OpFunction %void None %4
%18 = OpLabel
OpKill
OpFunctionEnd
)";
SinglePassRunAndMatch<WrapOpKill>(text, true);
}
} // namespace
} // namespace opt
} // namespace spvtools

View File

@ -57,7 +57,7 @@ class TestValidPassFlags(expect.ValidObjectFile1_4,
"""Tests that spirv-opt accepts all valid optimization flags."""
flags = [
'--ccp', '--cfg-cleanup', '--combine-access-chains', '--compact-ids',
'--wrap-opkill', '--ccp', '--cfg-cleanup', '--combine-access-chains', '--compact-ids',
'--convert-local-access-chains', '--copy-propagate-arrays',
'--eliminate-dead-branches',
'--eliminate-dead-code-aggressive', '--eliminate-dead-const',
@ -76,6 +76,7 @@ class TestValidPassFlags(expect.ValidObjectFile1_4,
'--unify-const'
]
expected_passes = [
'wrap-opkill',
'ccp',
'cfg-cleanup',
'combine-access-chains',
@ -134,6 +135,7 @@ class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_4,
flags = ['-O']
expected_passes = [
'wrap-opkill',
'eliminate-dead-branches',
'merge-return',
'inline-entry-points-exhaustive',
@ -181,6 +183,7 @@ class TestSizeOptimizationPasses(expect.ValidObjectFile1_4,
flags = ['-Os']
expected_passes = [
'wrap-opkill',
'eliminate-dead-branches',
'merge-return',
'inline-entry-points-exhaustive',
@ -221,6 +224,7 @@ class TestLegalizationPasses(expect.ValidObjectFile1_4,
flags = ['--legalize-hlsl']
expected_passes = [
'wrap-opkill',
'eliminate-dead-branches',
'merge-return',
'inline-entry-points-exhaustive',