Initial implementation of merge return pass.

Works with current DefUseManager infrastructure.

Added merge return to the standard opts.

Added validation to passes.

Disabled pass for shader capabilty.
This commit is contained in:
Alan Baker 2017-11-08 16:22:10 -05:00 committed by David Neto
parent 0126ad9785
commit a92d69b43d
10 changed files with 505 additions and 3 deletions

View File

@ -89,7 +89,8 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/strip_debug_info_pass.cpp \ source/opt/strip_debug_info_pass.cpp \
source/opt/type_manager.cpp \ source/opt/type_manager.cpp \
source/opt/types.cpp \ source/opt/types.cpp \
source/opt/unify_const_pass.cpp source/opt/unify_const_pass.cpp \
source/opt/merge_return_pass.cpp
# Locations of grammar files. # Locations of grammar files.
SPV_CORE10_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.0/spirv.core.grammar.json SPV_CORE10_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.0/spirv.core.grammar.json

View File

@ -406,6 +406,18 @@ Optimizer::PassToken CreateCFGCleanupPass();
// that are not referenced. // that are not referenced.
Optimizer::PassToken CreateDeadVariableEliminationPass(); Optimizer::PassToken CreateDeadVariableEliminationPass();
// Create merge return pass.
// This pass replaces all returns with unconditional branches to a new block
// containing a return. If necessary, this new block will contain a PHI node to
// select the correct return value.
//
// This pass does not consider unreachable code, nor does it perform any other
// optimizations.
//
// This pass does not currently support structured control flow. It bails out if
// the shader capability is detected.
Optimizer::PassToken CreateMergeReturnPass();
} // namespace spvtools } // namespace spvtools
#endif // SPIRV_TOOLS_OPTIMIZER_HPP_ #endif // SPIRV_TOOLS_OPTIMIZER_HPP_

View File

@ -43,6 +43,7 @@ add_library(SPIRV-Tools-opt
local_single_store_elim_pass.h local_single_store_elim_pass.h
local_ssa_elim_pass.h local_ssa_elim_pass.h
log.h log.h
merge_return_pass.h
module.h module.h
null_pass.h null_pass.h
reflect.h reflect.h
@ -88,6 +89,7 @@ add_library(SPIRV-Tools-opt
local_single_block_elim_pass.cpp local_single_block_elim_pass.cpp
local_single_store_elim_pass.cpp local_single_store_elim_pass.cpp
local_ssa_elim_pass.cpp local_ssa_elim_pass.cpp
merge_return_pass.cpp
module.cpp module.cpp
eliminate_dead_functions_pass.cpp eliminate_dead_functions_pass.cpp
remove_duplicates_pass.cpp remove_duplicates_pass.cpp

View File

@ -0,0 +1,120 @@
// Copyright (c) 2017 Google Inc.
//
// 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 "merge_return_pass.h"
#include "instruction.h"
#include "ir_context.h"
namespace spvtools {
namespace opt {
Pass::Status MergeReturnPass::Process(ir::IRContext* irContext) {
InitializeProcessing(irContext);
// TODO (alanbaker): Support structured control flow. Bail out in the
// meantime.
if (get_module()->HasCapability(SpvCapabilityShader))
return Status::SuccessWithoutChange;
bool modified = false;
for (auto& function : *get_module()) {
std::vector<ir::BasicBlock*> returnBlocks = CollectReturnBlocks(&function);
modified |= MergeReturnBlocks(&function, returnBlocks);
}
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
std::vector<ir::BasicBlock*> MergeReturnPass::CollectReturnBlocks(
ir::Function* function) {
std::vector<ir::BasicBlock*> returnBlocks;
for (auto& block : *function) {
ir::Instruction& terminator = *block.tail();
if (terminator.opcode() == SpvOpReturn ||
terminator.opcode() == SpvOpReturnValue) {
returnBlocks.push_back(&block);
}
}
return returnBlocks;
}
bool MergeReturnPass::MergeReturnBlocks(
ir::Function* function, const std::vector<ir::BasicBlock*>& returnBlocks) {
if (returnBlocks.size() <= 1) {
// No work to do.
return false;
}
// Create a label for the new return block
std::unique_ptr<ir::Instruction> returnLabel(
new ir::Instruction(SpvOpLabel, 0u, TakeNextId(), {}));
uint32_t returnId = returnLabel->result_id();
// Create the new basic block
std::unique_ptr<ir::BasicBlock> returnBlock(
new ir::BasicBlock(std::move(returnLabel)));
function->AddBasicBlock(std::move(returnBlock));
ir::Function::iterator retBlockIter = --function->end();
// Create the PHI for the merged block (if necessary)
// Create new return
std::vector<ir::Operand> phiOps;
for (auto block : returnBlocks) {
if (block->tail()->opcode() == SpvOpReturnValue) {
phiOps.push_back(
{SPV_OPERAND_TYPE_ID, {block->tail()->GetSingleWordInOperand(0u)}});
phiOps.push_back({SPV_OPERAND_TYPE_ID, {block->id()}});
}
}
if (!phiOps.empty()) {
// Need a PHI node to select the correct return value.
uint32_t phiResultId = TakeNextId();
uint32_t phiTypeId = function->type_id();
std::unique_ptr<ir::Instruction> phiInst(
new ir::Instruction(SpvOpPhi, phiTypeId, phiResultId, phiOps));
retBlockIter->AddInstruction(std::move(phiInst));
ir::BasicBlock::iterator phiIter = retBlockIter->tail();
std::unique_ptr<ir::Instruction> returnInst(new ir::Instruction(
SpvOpReturnValue, 0u, 0u, {{SPV_OPERAND_TYPE_ID, {phiResultId}}}));
retBlockIter->AddInstruction(std::move(returnInst));
ir::BasicBlock::iterator ret = retBlockIter->tail();
get_def_use_mgr()->AnalyzeInstDefUse(&*phiIter);
get_def_use_mgr()->AnalyzeInstDef(&*ret);
} else {
std::unique_ptr<ir::Instruction> returnInst(
new ir::Instruction(SpvOpReturn));
retBlockIter->AddInstruction(std::move(returnInst));
}
// Replace returns with branches
for (auto block : returnBlocks) {
context()->KillInst(&*block->tail());
block->tail()->SetOpcode(SpvOpBranch);
block->tail()->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {returnId}}});
get_def_use_mgr()->AnalyzeInstUse(&*block->tail());
get_def_use_mgr()->AnalyzeInstUse(block->GetLabelInst());
}
get_def_use_mgr()->AnalyzeInstDefUse(retBlockIter->GetLabelInst());
return true;
}
} // namespace opt
} // namespace spvtools

