spirv-reader: convert signedness of texturing result when needed

Fixed: tint:382
Change-Id: I786e1f5d5d122a82ef29c733e514bf3b45651c5d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/35180
Commit-Queue: David Neto <dneto@google.com>
Auto-Submit: David Neto <dneto@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 607dc39..6a0c855 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -3940,9 +3940,32 @@
 
   if (inst.type_id() != 0) {
     // It returns a value.
+    ast::Expression* value = call_expr;
+
+    // If necessary, convert the result to the signedness of the instruction
+    // result type. Compare the SPIR-V image's sampled component type with the
+    // component of the result type of the SPIR-V instruction.
     auto* result_type = parser_impl_.ConvertType(inst.type_id());
-    // TODO(dneto): Convert result signedness if needed. crbug.com/tint/382
-    EmitConstDefOrWriteToHoistedVar(inst, {result_type, call_expr});
+    auto* result_component_type = result_type;
+    if (auto* result_vector_type = result_type->As<ast::type::Vector>()) {
+      result_component_type = result_vector_type->type();
+    }
+    auto* spirv_image_type =
+        parser_impl_.GetSpirvTypeForHandleMemoryObjectDeclaration(*image);
+    if (!spirv_image_type || (spirv_image_type->opcode() != SpvOpTypeImage)) {
+      return Fail() << "invalid image type for image memory object declaration "
+                    << image->PrettyPrint();
+    }
+    auto* expected_component_type =
+        parser_impl_.ConvertType(spirv_image_type->GetSingleWordInOperand(0));
+    if (expected_component_type != result_component_type) {
+      // This occurs if one is signed integer and the other is unsigned integer,
+      // or vice versa. Perform a bitcast.
+      value =
+          ast_module_.create<ast::BitcastExpression>(result_type, call_expr);
+    }
+
+    EmitConstDefOrWriteToHoistedVar(inst, {result_type, value});
   } else {
     // It's an image write. No value is returned, so make a statement out
     // of the call.
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 3543a77..71e9319 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1683,7 +1683,8 @@
   }
 }
 
