Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2019-04-20 15:45:51 -07:00
parent 57496fad1f
commit 7c8270ea32
44 changed files with 1933 additions and 163 deletions

View File

@@ -92,6 +92,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/dead_branch_elim_pass.cpp \
source/opt/dead_insert_elim_pass.cpp \
source/opt/dead_variable_elimination.cpp \
source/opt/decompose_initialized_variables_pass.cpp \
source/opt/decoration_manager.cpp \
source/opt/def_use_manager.cpp \
source/opt/dominator_analysis.cpp \

View File

@@ -484,6 +484,8 @@ static_library("spvtools_opt") {
"source/opt/dead_insert_elim_pass.h",
"source/opt/dead_variable_elimination.cpp",
"source/opt/dead_variable_elimination.h",
"source/opt/decompose_initialized_variables_pass.cpp",
"source/opt/decompose_initialized_variables_pass.h",
"source/opt/decoration_manager.cpp",
"source/opt/decoration_manager.h",
"source/opt/def_use_manager.cpp",

View File

@@ -1 +1 @@
"v2019.3-dev", "SPIRV-Tools v2019.3-dev v2019.1-140-g102e430a"
"v2019.3-dev", "SPIRV-Tools v2019.3-dev v2019.1-147-g7aad9653"

View File

@@ -768,6 +768,10 @@ Optimizer::PassToken CreateFixStorageClassPass();
// converts those literals to 0.
Optimizer::PassToken CreateLegalizeVectorShufflePass();
// Create a pass to decompose initialized variables into a seperate variable
// declaration and an initial store.
Optimizer::PassToken CreateDecomposeInitializedVariablesPass();
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_

View File

@@ -101,7 +101,7 @@ macro(spvtools_opencl_tables CONFIG_VERSION)
list(APPEND EXTINST_CPP_DEPENDS ${GRAMMAR_INC_FILE})
endmacro(spvtools_opencl_tables)
macro(spvtools_vendor_tables VENDOR_TABLE)
macro(spvtools_vendor_tables VENDOR_TABLE SHORT_NAME)
set(INSTS_FILE "${spirv-tools_BINARY_DIR}/${VENDOR_TABLE}.insts.inc")
set(GRAMMAR_FILE "${spirv-tools_SOURCE_DIR}/source/extinst.${VENDOR_TABLE}.grammar.json")
add_custom_command(OUTPUT ${INSTS_FILE}
@@ -110,9 +110,9 @@ macro(spvtools_vendor_tables VENDOR_TABLE)
--vendor-insts-output=${INSTS_FILE}
DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_FILE}
COMMENT "Generate extended instruction tables for ${VENDOR_TABLE}.")
add_custom_target(spirv-tools-${VENDOR_TABLE} DEPENDS ${INSTS_FILE})
set_property(TARGET spirv-tools-${VENDOR_TABLE} PROPERTY FOLDER "SPIRV-Tools build")
list(APPEND EXTINST_CPP_DEPENDS spirv-tools-${VENDOR_TABLE})
add_custom_target(spv-tools-${SHORT_NAME} DEPENDS ${INSTS_FILE})
set_property(TARGET spv-tools-${SHORT_NAME} PROPERTY FOLDER "SPIRV-Tools build")
list(APPEND EXTINST_CPP_DEPENDS spv-tools-${SHORT_NAME})
endmacro(spvtools_vendor_tables)
macro(spvtools_extinst_lang_headers NAME GRAMMAR_FILE)
@@ -134,11 +134,11 @@ spvtools_core_tables("unified1")
spvtools_enum_string_mapping("unified1")
spvtools_opencl_tables("unified1")
spvtools_glsl_tables("unified1")
spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter")
spvtools_vendor_tables("spv-amd-shader-trinary-minmax")
spvtools_vendor_tables("spv-amd-gcn-shader")
spvtools_vendor_tables("spv-amd-shader-ballot")
spvtools_vendor_tables("debuginfo")
spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter" "spv-amd-sevp")
spvtools_vendor_tables("spv-amd-shader-trinary-minmax" "spv-amd-stm")
spvtools_vendor_tables("spv-amd-gcn-shader" "spv-amd-gs")
spvtools_vendor_tables("spv-amd-shader-ballot" "spv-amd-sb")
spvtools_vendor_tables("debuginfo" "debuginfo")
spvtools_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE})
spvtools_vimsyntax("unified1" "1.0")

View File

@@ -31,6 +31,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
dead_branch_elim_pass.h
dead_insert_elim_pass.h
dead_variable_elimination.h
decompose_initialized_variables_pass.h
decoration_manager.h
def_use_manager.h
dominator_analysis.h
@@ -130,6 +131,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
dead_branch_elim_pass.cpp
dead_insert_elim_pass.cpp
dead_variable_elimination.cpp
decompose_initialized_variables_pass.cpp
decoration_manager.cpp
def_use_manager.cpp
dominator_analysis.cpp

View File

@@ -0,0 +1,77 @@
// 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/decompose_initialized_variables_pass.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace opt {
using inst_iterator = InstructionList::iterator;
namespace {
bool HasInitializer(Instruction* inst) {
if (inst->opcode() != SpvOpVariable) return false;
if (inst->NumOperands() < 4) return false;
return true;
}
} // namespace
Pass::Status DecomposeInitializedVariablesPass::Process() {
auto* module = context()->module();
bool changed = false;
// TODO(zoddicus): Handle 'Output' variables
// TODO(zoddicus): Handle 'Private' variables
// Handle 'Function' variables
for (auto func = module->begin(); func != module->end(); ++func) {
auto block = func->entry().get();
std::vector<Instruction*> new_stores;
auto last_var = block->begin();
for (auto iter = block->begin();
iter != block->end() && iter->opcode() == SpvOpVariable; ++iter) {
last_var = iter;
Instruction* inst = &(*iter);
if (!HasInitializer(inst)) continue;
changed = true;
auto var_id = inst->result_id();
auto val_id = inst->GetOperand(3).words[0];
Instruction* store_inst = new Instruction(
context(), SpvOpStore, 0, 0,
{{SPV_OPERAND_TYPE_ID, {var_id}}, {SPV_OPERAND_TYPE_ID, {val_id}}});
new_stores.push_back(store_inst);
iter->RemoveOperand(3);
get_def_use_mgr()->UpdateDefUse(&*iter);
}
for (auto store = new_stores.begin(); store != new_stores.end(); ++store) {
context()->AnalyzeDefUse(*store);
context()->set_instr_block(*store, block);
(*store)->InsertAfter(&*last_var);
last_var = *store;
}
}
return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
} // namespace opt
} // namespace spvtools

