spirv-reader: Convert handle types

Synthesize Tint types for handle variables and function parameters.

Bug: tint:109
Change-Id: I3c155e03b154c5ebf46e79c96a6e2b054dbca9b4
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/33341
Reviewed-by: David Neto <dneto@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: David Neto <dneto@google.com>
Auto-Submit: David Neto <dneto@google.com>
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 91bfb21..f6a1489 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1602,6 +1602,145 @@
   }
 }
 
+ast::type::Type* ParserImpl::GetTypeForHandleVar(
+    const spvtools::opt::Instruction& var) {
+  // This can be a sampler or image.
+  // Determine it from the usage inferred for the variable.
+  const Usage& usage = handle_usage_[&var];
+  if (!usage.IsValid()) {
+    Fail() << "Invalid sampler or texture usage for variable "
+           << var.PrettyPrint() << "\n"
+           << usage;
+    return nullptr;
+  }
+  if (!usage.IsComplete()) {
+    // TODO(dneto): In SPIR-V you could statically reference a texture or
+    // sampler without using it in a way that gives us a clue on how to declare
+    // it.  In that case look at the underlying OpTypeSampler or OpTypeImage; in
+    // the OpTypeImage see if it has a format.  Sampled images alway have
+    // Unknown format.  For WGSL, storage images always have a format.  And then
+    // check for NonWritable or NonReadabl
+    Fail() << "Unsupported: Incomplete usage on samper or texture usage for "
+              "variable "
+           << var.PrettyPrint() << "\n"
+           << usage;
+    return nullptr;
+  }
+  ast::type::Type* ast_store_type = nullptr;
+  if (usage.IsSampler()) {
+    ast_store_type =
+        ctx_.type_mgr().Get(std::make_unique<ast::type::SamplerType>(
+            usage.IsComparisonSampler()
+                ? ast::type::SamplerKind::kComparisonSampler
+                : ast::type::SamplerKind::kSampler));
+  } else if (usage.IsTexture()) {
+    const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
+    if (!ptr_type) {
+      Fail() << "Invalid type for variable " << var.PrettyPrint();
+      return nullptr;
+    }
+    const auto* raw_image_type =
+        def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
+    if (!raw_image_type) {
+      Fail() << "Invalid pointer type for variable " << var.PrettyPrint();
+      return nullptr;
+    }
+    switch (raw_image_type->opcode()) {
+      case SpvOpTypeImage:  // The expected case.
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+        Fail() << "arrays of textures are not supported in WGSL; can't "
+                  "translate variable "
+               << var.PrettyPrint();
+        return nullptr;
+      default:
+        Fail() << "invalid type for image variable " << var.PrettyPrint();
+        return nullptr;
+    }
+    const spvtools::opt::analysis::Image* image_type =
+        type_mgr_->GetType(raw_image_type->result_id())->AsImage();
+    if (!image_type) {
+      Fail() << "internal error: Couldn't look up image type"
+             << raw_image_type->PrettyPrint();
+      return nullptr;
+    }
+
+    const ast::type::TextureDimension dim =
+        enum_converter_.ToDim(image_type->dim(), image_type->is_arrayed());
+    if (dim == ast::type::TextureDimension::kNone) {
+      return nullptr;
+    }
+
+    // WGSL textures are always formatted.  Unformatted textures are always
+    // sampled.
+    if (usage.IsSampledTexture() ||
+        (image_type->format() == SpvImageFormatUnknown)) {
+      // Make a sampled texture type.
+      auto* ast_sampled_component_type =
+          ConvertType(raw_image_type->GetSingleWordInOperand(0));
+
+      // Vulkan ignores the depth parameter on OpImage, so pay attention to the
+      // usage as well.  That is, it's valid for a Vulkan shader to use an
+      // OpImage variable with an OpImage*Dref* instruction.  In WGSL we must
+      // treat that as a depth texture.
+      if (image_type->depth() || usage.IsDepthTexture()) {
+        ast_store_type = ctx_.type_mgr().Get(
+            std::make_unique<ast::type::DepthTextureType>(dim));
+      } else if (image_type->is_multisampled()) {
+        // Multisampled textures are never depth textures.
+        ast_store_type = ctx_.type_mgr().Get(
+            std::make_unique<ast::type::MultisampledTextureType>(
+                dim, ast_sampled_component_type));
+      } else {
+        ast_store_type =
+            ctx_.type_mgr().Get(std::make_unique<ast::type::SampledTextureType>(
+                dim, ast_sampled_component_type));
+      }
+    } else {
+      // Make a storage texture.
+      bool is_nonwritable = false;
+      bool is_nonreadable = false;
+      for (const auto& deco : GetDecorationsFor(var.result_id())) {
+        if (deco.size() != 1) {
+          continue;
+        }
+        if (deco[0] == SpvDecorationNonWritable) {
+          is_nonwritable = true;
+        }
+        if (deco[0] == SpvDecorationNonReadable) {
+          is_nonreadable = true;
+        }
+      }
+      if (is_nonwritable && is_nonreadable) {
+        Fail() << "storage image variable is both NonWritable and NonReadable"
+               << var.PrettyPrint();
+      }
+      if (!is_nonwritable && !is_nonreadable) {
+        Fail()
+            << "storage image variable is neither NonWritable nor NonReadable"
+            << var.PrettyPrint();
+      }
+      const auto access = is_nonwritable ? ast::AccessControl::kReadOnly
+                                         : ast::AccessControl::kWriteOnly;
+      const auto format = enum_converter_.ToImageFormat(image_type->format());
+      if (format == ast::type::ImageFormat::kNone) {
+        return nullptr;
+      }
+      ast_store_type = ctx_.type_mgr().Get(
+          std::make_unique<ast::type::StorageTextureType>(dim, access, format));
+    }
+  } else {
+    Fail() << "unsupported: UniformConstant variable is not a recognized "
+              "sampler or texture"
+           << var.PrettyPrint();
+    return nullptr;
+  }
+  // Form the pointer type.
+  return ctx_.type_mgr().Get(std::make_unique<ast::type::PointerType>(
+      ast_store_type, ast::StorageClass::kUniformConstant));
+}
+
 bool ParserImpl::RegisterHandleUsage() {
   if (!success_) {
     return false;
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index f0a23ca..bfafb4b 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -405,6 +405,14 @@
   /// @returns the handle usage, or an empty usage object.
   Usage GetHandleUsage(uint32_t id) const;
 
+  /// Returns the AST type for the pointer-to-sampler or pointer-to-texture type
+  /// for the given variable in UniformConstant storage class.  Returns null and
+  /// emits an error on failure.
+  /// @param var the OpVariable instruction
+  /// @returns the Tint AST type for the poiner-to-{sampler|texture} or null on
+  /// error
+  ast::type::Type* GetTypeForHandleVar(const spvtools::opt::Instruction& var);
+
  private:
   /// Converts a specific SPIR-V type to a Tint type. Integer case
   ast::type::Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);