[tint][ast] Fix dynamic indices in DirectVariableAccess

The logic to extract the callee's dynamic indices was incorrect.

Add more tests, for both the AST and IR transforms.

Bug: tint:2053
Change-Id: I9fa2eeded5a9ba525b1529377a0ab443e80d8634
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/169263
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access.cc b/src/tint/lang/core/ir/transform/direct_variable_access.cc
index 12b078b..e62d52b 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access.cc
@@ -380,7 +380,7 @@
                 return variant_fn;
             });
 
-            // Repoint the target of the call to the variant.
+            // Re-point the target of the call to the variant.
             call->SetTarget(new_target);
         });
     }
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access_test.cc b/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
index ed37b51..e6244da 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
@@ -1158,6 +1158,128 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(IR_DirectVariableAccessTest_UniformAS, CallChaining2) {
+    auto* T3 = ty.vec4<i32>();
+    auto* T2 = ty.array(T3, 5);
+    auto* T1 = ty.array(T2, 5);
+    auto* T = ty.array(T1, 5);
+
+    Var* input = nullptr;
+    b.Append(b.ir.root_block,
+             [&] {  //
+                 input = b.Var("U", ty.ptr<uniform>(T));
+                 input->SetBindingPoint(0, 0);
+             });
+
+    auto* f2 = b.Function("f2", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<uniform>(T2));
+        f2->SetParams({p});
+        b.Append(f2->Block(),
+                 [&] { b.Return(f2, b.Load(b.Access<ptr<uniform, vec4<i32>>>(p, 3_u))); });
+    }
+
+    auto* f1 = b.Function("f1", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<uniform>(T1));
+        f1->SetParams({p});
+        b.Append(f1->Block(),
+                 [&] { b.Return(f1, b.Call(f2, b.Access(ty.ptr<uniform>(T2), p, 2_u))); });
+    }
+
+    auto* f0 = b.Function("f0", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<uniform>(T));
+        f0->SetParams({p});
+        b.Append(f0->Block(),
+                 [&] { b.Return(f0, b.Call(f1, b.Access(ty.ptr<uniform>(T1), p, 1_u))); });
+    }
+
+    auto* main = b.Function("main", ty.void_());
+    b.Append(main->Block(), [&] {
+        b.Call(f0, input);
+        b.Return(main);
+    });
+
+    auto* src = R"(
+%b1 = block {  # root
+  %U:ptr<uniform, array<array<array<vec4<i32>, 5>, 5>, 5>, read> = var @binding_point(0, 0)
+}
+
+%f2 = func(%p:ptr<uniform, array<vec4<i32>, 5>, read>):vec4<i32> -> %b2 {
+  %b2 = block {
+    %4:ptr<uniform, vec4<i32>, read> = access %p, 3u
+    %5:vec4<i32> = load %4
+    ret %5
+  }
+}
+%f1 = func(%p_1:ptr<uniform, array<array<vec4<i32>, 5>, 5>, read>):vec4<i32> -> %b3 {  # %p_1: 'p'
+  %b3 = block {
+    %8:ptr<uniform, array<vec4<i32>, 5>, read> = access %p_1, 2u
+    %9:vec4<i32> = call %f2, %8
+    ret %9
+  }
+}
+%f0 = func(%p_2:ptr<uniform, array<array<array<vec4<i32>, 5>, 5>, 5>, read>):vec4<i32> -> %b4 {  # %p_2: 'p'
+  %b4 = block {
+    %12:ptr<uniform, array<array<vec4<i32>, 5>, 5>, read> = access %p_2, 1u
+    %13:vec4<i32> = call %f1, %12
+    ret %13
+  }
+}
+%main = func():void -> %b5 {
+  %b5 = block {
+    %15:vec4<i32> = call %f0, %U
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%b1 = block {  # root
+  %U:ptr<uniform, array<array<array<vec4<i32>, 5>, 5>, 5>, read> = var @binding_point(0, 0)
+}
+
+%f2_U_X_X = func(%p_indices:array<u32, 2>):vec4<i32> -> %b2 {
+  %b2 = block {
+    %4:u32 = access %p_indices, 0u
+    %5:u32 = access %p_indices, 1u
+    %6:ptr<uniform, array<vec4<i32>, 5>, read> = access %U, %4, %5
+    %7:ptr<uniform, vec4<i32>, read> = access %6, 3u
+    %8:vec4<i32> = load %7
+    ret %8
+  }
+}
+%f1_U_X = func(%p_indices_1:array<u32, 1>):vec4<i32> -> %b3 {  # %p_indices_1: 'p_indices'
+  %b3 = block {
+    %11:u32 = access %p_indices_1, 0u
+    %12:array<u32, 2> = construct %11, 2u
+    %13:vec4<i32> = call %f2_U_X_X, %12
+    ret %13
+  }
+}
+%f0_U = func():vec4<i32> -> %b4 {
+  %b4 = block {
+    %15:array<u32, 1> = construct 1u
+    %16:vec4<i32> = call %f1_U_X, %15
+    ret %16
+  }
+}
+%main = func():void -> %b5 {
+  %b5 = block {
+    %18:vec4<i32> = call %f0_U
+    ret
+  }
+}
+)";
+
+    Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+    EXPECT_EQ(expect, str());
+}
+
 }  // namespace uniform_as_tests
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1727,6 +1849,128 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(IR_DirectVariableAccessTest_StorageAS, CallChaining2) {
+    auto* T3 = ty.vec4<i32>();
+    auto* T2 = ty.array(T3, 5);
+    auto* T1 = ty.array(T2, 5);
+    auto* T = ty.array(T1, 5);
+
+    Var* input = nullptr;
+    b.Append(b.ir.root_block,
+             [&] {  //
+                 input = b.Var("U", ty.ptr<storage>(T));
+                 input->SetBindingPoint(0, 0);
+             });
+
+    auto* f2 = b.Function("f2", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<storage>(T2));
+        f2->SetParams({p});
+        b.Append(f2->Block(),
+                 [&] { b.Return(f2, b.Load(b.Access<ptr<storage, vec4<i32>>>(p, 3_u))); });
+    }
+
+    auto* f1 = b.Function("f1", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<storage>(T1));
+        f1->SetParams({p});
+        b.Append(f1->Block(),
+                 [&] { b.Return(f1, b.Call(f2, b.Access(ty.ptr<storage>(T2), p, 2_u))); });
+    }
+
+    auto* f0 = b.Function("f0", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<storage>(T));
+        f0->SetParams({p});
+        b.Append(f0->Block(),
+                 [&] { b.Return(f0, b.Call(f1, b.Access(ty.ptr<storage>(T1), p, 1_u))); });
+    }
+
+    auto* main = b.Function("main", ty.void_());
+    b.Append(main->Block(), [&] {
+        b.Call(f0, input);
+        b.Return(main);
+    });
+
+    auto* src = R"(
+%b1 = block {  # root
+  %U:ptr<storage, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var @binding_point(0, 0)
+}
+
+%f2 = func(%p:ptr<storage, array<vec4<i32>, 5>, read_write>):vec4<i32> -> %b2 {
+  %b2 = block {
+    %4:ptr<storage, vec4<i32>, read_write> = access %p, 3u
+    %5:vec4<i32> = load %4
+    ret %5
+  }
+}
+%f1 = func(%p_1:ptr<storage, array<array<vec4<i32>, 5>, 5>, read_write>):vec4<i32> -> %b3 {  # %p_1: 'p'
+  %b3 = block {
+    %8:ptr<storage, array<vec4<i32>, 5>, read_write> = access %p_1, 2u
+    %9:vec4<i32> = call %f2, %8
+    ret %9
+  }
+}
+%f0 = func(%p_2:ptr<storage, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write>):vec4<i32> -> %b4 {  # %p_2: 'p'
+  %b4 = block {
+    %12:ptr<storage, array<array<vec4<i32>, 5>, 5>, read_write> = access %p_2, 1u
+    %13:vec4<i32> = call %f1, %12
+    ret %13
+  }
+}
+%main = func():void -> %b5 {
+  %b5 = block {
+    %15:vec4<i32> = call %f0, %U
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%b1 = block {  # root
+  %U:ptr<storage, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var @binding_point(0, 0)
+}
+
+%f2_U_X_X = func(%p_indices:array<u32, 2>):vec4<i32> -> %b2 {
+  %b2 = block {
+    %4:u32 = access %p_indices, 0u
+    %5:u32 = access %p_indices, 1u
+    %6:ptr<storage, array<vec4<i32>, 5>, read_write> = access %U, %4, %5
+    %7:ptr<storage, vec4<i32>, read_write> = access %6, 3u
+    %8:vec4<i32> = load %7
+    ret %8
+  }
+}
+%f1_U_X = func(%p_indices_1:array<u32, 1>):vec4<i32> -> %b3 {  # %p_indices_1: 'p_indices'
+  %b3 = block {
+    %11:u32 = access %p_indices_1, 0u
+    %12:array<u32, 2> = construct %11, 2u
+    %13:vec4<i32> = call %f2_U_X_X, %12
+    ret %13
+  }
+}
+%f0_U = func():vec4<i32> -> %b4 {
+  %b4 = block {
+    %15:array<u32, 1> = construct 1u
+    %16:vec4<i32> = call %f1_U_X, %15
+    ret %16
+  }
+}
+%main = func():void -> %b5 {
+  %b5 = block {
+    %18:vec4<i32> = call %f0_U
+    ret
+  }
+}
+)";
+
+    Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+    EXPECT_EQ(expect, str());
+}
+
 }  // namespace storage_as_tests
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -2194,6 +2438,128 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(IR_DirectVariableAccessTest_WorkgroupAS, CallChaining2) {
+    auto* T3 = ty.vec4<i32>();
+    auto* T2 = ty.array(T3, 5);
+    auto* T1 = ty.array(T2, 5);
+    auto* T = ty.array(T1, 5);
+
+    Var* input = nullptr;
+    b.Append(b.ir.root_block,
+             [&] {  //
+                 input = b.Var("U", ty.ptr<workgroup>(T));
+                 input->SetBindingPoint(0, 0);
+             });
+
+    auto* f2 = b.Function("f2", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<workgroup>(T2));
+        f2->SetParams({p});
+        b.Append(f2->Block(),
+                 [&] { b.Return(f2, b.Load(b.Access<ptr<workgroup, vec4<i32>>>(p, 3_u))); });
+    }
+
+    auto* f1 = b.Function("f1", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<workgroup>(T1));
+        f1->SetParams({p});
+        b.Append(f1->Block(),
+                 [&] { b.Return(f1, b.Call(f2, b.Access(ty.ptr<workgroup>(T2), p, 2_u))); });
+    }
+
+    auto* f0 = b.Function("f0", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<workgroup>(T));
+        f0->SetParams({p});
+        b.Append(f0->Block(),
+                 [&] { b.Return(f0, b.Call(f1, b.Access(ty.ptr<workgroup>(T1), p, 1_u))); });
+    }
+
+    auto* main = b.Function("main", ty.void_());
+    b.Append(main->Block(), [&] {
+        b.Call(f0, input);
+        b.Return(main);
+    });
+
+    auto* src = R"(
+%b1 = block {  # root
+  %U:ptr<workgroup, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var @binding_point(0, 0)
+}
+
+%f2 = func(%p:ptr<workgroup, array<vec4<i32>, 5>, read_write>):vec4<i32> -> %b2 {
+  %b2 = block {
+    %4:ptr<workgroup, vec4<i32>, read_write> = access %p, 3u
+    %5:vec4<i32> = load %4
+    ret %5
+  }
+}
+%f1 = func(%p_1:ptr<workgroup, array<array<vec4<i32>, 5>, 5>, read_write>):vec4<i32> -> %b3 {  # %p_1: 'p'
+  %b3 = block {
+    %8:ptr<workgroup, array<vec4<i32>, 5>, read_write> = access %p_1, 2u
+    %9:vec4<i32> = call %f2, %8
+    ret %9
+  }
+}
+%f0 = func(%p_2:ptr<workgroup, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write>):vec4<i32> -> %b4 {  # %p_2: 'p'
+  %b4 = block {
+    %12:ptr<workgroup, array<array<vec4<i32>, 5>, 5>, read_write> = access %p_2, 1u
+    %13:vec4<i32> = call %f1, %12
+    ret %13
+  }
+}
+%main = func():void -> %b5 {
+  %b5 = block {
+    %15:vec4<i32> = call %f0, %U
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%b1 = block {  # root
+  %U:ptr<workgroup, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var @binding_point(0, 0)
+}
+
+%f2_U_X_X = func(%p_indices:array<u32, 2>):vec4<i32> -> %b2 {
+  %b2 = block {
+    %4:u32 = access %p_indices, 0u
+    %5:u32 = access %p_indices, 1u
+    %6:ptr<workgroup, array<vec4<i32>, 5>, read_write> = access %U, %4, %5
+    %7:ptr<workgroup, vec4<i32>, read_write> = access %6, 3u
+    %8:vec4<i32> = load %7
+    ret %8
+  }
+}
+%f1_U_X = func(%p_indices_1:array<u32, 1>):vec4<i32> -> %b3 {  # %p_indices_1: 'p_indices'
+  %b3 = block {
+    %11:u32 = access %p_indices_1, 0u
+    %12:array<u32, 2> = construct %11, 2u
+    %13:vec4<i32> = call %f2_U_X_X, %12
+    ret %13
+  }
+}
+%f0_U = func():vec4<i32> -> %b4 {
+  %b4 = block {
+    %15:array<u32, 1> = construct 1u
+    %16:vec4<i32> = call %f1_U_X, %15
+    ret %16
+  }
+}
+%main = func():void -> %b5 {
+  %b5 = block {
+    %18:vec4<i32> = call %f0_U
+    ret
+  }
+}
+)";
+
+    Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+    EXPECT_EQ(expect, str());
+}
+
 }  // namespace workgroup_as_tests
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -3323,6 +3689,214 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Enabled_CallChaining2) {
+    auto* T3 = ty.vec4<i32>();
+    auto* T2 = ty.array(T3, 5);
+    auto* T1 = ty.array(T2, 5);
+    auto* T = ty.array(T1, 5);
+
+    Var* P = nullptr;
+    b.Append(b.ir.root_block,
+             [&] {  //
+                 P = b.Var("P", ty.ptr<private_>(T));
+             });
+
+    auto* f2 = b.Function("f2", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<private_>(T2));
+        f2->SetParams({p});
+        b.Append(f2->Block(), [&] {
+            b.Return(f2, b.Load(b.Access<ptr<private_, vec4<i32>, read_write>>(p, 3_u)));
+        });
+    }
+
+    auto* f1 = b.Function("f1", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<private_>(T1));
+        f1->SetParams({p});
+        b.Append(f1->Block(),
+                 [&] { b.Return(f1, b.Call(f2, b.Access(ty.ptr<private_>(T2), p, 2_u))); });
+    }
+
+    auto* f0 = b.Function("f0", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<private_>(T));
+        f0->SetParams({p});
+        b.Append(f0->Block(),
+                 [&] { b.Return(f0, b.Call(f1, b.Access(ty.ptr<private_>(T1), p, 1_u))); });
+    }
+
+    auto* main = b.Function("main", ty.void_());
+    b.Append(main->Block(), [&] {
+        b.Call(f0, P);
+        b.Return(main);
+    });
+
+    auto* src = R"(
+%b1 = block {  # root
+  %P:ptr<private, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var
+}
+
+%f2 = func(%p:ptr<private, array<vec4<i32>, 5>, read_write>):vec4<i32> -> %b2 {
+  %b2 = block {
+    %4:ptr<private, vec4<i32>, read_write> = access %p, 3u
+    %5:vec4<i32> = load %4
+    ret %5
+  }
+}
+%f1 = func(%p_1:ptr<private, array<array<vec4<i32>, 5>, 5>, read_write>):vec4<i32> -> %b3 {  # %p_1: 'p'
+  %b3 = block {
+    %8:ptr<private, array<vec4<i32>, 5>, read_write> = access %p_1, 2u
+    %9:vec4<i32> = call %f2, %8
+    ret %9
+  }
+}
+%f0 = func(%p_2:ptr<private, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write>):vec4<i32> -> %b4 {  # %p_2: 'p'
+  %b4 = block {
+    %12:ptr<private, array<array<vec4<i32>, 5>, 5>, read_write> = access %p_2, 1u
+    %13:vec4<i32> = call %f1, %12
+    ret %13
+  }
+}
+%main = func():void -> %b5 {
+  %b5 = block {
+    %15:vec4<i32> = call %f0, %P
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%b1 = block {  # root
+  %P:ptr<private, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var
+}
+
+%f2_P_X_X = func(%p_indices:array<u32, 2>):vec4<i32> -> %b2 {
+  %b2 = block {
+    %4:u32 = access %p_indices, 0u
+    %5:u32 = access %p_indices, 1u
+    %6:ptr<private, array<vec4<i32>, 5>, read_write> = access %P, %4, %5
+    %7:ptr<private, vec4<i32>, read_write> = access %6, 3u
+    %8:vec4<i32> = load %7
+    ret %8
+  }
+}
+%f1_P_X = func(%p_indices_1:array<u32, 1>):vec4<i32> -> %b3 {  # %p_indices_1: 'p_indices'
+  %b3 = block {
+    %11:u32 = access %p_indices_1, 0u
+    %12:array<u32, 2> = construct %11, 2u
+    %13:vec4<i32> = call %f2_P_X_X, %12
+    ret %13
+  }
+}
+%f0_P = func():vec4<i32> -> %b4 {
+  %b4 = block {
+    %15:array<u32, 1> = construct 1u
+    %16:vec4<i32> = call %f1_P_X, %15
+    ret %16
+  }
+}
+%main = func():void -> %b5 {
+  %b5 = block {
+    %18:vec4<i32> = call %f0_P
+    ret
+  }
+}
+)";
+
+    Run(DirectVariableAccess, kTransformPrivate);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_PrivateAS, Disabled_CallChaining2) {
+    auto* T3 = ty.vec4<i32>();
+    auto* T2 = ty.array(T3, 5);
+    auto* T1 = ty.array(T2, 5);
+    auto* T = ty.array(T1, 5);
+
+    Var* P = nullptr;
+    b.Append(b.ir.root_block,
+             [&] {  //
+                 P = b.Var("P", ty.ptr<private_>(T));
+             });
+
+    auto* f2 = b.Function("f2", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<private_>(T2));
+        f2->SetParams({p});
+        b.Append(f2->Block(), [&] {
+            b.Return(f2, b.Load(b.Access<ptr<private_, vec4<i32>, read_write>>(p, 3_u)));
+        });
+    }
+
+    auto* f1 = b.Function("f1", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<private_>(T1));
+        f1->SetParams({p});
+        b.Append(f1->Block(),
+                 [&] { b.Return(f1, b.Call(f2, b.Access(ty.ptr<private_>(T2), p, 2_u))); });
+    }
+
+    auto* f0 = b.Function("f0", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<private_>(T));
+        f0->SetParams({p});
+        b.Append(f0->Block(),
+                 [&] { b.Return(f0, b.Call(f1, b.Access(ty.ptr<private_>(T1), p, 1_u))); });
+    }
+
+    auto* main = b.Function("main", ty.void_());
+    b.Append(main->Block(), [&] {
+        b.Call(f0, P);
+        b.Return(main);
+    });
+
+    auto* src = R"(
+%b1 = block {  # root
+  %P:ptr<private, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var
+}
+
+%f2 = func(%p:ptr<private, array<vec4<i32>, 5>, read_write>):vec4<i32> -> %b2 {
+  %b2 = block {
+    %4:ptr<private, vec4<i32>, read_write> = access %p, 3u
+    %5:vec4<i32> = load %4
+    ret %5
+  }
+}
+%f1 = func(%p_1:ptr<private, array<array<vec4<i32>, 5>, 5>, read_write>):vec4<i32> -> %b3 {  # %p_1: 'p'
+  %b3 = block {
+    %8:ptr<private, array<vec4<i32>, 5>, read_write> = access %p_1, 2u
+    %9:vec4<i32> = call %f2, %8
+    ret %9
+  }
+}
+%f0 = func(%p_2:ptr<private, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write>):vec4<i32> -> %b4 {  # %p_2: 'p'
+  %b4 = block {
+    %12:ptr<private, array<array<vec4<i32>, 5>, 5>, read_write> = access %p_2, 1u
+    %13:vec4<i32> = call %f1, %12
+    ret %13
+  }
+}
+%main = func():void -> %b5 {
+  %b5 = block {
+    %15:vec4<i32> = call %f0, %P
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+
+    Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+    EXPECT_EQ(expect, str());
+}
+
 }  // namespace private_as_tests
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -3841,6 +4415,567 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Enabled_CallChaining) {
+    auto* Inner =
+        ty.Struct(mod.symbols.New("Inner"), {
+                                                {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+                                            });
+    auto* Outer =
+        ty.Struct(mod.symbols.New("Outer"), {
+                                                {mod.symbols.Register("arr"), ty.array(Inner, 4)},
+                                                {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+                                            });
+
+    auto* f0 = b.Function("f0", ty.f32());
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function, vec4<f32>>());
+        f0->SetParams({p});
+        b.Append(f0->Block(), [&] { b.Return(f0, b.LoadVectorElement(p, 0_u)); });
+    }
+
+    auto* f1 = b.Function("f1", ty.f32());
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function, mat3x4<f32>>());
+        f1->SetParams({p});
+        b.Append(f1->Block(), [&] {
+            auto* res = b.Var<function, f32>("res");
+            {
+                // res += f0(&(*p)[1]);
+                auto* call_0 = b.Call(f0, b.Access(ty.ptr<function, vec4<f32>>(), p, 1_i));
+                b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+            }
+            {
+                // let p_vec = &(*p)[1];
+                // res += f0(p_vec);
+                auto* p_vec = b.Access(ty.ptr<function, vec4<f32>>(), p, 1_i);
+                b.ir.SetName(p_vec, "p_vec");
+                auto* call_0 = b.Call(f0, p_vec);
+                b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+            }
+            b.Return(f1, b.Load(res));
+        });
+    }
+
+    auto* f2 = b.Function("f2", ty.f32());
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(Inner));
+        f2->SetParams({p});
+        b.Append(f2->Block(), [&] {
+            auto* p_mat = b.Access(ty.ptr<function, mat3x4<f32>>(), p, 0_u);
+            b.ir.SetName(p_mat, "p_mat");
+            b.Return(f2, b.Call(f1, p_mat));
+        });
+    }
+
+    auto* f3 = b.Function("f3", ty.f32());
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(ty.array(Inner, 4)));
+        f3->SetParams({p});
+        b.Append(f3->Block(), [&] {
+            auto* p_inner = b.Access(ty.ptr<function>(Inner), p, 3_i);
+            b.ir.SetName(p_inner, "p_inner");
+            b.Return(f3, b.Call(f2, p_inner));
+        });
+    }
+
+    auto* f4 = b.Function("f4", ty.f32());
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(Outer));
+        f4->SetParams({p});
+        b.Append(f4->Block(), [&] {
+            auto* access = b.Access(ty.ptr<function>(ty.array(Inner, 4)), p, 0_u);
+            b.Return(f4, b.Call(f3, access));
+        });
+    }
+
+    auto* fn_b = b.Function("b", ty.void_());
+    b.Append(fn_b->Block(), [&] {
+        auto F = b.Var("F", ty.ptr<function>(Outer));
+        b.Call(f4, F);
+        b.Return(fn_b);
+    });
+
+    auto* src = R"(
+Inner = struct @align(16) {
+  mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+  arr:array<Inner, 4> @offset(0)
+  mat:mat3x4<f32> @offset(192)
+}
+
+%f0 = func(%p:ptr<function, vec4<f32>, read_write>):f32 -> %b1 {
+  %b1 = block {
+    %3:f32 = load_vector_element %p, 0u
+    ret %3
+  }
+}
+%f1 = func(%p_1:ptr<function, mat3x4<f32>, read_write>):f32 -> %b2 {  # %p_1: 'p'
+  %b2 = block {
+    %res:ptr<function, f32, read_write> = var
+    %7:ptr<function, vec4<f32>, read_write> = access %p_1, 1i
+    %8:f32 = call %f0, %7
+    %9:f32 = load %res
+    %10:f32 = add %9, %8
+    store %res, %10
+    %p_vec:ptr<function, vec4<f32>, read_write> = access %p_1, 1i
+    %12:f32 = call %f0, %p_vec
+    %13:f32 = load %res
+    %14:f32 = add %13, %12
+    store %res, %14
+    %15:f32 = load %res
+    ret %15
+  }
+}
+%f2 = func(%p_2:ptr<function, Inner, read_write>):f32 -> %b3 {  # %p_2: 'p'
+  %b3 = block {
+    %p_mat:ptr<function, mat3x4<f32>, read_write> = access %p_2, 0u
+    %19:f32 = call %f1, %p_mat
+    ret %19
+  }
+}
+%f3 = func(%p_3:ptr<function, array<Inner, 4>, read_write>):f32 -> %b4 {  # %p_3: 'p'
+  %b4 = block {
+    %p_inner:ptr<function, Inner, read_write> = access %p_3, 3i
+    %23:f32 = call %f2, %p_inner
+    ret %23
+  }
+}
+%f4 = func(%p_4:ptr<function, Outer, read_write>):f32 -> %b5 {  # %p_4: 'p'
+  %b5 = block {
+    %26:ptr<function, array<Inner, 4>, read_write> = access %p_4, 0u
+    %27:f32 = call %f3, %26
+    ret %27
+  }
+}
+%b = func():void -> %b6 {
+  %b6 = block {
+    %F:ptr<function, Outer, read_write> = var
+    %30:f32 = call %f4, %F
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+Inner = struct @align(16) {
+  mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+  arr:array<Inner, 4> @offset(0)
+  mat:mat3x4<f32> @offset(192)
+}
+
+%f0_P_arr_X_mat_X = func(%p_root:ptr<function, Outer, read_write>, %p_indices:array<u32, 2>):f32 -> %b1 {
+  %b1 = block {
+    %4:u32 = access %p_indices, 0u
+    %5:u32 = access %p_indices, 1u
+    %6:ptr<function, vec4<f32>, read_write> = access %p_root, 0u, %4, 0u, %5
+    %7:f32 = load_vector_element %6, 0u
+    ret %7
+  }
+}
+%f1_P_arr_X_mat = func(%p_root_1:ptr<function, Outer, read_write>, %p_indices_1:array<u32, 1>):f32 -> %b2 {  # %p_root_1: 'p_root', %p_indices_1: 'p_indices'
+  %b2 = block {
+    %11:u32 = access %p_indices_1, 0u
+    %res:ptr<function, f32, read_write> = var
+    %13:u32 = convert 1i
+    %14:array<u32, 2> = construct %11, %13
+    %15:f32 = call %f0_P_arr_X_mat_X, %p_root_1, %14
+    %16:f32 = load %res
+    %17:f32 = add %16, %15
+    store %res, %17
+    %18:u32 = convert 1i
+    %19:array<u32, 2> = construct %11, %18
+    %20:f32 = call %f0_P_arr_X_mat_X, %p_root_1, %19
+    %21:f32 = load %res
+    %22:f32 = add %21, %20
+    store %res, %22
+    %23:f32 = load %res
+    ret %23
+  }
+}
+%f2_P_arr_X = func(%p_root_2:ptr<function, Outer, read_write>, %p_indices_2:array<u32, 1>):f32 -> %b3 {  # %p_root_2: 'p_root', %p_indices_2: 'p_indices'
+  %b3 = block {
+    %27:u32 = access %p_indices_2, 0u
+    %28:array<u32, 1> = construct %27
+    %29:f32 = call %f1_P_arr_X_mat, %p_root_2, %28
+    ret %29
+  }
+}
+%f3_P_arr = func(%p_root_3:ptr<function, Outer, read_write>):f32 -> %b4 {  # %p_root_3: 'p_root'
+  %b4 = block {
+    %32:u32 = convert 3i
+    %33:array<u32, 1> = construct %32
+    %34:f32 = call %f2_P_arr_X, %p_root_3, %33
+    ret %34
+  }
+}
+%f4_P = func(%p_root_4:ptr<function, Outer, read_write>):f32 -> %b5 {  # %p_root_4: 'p_root'
+  %b5 = block {
+    %37:f32 = call %f3_P_arr, %p_root_4
+    ret %37
+  }
+}
+%b = func():void -> %b6 {
+  %b6 = block {
+    %F:ptr<function, Outer, read_write> = var
+    %40:f32 = call %f4_P, %F
+    ret
+  }
+}
+)";
+
+    Run(DirectVariableAccess, kTransformFunction);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Disabled_CallChaining) {
+    auto* Inner =
+        ty.Struct(mod.symbols.New("Inner"), {
+                                                {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+                                            });
+    auto* Outer =
+        ty.Struct(mod.symbols.New("Outer"), {
+                                                {mod.symbols.Register("arr"), ty.array(Inner, 4)},
+                                                {mod.symbols.Register("mat"), ty.mat3x4<f32>()},
+                                            });
+
+    auto* f0 = b.Function("f0", ty.f32());
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function, vec4<f32>>());
+        f0->SetParams({p});
+        b.Append(f0->Block(), [&] { b.Return(f0, b.LoadVectorElement(p, 0_u)); });
+    }
+
+    auto* f1 = b.Function("f1", ty.f32());
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function, mat3x4<f32>>());
+        f1->SetParams({p});
+        b.Append(f1->Block(), [&] {
+            auto* res = b.Var<function, f32>("res");
+            {
+                // res += f0(&(*p)[1]);
+                auto* call_0 = b.Call(f0, b.Access(ty.ptr<function, vec4<f32>>(), p, 1_i));
+                b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+            }
+            {
+                // let p_vec = &(*p)[1];
+                // res += f0(p_vec);
+                auto* p_vec = b.Access(ty.ptr<function, vec4<f32>>(), p, 1_i);
+                b.ir.SetName(p_vec, "p_vec");
+                auto* call_0 = b.Call(f0, p_vec);
+                b.Store(res, b.Add(ty.f32(), b.Load(res), call_0));
+            }
+            b.Return(f1, b.Load(res));
+        });
+    }
+
+    auto* f2 = b.Function("f2", ty.f32());
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(Inner));
+        f2->SetParams({p});
+        b.Append(f2->Block(), [&] {
+            auto* p_mat = b.Access(ty.ptr<function, mat3x4<f32>>(), p, 0_u);
+            b.ir.SetName(p_mat, "p_mat");
+            b.Return(f2, b.Call(f1, p_mat));
+        });
+    }
+
+    auto* f3 = b.Function("f3", ty.f32());
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(ty.array(Inner, 4)));
+        f3->SetParams({p});
+        b.Append(f3->Block(), [&] {
+            auto* p_inner = b.Access(ty.ptr<function>(Inner), p, 3_i);
+            b.ir.SetName(p_inner, "p_inner");
+            b.Return(f3, b.Call(f2, p_inner));
+        });
+    }
+
+    auto* f4 = b.Function("f4", ty.f32());
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(Outer));
+        f4->SetParams({p});
+        b.Append(f4->Block(), [&] {
+            auto* access = b.Access(ty.ptr<function>(ty.array(Inner, 4)), p, 0_u);
+            b.Return(f4, b.Call(f3, access));
+        });
+    }
+
+    auto* fn_b = b.Function("b", ty.void_());
+    b.Append(fn_b->Block(), [&] {
+        auto F = b.Var("F", ty.ptr<function>(Outer));
+        b.Call(f4, F);
+        b.Return(fn_b);
+    });
+
+    auto* src = R"(
+Inner = struct @align(16) {
+  mat:mat3x4<f32> @offset(0)
+}
+
+Outer = struct @align(16) {
+  arr:array<Inner, 4> @offset(0)
+  mat:mat3x4<f32> @offset(192)
+}
+
+%f0 = func(%p:ptr<function, vec4<f32>, read_write>):f32 -> %b1 {
+  %b1 = block {
+    %3:f32 = load_vector_element %p, 0u
+    ret %3
+  }
+}
+%f1 = func(%p_1:ptr<function, mat3x4<f32>, read_write>):f32 -> %b2 {  # %p_1: 'p'
+  %b2 = block {
+    %res:ptr<function, f32, read_write> = var
+    %7:ptr<function, vec4<f32>, read_write> = access %p_1, 1i
+    %8:f32 = call %f0, %7
+    %9:f32 = load %res
+    %10:f32 = add %9, %8
+    store %res, %10
+    %p_vec:ptr<function, vec4<f32>, read_write> = access %p_1, 1i
+    %12:f32 = call %f0, %p_vec
+    %13:f32 = load %res
+    %14:f32 = add %13, %12
+    store %res, %14
+    %15:f32 = load %res
+    ret %15
+  }
+}
+%f2 = func(%p_2:ptr<function, Inner, read_write>):f32 -> %b3 {  # %p_2: 'p'
+  %b3 = block {
+    %p_mat:ptr<function, mat3x4<f32>, read_write> = access %p_2, 0u
+    %19:f32 = call %f1, %p_mat
+    ret %19
+  }
+}
+%f3 = func(%p_3:ptr<function, array<Inner, 4>, read_write>):f32 -> %b4 {  # %p_3: 'p'
+  %b4 = block {
+    %p_inner:ptr<function, Inner, read_write> = access %p_3, 3i
+    %23:f32 = call %f2, %p_inner
+    ret %23
+  }
+}
+%f4 = func(%p_4:ptr<function, Outer, read_write>):f32 -> %b5 {  # %p_4: 'p'
+  %b5 = block {
+    %26:ptr<function, array<Inner, 4>, read_write> = access %p_4, 0u
+    %27:f32 = call %f3, %26
+    ret %27
+  }
+}
+%b = func():void -> %b6 {
+  %b6 = block {
+    %F:ptr<function, Outer, read_write> = var
+    %30:f32 = call %f4, %F
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+
+    Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Enabled_CallChaining2) {
+    auto* T3 = ty.vec4<i32>();
+    auto* T2 = ty.array(T3, 5);
+    auto* T1 = ty.array(T2, 5);
+    auto* T = ty.array(T1, 5);
+
+    auto* f2 = b.Function("f2", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(T2));
+        f2->SetParams({p});
+        b.Append(f2->Block(), [&] {
+            b.Return(f2, b.Load(b.Access<ptr<function, vec4<i32>, read_write>>(p, 3_u)));
+        });
+    }
+
+    auto* f1 = b.Function("f1", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(T1));
+        f1->SetParams({p});
+        b.Append(f1->Block(),
+                 [&] { b.Return(f1, b.Call(f2, b.Access(ty.ptr<function>(T2), p, 2_u))); });
+    }
+
+    auto* f0 = b.Function("f0", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(T));
+        f0->SetParams({p});
+        b.Append(f0->Block(),
+                 [&] { b.Return(f0, b.Call(f1, b.Access(ty.ptr<function>(T1), p, 1_u))); });
+    }
+
+    auto* main = b.Function("main", ty.void_());
+    b.Append(main->Block(), [&] {
+        auto* F = b.Var("F", ty.ptr<function>(T));
+        b.Call(f0, F);
+        b.Return(main);
+    });
+
+    auto* src = R"(
+%f2 = func(%p:ptr<function, array<vec4<i32>, 5>, read_write>):vec4<i32> -> %b1 {
+  %b1 = block {
+    %3:ptr<function, vec4<i32>, read_write> = access %p, 3u
+    %4:vec4<i32> = load %3
+    ret %4
+  }
+}
+%f1 = func(%p_1:ptr<function, array<array<vec4<i32>, 5>, 5>, read_write>):vec4<i32> -> %b2 {  # %p_1: 'p'
+  %b2 = block {
+    %7:ptr<function, array<vec4<i32>, 5>, read_write> = access %p_1, 2u
+    %8:vec4<i32> = call %f2, %7
+    ret %8
+  }
+}
+%f0 = func(%p_2:ptr<function, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write>):vec4<i32> -> %b3 {  # %p_2: 'p'
+  %b3 = block {
+    %11:ptr<function, array<array<vec4<i32>, 5>, 5>, read_write> = access %p_2, 1u
+    %12:vec4<i32> = call %f1, %11
+    ret %12
+  }
+}
+%main = func():void -> %b4 {
+  %b4 = block {
+    %F:ptr<function, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var
+    %15:vec4<i32> = call %f0, %F
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%f2_P_X_X = func(%p_root:ptr<function, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write>, %p_indices:array<u32, 2>):vec4<i32> -> %b1 {
+  %b1 = block {
+    %4:u32 = access %p_indices, 0u
+    %5:u32 = access %p_indices, 1u
+    %6:ptr<function, array<vec4<i32>, 5>, read_write> = access %p_root, %4, %5
+    %7:ptr<function, vec4<i32>, read_write> = access %6, 3u
+    %8:vec4<i32> = load %7
+    ret %8
+  }
+}
+%f1_P_X = func(%p_root_1:ptr<function, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write>, %p_indices_1:array<u32, 1>):vec4<i32> -> %b2 {  # %p_root_1: 'p_root', %p_indices_1: 'p_indices'
+  %b2 = block {
+    %12:u32 = access %p_indices_1, 0u
+    %13:array<u32, 2> = construct %12, 2u
+    %14:vec4<i32> = call %f2_P_X_X, %p_root_1, %13
+    ret %14
+  }
+}
+%f0_P = func(%p_root_2:ptr<function, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write>):vec4<i32> -> %b3 {  # %p_root_2: 'p_root'
+  %b3 = block {
+    %17:array<u32, 1> = construct 1u
+    %18:vec4<i32> = call %f1_P_X, %p_root_2, %17
+    ret %18
+  }
+}
+%main = func():void -> %b4 {
+  %b4 = block {
+    %F:ptr<function, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var
+    %21:vec4<i32> = call %f0_P, %F
+    ret
+  }
+}
+)";
+
+    Run(DirectVariableAccess, kTransformFunction);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_DirectVariableAccessTest_FunctionAS, Disabled_CallChaining2) {
+    auto* T3 = ty.vec4<i32>();
+    auto* T2 = ty.array(T3, 5);
+    auto* T1 = ty.array(T2, 5);
+    auto* T = ty.array(T1, 5);
+
+    auto* f2 = b.Function("f2", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(T2));
+        f2->SetParams({p});
+        b.Append(f2->Block(), [&] {
+            b.Return(f2, b.Load(b.Access<ptr<function, vec4<i32>, read_write>>(p, 3_u)));
+        });
+    }
+
+    auto* f1 = b.Function("f1", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(T1));
+        f1->SetParams({p});
+        b.Append(f1->Block(),
+                 [&] { b.Return(f1, b.Call(f2, b.Access(ty.ptr<function>(T2), p, 2_u))); });
+    }
+
+    auto* f0 = b.Function("f0", T3);
+    {
+        auto* p = b.FunctionParam("p", ty.ptr<function>(T));
+        f0->SetParams({p});
+        b.Append(f0->Block(),
+                 [&] { b.Return(f0, b.Call(f1, b.Access(ty.ptr<function>(T1), p, 1_u))); });
+    }
+
+    auto* main = b.Function("main", ty.void_());
+    b.Append(main->Block(), [&] {
+        auto* F = b.Var("F", ty.ptr<function>(T));
+        b.Call(f0, F);
+        b.Return(main);
+    });
+
+    auto* src = R"(
+%f2 = func(%p:ptr<function, array<vec4<i32>, 5>, read_write>):vec4<i32> -> %b1 {
+  %b1 = block {
+    %3:ptr<function, vec4<i32>, read_write> = access %p, 3u
+    %4:vec4<i32> = load %3
+    ret %4
+  }
+}
+%f1 = func(%p_1:ptr<function, array<array<vec4<i32>, 5>, 5>, read_write>):vec4<i32> -> %b2 {  # %p_1: 'p'
+  %b2 = block {
+    %7:ptr<function, array<vec4<i32>, 5>, read_write> = access %p_1, 2u
+    %8:vec4<i32> = call %f2, %7
+    ret %8
+  }
+}
+%f0 = func(%p_2:ptr<function, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write>):vec4<i32> -> %b3 {  # %p_2: 'p'
+  %b3 = block {
+    %11:ptr<function, array<array<vec4<i32>, 5>, 5>, read_write> = access %p_2, 1u
+    %12:vec4<i32> = call %f1, %11
+    ret %12
+  }
+}
+%main = func():void -> %b4 {
+  %b4 = block {
+    %F:ptr<function, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var
+    %15:vec4<i32> = call %f0, %F
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+
+    Run(DirectVariableAccess, DirectVariableAccessOptions{});
+
+    EXPECT_EQ(expect, str());
+}
+
 }  // namespace function_as_tests
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc b/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc
index df9df7d..c73dead 100644
--- a/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc
+++ b/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc
@@ -84,16 +84,13 @@
 /// DynamicIndex is used by DirectVariableAccess::State::AccessOp to indicate an array, matrix or
 /// vector index.
 struct DynamicIndex {
-    /// The index of the expression in DirectVariableAccess::State::AccessChain::dynamic_indices
-    size_t slot = 0;
-
     /// @return a hash code for this object
-    size_t HashCode() const { return Hash(slot); }
+    size_t HashCode() const { return 42 /* empty struct: any number will do */; }
 };
 
 /// Inequality operator for DynamicIndex
