spirv-val: Combine Image Coordinate checks (#6494)

This originally came out of
https://gitlab.khronos.org/spirv/SPIR-V/-/issues/905 where we found a
driver that crashed passing it 16-bit floats as the coordinate

I tried to combine the logic and went through each type ... I am not
sure why/if there is a difference between Vulkan/OpenCL with respect to
allowing Float vs Int (but found 1 that we have active tests for
https://gitlab.khronos.org/spirv/SPIR-V/-/issues/908) ... the other is
`OpImageRead`/`OpImageWrite`, those were being validated incorrectly
according to the spec
diff --git a/source/opt/convert_to_half_pass.cpp b/source/opt/convert_to_half_pass.cpp
index 77558c7..a3527ff 100644
--- a/source/opt/convert_to_half_pass.cpp
+++ b/source/opt/convert_to_half_pass.cpp
@@ -22,6 +22,7 @@
 namespace opt {
 namespace {
 // Indices of operands in SPIR-V instructions
+constexpr int kImageSampleCoordinateIdInIdx = 1;
 constexpr int kImageSampleDrefIdInIdx = 2;
 }  // namespace
 
@@ -325,7 +326,7 @@
 
 bool ConvertToHalfPass::ProcessImageRef(Instruction* inst) {
   bool modified = false;
-  // If image reference, only need to convert dref args back to float32
+  // If image reference, some operands aren't allowed to be non-32 bit floats
   if (dref_image_ops_.count(inst->opcode()) != 0) {
     uint32_t dref_id = inst->GetSingleWordInOperand(kImageSampleDrefIdInIdx);
     if (converted_ids_.count(dref_id) > 0) {
@@ -338,6 +339,19 @@
       modified = true;
     }
   }
+  if (coordinate_image_ops_.count(inst->opcode()) != 0) {
+    uint32_t coordinate_id =
+        inst->GetSingleWordInOperand(kImageSampleCoordinateIdInIdx);
+    if (converted_ids_.count(coordinate_id) > 0) {
+      GenConvert(&coordinate_id, 32, inst);
+      if (status_ == Status::Failure) {
+        return false;
+      }
+      inst->SetInOperand(kImageSampleCoordinateIdInIdx, {coordinate_id});
+      get_def_use_mgr()->AnalyzeInstUse(inst);
+      modified = true;
+    }
+  }
   return modified;
 }
 
@@ -591,6 +605,30 @@
       spv::Op::OpImageSparseSampleProjDrefExplicitLod,
       spv::Op::OpImageSparseDrefGather,
   };
+  coordinate_image_ops_ = {
+      spv::Op::OpImageSampleImplicitLod,
+      spv::Op::OpImageSampleExplicitLod,
+      spv::Op::OpImageSampleDrefImplicitLod,
+      spv::Op::OpImageSampleDrefExplicitLod,
+      spv::Op::OpImageSampleProjImplicitLod,
+      spv::Op::OpImageSampleProjExplicitLod,
+      spv::Op::OpImageSampleProjDrefImplicitLod,
+      spv::Op::OpImageSampleProjDrefExplicitLod,
+      spv::Op::OpImageFetch,
+      spv::Op::OpImageGather,
+      spv::Op::OpImageDrefGather,
+      spv::Op::OpImageRead,
+      spv::Op::OpImageWrite,
+      spv::Op::OpImageQueryLod,
+      spv::Op::OpImageSparseSampleImplicitLod,
+      spv::Op::OpImageSparseSampleExplicitLod,
+      spv::Op::OpImageSparseSampleDrefImplicitLod,
+      spv::Op::OpImageSparseSampleDrefExplicitLod,
+      spv::Op::OpImageSparseFetch,
+      spv::Op::OpImageSparseGather,
+      spv::Op::OpImageSparseDrefGather,
+      spv::Op::OpImageSparseRead,
+  };
   closure_ops_ = {
       spv::Op::OpVectorExtractDynamic,
       spv::Op::OpVectorInsertDynamic,
diff --git a/source/opt/convert_to_half_pass.h b/source/opt/convert_to_half_pass.h
index 6e97a2d..c04f27a 100644
--- a/source/opt/convert_to_half_pass.h
+++ b/source/opt/convert_to_half_pass.h
@@ -145,6 +145,9 @@
   // Set of only dref sample operations
   std::unordered_set<spv::Op, hasher> dref_image_ops_;
 
+  // Set of only sample operations that have a Coordinate operand
+  std::unordered_set<spv::Op, hasher> coordinate_image_ops_;
+
   // Set of operations that can be marked as relaxed
   std::unordered_set<spv::Op, hasher> closure_ops_;
 
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index dad6bcc..ea39ea5 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -237,6 +237,23 @@
     return 3;
   }
 
+  if (opcode == spv::Op::OpImageQueryLod) {
+    return GetPlaneCoordSize(info);
+  }
+
+  if (opcode == spv::Op::OpImageTexelPointer) {
+    if (info.arrayed == 0) {
+      return GetPlaneCoordSize(info);
+    } else if (info.dim == spv::Dim::Dim1D) {
+      return 2;
+    } else if (info.dim == spv::Dim::Cube || info.dim == spv::Dim::Dim2D) {
+      return 3;
+    } else {
+      assert(false);
+      return 0;  // caught elsewhere
+    }
+  }
+
   return GetPlaneCoordSize(info) + info.arrayed + (IsProj(opcode) ? 1 : 0);
 }
 
@@ -1021,6 +1038,84 @@
   }
 }
 