View File

@@ -0,0 +1,57 @@
// 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_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
#define SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
#include "source/opt/ir_context.h"
#include "source/opt/module.h"
#include "source/opt/pass.h"
namespace spvtools {
namespace opt {
// Converts variable declartions with initializers into seperate declaration and
// assignment statements. This is done due to known issues with some Vulkan
// implementations' handling of initialized variables.
//
// Only decomposes variables with storage classes that are valid in Vulkan
// execution environments; Output, Private, and Function.
// Currently only Function is implemented.
class DecomposeInitializedVariablesPass : public Pass {
public:
const char* name() const override {
return "decompose-initialized-variables";
}
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
return IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
IRContext::kAnalysisScalarEvolution |
IRContext::kAnalysisRegisterPressure |
IRContext::kAnalysisValueNumberTable |
IRContext::kAnalysisStructuredCFG |
IRContext::kAnalysisBuiltinVarId |
IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
}
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2019 Google 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2019 Google 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2019 Google 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2019 Google 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.

View File

@@ -231,7 +231,9 @@ Optimizer& Optimizer::RegisterVulkanToWebGPUPasses() {
.RegisterPass(CreateDeadBranchElimPass());
}
Optimizer& Optimizer::RegisterWebGPUToVulkanPasses() { return *this; }
Optimizer& Optimizer::RegisterWebGPUToVulkanPasses() {
return RegisterPass(CreateDecomposeInitializedVariablesPass());
}
bool Optimizer::RegisterPassesFromFlags(const std::vector<std::string>& flags) {
for (const auto& flag : flags) {
@@ -866,4 +868,9 @@ Optimizer::PassToken CreateLegalizeVectorShufflePass() {
MakeUnique<opt::LegalizeVectorShufflePass>());
}
Optimizer::PassToken CreateDecomposeInitializedVariablesPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::DecomposeInitializedVariablesPass>());
}
} // namespace spvtools

View File

@@ -29,6 +29,7 @@
#include "source/opt/dead_branch_elim_pass.h"
#include "source/opt/dead_insert_elim_pass.h"
#include "source/opt/dead_variable_elimination.h"
#include "source/opt/decompose_initialized_variables_pass.h"
#include "source/opt/eliminate_dead_constant_pass.h"
#include "source/opt/eliminate_dead_functions_pass.h"
#include "source/opt/eliminate_dead_members_pass.h"

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2019 Google 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2019 Google 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.

View File

@@ -35,6 +35,10 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
remove_unreferenced_instruction_reduction_opportunity_finder.h
structured_loop_to_selection_reduction_opportunity.h
structured_loop_to_selection_reduction_opportunity_finder.h
conditional_branch_to_simple_conditional_branch_opportunity_finder.h
conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
simple_conditional_branch_to_branch_opportunity_finder.h
simple_conditional_branch_to_branch_reduction_opportunity.h
change_operand_reduction_opportunity.cpp
change_operand_to_undef_reduction_opportunity.cpp
@@ -58,7 +62,11 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
remove_opname_instruction_reduction_opportunity_finder.cpp
structured_loop_to_selection_reduction_opportunity.cpp
structured_loop_to_selection_reduction_opportunity_finder.cpp
)
conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
simple_conditional_branch_to_branch_opportunity_finder.cpp
simple_conditional_branch_to_branch_reduction_opportunity.cpp
)
if(MSVC)
# Enable parallel builds across four cores for this lib

View File

@@ -0,0 +1,89 @@
// 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/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
#include "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h"
#include "source/reduce/reduction_util.h"
namespace spvtools {
namespace reduce {
using opt::IRContext;
using opt::Instruction;
std::vector<std::unique_ptr<ReductionOpportunity>>
ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
GetAvailableOpportunities(IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
// Find the opportunities for redirecting all false targets before the
// opportunities for redirecting all true targets because the former
// opportunities disable the latter, and vice versa, and the efficiency of the
// reducer is improved by avoiding contiguous opportunities that disable one
// another.
for (bool redirect_to_true : {true, false}) {
// Consider every function.
for (auto& function : *context->module()) {
// Consider every block in the function.
for (auto& block : function) {
// The terminator must be SpvOpBranchConditional.
Instruction* terminator = block.terminator();
if (terminator->opcode() != SpvOpBranchConditional) {
continue;
}
uint32_t true_block_id =
terminator->GetSingleWordInOperand(kTrueBranchOperandIndex);
uint32_t false_block_id =
terminator->GetSingleWordInOperand(kFalseBranchOperandIndex);
// The conditional branch must not already be simplified.
if (true_block_id == false_block_id) {
continue;
}
// The redirected target must not be a back-edge to a structured loop
// header.
uint32_t redirected_block_id =
redirect_to_true ? false_block_id : true_block_id;
uint32_t containing_loop_header =
context->GetStructuredCFGAnalysis()->ContainingLoop(block.id());
// The structured CFG analysis does not include a loop header as part
// of the loop construct, but we want to include it, so handle this
// special case:
if (block.GetLoopMergeInst() != nullptr) {
containing_loop_header = block.id();
}
if (redirected_block_id == containing_loop_header) {
continue;
}
result.push_back(
MakeUnique<
ConditionalBranchToSimpleConditionalBranchReductionOpportunity>(
block.terminator(), redirect_to_true));
}
}
}
return result;
}
std::string
ConditionalBranchToSimpleConditionalBranchOpportunityFinder::GetName() const {
return "ConditionalBranchToSimpleConditionalBranchOpportunityFinder";
}
} // namespace reduce
} // namespace spvtools

View File