View File

@ -0,0 +1,55 @@
// Copyright (c) 2017 Google Inc.
//
// 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 LIBSPIRV_OPT_MERGE_RETURN_PASS_H_
#define LIBSPIRV_OPT_MERGE_RETURN_PASS_H_
#include "basic_block.h"
#include "function.h"
#include "pass.h"
#include <vector>
namespace spvtools {
namespace opt {
// Documented in optimizer.hpp
class MergeReturnPass : public Pass {
public:
MergeReturnPass() = default;
const char* name() const override { return "merge-return-pass"; }
Status Process(ir::IRContext*) override;
ir::IRContext::Analysis GetPreservedAnalyses() override {
return ir::IRContext::kAnalysisDefUse;
}
private:
// Returns all BasicBlocks terminated by OpReturn or OpReturnValue in
// |function|.
std::vector<ir::BasicBlock*> CollectReturnBlocks(ir::Function* function);
// Returns |true| if returns were merged, |false| otherwise.
//
// Creates a new basic block with a single return. If |function| returns a
// value, a phi node is created to select the correct value to return.
// Replaces old returns with an unconditional branch to the new block.
bool MergeReturnBlocks(ir::Function* function,
const std::vector<ir::BasicBlock*>& returnBlocks);
};
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_MERGE_RETURN_PASS_H_

View File

@ -67,7 +67,8 @@ Optimizer& Optimizer::RegisterPass(PassToken&& p) {
} }
Optimizer& Optimizer::RegisterPerformancePasses() { Optimizer& Optimizer::RegisterPerformancePasses() {
return RegisterPass(CreateInlineExhaustivePass()) return RegisterPass(CreateMergeReturnPass())
.RegisterPass(CreateInlineExhaustivePass())
.RegisterPass(CreateLocalAccessChainConvertPass()) .RegisterPass(CreateLocalAccessChainConvertPass())
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
.RegisterPass(CreateLocalSingleStoreElimPass()) .RegisterPass(CreateLocalSingleStoreElimPass())
@ -83,7 +84,8 @@ Optimizer& Optimizer::RegisterPerformancePasses() {
} }
Optimizer& Optimizer::RegisterSizePasses() { Optimizer& Optimizer::RegisterSizePasses() {
return RegisterPass(CreateInlineExhaustivePass()) return RegisterPass(CreateMergeReturnPass())
.RegisterPass(CreateInlineExhaustivePass())
.RegisterPass(CreateLocalAccessChainConvertPass()) .RegisterPass(CreateLocalAccessChainConvertPass())
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
.RegisterPass(CreateLocalSingleStoreElimPass()) .RegisterPass(CreateLocalSingleStoreElimPass())
@ -240,6 +242,11 @@ Optimizer::PassToken CreateCompactIdsPass() {
MakeUnique<opt::CompactIdsPass>()); MakeUnique<opt::CompactIdsPass>());
} }
Optimizer::PassToken CreateMergeReturnPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::MergeReturnPass>());
}
std::vector<const char*> Optimizer::GetPassNames() const { std::vector<const char*> Optimizer::GetPassNames() const {
std::vector<const char*> v; std::vector<const char*> v;
for (uint32_t i = 0; i < impl_->pass_manager.NumPasses(); i++) { for (uint32_t i = 0; i < impl_->pass_manager.NumPasses(); i++) {

View File

@ -41,5 +41,7 @@
#include "strength_reduction_pass.h" #include "strength_reduction_pass.h"
#include "strip_debug_info_pass.h" #include "strip_debug_info_pass.h"
#include "unify_const_pass.h" #include "unify_const_pass.h"
#include "eliminate_dead_functions_pass.h"
#include "merge_return_pass.h"
#endif // LIBSPIRV_OPT_PASSES_H_ #endif // LIBSPIRV_OPT_PASSES_H_

View File

@ -204,3 +204,7 @@ add_spvtools_unittest(TARGET ir_context
LIBS SPIRV-Tools-opt LIBS SPIRV-Tools-opt
) )
add_spvtools_unittest(TARGET pass_merge_return
SRCS pass_merge_return_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt
)

View File

@ -0,0 +1,291 @@
// Copyright (c) 2017 Google Inc.
//
// 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 "spirv-tools/libspirv.hpp"
#include "spirv-tools/optimizer.hpp"
#include "pass_fixture.h"
#include "pass_utils.h"
namespace {
using namespace spvtools;
using MergeReturnPassTest = PassTest<::testing::Test>;
TEST_F(MergeReturnPassTest, OneReturn) {
const std::string before =
R"(OpCapability Addresses
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %1 "simple_kernel"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%1 = OpFunction %2 None %3
%4 = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string after = before;
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
}
TEST_F(MergeReturnPassTest, TwoReturnsNoValue) {
const std::string before =
R"(OpCapability Addresses
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %6 "simple_kernel"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpBranchConditional %4 %8 %9
%8 = OpLabel
OpReturn
%9 = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string after =
R"(OpCapability Addresses
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %6 "simple_kernel"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpBranchConditional %4 %8 %9
%8 = OpLabel
OpBranch %10
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
}
TEST_F(MergeReturnPassTest, TwoReturnsWithValues) {
const std::string before =
R"(OpCapability Linkage
OpCapability Kernel
OpMemoryModel Logical OpenCL
%1 = OpTypeInt 32 0
%2 = OpTypeBool
%3 = OpConstantFalse %2
%4 = OpConstant %1 0
%5 = OpConstant %1 1
%6 = OpTypeFunction %1
%7 = OpFunction %1 None %6
%8 = OpLabel
OpBranchConditional %3 %9 %10
%9 = OpLabel
OpReturnValue %4
%10 = OpLabel
OpReturnValue %5
OpFunctionEnd
)";
const std::string after =
R"(OpCapability Linkage
OpCapability Kernel
OpMemoryModel Logical OpenCL
%1 = OpTypeInt 32 0
%2 = OpTypeBool
%3 = OpConstantFalse %2
%4 = OpConstant %1 0
%5 = OpConstant %1 1
%6 = OpTypeFunction %1
%7 = OpFunction %1 None %6
%8 = OpLabel
OpBranchConditional %3 %9 %10
%9 = OpLabel
OpBranch %11
%10 = OpLabel
OpBranch %11
%11 = OpLabel
%12 = OpPhi %1 %4 %9 %5 %10
OpReturnValue %12
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
}
TEST_F(MergeReturnPassTest, UnreachableReturnsNoValue) {
const std::string before =
R"(OpCapability Addresses
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %6 "simple_kernel"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpReturn
%8 = OpLabel
OpBranchConditional %4 %9 %10
%9 = OpLabel
OpReturn
%10 = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string after =
R"(OpCapability Addresses
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %6 "simple_kernel"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpBranch %11
%8 = OpLabel
OpBranchConditional %4 %9 %10
%9 = OpLabel
OpBranch %11
%10 = OpLabel
OpBranch %11
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
}
TEST_F(MergeReturnPassTest, UnreachableReturnsWithValues) {
const std::string before =
R"(OpCapability Linkage
OpCapability Kernel
OpMemoryModel Logical OpenCL
%1 = OpTypeInt 32 0
%2 = OpTypeBool
%3 = OpConstantFalse %2
%4 = OpConstant %1 0
%5 = OpConstant %1 1
%6 = OpTypeFunction %1
%7 = OpFunction %1 None %6
%8 = OpLabel
%9 = OpIAdd %1 %4 %5
OpReturnValue %9
%10 = OpLabel
OpBranchConditional %3 %11 %12
%11 = OpLabel
OpReturnValue %4
%12 = OpLabel
OpReturnValue %5
OpFunctionEnd
)";
const std::string after =
R"(OpCapability Linkage
OpCapability Kernel
OpMemoryModel Logical OpenCL
%1 = OpTypeInt 32 0
%2 = OpTypeBool
%3 = OpConstantFalse %2
%4 = OpConstant %1 0
%5 = OpConstant %1 1
%6 = OpTypeFunction %1
%7 = OpFunction %1 None %6
%8 = OpLabel
%9 = OpIAdd %1 %4 %5
OpBranch %13
%10 = OpLabel
OpBranchConditional %3 %11 %12
%11 = OpLabel
OpBranch %13
%12 = OpLabel
OpBranch %13
%13 = OpLabel
%14 = OpPhi %1 %9 %8 %4 %11 %5 %12
OpReturnValue %14
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
}
TEST_F(MergeReturnPassTest, StructuredControlFlowNOP) {
const std::string before =
R"(OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %6 "simple_shader"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpSelectionMerge %10 None
OpBranchConditional %4 %8 %9
%8 = OpLabel
OpReturn
%9 = OpLabel
OpReturn
%10 = OpLabel
OpUnreachable
OpFunctionEnd
)";
const std::string after = before;
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
}
} // anonymous namespace

