Add validation rules for atomic instructions
authorAndrey Tuganov <andreyt@google.com>
Thu, 30 Nov 2017 16:29:05 +0000 (11:29 -0500)
committerDavid Neto <dneto@google.com>
Wed, 13 Dec 2017 23:29:38 +0000 (18:29 -0500)
Validates all OpAtomicXXX instructions.

Android.mk
source/CMakeLists.txt
source/validate.cpp
source/validate.h
source/validate_atomics.cpp [new file with mode: 0644]
test/val/CMakeLists.txt
test/val/val_atomics_test.cpp [new file with mode: 0644]

index 0554e90..f9baa6a 100644 (file)
@@ -34,6 +34,7 @@ SPVTOOLS_SRC_FILES := \
                source/val/validation_state.cpp \
                source/validate.cpp \
                source/validate_arithmetics.cpp \
+               source/validate_atomics.cpp \
                source/validate_bitwise.cpp \
                source/validate_capability.cpp \
                source/validate_cfg.cpp \
index b6dbdce..87819d5 100644 (file)
@@ -254,6 +254,7 @@ set(SPIRV_SOURCES
   ${CMAKE_CURRENT_SOURCE_DIR}/text_handler.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate_arithmetics.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/validate_atomics.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate_bitwise.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate_capability.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/validate_cfg.cpp
index 896d23a..8320b6b 100644 (file)
@@ -186,6 +186,7 @@ spv_result_t ProcessInstruction(void* user_data,
   if (auto error = LogicalsPass(_, inst)) return error;
   if (auto error = BitwisePass(_, inst)) return error;
   if (auto error = ImagePass(_, inst)) return error;
+  if (auto error = AtomicsPass(_, inst)) return error;
 
   return SPV_SUCCESS;
 }
index 9bd129c..625713c 100644 (file)
@@ -139,6 +139,10 @@ spv_result_t BitwisePass(ValidationState_t& _,
 spv_result_t ImagePass(ValidationState_t& _,
                        const spv_parsed_instruction_t* inst);
 
+/// Validates correctness of atomic instructions.
+spv_result_t AtomicsPass(ValidationState_t& _,
+                         const spv_parsed_instruction_t* inst);
+
 // Validates that capability declarations use operands allowed in the current
 // context.
 spv_result_t CapabilityPass(ValidationState_t& _,
diff --git a/source/validate_atomics.cpp b/source/validate_atomics.cpp
new file mode 100644 (file)
index 0000000..22c8a96
--- /dev/null
@@ -0,0 +1,197 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Validates correctness of atomic SPIR-V instructions.
+
+#include "validate.h"
+
+#include "diagnostic.h"
+#include "opcode.h"
+#include "val/instruction.h"
+#include "val/validation_state.h"
+
+namespace libspirv {
+
+// Validates correctness of atomic instructions.
+spv_result_t AtomicsPass(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 SpvOpAtomicLoad:
+    case SpvOpAtomicStore:
+    case SpvOpAtomicExchange:
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:
+    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: {
+      if (opcode == SpvOpAtomicLoad || opcode == SpvOpAtomicExchange ||
+          opcode == SpvOpAtomicCompareExchange) {
+        if (!_.IsFloatScalarType(result_type) &&
+            !_.IsIntScalarType(result_type)) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+                 << spvOpcodeString(opcode)
+                 << ": expected Result Type to be int or float scalar type";
+        }
+      } else if (opcode == SpvOpAtomicFlagTestAndSet) {
+        if (!_.IsBoolScalarType(result_type)) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+                 << spvOpcodeString(opcode)
+                 << ": expected Result Type to be bool scalar type";
+        }
+      } else if (opcode == SpvOpAtomicFlagClear || opcode == SpvOpAtomicStore) {
+        assert(result_type == 0);
+      } else {
+        if (!_.IsIntScalarType(result_type)) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+                 << spvOpcodeString(opcode)
+                 << ": expected Result Type to be int scalar type";
+        }
+      }
+
+      uint32_t operand_index =
+          opcode == SpvOpAtomicFlagClear || opcode == SpvOpAtomicStore ? 0 : 2;
+      const uint32_t pointer_type = _.GetOperandTypeId(inst, operand_index++);
+
+      uint32_t data_type = 0;
+      uint32_t storage_class = 0;
+      if (!_.GetPointerTypeInfo(pointer_type, &data_type, &storage_class)) {
+        return _.diag(SPV_ERROR_INVALID_DATA)
+               << spvOpcodeString(opcode)
+               << ": expected Pointer to be of type OpTypePointer";
+      }
+
+      switch (storage_class) {
+        case SpvStorageClassUniform:
+        case SpvStorageClassWorkgroup:
+        case SpvStorageClassCrossWorkgroup:
+        case SpvStorageClassGeneric:
+        case SpvStorageClassAtomicCounter:
+        case SpvStorageClassImage:
+        case SpvStorageClassStorageBuffer:
+          break;
+        default:
+          return _.diag(SPV_ERROR_INVALID_DATA)
+              << spvOpcodeString(opcode)
+              << ": expected Pointer Storage Class to be Uniform, "
+              << "Workgroup, CrossWorkgroup, Generic, AtomicCounter, Image or "
+              << "StorageBuffer";
+      }
+
+      if (opcode == SpvOpAtomicFlagTestAndSet ||
+          opcode == SpvOpAtomicFlagClear) {
+        if (!_.IsIntScalarType(data_type) || _.GetBitWidth(data_type) != 32) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+                 << spvOpcodeString(opcode)
+                 << ": expected Pointer to point to a value of 32-bit int type";
+        }
+      } else if (opcode == SpvOpAtomicStore) {
+        if (!_.IsFloatScalarType(data_type) && !_.IsIntScalarType(data_type)) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+                 << spvOpcodeString(opcode)
+                 << ": expected Pointer to be a pointer to int or float "
+                 << "scalar type";
+        }
+      } else {
+        if (data_type != result_type) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+                 << spvOpcodeString(opcode)
+                 << ": expected Pointer to point to a value of type Result "
+                    "Type";
+        }
+      }
+
+      const uint32_t scope_type = _.GetOperandTypeId(inst, operand_index++);
+      if (!_.IsIntScalarType(scope_type) || _.GetBitWidth(scope_type) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA)
+               << spvOpcodeString(opcode)
+               << ": expected Scope to be 32-bit int";
+      }
+
+      const uint32_t memory_semantics_type1 =
+          _.GetOperandTypeId(inst, operand_index++);
+      if (!_.IsIntScalarType(memory_semantics_type1) ||
+          _.GetBitWidth(memory_semantics_type1) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA)
+               << spvOpcodeString(opcode)
+               << ": expected Memory Semantics to be 32-bit int";
+      }
+
+      if (opcode == SpvOpAtomicCompareExchange ||
+          opcode == SpvOpAtomicCompareExchangeWeak) {
+        const uint32_t memory_semantics_type2 =
+            _.GetOperandTypeId(inst, operand_index++);
+        if (!_.IsIntScalarType(memory_semantics_type2) ||
+            _.GetBitWidth(memory_semantics_type2) != 32) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+                 << spvOpcodeString(opcode)
+                 << ": expected Memory Semantics to be 32-bit int";
+        }
+      }
+
+      if (opcode == SpvOpAtomicStore) {
+        const uint32_t value_type = _.GetOperandTypeId(inst, 3);
+        if (value_type != data_type) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+                 << spvOpcodeString(opcode)
+                 << ": expected Value type and the type pointed to by Pointer "
+                    "to"
+                 << " be the same";
+        }
+      } else if (opcode != SpvOpAtomicLoad && opcode != SpvOpAtomicIIncrement &&
+                 opcode != SpvOpAtomicIDecrement &&
+                 opcode != SpvOpAtomicFlagTestAndSet &&
+                 opcode != SpvOpAtomicFlagClear) {
+        const uint32_t value_type = _.GetOperandTypeId(inst, operand_index++);
+        if (value_type != result_type) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+                 << spvOpcodeString(opcode)
+                 << ": expected Value to be of type Result Type";
+        }
+      }
+
+      if (opcode == SpvOpAtomicCompareExchange ||
+          opcode == SpvOpAtomicCompareExchangeWeak) {
+        const uint32_t comparator_type =
+            _.GetOperandTypeId(inst, operand_index++);
+        if (comparator_type != result_type) {
+          return _.diag(SPV_ERROR_INVALID_DATA)
+                 << spvOpcodeString(opcode)
+                 << ": expected Comparator to be of type Result Type";
+        }
+      }
+
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace libspirv
index 27d4d6d..d8fee6b 100644 (file)
@@ -116,6 +116,12 @@ add_spvtools_unittest(TARGET val_image
   LIBS ${SPIRV_TOOLS}
 )
 
