[spirv][ir] Add a spirv.image type.

Add a type to mirror the OpTypeImage in SPIR-V.

Bug: 391485311
Change-Id: I3e9fd37325939b410b19621f35b9d826c7a02a0d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/233674
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/spirv/type/BUILD.bazel b/src/tint/lang/spirv/type/BUILD.bazel
index 6dd7a9d..506a5e7 100644
--- a/src/tint/lang/spirv/type/BUILD.bazel
+++ b/src/tint/lang/spirv/type/BUILD.bazel
@@ -40,10 +40,12 @@
   name = "type",
   srcs = [
     "explicit_layout_array.cc",
+    "image.cc",
     "sampled_image.cc",
   ],
   hdrs = [
     "explicit_layout_array.h",
+    "image.h",
     "sampled_image.h",
   ],
   deps = [
@@ -70,6 +72,7 @@
   alwayslink = True,
   srcs = [
     "explicit_layout_array_test.cc",
+    "image_test.cc",
     "sampled_image_test.cc",
   ],
   deps = [
diff --git a/src/tint/lang/spirv/type/BUILD.cmake b/src/tint/lang/spirv/type/BUILD.cmake
index fc93108..d4bad52 100644
--- a/src/tint/lang/spirv/type/BUILD.cmake
+++ b/src/tint/lang/spirv/type/BUILD.cmake
@@ -41,6 +41,8 @@
 tint_add_target(tint_lang_spirv_type lib
   lang/spirv/type/explicit_layout_array.cc
   lang/spirv/type/explicit_layout_array.h
+  lang/spirv/type/image.cc
+  lang/spirv/type/image.h
   lang/spirv/type/sampled_image.cc
   lang/spirv/type/sampled_image.h
 )
@@ -71,6 +73,7 @@
 ################################################################################
 tint_add_target(tint_lang_spirv_type_test test
   lang/spirv/type/explicit_layout_array_test.cc
+  lang/spirv/type/image_test.cc
   lang/spirv/type/sampled_image_test.cc
 )
 
diff --git a/src/tint/lang/spirv/type/BUILD.gn b/src/tint/lang/spirv/type/BUILD.gn
index f927a5b..affe251 100644
--- a/src/tint/lang/spirv/type/BUILD.gn
+++ b/src/tint/lang/spirv/type/BUILD.gn
@@ -47,6 +47,8 @@
   sources = [
     "explicit_layout_array.cc",
     "explicit_layout_array.h",
+    "image.cc",
+    "image.h",
     "sampled_image.cc",
     "sampled_image.h",
   ]
@@ -71,6 +73,7 @@
   tint_unittests_source_set("unittests") {
     sources = [
       "explicit_layout_array_test.cc",
+      "image_test.cc",
       "sampled_image_test.cc",
     ]
     deps = [
diff --git a/src/tint/lang/spirv/type/image.cc b/src/tint/lang/spirv/type/image.cc
new file mode 100644
index 0000000..cecf84d
--- /dev/null
+++ b/src/tint/lang/spirv/type/image.cc
@@ -0,0 +1,175 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/spirv/type/image.h"
+
+#include <sstream>
+
+#include "src/tint/lang/core/type/manager.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::spirv::type::Image);
+
+namespace tint::spirv::type {
+namespace {
+
+std::ostream& operator<<(std::ostream& out, const Image::Dim dim) {
+    switch (dim) {
+        case Image::Dim::k1D:
+            out << "1d";
+            break;
+        case Image::Dim::k2D:
+            out << "2d";
+            break;
+        case Image::Dim::k3D:
+            out << "3d";
+            break;
+        case Image::Dim::kCube:
+            out << "cube";
+            break;
+        case Image::Dim::kRect:
+            out << "rect";
+            break;
+        case Image::Dim::kBuffer:
+            out << "buffer";
+            break;
+        case Image::Dim::kSubpassData:
+            out << "subpass_data";
+            break;
+    }
+    return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Image::Depth depth) {
+    switch (depth) {
+        case Image::Depth::kNotDepth:
+            out << "not_depth";
+            break;
+        case Image::Depth::kDepth:
+            out << "depth";
+            break;
+        case Image::Depth::kUnknown:
+            out << "depth_unknown";
+            break;
+    }
+    return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Image::Arrayed arrayed) {
+    switch (arrayed) {
+        case Image::Arrayed::kNonArrayed:
+            out << "non_arrayed";
+            break;
+        case Image::Arrayed::kArrayed:
+            out << "arrayed";
+            break;
+    }
+    return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Image::MultiSampled ms) {
+    switch (ms) {
+        case Image::MultiSampled::kSingleSampled:
+            out << "single_sampled";
+            break;
+        case Image::MultiSampled::kMultiSampled:
+            out << "multi_sampled";
+            break;
+    }
+    return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Image::Sampled sampled) {
+    switch (sampled) {
+        case Image::Sampled::kKnownAtRuntime:
+            out << "sampled_known_at_runtime";
+            break;
+        case Image::Sampled::kSamplingCompatible:
+            out << "sampling_compatible";
+            break;
+        case Image::Sampled::kReadWriteOpCompatible:
+            out << "rw_op_compatible";
+            break;
+    }
+    return out;
+}
+
+}  // namespace
+
+Image::Image(const core::type::Type* sampled_type,
+             Dim dim,
+             Depth depth,
+             Arrayed arrayed,
+             MultiSampled ms,
+             Sampled sampled,
+             core::TexelFormat fmt,
+             core::Access access)
+    : Base(static_cast<size_t>(Hash(tint::TypeCode::Of<Image>().bits,
+                                    sampled_type,
+                                    dim,
+                                    depth,
+                                    arrayed,
+                                    ms,
+                                    sampled,
+                                    fmt,
+                                    access)),
+           core::type::Flags{}),
+      sampled_type_(sampled_type),
+      dim_(dim),
+      depth_(depth),
+      arrayed_(arrayed),
+      ms_(ms),
+      sampled_(sampled),
+      fmt_(fmt),
+      access_(access) {}
+
+bool Image::Equals(const UniqueNode& other) const {
+    if (auto* o = other.As<Image>()) {
+        return o->sampled_type_ == sampled_type_ && o->dim_ == dim_ && o->depth_ == depth_ &&
+               o->arrayed_ == arrayed_ && o->ms_ == ms_ && o->sampled_ == sampled_ &&
+               o->fmt_ == fmt_ && o->access_ == access_;
+    }
+    return false;
+}
+
+std::string Image::FriendlyName() const {
+    std::stringstream str;
+
+    str << "spirv.image<" << sampled_type_->FriendlyName();
+    str << ", " << dim_ << ", " << depth_ << ", " << arrayed_;
+    str << ", " << ms_ << ", " << sampled_ << ", " << fmt_ << ", " << access_;
+    str << ">";
+
+    return str.str();
+}
+
+Image* Image::Clone(core::type::CloneContext& ctx) const {
+    auto* sampled_type = sampled_type_->Clone(ctx);
+    return ctx.dst.mgr->Get<Image>(sampled_type, dim_, depth_, arrayed_, ms_, sampled_, fmt_,
+                                   access_);
+}
+
+}  // namespace tint::spirv::type
diff --git a/src/tint/lang/spirv/type/image.h b/src/tint/lang/spirv/type/image.h
new file mode 100644
index 0000000..7542c76
--- /dev/null
+++ b/src/tint/lang/spirv/type/image.h
@@ -0,0 +1,116 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_SPIRV_TYPE_IMAGE_H_
+#define SRC_TINT_LANG_SPIRV_TYPE_IMAGE_H_
+
+#include <string>
+
+#include "src/tint/lang/core/access.h"
+#include "src/tint/lang/core/texel_format.h"
+#include "src/tint/lang/core/type/type.h"
+
+namespace tint::spirv::type {
+
+/// Image represents an OpTypeImage in SPIR-V.
+class Image final : public Castable<Image, core::type::Type> {
+  public:
+    enum class Dim : uint8_t {
+        k1D = 0,
+        k2D = 1,
+        k3D = 2,
+        kCube = 3,
+        kRect = 4,
+        kBuffer = 5,
+        kSubpassData = 6,
+    };
+
+    enum class Depth : uint8_t {
+        kNotDepth = 0,
+        kDepth = 1,
+        kUnknown = 2,
+    };
+
+    enum class Arrayed : uint8_t {
+        kNonArrayed = 0,
+        kArrayed = 1,
+    };
+
+    enum class MultiSampled : uint8_t {
+        kSingleSampled = 0,
+        kMultiSampled = 1,
+    };
+
+    enum class Sampled : uint8_t {
+        kKnownAtRuntime = 0,
+        kSamplingCompatible = 1,
+        kReadWriteOpCompatible = 2,
+    };
+
+    /// Constructor
+    /// @param sampled_type the type of the components that result from sampling or reading
+    /// @param dim the image dimensionality
+    /// @param depth image depth information
+    /// @param arrayed image arrayed information
+    /// @param ms the image multisampled information
+    /// @param sampled the image sampled information
+    /// @param fmt the image format
+    /// @param access the image access qualifier
+    Image(const core::type::Type* sampled_type,
+          Dim dim,
+          Depth depth,
+          Arrayed arrayed,
+          MultiSampled ms,
+          Sampled sampled,
+          core::TexelFormat fmt,
+          core::Access access);
+
+    /// @param other the other node to compare against
+    /// @returns true if the this type is equal to @p other
+    bool Equals(const UniqueNode& other) const override;
+
+    /// @returns the friendly name for this type
+    std::string FriendlyName() const override;
+
+    /// @param ctx the clone context
+    /// @returns a clone of this type
+    Image* Clone(core::type::CloneContext& ctx) const override;
+
+  private:
+    const core::type::Type* sampled_type_;
+    Dim dim_;
+    Depth depth_;
+    Arrayed arrayed_;
+    MultiSampled ms_;
+    Sampled sampled_;
+    core::TexelFormat fmt_;
+    core::Access access_;
+};
+
+}  // namespace tint::spirv::type
+
+#endif  // SRC_TINT_LANG_SPIRV_TYPE_IMAGE_H_
diff --git a/src/tint/lang/spirv/type/image_test.cc b/src/tint/lang/spirv/type/image_test.cc
new file mode 100644
index 0000000..063a2f2
--- /dev/null
+++ b/src/tint/lang/spirv/type/image_test.cc
@@ -0,0 +1,149 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/spirv/type/image.h"
+
+#include <gtest/gtest.h>
+
+#include "src/tint/lang/core/type/i32.h"
+#include "src/tint/lang/core/type/u32.h"
+
+namespace tint::spirv::type {
+namespace {
+
+TEST(ImageTest, Equals) {
+    core::type::I32 i32;
+    core::type::U32 u32;
+    Image a{&i32,
+            Image::Dim::k1D,
+            Image::Depth::kNotDepth,
+            Image::Arrayed::kNonArrayed,
+            Image::MultiSampled::kSingleSampled,
+            Image::Sampled::kKnownAtRuntime,
+            core::TexelFormat::kRgba32Float,
+            core::Access::kRead};
+    Image b{&i32,
+            Image::Dim::k1D,
+            Image::Depth::kNotDepth,
+            Image::Arrayed::kNonArrayed,
+            Image::MultiSampled::kSingleSampled,
+            Image::Sampled::kKnownAtRuntime,
+            core::TexelFormat::kRgba32Float,
+            core::Access::kRead};
+    Image c{&u32,
+            Image::Dim::k1D,
+            Image::Depth::kNotDepth,
+            Image::Arrayed::kNonArrayed,
+            Image::MultiSampled::kSingleSampled,
+            Image::Sampled::kKnownAtRuntime,
+            core::TexelFormat::kRgba32Float,
+            core::Access::kRead};
+    Image d{&i32,
+            Image::Dim::k2D,
+            Image::Depth::kNotDepth,
+            Image::Arrayed::kNonArrayed,
+            Image::MultiSampled::kSingleSampled,
+            Image::Sampled::kKnownAtRuntime,
+            core::TexelFormat::kRgba32Float,
+            core::Access::kRead};
+    Image e{&i32,
+            Image::Dim::k1D,
+            Image::Depth::kDepth,
+            Image::Arrayed::kNonArrayed,
+            Image::MultiSampled::kSingleSampled,
+            Image::Sampled::kKnownAtRuntime,
+            core::TexelFormat::kRgba32Float,
+            core::Access::kRead};
+    Image f{&i32,
+            Image::Dim::k1D,
+            Image::Depth::kNotDepth,
+            Image::Arrayed::kArrayed,
+            Image::MultiSampled::kSingleSampled,
+            Image::Sampled::kKnownAtRuntime,
+            core::TexelFormat::kRgba32Float,
+            core::Access::kRead};
+    Image g{&i32,
+            Image::Dim::k1D,
+            Image::Depth::kNotDepth,
+            Image::Arrayed::kNonArrayed,
+            Image::MultiSampled::kMultiSampled,
+            Image::Sampled::kKnownAtRuntime,
+            core::TexelFormat::kRgba32Float,
+            core::Access::kRead};
+    Image h{&i32,
+            Image::Dim::k1D,
+            Image::Depth::kNotDepth,
+            Image::Arrayed::kNonArrayed,
+            Image::MultiSampled::kSingleSampled,
+            Image::Sampled::kSamplingCompatible,
+            core::TexelFormat::kRgba32Float,
+            core::Access::kRead};
+    Image i{&i32,
+            Image::Dim::k1D,
+            Image::Depth::kNotDepth,
+            Image::Arrayed::kNonArrayed,
+            Image::MultiSampled::kSingleSampled,
+            Image::Sampled::kKnownAtRuntime,
+            core::TexelFormat::kR32Float,
+            core::Access::kRead};
+    Image j{&i32,
+            Image::Dim::k1D,
+            Image::Depth::kNotDepth,
+            Image::Arrayed::kNonArrayed,
+            Image::MultiSampled::kSingleSampled,
+            Image::Sampled::kKnownAtRuntime,
+            core::TexelFormat::kRgba32Float,
+            core::Access::kReadWrite};
+
+    EXPECT_TRUE(a.Equals(b));
+    EXPECT_FALSE(a.Equals(c));
+    EXPECT_FALSE(a.Equals(d));
+    EXPECT_FALSE(a.Equals(e));
+    EXPECT_FALSE(a.Equals(f));
+    EXPECT_FALSE(a.Equals(g));
+    EXPECT_FALSE(a.Equals(h));
+    EXPECT_FALSE(a.Equals(i));
+    EXPECT_FALSE(a.Equals(j));
+}
+
+TEST(ImageTest, FriendlyName) {
+    core::type::I32 i32;
+    Image s{&i32,
+            Image::Dim::k1D,
+            Image::Depth::kNotDepth,
+            Image::Arrayed::kNonArrayed,
+            Image::MultiSampled::kSingleSampled,
+            Image::Sampled::kKnownAtRuntime,
+            core::TexelFormat::kRgba32Float,
+            core::Access::kRead};
+    EXPECT_EQ(s.FriendlyName(),
+              "spirv.image<i32, 1d, not_depth, non_arrayed, single_sampled, "
+              "sampled_known_at_runtime, rgba32float, read>");
+}
+
+}  // namespace
+}  // namespace tint::spirv::type