diff options
author | Andrey Tuganov <andreyt@google.com> | 2018-01-31 16:29:54 -0500 |
---|---|---|
committer | David Neto <dneto@google.com> | 2018-02-05 13:14:55 -0500 |
commit | 12e6860d073bfa5a88f7925b58d52450811144a5 (patch) | |
tree | a8ba89c1c9b73f2df31464e7b939bd84d0e6ad80 | |
parent | 3ef4bb600f2ac09fda73671617e7793b5891afea (diff) | |
download | SPIRV-Tools-12e6860d073bfa5a88f7925b58d52450811144a5.tar.gz SPIRV-Tools-12e6860d073bfa5a88f7925b58d52450811144a5.tar.bz2 SPIRV-Tools-12e6860d073bfa5a88f7925b58d52450811144a5.zip |
Add barrier instructions validation pass
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/util/bitutils.h | 13 | ||||
-rw-r--r-- | source/val/function.cpp | 31 | ||||
-rw-r--r-- | source/val/function.h | 19 | ||||
-rw-r--r-- | source/validate.cpp | 1 | ||||
-rw-r--r-- | source/validate.h | 4 | ||||
-rw-r--r-- | source/validate_atomics.cpp | 21 | ||||
-rw-r--r-- | source/validate_barriers.cpp | 289 | ||||
-rw-r--r-- | source/validate_image.cpp | 19 | ||||
-rw-r--r-- | test/val/CMakeLists.txt | 6 | ||||
-rw-r--r-- | test/val/val_barriers_test.cpp | 601 |
12 files changed, 963 insertions, 43 deletions
@@ -36,6 +36,7 @@ SPVTOOLS_SRC_FILES := \ source/validate_adjacency.cpp \ source/validate_arithmetics.cpp \ source/validate_atomics.cpp \ + source/validate_barriers.cpp \ source/validate_bitwise.cpp \ source/validate_capability.cpp \ source/validate_cfg.cpp \ diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 91111f6d..d8f1d676 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -284,6 +284,7 @@ set(SPIRV_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/validate_adjacency.cpp ${CMAKE_CURRENT_SOURCE_DIR}/validate_arithmetics.cpp ${CMAKE_CURRENT_SOURCE_DIR}/validate_atomics.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_barriers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/validate_bitwise.cpp ${CMAKE_CURRENT_SOURCE_DIR}/validate_capability.cpp ${CMAKE_CURRENT_SOURCE_DIR}/validate_cfg.cpp diff --git a/source/util/bitutils.h b/source/util/bitutils.h index 31288ab6..9b53d3b2 100644 --- a/source/util/bitutils.h +++ b/source/util/bitutils.h @@ -76,6 +76,19 @@ static_assert(SetBits<uint64_t, 31, 1>::get == uint64_t(0x0000000080000000LL), static_assert(SetBits<uint64_t, 16, 16>::get == uint64_t(0x00000000FFFF0000LL), "SetBits failed"); +// Returns number of '1' bits in a word. +template <typename T> +size_t CountSetBits(T word) { + static_assert(std::is_integral<T>::value, + "CountSetBits requires integer type"); + size_t count = 0; + while (word) { + word &= word - 1; + ++count; + } + return count; +} + } // namespace spvutils #endif // LIBSPIRV_UTIL_BITUTILS_H_ diff --git a/source/val/function.cpp b/source/val/function.cpp index f67acc4b..3a5c8e28 100644 --- a/source/val/function.cpp +++ b/source/val/function.cpp @@ -349,24 +349,41 @@ int Function::GetBlockDepth(BasicBlock* bb) { return block_depth_[bb]; } +void Function::RegisterExecutionModelLimitation(SpvExecutionModel model, + const std::string& message) { + execution_model_limitations_.push_back( + [model, message](SpvExecutionModel in_model, std::string* out_message) { + if (model != in_model) { + if (out_message) { + *out_message = message; + } + return false; + } + return true; + }); +} + bool Function::IsCompatibleWithExecutionModel(SpvExecutionModel model, std::string* reason) const { - bool is_compatible = true; + bool return_value = true; std::stringstream ss_reason; - for (const auto& kv : execution_model_limitations_) { - if (kv.first != model) { + for (const auto& is_compatible : execution_model_limitations_) { + std::string message; + if (!is_compatible(model, &message)) { if (!reason) return false; - is_compatible = false; - ss_reason << kv.second << "\n"; + return_value = false; + if (!message.empty()) { + ss_reason << message << "\n"; + } } } - if (!is_compatible && reason) { + if (!return_value && reason) { *reason = ss_reason.str(); } - return is_compatible; + return return_value; } } // namespace libspirv diff --git a/source/val/function.h b/source/val/function.h index f6afb716..1984654f 100644 --- a/source/val/function.h +++ b/source/val/function.h @@ -203,11 +203,14 @@ class Function { void PrintBlocks() const; /// Registers execution model limitation such as "Feature X is only available - /// with Execution Model Y". Only the first message per model type is - /// registered. + /// with Execution Model Y". void RegisterExecutionModelLimitation(SpvExecutionModel model, - const std::string& message) { - execution_model_limitations_.emplace(model, message); + const std::string& message); + + /// Registers execution model limitation with an |is_compatible| functor. + void RegisterExecutionModelLimitation( + std::function<bool(SpvExecutionModel, std::string*)> is_compatible) { + execution_model_limitations_.push_back(is_compatible); } /// Returns true if the given execution model passes the limitations stored in @@ -338,9 +341,11 @@ class Function { std::unordered_map<BasicBlock*, int> block_depth_; /// Stores execution model limitations imposed by instructions used within the - /// function. The string contains message explaining why the limitation was - /// imposed. - std::map<SpvExecutionModel, std::string> execution_model_limitations_; + /// function. The functor stored in the list return true if execution model + /// is compatible, false otherwise. If the functor returns false, it can also + /// optionally fill the string parameter with the reason for incompatibility. + std::list<std::function<bool(SpvExecutionModel, std::string*)>> + execution_model_limitations_; /// Stores ids of all functions called from this function. std::set<uint32_t> function_call_targets_; diff --git a/source/validate.cpp b/source/validate.cpp index a51f2e09..fadecd00 100644 --- a/source/validate.cpp +++ b/source/validate.cpp @@ -189,6 +189,7 @@ spv_result_t ProcessInstruction(void* user_data, if (auto error = ExtInstPass(_, inst)) return error; if (auto error = ImagePass(_, inst)) return error; if (auto error = AtomicsPass(_, inst)) return error; + if (auto error = BarriersPass(_, inst)) return error; if (auto error = PrimitivesPass(_, inst)) return error; if (auto error = LiteralsPass(_, inst)) return error; diff --git a/source/validate.h b/source/validate.h index 5871b2f2..d0539432 100644 --- a/source/validate.h +++ b/source/validate.h @@ -155,6 +155,10 @@ spv_result_t ImagePass(ValidationState_t& _, spv_result_t AtomicsPass(ValidationState_t& _, const spv_parsed_instruction_t* inst); +/// Validates correctness of barrier instructions. +spv_result_t BarriersPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + /// Validates correctness of literal numbers. spv_result_t LiteralsPass(ValidationState_t& _, const spv_parsed_instruction_t* inst); diff --git a/source/validate_atomics.cpp b/source/validate_atomics.cpp index 2de4d81a..1254b737 100644 --- a/source/validate_atomics.cpp +++ b/source/validate_atomics.cpp @@ -18,21 +18,12 @@ #include "diagnostic.h" #include "opcode.h" +#include "util/bitutils.h" #include "val/instruction.h" #include "val/validation_state.h" namespace libspirv { -// Returns number of '1' bits in a word. -uint32_t CountSetBits(uint32_t word) { - uint32_t count = 0; - while (word) { - word &= word - 1; - ++count; - } - return count; -} - // Validates a Memory Semantics operand. spv_result_t ValidateMemorySemantics(ValidationState_t& _, const spv_parsed_instruction_t* inst, @@ -59,11 +50,11 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _, assert(memory_semantics_inst->words().size() == 4); const uint32_t flags = memory_semantics_inst->word(3); - if (CountSetBits(flags & (SpvMemorySemanticsAcquireMask | - SpvMemorySemanticsReleaseMask | - SpvMemorySemanticsAcquireReleaseMask | - SpvMemorySemanticsSequentiallyConsistentMask)) > - 1) { + if (spvutils::CountSetBits( + flags & + (SpvMemorySemanticsAcquireMask | SpvMemorySemanticsReleaseMask | + SpvMemorySemanticsAcquireReleaseMask | + SpvMemorySemanticsSequentiallyConsistentMask)) > 1) { return _.diag(SPV_ERROR_INVALID_DATA) << spvOpcodeString(opcode) << ": no more than one of the following Memory Semantics bits can " diff --git a/source/validate_barriers.cpp b/source/validate_barriers.cpp new file mode 100644 index 00000000..3df50115 --- /dev/null +++ b/source/validate_barriers.cpp @@ -0,0 +1,289 @@ +// 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 barrier SPIR-V instructions. + +#include "validate.h" + +#include <tuple> + +#include "diagnostic.h" +#include "opcode.h" +#include "spirv_target_env.h" +#include "util/bitutils.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +// Tries to evaluate a 32-bit signed or unsigned scalar integer constant. +// Returns tuple <is_int32, is_const_int32, value>. +std::tuple<bool, bool, uint32_t> EvalInt32IfConst(ValidationState_t& _, + uint32_t id) { + const Instruction* const inst = _.FindDef(id); + assert(inst); + const uint32_t type = inst->type_id(); + + if (!_.IsIntScalarType(type) || _.GetBitWidth(type) != 32) { + return std::make_tuple(false, false, 0); + } + + if (inst->opcode() != SpvOpConstant && inst->opcode() != SpvOpSpecConstant) { + return std::make_tuple(true, false, 0); + } + + assert(inst->words().size() == 4); + return std::make_tuple(true, true, inst->word(3)); +} + +// Validates Execution Scope operand. +spv_result_t ValidateExecutionScope(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + uint32_t id) { + const SpvOp opcode = static_cast<SpvOp>(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) + << spvOpcodeString(opcode) + << ": expected Execution Scope to be a 32-bit int"; + } + + if (!is_const_int32) { + return SPV_SUCCESS; + } + + if (spvIsVulkanEnv(_.context()->target_env)) { + if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": in Vulkan environment Execution Scope is limited to " + "Workgroup and Subgroup"; + } + } + + // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. + + return SPV_SUCCESS; +} + +// Validates Memory Scope operand. +spv_result_t ValidateMemoryScope(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + uint32_t id) { + const SpvOp opcode = static_cast<SpvOp>(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) + << spvOpcodeString(opcode) + << ": expected Memory Scope to be a 32-bit int"; + } + + if (!is_const_int32) { + return SPV_SUCCESS; + } + + if (spvIsVulkanEnv(_.context()->target_env)) { + if (value != SpvScopeDevice && value != SpvScopeWorkgroup && + value != SpvScopeInvocation) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": in Vulkan environment Memory Scope is limited to Device, " + "Workgroup and Invocation"; + } + } + + // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. + + return SPV_SUCCESS; +} + +// Validates Memory Semantics operand. +spv_result_t ValidateMemorySemantics(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + uint32_t id) { + const SpvOp opcode = static_cast<SpvOp>(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) + << spvOpcodeString(opcode) + << ": expected Memory Semantics to be a 32-bit int"; + } + + if (!is_const_int32) { + return SPV_SUCCESS; + } + + const size_t num_memory_order_set_bits = spvutils::CountSetBits( + value & (SpvMemorySemanticsAcquireMask | SpvMemorySemanticsReleaseMask | + SpvMemorySemanticsAcquireReleaseMask | + SpvMemorySemanticsSequentiallyConsistentMask)); + + if (num_memory_order_set_bits > 1) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": Memory Semantics can have at most one of the following bits " + "set: Acquire, Release, AcquireRelease or SequentiallyConsistent"; + } + + if (spvIsVulkanEnv(_.context()->target_env)) { + const bool includes_storage_class = + value & (SpvMemorySemanticsUniformMemoryMask | + SpvMemorySemanticsWorkgroupMemoryMask | + SpvMemorySemanticsImageMemoryMask); + + if (opcode == SpvOpMemoryBarrier && !num_memory_order_set_bits) { + return _.diag(SPV_ERROR_INVALID_DATA) + << 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) + << 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) + << spvOpcodeString(opcode) + << ": expected Memory Semantics to include a Vulkan-supported " + "storage class if Memory Semantics is not None"; + } +#endif + } + + // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. + + return SPV_SUCCESS; +} + +} // anonymous namespace + +// Validates correctness of barrier instructions. +spv_result_t BarriersPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast<SpvOp>(inst->opcode); + const uint32_t result_type = inst->type_id; + + switch (opcode) { + case SpvOpControlBarrier: { + _.current_function().RegisterExecutionModelLimitation( + [](SpvExecutionModel model, std::string* message) { + if (model != SpvExecutionModelTessellationControl && + model != SpvExecutionModelGLCompute && + model != SpvExecutionModelKernel) { + if (message) { + *message = + "OpControlBarrier requires one of the following Execution " + "Models: TessellationControl, GLCompute or Kernel"; + } + return false; + } + return true; + }); + + const uint32_t execution_scope = inst->words[1]; + const uint32_t memory_scope = inst->words[2]; + const uint32_t memory_semantics = inst->words[3]; + + if (auto error = ValidateExecutionScope(_, inst, execution_scope)) { + return error; + } + + if (auto error = ValidateMemoryScope(_, inst, memory_scope)) { + return error; + } + + if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) { + return error; + } + break; + } + + case SpvOpMemoryBarrier: { + const uint32_t memory_scope = inst->words[1]; + const uint32_t memory_semantics = inst->words[2]; + + if (auto error = ValidateMemoryScope(_, inst, memory_scope)) { + return error; + } + + if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) { + return error; + } + break; + } + + case SpvOpNamedBarrierInitialize: { + if (_.GetIdOpcode(result_type) != SpvOpTypeNamedBarrier) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be OpTypeNamedBarrier"; + } + + const uint32_t subgroup_count_type = _.GetOperandTypeId(inst, 2); + if (!_.IsIntScalarType(subgroup_count_type) || + _.GetBitWidth(subgroup_count_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Subgroup Count to be a 32-bit int"; + } + break; + } + + case SpvOpMemoryNamedBarrier: { + const uint32_t named_barrier_type = _.GetOperandTypeId(inst, 0); + if (_.GetIdOpcode(named_barrier_type) != SpvOpTypeNamedBarrier) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Named Barrier to be of type OpTypeNamedBarrier"; + } + + const uint32_t memory_scope = inst->words[2]; + const uint32_t memory_semantics = inst->words[3]; + + if (auto error = ValidateMemoryScope(_, inst, memory_scope)) { + return error; + } + + if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) { + return error; + } + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/source/validate_image.cpp b/source/validate_image.cpp index e8dec5c0..1d671b95 100644 --- a/source/validate_image.cpp +++ b/source/validate_image.cpp @@ -19,6 +19,7 @@ #include "diagnostic.h" #include "opcode.h" #include "spirv_target_env.h" +#include "util/bitutils.h" #include "val/instruction.h" #include "val/validation_state.h" @@ -57,16 +58,6 @@ bool CheckAllImageOperandsHandled() { return false; } -// Returns number of '1' bits in a word. -uint32_t CountSetBits(uint32_t word) { - uint32_t count = 0; - while (word) { - word &= word - 1; - ++count; - } - return count; -} - // Used by GetImageTypeInfo. See OpTypeImage spec for more information. struct ImageTypeInfo { uint32_t sampled_type = 0; @@ -217,7 +208,7 @@ spv_result_t ValidateImageOperands(ValidationState_t& _, const SpvOp opcode = static_cast<SpvOp>(inst.opcode); const uint32_t num_words = inst.num_words; - uint32_t expected_num_image_operand_words = CountSetBits(mask); + size_t expected_num_image_operand_words = spvutils::CountSetBits(mask); if (mask & SpvImageOperandsGradMask) { // Grad uses two words. ++expected_num_image_operand_words; @@ -229,9 +220,9 @@ spv_result_t ValidateImageOperands(ValidationState_t& _, << spvOpcodeString(opcode); } - if (CountSetBits(mask & (SpvImageOperandsOffsetMask | - SpvImageOperandsConstOffsetMask | - SpvImageOperandsConstOffsetsMask)) > 1) { + if (spvutils::CountSetBits(mask & (SpvImageOperandsOffsetMask | + SpvImageOperandsConstOffsetMask | + SpvImageOperandsConstOffsetsMask)) > 1) { return _.diag(SPV_ERROR_INVALID_DATA) << "Image Operands Offset, ConstOffset, ConstOffsets cannot be used " << "together: " << spvOpcodeString(opcode); diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt index b1673088..0c46984a 100644 --- a/test/val/CMakeLists.txt +++ b/test/val/CMakeLists.txt @@ -122,6 +122,12 @@ add_spvtools_unittest(TARGET val_atomics LIBS ${SPIRV_TOOLS} ) +add_spvtools_unittest(TARGET val_barriers + SRCS val_barriers_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + add_spvtools_unittest(TARGET val_primitives SRCS val_primitives_test.cpp ${VAL_TEST_COMMON_SRCS} diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp new file mode 100644 index 00000000..a4c6cabd --- /dev/null +++ b/test/val/val_barriers_test.cpp @@ -0,0 +1,601 @@ +// 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 <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateBarriers = spvtest::ValidateBase<bool>; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "", + const std::string& execution_model = "GLCompute") { + std::ostringstream ss; + ss << R"( +OpCapability Shader +OpCapability Int64 +)"; + + ss << capabilities_and_extensions; + ss << "OpMemoryModel Logical GLSL450\n"; + ss << "OpEntryPoint " << execution_model << " %main \"main\"\n"; + + ss << R"( +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%u64 = OpTypeInt 64 0 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_4 = OpConstant %u32 4 +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 + +%cross_device = OpConstant %u32 0 +%device = OpConstant %u32 1 +%workgroup = OpConstant %u32 2 +%subgroup = OpConstant %u32 3 +%invocation = OpConstant %u32 4 + +%none = OpConstant %u32 0 +%acquire = OpConstant %u32 2 +%release = OpConstant %u32 4 +%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 +%acquire_release_subgroup = OpConstant %u32 136 +%uniform = OpConstant %u32 64 + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +std::string GenerateKernelCode( + const std::string& body, + const std::string& capabilities_and_extensions = "") { + std::ostringstream ss; + ss << R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpCapability Int64 +OpCapability NamedBarrier +)"; + + ss << capabilities_and_extensions; + ss << R"( +OpMemoryModel Physical32 OpenCL +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%u64 = OpTypeInt 64 0 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_4 = OpConstant %f32 4 +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_4 = OpConstant %u32 4 +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_4 = OpConstant %u64 4 + +%cross_device = OpConstant %u32 0 +%device = OpConstant %u32 1 +%workgroup = OpConstant %u32 2 +%subgroup = OpConstant %u32 3 +%invocation = OpConstant %u32 4 + +%none = OpConstant %u32 0 +%acquire = OpConstant %u32 2 +%release = OpConstant %u32 4 +%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 + +%named_barrier = OpTypeNamedBarrier + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +TEST_F(ValidateBarriers, OpControlBarrierGLComputeSuccess) { + const std::string body = R"( +OpControlBarrier %device %device %none +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 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBarriers, OpControlBarrierKernelSuccess) { + const std::string body = R"( +OpControlBarrier %device %device %none +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 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); +} + +TEST_F(ValidateBarriers, OpControlBarrierTesselationControlSuccess) { + const std::string body = R"( +OpControlBarrier %device %device %none +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 +)"; + + CompileSuccessfully(GenerateShaderCode(body, "OpCapability Tessellation\n", + "TessellationControl")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBarriers, OpControlBarrierVulkanSuccess) { + const std::string body = R"( +OpControlBarrier %workgroup %device %none +OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragment) { + const std::string body = R"( +OpControlBarrier %device %device %none +)"; + + CompileSuccessfully(GenerateShaderCode(body, "", "Fragment")); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpControlBarrier requires one of the following Execution " + "Models: TessellationControl, GLCompute or Kernel")); +} + +TEST_F(ValidateBarriers, OpControlBarrierFloatExecutionScope) { + const std::string body = R"( +OpControlBarrier %f32_1 %device %none +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ControlBarrier: expected Execution Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierU64ExecutionScope) { + const std::string body = R"( +OpControlBarrier %u64_1 %device %none +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ControlBarrier: expected Execution Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierFloatMemoryScope) { + const std::string body = R"( +OpControlBarrier %device %f32_1 %none +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ControlBarrier: expected Memory Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierU64MemoryScope) { + const std::string body = R"( +OpControlBarrier %device %u64_1 %none +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ControlBarrier: expected Memory Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierFloatMemorySemantics) { + const std::string body = R"( +OpControlBarrier %device %device %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "ControlBarrier: expected Memory Semantics to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierU64MemorySemantics) { + const std::string body = R"( +OpControlBarrier %device %device %u64_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "ControlBarrier: expected Memory Semantics to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierVulkanExecutionScopeDevice) { + const std::string body = R"( +OpControlBarrier %device %workgroup %none +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ControlBarrier: in Vulkan environment Execution Scope " + "is limited to Workgroup and Subgroup")); +} + +TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroup) { + const std::string body = R"( +OpControlBarrier %subgroup %subgroup %none +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ControlBarrier: in Vulkan environment Memory Scope is " + "limited to Device, Workgroup and Invocation")); +} + +TEST_F(ValidateBarriers, OpControlBarrierAcquireAndRelease) { + const std::string body = R"( +OpControlBarrier %device %device %acquire_and_release_uniform +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ControlBarrier: Memory Semantics can have at most one " + "of the following bits set: Acquire, Release, " + "AcquireRelease or SequentiallyConsistent")); +} + +// TODO(atgoo@github.com): the corresponding check fails Vulkan CTS, +// reenable once fixed. +TEST_F(ValidateBarriers, DISABLED_OpControlBarrierVulkanSubgroupStorageClass) { + const std::string body = R"( +OpControlBarrier %workgroup %device %acquire_release_subgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "ControlBarrier: expected Memory Semantics to include a " + "Vulkan-supported storage class if Memory Semantics is not None")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierSuccess) { + const std::string body = R"( +OpMemoryBarrier %cross_device %acquire_release_uniform_workgroup +OpMemoryBarrier %device %uniform +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierKernelSuccess) { + const std::string body = R"( +OpMemoryBarrier %cross_device %acquire_release_uniform_workgroup +OpMemoryBarrier %device %uniform +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierVulkanSuccess) { + const std::string body = R"( +OpMemoryBarrier %workgroup %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemoryScope) { + const std::string body = R"( +OpMemoryBarrier %f32_1 %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierU64MemoryScope) { + const std::string body = R"( +OpMemoryBarrier %u64_1 %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemorySemantics) { + const std::string body = R"( +OpMemoryBarrier %device %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Semantics to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierU64MemorySemantics) { + const std::string body = R"( +OpMemoryBarrier %device %u64_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Semantics to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierVulkanMemoryScopeSubgroup) { + const std::string body = R"( +OpMemoryBarrier %subgroup %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryBarrier: in Vulkan environment Memory Scope is " + "limited to Device, Workgroup and Invocation")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierAcquireAndRelease) { + const std::string body = R"( +OpMemoryBarrier %device %acquire_and_release_uniform +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryBarrier: Memory Semantics can have at most one " + "of the following bits set: Acquire, Release, " + "AcquireRelease or SequentiallyConsistent")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierVulkanMemorySemanticsNone) { + const std::string body = R"( +OpMemoryBarrier %device %none +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MemoryBarrier: Vulkan specification requires Memory Semantics " + "to have one of the following bits set: Acquire, Release, " + "AcquireRelease or SequentiallyConsistent")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierVulkanMemorySemanticsAcquire) { + const std::string body = R"( +OpMemoryBarrier %device %acquire +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Semantics to include a " + "Vulkan-supported storage class")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierVulkanSubgroupStorageClass) { + const std::string body = R"( +OpMemoryBarrier %device %acquire_release_subgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Semantics to include a " + "Vulkan-supported storage class")); +} + +TEST_F(ValidateBarriers, OpNamedBarrierInitializeSuccess) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u32_4 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); +} + +TEST_F(ValidateBarriers, OpNamedBarrierInitializeWrongResultType) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %u32 %u32_4 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("NamedBarrierInitialize: expected Result Type to be " + "OpTypeNamedBarrier")); +} + +TEST_F(ValidateBarriers, OpNamedBarrierInitializeFloatSubgroupCount) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %f32_4 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("NamedBarrierInitialize: expected Subgroup Count to be " + "a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpNamedBarrierInitializeU64SubgroupCount) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u64_4 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("NamedBarrierInitialize: expected Subgroup Count to be " + "a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryNamedBarrierSuccess) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u32_4 +OpMemoryNamedBarrier %barrier %workgroup %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); +} + +TEST_F(ValidateBarriers, OpMemoryNamedBarrierNotNamedBarrier) { + const std::string body = R"( +OpMemoryNamedBarrier %u32_1 %workgroup %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryNamedBarrier: expected Named Barrier to be of " + "type OpTypeNamedBarrier")); +} + +TEST_F(ValidateBarriers, OpMemoryNamedBarrierFloatMemoryScope) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u32_4 +OpMemoryNamedBarrier %barrier %f32_1 %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "MemoryNamedBarrier: expected Memory Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryNamedBarrierFloatMemorySemantics) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u32_4 +OpMemoryNamedBarrier %barrier %workgroup %f32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "MemoryNamedBarrier: expected Memory Semantics to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryNamedBarrierAcquireAndRelease) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u32_4 +OpMemoryNamedBarrier %barrier %workgroup %acquire_and_release_uniform +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryNamedBarrier: Memory Semantics can have at most " + "one of the following bits set: Acquire, Release, " + "AcquireRelease or SequentiallyConsistent")); +} + +} // anonymous namespace |