+spv_result_t ValidateImageCoordinate(ValidationState_t& _,
+                                     const Instruction* inst,
+                                     const ImageTypeInfo& info,
+                                     uint32_t word_index) {
+  const spv::Op opcode = inst->opcode();
+  const uint32_t coord_type = _.GetOperandTypeId(inst, word_index);
+
+  const bool float_only =
+      opcode == spv::Op::OpImageSampleImplicitLod ||
+      opcode == spv::Op::OpImageSampleDrefImplicitLod ||
+      opcode == spv::Op::OpImageSampleDrefExplicitLod ||
+      opcode == spv::Op::OpImageSampleProjImplicitLod ||
+      opcode == spv::Op::OpImageSampleProjExplicitLod ||
+      opcode == spv::Op::OpImageSampleProjDrefImplicitLod ||
+      opcode == spv::Op::OpImageSampleProjDrefExplicitLod ||
+      opcode == spv::Op::OpImageGather ||
+      opcode == spv::Op::OpImageDrefGather ||
+      (opcode == spv::Op::OpImageQueryLod &&
+       !_.HasCapability(spv::Capability::Kernel)) ||
+      opcode == spv::Op::OpImageSparseSampleImplicitLod ||
+      opcode == spv::Op::OpImageSparseSampleDrefImplicitLod ||
+      opcode == spv::Op::OpImageSparseSampleDrefExplicitLod ||
+      opcode == spv::Op::OpImageSparseGather ||
+      opcode == spv::Op::OpImageSparseDrefGather;
+
+  const bool int_only = opcode == spv::Op::OpImageFetch ||
+                        opcode == spv::Op::OpImageSparseFetch ||
+                        opcode == spv::Op::OpImageTexelPointer;
+
+  const bool int_or_float = opcode == spv::Op::OpImageSampleExplicitLod ||
+                            opcode == spv::Op::OpImageSparseSampleExplicitLod ||
+                            opcode == spv::Op::OpImageRead ||
+                            opcode == spv::Op::OpImageWrite ||
+                            (opcode == spv::Op::OpImageQueryLod &&
+                             _.HasCapability(spv::Capability::Kernel)) ||
+                            opcode == spv::Op::OpImageSparseRead;
+
+  assert(float_only || int_only || int_or_float);
+
+  if (float_only && !_.IsFloatScalarOrVectorType(coord_type)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Coordinate to be a 32-bit float scalar or vector";
+  } else if (int_only && !_.IsIntScalarOrVectorType(coord_type)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Coordinate to be a 32-bit integer scalar or vector";
+  } else if (int_or_float) {
+    if (!_.IsFloatScalarOrVectorType(coord_type) &&
+        !_.IsIntScalarOrVectorType(coord_type)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Expected Coordinate to be a 32-bit integer or float scalar or "
+                "vector";
+    }
+  }
+
+  // Needs to be after we validate the scalar/vector
+  if (_.GetBitWidth(coord_type) != 32) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Coordinate to be a 32-bit scalar or vector";
+  }
+
+  const uint32_t min_coord_size = GetMinCoordSize(opcode, info);
+  const uint32_t actual_coord_size = _.GetDimension(coord_type);
+
+  if (opcode == spv::Op::OpImageTexelPointer) {
+    if (min_coord_size != actual_coord_size) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Expected Coordinate to have " << min_coord_size
+             << " components, but given " << actual_coord_size;
+    }
+  } else if (min_coord_size > actual_coord_size) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Coordinate to have at least " << min_coord_size
+           << " components, but given only " << actual_coord_size;
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateSampledImage(ValidationState_t& _,
                                   const Instruction* inst) {
   auto type_inst = _.FindDef(inst->type_id());
@@ -1222,38 +1317,9 @@
               "OpImageTexelPointer";
   }
 
