diff options
author | Yiwei Zhang <zzyiwei@google.com> | 2019-02-05 23:29:32 -0800 |
---|---|---|
committer | Yiwei Zhang <zzyiwei@google.com> | 2019-02-05 23:29:32 -0800 |
commit | 2e6244127648ea760172bf07ecf687a59ee7bd17 (patch) | |
tree | 2552178d8459e02ac87d88b24c7d58f8b0083b98 | |
parent | c5727b419b4d30f9e0fa44a6eb6a75a4b8c88ca9 (diff) | |
parent | e0292c269d6f5c8481afb9f2d043c74ee11ca24f (diff) | |
download | SPIRV-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
94 files changed, 9195 insertions, 987 deletions
@@ -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", @@ -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: @@ -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); |