-bool operator!=(const DynamicIndex& a, const DynamicIndex& b) {
-    return a.slot != b.slot;
+bool operator!=(const DynamicIndex&, const DynamicIndex&) {
+    return false;  // empty struct: two DynamicIndex objects are always equal
 }
 
 /// AccessOp describes a single access in an access chain.
@@ -484,7 +481,7 @@
                 // Store the index expression into AccessChain::dynamic_indices, append a
                 // DynamicIndex to the chain, and move the chain to the index accessor expression.
                 if (auto* chain = take_chain(a->Object())) {
-                    chain->ops.Push(DynamicIndex{chain->dynamic_indices.Length()});
+                    chain->ops.Push(DynamicIndex{});
                     chain->dynamic_indices.Push(a->Index());
                 }
             },
@@ -1025,20 +1022,23 @@
             // Chain starts with a pointer parameter.
             // Replace this with the variant's incoming shape. This will bring the expression up to
             // the incoming pointer.
+            size_t next_dyn_idx_from_indices = 0;
             auto indices =
                 clone_state->current_variant->ptr_param_symbols.Find(root_param)->indices;
             for (auto param_access : incoming_shape->ops) {
-                chain_expr = BuildAccessExpr(chain_expr, param_access, [&](size_t i) {
-                    return b.IndexAccessor(indices, AInt(i));
+                chain_expr = BuildAccessExpr(chain_expr, param_access, [&] {
+                    return b.IndexAccessor(indices, AInt(next_dyn_idx_from_indices++));
                 });
             }
 
             // Now build the expression chain within the function.
 
             // For each access in the chain (excluding the pointer parameter)...
+            size_t next_dyn_idx_from_chain = 0;
             for (auto& op : chain->ops) {
-                chain_expr = BuildAccessExpr(chain_expr, op, [&](size_t i) {
-                    return BuildDynamicIndex(chain->dynamic_indices[i], false);
+                chain_expr = BuildAccessExpr(chain_expr, op, [&] {
+                    return BuildDynamicIndex(chain->dynamic_indices[next_dyn_idx_from_chain++],
+                                             false);
                 });
             }
 
@@ -1131,13 +1131,13 @@
     /// The returned expression will always be of a reference type.
     /// @param expr the input expression
     /// @param access the access to perform on the current expression
-    /// @param dynamic_index a function that obtains the i'th dynamic index
+    /// @param dynamic_index a function that obtains the next dynamic index
     const Expression* BuildAccessExpr(const Expression* expr,
                                       const AccessOp& access,
-                                      std::function<const Expression*(size_t)> dynamic_index) {
-        if (auto* dyn_idx = std::get_if<DynamicIndex>(&access)) {
+                                      std::function<const Expression*()> dynamic_index) {
+        if (std::holds_alternative<DynamicIndex>(access)) {
             /// The access uses a dynamic (runtime-expression) index.
-            auto* idx = dynamic_index(dyn_idx->slot);
+            auto* idx = dynamic_index();
             return b.IndexAccessor(expr, idx);
         }
 
diff --git a/src/tint/lang/wgsl/ast/transform/direct_variable_access_test.cc b/src/tint/lang/wgsl/ast/transform/direct_variable_access_test.cc
index cb5598f..10f6421 100644
--- a/src/tint/lang/wgsl/ast/transform/direct_variable_access_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/direct_variable_access_test.cc
@@ -1035,12 +1035,6 @@
 alias U_arr_X_mat_X = array<u32, 2u>;
 
 fn f0_U_arr_X_mat_X(p : U_arr_X_mat_X) -> f32 {
-  return U.arr[p[0]].mat[p[0]].x;
-}
-
-alias U_arr_X_mat_X_1 = array<u32, 2u>;
-
-fn f0_U_arr_X_mat_X_1(p : U_arr_X_mat_X_1) -> f32 {
   return U.arr[p[0]].mat[p[1]].x;
 }
 
@@ -1054,11 +1048,11 @@
     res += f0_U_mat_X(U_mat_X(1));
   }
   {
-    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(U.arr[2].mat[1]);
-    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -1075,11 +1069,11 @@
     res += f0_U_arr_X_mat_X(U_arr_X_mat_X(p[0u], 1));
   }
   {
-    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(U.arr[2].mat[1]);
-    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -1198,12 +1192,6 @@
 alias U_arr_X_mat_X = array<u32, 2u>;
 
 fn f0_U_arr_X_mat_X(p : U_arr_X_mat_X) -> f32 {
-  return (&(U.arr[p[0]].mat[p[0]])).x;
-}
-
-alias U_arr_X_mat_X_1 = array<u32, 2u>;
-
-fn f0_U_arr_X_mat_X_1(p : U_arr_X_mat_X_1) -> f32 {
   return (&(U.arr[p[0]].mat[p[1]])).x;
 }
 
@@ -1217,11 +1205,11 @@
     res += f0_U_mat_X(U_mat_X(1));
   }
   {
-    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(U.arr[2].mat[1]);
-    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -1238,11 +1226,11 @@
     res += f0_U_arr_X_mat_X(U_arr_X_mat_X(p[0u], 1));
   }
   {
-    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(U.arr[2].mat[1]);
-    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -1273,6 +1261,76 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(DirectVariableAccessUniformASTest, CallChaining2) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+alias T3 = vec4i;
+alias T2 = array<T3, 5>;
+alias T1 = array<T2, 5>;
+alias T = array<T1, 5>;
+
+@binding(0) @group(0) var<uniform> input : T;
+
+fn f2(p : ptr<uniform, T2>) -> T3 {
+  return (*p)[3];
+}
+
+fn f1(p : ptr<uniform, T1>) -> T3 {
+  return f2(&(*p)[2]);
+}
+
+fn f0(p : ptr<uniform, T>) -> T3 {
+  return f1(&(*p)[1]);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  f0(&input);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_full_ptr_parameters;
+
+alias T3 = vec4i;
+
+alias T2 = array<T3, 5>;
+
+alias T1 = array<T2, 5>;
+
+alias T = array<T1, 5>;
+
+@binding(0) @group(0) var<uniform> input : T;
+
+alias input_X_X = array<u32, 2u>;
+
+fn f2_input_X_X(p : input_X_X) -> T3 {
+  return input[p[0]][p[1]][3];
+}
+
+alias input_X = array<u32, 1u>;
+
+fn f1_input_X(p : input_X) -> T3 {
+  return f2_input_X_X(input_X_X(p[0u], 2));
+}
+
+fn f0_input() -> T3 {
+  return f1_input_X(input_X(1));
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  f0_input();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 }  // namespace uniform_as_tests
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1492,12 +1550,6 @@
 alias S_arr_X_mat_X = array<u32, 2u>;
 
 fn f0_S_arr_X_mat_X(p : S_arr_X_mat_X) -> f32 {
-  return S.arr[p[0]].mat[p[0]].x;
-}
-
-alias S_arr_X_mat_X_1 = array<u32, 2u>;
-
-fn f0_S_arr_X_mat_X_1(p : S_arr_X_mat_X_1) -> f32 {
   return S.arr[p[0]].mat[p[1]].x;
 }
 
@@ -1511,11 +1563,11 @@
     res += f0_S_mat_X(S_mat_X(1));
   }
   {
-    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(S.arr[2].mat[1]);
-    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -1532,11 +1584,11 @@
     res += f0_S_arr_X_mat_X(S_arr_X_mat_X(p[0u], 1));
   }
   {
-    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(S.arr[2].mat[1]);
-    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -1655,12 +1707,6 @@
 alias S_arr_X_mat_X = array<u32, 2u>;
 
 fn f0_S_arr_X_mat_X(p : S_arr_X_mat_X) -> f32 {
-  return (&(S.arr[p[0]].mat[p[0]])).x;
-}
-
-alias S_arr_X_mat_X_1 = array<u32, 2u>;
-
-fn f0_S_arr_X_mat_X_1(p : S_arr_X_mat_X_1) -> f32 {
   return (&(S.arr[p[0]].mat[p[1]])).x;
 }
 
@@ -1674,11 +1720,11 @@
     res += f0_S_mat_X(S_mat_X(1));
   }
   {
-    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(S.arr[2].mat[1]);
-    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -1695,11 +1741,11 @@
     res += f0_S_arr_X_mat_X(S_arr_X_mat_X(p[0u], 1));
   }
   {
-    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(S.arr[2].mat[1]);
-    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -1730,6 +1776,76 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(DirectVariableAccessStorageASTest, CallChaining2) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+alias T3 = vec4i;
+alias T2 = array<T3, 5>;
+alias T1 = array<T2, 5>;
+alias T = array<T1, 5>;
+
+@binding(0) @group(0) var<storage> input : T;
+
+fn f2(p : ptr<storage, T2>) -> T3 {
+  return (*p)[3];
+}
+
+fn f1(p : ptr<storage, T1>) -> T3 {
+  return f2(&(*p)[2]);
+}
+
+fn f0(p : ptr<storage, T>) -> T3 {
+  return f1(&(*p)[1]);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  f0(&input);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_full_ptr_parameters;
+
+alias T3 = vec4i;
+
+alias T2 = array<T3, 5>;
+
+alias T1 = array<T2, 5>;
+
+alias T = array<T1, 5>;
+
+@binding(0) @group(0) var<storage> input : T;
+
+alias input_X_X = array<u32, 2u>;
+
+fn f2_input_X_X(p : input_X_X) -> T3 {
+  return input[p[0]][p[1]][3];
+}
+
+alias input_X = array<u32, 1u>;
+
+fn f1_input_X(p : input_X) -> T3 {
+  return f2_input_X_X(input_X_X(p[0u], 2));
+}
+
+fn f0_input() -> T3 {
+  return f1_input_X(input_X(1));
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  f0_input();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 }  // namespace storage_as_tests
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1899,12 +2015,6 @@
 alias W_arr_X_mat_X = array<u32, 2u>;
 
 fn f0_W_arr_X_mat_X(p : W_arr_X_mat_X) -> f32 {
-  return W.arr[p[0]].mat[p[0]].x;
-}
-
-alias W_arr_X_mat_X_1 = array<u32, 2u>;
-
-fn f0_W_arr_X_mat_X_1(p : W_arr_X_mat_X_1) -> f32 {
   return W.arr[p[0]].mat[p[1]].x;
 }
 
@@ -1918,11 +2028,11 @@
     res += f0_W_mat_X(W_mat_X(1));
   }
   {
-    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(W.arr[2].mat[1]);
-    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -1939,11 +2049,11 @@
     res += f0_W_arr_X_mat_X(W_arr_X_mat_X(p[0u], 1));
   }
   {
-    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(W.arr[2].mat[1]);
-    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -2062,12 +2172,6 @@
 alias W_arr_X_mat_X = array<u32, 2u>;
 
 fn f0_W_arr_X_mat_X(p : W_arr_X_mat_X) -> f32 {
-  return (&(W.arr[p[0]].mat[p[0]])).x;
-}
-
-alias W_arr_X_mat_X_1 = array<u32, 2u>;
-
-fn f0_W_arr_X_mat_X_1(p : W_arr_X_mat_X_1) -> f32 {
   return (&(W.arr[p[0]].mat[p[1]])).x;
 }
 
@@ -2081,11 +2185,11 @@
     res += f0_W_mat_X(W_mat_X(1));
   }
   {
-    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(W.arr[2].mat[1]);
-    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -2102,11 +2206,11 @@
     res += f0_W_arr_X_mat_X(W_arr_X_mat_X(p[0u], 1));
   }
   {
-    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(2, 1));
   }
   {
     let p_vec = &(W.arr[2].mat[1]);
-    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(2, 1));
   }
   return res;
 }
@@ -2137,6 +2241,81 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(DirectVariableAccessWorkgroupASTest, CallChaining2) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+alias T3 = vec4i;
+alias T2 = array<T3, 5>;
+alias T1 = array<T2, 5>;
+alias T = array<T1, 5>;
+
+@binding(0) @group(0) var<storage, read> input : T;
+
+fn f2(p : ptr<workgroup, T2>) -> T3 {
+  return (*p)[3];
+}
+
+fn f1(p : ptr<workgroup, T1>) -> T3 {
+  return f2(&(*p)[2]);
+}
+
+fn f0(p : ptr<workgroup, T>) -> T3 {
+  return f1(&(*p)[1]);
+}
+
+var<workgroup> W : T;
+@compute @workgroup_size(1)
+fn main() {
+  W = input;
+  f0(&W);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_full_ptr_parameters;
+
+alias T3 = vec4i;
+
+alias T2 = array<T3, 5>;
+
+alias T1 = array<T2, 5>;
+
+alias T = array<T1, 5>;
+
+@binding(0) @group(0) var<storage, read> input : T;
+
+alias W_X_X = array<u32, 2u>;
+
+fn f2_W_X_X(p : W_X_X) -> T3 {
+  return W[p[0]][p[1]][3];
+}
+
+alias W_X = array<u32, 1u>;
+
+fn f1_W_X(p : W_X) -> T3 {
+  return f2_W_X_X(W_X_X(p[0u], 2));
+}
+
+fn f0_W() -> T3 {
+  return f1_W_X(W_X(1));
+}
+
+var<workgroup> W : T;
+
+@compute @workgroup_size(1)
+fn main() {
+  W = input;
+  f0_W();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 }  // namespace workgroup_as_tests
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -2534,18 +2713,12 @@
 alias F_arr_X_mat_X = array<u32, 2u>;
 
 fn f0_F_arr_X_mat_X(p_base : ptr<private, Outer>, p_indices : F_arr_X_mat_X) -> f32 {
-  return (*(p_base)).arr[p_indices[0]].mat[p_indices[0]].x;
-}
-
-alias F_arr_X_mat_X_1 = array<u32, 2u>;
-
-fn f0_F_arr_X_mat_X_1(p_base : ptr<private, Outer>, p_indices : F_arr_X_mat_X_1) -> f32 {
   return (*(p_base)).arr[p_indices[0]].mat[p_indices[1]].x;
 }
 
 alias F_mat_X_1 = array<u32, 1u>;
 
-alias F_arr_X_mat_X_2 = array<u32, 2u>;
+alias F_arr_X_mat_X_1 = array<u32, 2u>;
 
 fn f1_F_mat(p : ptr<private, Outer>) -> f32 {
   var res : f32;
@@ -2557,34 +2730,34 @@
     res += f0_F_mat_X(p, F_mat_X_1(1));
   }
   {
-    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+    res += f0_F_arr_X_mat_X(&(P), F_arr_X_mat_X_1(2, 1));
   }
   {
     let p_vec = &(P.arr[2].mat[1]);
-    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+    res += f0_F_arr_X_mat_X(&(P), F_arr_X_mat_X_1(2, 1));
   }
   return res;
 }
 
 alias F_arr_X_mat = array<u32, 1u>;
 
-alias F_arr_X_mat_X_3 = array<u32, 2u>;
+alias F_arr_X_mat_X_2 = array<u32, 2u>;
 
 fn f1_F_arr_X_mat(p_base : ptr<private, Outer>, p_indices : F_arr_X_mat) -> f32 {
   var res : f32;
   {
-    res += f0_F_arr_X_mat_X(p_base, F_arr_X_mat_X_3(p_indices[0u], 1));
+    res += f0_F_arr_X_mat_X(p_base, F_arr_X_mat_X_2(p_indices[0u], 1));
   }
   {
     let p_vec = &((*(p_base)).arr[p_indices[0]].mat[1]);
-    res += f0_F_arr_X_mat_X(p_base, F_arr_X_mat_X_3(p_indices[0u], 1));
+    res += f0_F_arr_X_mat_X(p_base, F_arr_X_mat_X_2(p_indices[0u], 1));
   }
   {
-    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+    res += f0_F_arr_X_mat_X(&(P), F_arr_X_mat_X_1(2, 1));
   }
   {
     let p_vec = &(P.arr[2].mat[1]);
-    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+    res += f0_F_arr_X_mat_X(&(P), F_arr_X_mat_X_1(2, 1));
   }
   return res;
 }
@@ -2619,6 +2792,80 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_CallChaining2) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+alias T3 = vec4i;
+alias T2 = array<T3, 5>;
+alias T1 = array<T2, 5>;
+alias T = array<T1, 5>;
+
+fn f2(p : ptr<private, T2>) -> T3 {
+  return (*p)[3];
+}
+
+fn f1(p : ptr<private, T1>) -> T3 {
+  return f2(&(*p)[2]);
+}
+
+fn f0(p : ptr<private, T>) -> T3 {
+  return f1(&(*p)[1]);
+}
+
+var<private> P : T;
+
+@compute @workgroup_size(1)
+fn main() {
+  f0(&P);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_full_ptr_parameters;
+
+alias T3 = vec4i;
+
+alias T2 = array<T3, 5>;
+
+alias T1 = array<T2, 5>;
+
+alias T = array<T1, 5>;
+
+alias F_X_X = array<u32, 2u>;
+
+fn f2_F_X_X(p_base : ptr<private, array<array<array<vec4<i32>, 5u>, 5u>, 5u>>, p_indices : F_X_X) -> T3 {
+  return (*(p_base))[p_indices[0]][p_indices[1]][3];
+}
+
+alias F_X = array<u32, 1u>;
+
+alias F_X_X_1 = array<u32, 2u>;
+
+fn f1_F_X(p_base : ptr<private, array<array<array<vec4<i32>, 5u>, 5u>, 5u>>, p_indices : F_X) -> T3 {
+  return f2_F_X_X(p_base, F_X_X_1(p_indices[0u], 2));
+}
+
+alias F_X_1 = array<u32, 1u>;
+
+fn f0_F(p : ptr<private, array<array<array<vec4<i32>, 5u>, 5u>, 5u>>) -> T3 {
+  return f1_F_X(p, F_X_1(1));
+}
+
+var<private> P : T;
+
+@compute @workgroup_size(1)
+fn main() {
+  f0_F(&(P));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(DirectVariableAccessPrivateASTest, Disabled_CallChaining) {
     auto* src = R"(
 enable chromium_experimental_full_ptr_parameters;
@@ -2917,6 +3164,187 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+};
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+};
+
+fn f0(p : ptr<function, vec4<f32>>) -> f32 {
+  return (*p).x;
+}
+
+fn f1(p : ptr<function, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    // call f0() with inline usage of p
+    res += f0(&(*p)[1]);
+  }
+  {
+    // call f0() with pointer-let usage of p
+    let p_vec = &(*p)[1];
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<function, Inner>) -> f32 {
+  let p_mat = &(*p).mat;
+  return f1(p_mat);
+}
+
+fn f3(p : ptr<function, InnerArr>) -> f32 {
+  let p_inner = &(*p)[3];
+  return f2(p_inner);
+}
+
+fn f4(p : ptr<function, Outer>) -> f32 {
+  return f3(&(*p).arr);
+}
+
+fn b() {
+  var S : Outer;
+  f4(&S);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+alias InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+fn f0(p : ptr<function, vec4<f32>>) -> f32 {
+  return (*(p)).x;
+}
+
+fn f1(p : ptr<function, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    res += f0(&((*(p))[1]));
+  }
+  {
+    let p_vec = &((*(p))[1]);
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<function, Inner>) -> f32 {
+  let p_mat = &((*(p)).mat);
+  return f1(p_mat);
+}
+
+fn f3(p : ptr<function, InnerArr>) -> f32 {
+  let p_inner = &((*(p))[3]);
+  return f2(p_inner);
+}
+
+fn f4(p : ptr<function, Outer>) -> f32 {
+  return f3(&((*(p)).arr));
+}
+
+fn b() {
+  var S : Outer;
+  f4(&(S));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_CallChaining2) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+alias T3 = vec4i;
+alias T2 = array<T3, 5>;
+alias T1 = array<T2, 5>;
+alias T = array<T1, 5>;
+
+fn f2(p : ptr<function, T2>) -> T3 {
+  return (*p)[3];
+}
+
+fn f1(p : ptr<function, T1>) -> T3 {
+  return f2(&(*p)[2]);
+}
+
+fn f0(p : ptr<function, T>) -> T3 {
+  return f1(&(*p)[1]);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var v : T;
+  f0(&v);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_full_ptr_parameters;
+
+alias T3 = vec4i;
+
+alias T2 = array<T3, 5>;
+
+alias T1 = array<T2, 5>;
+
+alias T = array<T1, 5>;
+
+alias F_X_X = array<u32, 2u>;
+
+fn f2_F_X_X(p_base : ptr<function, array<array<array<vec4<i32>, 5u>, 5u>, 5u>>, p_indices : F_X_X) -> T3 {
+  return (*(p_base))[p_indices[0]][p_indices[1]][3];
+}
+
+alias F_X = array<u32, 1u>;
+
+alias F_X_X_1 = array<u32, 2u>;
+
+fn f1_F_X(p_base : ptr<function, array<array<array<vec4<i32>, 5u>, 5u>, 5u>>, p_indices : F_X) -> T3 {
+  return f2_F_X_X(p_base, F_X_X_1(p_indices[0u], 2));
+}
+
+alias F_X_1 = array<u32, 1u>;
+
+fn f0_F(p : ptr<function, array<array<array<vec4<i32>, 5u>, 5u>, 5u>>) -> T3 {
+  return f1_F_X(p, F_X_1(1));
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var v : T;
+  f0_F(&(v));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(DirectVariableAccessFunctionASTest, Disabled_Param_ptr_i32_Via_struct_read) {
     auto* src = R"(
 enable chromium_experimental_full_ptr_parameters;
@@ -3249,7 +3677,7 @@
 alias S_X_X_X_X = array<u32, 4u>;
 
 fn a_S_X_X_X_X(pre : i32, i : S_X_X_X_X, post : i32) -> i32 {
-  return S[i[0]][i[0]][i[1]][i[2]];
+  return S[i[0]][i[1]][i[2]][i[3]];
 }
 
 alias S_X = array<u32, 1u>;
diff --git a/test/tint/array/strides.spvasm.expected.glsl b/test/tint/array/strides.spvasm.expected.glsl
index b82b24f..94ee04c 100644
--- a/test/tint/array/strides.spvasm.expected.glsl
+++ b/test/tint/array/strides.spvasm.expected.glsl
@@ -38,7 +38,7 @@
 } s;
 
 void assign_and_preserve_padding_4_s_a_X_el_X_X(uint dest[3], strided_arr value) {
-  s.inner.a[dest[0]].el[dest[0]][dest[0]].el = value.el;
+  s.inner.a[dest[0]].el[dest[1]][dest[2]].el = value.el;
 }
 
 void assign_and_preserve_padding_3_s_a_X_el_X(uint dest[2], strided_arr value[2]) {
diff --git a/test/tint/array/strides.spvasm.expected.spvasm b/test/tint/array/strides.spvasm.expected.spvasm
index 3d87847..d319985 100644
--- a/test/tint/array/strides.spvasm.expected.spvasm
+++ b/test/tint/array/strides.spvasm.expected.spvasm
@@ -71,181 +71,181 @@
      %uint_0 = OpConstant %uint 0
         %int = OpTypeInt 32 1
          %24 = OpConstantNull %int
+      %int_1 = OpConstant %int 1
+      %int_2 = OpConstant %int 2
 %_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
 %_arr_uint_uint_2 = OpTypeArray %uint %uint_2
-         %31 = OpTypeFunction %void %_arr_uint_uint_2 %_arr_strided_arr_uint_2
-         %37 = OpConstantNull %uint
+         %33 = OpTypeFunction %void %_arr_uint_uint_2 %_arr_strided_arr_uint_2
+         %39 = OpConstantNull %uint
 %_ptr_Function_uint = OpTypePointer Function %uint
        %bool = OpTypeBool
 %_ptr_Function__arr_strided_arr_uint_2 = OpTypePointer Function %_arr_strided_arr_uint_2
-         %52 = OpConstantNull %_arr_strided_arr_uint_2
+         %54 = OpConstantNull %_arr_strided_arr_uint_2
      %uint_1 = OpConstant %uint 1
 %_ptr_Function_strided_arr = OpTypePointer Function %strided_arr
 %_arr_uint_uint_1 = OpTypeArray %uint %uint_1
-         %65 = OpTypeFunction %void %_arr_uint_uint_1 %_arr__arr_strided_arr_uint_2_uint_3
+         %67 = OpTypeFunction %void %_arr_uint_uint_1 %_arr__arr_strided_arr_uint_2_uint_3
 %_ptr_Function__arr__arr_strided_arr_uint_2_uint_3 = OpTypePointer Function %_arr__arr_strided_arr_uint_2_uint_3
-         %83 = OpConstantNull %_arr__arr_strided_arr_uint_2_uint_3
-         %93 = OpTypeFunction %void %_arr_uint_uint_1 %strided_arr_1
-        %102 = OpTypeFunction %void %_arr_strided_arr_1_uint_4
+         %85 = OpConstantNull %_arr__arr_strided_arr_uint_2_uint_3
+         %95 = OpTypeFunction %void %_arr_uint_uint_1 %strided_arr_1
+        %104 = OpTypeFunction %void %_arr_strided_arr_1_uint_4
 %_ptr_Function__arr_strided_arr_1_uint_4 = OpTypePointer Function %_arr_strided_arr_1_uint_4
-        %118 = OpConstantNull %_arr_strided_arr_1_uint_4
+        %120 = OpConstantNull %_arr_strided_arr_1_uint_4
 %_ptr_Function_strided_arr_1 = OpTypePointer Function %strided_arr_1
-        %128 = OpTypeFunction %void
+        %130 = OpTypeFunction %void
 %_ptr_StorageBuffer__arr_strided_arr_1_uint_4 = OpTypePointer StorageBuffer %_arr_strided_arr_1_uint_4
       %int_3 = OpConstant %int 3
 %_ptr_StorageBuffer__arr__arr_strided_arr_uint_2_uint_3 = OpTypePointer StorageBuffer %_arr__arr_strided_arr_uint_2_uint_3
-      %int_2 = OpConstant %int 2
 %_ptr_StorageBuffer__arr_strided_arr_uint_2 = OpTypePointer StorageBuffer %_arr_strided_arr_uint_2
-      %int_1 = OpConstant %int 1
     %float_5 = OpConstant %float 5
 %assign_and_preserve_padding_4_s_a_X_el_X_X = OpFunction %void None %15
        %dest = OpFunctionParameter %_arr_uint_uint_3
       %value = OpFunctionParameter %strided_arr
          %21 = OpLabel
          %25 = OpCompositeExtract %uint %dest 0
-         %26 = OpCompositeExtract %uint %dest 0
-         %27 = OpCompositeExtract %uint %dest 0
-         %29 = OpAccessChain %_ptr_StorageBuffer_float %s %uint_0 %uint_0 %25 %uint_0 %26 %27 %uint_0
-         %30 = OpCompositeExtract %float %value 0
-               OpStore %29 %30
+         %27 = OpCompositeExtract %uint %dest 1
+         %29 = OpCompositeExtract %uint %dest 2
+         %31 = OpAccessChain %_ptr_StorageBuffer_float %s %uint_0 %uint_0 %25 %uint_0 %27 %29 %uint_0
+         %32 = OpCompositeExtract %float %value 0
+               OpStore %31 %32
                OpReturn
                OpFunctionEnd
-%assign_and_preserve_padding_3_s_a_X_el_X = OpFunction %void None %31
+%assign_and_preserve_padding_3_s_a_X_el_X = OpFunction %void None %33
      %dest_0 = OpFunctionParameter %_arr_uint_uint_2
     %value_0 = OpFunctionParameter %_arr_strided_arr_uint_2
-         %36 = OpLabel
-          %i = OpVariable %_ptr_Function_uint Function %37
-%var_for_index = OpVariable %_ptr_Function__arr_strided_arr_uint_2 Function %52
-               OpStore %i %37
-               OpBranch %40
-         %40 = OpLabel
-               OpLoopMerge %41 %42 None
-               OpBranch %43
-         %43 = OpLabel
-         %45 = OpLoad %uint %i
-         %46 = OpULessThan %bool %45 %uint_2
-         %44 = OpLogicalNot %bool %46
-               OpSelectionMerge %48 None
-               OpBranchConditional %44 %49 %48
-         %49 = OpLabel
-               OpBranch %41
-         %48 = OpLabel
-               OpStore %var_for_index %value_0
-         %54 = OpCompositeExtract %uint %dest_0 0
-         %56 = OpCompositeExtract %uint %dest_0 1
-         %57 = OpLoad %uint %i
-         %58 = OpCompositeConstruct %_arr_uint_uint_3 %54 %56 %57
-         %59 = OpLoad %uint %i
-         %61 = OpAccessChain %_ptr_Function_strided_arr %var_for_index %59
-         %62 = OpLoad %strided_arr %61
-         %53 = OpFunctionCall %void %assign_and_preserve_padding_4_s_a_X_el_X_X %58 %62
+         %38 = OpLabel
+          %i = OpVariable %_ptr_Function_uint Function %39
+%var_for_index = OpVariable %_ptr_Function__arr_strided_arr_uint_2 Function %54
+               OpStore %i %39
                OpBranch %42
          %42 = OpLabel
-         %63 = OpLoad %uint %i
-         %64 = OpIAdd %uint %63 %uint_1
-               OpStore %i %64
-               OpBranch %40
-         %41 = OpLabel
+               OpLoopMerge %43 %44 None
+               OpBranch %45
+         %45 = OpLabel
+         %47 = OpLoad %uint %i
+         %48 = OpULessThan %bool %47 %uint_2
+         %46 = OpLogicalNot %bool %48
+               OpSelectionMerge %50 None
+               OpBranchConditional %46 %51 %50
+         %51 = OpLabel
+               OpBranch %43
+         %50 = OpLabel
+               OpStore %var_for_index %value_0
+         %56 = OpCompositeExtract %uint %dest_0 0
+         %58 = OpCompositeExtract %uint %dest_0 1
+         %59 = OpLoad %uint %i
+         %60 = OpCompositeConstruct %_arr_uint_uint_3 %56 %58 %59
+         %61 = OpLoad %uint %i
+         %63 = OpAccessChain %_ptr_Function_strided_arr %var_for_index %61
+         %64 = OpLoad %strided_arr %63
+         %55 = OpFunctionCall %void %assign_and_preserve_padding_4_s_a_X_el_X_X %60 %64
+               OpBranch %44
+         %44 = OpLabel
+         %65 = OpLoad %uint %i
+         %66 = OpIAdd %uint %65 %uint_1
+               OpStore %i %66
+               OpBranch %42
+         %43 = OpLabel
                OpReturn
                OpFunctionEnd
-%assign_and_preserve_padding_2_s_a_X_el = OpFunction %void None %65
+%assign_and_preserve_padding_2_s_a_X_el = OpFunction %void None %67
      %dest_1 = OpFunctionParameter %_arr_uint_uint_1
     %value_1 = OpFunctionParameter %_arr__arr_strided_arr_uint_2_uint_3
-         %70 = OpLabel
-        %i_0 = OpVariable %_ptr_Function_uint Function %37
-%var_for_index_1 = OpVariable %_ptr_Function__arr__arr_strided_arr_uint_2_uint_3 Function %83
-               OpStore %i_0 %37
-               OpBranch %72
          %72 = OpLabel
-               OpLoopMerge %73 %74 None
-               OpBranch %75
-         %75 = OpLabel
-         %77 = OpLoad %uint %i_0
-         %78 = OpULessThan %bool %77 %uint_3
-         %76 = OpLogicalNot %bool %78
-               OpSelectionMerge %79 None
-               OpBranchConditional %76 %80 %79
-         %80 = OpLabel
-               OpBranch %73
-         %79 = OpLabel
-               OpStore %var_for_index_1 %value_1
-         %85 = OpCompositeExtract %uint %dest_1 0
-         %86 = OpLoad %uint %i_0
-         %87 = OpCompositeConstruct %_arr_uint_uint_2 %85 %86
-         %88 = OpLoad %uint %i_0
-         %89 = OpAccessChain %_ptr_Function__arr_strided_arr_uint_2 %var_for_index_1 %88
-         %90 = OpLoad %_arr_strided_arr_uint_2 %89
-         %84 = OpFunctionCall %void %assign_and_preserve_padding_3_s_a_X_el_X %87 %90
+        %i_0 = OpVariable %_ptr_Function_uint Function %39
+%var_for_index_1 = OpVariable %_ptr_Function__arr__arr_strided_arr_uint_2_uint_3 Function %85
+               OpStore %i_0 %39
                OpBranch %74
          %74 = OpLabel
-         %91 = OpLoad %uint %i_0
-         %92 = OpIAdd %uint %91 %uint_1
-               OpStore %i_0 %92
-               OpBranch %72
-         %73 = OpLabel
+               OpLoopMerge %75 %76 None
+               OpBranch %77
+         %77 = OpLabel
+         %79 = OpLoad %uint %i_0
+         %80 = OpULessThan %bool %79 %uint_3
+         %78 = OpLogicalNot %bool %80
+               OpSelectionMerge %81 None
+               OpBranchConditional %78 %82 %81
+         %82 = OpLabel
+               OpBranch %75
+         %81 = OpLabel
+               OpStore %var_for_index_1 %value_1
+         %87 = OpCompositeExtract %uint %dest_1 0
+         %88 = OpLoad %uint %i_0
+         %89 = OpCompositeConstruct %_arr_uint_uint_2 %87 %88
+         %90 = OpLoad %uint %i_0
+         %91 = OpAccessChain %_ptr_Function__arr_strided_arr_uint_2 %var_for_index_1 %90
+         %92 = OpLoad %_arr_strided_arr_uint_2 %91
+         %86 = OpFunctionCall %void %assign_and_preserve_padding_3_s_a_X_el_X %89 %92
+               OpBranch %76
+         %76 = OpLabel
+         %93 = OpLoad %uint %i_0
+         %94 = OpIAdd %uint %93 %uint_1
+               OpStore %i_0 %94
+               OpBranch %74
+         %75 = OpLabel
                OpReturn
                OpFunctionEnd
-%assign_and_preserve_padding_1_s_a_X = OpFunction %void None %93
+%assign_and_preserve_padding_1_s_a_X = OpFunction %void None %95
      %dest_2 = OpFunctionParameter %_arr_uint_uint_1
     %value_2 = OpFunctionParameter %strided_arr_1
-         %97 = OpLabel
-         %99 = OpCompositeExtract %uint %dest_2 0
-        %100 = OpCompositeConstruct %_arr_uint_uint_1 %99
-        %101 = OpCompositeExtract %_arr__arr_strided_arr_uint_2_uint_3 %value_2 0
-         %98 = OpFunctionCall %void %assign_and_preserve_padding_2_s_a_X_el %100 %101
+         %99 = OpLabel
+        %101 = OpCompositeExtract %uint %dest_2 0
+        %102 = OpCompositeConstruct %_arr_uint_uint_1 %101
+        %103 = OpCompositeExtract %_arr__arr_strided_arr_uint_2_uint_3 %value_2 0
+        %100 = OpFunctionCall %void %assign_and_preserve_padding_2_s_a_X_el %102 %103
                OpReturn
                OpFunctionEnd
-%assign_and_preserve_padding_s_a = OpFunction %void None %102
+%assign_and_preserve_padding_s_a = OpFunction %void None %104
     %value_3 = OpFunctionParameter %_arr_strided_arr_1_uint_4
-        %105 = OpLabel
-        %i_1 = OpVariable %_ptr_Function_uint Function %37
-%var_for_index_2 = OpVariable %_ptr_Function__arr_strided_arr_1_uint_4 Function %118
-               OpStore %i_1 %37
-               OpBranch %107
         %107 = OpLabel
-               OpLoopMerge %108 %109 None
-               OpBranch %110
-        %110 = OpLabel
-        %112 = OpLoad %uint %i_1
-        %113 = OpULessThan %bool %112 %uint_4
-        %111 = OpLogicalNot %bool %113
-               OpSelectionMerge %114 None
-               OpBranchConditional %111 %115 %114
-        %115 = OpLabel
-               OpBranch %108
-        %114 = OpLabel
-               OpStore %var_for_index_2 %value_3
-        %120 = OpLoad %uint %i_1
-        %121 = OpCompositeConstruct %_arr_uint_uint_1 %120
-        %122 = OpLoad %uint %i_1
-        %124 = OpAccessChain %_ptr_Function_strided_arr_1 %var_for_index_2 %122
-        %125 = OpLoad %strided_arr_1 %124
-        %119 = OpFunctionCall %void %assign_and_preserve_padding_1_s_a_X %121 %125
+        %i_1 = OpVariable %_ptr_Function_uint Function %39
+%var_for_index_2 = OpVariable %_ptr_Function__arr_strided_arr_1_uint_4 Function %120
+               OpStore %i_1 %39
                OpBranch %109
         %109 = OpLabel
-        %126 = OpLoad %uint %i_1
-        %127 = OpIAdd %uint %126 %uint_1
-               OpStore %i_1 %127
-               OpBranch %107
-        %108 = OpLabel
+               OpLoopMerge %110 %111 None
+               OpBranch %112
+        %112 = OpLabel
+        %114 = OpLoad %uint %i_1
+        %115 = OpULessThan %bool %114 %uint_4
+        %113 = OpLogicalNot %bool %115
+               OpSelectionMerge %116 None
+               OpBranchConditional %113 %117 %116
+        %117 = OpLabel
+               OpBranch %110
+        %116 = OpLabel
+               OpStore %var_for_index_2 %value_3
+        %122 = OpLoad %uint %i_1
+        %123 = OpCompositeConstruct %_arr_uint_uint_1 %122
+        %124 = OpLoad %uint %i_1
+        %126 = OpAccessChain %_ptr_Function_strided_arr_1 %var_for_index_2 %124
+        %127 = OpLoad %strided_arr_1 %126
+        %121 = OpFunctionCall %void %assign_and_preserve_padding_1_s_a_X %123 %127
+               OpBranch %111
+        %111 = OpLabel
+        %128 = OpLoad %uint %i_1
+        %129 = OpIAdd %uint %128 %uint_1
+               OpStore %i_1 %129
+               OpBranch %109
+        %110 = OpLabel
                OpReturn
                OpFunctionEnd
-        %f_1 = OpFunction %void None %128
-        %130 = OpLabel
-        %132 = OpAccessChain %_ptr_StorageBuffer__arr_strided_arr_1_uint_4 %s %uint_0 %uint_0
-        %133 = OpLoad %_arr_strided_arr_1_uint_4 %132
-        %136 = OpAccessChain %_ptr_StorageBuffer__arr__arr_strided_arr_uint_2_uint_3 %s %uint_0 %uint_0 %int_3 %uint_0
-        %137 = OpLoad %_arr__arr_strided_arr_uint_2_uint_3 %136
-        %140 = OpAccessChain %_ptr_StorageBuffer__arr_strided_arr_uint_2 %s %uint_0 %uint_0 %int_3 %uint_0 %int_2
-        %141 = OpLoad %_arr_strided_arr_uint_2 %140
+        %f_1 = OpFunction %void None %130
+        %132 = OpLabel
+        %134 = OpAccessChain %_ptr_StorageBuffer__arr_strided_arr_1_uint_4 %s %uint_0 %uint_0
+        %135 = OpLoad %_arr_strided_arr_1_uint_4 %134
+        %138 = OpAccessChain %_ptr_StorageBuffer__arr__arr_strided_arr_uint_2_uint_3 %s %uint_0 %uint_0 %int_3 %uint_0
+        %139 = OpLoad %_arr__arr_strided_arr_uint_2_uint_3 %138
+        %141 = OpAccessChain %_ptr_StorageBuffer__arr_strided_arr_uint_2 %s %uint_0 %uint_0 %int_3 %uint_0 %int_2
+        %142 = OpLoad %_arr_strided_arr_uint_2 %141
         %143 = OpAccessChain %_ptr_StorageBuffer_float %s %uint_0 %uint_0 %int_3 %uint_0 %int_2 %int_1 %uint_0
         %144 = OpLoad %float %143
-        %145 = OpFunctionCall %void %assign_and_preserve_padding_s_a %118
+        %145 = OpFunctionCall %void %assign_and_preserve_padding_s_a %120
         %146 = OpAccessChain %_ptr_StorageBuffer_float %s %uint_0 %uint_0 %int_3 %uint_0 %int_2 %int_1 %uint_0
                OpStore %146 %float_5
                OpReturn
                OpFunctionEnd
-          %f = OpFunction %void None %128
+          %f = OpFunction %void None %130
         %149 = OpLabel
         %150 = OpFunctionCall %void %f_1
                OpReturn
diff --git a/test/tint/bug/tint/2059.wgsl.expected.glsl b/test/tint/bug/tint/2059.wgsl.expected.glsl
index 6a99d71..4b50ac1 100644
--- a/test/tint/bug/tint/2059.wgsl.expected.glsl
+++ b/test/tint/bug/tint/2059.wgsl.expected.glsl
@@ -67,9 +67,9 @@
 }
 
 void assign_and_preserve_padding_buffer7_X_m_X(uint dest[2], mat3 value) {
-  buffer7.inner[dest[0]].m[dest[0]][0] = value[0u];
-  buffer7.inner[dest[0]].m[dest[0]][1] = value[1u];
-  buffer7.inner[dest[0]].m[dest[0]][2] = value[2u];
+  buffer7.inner[dest[0]].m[dest[1]][0] = value[0u];
+  buffer7.inner[dest[0]].m[dest[1]][1] = value[1u];
+  buffer7.inner[dest[0]].m[dest[1]][2] = value[2u];
 }
 
 void assign_and_preserve_padding_buffer1_m(mat3 value) {
diff --git a/test/tint/bug/tint/2059.wgsl.expected.spvasm b/test/tint/bug/tint/2059.wgsl.expected.spvasm
index 4c40ec4..043a52c 100644
--- a/test/tint/bug/tint/2059.wgsl.expected.spvasm
+++ b/test/tint/bug/tint/2059.wgsl.expected.spvasm
@@ -286,17 +286,17 @@
     %value_2 = OpFunctionParameter %mat3v3float
          %89 = OpLabel
          %90 = OpCompositeExtract %uint %dest_1 0
-         %91 = OpCompositeExtract %uint %dest_1 0
+         %91 = OpCompositeExtract %uint %dest_1 1
          %92 = OpAccessChain %_ptr_StorageBuffer_v3float %buffer7 %uint_0 %90 %uint_0 %91 %44
          %93 = OpCompositeExtract %v3float %value_2 0
                OpStore %92 %93
          %94 = OpCompositeExtract %uint %dest_1 0
-         %95 = OpCompositeExtract %uint %dest_1 0
+         %95 = OpCompositeExtract %uint %dest_1 1
          %96 = OpAccessChain %_ptr_StorageBuffer_v3float %buffer7 %uint_0 %94 %uint_0 %95 %int_1
          %97 = OpCompositeExtract %v3float %value_2 1
                OpStore %96 %97
          %98 = OpCompositeExtract %uint %dest_1 0
-         %99 = OpCompositeExtract %uint %dest_1 0
+         %99 = OpCompositeExtract %uint %dest_1 1
         %100 = OpAccessChain %_ptr_StorageBuffer_v3float %buffer7 %uint_0 %98 %uint_0 %99 %int_2
         %101 = OpCompositeExtract %v3float %value_2 2
                OpStore %100 %101