-  const uint32_t coord_type = _.GetOperandTypeId(inst, 3);
-  if (!coord_type || !_.IsIntScalarOrVectorType(coord_type)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to be integer scalar or vector";
-  }
-
-  uint32_t expected_coord_size = 0;
-  if (info.arrayed == 0) {
-    expected_coord_size = GetPlaneCoordSize(info);
-  } else if (info.arrayed == 1) {
-    switch (info.dim) {
-      case spv::Dim::Dim1D:
-        expected_coord_size = 2;
-        break;
-      case spv::Dim::Cube:
-      case spv::Dim::Dim2D:
-        expected_coord_size = 3;
-        break;
-      default:
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected Image 'Dim' must be one of 1D, 2D, or Cube when "
-                  "Arrayed is 1";
-        break;
-    }
-  }
-
-  const uint32_t actual_coord_size = _.GetDimension(coord_type);
-  if (expected_coord_size != actual_coord_size) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to have " << expected_coord_size
-           << " components, but given " << actual_coord_size;
-  }
+  if (spv_result_t result =
+          ValidateImageCoordinate(_, inst, info, /* word_index = */ 3))
+    return result;
 
   const uint32_t sample_type = _.GetOperandTypeId(inst, 4);
   if (!sample_type || !_.IsIntScalarType(sample_type)) {
@@ -1344,29 +1410,9 @@
     }
   }
 
-  const uint32_t coord_type = _.GetOperandTypeId(inst, 3);
-  if ((opcode == spv::Op::OpImageSampleExplicitLod ||
-       opcode == spv::Op::OpImageSparseSampleExplicitLod) &&
-      _.HasCapability(spv::Capability::Kernel)) {
-    if (!_.IsFloatScalarOrVectorType(coord_type) &&
-        !_.IsIntScalarOrVectorType(coord_type)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Coordinate to be int or float scalar or vector";
-    }
-  } else {
-    if (!_.IsFloatScalarOrVectorType(coord_type)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Coordinate to be float scalar or vector";
-    }
-  }
-
-  const uint32_t min_coord_size = GetMinCoordSize(opcode, info);
-  const uint32_t actual_coord_size = _.GetDimension(coord_type);
-  if (min_coord_size > actual_coord_size) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to have at least " << min_coord_size
-           << " components, but given only " << actual_coord_size;
-  }
+  if (spv_result_t result =
+          ValidateImageCoordinate(_, inst, info, /* word_index = */ 3))
+    return result;
 
   const uint32_t mask = inst->words().size() <= 5 ? 0 : inst->word(5);
 
@@ -1453,19 +1499,9 @@
            << GetActualResultTypeStr(opcode);
   }
 