+add_spvtools_unittest(TARGET val_atomics
+       SRCS val_atomics_test.cpp
+       ${VAL_TEST_COMMON_SRCS}
+  LIBS ${SPIRV_TOOLS}
+)
+
 add_spvtools_unittest(TARGET val_limits
        SRCS val_limits_test.cpp
        ${VAL_TEST_COMMON_SRCS}
diff --git a/test/val/val_atomics_test.cpp b/test/val/val_atomics_test.cpp
new file mode 100644 (file)
index 0000000..a9b9079
--- /dev/null
@@ -0,0 +1,642 @@
+// Copyright (c) 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "unit_spirv.h"
+#include "val_fixtures.h"
+
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+using ValidateAtomics = spvtest::ValidateBase<bool>;
+
+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
+)";
+
+  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
+%f32vec4 = OpTypeVector %f32 4
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u64_1 = OpConstant %u64 1
+%f32vec4_0000 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0
+
+%scope = OpConstant %u32 1
+%memory_semantics = OpConstant %u32 1
+
+%f32_ptr = OpTypePointer Workgroup %f32
+%f32_var = OpVariable %f32_ptr Workgroup
+
+%u32_ptr = OpTypePointer Workgroup %u32
+%u32_var = OpVariable %u32_ptr Workgroup
+
+%u64_ptr = OpTypePointer Workgroup %u64
+%u64_var = OpVariable %u64_ptr Workgroup
+
+%f32vec4_ptr = OpTypePointer Workgroup %f32vec4
+%f32vec4_var = OpVariable %f32vec4_ptr Workgroup
+
+%f32_ptr_function = OpTypePointer Function %f32
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+)";
+
+  ss << body;
+
+  ss << R"(
+OpReturn
+OpFunctionEnd)";
+
+  return ss.str();
+}
+
+TEST_F(ValidateAtomics, AtomicLoadSuccess) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %f32 %f32_var %scope %memory_semantics
+%val2 = OpAtomicLoad %u32 %u32_var %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAtomics, AtomicLoadWrongResultType) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %f32vec4 %f32vec4_var %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicLoad: "
+                        "expected Result Type to be int or float scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadWrongPointerType) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %f32 %f32_ptr %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: expected Pointer to be of type OpTypePointer"));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadWrongPointerDataType) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %f32_var %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: "
+                "expected Pointer to point to a value of type Result Type"));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadWrongScopeType) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %f32 %f32_var %f32_1 %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicLoad: expected Scope to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadWrongMemorySemanticsType) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %f32 %f32_var %scope %u64_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: expected Memory Semantics to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreSuccess) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicStore %u32_var %scope %memory_semantics %u32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) {
+  const std::string body = R"(
+OpAtomicStore %f32_1 %scope %memory_semantics %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicStore: expected Pointer to be of type OpTypePointer"));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreWrongPointerDataType) {
+  const std::string body = R"(
+OpAtomicStore %f32vec4_var %scope %memory_semantics %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicStore: "
+                "expected Pointer to be a pointer to int or float scalar "
+                "type"));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreWrongPointerStorageType) {
+  const std::string body = R"(
+%f32_var_function = OpVariable %f32_ptr_function Function
+OpAtomicStore %f32_var_function %scope %memory_semantics %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicStore: expected Pointer Storage Class to be Uniform, "
+                "Workgroup, CrossWorkgroup, Generic, AtomicCounter, Image or "
+                "StorageBuffer"));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreWrongScopeType) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %f32_1 %memory_semantics %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicStore: expected Scope to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreWrongMemorySemanticsType) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %f32_1 %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicStore: expected Memory Semantics to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreWrongValueType) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %u32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicStore: "
+                "expected Value type and the type pointed to by Pointer to "
+                "be the same"));
+}
+
+TEST_F(ValidateAtomics, AtomicExchangeSuccess) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicExchange %f32 %f32_var %scope %memory_semantics %f32_0
+%val3 = OpAtomicStore %u32_var %scope %memory_semantics %u32_1
+%val4 = OpAtomicExchange %u32 %u32_var %scope %memory_semantics %u32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAtomics, AtomicExchangeWrongResultType) {
+  const std::string body = R"(
+%val1 = OpStore %f32vec4_var %f32vec4_0000
+%val2 = OpAtomicExchange %f32vec4 %f32vec4_var %scope %memory_semantics %f32vec4_0000
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicExchange: "
+                        "expected Result Type to be int or float scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicExchangeWrongPointerType) {
+  const std::string body = R"(
+%val2 = OpAtomicExchange %f32 %f32vec4_ptr %scope %memory_semantics %f32vec4_0000
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "AtomicExchange: expected Pointer to be of type OpTypePointer"));
+}
+
+TEST_F(ValidateAtomics, AtomicExchangeWrongPointerDataType) {
+  const std::string body = R"(
+%val1 = OpStore %f32vec4_var %f32vec4_0000
+%val2 = OpAtomicExchange %f32 %f32vec4_var %scope %memory_semantics %f32vec4_0000
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicExchange: "
+                "expected Pointer to point to a value of type Result Type"));
+}
+
+TEST_F(ValidateAtomics, AtomicExchangeWrongScopeType) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicExchange %f32 %f32_var %f32_1 %memory_semantics %f32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicExchange: expected Scope to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicExchangeWrongMemorySemanticsType) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicExchange %f32 %f32_var %scope %f32_1 %f32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicExchange: expected Memory Semantics to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicExchangeWrongValueType) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicExchange %f32 %f32_var %scope %memory_semantics %u32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicExchange: "
+                        "expected Value to be of type Result Type"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeSuccess) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicCompareExchange %f32 %f32_var %scope %memory_semantics %memory_semantics %f32_0 %f32_1
+%val3 = OpAtomicStore %u32_var %scope %memory_semantics %u32_1
+%val4 = OpAtomicCompareExchange %u32 %u32_var %scope %memory_semantics %memory_semantics %u32_0 %u32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeWrongResultType) {
+  const std::string body = R"(
+%val1 = OpStore %f32vec4_var %f32vec4_0000
+%val2 = OpAtomicCompareExchange %f32vec4 %f32vec4_var %scope %memory_semantics %memory_semantics %f32vec4_0000 %f32vec4_0000
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicCompareExchange: "
+                        "expected Result Type to be int or float scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeWrongPointerType) {
+  const std::string body = R"(
+%val2 = OpAtomicCompareExchange %f32 %f32vec4_ptr %scope %memory_semantics %memory_semantics %f32vec4_0000 %f32vec4_0000
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicCompareExchange: expected Pointer to be of type "
+                        "OpTypePointer"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeWrongPointerDataType) {
+  const std::string body = R"(
+%val1 = OpStore %f32vec4_var %f32vec4_0000
+%val2 = OpAtomicCompareExchange %f32 %f32vec4_var %scope %memory_semantics %memory_semantics %f32_0 %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicCompareExchange: "
+                "expected Pointer to point to a value of type Result Type"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeWrongScopeType) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicCompareExchange %f32 %f32_var %f32_1 %memory_semantics %memory_semantics %f32_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicCompareExchange: expected Scope to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeWrongMemorySemanticsType1) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicCompareExchange %f32 %f32_var %scope %f32_1 %memory_semantics %f32_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "AtomicCompareExchange: expected Memory Semantics to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeWrongMemorySemanticsType2) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicCompareExchange %f32 %f32_var %scope %memory_semantics %f32_1 %f32_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "AtomicCompareExchange: expected Memory Semantics to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeWrongValueType) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicCompareExchange %f32 %f32_var %scope %memory_semantics %memory_semantics %u32_0 %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicCompareExchange: "
+                        "expected Value to be of type Result Type"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeWrongComparatorType) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicCompareExchange %f32 %f32_var %scope %memory_semantics %memory_semantics %f32_0 %u32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicCompareExchange: "
+                        "expected Comparator to be of type Result Type"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeWeakSuccess) {
+  const std::string body = R"(
+%val3 = OpAtomicStore %u32_var %scope %memory_semantics %u32_1
+%val4 = OpAtomicCompareExchangeWeak %u32 %u32_var %scope %memory_semantics %memory_semantics %u32_0 %u32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeWeakWrongResultType) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %scope %memory_semantics %f32_1
+%val2 = OpAtomicCompareExchangeWeak %f32 %f32_var %scope %memory_semantics %memory_semantics %f32_0 %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicCompareExchangeWeak: "
+                        "expected Result Type to be int scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicArithmeticsSuccess) {
+  const std::string body = R"(
+OpAtomicStore %u32_var %scope %memory_semantics %u32_1
+%val1 = OpAtomicIIncrement %u32 %u32_var %scope %memory_semantics
+%val2 = OpAtomicIDecrement %u32 %u32_var %scope %memory_semantics
+%val3 = OpAtomicIAdd %u32 %u32_var %scope %memory_semantics %u32_1
+%val4 = OpAtomicISub %u32 %u32_var %scope %memory_semantics %u32_1
+%val5 = OpAtomicUMin %u32 %u32_var %scope %memory_semantics %u32_1
+%val6 = OpAtomicUMax %u32 %u32_var %scope %memory_semantics %u32_1
+%val7 = OpAtomicSMin %u32 %u32_var %scope %memory_semantics %u32_1
+%val8 = OpAtomicSMax %u32 %u32_var %scope %memory_semantics %u32_1
+%val9 = OpAtomicAnd %u32 %u32_var %scope %memory_semantics %u32_1
+%val10 = OpAtomicOr %u32 %u32_var %scope %memory_semantics %u32_1
+%val11 = OpAtomicXor %u32 %u32_var %scope %memory_semantics %u32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAtomics, AtomicFlagsSuccess) {
+  const std::string body = R"(
+OpAtomicFlagClear %u32_var %scope %memory_semantics
+%val1 = OpAtomicFlagTestAndSet %bool %u32_var %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongResultType) {
+  const std::string body = R"(
+%val1 = OpAtomicFlagTestAndSet %u32 %u32_var %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFlagTestAndSet: "
+                        "expected Result Type to be bool scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicFlagTestAndSetNotPointer) {
+  const std::string body = R"(
+%val1 = OpAtomicFlagTestAndSet %bool %u32_1 %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFlagTestAndSet: "
+                        "expected Pointer to be of type OpTypePointer"));
+}
+
+TEST_F(ValidateAtomics, AtomicFlagTestAndSetNotIntPointer) {
+  const std::string body = R"(
+%val1 = OpAtomicFlagTestAndSet %bool %f32_var %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicFlagTestAndSet: "
+                "expected Pointer to point to a value of 32-bit int type"));
+}
+
+TEST_F(ValidateAtomics, AtomicFlagTestAndSetNotInt32Pointer) {
+  const std::string body = R"(
+%val1 = OpAtomicFlagTestAndSet %bool %u64_var %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicFlagTestAndSet: "
+                "expected Pointer to point to a value of 32-bit int type"));
+}
+
+TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongScopeType) {
+  const std::string body = R"(
+%val1 = OpAtomicFlagTestAndSet %bool %u32_var %u64_1 %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFlagTestAndSet: "
+                        "expected Scope to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongMemorySemanticsType) {
+  const std::string body = R"(
+%val1 = OpAtomicFlagTestAndSet %bool %u32_var %scope %u64_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFlagTestAndSet: "
+                        "expected Memory Semantics to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicFlagClearNotPointer) {
+  const std::string body = R"(
+OpAtomicFlagClear %u32_1 %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFlagClear: "
+                        "expected Pointer to be of type OpTypePointer"));
+}
+
+TEST_F(ValidateAtomics, AtomicFlagClearNotIntPointer) {
+  const std::string body = R"(
+OpAtomicFlagClear %f32_var %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicFlagClear: "
+                "expected Pointer to point to a value of 32-bit int type"));
+}
+
+TEST_F(ValidateAtomics, AtomicFlagClearNotInt32Pointer) {
+  const std::string body = R"(
+OpAtomicFlagClear %u64_var %scope %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicFlagClear: "
+                "expected Pointer to point to a value of 32-bit int type"));
+}
+
+TEST_F(ValidateAtomics, AtomicFlagClearWrongScopeType) {
+  const std::string body = R"(
+OpAtomicFlagClear %u32_var %u64_1 %memory_semantics
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFlagClear: expected Scope to be 32-bit int"));
+}
+
+TEST_F(ValidateAtomics, AtomicFlagClearWrongMemorySemanticsType) {
+  const std::string body = R"(
+OpAtomicFlagClear %u32_var %scope %u64_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicFlagClear: expected Memory Semantics to be 32-bit int"));
+}
+
+}  // anonymous namespace