Skip to content
Commits on Source (81)
......@@ -76,10 +76,12 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/aggressive_dead_code_elim_pass.cpp \
source/opt/basic_block.cpp \
source/opt/block_merge_pass.cpp \
source/opt/block_merge_util.cpp \
source/opt/build_module.cpp \
source/opt/cfg.cpp \
source/opt/cfg_cleanup_pass.cpp \
source/opt/ccp_pass.cpp \
source/opt/code_sink.cpp \
source/opt/combine_access_chains.cpp \
source/opt/common_uniform_elim_pass.cpp \
source/opt/compact_ids_pass.cpp \
......@@ -96,6 +98,8 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/dominator_tree.cpp \
source/opt/eliminate_dead_constant_pass.cpp \
source/opt/eliminate_dead_functions_pass.cpp \
source/opt/eliminate_dead_functions_util.cpp \
source/opt/eliminate_dead_members_pass.cpp \
source/opt/feature_manager.cpp \
source/opt/flatten_decoration_pass.cpp \
source/opt/fold.cpp \
......
......@@ -305,7 +305,6 @@ source_set("spvtools_headers") {
static_library("spvtools") {
deps = [
":spvtools_core_enums_unified1",
":spvtools_core_tables_unified1",
":spvtools_generators_inc",
":spvtools_glsl_tables_glsl1-0",
......@@ -376,6 +375,7 @@ static_library("spvtools") {
]
public_deps = [
":spvtools_core_enums_unified1",
":spvtools_headers",
]
......@@ -452,6 +452,8 @@ static_library("spvtools_opt") {
"source/opt/basic_block.h",
"source/opt/block_merge_pass.cpp",
"source/opt/block_merge_pass.h",
"source/opt/block_merge_util.cpp",
"source/opt/block_merge_util.h",
"source/opt/build_module.cpp",
"source/opt/build_module.h",
"source/opt/ccp_pass.cpp",
......@@ -460,6 +462,8 @@ static_library("spvtools_opt") {
"source/opt/cfg.h",
"source/opt/cfg_cleanup_pass.cpp",
"source/opt/cfg_cleanup_pass.h",
"source/opt/code_sink.cpp",
"source/opt/code_sink.h",
"source/opt/combine_access_chains.cpp",
"source/opt/combine_access_chains.h",
"source/opt/common_uniform_elim_pass.cpp",
......@@ -492,6 +496,10 @@ static_library("spvtools_opt") {
"source/opt/eliminate_dead_constant_pass.h",
"source/opt/eliminate_dead_functions_pass.cpp",
"source/opt/eliminate_dead_functions_pass.h",
"source/opt/eliminate_dead_functions_util.cpp",
"source/opt/eliminate_dead_functions_util.h",
"source/opt/eliminate_dead_members_pass.cpp",
"source/opt/eliminate_dead_members_pass.h",
"source/opt/feature_manager.cpp",
"source/opt/feature_manager.h",
"source/opt/flatten_decoration_pass.cpp",
......@@ -649,46 +657,9 @@ group("SPIRV-Tools") {
]
}
if (!build_with_chromium) {
googletest_dir = spirv_tools_googletest_dir
config("gtest_config") {
include_dirs = [
"${googletest_dir}/googletest",
"${googletest_dir}/googletest/include",
]
}
static_library("gtest") {
testonly = true
sources = [
"${googletest_dir}/googletest/src/gtest-all.cc",
]
public_configs = [ ":gtest_config" ]
}
config("gmock_config") {
include_dirs = [
"${googletest_dir}/googlemock",
"${googletest_dir}/googlemock/include",
"${googletest_dir}/googletest/include",
]
if (is_clang) {
# TODO: Can remove this if/when the issue is fixed.
# https://github.com/google/googletest/issues/533
cflags = [ "-Wno-inconsistent-missing-override" ]
}
}
static_library("gmock") {
testonly = true
sources = [
"${googletest_dir}/googlemock/src/gmock-all.cc",
]
public_configs = [ ":gmock_config" ]
}
}
# The tests are scoped to Chromium to avoid needing to write gtest integration.
# See Chromium's third_party/googletest/BUILD.gn for a complete integration.
if (build_with_chromium) {
test("spvtools_test") {
sources = [
"test/assembly_context_test.cpp",
......@@ -751,24 +722,13 @@ test("spvtools_test") {
]
deps = [
":spvtools",
":spvtools_language_header_unified1",
":spvtools_val",
]
if (build_with_chromium) {
deps += [
"//testing/gmock",
"//testing/gtest",
"//testing/gtest:gtest_main",
":spvtools",
":spvtools_language_header_unified1",
":spvtools_val",
]
} else {
deps += [
":gmock",
":gtest",
]
sources += [ "${googletest_dir}/googletest/src/gtest_main.cc" ]
}
if (is_clang) {
cflags_cc = [ "-Wno-self-assign" ]
......@@ -776,6 +736,7 @@ test("spvtools_test") {
configs += [ ":spvtools_internal_config" ]
}
}
if (spirv_tools_standalone) {
group("fuzzers") {
......
Revision history for SPIRV-Tools
v2019.2 2019-02-20
- General:
- Support SPV_EXT_physical_storage_buffer
- A number of memory leak have been fixed.
- Removed use of deprecated Google test macro:
- Changed the BUILD.gn to only build tests in Chromium.
- Optimizer
- Upgrade memory model improvments for modf and frexp.
- Add a new pass to move loads closer to their uses: code sinking.
- Invalidating the type manager now invalidates the constnat manager.
- Expand instrumentation pass for bindless bounds checking to runtime-sized descriptor arrays.
- Add a new pass that removes members from structs that are not used: dead member elimination.
Fixes:
- #2292: Remove undefined behaviour when folding bit shifts.
- #2294: Fixes for instrumentation code.
- #2293: Fix overflow when folding -INT_MIN.
- #2374: Don't merge unreachable blocks when merging blocks.
- Validator
- Support SPV_KHR_no_integer_wrap and related decorations.
- Validate Vulkan rules for OpTypeRuntimeArray.
- Validate NonWritable decoration.
- Many WebGPU specific validation rules were added.
- Validate variable pointer related function call rules.
- Better error messages.
Fixes:
- #2307: Check forwards references in OpTypeArray.
- #2315, #2303: Fixed the layout check for relaxed layout.
- #1628: Emit an error when an OpSwitch target is not an OpLabel.
- Reduce
- Added more documentation for spirv-reduce.
- Add ability to remove OpPhi instructions.
- Add ability to merge two basic blocks.
- Add ability to remove unused functions and unused basic blocks.
Fixes:
v2019.1 2019-01-07
- General:
- Created a new tool called spirv-reduce.
......
......@@ -128,6 +128,23 @@ See the [CHANGES](CHANGES) file for reports on completed work, and the [General
sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/2) for
planned and in-progress work.
### Reducer
*Note:* The reducer is still under development.
The reducer simplifies and shrinks a SPIR-V module with respect to a
user-supplied *interestingness function*. For example, given a large
SPIR-V module that cause some SPIR-V compiler to fail with a given
fatal error message, the reducer could be used to look for a smaller
version of the module that causes the compiler to fail with the same
fatal error message.
To suggest an additional capability for the reducer, [file an
issue](https://github.com/KhronosGroup/SPIRV-Tools/issues]) with
"Reducer:" as the start of its title.
### Extras
* [Utility filters](#utility-filters)
......@@ -415,6 +432,18 @@ The validator operates on the binary form.
* `spirv-val` - the standalone validator
* `<spirv-dir>/tools/val`
### Reducer tool
The reducer shrinks a SPIR-V binary module, guided by a user-supplied
*interestingness test*.
This is a work in progress, with initially only shrinks a module in a few ways.
* `spirv-reduce` - the standalone reducer
* `<spirv-dir>/tools/reduce`
Run `spirv-reduce --help` to see how to specify interestingness.
### Control flow dumper tool
The control flow dumper prints the control flow graph for a SPIR-V module as a
......
......@@ -30,17 +30,17 @@ namespace spvtools {
// Stream Output Buffer Offsets
//
// The following values provide 32-bit word offsets into the output buffer
// The following values provide offsets into the output buffer struct
// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
// by InstBindlessCheckPass.
//
// The first word of the debug output buffer contains the next available word
// The first member of the debug output buffer contains the next available word
// in the data stream to be written. Shaders will atomically read and update
// this value so as not to overwrite each others records. This value must be
// initialized to zero
static const int kDebugOutputSizeOffset = 0;
// The second word of the output buffer is the start of the stream of records
// The second member of the output buffer is the start of the stream of records
// written by the instrumented shaders. Each record represents a validation
// error. The format of the records is documented below.
static const int kDebugOutputDataOffset = 1;
......@@ -75,8 +75,8 @@ static const int kInstCommonOutCnt = 4;
// error.
//
// Vertex Shader Output Record Offsets
static const int kInstVertOutVertexId = kInstCommonOutCnt;
static const int kInstVertOutInstanceId = kInstCommonOutCnt + 1;
static const int kInstVertOutVertexIndex = kInstCommonOutCnt;
static const int kInstVertOutInstanceIndex = kInstCommonOutCnt + 1;
// Frag Shader Output Record Offsets
static const int kInstFragOutFragCoordX = kInstCommonOutCnt;
......@@ -122,14 +122,45 @@ static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
// These are the possible validation error codes.
static const int kInstErrorBindlessBounds = 0;
// Direct Input Buffer Offsets
//
// The following values provide member offsets into the input buffers
// consumed by InstrumentPass::GenDebugDirectRead(). This method is utilized
// by InstBindlessCheckPass.
//
// The only object in an input buffer is a runtime array of unsigned
// integers. Each validation will have its own formatting of this array.
static const int kDebugInputDataOffset = 0;
// Debug Buffer Bindings
//
// These are the bindings for the different buffers which are
// read or written by the instrumentation passes.
//
// This is the output buffer written by InstBindlessCheckPass.
// This is the output buffer written by InstBindlessCheckPass
// and possibly other future validations.
static const int kDebugOutputBindingStream = 0;
// The binding for the input buffer for InstBindlessCheckPass. The input
// buffer needs only be created if the shaders being validated contain a
// descriptor array of runtime size, and validation of runtime size descriptor
// arrays have been enabled at the time of the bindless validation pass
// creation.
static const int kDebugInputBindingBindless = 1;
// Bindless Validation Input Buffer Format
//
// An input buffer for bindless validation consists of a single array of
// unsigned integers we will call Data[]. This array is formatted as follows.
//
// At the beginning of the array is a single uint reserved for future use.
static const int kDebugInputBindlessOffsetReserved = 0;
// Following the reserved uint is some number of uints such that the following
// is true: the number of descriptors at (set=s, binding=b) is:
// Data[ Data[ s + kDebugInputBindlessOffsetLengths ] + b ]
static const int kDebugInputBindlessOffsetLengths = 1;
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
......@@ -445,6 +445,10 @@ typedef enum {
// Returns a string describing the given SPIR-V target environment.
SPIRV_TOOLS_EXPORT const char* spvTargetEnvDescription(spv_target_env env);
// Parses s into *env and returns true if successful. If unparsable, returns
// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
SPIRV_TOOLS_EXPORT bool spvParseTargetEnv(const char* s, spv_target_env* env);
// Creates a context object. Returns null if env is invalid.
SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env);
......
......@@ -235,6 +235,9 @@ class SpirvTools {
bool Validate(const uint32_t* binary, size_t binary_size,
spv_validator_options options) const;
// Was this object successfully constructed.
bool IsValid() const;
private:
struct Impl; // Opaque struct for holding the data fields used by this class.
std::unique_ptr<Impl> impl_; // Unique pointer to implementation data.
......
......@@ -226,6 +226,11 @@ Optimizer::PassToken CreateStripReflectInfoPass();
// functions are not needed because they will never be called.
Optimizer::PassToken CreateEliminateDeadFunctionsPass();
// Creates an eliminate-dead-members pass.
// An eliminate-dead-members pass will remove all unused members of structures.
// This will not affect the data layout of the remaining members.
Optimizer::PassToken CreateEliminateDeadMembersPass();
// Creates a set-spec-constant-default-value pass from a mapping from spec-ids
// to the default values in the form of string.
// A set-spec-constant-default-value pass sets the default values for the
......@@ -711,10 +716,12 @@ Optimizer::PassToken CreateCombineAccessChainsPass();
// The instrumentation will read and write buffers in debug
// descriptor set |desc_set|. It will write |shader_id| in each output record
// to identify the shader module which generated the record.
// |runtime_array_enable| controls instrumentation of runtime arrays which
// require input buffer support.
//
// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
uint32_t shader_id);
Optimizer::PassToken CreateInstBindlessCheckPass(
uint32_t desc_set, uint32_t shader_id, bool runtime_array_enable = false);
// Create a pass to upgrade to the VulkanKHR memory model.
// This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
......@@ -722,6 +729,10 @@ Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
// conform to that model's requirements.
Optimizer::PassToken CreateUpgradeMemoryModelPass();
// Create a pass to do code sinking. Code sinking is a transformation
// where an instruction is moved into a more deeply nested construct.
Optimizer::PassToken CreateCodeSinkingPass();
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
......@@ -37,7 +37,7 @@ cd $SHADERC_DIR/third_party
# Get shaderc dependencies. Link the appropriate SPIRV-Tools.
git clone https://github.com/google/googletest.git
git clone https://github.com/google/glslang.git
git clone https://github.com/KhronosGroup/glslang.git
ln -s $GITHUB_DIR/SPIRV-Tools spirv-tools
git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-headers
git clone https://github.com/google/re2
......
......@@ -128,4 +128,6 @@ bool SpirvTools::Validate(const uint32_t* binary, const size_t binary_size,
return valid;
}
bool SpirvTools::IsValid() const { return impl_->context != nullptr; }
} // namespace spvtools
......@@ -15,10 +15,12 @@ set(SPIRV_TOOLS_OPT_SOURCES
aggressive_dead_code_elim_pass.h
basic_block.h
block_merge_pass.h
block_merge_util.h
build_module.h
ccp_pass.h
cfg_cleanup_pass.h
cfg.h
code_sink.h
combine_access_chains.h
common_uniform_elim_pass.h
compact_ids_pass.h
......@@ -35,6 +37,8 @@ set(SPIRV_TOOLS_OPT_SOURCES
dominator_tree.h
eliminate_dead_constant_pass.h
eliminate_dead_functions_pass.h
eliminate_dead_functions_util.h
eliminate_dead_members_pass.h
feature_manager.h
flatten_decoration_pass.h
fold.h
......@@ -107,10 +111,12 @@ set(SPIRV_TOOLS_OPT_SOURCES
aggressive_dead_code_elim_pass.cpp
basic_block.cpp
block_merge_pass.cpp
block_merge_util.cpp
build_module.cpp
ccp_pass.cpp
cfg_cleanup_pass.cpp
cfg.cpp
code_sink.cpp
combine_access_chains.cpp
common_uniform_elim_pass.cpp
compact_ids_pass.cpp
......@@ -127,6 +133,8 @@ set(SPIRV_TOOLS_OPT_SOURCES
dominator_tree.cpp
eliminate_dead_constant_pass.cpp
eliminate_dead_functions_pass.cpp
eliminate_dead_functions_util.cpp
eliminate_dead_members_pass.cpp
feature_manager.cpp
flatten_decoration_pass.cpp
fold.cpp
......
......@@ -18,6 +18,7 @@
#include <vector>
#include "source/opt/block_merge_util.h"
#include "source/opt/ir_context.h"
#include "source/opt/iterator.h"
......@@ -27,112 +28,17 @@ namespace opt {
bool BlockMergePass::MergeBlocks(Function* func) {
bool modified = false;
for (auto bi = func->begin(); bi != func->end();) {
// Find block with single successor which has no other predecessors.
auto ii = bi->end();
--ii;
Instruction* br = &*ii;
if (br->opcode() != SpvOpBranch) {
++bi;
continue;
}
const uint32_t lab_id = br->GetSingleWordInOperand(0);
if (cfg()->preds(lab_id).size() != 1) {
++bi;
continue;
}
bool pred_is_merge = IsMerge(&*bi);
bool succ_is_merge = IsMerge(lab_id);
if (pred_is_merge && succ_is_merge) {
// Cannot merge two merges together.
++bi;
continue;
}
Instruction* merge_inst = bi->GetMergeInst();
bool pred_is_header = IsHeader(&*bi);
if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
bool succ_is_header = IsHeader(lab_id);
if (pred_is_header && succ_is_header) {
// Cannot merge two headers together when the successor is not the merge
// block of the predecessor.
++bi;
continue;
}
// If this is a header block and the successor is not its merge, we must
// be careful about which blocks we are willing to merge together.
// OpLoopMerge must be followed by a conditional or unconditional branch.
// The merge must be a loop merge because a selection merge cannot be
// followed by an unconditional branch.
BasicBlock* succ_block = context()->get_instr_block(lab_id);
SpvOp succ_term_op = succ_block->terminator()->opcode();
assert(merge_inst->opcode() == SpvOpLoopMerge);
if (succ_term_op != SpvOpBranch &&
succ_term_op != SpvOpBranchConditional) {
++bi;
continue;
}
}
// Merge blocks.
context()->KillInst(br);
auto sbi = bi;
for (; sbi != func->end(); ++sbi)
if (sbi->id() == lab_id) break;
// If bi is sbi's only predecessor, it dominates sbi and thus
// sbi must follow bi in func's ordering.
assert(sbi != func->end());
// Update the inst-to-block mapping for the instructions in sbi.
for (auto& inst : *sbi) {
context()->set_instr_block(&inst, &*bi);
}
// Now actually move the instructions.
bi->AddInstructions(&*sbi);
if (merge_inst) {
if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) {
// Merging the header and merge blocks, so remove the structured control
// flow declaration.
context()->KillInst(merge_inst);
} else {
// Move the merge instruction to just before the terminator.
merge_inst->InsertBefore(bi->terminator());
}
}
context()->ReplaceAllUsesWith(lab_id, bi->id());
context()->KillInst(sbi->GetLabelInst());
(void)sbi.Erase();
if (blockmergeutil::CanMergeWithSuccessor(context(), &*bi)) {
blockmergeutil::MergeWithSuccessor(context(), func, bi);
// Reprocess block.
modified = true;
} else {
++bi;
}
return modified;
}
bool BlockMergePass::IsHeader(BasicBlock* block) {
return block->GetMergeInst() != nullptr;
}
bool BlockMergePass::IsHeader(uint32_t id) {
return IsHeader(context()->get_instr_block(get_def_use_mgr()->GetDef(id)));
}
bool BlockMergePass::IsMerge(uint32_t id) {
return !get_def_use_mgr()->WhileEachUse(id, [](Instruction* user,
uint32_t index) {
SpvOp op = user->opcode();
if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) {
return false;
}
return true;
});
return modified;
}
bool BlockMergePass::IsMerge(BasicBlock* block) { return IsMerge(block->id()); }
Pass::Status BlockMergePass::Process() {
// Process all entry point functions.
ProcessFunction pfn = [this](Function* fp) { return MergeBlocks(fp); };
......
......@@ -54,14 +54,6 @@ class BlockMergePass : public Pass {
// with no other predecessors. Merge these blocks into a single block.
bool MergeBlocks(Function* func);
// Returns true if |block| (or |id|) contains a merge instruction.
bool IsHeader(BasicBlock* block);
bool IsHeader(uint32_t id);
// Returns true if |block| (or |id|) is the merge target of a merge
// instruction.
bool IsMerge(BasicBlock* block);
bool IsMerge(uint32_t id);
};
} // namespace opt
......
// Copyright (c) 2017 The Khronos Group Inc.
// Copyright (c) 2017 Valve Corporation
// Copyright (c) 2017 LunarG Inc.
// 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 "block_merge_util.h"
namespace spvtools {
namespace opt {
namespace blockmergeutil {
namespace {
// Returns true if |block| contains a merge instruction.
bool IsHeader(BasicBlock* block) { return block->GetMergeInst() != nullptr; }
// Returns true if |id| contains a merge instruction.
bool IsHeader(IRContext* context, uint32_t id) {
return IsHeader(
context->get_instr_block(context->get_def_use_mgr()->GetDef(id)));
}
// Returns true if |id| is the merge target of a merge instruction.
bool IsMerge(IRContext* context, uint32_t id) {
return !context->get_def_use_mgr()->WhileEachUse(id, [](Instruction* user,
uint32_t index) {
SpvOp op = user->opcode();
if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) {
return false;
}
return true;
});
}
// Returns true if |block| is the merge target of a merge instruction.
bool IsMerge(IRContext* context, BasicBlock* block) {
return IsMerge(context, block->id());
}
// Removes any OpPhi instructions in |block|, which should have exactly one
// predecessor, replacing uses of OpPhi ids with the ids associated with the
// predecessor.
void EliminateOpPhiInstructions(IRContext* context, BasicBlock* block) {
block->ForEachPhiInst([context](Instruction* phi) {
assert(2 == phi->NumInOperands() &&
"A block can only have one predecessor for block merging to make "
"sense.");
context->ReplaceAllUsesWith(phi->result_id(),
phi->GetSingleWordInOperand(0));
context->KillInst(phi);
});
}
} // Anonymous namespace
bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block) {
// Find block with single successor which has no other predecessors.
auto ii = block->end();
--ii;
Instruction* br = &*ii;
if (br->opcode() != SpvOpBranch) {
return false;
}
const uint32_t lab_id = br->GetSingleWordInOperand(0);
if (context->cfg()->preds(lab_id).size() != 1) {
return false;
}
bool pred_is_merge = IsMerge(context, block);
bool succ_is_merge = IsMerge(context, lab_id);
if (pred_is_merge && succ_is_merge) {
// Cannot merge two merges together.
return false;
}
// Don't bother trying to merge unreachable blocks.
if (auto dominators = context->GetDominatorAnalysis(block->GetParent())) {
if (!dominators->IsReachable(block)) return false;
}
Instruction* merge_inst = block->GetMergeInst();
const bool pred_is_header = IsHeader(block);
if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
bool succ_is_header = IsHeader(context, lab_id);
if (pred_is_header && succ_is_header) {
// Cannot merge two headers together when the successor is not the merge
// block of the predecessor.
return false;
}
// If this is a header block and the successor is not its merge, we must
// be careful about which blocks we are willing to merge together.
// OpLoopMerge must be followed by a conditional or unconditional branch.
// The merge must be a loop merge because a selection merge cannot be
// followed by an unconditional branch.
BasicBlock* succ_block = context->get_instr_block(lab_id);
SpvOp succ_term_op = succ_block->terminator()->opcode();
assert(merge_inst->opcode() == SpvOpLoopMerge);
if (succ_term_op != SpvOpBranch && succ_term_op != SpvOpBranchConditional) {
return false;
}
}
return true;
}
void MergeWithSuccessor(IRContext* context, Function* func,
Function::iterator bi) {
assert(CanMergeWithSuccessor(context, &*bi) &&
"Precondition failure for MergeWithSuccessor: it must be legal to "
"merge the block and its successor.");
auto ii = bi->end();
--ii;
Instruction* br = &*ii;
const uint32_t lab_id = br->GetSingleWordInOperand(0);
Instruction* merge_inst = bi->GetMergeInst();
bool pred_is_header = IsHeader(&*bi);
// Merge blocks.
context->KillInst(br);
auto sbi = bi;
for (; sbi != func->end(); ++sbi)
if (sbi->id() == lab_id) break;
// If bi is sbi's only predecessor, it dominates sbi and thus
// sbi must follow bi in func's ordering.
assert(sbi != func->end());
// Update the inst-to-block mapping for the instructions in sbi.
for (auto& inst : *sbi) {
context->set_instr_block(&inst, &*bi);
}
EliminateOpPhiInstructions(context, &*sbi);
// Now actually move the instructions.
bi->AddInstructions(&*sbi);
if (merge_inst) {
if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) {
// Merging the header and merge blocks, so remove the structured control
// flow declaration.
context->KillInst(merge_inst);
} else {
// Move the merge instruction to just before the terminator.
merge_inst->InsertBefore(bi->terminator());
}
}
context->ReplaceAllUsesWith(lab_id, bi->id());
context->KillInst(sbi->GetLabelInst());
(void)sbi.Erase();
}
} // namespace blockmergeutil
} // namespace opt
} // namespace spvtools
// Copyright (c) 2017 The Khronos Group Inc.
// Copyright (c) 2017 Valve Corporation
// Copyright (c) 2017 LunarG Inc.
// 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_BLOCK_MERGE_UTIL_H_
#define SOURCE_OPT_BLOCK_MERGE_UTIL_H_
#include "source/opt/ir_context.h"
namespace spvtools {
namespace opt {
// Provides functions for determining when it is safe to merge blocks, and for
// actually merging blocks, for use by various analyses and passes.
namespace blockmergeutil {
// Returns true if and only if |block| has exactly one successor and merging
// this successor into |block| has no impact on the semantics or validity of the
// SPIR-V module.
bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block);
// Requires that |bi| has a successor that can be safely merged into |bi|, and
// performs the merge.
void MergeWithSuccessor(IRContext* context, Function* func,
Function::iterator bi);
} // namespace blockmergeutil
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_BLOCK_MERGE_UTIL_H_
// 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 "code_sink.h"
#include <set>
#include <vector>
#include "source/opt/instruction.h"
#include "source/opt/ir_builder.h"
#include "source/opt/ir_context.h"
#include "source/util/bit_vector.h"
namespace spvtools {
namespace opt {
Pass::Status CodeSinkingPass::Process() {
bool modified = false;
for (Function& function : *get_module()) {
cfg()->ForEachBlockInPostOrder(function.entry().get(),
[&modified, this](BasicBlock* bb) {
if (SinkInstructionsInBB(bb)) {
modified = true;
}
});
}
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
bool CodeSinkingPass::SinkInstructionsInBB(BasicBlock* bb) {
bool modified = false;
for (auto inst = bb->rbegin(); inst != bb->rend(); ++inst) {
if (SinkInstruction(&*inst)) {
inst = bb->rbegin();
modified = true;
}
}
return modified;
}
bool CodeSinkingPass::SinkInstruction(Instruction* inst) {
if (inst->opcode() != SpvOpLoad && inst->opcode() != SpvOpAccessChain) {
return false;
}
if (ReferencesMutableMemory(inst)) {
return false;
}
if (BasicBlock* target_bb = FindNewBasicBlockFor(inst)) {
Instruction* pos = &*target_bb->begin();
while (pos->opcode() == SpvOpPhi) {
pos = pos->NextNode();
}
inst->InsertBefore(pos);
context()->set_instr_block(inst, target_bb);
return true;
}
return false;
}
BasicBlock* CodeSinkingPass::FindNewBasicBlockFor(Instruction* inst) {
assert(inst->result_id() != 0 && "Instruction should have a result.");
BasicBlock* original_bb = context()->get_instr_block(inst);
BasicBlock* bb = original_bb;
std::unordered_set<uint32_t> bbs_with_uses;
get_def_use_mgr()->ForEachUse(
inst, [&bbs_with_uses, this](Instruction* use, uint32_t idx) {
if (use->opcode() != SpvOpPhi) {
BasicBlock* use_bb = context()->get_instr_block(use);
if (use_bb) {
bbs_with_uses.insert(use_bb->id());
}
} else {
bbs_with_uses.insert(use->GetSingleWordOperand(idx + 1));
}
});
while (true) {
// If |inst| is used in |bb|, then |inst| cannot be moved any further.
if (bbs_with_uses.count(bb->id())) {
break;
}
// If |bb| has one successor (succ_bb), and |bb| is the only predecessor
// of succ_bb, then |inst| can be moved to succ_bb. If succ_bb, has move
// then one predecessor, then moving |inst| into succ_bb could cause it to
// be executed more often, so the search has to stop.
if (bb->terminator()->opcode() == SpvOpBranch) {
uint32_t succ_bb_id = bb->terminator()->GetSingleWordInOperand(0);
if (cfg()->preds(succ_bb_id).size() == 1) {
bb = context()->get_instr_block(succ_bb_id);
continue;
} else {
break;
}
}
// The remaining checks need to know the merge node. If there is no merge
// instruction or an OpLoopMerge, then it is a break or continue. We could
// figure it out, but not worth doing it now.
Instruction* merge_inst = bb->GetMergeInst();
if (merge_inst == nullptr || merge_inst->opcode() != SpvOpSelectionMerge) {
break;
}
// Check all of the successors of |bb| it see which lead to a use of |inst|
// before reaching the merge node.
bool used_in_multiple_blocks = false;
uint32_t bb_used_in = 0;
bb->ForEachSuccessorLabel([this, bb, &bb_used_in, &used_in_multiple_blocks,
&bbs_with_uses](uint32_t* succ_bb_id) {
if (IntersectsPath(*succ_bb_id, bb->MergeBlockIdIfAny(), bbs_with_uses)) {
if (bb_used_in == 0) {
bb_used_in = *succ_bb_id;
} else {
used_in_multiple_blocks = true;
}
}
});
// If more than one successor, which is not the merge block, uses |inst|
// then we have to leave |inst| in bb because there is none of the
// successors dominate all uses of |inst|.
if (used_in_multiple_blocks) {
break;
}
if (bb_used_in == 0) {
// If |inst| is not used before reaching the merge node, then we can move
// |inst| to the merge node.
bb = context()->get_instr_block(bb->MergeBlockIdIfAny());
} else {
// If the only successor that leads to a used of |inst| has more than 1
// predecessor, then moving |inst| could cause it to be executed more
// often, so we cannot move it.
if (cfg()->preds(bb_used_in).size() != 1) {
break;
}
// If |inst| is used after the merge block, then |bb_used_in| does not
// dominate all of the uses. So we cannot move |inst| any further.
if (IntersectsPath(bb->MergeBlockIdIfAny(), original_bb->id(),
bbs_with_uses)) {
break;
}
// Otherwise, |bb_used_in| dominates all uses, so move |inst| into that
// block.
bb = context()->get_instr_block(bb_used_in);
}
continue;
}
return (bb != original_bb ? bb : nullptr);
}
bool CodeSinkingPass::ReferencesMutableMemory(Instruction* inst) {
if (!inst->IsLoad()) {
return false;
}
Instruction* base_ptr = inst->GetBaseAddress();
if (base_ptr->opcode() != SpvOpVariable) {
return true;
}
if (base_ptr->IsReadOnlyVariable()) {
return false;
}
if (HasUniformMemorySync()) {
return true;
}
if (base_ptr->GetSingleWordInOperand(0) != SpvStorageClassUniform) {
return true;
}
return HasPossibleStore(base_ptr);
}
bool CodeSinkingPass::HasUniformMemorySync() {
if (checked_for_uniform_sync_) {
return has_uniform_sync_;
}
bool has_sync = false;
get_module()->ForEachInst([this, &has_sync](Instruction* inst) {
switch (inst->opcode()) {
case SpvOpMemoryBarrier: {
uint32_t mem_semantics_id = inst->GetSingleWordInOperand(1);
if (IsSyncOnUniform(mem_semantics_id)) {
has_sync = true;
}
break;
}
case SpvOpControlBarrier:
case SpvOpAtomicLoad:
case SpvOpAtomicStore:
case SpvOpAtomicExchange:
case SpvOpAtomicIIncrement:
case SpvOpAtomicIDecrement:
case SpvOpAtomicIAdd:
case SpvOpAtomicISub:
case SpvOpAtomicSMin:
case SpvOpAtomicUMin:
case SpvOpAtomicSMax:
case SpvOpAtomicUMax:
case SpvOpAtomicAnd:
case SpvOpAtomicOr:
case SpvOpAtomicXor:
case SpvOpAtomicFlagTestAndSet:
case SpvOpAtomicFlagClear: {
uint32_t mem_semantics_id = inst->GetSingleWordInOperand(2);
if (IsSyncOnUniform(mem_semantics_id)) {
has_sync = true;
}
break;
}
case SpvOpAtomicCompareExchange:
case SpvOpAtomicCompareExchangeWeak:
if (IsSyncOnUniform(inst->GetSingleWordInOperand(2)) ||
IsSyncOnUniform(inst->GetSingleWordInOperand(3))) {
has_sync = true;
}
break;
default:
break;
}
});
has_uniform_sync_ = has_sync;
return has_sync;
}
bool CodeSinkingPass::IsSyncOnUniform(uint32_t mem_semantics_id) const {
const analysis::Constant* mem_semantics_const =
context()->get_constant_mgr()->FindDeclaredConstant(mem_semantics_id);
assert(mem_semantics_const != nullptr &&
"Expecting memory semantics id to be a constant.");
assert(mem_semantics_const->AsIntConstant() &&
"Memory semantics should be an integer.");
uint32_t mem_semantics_int = mem_semantics_const->GetU32();
// If it does not affect uniform memory, then it is does not apply to uniform
// memory.
if ((mem_semantics_int & SpvMemorySemanticsUniformMemoryMask) == 0) {
return false;
}
// Check if there is an acquire or release. If so not, this it does not add
// any memory constraints.
return (mem_semantics_int & (SpvMemorySemanticsAcquireMask |
SpvMemorySemanticsAcquireReleaseMask |
SpvMemorySemanticsReleaseMask)) != 0;
}
bool CodeSinkingPass::HasPossibleStore(Instruction* var_inst) {
assert(var_inst->opcode() == SpvOpVariable ||
var_inst->opcode() == SpvOpAccessChain ||
var_inst->opcode() == SpvOpPtrAccessChain);
return get_def_use_mgr()->WhileEachUser(var_inst, [this](Instruction* use) {
switch (use->opcode()) {
case SpvOpStore:
return true;
case SpvOpAccessChain:
case SpvOpPtrAccessChain:
return HasPossibleStore(use);
default:
return false;
}
});
}
bool CodeSinkingPass::IntersectsPath(uint32_t start, uint32_t end,
const std::unordered_set<uint32_t>& set) {
std::vector<uint32_t> worklist;
worklist.push_back(start);
std::unordered_set<uint32_t> already_done;
already_done.insert(start);
while (!worklist.empty()) {
BasicBlock* bb = context()->get_instr_block(worklist.back());
worklist.pop_back();
if (bb->id() == end) {
continue;
}
if (set.count(bb->id())) {
return true;
}
bb->ForEachSuccessorLabel([&already_done, &worklist](uint32_t* succ_bb_id) {
if (already_done.insert(*succ_bb_id).second) {
worklist.push_back(*succ_bb_id);
}
});
}
return false;
}
// namespace opt
} // namespace opt
} // namespace spvtools
// 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_CODE_SINK_H_
#define SOURCE_OPT_CODE_SINK_H_
#include <unordered_map>
#include "source/opt/ir_context.h"
#include "source/opt/module.h"
#include "source/opt/pass.h"
namespace spvtools {
namespace opt {
// This pass does code sinking for OpAccessChain and OpLoad on variables in
// uniform storage or in read only memory. Code sinking is a transformation
// where an instruction is moved into a more deeply nested construct.
//
// The goal is to move these instructions as close as possible to their uses
// without having to execute them more often or to replicate the instruction.
// Moving the instruction in this way can lead to shorter live ranges, which can
// lead to less register pressure. It can also cause instructions to be
// executed less often because they could be moved into one path of a selection
// construct.
//
// This optimization can cause register pressure to rise if the operands of the
// instructions go dead after the instructions being moved. That is why we only
// move certain OpLoad and OpAccessChain instructions. They generally have
// constants, loop induction variables, and global pointers as operands. The
// operands are live for a longer time in most cases.
class CodeSinkingPass : public Pass {
public:
const char* name() const override { return "code-sink"; }
Status Process() override;
// Return the mask of preserved Analyses.
IRContext::Analysis GetPreservedAnalyses() override {
return IRContext::kAnalysisDefUse |
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
IRContext::kAnalysisDominatorAnalysis |
IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
// Sinks the instructions in |bb| as much as possible. Returns true if
// something changes.
bool SinkInstructionsInBB(BasicBlock* bb);
// Tries the sink |inst| as much as possible. Returns true if the instruction
// is moved.
bool SinkInstruction(Instruction* inst);
// Returns the basic block in which to move |inst| to move is as close as
// possible to the uses of |inst| without increasing the number of times
// |inst| will be executed. Return |nullptr| if there is no need to move
// |inst|.
BasicBlock* FindNewBasicBlockFor(Instruction* inst);
// Return true if |inst| reference memory and it is possible that the data in
// the memory changes at some point.
bool ReferencesMutableMemory(Instruction* inst);
// Returns true if the module contains an instruction that has a memory
// semantics id as an operand, and the memory semantics enforces a
// synchronization of uniform memory. See section 3.25 of the SPIR-V
// specification.
bool HasUniformMemorySync();
// Returns true if there may be a store to the variable |var_inst|.
bool HasPossibleStore(Instruction* var_inst);
// Returns true if one of the basic blocks in |set| exists on a path from the
// basic block |start| to |end|.
bool IntersectsPath(uint32_t start, uint32_t end,
const std::unordered_set<uint32_t>& set);
// Returns true if |mem_semantics_id| is the id of a constant that, when
// interpreted as a memory semantics mask enforces synchronization of uniform
// memory. See section 3.25 of the SPIR-V specification.
bool IsSyncOnUniform(uint32_t mem_semantics_id) const;
// True if a check has for uniform storage has taken place.
bool checked_for_uniform_sync_;
// Cache of whether or not the module has a memory sync on uniform storage.
// only valid if |check_for_uniform_sync_| is true.
bool has_uniform_sync_;
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_CODE_SINK_H_
......@@ -13,6 +13,7 @@
// limitations under the License.
#include "source/opt/eliminate_dead_functions_pass.h"
#include "source/opt/eliminate_dead_functions_util.h"
#include <unordered_set>
......@@ -36,8 +37,8 @@ Pass::Status EliminateDeadFunctionsPass::Process() {
funcIter != get_module()->end();) {
if (live_function_set.count(&*funcIter) == 0) {
modified = true;
EliminateFunction(&*funcIter);
funcIter = funcIter.Erase();
funcIter =
eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter);
} else {
++funcIter;
}
......@@ -47,10 +48,5 @@ Pass::Status EliminateDeadFunctionsPass::Process() {
: Pass::Status::SuccessWithoutChange;
}
void EliminateDeadFunctionsPass::EliminateFunction(Function* func) {
// Remove all of the instruction in the function body
func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); },
true);
}
} // namespace opt
} // namespace spvtools
// 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 "eliminate_dead_functions_util.h"
namespace spvtools {
namespace opt {
namespace eliminatedeadfunctionsutil {
Module::iterator EliminateFunction(IRContext* context,
Module::iterator* func_iter) {
(*func_iter)
->ForEachInst([context](Instruction* inst) { context->KillInst(inst); },
true);
return func_iter->Erase();
}
} // namespace eliminatedeadfunctionsutil
} // namespace opt
} // namespace spvtools
// 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_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
#define SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
#include "source/opt/ir_context.h"
namespace spvtools {
namespace opt {
// Provides functionality for eliminating functions that are not needed, for use
// by various analyses and passes.
namespace eliminatedeadfunctionsutil {
// Removes all of the function's instructions, removes the function from the
// module, and returns the next iterator.
Module::iterator EliminateFunction(IRContext* context,
Module::iterator* func_iter);
} // namespace eliminatedeadfunctionsutil
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_