-  const uint32_t coord_type = _.GetOperandTypeId(inst, 3);
-  if (!_.IsFloatScalarOrVectorType(coord_type)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to be float scalar or vector";
-  }
-
-  const uint32_t min_coord_size = GetMinCoordSize(opcode, info);
-  const uint32_t actual_coord_size = _.GetDimension(coord_type);
-  if (min_coord_size > actual_coord_size) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to have at least " << min_coord_size
-           << " components, but given only " << actual_coord_size;
-  }
+  if (spv_result_t result =
+          ValidateImageCoordinate(_, inst, info, /* word_index = */ 3))
+    return result;
 
   if (spv_result_t result = ValidateImageDref(_, inst, info)) return result;
 
@@ -1527,19 +1563,9 @@
            << "Expected Image 'Sampled' parameter to be 1";
   }
 
-  const uint32_t coord_type = _.GetOperandTypeId(inst, 3);
-  if (!_.IsIntScalarOrVectorType(coord_type)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to be int scalar or vector";
-  }
-
-  const uint32_t min_coord_size = GetMinCoordSize(opcode, info);
-  const uint32_t actual_coord_size = _.GetDimension(coord_type);
-  if (min_coord_size > actual_coord_size) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to have at least " << min_coord_size
-           << " components, but given only " << actual_coord_size;
-  }
+  if (spv_result_t result =
+          ValidateImageCoordinate(_, inst, info, /* word_index = */ 3))
+    return result;
 
   if (spv_result_t result =
           ValidateImageOperands(_, inst, info, /* word_index = */ 6))
@@ -1607,19 +1633,9 @@
            << "Expected Image 'Dim' to be 2D, Cube, or Rect";
   }
 
-  const uint32_t coord_type = _.GetOperandTypeId(inst, 3);
-  if (!_.IsFloatScalarOrVectorType(coord_type)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to be float scalar or vector";
-  }
-
-  const uint32_t min_coord_size = GetMinCoordSize(opcode, info);
-  const uint32_t actual_coord_size = _.GetDimension(coord_type);
-  if (min_coord_size > actual_coord_size) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to have at least " << min_coord_size
-           << " components, but given only " << actual_coord_size;
-  }
+  if (spv_result_t result =
+          ValidateImageCoordinate(_, inst, info, /* word_index = */ 3))
+    return result;
 
   if (opcode == spv::Op::OpImageGather ||
       opcode == spv::Op::OpImageSparseGather) {
@@ -1749,19 +1765,9 @@
   if (spv_result_t result = ValidateImageReadWrite(_, inst, info))
     return result;
 
-  const uint32_t coord_type = _.GetOperandTypeId(inst, 3);
-  if (!_.IsIntScalarOrVectorType(coord_type)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to be int scalar or vector";
-  }
-
-  const uint32_t min_coord_size = GetMinCoordSize(opcode, info);
-  const uint32_t actual_coord_size = _.GetDimension(coord_type);
-  if (min_coord_size > actual_coord_size) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to have at least " << min_coord_size
-           << " components, but given only " << actual_coord_size;
-  }
+  if (spv_result_t result =
+          ValidateImageCoordinate(_, inst, info, /* word_index = */ 3))
+    return result;
 
   if (spvIsVulkanEnv(_.context()->target_env)) {
     if (info.format == spv::ImageFormat::Unknown &&
@@ -1806,19 +1812,9 @@
   if (spv_result_t result = ValidateImageReadWrite(_, inst, info))
     return result;
 
-  const uint32_t coord_type = _.GetOperandTypeId(inst, 1);
-  if (!_.IsIntScalarOrVectorType(coord_type)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to be int scalar or vector";
-  }
-
-  const uint32_t min_coord_size = GetMinCoordSize(inst->opcode(), info);
-  const uint32_t actual_coord_size = _.GetDimension(coord_type);
-  if (min_coord_size > actual_coord_size) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to have at least " << min_coord_size
-           << " components, but given only " << actual_coord_size;
-  }
+  if (spv_result_t result =
+          ValidateImageCoordinate(_, inst, info, /* word_index = */ 1))
+    return result;
 
   // because it needs to match with 'Sampled Type' the Texel can't be a boolean
   const uint32_t texel_type = _.GetOperandTypeId(inst, 2);
@@ -2109,27 +2105,9 @@
            << "Image 'Dim' must be 1D, 2D, 3D or Cube";
   }
 
