[hlsl] Add `Access` instruction to IR backend.

This CL adds support for the `access` instruction to the HLSL IR
backend.

Bug: 42251045
Change-Id: I08323da0850cb3af4d36b7df10cfadeeb654f826
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/194021
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/hlsl/writer/BUILD.bazel b/src/tint/lang/hlsl/writer/BUILD.bazel
index 3649a92..f792a23 100644
--- a/src/tint/lang/hlsl/writer/BUILD.bazel
+++ b/src/tint/lang/hlsl/writer/BUILD.bazel
@@ -89,6 +89,7 @@
   name = "test",
   alwayslink = True,
   srcs = [
+    "access_test.cc",
     "constant_test.cc",
     "function_test.cc",
     "helper_test.h",
diff --git a/src/tint/lang/hlsl/writer/BUILD.cmake b/src/tint/lang/hlsl/writer/BUILD.cmake
index c417b4b..ec88170 100644
--- a/src/tint/lang/hlsl/writer/BUILD.cmake
+++ b/src/tint/lang/hlsl/writer/BUILD.cmake
@@ -100,6 +100,7 @@
 # Condition: TINT_BUILD_HLSL_WRITER
 ################################################################################
 tint_add_target(tint_lang_hlsl_writer_test test
+  lang/hlsl/writer/access_test.cc
   lang/hlsl/writer/constant_test.cc
   lang/hlsl/writer/function_test.cc
   lang/hlsl/writer/helper_test.h
diff --git a/src/tint/lang/hlsl/writer/BUILD.gn b/src/tint/lang/hlsl/writer/BUILD.gn
index c23a72c..0cd6d0d 100644
--- a/src/tint/lang/hlsl/writer/BUILD.gn
+++ b/src/tint/lang/hlsl/writer/BUILD.gn
@@ -92,6 +92,7 @@
   if (tint_build_hlsl_writer) {
     tint_unittests_source_set("unittests") {
       sources = [
+        "access_test.cc",
         "constant_test.cc",
         "function_test.cc",
         "helper_test.h",
diff --git a/src/tint/lang/hlsl/writer/access_test.cc b/src/tint/lang/hlsl/writer/access_test.cc
new file mode 100644
index 0000000..087b791
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/access_test.cc
@@ -0,0 +1,187 @@
+// Copyright 2024 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/hlsl/writer/helper_test.h"
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+namespace tint::hlsl::writer {
+namespace {
+
+TEST_F(HlslWriterTest, AccessArray) {
+    auto* func = b.Function("a", ty.void_(), core::ir::Function::PipelineStage::kCompute);
+    func->SetWorkgroupSize(1, 1, 1);
+
+    b.Append(func->Block(), [&] {
+        auto* v = b.Var("v", b.Zero<array<f32, 3>>());
+        b.Let("x", b.Load(b.Access(ty.ptr<function, f32>(), v, 1_u)));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+[numthreads(1, 1, 1)]
+void a() {
+  float v[3] = (float[3])0;
+  float x = v[1u];
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, AccessStruct) {
+    Vector members{
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("a"), ty.i32(), 0u, 0u, 4u, 4u,
+                                         core::type::StructMemberAttributes{}),
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("b"), ty.f32(), 1u, 4u, 4u, 4u,
+                                         core::type::StructMemberAttributes{}),
+    };
+    auto* strct = ty.Struct(b.ir.symbols.New("S"), std::move(members));
+
+    auto* f = b.Function("a", ty.void_(), core::ir::Function::PipelineStage::kCompute);
+    f->SetWorkgroupSize(1, 1, 1);
+
+    b.Append(f->Block(), [&] {
+        auto* v = b.Var("v", b.Zero(strct));
+        b.Let("x", b.Load(b.Access(ty.ptr<function, f32>(), v, 1_u)));
+        b.Return(f);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(struct S {
+  int a;
+  float b;
+};
+
+
+[numthreads(1, 1, 1)]
+void a() {
+  S v = (S)0;
+  float x = v.b;
+}
+
+)");
+}
+
+// TODO(dsinclair): Needs `LoadVectorElement`
+TEST_F(HlslWriterTest, DISABLED_AccessVector) {
+    auto* func = b.Function("a", ty.void_(), core::ir::Function::PipelineStage::kCompute);
+    func->SetWorkgroupSize(1, 1, 1);
+
+    b.Append(func->Block(), [&] {
+        auto* v = b.Var("v", b.Zero<vec3<f32>>());
+        b.Let("x", b.LoadVectorElement(v, 1_u));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+[numthreads(1, 1, 1)]
+void a() {
+  float3 v = (float3)0;
+  float x = v[1u];
+}
+
+)");
+}
+
+// TODO(dsinclair): Needs `LoadVectorElement`
+TEST_F(HlslWriterTest, DISABLED_AccessMatrix) {
+    auto* func = b.Function("a", ty.void_(), core::ir::Function::PipelineStage::kCompute);
+    func->SetWorkgroupSize(1, 1, 1);
+
+    b.Append(func->Block(), [&] {
+        auto* v = b.Var("v", b.Zero<mat4x4<f32>>());
+        auto* v1 = b.Access(ty.ptr<function, vec4<f32>>(), v, 1_u);
+        b.Let("x", b.LoadVectorElement(v1, 2_u));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+[numthreads(1, 1, 1)]
+void a() {
+  float3 v = (float3)0;
+  float x = v[1u][2u];
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, AccessNested) {
+    Vector members_a{
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("d"), ty.i32(), 0u, 0u, 4u, 4u,
+                                         core::type::StructMemberAttributes{}),
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("e"), ty.array<f32, 3>(), 1u, 4u, 4u, 4u,
+                                         core::type::StructMemberAttributes{}),
+    };
+    auto* a_strct = ty.Struct(b.ir.symbols.New("A"), std::move(members_a));
+
+    Vector members_s{
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("a"), ty.i32(), 0u, 0u, 4u, 4u,
+                                         core::type::StructMemberAttributes{}),
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("b"), ty.f32(), 1u, 4u, 4u, 4u,
+                                         core::type::StructMemberAttributes{}),
+        ty.Get<core::type::StructMember>(b.ir.symbols.New("c"), a_strct, 2u, 8u, 8u, 8u,
+                                         core::type::StructMemberAttributes{}),
+    };
+    auto* s_strct = ty.Struct(b.ir.symbols.New("S"), std::move(members_s));
+
+    auto* f = b.Function("a", ty.void_(), core::ir::Function::PipelineStage::kCompute);
+    f->SetWorkgroupSize(1, 1, 1);
+
+    b.Append(f->Block(), [&] {
+        auto* v = b.Var("v", b.Zero(s_strct));
+        b.Let("x", b.Load(b.Access(ty.ptr<function, f32>(), v, 2_u, 1_u, 1_i)));
+        b.Return(f);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(struct A {
+  int d;
+  float e[3];
+};
+
+struct S {
+  int a;
+  float b;
+  A c;
+};
+
+
+[numthreads(1, 1, 1)]
+void a() {
+  S v = (S)0;
+  float x = v.c.e[1];
+}
+
+)");
+}
+
+}  // namespace
+}  // namespace tint::hlsl::writer
diff --git a/src/tint/lang/hlsl/writer/printer/printer.cc b/src/tint/lang/hlsl/writer/printer/printer.cc
index 9d81451..34ac314 100644
--- a/src/tint/lang/hlsl/writer/printer/printer.cc
+++ b/src/tint/lang/hlsl/writer/printer/printer.cc
@@ -282,7 +282,8 @@
             [&](const core::ir::Constant* c) { EmitConstant(out, c); },  //
             [&](const core::ir::InstructionResult* r) {
                 Switch(
-                    r->Instruction(),
+                    r->Instruction(),                                                          //
+                    [&](const core::ir::Access* a) { EmitAccess(out, a); },                    //
                     [&](const core::ir::CoreBinary* b) { EmitBinary(out, b); },                //
                     [&](const core::ir::CoreBuiltinCall* c) { EmitCoreBuiltinCall(out, c); },  //
                     [&](const core::ir::Let* l) { out << NameOf(l->Result(0)); },              //
@@ -294,6 +295,32 @@
             TINT_ICE_ON_NO_MATCH);
     }
 
+    /// Emit an access instruction
+    void EmitAccess(StringStream& out, const core::ir::Access* a) {
+        EmitValue(out, a->Object());
+
+        auto* current_type = a->Object()->Type();
+        for (auto* index : a->Indices()) {
+            TINT_ASSERT(current_type);
+
+            current_type = current_type->UnwrapPtr();
+            Switch(
+                current_type,  //
+                [&](const core::type::Struct* s) {
+                    auto* c = index->As<core::ir::Constant>();
+                    auto* member = s->Members()[c->Value()->ValueAs<uint32_t>()];
+                    out << "." << member->Name().Name();
+                    current_type = member->Type();
+                },
+                [&](Default) {
+                    out << "[";
+                    EmitValue(out, index);
+                    out << "]";
+                    current_type = current_type->Element(0);
+                });
+        }
+    }
+
     void EmitCoreBuiltinCall(StringStream& out, const core::ir::CoreBuiltinCall* c) {
         EmitCoreBuiltinName(out, c->Func());