-const spvtools::opt::Instruction* ParserImpl::GetSpirvTypeForHandleVar(
+const spvtools::opt::Instruction*
+ParserImpl::GetSpirvTypeForHandleMemoryObjectDeclaration(
     const spvtools::opt::Instruction& var) {
   if (!success()) {
     return nullptr;
@@ -1700,14 +1701,16 @@
 
   // Get the SPIR-V handle type.
   const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
-  if (!ptr_type) {
-    Fail() << "Invalid type for variable " << var.PrettyPrint();
+  if (!ptr_type || (ptr_type->opcode() != SpvOpTypePointer)) {
+    Fail() << "Invalid type for variable or function parameter "
+           << var.PrettyPrint();
     return nullptr;
   }
   const auto* raw_handle_type =
       def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
   if (!raw_handle_type) {
-    Fail() << "Invalid pointer type for variable " << var.PrettyPrint();
+    Fail() << "Invalid pointer type for variable or function parameter "
+           << var.PrettyPrint();
     return nullptr;
   }
   switch (raw_handle_type->opcode()) {
@@ -1719,11 +1722,12 @@
     case SpvOpTypeRuntimeArray:
       Fail()
           << "arrays of textures or samplers are not supported in WGSL; can't "
-             "translate variable "
+             "translate variable or function parameter: "
           << var.PrettyPrint();
       return nullptr;
     default:
-      Fail() << "invalid type for image or sampler variable: "
+      Fail() << "invalid type for image or sampler variable or function "
+                "parameter: "
              << var.PrettyPrint();
       return nullptr;
   }
@@ -1738,7 +1742,7 @@
   }
 
   const spvtools::opt::Instruction* raw_handle_type =
-      GetSpirvTypeForHandleVar(var);
+      GetSpirvTypeForHandleMemoryObjectDeclaration(var);
   if (!raw_handle_type) {
     return nullptr;
   }
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 4d3b22a..2e9e9d7 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -431,11 +431,13 @@
   Usage GetHandleUsage(uint32_t id) const;
 
   /// Returns the SPIR-V type for the sampler or image type for the given
-  /// variable in UniformConstant storage class.  Returns null and emits an
+  /// variable in UniformConstant storage class, or function parameter pointing
+  /// into the UniformConstant storage class .  Returns null and emits an
   /// error on failure.
-  /// @param var the OpVariable instruction
+  /// @param var the OpVariable instruction or OpFunctionParameter
   /// @returns the Tint AST type for the sampler or texture, or null on error
-  const spvtools::opt::Instruction* GetSpirvTypeForHandleVar(
+  const spvtools::opt::Instruction*
+  GetSpirvTypeForHandleMemoryObjectDeclaration(
       const spvtools::opt::Instruction& var);
 
   /// Returns the AST type for the pointer-to-sampler or pointer-to-texture type
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
index cb40386..81726fe 100644
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -1229,6 +1229,14 @@
      OpName %vf12 "vf12"
      OpName %vf123 "vf123"
      OpName %vf1234 "vf1234"
+     OpName %u1 "u1"
+     OpName %vu12 "vu12"
+     OpName %vu123 "vu123"
+     OpName %vu1234 "vu1234"
+     OpName %i1 "i1"
+     OpName %vi12 "vi12"
+     OpName %vi123 "vi123"
+     OpName %vi1234 "vi1234"
      OpName %coords1 "coords1"
      OpName %coords12 "coords12"
      OpName %coords123 "coords123"
@@ -1261,6 +1269,16 @@
      %vf123 = OpCopyObject %v3float %the_vf123
      %vf1234 = OpCopyObject %v4float %the_vf1234
 
+     %i1 = OpCopyObject %int %int_1
+     %vi12 = OpCopyObject %v2int %the_vi12
+     %vi123 = OpCopyObject %v3int %the_vi123
+     %vi1234 = OpCopyObject %v4int %the_vi1234
+
+     %u1 = OpCopyObject %uint %uint_1
+     %vu12 = OpCopyObject %v2uint %the_vu12
+     %vu123 = OpCopyObject %v3uint %the_vu123
+     %vu1234 = OpCopyObject %v4uint %the_vu1234
+
      %coords1 = OpCopyObject %float %float_1
      %coords12 = OpCopyObject %v2float %vf12
      %coords123 = OpCopyObject %v3float %vf123
@@ -2889,7 +2907,7 @@
     SpvParserTest_ImageAccessTest,
     ::testing::ValuesIn(std::vector<ImageAccessCase>{
         // OpImageFetch with no extra params
-        {"%float 2D 0 0 0 1 Rgba32f", "%99 = OpImageFetch %v4float %im %vu12",
+        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vu12",
          R"(DecoratedVariable{
     Decorations{
       SetDecoration{2}
@@ -2916,7 +2934,7 @@
       }
     })"},
         // OpImageFetch with ConstOffset
-        {"%float 2D 0 0 0 1 Rgba32f",
+        {"%float 2D 0 0 0 1 Unknown",
          "%99 = OpImageFetch %v4float %im %vu12 ConstOffset %offsets2d",
          R"(DecoratedVariable{
     Decorations{
@@ -2946,6 +2964,502 @@
     })"}}));
 
 INSTANTIATE_TEST_SUITE_P(
+    ConvertResultSignedness,
+    SpvParserTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // Valid SPIR-V only has:
+        //      float scalar sampled type vs. floating result
+        //      integral scalar sampled type vs. integral result
+        // Any of the sampling, reading, or fetching use the same codepath.
+
+        // We'll test with:
+        //     OpImageFetch
+        //     OpImageRead
+        //     OpImageSampleImplicitLod - representative of sampling
+
+        //
+        // OpImageRead
+        //
+
+        // OpImageFetch requires no conversion, float -> v4float
+        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__f32
+        {
+          Call[not set]{
+            Identifier[not set]{textureLoad}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{vu12}
+            )
+          }
+        }
+      }
+    })"},
+        // OpImageFetch requires no conversion, uint -> v4uint
+        {"%uint 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4uint %im %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__u32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__u32
+        {
+          Call[not set]{
+            Identifier[not set]{textureLoad}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{vu12}
+            )
+          }
+        }
+      }
+    })"},
+        // OpImageFetch requires conversion, uint -> v4int
+        {"%uint 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4int %im %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__u32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__i32
+        {
+          Bitcast[not set]<__vec_4__i32>{
+            Call[not set]{
+              Identifier[not set]{textureLoad}
+              (
+                Identifier[not set]{x_20}
+                Identifier[not set]{vu12}
+              )
+            }
+          }
+        }
+      }
+    })"},
+        // OpImageFetch requires no conversion, int -> v4int
+        {"%int 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4int %im %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__i32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureLoad}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{vu12}
+            )
+          }
+        }
+      }
+    })"},
+        // OpImageFetch requires conversion, int -> v4uint
+        {"%int 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4uint %im %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__i32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__u32
+        {
+          Bitcast[not set]<__vec_4__u32>{
+            Call[not set]{
+              Identifier[not set]{textureLoad}
+              (
+                Identifier[not set]{x_20}
+                Identifier[not set]{vu12}
+              )
+            }
+          }
+        }
+      }
+    })"},
+
+        //
+        // OpImageRead
+        //
+
+        // OpImageRead requires no conversion, float -> v4float
+        {"%float 2D 0 0 0 1 Rgba32f", "%99 = OpImageRead %v4float %im %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __storage_texture_read_only_2d_rgba32float
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__f32
+        {
+          Call[not set]{
+            Identifier[not set]{textureLoad}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{vu12}
+            )
+          }
+        }
+      }
+    })"},
+        // OpImageRead requires no conversion, uint -> v4uint
+        {"%uint 2D 0 0 0 1 Rgba32ui", "%99 = OpImageRead %v4uint %im %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __storage_texture_read_only_2d_rgba32uint
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__u32
+        {
+          Call[not set]{
+            Identifier[not set]{textureLoad}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{vu12}
+            )
+          }
+        }
+      }
+    })"},
+        // OpImageRead requires conversion, uint -> v4int
+        {"%uint 2D 0 0 0 1 Rgba32ui", "%99 = OpImageRead %v4int %im %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __storage_texture_read_only_2d_rgba32uint
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__i32
+        {
+          Bitcast[not set]<__vec_4__i32>{
+            Call[not set]{
+              Identifier[not set]{textureLoad}
+              (
+                Identifier[not set]{x_20}
+                Identifier[not set]{vu12}
+              )
+            }
+          }
+        }
+      }
+    })"},
+        // OpImageRead requires no conversion, int -> v4int
+        {"%int 2D 0 0 0 1 Rgba32i", "%99 = OpImageRead %v4int %im %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __storage_texture_read_only_2d_rgba32sint
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureLoad}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{vu12}
+            )
+          }
+        }
+      }
+    })"},
+        // OpImageRead requires conversion, int -> v4uint
+        {"%int 2D 0 0 0 1 Rgba32i", "%99 = OpImageRead %v4uint %im %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __storage_texture_read_only_2d_rgba32sint
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__u32
+        {
+          Bitcast[not set]<__vec_4__u32>{
+            Call[not set]{
+              Identifier[not set]{textureLoad}
+              (
+                Identifier[not set]{x_20}
+                Identifier[not set]{vu12}
+              )
+            }
+          }
+        }
+      }
+    })"},
+
+        //
+        // Sampling operations, using OpImageSampleImplicitLod as an example.
+        //
+
+        // OpImageSampleImplicitLod requires no conversion, float -> v4float
+        {"%float 2D 0 0 0 1 Unknown",
+         "%99 = OpImageSampleImplicitLod %v4float %sampled_image %vu12",
+         R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__f32
+        {
+          Call[not set]{
+            Identifier[not set]{textureSample}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              Identifier[not set]{vu12}
+            )
+          }
+        }
+      }
+    })"},
+        // OpImageSampleImplicitLod requires no conversion, uint -> v4uint
+        {"%uint 2D 0 0 0 1 Unknown",
+         "%99 = OpImageSampleImplicitLod %v4uint %sampled_image %vu12",
+         R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__u32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__u32
+        {
+          Call[not set]{
+            Identifier[not set]{textureSample}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              Identifier[not set]{vu12}
+            )
+          }
+        }
+      }
+    })"},
+        // OpImageSampleImplicitLod requires conversion, uint -> v4int
+        {"%uint 2D 0 0 0 1 Unknown",
+         "%99 = OpImageSampleImplicitLod %v4int %sampled_image %vu12",
+         R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__u32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__i32
+        {
+          Bitcast[not set]<__vec_4__i32>{
+            Call[not set]{
+              Identifier[not set]{textureSample}
+              (
+                Identifier[not set]{x_20}
+                Identifier[not set]{x_10}
+                Identifier[not set]{vu12}
+              )
+            }
+          }
+        }
+      }
+    })"},
+        // OpImageSampleImplicitLod requires no conversion, int -> v4int
+        {"%int 2D 0 0 0 1 Unknown",
+         "%99 = OpImageSampleImplicitLod %v4int %sampled_image %vu12",
+         R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__i32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureSample}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              Identifier[not set]{vu12}
+            )
+          }
+        }
+      }
+    })"},
+        // OpImageSampleImplicitLod requires conversion, int -> v4uint
+        {"%int 2D 0 0 0 1 Unknown",
+         "%99 = OpImageSampleImplicitLod %v4uint %sampled_image %vu12",
+         R"(DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__i32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __vec_4__u32
+        {
+          Bitcast[not set]<__vec_4__u32>{
+            Call[not set]{
+              Identifier[not set]{textureSample}
+              (
+                Identifier[not set]{x_20}
+                Identifier[not set]{x_10}
+                Identifier[not set]{vu12}
+              )
+            }
+          }
+        }
+      }
+    })"}}));
+
+INSTANTIATE_TEST_SUITE_P(
     // The SPIR-V result type could be integral but of different signedness
     // than the sampled texel type.  In these cases the result should be
     // converted to match the signedness of the SPIR-V result type.  This
@@ -3297,27 +3811,28 @@
 }
 )"}}}));
 