-  const uint32_t coord_type = _.GetOperandTypeId(inst, 3);
-  if (_.HasCapability(spv::Capability::Kernel)) {
-    if (!_.IsFloatScalarOrVectorType(coord_type) &&
-        !_.IsIntScalarOrVectorType(coord_type)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Coordinate to be int or float scalar or vector";
-    }
-  } else {
-    if (!_.IsFloatScalarOrVectorType(coord_type)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Coordinate to be float scalar or vector";
-    }
-  }
-
-  const uint32_t min_coord_size = GetPlaneCoordSize(info);
-  const uint32_t actual_coord_size = _.GetDimension(coord_type);
-  if (min_coord_size > actual_coord_size) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Coordinate to have at least " << min_coord_size
-           << " components, but given only " << actual_coord_size;
-  }
+  if (spv_result_t result =
+          ValidateImageCoordinate(_, inst, info, /* word_index = */ 3))
+    return result;
 
   // The operand is a sampled image.
   // The sampled image type is already checked to be parameterized by an image
diff --git a/test/opt/convert_relaxed_to_half_test.cpp b/test/opt/convert_relaxed_to_half_test.cpp
index c577404..169ab29 100644
--- a/test/opt/convert_relaxed_to_half_test.cpp
+++ b/test/opt/convert_relaxed_to_half_test.cpp
@@ -514,28 +514,30 @@
 %14 = OpFAdd %half %55 %56
 %16 = OpCompositeConstruct %v2half %13 %14
 %58 = OpFConvert %float %14
-%18 = OpImageSampleDrefImplicitLod %float %46 %16 %58
+%59 = OpFConvert %v2float %16
+%18 = OpImageSampleDrefImplicitLod %float %46 %59 %58
 %48 = OpLoad %27 %g_tTex1df4
 %49 = OpLoad %29 %g_sSamp
 %50 = OpSampledImage %31 %48 %49
-%59 = OpFConvert %half %12
-%60 = OpFConvert %half %float_0_200000003
-%15 = OpFMul %half %59 %60
+%60 = OpFConvert %half %12
+%61 = OpFConvert %half %float_0_200000003
+%15 = OpFMul %half %60 %61
 %51 = OpAccessChain %_ptr_Uniform_float %_ %int_1
 %17 = OpLoad %float %51
-%61 = OpFConvert %half %17
-%62 = OpFConvert %half %float_0_200000003
-%19 = OpFAdd %half %61 %62
+%62 = OpFConvert %half %17
+%63 = OpFConvert %half %float_0_200000003
+%19 = OpFAdd %half %62 %63
 %20 = OpCompositeConstruct %v2half %15 %19
-%63 = OpFConvert %float %19
-%21 = OpImageSampleDrefImplicitLod %float %50 %20 %63
-%64 = OpFConvert %half %18
-%65 = OpFConvert %half %21
-%22 = OpFAdd %half %64 %65
-%66 = OpFConvert %half %float_0_5
-%23 = OpFMul %half %22 %66
-%67 = OpFConvert %float %23
-OpStore %_entryPointOutput_Color %67
+%64 = OpFConvert %float %19
+%65 = OpFConvert %v2float %20
+%21 = OpImageSampleDrefImplicitLod %float %50 %65 %64
+%66 = OpFConvert %half %18
+%67 = OpFConvert %half %21
+%22 = OpFAdd %half %66 %67
+%68 = OpFConvert %half %float_0_5
+%23 = OpFMul %half %22 %68
+%69 = OpFConvert %float %23
+OpStore %_entryPointOutput_Color %69
 OpReturn
 OpFunctionEnd
 )";
