aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYiwei Zhang <zzyiwei@google.com>2019-02-05 23:29:32 -0800
committerYiwei Zhang <zzyiwei@google.com>2019-02-05 23:29:32 -0800
commit2e6244127648ea760172bf07ecf687a59ee7bd17 (patch)
tree2552178d8459e02ac87d88b24c7d58f8b0083b98
parentc5727b419b4d30f9e0fa44a6eb6a75a4b8c88ca9 (diff)
parente0292c269d6f5c8481afb9f2d043c74ee11ca24f (diff)
downloadSPIRV-Tools-android10-qpr3-release.tar.gz
Merge commit 'e0292c269d6f5c8481afb9f2d043c74ee11ca24f' into goog/masterq_tzdata_aml_297100400q_tzdata_aml_297100300q_tzdata_aml_297100000q_tzdata_aml_296200000q_tzdata_aml_295600118q_tzdata_aml_295600110q_tzdata_aml_295500002q_tzdata_aml_295500001q_tzdata_aml_294400310platform-tools-29.0.6platform-tools-29.0.5android-r-preview-4android-r-preview-3android-r-preview-2android-r-preview-1android-mainline-12.0.0_r54android-mainline-12.0.0_r111android-mainline-10.0.0_r9android-mainline-10.0.0_r8android-mainline-10.0.0_r7android-mainline-10.0.0_r6android-mainline-10.0.0_r5android-mainline-10.0.0_r4android-mainline-10.0.0_r13android-mainline-10.0.0_r12android-mainline-10.0.0_r11android-mainline-10.0.0_r10android-10.0.0_r9android-10.0.0_r8android-10.0.0_r7android-10.0.0_r45android-10.0.0_r44android-10.0.0_r43android-10.0.0_r42android-10.0.0_r41android-10.0.0_r40android-10.0.0_r39android-10.0.0_r38android-10.0.0_r37android-10.0.0_r36android-10.0.0_r35android-10.0.0_r34android-10.0.0_r33android-10.0.0_r32android-10.0.0_r31android-10.0.0_r30android-10.0.0_r29android-10.0.0_r28android-10.0.0_r27android-10.0.0_r26android-10.0.0_r25android-10.0.0_r24android-10.0.0_r23android-10.0.0_r22android-10.0.0_r21android-10.0.0_r20android-10.0.0_r19android-10.0.0_r18android-10.0.0_r16android-10.0.0_r15android-10.0.0_r14android-10.0.0_r13android-10.0.0_r12q_tzdata_aml_297100000ndk-sysroot-r21android12-mainline-tzdata-releaseandroid10-qpr3-s1-releaseandroid10-qpr3-releaseandroid10-qpr2-s4-releaseandroid10-qpr2-s3-releaseandroid10-qpr2-s2-releaseandroid10-qpr2-s1-releaseandroid10-qpr2-releaseandroid10-qpr1-releaseandroid10-qpr1-mainline-releaseandroid10-qpr1-d-releaseandroid10-qpr1-c-s1-releaseandroid10-qpr1-c-releaseandroid10-qpr1-b-s1-releaseandroid10-qpr1-b-releaseandroid10-mainline-tzdata-releaseandroid10-mainline-resolv-releaseandroid10-mainline-networking-releaseandroid10-mainline-media-releaseandroid10-devandroid10-d4-s1-releaseandroid10-d4-releaseandroid10-c2f2-s2-releaseandroid10-c2f2-s1-releaseandroid10-c2f2-releaseandroid10-android13-mainline-tzdata-release
Bug: 123431604 Test: CtsDeqpTestCases Change-Id: Ie664ea90afc4dff0b4c662a77d2bd4051f8ad4ba
-rw-r--r--BUILD.gn1
-rw-r--r--CHANGES40
-rw-r--r--README.md2
-rw-r--r--include/spirv-tools/optimizer.hpp4
-rw-r--r--source/CMakeLists.txt1
-rw-r--r--source/opt/aggressive_dead_code_elim_pass.cpp8
-rw-r--r--source/opt/cfg.cpp12
-rw-r--r--source/opt/cfg.h3
-rw-r--r--source/opt/constants.cpp1
-rw-r--r--source/opt/decoration_manager.cpp5
-rw-r--r--source/opt/decoration_manager.h25
-rw-r--r--source/opt/fold.cpp15
-rw-r--r--source/opt/ir_builder.h15
-rw-r--r--source/opt/ir_context.cpp9
-rw-r--r--source/opt/ir_context.h3
-rw-r--r--source/opt/licm_pass.cpp74
-rw-r--r--source/opt/licm_pass.h25
-rw-r--r--source/opt/loop_descriptor.cpp1
-rw-r--r--source/opt/loop_descriptor.h2
-rw-r--r--source/opt/loop_fission.cpp1
-rw-r--r--source/opt/loop_peeling.cpp4
-rw-r--r--source/opt/loop_unroller.cpp3
-rw-r--r--source/opt/loop_unswitch_pass.cpp50
-rw-r--r--source/opt/loop_utils.cpp5
-rw-r--r--source/opt/merge_return_pass.cpp45
-rw-r--r--source/opt/merge_return_pass.h8
-rw-r--r--source/opt/optimizer.cpp6
-rw-r--r--source/opt/pass.h5
-rw-r--r--source/opt/reflect.h3
-rw-r--r--source/opt/ssa_rewrite_pass.cpp1
-rw-r--r--source/opt/type_manager.cpp4
-rw-r--r--source/opt/types.cpp3
-rw-r--r--source/opt/types.h4
-rw-r--r--source/reduce/CMakeLists.txt6
-rw-r--r--source/reduce/reducer.cpp18
-rw-r--r--source/reduce/remove_opname_instruction_reduction_pass.cpp44
-rw-r--r--source/reduce/remove_opname_instruction_reduction_pass.h50
-rw-r--r--source/reduce/structured_loop_to_selection_reduction_opportunity.cpp377
-rw-r--r--source/reduce/structured_loop_to_selection_reduction_opportunity.h125
-rw-r--r--source/reduce/structured_loop_to_selection_reduction_pass.cpp95
-rw-r--r--source/reduce/structured_loop_to_selection_reduction_pass.h64
-rw-r--r--source/text_handler.cpp2
-rw-r--r--source/val/validate.cpp36
-rw-r--r--source/val/validate.h3
-rw-r--r--source/val/validate_atomics.cpp164
-rw-r--r--source/val/validate_barriers.cpp154
-rw-r--r--source/val/validate_decorations.cpp393
-rw-r--r--source/val/validate_image.cpp14
-rw-r--r--source/val/validate_memory.cpp55
-rw-r--r--source/val/validate_memory_semantics.cpp248
-rw-r--r--source/val/validate_memory_semantics.h28
-rw-r--r--source/val/validate_scopes.cpp8
-rw-r--r--source/val/validate_type.cpp32
-rw-r--r--source/val/validation_state.cpp80
-rw-r--r--source/val/validation_state.h31
-rw-r--r--test/cpp_interface_test.cpp3
-rw-r--r--test/opt/CMakeLists.txt1
-rw-r--r--test/opt/aggressive_dead_code_elim_test.cpp45
-rw-r--r--test/opt/fold_test.cpp31
-rw-r--r--test/opt/ir_builder.cpp30
-rw-r--r--test/opt/ir_context_test.cpp97
-rw-r--r--test/opt/loop_optimizations/hoist_without_preheader.cpp75
-rw-r--r--test/opt/pass_merge_return_test.cpp109
-rw-r--r--test/opt/type_manager_test.cpp4
-rw-r--r--test/opt/types_test.cpp1
-rw-r--r--test/reduce/CMakeLists.txt3
-rw-r--r--test/reduce/reduce_test_util.cpp20
-rw-r--r--test/reduce/reduce_test_util.h12
-rw-r--r--test/reduce/reducer_test.cpp79
-rw-r--r--test/reduce/remove_opname_instruction_reduction_pass_test.cpp216
-rw-r--r--test/reduce/structured_loop_to_selection_reduction_pass_test.cpp3440
-rw-r--r--test/reduce/validation_during_reduction_test.cpp376
-rw-r--r--test/val/val_adjacency_test.cpp3
-rw-r--r--test/val/val_arithmetics_test.cpp3
-rw-r--r--test/val/val_atomics_test.cpp226
-rw-r--r--test/val/val_barriers_test.cpp82
-rw-r--r--test/val/val_builtins_test.cpp31
-rw-r--r--test/val/val_capability_test.cpp7
-rw-r--r--test/val/val_cfg_test.cpp112
-rw-r--r--test/val/val_composites_test.cpp9
-rw-r--r--test/val/val_conversion_test.cpp9
-rw-r--r--test/val/val_data_test.cpp15
-rw-r--r--test/val/val_decoration_test.cpp1091
-rw-r--r--test/val/val_derivatives_test.cpp3
-rw-r--r--test/val/val_ext_inst_test.cpp25
-rw-r--r--test/val/val_id_test.cpp590
-rw-r--r--test/val/val_image_test.cpp120
-rw-r--r--test/val/val_layout_test.cpp51
-rw-r--r--test/val/val_limits_test.cpp4
-rw-r--r--test/val/val_memory_test.cpp626
-rw-r--r--test/val/val_ssa_test.cpp22
-rw-r--r--test/val/val_validation_state_test.cpp164
-rw-r--r--tools/opt/opt.cpp26
-rw-r--r--tools/reduce/reduce.cpp6
94 files changed, 9195 insertions, 987 deletions
diff --git a/BUILD.gn b/BUILD.gn
index ee897b38..5f0eba95 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -421,6 +421,7 @@ static_library("spvtools_val") {
"source/val/validate_literals.cpp",
"source/val/validate_logicals.cpp",
"source/val/validate_memory.cpp",
+ "source/val/validate_memory_semantics.cpp",
"source/val/validate_mode_setting.cpp",
"source/val/validate_non_uniform.cpp",
"source/val/validate_primitives.cpp",
diff --git a/CHANGES b/CHANGES
index f9012fca..7f9008f0 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,7 +1,43 @@
Revision history for SPIRV-Tools
-v2018.7-dev 2018-11-07
- - Start v2018.7-dev
+v2018.7-dev 2018-12-10
+ - General:
+ - Created a new tool called spirv-reduce.
+ - Add cmake option to turn off SPIRV_TIMER_ENABLED (#2103)
+ - New optimization pass to update the memory model from GLSL450 to VulkanKHR.
+ - Optimizer
+ - Added the instrumentation passes for bindless validation.
+ - Added passes to help preserve OpLine information (#2027)
+ - Add basic support for EXT_fragment_invocation_density (#2100)
+ - Fix invalid OpPhi generated by merge-return. (#2172)
+ Fixes:
+ - #2018: Don't inline functions with a return in a structured CFG contstruct.
+ - #2047: Fix bug in folding when volatile stores are present.
+ - #2053: Fix check for when folding floating pointer values is allowed.
+ - #2130: Don't inline recursive functions.
+ - Validator
+ - Changed the naming convention of outputing ids with names in diagnostic messages.
+ - Added validation rules for UniformConstant variables in Vulkan.
+ - #1949: Validate uniform variable type in Vulkan
+ - Ensure for OpVariable that result type and storage class operand agree (#2052)
+ - Validator: Support VK_EXT_scalar_block_layout
+ - Added Vulkan memory model semantics validation
+ - Added validation checkes spefic to WebGPU environment.
+ - Add support for VK_EXT_Transform_feedback capabilities (#2088)
+ - Add validation for OpArrayLength. (#2117)
+ - Ensure that function parameter's type is not void (#2118)
+ - Validate pointer variables (#2111)
+ - Add check for QueueFamilyKHMR memory scope (#2144)
+ - Validate PushConstants annotation and type (#2140)
+ - Allow Float16/Int8 for Vulkan 1.0 (#2153)
+ - Check binding annotations in resource variables (#2151, #2167)
+ - Validate OpForwardPointer (#2156)
+ Fixes:
+ - #2049: Allow InstanceId for NV ray tracing
+ - Reduce
+ - Initial commit wit a few passes to reduce test cases.
+ Fixes:
+
v2018.6 2018-11-07
- General:
diff --git a/README.md b/README.md
index 903200a1..534664f7 100644
--- a/README.md
+++ b/README.md
@@ -273,7 +273,7 @@ The following CMake options are supported:
See [`CMakeLists.txt`](CMakeLists.txt) for details.
* `SPIRV_WERROR={ON|OFF}`, default `ON` - Forces a compilation error on any
warnings encountered by enabling the compiler-specific compiler front-end
- option.
+ option. No compiler front-end options are enabled when this option is OFF.
Additionally, you can pass additional C preprocessor definitions to SPIRV-Tools
via setting `SPIRV_TOOLS_EXTRA_DEFINITIONS`. For example, by setting it to
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 8c6b396c..f8769c97 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -148,6 +148,10 @@ class Optimizer {
// returns false.
bool FlagHasValidForm(const std::string& flag) const;
+ // Allows changing, after creation time, the target environment to be
+ // optimized for. Should be called before calling Run().
+ void SetTargetEnv(const spv_target_env env);
+
// Optimizes the given SPIR-V module |original_binary| and writes the
// optimized binary into |optimized_binary|.
// Returns true on successful optimization, whether or not the module is
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index da3c1cb0..efd6330c 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -316,6 +316,7 @@ set(SPIRV_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_literals.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_logicals.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory_semantics.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_mode_setting.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_non_uniform.cpp
${CMAKE_CURRENT_SOURCE_DIR}/val/validate_primitives.cpp
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 27933123..82d74990 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -555,6 +555,14 @@ Pass::Status AggressiveDCEPass::ProcessImpl() {
// return unmodified.
if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
+ // If the decoration manager is kept live then the context will try to keep it
+ // up to date. ADCE deals with group decorations by changing the operands in
+ // |OpGroupDecorate| instruction directly without informing the decoration
+ // manager. This can put it in an invalid state which will cause an error
+ // when the context tries to update it. To avoid this problem invalidate
+ // the decoration manager upfront.
+ context()->InvalidateAnalyses(IRContext::Analysis::kAnalysisDecorations);
+
// Eliminate Dead functions.
bool modified = EliminateDeadFunctions();
diff --git a/source/opt/cfg.cpp b/source/opt/cfg.cpp
index 778c5274..7e1097e3 100644
--- a/source/opt/cfg.cpp
+++ b/source/opt/cfg.cpp
@@ -167,6 +167,13 @@ BasicBlock* CFG::SplitLoopHeader(BasicBlock* bb) {
Function* fn = bb->GetParent();
IRContext* context = module_->context();
+ // Get the new header id up front. If we are out of ids, then we cannot split
+ // the loop.
+ uint32_t new_header_id = context->TakeNextId();
+ if (new_header_id == 0) {
+ return nullptr;
+ }
+
// Find the insertion point for the new bb.
Function::iterator header_it = std::find_if(
fn->begin(), fn->end(),
@@ -197,10 +204,7 @@ BasicBlock* CFG::SplitLoopHeader(BasicBlock* bb) {
++iter;
}
- BasicBlock* new_header =
- bb->SplitBasicBlock(context, context->TakeNextId(), iter);
-
- uint32_t new_header_id = new_header->id();
+ BasicBlock* new_header = bb->SplitBasicBlock(context, new_header_id, iter);
context->AnalyzeDefUse(new_header->GetLabelInst());
// Update cfg
diff --git a/source/opt/cfg.h b/source/opt/cfg.h
index 7bb8ecbb..5ff3aa03 100644
--- a/source/opt/cfg.h
+++ b/source/opt/cfg.h
@@ -128,7 +128,8 @@ class CFG {
// id as |block| and will become a preheader for the loop. The other block
// is a new block that will be the new loop header.
//
- // Returns a pointer to the new loop header.
+ // Returns a pointer to the new loop header. Returns |nullptr| if the new
+ // loop pointer could not be created.
BasicBlock* SplitLoopHeader(BasicBlock* bb);
private:
diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp
index ecb5f97c..768364be 100644
--- a/source/opt/constants.cpp
+++ b/source/opt/constants.cpp
@@ -165,6 +165,7 @@ std::vector<const Constant*> ConstantManager::GetConstantsFromIds(
Instruction* ConstantManager::BuildInstructionAndAddToModule(
const Constant* new_const, Module::inst_iterator* pos, uint32_t type_id) {
+ // TODO(1841): Handle id overflow.
uint32_t new_id = context()->TakeNextId();
auto new_inst = CreateInstruction(new_id, new_const, type_id);
if (!new_inst) {
diff --git a/source/opt/decoration_manager.cpp b/source/opt/decoration_manager.cpp
index 9990661b..a12326ba 100644
--- a/source/opt/decoration_manager.cpp
+++ b/source/opt/decoration_manager.cpp
@@ -517,6 +517,11 @@ void DecorationManager::RemoveDecoration(Instruction* inst) {
break;
}
}
+
+bool operator==(const DecorationManager& lhs, const DecorationManager& rhs) {
+ return lhs.id_to_decoration_insts_ == rhs.id_to_decoration_insts_;
+}
+
} // namespace analysis
} // namespace opt
} // namespace spvtools
diff --git a/source/opt/decoration_manager.h b/source/opt/decoration_manager.h
index 2d87f5f8..a5fb4c86 100644
--- a/source/opt/decoration_manager.h
+++ b/source/opt/decoration_manager.h
@@ -125,6 +125,12 @@ class DecorationManager {
void AddMemberDecoration(uint32_t member, uint32_t inst_id,
uint32_t decoration, uint32_t decoration_value);
+ friend bool operator==(const DecorationManager&, const DecorationManager&);
+ friend bool operator!=(const DecorationManager& lhs,
+ const DecorationManager& rhs) {
+ return !(lhs == rhs);
+ }
+
private:
// Analyzes the defs and uses in the given |module| and populates data
// structures in this class. Does nothing if |module| is nullptr.
@@ -150,6 +156,25 @@ class DecorationManager {
// group.
};
+ friend bool operator==(const TargetData& lhs, const TargetData& rhs) {
+ if (!std::is_permutation(lhs.direct_decorations.begin(),
+ lhs.direct_decorations.end(),
+ rhs.direct_decorations.begin())) {
+ return false;
+ }
+ if (!std::is_permutation(lhs.indirect_decorations.begin(),
+ lhs.indirect_decorations.end(),
+ rhs.indirect_decorations.begin())) {
+ return false;
+ }
+ if (!std::is_permutation(lhs.decorate_insts.begin(),
+ lhs.decorate_insts.end(),
+ rhs.decorate_insts.begin())) {
+ return false;
+ }
+ return true;
+ }
+
// Mapping from ids to the instructions applying a decoration to those ids.
// In other words, for each id you get all decoration instructions
// referencing that id, be it directly (SpvOpDecorate, SpvOpMemberDecorate
diff --git a/source/opt/fold.cpp b/source/opt/fold.cpp
index 0cda772c..d6b583f9 100644
--- a/source/opt/fold.cpp
+++ b/source/opt/fold.cpp
@@ -114,12 +114,23 @@ uint32_t InstructionFolder::BinaryOperate(SpvOp opcode, uint32_t a,
}
// Shifting
- case SpvOp::SpvOpShiftRightLogical: {
+ case SpvOp::SpvOpShiftRightLogical:
+ if (b > 32) {
+ // This is undefined behaviour. Choose 0 for consistency.
+ return 0;
+ }
return a >> b;
- }
case SpvOp::SpvOpShiftRightArithmetic:
+ if (b > 32) {
+ // This is undefined behaviour. Choose 0 for consistency.
+ return 0;
+ }
return (static_cast<int32_t>(a)) >> b;
case SpvOp::SpvOpShiftLeftLogical:
+ if (b > 32) {
+ // This is undefined behaviour. Choose 0 for consistency.
+ return 0;
+ }
return a << b;
// Bitwise operations
diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
index 434c3802..2f741d88 100644
--- a/source/opt/ir_builder.h
+++ b/source/opt/ir_builder.h
@@ -59,6 +59,7 @@ class InstructionBuilder {
preserved_analyses) {}
Instruction* AddNullaryOp(uint32_t type_id, SpvOp opcode) {
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> newUnOp(new Instruction(
GetContext(), opcode, type_id,
opcode == SpvOpReturn ? 0 : GetContext()->TakeNextId(), {}));
@@ -66,6 +67,7 @@ class InstructionBuilder {
}
Instruction* AddUnaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1) {
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> newUnOp(new Instruction(
GetContext(), opcode, type_id, GetContext()->TakeNextId(),
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}}}));
@@ -74,6 +76,7 @@ class InstructionBuilder {
Instruction* AddBinaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
uint32_t operand2) {
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> newBinOp(new Instruction(
GetContext(), opcode, type_id,
opcode == SpvOpStore ? 0 : GetContext()->TakeNextId(),
@@ -84,6 +87,7 @@ class InstructionBuilder {
Instruction* AddTernaryOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
uint32_t operand2, uint32_t operand3) {
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> newTernOp(new Instruction(
GetContext(), opcode, type_id, GetContext()->TakeNextId(),
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
@@ -95,6 +99,7 @@ class InstructionBuilder {
Instruction* AddQuadOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
uint32_t operand2, uint32_t operand3,
uint32_t operand4) {
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> newQuadOp(new Instruction(
GetContext(), opcode, type_id, GetContext()->TakeNextId(),
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
@@ -106,6 +111,7 @@ class InstructionBuilder {
Instruction* AddIdLiteralOp(uint32_t type_id, SpvOp opcode, uint32_t operand1,
uint32_t operand2) {
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> newBinOp(new Instruction(
GetContext(), opcode, type_id, GetContext()->TakeNextId(),
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {operand1}},
@@ -124,6 +130,7 @@ class InstructionBuilder {
for (size_t i = 0; i < operands.size(); i++) {
ops.push_back({SPV_OPERAND_TYPE_ID, {operands[i]}});
}
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> new_inst(new Instruction(
GetContext(), opcode, type_id,
result != 0 ? result : GetContext()->TakeNextId(), ops));
@@ -251,6 +258,7 @@ class InstructionBuilder {
// The id |op1| is the left hand side of the operation.
// The id |op2| is the right hand side of the operation.
Instruction* AddIAdd(uint32_t type, uint32_t op1, uint32_t op2) {
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> inst(new Instruction(
GetContext(), SpvOpIAdd, type, GetContext()->TakeNextId(),
{{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}));
@@ -264,6 +272,7 @@ class InstructionBuilder {
Instruction* AddULessThan(uint32_t op1, uint32_t op2) {
analysis::Bool bool_type;
uint32_t type = GetContext()->get_type_mgr()->GetId(&bool_type);
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> inst(new Instruction(
GetContext(), SpvOpULessThan, type, GetContext()->TakeNextId(),
{{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}));
@@ -277,6 +286,7 @@ class InstructionBuilder {
Instruction* AddSLessThan(uint32_t op1, uint32_t op2) {
analysis::Bool bool_type;
uint32_t type = GetContext()->get_type_mgr()->GetId(&bool_type);
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> inst(new Instruction(
GetContext(), SpvOpSLessThan, type, GetContext()->TakeNextId(),
{{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}));
@@ -306,6 +316,7 @@ class InstructionBuilder {
// bool) for |type|.
Instruction* AddSelect(uint32_t type, uint32_t cond, uint32_t true_value,
uint32_t false_value) {
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> select(new Instruction(
GetContext(), SpvOpSelect, type, GetContext()->TakeNextId(),
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {cond}},
@@ -330,6 +341,7 @@ class InstructionBuilder {
ops.emplace_back(SPV_OPERAND_TYPE_ID,
std::initializer_list<uint32_t>{id});
}
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> construct(
new Instruction(GetContext(), SpvOpCompositeConstruct, type,
GetContext()->TakeNextId(), ops));
@@ -401,6 +413,7 @@ class InstructionBuilder {
operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}});
}
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> new_inst(
new Instruction(GetContext(), SpvOpCompositeExtract, type,
GetContext()->TakeNextId(), operands));
@@ -424,6 +437,7 @@ class InstructionBuilder {
operands.push_back({SPV_OPERAND_TYPE_ID, {index_id}});
}
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> new_inst(
new Instruction(GetContext(), SpvOpAccessChain, type_id,
GetContext()->TakeNextId(), operands));
@@ -434,6 +448,7 @@ class InstructionBuilder {
std::vector<Operand> operands;
operands.push_back({SPV_OPERAND_TYPE_ID, {base_ptr_id}});
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> new_inst(
new Instruction(GetContext(), SpvOpLoad, type_id,
GetContext()->TakeNextId(), operands));
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index c1158e79..af9ac1ab 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -252,6 +252,14 @@ bool IRContext::IsConsistent() {
return false;
}
+ if (AreAnalysesValid(kAnalysisDecorations)) {
+ analysis::DecorationManager* dec_mgr = get_decoration_mgr();
+ analysis::DecorationManager current(module());
+
+ if (*dec_mgr != current) {
+ return false;
+ }
+ }
return true;
}
@@ -665,6 +673,7 @@ uint32_t IRContext::GetBuiltinVarId(uint32_t builtin) {
uint32_t type_id = type_mgr->GetTypeInstruction(reg_type);
uint32_t varTyPtrId =
type_mgr->FindPointerToType(type_id, SpvStorageClassInput);
+ // TODO(1841): Handle id overflow.
var_id = TakeNextId();
std::unique_ptr<Instruction> newVarOp(
new Instruction(this, SpvOpVariable, varTyPtrId, var_id,
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 83e06b82..94bfd015 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -450,7 +450,8 @@ class IRContext {
post_dominator_trees_.erase(f);
}
- // Return the next available SSA id and increment it.
+ // Return the next available SSA id and increment it. Returns 0 if the
+ // maximum SSA id has been reached.
inline uint32_t TakeNextId() { return module()->TakeNextIdBound(); }
FeatureManager* get_feature_mgr() {
diff --git a/source/opt/licm_pass.cpp b/source/opt/licm_pass.cpp
index d8256679..c5532219 100644
--- a/source/opt/licm_pass.cpp
+++ b/source/opt/licm_pass.cpp
@@ -23,70 +23,81 @@
namespace spvtools {
namespace opt {
-Pass::Status LICMPass::Process() {
- return ProcessIRContext() ? Status::SuccessWithChange
- : Status::SuccessWithoutChange;
-}
+Pass::Status LICMPass::Process() { return ProcessIRContext(); }
-bool LICMPass::ProcessIRContext() {
- bool modified = false;
+Pass::Status LICMPass::ProcessIRContext() {
+ Status status = Status::SuccessWithoutChange;
Module* module = get_module();
// Process each function in the module
- for (Function& f : *module) {
- modified |= ProcessFunction(&f);
+ for (auto func = module->begin();
+ func != module->end() && status != Status::Failure; ++func) {
+ status = CombineStatus(status, ProcessFunction(&*func));
}
- return modified;
+ return status;
}
-bool LICMPass::ProcessFunction(Function* f) {
- bool modified = false;
+Pass::Status LICMPass::ProcessFunction(Function* f) {
+ Status status = Status::SuccessWithoutChange;
LoopDescriptor* loop_descriptor = context()->GetLoopDescriptor(f);
// Process each loop in the function
- for (Loop& loop : *loop_descriptor) {
+ for (auto it = loop_descriptor->begin();
+ it != loop_descriptor->end() && status != Status::Failure; ++it) {
+ Loop& loop = *it;
// Ignore nested loops, as we will process them in order in ProcessLoop
if (loop.IsNested()) {
continue;
}
- modified |= ProcessLoop(&loop, f);
+ status = CombineStatus(status, ProcessLoop(&loop, f));
}
- return modified;
+ return status;
}
-bool LICMPass::ProcessLoop(Loop* loop, Function* f) {
- bool modified = false;
+Pass::Status LICMPass::ProcessLoop(Loop* loop, Function* f) {
+ Status status = Status::SuccessWithoutChange;
// Process all nested loops first
- for (Loop* nested_loop : *loop) {
- modified |= ProcessLoop(nested_loop, f);
+ for (auto nl = loop->begin(); nl != loop->end() && status != Status::Failure;
+ ++nl) {
+ Loop* nested_loop = *nl;
+ status = CombineStatus(status, ProcessLoop(nested_loop, f));
}
std::vector<BasicBlock*> loop_bbs{};
- modified |= AnalyseAndHoistFromBB(loop, f, loop->GetHeaderBlock(), &loop_bbs);
+ status = CombineStatus(
+ status,
+ AnalyseAndHoistFromBB(loop, f, loop->GetHeaderBlock(), &loop_bbs));
- for (size_t i = 0; i < loop_bbs.size(); ++i) {
+ for (size_t i = 0; i < loop_bbs.size() && status != Status::Failure; ++i) {
BasicBlock* bb = loop_bbs[i];
// do not delete the element
- modified |= AnalyseAndHoistFromBB(loop, f, bb, &loop_bbs);
+ status =
+ CombineStatus(status, AnalyseAndHoistFromBB(loop, f, bb, &loop_bbs));
}
- return modified;
+ return status;
}
-bool LICMPass::AnalyseAndHoistFromBB(Loop* loop, Function* f, BasicBlock* bb,
- std::vector<BasicBlock*>* loop_bbs) {
+Pass::Status LICMPass::AnalyseAndHoistFromBB(
+ Loop* loop, Function* f, BasicBlock* bb,
+ std::vector<BasicBlock*>* loop_bbs) {
bool modified = false;
- std::function<void(Instruction*)> hoist_inst =
+ std::function<bool(Instruction*)> hoist_inst =
[this, &loop, &modified](Instruction* inst) {
if (loop->ShouldHoistInstruction(this->context(), inst)) {
- HoistInstruction(loop, inst);
+ if (!HoistInstruction(loop, inst)) {
+ return false;
+ }
modified = true;
}
+ return true;
};
if (IsImmediatelyContainedInLoop(loop, f, bb)) {
- bb->ForEachInst(hoist_inst, false);
+ if (!bb->WhileEachInst(hoist_inst, false)) {
+ return Status::Failure;
+ }
}
DominatorAnalysis* dom_analysis = context()->GetDominatorAnalysis(f);
@@ -98,7 +109,7 @@ bool LICMPass::AnalyseAndHoistFromBB(Loop* loop, Function* f, BasicBlock* bb,
}
}
- return modified;
+ return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
}
bool LICMPass::IsImmediatelyContainedInLoop(Loop* loop, Function* f,
@@ -107,10 +118,15 @@ bool LICMPass::IsImmediatelyContainedInLoop(Loop* loop, Function* f,
return loop == (*loop_descriptor)[bb->id()];
}
-void LICMPass::HoistInstruction(Loop* loop, Instruction* inst) {
+bool LICMPass::HoistInstruction(Loop* loop, Instruction* inst) {
+ // TODO(1841): Handle failure to create pre-header.
BasicBlock* pre_header_bb = loop->GetOrCreatePreHeaderBlock();
+ if (!pre_header_bb) {
+ return false;
+ }
inst->InsertBefore(std::move(&(*pre_header_bb->tail())));
context()->set_instr_block(inst, pre_header_bb);
+ return true;
}
} // namespace opt
diff --git a/source/opt/licm_pass.h b/source/opt/licm_pass.h
index a1745004..a94ae118 100644
--- a/source/opt/licm_pass.h
+++ b/source/opt/licm_pass.h
@@ -35,30 +35,35 @@ class LICMPass : public Pass {
private:
// Searches the IRContext for functions and processes each, moving invariants
- // outside loops within the function where possible
- // Returns true if a change was made to a function within the IRContext
- bool ProcessIRContext();
+ // outside loops within the function where possible.
+ // Returns the status depending on whether or not there was a failure or
+ // change.
+ Pass::Status ProcessIRContext();
// Checks the function for loops, calling ProcessLoop on each one found.
- // Returns true if a change was made to the function, false otherwise.
- bool ProcessFunction(Function* f);
+ // Returns the status depending on whether or not there was a failure or
+ // change.
+ Pass::Status ProcessFunction(Function* f);
// Checks for invariants in the loop and attempts to move them to the loops
// preheader. Works from inner loop to outer when nested loops are found.
- // Returns true if a change was made to the loop, false otherwise.
- bool ProcessLoop(Loop* loop, Function* f);
+ // Returns the status depending on whether or not there was a failure or
+ // change.
+ Pass::Status ProcessLoop(Loop* loop, Function* f);
// Analyses each instruction in |bb|, hoisting invariants to |pre_header_bb|.
// Each child of |bb| wrt to |dom_tree| is pushed to |loop_bbs|
- bool AnalyseAndHoistFromBB(Loop* loop, Function* f, BasicBlock* bb,
- std::vector<BasicBlock*>* loop_bbs);
+ // Returns the status depending on whether or not there was a failure or
+ // change.
+ Pass::Status AnalyseAndHoistFromBB(Loop* loop, Function* f, BasicBlock* bb,
+ std::vector<BasicBlock*>* loop_bbs);
// Returns true if |bb| is immediately contained in |loop|
bool IsImmediatelyContainedInLoop(Loop* loop, Function* f, BasicBlock* bb);
// Move the instruction to the given BasicBlock
// This method will update the instruction to block mapping for the context
- void HoistInstruction(Loop* loop, Instruction* inst);
+ bool HoistInstruction(Loop* loop, Instruction* inst);
};
} // namespace opt
diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp
index efc56bdb..5aff34ce 100644
--- a/source/opt/loop_descriptor.cpp
+++ b/source/opt/loop_descriptor.cpp
@@ -914,6 +914,7 @@ bool LoopDescriptor::CreatePreHeaderBlocksIfMissing() {
for (auto& loop : *this) {
if (!loop.GetPreHeaderBlock()) {
modified = true;
+ // TODO(1841): Handle failure to create pre-header.
loop.GetOrCreatePreHeaderBlock();
}
}
diff --git a/source/opt/loop_descriptor.h b/source/opt/loop_descriptor.h
index 45a175a0..38f017b5 100644
--- a/source/opt/loop_descriptor.h
+++ b/source/opt/loop_descriptor.h
@@ -132,7 +132,7 @@ class Loop {
void SetPreHeaderBlock(BasicBlock* preheader);
// Returns the loop pre-header, if there is no suitable preheader it will be
- // created.
+ // created. Returns |nullptr| if it fails to create the preheader.
BasicBlock* GetOrCreatePreHeaderBlock();
// Returns true if this loop contains any nested loops.
diff --git a/source/opt/loop_fission.cpp b/source/opt/loop_fission.cpp
index 0052406d..0678113c 100644
--- a/source/opt/loop_fission.cpp
+++ b/source/opt/loop_fission.cpp
@@ -367,6 +367,7 @@ Loop* LoopFissionImpl::SplitLoop() {
cloned_loop->UpdateLoopMergeInst();
// Add the loop_ to the module.
+ // TODO(1841): Handle failure to create pre-header.
Function::iterator it =
util.GetFunction()->FindBlock(loop_->GetOrCreatePreHeaderBlock()->id());
util.GetFunction()->AddBasicBlocks(clone_results.cloned_bb_.begin(),
diff --git a/source/opt/loop_peeling.cpp b/source/opt/loop_peeling.cpp
index 227ba4a5..b640542d 100644
--- a/source/opt/loop_peeling.cpp
+++ b/source/opt/loop_peeling.cpp
@@ -39,6 +39,7 @@ void LoopPeeling::DuplicateAndConnectLoop(
assert(CanPeelLoop() && "Cannot peel loop!");
std::vector<BasicBlock*> ordered_loop_blocks;
+ // TODO(1841): Handle failure to create pre-header.
BasicBlock* pre_header = loop_->GetOrCreatePreHeaderBlock();
loop_->ComputeLoopStructuredOrder(&ordered_loop_blocks);
@@ -131,6 +132,7 @@ void LoopPeeling::DuplicateAndConnectLoop(
// Force the creation of a new preheader for the original loop and set it as
// the merge block for the cloned loop.
+ // TODO(1841): Handle failure to create pre-header.
cloned_loop_->SetMergeBlock(loop_->GetOrCreatePreHeaderBlock());
}
@@ -345,6 +347,7 @@ BasicBlock* LoopPeeling::CreateBlockBefore(BasicBlock* bb) {
CFG& cfg = *context_->cfg();
assert(cfg.preds(bb->id()).size() == 1 && "More than one predecessor");
+ // TODO(1841): Handle id overflow.
std::unique_ptr<BasicBlock> new_bb =
MakeUnique<BasicBlock>(std::unique_ptr<Instruction>(new Instruction(
context_, SpvOpLabel, 0, context_->TakeNextId(), {})));
@@ -391,6 +394,7 @@ BasicBlock* LoopPeeling::CreateBlockBefore(BasicBlock* bb) {
BasicBlock* LoopPeeling::ProtectLoop(Loop* loop, Instruction* condition,
BasicBlock* if_merge) {
+ // TODO(1841): Handle failure to create pre-header.
BasicBlock* if_block = loop->GetOrCreatePreHeaderBlock();
// Will no longer be a pre-header because of the if.
loop->SetPreHeaderBlock(nullptr);
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index d3e733ac..0d49d881 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -377,6 +377,7 @@ void LoopUnrollerUtilsImpl::Init(Loop* loop) {
// number of bodies.
void LoopUnrollerUtilsImpl::PartiallyUnrollResidualFactor(Loop* loop,
size_t factor) {
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> new_label{new Instruction(
context_, SpvOp::SpvOpLabel, 0, context_->TakeNextId(), {})};
std::unique_ptr<BasicBlock> new_exit_bb{new BasicBlock(std::move(new_label))};
@@ -834,6 +835,7 @@ void LoopUnrollerUtilsImpl::AssignNewResultIds(BasicBlock* basic_block) {
// Label instructions aren't covered by normal traversal of the
// instructions.
+ // TODO(1841): Handle id overflow.
uint32_t new_label_id = context_->TakeNextId();
// Assign a new id to the label.
@@ -850,6 +852,7 @@ void LoopUnrollerUtilsImpl::AssignNewResultIds(BasicBlock* basic_block) {
}
// Give the instruction a new id.
+ // TODO(1841): Handle id overflow.
inst.SetResultId(context_->TakeNextId());
def_use_mgr->AnalyzeInstDef(&inst);
diff --git a/source/opt/loop_unswitch_pass.cpp b/source/opt/loop_unswitch_pass.cpp
index 59a0cbcd..7e374d95 100644
--- a/source/opt/loop_unswitch_pass.cpp
+++ b/source/opt/loop_unswitch_pass.cpp
@@ -99,6 +99,7 @@ class LoopUnswitch {
BasicBlock* CreateBasicBlock(Function::iterator ip) {
analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr();
+ // TODO(1841): Handle id overflow.
BasicBlock* bb = &*ip.InsertBefore(std::unique_ptr<BasicBlock>(
new BasicBlock(std::unique_ptr<Instruction>(new Instruction(
context_, SpvOpLabel, 0, context_->TakeNextId(), {})))));
@@ -459,7 +460,10 @@ class LoopUnswitch {
std::vector<BasicBlock*> ordered_loop_blocks_;
// Returns the next usable id for the context.
- uint32_t TakeNextId() { return context_->TakeNextId(); }
+ uint32_t TakeNextId() {
+ // TODO(1841): Handle id overflow.
+ return context_->TakeNextId();
+ }
// Patches |bb|'s phi instruction by removing incoming value from unexisting
// or tagged as dead branches.
@@ -474,28 +478,28 @@ class LoopUnswitch {
return dead_blocks.count(id) ||
std::find(bb_preds.begin(), bb_preds.end(), id) == bb_preds.end();
};
- bb->ForEachPhiInst([&phi_to_kill, &is_branch_dead, preserve_phi,
- this](Instruction* insn) {
- uint32_t i = 0;
- while (i < insn->NumInOperands()) {
- uint32_t incoming_id = insn->GetSingleWordInOperand(i + 1);
- if (is_branch_dead(incoming_id)) {
- // Remove the incoming block id operand.
- insn->RemoveInOperand(i + 1);
- // Remove the definition id operand.
- insn->RemoveInOperand(i);
- continue;
- }
- i += 2;
- }
- // If there is only 1 remaining edge, propagate the value and
- // kill the instruction.
- if (insn->NumInOperands() == 2 && !preserve_phi) {
- phi_to_kill.push_back(insn);
- context_->ReplaceAllUsesWith(insn->result_id(),
- insn->GetSingleWordInOperand(0));
- }
- });
+ bb->ForEachPhiInst(
+ [&phi_to_kill, &is_branch_dead, preserve_phi, this](Instruction* insn) {
+ uint32_t i = 0;
+ while (i < insn->NumInOperands()) {
+ uint32_t incoming_id = insn->GetSingleWordInOperand(i + 1);
+ if (is_branch_dead(incoming_id)) {
+ // Remove the incoming block id operand.
+ insn->RemoveInOperand(i + 1);
+ // Remove the definition id operand.
+ insn->RemoveInOperand(i);
+ continue;
+ }
+ i += 2;
+ }
+ // If there is only 1 remaining edge, propagate the value and
+ // kill the instruction.
+ if (insn->NumInOperands() == 2 && !preserve_phi) {
+ phi_to_kill.push_back(insn);
+ context_->ReplaceAllUsesWith(insn->result_id(),
+ insn->GetSingleWordInOperand(0));
+ }
+ });
for (Instruction* insn : phi_to_kill) {
context_->KillInst(insn);
}
diff --git a/source/opt/loop_utils.cpp b/source/opt/loop_utils.cpp
index 482335f3..8c6d355d 100644
--- a/source/opt/loop_utils.cpp
+++ b/source/opt/loop_utils.cpp
@@ -352,6 +352,7 @@ void LoopUtils::CreateLoopDedicatedExits() {
assert(insert_pt != function->end() && "Basic Block not found");
// Create the dedicate exit basic block.
+ // TODO(1841): Handle id overflow.
BasicBlock& exit = *insert_pt.InsertBefore(std::unique_ptr<BasicBlock>(
new BasicBlock(std::unique_ptr<Instruction>(new Instruction(
context_, SpvOpLabel, 0, context_->TakeNextId(), {})))));
@@ -491,6 +492,7 @@ Loop* LoopUtils::CloneAndAttachLoopToHeader(LoopCloningResult* cloning_result) {
Loop* new_loop = CloneLoop(cloning_result);
// Create a new exit block/label for the new loop.
+ // TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> new_label{new Instruction(
context_, SpvOp::SpvOpLabel, 0, context_->TakeNextId(), {})};
std::unique_ptr<BasicBlock> new_exit_bb{new BasicBlock(std::move(new_label))};
@@ -528,6 +530,7 @@ Loop* LoopUtils::CloneAndAttachLoopToHeader(LoopCloningResult* cloning_result) {
inst->SetOperand(operand, {new_header});
});
+ // TODO(1841): Handle failure to create pre-header.
def_use->ForEachUse(
loop_->GetOrCreatePreHeaderBlock()->id(),
[new_merge_block, this](Instruction* inst, uint32_t operand) {
@@ -560,6 +563,7 @@ Loop* LoopUtils::CloneLoop(
// between old and new ids.
BasicBlock* new_bb = old_bb->Clone(context_);
new_bb->SetParent(&function_);
+ // TODO(1841): Handle id overflow.
new_bb->GetLabelInst()->SetResultId(context_->TakeNextId());
def_use_mgr->AnalyzeInstDef(new_bb->GetLabelInst());
context_->set_instr_block(new_bb->GetLabelInst(), new_bb);
@@ -575,6 +579,7 @@ Loop* LoopUtils::CloneLoop(
new_inst != new_bb->end(); ++new_inst, ++old_inst) {
cloning_result->ptr_map_[&*new_inst] = &*old_inst;
if (new_inst->HasResultId()) {
+ // TODO(1841): Handle id overflow.
new_inst->SetResultId(context_->TakeNextId());
cloning_result->value_map_[old_inst->result_id()] =
new_inst->result_id();
diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp
index 820760cb..a13540c7 100644
--- a/source/opt/merge_return_pass.cpp
+++ b/source/opt/merge_return_pass.cpp
@@ -121,7 +121,9 @@ bool MergeReturnPass::ProcessStructured(
// Predicate successors of the original return blocks as necessary.
if (std::find(return_blocks.begin(), return_blocks.end(), block) !=
return_blocks.end()) {
- PredicateBlocks(block, &predicated, &order);
+ if (!PredicateBlocks(block, &predicated, &order)) {
+ return false;
+ }
}
// Generate state for next block
@@ -204,7 +206,11 @@ void MergeReturnPass::BranchToBlock(BasicBlock* block, uint32_t target) {
RecordReturned(block);
RecordReturnValue(block);
}
+
BasicBlock* target_block = context()->get_instr_block(target);
+ if (target_block->GetLoopMergeInst()) {
+ cfg()->SplitLoopHeader(target_block);
+ }
UpdatePhiNodes(block, target_block);
Instruction* return_inst = block->terminator();
@@ -239,8 +245,22 @@ void MergeReturnPass::CreatePhiNodesForInst(BasicBlock* merge_block,
if (inst.result_id() != 0) {
std::vector<Instruction*> users_to_update;
context()->get_def_use_mgr()->ForEachUser(
- &inst, [&users_to_update, &dom_tree, inst_bb, this](Instruction* user) {
- BasicBlock* user_bb = context()->get_instr_block(user);
+ &inst,
+ [&users_to_update, &dom_tree, &inst, inst_bb, this](Instruction* user) {
+ BasicBlock* user_bb = nullptr;
+ if (user->opcode() != SpvOpPhi) {
+ user_bb = context()->get_instr_block(user);
+ } else {
+ // For OpPhi, the use should be considered to be in the predecessor.
+ for (uint32_t i = 0; i < user->NumInOperands(); i += 2) {
+ if (user->GetSingleWordInOperand(i) == inst.result_id()) {
+ uint32_t user_bb_id = user->GetSingleWordInOperand(i + 1);
+ user_bb = context()->get_instr_block(user_bb_id);
+ break;
+ }
+ }
+ }
+
// If |user_bb| is nullptr, then |user| is not in the function. It is
// something like an OpName or decoration, which should not be
// replaced with the result of the OpPhi.
@@ -288,14 +308,14 @@ void MergeReturnPass::CreatePhiNodesForInst(BasicBlock* merge_block,
}
}
-void MergeReturnPass::PredicateBlocks(
+bool MergeReturnPass::PredicateBlocks(
BasicBlock* return_block, std::unordered_set<BasicBlock*>* predicated,
std::list<BasicBlock*>* order) {
// The CFG is being modified as the function proceeds so avoid caching
// successors.
if (predicated->count(return_block)) {
- return;
+ return true;
}
BasicBlock* block = nullptr;
@@ -328,12 +348,15 @@ void MergeReturnPass::PredicateBlocks(
while (state->LoopMergeId() == next->id()) {
state++;
}
- BreakFromConstruct(block, next, predicated, order);
+ if (!BreakFromConstruct(block, next, predicated, order)) {
+ return false;
+ }
block = next;
}
+ return true;
}
-void MergeReturnPass::BreakFromConstruct(
+bool MergeReturnPass::BreakFromConstruct(
BasicBlock* block, BasicBlock* merge_block,
std::unordered_set<BasicBlock*>* predicated,
std::list<BasicBlock*>* order) {
@@ -353,7 +376,12 @@ void MergeReturnPass::BreakFromConstruct(
// If |block| is a loop header, then the back edge must jump to the original
// code, not the new header.
if (block->GetLoopMergeInst()) {
- cfg()->SplitLoopHeader(block);
+ if (cfg()->SplitLoopHeader(block) == nullptr) {
+ return false;
+ }
+ }
+ if (merge_block->GetLoopMergeInst()) {
+ cfg()->SplitLoopHeader(merge_block);
}
// Leave the phi instructions behind.
@@ -407,6 +435,7 @@ void MergeReturnPass::BreakFromConstruct(
assert(old_body->begin() != old_body->end());
assert(block->begin() != block->end());
+ return true;
}
void MergeReturnPass::RecordReturned(BasicBlock* block) {
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index f27332f1..264e8b77 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -212,7 +212,9 @@ class MergeReturnPass : public MemPass {
//
// If new blocks that are created will be added to |order|. This way a call
// can traverse these new block in structured order.
- void PredicateBlocks(BasicBlock* return_block,
+ //
+ // Returns true if successful.
+ bool PredicateBlocks(BasicBlock* return_block,
std::unordered_set<BasicBlock*>* pSet,
std::list<BasicBlock*>* order);
@@ -222,7 +224,9 @@ class MergeReturnPass : public MemPass {
//
// If new blocks that are created will be added to |order|. This way a call
// can traverse these new block in structured order.
- void BreakFromConstruct(BasicBlock* block, BasicBlock* merge_block,
+ //
+ // Returns true if successful.
+ bool BreakFromConstruct(BasicBlock* block, BasicBlock* merge_block,
std::unordered_set<BasicBlock*>* predicated,
std::list<BasicBlock*>* order);
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index d30528be..856ede7a 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -56,7 +56,7 @@ Optimizer::PassToken::~PassToken() {}
struct Optimizer::Impl {
explicit Impl(spv_target_env env) : target_env(env), pass_manager() {}
- const spv_target_env target_env; // Target environment.
+ spv_target_env target_env; // Target environment.
opt::PassManager pass_manager; // Internal implementation pass manager.
};
@@ -450,6 +450,10 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
return true;
}
+void Optimizer::SetTargetEnv(const spv_target_env env) {
+ impl_->target_env = env;
+}
+
bool Optimizer::Run(const uint32_t* original_binary,
const size_t original_binary_size,
std::vector<uint32_t>* optimized_binary) const {
diff --git a/source/opt/pass.h b/source/opt/pass.h
index aabc6456..c95f5022 100644
--- a/source/opt/pass.h
+++ b/source/opt/pass.h
@@ -122,6 +122,7 @@ class Pass {
virtual Status Process() = 0;
// Return the next available SSA id and increment it.
+ // TODO(1841): Handle id overflow.
uint32_t TakeNextId() { return context_->TakeNextId(); }
private:
@@ -136,6 +137,10 @@ class Pass {
bool already_run_;
};
+inline Pass::Status CombineStatus(Pass::Status a, Pass::Status b) {
+ return std::min(a, b);
+}
+
} // namespace opt
} // namespace spvtools
diff --git a/source/opt/reflect.h b/source/opt/reflect.h
index fb2de7b1..79d90bda 100644
--- a/source/opt/reflect.h
+++ b/source/opt/reflect.h
@@ -44,7 +44,8 @@ inline bool IsAnnotationInst(SpvOp opcode) {
}
inline bool IsTypeInst(SpvOp opcode) {
return (opcode >= SpvOpTypeVoid && opcode <= SpvOpTypeForwardPointer) ||
- opcode == SpvOpTypePipeStorage || opcode == SpvOpTypeNamedBarrier;
+ opcode == SpvOpTypePipeStorage || opcode == SpvOpTypeNamedBarrier ||
+ opcode == SpvOpTypeAccelerationStructureNV;
}
inline bool IsConstantInst(SpvOp opcode) {
return opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp;
diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp
index 83d24331..0a5d390e 100644
--- a/source/opt/ssa_rewrite_pass.cpp
+++ b/source/opt/ssa_rewrite_pass.cpp
@@ -90,6 +90,7 @@ std::string SSARewriter::PhiCandidate::PrettyPrint(const CFG* cfg) const {
SSARewriter::PhiCandidate& SSARewriter::CreatePhiCandidate(uint32_t var_id,
BasicBlock* bb) {
+ // TODO(1841): Handle id overflow.
uint32_t phi_result_id = pass_->context()->TakeNextId();
auto result = phi_candidates_.emplace(
phi_result_id, PhiCandidate(var_id, phi_result_id, bb));
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
index cb19ccac..722b9b9f 100644
--- a/source/opt/type_manager.cpp
+++ b/source/opt/type_manager.cpp
@@ -205,6 +205,7 @@ uint32_t TypeManager::GetTypeInstruction(const Type* type) {
if (id != 0) return id;
std::unique_ptr<Instruction> typeInst;
+ // TODO(1841): Handle id overflow.
id = context()->TakeNextId();
RegisterType(id, *type);
switch (type->kind()) {
@@ -222,6 +223,7 @@ uint32_t TypeManager::GetTypeInstruction(const Type* type) {
DefineParameterlessCase(Queue);
DefineParameterlessCase(PipeStorage);
DefineParameterlessCase(NamedBarrier);
+ DefineParameterlessCase(AccelerationStructureNV);
#undef DefineParameterlessCase
case Type::kInteger:
typeInst = MakeUnique<Instruction>(
@@ -397,6 +399,7 @@ uint32_t TypeManager::FindPointerToType(uint32_t type_id,
}
// Must create the pointer type.
+ // TODO(1841): Handle id overflow.
uint32_t resultId = context()->TakeNextId();
std::unique_ptr<Instruction> type_inst(
new Instruction(context(), SpvOpTypePointer, 0, resultId,
@@ -467,6 +470,7 @@ Type* TypeManager::RebuildType(const Type& type) {
DefineNoSubtypeCase(Pipe);
DefineNoSubtypeCase(PipeStorage);
DefineNoSubtypeCase(NamedBarrier);
+ DefineNoSubtypeCase(AccelerationStructureNV);
#undef DefineNoSubtypeCase
case Type::kVector: {
const Vector* vec_ty = type.AsVector();
diff --git a/source/opt/types.cpp b/source/opt/types.cpp
index 96644c5d..cfafc7dc 100644
--- a/source/opt/types.cpp
+++ b/source/opt/types.cpp
@@ -123,6 +123,7 @@ std::unique_ptr<Type> Type::Clone() const {
DeclareKindCase(ForwardPointer);
DeclareKindCase(PipeStorage);
DeclareKindCase(NamedBarrier);
+ DeclareKindCase(AccelerationStructureNV);
#undef DeclareKindCase
default:
assert(false && "Unhandled type");
@@ -166,6 +167,7 @@ bool Type::operator==(const Type& other) const {
DeclareKindCase(ForwardPointer);
DeclareKindCase(PipeStorage);
DeclareKindCase(NamedBarrier);
+ DeclareKindCase(AccelerationStructureNV);
#undef DeclareKindCase
default:
assert(false && "Unhandled type");
@@ -214,6 +216,7 @@ void Type::GetHashWords(std::vector<uint32_t>* words,
DeclareKindCase(ForwardPointer);
DeclareKindCase(PipeStorage);
DeclareKindCase(NamedBarrier);
+ DeclareKindCase(AccelerationStructureNV);
#undef DeclareKindCase
default:
assert(false && "Unhandled type");
diff --git a/source/opt/types.h b/source/opt/types.h
index 28731c67..d77117d2 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -56,6 +56,7 @@ class Pipe;
class ForwardPointer;
class PipeStorage;
class NamedBarrier;
+class AccelerationStructureNV;
// Abstract class for a SPIR-V type. It has a bunch of As<sublcass>() methods,
// which is used as a way to probe the actual <subclass>.
@@ -90,6 +91,7 @@ class Type {
kForwardPointer,
kPipeStorage,
kNamedBarrier,
+ kAccelerationStructureNV,
};
Type(Kind k) : kind_(k) {}
@@ -170,6 +172,7 @@ class Type {
DeclareCastMethod(ForwardPointer);
DeclareCastMethod(PipeStorage);
DeclareCastMethod(NamedBarrier);
+ DeclareCastMethod(AccelerationStructureNV);
#undef DeclareCastMethod
bool operator==(const Type& other) const;
@@ -595,6 +598,7 @@ DefineParameterlessType(ReserveId, reserve_id);
DefineParameterlessType(Queue, queue);
DefineParameterlessType(PipeStorage, pipe_storage);
DefineParameterlessType(NamedBarrier, named_barrier);
+DefineParameterlessType(AccelerationStructureNV, acceleration_structure);
#undef DefineParameterlessType
} // namespace analysis
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index fc7369fa..57475ac5 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -19,7 +19,10 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
reduction_opportunity.h
reduction_pass.h
remove_instruction_reduction_opportunity.h
+ remove_opname_instruction_reduction_pass.h
remove_unreferenced_instruction_reduction_pass.h
+ structured_loop_to_selection_reduction_opportunity.h
+ structured_loop_to_selection_reduction_pass.h
change_operand_reduction_opportunity.cpp
operand_to_const_reduction_pass.cpp
@@ -29,6 +32,9 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
reduction_pass.cpp
remove_instruction_reduction_opportunity.cpp
remove_unreferenced_instruction_reduction_pass.cpp
+ remove_opname_instruction_reduction_pass.cpp
+ structured_loop_to_selection_reduction_opportunity.cpp
+ structured_loop_to_selection_reduction_pass.cpp
)
if(MSVC)
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index a751b710..4f4429aa 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -81,6 +81,11 @@ Reducer::ReductionResultStatus Reducer::Run(
// Iterate through the available passes
for (auto& pass : impl_->passes) {
+ // If this pass hasn't reached its minimum granularity then it's
+ // worth eventually doing another round of reductions, in order to
+ // try this pass at a finer granularity.
+ another_round_worthwhile |= !pass->ReachedMinimumGranularity();
+
// Keep applying this pass at its current granularity until it stops
// working or we hit the reduction step limit.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
@@ -89,14 +94,10 @@ Reducer::ReductionResultStatus Reducer::Run(
auto maybe_result = pass->TryApplyReduction(current_binary);
if (maybe_result.empty()) {
// This pass did not have any impact, so move on to the next pass.
- // If this pass hasn't reached its minimum granularity then it's
- // worth eventually doing another round of reductions, in order to
- // try this pass at a finer granularity.
impl_->consumer(
SPV_MSG_INFO, nullptr, {},
("Pass " + pass->GetName() + " did not make a reduction step.")
.c_str());
- another_round_worthwhile |= !pass->ReachedMinimumGranularity();
break;
}
std::stringstream stringstream;
@@ -105,7 +106,14 @@ Reducer::ReductionResultStatus Reducer::Run(
<< reductions_applied << ".";
impl_->consumer(SPV_MSG_INFO, nullptr, {},
(stringstream.str().c_str()));
- if (impl_->interestingness_function(maybe_result, reductions_applied)) {
+ if (!spvtools::SpirvTools(impl_->target_env).Validate(maybe_result)) {
+ // The reduction step went wrong and an invalid binary was produced.
+ // By design, this shouldn't happen; this is a safeguard to stop an
+ // invalid binary from being regarded as interesting.
+ impl_->consumer(SPV_MSG_INFO, nullptr, {},
+ "Reduction step produced an invalid binary.");
+ } else if (impl_->interestingness_function(maybe_result,
+ reductions_applied)) {
// Success! The binary produced by this reduction step is
// interesting, so make it the binary of interest henceforth, and
// note that it's worth doing another round of reduction passes.
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.cpp b/source/reduce/remove_opname_instruction_reduction_pass.cpp
new file mode 100644
index 00000000..bf99bc57
--- /dev/null
+++ b/source/reduce/remove_opname_instruction_reduction_pass.cpp
@@ -0,0 +1,44 @@
+// Copyright (c) 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "remove_opname_instruction_reduction_pass.h"
+#include "remove_instruction_reduction_opportunity.h"
+#include "source/opcode.h"
+#include "source/opt/instruction.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveOpNameInstructionReductionPass::GetAvailableOpportunities(
+ opt::IRContext* context) const {
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+ for (auto& inst : context->module()->debugs2()) {
+ if (inst.opcode() == SpvOpName || inst.opcode() == SpvOpMemberName) {
+ result.push_back(
+ MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+ }
+ }
+ return result;
+}
+
+std::string RemoveOpNameInstructionReductionPass::GetName() const {
+ return "RemoveOpNameInstructionReductionPass";
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.h b/source/reduce/remove_opname_instruction_reduction_pass.h
new file mode 100644
index 00000000..d20b6e18
--- /dev/null
+++ b/source/reduce/remove_opname_instruction_reduction_pass.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2018 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_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
+#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
+
+#include "reduction_pass.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A reduction pass for removing OpName instructions. As well as making the
+// module smaller, removing an OpName instruction may create opportunities to
+// remove the instruction that create the id to which the OpName applies.
+class RemoveOpNameInstructionReductionPass : public ReductionPass {
+ public:
+ // Creates the reduction pass in the context of the given target environment
+ // |target_env|
+ explicit RemoveOpNameInstructionReductionPass(const spv_target_env target_env)
+ : ReductionPass(target_env) {}
+
+ ~RemoveOpNameInstructionReductionPass() override = default;
+
+ // The name of this pass.
+ std::string GetName() const final;
+
+ protected:
+ // Finds all opportunities for removing opName instructions in the
+ // given module.
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ private:
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REMOVE_OpName_INSTRUCTION_REDUCTION_PASS_H_
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
new file mode 100644
index 00000000..679cfc1a
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
@@ -0,0 +1,377 @@
+// Copyright (c) 2018 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 "structured_loop_to_selection_reduction_opportunity.h"
+#include "source/opt/aggressive_dead_code_elim_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+namespace {
+const uint32_t kMergeNodeIndex = 0;
+const uint32_t kContinueNodeIndex = 1;
+} // namespace
+
+bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() {
+ // Is the loop header reachable?
+ return loop_construct_header_->GetLabel()
+ ->context()
+ ->GetDominatorAnalysis(enclosing_function_)
+ ->IsReachable(loop_construct_header_);
+}
+
+void StructuredLoopToSelectionReductionOpportunity::Apply() {
+ // Force computation of dominator analysis, CFG and structured CFG analysis
+ // before we start to mess with edges in the function.
+ context_->GetDominatorAnalysis(enclosing_function_);
+ context_->cfg();
+ context_->GetStructuredCFGAnalysis();
+
+ // (1) Redirect edges that point to the loop's continue target to their
+ // closest merge block.
+ RedirectToClosestMergeBlock(
+ loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
+ kContinueNodeIndex));
+
+ // (2) Redirect edges that point to the loop's merge block to their closest
+ // merge block (which might be that of an enclosing selection, for instance).
+ RedirectToClosestMergeBlock(
+ loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
+ kMergeNodeIndex));
+
+ // (3) Turn the loop construct header into a selection.
+ ChangeLoopToSelection();
+
+ // We have made control flow changes that do not preserve the analyses that
+ // were performed.
+ context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+
+ // (4) By changing CFG edges we may have created scenarios where ids are used
+ // without being dominated; we fix instances of this.
+ FixNonDominatedIdUses();
+
+ // Invalidate the analyses we just used.
+ context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+}
+
+void StructuredLoopToSelectionReductionOpportunity::RedirectToClosestMergeBlock(
+ uint32_t original_target_id) {
+ // Consider every predecessor of the node with respect to which edges should
+ // be redirected.
+ std::set<uint32_t> already_seen;
+ for (auto pred : context_->cfg()->preds(original_target_id)) {
+ if (already_seen.find(pred) != already_seen.end()) {
+ // We have already handled this predecessor (this scenario can arise if
+ // there are multiple edges from a block b to original_target_id).
+ continue;
+ }
+ already_seen.insert(pred);
+
+ if (!context_->GetDominatorAnalysis(enclosing_function_)
+ ->IsReachable(pred)) {
+ // We do not care about unreachable predecessors (and dominance
+ // information, and thus the notion of structured control flow, makes
+ // little sense for unreachable blocks).
+ continue;
+ }
+ // Find the merge block of the structured control construct that most
+ // tightly encloses the predecessor.
+ uint32_t new_merge_target;
+ // The structured CFG analysis deliberately does not regard a header as
+ // belonging to the structure that it heads. We want it to, so handle this
+ // case specially.
+ if (context_->cfg()->block(pred)->MergeBlockIdIfAny()) {
+ new_merge_target = context_->cfg()->block(pred)->MergeBlockIdIfAny();
+ } else {
+ new_merge_target = context_->GetStructuredCFGAnalysis()->MergeBlock(pred);
+ }
+ assert(new_merge_target != pred);
+
+ if (!new_merge_target) {
+ // If the loop being transformed is outermost, and the predecessor is
+ // part of that loop's continue construct, there will be no such
+ // enclosing control construct. In this case, the continue construct
+ // will become unreachable anyway, so it is fine not to redirect the
+ // edge.
+ continue;
+ }
+
+ if (new_merge_target != original_target_id) {
+ // Redirect the edge if it doesn't already point to the desired block.
+ RedirectEdge(pred, original_target_id, new_merge_target);
+ }
+ }
+}
+
+void StructuredLoopToSelectionReductionOpportunity::RedirectEdge(
+ uint32_t source_id, uint32_t original_target_id, uint32_t new_target_id) {
+ // Redirect edge source_id->original_target_id to edge
+ // source_id->new_target_id, where the blocks involved are all different.
+ assert(source_id != original_target_id);
+ assert(source_id != new_target_id);
+ assert(original_target_id != new_target_id);
+
+ // original_target_id must either be the merge target or continue construct
+ // for the loop being operated on.
+ assert(original_target_id ==
+ loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
+ kMergeNodeIndex) ||
+ original_target_id ==
+ loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
+ kContinueNodeIndex));
+
+ auto terminator = context_->cfg()->block(source_id)->terminator();
+
+ // Figure out which operands of the terminator need to be considered for
+ // redirection.
+ std::vector<uint32_t> operand_indices;
+ if (terminator->opcode() == SpvOpBranch) {
+ operand_indices = {0};
+ } else if (terminator->opcode() == SpvOpBranchConditional) {
+ operand_indices = {1, 2};
+ } else {
+ assert(terminator->opcode() == SpvOpSwitch);
+ for (uint32_t label_index = 1; label_index < terminator->NumOperands();
+ label_index += 2) {
+ operand_indices.push_back(label_index);
+ }
+ }
+
+ // Redirect the relevant operands, asserting that at least one redirection is
+ // made.
+ bool redirected = false;
+ for (auto operand_index : operand_indices) {
+ if (terminator->GetSingleWordOperand(operand_index) == original_target_id) {
+ terminator->SetOperand(operand_index, {new_target_id});
+ redirected = true;
+ }
+ }
+ (void)(redirected);
+ assert(redirected);
+
+ // The old and new targets may have phi instructions; these will need to
+ // respect the change in edges.
+ AdaptPhiInstructionsForRemovedEdge(
+ source_id, context_->cfg()->block(original_target_id));
+ AdaptPhiInstructionsForAddedEdge(source_id,
+ context_->cfg()->block(new_target_id));
+}
+
+void StructuredLoopToSelectionReductionOpportunity::
+ AdaptPhiInstructionsForRemovedEdge(uint32_t from_id, BasicBlock* to_block) {
+ to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) {
+ Instruction::OperandList new_in_operands;
+ // Go through the OpPhi's input operands in (variable, parent) pairs.
+ for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) {
+ // Keep all pairs where the parent is not the block from which the edge
+ // is being removed.
+ if (phi_inst->GetInOperand(index + 1).words[0] != from_id) {
+ new_in_operands.push_back(phi_inst->GetInOperand(index));
+ new_in_operands.push_back(phi_inst->GetInOperand(index + 1));
+ }
+ }
+ phi_inst->SetInOperands(std::move(new_in_operands));
+ });
+}
+
+void StructuredLoopToSelectionReductionOpportunity::
+ AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block) {
+ to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) {
+ // Add to the phi operand an (undef, from_id) pair to reflect the added
+ // edge.
+ auto undef_id = FindOrCreateGlobalUndef(phi_inst->type_id());
+ phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
+ phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {from_id}));
+ });
+}
+
+void StructuredLoopToSelectionReductionOpportunity::ChangeLoopToSelection() {
+ // Change the merge instruction from OpLoopMerge to OpSelectionMerge, with
+ // the same merge block.
+ auto loop_merge_inst = loop_construct_header_->GetLoopMergeInst();
+ auto const loop_merge_block_id =
+ loop_merge_inst->GetSingleWordOperand(kMergeNodeIndex);
+ loop_merge_inst->SetOpcode(SpvOpSelectionMerge);
+ loop_merge_inst->ReplaceOperands(
+ {{loop_merge_inst->GetOperand(kMergeNodeIndex).type,
+ {loop_merge_block_id}},
+ {SPV_OPERAND_TYPE_SELECTION_CONTROL, {SpvSelectionControlMaskNone}}});
+
+ // The loop header either finishes with OpBranch or OpBranchConditional.
+ // The latter is fine for a selection. In the former case we need to turn
+ // it into OpBranchConditional. We use "true" as the condition, and make
+ // the "else" branch be the merge block.
+ auto terminator = loop_construct_header_->terminator();
+ if (terminator->opcode() == SpvOpBranch) {
+ analysis::Bool temp;
+ const analysis::Bool* bool_type =
+ context_->get_type_mgr()->GetRegisteredType(&temp)->AsBool();
+ auto const_mgr = context_->get_constant_mgr();
+ auto true_const = const_mgr->GetConstant(bool_type, {true});
+ auto true_const_result_id =
+ const_mgr->GetDefiningInstruction(true_const)->result_id();
+ auto original_branch_id = terminator->GetSingleWordOperand(0);
+ terminator->SetOpcode(SpvOpBranchConditional);
+ terminator->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {true_const_result_id}},
+ {SPV_OPERAND_TYPE_ID, {original_branch_id}},
+ {SPV_OPERAND_TYPE_ID, {loop_merge_block_id}}});
+ if (original_branch_id != loop_merge_block_id) {
+ AdaptPhiInstructionsForAddedEdge(
+ loop_construct_header_->id(),
+ context_->cfg()->block(loop_merge_block_id));
+ }
+ }
+}
+
+void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
+ // Consider each instruction in the function.
+ for (auto& block : *enclosing_function_) {
+ for (auto& def : block) {
+ if (def.opcode() == SpvOpVariable) {
+ // Variables are defined at the start of the function, and can be
+ // accessed by all blocks, even by unreachable blocks that have no
+ // dominators, so we do not need to worry about them.
+ continue;
+ }
+ context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
+ Instruction* use,
+ uint32_t index) {
+ // If a use is not appropriately dominated by its definition,
+ // replace the use with an OpUndef, unless the definition is an
+ // access chain, in which case replace it with some (possibly fresh)
+ // variable (as we cannot load from / store to OpUndef).
+ if (!DefinitionSufficientlyDominatesUse(&def, use, index, block)) {
+ if (def.opcode() == SpvOpAccessChain) {
+ auto pointer_type =
+ context_->get_type_mgr()->GetType(def.type_id())->AsPointer();
+ switch (pointer_type->storage_class()) {
+ case SpvStorageClassFunction:
+ use->SetOperand(
+ index, {FindOrCreateFunctionVariable(
+ context_->get_type_mgr()->GetId(pointer_type))});
+ break;
+ default:
+ // TODO(2183) Need to think carefully about whether it makes
+ // sense to add new variables for all storage classes; it's fine
+ // for Private but might not be OK for input/output storage
+ // classes for example.
+ use->SetOperand(
+ index, {FindOrCreateGlobalVariable(
+ context_->get_type_mgr()->GetId(pointer_type))});
+ break;
+ }
+ } else {
+ use->SetOperand(index, {FindOrCreateGlobalUndef(def.type_id())});
+ }
+ }
+ });
+ }
+ }
+}
+
+bool StructuredLoopToSelectionReductionOpportunity::
+ DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
+ uint32_t use_index,
+ BasicBlock& def_block) {
+ if (use->opcode() == SpvOpPhi) {
+ // A use in a phi doesn't need to be dominated by its definition, but the
+ // associated parent block does need to be dominated by the definition.
+ return context_->GetDominatorAnalysis(enclosing_function_)
+ ->Dominates(def_block.id(), use->GetSingleWordOperand(use_index + 1));
+ }
+ // In non-phi cases, a use needs to be dominated by its definition.
+ return context_->GetDominatorAnalysis(enclosing_function_)
+ ->Dominates(def, use);
+}
+
+uint32_t StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalUndef(
+ uint32_t type_id) {
+ for (auto& inst : context_->module()->types_values()) {
+ if (inst.opcode() != SpvOpUndef) {
+ continue;
+ }
+ if (inst.type_id() == type_id) {
+ return inst.result_id();
+ }
+ }
+ // TODO(2182): this is adapted from MemPass::Type2Undef. In due course it
+ // would be good to factor out this duplication.
+ const uint32_t undef_id = context_->TakeNextId();
+ std::unique_ptr<Instruction> undef_inst(
+ new Instruction(context_, SpvOpUndef, type_id, undef_id, {}));
+ assert(undef_id == undef_inst->result_id());
+ context_->module()->AddGlobalValue(std::move(undef_inst));
+ return undef_id;
+}
+
+uint32_t
+StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable(
+ uint32_t pointer_type_id) {
+ for (auto& inst : context_->module()->types_values()) {
+ if (inst.opcode() != SpvOpVariable) {
+ continue;
+ }
+ if (inst.type_id() == pointer_type_id) {
+ return inst.result_id();
+ }
+ }
+ const uint32_t variable_id = context_->TakeNextId();
+ std::unique_ptr<Instruction> variable_inst(
+ new Instruction(context_, SpvOpVariable, pointer_type_id, variable_id,
+ {{SPV_OPERAND_TYPE_STORAGE_CLASS,
+ {(uint32_t)context_->get_type_mgr()
+ ->GetType(pointer_type_id)
+ ->AsPointer()
+ ->storage_class()}}}));
+ context_->module()->AddGlobalValue(std::move(variable_inst));
+ return variable_id;
+}
+
+uint32_t
+StructuredLoopToSelectionReductionOpportunity::FindOrCreateFunctionVariable(
+ uint32_t pointer_type_id) {
+ // The pointer type of a function variable must have Function storage class.
+ assert(context_->get_type_mgr()
+ ->GetType(pointer_type_id)
+ ->AsPointer()
+ ->storage_class() == SpvStorageClassFunction);
+
+ // Go through the instructions in the function's first block until we find a
+ // suitable variable, or go past all the variables.
+ BasicBlock::iterator iter = enclosing_function_->begin()->begin();
+ for (;; ++iter) {
+ // We will either find a suitable variable, or find a non-variable
+ // instruction; we won't exhaust all instructions.
+ assert(iter != enclosing_function_->begin()->end());
+ if (iter->opcode() != SpvOpVariable) {
+ // If we see a non-variable, we have gone through all the variables.
+ break;
+ }
+ if (iter->type_id() == pointer_type_id) {
+ return iter->result_id();
+ }
+ }
+ // At this point, iter refers to the first non-function instruction of the
+ // function's entry block.
+ const uint32_t variable_id = context_->TakeNextId();
+ std::unique_ptr<Instruction> variable_inst(new Instruction(
+ context_, SpvOpVariable, pointer_type_id, variable_id,
+ {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
+ iter->InsertBefore(std::move(variable_inst));
+ return variable_id;
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
new file mode 100644
index 00000000..b1390168
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2018 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_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
+
+#include <source/opt/def_use_manager.h>
+#include "reduction_opportunity.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/function.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+// Captures an opportunity to replace a structured loop with a selection.
+class StructuredLoopToSelectionReductionOpportunity
+ : public ReductionOpportunity {
+ public:
+ // Constructs an opportunity from a loop header block and the function that
+ // encloses it.
+ explicit StructuredLoopToSelectionReductionOpportunity(
+ IRContext* context, BasicBlock* loop_construct_header,
+ Function* enclosing_function)
+ : context_(context),
+ loop_construct_header_(loop_construct_header),
+ enclosing_function_(enclosing_function) {}
+
+ // We require the loop header to be reachable. A structured loop might
+ // become unreachable as a result of turning another structured loop into
+ // a selection.
+ bool PreconditionHolds() override;
+
+ protected:
+ // Perform the structured loop to selection transformation.
+ void Apply() override;
+
+ private:
+ // Parameter |original_target_id| is the id of the loop's merge block or
+ // continue target. This method considers each edge of the form
+ // b->original_target_id and transforms it into an edge of the form b->c,
+ // where c is the merge block of the structured control flow construct that
+ // most tightly contains b.
+ void RedirectToClosestMergeBlock(uint32_t original_target_id);
+
+ // |source_id|, |original_target_id| and |new_target_id| are required to all
+ // be distinct, with a CFG edge existing from |source_id| to
+ // |original_target_id|, and |original_target_id| being either the merge block
+ // or continue target for the loop being operated on.
+ // The method removes this edge and adds an edge from
+ // |source_id| to |new_target_id|. It takes care of fixing up any OpPhi
+ // instructions associated with |original_target_id| and |new_target_id|.
+ void RedirectEdge(uint32_t source_id, uint32_t original_target_id,
+ uint32_t new_target_id);
+
+ // Removes any components of |to_block|'s phi instructions relating to
+ // |from_id|.
+ void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
+ BasicBlock* to_block);
+
+ // Adds components to |to_block|'s phi instructions to account for a new
+ // incoming edge from |from_id|.
+ void AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block);
+
+ // Turns the OpLoopMerge for the loop into OpSelectionMerge, and adapts the
+ // following branch instruction accordingly.
+ void ChangeLoopToSelection();
+
+ // Fixes any scenarios where, due to CFG changes, ids have uses not dominated
+ // by their definitions, by changing such uses to uses of OpUndef or of dummy
+ // variables.
+ void FixNonDominatedIdUses();
+
+ // Returns true if and only if at least one of the following holds:
+ // 1) |def| dominates |use|
+ // 2) |def| is an OpVariable
+ // 3) |use| is part of an OpPhi, with associated incoming block b, and |def|
+ // dominates b.
+ bool DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
+ uint32_t use_index,
+ BasicBlock& def_block);
+
+ // Checks whether the global value list has an OpUndef of the given type,
+ // adding one if not, and returns the id of such an OpUndef.
+ //
+ // TODO(2184): This will likely be used by other reduction passes, so should
+ // be factored out in due course. Parts of the spirv-opt framework provide
+ // similar functionality, so there may be a case for further refactoring.
+ uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
+
+ // Checks whether the global value list has an OpVariable of the given pointer
+ // type, adding one if not, and returns the id of such an OpVariable.
+ //
+ // TODO(2184): This will likely be used by other reduction passes, so should
+ // be factored out in due course.
+ uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id);
+
+ // Checks whether the enclosing function has an OpVariable of the given
+ // pointer type, adding one if not, and returns the id of such an OpVariable.
+ //
+ // TODO(2184): This will likely be used by other reduction passes, so should
+ // be factored out in due course.
+ uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id);
+
+ IRContext* context_;
+ BasicBlock* loop_construct_header_;
+ Function* enclosing_function_;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.cpp b/source/reduce/structured_loop_to_selection_reduction_pass.cpp
new file mode 100644
index 00000000..768a2e8e
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_pass.cpp
@@ -0,0 +1,95 @@
+// Copyright (c) 2018 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 "structured_loop_to_selection_reduction_pass.h"
+#include "structured_loop_to_selection_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+namespace {
+const uint32_t kMergeNodeIndex = 0;
+const uint32_t kContinueNodeIndex = 1;
+} // namespace
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+StructuredLoopToSelectionReductionPass::GetAvailableOpportunities(
+ opt::IRContext* context) const {
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+ std::set<uint32_t> merge_block_ids;
+ for (auto& function : *context->module()) {
+ for (auto& block : function) {
+ auto merge_inst = block.GetMergeInst();
+ if (merge_inst) {
+ merge_block_ids.insert(
+ merge_inst->GetSingleWordOperand(kMergeNodeIndex));
+ }
+ }
+ }
+
+ // Consider each loop construct header in the module.
+ for (auto& function : *context->module()) {
+ for (auto& block : function) {
+ auto loop_merge_inst = block.GetLoopMergeInst();
+ if (!loop_merge_inst) {
+ // This is not a loop construct header.
+ continue;
+ }
+
+ // Check whether the loop construct's continue target is the merge block
+ // of some structured control flow construct. If it is, we cautiously do
+ // not consider applying a transformation.
+ if (merge_block_ids.find(loop_merge_inst->GetSingleWordOperand(
+ kContinueNodeIndex)) != merge_block_ids.end()) {
+ continue;
+ }
+
+ // Check whether the loop construct header dominates its merge block.
+ // If not, the merge block must be unreachable in the control flow graph
+ // so we cautiously do not consider applying a transformation.
+ auto merge_block_id =
+ loop_merge_inst->GetSingleWordInOperand(kMergeNodeIndex);
+ if (!context->GetDominatorAnalysis(&function)->Dominates(
+ block.id(), merge_block_id)) {
+ continue;
+ }
+
+ // Check whether the loop construct merge block postdominates the loop
+ // construct header. If not (e.g. because the loop contains OpReturn,
+ // OpKill or OpUnreachable), we cautiously do not consider applying
+ // a transformation.
+ if (!context->GetPostDominatorAnalysis(&function)->Dominates(
+ merge_block_id, block.id())) {
+ continue;
+ }
+
+ // We can turn this structured loop into a selection, so add the
+ // opportunity to do so.
+ result.push_back(
+ MakeUnique<StructuredLoopToSelectionReductionOpportunity>(
+ context, &block, &function));
+ }
+ }
+ return result;
+}
+
+std::string StructuredLoopToSelectionReductionPass::GetName() const {
+ return "StructuredLoopToSelectionReductionPass";
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.h b/source/reduce/structured_loop_to_selection_reduction_pass.h
new file mode 100644
index 00000000..9c4d4cae
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_pass.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2018 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_CUT_LOOP_REDUCTION_PASS_H_
+#define SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
+
+#include "reduction_pass.h"
+
+namespace spvtools {
+namespace reduce {
+
+// Turns structured loops into selections, generalizing from a human-writable
+// language the idea of turning a loop:
+//
+// while (c) {
+// body;
+// }
+//
+// into:
+//
+// if (c) {
+// body;
+// }
+//
+// The pass results in continue constructs of transformed loops becoming
+// unreachable; another pass for eliminating blocks may end up being able to
+// remove them.
+class StructuredLoopToSelectionReductionPass : public ReductionPass {
+ public:
+ // Creates the reduction pass in the context of the given target environment
+ // |target_env|
+ explicit StructuredLoopToSelectionReductionPass(
+ const spv_target_env target_env)
+ : ReductionPass(target_env) {}
+
+ ~StructuredLoopToSelectionReductionPass() override = default;
+
+ // The name of this pass.
+ std::string GetName() const final;
+
+ protected:
+ // Finds all opportunities for transforming a structured loop to a selection
+ // in the given module.
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ private:
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
diff --git a/source/text_handler.cpp b/source/text_handler.cpp
index 5f6e8c41..c31f34a6 100644
--- a/source/text_handler.cpp
+++ b/source/text_handler.cpp
@@ -313,7 +313,7 @@ spv_result_t AssemblyContext::binaryEncodeString(const char* value,
pInst->words.back() = 0;
char* dest = (char*)&pInst->words[oldWordCount];
- strncpy(dest, value, length);
+ strncpy(dest, value, length + 1);
return SPV_SUCCESS;
}
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index a88ea921..5d0c6243 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -116,18 +116,18 @@ void printDot(const ValidationState_t& _, const BasicBlock& other) {
block_string += "end ";
} else {
for (auto block : *other.successors()) {
- block_string += _.getIdOrName(block->id()) + " ";
+ block_string += _.getIdName(block->id()) + " ";
}
}
- printf("%10s -> {%s\b}\n", _.getIdOrName(other.id()).c_str(),
+ 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", _.getIdOrName(func.id()).c_str(),
- _.getIdOrName(func.first_block()->id()).c_str());
+ 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);
}
@@ -145,7 +145,7 @@ void PrintBlocks(ValidationState_t& _, Function func) {
UNUSED(void PrintDotGraph(ValidationState_t& _, Function func)) {
if (func.first_block()) {
- std::string func_name(_.getIdOrName(func.id()));
+ std::string func_name(_.getIdName(func.id()));
printf("digraph %s {\n", func_name.c_str());
PrintBlocks(_, func);
printf("}\n");
@@ -175,14 +175,19 @@ spv_result_t ValidateForwardDecls(ValidationState_t& _) {
// capability is being used.
// * No function can be targeted by both an OpEntryPoint instruction and an
// OpFunctionCall instruction.
+//
+// Additionally enforces that entry points for Vulkan and WebGPU should not have
+// recursion.
spv_result_t ValidateEntryPoints(ValidationState_t& _) {
_.ComputeFunctionToEntryPointMapping();
+ _.ComputeRecursiveEntryPoints();
if (_.entry_points().empty() && !_.HasCapability(SpvCapabilityLinkage)) {
return _.diag(SPV_ERROR_INVALID_BINARY, nullptr)
<< "No OpEntryPoint instruction was found. This is only allowed if "
"the Linkage capability is being used.";
}
+
for (const auto& entry_point : _.entry_points()) {
if (_.IsFunctionCallTarget(entry_point)) {
return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(entry_point))
@@ -190,6 +195,17 @@ spv_result_t ValidateEntryPoints(ValidationState_t& _) {
<< ") may not be targeted by both an OpEntryPoint instruction and "
"an OpFunctionCall instruction.";
}
+
+ // For Vulkan and WebGPU, the static function-call graph for an entry point
+ // must not contain cycles.
+ if (spvIsWebGPUEnv(_.context()->target_env) ||
+ spvIsVulkanEnv(_.context()->target_env)) {
+ if (_.recursive_entry_points().find(entry_point) !=
+ _.recursive_entry_points().end()) {
+ return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(entry_point))
+ << "Entry points may not have a call graph with cycles.";
+ }
+ }
}
return SPV_SUCCESS;
@@ -279,7 +295,15 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
<< "A FunctionCall must happen within a function body.";
}
- vstate->AddFunctionCallTarget(inst->GetOperandAs<uint32_t>(2));
+ const auto called_id = inst->GetOperandAs<uint32_t>(2);
+ if (spvIsWebGPUEnv(context.target_env) &&
+ !vstate->IsFunctionCallDefined(called_id)) {
+ return vstate->diag(SPV_ERROR_INVALID_LAYOUT, &instruction)
+ << "For WebGPU, functions need to be defined before being "
+ "called.";
+ }
+
+ vstate->AddFunctionCallTarget(called_id);
}
if (vstate->in_function_body()) {
diff --git a/source/val/validate.h b/source/val/validate.h
index 6418839c..fe357a2f 100644
--- a/source/val/validate.h
+++ b/source/val/validate.h
@@ -131,7 +131,8 @@ spv_result_t DataRulesPass(ValidationState_t& _, const Instruction* inst);
/// Performs instruction validation.
spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst);
-/// Performs decoration validation.
+/// Performs decoration validation. Assumes each decoration on a group
+/// has been propagated down to the group members.
spv_result_t ValidateDecorations(ValidationState_t& _);
/// Performs validation of built-in variables.
diff --git a/source/val/validate_atomics.cpp b/source/val/validate_atomics.cpp
index ecc74d61..6bfd06d4 100644
--- a/source/val/validate_atomics.cpp
+++ b/source/val/validate_atomics.cpp
@@ -21,175 +21,13 @@
#include "source/spirv_target_env.h"
#include "source/util/bitutils.h"
#include "source/val/instruction.h"
+#include "source/val/validate_memory_semantics.h"
#include "source/val/validate_scopes.h"
#include "source/val/validation_state.h"
namespace spvtools {
namespace val {
-// Validates a Memory Semantics operand.
-spv_result_t ValidateMemorySemantics(ValidationState_t& _,
- const Instruction* inst,
- uint32_t operand_index) {
- const SpvOp opcode = inst->opcode();
- bool is_int32 = false, is_const_int32 = false;
- uint32_t flags = 0;
- auto memory_semantics_id = inst->GetOperandAs<const uint32_t>(operand_index);
- std::tie(is_int32, is_const_int32, flags) =
- _.EvalInt32IfConst(memory_semantics_id);
-
- if (!is_int32) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": expected Memory Semantics to be 32-bit int";
- }
-
- if (!is_const_int32) {
- if (_.HasCapability(SpvCapabilityShader)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << "Memory Semantics ids must be OpConstant when Shader "
- "capability is present";
- }
- return SPV_SUCCESS;
- }
-
- if (_.memory_model() == SpvMemoryModelVulkanKHR &&
- flags & SpvMemorySemanticsSequentiallyConsistentMask) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << "SequentiallyConsistent memory "
- "semantics cannot be used with "
- "the VulkanKHR memory model.";
- }
-
- if (spvtools::utils::CountSetBits(
- flags &
- (SpvMemorySemanticsAcquireMask | SpvMemorySemanticsReleaseMask |
- SpvMemorySemanticsAcquireReleaseMask |
- SpvMemorySemanticsSequentiallyConsistentMask)) > 1) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": no more than one of the following Memory Semantics bits can "
- "be set at the same time: Acquire, Release, AcquireRelease or "
- "SequentiallyConsistent";
- }
-
- if (flags & SpvMemorySemanticsUniformMemoryMask &&
- !_.HasCapability(SpvCapabilityShader)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Memory Semantics UniformMemory requires capability Shader";
- }
-
- if (flags & SpvMemorySemanticsAtomicCounterMemoryMask &&
- !_.HasCapability(SpvCapabilityAtomicStorage)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Memory Semantics UniformMemory requires capability "
- "AtomicStorage";
- }
-
- if (flags & SpvMemorySemanticsOutputMemoryKHRMask &&
- !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Memory Semantics OutputMemoryKHR requires capability "
- << "VulkanMemoryModelKHR";
- }
-
- if (flags & SpvMemorySemanticsMakeAvailableKHRMask &&
- !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Memory Semantics MakeAvailableKHR requires capability "
- << "VulkanMemoryModelKHR";
- }
-
- if (flags & SpvMemorySemanticsMakeVisibleKHRMask &&
- !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Memory Semantics MakeVisibleKHR requires capability "
- << "VulkanMemoryModelKHR";
- }
-
- if (opcode == SpvOpAtomicFlagClear &&
- (flags & SpvMemorySemanticsAcquireMask ||
- flags & SpvMemorySemanticsAcquireReleaseMask)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << "Memory Semantics Acquire and AcquireRelease cannot be used with "
- << spvOpcodeString(opcode);
- }
-
- if (opcode == SpvOpAtomicCompareExchange && operand_index == 5 &&
- (flags & SpvMemorySemanticsReleaseMask ||
- flags & SpvMemorySemanticsAcquireReleaseMask)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Memory Semantics Release and AcquireRelease cannot be used "
- "for operand Unequal";
- }
-
- if (spvIsVulkanEnv(_.context()->target_env)) {
- if (opcode == SpvOpAtomicLoad &&
- (flags & SpvMemorySemanticsReleaseMask ||
- flags & SpvMemorySemanticsAcquireReleaseMask ||
- flags & SpvMemorySemanticsSequentiallyConsistentMask)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << "Vulkan spec disallows OpAtomicLoad with Memory Semantics "
- "Release, AcquireRelease and SequentiallyConsistent";
- }
-
- if (opcode == SpvOpAtomicStore &&
- (flags & SpvMemorySemanticsAcquireMask ||
- flags & SpvMemorySemanticsAcquireReleaseMask ||
- flags & SpvMemorySemanticsSequentiallyConsistentMask)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << "Vulkan spec disallows OpAtomicStore with Memory Semantics "
- "Acquire, AcquireRelease and SequentiallyConsistent";
- }
- }
-
- if (flags & SpvMemorySemanticsMakeAvailableKHRMask &&
- !(flags & (SpvMemorySemanticsReleaseMask |
- SpvMemorySemanticsAcquireReleaseMask))) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": MakeAvailableKHR Memory Semantics also requires either "
- "Release or AcquireRelease Memory Semantics";
- }
-
- if (flags & SpvMemorySemanticsMakeVisibleKHRMask &&
- !(flags & (SpvMemorySemanticsAcquireMask |
- SpvMemorySemanticsAcquireReleaseMask))) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": MakeVisibleKHR Memory Semantics also requires either Acquire "
- "or AcquireRelease Memory Semantics";
- }
-
- if (flags & (SpvMemorySemanticsMakeAvailableKHRMask |
- SpvMemorySemanticsMakeVisibleKHRMask)) {
- const bool includes_storage_class =
- flags & (SpvMemorySemanticsUniformMemoryMask |
- SpvMemorySemanticsSubgroupMemoryMask |
- SpvMemorySemanticsWorkgroupMemoryMask |
- SpvMemorySemanticsCrossWorkgroupMemoryMask |
- SpvMemorySemanticsAtomicCounterMemoryMask |
- SpvMemorySemanticsImageMemoryMask |
- SpvMemorySemanticsOutputMemoryKHRMask);
-
- if (!includes_storage_class) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": expected Memory Semantics to include a storage class";
- }
- }
-
- // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
-
- return SPV_SUCCESS;
-}
-
// Validates correctness of atomic instructions.
spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst) {
const SpvOp opcode = inst->opcode();
diff --git a/source/val/validate_barriers.cpp b/source/val/validate_barriers.cpp
index 7948325d..4fbe9c90 100644
--- a/source/val/validate_barriers.cpp
+++ b/source/val/validate_barriers.cpp
@@ -24,155 +24,12 @@
#include "source/spirv_target_env.h"
#include "source/util/bitutils.h"
#include "source/val/instruction.h"
+#include "source/val/validate_memory_semantics.h"
#include "source/val/validate_scopes.h"
#include "source/val/validation_state.h"
namespace spvtools {
namespace val {
-namespace {
-
-// Validates Memory Semantics operand.
-spv_result_t ValidateMemorySemantics(ValidationState_t& _,
- const Instruction* inst, uint32_t id) {
- const SpvOp opcode = inst->opcode();
- bool is_int32 = false, is_const_int32 = false;
- uint32_t value = 0;
- std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(id);
-
- if (!is_int32) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": expected Memory Semantics to be a 32-bit int";
- }
-
- if (!is_const_int32) {
- if (_.HasCapability(SpvCapabilityShader)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << "Memory Semantics ids must be OpConstant when Shader "
- "capability is present";
- }
- return SPV_SUCCESS;
- }
-
- if (_.memory_model() == SpvMemoryModelVulkanKHR &&
- value & SpvMemorySemanticsSequentiallyConsistentMask) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << "SequentiallyConsistent memory "
- "semantics cannot be used with "
- "the VulkanKHR memory model.";
- }
-
- if (value & SpvMemorySemanticsOutputMemoryKHRMask &&
- !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Memory Semantics OutputMemoryKHR requires capability "
- << "VulkanMemoryModelKHR";
- }
-
- if (value & SpvMemorySemanticsMakeAvailableKHRMask &&
- !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Memory Semantics MakeAvailableKHR requires capability "
- << "VulkanMemoryModelKHR";
- }
-
- if (value & SpvMemorySemanticsMakeVisibleKHRMask &&
- !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Memory Semantics MakeVisibleKHR requires capability "
- << "VulkanMemoryModelKHR";
- }
-
- const size_t num_memory_order_set_bits = spvtools::utils::CountSetBits(
- value & (SpvMemorySemanticsAcquireMask | SpvMemorySemanticsReleaseMask |
- SpvMemorySemanticsAcquireReleaseMask |
- SpvMemorySemanticsSequentiallyConsistentMask));
-
- if (num_memory_order_set_bits > 1) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Memory Semantics can have at most one of the following bits "
- "set: Acquire, Release, AcquireRelease or SequentiallyConsistent";
- }
-
- if (value & SpvMemorySemanticsMakeAvailableKHRMask &&
- !(value & (SpvMemorySemanticsReleaseMask |
- SpvMemorySemanticsAcquireReleaseMask))) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": MakeAvailableKHR Memory Semantics also requires either "
- "Release or AcquireRelease Memory Semantics";
- }
-
- if (value & SpvMemorySemanticsMakeVisibleKHRMask &&
- !(value & (SpvMemorySemanticsAcquireMask |
- SpvMemorySemanticsAcquireReleaseMask))) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": MakeVisibleKHR Memory Semantics also requires either Acquire "
- "or AcquireRelease Memory Semantics";
- }
-
- if (spvIsVulkanEnv(_.context()->target_env)) {
- const bool includes_storage_class =
- value & (SpvMemorySemanticsUniformMemoryMask |
- SpvMemorySemanticsWorkgroupMemoryMask |
- SpvMemorySemanticsImageMemoryMask |
- SpvMemorySemanticsOutputMemoryKHRMask);
-
- if (opcode == SpvOpMemoryBarrier && !num_memory_order_set_bits) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": Vulkan specification requires Memory Semantics to have one "
- "of the following bits set: Acquire, Release, AcquireRelease "
- "or SequentiallyConsistent";
- }
-
- if (opcode == SpvOpMemoryBarrier && !includes_storage_class) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": expected Memory Semantics to include a Vulkan-supported "
- "storage class";
- }
-
-#if 0
- // TODO(atgoo@github.com): this check fails Vulkan CTS, reenable once fixed.
- if (opcode == SpvOpControlBarrier && value && !includes_storage_class) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": expected Memory Semantics to include a Vulkan-supported "
- "storage class if Memory Semantics is not None";
- }
-#endif
- }
-
- if (value & (SpvMemorySemanticsMakeAvailableKHRMask |
- SpvMemorySemanticsMakeVisibleKHRMask)) {
- const bool includes_storage_class =
- value & (SpvMemorySemanticsUniformMemoryMask |
- SpvMemorySemanticsSubgroupMemoryMask |
- SpvMemorySemanticsWorkgroupMemoryMask |
- SpvMemorySemanticsCrossWorkgroupMemoryMask |
- SpvMemorySemanticsAtomicCounterMemoryMask |
- SpvMemorySemanticsImageMemoryMask |
- SpvMemorySemanticsOutputMemoryKHRMask);
-
- if (!includes_storage_class) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << spvOpcodeString(opcode)
- << ": expected Memory Semantics to include a storage class";
- }
- }
-
- // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
-
- return SPV_SUCCESS;
-}
-
-} // namespace
// Validates correctness of barrier instructions.
spv_result_t BarriersPass(ValidationState_t& _, const Instruction* inst) {
@@ -205,7 +62,6 @@ spv_result_t BarriersPass(ValidationState_t& _, const Instruction* inst) {
const uint32_t execution_scope = inst->word(1);
const uint32_t memory_scope = inst->word(2);
- const uint32_t memory_semantics = inst->word(3);
if (auto error = ValidateExecutionScope(_, inst, execution_scope)) {
return error;
@@ -215,7 +71,7 @@ spv_result_t BarriersPass(ValidationState_t& _, const Instruction* inst) {
return error;
}
- if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) {
+ if (auto error = ValidateMemorySemantics(_, inst, 2)) {
return error;
}
break;
@@ -223,13 +79,12 @@ spv_result_t BarriersPass(ValidationState_t& _, const Instruction* inst) {
case SpvOpMemoryBarrier: {
const uint32_t memory_scope = inst->word(1);
- const uint32_t memory_semantics = inst->word(2);
if (auto error = ValidateMemoryScope(_, inst, memory_scope)) {
return error;
}
- if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) {
+ if (auto error = ValidateMemorySemantics(_, inst, 1)) {
return error;
}
break;
@@ -261,13 +116,12 @@ spv_result_t BarriersPass(ValidationState_t& _, const Instruction* inst) {
}
const uint32_t memory_scope = inst->word(2);
- const uint32_t memory_semantics = inst->word(3);
if (auto error = ValidateMemoryScope(_, inst, memory_scope)) {
return error;
}
- if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) {
+ if (auto error = ValidateMemorySemantics(_, inst, 2)) {
return error;
}
break;
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 96dd33d1..582d11cc 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -17,7 +17,9 @@
#include <algorithm>
#include <cassert>
#include <string>
+#include <tuple>
#include <unordered_map>
+#include <unordered_set>
#include <utility>
#include <vector>
@@ -44,6 +46,13 @@ struct PairHash {
}
};
+// A functor for hashing decoration types.
+struct SpvDecorationHash {
+ std::size_t operator()(SpvDecoration dec) const {
+ return static_cast<std::size_t>(dec);
+ }
+};
+
// Struct member layout attributes that are inherited through arrays.
struct LayoutConstraints {
explicit LayoutConstraints(
@@ -520,15 +529,18 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
return SPV_SUCCESS;
}
-// Returns true if structure id has given decoration. Handles also nested
-// structures.
-bool hasDecoration(uint32_t struct_id, SpvDecoration decoration,
+// Returns true if variable or structure id has given decoration. Handles also
+// nested structures.
+bool hasDecoration(uint32_t id, SpvDecoration decoration,
ValidationState_t& vstate) {
- for (auto& dec : vstate.id_decorations(struct_id)) {
+ for (auto& dec : vstate.id_decorations(id)) {
if (decoration == dec.dec_type()) return true;
}
- for (auto id : getStructMembers(struct_id, SpvOpTypeStruct, vstate)) {
- if (hasDecoration(id, decoration, vstate)) {
+ if (SpvOpTypeStruct != vstate.FindDef(id)->opcode()) {
+ return false;
+ }
+ for (auto member_id : getStructMembers(id, SpvOpTypeStruct, vstate)) {
+ if (hasDecoration(member_id, decoration, vstate)) {
return true;
}
}
@@ -778,15 +790,63 @@ void ComputeMemberConstraintsForArray(MemberConstraints* constraints,
}
spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
+ // Set of entry points that are known to use a push constant.
+ std::unordered_set<uint32_t> uses_push_constant;
for (const auto& inst : vstate.ordered_instructions()) {
const auto& words = inst.words();
if (SpvOpVariable == inst.opcode()) {
+ const auto var_id = inst.id();
// For storage class / decoration combinations, see Vulkan 14.5.4 "Offset
// and Stride Assignment".
const auto storageClass = words[3];
const bool uniform = storageClass == SpvStorageClassUniform;
+ const bool uniform_constant =
+ storageClass == SpvStorageClassUniformConstant;
const bool push_constant = storageClass == SpvStorageClassPushConstant;
const bool storage_buffer = storageClass == SpvStorageClassStorageBuffer;
+
+ if (spvIsVulkanEnv(vstate.context()->target_env)) {
+ // Vulkan 14.5.1: There must be no more than one PushConstant block
+ // per entry point.
+ if (push_constant) {
+ auto entry_points = vstate.EntryPointReferences(var_id);
+ for (auto ep_id : entry_points) {
+ const bool already_used = !uses_push_constant.insert(ep_id).second;
+ if (already_used) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+ << "Entry point id '" << ep_id
+ << "' uses more than one PushConstant interface.\n"
+ << "From Vulkan spec, section 14.5.1:\n"
+ << "There must be no more than one push constant block "
+ << "statically used per shader entry point.";
+ }
+ }
+ }
+ // Vulkan 14.5.2: Check DescriptorSet and Binding decoration for
+ // UniformConstant which cannot be a struct.
+ if (uniform_constant) {
+ auto entry_points = vstate.EntryPointReferences(var_id);
+ if (!entry_points.empty() &&
+ !hasDecoration(var_id, SpvDecorationDescriptorSet, vstate)) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+ << "UniformConstant id '" << var_id
+ << "' is missing DescriptorSet decoration.\n"
+ << "From Vulkan spec, section 14.5.2:\n"
+ << "These variables must have DescriptorSet and Binding "
+ "decorations specified";
+ }
+ if (!entry_points.empty() &&
+ !hasDecoration(var_id, SpvDecorationBinding, vstate)) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+ << "UniformConstant id '" << var_id
+ << "' is missing Binding decoration.\n"
+ << "From Vulkan spec, section 14.5.2:\n"
+ << "These variables must have DescriptorSet and Binding "
+ "decorations specified";
+ }
+ }
+ }
+
if (uniform || push_constant || storage_buffer) {
const auto ptrInst = vstate.FindDef(words[1]);
assert(SpvOpTypePointer == ptrInst->opcode());
@@ -795,9 +855,13 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
MemberConstraints constraints;
ComputeMemberConstraintsForStruct(&constraints, id, LayoutConstraints(),
vstate);
+ // Prepare for messages
+ const char* sc_str =
+ uniform ? "Uniform"
+ : (push_constant ? "PushConstant" : "StorageBuffer");
- // Vulkan 14.5.1: Check Block annotation for PushConstant variables.
if (spvIsVulkanEnv(vstate.context()->target_env)) {
+ // Vulkan 14.5.1: Check Block decoration for PushConstant variables.
if (push_constant && !hasDecoration(id, SpvDecorationBlock, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "PushConstant id '" << id
@@ -806,12 +870,31 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
<< "Such variables must be identified with a Block "
"decoration";
}
+ // Vulkan 14.5.2: Check DescriptorSet and Binding decoration for
+ // Uniform and StorageBuffer variables.
+ if (uniform || storage_buffer) {
+ auto entry_points = vstate.EntryPointReferences(var_id);
+ if (!entry_points.empty() &&
+ !hasDecoration(var_id, SpvDecorationDescriptorSet, vstate)) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+ << sc_str << " id '" << var_id
+ << "' is missing DescriptorSet decoration.\n"
+ << "From Vulkan spec, section 14.5.2:\n"
+ << "These variables must have DescriptorSet and Binding "
+ "decorations specified";
+ }
+ if (!entry_points.empty() &&
+ !hasDecoration(var_id, SpvDecorationBinding, vstate)) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+ << sc_str << " id '" << var_id
+ << "' is missing Binding decoration.\n"
+ << "From Vulkan spec, section 14.5.2:\n"
+ << "These variables must have DescriptorSet and Binding "
+ "decorations specified";
+ }
+ }
}
- // Prepare for messages
- const char* sc_str =
- uniform ? "Uniform"
- : (push_constant ? "PushConstant" : "StorageBuffer");
for (const auto& dec : vstate.id_decorations(id)) {
const bool blockDeco = SpvDecorationBlock == dec.dec_type();
const bool bufferDeco = SpvDecorationBufferBlock == dec.dec_type();
@@ -825,7 +908,8 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
if (isMissingOffsetInStruct(id, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
- << " must be explicitly laid out with Offset decorations.";
+ << " must be explicitly laid out with Offset "
+ "decorations.";
} else if (hasDecoration(id, SpvDecorationGLSLShared, vstate)) {
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
<< "Structure id " << id << " decorated as " << deco_str
@@ -866,6 +950,109 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
return SPV_SUCCESS;
}
+spv_result_t CheckDecorationsCompatibility(ValidationState_t& vstate) {
+ using AtMostOnceSet = std::unordered_set<SpvDecoration, SpvDecorationHash>;
+ using MutuallyExclusiveSets =
+ std::vector<std::unordered_set<SpvDecoration, SpvDecorationHash>>;
+ using PerIDKey = std::tuple<SpvDecoration, uint32_t>;
+ using PerMemberKey = std::tuple<SpvDecoration, uint32_t, uint32_t>;
+ using DecorationNameTable =
+ std::unordered_map<SpvDecoration, std::string, SpvDecorationHash>;
+
+ static const auto* const at_most_once_per_id = new AtMostOnceSet{
+ SpvDecorationArrayStride,
+ };
+ static const auto* const at_most_once_per_member = new AtMostOnceSet{
+ SpvDecorationOffset,
+ SpvDecorationMatrixStride,
+ SpvDecorationRowMajor,
+ SpvDecorationColMajor,
+ };
+ static const auto* const mutually_exclusive_per_id =
+ new MutuallyExclusiveSets{
+ {SpvDecorationBlock, SpvDecorationBufferBlock},
+ };
+ static const auto* const mutually_exclusive_per_member =
+ new MutuallyExclusiveSets{
+ {SpvDecorationRowMajor, SpvDecorationColMajor},
+ };
+ // For printing the decoration name.
+ static const auto* const decoration_name = new DecorationNameTable{
+ {SpvDecorationArrayStride, "ArrayStride"},
+ {SpvDecorationOffset, "Offset"},
+ {SpvDecorationMatrixStride, "MatrixStride"},
+ {SpvDecorationRowMajor, "RowMajor"},
+ {SpvDecorationColMajor, "ColMajor"},
+ {SpvDecorationBlock, "Block"},
+ {SpvDecorationBufferBlock, "BufferBlock"},
+ };
+
+ std::set<PerIDKey> seen_per_id;
+ std::set<PerMemberKey> seen_per_member;
+
+ for (const auto& inst : vstate.ordered_instructions()) {
+ const auto& words = inst.words();
+ if (SpvOpDecorate == inst.opcode()) {
+ const auto id = words[1];
+ const auto dec_type = static_cast<SpvDecoration>(words[2]);
+ const auto k = PerIDKey(dec_type, id);
+ const auto already_used = !seen_per_id.insert(k).second;
+ if (already_used &&
+ at_most_once_per_id->find(dec_type) != at_most_once_per_id->end()) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+ << "ID '" << id << "' decorated with "
+ << decoration_name->at(dec_type)
+ << " multiple times is not allowed.";
+ }
+ // Verify certain mutually exclusive decorations are not both applied on
+ // an ID.
+ for (const auto& s : *mutually_exclusive_per_id) {
+ if (s.find(dec_type) == s.end()) continue;
+ for (auto excl_dec_type : s) {
+ if (excl_dec_type == dec_type) continue;
+ const auto excl_k = PerIDKey(excl_dec_type, id);
+ if (seen_per_id.find(excl_k) != seen_per_id.end()) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+ << "ID '" << id << "' decorated with both "
+ << decoration_name->at(dec_type) << " and "
+ << decoration_name->at(excl_dec_type) << " is not allowed.";
+ }
+ }
+ }
+ } else if (SpvOpMemberDecorate == inst.opcode()) {
+ const auto id = words[1];
+ const auto member_id = words[2];
+ const auto dec_type = static_cast<SpvDecoration>(words[3]);
+ const auto k = PerMemberKey(dec_type, id, member_id);
+ const auto already_used = !seen_per_member.insert(k).second;
+ if (already_used && at_most_once_per_member->find(dec_type) !=
+ at_most_once_per_member->end()) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+ << "ID '" << id << "', member '" << member_id
+ << "' decorated with " << decoration_name->at(dec_type)
+ << " multiple times is not allowed.";
+ }
+ // Verify certain mutually exclusive decorations are not both applied on
+ // a (ID, member) tuple.
+ for (const auto& s : *mutually_exclusive_per_member) {
+ if (s.find(dec_type) == s.end()) continue;
+ for (auto excl_dec_type : s) {
+ if (excl_dec_type == dec_type) continue;
+ const auto excl_k = PerMemberKey(excl_dec_type, id, member_id);
+ if (seen_per_member.find(excl_k) != seen_per_member.end()) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+ << "ID '" << id << "', member '" << member_id
+ << "' decorated with both " << decoration_name->at(dec_type)
+ << " and " << decoration_name->at(excl_dec_type)
+ << " is not allowed.";
+ }
+ }
+ }
+ }
+ }
+ return SPV_SUCCESS;
+}
+
spv_result_t CheckVulkanMemoryModelDeprecatedDecorations(
ValidationState_t& vstate) {
if (vstate.memory_model() != SpvMemoryModelVulkanKHR) return SPV_SUCCESS;
@@ -893,76 +1080,134 @@ spv_result_t CheckVulkanMemoryModelDeprecatedDecorations(
return SPV_SUCCESS;
}
-spv_result_t CheckDecorationsOfConversions(ValidationState_t& vstate) {
- // Validates FPRoundingMode decoration for Shader Capabilities
- if (!vstate.HasCapability(SpvCapabilityShader)) return SPV_SUCCESS;
+// Returns SPV_SUCCESS if validation rules are satisfied for FPRoundingMode
+// decorations. Otherwise emits a diagnostic and returns something other than
+// SPV_SUCCESS.
+spv_result_t CheckFPRoundingModeForShaders(ValidationState_t& vstate,
+ const Instruction& inst) {
+ // Validates width-only conversion instruction for floating-point object
+ // i.e., OpFConvert
+ if (inst.opcode() != SpvOpFConvert) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "FPRoundingMode decoration can be applied only to a "
+ "width-only conversion instruction for floating-point "
+ "object.";
+ }
+
+ // Validates Object operand of an OpStore
+ for (const auto& use : inst.uses()) {
+ const auto store = use.first;
+ if (store->opcode() != SpvOpStore) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "FPRoundingMode decoration can be applied only to the "
+ "Object operand of an OpStore.";
+ }
+
+ if (use.second != 2) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "FPRoundingMode decoration can be applied only to the "
+ "Object operand of an OpStore.";
+ }
+
+ const auto ptr_inst = vstate.FindDef(store->GetOperandAs<uint32_t>(0));
+ const auto ptr_type = vstate.FindDef(ptr_inst->GetOperandAs<uint32_t>(0));
+
+ const auto half_float_id = ptr_type->GetOperandAs<uint32_t>(2);
+ if (!vstate.IsFloatScalarOrVectorType(half_float_id) ||
+ vstate.GetBitWidth(half_float_id) != 16) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "FPRoundingMode decoration can be applied only to the "
+ "Object operand of an OpStore storing through a pointer "
+ "to "
+ "a 16-bit floating-point scalar or vector object.";
+ }
+
+ // Validates storage class of the pointer to the OpStore
+ const auto storage = ptr_type->GetOperandAs<uint32_t>(1);
+ if (storage != SpvStorageClassStorageBuffer &&
+ storage != SpvStorageClassUniform &&
+ storage != SpvStorageClassPushConstant &&
+ storage != SpvStorageClassInput && storage != SpvStorageClassOutput) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "FPRoundingMode decoration can be applied only to the "
+ "Object operand of an OpStore in the StorageBuffer, "
+ "Uniform, PushConstant, Input, or Output Storage "
+ "Classes.";
+ }
+ }
+ return SPV_SUCCESS;
+}
+
+// Returns SPV_SUCCESS if validation rules are satisfied for Uniform
+// decorations. Otherwise emits a diagnostic and returns something other than
+// SPV_SUCCESS. Assumes each decoration on a group has been propagated down to
+// the group members.
+spv_result_t CheckUniformDecoration(ValidationState_t& vstate,
+ const Instruction& inst,
+ const Decoration&) {
+ // Uniform must decorate an "object"
+ // - has a result ID
+ // - is an instantiation of a non-void type. So it has a type ID, and that
+ // type is not void.
+
+ // We already know the result ID is non-zero.
+
+ if (inst.type_id() == 0) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "Uniform decoration applied to a non-object";
+ }
+ if (Instruction* type_inst = vstate.FindDef(inst.type_id())) {
+ if (type_inst->opcode() == SpvOpTypeVoid) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "Uniform decoration applied to a value with void type";
+ }
+ } else {
+ // We might never get here because this would have been rejected earlier in
+ // the flow.
+ return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "Uniform decoration applied to an object with invalid type";
+ }
+ return SPV_SUCCESS;
+}
+
+#define PASS_OR_BAIL_AT_LINE(X, LINE) \
+ { \
+ spv_result_t e##LINE = (X); \
+ if (e##LINE != SPV_SUCCESS) return e##LINE; \
+ }
+#define PASS_OR_BAIL(X) PASS_OR_BAIL_AT_LINE(X, __LINE__)
+
+// Check rules for decorations where we start from the decoration rather
+// than the decorated object. Assumes each decoration on a group have been
+// propagated down to the group members.
+spv_result_t CheckDecorationsFromDecoration(ValidationState_t& vstate) {
+ // Some rules are only checked for shaders.
+ const bool is_shader = vstate.HasCapability(SpvCapabilityShader);
for (const auto& kv : vstate.id_decorations()) {
const uint32_t id = kv.first;
const auto& decorations = kv.second;
- if (decorations.empty()) {
- continue;
- }
+ if (decorations.empty()) continue;
const Instruction* inst = vstate.FindDef(id);
assert(inst);
+ // We assume the decorations applied to a decoration group have already
+ // been propagated down to the group members.
+ if (inst->opcode() == SpvOpDecorationGroup) continue;
+
// Validates FPRoundingMode decoration
for (const auto& decoration : decorations) {
- if (decoration.dec_type() != SpvDecorationFPRoundingMode) {
- continue;
- }
-
- // Validates width-only conversion instruction for floating-point object
- // i.e., OpFConvert
- if (inst->opcode() != SpvOpFConvert) {
- return vstate.diag(SPV_ERROR_INVALID_ID, inst)
- << "FPRoundingMode decoration can be applied only to a "
- "width-only conversion instruction for floating-point "
- "object.";
- }
-
- // Validates Object operand of an OpStore
- for (const auto& use : inst->uses()) {
- const auto store = use.first;
- if (store->opcode() != SpvOpStore) {
- return vstate.diag(SPV_ERROR_INVALID_ID, inst)
- << "FPRoundingMode decoration can be applied only to the "
- "Object operand of an OpStore.";
- }
-
- if (use.second != 2) {
- return vstate.diag(SPV_ERROR_INVALID_ID, inst)
- << "FPRoundingMode decoration can be applied only to the "
- "Object operand of an OpStore.";
- }
-
- const auto ptr_inst = vstate.FindDef(store->GetOperandAs<uint32_t>(0));
- const auto ptr_type =
- vstate.FindDef(ptr_inst->GetOperandAs<uint32_t>(0));
-
- const auto half_float_id = ptr_type->GetOperandAs<uint32_t>(2);
- if (!vstate.IsFloatScalarOrVectorType(half_float_id) ||
- vstate.GetBitWidth(half_float_id) != 16) {
- return vstate.diag(SPV_ERROR_INVALID_ID, inst)
- << "FPRoundingMode decoration can be applied only to the "
- "Object operand of an OpStore storing through a pointer to "
- "a 16-bit floating-point scalar or vector object.";
- }
-
- // Validates storage class of the pointer to the OpStore
- const auto storage = ptr_type->GetOperandAs<uint32_t>(1);
- if (storage != SpvStorageClassStorageBuffer &&
- storage != SpvStorageClassUniform &&
- storage != SpvStorageClassPushConstant &&
- storage != SpvStorageClassInput &&
- storage != SpvStorageClassOutput) {
- return vstate.diag(SPV_ERROR_INVALID_ID, inst)
- << "FPRoundingMode decoration can be applied only to the "
- "Object operand of an OpStore in the StorageBuffer, "
- "Uniform, PushConstant, Input, or Output Storage "
- "Classes.";
- }
+ switch (decoration.dec_type()) {
+ case SpvDecorationFPRoundingMode:
+ if (is_shader)
+ PASS_OR_BAIL(CheckFPRoundingModeForShaders(vstate, *inst));
+ break;
+ case SpvDecorationUniform:
+ PASS_OR_BAIL(CheckUniformDecoration(vstate, *inst, decoration));
+ break;
+ default:
+ break;
}
}
}
@@ -971,15 +1216,15 @@ spv_result_t CheckDecorationsOfConversions(ValidationState_t& vstate) {
} // namespace
-// Validates that decorations have been applied properly.
spv_result_t ValidateDecorations(ValidationState_t& vstate) {
if (auto error = CheckImportedVariableInitialization(vstate)) return error;
if (auto error = CheckDecorationsOfEntryPoints(vstate)) return error;
if (auto error = CheckDecorationsOfBuffers(vstate)) return error;
+ if (auto error = CheckDecorationsCompatibility(vstate)) return error;
if (auto error = CheckLinkageAttrOfFunctions(vstate)) return error;
if (auto error = CheckVulkanMemoryModelDeprecatedDecorations(vstate))
return error;
- if (auto error = CheckDecorationsOfConversions(vstate)) return error;
+ if (auto error = CheckDecorationsFromDecoration(vstate)) return error;
return SPV_SUCCESS;
}
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index 67da3f74..8a357ff3 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -23,6 +23,7 @@
#include "source/spirv_target_env.h"
#include "source/util/bitutils.h"
#include "source/val/instruction.h"
+#include "source/val/validate_scopes.h"
#include "source/val/validation_state.h"
namespace spvtools {
@@ -513,6 +514,10 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
"NonPrivateTexelKHR is also specified: Op"
<< spvOpcodeString(opcode);
}
+
+ const auto available_scope = inst->word(word_index++);
+ if (auto error = ValidateMemoryScope(_, inst, available_scope))
+ return error;
}
if (mask & SpvImageOperandsMakeTexelVisibleKHRMask) {
@@ -531,6 +536,9 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
"is also specified: Op"
<< spvOpcodeString(opcode);
}
+
+ const auto visible_scope = inst->word(word_index++);
+ if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
}
return SPV_SUCCESS;
@@ -1512,7 +1520,7 @@ spv_result_t ValidateImageQuerySize(ValidationState_t& _,
ImageTypeInfo info;
if (!GetImageTypeInfo(_, image_type, &info)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << "Corrupt image type definition";
+ << "Corrupt image type definition";
}
uint32_t expected_num_components = info.arrayed;
@@ -1545,8 +1553,8 @@ spv_result_t ValidateImageQuerySize(ValidationState_t& _,
uint32_t result_num_components = _.GetDimension(result_type);
if (result_num_components != expected_num_components) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << "Result Type has " << result_num_components << " components, "
- << "but " << expected_num_components << " expected";
+ << "Result Type has " << result_num_components << " components, "
+ << "but " << expected_num_components << " expected";
}
return SPV_SUCCESS;
diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp
index 21b8c8bf..6e7c9e84 100644
--- a/source/val/validate_memory.cpp
+++ b/source/val/validate_memory.cpp
@@ -21,6 +21,7 @@
#include "source/opcode.h"
#include "source/spirv_target_env.h"
#include "source/val/instruction.h"
+#include "source/val/validate_scopes.h"
#include "source/val/validation_state.h"
namespace spvtools {
@@ -230,6 +231,51 @@ std::pair<SpvStorageClass, SpvStorageClass> GetStorageClass(
return std::make_pair(dst_sc, src_sc);
}
+// This function is only called for OpLoad, OpStore, OpCopyMemory and
+// OpCopyMemorySized.
+uint32_t GetMakeAvailableScope(const Instruction* inst, uint32_t mask) {
+ uint32_t offset = 1;
+ if (mask & SpvMemoryAccessAlignedMask) ++offset;
+
+ uint32_t scope_id = 0;
+ switch (inst->opcode()) {
+ case SpvOpLoad:
+ case SpvOpCopyMemorySized:
+ return inst->GetOperandAs<uint32_t>(3 + offset);
+ case SpvOpStore:
+ case SpvOpCopyMemory:
+ return inst->GetOperandAs<uint32_t>(2 + offset);
+ default:
+ assert(false && "unexpected opcode");
+ break;
+ }
+
+ return scope_id;
+}
+
+// This function is only called for OpLoad, OpStore, OpCopyMemory and
+// OpCopyMemorySized.
+uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask) {
+ uint32_t offset = 1;
+ if (mask & SpvMemoryAccessAlignedMask) ++offset;
+ if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++offset;
+
+ uint32_t scope_id = 0;
+ switch (inst->opcode()) {
+ case SpvOpLoad:
+ case SpvOpCopyMemorySized:
+ return inst->GetOperandAs<uint32_t>(3 + offset);
+ case SpvOpStore:
+ case SpvOpCopyMemory:
+ return inst->GetOperandAs<uint32_t>(2 + offset);
+ default:
+ assert(false && "unexpected opcode");
+ break;
+ }
+
+ return scope_id;
+}
+
spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
uint32_t mask) {
if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) {
@@ -243,6 +289,11 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
<< "NonPrivatePointerKHR must be specified if "
"MakePointerAvailableKHR is specified.";
}
+
+ // Check the associated scope for MakeAvailableKHR.
+ const auto available_scope = GetMakeAvailableScope(inst, mask);
+ if (auto error = ValidateMemoryScope(_, inst, available_scope))
+ return error;
}
if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) {
@@ -256,6 +307,10 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
<< "NonPrivatePointerKHR must be specified if "
"MakePointerVisibleKHR is specified.";
}
+
+ // Check the associated scope for MakeVisibleKHR.
+ const auto visible_scope = GetMakeVisibleScope(inst, mask);
+ if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
}
if (mask & SpvMemoryAccessNonPrivatePointerKHRMask) {
diff --git a/source/val/validate_memory_semantics.cpp b/source/val/validate_memory_semantics.cpp
new file mode 100644
index 00000000..ec6df38a
--- /dev/null
+++ b/source/val/validate_memory_semantics.cpp
@@ -0,0 +1,248 @@
+// Copyright (c) 2018 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/val/validate_memory_semantics.h"
+
+#include "source/diagnostic.h"
+#include "source/spirv_target_env.h"
+#include "source/util/bitutils.h"
+#include "source/val/instruction.h"
+#include "source/val/validation_state.h"
+
+namespace spvtools {
+namespace val {
+
+spv_result_t ValidateMemorySemantics(ValidationState_t& _,
+ const Instruction* inst,
+ uint32_t operand_index) {
+ const SpvOp opcode = inst->opcode();
+ const auto id = inst->GetOperandAs<const uint32_t>(operand_index);
+ bool is_int32 = false, is_const_int32 = false;
+ uint32_t value = 0;
+ std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(id);
+
+ if (!is_int32) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": expected Memory Semantics to be a 32-bit int";
+ }
+
+ if (!is_const_int32) {
+ if (_.HasCapability(SpvCapabilityShader)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << "Memory Semantics ids must be OpConstant when Shader "
+ "capability is present";
+ }
+ return SPV_SUCCESS;
+ }
+
+ if (spvIsWebGPUEnv(_.context()->target_env)) {
+ uint32_t valid_bits = SpvMemorySemanticsAcquireMask |
+ SpvMemorySemanticsReleaseMask |
+ SpvMemorySemanticsAcquireReleaseMask |
+ SpvMemorySemanticsUniformMemoryMask |
+ SpvMemorySemanticsWorkgroupMemoryMask |
+ SpvMemorySemanticsImageMemoryMask |
+ SpvMemorySemanticsOutputMemoryKHRMask |
+ SpvMemorySemanticsMakeAvailableKHRMask |
+ SpvMemorySemanticsMakeVisibleKHRMask;
+ 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";
+ }
+ }
+
+ const size_t num_memory_order_set_bits = spvtools::utils::CountSetBits(
+ value & (SpvMemorySemanticsAcquireMask | SpvMemorySemanticsReleaseMask |
+ SpvMemorySemanticsAcquireReleaseMask |
+ SpvMemorySemanticsSequentiallyConsistentMask));
+
+ if (num_memory_order_set_bits > 1) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": Memory Semantics can have at most one of the following "
+ "bits "
+ "set: Acquire, Release, AcquireRelease or "
+ "SequentiallyConsistent";
+ }
+
+ if (_.memory_model() == SpvMemoryModelVulkanKHR &&
+ value & SpvMemorySemanticsSequentiallyConsistentMask) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << "SequentiallyConsistent memory "
+ "semantics cannot be used with "
+ "the VulkanKHR memory model.";
+ }
+
+ if (value & SpvMemorySemanticsMakeAvailableKHRMask &&
+ !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": Memory Semantics MakeAvailableKHR requires capability "
+ << "VulkanMemoryModelKHR";
+ }
+
+ if (value & SpvMemorySemanticsMakeVisibleKHRMask &&
+ !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": Memory Semantics MakeVisibleKHR requires capability "
+ << "VulkanMemoryModelKHR";
+ }
+
+ if (value & SpvMemorySemanticsOutputMemoryKHRMask &&
+ !_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": Memory Semantics OutputMemoryKHR requires capability "
+ << "VulkanMemoryModelKHR";
+ }
+
+ if (value & SpvMemorySemanticsUniformMemoryMask &&
+ !_.HasCapability(SpvCapabilityShader)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": Memory Semantics UniformMemory requires capability Shader";
+ }
+
+ // Disabling this check until
+ // https://github.com/KhronosGroup/glslang/issues/1618 is resolved.
+ // if (value & SpvMemorySemanticsAtomicCounterMemoryMask &&
+ // !_.HasCapability(SpvCapabilityAtomicStorage)) {
+ // return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ // << spvOpcodeString(opcode)
+ // << ": Memory Semantics AtomicCounterMemory requires capability "
+ // "AtomicStorage";
+ // }
+
+ if (value & (SpvMemorySemanticsMakeAvailableKHRMask |
+ SpvMemorySemanticsMakeVisibleKHRMask)) {
+ const bool includes_storage_class =
+ value & (SpvMemorySemanticsUniformMemoryMask |
+ SpvMemorySemanticsSubgroupMemoryMask |
+ SpvMemorySemanticsWorkgroupMemoryMask |
+ SpvMemorySemanticsCrossWorkgroupMemoryMask |
+ SpvMemorySemanticsAtomicCounterMemoryMask |
+ SpvMemorySemanticsImageMemoryMask |
+ SpvMemorySemanticsOutputMemoryKHRMask);
+
+ if (!includes_storage_class) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": expected Memory Semantics to include a storage class";
+ }
+ }
+
+ if (value & SpvMemorySemanticsMakeVisibleKHRMask &&
+ !(value & (SpvMemorySemanticsAcquireMask |
+ SpvMemorySemanticsAcquireReleaseMask))) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": MakeVisibleKHR Memory Semantics also requires either Acquire "
+ "or AcquireRelease Memory Semantics";
+ }
+
+ if (value & SpvMemorySemanticsMakeAvailableKHRMask &&
+ !(value & (SpvMemorySemanticsReleaseMask |
+ SpvMemorySemanticsAcquireReleaseMask))) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": MakeAvailableKHR Memory Semantics also requires either "
+ "Release or AcquireRelease Memory Semantics";
+ }
+
+ if (spvIsVulkanEnv(_.context()->target_env)) {
+ const bool includes_storage_class =
+ value & (SpvMemorySemanticsUniformMemoryMask |
+ SpvMemorySemanticsWorkgroupMemoryMask |
+ SpvMemorySemanticsImageMemoryMask |
+ SpvMemorySemanticsOutputMemoryKHRMask);
+
+ if (opcode == SpvOpMemoryBarrier && !num_memory_order_set_bits) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": Vulkan specification requires Memory Semantics to have "
+ "one "
+ "of the following bits set: Acquire, Release, "
+ "AcquireRelease "
+ "or SequentiallyConsistent";
+ }
+
+ if (opcode == SpvOpMemoryBarrier && !includes_storage_class) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": expected Memory Semantics to include a Vulkan-supported "
+ "storage class";
+ }
+
+#if 0
+ // TODO(atgoo@github.com): this check fails Vulkan CTS, reenable once fixed.
+ if (opcode == SpvOpControlBarrier && value && !includes_storage_class) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": expected Memory Semantics to include a Vulkan-supported "
+ "storage class if Memory Semantics is not None";
+ }
+#endif
+ }
+
+ if (opcode == SpvOpAtomicFlagClear &&
+ (value & SpvMemorySemanticsAcquireMask ||
+ value & SpvMemorySemanticsAcquireReleaseMask)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << "Memory Semantics Acquire and AcquireRelease cannot be used "
+ "with "
+ << spvOpcodeString(opcode);
+ }
+
+ if (opcode == SpvOpAtomicCompareExchange && operand_index == 5 &&
+ (value & SpvMemorySemanticsReleaseMask ||
+ value & SpvMemorySemanticsAcquireReleaseMask)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << spvOpcodeString(opcode)
+ << ": Memory Semantics Release and AcquireRelease cannot be "
+ "used "
+ "for operand Unequal";
+ }
+
+ if (spvIsVulkanEnv(_.context()->target_env)) {
+ if (opcode == SpvOpAtomicLoad &&
+ (value & SpvMemorySemanticsReleaseMask ||
+ value & SpvMemorySemanticsAcquireReleaseMask ||
+ value & SpvMemorySemanticsSequentiallyConsistentMask)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << "Vulkan spec disallows OpAtomicLoad with Memory Semantics "
+ "Release, AcquireRelease and SequentiallyConsistent";
+ }
+
+ if (opcode == SpvOpAtomicStore &&
+ (value & SpvMemorySemanticsAcquireMask ||
+ value & SpvMemorySemanticsAcquireReleaseMask ||
+ value & SpvMemorySemanticsSequentiallyConsistentMask)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << "Vulkan spec disallows OpAtomicStore with Memory Semantics "
+ "Acquire, AcquireRelease and SequentiallyConsistent";
+ }
+ }
+
+ // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
+
+ return SPV_SUCCESS;
+}
+
+} // namespace val
+} // namespace spvtools
diff --git a/source/val/validate_memory_semantics.h b/source/val/validate_memory_semantics.h
new file mode 100644
index 00000000..72a3e100
--- /dev/null
+++ b/source/val/validate_memory_semantics.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2018 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.
+
+// Validates correctness of memory semantics for SPIR-V instructions.
+
+#include "source/opcode.h"
+#include "source/val/validate.h"
+
+namespace spvtools {
+namespace val {
+
+spv_result_t ValidateMemorySemantics(ValidationState_t& _,
+ const Instruction* inst,
+ uint32_t operand_index);
+
+} // namespace val
+} // namespace spvtools
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp
index 8f29ae04..3ba8f3bf 100644
--- a/source/val/validate_scopes.cpp
+++ b/source/val/validate_scopes.cpp
@@ -149,6 +149,14 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
}
}
+ if (value == SpvScopeDevice &&
+ _.HasCapability(SpvCapabilityVulkanMemoryModelKHR) &&
+ !_.HasCapability(SpvCapabilityVulkanMemoryModelDeviceScopeKHR)) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << "Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability";
+ }
+
// Vulkan Specific rules
if (spvIsVulkanEnv(_.context()->target_env)) {
if (value == SpvScopeCrossDevice) {
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index 3bbdb873..e0f27863 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -191,11 +191,6 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) {
<< _.getIdName(member_type_id) << ".";
}
if (_.IsForwardPointer(member_type_id)) {
- if (member_type->opcode() != SpvOpTypePointer) {
- return _.diag(SPV_ERROR_INVALID_ID, inst)
- << "Found a forward reference to a non-pointer "
- "type in OpTypeStruct instruction.";
- }
// If we're dealing with a forward pointer:
// Find out the type that the pointer is pointing to (must be struct)
// word 3 is the <id> of the type being pointed to.
@@ -296,10 +291,32 @@ spv_result_t ValidateTypeFunction(ValidationState_t& _,
return SPV_SUCCESS;
}
+spv_result_t ValidateTypeForwardPointer(ValidationState_t& _,
+ const Instruction* inst) {
+ const auto pointer_type_id = inst->GetOperandAs<uint32_t>(0);
+ const auto pointer_type_inst = _.FindDef(pointer_type_id);
+ if (pointer_type_inst->opcode() != SpvOpTypePointer) {
+ return _.diag(SPV_ERROR_INVALID_ID, inst)
+ << "Pointer type in OpTypeForwardPointer is not a pointer type.";
+ }
+
+ if (inst->GetOperandAs<uint32_t>(1) !=
+ pointer_type_inst->GetOperandAs<uint32_t>(1)) {
+ return _.diag(SPV_ERROR_INVALID_ID, inst)
+ << "Storage class in OpTypeForwardPointer does not match the "
+ "pointer definition.";
+ }
+
+ return SPV_SUCCESS;
+}
+
} // namespace
spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
- if (!spvOpcodeGeneratesType(inst->opcode())) return SPV_SUCCESS;
+ if (!spvOpcodeGeneratesType(inst->opcode()) &&
+ inst->opcode() != SpvOpTypeForwardPointer) {
+ return SPV_SUCCESS;
+ }
if (auto error = ValidateUniqueness(_, inst)) return error;
@@ -325,6 +342,9 @@ spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
case SpvOpTypeFunction:
if (auto error = ValidateTypeFunction(_, inst)) return error;
break;
+ case SpvOpTypeForwardPointer:
+ if (auto error = ValidateTypeForwardPointer(_, inst)) return error;
+ break;
default:
break;
}
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index b8f991f1..a10186fb 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -208,6 +208,10 @@ ValidationState_t::ValidationState_t(const spv_const_context ctx,
/* diagnostic = */ nullptr);
preallocateStorage();
}
+
+ friendly_mapper_ = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
+ context_, words_, num_words_);
+ name_mapper_ = friendly_mapper_->GetNameMapper();
}
void ValidationState_t::preallocateStorage() {
@@ -239,21 +243,10 @@ void ValidationState_t::AssignNameToId(uint32_t id, std::string name) {
}
std::string ValidationState_t::getIdName(uint32_t id) const {
- std::stringstream out;
- out << id;
- if (operand_names_.find(id) != end(operand_names_)) {
- out << "[" << operand_names_.at(id) << "]";
- }
- return out.str();
-}
+ const std::string id_name = name_mapper_(id);
-std::string ValidationState_t::getIdOrName(uint32_t id) const {
std::stringstream out;
- if (operand_names_.find(id) != std::end(operand_names_)) {
- out << operand_names_.at(id);
- } else {
- out << id;
- }
+ out << id << "[%" << id_name << "]";
return out.str();
}
@@ -926,6 +919,39 @@ void ValidationState_t::ComputeFunctionToEntryPointMapping() {
}
}
+void ValidationState_t::ComputeRecursiveEntryPoints() {
+ for (const Function func : functions()) {
+ std::stack<uint32_t> call_stack;
+ std::set<uint32_t> visited;
+
+ for (const uint32_t new_call : func.function_call_targets()) {
+ call_stack.push(new_call);
+ }
+
+ while (!call_stack.empty()) {
+ const uint32_t called_func_id = call_stack.top();
+ call_stack.pop();
+
+ if (!visited.insert(called_func_id).second) continue;
+
+ if (called_func_id == func.id()) {
+ for (const uint32_t entry_point :
+ function_to_entry_points_[called_func_id])
+ recursive_entry_points_.insert(entry_point);
+ break;
+ }
+
+ const Function* called_func = function(called_func_id);
+ if (called_func) {
+ // Other checks should error out on this invalid SPIR-V.
+ for (const uint32_t new_call : called_func->function_call_targets()) {
+ call_stack.push(new_call);
+ }
+ }
+ }
+ }
+}
+
const std::vector<uint32_t>& ValidationState_t::FunctionEntryPoints(
uint32_t func) const {
auto iter = function_to_entry_points_.find(func);
@@ -936,6 +962,34 @@ const std::vector<uint32_t>& ValidationState_t::FunctionEntryPoints(
}
}
+std::set<uint32_t> ValidationState_t::EntryPointReferences(uint32_t id) const {
+ std::set<uint32_t> referenced_entry_points;
+ const auto inst = FindDef(id);
+ if (!inst) return referenced_entry_points;
+
+ std::vector<const Instruction*> stack;
+ stack.push_back(inst);
+ while (!stack.empty()) {
+ const auto current_inst = stack.back();
+ stack.pop_back();
+
+ if (const auto func = current_inst->function()) {
+ // Instruction lives in a function, we can stop searching.
+ const auto function_entry_points = FunctionEntryPoints(func->id());
+ referenced_entry_points.insert(function_entry_points.begin(),
+ function_entry_points.end());
+ } else {
+ // Instruction is in the global scope, keep searching its uses.
+ for (auto pair : current_inst->uses()) {
+ const auto next_inst = pair.first;
+ stack.push_back(next_inst);
+ }
+ }
+ }
+
+ return referenced_entry_points;
+}
+
std::string ValidationState_t::Disassemble(const Instruction& inst) const {
const spv_parsed_instruction_t& c_inst(inst.c_inst());
return Disassemble(c_inst.words, c_inst.num_words);
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index d5b996c6..85229f2d 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -28,6 +28,7 @@
#include "source/disassemble.h"
#include "source/enum_set.h"
#include "source/latest_version_spirv_header.h"
+#include "source/name_mapper.h"
#include "source/spirv_definition.h"
#include "source/spirv_validator_options.h"
#include "source/val/decoration.h"
@@ -154,9 +155,6 @@ class ValidationState_t {
/// Mutator function for ID bound.
void setIdBound(uint32_t bound);
- /// Like getIdName but does not display the id if the \p id has a name
- std::string getIdOrName(uint32_t id) const;
-
/// Returns the number of ID which have been forward referenced but not
/// defined
size_t unresolved_forward_id_count() const;
@@ -224,6 +222,12 @@ class ValidationState_t {
/// Returns a list of entry point function ids
const std::vector<uint32_t>& entry_points() const { return entry_points_; }
+ /// Returns the set of entry points that root call graphs that contain
+ /// recursion.
+ const std::set<uint32_t>& recursive_entry_points() const {
+ return recursive_entry_points_;
+ }
+
/// Registers execution mode for the given entry point.
void RegisterExecutionModeForEntryPoint(uint32_t entry_point,
SpvExecutionMode execution_mode) {
@@ -263,9 +267,19 @@ class ValidationState_t {
/// Note: called after fully parsing the binary.
void ComputeFunctionToEntryPointMapping();
+ /// Traverse call tree and computes recursive_entry_points_.
+ /// Note: called after fully parsing the binary and calling
+ /// ComputeFunctionToEntryPointMapping.
+ void ComputeRecursiveEntryPoints();
+
/// Returns all the entry points that can call |func|.
const std::vector<uint32_t>& FunctionEntryPoints(uint32_t func) const;
+ /// Returns all the entry points that statically use |id|.
+ ///
+ /// Note: requires ComputeFunctionToEntryPointMapping to have been called.
+ std::set<uint32_t> EntryPointReferences(uint32_t id) const;
+
/// Inserts an <id> to the set of functions that are target of OpFunctionCall.
void AddFunctionCallTarget(const uint32_t id) {
function_call_targets_.insert(id);
@@ -277,6 +291,9 @@ class ValidationState_t {
return (function_call_targets_.find(id) != function_call_targets_.end());
}
+ bool IsFunctionCallDefined(const uint32_t id) {
+ return (id_to_function_.find(id) != id_to_function_.end());
+ }
/// Registers the capability and its dependent capabilities
void RegisterCapability(SpvCapability cap);
@@ -604,6 +621,10 @@ class ValidationState_t {
std::unordered_map<uint32_t, std::vector<EntryPointDescription>>
entry_point_descriptions_;
+ /// IDs that are entry points, ie, arguments to OpEntryPoint, and root a call
+ /// graph that recurses.
+ std::set<uint32_t> recursive_entry_points_;
+
/// Functions IDs that are target of OpFunctionCall.
std::unordered_set<uint32_t> function_call_targets_;
@@ -661,6 +682,10 @@ class ValidationState_t {
std::unordered_map<uint32_t, std::vector<uint32_t>> function_to_entry_points_;
const std::vector<uint32_t> empty_ids_;
+ /// Maps ids to friendly names.
+ std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper_;
+ spvtools::NameMapper name_mapper_;
+
/// Variables used to reduce the number of diagnostic messages.
uint32_t num_of_warnings_;
uint32_t max_num_of_warnings_;
diff --git a/test/cpp_interface_test.cpp b/test/cpp_interface_test.cpp
index 463d9694..538d40fd 100644
--- a/test/cpp_interface_test.cpp
+++ b/test/cpp_interface_test.cpp
@@ -58,7 +58,8 @@ TEST(CppInterface, SuccessfulRoundTrip) {
EXPECT_EQ(0u, position.line);
EXPECT_EQ(0u, position.column);
EXPECT_EQ(1u, position.index);
- EXPECT_STREQ("ID 1 has not been defined\n %2 = OpSizeOf %1 %3\n", message);
+ EXPECT_STREQ("ID 1[%1] has not been defined\n %2 = OpSizeOf %1 %3\n",
+ message);
});
EXPECT_FALSE(t.Validate(binary));
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 56414ae1..1e32df38 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -92,4 +92,5 @@ add_spvtools_unittest(TARGET opt
add_spvtools_unittest(TARGET upgrade_memory_model
SRCS upgrade_memory_model_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt
+ PCH_FILE pch_test_opt
)
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index a53418c4..e58a76f7 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -5111,6 +5111,43 @@ OpFunctionEnd
SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
}
+TEST_F(AggressiveDCETest, DeadDecorationGroupAndValidDecorationMgr) {
+ // The decoration group should be eliminated because the target of group
+ // decorate is dead.
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Restrict
+OpDecorate %1 Aliased
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %var
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_ptr = OpTypePointer Function %uint
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %uint_ptr Function
+OpReturn
+OpFunctionEnd
+ )";
+
+ auto pass = MakeUnique<AggressiveDCEPass>();
+ auto consumer = [](spv_message_level_t, const char*, const spv_position_t&,
+ const char* message) {
+ std::cerr << message << std::endl;
+ };
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer, text);
+
+ // Build the decoration manager before the pass.
+ context->get_decoration_mgr();
+
+ const auto status = pass->Run(context.get());
+ EXPECT_EQ(status, Pass::Status::SuccessWithChange);
+}
+
TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroup) {
const std::string text = R"(
; CHECK: OpDecorate [[grp:%\w+]] Restrict
@@ -5211,7 +5248,7 @@ OpFunctionEnd
TEST_F(AggressiveDCETest, PartiallyDeadGroupMemberDecorate) {
const std::string text = R"(
; CHECK: OpDecorate [[grp:%\w+]] Offset 0
-; CHECK: OpDecorate [[grp]] Uniform
+; CHECK: OpDecorate [[grp]] RelaxedPrecision
; CHECK: [[grp]] = OpDecorationGroup
; CHECK: OpGroupMemberDecorate [[grp]] [[output:%\w+]] 1
; CHECK: [[output]] = OpTypeStruct
@@ -5221,7 +5258,7 @@ OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %output
OpExecutionMode %main OriginUpperLeft
OpDecorate %1 Offset 0
-OpDecorate %1 Uniform
+OpDecorate %1 RelaxedPrecision
%1 = OpDecorationGroup
OpGroupMemberDecorate %1 %var_struct 0 %output_struct 1
%void = OpTypeVoid
@@ -5250,7 +5287,7 @@ TEST_F(AggressiveDCETest,
PartiallyDeadGroupMemberDecorateDifferentGroupDecorate) {
const std::string text = R"(
; CHECK: OpDecorate [[grp:%\w+]] Offset 0
-; CHECK: OpDecorate [[grp]] Uniform
+; CHECK: OpDecorate [[grp]] RelaxedPrecision
; CHECK: [[grp]] = OpDecorationGroup
; CHECK: OpGroupMemberDecorate [[grp]] [[output:%\w+]] 1
; CHECK-NOT: OpGroupMemberDecorate
@@ -5261,7 +5298,7 @@ OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %output
OpExecutionMode %main OriginUpperLeft
OpDecorate %1 Offset 0
-OpDecorate %1 Uniform
+OpDecorate %1 RelaxedPrecision
%1 = OpDecorationGroup
OpGroupMemberDecorate %1 %var_struct 0
OpGroupMemberDecorate %1 %output_struct 1
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index 9aae338b..1a544218 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -179,6 +179,7 @@ OpName %main "main"
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_32 = OpConstant %uint 32
+%uint_42 = OpConstant %uint 42
%uint_max = OpConstant %uint 4294967295
%v2int_undef = OpUndef %v2int
%v2int_0_0 = OpConstantComposite %v2int %int_0 %int_0
@@ -474,6 +475,36 @@ INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTest,
"%2 = OpUMod %uint %uint_1 %uint_0\n" +
"OpReturn\n" +
"OpFunctionEnd",
+ 2, 0),
+ // Test case 22: fold unsigned n >> 42 (undefined, so set to zero).
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpShiftRightLogical %uint %load %uint_42\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 21: fold signed n >> 42 (undefined, so set to zero).
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpShiftRightLogical %int %load %uint_42\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 22: fold n << 42 (undefined, so set to zero).
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpShiftLeftLogical %int %load %uint_42\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
2, 0)
));
// clang-format on
diff --git a/test/opt/ir_builder.cpp b/test/opt/ir_builder.cpp
index 3fd792ea..f800ca43 100644
--- a/test/opt/ir_builder.cpp
+++ b/test/opt/ir_builder.cpp
@@ -198,6 +198,7 @@ TEST_F(IRBuilderTest, TestCondBranchAddition) {
BasicBlock& bb_merge = *fn.begin();
+ // TODO(1841): Handle id overflow.
fn.begin().InsertBefore(std::unique_ptr<BasicBlock>(
new BasicBlock(std::unique_ptr<Instruction>(new Instruction(
context.get(), SpvOpLabel, 0, context->TakeNextId(), {})))));
@@ -207,6 +208,7 @@ TEST_F(IRBuilderTest, TestCondBranchAddition) {
builder.AddBranch(bb_merge.id());
}
+ // TODO(1841): Handle id overflow.
fn.begin().InsertBefore(std::unique_ptr<BasicBlock>(
new BasicBlock(std::unique_ptr<Instruction>(new Instruction(
context.get(), SpvOpLabel, 0, context->TakeNextId(), {})))));
@@ -404,6 +406,34 @@ OpFunctionEnd
Match(text, context.get());
}
+TEST_F(IRBuilderTest, AccelerationStructureNV) {
+ const std::string text = R"(
+; CHECK: OpTypeAccelerationStructureNV
+OpCapability Shader
+OpCapability RayTracingNV
+OpExtension "SPV_NV_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %8 "main"
+OpExecutionMode %8 OriginUpperLeft
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeAccelerationStructureNV
+%7 = OpTypeFunction %1
+%8 = OpFunction %1 None %7
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ EXPECT_NE(nullptr, context);
+
+ InstructionBuilder builder(context.get(),
+ &*context->module()->begin()->begin()->begin());
+ Match(text, context.get());
+}
+
} // namespace
} // namespace opt
} // namespace spvtools
diff --git a/test/opt/ir_context_test.cpp b/test/opt/ir_context_test.cpp
index f66b16e7..4e2f5b2c 100644
--- a/test/opt/ir_context_test.cpp
+++ b/test/opt/ir_context_test.cpp
@@ -567,6 +567,103 @@ TEST_F(IRContextTest, BasicDontVisitExportedVariable) {
EXPECT_THAT(processed, UnorderedElementsAre(10));
}
+TEST_F(IRContextTest, IdBoundTestAtLimit) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound);
+ uint32_t next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, 0);
+ EXPECT_EQ(current_bound, context->module()->id_bound());
+ next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, 0);
+}
+
+TEST_F(IRContextTest, IdBoundTestBelowLimit) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound + 100);
+ uint32_t next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, current_bound);
+ EXPECT_EQ(current_bound + 1, context->module()->id_bound());
+ next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, current_bound + 1);
+}
+
+TEST_F(IRContextTest, IdBoundTestNearLimit) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound + 1);
+ uint32_t next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, current_bound);
+ EXPECT_EQ(current_bound + 1, context->module()->id_bound());
+ next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, 0);
+}
+
+TEST_F(IRContextTest, IdBoundTestUIntMax) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4294967294 = OpLabel ; ID is UINT_MAX-1
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ uint32_t current_bound = context->module()->id_bound();
+
+ // Expecting |BuildModule| to preserve the numeric ids.
+ EXPECT_EQ(current_bound, std::numeric_limits<uint32_t>::max());
+
+ context->set_max_id_bound(current_bound);
+ uint32_t next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, 0);
+ EXPECT_EQ(current_bound, context->module()->id_bound());
+}
} // namespace
} // namespace opt
} // namespace spvtools
diff --git a/test/opt/loop_optimizations/hoist_without_preheader.cpp b/test/opt/loop_optimizations/hoist_without_preheader.cpp
index 9a149964..2e34b014 100644
--- a/test/opt/loop_optimizations/hoist_without_preheader.cpp
+++ b/test/opt/loop_optimizations/hoist_without_preheader.cpp
@@ -117,6 +117,81 @@ OpFunctionEnd
SinglePassRunAndMatch<LICMPass>(text, false);
}
+TEST_F(PassClassTest, HoistWithoutPreheaderAtIdBound) {
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_5 = OpConstant %int 5
+%main = OpFunction %void None %4
+%13 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%15 = OpPhi %int %int_0 %13 %16 %17
+OpLoopMerge %25 %17 None
+OpBranch %19
+%19 = OpLabel
+%20 = OpSLessThan %bool %15 %int_10
+OpBranchConditional %20 %21 %25
+%21 = OpLabel
+%22 = OpIEqual %bool %15 %int_5
+OpSelectionMerge %23 None
+OpBranchConditional %22 %24 %23
+%24 = OpLabel
+OpBranch %25
+%23 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%16 = OpIAdd %int %15 %int_1
+OpBranch %14
+%25 = OpLabel
+%26 = OpPhi %int %int_0 %24 %int_0 %19 %27 %28
+%29 = OpPhi %int %int_0 %24 %int_0 %19 %30 %28
+OpLoopMerge %31 %28 None
+OpBranch %32
+%32 = OpLabel
+%33 = OpSLessThan %bool %29 %int_10
+OpBranchConditional %33 %34 %31
+%34 = OpLabel
+%27 = OpIAdd %int %int_1 %int_2
+OpBranch %28
+%28 = OpLabel
+%30 = OpIAdd %int %29 %int_1
+OpBranch %25
+%31 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound);
+
+ auto pass = MakeUnique<LICMPass>();
+ auto result = pass->Run(context.get());
+ EXPECT_EQ(result, Pass::Status::Failure);
+
+ std::vector<uint32_t> binary;
+ context->module()->ToBinary(&binary, false);
+ std::string optimized_asm;
+ SpirvTools tools_(SPV_ENV_UNIVERSAL_1_1);
+ tools_.Disassemble(binary, &optimized_asm);
+ std::cout << optimized_asm << std::endl;
+}
} // namespace
} // namespace opt
} // namespace spvtools
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
index 7f2c0589..e49fec95 100644
--- a/test/opt/pass_merge_return_test.cpp
+++ b/test/opt/pass_merge_return_test.cpp
@@ -1145,6 +1145,115 @@ OpFunctionEnd
EXPECT_TRUE(messages.empty());
}
+TEST_F(MergeReturnPassTest, StructuredControlFlowDontChangeEntryPhi) {
+ const std::string before =
+ R"(
+; CHECK: OpFunction %void
+; CHECK: OpLabel
+; CHECK: OpLabel
+; CHECK: [[pre_header:%\w+]] = OpLabel
+; CHECK: [[header:%\w+]] = OpLabel
+; CHECK-NEXT: OpPhi %bool {{%\w+}} [[pre_header]] [[iv:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]]
+; CHECK: [[continue]] = OpLabel
+; CHECK-NEXT: [[iv]] = Op
+; CHECK: [[merge]] = OpLabel
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %4 = OpTypeFunction %void
+ %1 = OpFunction %void None %4
+ %5 = OpLabel
+ %6 = OpUndef %bool
+ OpBranch %7
+ %7 = OpLabel
+ %8 = OpPhi %bool %6 %5 %9 %10
+ OpLoopMerge %11 %10 None
+ OpBranch %12
+ %12 = OpLabel
+ %13 = OpUndef %bool
+ OpSelectionMerge %10 DontFlatten
+ OpBranchConditional %13 %10 %14
+ %14 = OpLabel
+ OpReturn
+ %10 = OpLabel
+ %9 = OpUndef %bool
+ OpBranchConditional %13 %7 %11
+ %11 = OpLabel
+ OpReturn
+ OpFunctionEnd
+
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndMatch<MergeReturnPass>(before, false);
+}
+
+TEST_F(MergeReturnPassTest, StructuredControlFlowPartialReplacePhi) {
+ const std::string before =
+ R"(
+; CHECK: OpFunction %void
+; CHECK: OpLabel
+; CHECK: OpLabel
+; CHECK: [[pre_header:%\w+]] = OpLabel
+; CHECK: [[header:%\w+]] = OpLabel
+; CHECK-NEXT: OpPhi
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]]
+; CHECK: OpLabel
+; CHECK: [[old_ret_block:%\w+]] = OpLabel
+; CHECK: [[bb:%\w+]] = OpLabel
+; CHECK-NEXT: [[val:%\w+]] = OpUndef %bool
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi1:%\w+]] = OpPhi %bool [[val]] [[bb]] {{%\w+}} [[old_ret_block]]
+; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK: OpBranch [[header2:%\w+]]
+; CHECK: [[header2]] = OpLabel
+; CHECK-NEXT: [[phi2:%\w+]] = OpPhi %bool [[phi1]] [[continue2:%\w+]] [[phi1]] [[bb2]]
+; CHECK-NEXT: OpLoopMerge {{%\w+}} [[continue2]]
+; CHECK: [[continue2]] = OpLabel
+; CHECK-NEXT: OpBranch [[header2]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %4 = OpTypeFunction %void
+ %1 = OpFunction %void None %4
+ %5 = OpLabel
+ %6 = OpUndef %bool
+ OpBranch %7
+ %7 = OpLabel
+ %8 = OpPhi %bool %6 %5 %9 %10
+ OpLoopMerge %11 %10 None
+ OpBranch %12
+ %12 = OpLabel
+ %13 = OpUndef %bool
+ OpSelectionMerge %10 DontFlatten
+ OpBranchConditional %13 %10 %14
+ %14 = OpLabel
+ OpReturn
+ %10 = OpLabel
+ %9 = OpUndef %bool
+ OpBranchConditional %13 %7 %11
+ %11 = OpLabel
+ %phi = OpPhi %bool %9 %10 %9 %cont
+ OpLoopMerge %ret %cont None
+ OpBranch %bb
+ %bb = OpLabel
+ OpBranchConditional %13 %ret %cont
+ %cont = OpLabel
+ OpBranch %11
+ %ret = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndMatch<MergeReturnPass>(before, false);
+}
} // namespace
} // namespace opt
} // namespace spvtools
diff --git a/test/opt/type_manager_test.cpp b/test/opt/type_manager_test.cpp
index 07a2ac47..fc9089ef 100644
--- a/test/opt/type_manager_test.cpp
+++ b/test/opt/type_manager_test.cpp
@@ -156,7 +156,7 @@ std::vector<std::unique_ptr<Type>> GenerateAllTypes() {
types.emplace_back(new ReserveId());
types.emplace_back(new Queue());
- // Pipe, Forward Pointer, PipeStorage, NamedBarrier
+ // Pipe, Forward Pointer, PipeStorage, NamedBarrier, AccelerationStructureNV
types.emplace_back(new Pipe(SpvAccessQualifierReadWrite));
types.emplace_back(new Pipe(SpvAccessQualifierReadOnly));
types.emplace_back(new ForwardPointer(1, SpvStorageClassInput));
@@ -164,6 +164,7 @@ std::vector<std::unique_ptr<Type>> GenerateAllTypes() {
types.emplace_back(new ForwardPointer(2, SpvStorageClassUniform));
types.emplace_back(new PipeStorage());
types.emplace_back(new NamedBarrier());
+ types.emplace_back(new AccelerationStructureNV());
return types;
}
@@ -1035,6 +1036,7 @@ TEST(TypeManager, GetTypeInstructionAllTypes) {
; CHECK: OpTypeForwardPointer [[uniform_ptr]] Uniform
; CHECK: OpTypePipeStorage
; CHECK: OpTypeNamedBarrier
+; CHECK: OpTypeAccelerationStructureNV
OpCapability Shader
OpCapability Int64
OpCapability Linkage
diff --git a/test/opt/types_test.cpp b/test/opt/types_test.cpp
index c11187e8..7426ed79 100644
--- a/test/opt/types_test.cpp
+++ b/test/opt/types_test.cpp
@@ -88,6 +88,7 @@ TestMultipleInstancesOfTheSameType(Pipe, SpvAccessQualifierReadWrite);
TestMultipleInstancesOfTheSameType(ForwardPointer, 10, SpvStorageClassUniform);
TestMultipleInstancesOfTheSameType(PipeStorage);
TestMultipleInstancesOfTheSameType(NamedBarrier);
+TestMultipleInstancesOfTheSameType(AccelerationStructureNV);
#undef TestMultipleInstanceOfTheSameType
std::vector<std::unique_ptr<Type>> GenerateAllTypes() {
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index 828bf68a..cdd27b9e 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -18,7 +18,10 @@ add_spvtools_unittest(TARGET reduce
reduce_test_util.cpp
reduce_test_util.h
reducer_test.cpp
+ remove_opname_instruction_reduction_pass_test.cpp
remove_unreferenced_instruction_reduction_pass_test.cpp
+ structured_loop_to_selection_reduction_pass_test.cpp
+ validation_during_reduction_test.cpp
LIBS SPIRV-Tools-reduce
)
diff --git a/test/reduce/reduce_test_util.cpp b/test/reduce/reduce_test_util.cpp
index 022e7e36..19ef7498 100644
--- a/test/reduce/reduce_test_util.cpp
+++ b/test/reduce/reduce_test_util.cpp
@@ -48,5 +48,25 @@ void CheckEqual(const spv_target_env env, const std::string& expected_text,
CheckEqual(env, expected_text, actual_binary);
}
+void CheckValid(spv_target_env env, const opt::IRContext* ir) {
+ std::vector<uint32_t> binary;
+ ir->module()->ToBinary(&binary, false);
+ SpirvTools t(env);
+ ASSERT_TRUE(t.Validate(binary));
+}
+
+std::string ToString(spv_target_env env, const opt::IRContext* ir) {
+ std::vector<uint32_t> binary;
+ ir->module()->ToBinary(&binary, false);
+ SpirvTools t(env);
+ std::string result;
+ t.Disassemble(binary, &result, kReduceDisassembleOption);
+ return result;
+}
+
+void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
+ const spv_position_t& /*position*/,
+ const char* /*message*/) {}
+
} // namespace reduce
} // namespace spvtools
diff --git a/test/reduce/reduce_test_util.h b/test/reduce/reduce_test_util.h
index 0331799f..499c7747 100644
--- a/test/reduce/reduce_test_util.h
+++ b/test/reduce/reduce_test_util.h
@@ -56,6 +56,14 @@ void CheckEqual(spv_target_env env, const std::string& expected_text,
void CheckEqual(spv_target_env env, const std::string& expected_text,
const opt::IRContext* actual_ir);
+// Assembles the given IR context and checks whether the resulting binary is
+// valid.
+void CheckValid(spv_target_env env, const opt::IRContext* ir);
+
+// Assembles the given IR context, then returns its disassembly as a string.
+// Useful for debugging.
+std::string ToString(spv_target_env env, const opt::IRContext* ir);
+
// Assembly options for writing reduction tests. It simplifies matters if
// numeric ids do not change.
const uint32_t kReduceAssembleOption =
@@ -64,6 +72,10 @@ const uint32_t kReduceAssembleOption =
const uint32_t kReduceDisassembleOption =
SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | SPV_BINARY_TO_TEXT_OPTION_INDENT;
+// Don't print reducer info during testing.
+void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
+ const spv_position_t& /*position*/, const char* /*message*/);
+
} // namespace reduce
} // namespace spvtools
diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp
index 5441a56a..88fc5e44 100644
--- a/test/reduce/reducer_test.cpp
+++ b/test/reduce/reducer_test.cpp
@@ -16,18 +16,14 @@
#include "source/reduce/operand_to_const_reduction_pass.h"
#include "source/reduce/reducer.h"
+#include "source/reduce/remove_opname_instruction_reduction_pass.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
namespace spvtools {
namespace reduce {
namespace {
-// Don't print reducer info during testing.
-void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
- const spv_position_t& /*position*/,
- const char* /*message*/) {}
-
-// This changes is its mind each time IsInteresting is invoked as to whether the
+// This changes its mind each time IsInteresting is invoked as to whether the
// binary is interesting, until some limit is reached after which the binary is
// always deemed interesting. This is useful to test that reduction passes
// interleave in interesting ways for a while, and then always succeed after
@@ -238,6 +234,77 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
CheckEqual(env, expected, binary_out);
}
+TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) {
+ const std::string original = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %2 "main"
+ OpName %3 "a"
+ OpName %4 "this-name-counts-as-usage-for-load-instruction"
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %7 = OpTypeFloat 32
+ %8 = OpTypePointer Function %7
+ %9 = OpConstant %7 1
+ %2 = OpFunction %5 None %6
+ %10 = OpLabel
+ %3 = OpVariable %8 Function
+ %4 = OpLoad %7 %3
+ OpStore %3 %7
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string expected = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %7 = OpTypeFloat 32
+ %8 = OpTypePointer Function %7
+ %9 = OpConstant %7 1
+ %2 = OpFunction %5 None %6
+ %10 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+ Reducer reducer(env);
+ // Make ping-pong interesting very quickly, as there are not much
+ // opportunities.
+ PingPongInteresting ping_pong_interesting(1);
+ reducer.SetMessageConsumer(NopDiagnostic);
+ reducer.SetInterestingnessFunction(
+ [&](const std::vector<uint32_t>& binary, uint32_t) -> bool {
+ return ping_pong_interesting.IsInteresting(binary);
+ });
+ reducer.AddReductionPass(
+ MakeUnique<RemoveOpNameInstructionReductionPass>(env));
+ reducer.AddReductionPass(
+ MakeUnique<RemoveUnreferencedInstructionReductionPass>(env));
+
+ std::vector<uint32_t> binary_in;
+ SpirvTools t(env);
+
+ ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
+ std::vector<uint32_t> binary_out;
+ spvtools::ReducerOptions reducer_options;
+ reducer_options.set_step_limit(500);
+
+ reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+
+ CheckEqual(env, expected, binary_out);
+}
+
} // namespace
} // namespace reduce
} // namespace spvtools \ No newline at end of file
diff --git a/test/reduce/remove_opname_instruction_reduction_pass_test.cpp b/test/reduce/remove_opname_instruction_reduction_pass_test.cpp
new file mode 100644
index 00000000..38a2d7f7
--- /dev/null
+++ b/test/reduce/remove_opname_instruction_reduction_pass_test.cpp
@@ -0,0 +1,216 @@
+// Copyright (c) 2018 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 "reduce_test_util.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/reduce/remove_opname_instruction_reduction_pass.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(RemoveOpnameInstructionReductionPassTest, NothingToRemove) {
+ const std::string source = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, source, kReduceAssembleOption);
+ const auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveOpnameInstructionReductionPassTest, RemoveSingleOpName) {
+ const std::string prologue = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ )";
+
+ const std::string epilogue = R"(
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string original = prologue + R"(
+ OpName %4 "main"
+ )" + epilogue;
+
+ const std::string expected = prologue + epilogue;
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, original, kReduceAssembleOption);
+ const auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(RemoveOpnameInstructionReductionPassTest, TryApplyRemovesAllOpName) {
+ const std::string prologue = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ )";
+
+ const std::string epilogue = R"(
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %11 = OpVariable %7 Function
+ %12 = OpVariable %7 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpStore %11 %9
+ OpStore %12 %9
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string original = prologue + R"(
+ OpName %4 "main"
+ OpName %8 "a"
+ OpName %10 "b"
+ OpName %11 "c"
+ OpName %12 "d"
+ )" + epilogue;
+
+ const std::string expected = prologue + epilogue;
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+
+ {
+ // Check the right number of opportunities is detected
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, original, kReduceAssembleOption);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(5, ops.size());
+ }
+
+ {
+ // The reduction should remove all OpName
+ std::vector<uint32_t> binary;
+ SpirvTools t(env);
+ ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
+ auto reduced_binary = pass.TryApplyReduction(binary);
+ CheckEqual(env, expected, reduced_binary);
+ }
+}
+
+TEST(RemoveOpnameInstructionReductionPassTest,
+ TryApplyRemovesAllOpNameAndOpMemberName) {
+ const std::string prologue = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ )";
+
+ const std::string epilogue = R"(
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeInt 32 1
+ %8 = OpTypeVector %6 3
+ %9 = OpTypeStruct %6 %7 %8
+ %10 = OpTypePointer Function %9
+ %12 = OpConstant %7 0
+ %13 = OpConstant %6 1
+ %14 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %11 = OpVariable %10 Function
+ %15 = OpAccessChain %14 %11 %12
+ OpStore %15 %13
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string original = prologue + R"(
+ OpName %4 "main"
+ OpName %9 "S"
+ OpMemberName %9 0 "f"
+ OpMemberName %9 1 "i"
+ OpMemberName %9 2 "v"
+ OpName %11 "s"
+ )" + epilogue;
+
+ const std::string expected = prologue + epilogue;
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+
+ {
+ // Check the right number of opportunities is detected
+ const auto consumer = nullptr;
+ const auto context =
+ BuildModule(env, consumer, original, kReduceAssembleOption);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(6, ops.size());
+ }
+
+ {
+ // The reduction should remove all OpName
+ std::vector<uint32_t> binary;
+ SpirvTools t(env);
+ ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
+ auto reduced_binary = pass.TryApplyReduction(binary);
+ CheckEqual(env, expected, reduced_binary);
+ }
+}
+
+} // namespace
+} // namespace reduce
+} // namespace spvtools
diff --git a/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp b/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp
new file mode 100644
index 00000000..8388cb2e
--- /dev/null
+++ b/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp
@@ -0,0 +1,3440 @@
+// Copyright (c) 2018 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/structured_loop_to_selection_reduction_pass.h"
+#include "reduce_test_util.h"
+#include "source/opt/build_module.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader1) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 100
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSLessThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %19 = OpLoad %6 %8
+ %21 = OpIAdd %6 %19 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+ CheckValid(env, context.get());
+
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 100
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %22 = OpConstantTrue %17
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %22 %14 %12
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSLessThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %12
+ %13 = OpLabel
+ %19 = OpLoad %6 %8
+ %21 = OpIAdd %6 %19 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader2) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 100
+ %17 = OpTypeBool
+ %28 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %32 = OpVariable %7 Function
+ %40 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSLessThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %25 = OpLoad %6 %19
+ %26 = OpSLessThan %17 %25 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %27 = OpLoad %6 %19
+ %29 = OpIAdd %6 %27 %28
+ OpStore %19 %29
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %30 = OpLoad %6 %8
+ %31 = OpIAdd %6 %30 %28
+ OpStore %8 %31
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %32 %9
+ OpBranch %33
+ %33 = OpLabel
+ OpLoopMerge %35 %36 None
+ OpBranch %37
+ %37 = OpLabel
+ %38 = OpLoad %6 %32
+ %39 = OpSLessThan %17 %38 %16
+ OpBranchConditional %39 %34 %35
+ %34 = OpLabel
+ OpStore %40 %9
+ OpBranch %41
+ %41 = OpLabel
+ OpLoopMerge %43 %44 None
+ OpBranch %45
+ %45 = OpLabel
+ %46 = OpLoad %6 %40
+ %47 = OpSLessThan %17 %46 %16
+ OpBranchConditional %47 %42 %43
+ %42 = OpLabel
+ OpBranch %44
+ %44 = OpLabel
+ %48 = OpLoad %6 %40
+ %49 = OpIAdd %6 %48 %28
+ OpStore %40 %49
+ OpBranch %41
+ %43 = OpLabel
+ OpBranch %36
+ %36 = OpLabel
+ %50 = OpLoad %6 %32
+ %51 = OpIAdd %6 %50 %28
+ OpStore %32 %51
+ OpBranch %33
+ %35 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(4, ops.size());
+
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 100
+ %17 = OpTypeBool
+ %28 = OpConstant %6 1
+ %52 = OpConstantTrue %17
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %32 = OpVariable %7 Function
+ %40 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %52 %14 %12
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSLessThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %25 = OpLoad %6 %19
+ %26 = OpSLessThan %17 %25 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %27 = OpLoad %6 %19
+ %29 = OpIAdd %6 %27 %28
+ OpStore %19 %29
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %12
+ %13 = OpLabel
+ %30 = OpLoad %6 %8
+ %31 = OpIAdd %6 %30 %28
+ OpStore %8 %31
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %32 %9
+ OpBranch %33
+ %33 = OpLabel
+ OpLoopMerge %35 %36 None
+ OpBranch %37
+ %37 = OpLabel
+ %38 = OpLoad %6 %32
+ %39 = OpSLessThan %17 %38 %16
+ OpBranchConditional %39 %34 %35
+ %34 = OpLabel
+ OpStore %40 %9
+ OpBranch %41
+ %41 = OpLabel
+ OpLoopMerge %43 %44 None
+ OpBranch %45
+ %45 = OpLabel
+ %46 = OpLoad %6 %40
+ %47 = OpSLessThan %17 %46 %16
+ OpBranchConditional %47 %42 %43
+ %42 = OpLabel
+ OpBranch %44
+ %44 = OpLabel
+ %48 = OpLoad %6 %40
+ %49 = OpIAdd %6 %48 %28
+ OpStore %40 %49
+ OpBranch %41
+ %43 = OpLabel
+ OpBranch %36
+ %36 = OpLabel
+ %50 = OpLoad %6 %32
+ %51 = OpIAdd %6 %50 %28
+ OpStore %32 %51
+ OpBranch %33
+ %35 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+
+ ASSERT_TRUE(ops[1]->PreconditionHolds());
+ ops[1]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_1 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 100
+ %17 = OpTypeBool
+ %28 = OpConstant %6 1
+ %52 = OpConstantTrue %17
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %32 = OpVariable %7 Function
+ %40 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %52 %14 %12
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSLessThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ OpSelectionMerge %22 None
+ OpBranchConditional %52 %24 %22
+ %24 = OpLabel
+ %25 = OpLoad %6 %19
+ %26 = OpSLessThan %17 %25 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ OpBranch %22
+ %23 = OpLabel
+ %27 = OpLoad %6 %19
+ %29 = OpIAdd %6 %27 %28
+ OpStore %19 %29
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %12
+ %13 = OpLabel
+ %30 = OpLoad %6 %8
+ %31 = OpIAdd %6 %30 %28
+ OpStore %8 %31
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %32 %9
+ OpBranch %33
+ %33 = OpLabel
+ OpLoopMerge %35 %36 None
+ OpBranch %37
+ %37 = OpLabel
+ %38 = OpLoad %6 %32
+ %39 = OpSLessThan %17 %38 %16
+ OpBranchConditional %39 %34 %35
+ %34 = OpLabel
+ OpStore %40 %9
+ OpBranch %41
+ %41 = OpLabel
+ OpLoopMerge %43 %44 None
+ OpBranch %45
+ %45 = OpLabel
+ %46 = OpLoad %6 %40
+ %47 = OpSLessThan %17 %46 %16
+ OpBranchConditional %47 %42 %43
+ %42 = OpLabel
+ OpBranch %44
+ %44 = OpLabel
+ %48 = OpLoad %6 %40
+ %49 = OpIAdd %6 %48 %28
+ OpStore %40 %49
+ OpBranch %41
+ %43 = OpLabel
+ OpBranch %36
+ %36 = OpLabel
+ %50 = OpLoad %6 %32
+ %51 = OpIAdd %6 %50 %28
+ OpStore %32 %51
+ OpBranch %33
+ %35 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_1, context.get());
+
+ ASSERT_TRUE(ops[2]->PreconditionHolds());
+ ops[2]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_2 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 100
+ %17 = OpTypeBool
+ %28 = OpConstant %6 1
+ %52 = OpConstantTrue %17
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %32 = OpVariable %7 Function
+ %40 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %52 %14 %12
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSLessThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ OpSelectionMerge %22 None
+ OpBranchConditional %52 %24 %22
+ %24 = OpLabel
+ %25 = OpLoad %6 %19
+ %26 = OpSLessThan %17 %25 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ OpBranch %22
+ %23 = OpLabel
+ %27 = OpLoad %6 %19
+ %29 = OpIAdd %6 %27 %28
+ OpStore %19 %29
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %12
+ %13 = OpLabel
+ %30 = OpLoad %6 %8
+ %31 = OpIAdd %6 %30 %28
+ OpStore %8 %31
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %32 %9
+ OpBranch %33
+ %33 = OpLabel
+ OpSelectionMerge %35 None
+ OpBranchConditional %52 %37 %35
+ %37 = OpLabel
+ %38 = OpLoad %6 %32
+ %39 = OpSLessThan %17 %38 %16
+ OpBranchConditional %39 %34 %35
+ %34 = OpLabel
+ OpStore %40 %9
+ OpBranch %41
+ %41 = OpLabel
+ OpLoopMerge %43 %44 None
+ OpBranch %45
+ %45 = OpLabel
+ %46 = OpLoad %6 %40
+ %47 = OpSLessThan %17 %46 %16
+ OpBranchConditional %47 %42 %43
+ %42 = OpLabel
+ OpBranch %44
+ %44 = OpLabel
+ %48 = OpLoad %6 %40
+ %49 = OpIAdd %6 %48 %28
+ OpStore %40 %49
+ OpBranch %41
+ %43 = OpLabel
+ OpBranch %35
+ %36 = OpLabel
+ %50 = OpLoad %6 %32
+ %51 = OpIAdd %6 %50 %28
+ OpStore %32 %51
+ OpBranch %33
+ %35 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_2, context.get());
+
+ ASSERT_TRUE(ops[3]->PreconditionHolds());
+ ops[3]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_3 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 100
+ %17 = OpTypeBool
+ %28 = OpConstant %6 1
+ %52 = OpConstantTrue %17
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %32 = OpVariable %7 Function
+ %40 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %52 %14 %12
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSLessThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ OpSelectionMerge %22 None
+ OpBranchConditional %52 %24 %22
+ %24 = OpLabel
+ %25 = OpLoad %6 %19
+ %26 = OpSLessThan %17 %25 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ OpBranch %22
+ %23 = OpLabel
+ %27 = OpLoad %6 %19
+ %29 = OpIAdd %6 %27 %28
+ OpStore %19 %29
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %12
+ %13 = OpLabel
+ %30 = OpLoad %6 %8
+ %31 = OpIAdd %6 %30 %28
+ OpStore %8 %31
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %32 %9
+ OpBranch %33
+ %33 = OpLabel
+ OpSelectionMerge %35 None
+ OpBranchConditional %52 %37 %35
+ %37 = OpLabel
+ %38 = OpLoad %6 %32
+ %39 = OpSLessThan %17 %38 %16
+ OpBranchConditional %39 %34 %35
+ %34 = OpLabel
+ OpStore %40 %9
+ OpBranch %41
+ %41 = OpLabel
+ OpSelectionMerge %43 None
+ OpBranchConditional %52 %45 %43
+ %45 = OpLabel
+ %46 = OpLoad %6 %40
+ %47 = OpSLessThan %17 %46 %16
+ OpBranchConditional %47 %42 %43
+ %42 = OpLabel
+ OpBranch %43
+ %44 = OpLabel
+ %48 = OpLoad %6 %40
+ %49 = OpIAdd %6 %48 %28
+ OpStore %40 %49
+ OpBranch %41
+ %43 = OpLabel
+ OpBranch %35
+ %36 = OpLabel
+ %50 = OpLoad %6 %32
+ %51 = OpIAdd %6 %50 %28
+ OpStore %32 %51
+ OpBranch %33
+ %35 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_3, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader3) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 10
+ %16 = OpConstant %6 0
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %23 = OpConstant %6 3
+ %40 = OpConstant %6 5
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSGreaterThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %19 = OpLoad %6 %8
+ %21 = OpISub %6 %19 %20
+ OpStore %8 %21
+ %22 = OpLoad %6 %8
+ %24 = OpSLessThan %17 %22 %23
+ OpSelectionMerge %26 None
+ OpBranchConditional %24 %25 %26
+ %25 = OpLabel
+ OpBranch %13
+ %26 = OpLabel
+ OpBranch %28
+ %28 = OpLabel
+ OpLoopMerge %30 %31 None
+ OpBranch %29
+ %29 = OpLabel
+ %32 = OpLoad %6 %8
+ %33 = OpISub %6 %32 %20
+ OpStore %8 %33
+ %34 = OpLoad %6 %8
+ %35 = OpIEqual %17 %34 %20
+ OpSelectionMerge %37 None
+ OpBranchConditional %35 %36 %37
+ %36 = OpLabel
+ OpReturn ; This return spoils everything: it means the merge does not post-dominate the header.
+ %37 = OpLabel
+ OpBranch %31
+ %31 = OpLabel
+ %39 = OpLoad %6 %8
+ %41 = OpSGreaterThan %17 %39 %40
+ OpBranchConditional %41 %28 %30
+ %30 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(0, ops.size());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader4) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %6 %7
+ %13 = OpConstant %6 0
+ %22 = OpTypeBool
+ %25 = OpConstant %6 1
+ %39 = OpConstant %6 100
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %45 = OpVariable %7 Function
+ %46 = OpVariable %7 Function
+ %47 = OpVariable %7 Function
+ %32 = OpVariable %7 Function
+ %42 = OpVariable %7 Function
+ OpStore %32 %13
+ OpBranch %33
+ %33 = OpLabel
+ OpLoopMerge %35 %36 None
+ OpBranch %37
+ %37 = OpLabel
+ %38 = OpLoad %6 %32
+ %40 = OpSLessThan %22 %38 %39
+ OpBranchConditional %40 %34 %35
+ %34 = OpLabel
+ OpBranch %36
+ %36 = OpLabel
+ %41 = OpLoad %6 %32
+ OpStore %42 %25
+ OpStore %45 %13
+ OpStore %46 %13
+ OpBranch %48
+ %48 = OpLabel
+ OpLoopMerge %49 %50 None
+ OpBranch %51
+ %51 = OpLabel
+ %52 = OpLoad %6 %46
+ %53 = OpLoad %6 %42
+ %54 = OpSLessThan %22 %52 %53
+ OpBranchConditional %54 %55 %49
+ %55 = OpLabel
+ %56 = OpLoad %6 %45
+ %57 = OpIAdd %6 %56 %25
+ OpStore %45 %57
+ OpBranch %50
+ %50 = OpLabel
+ %58 = OpLoad %6 %46
+ %59 = OpIAdd %6 %58 %25
+ OpStore %46 %59
+ OpBranch %48
+ %49 = OpLabel
+ %60 = OpLoad %6 %45
+ OpStore %47 %60
+ %43 = OpLoad %6 %47
+ %44 = OpIAdd %6 %41 %43
+ OpStore %32 %44
+ OpBranch %33
+ %35 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ // Initially there are two opportunities.
+ ASSERT_EQ(2, ops.size());
+
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %6 %7
+ %13 = OpConstant %6 0
+ %22 = OpTypeBool
+ %25 = OpConstant %6 1
+ %39 = OpConstant %6 100
+ %61 = OpConstantTrue %22
+ %62 = OpUndef %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %45 = OpVariable %7 Function
+ %46 = OpVariable %7 Function
+ %47 = OpVariable %7 Function
+ %32 = OpVariable %7 Function
+ %42 = OpVariable %7 Function
+ OpStore %32 %13
+ OpBranch %33
+ %33 = OpLabel
+ OpSelectionMerge %35 None
+ OpBranchConditional %61 %37 %35
+ %37 = OpLabel
+ %38 = OpLoad %6 %32
+ %40 = OpSLessThan %22 %38 %39
+ OpBranchConditional %40 %34 %35
+ %34 = OpLabel
+ OpBranch %35
+ %36 = OpLabel
+ %41 = OpLoad %6 %32
+ OpStore %42 %25
+ OpStore %45 %13
+ OpStore %46 %13
+ OpBranch %48
+ %48 = OpLabel
+ OpLoopMerge %49 %50 None
+ OpBranch %51
+ %51 = OpLabel
+ %52 = OpLoad %6 %46
+ %53 = OpLoad %6 %42
+ %54 = OpSLessThan %22 %52 %53
+ OpBranchConditional %54 %55 %49
+ %55 = OpLabel
+ %56 = OpLoad %6 %45
+ %57 = OpIAdd %6 %56 %25
+ OpStore %45 %57
+ OpBranch %50
+ %50 = OpLabel
+ %58 = OpLoad %6 %46
+ %59 = OpIAdd %6 %58 %25
+ OpStore %46 %59
+ OpBranch %48
+ %49 = OpLabel
+ %60 = OpLoad %6 %45
+ OpStore %47 %60
+ %43 = OpLoad %6 %47
+ %44 = OpIAdd %6 %62 %43
+ OpStore %32 %44
+ OpBranch %33
+ %35 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+
+ // Applying the first opportunity has killed the second opportunity, because
+ // there was a loop embedded in the continue target of the loop we have just
+ // eliminated; the continue-embedded loop is now unreachable.
+ ASSERT_FALSE(ops[1]->PreconditionHolds());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, ConditionalBreak1) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeBool
+ %11 = OpConstantFalse %10
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %6
+ %6 = OpLabel
+ OpLoopMerge %8 %9 None
+ OpBranch %7
+ %7 = OpLabel
+ OpSelectionMerge %13 None
+ OpBranchConditional %11 %12 %13
+ %12 = OpLabel
+ OpBranch %8
+ %13 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpBranchConditional %11 %6 %8
+ %8 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeBool
+ %11 = OpConstantFalse %10
+ %14 = OpConstantTrue %10
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %6
+ %6 = OpLabel
+ OpSelectionMerge %8 None
+ OpBranchConditional %14 %7 %8
+ %7 = OpLabel
+ OpSelectionMerge %13 None
+ OpBranchConditional %11 %12 %13
+ %12 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpBranch %8
+ %9 = OpLabel
+ OpBranchConditional %11 %6 %8
+ %8 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, ConditionalBreak2) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeBool
+ %11 = OpConstantFalse %10
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %6
+ %6 = OpLabel
+ OpLoopMerge %8 %9 None
+ OpBranch %7
+ %7 = OpLabel
+ OpSelectionMerge %13 None
+ OpBranchConditional %11 %8 %13
+ %13 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpBranchConditional %11 %6 %8
+ %8 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeBool
+ %11 = OpConstantFalse %10
+ %14 = OpConstantTrue %10
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %6
+ %6 = OpLabel
+ OpSelectionMerge %8 None
+ OpBranchConditional %14 %7 %8
+ %7 = OpLabel
+ OpSelectionMerge %13 None
+ OpBranchConditional %11 %13 %13
+ %13 = OpLabel
+ OpBranch %8
+ %9 = OpLabel
+ OpBranchConditional %11 %6 %8
+ %8 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, UnconditionalBreak) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %6
+ %6 = OpLabel
+ OpLoopMerge %8 %9 None
+ OpBranch %7
+ %7 = OpLabel
+ OpBranch %8
+ %9 = OpLabel
+ OpBranch %6
+ %8 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeBool
+ %11 = OpConstantTrue %10
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %6
+ %6 = OpLabel
+ OpSelectionMerge %8 None
+ OpBranchConditional %11 %7 %8
+ %7 = OpLabel
+ OpBranch %8
+ %9 = OpLabel
+ OpBranch %6
+ %8 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, Complex) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %4 0 Offset 0
+ OpMemberDecorate %4 1 Offset 4
+ OpMemberDecorate %4 2 Offset 8
+ OpMemberDecorate %4 3 Offset 12
+ OpDecorate %4 Block
+ OpDecorate %5 DescriptorSet 0
+ OpDecorate %5 Binding 0
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeBool
+ %9 = OpTypePointer Function %8
+ %10 = OpTypeInt 32 1
+ %4 = OpTypeStruct %10 %10 %10 %10
+ %11 = OpTypePointer Uniform %4
+ %5 = OpVariable %11 Uniform
+ %12 = OpConstant %10 0
+ %13 = OpTypePointer Uniform %10
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 0
+ %16 = OpConstant %10 1
+ %17 = OpConstant %10 2
+ %18 = OpConstant %10 3
+ %19 = OpTypePointer Function %10
+ %20 = OpConstantFalse %8
+ %21 = OpTypeFloat 32
+ %22 = OpTypeVector %21 4
+ %23 = OpTypePointer Output %22
+ %3 = OpVariable %23 Output
+ %2 = OpFunction %6 None %7
+ %24 = OpLabel
+ %25 = OpVariable %9 Function
+ %26 = OpVariable %9 Function
+ %27 = OpVariable %9 Function
+ %28 = OpVariable %9 Function
+ %29 = OpVariable %9 Function
+ %30 = OpVariable %19 Function
+ %31 = OpAccessChain %13 %5 %12
+ %32 = OpLoad %10 %31
+ %33 = OpINotEqual %8 %32 %15
+ OpStore %25 %33
+ %34 = OpAccessChain %13 %5 %16
+ %35 = OpLoad %10 %34
+ %36 = OpINotEqual %8 %35 %15
+ OpStore %26 %36
+ %37 = OpAccessChain %13 %5 %17
+ %38 = OpLoad %10 %37
+ %39 = OpINotEqual %8 %38 %15
+ OpStore %27 %39
+ %40 = OpAccessChain %13 %5 %18
+ %41 = OpLoad %10 %40
+ %42 = OpINotEqual %8 %41 %15
+ OpStore %28 %42
+ %43 = OpLoad %8 %25
+ OpStore %29 %43
+ OpStore %30 %12
+ OpBranch %44
+ %44 = OpLabel
+ OpLoopMerge %45 %46 None
+ OpBranch %47
+ %47 = OpLabel
+ %48 = OpLoad %8 %29
+ OpBranchConditional %48 %49 %45
+ %49 = OpLabel
+ %50 = OpLoad %8 %25
+ OpSelectionMerge %51 None
+ OpBranchConditional %50 %52 %51
+ %52 = OpLabel
+ %53 = OpLoad %8 %26
+ OpStore %29 %53
+ %54 = OpLoad %10 %30
+ %55 = OpIAdd %10 %54 %16
+ OpStore %30 %55
+ OpBranch %51
+ %51 = OpLabel
+ %56 = OpLoad %8 %26
+ OpSelectionMerge %57 None
+ OpBranchConditional %56 %58 %57
+ %58 = OpLabel
+ %59 = OpLoad %10 %30
+ %60 = OpIAdd %10 %59 %16
+ OpStore %30 %60
+ %61 = OpLoad %8 %29
+ %62 = OpLoad %8 %25
+ %63 = OpLogicalOr %8 %61 %62
+ OpStore %29 %63
+ %64 = OpLoad %8 %27
+ OpSelectionMerge %65 None
+ OpBranchConditional %64 %66 %65
+ %66 = OpLabel
+ %67 = OpLoad %10 %30
+ %68 = OpIAdd %10 %67 %17
+ OpStore %30 %68
+ %69 = OpLoad %8 %29
+ %70 = OpLogicalNot %8 %69
+ OpStore %29 %70
+ OpBranch %46
+ %65 = OpLabel
+ %71 = OpLoad %8 %29
+ %72 = OpLogicalOr %8 %71 %20
+ OpStore %29 %72
+ OpBranch %46
+ %57 = OpLabel
+ OpBranch %73
+ %73 = OpLabel
+ OpLoopMerge %74 %75 None
+ OpBranch %76
+ %76 = OpLabel
+ %77 = OpLoad %8 %28
+ OpSelectionMerge %78 None
+ OpBranchConditional %77 %79 %80
+ %79 = OpLabel
+ %81 = OpLoad %10 %30
+ OpSelectionMerge %82 None
+ OpSwitch %81 %83 1 %84 2 %85
+ %83 = OpLabel
+ OpBranch %82
+ %84 = OpLabel
+ %86 = OpLoad %8 %29
+ %87 = OpSelect %10 %86 %16 %17
+ %88 = OpLoad %10 %30
+ %89 = OpIAdd %10 %88 %87
+ OpStore %30 %89
+ OpBranch %82
+ %85 = OpLabel
+ OpBranch %75
+ %82 = OpLabel
+ %90 = OpLoad %8 %27
+ OpSelectionMerge %91 None
+ OpBranchConditional %90 %92 %91
+ %92 = OpLabel
+ OpBranch %75
+ %91 = OpLabel
+ OpBranch %78
+ %80 = OpLabel
+ OpBranch %74
+ %78 = OpLabel
+ OpBranch %75
+ %75 = OpLabel
+ %93 = OpLoad %8 %29
+ OpBranchConditional %93 %73 %74
+ %74 = OpLabel
+ OpBranch %46
+ %46 = OpLabel
+ OpBranch %44
+ %45 = OpLabel
+ %94 = OpLoad %10 %30
+ %95 = OpConvertSToF %21 %94
+ %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+ OpStore %3 %96
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ ASSERT_EQ(2, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %4 0 Offset 0
+ OpMemberDecorate %4 1 Offset 4
+ OpMemberDecorate %4 2 Offset 8
+ OpMemberDecorate %4 3 Offset 12
+ OpDecorate %4 Block
+ OpDecorate %5 DescriptorSet 0
+ OpDecorate %5 Binding 0
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeBool
+ %9 = OpTypePointer Function %8
+ %10 = OpTypeInt 32 1
+ %4 = OpTypeStruct %10 %10 %10 %10
+ %11 = OpTypePointer Uniform %4
+ %5 = OpVariable %11 Uniform
+ %12 = OpConstant %10 0
+ %13 = OpTypePointer Uniform %10
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 0
+ %16 = OpConstant %10 1
+ %17 = OpConstant %10 2
+ %18 = OpConstant %10 3
+ %19 = OpTypePointer Function %10
+ %20 = OpConstantFalse %8
+ %21 = OpTypeFloat 32
+ %22 = OpTypeVector %21 4
+ %23 = OpTypePointer Output %22
+ %3 = OpVariable %23 Output
+ %97 = OpConstantTrue %8
+ %2 = OpFunction %6 None %7
+ %24 = OpLabel
+ %25 = OpVariable %9 Function
+ %26 = OpVariable %9 Function
+ %27 = OpVariable %9 Function
+ %28 = OpVariable %9 Function
+ %29 = OpVariable %9 Function
+ %30 = OpVariable %19 Function
+ %31 = OpAccessChain %13 %5 %12
+ %32 = OpLoad %10 %31
+ %33 = OpINotEqual %8 %32 %15
+ OpStore %25 %33
+ %34 = OpAccessChain %13 %5 %16
+ %35 = OpLoad %10 %34
+ %36 = OpINotEqual %8 %35 %15
+ OpStore %26 %36
+ %37 = OpAccessChain %13 %5 %17
+ %38 = OpLoad %10 %37
+ %39 = OpINotEqual %8 %38 %15
+ OpStore %27 %39
+ %40 = OpAccessChain %13 %5 %18
+ %41 = OpLoad %10 %40
+ %42 = OpINotEqual %8 %41 %15
+ OpStore %28 %42
+ %43 = OpLoad %8 %25
+ OpStore %29 %43
+ OpStore %30 %12
+ OpBranch %44
+ %44 = OpLabel
+ OpSelectionMerge %45 None ; Was OpLoopMerge %45 %46 None
+ OpBranchConditional %97 %47 %45 ; Was OpBranch %47
+ %47 = OpLabel
+ %48 = OpLoad %8 %29
+ OpBranchConditional %48 %49 %45
+ %49 = OpLabel
+ %50 = OpLoad %8 %25
+ OpSelectionMerge %51 None
+ OpBranchConditional %50 %52 %51
+ %52 = OpLabel
+ %53 = OpLoad %8 %26
+ OpStore %29 %53
+ %54 = OpLoad %10 %30
+ %55 = OpIAdd %10 %54 %16
+ OpStore %30 %55
+ OpBranch %51
+ %51 = OpLabel
+ %56 = OpLoad %8 %26
+ OpSelectionMerge %57 None
+ OpBranchConditional %56 %58 %57
+ %58 = OpLabel
+ %59 = OpLoad %10 %30
+ %60 = OpIAdd %10 %59 %16
+ OpStore %30 %60
+ %61 = OpLoad %8 %29
+ %62 = OpLoad %8 %25
+ %63 = OpLogicalOr %8 %61 %62
+ OpStore %29 %63
+ %64 = OpLoad %8 %27
+ OpSelectionMerge %65 None
+ OpBranchConditional %64 %66 %65
+ %66 = OpLabel
+ %67 = OpLoad %10 %30
+ %68 = OpIAdd %10 %67 %17
+ OpStore %30 %68
+ %69 = OpLoad %8 %29
+ %70 = OpLogicalNot %8 %69
+ OpStore %29 %70
+ OpBranch %65 ; Was OpBranch %46
+ %65 = OpLabel
+ %71 = OpLoad %8 %29
+ %72 = OpLogicalOr %8 %71 %20
+ OpStore %29 %72
+ OpBranch %57 ; Was OpBranch %46
+ %57 = OpLabel
+ OpBranch %73
+ %73 = OpLabel
+ OpLoopMerge %74 %75 None
+ OpBranch %76
+ %76 = OpLabel
+ %77 = OpLoad %8 %28
+ OpSelectionMerge %78 None
+ OpBranchConditional %77 %79 %80
+ %79 = OpLabel
+ %81 = OpLoad %10 %30
+ OpSelectionMerge %82 None
+ OpSwitch %81 %83 1 %84 2 %85
+ %83 = OpLabel
+ OpBranch %82
+ %84 = OpLabel
+ %86 = OpLoad %8 %29
+ %87 = OpSelect %10 %86 %16 %17
+ %88 = OpLoad %10 %30
+ %89 = OpIAdd %10 %88 %87
+ OpStore %30 %89
+ OpBranch %82
+ %85 = OpLabel
+ OpBranch %75
+ %82 = OpLabel
+ %90 = OpLoad %8 %27
+ OpSelectionMerge %91 None
+ OpBranchConditional %90 %92 %91
+ %92 = OpLabel
+ OpBranch %75
+ %91 = OpLabel
+ OpBranch %78
+ %80 = OpLabel
+ OpBranch %74
+ %78 = OpLabel
+ OpBranch %75
+ %75 = OpLabel
+ %93 = OpLoad %8 %29
+ OpBranchConditional %93 %73 %74
+ %74 = OpLabel
+ OpBranch %45 ; Was OpBranch %46
+ %46 = OpLabel
+ OpBranch %44
+ %45 = OpLabel
+ %94 = OpLoad %10 %30
+ %95 = OpConvertSToF %21 %94
+ %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+ OpStore %3 %96
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+ ASSERT_TRUE(ops[1]->PreconditionHolds());
+ ops[1]->TryToApply();
+ CheckValid(env, context.get());
+
+ std::string after_op_1 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %4 0 Offset 0
+ OpMemberDecorate %4 1 Offset 4
+ OpMemberDecorate %4 2 Offset 8
+ OpMemberDecorate %4 3 Offset 12
+ OpDecorate %4 Block
+ OpDecorate %5 DescriptorSet 0
+ OpDecorate %5 Binding 0
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeBool
+ %9 = OpTypePointer Function %8
+ %10 = OpTypeInt 32 1
+ %4 = OpTypeStruct %10 %10 %10 %10
+ %11 = OpTypePointer Uniform %4
+ %5 = OpVariable %11 Uniform
+ %12 = OpConstant %10 0
+ %13 = OpTypePointer Uniform %10
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 0
+ %16 = OpConstant %10 1
+ %17 = OpConstant %10 2
+ %18 = OpConstant %10 3
+ %19 = OpTypePointer Function %10
+ %20 = OpConstantFalse %8
+ %21 = OpTypeFloat 32
+ %22 = OpTypeVector %21 4
+ %23 = OpTypePointer Output %22
+ %3 = OpVariable %23 Output
+ %97 = OpConstantTrue %8
+ %2 = OpFunction %6 None %7
+ %24 = OpLabel
+ %25 = OpVariable %9 Function
+ %26 = OpVariable %9 Function
+ %27 = OpVariable %9 Function
+ %28 = OpVariable %9 Function
+ %29 = OpVariable %9 Function
+ %30 = OpVariable %19 Function
+ %31 = OpAccessChain %13 %5 %12
+ %32 = OpLoad %10 %31
+ %33 = OpINotEqual %8 %32 %15
+ OpStore %25 %33
+ %34 = OpAccessChain %13 %5 %16
+ %35 = OpLoad %10 %34
+ %36 = OpINotEqual %8 %35 %15
+ OpStore %26 %36
+ %37 = OpAccessChain %13 %5 %17
+ %38 = OpLoad %10 %37
+ %39 = OpINotEqual %8 %38 %15
+ OpStore %27 %39
+ %40 = OpAccessChain %13 %5 %18
+ %41 = OpLoad %10 %40
+ %42 = OpINotEqual %8 %41 %15
+ OpStore %28 %42
+ %43 = OpLoad %8 %25
+ OpStore %29 %43
+ OpStore %30 %12
+ OpBranch %44
+ %44 = OpLabel
+ OpSelectionMerge %45 None ; Was OpLoopMerge %45 %46 None
+ OpBranchConditional %97 %47 %45 ; Was OpBranch %47
+ %47 = OpLabel
+ %48 = OpLoad %8 %29
+ OpBranchConditional %48 %49 %45
+ %49 = OpLabel
+ %50 = OpLoad %8 %25
+ OpSelectionMerge %51 None
+ OpBranchConditional %50 %52 %51
+ %52 = OpLabel
+ %53 = OpLoad %8 %26
+ OpStore %29 %53
+ %54 = OpLoad %10 %30
+ %55 = OpIAdd %10 %54 %16
+ OpStore %30 %55
+ OpBranch %51
+ %51 = OpLabel
+ %56 = OpLoad %8 %26
+ OpSelectionMerge %57 None
+ OpBranchConditional %56 %58 %57
+ %58 = OpLabel
+ %59 = OpLoad %10 %30
+ %60 = OpIAdd %10 %59 %16
+ OpStore %30 %60
+ %61 = OpLoad %8 %29
+ %62 = OpLoad %8 %25
+ %63 = OpLogicalOr %8 %61 %62
+ OpStore %29 %63
+ %64 = OpLoad %8 %27
+ OpSelectionMerge %65 None
+ OpBranchConditional %64 %66 %65
+ %66 = OpLabel
+ %67 = OpLoad %10 %30
+ %68 = OpIAdd %10 %67 %17
+ OpStore %30 %68
+ %69 = OpLoad %8 %29
+ %70 = OpLogicalNot %8 %69
+ OpStore %29 %70
+ OpBranch %65 ; Was OpBranch %46
+ %65 = OpLabel
+ %71 = OpLoad %8 %29
+ %72 = OpLogicalOr %8 %71 %20
+ OpStore %29 %72
+ OpBranch %57 ; Was OpBranch %46
+ %57 = OpLabel
+ OpBranch %73
+ %73 = OpLabel
+ OpSelectionMerge %74 None ; Was OpLoopMerge %74 %75 None
+ OpBranchConditional %97 %76 %74 ; Was OpBranch %76
+ %76 = OpLabel
+ %77 = OpLoad %8 %28
+ OpSelectionMerge %78 None
+ OpBranchConditional %77 %79 %80
+ %79 = OpLabel
+ %81 = OpLoad %10 %30
+ OpSelectionMerge %82 None
+ OpSwitch %81 %83 1 %84 2 %85
+ %83 = OpLabel
+ OpBranch %82
+ %84 = OpLabel
+ %86 = OpLoad %8 %29
+ %87 = OpSelect %10 %86 %16 %17
+ %88 = OpLoad %10 %30
+ %89 = OpIAdd %10 %88 %87
+ OpStore %30 %89
+ OpBranch %82
+ %85 = OpLabel
+ OpBranch %82
+ %82 = OpLabel
+ %90 = OpLoad %8 %27
+ OpSelectionMerge %91 None
+ OpBranchConditional %90 %92 %91
+ %92 = OpLabel
+ OpBranch %91
+ %91 = OpLabel
+ OpBranch %78
+ %80 = OpLabel
+ OpBranch %78 ; Was OpBranch %74
+ %78 = OpLabel
+ OpBranch %74
+ %75 = OpLabel
+ %93 = OpLoad %8 %29
+ OpBranchConditional %93 %73 %74
+ %74 = OpLabel
+ OpBranch %45 ; Was OpBranch %46
+ %46 = OpLabel
+ OpBranch %44
+ %45 = OpLabel
+ %94 = OpLoad %10 %30
+ %95 = OpConvertSToF %21 %94
+ %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+ OpStore %3 %96
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, ComplexOptimized) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %4 0 Offset 0
+ OpMemberDecorate %4 1 Offset 4
+ OpMemberDecorate %4 2 Offset 8
+ OpMemberDecorate %4 3 Offset 12
+ OpDecorate %4 Block
+ OpDecorate %5 DescriptorSet 0
+ OpDecorate %5 Binding 0
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeBool
+ %10 = OpTypeInt 32 1
+ %4 = OpTypeStruct %10 %10 %10 %10
+ %11 = OpTypePointer Uniform %4
+ %5 = OpVariable %11 Uniform
+ %12 = OpConstant %10 0
+ %13 = OpTypePointer Uniform %10
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 0
+ %16 = OpConstant %10 1
+ %17 = OpConstant %10 2
+ %18 = OpConstant %10 3
+ %20 = OpConstantFalse %8
+ %21 = OpTypeFloat 32
+ %22 = OpTypeVector %21 4
+ %23 = OpTypePointer Output %22
+ %3 = OpVariable %23 Output
+ %2 = OpFunction %6 None %7
+ %24 = OpLabel
+ %31 = OpAccessChain %13 %5 %12
+ %32 = OpLoad %10 %31
+ %33 = OpINotEqual %8 %32 %15
+ %34 = OpAccessChain %13 %5 %16
+ %35 = OpLoad %10 %34
+ %36 = OpINotEqual %8 %35 %15
+ %37 = OpAccessChain %13 %5 %17
+ %38 = OpLoad %10 %37
+ %39 = OpINotEqual %8 %38 %15
+ %40 = OpAccessChain %13 %5 %18
+ %41 = OpLoad %10 %40
+ %42 = OpINotEqual %8 %41 %15
+ OpBranch %44
+ %44 = OpLabel
+ %98 = OpPhi %10 %12 %24 %107 %46
+ %97 = OpPhi %8 %33 %24 %105 %46
+ OpLoopMerge %45 %46 None
+ OpBranchConditional %97 %49 %45
+ %49 = OpLabel
+ OpSelectionMerge %51 None
+ OpBranchConditional %33 %52 %51
+ %52 = OpLabel
+ %55 = OpIAdd %10 %98 %16
+ OpBranch %51
+ %51 = OpLabel
+ %100 = OpPhi %10 %98 %49 %55 %52
+ %113 = OpSelect %8 %33 %36 %97
+ OpSelectionMerge %57 None
+ OpBranchConditional %36 %58 %57
+ %58 = OpLabel
+ %60 = OpIAdd %10 %100 %16
+ %63 = OpLogicalOr %8 %113 %33
+ OpSelectionMerge %65 None
+ OpBranchConditional %39 %66 %65
+ %66 = OpLabel
+ %68 = OpIAdd %10 %100 %18
+ %70 = OpLogicalNot %8 %63
+ OpBranch %46
+ %65 = OpLabel
+ %72 = OpLogicalOr %8 %63 %20
+ OpBranch %46
+ %57 = OpLabel
+ OpBranch %73
+ %73 = OpLabel
+ %99 = OpPhi %10 %100 %57 %109 %75
+ OpLoopMerge %74 %75 None
+ OpBranch %76
+ %76 = OpLabel
+ OpSelectionMerge %78 None
+ OpBranchConditional %42 %79 %80
+ %79 = OpLabel
+ OpSelectionMerge %82 None
+ OpSwitch %99 %83 1 %84 2 %85
+ %83 = OpLabel
+ OpBranch %82
+ %84 = OpLabel
+ %87 = OpSelect %10 %113 %16 %17
+ %89 = OpIAdd %10 %99 %87
+ OpBranch %82
+ %85 = OpLabel
+ OpBranch %75
+ %82 = OpLabel
+ %110 = OpPhi %10 %99 %83 %89 %84
+ OpSelectionMerge %91 None
+ OpBranchConditional %39 %92 %91
+ %92 = OpLabel
+ OpBranch %75
+ %91 = OpLabel
+ OpBranch %78
+ %80 = OpLabel
+ OpBranch %74
+ %78 = OpLabel
+ OpBranch %75
+ %75 = OpLabel
+ %109 = OpPhi %10 %99 %85 %110 %92 %110 %78
+ OpBranchConditional %113 %73 %74
+ %74 = OpLabel
+ %108 = OpPhi %10 %99 %80 %109 %75
+ OpBranch %46
+ %46 = OpLabel
+ %107 = OpPhi %10 %68 %66 %60 %65 %108 %74
+ %105 = OpPhi %8 %70 %66 %72 %65 %113 %74
+ OpBranch %44
+ %45 = OpLabel
+ %95 = OpConvertSToF %21 %98
+ %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+ OpStore %3 %96
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ ASSERT_EQ(2, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %4 0 Offset 0
+ OpMemberDecorate %4 1 Offset 4
+ OpMemberDecorate %4 2 Offset 8
+ OpMemberDecorate %4 3 Offset 12
+ OpDecorate %4 Block
+ OpDecorate %5 DescriptorSet 0
+ OpDecorate %5 Binding 0
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeBool
+ %10 = OpTypeInt 32 1
+ %4 = OpTypeStruct %10 %10 %10 %10
+ %11 = OpTypePointer Uniform %4
+ %5 = OpVariable %11 Uniform
+ %12 = OpConstant %10 0
+ %13 = OpTypePointer Uniform %10
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 0
+ %16 = OpConstant %10 1
+ %17 = OpConstant %10 2
+ %18 = OpConstant %10 3
+ %20 = OpConstantFalse %8
+ %21 = OpTypeFloat 32
+ %22 = OpTypeVector %21 4
+ %23 = OpTypePointer Output %22
+ %3 = OpVariable %23 Output
+ %114 = OpUndef %10
+ %115 = OpUndef %8
+ %2 = OpFunction %6 None %7
+ %24 = OpLabel
+ %31 = OpAccessChain %13 %5 %12
+ %32 = OpLoad %10 %31
+ %33 = OpINotEqual %8 %32 %15
+ %34 = OpAccessChain %13 %5 %16
+ %35 = OpLoad %10 %34
+ %36 = OpINotEqual %8 %35 %15
+ %37 = OpAccessChain %13 %5 %17
+ %38 = OpLoad %10 %37
+ %39 = OpINotEqual %8 %38 %15
+ %40 = OpAccessChain %13 %5 %18
+ %41 = OpLoad %10 %40
+ %42 = OpINotEqual %8 %41 %15
+ OpBranch %44
+ %44 = OpLabel
+ %98 = OpPhi %10 %12 %24 %114 %46
+ %97 = OpPhi %8 %33 %24 %115 %46
+ OpSelectionMerge %45 None ; Was OpLoopMerge %45 %46 None
+ OpBranchConditional %97 %49 %45
+ %49 = OpLabel
+ OpSelectionMerge %51 None
+ OpBranchConditional %33 %52 %51
+ %52 = OpLabel
+ %55 = OpIAdd %10 %98 %16
+ OpBranch %51
+ %51 = OpLabel
+ %100 = OpPhi %10 %98 %49 %55 %52
+ %113 = OpSelect %8 %33 %36 %97
+ OpSelectionMerge %57 None
+ OpBranchConditional %36 %58 %57
+ %58 = OpLabel
+ %60 = OpIAdd %10 %100 %16
+ %63 = OpLogicalOr %8 %113 %33
+ OpSelectionMerge %65 None
+ OpBranchConditional %39 %66 %65
+ %66 = OpLabel
+ %68 = OpIAdd %10 %100 %18
+ %70 = OpLogicalNot %8 %63
+ OpBranch %65 ; Was OpBranch %46
+ %65 = OpLabel
+ %72 = OpLogicalOr %8 %63 %20
+ OpBranch %57 ; Was OpBranch %46
+ %57 = OpLabel
+ OpBranch %73
+ %73 = OpLabel
+ %99 = OpPhi %10 %100 %57 %109 %75
+ OpLoopMerge %74 %75 None
+ OpBranch %76
+ %76 = OpLabel
+ OpSelectionMerge %78 None
+ OpBranchConditional %42 %79 %80
+ %79 = OpLabel
+ OpSelectionMerge %82 None
+ OpSwitch %99 %83 1 %84 2 %85
+ %83 = OpLabel
+ OpBranch %82
+ %84 = OpLabel
+ %87 = OpSelect %10 %113 %16 %17
+ %89 = OpIAdd %10 %99 %87
+ OpBranch %82
+ %85 = OpLabel
+ OpBranch %75
+ %82 = OpLabel
+ %110 = OpPhi %10 %99 %83 %89 %84
+ OpSelectionMerge %91 None
+ OpBranchConditional %39 %92 %91
+ %92 = OpLabel
+ OpBranch %75
+ %91 = OpLabel
+ OpBranch %78
+ %80 = OpLabel
+ OpBranch %74
+ %78 = OpLabel
+ OpBranch %75
+ %75 = OpLabel
+ %109 = OpPhi %10 %99 %85 %110 %92 %110 %78
+ OpBranchConditional %113 %73 %74
+ %74 = OpLabel
+ %108 = OpPhi %10 %99 %80 %109 %75
+ OpBranch %45 ; Was OpBranch %46
+ %46 = OpLabel
+ %107 = OpPhi %10 ; Was OpPhi %10 %68 %66 %60 %65 %108 %74
+ %105 = OpPhi %8 ; Was OpPhi %8 %70 %66 %72 %65 %113 %74
+ OpBranch %44
+ %45 = OpLabel
+ %95 = OpConvertSToF %21 %98
+ %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+ OpStore %3 %96
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+
+ ASSERT_TRUE(ops[1]->PreconditionHolds());
+ ops[1]->TryToApply();
+ CheckValid(env, context.get());
+ std::string after_op_1 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %4 0 Offset 0
+ OpMemberDecorate %4 1 Offset 4
+ OpMemberDecorate %4 2 Offset 8
+ OpMemberDecorate %4 3 Offset 12
+ OpDecorate %4 Block
+ OpDecorate %5 DescriptorSet 0
+ OpDecorate %5 Binding 0
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeBool
+ %10 = OpTypeInt 32 1
+ %4 = OpTypeStruct %10 %10 %10 %10
+ %11 = OpTypePointer Uniform %4
+ %5 = OpVariable %11 Uniform
+ %12 = OpConstant %10 0
+ %13 = OpTypePointer Uniform %10
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 0
+ %16 = OpConstant %10 1
+ %17 = OpConstant %10 2
+ %18 = OpConstant %10 3
+ %20 = OpConstantFalse %8
+ %21 = OpTypeFloat 32
+ %22 = OpTypeVector %21 4
+ %23 = OpTypePointer Output %22
+ %3 = OpVariable %23 Output
+ %114 = OpUndef %10
+ %115 = OpUndef %8
+ %116 = OpConstantTrue %8
+ %2 = OpFunction %6 None %7
+ %24 = OpLabel
+ %31 = OpAccessChain %13 %5 %12
+ %32 = OpLoad %10 %31
+ %33 = OpINotEqual %8 %32 %15
+ %34 = OpAccessChain %13 %5 %16
+ %35 = OpLoad %10 %34
+ %36 = OpINotEqual %8 %35 %15
+ %37 = OpAccessChain %13 %5 %17
+ %38 = OpLoad %10 %37
+ %39 = OpINotEqual %8 %38 %15
+ %40 = OpAccessChain %13 %5 %18
+ %41 = OpLoad %10 %40
+ %42 = OpINotEqual %8 %41 %15
+ OpBranch %44
+ %44 = OpLabel
+ %98 = OpPhi %10 %12 %24 %114 %46
+ %97 = OpPhi %8 %33 %24 %115 %46
+ OpSelectionMerge %45 None ; Was OpLoopMerge %45 %46 None
+ OpBranchConditional %97 %49 %45
+ %49 = OpLabel
+ OpSelectionMerge %51 None
+ OpBranchConditional %33 %52 %51
+ %52 = OpLabel
+ %55 = OpIAdd %10 %98 %16
+ OpBranch %51
+ %51 = OpLabel
+ %100 = OpPhi %10 %98 %49 %55 %52
+ %113 = OpSelect %8 %33 %36 %97
+ OpSelectionMerge %57 None
+ OpBranchConditional %36 %58 %57
+ %58 = OpLabel
+ %60 = OpIAdd %10 %100 %16
+ %63 = OpLogicalOr %8 %113 %33
+ OpSelectionMerge %65 None
+ OpBranchConditional %39 %66 %65
+ %66 = OpLabel
+ %68 = OpIAdd %10 %100 %18
+ %70 = OpLogicalNot %8 %63
+ OpBranch %65 ; Was OpBranch %46
+ %65 = OpLabel
+ %72 = OpLogicalOr %8 %63 %20
+ OpBranch %57 ; Was OpBranch %46
+ %57 = OpLabel
+ OpBranch %73
+ %73 = OpLabel
+ %99 = OpPhi %10 %100 %57 %114 %75
+ OpSelectionMerge %74 None ; Was OpLoopMerge %74 %75 None
+ OpBranchConditional %116 %76 %74
+ %76 = OpLabel
+ OpSelectionMerge %78 None
+ OpBranchConditional %42 %79 %80
+ %79 = OpLabel
+ OpSelectionMerge %82 None
+ OpSwitch %99 %83 1 %84 2 %85
+ %83 = OpLabel
+ OpBranch %82
+ %84 = OpLabel
+ %87 = OpSelect %10 %113 %16 %17
+ %89 = OpIAdd %10 %99 %87
+ OpBranch %82
+ %85 = OpLabel
+ OpBranch %82 ; Was OpBranch %75
+ %82 = OpLabel
+ %110 = OpPhi %10 %99 %83 %89 %84 %114 %85 ; Was OpPhi %10 %99 %83 %89 %84
+ OpSelectionMerge %91 None
+ OpBranchConditional %39 %92 %91
+ %92 = OpLabel
+ OpBranch %91 ; OpBranch %75
+ %91 = OpLabel
+ OpBranch %78
+ %80 = OpLabel
+ OpBranch %78 ; Was OpBranch %74
+ %78 = OpLabel
+ OpBranch %74 ; Was OpBranch %75
+ %75 = OpLabel
+ %109 = OpPhi %10 ; Was OpPhi %10 %99 %85 %110 %92 %110 %78
+ OpBranchConditional %115 %73 %74
+ %74 = OpLabel
+ %108 = OpPhi %10 %114 %75 %114 %78 %114 %73 ; Was OpPhi %10 %99 %80 %109 %75
+ OpBranch %45 ; Was OpBranch %46
+ %46 = OpLabel
+ %107 = OpPhi %10 ; Was OpPhi %10 %68 %66 %60 %65 %108 %74
+ %105 = OpPhi %8 ; Was OpPhi %8 %70 %66 %72 %65 %113 %74
+ OpBranch %44
+ %45 = OpLabel
+ %95 = OpConvertSToF %21 %98
+ %96 = OpCompositeConstruct %22 %95 %95 %95 %95
+ OpStore %3 %96
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, DominanceIssue) {
+ // Exposes a scenario where redirecting edges results in uses of ids being
+ // non-dominated. We replace such uses with OpUndef to account for this.
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %5 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %5
+ %6 = OpTypeBool
+ %8 = OpConstantTrue %6
+ %9 = OpConstant %5 10
+ %10 = OpConstant %5 20
+ %11 = OpConstant %5 30
+ %4 = OpFunction %2 None %3
+ %12 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ OpSelectionMerge %17 None
+ OpBranchConditional %8 %18 %19
+ %18 = OpLabel
+ OpBranch %14
+ %19 = OpLabel
+ %20 = OpIAdd %5 %9 %10
+ OpBranch %17
+ %17 = OpLabel
+ %21 = OpIAdd %5 %20 %11
+ OpBranchConditional %8 %14 %15
+ %15 = OpLabel
+ OpBranch %13
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %5 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %5
+ %6 = OpTypeBool
+ %8 = OpConstantTrue %6
+ %9 = OpConstant %5 10
+ %10 = OpConstant %5 20
+ %11 = OpConstant %5 30
+ %22 = OpUndef %5
+ %4 = OpFunction %2 None %3
+ %12 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpSelectionMerge %14 None
+ OpBranchConditional %8 %16 %14
+ %16 = OpLabel
+ OpSelectionMerge %17 None
+ OpBranchConditional %8 %18 %19
+ %18 = OpLabel
+ OpBranch %17
+ %19 = OpLabel
+ %20 = OpIAdd %5 %9 %10
+ OpBranch %17
+ %17 = OpLabel
+ %21 = OpIAdd %5 %22 %11
+ OpBranchConditional %8 %14 %14
+ %15 = OpLabel
+ OpBranch %13
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, AccessChainIssue) {
+ // Exposes a scenario where redirecting edges results in a use of an id
+ // generated by an access chain being non-dominated.
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %56
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %28 0 Offset 0
+ OpDecorate %28 Block
+ OpDecorate %30 DescriptorSet 0
+ OpDecorate %30 Binding 0
+ OpDecorate %56 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 2
+ %8 = OpTypePointer Function %7
+ %60 = OpTypePointer Private %7
+ %10 = OpConstant %6 0
+ %11 = OpConstantComposite %7 %10 %10
+ %12 = OpTypePointer Function %6
+ %59 = OpTypePointer Private %6
+ %14 = OpTypeInt 32 1
+ %15 = OpTypePointer Function %14
+ %17 = OpConstant %14 0
+ %24 = OpConstant %14 100
+ %25 = OpTypeBool
+ %28 = OpTypeStruct %6
+ %29 = OpTypePointer Uniform %28
+ %30 = OpVariable %29 Uniform
+ %31 = OpTypePointer Uniform %6
+ %39 = OpTypeInt 32 0
+ %40 = OpConstant %39 1
+ %45 = OpConstant %39 0
+ %52 = OpConstant %14 1
+ %54 = OpTypeVector %6 4
+ %55 = OpTypePointer Output %54
+ %56 = OpVariable %55 Output
+ %9 = OpVariable %60 Private
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %13 = OpVariable %12 Function
+ %16 = OpVariable %15 Function
+ %38 = OpVariable %12 Function
+ OpStore %9 %11
+ OpStore %13 %10
+ OpStore %16 %17
+ OpBranch %18
+ %18 = OpLabel
+ OpLoopMerge %20 %21 None
+ OpBranch %22
+ %22 = OpLabel
+ %23 = OpLoad %14 %16
+ %26 = OpSLessThan %25 %23 %24
+ OpBranchConditional %26 %19 %20
+ %19 = OpLabel
+ %27 = OpLoad %14 %16
+ %32 = OpAccessChain %31 %30 %17
+ %33 = OpLoad %6 %32
+ %34 = OpConvertFToS %14 %33
+ %35 = OpSLessThan %25 %27 %34
+ OpSelectionMerge %37 None
+ OpBranchConditional %35 %36 %44
+ %36 = OpLabel
+ %41 = OpAccessChain %59 %9 %40
+ %42 = OpLoad %6 %41
+ OpStore %38 %42
+ OpBranch %20
+ %44 = OpLabel
+ %46 = OpAccessChain %59 %9 %45
+ OpBranch %37
+ %37 = OpLabel
+ %47 = OpLoad %6 %46
+ OpStore %38 %47
+ %48 = OpLoad %6 %38
+ %49 = OpLoad %6 %13
+ %50 = OpFAdd %6 %49 %48
+ OpStore %13 %50
+ OpBranch %21
+ %21 = OpLabel
+ %51 = OpLoad %14 %16
+ %53 = OpIAdd %14 %51 %52
+ OpStore %16 %53
+ OpBranch %18
+ %20 = OpLabel
+ %57 = OpLoad %6 %13
+ %58 = OpCompositeConstruct %54 %57 %57 %57 %57
+ OpStore %56 %58
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %56
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %28 0 Offset 0
+ OpDecorate %28 Block
+ OpDecorate %30 DescriptorSet 0
+ OpDecorate %30 Binding 0
+ OpDecorate %56 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 2
+ %8 = OpTypePointer Function %7
+ %60 = OpTypePointer Private %7
+ %10 = OpConstant %6 0
+ %11 = OpConstantComposite %7 %10 %10
+ %12 = OpTypePointer Function %6
+ %59 = OpTypePointer Private %6
+ %14 = OpTypeInt 32 1
+ %15 = OpTypePointer Function %14
+ %17 = OpConstant %14 0
+ %24 = OpConstant %14 100
+ %25 = OpTypeBool
+ %28 = OpTypeStruct %6
+ %29 = OpTypePointer Uniform %28
+ %30 = OpVariable %29 Uniform
+ %31 = OpTypePointer Uniform %6
+ %39 = OpTypeInt 32 0
+ %40 = OpConstant %39 1
+ %45 = OpConstant %39 0
+ %52 = OpConstant %14 1
+ %54 = OpTypeVector %6 4
+ %55 = OpTypePointer Output %54
+ %56 = OpVariable %55 Output
+ %9 = OpVariable %60 Private
+ %61 = OpConstantTrue %25
+ %62 = OpVariable %59 Private
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %13 = OpVariable %12 Function
+ %16 = OpVariable %15 Function
+ %38 = OpVariable %12 Function
+ OpStore %9 %11
+ OpStore %13 %10
+ OpStore %16 %17
+ OpBranch %18
+ %18 = OpLabel
+ OpSelectionMerge %20 None
+ OpBranchConditional %61 %22 %20
+ %22 = OpLabel
+ %23 = OpLoad %14 %16
+ %26 = OpSLessThan %25 %23 %24
+ OpBranchConditional %26 %19 %20
+ %19 = OpLabel
+ %27 = OpLoad %14 %16
+ %32 = OpAccessChain %31 %30 %17
+ %33 = OpLoad %6 %32
+ %34 = OpConvertFToS %14 %33
+ %35 = OpSLessThan %25 %27 %34
+ OpSelectionMerge %37 None
+ OpBranchConditional %35 %36 %44
+ %36 = OpLabel
+ %41 = OpAccessChain %59 %9 %40
+ %42 = OpLoad %6 %41
+ OpStore %38 %42
+ OpBranch %37
+ %44 = OpLabel
+ %46 = OpAccessChain %59 %9 %45
+ OpBranch %37
+ %37 = OpLabel
+ %47 = OpLoad %6 %62
+ OpStore %38 %47
+ %48 = OpLoad %6 %38
+ %49 = OpLoad %6 %13
+ %50 = OpFAdd %6 %49 %48
+ OpStore %13 %50
+ OpBranch %20
+ %21 = OpLabel
+ %51 = OpLoad %14 %16
+ %53 = OpIAdd %14 %51 %52
+ OpStore %16 %53
+ OpBranch %18
+ %20 = OpLabel
+ %57 = OpLoad %6 %13
+ %58 = OpCompositeConstruct %54 %57 %57 %57 %57
+ OpStore %56 %58
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, DominanceAndPhiIssue) {
+ // Exposes an interesting scenario where a use in a phi stops being dominated
+ // by the block with which it is associated in the phi.
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %17 = OpTypeBool
+ %18 = OpConstantTrue %17
+ %19 = OpConstantFalse %17
+ %20 = OpTypeInt 32 1
+ %21 = OpConstant %20 5
+ %22 = OpConstant %20 6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %6
+ %6 = OpLabel
+ OpLoopMerge %16 %15 None
+ OpBranch %7
+ %7 = OpLabel
+ OpSelectionMerge %13 None
+ OpBranchConditional %18 %8 %9
+ %8 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %18 %10 %11
+ %9 = OpLabel
+ OpBranch %16
+ %10 = OpLabel
+ OpBranch %16
+ %11 = OpLabel
+ %23 = OpIAdd %20 %21 %22
+ OpBranch %12
+ %12 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ %24 = OpPhi %20 %23 %13
+ OpBranchConditional %19 %15 %16
+ %15 = OpLabel
+ OpBranch %6
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %17 = OpTypeBool
+ %18 = OpConstantTrue %17
+ %19 = OpConstantFalse %17
+ %20 = OpTypeInt 32 1
+ %21 = OpConstant %20 5
+ %22 = OpConstant %20 6
+ %25 = OpUndef %20
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %6
+ %6 = OpLabel
+ OpSelectionMerge %16 None
+ OpBranchConditional %18 %7 %16
+ %7 = OpLabel
+ OpSelectionMerge %13 None
+ OpBranchConditional %18 %8 %9
+ %8 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %18 %10 %11
+ %9 = OpLabel
+ OpBranch %13
+ %10 = OpLabel
+ OpBranch %12
+ %11 = OpLabel
+ %23 = OpIAdd %20 %21 %22
+ OpBranch %12
+ %12 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ %24 = OpPhi %20 %25 %13
+ OpBranchConditional %19 %16 %16
+ %15 = OpLabel
+ OpBranch %6
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, OpLineBeforeOpPhi) {
+ // Test to ensure the pass knows OpLine and OpPhi instructions can be
+ // interleaved.
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpString "somefile"
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeInt 32 1
+ %7 = OpConstant %6 10
+ %8 = OpConstant %6 20
+ %9 = OpConstant %6 30
+ %10 = OpTypeBool
+ %11 = OpConstantTrue %10
+ %2 = OpFunction %4 None %5
+ %12 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ OpSelectionMerge %17 None
+ OpBranchConditional %11 %18 %19
+ %18 = OpLabel
+ %20 = OpIAdd %6 %7 %8
+ %21 = OpIAdd %6 %7 %9
+ OpBranch %17
+ %19 = OpLabel
+ OpBranch %14
+ %17 = OpLabel
+ %22 = OpPhi %6 %20 %18
+ OpLine %3 0 0
+ %23 = OpPhi %6 %21 %18
+ OpBranch %15
+ %15 = OpLabel
+ OpBranch %13
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+ ASSERT_EQ(1, ops.size());
+
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpString "somefile"
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeInt 32 1
+ %7 = OpConstant %6 10
+ %8 = OpConstant %6 20
+ %9 = OpConstant %6 30
+ %10 = OpTypeBool
+ %11 = OpConstantTrue %10
+ %24 = OpUndef %6
+ %2 = OpFunction %4 None %5
+ %12 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpSelectionMerge %14 None
+ OpBranchConditional %11 %16 %14
+ %16 = OpLabel
+ OpSelectionMerge %17 None
+ OpBranchConditional %11 %18 %19
+ %18 = OpLabel
+ %20 = OpIAdd %6 %7 %8
+ %21 = OpIAdd %6 %7 %9
+ OpBranch %17
+ %19 = OpLabel
+ OpBranch %17
+ %17 = OpLabel
+ %22 = OpPhi %6 %20 %18 %24 %19
+ OpLine %3 0 0
+ %23 = OpPhi %6 %21 %18 %24 %19
+ OpBranch %14
+ %15 = OpLabel
+ OpBranch %13
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+ SelectionMergeIsContinueTarget) {
+ // Example where a loop's continue target is also the target of a selection.
+ // In this scenario we cautiously do not apply the transformation.
+ std::string shader = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeBool
+ %4 = OpTypeFunction %2
+ %1 = OpFunction %2 None %4
+ %5 = OpLabel
+ %6 = OpUndef %3
+ OpBranch %7
+ %7 = OpLabel
+ %8 = OpPhi %3 %6 %5 %9 %10
+ OpLoopMerge %11 %10 None
+ OpBranch %12
+ %12 = OpLabel
+ %13 = OpUndef %3
+ OpSelectionMerge %10 None
+ OpBranchConditional %13 %14 %10
+ %14 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ %9 = OpUndef %3
+ OpBranchConditional %9 %7 %11
+ %11 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ // There should be no opportunities.
+ ASSERT_EQ(0, ops.size());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+ SwitchSelectionMergeIsContinueTarget) {
+ // Another example where a loop's continue target is also the target of a
+ // selection; this time a selection associated with an OpSwitch. We
+ // cautiously do not apply the transformation.
+ std::string shader = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeBool
+ %5 = OpTypeInt 32 1
+ %4 = OpTypeFunction %2
+ %6 = OpConstant %5 2
+ %7 = OpConstantTrue %3
+ %1 = OpFunction %2 None %4
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpLoopMerge %14 %15 None
+ OpBranchConditional %7 %10 %14
+ %10 = OpLabel
+ OpSelectionMerge %15 None
+ OpSwitch %6 %12 1 %11 2 %11 3 %15
+ %11 = OpLabel
+ OpBranch %12
+ %12 = OpLabel
+ OpBranch %15
+ %15 = OpLabel
+ OpBranch %9
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ // There should be no opportunities.
+ ASSERT_EQ(0, ops.size());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, ContinueTargetIsSwitchTarget) {
+ std::string shader = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeBool
+ %5 = OpTypeInt 32 1
+ %4 = OpTypeFunction %2
+ %6 = OpConstant %5 2
+ %7 = OpConstantTrue %3
+ %1 = OpFunction %2 None %4
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpLoopMerge %14 %12 None
+ OpBranchConditional %7 %10 %14
+ %10 = OpLabel
+ OpSelectionMerge %15 None
+ OpSwitch %6 %12 1 %11 2 %11 3 %15
+ %11 = OpLabel
+ OpBranch %12
+ %12 = OpLabel
+ OpBranch %9
+ %15 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeBool
+ %5 = OpTypeInt 32 1
+ %4 = OpTypeFunction %2
+ %6 = OpConstant %5 2
+ %7 = OpConstantTrue %3
+ %1 = OpFunction %2 None %4
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpSelectionMerge %14 None
+ OpBranchConditional %7 %10 %14
+ %10 = OpLabel
+ OpSelectionMerge %15 None
+ OpSwitch %6 %15 1 %11 2 %11 3 %15
+ %11 = OpLabel
+ OpBranch %15
+ %12 = OpLabel
+ OpBranch %9
+ %15 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+ MultipleSwitchTargetsAreContinueTarget) {
+ std::string shader = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeBool
+ %5 = OpTypeInt 32 1
+ %4 = OpTypeFunction %2
+ %6 = OpConstant %5 2
+ %7 = OpConstantTrue %3
+ %1 = OpFunction %2 None %4
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpLoopMerge %14 %12 None
+ OpBranchConditional %7 %10 %14
+ %10 = OpLabel
+ OpSelectionMerge %15 None
+ OpSwitch %6 %11 1 %12 2 %12 3 %15
+ %11 = OpLabel
+ OpBranch %12
+ %12 = OpLabel
+ OpBranch %9
+ %15 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeBool
+ %5 = OpTypeInt 32 1
+ %4 = OpTypeFunction %2
+ %6 = OpConstant %5 2
+ %7 = OpConstantTrue %3
+ %1 = OpFunction %2 None %4
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpSelectionMerge %14 None
+ OpBranchConditional %7 %10 %14
+ %10 = OpLabel
+ OpSelectionMerge %15 None
+ OpSwitch %6 %11 1 %15 2 %15 3 %15
+ %11 = OpLabel
+ OpBranch %15
+ %12 = OpLabel
+ OpBranch %9
+ %15 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, LoopBranchesStraightToMerge) {
+ std::string shader = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %2 = OpTypeVoid
+ %4 = OpTypeFunction %2
+ %1 = OpFunction %2 None %4
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpLoopMerge %14 %12 None
+ OpBranch %14
+ %12 = OpLabel
+ OpBranch %9
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %2 = OpTypeVoid
+ %4 = OpTypeFunction %2
+ %15 = OpTypeBool
+ %16 = OpConstantTrue %15
+ %1 = OpFunction %2 None %4
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpSelectionMerge %14 None
+ OpBranchConditional %16 %14 %14
+ %12 = OpLabel
+ OpBranch %9
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+ LoopConditionallyJumpsToMergeOrContinue) {
+ std::string shader = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeBool
+ %4 = OpTypeFunction %2
+ %7 = OpConstantTrue %3
+ %1 = OpFunction %2 None %4
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpLoopMerge %14 %12 None
+ OpBranchConditional %7 %14 %12
+ %12 = OpLabel
+ OpBranch %9
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeBool
+ %4 = OpTypeFunction %2
+ %7 = OpConstantTrue %3
+ %1 = OpFunction %2 None %4
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpSelectionMerge %14 None
+ OpBranchConditional %7 %14 %14
+ %12 = OpLabel
+ OpBranch %9
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, MultipleAccessChains) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeStruct %6
+ %8 = OpTypeStruct %7
+ %9 = OpTypePointer Function %8
+ %11 = OpConstant %6 3
+ %12 = OpConstantComposite %7 %11
+ %13 = OpConstantComposite %8 %12
+ %14 = OpTypePointer Function %7
+ %16 = OpConstant %6 0
+ %19 = OpTypePointer Function %6
+ %15 = OpTypeBool
+ %18 = OpConstantTrue %15
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %10 = OpVariable %9 Function
+ %20 = OpVariable %19 Function
+ OpStore %10 %13
+ OpBranch %23
+ %23 = OpLabel
+ OpLoopMerge %25 %26 None
+ OpBranch %27
+ %27 = OpLabel
+ OpSelectionMerge %28 None
+ OpBranchConditional %18 %29 %25
+ %29 = OpLabel
+ %17 = OpAccessChain %14 %10 %16
+ OpBranch %28
+ %28 = OpLabel
+ %21 = OpAccessChain %19 %17 %16
+ %22 = OpLoad %6 %21
+ %24 = OpAccessChain %19 %10 %16 %16
+ OpStore %24 %22
+ OpBranch %25
+ %26 = OpLabel
+ OpBranch %23
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string expected = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeStruct %6
+ %8 = OpTypeStruct %7
+ %9 = OpTypePointer Function %8
+ %11 = OpConstant %6 3
+ %12 = OpConstantComposite %7 %11
+ %13 = OpConstantComposite %8 %12
+ %14 = OpTypePointer Function %7
+ %16 = OpConstant %6 0
+ %19 = OpTypePointer Function %6
+ %15 = OpTypeBool
+ %18 = OpConstantTrue %15
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %10 = OpVariable %9 Function
+ %20 = OpVariable %19 Function
+ %30 = OpVariable %14 Function
+ OpStore %10 %13
+ OpBranch %23
+ %23 = OpLabel
+ OpSelectionMerge %25 None
+ OpBranchConditional %18 %27 %25
+ %27 = OpLabel
+ OpSelectionMerge %28 None
+ OpBranchConditional %18 %29 %28
+ %29 = OpLabel
+ %17 = OpAccessChain %14 %10 %16
+ OpBranch %28
+ %28 = OpLabel
+ %21 = OpAccessChain %19 %30 %16
+ %22 = OpLoad %6 %21
+ %24 = OpAccessChain %19 %10 %16 %16
+ OpStore %24 %22
+ OpBranch %25
+ %26 = OpLabel
+ OpBranch %23
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+ UnreachableInnerLoopContinueBranchingToOuterLoopMerge) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ OpBranch %8
+ %8 = OpLabel
+ OpLoopMerge %9 %10 None
+ OpBranch %11
+ %11 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %12
+ %13 = OpLabel
+ OpBranchConditional %6 %9 %11
+ %12 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ OpBranchConditional %6 %9 %8
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ ASSERT_EQ(2, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ OpBranch %8
+ %8 = OpLabel
+ OpSelectionMerge %9 None
+ OpBranchConditional %6 %11 %9
+ %11 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %12
+ %13 = OpLabel
+ OpBranchConditional %6 %9 %11
+ %12 = OpLabel
+ OpBranch %9
+ %10 = OpLabel
+ OpBranchConditional %6 %9 %8
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+
+ ASSERT_TRUE(ops[1]->PreconditionHolds());
+ ops[1]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string after_op_1 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ OpBranch %8
+ %8 = OpLabel
+ OpSelectionMerge %9 None
+ OpBranchConditional %6 %11 %9
+ %11 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %6 %12 %12
+ %13 = OpLabel
+ OpBranchConditional %6 %9 %11
+ %12 = OpLabel
+ OpBranch %9
+ %10 = OpLabel
+ OpBranchConditional %6 %9 %8
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+ UnreachableInnerLoopContinueBranchingToOuterLoopMerge2) {
+ // In this test, the branch to the outer loop merge from the inner loop's
+ // continue is part of a structured selection.
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ OpBranch %8
+ %8 = OpLabel
+ OpLoopMerge %9 %10 None
+ OpBranch %11
+ %11 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %12
+ %13 = OpLabel
+ OpSelectionMerge %14 None
+ OpBranchConditional %6 %9 %14
+ %14 = OpLabel
+ OpBranch %11
+ %12 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ OpBranchConditional %6 %9 %8
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ ASSERT_EQ(2, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ OpBranch %8
+ %8 = OpLabel
+ OpSelectionMerge %9 None
+ OpBranchConditional %6 %11 %9
+ %11 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %12
+ %13 = OpLabel
+ OpSelectionMerge %14 None
+ OpBranchConditional %6 %9 %14
+ %14 = OpLabel
+ OpBranch %11
+ %12 = OpLabel
+ OpBranch %9
+ %10 = OpLabel
+ OpBranchConditional %6 %9 %8
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+
+ ASSERT_TRUE(ops[1]->PreconditionHolds());
+ ops[1]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string after_op_1 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ OpBranch %8
+ %8 = OpLabel
+ OpSelectionMerge %9 None
+ OpBranchConditional %6 %11 %9
+ %11 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %6 %12 %12
+ %13 = OpLabel
+ OpSelectionMerge %14 None
+ OpBranchConditional %6 %9 %14
+ %14 = OpLabel
+ OpBranch %11
+ %12 = OpLabel
+ OpBranch %9
+ %10 = OpLabel
+ OpBranchConditional %6 %9 %8
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+ InnerLoopHeaderBranchesToOuterLoopMerge) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ OpBranch %8
+ %8 = OpLabel
+ OpLoopMerge %9 %10 None
+ OpBranch %11
+ %11 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranchConditional %6 %9 %13
+ %13 = OpLabel
+ OpBranchConditional %6 %11 %12
+ %12 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ OpBranchConditional %6 %9 %8
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ // We cannot transform the inner loop due to its header jumping straight to
+ // the outer loop merge (the inner loop's merge does not post-dominate its
+ // header).
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string after_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ OpBranch %8
+ %8 = OpLabel
+ OpSelectionMerge %9 None
+ OpBranchConditional %6 %11 %9
+ %11 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranchConditional %6 %12 %13
+ %13 = OpLabel
+ OpBranchConditional %6 %11 %12
+ %12 = OpLabel
+ OpBranch %9
+ %10 = OpLabel
+ OpBranchConditional %6 %9 %8
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_op_0, context.get());
+
+ // Now look again for more opportunities.
+ ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ // What was the inner loop should now be transformable, as the jump to the
+ // outer loop's merge has been redirected.
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ std::string after_another_op_0 = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeBool
+ %6 = OpConstantTrue %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ OpBranch %8
+ %8 = OpLabel
+ OpSelectionMerge %9 None
+ OpBranchConditional %6 %11 %9
+ %11 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %6 %12 %12
+ %13 = OpLabel
+ OpBranchConditional %6 %11 %12
+ %12 = OpLabel
+ OpBranch %9
+ %10 = OpLabel
+ OpBranchConditional %6 %9 %8
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ CheckEqual(env, after_another_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest, LongAccessChains) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeInt 32 1
+ %6 = OpTypeInt 32 0
+ %7 = OpConstant %6 5
+ %8 = OpTypeArray %5 %7
+ %9 = OpTypeStruct %8
+ %10 = OpTypeStruct %9 %9
+ %11 = OpConstant %6 2
+ %12 = OpTypeArray %10 %11
+ %13 = OpTypeStruct %12
+ %14 = OpTypePointer Function %13
+ %15 = OpConstant %5 0
+ %16 = OpConstant %5 1
+ %17 = OpConstant %5 2
+ %18 = OpConstant %5 3
+ %19 = OpConstant %5 4
+ %20 = OpConstantComposite %8 %15 %16 %17 %18 %19
+ %21 = OpConstantComposite %9 %20
+ %22 = OpConstant %5 5
+ %23 = OpConstant %5 6
+ %24 = OpConstant %5 7
+ %25 = OpConstant %5 8
+ %26 = OpConstant %5 9
+ %27 = OpConstantComposite %8 %22 %23 %24 %25 %26
+ %28 = OpConstantComposite %9 %27
+ %29 = OpConstantComposite %10 %21 %28
+ %30 = OpConstant %5 10
+ %31 = OpConstant %5 11
+ %32 = OpConstant %5 12
+ %33 = OpConstant %5 13
+ %34 = OpConstant %5 14
+ %35 = OpConstantComposite %8 %30 %31 %32 %33 %34
+ %36 = OpConstantComposite %9 %35
+ %37 = OpConstant %5 15
+ %38 = OpConstant %5 16
+ %39 = OpConstant %5 17
+ %40 = OpConstant %5 18
+ %41 = OpConstant %5 19
+ %42 = OpConstantComposite %8 %37 %38 %39 %40 %41
+ %43 = OpConstantComposite %9 %42
+ %44 = OpConstantComposite %10 %36 %43
+ %45 = OpConstantComposite %12 %29 %44
+ %46 = OpConstantComposite %13 %45
+ %47 = OpTypePointer Function %12
+ %48 = OpTypePointer Function %10
+ %49 = OpTypePointer Function %9
+ %50 = OpTypePointer Function %8
+ %51 = OpTypePointer Function %5
+ %52 = OpTypeBool
+ %53 = OpConstantTrue %52
+ %2 = OpFunction %3 None %4
+ %54 = OpLabel
+ %55 = OpVariable %14 Function
+ OpStore %55 %46
+ OpBranch %56
+ %56 = OpLabel
+ OpLoopMerge %57 %58 None
+ OpBranchConditional %53 %57 %59
+ %59 = OpLabel
+ OpSelectionMerge %60 None
+ OpBranchConditional %53 %61 %57
+ %61 = OpLabel
+ %62 = OpAccessChain %47 %55 %15
+ OpBranch %63
+ %63 = OpLabel
+ OpSelectionMerge %64 None
+ OpBranchConditional %53 %65 %57
+ %65 = OpLabel
+ %66 = OpAccessChain %48 %62 %16
+ OpBranch %67
+ %67 = OpLabel
+ OpSelectionMerge %68 None
+ OpBranchConditional %53 %69 %57
+ %69 = OpLabel
+ %70 = OpAccessChain %49 %66 %16
+ OpBranch %71
+ %71 = OpLabel
+ OpSelectionMerge %72 None
+ OpBranchConditional %53 %73 %57
+ %73 = OpLabel
+ %74 = OpAccessChain %50 %70 %15
+ OpBranch %75
+ %75 = OpLabel
+ OpSelectionMerge %76 None
+ OpBranchConditional %53 %77 %57
+ %77 = OpLabel
+ %78 = OpAccessChain %51 %74 %17
+ OpBranch %79
+ %79 = OpLabel
+ OpSelectionMerge %80 None
+ OpBranchConditional %53 %81 %57
+ %81 = OpLabel
+ %82 = OpLoad %5 %78
+ OpBranch %80
+ %80 = OpLabel
+ OpBranch %76
+ %76 = OpLabel
+ OpBranch %72
+ %72 = OpLabel
+ OpBranch %68
+ %68 = OpLabel
+ OpBranch %64
+ %64 = OpLabel
+ OpBranch %60
+ %60 = OpLabel
+ OpBranch %58
+ %58 = OpLabel
+ OpBranch %56
+ %57 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+ const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
+ auto ops = pass.WrapGetAvailableOpportunities(context.get());
+
+ ASSERT_EQ(1, ops.size());
+ ASSERT_TRUE(ops[0]->PreconditionHolds());
+ ops[0]->TryToApply();
+
+ CheckValid(env, context.get());
+
+ // TODO(2183): When we have a more general solution for handling access
+ // chains, write an expected result for this test.
+ // std::string expected = R"(
+ // Expected text for transformed shader
+ //)";
+ // CheckEqual(env, expected, context.get());
+}
+
+} // namespace
+} // namespace reduce
+} // namespace spvtools
diff --git a/test/reduce/validation_during_reduction_test.cpp b/test/reduce/validation_during_reduction_test.cpp
new file mode 100644
index 00000000..bb7d14e1
--- /dev/null
+++ b/test/reduce/validation_during_reduction_test.cpp
@@ -0,0 +1,376 @@
+// Copyright (c) 2018 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 "reduce_test_util.h"
+
+#include "source/reduce/reducer.h"
+#include "source/reduce/reduction_pass.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+// A dumb reduction pass that removes global values regardless of whether they
+// are referenced. This is very likely to make the resulting module invalid. We
+// use this to test the reducer's behavior in the scenario where a bad reduction
+// pass leads to an invalid module.
+class BlindlyRemoveGlobalValuesPass : public ReductionPass {
+ public:
+ // Creates the reduction pass in the context of the given target environment
+ // |target_env|
+ explicit BlindlyRemoveGlobalValuesPass(const spv_target_env target_env)
+ : ReductionPass(target_env) {}
+
+ ~BlindlyRemoveGlobalValuesPass() override = default;
+
+ // The name of this pass.
+ std::string GetName() const final { return "BlindlyRemoveGlobalValuesPass"; };
+
+ protected:
+ // Adds opportunities to remove all global values. Assuming they are all
+ // referenced (directly or indirectly) from elsewhere in the module, each such
+ // opportunity will make the module invalid.
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final {
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+ for (auto& inst : context->module()->types_values()) {
+ if (inst.HasResultId()) {
+ result.push_back(
+ MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+ }
+ }
+ return result;
+ }
+};
+
+TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) {
+ // A module whose global values are all referenced, so that any application of
+ // MakeModuleInvalidPass will make the module invalid.
+ std::string original = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %60
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %16 "buf2"
+ OpMemberName %16 0 "i"
+ OpName %18 ""
+ OpName %25 "buf1"
+ OpMemberName %25 0 "f"
+ OpName %27 ""
+ OpName %60 "_GLF_color"
+ OpMemberDecorate %16 0 Offset 0
+ OpDecorate %16 Block
+ OpDecorate %18 DescriptorSet 0
+ OpDecorate %18 Binding 2
+ OpMemberDecorate %25 0 Offset 0
+ OpDecorate %25 Block
+ OpDecorate %27 DescriptorSet 0
+ OpDecorate %27 Binding 1
+ OpDecorate %60 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %9 = OpConstant %6 0
+ %16 = OpTypeStruct %6
+ %17 = OpTypePointer Uniform %16
+ %18 = OpVariable %17 Uniform
+ %19 = OpTypePointer Uniform %6
+ %22 = OpTypeBool
+ %24 = OpTypeFloat 32
+ %25 = OpTypeStruct %24
+ %26 = OpTypePointer Uniform %25
+ %27 = OpVariable %26 Uniform
+ %28 = OpTypePointer Uniform %24
+ %31 = OpConstant %24 2
+ %56 = OpConstant %6 1
+ %58 = OpTypeVector %24 4
+ %59 = OpTypePointer Output %58
+ %60 = OpVariable %59 Output
+ %72 = OpUndef %24
+ %74 = OpUndef %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ %73 = OpPhi %6 %74 %5 %77 %34
+ %71 = OpPhi %24 %72 %5 %76 %34
+ %70 = OpPhi %6 %9 %5 %57 %34
+ %20 = OpAccessChain %19 %18 %9
+ %21 = OpLoad %6 %20
+ %23 = OpSLessThan %22 %70 %21
+ OpLoopMerge %12 %34 None
+ OpBranchConditional %23 %11 %12
+ %11 = OpLabel
+ %29 = OpAccessChain %28 %27 %9
+ %30 = OpLoad %24 %29
+ %32 = OpFOrdGreaterThan %22 %30 %31
+ OpSelectionMerge %34 None
+ OpBranchConditional %32 %33 %46
+ %33 = OpLabel
+ %40 = OpFAdd %24 %71 %30
+ %45 = OpISub %6 %73 %21
+ OpBranch %34
+ %46 = OpLabel
+ %50 = OpFMul %24 %71 %30
+ %54 = OpSDiv %6 %73 %21
+ OpBranch %34
+ %34 = OpLabel
+ %77 = OpPhi %6 %45 %33 %54 %46
+ %76 = OpPhi %24 %40 %33 %50 %46
+ %57 = OpIAdd %6 %70 %56
+ OpBranch %10
+ %12 = OpLabel
+ %61 = OpAccessChain %28 %27 %9
+ %62 = OpLoad %24 %61
+ %66 = OpConvertSToF %24 %21
+ %68 = OpConvertSToF %24 %73
+ %69 = OpCompositeConstruct %58 %62 %71 %66 %68
+ OpStore %60 %69
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+ Reducer reducer(env);
+ reducer.SetMessageConsumer(NopDiagnostic);
+
+ // Say that every module is interesting.
+ reducer.SetInterestingnessFunction(
+ [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
+
+ reducer.AddReductionPass(MakeUnique<BlindlyRemoveGlobalValuesPass>(env));
+
+ std::vector<uint32_t> binary_in;
+ SpirvTools t(env);
+
+ ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
+ std::vector<uint32_t> binary_out;
+ spvtools::ReducerOptions reducer_options;
+ reducer_options.set_step_limit(500);
+
+ reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+
+ // The reducer should have no impact.
+ CheckEqual(env, original, binary_out);
+}
+
+TEST(ValidationDuringReductionTest, CheckNotAlwaysInvalidCanMakeProgress) {
+ // A module with just one unreferenced global value. All but one application
+ // of MakeModuleInvalidPass will make the module invalid.
+ std::string original = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %60
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %16 "buf2"
+ OpMemberName %16 0 "i"
+ OpName %18 ""
+ OpName %25 "buf1"
+ OpMemberName %25 0 "f"
+ OpName %27 ""
+ OpName %60 "_GLF_color"
+ OpMemberDecorate %16 0 Offset 0
+ OpDecorate %16 Block
+ OpDecorate %18 DescriptorSet 0
+ OpDecorate %18 Binding 2
+ OpMemberDecorate %25 0 Offset 0
+ OpDecorate %25 Block
+ OpDecorate %27 DescriptorSet 0
+ OpDecorate %27 Binding 1
+ OpDecorate %60 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %9 = OpConstant %6 0
+ %16 = OpTypeStruct %6
+ %17 = OpTypePointer Uniform %16
+ %18 = OpVariable %17 Uniform
+ %19 = OpTypePointer Uniform %6
+ %22 = OpTypeBool
+ %24 = OpTypeFloat 32
+ %25 = OpTypeStruct %24
+ %26 = OpTypePointer Uniform %25
+ %27 = OpVariable %26 Uniform
+ %28 = OpTypePointer Uniform %24
+ %31 = OpConstant %24 2
+ %56 = OpConstant %6 1
+ %1000 = OpConstant %6 1000 ; It should be possible to remove this instruction without making the module invalid.
+ %58 = OpTypeVector %24 4
+ %59 = OpTypePointer Output %58
+ %60 = OpVariable %59 Output
+ %72 = OpUndef %24
+ %74 = OpUndef %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ %73 = OpPhi %6 %74 %5 %77 %34
+ %71 = OpPhi %24 %72 %5 %76 %34
+ %70 = OpPhi %6 %9 %5 %57 %34
+ %20 = OpAccessChain %19 %18 %9
+ %21 = OpLoad %6 %20
+ %23 = OpSLessThan %22 %70 %21
+ OpLoopMerge %12 %34 None
+ OpBranchConditional %23 %11 %12
+ %11 = OpLabel
+ %29 = OpAccessChain %28 %27 %9
+ %30 = OpLoad %24 %29
+ %32 = OpFOrdGreaterThan %22 %30 %31
+ OpSelectionMerge %34 None
+ OpBranchConditional %32 %33 %46
+ %33 = OpLabel
+ %40 = OpFAdd %24 %71 %30
+ %45 = OpISub %6 %73 %21
+ OpBranch %34
+ %46 = OpLabel
+ %50 = OpFMul %24 %71 %30
+ %54 = OpSDiv %6 %73 %21
+ OpBranch %34
+ %34 = OpLabel
+ %77 = OpPhi %6 %45 %33 %54 %46
+ %76 = OpPhi %24 %40 %33 %50 %46
+ %57 = OpIAdd %6 %70 %56
+ OpBranch %10
+ %12 = OpLabel
+ %61 = OpAccessChain %28 %27 %9
+ %62 = OpLoad %24 %61
+ %66 = OpConvertSToF %24 %21
+ %68 = OpConvertSToF %24 %73
+ %69 = OpCompositeConstruct %58 %62 %71 %66 %68
+ OpStore %60 %69
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ // This is the same as the original, except that the constant declaration of
+ // 1000 is gone.
+ std::string expected = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %60
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %16 "buf2"
+ OpMemberName %16 0 "i"
+ OpName %18 ""
+ OpName %25 "buf1"
+ OpMemberName %25 0 "f"
+ OpName %27 ""
+ OpName %60 "_GLF_color"
+ OpMemberDecorate %16 0 Offset 0
+ OpDecorate %16 Block
+ OpDecorate %18 DescriptorSet 0
+ OpDecorate %18 Binding 2
+ OpMemberDecorate %25 0 Offset 0
+ OpDecorate %25 Block
+ OpDecorate %27 DescriptorSet 0
+ OpDecorate %27 Binding 1
+ OpDecorate %60 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %9 = OpConstant %6 0
+ %16 = OpTypeStruct %6
+ %17 = OpTypePointer Uniform %16
+ %18 = OpVariable %17 Uniform
+ %19 = OpTypePointer Uniform %6
+ %22 = OpTypeBool
+ %24 = OpTypeFloat 32
+ %25 = OpTypeStruct %24
+ %26 = OpTypePointer Uniform %25
+ %27 = OpVariable %26 Uniform
+ %28 = OpTypePointer Uniform %24
+ %31 = OpConstant %24 2
+ %56 = OpConstant %6 1
+ %58 = OpTypeVector %24 4
+ %59 = OpTypePointer Output %58
+ %60 = OpVariable %59 Output
+ %72 = OpUndef %24
+ %74 = OpUndef %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ %73 = OpPhi %6 %74 %5 %77 %34
+ %71 = OpPhi %24 %72 %5 %76 %34
+ %70 = OpPhi %6 %9 %5 %57 %34
+ %20 = OpAccessChain %19 %18 %9
+ %21 = OpLoad %6 %20
+ %23 = OpSLessThan %22 %70 %21
+ OpLoopMerge %12 %34 None
+ OpBranchConditional %23 %11 %12
+ %11 = OpLabel
+ %29 = OpAccessChain %28 %27 %9
+ %30 = OpLoad %24 %29
+ %32 = OpFOrdGreaterThan %22 %30 %31
+ OpSelectionMerge %34 None
+ OpBranchConditional %32 %33 %46
+ %33 = OpLabel
+ %40 = OpFAdd %24 %71 %30
+ %45 = OpISub %6 %73 %21
+ OpBranch %34
+ %46 = OpLabel
+ %50 = OpFMul %24 %71 %30
+ %54 = OpSDiv %6 %73 %21
+ OpBranch %34
+ %34 = OpLabel
+ %77 = OpPhi %6 %45 %33 %54 %46
+ %76 = OpPhi %24 %40 %33 %50 %46
+ %57 = OpIAdd %6 %70 %56
+ OpBranch %10
+ %12 = OpLabel
+ %61 = OpAccessChain %28 %27 %9
+ %62 = OpLoad %24 %61
+ %66 = OpConvertSToF %24 %21
+ %68 = OpConvertSToF %24 %73
+ %69 = OpCompositeConstruct %58 %62 %71 %66 %68
+ OpStore %60 %69
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+ Reducer reducer(env);
+ reducer.SetMessageConsumer(NopDiagnostic);
+
+ // Say that every module is interesting.
+ reducer.SetInterestingnessFunction(
+ [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
+
+ reducer.AddReductionPass(MakeUnique<BlindlyRemoveGlobalValuesPass>(env));
+
+ std::vector<uint32_t> binary_in;
+ SpirvTools t(env);
+
+ ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
+ std::vector<uint32_t> binary_out;
+ spvtools::ReducerOptions reducer_options;
+ reducer_options.set_step_limit(500);
+
+ reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+ CheckEqual(env, expected, binary_out);
+}
+
+} // namespace
+} // namespace reduce
+} // namespace spvtools
diff --git a/test/val/val_adjacency_test.cpp b/test/val/val_adjacency_test.cpp
index 10002ef3..5c1124ae 100644
--- a/test/val/val_adjacency_test.cpp
+++ b/test/val/val_adjacency_test.cpp
@@ -53,7 +53,8 @@ OpFunctionEnd
CompileSuccessfully(module);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 1 has not been defined"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("ID 1[%bool] has not been defined"));
}
TEST_F(ValidateAdjacency, OpLoopMergeEndsModuleFail) {
diff --git a/test/val/val_arithmetics_test.cpp b/test/val/val_arithmetics_test.cpp
index c43d1012..87e006c1 100644
--- a/test/val/val_arithmetics_test.cpp
+++ b/test/val/val_arithmetics_test.cpp
@@ -606,7 +606,8 @@ TEST_F(ValidateArithmetics, DotNotVectorTypeOperand1) {
CompileSuccessfully(GenerateCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 6 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 6[%float] cannot be a "
+ "type"));
}
TEST_F(ValidateArithmetics, DotNotVectorTypeOperand2) {
diff --git a/test/val/val_atomics_test.cpp b/test/val/val_atomics_test.cpp
index a4936337..98a2149f 100644
--- a/test/val/val_atomics_test.cpp
+++ b/test/val/val_atomics_test.cpp
@@ -28,16 +28,13 @@ using ::testing::Not;
using ValidateAtomics = spvtest::ValidateBase<bool>;
-std::string GenerateShaderCode(
- const std::string& body,
- const std::string& capabilities_and_extensions = "",
- const std::string& memory_model = "GLSL450") {
+std::string GenerateShaderCodeImpl(
+ const std::string& body, const std::string& capabilities_and_extensions,
+ const std::string& definitions, const std::string& memory_model) {
std::ostringstream ss;
ss << R"(
OpCapability Shader
-OpCapability Int64
)";
-
ss << capabilities_and_extensions;
ss << "OpMemoryModel Logical " << memory_model << "\n";
ss << R"(
@@ -48,16 +45,12 @@ OpExecutionMode %main OriginUpperLeft
%bool = OpTypeBool
%f32 = OpTypeFloat 32
%u32 = OpTypeInt 32 0
-%u64 = OpTypeInt 64 0
-%s64 = OpTypeInt 64 1
%f32vec4 = OpTypeVector %f32 4
%f32_0 = OpConstant %f32 0
%f32_1 = OpConstant %f32 1
%u32_0 = OpConstant %u32 0
%u32_1 = OpConstant %u32 1
-%u64_1 = OpConstant %u64 1
-%s64_1 = OpConstant %s64 1
%f32vec4_0000 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0
%cross_device = OpConstant %u32 0
@@ -81,22 +74,17 @@ OpExecutionMode %main OriginUpperLeft
%u32_ptr = OpTypePointer Workgroup %u32
%u32_var = OpVariable %u32_ptr Workgroup
-%u64_ptr = OpTypePointer Workgroup %u64
-%s64_ptr = OpTypePointer Workgroup %s64
-%u64_var = OpVariable %u64_ptr Workgroup
-%s64_var = OpVariable %s64_ptr Workgroup
-
%f32vec4_ptr = OpTypePointer Workgroup %f32vec4
%f32vec4_var = OpVariable %f32vec4_ptr Workgroup
%f32_ptr_function = OpTypePointer Function %f32
-
+)";
+ ss << definitions;
+ ss << R"(
%main = OpFunction %void None %func
%main_entry = OpLabel
)";
-
ss << body;
-
ss << R"(
OpReturn
OpFunctionEnd)";
@@ -104,6 +92,44 @@ OpFunctionEnd)";
return ss.str();
}
+std::string GenerateShaderCode(
+ const std::string& body,
+ const std::string& capabilities_and_extensions = "",
+ const std::string& memory_model = "GLSL450") {
+ const std::string defintions = R"(
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+
+%u64_1 = OpConstant %u64 1
+%s64_1 = OpConstant %s64 1
+
+%u64_ptr = OpTypePointer Workgroup %u64
+%s64_ptr = OpTypePointer Workgroup %s64
+%u64_var = OpVariable %u64_ptr Workgroup
+%s64_var = OpVariable %s64_ptr Workgroup
+)";
+ return GenerateShaderCodeImpl(
+ body, "OpCapability Int64\n" + capabilities_and_extensions, defintions,
+ memory_model);
+}
+
+std::string GenerateWebGPUShaderCode(
+ const std::string& body,
+ const std::string& capabilities_and_extensions = "") {
+ const std::string vulkan_memory_capability = R"(
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability VulkanMemoryModelKHR
+)";
+ const std::string vulkan_memory_extension = R"(
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+ return GenerateShaderCodeImpl(body,
+ vulkan_memory_capability +
+ capabilities_and_extensions +
+ vulkan_memory_extension,
+ "", "VulkanKHR");
+}
+
std::string GenerateKernelCode(
const std::string& body,
const std::string& capabilities_and_extensions = "") {
@@ -312,6 +338,32 @@ TEST_F(ValidateAtomics, AtomicLoadVulkanInt64) {
"AtomicLoad: 64-bit atomics require the Int64Atomics capability"));
}
+TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSuccess) {
+ const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
+%val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire
+)";
+
+ CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSequentiallyConsistentFailure) {
+ 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"));
+}
+
TEST_F(ValidateAtomics, VK_KHR_shader_atomic_int64Success) {
const std::string body = R"(
%val1 = OpAtomicUMin %u64 %u64_var %device %relaxed %u64_1
@@ -380,7 +432,8 @@ TEST_F(ValidateAtomics, AtomicLoadWrongPointerType) {
CompileSuccessfully(GenerateKernelCode(body));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 27 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 27[%_ptr_Workgroup_float] cannot be a type"));
}
TEST_F(ValidateAtomics, AtomicLoadWrongPointerDataType) {
@@ -418,7 +471,7 @@ TEST_F(ValidateAtomics, AtomicLoadWrongMemorySemanticsType) {
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("AtomicLoad: expected Memory Semantics to be 32-bit int"));
+ HasSubstr("AtomicLoad: expected Memory Semantics to be a 32-bit int"));
}
TEST_F(ValidateAtomics, AtomicStoreKernelSuccess) {
@@ -489,6 +542,31 @@ OpAtomicStore %u32_var %device %sequentially_consistent %u32_1
"Acquire, AcquireRelease and SequentiallyConsistent"));
}
+TEST_F(ValidateAtomics, AtomicStoreWebGPUSuccess) {
+ const std::string body = R"(
+OpAtomicStore %u32_var %device %release %u32_1
+)";
+
+ CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreWebGPUSequentiallyConsistent) {
+ const std::string body = R"(
+OpAtomicStore %u32_var %device %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_1_0 %uint_16 %uint_1\n"));
+}
+
TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) {
const std::string body = R"(
OpAtomicStore %f32_1 %device %relaxed %f32_1
@@ -551,7 +629,7 @@ OpAtomicStore %f32_var %device %f32_1 %f32_1
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("AtomicStore: expected Memory Semantics to be 32-bit int"));
+ HasSubstr("AtomicStore: expected Memory Semantics to be a 32-bit int"));
}
TEST_F(ValidateAtomics, AtomicStoreWrongValueType) {
@@ -623,7 +701,9 @@ TEST_F(ValidateAtomics, AtomicExchangeWrongPointerType) {
CompileSuccessfully(GenerateKernelCode(body));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 33 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 33[%_ptr_Workgroup_v4float] cannot be a "
+ "type"));
}
TEST_F(ValidateAtomics, AtomicExchangeWrongPointerDataType) {
@@ -665,7 +745,8 @@ OpAtomicStore %f32_var %device %relaxed %f32_1
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("AtomicExchange: expected Memory Semantics to be 32-bit int"));
+ HasSubstr(
+ "AtomicExchange: expected Memory Semantics to be a 32-bit int"));
}
TEST_F(ValidateAtomics, AtomicExchangeWrongValueType) {
@@ -736,7 +817,9 @@ TEST_F(ValidateAtomics, AtomicCompareExchangeWrongPointerType) {
CompileSuccessfully(GenerateKernelCode(body));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 33 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 33[%_ptr_Workgroup_v4float] cannot be a "
+ "type"));
}
TEST_F(ValidateAtomics, AtomicCompareExchangeWrongPointerDataType) {
@@ -776,10 +859,9 @@ OpAtomicStore %f32_var %device %relaxed %f32_1
CompileSuccessfully(GenerateKernelCode(body));
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
- EXPECT_THAT(
- getDiagnosticString(),
- HasSubstr(
- "AtomicCompareExchange: expected Memory Semantics to be 32-bit int"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("AtomicCompareExchange: expected Memory Semantics to "
+ "be a 32-bit int"));
}
TEST_F(ValidateAtomics, AtomicCompareExchangeWrongMemorySemanticsType2) {
@@ -790,10 +872,9 @@ OpAtomicStore %f32_var %device %relaxed %f32_1
CompileSuccessfully(GenerateKernelCode(body));
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
- EXPECT_THAT(
- getDiagnosticString(),
- HasSubstr(
- "AtomicCompareExchange: expected Memory Semantics to be 32-bit int"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("AtomicCompareExchange: expected Memory Semantics to "
+ "be a 32-bit int"));
}
TEST_F(ValidateAtomics, AtomicCompareExchangeUnequalRelease) {
@@ -961,7 +1042,7 @@ TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongMemorySemanticsType) {
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("AtomicFlagTestAndSet: "
- "expected Memory Semantics to be 32-bit int"));
+ "expected Memory Semantics to be a 32-bit int"));
}
TEST_F(ValidateAtomics, AtomicFlagClearAcquire) {
@@ -1035,7 +1116,8 @@ OpAtomicFlagClear %u32_var %device %u64_1
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("AtomicFlagClear: expected Memory Semantics to be 32-bit int"));
+ HasSubstr(
+ "AtomicFlagClear: expected Memory Semantics to be a 32-bit int"));
}
TEST_F(ValidateAtomics, AtomicIIncrementAcquireAndRelease) {
@@ -1046,11 +1128,11 @@ OpAtomicStore %u32_var %device %relaxed %u32_1
CompileSuccessfully(GenerateKernelCode(body));
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
- EXPECT_THAT(
- getDiagnosticString(),
- HasSubstr("AtomicIIncrement: no more than one of the following Memory "
- "Semantics bits can be set at the same time: Acquire, Release, "
- "AcquireRelease or SequentiallyConsistent"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("AtomicIIncrement: Memory Semantics can have at most "
+ "one of the following bits set: Acquire, Release, "
+ "AcquireRelease or SequentiallyConsistent\n %40 = "
+ "OpAtomicIIncrement %uint %30 %uint_1_0 %uint_6\n"));
}
TEST_F(ValidateAtomics, AtomicUniformMemorySemanticsShader) {
@@ -1076,18 +1158,24 @@ OpAtomicStore %u32_var %device %relaxed %u32_1
"requires capability Shader"));
}
-TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsNoCapability) {
- const std::string body = R"(
-OpAtomicStore %u32_var %device %relaxed %u32_1
-%val1 = OpAtomicIIncrement %u32 %u32_var %device %acquire_release_atomic_counter_workgroup
-)";
-
- CompileSuccessfully(GenerateKernelCode(body));
- ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(),
- HasSubstr("AtomicIIncrement: Memory Semantics UniformMemory "
- "requires capability AtomicStorage"));
-}
+// Disabling this test until
+// https://github.com/KhronosGroup/glslang/issues/1618 is resolved.
+// TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsNoCapability) {
+// const std::string body = R"(
+// OpAtomicStore %u32_var %device %relaxed %u32_1
+//%val1 = OpAtomicIIncrement %u32 %u32_var %device
+//%acquire_release_atomic_counter_workgroup
+//)";
+//
+// CompileSuccessfully(GenerateKernelCode(body));
+// ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+// EXPECT_THAT(
+// getDiagnosticString(),
+// HasSubstr("AtomicIIncrement: Memory Semantics AtomicCounterMemory "
+// "requires capability AtomicStorage\n %40 = OpAtomicIIncrement
+// "
+// "%uint %30 %uint_1_0 %uint_1288\n"));
+//}
TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsWithCapability) {
const std::string body = R"(
@@ -1638,7 +1726,7 @@ TEST_F(ValidateAtomics, NonVulkanMemoryModelDisallowsQueueFamilyKHR) {
EXPECT_THAT(getDiagnosticString(),
HasSubstr("AtomicAnd: Memory Scope QueueFamilyKHR requires "
"capability VulkanMemoryModelKHR\n %42 = OpAtomicAnd "
- "%uint %33 %uint_5 %uint_0_1 %uint_1\n"));
+ "%uint %29 %uint_5 %uint_0_1 %uint_1\n"));
}
TEST_F(ValidateAtomics, SemanticsSpecConstantShader) {
@@ -1742,6 +1830,40 @@ OpFunctionEnd
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
+TEST_F(ValidateAtomics, VulkanMemoryModelDeviceScopeBad) {
+ const std::string body = R"(
+%val = OpAtomicAnd %u32 %u32_var %device %relaxed %u32_1
+)";
+
+ const std::string extra = R"(OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+
+ CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+ SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateAtomics, VulkanMemoryModelDeviceScopeGood) {
+ const std::string body = R"(
+%val = OpAtomicAnd %u32 %u32_var %device %relaxed %u32_1
+)";
+
+ const std::string extra = R"(OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+
+ CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+ SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
} // namespace
} // namespace val
} // namespace spvtools
diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp
index a886a055..264d130b 100644
--- a/test/val/val_barriers_test.cpp
+++ b/test/val/val_barriers_test.cpp
@@ -69,6 +69,7 @@ OpCapability Shader
%workgroup = OpConstant %u32 2
%subgroup = OpConstant %u32 3
%invocation = OpConstant %u32 4
+%queuefamily = OpConstant %u32 5
%none = OpConstant %u32 0
%acquire = OpConstant %u32 2
@@ -174,9 +175,7 @@ OpMemoryModel Physical32 OpenCL
%acquire_release = OpConstant %u32 8
%acquire_and_release = OpConstant %u32 6
%sequentially_consistent = OpConstant %u32 16
-%acquire_release_uniform_workgroup = OpConstant %u32 328
-%acquire_and_release_uniform = OpConstant %u32 70
-%uniform = OpConstant %u32 64
+%acquire_release_workgroup = OpConstant %u32 264
%named_barrier = OpTypeNamedBarrier
@@ -214,7 +213,7 @@ OpControlBarrier %workgroup %workgroup %acquire
OpControlBarrier %workgroup %device %release
OpControlBarrier %cross_device %cross_device %acquire_release
OpControlBarrier %cross_device %cross_device %sequentially_consistent
-OpControlBarrier %cross_device %cross_device %acquire_release_uniform_workgroup
+OpControlBarrier %cross_device %cross_device %acquire_release_workgroup
)";
CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -248,7 +247,7 @@ OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
TEST_F(ValidateBarriers, OpControlBarrierWebGPUSuccess) {
const std::string body = R"(
-OpControlBarrier %workgroup %device %none
+OpControlBarrier %workgroup %queuefamily %none
OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
)";
@@ -614,8 +613,8 @@ OpMemoryBarrier %device %uniform
TEST_F(ValidateBarriers, OpMemoryBarrierKernelSuccess) {
const std::string body = R"(
-OpMemoryBarrier %cross_device %acquire_release_uniform_workgroup
-OpMemoryBarrier %device %uniform
+OpMemoryBarrier %cross_device %acquire_release_workgroup
+OpMemoryBarrier %device %none
)";
CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -803,7 +802,7 @@ TEST_F(ValidateBarriers, OpNamedBarrierInitializeU64SubgroupCount) {
TEST_F(ValidateBarriers, OpMemoryNamedBarrierSuccess) {
const std::string body = R"(
%barrier = OpNamedBarrierInitialize %named_barrier %u32_4
-OpMemoryNamedBarrier %barrier %workgroup %acquire_release_uniform_workgroup
+OpMemoryNamedBarrier %barrier %workgroup %acquire_release_workgroup
)";
CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -812,7 +811,7 @@ OpMemoryNamedBarrier %barrier %workgroup %acquire_release_uniform_workgroup
TEST_F(ValidateBarriers, OpMemoryNamedBarrierNotNamedBarrier) {
const std::string body = R"(
-OpMemoryNamedBarrier %u32_1 %workgroup %acquire_release_uniform_workgroup
+OpMemoryNamedBarrier %u32_1 %workgroup %acquire_release_workgroup
)";
CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -826,7 +825,7 @@ OpMemoryNamedBarrier %u32_1 %workgroup %acquire_release_uniform_workgroup
TEST_F(ValidateBarriers, OpMemoryNamedBarrierFloatMemoryScope) {
const std::string body = R"(
%barrier = OpNamedBarrierInitialize %named_barrier %u32_4
-OpMemoryNamedBarrier %barrier %f32_1 %acquire_release_uniform_workgroup
+OpMemoryNamedBarrier %barrier %f32_1 %acquire_release_workgroup
)";
CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -856,7 +855,7 @@ OpMemoryNamedBarrier %barrier %workgroup %f32_0
TEST_F(ValidateBarriers, OpMemoryNamedBarrierAcquireAndRelease) {
const std::string body = R"(
%barrier = OpNamedBarrierInitialize %named_barrier %u32_4
-OpMemoryNamedBarrier %barrier %workgroup %acquire_and_release_uniform
+OpMemoryNamedBarrier %barrier %workgroup %acquire_and_release
)";
CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
@@ -875,7 +874,8 @@ OpMemoryBarrier %u32 %u32_0
CompileSuccessfully(GenerateKernelCode(body));
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5[%uint] cannot be a "
+ "type"));
}
TEST_F(ValidateBarriers,
@@ -891,7 +891,7 @@ OpExecutionMode %1 OriginUpperLeft
%3 = OpTypeInt 32 0
%4 = OpConstant %3 16
%5 = OpTypeFunction %2
-%6 = OpConstant %3 1
+%6 = OpConstant %3 5
%1 = OpFunction %2 None %5
%7 = OpLabel
OpControlBarrier %6 %6 %4
@@ -920,7 +920,7 @@ OpExecutionMode %1 OriginUpperLeft
%3 = OpTypeInt 32 0
%4 = OpConstant %3 16
%5 = OpTypeFunction %2
-%6 = OpConstant %3 1
+%6 = OpConstant %3 5
%1 = OpFunction %2 None %5
%7 = OpLabel
OpMemoryBarrier %6 %4
@@ -1225,6 +1225,60 @@ OpFunctionEnd
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
+TEST_F(ValidateBarriers, VulkanMemoryModelDeviceScopeBad) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%semantics = OpConstant %int 0
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpMemoryBarrier %device %semantics
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateBarriers, VulkanMemoryModelDeviceScopeGood) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%semantics = OpConstant %int 0
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpMemoryBarrier %device %semantics
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
} // namespace
} // namespace val
} // namespace spvtools
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index b1458c93..ec075827 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -1926,6 +1926,37 @@ OpStore %output_pos %pos
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}
+TEST_F(ValidateBuiltIns, WorkgroupIdNotVec3) {
+ CodeGenerator generator = GetDefaultShaderCodeGenerator();
+ generator.before_types_ = R"(
+OpDecorate %workgroup_size BuiltIn WorkgroupSize
+OpDecorate %workgroup_id BuiltIn WorkgroupId
+)";
+
+ generator.after_types_ = R"(
+%workgroup_size = OpConstantComposite %u32vec3 %u32_1 %u32_1 %u32_1
+ %input_ptr = OpTypePointer Input %u32vec2
+ %workgroup_id = OpVariable %input_ptr Input
+)";
+
+ EntryPoint entry_point;
+ entry_point.name = "main";
+ entry_point.execution_model = "GLCompute";
+ entry_point.interfaces = "%workgroup_id";
+ entry_point.body = R"(
+%copy_size = OpCopyObject %u32vec3 %workgroup_size
+ %load_id = OpLoad %u32vec2 %workgroup_id
+)";
+ generator.entry_points_.push_back(std::move(entry_point));
+
+ CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
+ ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("According to the Vulkan spec BuiltIn WorkgroupId "
+ "variable needs to be a 3-component 32-bit int vector. "
+ "ID <2> (OpVariable) has 2 components."));
+}
+
TEST_F(ValidateBuiltIns, TwoBuiltInsFirstFails) {
CodeGenerator generator = GetDefaultShaderCodeGenerator();
diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp
index f5650abd..488e957a 100644
--- a/test/val/val_capability_test.cpp
+++ b/test/val/val_capability_test.cpp
@@ -1139,9 +1139,12 @@ std::make_pair(std::string(kOpenCLMemoryModel) +
"%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
AllCapabilities()),
std::make_pair(std::string(kOpenCLMemoryModel) +
+ // Uniform must target a non-void value.
"OpEntryPoint Kernel %func \"compute\" \n"
- "OpDecorate %intt Uniform\n"
- "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+ "OpDecorate %int0 Uniform\n"
+ "%intt = OpTypeInt 32 0\n" +
+ "%int0 = OpConstantNull %intt"
+ + std::string(kVoidFVoid),
ShaderDependencies()),
std::make_pair(std::string(kGLSL450MemoryModel) +
"OpEntryPoint Vertex %func \"shader\" \n"
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index f741ca29..aed0a578 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -378,8 +378,8 @@ TEST_P(ValidateCFG, BlockAppearsBeforeDominatorBad) {
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("Block .\\[cont\\] appears in the binary "
- "before its dominator .\\[branch\\]\n"
+ MatchesRegex("Block .\\[%cont\\] appears in the binary "
+ "before its dominator .\\[%branch\\]\n"
" %branch = OpLabel\n"));
}
@@ -410,7 +410,7 @@ TEST_P(ValidateCFG, MergeBlockTargetedByMultipleHeaderBlocksBad) {
if (is_shader) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("Block .\\[merge\\] is already a merge block "
+ MatchesRegex("Block .\\[%merge\\] is already a merge block "
"for another header\n"
" %Main = OpFunction %void None %9\n"));
} else {
@@ -445,7 +445,7 @@ TEST_P(ValidateCFG, MergeBlockTargetedByMultipleHeaderBlocksSelectionBad) {
if (is_shader) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("Block .\\[merge\\] is already a merge block "
+ MatchesRegex("Block .\\[%merge\\] is already a merge block "
"for another header\n"
" %Main = OpFunction %void None %9\n"));
} else {
@@ -470,8 +470,8 @@ TEST_P(ValidateCFG, BranchTargetFirstBlockBadSinceEntryBlock) {
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] "
- "is targeted by block .\\[bad\\]\n"
+ MatchesRegex("First block .\\[%entry\\] of function "
+ ".\\[%Main\\] is targeted by block .\\[%bad\\]\n"
" %Main = OpFunction %void None %10\n"));
}
@@ -494,10 +494,11 @@ TEST_P(ValidateCFG, BranchTargetFirstBlockBadSinceValue) {
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("Block\\(s\\) \\{..\\} are referenced but not "
- "defined in function .\\[Main\\]\n"
- " %Main = OpFunction %void None %10\n"))
+ EXPECT_THAT(
+ getDiagnosticString(),
+ MatchesRegex("Block\\(s\\) \\{11\\[%11\\]\\} are referenced but not "
+ "defined in function .\\[%Main\\]\n %Main = OpFunction "
+ "%void None %10\n"))
<< str;
}
@@ -522,8 +523,8 @@ TEST_P(ValidateCFG, BranchConditionalTrueTargetFirstBlockBad) {
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] "
- "is targeted by block .\\[bad\\]\n"
+ MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
+ "is targeted by block .\\[%bad\\]\n"
" %Main = OpFunction %void None %10\n"));
}
@@ -551,8 +552,8 @@ TEST_P(ValidateCFG, BranchConditionalFalseTargetFirstBlockBad) {
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] "
- "is targeted by block .\\[bad\\]\n"
+ MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
+ "is targeted by block .\\[%bad\\]\n"
" %Main = OpFunction %void None %10\n"));
}
@@ -587,8 +588,8 @@ TEST_P(ValidateCFG, SwitchTargetFirstBlockBad) {
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] "
- "is targeted by block .\\[bad\\]\n"
+ MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
+ "is targeted by block .\\[%bad\\]\n"
" %Main = OpFunction %void None %10\n"));
}
@@ -623,8 +624,8 @@ TEST_P(ValidateCFG, BranchToBlockInOtherFunctionBad) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- MatchesRegex("Block\\(s\\) \\{.\\[middle2\\]\\} are referenced but not "
- "defined in function .\\[Main\\]\n"
+ MatchesRegex("Block\\(s\\) \\{.\\[%middle2\\]\\} are referenced but not "
+ "defined in function .\\[%Main\\]\n"
" %Main = OpFunction %void None %9\n"));
}
@@ -656,8 +657,8 @@ TEST_P(ValidateCFG, HeaderDoesntDominatesMergeBad) {
EXPECT_THAT(
getDiagnosticString(),
MatchesRegex("The selection construct with the selection header "
- ".\\[head\\] does not dominate the merge block "
- ".\\[merge\\]\n %merge = OpLabel\n"));
+ ".\\[%head\\] does not dominate the merge block "
+ ".\\[%merge\\]\n %merge = OpLabel\n"));
} else {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
@@ -689,8 +690,8 @@ TEST_P(ValidateCFG, HeaderDoesntStrictlyDominateMergeBad) {
EXPECT_THAT(
getDiagnosticString(),
MatchesRegex("The selection construct with the selection header "
- ".\\[head\\] does not strictly dominate the merge block "
- ".\\[head\\]\n %head = OpLabel\n"));
+ ".\\[%head\\] does not strictly dominate the merge block "
+ ".\\[%head\\]\n %head = OpLabel\n"));
} else {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str;
}
@@ -940,8 +941,8 @@ TEST_P(ValidateCFG, BackEdgeBlockDoesntPostDominateContinueTargetBad) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
MatchesRegex("The continue construct with the continue target "
- ".\\[loop2_merge\\] is not post dominated by the "
- "back-edge block .\\[be_block\\]\n"
+ ".\\[%loop2_merge\\] is not post dominated by the "
+ "back-edge block .\\[%be_block\\]\n"
" %be_block = OpLabel\n"));
} else {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -975,7 +976,7 @@ TEST_P(ValidateCFG, BranchingToNonLoopHeaderBlockBad) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- MatchesRegex("Back-edges \\(.\\[f\\] -> .\\[split\\]\\) can only "
+ MatchesRegex("Back-edges \\(.\\[%f\\] -> .\\[%split\\]\\) can only "
"be formed between a block and a loop header.\n"
" %f = OpLabel\n"));
} else {
@@ -1003,11 +1004,11 @@ TEST_P(ValidateCFG, BranchingToSameNonLoopHeaderBlockBad) {
CompileSuccessfully(str);
if (is_shader) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(),
- MatchesRegex(
- "Back-edges \\(.\\[split\\] -> .\\[split\\]\\) can only be "
- "formed between a block and a loop header.\n"
- " %split = OpLabel\n"));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ MatchesRegex(
+ "Back-edges \\(.\\[%split\\] -> .\\[%split\\]\\) can only be "
+ "formed between a block and a loop header.\n %split = OpLabel\n"));
} else {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
@@ -1038,11 +1039,11 @@ TEST_P(ValidateCFG, MultipleBackEdgeBlocksToLoopHeaderBad) {
CompileSuccessfully(str);
if (is_shader) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(),
- MatchesRegex(
- "Loop header .\\[loop\\] is targeted by 2 back-edge blocks "
- "but the standard requires exactly one\n"
- " %loop = OpLabel\n"))
+ EXPECT_THAT(
+ getDiagnosticString(),
+ MatchesRegex(
+ "Loop header .\\[%loop\\] is targeted by 2 back-edge blocks but "
+ "the standard requires exactly one\n %loop = OpLabel\n"))
<< str;
} else {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -1078,8 +1079,8 @@ TEST_P(ValidateCFG, ContinueTargetMustBePostDominatedByBackEdge) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
MatchesRegex("The continue construct with the continue target "
- ".\\[cheader\\] is not post dominated by the "
- "back-edge block .\\[be_block\\]\n"
+ ".\\[%cheader\\] is not post dominated by the "
+ "back-edge block .\\[%be_block\\]\n"
" %be_block = OpLabel\n"));
} else {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -1111,8 +1112,8 @@ TEST_P(ValidateCFG, BranchOutOfConstructToMergeBad) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
MatchesRegex("The continue construct with the continue target "
- ".\\[loop\\] is not post dominated by the "
- "back-edge block .\\[cont\\]\n"
+ ".\\[%loop\\] is not post dominated by the "
+ "back-edge block .\\[%cont\\]\n"
" %cont = OpLabel\n"))
<< str;
} else {
@@ -1147,8 +1148,8 @@ TEST_P(ValidateCFG, BranchOutOfConstructBad) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
MatchesRegex("The continue construct with the continue target "
- ".\\[loop\\] is not post dominated by the "
- "back-edge block .\\[cont\\]\n"
+ ".\\[%loop\\] is not post dominated by the "
+ "back-edge block .\\[%cont\\]\n"
" %cont = OpLabel\n"));
} else {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -1222,7 +1223,7 @@ TEST_F(ValidateCFG, LoopWithZeroBackEdgesBad) {
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- MatchesRegex("Loop header .\\[loop\\] is targeted by "
+ MatchesRegex("Loop header .\\[%loop\\] is targeted by "
"0 back-edge blocks but the standard requires exactly "
"one\n %loop = OpLabel\n"));
}
@@ -1567,8 +1568,8 @@ OpFunctionEnd
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "Case construct that targets 10 has branches to multiple other case "
- "construct targets 12 and 11\n %10 = OpLabel"));
+ "Case construct that targets 10[%10] has branches to multiple other "
+ "case construct targets 12[%12] and 11[%11]\n %10 = OpLabel"));
}
TEST_F(ValidateCFG, MultipleFallThroughToDefault) {
@@ -1602,7 +1603,7 @@ OpFunctionEnd
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Multiple case constructs have branches to the case construct "
- "that targets 10\n %10 = OpLabel"));
+ "that targets 10[%10]\n %10 = OpLabel"));
}
TEST_F(ValidateCFG, MultipleFallThroughToNonDefault) {
@@ -1636,7 +1637,7 @@ OpFunctionEnd
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Multiple case constructs have branches to the case construct "
- "that targets 12\n %12 = OpLabel"));
+ "that targets 12[%12]\n %12 = OpLabel"));
}
TEST_F(ValidateCFG, DuplicateTargetWithFallThrough) {
@@ -1697,8 +1698,8 @@ OpFunctionEnd
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("Case construct that targets 12 has branches to the case "
- "construct that targets 11, but does not immediately "
+ HasSubstr("Case construct that targets 12[%12] has branches to the case "
+ "construct that targets 11[%11], but does not immediately "
"precede it in the OpSwitch's target list\n"
" OpSwitch %uint_0 %10 0 %11 1 %12"));
}
@@ -1733,8 +1734,8 @@ OpFunctionEnd
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("Case construct that targets 12 has branches to the case "
- "construct that targets 11, but does not immediately "
+ HasSubstr("Case construct that targets 12[%12] has branches to the case "
+ "construct that targets 11[%11], but does not immediately "
"precede it in the OpSwitch's target list\n"
" OpSwitch %uint_0 %10 0 %11 1 %12"));
}
@@ -1771,8 +1772,8 @@ OpFunctionEnd
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("Case construct that targets 12 has branches to the case "
- "construct that targets 11, but does not immediately "
+ HasSubstr("Case construct that targets 12[%12] has branches to the case "
+ "construct that targets 11[%11], but does not immediately "
"precede it in the OpSwitch's target list\n"
" OpSwitch %uint_0 %10 0 %11 1 %12 2 %13"));
}
@@ -1839,9 +1840,10 @@ OpFunctionEnd
CompileSuccessfully(text);
ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Case construct that targets 8 has invalid branch to "
- "block 10 (not another case construct, corresponding "
- "merge, outer loop merge or outer loop continue"));
+ HasSubstr("Case construct that targets 8[%8] has invalid branch "
+ "to block 10[%10] (not another case construct, "
+ "corresponding merge, outer loop merge or outer loop "
+ "continue)"));
}
TEST_F(ValidateCFG, GoodCaseExitsToOuterConstructs) {
diff --git a/test/val/val_composites_test.cpp b/test/val/val_composites_test.cpp
index ec97d30a..92c4cc39 100644
--- a/test/val/val_composites_test.cpp
+++ b/test/val/val_composites_test.cpp
@@ -321,7 +321,8 @@ TEST_F(ValidateComposites, CompositeConstructVectorWrongConsituent1) {
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5[%float] cannot be a "
+ "type"));
}
TEST_F(ValidateComposites, CompositeConstructVectorWrongConsituent2) {
@@ -537,7 +538,8 @@ TEST_F(ValidateComposites, CopyObjectResultTypeNotType) {
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 19 is not a type id"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("ID 19[%float_0] is not a type id"));
}
TEST_F(ValidateComposites, CopyObjectWrongOperandType) {
@@ -658,7 +660,8 @@ TEST_F(ValidateComposites, CompositeExtractNotObject) {
CompileSuccessfully(GenerateShaderCode(body));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 11 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 11[%v4float] cannot "
+ "be a type"));
}
TEST_F(ValidateComposites, CompositeExtractNotComposite) {
diff --git a/test/val/val_conversion_test.cpp b/test/val/val_conversion_test.cpp
index 1c968707..4161c742 100644
--- a/test/val/val_conversion_test.cpp
+++ b/test/val/val_conversion_test.cpp
@@ -973,7 +973,8 @@ TEST_F(ValidateConversion, PtrCastToGenericWrongInputType) {
CompileSuccessfully(GenerateKernelCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4[%float] cannot be a "
+ "type"));
}
TEST_F(ValidateConversion, PtrCastToGenericWrongInputStorageClass) {
@@ -1208,7 +1209,8 @@ TEST_F(ValidateConversion, BitcastInputHasNoType) {
CompileSuccessfully(GenerateKernelCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4[%float] cannot be a "
+ "type"));
}
TEST_F(ValidateConversion, BitcastWrongResultType) {
@@ -1296,7 +1298,8 @@ OpFunctionEnd
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%uint] cannot be a "
+ "type"));
}
} // namespace
diff --git a/test/val/val_data_test.cpp b/test/val/val_data_test.cpp
index e6bb6731..fcf447a5 100644
--- a/test/val/val_data_test.cpp
+++ b/test/val/val_data_test.cpp
@@ -386,7 +386,8 @@ TEST_F(ValidateData, ids_should_be_validated_before_data) {
)";
CompileSuccessfully(str.c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 3 has not been defined"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("ID 3[%3] has not been defined"));
}
TEST_F(ValidateData, matrix_bad_column_type) {
@@ -573,8 +574,8 @@ OpTypeForwardPointer %_ptr_Generic_struct_A Generic
CompileSuccessfully(str.c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Found a forward reference to a non-pointer type in "
- "OpTypeStruct instruction."));
+ HasSubstr("Pointer type in OpTypeForwardPointer is not a pointer "
+ "type.\n OpTypeForwardPointer %float Generic\n"));
}
TEST_F(ValidateData, forward_ref_points_to_non_struct) {
@@ -684,8 +685,9 @@ TEST_F(ValidateData, void_array) {
CompileSuccessfully(str.c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpTypeArray Element Type <id> '1' is a void type."));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("OpTypeArray Element Type <id> '1[%void]' is a void type."));
}
TEST_F(ValidateData, void_runtime_array) {
@@ -698,7 +700,8 @@ TEST_F(ValidateData, void_runtime_array) {
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpTypeRuntimeArray Element Type <id> '1' is a void type."));
+ HasSubstr(
+ "OpTypeRuntimeArray Element Type <id> '1[%void]' is a void type."));
}
} // namespace
} // namespace val
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index c67cf454..3a4320db 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -37,7 +37,7 @@ TEST_F(ValidateDecorations, ValidateOpDecorateRegistration) {
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpDecorate %1 ArrayStride 4
- OpDecorate %1 Uniform
+ OpDecorate %1 RelaxedPrecision
%2 = OpTypeFloat 32
%1 = OpTypeRuntimeArray %2
; Since %1 is used first in Decoration, it gets id 1.
@@ -49,7 +49,7 @@ TEST_F(ValidateDecorations, ValidateOpDecorateRegistration) {
EXPECT_THAT(
vstate_->id_decorations(id),
Eq(std::vector<Decoration>{Decoration(SpvDecorationArrayStride, {4}),
- Decoration(SpvDecorationUniform)}));
+ Decoration(SpvDecorationRelaxedPrecision)}));
}
TEST_F(ValidateDecorations, ValidateOpMemberDecorateRegistration) {
@@ -105,8 +105,8 @@ TEST_F(ValidateDecorations, ValidateOpMemberDecorateOutOfBound) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Index 1 provided in OpMemberDecorate for struct <id> "
- "2 is out of bounds. The structure has 1 members. "
- "Largest valid index is 0."));
+ "2[%_struct_2] is out of bounds. The structure has 1 "
+ "members. Largest valid index is 0."));
}
TEST_F(ValidateDecorations, ValidateGroupDecorateRegistration) {
@@ -300,10 +300,11 @@ TEST_F(ValidateDecorations, StructContainsBuiltInStructBad) {
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Structure <id> 1 contains members with BuiltIn "
- "decoration. Therefore this structure may not be "
- "contained as a member of another structure type. "
- "Structure <id> 4 contains structure <id> 1."));
+ HasSubstr("Structure <id> 1[%_struct_1] contains members with "
+ "BuiltIn decoration. Therefore this structure may not "
+ "be contained as a member of another structure type. "
+ "Structure <id> 4[%_struct_4] contains structure <id> "
+ "1[%_struct_1]."));
}
TEST_F(ValidateDecorations, StructContainsNonBuiltInStructGood) {
@@ -2012,6 +2013,8 @@ TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithRelaxedLayoutStillBad) {
OpEntryPoint Vertex %main "main"
OpSource GLSL 450
OpDecorate %_arr_float_uint_2 ArrayStride 16
+ OpDecorate %u DescriptorSet 0
+ OpDecorate %u Binding 0
OpMemberDecorate %S 0 Offset 0
OpMemberDecorate %S 1 Offset 8
OpDecorate %S Block
@@ -2038,7 +2041,7 @@ TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithRelaxedLayoutStillBad) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "Structure id 3 decorated as Block for variable in Uniform "
+ "Structure id 4 decorated as Block for variable in Uniform "
"storage class must follow standard uniform buffer layout rules: "
"member 1 at offset 8 is not aligned to 16"));
}
@@ -2052,6 +2055,8 @@ TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithVulkan1_1StillBad) {
OpEntryPoint Vertex %main "main"
OpSource GLSL 450
OpDecorate %_arr_float_uint_2 ArrayStride 16
+ OpDecorate %u DescriptorSet 0
+ OpDecorate %u Binding 0
OpMemberDecorate %S 0 Offset 0
OpMemberDecorate %S 1 Offset 8
OpDecorate %S Block
@@ -2077,7 +2082,7 @@ TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithVulkan1_1StillBad) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "Structure id 3 decorated as Block for variable in Uniform "
+ "Structure id 4 decorated as Block for variable in Uniform "
"storage class must follow relaxed uniform buffer layout rules: "
"member 1 at offset 8 is not aligned to 16"));
}
@@ -2278,6 +2283,738 @@ TEST_F(ValidateDecorations, VulkanPushConstantMissingBlockBad) {
"decoration"));
}
+TEST_F(ValidateDecorations, MultiplePushConstantsSingleEntryPointGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %2 = OpAccessChain %ptr_float %pc1 %int_0
+ %3 = OpLoad %float %2
+ %4 = OpAccessChain %ptr_float %pc2 %int_0
+ %5 = OpLoad %float %4
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations,
+ VulkanMultiplePushConstantsDifferentEntryPointGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "func1"
+ OpEntryPoint Fragment %2 "func2"
+ OpExecutionMode %2 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label1 = OpLabel
+ %3 = OpAccessChain %ptr_float %pc1 %int_0
+ %4 = OpLoad %float %3
+ OpReturn
+ OpFunctionEnd
+
+ %2 = OpFunction %void None %voidfn
+ %label2 = OpLabel
+ %5 = OpAccessChain %ptr_float %pc2 %int_0
+ %6 = OpLoad %float %5
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1))
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations,
+ VulkanMultiplePushConstantsUnusedSingleEntryPointGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1))
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, VulkanMultiplePushConstantsSingleEntryPointBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %2 = OpAccessChain %ptr_float %pc1 %int_0
+ %3 = OpLoad %float %2
+ %4 = OpAccessChain %ptr_float %pc2 %int_0
+ %5 = OpLoad %float %4
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr(
+ "Entry point id '1' uses more than one PushConstant interface.\n"
+ "From Vulkan spec, section 14.5.1:\n"
+ "There must be no more than one push constant block "
+ "statically used per shader entry point."));
+}
+
+TEST_F(ValidateDecorations,
+ VulkanMultiplePushConstantsDifferentEntryPointSubFunctionGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "func1"
+ OpEntryPoint Fragment %2 "func2"
+ OpExecutionMode %2 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %sub1 = OpFunction %void None %voidfn
+ %label_sub1 = OpLabel
+ %3 = OpAccessChain %ptr_float %pc1 %int_0
+ %4 = OpLoad %float %3
+ OpReturn
+ OpFunctionEnd
+
+ %sub2 = OpFunction %void None %voidfn
+ %label_sub2 = OpLabel
+ %5 = OpAccessChain %ptr_float %pc2 %int_0
+ %6 = OpLoad %float %5
+ OpReturn
+ OpFunctionEnd
+
+ %1 = OpFunction %void None %voidfn
+ %label1 = OpLabel
+ %call1 = OpFunctionCall %void %sub1
+ OpReturn
+ OpFunctionEnd
+
+ %2 = OpFunction %void None %voidfn
+ %label2 = OpLabel
+ %call2 = OpFunctionCall %void %sub2
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1))
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations,
+ VulkanMultiplePushConstantsSingleEntryPointSubFunctionBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer PushConstant %struct
+ %ptr_float = OpTypePointer PushConstant %float
+ %pc1 = OpVariable %ptr PushConstant
+ %pc2 = OpVariable %ptr PushConstant
+
+ %sub1 = OpFunction %void None %voidfn
+ %label_sub1 = OpLabel
+ %3 = OpAccessChain %ptr_float %pc1 %int_0
+ %4 = OpLoad %float %3
+ OpReturn
+ OpFunctionEnd
+
+ %sub2 = OpFunction %void None %voidfn
+ %label_sub2 = OpLabel
+ %5 = OpAccessChain %ptr_float %pc2 %int_0
+ %6 = OpLoad %float %5
+ OpReturn
+ OpFunctionEnd
+
+ %1 = OpFunction %void None %voidfn
+ %label1 = OpLabel
+ %call1 = OpFunctionCall %void %sub1
+ %call2 = OpFunctionCall %void %sub2
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr(
+ "Entry point id '1' uses more than one PushConstant interface.\n"
+ "From Vulkan spec, section 14.5.1:\n"
+ "There must be no more than one push constant block "
+ "statically used per shader entry point."));
+}
+
+TEST_F(ValidateDecorations, VulkanUniformMissingDescriptorSetBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+ OpDecorate %var Binding 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer Uniform %struct
+%ptr_float = OpTypePointer Uniform %float
+ %var = OpVariable %ptr Uniform
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %2 = OpAccessChain %ptr_float %var %int_0
+ %3 = OpLoad %float %2
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Uniform id '3' is missing DescriptorSet decoration.\n"
+ "From Vulkan spec, section 14.5.2:\n"
+ "These variables must have DescriptorSet and Binding "
+ "decorations specified"));
+}
+
+TEST_F(ValidateDecorations, VulkanUniformMissingBindingBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+ OpDecorate %var DescriptorSet 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer Uniform %struct
+%ptr_float = OpTypePointer Uniform %float
+ %var = OpVariable %ptr Uniform
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %2 = OpAccessChain %ptr_float %var %int_0
+ %3 = OpLoad %float %2
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Uniform id '3' is missing Binding decoration.\n"
+ "From Vulkan spec, section 14.5.2:\n"
+ "These variables must have DescriptorSet and Binding "
+ "decorations specified"));
+}
+
+TEST_F(ValidateDecorations, VulkanUniformConstantMissingDescriptorSetBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %var Binding 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %sampler = OpTypeSampler
+ %ptr = OpTypePointer UniformConstant %sampler
+ %var = OpVariable %ptr UniformConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %2 = OpLoad %sampler %var
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("UniformConstant id '2' is missing DescriptorSet decoration.\n"
+ "From Vulkan spec, section 14.5.2:\n"
+ "These variables must have DescriptorSet and Binding "
+ "decorations specified"));
+}
+
+TEST_F(ValidateDecorations, VulkanUniformConstantMissingBindingBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %var DescriptorSet 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %sampler = OpTypeSampler
+ %ptr = OpTypePointer UniformConstant %sampler
+ %var = OpVariable %ptr UniformConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %2 = OpLoad %sampler %var
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("UniformConstant id '2' is missing Binding decoration.\n"
+ "From Vulkan spec, section 14.5.2:\n"
+ "These variables must have DescriptorSet and Binding "
+ "decorations specified"));
+}
+
+TEST_F(ValidateDecorations, VulkanStorageBufferMissingDescriptorSetBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpExtension "SPV_KHR_storage_buffer_storage_class"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpDecorate %var Binding 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer StorageBuffer %struct
+ %var = OpVariable %ptr StorageBuffer
+%ptr_float = OpTypePointer StorageBuffer %float
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %2 = OpAccessChain %ptr_float %var %int_0
+ %3 = OpLoad %float %2
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("StorageBuffer id '3' is missing DescriptorSet decoration.\n"
+ "From Vulkan spec, section 14.5.2:\n"
+ "These variables must have DescriptorSet and Binding "
+ "decorations specified"));
+}
+
+TEST_F(ValidateDecorations, VulkanStorageBufferMissingBindingBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpExtension "SPV_KHR_storage_buffer_storage_class"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpDecorate %var DescriptorSet 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer StorageBuffer %struct
+ %var = OpVariable %ptr StorageBuffer
+%ptr_float = OpTypePointer StorageBuffer %float
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %2 = OpAccessChain %ptr_float %var %int_0
+ %3 = OpLoad %float %2
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("StorageBuffer id '3' is missing Binding decoration.\n"
+ "From Vulkan spec, section 14.5.2:\n"
+ "These variables must have DescriptorSet and Binding "
+ "decorations specified"));
+}
+
+TEST_F(ValidateDecorations,
+ VulkanStorageBufferMissingDescriptorSetSubFunctionBad) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpExtension "SPV_KHR_storage_buffer_storage_class"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpDecorate %var Binding 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer StorageBuffer %struct
+ %var = OpVariable %ptr StorageBuffer
+%ptr_float = OpTypePointer StorageBuffer %float
+ %int = OpTypeInt 32 0
+ %int_0 = OpConstant %int 0
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ %call = OpFunctionCall %void %2
+ OpReturn
+ OpFunctionEnd
+ %2 = OpFunction %void None %voidfn
+ %label2 = OpLabel
+ %3 = OpAccessChain %ptr_float %var %int_0
+ %4 = OpLoad %float %3
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("StorageBuffer id '3' is missing DescriptorSet decoration.\n"
+ "From Vulkan spec, section 14.5.2:\n"
+ "These variables must have DescriptorSet and Binding "
+ "decorations specified"));
+}
+
+TEST_F(ValidateDecorations,
+ VulkanStorageBufferMissingDescriptorAndBindingUnusedGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpExtension "SPV_KHR_storage_buffer_storage_class"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct BufferBlock
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer StorageBuffer %struct
+ %var = OpVariable %ptr StorageBuffer
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_SUCCESS,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateDecorations, UniformMissingDescriptorSetGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+ OpDecorate %var Binding 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer Uniform %struct
+ %var = OpVariable %ptr Uniform
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, UniformMissingBindingGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+ OpDecorate %var DescriptorSet 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer Uniform %struct
+ %var = OpVariable %ptr Uniform
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, UniformConstantMissingDescriptorSetGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %var Binding 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %sampler = OpTypeSampler
+ %ptr = OpTypePointer UniformConstant %sampler
+ %var = OpVariable %ptr UniformConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, UniformConstantMissingBindingGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %var DescriptorSet 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %sampler = OpTypeSampler
+ %ptr = OpTypePointer UniformConstant %sampler
+ %var = OpVariable %ptr UniformConstant
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, StorageBufferMissingDescriptorSetGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpExtension "SPV_KHR_storage_buffer_storage_class"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct BufferBlock
+ OpDecorate %var Binding 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer StorageBuffer %struct
+ %var = OpVariable %ptr StorageBuffer
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+ << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, StorageBufferMissingBindingGood) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpExtension "SPV_KHR_storage_buffer_storage_class"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct BufferBlock
+ OpDecorate %var DescriptorSet 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+ %ptr = OpTypePointer StorageBuffer %struct
+ %var = OpVariable %ptr StorageBuffer
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState())
+ << getDiagnosticString();
+}
+
TEST_F(ValidateDecorations, StorageBufferStorageClassArrayBaseAlignmentGood) {
// Spot check buffer rules when using StorageBuffer storage class with Block
// decoration.
@@ -3322,8 +4059,8 @@ OpDecorate %1 Coherent
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Coherent decoration targeting 1 is banned when using "
- "the Vulkan memory model."));
+ HasSubstr("Coherent decoration targeting 1[%1] is "
+ "banned when using the Vulkan memory model."));
}
TEST_F(ValidateDecorations, VulkanMemoryModelNoCoherentMember) {
@@ -3340,10 +4077,10 @@ OpMemberDecorate %1 0 Coherent
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
- EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Coherent decoration targeting 1 (member index 0) is "
- "banned when using "
- "the Vulkan memory model."));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Coherent decoration targeting 1[%_struct_1] (member index 0) "
+ "is banned when using the Vulkan memory model."));
}
TEST_F(ValidateDecorations, VulkanMemoryModelNoVolatile) {
@@ -3363,8 +4100,8 @@ OpDecorate %1 Volatile
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Volatile decoration targeting 1 is banned when using "
- "the Vulkan memory model."));
+ HasSubstr("Volatile decoration targeting 1[%1] is banned when "
+ "using the Vulkan memory model."));
}
TEST_F(ValidateDecorations, VulkanMemoryModelNoVolatileMember) {
@@ -3382,9 +4119,9 @@ OpMemberDecorate %1 1 Volatile
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Volatile decoration targeting 1 (member index 1) is "
- "banned when using "
- "the Vulkan memory model."));
+ HasSubstr("Volatile decoration targeting 1[%_struct_1] (member "
+ "index 1) is banned when using the Vulkan memory "
+ "model."));
}
TEST_F(ValidateDecorations, FPRoundingModeGood) {
@@ -3696,9 +4433,9 @@ OpGroupDecorate %1 %1
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(
- getDiagnosticString(),
- HasSubstr("OpGroupDecorate may not target OpDecorationGroup <id> '1'"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("OpGroupDecorate may not target OpDecorationGroup <id> "
+ "'1[%1]'"));
}
TEST_F(ValidateDecorations, GroupDecorateTargetsDecorationGroup2) {
@@ -3713,9 +4450,9 @@ OpGroupDecorate %1 %2 %1
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(
- getDiagnosticString(),
- HasSubstr("OpGroupDecorate may not target OpDecorationGroup <id> '1'"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("OpGroupDecorate may not target OpDecorationGroup <id> "
+ "'1[%1]'"));
}
TEST_F(ValidateDecorations, RecurseThroughRuntimeArray) {
@@ -3773,6 +4510,306 @@ OpFunctionEnd
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
+// Uniform decoration
+
+TEST_F(ValidateDecorations, UniformDecorationGood) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %int0 Uniform
+OpDecorate %var Uniform
+OpDecorate %val Uniform
+%void = OpTypeVoid
+%int = OpTypeInt 32 1
+%int0 = OpConstantNull %int
+%intptr = OpTypePointer Private %int
+%var = OpVariable %intptr Private
+%fn = OpTypeFunction %void
+%main = OpFunction %void None %fn
+%entry = OpLabel
+%val = OpLoad %int %var
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationTargetsTypeBad) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %fn Uniform
+%void = OpTypeVoid
+%fn = OpTypeFunction %void
+%main = OpFunction %void None %fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Uniform decoration applied to a non-object"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("%2 = OpTypeFunction %void"));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationTargetsVoidValueBad) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpName %call "call"
+OpName %myfunc "myfunc"
+OpDecorate %call Uniform
+%void = OpTypeVoid
+%fnty = OpTypeFunction %void
+%myfunc = OpFunction %void None %fnty
+%myfuncentry = OpLabel
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %fnty
+%entry = OpLabel
+%call = OpFunctionCall %void %myfunc
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Uniform decoration applied to a value with void type\n"
+ " %call = OpFunctionCall %void %myfunc"));
+}
+
+TEST_F(ValidateDecorations, MultipleOffsetDecorationsOnSameID) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpMemberDecorate %struct 0 Offset 0
+ OpMemberDecorate %struct 0 Offset 0
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %struct = OpTypeStruct %float
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("ID '2', member '0' decorated with Offset multiple "
+ "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleArrayStrideDecorationsOnSameID) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %array ArrayStride 4
+ OpDecorate %array ArrayStride 4
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %uint = OpTypeInt 32 0
+ %uint_4 = OpConstant %uint 4
+ %array = OpTypeArray %float %uint_4
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("ID '2' decorated with ArrayStride multiple "
+ "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleMatrixStrideDecorationsOnSameID) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpMemberDecorate %struct 0 Offset 0
+ OpMemberDecorate %struct 0 ColMajor
+ OpMemberDecorate %struct 0 MatrixStride 16
+ OpMemberDecorate %struct 0 MatrixStride 16
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %fvec4 = OpTypeVector %float 4
+ %fmat4 = OpTypeMatrix %fvec4 4
+ %struct = OpTypeStruct %fmat4
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("ID '2', member '0' decorated with MatrixStride "
+ "multiple times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleRowMajorDecorationsOnSameID) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpMemberDecorate %struct 0 Offset 0
+ OpMemberDecorate %struct 0 MatrixStride 16
+ OpMemberDecorate %struct 0 RowMajor
+ OpMemberDecorate %struct 0 RowMajor
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %fvec4 = OpTypeVector %float 4
+ %fmat4 = OpTypeMatrix %fvec4 4
+ %struct = OpTypeStruct %fmat4
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("ID '2', member '0' decorated with RowMajor multiple "
+ "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, MultipleColMajorDecorationsOnSameID) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpMemberDecorate %struct 0 Offset 0
+ OpMemberDecorate %struct 0 MatrixStride 16
+ OpMemberDecorate %struct 0 ColMajor
+ OpMemberDecorate %struct 0 ColMajor
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %fvec4 = OpTypeVector %float 4
+ %fmat4 = OpTypeMatrix %fvec4 4
+ %struct = OpTypeStruct %fmat4
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("ID '2', member '0' decorated with ColMajor multiple "
+ "times is not allowed."));
+}
+
+TEST_F(ValidateDecorations, RowMajorAndColMajorDecorationsOnSameID) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpMemberDecorate %struct 0 Offset 0
+ OpMemberDecorate %struct 0 MatrixStride 16
+ OpMemberDecorate %struct 0 ColMajor
+ OpMemberDecorate %struct 0 RowMajor
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %fvec4 = OpTypeVector %float 4
+ %fmat4 = OpTypeMatrix %fvec4 4
+ %struct = OpTypeStruct %fmat4
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("ID '2', member '0' decorated with both RowMajor and "
+ "ColMajor is not allowed."));
+}
+
+TEST_F(ValidateDecorations, BlockAndBufferBlockDecorationsOnSameID) {
+ std::string spirv = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginUpperLeft
+
+ OpDecorate %struct Block
+ OpDecorate %struct BufferBlock
+ OpMemberDecorate %struct 0 Offset 0
+ OpMemberDecorate %struct 0 MatrixStride 16
+ OpMemberDecorate %struct 0 RowMajor
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %fvec4 = OpTypeVector %float 4
+ %fmat4 = OpTypeMatrix %fvec4 4
+ %struct = OpTypeStruct %fmat4
+
+ %1 = OpFunction %void None %voidfn
+ %label = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr(
+ "ID '2' decorated with both BufferBlock and Block is not allowed."));
+}
+
} // namespace
} // namespace val
} // namespace spvtools
diff --git a/test/val/val_derivatives_test.cpp b/test/val/val_derivatives_test.cpp
index 19e1cf33..480042a7 100644
--- a/test/val/val_derivatives_test.cpp
+++ b/test/val/val_derivatives_test.cpp
@@ -120,7 +120,8 @@ TEST_F(ValidateDerivatives, OpDPdxWrongResultType) {
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 10 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 10[%v4float] cannot "
+ "be a type"));
}
TEST_F(ValidateDerivatives, OpDPdxWrongPType) {
diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp
index 94a97a38..d1505da4 100644
--- a/test/val/val_ext_inst_test.cpp
+++ b/test/val/val_ext_inst_test.cpp
@@ -4134,7 +4134,8 @@ TEST_P(ValidateOpenCLStdVStoreHalfLike, PNotPointer) {
CompileSuccessfully(GenerateKernelCode(ss.str()));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 89 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 89[%_ptr_Workgroup_half] cannot be a type"));
}
TEST_P(ValidateOpenCLStdVStoreHalfLike, ConstPointer) {
@@ -4305,7 +4306,8 @@ TEST_P(ValidateOpenCLStdVLoadHalfLike, PNotPointer) {
CompileSuccessfully(GenerateKernelCode(ss.str()));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 89 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 89[%_ptr_Workgroup_half] cannot be a type"));
}
TEST_P(ValidateOpenCLStdVLoadHalfLike, OffsetWrongStorageType) {
@@ -4476,7 +4478,9 @@ TEST_F(ValidateExtInst, VLoadNPNotPointer) {
CompileSuccessfully(GenerateKernelCode(ss.str()));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 120 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 120[%_ptr_UniformConstant_float] cannot be a "
+ "type"));
}
TEST_F(ValidateExtInst, VLoadNWrongStorageClass) {
@@ -4587,7 +4591,9 @@ TEST_F(ValidateExtInst, VLoadHalfPNotPointer) {
CompileSuccessfully(GenerateKernelCode(ss.str()));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 114 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 114[%_ptr_UniformConstant_half] cannot be a "
+ "type"));
}
TEST_F(ValidateExtInst, VLoadHalfWrongStorageClass) {
@@ -4739,7 +4745,8 @@ TEST_F(ValidateExtInst, VStoreNPNotPointer) {
CompileSuccessfully(GenerateKernelCode(ss.str()));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 124 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 124[%_ptr_Generic_float] cannot be a type"));
}
TEST_F(ValidateExtInst, VStoreNPNotGeneric) {
@@ -5052,7 +5059,9 @@ TEST_F(ValidateExtInst, OpenCLStdPrintfFormatNotPointer) {
CompileSuccessfully(GenerateKernelCode(body));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 134 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 134[%_ptr_UniformConstant_uchar] cannot be a "
+ "type"));
}
TEST_F(ValidateExtInst, OpenCLStdPrintfFormatNotUniformConstStorageClass) {
@@ -5143,7 +5152,9 @@ TEST_F(ValidateExtInst, OpenCLStdPrefetchPtrNotPointer) {
CompileSuccessfully(GenerateKernelCode(body));
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 99 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 99[%_ptr_CrossWorkgroup_uint] cannot be a "
+ "type"));
}
TEST_F(ValidateExtInst, OpenCLStdPrefetchPtrNotCrossWorkgroup) {
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp
index c96aa17f..a162648c 100644
--- a/test/val/val_id_test.cpp
+++ b/test/val/val_id_test.cpp
@@ -205,7 +205,7 @@ TEST_F(ValidateIdWithMessage, OpMemberNameTypeBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpMemberName Type <id> '1[foo]' is not a struct type."));
+ HasSubstr("OpMemberName Type <id> '1[%uint]' is not a struct type."));
}
TEST_F(ValidateIdWithMessage, OpMemberNameMemberBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -216,8 +216,8 @@ TEST_F(ValidateIdWithMessage, OpMemberNameMemberBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpMemberName Member <id> '1[foo]' index is larger than "
- "Type <id> '1[foo]'s member count."));
+ HasSubstr("OpMemberName Member <id> '1[%_struct_1]' index is larger "
+ "than Type <id> '1[%_struct_1]'s member count."));
}
TEST_F(ValidateIdWithMessage, OpLineGood) {
@@ -239,7 +239,7 @@ TEST_F(ValidateIdWithMessage, OpLineFileBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpLine Target <id> '1' is not an OpString."));
+ HasSubstr("OpLine Target <id> '1[%uint]' is not an OpString."));
}
TEST_F(ValidateIdWithMessage, OpDecorateGood) {
@@ -261,7 +261,7 @@ OpDecorate %1 GLSLShared)";
TEST_F(ValidateIdWithMessage, OpMemberDecorateGood) {
std::string spirv = kGLSL450MemoryModel + R"(
- OpMemberDecorate %2 0 Uniform
+ OpMemberDecorate %2 0 RelaxedPrecision
%1 = OpTypeInt 32 0
%2 = OpTypeStruct %1 %1)";
CompileSuccessfully(spirv.c_str());
@@ -269,32 +269,31 @@ TEST_F(ValidateIdWithMessage, OpMemberDecorateGood) {
}
TEST_F(ValidateIdWithMessage, OpMemberDecorateBad) {
std::string spirv = kGLSL450MemoryModel + R"(
- OpMemberDecorate %1 0 Uniform
+ OpMemberDecorate %1 0 RelaxedPrecision
%1 = OpTypeInt 32 0)";
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(
- getDiagnosticString(),
- HasSubstr(
- "OpMemberDecorate Structure type <id> '1' is not a struct type."));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("OpMemberDecorate Structure type <id> '1[%uint]' is "
+ "not a struct type."));
}
TEST_F(ValidateIdWithMessage, OpMemberDecorateMemberBad) {
std::string spirv = kGLSL450MemoryModel + R"(
- OpMemberDecorate %1 3 Uniform
+ OpMemberDecorate %1 3 RelaxedPrecision
%int = OpTypeInt 32 0
%1 = OpTypeStruct %int %int)";
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Index 3 provided in OpMemberDecorate for struct <id> "
- "1 is out of bounds. The structure has 2 members. "
- "Largest valid index is 1."));
+ "1[%_struct_1] is out of bounds. The structure has 2 "
+ "members. Largest valid index is 1."));
}
TEST_F(ValidateIdWithMessage, OpGroupDecorateGood) {
std::string spirv = kGLSL450MemoryModel + R"(
%1 = OpDecorationGroup
- OpDecorate %1 Uniform
+ OpDecorate %1 RelaxedPrecision
OpDecorate %1 GLSLShared
OpGroupDecorate %1 %3 %4
%2 = OpTypeInt 32 0
@@ -306,7 +305,7 @@ TEST_F(ValidateIdWithMessage, OpGroupDecorateGood) {
TEST_F(ValidateIdWithMessage, OpDecorationGroupBad) {
std::string spirv = kGLSL450MemoryModel + R"(
%1 = OpDecorationGroup
- OpDecorate %1 Uniform
+ OpDecorate %1 RelaxedPrecision
OpDecorate %1 GLSLShared
OpMemberDecorate %1 0 Constant
)";
@@ -329,13 +328,13 @@ TEST_F(ValidateIdWithMessage, OpGroupDecorateDecorationGroupBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpGroupDecorate Decoration group <id> '1' is not a "
- "decoration group."));
+ HasSubstr("OpGroupDecorate Decoration group <id> '1[%1]' is not "
+ "a decoration group."));
}
TEST_F(ValidateIdWithMessage, OpGroupDecorateTargetBad) {
std::string spirv = kGLSL450MemoryModel + R"(
%1 = OpDecorationGroup
- OpDecorate %1 Uniform
+ OpDecorate %1 RelaxedPrecision
OpDecorate %1 GLSLShared
OpGroupDecorate %1 %3
%2 = OpTypeInt 32 0)";
@@ -355,8 +354,8 @@ TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateDecorationGroupBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpGroupMemberDecorate Decoration group <id> '1' is "
- "not a decoration group."));
+ HasSubstr("OpGroupMemberDecorate Decoration group <id> '1[%1]' "
+ "is not a decoration group."));
}
TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateIdNotStructBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -366,8 +365,8 @@ TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateIdNotStructBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpGroupMemberDecorate Structure type <id> '2' is not "
- "a struct type."));
+ HasSubstr("OpGroupMemberDecorate Structure type <id> '2[%uint]' "
+ "is not a struct type."));
}
TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateIndexOutOfBoundBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -381,8 +380,8 @@ TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateIndexOutOfBoundBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Index 3 provided in OpGroupMemberDecorate for struct "
- "<id> 2 is out of bounds. The structure has 3 members. "
- "Largest valid index is 2."));
+ "<id> 2[%_struct_2] is out of bounds. The structure "
+ "has 3 members. Largest valid index is 2."));
}
// TODO: OpExtInst
@@ -406,9 +405,9 @@ TEST_F(ValidateIdWithMessage, OpEntryPointFunctionBad) {
%1 = OpTypeVoid)";
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(
- getDiagnosticString(),
- HasSubstr("OpEntryPoint Entry Point <id> '1' is not a function."));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("OpEntryPoint Entry Point <id> '1[%void]' is not a "
+ "function."));
}
TEST_F(ValidateIdWithMessage, OpEntryPointParameterCountBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -422,8 +421,8 @@ TEST_F(ValidateIdWithMessage, OpEntryPointParameterCountBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpEntryPoint Entry Point <id> '1's function parameter "
- "count is not zero"));
+ HasSubstr("OpEntryPoint Entry Point <id> '1[%1]'s function "
+ "parameter count is not zero"));
}
TEST_F(ValidateIdWithMessage, OpEntryPointReturnTypeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -438,8 +437,8 @@ TEST_F(ValidateIdWithMessage, OpEntryPointReturnTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpEntryPoint Entry Point <id> '1's function return "
- "type is not void."));
+ HasSubstr("OpEntryPoint Entry Point <id> '1[%1]'s function "
+ "return type is not void."));
}
TEST_F(ValidateIdWithMessage, OpEntryPointInterfaceIsNotVariableTypeBad) {
@@ -522,8 +521,8 @@ TEST_F(ValidateIdWithMessage, OpExecutionModeEntryPointMissing) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpExecutionMode Entry Point <id> '1' is not the Entry "
- "Point operand of an OpEntryPoint."));
+ HasSubstr("OpExecutionMode Entry Point <id> '1[%1]' is not the "
+ "Entry Point operand of an OpEntryPoint."));
}
TEST_F(ValidateIdWithMessage, OpExecutionModeEntryPointBad) {
@@ -541,8 +540,8 @@ TEST_F(ValidateIdWithMessage, OpExecutionModeEntryPointBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpExecutionMode Entry Point <id> '2' is not the Entry "
- "Point operand of an OpEntryPoint."));
+ HasSubstr("OpExecutionMode Entry Point <id> '2[%2]' is not the "
+ "Entry Point operand of an OpEntryPoint."));
}
TEST_F(ValidateIdWithMessage, OpTypeVectorFloat) {
@@ -586,7 +585,8 @@ TEST_F(ValidateIdWithMessage, OpTypeVectorComponentTypeBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpTypeVector Component Type <id> '2' is not a scalar type."));
+ HasSubstr("OpTypeVector Component Type <id> "
+ "'2[%_ptr_UniformConstant_float]' is not a scalar type."));
}
TEST_F(ValidateIdWithMessage, OpTypeVectorColumnCountLessThanTwoBad) {
@@ -742,7 +742,8 @@ TEST_F(ValidateIdWithMessage, OpTypeArrayElementTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpTypeArray Element Type <id> '2' is not a type."));
+ HasSubstr("OpTypeArray Element Type <id> '2[%uint_1]' is not a "
+ "type."));
}
// Signed or unsigned.
@@ -783,7 +784,8 @@ class OpTypeArrayLengthTest
spvValidate(ScopedContext().context, &cbinary, &diagnostic_);
if (status != SPV_SUCCESS) {
spvDiagnosticPrint(diagnostic_);
- EXPECT_THAT(std::string(diagnostic_->error), HasSubstr(expected_err));
+ EXPECT_THAT(std::string(diagnostic_->error),
+ testing::ContainsRegex(expected_err));
}
return status;
}
@@ -817,35 +819,35 @@ TEST_P(OpTypeArrayLengthTest, LengthPositive) {
TEST_P(OpTypeArrayLengthTest, LengthZero) {
const int width = GetParam();
- EXPECT_EQ(
- SPV_ERROR_INVALID_ID,
- Val(CompileSuccessfully(MakeArrayLength("0", kSigned, width)),
- "OpTypeArray Length <id> '2' default value must be at least 1."));
- EXPECT_EQ(
- SPV_ERROR_INVALID_ID,
- Val(CompileSuccessfully(MakeArrayLength("0", kUnsigned, width)),
- "OpTypeArray Length <id> '2' default value must be at least 1."));
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ Val(CompileSuccessfully(MakeArrayLength("0", kSigned, width)),
+ "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+ "least 1."));
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ Val(CompileSuccessfully(MakeArrayLength("0", kUnsigned, width)),
+ "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+ "least 1."));
}
TEST_P(OpTypeArrayLengthTest, LengthNegative) {
const int width = GetParam();
- EXPECT_EQ(
- SPV_ERROR_INVALID_ID,
- Val(CompileSuccessfully(MakeArrayLength("-1", kSigned, width)),
- "OpTypeArray Length <id> '2' default value must be at least 1."));
- EXPECT_EQ(
- SPV_ERROR_INVALID_ID,
- Val(CompileSuccessfully(MakeArrayLength("-2", kSigned, width)),
- "OpTypeArray Length <id> '2' default value must be at least 1."));
- EXPECT_EQ(
- SPV_ERROR_INVALID_ID,
- Val(CompileSuccessfully(MakeArrayLength("-123", kSigned, width)),
- "OpTypeArray Length <id> '2' default value must be at least 1."));
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ Val(CompileSuccessfully(MakeArrayLength("-1", kSigned, width)),
+ "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+ "least 1."));
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ Val(CompileSuccessfully(MakeArrayLength("-2", kSigned, width)),
+ "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+ "least 1."));
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ Val(CompileSuccessfully(MakeArrayLength("-123", kSigned, width)),
+ "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+ "least 1."));
const std::string neg_max = "0x8" + std::string(width / 4 - 1, '0');
- EXPECT_EQ(
- SPV_ERROR_INVALID_ID,
- Val(CompileSuccessfully(MakeArrayLength(neg_max, kSigned, width)),
- "OpTypeArray Length <id> '2' default value must be at least 1."));
+ EXPECT_EQ(SPV_ERROR_INVALID_ID,
+ Val(CompileSuccessfully(MakeArrayLength(neg_max, kSigned, width)),
+ "OpTypeArray Length <id> '2\\[%.*\\]' default value must be at "
+ "least 1."));
}
// The only valid widths for integers are 8, 16, 32, and 64.
@@ -866,7 +868,7 @@ TEST_F(ValidateIdWithMessage, OpTypeArrayLengthNull) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "OpTypeArray Length <id> '2' default value must be at least 1."));
+ "OpTypeArray Length <id> '2[%2]' default value must be at least 1."));
}
TEST_F(ValidateIdWithMessage, OpTypeArrayLengthSpecConst) {
@@ -905,7 +907,8 @@ TEST_F(ValidateIdWithMessage, OpTypeRuntimeArrayBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpTypeRuntimeArray Element Type <id> '2' is not a type."));
+ HasSubstr("OpTypeRuntimeArray Element Type <id> '2[%uint_0]' is not a "
+ "type."));
}
// TODO: Object of this type can only be created with OpVariable using the
// Unifrom Storage Class
@@ -928,7 +931,8 @@ TEST_F(ValidateIdWithMessage, OpTypeStructMemberTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpTypeStruct Member Type <id> '3' is not a type."));
+ HasSubstr("OpTypeStruct Member Type <id> '3[%double_0]' is not "
+ "a type."));
}
TEST_F(ValidateIdWithMessage, OpTypePointerGood) {
@@ -946,7 +950,8 @@ TEST_F(ValidateIdWithMessage, OpTypePointerBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpTypePointer Type <id> '2' is not a type."));
+ HasSubstr("OpTypePointer Type <id> '2[%uint_0]' is not a "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpTypeFunctionGood) {
@@ -964,7 +969,8 @@ TEST_F(ValidateIdWithMessage, OpTypeFunctionReturnTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpTypeFunction Return Type <id> '2' is not a type."));
+ HasSubstr("OpTypeFunction Return Type <id> '2[%uint_0]' is not "
+ "a type."));
}
TEST_F(ValidateIdWithMessage, OpTypeFunctionParameterBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -976,7 +982,8 @@ TEST_F(ValidateIdWithMessage, OpTypeFunctionParameterBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpTypeFunction Parameter Type <id> '3' is not a type."));
+ HasSubstr("OpTypeFunction Parameter Type <id> '3[%uint_0]' is not a "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpTypeFunctionParameterTypeVoidBad) {
@@ -987,8 +994,8 @@ TEST_F(ValidateIdWithMessage, OpTypeFunctionParameterTypeVoidBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpTypeFunction Parameter Type <id> '1' cannot be "
- "OpTypeVoid."));
+ HasSubstr("OpTypeFunction Parameter Type <id> '1[%void]' cannot "
+ "be OpTypeVoid."));
}
TEST_F(ValidateIdWithMessage, OpTypePipeGood) {
@@ -1015,7 +1022,8 @@ TEST_F(ValidateIdWithMessage, OpConstantTrueBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpConstantTrue Result Type <id> '1' is not a boolean type."));
+ HasSubstr("OpConstantTrue Result Type <id> '1[%void]' is not a boolean "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpConstantFalseGood) {
@@ -1033,7 +1041,8 @@ TEST_F(ValidateIdWithMessage, OpConstantFalseBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpConstantFalse Result Type <id> '1' is not a boolean type."));
+ HasSubstr("OpConstantFalse Result Type <id> '1[%void]' is not a boolean "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpConstantGood) {
@@ -1083,8 +1092,8 @@ TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorResultTypeBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr(
- "OpConstantComposite Result Type <id> '1' is not a composite type."));
+ HasSubstr("OpConstantComposite Result Type <id> '1[%float]' is not a "
+ "composite type."));
}
TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorConstituentTypeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -1098,8 +1107,9 @@ TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorConstituentTypeBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpConstantComposite Constituent <id> '5's type does not match "
- "Result Type <id> '2's vector element type."));
+ HasSubstr("OpConstantComposite Constituent <id> '5[%uint_42]'s type "
+ "does not match Result Type <id> '2[%v4float]'s vector "
+ "element type."));
}
TEST_F(ValidateIdWithMessage,
OpConstantCompositeVectorConstituentUndefTypeBad) {
@@ -1114,8 +1124,8 @@ TEST_F(ValidateIdWithMessage,
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpConstantComposite Constituent <id> '5's type does not match "
- "Result Type <id> '2's vector element type."));
+ HasSubstr("OpConstantComposite Constituent <id> '5[%5]'s type does not "
+ "match Result Type <id> '2[%v4float]'s vector element type."));
}
TEST_F(ValidateIdWithMessage, OpConstantCompositeMatrixGood) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -1163,9 +1173,9 @@ TEST_F(ValidateIdWithMessage, OpConstantCompositeMatrixConstituentTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpConstantComposite Constituent <id> '10' vector "
- "component count does not match Result Type <id> '4's "
- "vector component count."));
+ HasSubstr("OpConstantComposite Constituent <id> '10[%10]' vector "
+ "component count does not match Result Type <id> "
+ "'4[%mat4v4float]'s vector component count."));
}
TEST_F(ValidateIdWithMessage,
OpConstantCompositeMatrixConstituentUndefTypeBad) {
@@ -1184,9 +1194,9 @@ TEST_F(ValidateIdWithMessage,
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpConstantComposite Constituent <id> '10' vector "
- "component count does not match Result Type <id> '4's "
- "vector component count."));
+ HasSubstr("OpConstantComposite Constituent <id> '10[%10]' vector "
+ "component count does not match Result Type <id> "
+ "'4[%mat4v4float]'s vector component count."));
}
TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayGood) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -1207,6 +1217,7 @@ TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayWithUndefGood) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
+
TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentTypeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
%1 = OpTypeInt 32 0
@@ -1215,7 +1226,8 @@ TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentTypeBad) {
%4 = OpConstantComposite %3 %2 %2 %2 %1)"; // Uses a type as operand
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%uint] cannot be a "
+ "type"));
}
TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -1228,7 +1240,7 @@ TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpConstantComposite Constituent <id> '5' is not a "
+ HasSubstr("OpConstantComposite Constituent <id> '5[%5]' is not a "
"constant or undef."));
}
TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstituentTypeBad) {
@@ -1242,8 +1254,10 @@ TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstituentTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpConstantComposite Constituent <id> '5's type does "
- "not match Result Type <id> '3's array element type."));
+ HasSubstr("OpConstantComposite Constituent <id> "
+ "'5[%float_3_1400001]'s type does not match Result "
+ "Type <id> '3[%_arr_uint_uint_4]'s array element "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstituentUndefTypeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -1256,8 +1270,10 @@ TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstituentUndefTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpConstantComposite Constituent <id> '5's type does "
- "not match Result Type <id> '3's array element type."));
+ HasSubstr("OpConstantComposite Constituent <id> "
+ "'5[%5]'s type does not match Result "
+ "Type <id> '3[%_arr_uint_uint_4]'s array element "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpConstantCompositeStructGood) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -1292,8 +1308,9 @@ TEST_F(ValidateIdWithMessage, OpConstantCompositeStructMemberTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpConstantComposite Constituent <id> '5' type does "
- "not match the Result Type <id> '3's member type."));
+ HasSubstr("OpConstantComposite Constituent <id> "
+ "'5[%ulong_4300000000]' type does not match the "
+ "Result Type <id> '3[%_struct_3]'s member type."));
}
TEST_F(ValidateIdWithMessage, OpConstantCompositeStructMemberUndefTypeBad) {
@@ -1307,8 +1324,9 @@ TEST_F(ValidateIdWithMessage, OpConstantCompositeStructMemberUndefTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpConstantComposite Constituent <id> '5' type does "
- "not match the Result Type <id> '3's member type."));
+ HasSubstr("OpConstantComposite Constituent <id> '5[%5]' type "
+ "does not match the Result Type <id> '3[%_struct_3]'s "
+ "member type."));
}
TEST_F(ValidateIdWithMessage, OpConstantSamplerGood) {
@@ -1328,7 +1346,8 @@ TEST_F(ValidateIdWithMessage, OpConstantSamplerResultTypeBad) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "OpConstantSampler Result Type <id> '1' is not a sampler type."));
+ "OpConstantSampler Result Type <id> '1[%float]' is not a sampler "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpConstantNullGood) {
@@ -1375,8 +1394,8 @@ TEST_F(ValidateIdWithMessage, OpConstantNullBasicBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr(
- "OpConstantNull Result Type <id> '1' cannot have a null value."));
+ HasSubstr("OpConstantNull Result Type <id> '1[%void]' cannot have a null "
+ "value."));
}
TEST_F(ValidateIdWithMessage, OpConstantNullArrayBad) {
@@ -1391,7 +1410,8 @@ TEST_F(ValidateIdWithMessage, OpConstantNullArrayBad) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "OpConstantNull Result Type <id> '4' cannot have a null value."));
+ "OpConstantNull Result Type <id> '4[%_arr_2_uint_4]' cannot have a "
+ "null value."));
}
TEST_F(ValidateIdWithMessage, OpConstantNullStructBad) {
@@ -1401,10 +1421,9 @@ TEST_F(ValidateIdWithMessage, OpConstantNullStructBad) {
%4 = OpConstantNull %3)";
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(
- getDiagnosticString(),
- HasSubstr(
- "OpConstantNull Result Type <id> '2' cannot have a null value."));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("OpConstantNull Result Type <id> '2[%_struct_2]' "
+ "cannot have a null value."));
}
TEST_F(ValidateIdWithMessage, OpConstantNullRuntimeArrayBad) {
@@ -1417,7 +1436,8 @@ TEST_F(ValidateIdWithMessage, OpConstantNullRuntimeArrayBad) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "OpConstantNull Result Type <id> '2' cannot have a null value."));
+ "OpConstantNull Result Type <id> '2[%_runtimearr_bool]' cannot have "
+ "a null value."));
}
TEST_F(ValidateIdWithMessage, OpSpecConstantTrueGood) {
@@ -1523,9 +1543,9 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorConstituentTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '5's type "
- "does not match Result Type <id> '2's vector element "
- "type."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> "
+ "'5[%uint_42]'s type does not match Result Type <id> "
+ "'2[%v4float]'s vector element type."));
}
// Invalid: Constituent is not a constant
@@ -1542,8 +1562,8 @@ TEST_F(ValidateIdWithMessage,
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '6' is not a "
- "constant or undef."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '6[%6]' is "
+ "not a constant or undef."));
}
// Invalid: Vector contains a mix of Undef-int and Float.
@@ -1559,9 +1579,9 @@ TEST_F(ValidateIdWithMessage,
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '5's type "
- "does not match Result Type <id> '2's vector element "
- "type."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]'s "
+ "type does not match Result Type <id> '2[%v4float]'s "
+ "vector element type."));
}
// Invalid: Vector expects 3 components, but 4 specified.
@@ -1576,8 +1596,8 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorNumComponentsBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpSpecConstantComposite Constituent <id> count does "
- "not match Result Type <id> '2's vector component "
- "count."));
+ "not match Result Type <id> '2[%v3float]'s vector "
+ "component count."));
}
// Valid: 4x4 matrix of floats
@@ -1631,9 +1651,9 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixConstituentTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '10' vector "
- "component count does not match Result Type <id> '4's "
- "vector component count."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '10[%10]' "
+ "vector component count does not match Result Type "
+ "<id> '4[%mat4v4float]'s vector component count."));
}
// Invalid: Matrix type expects 4 columns but only 3 specified.
@@ -1653,7 +1673,8 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixNumColsBad) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("OpSpecConstantComposite Constituent <id> count does "
- "not match Result Type <id> '3's matrix column count."));
+ "not match Result Type <id> '3[%mat4v4float]'s matrix column "
+ "count."));
}
// Invalid: Composite contains a non-const/undef component
@@ -1671,8 +1692,8 @@ TEST_F(ValidateIdWithMessage,
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '7' is not a "
- "constant composite or undef."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '7[%7]' is "
+ "not a constant composite or undef."));
}
// Invalid: Composite contains a column that is *not* a vector (it's an array)
@@ -1691,9 +1712,9 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixColTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '8' type "
- "does not match Result Type <id> '7's matrix column "
- "type."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '8[%8]' type "
+ "does not match Result Type <id> '7[%mat4v4float]'s "
+ "matrix column type."));
}
// Invalid: Matrix with an Undef column of the wrong size.
@@ -1714,9 +1735,9 @@ TEST_F(ValidateIdWithMessage,
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '10' vector "
- "component count does not match Result Type <id> '4's "
- "vector component count."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '10[%10]' "
+ "vector component count does not match Result Type "
+ "<id> '4[%mat4v4float]'s vector component count."));
}
// Invalid: Matrix in which some columns are Int and some are Float.
@@ -1735,9 +1756,9 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixColumnTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '8' "
- "component type does not match Result Type <id> '5's "
- "matrix column component type."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '8[%8]' "
+ "component type does not match Result Type <id> "
+ "'5[%mat2v2float]'s matrix column component type."));
}
// Valid: Array of integers
@@ -1765,7 +1786,8 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayNumComponentsBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpSpecConstantComposite Constituent count does not "
- "match Result Type <id> '3's array length."));
+ "match Result Type <id> '3[%_arr_uint_uint_4]'s array "
+ "length."));
}
// Valid: Array of Integers and Undef-int
@@ -1792,8 +1814,8 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayConstConstituentBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '5' is not a "
- "constant or undef."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]' is "
+ "not a constant or undef."));
}
// Invalid: Array has a mix of Int and Float components.
@@ -1808,9 +1830,9 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayConstituentTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '5's type "
- "does not match Result Type <id> '3's array element "
- "type."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]'s "
+ "type does not match Result Type <id> "
+ "'3[%_arr_uint_uint_4]'s array element type."));
}
// Invalid: Array has a mix of Int and Undef-float.
@@ -1826,9 +1848,9 @@ TEST_F(ValidateIdWithMessage,
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '5's type "
- "does not match Result Type <id> '3's array element "
- "type."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]'s "
+ "type does not match Result Type <id> "
+ "'3[%_arr_uint_2]'s array element type."));
}
// Valid: Struct of {Int32,Int32,Int64}.
@@ -1856,9 +1878,9 @@ TEST_F(ValidateIdWithMessage,
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '2' count "
- "does not match Result Type <id> '2's struct member "
- "count."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> "
+ "'2[%_struct_2]' count does not match Result Type "
+ "<id> '2[%_struct_2]'s struct member count."));
}
// Valid: Struct uses Undef-int64.
@@ -1888,8 +1910,8 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructNonConstBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '7' is not a "
- "constant or undef."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '7[%7]' is "
+ "not a constant or undef."));
}
// Invalid: Struct component type does not match expected specialization type.
@@ -1905,9 +1927,9 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructMemberTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '5' type "
- "does not match the Result Type <id> '3's member "
- "type."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]' type "
+ "does not match the Result Type <id> '3[%_struct_3]'s "
+ "member type."));
}
// Invalid: Undef-int64 used when Int32 was expected.
@@ -1922,9 +1944,9 @@ TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructMemberUndefTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpSpecConstantComposite Constituent <id> '5' type "
- "does not match the Result Type <id> '3's member "
- "type."));
+ HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]' type "
+ "does not match the Result Type <id> '3[%_struct_3]'s "
+ "member type."));
}
// TODO: OpSpecConstantOp
@@ -1966,7 +1988,8 @@ TEST_F(ValidateIdWithMessage, OpVariableResultTypeBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpVariable Result Type <id> '1' is not a pointer type."));
+ HasSubstr("OpVariable Result Type <id> '1[%uint]' is not a pointer "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpVariableInitializerIsTypeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -1975,7 +1998,8 @@ TEST_F(ValidateIdWithMessage, OpVariableInitializerIsTypeBad) {
%3 = OpVariable %2 Input %2)";
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 2 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 2[%_ptr_Input_uint] "
+ "cannot be a type"));
}
TEST_F(ValidateIdWithMessage, OpVariableInitializerIsFunctionVarBad) {
@@ -1995,8 +2019,8 @@ OpFunctionEnd
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpVariable Initializer <id> '8' is not a constant or "
- "module-scope variable"));
+ HasSubstr("OpVariable Initializer <id> '8[%8]' is not a constant "
+ "or module-scope variable"));
}
TEST_F(ValidateIdWithMessage, OpVariableInitializerIsModuleVarGood) {
@@ -2389,8 +2413,9 @@ TEST_F(ValidateIdWithMessage, OpLoadResultTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpLoad Result Type <id> '3' does not match Pointer "
- "<id> '5's type."));
+ HasSubstr("OpLoad Result Type <id> "
+ "'3[%_ptr_UniformConstant_uint]' does not match "
+ "Pointer <id> '5[%5]'s type."));
}
TEST_F(ValidateIdWithMessage, OpLoadPointerBad) {
@@ -2409,7 +2434,8 @@ TEST_F(ValidateIdWithMessage, OpLoadPointerBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
// Prove that SSA checks trigger for a bad Id value.
// The next test case show the not-a-logical-pointer case.
- EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 8 has not been defined"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 8[%8] has not been "
+ "defined"));
}
// Disabled as bitcasting type to object is now not valid.
@@ -2471,7 +2497,8 @@ TEST_F(ValidateIdWithMessage, OpStorePointerBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpStore Pointer <id> '7' is not a logical pointer."));
+ HasSubstr("OpStore Pointer <id> '7[%uint_0]' is not a logical "
+ "pointer."));
}
// Disabled as bitcasting type to object is now not valid.
@@ -2551,7 +2578,7 @@ TEST_F(ValidateIdWithMessage, OpStoreObjectGood) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpStore Object <id> '9's type is void."));
+ HasSubstr("OpStore Object <id> '9[%9]'s type is void."));
}
TEST_F(ValidateIdWithMessage, OpStoreTypeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -2570,8 +2597,8 @@ TEST_F(ValidateIdWithMessage, OpStoreTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpStore Pointer <id> '7's type does not match Object "
- "<id> '6's type."));
+ HasSubstr("OpStore Pointer <id> '7[%7]'s type does not match "
+ "Object <id> '6[%float_3_1400001]'s type."));
}
// The next series of test check test a relaxation of the rules for stores to
@@ -2603,8 +2630,8 @@ TEST_F(ValidateIdWithMessage, OpStoreTypeBadStruct) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpStore Pointer <id> '8's type does not match Object "
- "<id> '11's type."));
+ HasSubstr("OpStore Pointer <id> '8[%8]'s type does not match "
+ "Object <id> '11[%11]'s type."));
}
// Same code as the last test. The difference is that we relax the rule.
@@ -2734,8 +2761,8 @@ TEST_F(ValidateIdWithMessage, OpStoreTypeBadRelaxedStruct1) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpStore Pointer <id> '13's layout does not match Object "
- "<id> '16's layout."));
+ HasSubstr("OpStore Pointer <id> '13[%13]'s layout does not match Object "
+ "<id> '16[%16]'s layout."));
}
// This test check that the even with the relaxed rules an error is identified
@@ -2774,8 +2801,8 @@ TEST_F(ValidateIdWithMessage, OpStoreTypeBadRelaxedStruct2) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpStore Pointer <id> '13's layout does not match Object "
- "<id> '16's layout."));
+ HasSubstr("OpStore Pointer <id> '13[%13]'s layout does not match Object "
+ "<id> '16[%16]'s layout."));
}
TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedLogicalPointerReturnPointer) {
@@ -2836,7 +2863,7 @@ TEST_F(ValidateIdWithMessage, OpStoreVoid) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpStore Object <id> '8's type is void."));
+ HasSubstr("OpStore Object <id> '8[%8]'s type is void."));
}
TEST_F(ValidateIdWithMessage, OpStoreLabel) {
@@ -2854,7 +2881,7 @@ TEST_F(ValidateIdWithMessage, OpStoreLabel) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpStore Object <id> '7' is not an object."));
+ HasSubstr("OpStore Object <id> '7[%7]' is not an object."));
}
// TODO: enable when this bug is fixed:
@@ -2951,7 +2978,7 @@ OpFunctionEnd
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Target operand <id> '6' is not a pointer."));
+ HasSubstr("Target operand <id> '6[%6]' is not a pointer."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemoryNonPointerSource) {
@@ -2972,7 +2999,7 @@ OpFunctionEnd
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Source operand <id> '6' is not a pointer."));
+ HasSubstr("Source operand <id> '6[%6]' is not a pointer."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemoryBad) {
@@ -2995,8 +3022,8 @@ TEST_F(ValidateIdWithMessage, OpCopyMemoryBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Target <id> '5's type does not match "
- "Source <id> '2's type."));
+ HasSubstr("Target <id> '5[%5]'s type does not match "
+ "Source <id> '2[%uint]'s type."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemoryVoidTarget) {
@@ -3018,7 +3045,8 @@ OpFunctionEnd
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Target operand <id> '7' cannot be a void pointer."));
+ HasSubstr("Target operand <id> '7[%7]' cannot be a void "
+ "pointer."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemoryVoidSource) {
@@ -3040,7 +3068,8 @@ OpFunctionEnd
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Source operand <id> '7' cannot be a void pointer."));
+ HasSubstr("Source operand <id> '7[%7]' cannot be a void "
+ "pointer."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemorySizedGood) {
@@ -3078,7 +3107,7 @@ TEST_F(ValidateIdWithMessage, OpCopyMemorySizedTargetBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Target operand <id> '9' is not a pointer."));
+ HasSubstr("Target operand <id> '9[%9]' is not a pointer."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSourceBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -3097,7 +3126,7 @@ TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSourceBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Source operand <id> '5' is not a pointer."));
+ HasSubstr("Source operand <id> '5[%uint_4]' is not a pointer."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -3118,7 +3147,7 @@ TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("Size operand <id> '6' must be a scalar integer type."));
+ HasSubstr("Size operand <id> '6[%6]' must be a scalar integer type."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeTypeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -3141,7 +3170,8 @@ TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeTypeBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("Size operand <id> '9' must be a scalar integer type."));
+ HasSubstr("Size operand <id> '9[%float_1]' must be a scalar integer "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNull) {
@@ -3165,7 +3195,8 @@ OpFunctionEnd
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Size operand <id> '3' cannot be a constant zero."));
+ HasSubstr("Size operand <id> '3[%3]' cannot be a constant "
+ "zero."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantZero) {
@@ -3189,7 +3220,8 @@ OpFunctionEnd
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Size operand <id> '3' cannot be a constant zero."));
+ HasSubstr("Size operand <id> '3[%uint_0]' cannot be a constant "
+ "zero."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantZero64) {
@@ -3213,7 +3245,8 @@ OpFunctionEnd
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Size operand <id> '3' cannot be a constant zero."));
+ HasSubstr("Size operand <id> '3[%ulong_0]' cannot be a constant "
+ "zero."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNegative) {
@@ -3238,7 +3271,8 @@ OpFunctionEnd
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("Size operand <id> '3' cannot have the sign bit set to 1."));
+ HasSubstr("Size operand <id> '3[%int_n1]' cannot have the sign bit set "
+ "to 1."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNegative64) {
@@ -3263,7 +3297,8 @@ OpFunctionEnd
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("Size operand <id> '3' cannot have the sign bit set to 1."));
+ HasSubstr("Size operand <id> '3[%long_n1]' cannot have the sign bit set "
+ "to 1."));
}
TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeUnsignedNegative) {
@@ -3403,7 +3438,7 @@ OpFunctionEnd
)";
const std::string expected_err = "The Result Type of " + instr +
- " <id> '36' must be "
+ " <id> '36[%36]' must be "
"OpTypePointer. Found OpTypeFloat.";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
@@ -3423,7 +3458,8 @@ OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%void] cannot be a "
+ "type"));
}
// Invalid. The base type of an access chain instruction must be a pointer.
@@ -3440,7 +3476,8 @@ OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 8 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Operand 8[%_ptr_Private_float] cannot be a type"));
}
// Invalid: The storage class of Base and Result do not match.
@@ -3743,8 +3780,8 @@ OpFunctionEnd
)";
const std::string expected_err = "Index is out of bounds: " + instr +
" can not find index 3 into the structure "
- "<id> '25'. This structure has 3 members. "
- "Largest valid index is 2.";
+ "<id> '25[%_struct_25]'. This structure "
+ "has 3 members. Largest valid index is 2.";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err));
@@ -3890,8 +3927,9 @@ TEST_F(ValidateIdWithMessage, OpFunctionResultTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpFunction Result Type <id> '2' does not match the "
- "Function Type's return type <id> '1'."));
+ HasSubstr("OpFunction Result Type <id> '2[%uint]' does not "
+ "match the Function Type's return type <id> "
+ "'1[%void]'."));
}
TEST_F(ValidateIdWithMessage, OpReturnValueTypeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -3906,8 +3944,8 @@ TEST_F(ValidateIdWithMessage, OpReturnValueTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpReturnValue Value <id> '3's type does not match "
- "OpFunction's return type."));
+ HasSubstr("OpReturnValue Value <id> '3[%float_0]'s type does "
+ "not match OpFunction's return type."));
}
TEST_F(ValidateIdWithMessage, OpFunctionFunctionTypeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -3921,7 +3959,8 @@ OpFunctionEnd)";
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpFunction Function Type <id> '2' is not a function type."));
+ HasSubstr("OpFunction Function Type <id> '2[%uint]' is not a function "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpFunctionUseBad) {
@@ -3937,7 +3976,7 @@ OpFunctionEnd
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Invalid use of function result id 3."));
+ HasSubstr("Invalid use of function result id 3[%3]."));
}
TEST_F(ValidateIdWithMessage, OpFunctionParameterGood) {
@@ -3981,8 +4020,8 @@ TEST_F(ValidateIdWithMessage, OpFunctionParameterResultTypeBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpFunctionParameter Result Type <id> '1' does not match the "
- "OpTypeFunction parameter type of the same index."));
+ HasSubstr("OpFunctionParameter Result Type <id> '1[%void]' does not "
+ "match the OpTypeFunction parameter type of the same index."));
}
TEST_F(ValidateIdWithMessage, OpFunctionCallGood) {
@@ -4030,8 +4069,9 @@ TEST_F(ValidateIdWithMessage, OpFunctionCallResultTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpFunctionCall Result Type <id> '1's type does not "
- "match Function <id> '2's return type."));
+ HasSubstr("OpFunctionCall Result Type <id> '1[%void]'s type "
+ "does not match Function <id> '2[%uint]'s return "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpFunctionCallFunctionBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -4049,7 +4089,8 @@ TEST_F(ValidateIdWithMessage, OpFunctionCallFunctionBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpFunctionCall Function <id> '5' is not a function."));
+ HasSubstr("OpFunctionCall Function <id> '5[%uint_42]' is not a "
+ "function."));
}
TEST_F(ValidateIdWithMessage, OpFunctionCallArgumentTypeBad) {
std::string spirv = kGLSL450MemoryModel + R"(
@@ -4077,8 +4118,9 @@ TEST_F(ValidateIdWithMessage, OpFunctionCallArgumentTypeBad) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpFunctionCall Argument <id> '7's type does not match "
- "Function <id> '2's parameter type."));
+ HasSubstr("OpFunctionCall Argument <id> '7[%float_3_1400001]'s "
+ "type does not match Function <id> '2[%uint]'s "
+ "parameter type."));
}
// Valid: OpSampledImage result <id> is used in the same block by
@@ -4109,8 +4151,8 @@ OpFunctionEnd)";
getDiagnosticString(),
HasSubstr("All OpSampledImage instructions must be in the same block in "
"which their Result <id> are consumed. OpSampledImage Result "
- "Type <id> '23' has a consumer in a different basic block. The "
- "consumer instruction <id> is '25'."));
+ "Type <id> '23[%23]' has a consumer in a different basic "
+ "block. The consumer instruction <id> is '25[%25]'."));
}
// Invalid: OpSampledImage result <id> is used by OpSelect
@@ -4314,7 +4356,7 @@ TEST_F(ValidateIdWithMessage, OpVectorShuffleComponentCount) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("OpVectorShuffle component literals count does not match "
- "Result Type <id> '2's vector component count."));
+ "Result Type <id> '2[%v3uint]'s vector component count."));
}
TEST_F(ValidateIdWithMessage, OpVectorShuffleVector1Type) {
@@ -4591,7 +4633,8 @@ OpFunctionEnd
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 3 is not a type id"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 3[%true] is not a type "
+ "id"));
}
TEST_F(ValidateIdWithMessage, OpPhiSamePredecessor) {
@@ -4703,8 +4746,9 @@ OpFunctionEnd
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpPhi's result type <id> 2 does not match incoming "
- "value <id> 6 type <id> 5."));
+ HasSubstr("OpPhi's result type <id> 2[%bool] does not match "
+ "incoming value <id> 6[%uint_0] type <id> "
+ "5[%uint]."));
}
TEST_F(ValidateIdWithMessage, OpPhiPredecessorNotABlock) {
@@ -4728,9 +4772,9 @@ OpFunctionEnd
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(
- getDiagnosticString(),
- HasSubstr("OpPhi's incoming basic block <id> 3 is not an OpLabel."));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("OpPhi's incoming basic block <id> 3[%true] is not an "
+ "OpLabel."));
}
TEST_F(ValidateIdWithMessage, OpPhiNotAPredecessor) {
@@ -4755,8 +4799,8 @@ OpFunctionEnd
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpPhi's incoming basic block <id> 9 is not a "
- "predecessor of <id> 8."));
+ HasSubstr("OpPhi's incoming basic block <id> 9[%9] is not a "
+ "predecessor of <id> 8[%8]."));
}
TEST_F(ValidateIdWithMessage, OpBranchConditionalGood) {
@@ -4856,7 +4900,8 @@ OpBranchConditional %bool %target_t %target_f
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 3 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 3[%bool] cannot be a "
+ "type"));
}
// TODO: OpSwitch
@@ -4918,7 +4963,8 @@ TEST_F(ValidateIdWithMessage, OpReturnValueIsType) {
OpFunctionEnd)";
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%void] cannot be a "
+ "type"));
}
TEST_F(ValidateIdWithMessage, OpReturnValueIsLabel) {
@@ -4932,9 +4978,9 @@ TEST_F(ValidateIdWithMessage, OpReturnValueIsLabel) {
OpFunctionEnd)";
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(
- getDiagnosticString(),
- HasSubstr("OpReturnValue Value <id> '5' does not represent a value."));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("OpReturnValue Value <id> '5[%5]' does not represent a "
+ "value."));
}
TEST_F(ValidateIdWithMessage, OpReturnValueIsVoid) {
@@ -4951,7 +4997,8 @@ TEST_F(ValidateIdWithMessage, OpReturnValueIsVoid) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpReturnValue value's type <id> '1' is missing or void."));
+ HasSubstr("OpReturnValue value's type <id> '1[%void]' is missing or "
+ "void."));
}
TEST_F(ValidateIdWithMessage, OpReturnValueIsVariableInPhysical) {
@@ -4987,8 +5034,9 @@ TEST_F(ValidateIdWithMessage, OpReturnValueIsVariableInLogical) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpReturnValue value's type <id> '3' is a pointer, "
- "which is invalid in the Logical addressing model."));
+ HasSubstr("OpReturnValue value's type <id> "
+ "'3[%_ptr_Function_uint]' is a pointer, which is "
+ "invalid in the Logical addressing model."));
}
// With the VariablePointer Capability, the return value of a function is
@@ -5060,7 +5108,8 @@ TEST_F(ValidateIdWithMessage, UndefinedIdScope) {
)";
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7 has not been defined"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7[%7] has not been "
+ "defined"));
}
TEST_F(ValidateIdWithMessage, UndefinedIdMemSem) {
@@ -5077,7 +5126,8 @@ TEST_F(ValidateIdWithMessage, UndefinedIdMemSem) {
)";
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7 has not been defined"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7[%7] has not been "
+ "defined"));
}
TEST_F(ValidateIdWithMessage,
@@ -5212,7 +5262,8 @@ TEST_F(ValidateIdWithMessage, OpLoadBitcastNonPointerBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpLoad type for pointer <id> '11' is not a pointer type."));
+ HasSubstr("OpLoad type for pointer <id> '11[%11]' is not a pointer "
+ "type."));
}
TEST_F(ValidateIdWithMessage, OpStoreBitcastPointerGood) {
std::string spirv = kOpenCLMemoryModel64 + R"(
@@ -5252,7 +5303,8 @@ TEST_F(ValidateIdWithMessage, OpStoreBitcastNonPointerBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("OpStore type for pointer <id> '11' is not a pointer type."));
+ HasSubstr("OpStore type for pointer <id> '11[%11]' is not a pointer "
+ "type."));
}
// Result <id> resulting from an instruction within a function may not be used
@@ -5279,7 +5331,8 @@ OpFunctionEnd
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "ID 7 defined in block 6 does not dominate its use in block 9"));
+ "ID 7[%7] defined in block 6[%6] does not dominate its use in block "
+ "9[%9]"));
}
TEST_F(ValidateIdWithMessage, SpecIdTargetNotSpecializationConstant) {
@@ -5297,8 +5350,9 @@ OpFunctionEnd
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpDecorate SpecId decoration target <id> '1' is not a "
- "scalar specialization constant."));
+ HasSubstr("OpDecorate SpecId decoration target <id> "
+ "'1[%uint_3]' is not a scalar specialization "
+ "constant."));
}
TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantOpBad) {
@@ -5318,8 +5372,8 @@ OpFunctionEnd
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpDecorate SpecId decoration target <id> '1' is not a "
- "scalar specialization constant."));
+ HasSubstr("OpDecorate SpecId decoration target <id> '1[%1]' is "
+ "not a scalar specialization constant."));
}
TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantCompositeBad) {
@@ -5338,8 +5392,8 @@ OpFunctionEnd
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("OpDecorate SpecId decoration target <id> '1' is not a "
- "scalar specialization constant."));
+ HasSubstr("OpDecorate SpecId decoration target <id> '1[%1]' is "
+ "not a scalar specialization constant."));
}
TEST_F(ValidateIdWithMessage, SpecIdTargetGood) {
@@ -5415,7 +5469,7 @@ TEST_F(ValidateIdWithMessage, TypeFunctionBadUse) {
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("Invalid use of function type result id 2."));
+ HasSubstr("Invalid use of function type result id 2[%2]."));
}
TEST_F(ValidateIdWithMessage, BadTypeId) {
@@ -5433,7 +5487,8 @@ TEST_F(ValidateIdWithMessage, BadTypeId) {
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 4 is not a type id"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 4[%float_0] is not a type "
+ "id"));
}
TEST_F(ValidateIdWithMessage, VulkanMemoryModelLoadMakePointerVisibleGood) {
@@ -6094,8 +6149,8 @@ OpFunctionEnd
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("ID 7 defined in block 6 does not dominate its use in "
- "block 9\n %9 = OpLabel"));
+ HasSubstr("ID 7[%7] defined in block 6[%6] does not dominate its "
+ "use in block 9[%9]\n %9 = OpLabel"));
}
TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock2) {
@@ -6120,8 +6175,8 @@ OpFunctionEnd
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("ID 8 defined in block 7 does not dominate its use in "
- "block 10\n %10 = OpLabel"));
+ HasSubstr("ID 8[%8] defined in block 7[%7] does not dominate its "
+ "use in block 10[%10]\n %10 = OpLabel"));
}
TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock3) {
@@ -6146,8 +6201,8 @@ OpFunctionEnd
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("ID 8 defined in block 7 does not dominate its use in "
- "block 10\n %10 = OpLabel"));
+ HasSubstr("ID 8[%8] defined in block 7[%7] does not dominate its "
+ "use in block 10[%10]\n %10 = OpLabel"));
}
TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock4) {
@@ -6213,8 +6268,8 @@ OpFunctionEnd
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("ID 9 defined in block 8 does not dominate its use in "
- "block 7\n %7 = OpLabel"));
+ HasSubstr("ID 9[%9] defined in block 8[%8] does not dominate its "
+ "use in block 7[%7]\n %7 = OpLabel"));
}
TEST_F(ValidateIdWithMessage, ReachableDefUnreachableUse) {
@@ -6265,8 +6320,59 @@ TEST_F(ValidateIdWithMessage, UnreachableDefUsedInPhi) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("In OpPhi instruction 14, ID 13 definition does not dominate "
- "its parent 7\n %14 = OpPhi %float %11 %10 %13 %7"));
+ HasSubstr("In OpPhi instruction 14[%14], ID 13[%13] definition does not "
+ "dominate its parent 7[%7]\n %14 = OpPhi %float %11 %10 %13 "
+ "%7"));
+}
+
+TEST_F(ValidateIdWithMessage, OpTypeForwardPointerNotAPointerType) {
+ std::string spirv = R"(
+ OpCapability GenericPointer
+ OpCapability VariablePointersStorageBuffer
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginLowerLeft
+ OpTypeForwardPointer %2 CrossWorkgroup
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %2 DontInline %3
+%4 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Pointer type in OpTypeForwardPointer is not a pointer "
+ "type.\n OpTypeForwardPointer %void CrossWorkgroup"));
+}
+
+TEST_F(ValidateIdWithMessage, OpTypeForwardPointerWrongStorageClass) {
+ std::string spirv = R"(
+ OpCapability GenericPointer
+ OpCapability VariablePointersStorageBuffer
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginLowerLeft
+ OpTypeForwardPointer %2 CrossWorkgroup
+%int = OpTypeInt 32 1
+%2 = OpTypePointer Function %int
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%1 = OpFunction %void None %3
+%4 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Storage class in OpTypeForwardPointer does not match the "
+ "pointer definition.\n OpTypeForwardPointer "
+ "%_ptr_Function_int CrossWorkgroup"));
}
} // namespace
} // namespace val
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index a84b4015..79aecb25 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -61,6 +61,37 @@ OpCapability ImageBuffer
ss << "OpExecutionMode %main OriginUpperLeft\n";
}
+ if (env == SPV_ENV_VULKAN_1_0) {
+ ss << R"(
+OpDecorate %uniform_image_f32_1d_0001 DescriptorSet 0
+OpDecorate %uniform_image_f32_1d_0001 Binding 0
+OpDecorate %uniform_image_f32_1d_0002_rgba32f DescriptorSet 0
+OpDecorate %uniform_image_f32_1d_0002_rgba32f Binding 1
+OpDecorate %uniform_image_f32_2d_0001 DescriptorSet 0
+OpDecorate %uniform_image_f32_2d_0001 Binding 2
+OpDecorate %uniform_image_f32_2d_0010 DescriptorSet 0
+OpDecorate %uniform_image_f32_2d_0010 Binding 3
+OpDecorate %uniform_image_u32_2d_0001 DescriptorSet 1
+OpDecorate %uniform_image_u32_2d_0001 Binding 0
+OpDecorate %uniform_image_u32_2d_0000 DescriptorSet 1
+OpDecorate %uniform_image_u32_2d_0000 Binding 1
+OpDecorate %uniform_image_s32_3d_0001 DescriptorSet 1
+OpDecorate %uniform_image_s32_3d_0001 Binding 2
+OpDecorate %uniform_image_f32_2d_0002 DescriptorSet 1
+OpDecorate %uniform_image_f32_2d_0002 Binding 3
+OpDecorate %uniform_image_f32_spd_0002 DescriptorSet 2
+OpDecorate %uniform_image_f32_spd_0002 Binding 0
+OpDecorate %uniform_image_f32_3d_0111 DescriptorSet 2
+OpDecorate %uniform_image_f32_3d_0111 Binding 1
+OpDecorate %uniform_image_f32_cube_0101 DescriptorSet 2
+OpDecorate %uniform_image_f32_cube_0101 Binding 2
+OpDecorate %uniform_image_f32_cube_0102_rgba32f DescriptorSet 2
+OpDecorate %uniform_image_f32_cube_0102_rgba32f Binding 3
+OpDecorate %uniform_sampler DescriptorSet 3
+OpDecorate %uniform_sampler Binding 0
+)";
+ }
+
ss << R"(
%void = OpTypeVoid
%func = OpTypeFunction %void
@@ -672,7 +703,8 @@ TEST_F(ValidateImage, ImageTexelPointerImageNotResultTypePointer) {
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 136 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 136[%136] cannot be a "
+ "type"));
}
TEST_F(ValidateImage, ImageTexelPointerImageNotImage) {
@@ -4203,7 +4235,7 @@ TEST_F(ValidateImage, SparseTexelsResidentResultTypeNotBool) {
TEST_F(ValidateImage, MakeTexelVisibleKHRSuccessImageRead) {
const std::string body = R"(
%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
-%res1 = OpImageRead %u32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_1
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_2
)";
const std::string extra = R"(
@@ -4220,7 +4252,7 @@ OpExtension "SPV_KHR_vulkan_memory_model"
TEST_F(ValidateImage, MakeTexelVisibleKHRSuccessImageSparseRead) {
const std::string body = R"(
%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
-%res1 = OpImageSparseRead %struct_u32_f32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_1
+%res1 = OpImageSparseRead %struct_u32_f32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_2
)";
const std::string extra = R"(
@@ -4282,7 +4314,7 @@ OpExtension "SPV_KHR_vulkan_memory_model"
TEST_F(ValidateImage, MakeTexelAvailableKHRSuccessImageWrite) {
const std::string body = R"(
%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
-%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123 MakeTexelAvailableKHR|NonPrivateTexelKHR %u32_1
+%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123 MakeTexelAvailableKHR|NonPrivateTexelKHR %u32_2
)";
const std::string extra = R"(
@@ -4340,6 +4372,86 @@ OpExtension "SPV_KHR_vulkan_memory_model"
"NonPrivateTexelKHR is also specified: OpImageWrite"));
}
+TEST_F(ValidateImage, VulkanMemoryModelDeviceScopeImageWriteBad) {
+ const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123 MakeTexelAvailableKHR|NonPrivateTexelKHR %u32_1
+)";
+
+ const std::string extra = R"(
+OpCapability StorageImageWriteWithoutFormat
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+ CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+ SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
+ .c_str());
+ ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateImage, VulkanMemoryModelDeviceScopeImageWriteGood) {
+ const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123 MakeTexelAvailableKHR|NonPrivateTexelKHR %u32_1
+)";
+
+ const std::string extra = R"(
+OpCapability StorageImageWriteWithoutFormat
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+ CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+ SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
+ .c_str());
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateImage, VulkanMemoryModelDeviceScopeImageReadBad) {
+ const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_1
+)";
+
+ const std::string extra = R"(
+OpCapability StorageImageReadWithoutFormat
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+ CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+ SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
+ .c_str());
+ ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateImage, VulkanMemoryModelDeviceScopeImageReadGood) {
+ const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 MakeTexelVisibleKHR|NonPrivateTexelKHR %u32_1
+)";
+
+ const std::string extra = R"(
+OpCapability StorageImageReadWithoutFormat
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+ CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+ SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
+ .c_str());
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
} // namespace
} // namespace val
} // namespace spvtools
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp
index e67c840c..e502d8c2 100644
--- a/test/val/val_layout_test.cpp
+++ b/test/val/val_layout_test.cpp
@@ -648,6 +648,57 @@ TEST_F(ValidateLayout, ModuleProcessedInvalidInBasicBlock) {
HasSubstr("ModuleProcessed cannot appear in a function declaration"));
}
+TEST_F(ValidateLayout, WebGPUCallerBeforeCalleeBad) {
+ char str[] = R"(
+ OpCapability Shader
+ OpCapability VulkanMemoryModelKHR
+ OpExtension "SPV_KHR_vulkan_memory_model"
+ OpMemoryModel Logical VulkanKHR
+ OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%1 = OpLabel
+%2 = OpFunctionCall %void %callee
+ OpReturn
+ OpFunctionEnd
+%callee = OpFunction %void None %voidfn
+%3 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str, SPV_ENV_WEBGPU_0);
+ ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_WEBGPU_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("For WebGPU, functions need to be defined before being "
+ "called.\n %5 = OpFunctionCall %void %6\n"));
+}
+
+TEST_F(ValidateLayout, WebGPUCalleeBeforeCallerGood) {
+ char str[] = R"(
+ OpCapability Shader
+ OpCapability VulkanMemoryModelKHR
+ OpExtension "SPV_KHR_vulkan_memory_model"
+ OpMemoryModel Logical VulkanKHR
+ OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%callee = OpFunction %void None %voidfn
+%3 = OpLabel
+ OpReturn
+ OpFunctionEnd
+%main = OpFunction %void None %voidfn
+%1 = OpLabel
+%2 = OpFunctionCall %void %callee
+ OpReturn
+ OpFunctionEnd
+)";
+
+ CompileSuccessfully(str, SPV_ENV_WEBGPU_0);
+ ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
// TODO(umar): Test optional instructions
} // namespace
diff --git a/test/val/val_limits_test.cpp b/test/val/val_limits_test.cpp
index 1368109b..791b709f 100644
--- a/test/val/val_limits_test.cpp
+++ b/test/val/val_limits_test.cpp
@@ -353,7 +353,7 @@ TEST_F(ValidateLimits, OpTypeFunctionBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpTypeFunction may not take more than 255 arguments. "
- "OpTypeFunction <id> '2' has 256 arguments."));
+ "OpTypeFunction <id> '2[%2]' has 256 arguments."));
}
// Valid: OpTypeFunction with 100 arguments (Custom limit: 100)
@@ -389,7 +389,7 @@ TEST_F(ValidateLimits, CustomizedOpTypeFunctionBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpTypeFunction may not take more than 100 arguments. "
- "OpTypeFunction <id> '2' has 101 arguments."));
+ "OpTypeFunction <id> '2[%2]' has 101 arguments."));
}
// Valid: module has 65,535 global variables.
diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp
index 9d35531b..296ea438 100644
--- a/test/val/val_memory_test.cpp
+++ b/test/val/val_memory_test.cpp
@@ -64,6 +64,8 @@ OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
%sampler = OpTypeSampler
%sampler_ptr = OpTypePointer UniformConstant %sampler
%2 = OpVariable %sampler_ptr UniformConstant
@@ -115,6 +117,8 @@ OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
%sampler = OpTypeSampler
%uint = OpTypeInt 32 0
%array_size = OpConstant %uint 5
@@ -138,6 +142,8 @@ OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
%sampler = OpTypeSampler
%uint = OpTypeInt 32 0
%array = OpTypeRuntimeArray %sampler
@@ -442,9 +448,8 @@ OpFunctionEnd
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "OpVariable, <id> '5', has a disallowed initializer & storage class "
- "combination.\n"
- "From WebGPU execution environment spec:\n"
+ "OpVariable, <id> '5[%5]', has a disallowed initializer & storage "
+ "class combination.\nFrom WebGPU execution environment spec:\n"
"Variable declarations that include initializers must have one of "
"the following storage classes: Output, Private, or Function\n"
" %5 = OpVariable %_ptr_Uniform_float Uniform %float_1\n"));
@@ -535,9 +540,8 @@ OpFunctionEnd
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "OpVariable, <id> '5', has a disallowed initializer & storage class "
- "combination.\n"
- "From Vulkan spec, Appendix A:\n"
+ "OpVariable, <id> '5[%5]', has a disallowed initializer & storage "
+ "class combination.\nFrom Vulkan spec, Appendix A:\n"
"Variable declarations that include initializers must have one of "
"the following storage classes: Output, Private, or Function\n "
"%5 = OpVariable %_ptr_Input_float Input %float_1\n"));
@@ -620,8 +624,9 @@ TEST_F(ValidateMemory, ArrayLenResultNotIntType) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "The Result Type of OpArrayLength <id> '10' must be OpTypeInt with "
- "width 32 and signedness 0.\n %10 = OpArrayLength %float %9 0\n"));
+ "The Result Type of OpArrayLength <id> '10[%10]' must be OpTypeInt "
+ "with width 32 and signedness 0.\n %10 = OpArrayLength %float %9 "
+ "0\n"));
}
TEST_F(ValidateMemory, ArrayLenResultNot32bits) {
@@ -652,8 +657,9 @@ TEST_F(ValidateMemory, ArrayLenResultNot32bits) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "The Result Type of OpArrayLength <id> '11' must be OpTypeInt with "
- "width 32 and signedness 0.\n %11 = OpArrayLength %ushort %10 0\n"));
+ "The Result Type of OpArrayLength <id> '11[%11]' must be OpTypeInt "
+ "with width 32 and signedness 0.\n %11 = OpArrayLength %ushort %10 "
+ "0\n"));
}
TEST_F(ValidateMemory, ArrayLenResultSigned) {
@@ -683,8 +689,9 @@ TEST_F(ValidateMemory, ArrayLenResultSigned) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "The Result Type of OpArrayLength <id> '11' must be OpTypeInt with "
- "width 32 and signedness 0.\n %11 = OpArrayLength %int %10 0\n"));
+ "The Result Type of OpArrayLength <id> '11[%11]' must be OpTypeInt "
+ "with width 32 and signedness 0.\n %11 = OpArrayLength %int %10 "
+ "0\n"));
}
TEST_F(ValidateMemory, ArrayLenInputNotStruct) {
@@ -712,8 +719,8 @@ TEST_F(ValidateMemory, ArrayLenInputNotStruct) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("The Struture's type in OpArrayLength <id> '11' must "
- "be a pointer to an OpTypeStruct."));
+ HasSubstr("The Struture's type in OpArrayLength <id> '11[%11]' "
+ "must be a pointer to an OpTypeStruct."));
}
TEST_F(ValidateMemory, ArrayLenInputLastMemberNoRTA) {
@@ -742,8 +749,9 @@ TEST_F(ValidateMemory, ArrayLenInputLastMemberNoRTA) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("The Struture's last member in OpArrayLength <id> '11' must be "
- "an OpTypeRuntimeArray.\n %11 = OpArrayLength %uint %10 0\n"));
+ HasSubstr("The Struture's last member in OpArrayLength <id> '11[%11]' "
+ "must be an OpTypeRuntimeArray.\n %11 = OpArrayLength %uint "
+ "%10 0\n"));
}
TEST_F(ValidateMemory, ArrayLenInputLastMemberNoRTA2) {
@@ -772,8 +780,9 @@ TEST_F(ValidateMemory, ArrayLenInputLastMemberNoRTA2) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- HasSubstr("The Struture's last member in OpArrayLength <id> '11' must be "
- "an OpTypeRuntimeArray.\n %11 = OpArrayLength %uint %10 1\n"));
+ HasSubstr("The Struture's last member in OpArrayLength <id> '11[%11]' "
+ "must be an OpTypeRuntimeArray.\n %11 = OpArrayLength %uint "
+ "%10 1\n"));
}
TEST_F(ValidateMemory, ArrayLenIndexNotLastMember) {
@@ -803,8 +812,8 @@ TEST_F(ValidateMemory, ArrayLenIndexNotLastMember) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "The array member in OpArrayLength <id> '11' must be an the last "
- "member of the struct.\n %11 = OpArrayLength %uint %10 0\n"));
+ "The array member in OpArrayLength <id> '11[%11]' must be an the "
+ "last member of the struct.\n %11 = OpArrayLength %uint %10 0\n"));
}
TEST_F(ValidateMemory, ArrayLenIndexNotPointerToStruct) {
@@ -835,8 +844,8 @@ TEST_F(ValidateMemory, ArrayLenIndexNotPointerToStruct) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
- "The Struture's type in OpArrayLength <id> '12' must be a pointer to "
- "an OpTypeStruct.\n %12 = OpArrayLength %uint %11 0\n"));
+ "The Struture's type in OpArrayLength <id> '12[%12]' must be a "
+ "pointer to an OpTypeStruct.\n %12 = OpArrayLength %uint %11 0\n"));
}
TEST_F(ValidateMemory, ArrayLenPointerIsAType) {
@@ -859,7 +868,8 @@ TEST_F(ValidateMemory, ArrayLenPointerIsAType) {
CompileSuccessfully(spirv.c_str());
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4 cannot be a type"));
+ EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4[%float] cannot be a "
+ "type"));
}
TEST_F(ValidateMemory, PushConstantNotStructGood) {
@@ -905,8 +915,8 @@ TEST_F(ValidateMemory, VulkanPushConstantNotStructBad) {
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
EXPECT_THAT(getDiagnosticString(),
- HasSubstr("PushConstant OpVariable <id> '6' has illegal type.\n"
- "From Vulkan spec, section 14.5.1:\n"
+ HasSubstr("PushConstant OpVariable <id> '6[%6]' has illegal "
+ "type.\nFrom Vulkan spec, section 14.5.1:\n"
"Such variables must be typed as OpTypeStruct, "
"or an array of this type"));
}
@@ -937,6 +947,572 @@ TEST_F(ValidateMemory, VulkanPushConstant) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadBad1) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+%load = OpLoad %int %var MakePointerVisibleKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadBad2) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+%load = OpLoad %int %var Aligned|MakePointerVisibleKHR|NonPrivatePointerKHR 4 %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadGood1) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+%load = OpLoad %int %var MakePointerVisibleKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadGood2) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+%load = OpLoad %int %var Aligned|MakePointerVisibleKHR|NonPrivatePointerKHR 4 %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreBad1) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpStore %var %device MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreBad2) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpStore %var %device Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreGood1) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpStore %var %device MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreGood2) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpStore %var %device Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryBad1) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryBad2) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryBad3) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood1) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood2) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood3) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad1) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad2) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad3) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+ ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr("Use of device scope with VulkanKHR memory model requires the "
+ "VulkanMemoryModelDeviceScopeKHR capability"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedGood1) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedGood2) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedGood3) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %device Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
} // namespace
} // namespace val
} // namespace spvtools
diff --git a/test/val/val_ssa_test.cpp b/test/val/val_ssa_test.cpp
index 9da80c0e..5d8fa4b7 100644
--- a/test/val/val_ssa_test.cpp
+++ b/test/val/val_ssa_test.cpp
@@ -118,7 +118,7 @@ TEST_F(ValidateSSA, DominateUsageWithinBlockBad) {
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("ID .\\[bad\\] has not been defined\n"
+ MatchesRegex("ID .\\[%bad\\] has not been defined\n"
" %8 = OpIAdd %uint %uint_1 %bad\n"));
}
@@ -141,7 +141,7 @@ TEST_F(ValidateSSA, DominateUsageSameInstructionBad) {
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("ID .\\[sum\\] has not been defined\n"
+ MatchesRegex("ID .\\[%sum\\] has not been defined\n"
" %sum = OpIAdd %uint %uint_1 %sum\n"));
}
@@ -202,7 +202,9 @@ TEST_F(ValidateSSA, ForwardMemberNameMissingTargetBad) {
)";
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
- EXPECT_THAT(getDiagnosticString(), HasSubstr("size"));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("The following forward referenced IDs have not been "
+ "defined:\n2[%2]"));
}
TEST_F(ValidateSSA, ForwardDecorateGood) {
@@ -1124,8 +1126,8 @@ TEST_F(ValidateSSA, IdDoesNotDominateItsUseBad) {
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- MatchesRegex("ID .\\[eleven\\] defined in block .\\[true_block\\] does "
- "not dominate its use in block .\\[false_block\\]\n"
+ MatchesRegex("ID .\\[%eleven\\] defined in block .\\[%true_block\\] "
+ "does not dominate its use in block .\\[%false_block\\]\n"
" %false_block = OpLabel\n"));
}
@@ -1185,7 +1187,7 @@ TEST_F(ValidateSSA,
CompileSuccessfully(str);
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
- MatchesRegex("ID .\\[inew\\] has not been defined\n"
+ MatchesRegex("ID .\\[%inew\\] has not been defined\n"
" %19 = OpIAdd %uint %inew %uint_1\n"));
}
@@ -1268,8 +1270,8 @@ TEST_F(ValidateSSA, PhiVariableDefNotDominatedByParentBlockBad) {
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- MatchesRegex("In OpPhi instruction .\\[phi\\], ID .\\[true_copy\\] "
- "definition does not dominate its parent .\\[if_false\\]\n"
+ MatchesRegex("In OpPhi instruction .\\[%phi\\], ID .\\[%true_copy\\] "
+ "definition does not dominate its parent .\\[%if_false\\]\n"
" %phi = OpPhi %bool %true_copy %if_false %false_copy "
"%if_true\n"));
}
@@ -1396,8 +1398,8 @@ TEST_F(ValidateSSA, UseFunctionParameterFromOtherFunctionBad) {
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
- MatchesRegex("ID .\\[first\\] used in function .\\[func2\\] is used "
- "outside of it's defining function .\\[func\\]\n"
+ MatchesRegex("ID .\\[%first\\] used in function .\\[%func2\\] is used "
+ "outside of it's defining function .\\[%func\\]\n"
" %func = OpFunction %void None %14\n"));
}
diff --git a/test/val/val_validation_state_test.cpp b/test/val/val_validation_state_test.cpp
index 68504c52..e010fe9f 100644
--- a/test/val/val_validation_state_test.cpp
+++ b/test/val/val_validation_state_test.cpp
@@ -29,11 +29,17 @@ using ::testing::HasSubstr;
using ValidationStateTest = spvtest::ValidateBase<bool>;
-const char header[] =
+const char kHeader[] =
" OpCapability Shader"
" OpCapability Linkage"
" OpMemoryModel Logical GLSL450 ";
+const char kVulkanMemoryHeader[] =
+ " OpCapability Shader"
+ " OpCapability VulkanMemoryModelKHR"
+ " OpExtension \"SPV_KHR_vulkan_memory_model\""
+ " OpMemoryModel Logical VulkanKHR ";
+
const char kVoidFVoid[] =
" %void = OpTypeVoid"
" %void_f = OpTypeFunction %void"
@@ -42,9 +48,79 @@ const char kVoidFVoid[] =
" OpReturn"
" OpFunctionEnd ";
+// k*RecursiveBody examples originally from test/opt/function_test.cpp
+const char* kNonRecursiveBody = R"(
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%12 = OpFunction %_struct_6 None %7
+%13 = OpLabel
+OpUnreachable
+OpFunctionEnd
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %12
+OpUnreachable
+OpFunctionEnd
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+)";
+
+const char* kDirectlyRecursiveBody = R"(
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpUnreachable
+OpFunctionEnd
+)";
+
+const char* kIndirectlyRecursiveBody = R"(
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %12
+OpUnreachable
+OpFunctionEnd
+%12 = OpFunction %_struct_6 None %7
+%13 = OpLabel
+%14 = OpFunctionCall %_struct_6 %9
+OpUnreachable
+OpFunctionEnd
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+)";
+
// Tests that the instruction count in ValidationState is correct.
TEST_F(ValidationStateTest, CheckNumInstructions) {
- std::string spirv = std::string(header) + "%int = OpTypeInt 32 0";
+ std::string spirv = std::string(kHeader) + "%int = OpTypeInt 32 0";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
EXPECT_EQ(size_t(4), vstate_->ordered_instructions().size());
@@ -52,7 +128,7 @@ TEST_F(ValidationStateTest, CheckNumInstructions) {
// Tests that the number of global variables in ValidationState is correct.
TEST_F(ValidationStateTest, CheckNumGlobalVars) {
- std::string spirv = std::string(header) + R"(
+ std::string spirv = std::string(kHeader) + R"(
%int = OpTypeInt 32 0
%_ptr_int = OpTypePointer Input %int
%var_1 = OpVariable %_ptr_int Input
@@ -65,7 +141,7 @@ TEST_F(ValidationStateTest, CheckNumGlobalVars) {
// Tests that the number of local variables in ValidationState is correct.
TEST_F(ValidationStateTest, CheckNumLocalVars) {
- std::string spirv = std::string(header) + R"(
+ std::string spirv = std::string(kHeader) + R"(
%int = OpTypeInt 32 0
%_ptr_int = OpTypePointer Function %int
%voidt = OpTypeVoid
@@ -85,7 +161,7 @@ TEST_F(ValidationStateTest, CheckNumLocalVars) {
// Tests that the "id bound" in ValidationState is correct.
TEST_F(ValidationStateTest, CheckIdBound) {
- std::string spirv = std::string(header) + R"(
+ std::string spirv = std::string(kHeader) + R"(
%int = OpTypeInt 32 0
%voidt = OpTypeVoid
)";
@@ -96,7 +172,7 @@ TEST_F(ValidationStateTest, CheckIdBound) {
// Tests that the entry_points in ValidationState is correct.
TEST_F(ValidationStateTest, CheckEntryPoints) {
- std::string spirv = std::string(header) +
+ std::string spirv = std::string(kHeader) +
" OpEntryPoint Vertex %func \"shader\"" +
std::string(kVoidFVoid);
CompileSuccessfully(spirv);
@@ -154,6 +230,82 @@ TEST_F(ValidationStateTest, CheckAccessChainIndexesLimitOption) {
EXPECT_EQ(100u, options_->universal_limits_.max_access_chain_indexes);
}
+TEST_F(ValidationStateTest, CheckNonRecursiveBodyGood) {
+ std::string spirv = std::string(kHeader) + kNonRecursiveBody;
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidationStateTest, CheckVulkanNonRecursiveBodyGood) {
+ std::string spirv = std::string(kVulkanMemoryHeader) + kNonRecursiveBody;
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_SUCCESS,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidationStateTest, CheckWebGPUNonRecursiveBodyGood) {
+ std::string spirv = std::string(kVulkanMemoryHeader) + kNonRecursiveBody;
+ CompileSuccessfully(spirv, SPV_ENV_WEBGPU_0);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidationStateTest, CheckDirectlyRecursiveBodyGood) {
+ std::string spirv = std::string(kHeader) + kDirectlyRecursiveBody;
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidationStateTest, CheckVulkanDirectlyRecursiveBodyBad) {
+ std::string spirv = std::string(kVulkanMemoryHeader) + kDirectlyRecursiveBody;
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry points may not have a call graph with cycles.\n "
+ " %1 = OpFunction %void Pure|Const %3\n"));
+}
+
+TEST_F(ValidationStateTest, CheckWebGPUDirectlyRecursiveBodyBad) {
+ std::string spirv = std::string(kVulkanMemoryHeader) + kDirectlyRecursiveBody;
+ CompileSuccessfully(spirv, SPV_ENV_WEBGPU_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+ ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry points may not have a call graph with cycles.\n "
+ " %1 = OpFunction %void Pure|Const %3\n"));
+}
+
+TEST_F(ValidationStateTest, CheckIndirectlyRecursiveBodyGood) {
+ std::string spirv = std::string(kHeader) + kIndirectlyRecursiveBody;
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidationStateTest, CheckVulkanIndirectlyRecursiveBodyBad) {
+ std::string spirv =
+ std::string(kVulkanMemoryHeader) + kIndirectlyRecursiveBody;
+ CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+ EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+ ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("Entry points may not have a call graph with cycles.\n "
+ " %1 = OpFunction %void Pure|Const %3\n"));
+}
+
+// Indirectly recursive functions are caught by the function definition layout
+// rules, because they cause a situation where there are 2 functions that have
+// to be before each other, and layout is checked earlier.
+TEST_F(ValidationStateTest, CheckWebGPUIndirectlyRecursiveBodyBad) {
+ std::string spirv =
+ std::string(kVulkanMemoryHeader) + kIndirectlyRecursiveBody;
+ CompileSuccessfully(spirv, SPV_ENV_WEBGPU_0);
+ EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT,
+ ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("For WebGPU, functions need to be defined before being "
+ "called.\n %9 = OpFunctionCall %_struct_5 %10\n"));
+}
+
} // namespace
} // namespace val
} // namespace spvtools
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index 32b4b23f..0e8088f4 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -23,6 +23,7 @@
#include <vector>
#include "source/opt/log.h"
+#include "source/spirv_target_env.h"
#include "source/util/string_utils.h"
#include "spirv-tools/libspirv.hpp"
#include "spirv-tools/optimizer.hpp"
@@ -189,9 +190,9 @@ Options (in lexicographical order):
Looks for instructions in the same basic block that compute the
same value, and deletes the redundant ones.
--loop-fission
- Splits any top level loops in which the register pressure has exceeded
- a given threshold. The threshold must follow the use of this flag and
- must be a positive integer value.
+ Splits any top level loops in which the register pressure has
+ exceeded a given threshold. The threshold must follow the use of
+ this flag and must be a positive integer value.
--loop-fusion
Identifies adjacent loops with the same lower and upper bound.
If this is legal, then merge the loops into a single loop.
@@ -199,6 +200,9 @@ Options (in lexicographical order):
registers too much, while reducing the number of loads from
memory. Takes an additional positive integer argument to set
the maximum number of registers.
+ --loop-invariant-code-motion
+ Identifies code in loops that has the same value for every
+ iteration of the loop, and move it to the loop pre-header.
--loop-unroll
Fully unrolls loops marked with the Unroll flag
--loop-unroll-partial
@@ -335,6 +339,11 @@ Options (in lexicographical order):
--strip-reflect
Remove all reflection information. For now, this covers
reflection information defined by SPV_GOOGLE_hlsl_functionality1.
+ --target-env=<env>
+ Set the target environment. Without this flag the target
+ enviroment defaults to spv1.3.
+ <env> must be one of vulkan1.0, vulkan1.1, opencl2.2, spv1.0,
+ spv1.1, spv1.2, spv1.3, or webgpu0.
--time-report
Print the resource utilization of each pass (e.g., CPU time,
RSS) to standard error output. Currently it supports only Unix
@@ -563,6 +572,17 @@ OptStatus ParseFlags(int argc, const char** argv,
optimizer_options->set_max_id_bound(max_id_bound);
validator_options->SetUniversalLimit(spv_validator_limit_max_id_bound,
max_id_bound);
+ } else if (0 == strncmp(cur_arg,
+ "--target-env=", sizeof("--target-env=") - 1)) {
+ const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
+ const auto target_env_str = split_flag.second.c_str();
+ spv_target_env target_env;
+ if (!spvParseTargetEnv(target_env_str, &target_env)) {
+ spvtools::Error(opt_diagnostic, nullptr, {},
+ "Invalid value passed to --target-env");
+ return {OPT_STOP, 1};
+ }
+ optimizer->SetTargetEnv(target_env);
} else {
// Some passes used to accept the form '--pass arg', canonicalize them
// to '--pass=arg'.
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
index 6b0af098..390724a6 100644
--- a/tools/reduce/reduce.cpp
+++ b/tools/reduce/reduce.cpp
@@ -23,7 +23,9 @@
#include "source/reduce/operand_to_const_reduction_pass.h"
#include "source/reduce/operand_to_dominating_id_reduction_pass.h"
#include "source/reduce/reducer.h"
+#include "source/reduce/remove_opname_instruction_reduction_pass.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
+#include "source/reduce/structured_loop_to_selection_reduction_pass.h"
#include "source/spirv_reducer_options.h"
#include "source/util/make_unique.h"
#include "source/util/string_utils.h"
@@ -204,12 +206,16 @@ int main(int argc, const char** argv) {
});
reducer.AddReductionPass(
+ spvtools::MakeUnique<RemoveOpNameInstructionReductionPass>(target_env));
+ reducer.AddReductionPass(
spvtools::MakeUnique<OperandToConstReductionPass>(target_env));
reducer.AddReductionPass(
spvtools::MakeUnique<OperandToDominatingIdReductionPass>(target_env));
reducer.AddReductionPass(
spvtools::MakeUnique<RemoveUnreferencedInstructionReductionPass>(
target_env));
+ reducer.AddReductionPass(
+ spvtools::MakeUnique<StructuredLoopToSelectionReductionPass>(target_env));
reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);