@@ -0,0 +1,37 @@
// 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_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
#define SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
#include "source/reduce/reduction_opportunity_finder.h"
namespace spvtools {
namespace reduce {
// A finder for opportunities to simplify conditional branches into simple
// conditional branches (conditional branches with one target).
class ConditionalBranchToSimpleConditionalBranchOpportunityFinder
: public ReductionOpportunityFinder {
public:
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
opt::IRContext* context) const override;
std::string GetName() const override;
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_

View File

@@ -0,0 +1,54 @@
// 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/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h"
#include "source/reduce/reduction_util.h"
namespace spvtools {
namespace reduce {
using opt::Instruction;
ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
Instruction* conditional_branch_instruction, bool redirect_to_true)
: conditional_branch_instruction_(conditional_branch_instruction),
redirect_to_true_(redirect_to_true) {}
bool ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
PreconditionHolds() {
// Another opportunity may have already simplified this conditional branch,
// which should disable this opportunity.
return conditional_branch_instruction_->GetSingleWordInOperand(
kTrueBranchOperandIndex) !=
conditional_branch_instruction_->GetSingleWordInOperand(
kFalseBranchOperandIndex);
}
void ConditionalBranchToSimpleConditionalBranchReductionOpportunity::Apply() {
uint32_t operand_to_modify =
redirect_to_true_ ? kFalseBranchOperandIndex : kTrueBranchOperandIndex;
uint32_t operand_to_copy =
redirect_to_true_ ? kTrueBranchOperandIndex : kFalseBranchOperandIndex;
// Do the branch redirection.
conditional_branch_instruction_->SetInOperand(
operand_to_modify,
{conditional_branch_instruction_->GetSingleWordInOperand(
operand_to_copy)});
}
} // namespace reduce
} // namespace spvtools

View File

@@ -0,0 +1,52 @@
// 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_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
#define SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
#include "source/opt/basic_block.h"
#include "source/reduce/reduction_opportunity.h"
namespace spvtools {
namespace reduce {
// An opportunity to simplify a conditional branch to a simple conditional
// branch (a conditional branch with one target).
class ConditionalBranchToSimpleConditionalBranchReductionOpportunity
: public ReductionOpportunity {
public:
// Constructs an opportunity to simplify |conditional_branch_instruction|. If
// |redirect_to_true| is true, the false target will be changed to also point
// to the true target; otherwise, the true target will be changed to also
// point to the false target.
explicit ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
opt::Instruction* conditional_branch_instruction, bool redirect_to_true);
bool PreconditionHolds() override;
protected:
void Apply() override;
private:
opt::Instruction* conditional_branch_instruction_;
// If true, the false target will be changed to point to the true target;
// otherwise, the true target will be changed to point to the false target.
bool redirect_to_true_;
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_

View File

@@ -17,6 +17,7 @@
#include <cassert>
#include <sstream>
#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
@@ -26,6 +27,7 @@
#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
#include "source/spirv_reducer_options.h"
@@ -191,6 +193,11 @@ void Reducer::AddDefaultReductionPasses() {
spvtools::MakeUnique<RemoveBlockReductionOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<RemoveSelectionReductionOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<
ConditionalBranchToSimpleConditionalBranchOpportunityFinder>());
AddReductionPass(
spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>());
}
void Reducer::AddReductionPass(

View File

@@ -22,6 +22,9 @@ namespace reduce {
using opt::IRContext;
using opt::Instruction;
const uint32_t kTrueBranchOperandIndex = 1;
const uint32_t kFalseBranchOperandIndex = 2;
uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
for (auto& inst : context->module()->types_values()) {
if (inst.opcode() != SpvOpUndef) {

View File

@@ -23,6 +23,9 @@
namespace spvtools {
namespace reduce {
extern const uint32_t kTrueBranchOperandIndex;
extern const uint32_t kFalseBranchOperandIndex;
// Returns an OpUndef id from the global value list that is of the given type,
// adding one if it does not exist.
uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id);

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2019 Google 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.

View File

@@ -0,0 +1,65 @@
// 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/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
#include "source/reduce/reduction_util.h"
#include "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
using opt::IRContext;
using opt::Instruction;
std::vector<std::unique_ptr<ReductionOpportunity>>
SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities(
IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
// Consider every function.
for (auto& function : *context->module()) {
// Consider every block in the function.
for (auto& block : function) {
// The terminator must be SpvOpBranchConditional.
Instruction* terminator = block.terminator();
if (terminator->opcode() != SpvOpBranchConditional) {
continue;
}
// It must not be a selection header, as these cannot be followed by
// OpBranch.
if (block.GetMergeInst() &&
block.GetMergeInst()->opcode() == SpvOpSelectionMerge) {
continue;
}
// The conditional branch must be simplified.
if (terminator->GetSingleWordInOperand(kTrueBranchOperandIndex) !=
terminator->GetSingleWordInOperand(kFalseBranchOperandIndex)) {
continue;
}
result.push_back(
MakeUnique<SimpleConditionalBranchToBranchReductionOpportunity>(
block.terminator()));
}
}
return result;
}
std::string SimpleConditionalBranchToBranchOpportunityFinder::GetName() const {
return "SimpleConditionalBranchToBranchOpportunityFinder";
}
} // namespace reduce
} // namespace spvtools

View File

@@ -0,0 +1,37 @@
// 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_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
#define SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
#include "source/reduce/reduction_opportunity_finder.h"
namespace spvtools {
namespace reduce {
// A finder for opportunities to change simple conditional branches (conditional
// branches with one target) to an OpBranch.
class SimpleConditionalBranchToBranchOpportunityFinder
: public ReductionOpportunityFinder {
public:
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
opt::IRContext* context) const override;
std::string GetName() const override;
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_

View File

@@ -0,0 +1,59 @@
// 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/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h"
#include "source/reduce/reduction_util.h"
namespace spvtools {
namespace reduce {
using namespace opt;
SimpleConditionalBranchToBranchReductionOpportunity::
SimpleConditionalBranchToBranchReductionOpportunity(
Instruction* conditional_branch_instruction)
: conditional_branch_instruction_(conditional_branch_instruction) {}
bool SimpleConditionalBranchToBranchReductionOpportunity::PreconditionHolds() {
// We find at most one opportunity per conditional branch and simplifying
// another branch cannot disable this opportunity.
return true;
}
void SimpleConditionalBranchToBranchReductionOpportunity::Apply() {
assert(conditional_branch_instruction_->opcode() == SpvOpBranchConditional &&
"SimpleConditionalBranchToBranchReductionOpportunity: branch was not "
"a conditional branch");
assert(conditional_branch_instruction_->GetSingleWordInOperand(
kTrueBranchOperandIndex) ==
conditional_branch_instruction_->GetSingleWordInOperand(
kFalseBranchOperandIndex) &&
"SimpleConditionalBranchToBranchReductionOpportunity: branch was not "
"simple");
// OpBranchConditional %condition %block_id %block_id ...
// ->
// OpBranch %block_id
conditional_branch_instruction_->SetOpcode(SpvOpBranch);
conditional_branch_instruction_->ReplaceOperands(
{{SPV_OPERAND_TYPE_ID,
{conditional_branch_instruction_->GetSingleWordInOperand(
kTrueBranchOperandIndex)}}});
}
} // namespace reduce
} // namespace spvtools

View File

@@ -0,0 +1,45 @@
// 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_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
#define SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
#include "source/opt/instruction.h"
#include "source/reduce/reduction_opportunity.h"
namespace spvtools {
namespace reduce {
// An opportunity to change simple conditional branches (conditional branches
// with one target) to an OpBranch.
class SimpleConditionalBranchToBranchReductionOpportunity
: public ReductionOpportunity {
public:
// Constructs an opportunity to simplify |conditional_branch_instruction|.
explicit SimpleConditionalBranchToBranchReductionOpportunity(
opt::Instruction* conditional_branch_instruction);
bool PreconditionHolds() override;
protected:
void Apply() override;
private:
opt::Instruction* conditional_branch_instruction_;
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_

View File

@@ -110,48 +110,6 @@ spv_result_t ProcessInstruction(void* user_data,
return SPV_SUCCESS;
}
void printDot(const ValidationState_t& _, const BasicBlock& other) {
std::string block_string;
if (other.successors()->empty()) {
block_string += "end ";
} else {
for (auto block : *other.successors()) {
block_string += _.getIdName(block->id()) + " ";
}
}
printf("%10s -> {%s\b}\n", _.getIdName(other.id()).c_str(),
block_string.c_str());
}
void PrintBlocks(ValidationState_t& _, Function func) {
assert(func.first_block());
printf("%10s -> %s\n", _.getIdName(func.id()).c_str(),
_.getIdName(func.first_block()->id()).c_str());
for (const auto& block : func.ordered_blocks()) {
printDot(_, *block);
}
}
#ifdef __clang__
#define UNUSED(func) [[gnu::unused]] func
#elif defined(__GNUC__)
#define UNUSED(func) \
func __attribute__((unused)); \
func
#elif defined(_MSC_VER)
#define UNUSED(func) func
#endif
UNUSED(void PrintDotGraph(ValidationState_t& _, Function func)) {
if (func.first_block()) {
std::string func_name(_.getIdName(func.id()));
printf("digraph %s {\n", func_name.c_str());
PrintBlocks(_, func);
printf("}\n");
}
}
spv_result_t ValidateForwardDecls(ValidationState_t& _) {
if (_.unresolved_forward_id_count() == 0) return SPV_SUCCESS;
@@ -281,6 +239,12 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
<< "Invalid SPIR-V magic number.";
}
if (spvIsWebGPUEnv(context.target_env) && endian != SPV_ENDIANNESS_LITTLE) {
return DiagnosticStream(position, context.consumer, "",
SPV_ERROR_INVALID_BINARY)
<< "WebGPU requires SPIR-V to be little endian.";
}
spv_header_t header;
if (spvBinaryHeaderGet(binary.get(), endian, &header)) {
return DiagnosticStream(position, context.consumer, "",

View File

@@ -725,36 +725,6 @@ spv_result_t PerformWebGPUCfgChecks(ValidationState_t& _, Function* function) {
return SPV_SUCCESS;
}
// Checks that there are no OpUnreachable instructions reachable from the entry
// block of |function|.
spv_result_t ValidateUnreachableBlocks(ValidationState_t& _,
Function& function) {
auto* entry_block = function.first_block();
std::vector<BasicBlock*> stack;
std::unordered_set<BasicBlock*> seen;
if (entry_block) stack.push_back(entry_block);
while (!stack.empty()) {
auto* block = stack.back();
stack.pop_back();
if (!seen.insert(block).second) continue;
auto* terminator = block->terminator();
if (terminator->opcode() == SpvOpUnreachable) {
return _.diag(SPV_ERROR_INVALID_CFG, block->label())
<< "Statically reachable blocks cannot be terminated by "
"OpUnreachable";
}
for (auto* succ : *block->successors()) {
stack.push_back(succ);
}
}
return SPV_SUCCESS;
}
spv_result_t PerformCfgChecks(ValidationState_t& _) {
for (auto& function : _.functions()) {
// Check all referenced blocks are defined within a function
@@ -857,8 +827,6 @@ spv_result_t PerformCfgChecks(ValidationState_t& _) {
}
}
if (auto error = ValidateUnreachableBlocks(_, function)) return error;
/// Structured control flow checks are only required for shader capabilities
if (_.HasCapability(SpvCapabilityShader)) {
if (auto error = StructuredControlFlowChecks(_, &function, back_edges))

View File

@@ -48,21 +48,31 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _,
}
if (spvIsWebGPUEnv(_.context()->target_env)) {
uint32_t valid_bits = SpvMemorySemanticsAcquireMask |
SpvMemorySemanticsReleaseMask |
SpvMemorySemanticsAcquireReleaseMask |
SpvMemorySemanticsUniformMemoryMask |
uint32_t valid_bits = SpvMemorySemanticsUniformMemoryMask |
SpvMemorySemanticsWorkgroupMemoryMask |
SpvMemorySemanticsImageMemoryMask |
SpvMemorySemanticsOutputMemoryKHRMask |
SpvMemorySemanticsMakeAvailableKHRMask |
SpvMemorySemanticsMakeVisibleKHRMask;
if (!spvOpcodeIsAtomicOp(inst->opcode())) {
valid_bits |= SpvMemorySemanticsAcquireMask |
SpvMemorySemanticsReleaseMask |
SpvMemorySemanticsAcquireReleaseMask;
}
if (value & ~valid_bits) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "WebGPU spec disallows any bit masks in Memory Semantics that "
"are not Acquire, Release, AcquireRelease, UniformMemory, "
"WorkgroupMemory, ImageMemory, OutputMemoryKHR, "
"MakeAvailableKHR, or MakeVisibleKHR";
if (spvOpcodeIsAtomicOp(inst->opcode())) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "WebGPU spec disallows, for OpAtomic*, any bit masks in "
"Memory Semantics that are not UniformMemory, "
"WorkgroupMemory, ImageMemory, or OutputMemoryKHR";
} else {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "WebGPU spec disallows any bit masks in Memory Semantics "
"that are not Acquire, Release, AcquireRelease, "
"UniformMemory, WorkgroupMemory, ImageMemory, "
"OutputMemoryKHR, MakeAvailableKHR, or MakeVisibleKHR";
}
}
}

View File

@@ -30,6 +30,7 @@ add_spvtools_unittest(TARGET opt
dead_branch_elim_test.cpp
dead_insert_elim_test.cpp
dead_variable_elim_test.cpp
decompose_initialized_variables_test.cpp
decoration_manager_test.cpp
def_use_test.cpp
eliminate_dead_const_test.cpp

View File

@@ -0,0 +1,85 @@
// 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 <vector>
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"
namespace spvtools {
namespace opt {
namespace {
using DecomposeInitializedVariablesTest = PassTest<::testing::Test>;
void operator+=(std::vector<const char*>& lhs,
const std::vector<const char*> rhs) {
for (auto elem : rhs) lhs.push_back(elem);
}
std::vector<const char*> header = {
"OpCapability Shader",
"OpCapability VulkanMemoryModelKHR",
"OpExtension \"SPV_KHR_vulkan_memory_model\"",
"OpMemoryModel Logical VulkanKHR",
"OpEntryPoint Vertex %1 \"shader\"",
"%uint = OpTypeInt 32 0",
"%uint_1 = OpConstant %uint 1",
"%4 = OpConstantNull %uint",
"%void = OpTypeVoid",
"%6 = OpTypeFunction %void"};
std::string GetFunctionTest(std::vector<const char*> body) {
auto result = header;
result += {"%_ptr_Function_uint = OpTypePointer Function %uint",
"%1 = OpFunction %void None %6", "%8 = OpLabel"};
result += body;
result += {"OpReturn", "OpFunctionEnd"};
return JoinAllInsts(result);
}
TEST_F(DecomposeInitializedVariablesTest, FunctionChanged) {
std::string input =
GetFunctionTest({"%9 = OpVariable %_ptr_Function_uint Function %uint_1"});
std::string expected = GetFunctionTest(
{"%9 = OpVariable %_ptr_Function_uint Function", "OpStore %9 %uint_1"});
SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
input, expected, /* skip_nop = */ false);
}
TEST_F(DecomposeInitializedVariablesTest, FunctionUnchanged) {
std::string input =
GetFunctionTest({"%9 = OpVariable %_ptr_Function_uint Function"});
SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
input, input, /* skip_nop = */ false);
}
TEST_F(DecomposeInitializedVariablesTest, FunctionMultipleVariables) {
std::string input =
GetFunctionTest({"%9 = OpVariable %_ptr_Function_uint Function %uint_1",
"%10 = OpVariable %_ptr_Function_uint Function %4"});
std::string expected =
GetFunctionTest({"%9 = OpVariable %_ptr_Function_uint Function",
"%10 = OpVariable %_ptr_Function_uint Function",
"OpStore %9 %uint_1", "OpStore %10 %4"});
SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
input, expected, /* skip_nop = */ false);
}
} // namespace
} // namespace opt
} // namespace spvtools

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2019 Google 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.

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2019 Google 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.

View File

@@ -222,7 +222,7 @@ TEST(Optimizer, CanRegisterPassesFromFlags) {
EXPECT_EQ(msg_level, SPV_MSG_ERROR);
}
TEST(Optimizer, VulkanToWebGPUModeSetsCorrectPasses) {
TEST(Optimizer, VulkanToWebGPUSetsCorrectPasses) {
Optimizer opt(SPV_ENV_WEBGPU_0);
opt.RegisterVulkanToWebGPUPasses();
std::vector<const char*> pass_names = opt.GetPassNames();
@@ -260,9 +260,11 @@ using VulkanToWebGPUPassTest =
PassTest<::testing::TestWithParam<VulkanToWebGPUPassCase>>;
TEST_P(VulkanToWebGPUPassTest, Ran) {
SpirvTools tools(SPV_ENV_WEBGPU_0);
std::vector<uint32_t> binary;
tools.Assemble(GetParam().input, &binary);
{
SpirvTools tools(SPV_ENV_VULKAN_1_1);
tools.Assemble(GetParam().input, &binary);
}
Optimizer opt(SPV_ENV_WEBGPU_0);
opt.RegisterVulkanToWebGPUPasses();
@@ -272,7 +274,10 @@ TEST_P(VulkanToWebGPUPassTest, Ran) {
ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
validator_options, true));
std::string disassembly;
tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
{
SpirvTools tools(SPV_ENV_WEBGPU_0);
tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
}
EXPECT_EQ(GetParam().expected, disassembly)
<< "Was expecting pass '" << GetParam().pass << "' to have been run.\n";
@@ -521,6 +526,101 @@ INSTANTIATE_TEST_SUITE_P(
// pass
"legalize-vector-shuffle"}}));
TEST(Optimizer, WebGPUToVulkanSetsCorrectPasses) {
Optimizer opt(SPV_ENV_VULKAN_1_1);
opt.RegisterWebGPUToVulkanPasses();
std::vector<const char*> pass_names = opt.GetPassNames();
std::vector<std::string> registered_passes;
for (auto name = pass_names.begin(); name != pass_names.end(); ++name)
registered_passes.push_back(*name);
std::vector<std::string> expected_passes = {
"decompose-initialized-variables"};
std::sort(registered_passes.begin(), registered_passes.end());
std::sort(expected_passes.begin(), expected_passes.end());
ASSERT_EQ(registered_passes.size(), expected_passes.size());
for (size_t i = 0; i < registered_passes.size(); i++)
EXPECT_EQ(registered_passes[i], expected_passes[i]);
}
struct WebGPUToVulkanPassCase {
// Input SPIR-V
std::string input;
// Expected result SPIR-V
std::string expected;
// Specific pass under test, used for logging messages.
std::string pass;
};
using WebGPUToVulkanPassTest =
PassTest<::testing::TestWithParam<WebGPUToVulkanPassCase>>;
TEST_P(WebGPUToVulkanPassTest, Ran) {
std::vector<uint32_t> binary;
{
SpirvTools tools(SPV_ENV_WEBGPU_0);
tools.Assemble(GetParam().input, &binary);
}
Optimizer opt(SPV_ENV_VULKAN_1_1);
opt.RegisterWebGPUToVulkanPasses();
std::vector<uint32_t> optimized;
class ValidatorOptions validator_options;
ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
validator_options, true));
std::string disassembly;
{
SpirvTools tools(SPV_ENV_VULKAN_1_1);
tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
}
EXPECT_EQ(GetParam().expected, disassembly)
<< "Was expecting pass '" << GetParam().pass << "' to have been run.\n";
}
INSTANTIATE_TEST_SUITE_P(
Optimizer, WebGPUToVulkanPassTest,
::testing::ValuesIn(std::vector<WebGPUToVulkanPassCase>{
// Decompose Initialized Variables
{// input
"OpCapability Shader\n"
"OpCapability VulkanMemoryModelKHR\n"
"OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
"OpMemoryModel Logical VulkanKHR\n"
"OpEntryPoint Vertex %1 \"shader\"\n"
"%uint = OpTypeInt 32 0\n"
"%_ptr_Function_uint = OpTypePointer Function %uint\n"
"%4 = OpConstantNull %uint\n"
"%void = OpTypeVoid\n"
"%6 = OpTypeFunction %void\n"
"%1 = OpFunction %void None %6\n"
"%7 = OpLabel\n"
"%8 = OpVariable %_ptr_Function_uint Function %4\n"
"OpReturn\n"
"OpFunctionEnd\n",
// expected
"OpCapability Shader\n"
"OpCapability VulkanMemoryModelKHR\n"
"OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
"OpMemoryModel Logical VulkanKHR\n"
"OpEntryPoint Vertex %1 \"shader\"\n"
"%uint = OpTypeInt 32 0\n"
"%_ptr_Function_uint = OpTypePointer Function %uint\n"
"%4 = OpConstantNull %uint\n"
"%void = OpTypeVoid\n"
"%6 = OpTypeFunction %void\n"
"%1 = OpFunction %void None %6\n"
"%7 = OpLabel\n"
"%8 = OpVariable %_ptr_Function_uint Function\n"
"OpStore %8 %4\n"
"OpReturn\n"
"OpFunctionEnd\n",
// pass
"decompose-initialized-variables"}}));
} // namespace
} // namespace opt
} // namespace spvtools

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2019 Google 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.

View File

@@ -13,7 +13,8 @@
# limitations under the License.
add_spvtools_unittest(TARGET reduce
SRCS merge_blocks_test.cpp
SRCS
merge_blocks_test.cpp
operand_to_constant_test.cpp
operand_to_undef_test.cpp
operand_to_dominating_id_test.cpp
@@ -27,6 +28,8 @@ add_spvtools_unittest(TARGET reduce
remove_unreferenced_instruction_test.cpp
structured_loop_to_selection_test.cpp
validation_during_reduction_test.cpp
conditional_branch_to_simple_conditional_branch_test.cpp
simple_conditional_branch_to_branch_test.cpp
LIBS SPIRV-Tools-reduce
)

View File

@@ -0,0 +1,501 @@
// 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/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
#include "source/opt/build_module.h"
#include "source/reduce/reduction_opportunity.h"
#include "source/reduce/reduction_pass.h"
#include "test/reduce/reduce_test_util.h"
namespace spvtools {
namespace reduce {
namespace {
const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
TEST(ConditionalBranchToSimpleConditionalBranchTest, Diamond) {
// A test with the following structure.
//
// selection header
// OpBranchConditional
// | |
// b b
// | |
// selection merge
//
// There should be two opportunities for redirecting the OpBranchConditional
// targets: redirecting the true to false, and vice-versa. E.g. false to true:
//
// selection header
// OpBranchConditional
// ||
// b b
// | |
// selection merge
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %8 %12 %13
%12 = OpLabel
OpBranch %11
%13 = OpLabel
OpBranch %11
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(2, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ASSERT_TRUE(ops[1]->PreconditionHolds());
ops[0]->TryToApply();
// The other opportunity should now be disabled.
ASSERT_FALSE(ops[1]->PreconditionHolds());
CheckValid(kEnv, context.get());
{
std::string after = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %8 %12 %12
%12 = OpLabel
OpBranch %11
%13 = OpLabel
OpBranch %11
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after, context.get());
}
ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
// Start again, and apply the other op.
context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(2, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ASSERT_TRUE(ops[1]->PreconditionHolds());
ops[1]->TryToApply();
// The other opportunity should now be disabled.
ASSERT_FALSE(ops[0]->PreconditionHolds());
CheckValid(kEnv, context.get());
{
std::string after2 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %8 %13 %13
%12 = OpLabel
OpBranch %11
%13 = OpLabel
OpBranch %11
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after2, context.get());
}
}
TEST(ConditionalBranchToSimpleConditionalBranchTest, AlreadySimplified) {
// A test with the following structure.
//
// selection header
// OpBranchConditional
// ||
// b b
// | |
// selection merge
//
// There should be no opportunities for redirecting the OpBranchConditional
// as it is already simplified.
//
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %8 %12 %12
%12 = OpLabel
OpBranch %11
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(ConditionalBranchToSimpleConditionalBranchTest, DontRemoveBackEdge) {
// A test with the following structure. The loop has a continue construct that
// ends with OpBranchConditional. The OpBranchConditional can be simplified,
// but only to point to the loop header, otherwise we have removed the
// back-edge. Thus, there should be one opportunity instead of two.
//
// loop header
// |
// loop continue target and back-edge block
// OpBranchConditional
// | |
// loop merge (to loop header^)
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %12 None
OpBranch %12
%12 = OpLabel
OpBranchConditional %8 %11 %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto context =
BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
CheckValid(kEnv, context.get());
std::string after = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %12 None
OpBranch %12
%12 = OpLabel
OpBranchConditional %8 %10 %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after, context.get());
ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(ConditionalBranchToSimpleConditionalBranchTest,
DontRemoveBackEdgeCombinedHeaderContinue) {
// A test with the following structure.
//
// loop header and continue target and back-edge block
// OpBranchConditional
// | |
// loop merge (to loop header^)
//
// The OpBranchConditional-to-header edge must not be removed, so there should
// only be one opportunity. It should change both targets to be to the loop
// header.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %10 None
OpBranchConditional %8 %11 %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto context =
BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
CheckValid(kEnv, context.get());
std::string after = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %10 None
OpBranchConditional %8 %10 %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after, context.get());
ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(ConditionalBranchToSimpleConditionalBranchTest, BackEdgeUnreachable) {
// A test with the following structure. I.e. a loop with an unreachable
// continue construct that ends with OpBranchConditional.
//
// loop header
// |
// | loop continue target (unreachable)
// | |
// | back-edge block (unreachable)
// | OpBranchConditional
// | | |
// loop merge (to loop header^)
//
// The branch to the loop header must not be removed, even though the continue
// construct is unreachable. So there should only be one opportunity to make
// the true and false targets of the OpBranchConditional to point to the loop
// header.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %12 None
OpBranch %11
%12 = OpLabel
OpBranch %13
%13 = OpLabel
OpBranchConditional %8 %11 %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto context =
BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
CheckValid(kEnv, context.get());
std::string after = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %12 None
OpBranch %11
%12 = OpLabel
OpBranch %13
%13 = OpLabel
OpBranchConditional %8 %10 %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after, context.get());
ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@@ -0,0 +1,486 @@
// 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/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
#include "source/opt/build_module.h"
#include "source/reduce/reduction_opportunity.h"
#include "source/reduce/reduction_pass.h"
#include "test/reduce/reduce_test_util.h"
namespace spvtools {
namespace reduce {
namespace {
const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
TEST(SimpleConditionalBranchToBranchTest, Diamond) {
// A test with the following structure.
//
// selection header
// OpBranchConditional
// ||
// b b
// | |
// selection merge
//
// The conditional branch cannot be simplified because selection headers
// cannot end with OpBranch.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %8 %12 %12
%12 = OpLabel
OpBranch %11
%13 = OpLabel
OpBranch %11
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(SimpleConditionalBranchToBranchTest, DiamondNoSelection) {
// A test with the following structure.
//
// OpBranchConditional
// ||
// b b
// | /
// b
//
// The conditional branch can be simplified.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpBranchConditional %8 %12 %12
%12 = OpLabel
OpBranch %11
%13 = OpLabel
OpBranch %11
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
CheckValid(kEnv, context.get());
std::string after = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpBranch %12
%12 = OpLabel
OpBranch %11
%13 = OpLabel
OpBranch %11
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after, context.get());
ops = SimpleConditionalBranchToBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(SimpleConditionalBranchToBranchTest, ConditionalBranchesButNotSimple) {
// A test with the following structure.
//
// selection header
// OpBranchConditional
// | |
// b OpBranchConditional
// | | |
// | b |
// | | |
// selection merge
//
// None of the conditional branches can be simplified; the first is not simple
// AND part of a selection header; the second is just not simple (where
// "simple" means it only has one target).
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %8 %12 %13
%12 = OpLabel
OpBranch %11
%13 = OpLabel
OpBranchConditional %8 %14 %11
%14 = OpLabel
OpBranch %11
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(SimpleConditionalBranchToBranchTest, SimplifyBackEdge) {
// A test with the following structure. The loop has a continue construct that
// ends with OpBranchConditional. The OpBranchConditional can be simplified.
//
// loop header
// |
// loop continue target and back-edge block
// OpBranchConditional
// ||
// loop merge (to loop header^)
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %12 None
OpBranch %12
%12 = OpLabel
OpBranchConditional %8 %10 %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto context =
BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
CheckValid(kEnv, context.get());
std::string after = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %12 None
OpBranch %12
%12 = OpLabel
OpBranch %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after, context.get());
ops = SimpleConditionalBranchToBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(SimpleConditionalBranchToBranchTest,
DontRemoveBackEdgeCombinedHeaderContinue) {
// A test with the following structure.
//
// loop header and continue target and back-edge block
// OpBranchConditional
// ||
// loop merge (to loop header^)
//
// The conditional branch can be simplified.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %10 None
OpBranchConditional %8 %10 %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto context =
BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
CheckValid(kEnv, context.get());
std::string after = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %10 None
OpBranch %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after, context.get());
ops = SimpleConditionalBranchToBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
TEST(SimpleConditionalBranchToBranchTest, BackEdgeUnreachable) {
// A test with the following structure. I.e. a loop with an unreachable
// continue construct that ends with OpBranchConditional.
//
// loop header
// |
// | loop continue target (unreachable)
// | |
// | back-edge block (unreachable)
// | OpBranchConditional
// | ||
// loop merge (to loop header^)
//
// The conditional branch can be simplified.
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %12 None
OpBranch %11
%12 = OpLabel
OpBranch %13
%13 = OpLabel
OpBranchConditional %8 %10 %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto context =
BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
CheckValid(kEnv, context.get());
auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
CheckValid(kEnv, context.get());
std::string after = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
OpSource ESSL 310
OpName %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%5 = OpTypeInt 32 1
%6 = OpTypePointer Function %5
%7 = OpTypeBool
%8 = OpConstantTrue %7
%2 = OpFunction %3 None %4
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpLoopMerge %11 %12 None
OpBranch %11
%12 = OpLabel
OpBranch %13
%13 = OpLabel
OpBranch %10
%11 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(kEnv, after, context.get());
ops = SimpleConditionalBranchToBranchOpportunityFinder()
.GetAvailableOpportunities(context.get());
ASSERT_EQ(0, ops.size());
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@@ -373,30 +373,37 @@ TEST_F(ValidateAtomics, AtomicLoadVulkanInt64) {
"AtomicLoad: 64-bit atomics require the Int64Atomics capability"));
}
TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSuccess) {
TEST_F(ValidateAtomics, AtomicLoadWebGPUSuccess) {
const std::string body = R"(
%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %relaxed
%val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire
%val2 = OpAtomicLoad %u32 %u32_var %workgroup %relaxed
)";
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
}
TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSequentiallyConsistentFailure) {
TEST_F(ValidateAtomics, AtomicLoadWebGPUNonRelaxedFailure) {
const std::string body = R"(
%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %acquire
%val2 = OpAtomicLoad %u32 %u32_var %workgroup %release
)";
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
}
TEST_F(ValidateAtomics, AtomicLoadWebGPUSequentiallyConsistentFailure) {
const std::string body = R"(
%val3 = OpAtomicLoad %u32 %u32_var %subgroup %sequentially_consistent
)";
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"WebGPU spec disallows any bit masks in Memory Semantics that are "
"not Acquire, Release, AcquireRelease, UniformMemory, "
"WorkgroupMemory, ImageMemory, OutputMemoryKHR, MakeAvailableKHR, or "
"MakeVisibleKHR\n %34 = OpAtomicLoad %uint %29 %uint_3 %uint_16\n"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
}
TEST_F(ValidateAtomics, VK_KHR_shader_atomic_int64Success) {
@@ -579,13 +586,24 @@ OpAtomicStore %u32_var %device %sequentially_consistent %u32_1
TEST_F(ValidateAtomics, AtomicStoreWebGPUSuccess) {
const std::string body = R"(
OpAtomicStore %u32_var %queuefamily %release %u32_1
OpAtomicStore %u32_var %queuefamily %relaxed %u32_1
)";
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
}
TEST_F(ValidateAtomics, AtomicStoreWebGPUNonRelaxedFailure) {
const std::string body = R"(
OpAtomicStore %u32_var %queuefamily %release %u32_1
)";
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
}
TEST_F(ValidateAtomics, AtomicStoreWebGPUSequentiallyConsistent) {
const std::string body = R"(
OpAtomicStore %u32_var %queuefamily %sequentially_consistent %u32_1
@@ -593,14 +611,8 @@ OpAtomicStore %u32_var %queuefamily %sequentially_consistent %u32_1
CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"WebGPU spec disallows any bit masks in Memory Semantics that are "
"not Acquire, Release, AcquireRelease, UniformMemory, "
"WorkgroupMemory, ImageMemory, OutputMemoryKHR, MakeAvailableKHR, or "
"MakeVisibleKHR\n"
" OpAtomicStore %29 %uint_5 %uint_16 %uint_1\n"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
}
TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) {

View File

@@ -1203,11 +1203,7 @@ std::string GetUnreachableMergeWithBranchUse(SpvCapability cap,
TEST_P(ValidateCFG, UnreachableMergeWithBranchUse) {
CompileSuccessfully(
GetUnreachableMergeWithBranchUse(GetParam(), SPV_ENV_UNIVERSAL_1_0));
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, WebGPUUnreachableMergeWithBranchUse) {
@@ -1942,10 +1938,7 @@ OpDecorate %id BuiltIn GlobalInvocationId
str += "OpFunctionEnd";
CompileSuccessfully(str);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Statically reachable blocks cannot be terminated by "
"OpUnreachable"));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, LoopWithZeroBackEdgesBad) {
@@ -2873,11 +2866,7 @@ OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, ReachableOpUnreachableOpBranch) {
@@ -2896,11 +2885,7 @@ OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, ReachableOpUnreachableOpBranchConditional) {
@@ -2923,11 +2908,7 @@ OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, ReachableOpUnreachableOpSwitch) {
@@ -2952,11 +2933,7 @@ OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, ReachableOpUnreachableLoop) {
@@ -2980,11 +2957,7 @@ OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Statically reachable blocks cannot be terminated by OpUnreachable"));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateCFG, UnreachableLoopBadBackedge) {

View File

@@ -276,6 +276,63 @@ TEST_F(ValidateWebGPU, NonVulkanKHRMemoryModelExtensionBad) {
"\"SPV_KHR_8bit_storage\"\n"));
}
spv_binary GenerateTrivialBinary(bool need_little_endian) {
// Smallest possible valid WebGPU SPIR-V binary in little endian. Contains all
// the required boilerplate and a trivial entry point function.
static const uint8_t binary_bytes[] = {
// clang-format off
0x03, 0x02, 0x23, 0x07, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0xE1, 0x14, 0x00, 0x00,
0x0A, 0x00, 0x08, 0x00, 0x53, 0x50, 0x56, 0x5F, 0x4B, 0x48, 0x52, 0x5F,
0x76, 0x75, 0x6C, 0x6B, 0x61, 0x6E, 0x5F, 0x6D, 0x65, 0x6D, 0x6F, 0x72,
0x79, 0x5F, 0x6D, 0x6F, 0x64, 0x65, 0x6C, 0x00, 0x0E, 0x00, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x05, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x73, 0x68, 0x61, 0x64,
0x65, 0x72, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
0x04, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00
// clang-format on
};
static const size_t word_count = sizeof(binary_bytes) / sizeof(uint32_t);
std::unique_ptr<spv_binary_t> result(new spv_binary_t);
if (!result) return nullptr;
result->wordCount = word_count;
result->code = new uint32_t[word_count];
if (!result->code) return nullptr;
if (need_little_endian) {
memcpy(result->code, binary_bytes, sizeof(binary_bytes));
} else {
uint8_t* code_bytes = reinterpret_cast<uint8_t*>(result->code);
for (size_t word = 0; word < word_count; ++word) {
code_bytes[4 * word] = binary_bytes[4 * word + 3];
code_bytes[4 * word + 1] = binary_bytes[4 * word + 2];
code_bytes[4 * word + 2] = binary_bytes[4 * word + 1];
code_bytes[4 * word + 3] = binary_bytes[4 * word];
}
}
return result.release();
}
TEST_F(ValidateWebGPU, LittleEndianGood) {
DestroyBinary();
binary_ = GenerateTrivialBinary(true);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
}
TEST_F(ValidateWebGPU, BigEndianBad) {
DestroyBinary();
binary_ = GenerateTrivialBinary(false);
EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions(SPV_ENV_WEBGPU_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("WebGPU requires SPIR-V to be little endian."));
}
} // namespace
} // namespace val
} // namespace spvtools