@@ -1616,6 +1618,7 @@
 TEST_F(ConvertToHalfTest, PreserveImageOperandPrecision) {
   // Ensure that a non-relaxed texture coordinate does not get relaxed nor
   // converted to half precision if the image instruction is marked relaxed.
+  // Update - coordinates are not allowed to be 16-bit regardless
 
   // Also ensure that a relaxed local variable does get converted to half
   // precision before being passed to an image opeartor.
@@ -1639,7 +1642,7 @@
                OpCapability Float16
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main" %13 %25
+               OpEntryPoint Fragment %4 "main" %13 %26
                OpExecutionMode %4 OriginUpperLeft
                OpSource ESSL 310
                OpDecorate %9 RelaxedPrecision
@@ -1648,8 +1651,8 @@
                OpDecorate %17 DescriptorSet 3
                OpDecorate %17 Binding 0
                OpDecorate %18 RelaxedPrecision
-               OpDecorate %23 RelaxedPrecision
-               OpDecorate %25 Location 10
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %26 Location 10
           %2 = OpTypeVoid
           %3 = OpTypeFunction %2
           %6 = OpTypeFloat 32
@@ -1669,13 +1672,13 @@
          %17 = OpVariable %16 UniformConstant
          %19 = OpTypeVector %6 2
 ;CHECK: [[vec2_t:%\w+]] = OpTypeVector [[float32_t]] 2
-         %24 = OpTypePointer Input %7
+         %25 = OpTypePointer Input %7
 ;CHECK: [[input_ptr_t:%\w+]] = OpTypePointer Input [[vec4_t]]
-         %25 = OpVariable %24 Input
-         %29 = OpTypeFloat 16
+         %26 = OpVariable %25 Input
+         %30 = OpTypeFloat 16
 ;CHECK: [[float16_t:%\w+]] = OpTypeFloat 16
-         %30 = OpTypeVector %29 4
-         %33 = OpTypeVector %29 2
+         %31 = OpTypeVector %30 4
+         %34 = OpTypeVector %30 2
 ;CHECK: [[vec2_16b_t:%\w+]] = OpTypeVector [[float16_t]] 2
           %4 = OpFunction %2 None %3
           %5 = OpLabel
@@ -1686,26 +1689,27 @@
                OpStore %9 %11
          %18 = OpLoad %15 %17
          %20 = OpLoad %7 %9
-         %31 = OpFConvert %30 %20
-         %32 = OpFConvert %30 %20
+         %32 = OpFConvert %31 %20
+         %33 = OpFConvert %31 %20
 
-; The first sample op should get a 16b coordinate
-         %21 = OpVectorShuffle %33 %31 %32 0 1
+; The first sample op should get a 32b coordinate, even if relaxed
+         %21 = OpVectorShuffle %34 %32 %33 0 1
 ;CHECK: [[uv_16b:%\w+]] = OpVectorShuffle [[vec2_16b_t]]
-         %22 = OpImageSampleImplicitLod %7 %18 %21
-;CHECK: OpImageSampleImplicitLod [[vec4_t]] {{%\w+}} [[uv_16b]]
+         %22 = OpFConvert %19 %21
+         %23 = OpImageSampleImplicitLod %7 %18 %22
+;CHECK: OpImageSampleImplicitLod [[vec4_t]] {{%\w+}} {{%\w+}}
 
-               OpStore %13 %22
-         %23 = OpLoad %15 %17
-         %26 = OpLoad %7 %25
+               OpStore %13 %23
+         %24 = OpLoad %15 %17
+         %27 = OpLoad %7 %26
 
 ; The second sample op should get a 32b coordinate
-         %27 = OpVectorShuffle %19 %26 %26 0 1
+         %28 = OpVectorShuffle %19 %27 %27 0 1
 ;CHECK: [[uv_32b:%\w+]] = OpVectorShuffle [[vec2_t]]
-         %28 = OpImageSampleImplicitLod %7 %23 %27
+         %29 = OpImageSampleImplicitLod %7 %24 %28
 ;CHECK: OpImageSampleImplicitLod [[vec4_t]] {{%\w+}} [[uv_32b]]
 
-               OpStore %13 %28
+               OpStore %13 %29
                OpReturn
                OpFunctionEnd
   )";
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index 106e097..c17b822 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -1493,8 +1493,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be integer scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit integer scalar or vector"));
 }
 
 TEST_F(ValidateImage, ImageTexelPointerImageCoordSizeBad) {
@@ -1652,8 +1653,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be float scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit float scalar or vector"));
 }
 
 TEST_F(ValidateImage, SampleImplicitLodCoordinateSizeTooSmall) {
@@ -1816,7 +1818,22 @@
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be float scalar or vector"));
+              HasSubstr("Expected Coordinate to be a 32-bit integer or float "
+                        "scalar or vector"));
+}
+
+TEST_F(ValidateImage, SampleExplicitLodWrongCoordinateType16Bit) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f16vec2_00 Lod %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Coordinate to be a 32-bit scalar or vector"));
 }
 
 TEST_F(ValidateImage, SampleExplicitLodCoordinateSizeTooSmall) {
@@ -2515,8 +2532,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be float scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit float scalar or vector"));
 }
 
 TEST_F(ValidateImage, SampleProjExplicitLodCoordinateSizeTooSmall) {
@@ -2651,8 +2669,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be float scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit float scalar or vector"));
 }
 
 TEST_F(ValidateImage, SampleProjImplicitLodCoordinateSizeTooSmall) {
@@ -2777,8 +2796,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be float scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit float scalar or vector"));
 }
 
 TEST_F(ValidateImage, SampleDrefImplicitLodCoordinateSizeTooSmall) {
@@ -2934,8 +2954,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be float scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit float scalar or vector"));
 }
 
 TEST_F(ValidateImage, SampleDrefExplicitLodCoordinateSizeTooSmall) {
@@ -3074,8 +3095,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be float scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit float scalar or vector"));
 }
 
 TEST_F(ValidateImage, SampleProjDrefImplicitLodCoordinateSizeTooSmall) {
@@ -3213,8 +3235,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be float scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit float scalar or vector"));
 }
 
 TEST_F(ValidateImage, SampleProjDrefExplicitLodCoordinateSizeTooSmall) {
@@ -3377,8 +3400,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be int scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit integer scalar or vector"));
 }
 
 TEST_F(ValidateImage, FetchCoordinateSizeTooSmall) {
@@ -3552,8 +3576,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be float scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit float scalar or vector"));
 }
 
 TEST_F(ValidateImage, GatherCoordinateSizeTooSmall) {
@@ -4043,7 +4068,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateImage, ReadWrongCoordinateType) {
+TEST_F(ValidateImage, ReadFloatCoordinateType) {
   const std::string body = R"(
 %img = OpLoad %type_image_u32_2d_0002 %uniform_image_u32_2d_0002
 %res1 = OpImageRead %u32vec4 %img %f32vec2_00
@@ -4051,9 +4076,20 @@
 
   const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
   CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, ReadWrongCoordinateType) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0002 %uniform_image_u32_2d_0002
+%res1 = OpImageRead %u32vec4 %img %f16vec2_00
+)";
+
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+  CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be int scalar or vector"));
+              HasSubstr("Expected Coordinate to be a 32-bit scalar or vector"));
 }
 
 TEST_F(ValidateImage, ReadCoordinateSizeTooSmall) {
@@ -4217,7 +4253,7 @@
               HasSubstr("Expected Image 'Sampled' parameter to be 0 or 2"));
 }
 
-TEST_F(ValidateImage, WriteWrongCoordinateType) {
+TEST_F(ValidateImage, WriteFloatCoordinateType) {
   const std::string body = R"(
 %img = OpLoad %type_image_u32_2d_0002 %uniform_image_u32_2d_0002
 OpImageWrite %img %f32vec2_00 %u32vec4_0123
@@ -4225,9 +4261,20 @@
 
   const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n";
   CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, WriteWrongCoordinateType) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0002 %uniform_image_u32_2d_0002
+OpImageWrite %img %f16vec2_00 %u32vec4_0123
+)";
+
+  const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n";
+  CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be int scalar or vector"));
+              HasSubstr("Expected Coordinate to be a 32-bit scalar or vector"));
 }
 
 TEST_F(ValidateImage, WriteCoordinateSizeTooSmall) {
@@ -4843,8 +4890,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Coordinate to be float scalar or vector"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Coordinate to be a 32-bit float scalar or vector"));
 }
 
 TEST_F(ValidateImage, QueryLodCoordinateSizeTooSmall) {