View File

@ -152,6 +152,12 @@ Options:
Join two blocks into a single block if the second has the Join two blocks into a single block if the second has the
first as its only predecessor. Performed only on entry point first as its only predecessor. Performed only on entry point
call tree functions. call tree functions.
--merge-return
Replace all return instructions with unconditional branches to
a new basic block containing an unified return.
This pass does not currently support structured control flow. It
makes no changes if the shader capability is detected.
--strength-reduction --strength-reduction
Replaces instructions with equivalent and less expensive ones. Replaces instructions with equivalent and less expensive ones.
--eliminate-dead-variables --eliminate-dead-variables
@ -352,6 +358,8 @@ OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
optimizer->RegisterPass(CreateLocalSingleStoreElimPass()); optimizer->RegisterPass(CreateLocalSingleStoreElimPass());
} else if (0 == strcmp(cur_arg, "--merge-blocks")) { } else if (0 == strcmp(cur_arg, "--merge-blocks")) {
optimizer->RegisterPass(CreateBlockMergePass()); optimizer->RegisterPass(CreateBlockMergePass());
} else if (0 == strcmp(cur_arg, "--merge-return")) {
optimizer->RegisterPass(CreateMergeReturnPass());
} else if (0 == strcmp(cur_arg, "--eliminate-dead-branches")) { } else if (0 == strcmp(cur_arg, "--eliminate-dead-branches")) {
optimizer->RegisterPass(CreateDeadBranchElimPass()); optimizer->RegisterPass(CreateDeadBranchElimPass());
} else if (0 == strcmp(cur_arg, "--eliminate-dead-functions")) { } else if (0 == strcmp(cur_arg, "--eliminate-dead-functions")) {