Validate OpenCL environment rules for OpTypeImage (#2606)

It is currently not possible to use an Image Format that is
not Unknown without requiring a capability forbidden by the
OpenCL environment. As such the validation of Image Format
currently leans on capability validation entirely.

Fixes #2592.

Signed-off-by: Kevin Petit <kevin.petit@arm.com>
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index ebf9ae0..28fd9d1 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -718,6 +718,11 @@
              << "Expected Sampled Type to be a 32-bit int or float "
                 "scalar type for Vulkan environment";
     }
+  } else if (spvIsOpenCLEnv(_.context()->target_env)) {
+    if (!_.IsVoidType(info.sampled_type)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Sampled Type must be OpTypeVoid in the OpenCL environment.";
+    }
   } else {
     const SpvOp sampled_type_opcode = _.GetIdOpcode(info.sampled_type);
     if (sampled_type_opcode != SpvOpTypeVoid &&
@@ -741,16 +746,39 @@
            << "Invalid Arrayed " << info.arrayed << " (must be 0 or 1)";
   }
 
+  if (spvIsOpenCLEnv(_.context()->target_env)) {
+    if ((info.arrayed == 1) && (info.dim != SpvDim1D) &&
+        (info.dim != SpvDim2D)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "In the OpenCL environment, Arrayed may only be set to 1 "
+             << "when Dim is either 1D or 2D.";
+    }
+  }
+
   if (info.multisampled > 1) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "Invalid MS " << info.multisampled << " (must be 0 or 1)";
   }
 
+  if (spvIsOpenCLEnv(_.context()->target_env)) {
+    if (info.multisampled != 0) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "MS must be 0 in the OpenCL environement.";
+    }
+  }
+
   if (info.sampled > 2) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "Invalid Sampled " << info.sampled << " (must be 0, 1 or 2)";
   }
 
+  if (spvIsOpenCLEnv(_.context()->target_env)) {
+    if (info.sampled != 0) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Sampled must be 0 in the OpenCL environment.";
+    }
+  }
+
   if (info.dim == SpvDimSubpassData) {
     if (info.sampled != 2) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -763,7 +791,15 @@
     }
   }
 
-  // Format and Access Qualifier are checked elsewhere.
+  // Format and Access Qualifier are also checked elsewhere.
+
+  if (spvIsOpenCLEnv(_.context()->target_env)) {
+    if (info.access_qualifier == SpvAccessQualifierMax) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "In the OpenCL environment, the optional Access Qualifier"
+             << " must be present.";
+    }
+  }
 
   return SPV_SUCCESS;
 }
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index 745c82b..394f728 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -691,6 +691,12 @@
   return 0;
 }
 
+bool ValidationState_t::IsVoidType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->opcode() == SpvOpTypeVoid;
+}
+
 bool ValidationState_t::IsFloatScalarType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
   assert(inst);
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index 9b0a586..c34c1ec 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -548,6 +548,7 @@
 
   // Returns true iff |id| is a type corresponding to the name of the function.
   // Only works for types not for objects.
+  bool IsVoidType(uint32_t id) const;
   bool IsFloatScalarType(uint32_t id) const;
   bool IsFloatVectorType(uint32_t id) const;
   bool IsFloatScalarOrVectorType(uint32_t id) const;
diff --git a/test/val/val_opencl_test.cpp b/test/val/val_opencl_test.cpp
index 52e4db6..1a440e8 100644
--- a/test/val/val_opencl_test.cpp
+++ b/test/val/val_opencl_test.cpp
@@ -56,6 +56,97 @@
                         "\n  OpMemoryModel Physical32 GLSL450\n"));
 }
 
+TEST_F(ValidateOpenCL, NonVoidSampledTypeImageBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpMemoryModel Physical32 OpenCL
+    %1 = OpTypeInt 32 0
+    %2 = OpTypeImage %1 2D 0 0 0 0 Unknown ReadOnly
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Sampled Type must be OpTypeVoid in the OpenCL environment."
+                "\n  %2 = OpTypeImage %uint 2D 0 0 0 0 Unknown ReadOnly\n"));
+}
+
+TEST_F(ValidateOpenCL, NonZeroMSImageBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpMemoryModel Physical32 OpenCL
+    %1 = OpTypeVoid
+    %2 = OpTypeImage %1 2D 0 0 1 0 Unknown ReadOnly
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("MS must be 0 in the OpenCL environement."
+                "\n  %2 = OpTypeImage %void 2D 0 0 1 0 Unknown ReadOnly\n"));
+}
+
+TEST_F(ValidateOpenCL, Non1D2DArrayedImageBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpMemoryModel Physical32 OpenCL
+    %1 = OpTypeVoid
+    %2 = OpTypeImage %1 3D 0 1 0 0 Unknown ReadOnly
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("In the OpenCL environment, Arrayed may only be set to 1 "
+                "when Dim is either 1D or 2D."
+                "\n  %2 = OpTypeImage %void 3D 0 1 0 0 Unknown ReadOnly\n"));
+}
+
+TEST_F(ValidateOpenCL, NonZeroSampledImageBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpMemoryModel Physical32 OpenCL
+    %1 = OpTypeVoid
+    %2 = OpTypeImage %1 3D 0 0 0 1 Unknown ReadOnly
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Sampled must be 0 in the OpenCL environment."
+                "\n  %2 = OpTypeImage %void 3D 0 0 0 1 Unknown ReadOnly\n"));
+}
+
+TEST_F(ValidateOpenCL, NoAccessQualifierImageBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpMemoryModel Physical32 OpenCL
+    %1 = OpTypeVoid
+    %2 = OpTypeImage %1 3D 0 0 0 0 Unknown
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("In the OpenCL environment, the optional "
+                        "Access Qualifier must be present."
+                        "\n  %2 = OpTypeImage %void 3D 0 0 0 0 Unknown\n"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools