Sampled images as read-only storage (#3295)

There are some cases where a variable that is declared as a sampled
image could be read only.  That is when the image type has sampled == 1.

Fixes #3288
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index a9d778a..9e54e7e 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -182,10 +182,27 @@
 bool Instruction::IsReadOnlyLoad() const {
   if (IsLoad()) {
     Instruction* address_def = GetBaseAddress();
-    if (!address_def || address_def->opcode() != SpvOpVariable) {
+    if (!address_def) {
       return false;
     }
-    return address_def->IsReadOnlyVariable();
+
+    if (address_def->opcode() == SpvOpVariable) {
+      if (address_def->IsReadOnlyVariable()) {
+        return true;
+      }
+    }
+
+    if (address_def->opcode() == SpvOpLoad) {
+      const analysis::Type* address_type =
+          context()->get_type_mgr()->GetType(address_def->type_id());
+      if (address_type->AsSampledImage() != nullptr) {
+        const auto* image_type =
+            address_type->AsSampledImage()->image_type()->AsImage();
+        if (image_type->sampled() == 1) {
+          return true;
+        }
+      }
+    }
   }
   return false;
 }
diff --git a/test/opt/value_table_test.cpp b/test/opt/value_table_test.cpp
index a0942cc..76e7f73 100644
--- a/test/opt/value_table_test.cpp
+++ b/test/opt/value_table_test.cpp
@@ -684,6 +684,50 @@
   vtable.GetValueNumber(inst);
 }
 
+TEST_F(ValueTableTest, RedundantSampledImageLoad) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %gl_FragColor
+               OpExecutionMode %main OriginLowerLeft
+               OpSource GLSL 330
+               OpName %main "main"
+               OpName %tex0 "tex0"
+               OpName %gl_FragColor "gl_FragColor"
+               OpDecorate %tex0 Location 0
+               OpDecorate %tex0 DescriptorSet 0
+               OpDecorate %tex0 Binding 0
+               OpDecorate %gl_FragColor Location 0
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+          %9 = OpTypeImage %float 2D 0 0 0 1 Unknown
+         %10 = OpTypeSampledImage %9
+%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
+       %tex0 = OpVariable %_ptr_UniformConstant_10 UniformConstant
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %13 = OpConstantNull %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+         %14 = OpUndef %v4float
+       %main = OpFunction %void None %6
+         %15 = OpLabel
+         %16 = OpLoad %10 %tex0
+         %17 = OpImageSampleProjImplicitLod %v4float %16 %13
+         %18 = OpImageSampleProjImplicitLod %v4float %16 %13
+         %19 = OpFAdd %v4float %18 %17
+               OpStore %gl_FragColor %19
+               OpReturn
+               OpFunctionEnd
+  )";
+  auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  ValueNumberTable vtable(context.get());
+  Instruction* load1 = context->get_def_use_mgr()->GetDef(17);
+  Instruction* load2 = context->get_def_use_mgr()->GetDef(18);
+  EXPECT_EQ(vtable.GetValueNumber(load1), vtable.GetValueNumber(load2));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools