diff --git a/3rdparty/spirv-tools/Android.mk b/3rdparty/spirv-tools/Android.mk index 8597c5056..179cf2614 100644 --- a/3rdparty/spirv-tools/Android.mk +++ b/3rdparty/spirv-tools/Android.mk @@ -80,6 +80,7 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/cfg.cpp \ source/opt/cfg_cleanup_pass.cpp \ source/opt/ccp_pass.cpp \ + source/opt/code_sink.cpp \ source/opt/combine_access_chains.cpp \ source/opt/common_uniform_elim_pass.cpp \ source/opt/compact_ids_pass.cpp \ diff --git a/3rdparty/spirv-tools/BUILD.gn b/3rdparty/spirv-tools/BUILD.gn index 5f0eba954..69e3dc812 100644 --- a/3rdparty/spirv-tools/BUILD.gn +++ b/3rdparty/spirv-tools/BUILD.gn @@ -460,6 +460,8 @@ static_library("spvtools_opt") { "source/opt/cfg.h", "source/opt/cfg_cleanup_pass.cpp", "source/opt/cfg_cleanup_pass.h", + "source/opt/code_sink.cpp", + "source/opt/code_sink.h", "source/opt/combine_access_chains.cpp", "source/opt/combine_access_chains.h", "source/opt/common_uniform_elim_pass.cpp", diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index 1f3cadec2..b0c547197 100644 --- a/3rdparty/spirv-tools/include/generated/build-version.inc +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -1 +1 @@ -"v2019.2-dev", "SPIRV-Tools v2019.2-dev 3c8b3c81ef7f378717ef88916c06cb221eea890b" +"v2019.2-dev", "SPIRV-Tools v2019.2-dev 7d149d91055744e5a9bc6368de7b3bb605d0e1a9" diff --git a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp index b2865a80e..b59329db4 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp @@ -722,6 +722,10 @@ Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set, // conform to that model's requirements. Optimizer::PassToken CreateUpgradeMemoryModelPass(); +// Create a pass to do code sinking. Code sinking is a transformation +// where an instruction is moved into a more deeply nested construct. +Optimizer::PassToken CreateCodeSinkingPass(); + } // namespace spvtools #endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_ diff --git a/3rdparty/spirv-tools/source/opt/CMakeLists.txt b/3rdparty/spirv-tools/source/opt/CMakeLists.txt index 96ee8b319..0f3835cbb 100644 --- a/3rdparty/spirv-tools/source/opt/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/opt/CMakeLists.txt @@ -19,6 +19,7 @@ set(SPIRV_TOOLS_OPT_SOURCES ccp_pass.h cfg_cleanup_pass.h cfg.h + code_sink.h combine_access_chains.h common_uniform_elim_pass.h compact_ids_pass.h @@ -111,6 +112,7 @@ set(SPIRV_TOOLS_OPT_SOURCES ccp_pass.cpp cfg_cleanup_pass.cpp cfg.cpp + code_sink.cpp combine_access_chains.cpp common_uniform_elim_pass.cpp compact_ids_pass.cpp diff --git a/3rdparty/spirv-tools/source/opt/code_sink.cpp b/3rdparty/spirv-tools/source/opt/code_sink.cpp new file mode 100644 index 000000000..e1e819017 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/code_sink.cpp @@ -0,0 +1,316 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "code_sink.h" + +#include +#include + +#include "source/opt/instruction.h" +#include "source/opt/ir_builder.h" +#include "source/opt/ir_context.h" +#include "source/util/bit_vector.h" + +namespace spvtools { +namespace opt { + +Pass::Status CodeSinkingPass::Process() { + bool modified = false; + for (Function& function : *get_module()) { + cfg()->ForEachBlockInPostOrder(function.entry().get(), + [&modified, this](BasicBlock* bb) { + if (SinkInstructionsInBB(bb)) { + modified = true; + } + }); + } + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +bool CodeSinkingPass::SinkInstructionsInBB(BasicBlock* bb) { + bool modified = false; + for (auto inst = bb->rbegin(); inst != bb->rend(); ++inst) { + if (SinkInstruction(&*inst)) { + inst = bb->rbegin(); + modified = true; + } + } + return modified; +} + +bool CodeSinkingPass::SinkInstruction(Instruction* inst) { + if (inst->opcode() != SpvOpLoad && inst->opcode() != SpvOpAccessChain) { + return false; + } + + if (ReferencesMutableMemory(inst)) { + return false; + } + + if (BasicBlock* target_bb = FindNewBasicBlockFor(inst)) { + Instruction* pos = &*target_bb->begin(); + while (pos->opcode() == SpvOpPhi) { + pos = pos->NextNode(); + } + + inst->InsertBefore(pos); + context()->set_instr_block(inst, target_bb); + return true; + } + return false; +} + +BasicBlock* CodeSinkingPass::FindNewBasicBlockFor(Instruction* inst) { + assert(inst->result_id() != 0 && "Instruction should have a result."); + BasicBlock* original_bb = context()->get_instr_block(inst); + BasicBlock* bb = original_bb; + + std::unordered_set bbs_with_uses; + get_def_use_mgr()->ForEachUse( + inst, [&bbs_with_uses, this](Instruction* use, uint32_t idx) { + if (use->opcode() != SpvOpPhi) { + bbs_with_uses.insert(context()->get_instr_block(use)->id()); + } else { + bbs_with_uses.insert(use->GetSingleWordOperand(idx + 1)); + } + }); + + while (true) { + // If |inst| is used in |bb|, then |inst| cannot be moved any further. + if (bbs_with_uses.count(bb->id())) { + break; + } + + // If |bb| has one successor (succ_bb), and |bb| is the only predecessor + // of succ_bb, then |inst| can be moved to succ_bb. If succ_bb, has move + // then one predecessor, then moving |inst| into succ_bb could cause it to + // be executed more often, so the search has to stop. + if (bb->terminator()->opcode() == SpvOpBranch) { + uint32_t succ_bb_id = bb->terminator()->GetSingleWordInOperand(0); + if (cfg()->preds(succ_bb_id).size() == 1) { + bb = context()->get_instr_block(succ_bb_id); + continue; + } else { + break; + } + } + + // The remaining checks need to know the merge node. If there is no merge + // instruction or an OpLoopMerge, then it is a break or continue. We could + // figure it out, but not worth doing it now. + Instruction* merge_inst = bb->GetMergeInst(); + if (merge_inst == nullptr || merge_inst->opcode() != SpvOpSelectionMerge) { + break; + } + + // Check all of the successors of |bb| it see which lead to a use of |inst| + // before reaching the merge node. + bool used_in_multiple_blocks = false; + uint32_t bb_used_in = 0; + bb->ForEachSuccessorLabel([this, bb, &bb_used_in, &used_in_multiple_blocks, + &bbs_with_uses](uint32_t* succ_bb_id) { + if (IntersectsPath(*succ_bb_id, bb->MergeBlockIdIfAny(), bbs_with_uses)) { + if (bb_used_in == 0) { + bb_used_in = *succ_bb_id; + } else { + used_in_multiple_blocks = true; + } + } + }); + + // If more than one successor, which is not the merge block, uses |inst| + // then we have to leave |inst| in bb because there is none of the + // successors dominate all uses of |inst|. + if (used_in_multiple_blocks) { + break; + } + + if (bb_used_in == 0) { + // If |inst| is not used before reaching the merge node, then we can move + // |inst| to the merge node. + bb = context()->get_instr_block(bb->MergeBlockIdIfAny()); + } else { + // If the only successor that leads to a used of |inst| has more than 1 + // predecessor, then moving |inst| could cause it to be executed more + // often, so we cannot move it. + if (cfg()->preds(bb_used_in).size() != 1) { + break; + } + + // If |inst| is used after the merge block, then |bb_used_in| does not + // dominate all of the uses. So we cannot move |inst| any further. + if (IntersectsPath(bb->MergeBlockIdIfAny(), original_bb->id(), + bbs_with_uses)) { + break; + } + + // Otherwise, |bb_used_in| dominates all uses, so move |inst| into that + // block. + bb = context()->get_instr_block(bb_used_in); + } + continue; + } + return (bb != original_bb ? bb : nullptr); +} + +bool CodeSinkingPass::ReferencesMutableMemory(Instruction* inst) { + if (!inst->IsLoad()) { + return false; + } + + Instruction* base_ptr = inst->GetBaseAddress(); + if (base_ptr->opcode() != SpvOpVariable) { + return true; + } + + if (base_ptr->IsReadOnlyVariable()) { + return false; + } + + if (HasUniformMemorySync()) { + return true; + } + + if (base_ptr->GetSingleWordInOperand(0) != SpvStorageClassUniform) { + return true; + } + + return HasPossibleStore(base_ptr); +} + +bool CodeSinkingPass::HasUniformMemorySync() { + if (checked_for_uniform_sync_) { + return has_uniform_sync_; + } + + bool has_sync = false; + get_module()->ForEachInst([this, &has_sync](Instruction* inst) { + switch (inst->opcode()) { + case SpvOpMemoryBarrier: { + uint32_t mem_semantics_id = inst->GetSingleWordInOperand(1); + if (IsSyncOnUniform(mem_semantics_id)) { + has_sync = true; + } + break; + } + case SpvOpControlBarrier: + case SpvOpAtomicLoad: + case SpvOpAtomicStore: + case SpvOpAtomicExchange: + case SpvOpAtomicIIncrement: + case SpvOpAtomicIDecrement: + case SpvOpAtomicIAdd: + case SpvOpAtomicISub: + case SpvOpAtomicSMin: + case SpvOpAtomicUMin: + case SpvOpAtomicSMax: + case SpvOpAtomicUMax: + case SpvOpAtomicAnd: + case SpvOpAtomicOr: + case SpvOpAtomicXor: + case SpvOpAtomicFlagTestAndSet: + case SpvOpAtomicFlagClear: { + uint32_t mem_semantics_id = inst->GetSingleWordInOperand(2); + if (IsSyncOnUniform(mem_semantics_id)) { + has_sync = true; + } + break; + } + case SpvOpAtomicCompareExchange: + case SpvOpAtomicCompareExchangeWeak: + if (IsSyncOnUniform(inst->GetSingleWordInOperand(2)) || + IsSyncOnUniform(inst->GetSingleWordInOperand(3))) { + has_sync = true; + } + break; + default: + break; + } + }); + has_uniform_sync_ = has_sync; + return has_sync; +} + +bool CodeSinkingPass::IsSyncOnUniform(uint32_t mem_semantics_id) const { + const analysis::Constant* mem_semantics_const = + context()->get_constant_mgr()->FindDeclaredConstant(mem_semantics_id); + assert(mem_semantics_const != nullptr && + "Expecting memory semantics id to be a constant."); + assert(mem_semantics_const->AsIntConstant() && + "Memory semantics should be an integer."); + uint32_t mem_semantics_int = mem_semantics_const->GetU32(); + + // If it does not affect uniform memory, then it is does not apply to uniform + // memory. + if ((mem_semantics_int & SpvMemorySemanticsUniformMemoryMask) == 0) { + return false; + } + + // Check if there is an acquire or release. If so not, this it does not add + // any memory constraints. + return (mem_semantics_int & (SpvMemorySemanticsAcquireMask | + SpvMemorySemanticsAcquireReleaseMask | + SpvMemorySemanticsReleaseMask)) != 0; +} + +bool CodeSinkingPass::HasPossibleStore(Instruction* var_inst) { + assert(var_inst->opcode() == SpvOpVariable || + var_inst->opcode() == SpvOpAccessChain || + var_inst->opcode() == SpvOpPtrAccessChain); + + return get_def_use_mgr()->WhileEachUser(var_inst, [this](Instruction* use) { + switch (use->opcode()) { + case SpvOpStore: + return true; + case SpvOpAccessChain: + case SpvOpPtrAccessChain: + return HasPossibleStore(use); + default: + return false; + } + }); +} + +bool CodeSinkingPass::IntersectsPath(uint32_t start, uint32_t end, + const std::unordered_set& set) { + std::vector worklist; + worklist.push_back(start); + std::unordered_set already_done; + already_done.insert(start); + + while (!worklist.empty()) { + BasicBlock* bb = context()->get_instr_block(worklist.back()); + worklist.pop_back(); + + if (bb->id() == end) { + continue; + } + + if (set.count(bb->id())) { + return true; + } + + bb->ForEachSuccessorLabel([&already_done, &worklist](uint32_t* succ_bb_id) { + if (already_done.insert(*succ_bb_id).second) { + worklist.push_back(*succ_bb_id); + } + }); + } + return false; +} + +// namespace opt + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/code_sink.h b/3rdparty/spirv-tools/source/opt/code_sink.h new file mode 100644 index 000000000..d24df030d --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/code_sink.h @@ -0,0 +1,107 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_OPT_CODE_SINK_H_ +#define SOURCE_OPT_CODE_SINK_H_ + +#include + +#include "source/opt/ir_context.h" +#include "source/opt/module.h" +#include "source/opt/pass.h" + +namespace spvtools { +namespace opt { + +// This pass does code sinking for OpAccessChain and OpLoad on variables in +// uniform storage or in read only memory. Code sinking is a transformation +// where an instruction is moved into a more deeply nested construct. +// +// The goal is to move these instructions as close as possible to their uses +// without having to execute them more often or to replicate the instruction. +// Moving the instruction in this way can lead to shorter live ranges, which can +// lead to less register pressure. It can also cause instructions to be +// executed less often because they could be moved into one path of a selection +// construct. +// +// This optimization can cause register pressure to rise if the operands of the +// instructions go dead after the instructions being moved. That is why we only +// move certain OpLoad and OpAccessChain instructions. They generally have +// constants, loop induction variables, and global pointers as operands. The +// operands are live for a longer time in most cases. +class CodeSinkingPass : public Pass { + public: + const char* name() const override { return "code-sink"; } + Status Process() override; + + // Return the mask of preserved Analyses. + IRContext::Analysis GetPreservedAnalyses() override { + return IRContext::kAnalysisDefUse | + IRContext::kAnalysisInstrToBlockMapping | + IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG | + IRContext::kAnalysisDominatorAnalysis | + IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap | + IRContext::kAnalysisConstants | IRContext::kAnalysisTypes; + } + + private: + // Sinks the instructions in |bb| as much as possible. Returns true if + // something changes. + bool SinkInstructionsInBB(BasicBlock* bb); + + // Tries the sink |inst| as much as possible. Returns true if the instruction + // is moved. + bool SinkInstruction(Instruction* inst); + + // Returns the basic block in which to move |inst| to move is as close as + // possible to the uses of |inst| without increasing the number of times + // |inst| will be executed. Return |nullptr| if there is no need to move + // |inst|. + BasicBlock* FindNewBasicBlockFor(Instruction* inst); + + // Return true if |inst| reference memory and it is possible that the data in + // the memory changes at some point. + bool ReferencesMutableMemory(Instruction* inst); + + // Returns true if the module contains an instruction that has a memory + // semantics id as an operand, and the memory semantics enforces a + // synchronization of uniform memory. See section 3.25 of the SPIR-V + // specification. + bool HasUniformMemorySync(); + + // Returns true if there may be a store to the variable |var_inst|. + bool HasPossibleStore(Instruction* var_inst); + + // Returns true if one of the basic blocks in |set| exists on a path from the + // basic block |start| to |end|. + bool IntersectsPath(uint32_t start, uint32_t end, + const std::unordered_set& set); + + // Returns true if |mem_semantics_id| is the id of a constant that, when + // interpreted as a memory semantics mask enforces synchronization of uniform + // memory. See section 3.25 of the SPIR-V specification. + bool IsSyncOnUniform(uint32_t mem_semantics_id) const; + + // True if a check has for uniform storage has taken place. + bool checked_for_uniform_sync_; + + // Cache of whether or not the module has a memory sync on uniform storage. + // only valid if |check_for_uniform_sync_| is true. + bool has_uniform_sync_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_CODE_SINK_H_ diff --git a/3rdparty/spirv-tools/source/opt/fold.cpp b/3rdparty/spirv-tools/source/opt/fold.cpp index 0604da2a1..944f43870 100644 --- a/3rdparty/spirv-tools/source/opt/fold.cpp +++ b/3rdparty/spirv-tools/source/opt/fold.cpp @@ -45,8 +45,13 @@ namespace { uint32_t InstructionFolder::UnaryOperate(SpvOp opcode, uint32_t operand) const { switch (opcode) { // Arthimetics - case SpvOp::SpvOpSNegate: - return -static_cast(operand); + case SpvOp::SpvOpSNegate: { + int32_t s_operand = static_cast(operand); + if (s_operand == std::numeric_limits::min()) { + return s_operand; + } + return -s_operand; + } case SpvOp::SpvOpNot: return ~operand; case SpvOp::SpvOpLogicalNot: diff --git a/3rdparty/spirv-tools/source/opt/ir_context.cpp b/3rdparty/spirv-tools/source/opt/ir_context.cpp index fe69027f8..a31349f45 100644 --- a/3rdparty/spirv-tools/source/opt/ir_context.cpp +++ b/3rdparty/spirv-tools/source/opt/ir_context.cpp @@ -127,6 +127,9 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) { constant_mgr_.reset(nullptr); } if (analyses_to_invalidate & kAnalysisTypes) { + // The ConstantManager contains Type pointers. If the TypeManager goes + // away, the ConstantManager has to go away. + constant_mgr_.reset(nullptr); type_mgr_.reset(nullptr); } diff --git a/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp b/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp index 5aff34ce7..11f7e9cfa 100644 --- a/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp +++ b/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp @@ -937,22 +937,23 @@ void LoopDescriptor::PostModificationCleanup() { for (Loop* loop : loops_to_remove_) { loops_.erase(std::find(loops_.begin(), loops_.end(), loop)); + delete loop; } for (auto& pair : loops_to_add_) { Loop* parent = pair.first; - Loop* loop = pair.second; + std::unique_ptr loop = std::move(pair.second); if (parent) { loop->SetParent(nullptr); - parent->AddNestedLoop(loop); + parent->AddNestedLoop(loop.get()); for (uint32_t block_id : loop->GetBlocks()) { parent->AddBasicBlock(block_id); } } - loops_.emplace_back(loop); + loops_.emplace_back(loop.release()); } loops_to_add_.clear(); diff --git a/3rdparty/spirv-tools/source/opt/loop_descriptor.h b/3rdparty/spirv-tools/source/opt/loop_descriptor.h index 38f017b5f..6e2b82896 100644 --- a/3rdparty/spirv-tools/source/opt/loop_descriptor.h +++ b/3rdparty/spirv-tools/source/opt/loop_descriptor.h @@ -499,8 +499,8 @@ class LoopDescriptor { // Mark the loop |loop_to_add| as needing to be added when the user calls // PostModificationCleanup. |parent| may be null. - inline void AddLoop(Loop* loop_to_add, Loop* parent) { - loops_to_add_.emplace_back(std::make_pair(parent, loop_to_add)); + inline void AddLoop(std::unique_ptr&& loop_to_add, Loop* parent) { + loops_to_add_.emplace_back(std::make_pair(parent, std::move(loop_to_add))); } // Checks all loops in |this| and will create pre-headers for all loops @@ -537,7 +537,9 @@ class LoopDescriptor { // TODO(dneto): This should be a vector of unique_ptr. But VisualStudio 2013 // is unable to compile it. using LoopContainerType = std::vector; - using LoopsToAddContainerType = std::vector>; + + using LoopsToAddContainerType = + std::vector>>; // Creates loop descriptors for the function |f|. void PopulateList(IRContext* context, const Function* f); diff --git a/3rdparty/spirv-tools/source/opt/loop_unroller.cpp b/3rdparty/spirv-tools/source/opt/loop_unroller.cpp index 0d49d881b..10fac0433 100644 --- a/3rdparty/spirv-tools/source/opt/loop_unroller.cpp +++ b/3rdparty/spirv-tools/source/opt/loop_unroller.cpp @@ -394,12 +394,12 @@ void LoopUnrollerUtilsImpl::PartiallyUnrollResidualFactor(Loop* loop, // This is a naked new due to the VS2013 requirement of not having unique // pointers in vectors, as it will be inserted into a vector with // loop_descriptor.AddLoop. - Loop* new_loop = new Loop(*loop); + std::unique_ptr new_loop = MakeUnique(*loop); // Clear the basic blocks of the new loop. new_loop->ClearBlocks(); - DuplicateLoop(loop, new_loop); + DuplicateLoop(loop, new_loop.get()); // Add the blocks to the function. AddBlocksToFunction(loop->GetMergeBlock()); @@ -414,10 +414,10 @@ void LoopUnrollerUtilsImpl::PartiallyUnrollResidualFactor(Loop* loop, loop_induction_variable_ = state_.new_phi; // Unroll the new loop by the factor with the usual -1 to account for the // existing block iteration. - Unroll(new_loop, factor); + Unroll(new_loop.get(), factor); - LinkLastPhisToStart(new_loop); - AddBlocksToLoop(new_loop); + LinkLastPhisToStart(new_loop.get()); + AddBlocksToLoop(new_loop.get()); // Add the new merge block to the back of the list of blocks to be added. It // needs to be the last block added to maintain dominator order in the binary. @@ -507,7 +507,7 @@ void LoopUnrollerUtilsImpl::PartiallyUnrollResidualFactor(Loop* loop, LoopDescriptor& loop_descriptor = *context_->GetLoopDescriptor(&function_); - loop_descriptor.AddLoop(new_loop, loop->GetParent()); + loop_descriptor.AddLoop(std::move(new_loop), loop->GetParent()); RemoveDeadInstructions(); } diff --git a/3rdparty/spirv-tools/source/opt/optimizer.cpp b/3rdparty/spirv-tools/source/opt/optimizer.cpp index fb58eedb2..30e80d718 100644 --- a/3rdparty/spirv-tools/source/opt/optimizer.cpp +++ b/3rdparty/spirv-tools/source/opt/optimizer.cpp @@ -21,6 +21,7 @@ #include #include +#include "code_sink.h" #include "source/opt/build_module.h" #include "source/opt/log.h" #include "source/opt/pass_manager.h" @@ -181,7 +182,8 @@ Optimizer& Optimizer::RegisterPerformancePasses() { .RegisterPass(CreateRedundancyEliminationPass()) .RegisterPass(CreateDeadBranchElimPass()) .RegisterPass(CreateBlockMergePass()) - .RegisterPass(CreateSimplificationPass()); + .RegisterPass(CreateSimplificationPass()) + .RegisterPass(CreateCodeSinkingPass()); // Currently exposing driver bugs resulting in crashes (#946) // .RegisterPass(CreateCommonUniformElimPass()) } @@ -439,6 +441,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { } } else if (pass_name == "ccp") { RegisterPass(CreateCCPPass()); + } else if (pass_name == "code-sink") { + RegisterPass(CreateCodeSinkingPass()); } else if (pass_name == "O") { RegisterPerformancePasses(); } else if (pass_name == "Os") { @@ -790,4 +794,9 @@ Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set, MakeUnique(desc_set, shader_id)); } +Optimizer::PassToken CreateCodeSinkingPass() { + return MakeUnique( + MakeUnique()); +} + } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/passes.h b/3rdparty/spirv-tools/source/opt/passes.h index b9f30ae57..f7b675e28 100644 --- a/3rdparty/spirv-tools/source/opt/passes.h +++ b/3rdparty/spirv-tools/source/opt/passes.h @@ -21,6 +21,7 @@ #include "source/opt/block_merge_pass.h" #include "source/opt/ccp_pass.h" #include "source/opt/cfg_cleanup_pass.h" +#include "source/opt/code_sink.h" #include "source/opt/combine_access_chains.h" #include "source/opt/common_uniform_elim_pass.h" #include "source/opt/compact_ids_pass.h" diff --git a/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp b/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp index 80ff68a66..2b2397d38 100644 --- a/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp @@ -638,8 +638,8 @@ bool ScalarReplacementPass::CheckUses(const Instruction* inst, switch (user->opcode()) { case SpvOpAccessChain: case SpvOpInBoundsAccessChain: - if (index == 2u) { - uint32_t id = user->GetSingleWordOperand(3u); + if (index == 2u && user->NumInOperands() > 1) { + uint32_t id = user->GetSingleWordInOperand(1u); const Instruction* opInst = get_def_use_mgr()->GetDef(id); if (!IsCompileTimeConstantInst(opInst->opcode())) { ok = false; @@ -783,16 +783,6 @@ ScalarReplacementPass::GetUsedComponents(Instruction* inst) { return false; } } - case SpvOpCopyObject: { - // Follow the copy to see which components are used. - auto t = GetUsedComponents(use); - if (!t) { - result.reset(nullptr); - return false; - } - result->insert(t->begin(), t->end()); - return true; - } default: // We do not know what is happening. Have to assume the worst. result.reset(nullptr); diff --git a/3rdparty/spirv-tools/source/opt/vector_dce.cpp b/3rdparty/spirv-tools/source/opt/vector_dce.cpp index 314d6513f..92532e31a 100644 --- a/3rdparty/spirv-tools/source/opt/vector_dce.cpp +++ b/3rdparty/spirv-tools/source/opt/vector_dce.cpp @@ -66,7 +66,8 @@ void VectorDCE::FindLiveComponents(Function* function, switch (current_inst->opcode()) { case SpvOpCompositeExtract: - MarkExtractUseAsLive(current_inst, live_components, &work_list); + MarkExtractUseAsLive(current_inst, current_item.components, + live_components, &work_list); break; case SpvOpCompositeInsert: MarkInsertUsesAsLive(current_item, live_components, &work_list); @@ -92,6 +93,7 @@ void VectorDCE::FindLiveComponents(Function* function, } void VectorDCE::MarkExtractUseAsLive(const Instruction* current_inst, + const utils::BitVector& live_elements, LiveComponentMap* live_components, std::vector* work_list) { analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); @@ -102,7 +104,11 @@ void VectorDCE::MarkExtractUseAsLive(const Instruction* current_inst, if (HasVectorOrScalarResult(operand_inst)) { WorkListItem new_item; new_item.instruction = operand_inst; - new_item.components.Set(current_inst->GetSingleWordInOperand(1)); + if (current_inst->NumInOperands() < 2) { + new_item.components = live_elements; + } else { + new_item.components.Set(current_inst->GetSingleWordInOperand(1)); + } AddItemToWorkListIfNeeded(new_item, live_components, work_list); } } diff --git a/3rdparty/spirv-tools/source/opt/vector_dce.h b/3rdparty/spirv-tools/source/opt/vector_dce.h index 638b34b6a..4f039c53f 100644 --- a/3rdparty/spirv-tools/source/opt/vector_dce.h +++ b/3rdparty/spirv-tools/source/opt/vector_dce.h @@ -129,6 +129,7 @@ class VectorDCE : public MemPass { // live. If anything becomes live they are added to |work_list| and // |live_components| is updated accordingly. void MarkExtractUseAsLive(const Instruction* current_inst, + const utils::BitVector& live_elements, LiveComponentMap* live_components, std::vector* work_list); diff --git a/3rdparty/spirv-tools/source/val/validate_memory.cpp b/3rdparty/spirv-tools/source/val/validate_memory.cpp index 2b0db56a1..3b104be8d 100644 --- a/3rdparty/spirv-tools/source/val/validate_memory.cpp +++ b/3rdparty/spirv-tools/source/val/validate_memory.cpp @@ -276,6 +276,16 @@ uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask) { return scope_id; } +bool DoesStructContainRTA(const ValidationState_t& _, const Instruction* inst) { + for (size_t member_index = 1; member_index < inst->operands().size(); + ++member_index) { + const auto member_id = inst->GetOperandAs(member_index); + const auto member_type = _.FindDef(member_id); + if (member_type->opcode() == SpvOpTypeRuntimeArray) return true; + } + return false; +} + spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, uint32_t index) { SpvStorageClass dst_sc, src_sc; @@ -283,9 +293,9 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, if (inst->operands().size() <= index) { if (src_sc == SpvStorageClassPhysicalStorageBufferEXT || dst_sc == SpvStorageClassPhysicalStorageBufferEXT) { - return _.diag(SPV_ERROR_INVALID_ID, inst) << "Memory accesses with " - "PhysicalStorageBufferEXT " - "must use Aligned."; + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Memory accesses with PhysicalStorageBufferEXT must use " + "Aligned."; } return SPV_SUCCESS; } @@ -318,7 +328,7 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, if (!(mask & SpvMemoryAccessNonPrivatePointerKHRMask)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "NonPrivatePointerKHR must be specified if " - "MakePointerVisibleKHR is specified."; + << "MakePointerVisibleKHR is specified."; } // Check the associated scope for MakeVisibleKHR. @@ -335,8 +345,8 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, dst_sc != SpvStorageClassPhysicalStorageBufferEXT) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "NonPrivatePointerKHR requires a pointer in Uniform, " - "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer " - "storage classes."; + << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer " + << "storage classes."; } if (src_sc != SpvStorageClassMax && src_sc != SpvStorageClassUniform && src_sc != SpvStorageClassWorkgroup && @@ -346,17 +356,17 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, src_sc != SpvStorageClassPhysicalStorageBufferEXT) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "NonPrivatePointerKHR requires a pointer in Uniform, " - "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer " - "storage classes."; + << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer " + << "storage classes."; } } if (!(mask & SpvMemoryAccessAlignedMask)) { if (src_sc == SpvStorageClassPhysicalStorageBufferEXT || dst_sc == SpvStorageClassPhysicalStorageBufferEXT) { - return _.diag(SPV_ERROR_INVALID_ID, inst) << "Memory accesses with " - "PhysicalStorageBufferEXT " - "must use Aligned."; + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Memory accesses with PhysicalStorageBufferEXT must use " + "Aligned."; } } @@ -442,15 +452,15 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { // VariablePointersStorageBuffer is implied by VariablePointers. if (pointee->opcode() == SpvOpTypePointer) { if (!_.HasCapability(SpvCapabilityVariablePointersStorageBuffer)) { - return _.diag(SPV_ERROR_INVALID_ID, inst) << "In Logical addressing, " - "variables may not " - "allocate a pointer type"; + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "In Logical addressing, variables may not allocate a pointer " + << "type"; } else if (storage_class != SpvStorageClassFunction && storage_class != SpvStorageClassPrivate) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "In Logical addressing with variable pointers, variables " - "that allocate pointers must be in Function or Private " - "storage classes"; + << "that allocate pointers must be in Function or Private " + << "storage classes"; } } } @@ -493,10 +503,9 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { << "' has illegal type.\n" << "From Vulkan spec, section 14.5.2:\n" << "Variables identified with the Uniform storage class are " - "used " - << "to access transparent buffer backed resources. Such " - "variables " - << "must be typed as OpTypeStruct, or an array of this type"; + << "used to access transparent buffer backed resources. Such " + << "variables must be typed as OpTypeStruct, or an array of " + << "this type"; } } } @@ -564,13 +573,13 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpVariable " << inst->id() << ": expected AliasedPointerEXT or RestrictPointerEXT for " - "PhysicalStorageBufferEXT pointer."; + << "PhysicalStorageBufferEXT pointer."; } if (foundAliased && foundRestrict) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpVariable " << inst->id() << ": can't specify both AliasedPointerEXT and " - "RestrictPointerEXT for PhysicalStorageBufferEXT pointer."; + << "RestrictPointerEXT for PhysicalStorageBufferEXT pointer."; } } } @@ -588,9 +597,9 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpVariable, '" << _.getIdName(inst->id()) << "', is attempting to create memory for an illegal type, " - "OpTypeRuntimeArray.\nFor Vulkan OpTypeRuntimeArray can only " - "appear as the final member of an OpTypeStruct, thus cannot " - "be instantiated via OpVariable"; + << "OpTypeRuntimeArray.\nFor Vulkan OpTypeRuntimeArray can only " + << "appear as the final member of an OpTypeStruct, thus cannot " + << "be instantiated via OpVariable"; } else { // A bare variable OpTypeRuntimeArray is allowed in this context, but // still need to check the storage class. @@ -599,8 +608,8 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { storage_class != SpvStorageClassUniformConstant) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "For Vulkan with RuntimeDescriptorArrayEXT, a variable " - "containing OpTypeRuntimeArray must have storage class of " - "StorageBuffer, Uniform, or UniformConstant."; + << "containing OpTypeRuntimeArray must have storage class of " + << "StorageBuffer, Uniform, or UniformConstant."; } } } @@ -610,39 +619,64 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { // with Block, or it must be in the Uniform storage class and be decorated // as BufferBlock. if (value_type && value_type->opcode() == SpvOpTypeStruct) { - bool contains_RTA = false; - for (size_t member_type_index = 1; - member_type_index < value_type->operands().size(); - ++member_type_index) { - const auto member_type_id = - value_type->GetOperandAs(member_type_index); - const auto member_type = _.FindDef(member_type_id); - if (member_type->opcode() == SpvOpTypeRuntimeArray) { - contains_RTA = true; - break; - } - } - - if (contains_RTA) { + if (DoesStructContainRTA(_, value_type)) { if (storage_class == SpvStorageClassStorageBuffer) { if (!_.HasDecoration(value_id, SpvDecorationBlock)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "For Vulkan, an OpTypeStruct variable containing an " - "OpTypeRuntimeArray must be decorated with Block if it " - "has storage class StorageBuffer."; + << "OpTypeRuntimeArray must be decorated with Block if it " + << "has storage class StorageBuffer."; } } else if (storage_class == SpvStorageClassUniform) { if (!_.HasDecoration(value_id, SpvDecorationBufferBlock)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "For Vulkan, an OpTypeStruct variable containing an " - "OpTypeRuntimeArray must be decorated with BufferBlock " - "if it has storage class Uniform."; + << "OpTypeRuntimeArray must be decorated with BufferBlock " + << "if it has storage class Uniform."; } } else { return _.diag(SPV_ERROR_INVALID_ID, inst) << "For Vulkan, OpTypeStruct variables containing " - "OpTypeRuntimeArray must have storage class of " - "StorageBuffer or Uniform."; + << "OpTypeRuntimeArray must have storage class of " + << "StorageBuffer or Uniform."; + } + } + } + } + + // WebGPU specific validation rules for OpTypeRuntimeArray + if (spvIsWebGPUEnv(_.context()->target_env)) { + const auto type_index = 2; + const auto value_id = result_type->GetOperandAs(type_index); + auto value_type = _.FindDef(value_id); + // OpTypeRuntimeArray should only ever be in an OpTypeStruct, + // so should never appear as a bare variable. + if (value_type && value_type->opcode() == SpvOpTypeRuntimeArray) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "OpVariable, '" << _.getIdName(inst->id()) + << "', is attempting to create memory for an illegal type, " + << "OpTypeRuntimeArray.\nFor WebGPU OpTypeRuntimeArray can only " + << "appear as the final member of an OpTypeStruct, thus cannot " + << "be instantiated via OpVariable"; + } + + // If an OpStruct has an OpTypeRuntimeArray somewhere within it, then it + // must have the storage class StorageBuffer and be decorated + // with Block. + if (value_type && value_type->opcode() == SpvOpTypeStruct) { + if (DoesStructContainRTA(_, value_type)) { + if (storage_class == SpvStorageClassStorageBuffer) { + if (!_.HasDecoration(value_id, SpvDecorationBlock)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "For WebGPU, an OpTypeStruct variable containing an " + << "OpTypeRuntimeArray must be decorated with Block if it " + << "has storage class StorageBuffer."; + } + } else { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "For WebGPU, OpTypeStruct variables containing " + << "OpTypeRuntimeArray must have storage class of " + << "StorageBuffer"; } } } diff --git a/3rdparty/spirv-tools/source/val/validate_scopes.cpp b/3rdparty/spirv-tools/source/val/validate_scopes.cpp index 3ba8f3bf9..b6401310d 100644 --- a/3rdparty/spirv-tools/source/val/validate_scopes.cpp +++ b/3rdparty/spirv-tools/source/val/validate_scopes.cpp @@ -37,9 +37,9 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _, if (!is_const_int32) { if (_.HasCapability(SpvCapabilityShader)) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Scope ids must be " - "OpConstant when Shader " - "capability is present"; + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Scope ids must be OpConstant when Shader capability is " + << "present"; } return SPV_SUCCESS; } @@ -54,7 +54,7 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _, return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) << ": in Vulkan environment Execution scope is limited to " - "Subgroup"; + << "Subgroup"; } } @@ -86,7 +86,7 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _, return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) << ": in Vulkan environment Execution Scope is limited to " - "Workgroup and Subgroup"; + << "Workgroup and Subgroup"; } } @@ -97,7 +97,7 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _, return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) << ": in WebGPU environment Execution Scope is limited to " - "Workgroup and Subgroup"; + << "Workgroup and Subgroup"; } } @@ -131,9 +131,9 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst, if (!is_const_int32) { if (_.HasCapability(SpvCapabilityShader)) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Scope ids must be " - "OpConstant when Shader " - "capability is present"; + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Scope ids must be OpConstant when Shader capability is " + << "present"; } return SPV_SUCCESS; } @@ -145,7 +145,7 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst, return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) << ": Memory Scope QueueFamilyKHR requires capability " - "VulkanMemoryModelKHR"; + << "VulkanMemoryModelKHR"; } } @@ -154,7 +154,7 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst, !_.HasCapability(SpvCapabilityVulkanMemoryModelDeviceScopeKHR)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Use of device scope with VulkanKHR memory model requires the " - "VulkanMemoryModelDeviceScopeKHR capability"; + << "VulkanMemoryModelDeviceScopeKHR capability"; } // Vulkan Specific rules @@ -171,8 +171,7 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst, return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) << ": in Vulkan 1.0 environment Memory Scope is limited to " - "Device, " - "Workgroup and Invocation"; + << "Device, Workgroup and Invocation"; } // Vulkan 1.1 specifc rules if (_.context()->target_env == SPV_ENV_VULKAN_1_1 && @@ -181,8 +180,18 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst, return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) << ": in Vulkan 1.1 environment Memory Scope is limited to " - "Device, " - "Workgroup and Invocation"; + << "Device, Workgroup and Invocation"; + } + } + + // WebGPU specific rules + if (spvIsWebGPUEnv(_.context()->target_env)) { + if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup && + value != SpvScopeQueueFamilyKHR) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << spvOpcodeString(opcode) + << ": in WebGPU environment Memory Scope is limited to " + << "Workgroup, Subgroup and QueuFamilyKHR"; } } diff --git a/3rdparty/spirv-tools/source/val/validate_type.cpp b/3rdparty/spirv-tools/source/val/validate_type.cpp index 6dbca3946..94ea66034 100644 --- a/3rdparty/spirv-tools/source/val/validate_type.cpp +++ b/3rdparty/spirv-tools/source/val/validate_type.cpp @@ -107,11 +107,14 @@ spv_result_t ValidateTypeArray(ValidationState_t& _, const Instruction* inst) { << "' is a void type."; } - if (spvIsVulkanEnv(_.context()->target_env) && + if ((spvIsVulkanEnv(_.context()->target_env) || + spvIsWebGPUEnv(_.context()->target_env)) && element_type->opcode() == SpvOpTypeRuntimeArray) { + const char* env_text = + spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU"; return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeArray Element Type '" << _.getIdName(element_type_id) - << "' is not valid in Vulkan environment."; + << "' is not valid in " << env_text << " environment."; } const auto length_index = 2; @@ -169,12 +172,15 @@ spv_result_t ValidateTypeRuntimeArray(ValidationState_t& _, << _.getIdName(element_id) << "' is a void type."; } - if (spvIsVulkanEnv(_.context()->target_env) && + if ((spvIsVulkanEnv(_.context()->target_env) || + spvIsWebGPUEnv(_.context()->target_env)) && element_type->opcode() == SpvOpTypeRuntimeArray) { + const char* env_text = + spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU"; return _.diag(SPV_ERROR_INVALID_ID, inst) << "OpTypeRuntimeArray Element Type '" - << _.getIdName(element_id) - << "' is not valid in Vulkan environment."; + << _.getIdName(element_id) << "' is not valid in " << env_text + << " environment."; } return SPV_SUCCESS; @@ -200,11 +206,11 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Structure " << _.getIdName(member_type_id) << " contains members with BuiltIn decoration. Therefore this " - "structure may not be contained as a member of another " - "structure " - "type. Structure " - << _.getIdName(struct_id) << " contains structure " - << _.getIdName(member_type_id) << "."; + << "structure may not be contained as a member of another " + << "structure " + << "type. Structure " << _.getIdName(struct_id) + << " contains structure " << _.getIdName(member_type_id) + << "."; } if (_.IsForwardPointer(member_type_id)) { // If we're dealing with a forward pointer: @@ -223,14 +229,17 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) { } } - if (spvIsVulkanEnv(_.context()->target_env) && + if ((spvIsVulkanEnv(_.context()->target_env) || + spvIsWebGPUEnv(_.context()->target_env)) && member_type->opcode() == SpvOpTypeRuntimeArray) { const bool is_last_member = member_type_index == inst->operands().size() - 1; if (!is_last_member) { + const char* env_text = + spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU"; return _.diag(SPV_ERROR_INVALID_ID, inst) - << "In Vulkan, OpTypeRuntimeArray must only be used for the " - "last member of an OpTypeStruct"; + << "In " << env_text << ", OpTypeRuntimeArray must only be used " + << "for the last member of an OpTypeStruct"; } } } @@ -247,9 +256,9 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) { if (num_builtin_members > 0 && num_builtin_members != num_struct_members) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "When BuiltIn decoration is applied to a structure-type member, " - "all members of that structure type must also be decorated with " - "BuiltIn (No allowed mixing of built-in variables and " - "non-built-in variables within a single structure). Structure id " + << "all members of that structure type must also be decorated with " + << "BuiltIn (No allowed mixing of built-in variables and " + << "non-built-in variables within a single structure). Structure id " << struct_id << " does not meet this requirement."; } if (num_builtin_members > 0) { @@ -332,7 +341,7 @@ spv_result_t ValidateTypeForwardPointer(ValidationState_t& _, pointer_type_inst->GetOperandAs(1)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Storage class in OpTypeForwardPointer does not match the " - "pointer definition."; + << "pointer definition."; } return SPV_SUCCESS; diff --git a/3rdparty/spirv-tools/test/opt/CMakeLists.txt b/3rdparty/spirv-tools/test/opt/CMakeLists.txt index 827f5011d..631538515 100644 --- a/3rdparty/spirv-tools/test/opt/CMakeLists.txt +++ b/3rdparty/spirv-tools/test/opt/CMakeLists.txt @@ -21,6 +21,7 @@ add_spvtools_unittest(TARGET opt block_merge_test.cpp ccp_test.cpp cfg_cleanup_test.cpp + code_sink_test.cpp combine_access_chains_test.cpp common_uniform_elim_test.cpp compact_ids_test.cpp diff --git a/3rdparty/spirv-tools/test/opt/code_sink_test.cpp b/3rdparty/spirv-tools/test/opt/code_sink_test.cpp new file mode 100644 index 000000000..9b86c660a --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/code_sink_test.cpp @@ -0,0 +1,533 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "gmock/gmock.h" +#include "test/opt/assembly_builder.h" +#include "test/opt/pass_fixture.h" +#include "test/opt/pass_utils.h" + +namespace spvtools { +namespace opt { +namespace { + +using CodeSinkTest = PassTest<::testing::Test>; + +TEST_F(CodeSinkTest, MoveToNextBlock) { + const std::string text = R"( +;CHECK: OpFunction +;CHECK: OpLabel +;CHECK: OpLabel +;CHECK: [[ac:%\w+]] = OpAccessChain +;CHECK: [[ld:%\w+]] = OpLoad %uint [[ac]] +;CHECK: OpCopyObject %uint [[ld]] + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + %void = OpTypeVoid + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%_arr_uint_uint_4 = OpTypeArray %uint %uint_4 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %9 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %10 = OpTypeFunction %void + %1 = OpFunction %void None %10 + %11 = OpLabel + %12 = OpAccessChain %_ptr_Uniform_uint %9 %uint_0 + %13 = OpLoad %uint %12 + OpBranch %14 + %14 = OpLabel + %15 = OpCopyObject %uint %13 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(CodeSinkTest, MovePastSelection) { + const std::string text = R"( +;CHECK: OpFunction +;CHECK: OpLabel +;CHECK: OpSelectionMerge [[merge_bb:%\w+]] +;CHECK: [[merge_bb]] = OpLabel +;CHECK: [[ac:%\w+]] = OpAccessChain +;CHECK: [[ld:%\w+]] = OpLoad %uint [[ac]] +;CHECK: OpCopyObject %uint [[ld]] + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%_arr_uint_uint_4 = OpTypeArray %uint %uint_4 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + OpSelectionMerge %16 None + OpBranchConditional %true %17 %16 + %17 = OpLabel + OpBranch %16 + %16 = OpLabel + %18 = OpCopyObject %uint %15 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(CodeSinkTest, MoveIntoSelection) { + const std::string text = R"( +;CHECK: OpFunction +;CHECK: OpLabel +;CHECK: OpSelectionMerge [[merge_bb:%\w+]] +;CHECK-NEXT: OpBranchConditional %true [[bb:%\w+]] [[merge_bb]] +;CHECK: [[bb]] = OpLabel +;CHECK-NEXT: [[ac:%\w+]] = OpAccessChain +;CHECK-NEXT: [[ld:%\w+]] = OpLoad %uint [[ac]] +;CHECK-NEXT: OpCopyObject %uint [[ld]] + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%_arr_uint_uint_4 = OpTypeArray %uint %uint_4 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + OpSelectionMerge %16 None + OpBranchConditional %true %17 %16 + %17 = OpLabel + %18 = OpCopyObject %uint %15 + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(CodeSinkTest, LeaveBeforeSelection) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%_arr_uint_uint_4 = OpTypeArray %uint %uint_4 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + OpSelectionMerge %16 None + OpBranchConditional %true %17 %20 + %20 = OpLabel + OpBranch %16 + %17 = OpLabel + %18 = OpCopyObject %uint %15 + OpBranch %16 + %16 = OpLabel + %19 = OpCopyObject %uint %15 + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CodeSinkTest, LeaveAloneUseInSameBlock) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%_arr_uint_uint_4 = OpTypeArray %uint %uint_4 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + %cond = OpIEqual %bool %15 %uint_0 + OpSelectionMerge %16 None + OpBranchConditional %cond %17 %16 + %17 = OpLabel + OpBranch %16 + %16 = OpLabel + %19 = OpCopyObject %uint %15 + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CodeSinkTest, DontMoveIntoLoop) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%_arr_uint_uint_4 = OpTypeArray %uint %uint_4 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + OpBranch %17 + %17 = OpLabel + OpLoopMerge %merge %cont None + OpBranch %cont + %cont = OpLabel + %cond = OpIEqual %bool %15 %uint_0 + OpBranchConditional %cond %merge %17 + %merge = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CodeSinkTest, DontMoveIntoLoop2) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%_arr_uint_uint_4 = OpTypeArray %uint %uint_4 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + OpSelectionMerge %16 None + OpBranchConditional %true %17 %16 + %17 = OpLabel + OpLoopMerge %merge %cont None + OpBranch %cont + %cont = OpLabel + %cond = OpIEqual %bool %15 %uint_0 + OpBranchConditional %cond %merge %17 + %merge = OpLabel + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CodeSinkTest, DontMoveSelectionUsedInBothSides) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%_arr_uint_uint_4 = OpTypeArray %uint %uint_4 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + OpSelectionMerge %16 None + OpBranchConditional %true %17 %20 + %20 = OpLabel + %19 = OpCopyObject %uint %15 + OpBranch %16 + %17 = OpLabel + %18 = OpCopyObject %uint %15 + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CodeSinkTest, DontMoveBecauseOfStore) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%_arr_uint_uint_4 = OpTypeArray %uint %uint_4 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + OpStore %14 %15 + OpSelectionMerge %16 None + OpBranchConditional %true %17 %20 + %20 = OpLabel + OpBranch %16 + %17 = OpLabel + %18 = OpCopyObject %uint %15 + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CodeSinkTest, MoveReadOnlyLoadWithSync) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire +%_arr_uint_uint_4 = OpTypeArray %uint %uint_4 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + OpMemoryBarrier %uint_4 %mem_semantics + OpSelectionMerge %16 None + OpBranchConditional %true %17 %20 + %20 = OpLabel + OpBranch %16 + %17 = OpLabel + %18 = OpCopyObject %uint %15 + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result)); +} + +TEST_F(CodeSinkTest, DontMoveBecauseOfSync) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + OpDecorate %_arr_uint_uint_4 BufferBlock + OpMemberDecorate %_arr_uint_uint_4 0 Offset 0 + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire +%_arr_uint_uint_4 = OpTypeStruct %uint +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + OpMemoryBarrier %uint_4 %mem_semantics + OpSelectionMerge %16 None + OpBranchConditional %true %17 %20 + %20 = OpLabel + OpBranch %16 + %17 = OpLabel + %18 = OpCopyObject %uint %15 + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CodeSinkTest, DontMoveBecauseOfAtomicWithSync) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + OpDecorate %_arr_uint_uint_4 BufferBlock + OpMemberDecorate %_arr_uint_uint_4 0 Offset 0 + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire +%_arr_uint_uint_4 = OpTypeStruct %uint +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + %al = OpAtomicLoad %uint %14 %uint_4 %mem_semantics + OpSelectionMerge %16 None + OpBranchConditional %true %17 %20 + %20 = OpLabel + OpBranch %16 + %17 = OpLabel + %18 = OpCopyObject %uint %15 + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CodeSinkTest, MoveWithAtomicWithoutSync) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "main" + OpDecorate %_arr_uint_uint_4 BufferBlock + OpMemberDecorate %_arr_uint_uint_4 0 Offset 0 + %void = OpTypeVoid + %bool = OpTypeBool + %true = OpConstantTrue %bool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_4 = OpConstant %uint 4 +%_arr_uint_uint_4 = OpTypeStruct %uint +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4 + %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform + %12 = OpTypeFunction %void + %1 = OpFunction %void None %12 + %13 = OpLabel + %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0 + %15 = OpLoad %uint %14 + %al = OpAtomicLoad %uint %14 %uint_4 %uint_0 + OpSelectionMerge %16 None + OpBranchConditional %true %17 %20 + %20 = OpLabel + OpBranch %16 + %17 = OpLabel + %18 = OpCopyObject %uint %15 + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result)); +} + +} // namespace +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/fold_test.cpp b/3rdparty/spirv-tools/test/opt/fold_test.cpp index 88248ba79..487c18a83 100644 --- a/3rdparty/spirv-tools/test/opt/fold_test.cpp +++ b/3rdparty/spirv-tools/test/opt/fold_test.cpp @@ -538,7 +538,15 @@ INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTest, "%2 = OpShiftLeftLogical %int %int_2 %uint_32\n" + "OpReturn\n" + "OpFunctionEnd", - 2, 0) + 2, 0), + // Test case 29: fold -INT_MIN + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpSNegate %int %int_min\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, std::numeric_limits::min()) )); // clang-format on diff --git a/3rdparty/spirv-tools/test/opt/pass_manager_test.cpp b/3rdparty/spirv-tools/test/opt/pass_manager_test.cpp index c7273e9c1..22d5e22ec 100644 --- a/3rdparty/spirv-tools/test/opt/pass_manager_test.cpp +++ b/3rdparty/spirv-tools/test/opt/pass_manager_test.cpp @@ -107,8 +107,7 @@ class DuplicateInstPass : public Pass { public: const char* name() const override { return "DuplicateInst"; } Status Process() override { - auto inst = - MakeUnique(*(--context()->debug1_end())->Clone(context())); + auto inst = MakeUnique(*(--context()->debug1_end())); context()->AddDebug1Inst(std::move(inst)); return Status::SuccessWithChange; } @@ -121,21 +120,21 @@ TEST_F(PassManagerTest, Run) { AddPass(); AddPass(); - RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\n").c_str()); + RunAndCheck(text, text + "OpNop\nOpNop\n"); RenewPassManger(); AddPass(); AddPass(); - RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\n").c_str()); + RunAndCheck(text, text + "OpNop\nOpNop\n"); RenewPassManger(); AddPass(); AddPass(); - RunAndCheck(text.c_str(), (text + "OpSource ESSL 310\nOpNop\n").c_str()); + RunAndCheck(text, text + "OpSource ESSL 310\nOpNop\n"); RenewPassManger(); AddPass(3); - RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\nOpNop\n").c_str()); + RunAndCheck(text, text + "OpNop\nOpNop\nOpNop\n"); } // A pass that appends an OpTypeVoid instruction that uses a given id. diff --git a/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp b/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp index 67c97a595..a53f09dfd 100644 --- a/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp +++ b/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp @@ -1593,6 +1593,33 @@ TEST_F(ScalarReplacementTest, AmbigousPointer) { SinglePassRunAndMatch(text, true); } +// Test that scalar replacement does not crash when there is an OpAccessChain +// with no index. If we choose to handle this case in the future, then the +// result can change. +TEST_F(ScalarReplacementTest, TestAccessChainWithNoIndexes) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %1 "main" + OpExecutionMode %1 OriginLowerLeft + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %_struct_5 = OpTypeStruct %float +%_ptr_Function__struct_5 = OpTypePointer Function %_struct_5 + %1 = OpFunction %void None %3 + %7 = OpLabel + %8 = OpVariable %_ptr_Function__struct_5 Function + %9 = OpAccessChain %_ptr_Function__struct_5 %8 + OpReturn + OpFunctionEnd + )"; + + auto result = + SinglePassRunAndDisassemble(text, true, false); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/vector_dce_test.cpp b/3rdparty/spirv-tools/test/opt/vector_dce_test.cpp index 40f115c34..a978a07f9 100644 --- a/3rdparty/spirv-tools/test/opt/vector_dce_test.cpp +++ b/3rdparty/spirv-tools/test/opt/vector_dce_test.cpp @@ -1190,6 +1190,42 @@ TEST_F(VectorDCETest, InsertWithNoIndices) { SinglePassRunAndMatch(text, true); } +TEST_F(VectorDCETest, ExtractWithNoIndices) { + const std::string text = R"( +; CHECK: OpLoad %float +; CHECK: [[ld:%\w+]] = OpLoad %v4float +; CHECK: [[ex1:%\w+]] = OpCompositeExtract %v4float [[ld]] +; CHECK: [[ex2:%\w+]] = OpCompositeExtract %float [[ex1]] 1 +; CHECK: OpStore {{%\w+}} [[ex2]] + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %1 "PSMain" %2 %14 %3 + OpExecutionMode %1 OriginUpperLeft + %void = OpTypeVoid + %5 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Input_float = OpTypePointer Input %float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_float = OpTypePointer Output %float + %2 = OpVariable %_ptr_Input_v4float Input + %14 = OpVariable %_ptr_Input_float Input + %3 = OpVariable %_ptr_Output_float Output + %1 = OpFunction %void None %5 + %10 = OpLabel + %13 = OpLoad %float %14 + %11 = OpLoad %v4float %2 + %12 = OpCompositeInsert %v4float %13 %11 0 + %20 = OpCompositeExtract %v4float %12 + %21 = OpCompositeExtract %float %20 1 + OpStore %3 %21 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_atomics_test.cpp b/3rdparty/spirv-tools/test/val/val_atomics_test.cpp index 03bbe82f9..1f3001886 100644 --- a/3rdparty/spirv-tools/test/val/val_atomics_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_atomics_test.cpp @@ -340,7 +340,7 @@ TEST_F(ValidateAtomics, AtomicLoadVulkanInt64) { TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSuccess) { const std::string body = R"( -%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed +%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %relaxed %val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire )"; @@ -544,7 +544,7 @@ OpAtomicStore %u32_var %device %sequentially_consistent %u32_1 TEST_F(ValidateAtomics, AtomicStoreWebGPUSuccess) { const std::string body = R"( -OpAtomicStore %u32_var %device %release %u32_1 +OpAtomicStore %u32_var %queuefamily %release %u32_1 )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); @@ -553,7 +553,7 @@ OpAtomicStore %u32_var %device %release %u32_1 TEST_F(ValidateAtomics, AtomicStoreWebGPUSequentiallyConsistent) { const std::string body = R"( -OpAtomicStore %u32_var %device %sequentially_consistent %u32_1 +OpAtomicStore %u32_var %queuefamily %sequentially_consistent %u32_1 )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); @@ -564,7 +564,8 @@ OpAtomicStore %u32_var %device %sequentially_consistent %u32_1 "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")); + "MakeVisibleKHR\n" + " OpAtomicStore %29 %uint_5 %uint_16 %uint_1\n")); } TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) { @@ -1858,6 +1859,75 @@ OpExtension "SPV_KHR_vulkan_memory_model" EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); } +TEST_F(ValidateAtomics, WebGPUCrossDeviceMemoryScopeBad) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %cross_device %relaxed +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to " + "Workgroup, Subgroup and QueuFamilyKHR\n" + " %34 = OpAtomicLoad %uint %29 %uint_0_0 %uint_0_1\n")); +} + +TEST_F(ValidateAtomics, WebGPUDeviceMemoryScopeBad) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to " + "Workgroup, Subgroup and QueuFamilyKHR\n" + " %34 = OpAtomicLoad %uint %29 %uint_1_0 %uint_0_1\n")); +} + +TEST_F(ValidateAtomics, WebGPUWorkgroupMemoryScopeGood) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %workgroup %relaxed +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); +} + +TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeGood) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %subgroup %relaxed +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); +} + +TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeBad) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %invocation %relaxed +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to " + "Workgroup, Subgroup and QueuFamilyKHR\n" + " %34 = OpAtomicLoad %uint %29 %uint_4 %uint_0_1\n")); +} + +TEST_F(ValidateAtomics, WebGPUQueueFamilyMemoryScopeGood) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %relaxed +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_data_test.cpp b/3rdparty/spirv-tools/test/val/val_data_test.cpp index 48a484612..0aded92b5 100644 --- a/3rdparty/spirv-tools/test/val/val_data_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_data_test.cpp @@ -872,6 +872,68 @@ TEST_F(ValidateData, vulkan_RTA_not_at_end_of_struct) { "OpTypeStruct %_runtimearr_uint %uint\n")); } +TEST_F(ValidateData, webgpu_RTA_array_at_end_of_struct) { + std::string str = R"( + OpCapability Shader + OpCapability VulkanMemoryModelKHR + OpExtension "SPV_KHR_vulkan_memory_model" + OpMemoryModel Logical VulkanKHR + OpEntryPoint Fragment %func "func" + OpExecutionMode %func OriginUpperLeft + OpDecorate %array_t ArrayStride 4 + OpMemberDecorate %struct_t 0 Offset 0 + OpMemberDecorate %struct_t 1 Offset 4 + OpDecorate %struct_t Block + %uint_t = OpTypeInt 32 0 + %array_t = OpTypeRuntimeArray %uint_t + %struct_t = OpTypeStruct %uint_t %array_t +%struct_ptr = OpTypePointer StorageBuffer %struct_t + %2 = OpVariable %struct_ptr StorageBuffer + %void = OpTypeVoid + %func_t = OpTypeFunction %void + %func = OpFunction %void None %func_t + %1 = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); +} + +TEST_F(ValidateData, webgpu_RTA_not_at_end_of_struct) { + std::string str = R"( + OpCapability Shader + OpCapability VulkanMemoryModelKHR + OpExtension "SPV_KHR_vulkan_memory_model" + OpMemoryModel Logical VulkanKHR + OpEntryPoint Fragment %func "func" + OpExecutionMode %func OriginUpperLeft + OpDecorate %array_t ArrayStride 4 + OpMemberDecorate %struct_t 0 Offset 0 + OpMemberDecorate %struct_t 1 Offset 4 + OpDecorate %struct_t Block + %uint_t = OpTypeInt 32 0 + %array_t = OpTypeRuntimeArray %uint_t + %struct_t = OpTypeStruct %array_t %uint_t +%struct_ptr = OpTypePointer StorageBuffer %struct_t + %2 = OpVariable %struct_ptr StorageBuffer + %void = OpTypeVoid + %func_t = OpTypeFunction %void + %func = OpFunction %void None %func_t + %1 = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("In WebGPU, OpTypeRuntimeArray must only be used for " + "the last member of an OpTypeStruct\n %_struct_3 = " + "OpTypeStruct %_runtimearr_uint %uint\n")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_memory_test.cpp b/3rdparty/spirv-tools/test/val/val_memory_test.cpp index 8ad1db46d..097477c31 100644 --- a/3rdparty/spirv-tools/test/val/val_memory_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_memory_test.cpp @@ -1806,6 +1806,38 @@ OpFunctionEnd "%_ptr_UniformConstant__runtimearr_2 UniformConstant\n")); } +TEST_F(ValidateMemory, WebGPURTAOutsideOfStructBad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%sampler_t = OpTypeSampler +%array_t = OpTypeRuntimeArray %sampler_t +%array_ptr = OpTypePointer UniformConstant %array_t +%2 = OpVariable %array_ptr UniformConstant +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpVariable, '5[%5]', is attempting to create memory for an " + "illegal type, OpTypeRuntimeArray.\nFor WebGPU OpTypeRuntimeArray " + "can only appear as the final member of an OpTypeStruct, thus cannot " + "be instantiated via OpVariable\n %5 = OpVariable " + "%_ptr_UniformConstant__runtimearr_2 UniformConstant\n")); +} + TEST_F(ValidateMemory, VulkanRTAOutsideOfStructWithRuntimeDescriptorArrayGood) { std::string spirv = R"( OpCapability Shader @@ -1890,6 +1922,34 @@ OpFunctionEnd EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); } +TEST_F(ValidateMemory, WebGPURTAInsideStorageBufferStructGood) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +OpDecorate %array_t ArrayStride 4 +OpMemberDecorate %struct_t 0 Offset 0 +OpDecorate %struct_t Block +%uint_t = OpTypeInt 32 0 +%array_t = OpTypeRuntimeArray %uint_t +%struct_t = OpTypeStruct %array_t +%struct_ptr = OpTypePointer StorageBuffer %struct_t +%2 = OpVariable %struct_ptr StorageBuffer +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); +} + TEST_F(ValidateMemory, VulkanRTAInsideWrongStorageClassStructBad) { std::string spirv = R"( OpCapability Shader @@ -1919,6 +1979,36 @@ OpFunctionEnd "OpVariable %_ptr_Workgroup__struct_4 Workgroup\n")); } +TEST_F(ValidateMemory, WebGPURTAInsideWrongStorageClassStructBad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%uint_t = OpTypeInt 32 0 +%array_t = OpTypeRuntimeArray %uint_t +%struct_t = OpTypeStruct %array_t +%struct_ptr = OpTypePointer Workgroup %struct_t +%2 = OpVariable %struct_ptr Workgroup +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("For WebGPU, OpTypeStruct variables containing " + "OpTypeRuntimeArray must have storage class of StorageBuffer\n " + " %6 = OpVariable %_ptr_Workgroup__struct_4 Workgroup\n")); +} + TEST_F(ValidateMemory, VulkanRTAInsideStorageBufferStructWithoutBlockBad) { std::string spirv = R"( OpCapability Shader @@ -1947,6 +2037,36 @@ OpFunctionEnd "%_ptr_StorageBuffer__struct_4 StorageBuffer\n")); } +TEST_F(ValidateMemory, WebGPURTAInsideStorageBufferStructWithoutBlockBad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%uint_t = OpTypeInt 32 0 +%array_t = OpTypeRuntimeArray %uint_t +%struct_t = OpTypeStruct %array_t +%struct_ptr = OpTypePointer StorageBuffer %struct_t +%2 = OpVariable %struct_ptr StorageBuffer +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("For WebGPU, an OpTypeStruct variable containing an " + "OpTypeRuntimeArray must be decorated with Block if it " + "has storage class StorageBuffer.\n %6 = OpVariable " + "%_ptr_StorageBuffer__struct_4 StorageBuffer\n")); +} + TEST_F(ValidateMemory, VulkanRTAInsideUniformStructGood) { std::string spirv = R"( OpCapability Shader @@ -1973,6 +2093,39 @@ OpFunctionEnd EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1)); } +TEST_F(ValidateMemory, WebGPURTAInsideUniformStructBad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +OpDecorate %array_t ArrayStride 4 +OpMemberDecorate %struct_t 0 Offset 0 +OpDecorate %struct_t BufferBlock +%uint_t = OpTypeInt 32 0 +%array_t = OpTypeRuntimeArray %uint_t +%struct_t = OpTypeStruct %array_t +%struct_ptr = OpTypePointer Uniform %struct_t +%2 = OpVariable %struct_ptr Uniform +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("For WebGPU, OpTypeStruct variables containing " + "OpTypeRuntimeArray must have storage class of StorageBuffer\n " + " %6 = OpVariable %_ptr_Uniform__struct_3 Uniform\n")); +} + TEST_F(ValidateMemory, VulkanRTAInsideUniformStructWithoutBufferBlockBad) { std::string spirv = R"( OpCapability Shader @@ -2030,6 +2183,37 @@ OpFunctionEnd "OpTypeRuntimeArray %_runtimearr_2\n")); } +TEST_F(ValidateMemory, WebGPURTAInsideRTABad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%sampler_t = OpTypeSampler +%inner_array_t = OpTypeRuntimeArray %sampler_t +%array_t = OpTypeRuntimeArray %inner_array_t +%array_ptr = OpTypePointer UniformConstant %array_t +%2 = OpVariable %array_ptr UniformConstant +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpTypeRuntimeArray Element Type '3[%_runtimearr_2]' is not " + "valid in WebGPU environment.\n %_runtimearr__runtimearr_2 = " + "OpTypeRuntimeArray %_runtimearr_2\n")); +} + TEST_F(ValidateMemory, VulkanRTAInsideRTAWithRuntimeDescriptorArrayBad) { std::string spirv = R"( OpCapability RuntimeDescriptorArrayEXT @@ -2190,6 +2374,38 @@ OpFunctionEnd "OpTypeArray %_runtimearr_4 %uint_1\n")); } +TEST_F(ValidateMemory, WebGPURTAInsideArrayBad) { + std::string spirv = R"( +OpCapability Shader +OpCapability VulkanMemoryModelKHR +OpExtension "SPV_KHR_vulkan_memory_model" +OpMemoryModel Logical VulkanKHR +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%uint_t = OpTypeInt 32 0 +%dim = OpConstant %uint_t 1 +%sampler_t = OpTypeSampler +%inner_array_t = OpTypeRuntimeArray %sampler_t +%array_t = OpTypeArray %inner_array_t %dim +%array_ptr = OpTypePointer UniformConstant %array_t +%2 = OpVariable %array_ptr UniformConstant +%void = OpTypeVoid +%func_t = OpTypeFunction %void +%func = OpFunction %void None %func_t +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpTypeArray Element Type '5[%_runtimearr_4]' is not " + "valid in WebGPU environment.\n %_arr__runtimearr_4_uint_1 = " + "OpTypeArray %_runtimearr_4 %uint_1\n")); +} + TEST_F(ValidateMemory, VulkanRTAInsideArrayWithRuntimeDescriptorArrayBad) { std::string spirv = R"( OpCapability RuntimeDescriptorArrayEXT diff --git a/3rdparty/spirv-tools/tools/opt/opt.cpp b/3rdparty/spirv-tools/tools/opt/opt.cpp index db056b1c3..fb794270e 100644 --- a/3rdparty/spirv-tools/tools/opt/opt.cpp +++ b/3rdparty/spirv-tools/tools/opt/opt.cpp @@ -478,8 +478,11 @@ OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag, new_argv[i] = flags[i].c_str(); } - return ParseFlags(static_cast(flags.size()), new_argv, optimizer, - in_file, out_file, validator_options, optimizer_options); + auto ret_val = + ParseFlags(static_cast(flags.size()), new_argv, optimizer, in_file, + out_file, validator_options, optimizer_options); + delete[] new_argv; + return ret_val; } // Canonicalize the flag in |argv[argi]| of the form '--pass arg' into @@ -660,7 +663,6 @@ int main(int argc, const char** argv) { spv_target_env target_env = kDefaultEnvironment; - spvtools::Optimizer optimizer(target_env); optimizer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);