-INSTANTIATE_TEST_SUITE_P(BadInstructions,
-                         SpvParserTest_ImageCoordsTest,
-                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
-                             {"%float 1D 0 0 0 1 Unknown",
-                              "OpNop",
-                              "internal error: not an image access "
-                              "instruction: OpNop",
-                              {}},
-                             {"%float 1D 0 0 0 1 Unknown",
-                              "%50 = OpCopyObject %float %float_1",
-                              "internal error: couldn't find image for "
-                              "%50 = OpCopyObject %9 %36",
-                              {}},
-                             {"%float 1D 0 0 0 1 Unknown",
-                              "OpStore %float_var %float_1",
-                              "invalid type for image or sampler "
-                              "variable: %1 = OpVariable %2 Function",
-                              {}},
-                             // An example with a missing coordinate
-                             // won't assemble, so we skip it.
-                         }));
+INSTANTIATE_TEST_SUITE_P(
+    BadInstructions,
+    SpvParserTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 1D 0 0 0 1 Unknown",
+         "OpNop",
+         "internal error: not an image access "
+         "instruction: OpNop",
+         {}},
+        {"%float 1D 0 0 0 1 Unknown",
+         "%50 = OpCopyObject %float %float_1",
+         "internal error: couldn't find image for "
+         "%50 = OpCopyObject %9 %36",
+         {}},
+        {"%float 1D 0 0 0 1 Unknown",
+         "OpStore %float_var %float_1",
+         "invalid type for image or sampler "
+         "variable or function parameter: %1 = OpVariable %2 Function",
+         {}},
+        // An example with a missing coordinate
+        // won't assemble, so we skip it.
+    }));
 
 INSTANTIATE_TEST_SUITE_P(
     Bad_Coordinate,