[hlsl] Add `arrayLength` tests.

Now that DecomposeMemoryAccess transform supports `arrayLength` this CL
adds various tests. The support for directly accessing the storage
buffer in the `arrayLength` call is added.

Bug: 349867642
Change-Id: Idf4a86323775e6d48b2f379398aad47b22bc7dc8
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/196969
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/hlsl/hlsl.def b/src/tint/lang/hlsl/hlsl.def
index 627646d..4378ba6 100644
--- a/src/tint/lang/hlsl/hlsl.def
+++ b/src/tint/lang/hlsl/hlsl.def
@@ -74,6 +74,7 @@
 match f32_f16: f32 | f16
 
 match storage: address_space.storage
+match function: address_space.function
 
 match readable
   : access.read
@@ -119,5 +120,5 @@
 @member_function fn Store3(byte_address_buffer<writable>, offset: u32, value: vec3<u32>)
 @member_function fn Store4(byte_address_buffer<writable>, offset: u32, value: vec4<u32>)
 
-@member_function fn GetDimensions[A: access](byte_address_buffer<A>, u32)
+@member_function fn GetDimensions[A: access](byte_address_buffer<A>, ptr<function, u32, writable>)
 
diff --git a/src/tint/lang/hlsl/intrinsic/data.cc b/src/tint/lang/hlsl/intrinsic/data.cc
index dc2b89e..4232917 100644
--- a/src/tint/lang/hlsl/intrinsic/data.cc
+++ b/src/tint/lang/hlsl/intrinsic/data.cc
@@ -562,6 +562,19 @@
   }
 };
 
+/// EnumMatcher for 'match function'
+constexpr NumberMatcher kFunctionMatcher {
+/* match */ [](MatchState&, Number number) -> Number {
+    if (number.IsAny() || number.Value() == static_cast<uint32_t>(core::AddressSpace::kFunction)) {
+      return Number(static_cast<uint32_t>(core::AddressSpace::kFunction));
+    }
+    return Number::invalid;
+  },
+/* print */ [](MatchState*, StyledText& out) {
+  out<< style::Enum("function");
+  }
+};
+
 /// EnumMatcher for 'match readable'
 constexpr NumberMatcher kReadableMatcher {
 /* match */ [](MatchState&, Number number) -> Number {
@@ -635,8 +648,9 @@
   /* [2] */ TemplateNumberMatcher<2>::matcher,
   /* [3] */ TemplateNumberMatcher<3>::matcher,
   /* [4] */ kStorageMatcher,
-  /* [5] */ kReadableMatcher,
-  /* [6] */ kWritableMatcher,
+  /* [5] */ kFunctionMatcher,
+  /* [6] */ kReadableMatcher,
+  /* [7] */ kWritableMatcher,
 };
 
 constexpr MatcherIndex kMatcherIndices[] = {
@@ -656,49 +670,53 @@
   /* [13] */ MatcherIndex(2),
   /* [14] */ MatcherIndex(1),
   /* [15] */ MatcherIndex(0),
-  /* [16] */ MatcherIndex(12),
-  /* [17] */ MatcherIndex(1),
-  /* [18] */ MatcherIndex(4),
-  /* [19] */ MatcherIndex(12),
-  /* [20] */ MatcherIndex(1),
-  /* [21] */ MatcherIndex(0),
-  /* [22] */ MatcherIndex(12),
-  /* [23] */ MatcherIndex(1),
-  /* [24] */ MatcherIndex(5),
-  /* [25] */ MatcherIndex(12),
-  /* [26] */ MatcherIndex(1),
-  /* [27] */ MatcherIndex(6),
-  /* [28] */ MatcherIndex(12),
-  /* [29] */ MatcherIndex(0),
-  /* [30] */ MatcherIndex(5),
-  /* [31] */ MatcherIndex(12),
-  /* [32] */ MatcherIndex(0),
-  /* [33] */ MatcherIndex(6),
-  /* [34] */ MatcherIndex(12),
-  /* [35] */ MatcherIndex(2),
+  /* [16] */ MatcherIndex(8),
+  /* [17] */ MatcherIndex(5),
+  /* [18] */ MatcherIndex(5),
+  /* [19] */ MatcherIndex(7),
+  /* [20] */ MatcherIndex(12),
+  /* [21] */ MatcherIndex(1),
+  /* [22] */ MatcherIndex(4),
+  /* [23] */ MatcherIndex(12),
+  /* [24] */ MatcherIndex(1),
+  /* [25] */ MatcherIndex(0),
+  /* [26] */ MatcherIndex(12),
+  /* [27] */ MatcherIndex(1),
+  /* [28] */ MatcherIndex(5),
+  /* [29] */ MatcherIndex(12),
+  /* [30] */ MatcherIndex(1),
+  /* [31] */ MatcherIndex(6),
+  /* [32] */ MatcherIndex(12),
+  /* [33] */ MatcherIndex(0),
+  /* [34] */ MatcherIndex(5),
+  /* [35] */ MatcherIndex(12),
   /* [36] */ MatcherIndex(0),
-  /* [37] */ MatcherIndex(23),
-  /* [38] */ MatcherIndex(5),
-  /* [39] */ MatcherIndex(9),
-  /* [40] */ MatcherIndex(5),
-  /* [41] */ MatcherIndex(10),
-  /* [42] */ MatcherIndex(5),
-  /* [43] */ MatcherIndex(11),
+  /* [37] */ MatcherIndex(6),
+  /* [38] */ MatcherIndex(12),
+  /* [39] */ MatcherIndex(2),
+  /* [40] */ MatcherIndex(0),
+  /* [41] */ MatcherIndex(23),
+  /* [42] */ MatcherIndex(6),
+  /* [43] */ MatcherIndex(9),
   /* [44] */ MatcherIndex(5),
-  /* [45] */ MatcherIndex(9),
-  /* [46] */ MatcherIndex(7),
-  /* [47] */ MatcherIndex(10),
-  /* [48] */ MatcherIndex(7),
-  /* [49] */ MatcherIndex(11),
+  /* [45] */ MatcherIndex(10),
+  /* [46] */ MatcherIndex(5),
+  /* [47] */ MatcherIndex(11),
+  /* [48] */ MatcherIndex(5),
+  /* [49] */ MatcherIndex(9),
   /* [50] */ MatcherIndex(7),
-  /* [51] */ MatcherIndex(23),
-  /* [52] */ MatcherIndex(6),
-  /* [53] */ MatcherIndex(23),
-  /* [54] */ MatcherIndex(0),
-  /* [55] */ MatcherIndex(25),
-  /* [56] */ MatcherIndex(26),
-  /* [57] */ MatcherIndex(24),
-  /* [58] */ MatcherIndex(27),
+  /* [51] */ MatcherIndex(10),
+  /* [52] */ MatcherIndex(7),
+  /* [53] */ MatcherIndex(11),
+  /* [54] */ MatcherIndex(7),
+  /* [55] */ MatcherIndex(23),
+  /* [56] */ MatcherIndex(7),
+  /* [57] */ MatcherIndex(23),
+  /* [58] */ MatcherIndex(0),
+  /* [59] */ MatcherIndex(25),
+  /* [60] */ MatcherIndex(26),
+  /* [61] */ MatcherIndex(24),
+  /* [62] */ MatcherIndex(27),
 };
 
 static_assert(MatcherIndicesIndex::CanIndex(kMatcherIndices),
@@ -708,62 +726,62 @@
   {
     /* [0] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(51),
+    /* matcher_indices */ MatcherIndicesIndex(55),
   },
   {
     /* [1] */
     /* usage */ core::ParameterUsage::kOffset,
-    /* matcher_indices */ MatcherIndicesIndex(24),
+    /* matcher_indices */ MatcherIndicesIndex(17),
   },
   {
     /* [2] */
     /* usage */ core::ParameterUsage::kValue,
-    /* matcher_indices */ MatcherIndicesIndex(24),
+    /* matcher_indices */ MatcherIndicesIndex(17),
   },
   {
     /* [3] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(51),
+    /* matcher_indices */ MatcherIndicesIndex(55),
   },
   {
     /* [4] */
     /* usage */ core::ParameterUsage::kOffset,
-    /* matcher_indices */ MatcherIndicesIndex(24),
+    /* matcher_indices */ MatcherIndicesIndex(17),
   },
   {
     /* [5] */
     /* usage */ core::ParameterUsage::kValue,
-    /* matcher_indices */ MatcherIndicesIndex(39),
+    /* matcher_indices */ MatcherIndicesIndex(43),
   },
   {
     /* [6] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(51),
+    /* matcher_indices */ MatcherIndicesIndex(55),
   },
   {
     /* [7] */
     /* usage */ core::ParameterUsage::kOffset,
-    /* matcher_indices */ MatcherIndicesIndex(24),
+    /* matcher_indices */ MatcherIndicesIndex(17),
   },
   {
     /* [8] */
     /* usage */ core::ParameterUsage::kValue,
-    /* matcher_indices */ MatcherIndicesIndex(41),
+    /* matcher_indices */ MatcherIndicesIndex(45),
   },
   {
     /* [9] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(51),
+    /* matcher_indices */ MatcherIndicesIndex(55),
   },
   {
     /* [10] */
     /* usage */ core::ParameterUsage::kOffset,
-    /* matcher_indices */ MatcherIndicesIndex(24),
+    /* matcher_indices */ MatcherIndicesIndex(17),
   },
   {
     /* [11] */
     /* usage */ core::ParameterUsage::kValue,
-    /* matcher_indices */ MatcherIndicesIndex(43),
+    /* matcher_indices */ MatcherIndicesIndex(47),
   },
   {
     /* [12] */
@@ -773,12 +791,12 @@
   {
     /* [13] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(19),
+    /* matcher_indices */ MatcherIndicesIndex(23),
   },
   {
     /* [14] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(34),
+    /* matcher_indices */ MatcherIndicesIndex(38),
   },
   {
     /* [15] */
@@ -798,22 +816,22 @@
   {
     /* [18] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(37),
+    /* matcher_indices */ MatcherIndicesIndex(41),
   },
   {
     /* [19] */
     /* usage */ core::ParameterUsage::kOffset,
-    /* matcher_indices */ MatcherIndicesIndex(24),
+    /* matcher_indices */ MatcherIndicesIndex(17),
   },
   {
     /* [20] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(53),
+    /* matcher_indices */ MatcherIndicesIndex(57),
   },
   {
     /* [21] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(24),
+    /* matcher_indices */ MatcherIndicesIndex(16),
   },
   {
     /* [22] */
@@ -823,17 +841,22 @@
   {
     /* [23] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(27),
+    /* matcher_indices */ MatcherIndicesIndex(31),
   },
   {
     /* [24] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(31),
+    /* matcher_indices */ MatcherIndicesIndex(35),
   },
   {
     /* [25] */
     /* usage */ core::ParameterUsage::kNone,
-    /* matcher_indices */ MatcherIndicesIndex(28),
+    /* matcher_indices */ MatcherIndicesIndex(17),
+  },
+  {
+    /* [26] */
+    /* usage */ core::ParameterUsage::kNone,
+    /* matcher_indices */ MatcherIndicesIndex(32),
   },
 };
 
@@ -844,7 +867,7 @@
   {
     /* [0] */
     /* name */ "T",
-    /* matcher_indices */ MatcherIndicesIndex(58),
+    /* matcher_indices */ MatcherIndicesIndex(62),
     /* kind */ TemplateInfo::Kind::kType,
   },
   {
@@ -868,7 +891,7 @@
   {
     /* [4] */
     /* name */ "T",
-    /* matcher_indices */ MatcherIndicesIndex(58),
+    /* matcher_indices */ MatcherIndicesIndex(62),
     /* kind */ TemplateInfo::Kind::kType,
   },
   {
@@ -886,7 +909,7 @@
   {
     /* [7] */
     /* name */ "T",
-    /* matcher_indices */ MatcherIndicesIndex(55),
+    /* matcher_indices */ MatcherIndicesIndex(59),
     /* kind */ TemplateInfo::Kind::kType,
   },
   {
@@ -898,7 +921,7 @@
   {
     /* [9] */
     /* name */ "T",
-    /* matcher_indices */ MatcherIndicesIndex(56),
+    /* matcher_indices */ MatcherIndicesIndex(60),
     /* kind */ TemplateInfo::Kind::kType,
   },
   {
@@ -910,7 +933,7 @@
   {
     /* [11] */
     /* name */ "T",
-    /* matcher_indices */ MatcherIndicesIndex(57),
+    /* matcher_indices */ MatcherIndicesIndex(61),
     /* kind */ TemplateInfo::Kind::kType,
   },
   {
@@ -939,7 +962,7 @@
     /* num_templates   */ 3,
     /* templates */ TemplateIndex(4),
     /* parameters */ ParameterIndex(12),
-    /* return_matcher_indices */ MatcherIndicesIndex(34),
+    /* return_matcher_indices */ MatcherIndicesIndex(38),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -950,7 +973,7 @@
     /* num_templates   */ 3,
     /* templates */ TemplateIndex(4),
     /* parameters */ ParameterIndex(14),
-    /* return_matcher_indices */ MatcherIndicesIndex(19),
+    /* return_matcher_indices */ MatcherIndicesIndex(23),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -972,7 +995,7 @@
     /* num_templates   */ 1,
     /* templates */ TemplateIndex(7),
     /* parameters */ ParameterIndex(22),
-    /* return_matcher_indices */ MatcherIndicesIndex(18),
+    /* return_matcher_indices */ MatcherIndicesIndex(22),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -983,7 +1006,7 @@
     /* num_templates   */ 2,
     /* templates */ TemplateIndex(7),
     /* parameters */ ParameterIndex(13),
-    /* return_matcher_indices */ MatcherIndicesIndex(16),
+    /* return_matcher_indices */ MatcherIndicesIndex(20),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -994,7 +1017,7 @@
     /* num_templates   */ 1,
     /* templates */ TemplateIndex(9),
     /* parameters */ ParameterIndex(22),
-    /* return_matcher_indices */ MatcherIndicesIndex(24),
+    /* return_matcher_indices */ MatcherIndicesIndex(17),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1005,7 +1028,7 @@
     /* num_templates   */ 2,
     /* templates */ TemplateIndex(9),
     /* parameters */ ParameterIndex(13),
-    /* return_matcher_indices */ MatcherIndicesIndex(22),
+    /* return_matcher_indices */ MatcherIndicesIndex(26),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1016,7 +1039,7 @@
     /* num_templates   */ 1,
     /* templates */ TemplateIndex(11),
     /* parameters */ ParameterIndex(22),
-    /* return_matcher_indices */ MatcherIndicesIndex(27),
+    /* return_matcher_indices */ MatcherIndicesIndex(31),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1027,7 +1050,7 @@
     /* num_templates   */ 2,
     /* templates */ TemplateIndex(11),
     /* parameters */ ParameterIndex(13),
-    /* return_matcher_indices */ MatcherIndicesIndex(25),
+    /* return_matcher_indices */ MatcherIndicesIndex(29),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1038,7 +1061,7 @@
     /* num_templates   */ 0,
     /* templates */ TemplateIndex(/* invalid */),
     /* parameters */ ParameterIndex(23),
-    /* return_matcher_indices */ MatcherIndicesIndex(24),
+    /* return_matcher_indices */ MatcherIndicesIndex(17),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1049,7 +1072,7 @@
     /* num_templates   */ 1,
     /* templates */ TemplateIndex(8),
     /* parameters */ ParameterIndex(24),
-    /* return_matcher_indices */ MatcherIndicesIndex(28),
+    /* return_matcher_indices */ MatcherIndicesIndex(32),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1059,8 +1082,8 @@
     /* num_explicit_templates */ 0,
     /* num_templates   */ 0,
     /* templates */ TemplateIndex(/* invalid */),
-    /* parameters */ ParameterIndex(21),
-    /* return_matcher_indices */ MatcherIndicesIndex(27),
+    /* parameters */ ParameterIndex(25),
+    /* return_matcher_indices */ MatcherIndicesIndex(31),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1070,8 +1093,8 @@
     /* num_explicit_templates */ 0,
     /* num_templates   */ 1,
     /* templates */ TemplateIndex(8),
-    /* parameters */ ParameterIndex(25),
-    /* return_matcher_indices */ MatcherIndicesIndex(31),
+    /* parameters */ ParameterIndex(26),
+    /* return_matcher_indices */ MatcherIndicesIndex(35),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1082,7 +1105,7 @@
     /* num_templates   */ 0,
     /* templates */ TemplateIndex(/* invalid */),
     /* parameters */ ParameterIndex(18),
-    /* return_matcher_indices */ MatcherIndicesIndex(24),
+    /* return_matcher_indices */ MatcherIndicesIndex(17),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1093,7 +1116,7 @@
     /* num_templates   */ 0,
     /* templates */ TemplateIndex(/* invalid */),
     /* parameters */ ParameterIndex(18),
-    /* return_matcher_indices */ MatcherIndicesIndex(39),
+    /* return_matcher_indices */ MatcherIndicesIndex(43),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1104,7 +1127,7 @@
     /* num_templates   */ 0,
     /* templates */ TemplateIndex(/* invalid */),
     /* parameters */ ParameterIndex(18),
-    /* return_matcher_indices */ MatcherIndicesIndex(41),
+    /* return_matcher_indices */ MatcherIndicesIndex(45),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1115,7 +1138,7 @@
     /* num_templates   */ 0,
     /* templates */ TemplateIndex(/* invalid */),
     /* parameters */ ParameterIndex(18),
-    /* return_matcher_indices */ MatcherIndicesIndex(43),
+    /* return_matcher_indices */ MatcherIndicesIndex(47),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1126,7 +1149,7 @@
     /* num_templates   */ 0,
     /* templates */ TemplateIndex(/* invalid */),
     /* parameters */ ParameterIndex(18),
-    /* return_matcher_indices */ MatcherIndicesIndex(46),
+    /* return_matcher_indices */ MatcherIndicesIndex(19),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1137,7 +1160,7 @@
     /* num_templates   */ 0,
     /* templates */ TemplateIndex(/* invalid */),
     /* parameters */ ParameterIndex(18),
-    /* return_matcher_indices */ MatcherIndicesIndex(45),
+    /* return_matcher_indices */ MatcherIndicesIndex(49),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1148,7 +1171,7 @@
     /* num_templates   */ 0,
     /* templates */ TemplateIndex(/* invalid */),
     /* parameters */ ParameterIndex(18),
-    /* return_matcher_indices */ MatcherIndicesIndex(47),
+    /* return_matcher_indices */ MatcherIndicesIndex(51),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1159,7 +1182,7 @@
     /* num_templates   */ 0,
     /* templates */ TemplateIndex(/* invalid */),
     /* parameters */ ParameterIndex(18),
-    /* return_matcher_indices */ MatcherIndicesIndex(49),
+    /* return_matcher_indices */ MatcherIndicesIndex(53),
     /* const_eval_fn */ ConstEvalFunctionIndex(/* invalid */),
   },
   {
@@ -1340,7 +1363,7 @@
   },
   {
     /* [18] */
-    /* fn GetDimensions[A : access](byte_address_buffer<A>, u32) */
+    /* fn GetDimensions[A : access](byte_address_buffer<A>, ptr<function, u32, writable>) */
     /* num overloads */ 1,
     /* overloads */ OverloadIndex(25),
   },
diff --git a/src/tint/lang/hlsl/writer/BUILD.bazel b/src/tint/lang/hlsl/writer/BUILD.bazel
index bcb889a..b66d284 100644
--- a/src/tint/lang/hlsl/writer/BUILD.bazel
+++ b/src/tint/lang/hlsl/writer/BUILD.bazel
@@ -90,6 +90,7 @@
   alwayslink = True,
   srcs = [
     "access_test.cc",
+    "arraylength_test.cc",
     "binary_test.cc",
     "bitcast_test.cc",
     "builtin_test.cc",
diff --git a/src/tint/lang/hlsl/writer/BUILD.cmake b/src/tint/lang/hlsl/writer/BUILD.cmake
index cb0fdde..1113e5f 100644
--- a/src/tint/lang/hlsl/writer/BUILD.cmake
+++ b/src/tint/lang/hlsl/writer/BUILD.cmake
@@ -101,6 +101,7 @@
 ################################################################################
 tint_add_target(tint_lang_hlsl_writer_test test
   lang/hlsl/writer/access_test.cc
+  lang/hlsl/writer/arraylength_test.cc
   lang/hlsl/writer/binary_test.cc
   lang/hlsl/writer/bitcast_test.cc
   lang/hlsl/writer/builtin_test.cc
diff --git a/src/tint/lang/hlsl/writer/BUILD.gn b/src/tint/lang/hlsl/writer/BUILD.gn
index b4ff72d..13709f4 100644
--- a/src/tint/lang/hlsl/writer/BUILD.gn
+++ b/src/tint/lang/hlsl/writer/BUILD.gn
@@ -93,6 +93,7 @@
     tint_unittests_source_set("unittests") {
       sources = [
         "access_test.cc",
+        "arraylength_test.cc",
         "binary_test.cc",
         "bitcast_test.cc",
         "builtin_test.cc",
diff --git a/src/tint/lang/hlsl/writer/access_test.cc b/src/tint/lang/hlsl/writer/access_test.cc
index cc47037..1a2cbd0 100644
--- a/src/tint/lang/hlsl/writer/access_test.cc
+++ b/src/tint/lang/hlsl/writer/access_test.cc
@@ -731,11 +731,12 @@
   int v_2 = k;
   uint v_3 = 0u;
   sb.GetDimensions(v_3);
-  uint v_4 = min(uint(v), (((v_3 - 16u) / 128u) - 1u));
-  uint v_5 = min(v_1, 2u);
-  uint v_6 = (uint(v_4) * 128u);
-  uint v_7 = (uint(v_5) * 32u);
-  float x = asfloat(sb.Load((((48u + v_6) + v_7) + (uint(min(uint(v_2), 2u)) * 4u))));
+  uint v_4 = (((v_3 - 16u) / 128u) - 1u);
+  uint v_5 = min(uint(v), v_4);
+  uint v_6 = min(v_1, 2u);
+  uint v_7 = (uint(v_5) * 128u);
+  uint v_8 = (uint(v_6) * 32u);
+  float x = asfloat(sb.Load((((48u + v_7) + v_8) + (uint(min(uint(v_2), 2u)) * 4u))));
 }
 
 )");
diff --git a/src/tint/lang/hlsl/writer/arraylength_test.cc b/src/tint/lang/hlsl/writer/arraylength_test.cc
new file mode 100644
index 0000000..4707eb5
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/arraylength_test.cc
@@ -0,0 +1,232 @@
+// 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/core/fluent_types.h"
+#include "src/tint/lang/core/ir/function.h"
+#include "src/tint/lang/core/number.h"
+#include "src/tint/lang/hlsl/writer/helper_test.h"
+
+#include "gtest/gtest.h"
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+namespace tint::hlsl::writer {
+namespace {
+
+TEST_F(HlslWriterTest, ArrayLengthDirect) {
+    auto* sb = b.Var("sb", ty.ptr<storage, array<i32>>());
+    sb->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("len", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer sb : register(u0);
+void foo() {
+  uint v = 0u;
+  sb.GetDimensions(v);
+  uint len = (v / 4u);
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, ArrayLengthInStruct) {
+    auto* SB =
+        ty.Struct(mod.symbols.New("SB"), {
+                                             {mod.symbols.New("x"), ty.i32()},
+                                             {mod.symbols.New("arr"), ty.runtime_array(ty.i32())},
+                                         });
+
+    auto* sb = b.Var("sb", ty.ptr(storage, SB));
+    sb->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("len", b.Call(ty.u32(), core::BuiltinFn::kArrayLength,
+                            b.Access(ty.ptr<storage, array<i32>>(), sb, 1_u)));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer sb : register(u0);
+void foo() {
+  uint v = 0u;
+  sb.GetDimensions(v);
+  uint len = ((v - 4u) / 4u);
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, ArrayLengthOfStruct) {
+    auto* SB = ty.Struct(mod.symbols.New("SB"), {
+                                                    {mod.symbols.New("f"), ty.f32()},
+                                                });
+
+    auto* sb = b.Var("sb", ty.ptr(storage, ty.runtime_array(SB), core::Access::kRead));
+    sb->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("len", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+ByteAddressBuffer sb : register(t0);
+void foo() {
+  uint v = 0u;
+  sb.GetDimensions(v);
+  uint len = (v / 4u);
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, ArrayLengthArrayOfArrayOfStruct) {
+    auto* SB = ty.Struct(mod.symbols.New("SB"), {
+                                                    {mod.symbols.New("f"), ty.f32()},
+                                                });
+    auto* sb = b.Var("sb", ty.ptr(storage, ty.runtime_array(ty.array(SB, 4))));
+    sb->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("len", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer sb : register(u0);
+void foo() {
+  uint v = 0u;
+  sb.GetDimensions(v);
+  uint len = (v / 16u);
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, ArrayLengthMultiple) {
+    auto* sb = b.Var("sb", ty.ptr<storage, array<i32>>());
+    sb->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("a", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Let("b", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Let("c", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer sb : register(u0);
+void foo() {
+  uint v = 0u;
+  sb.GetDimensions(v);
+  uint a = (v / 4u);
+  uint v_1 = 0u;
+  sb.GetDimensions(v_1);
+  uint b = (v_1 / 4u);
+  uint v_2 = 0u;
+  sb.GetDimensions(v_2);
+  uint c = (v_2 / 4u);
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, ArrayLengthMultipleStorageBuffers) {
+    auto* SB1 =
+        ty.Struct(mod.symbols.New("SB1"), {
+                                              {mod.symbols.New("x"), ty.i32()},
+                                              {mod.symbols.New("arr1"), ty.runtime_array(ty.i32())},
+                                          });
+    auto* SB2 = ty.Struct(mod.symbols.New("SB2"),
+                          {
+                              {mod.symbols.New("x"), ty.i32()},
+                              {mod.symbols.New("arr2"), ty.runtime_array(ty.vec4<f32>())},
+                          });
+    auto* sb1 = b.Var("sb1", ty.ptr(storage, SB1));
+    sb1->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb1);
+
+    auto* sb2 = b.Var("sb2", ty.ptr(storage, SB2));
+    sb2->SetBindingPoint(0, 1);
+    b.ir.root_block->Append(sb2);
+
+    auto* sb3 = b.Var("sb3", ty.ptr(storage, ty.runtime_array(ty.i32())));
+    sb3->SetBindingPoint(0, 2);
+    b.ir.root_block->Append(sb3);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("len1", b.Call(ty.u32(), core::BuiltinFn::kArrayLength,
+                             b.Access(ty.ptr<storage, array<i32>>(), sb1, 1_u)));
+        b.Let("len2", b.Call(ty.u32(), core::BuiltinFn::kArrayLength,
+                             b.Access(ty.ptr<storage, array<vec4<f32>>>(), sb2, 1_u)));
+        b.Let("len3", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb3));
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer sb1 : register(u0);
+RWByteAddressBuffer sb2 : register(u1);
+RWByteAddressBuffer sb3 : register(u2);
+void foo() {
+  uint v = 0u;
+  sb1.GetDimensions(v);
+  uint len1 = ((v - 4u) / 4u);
+  uint v_1 = 0u;
+  sb2.GetDimensions(v_1);
+  uint len2 = ((v_1 - 16u) / 16u);
+  uint v_2 = 0u;
+  sb3.GetDimensions(v_2);
+  uint len3 = (v_2 / 4u);
+}
+
+)");
+}
+
+}  // namespace
+}  // namespace tint::hlsl::writer
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_memory_access.cc b/src/tint/lang/hlsl/writer/raise/decompose_memory_access.cc
index 58ea58b..e26ea3b 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_memory_access.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_memory_access.cc
@@ -93,7 +93,12 @@
                     [&](core::ir::Store* st) { usage_worklist.Push(st); },
                     [&](core::ir::Load* ld) { usage_worklist.Push(ld); },
                     [&](core::ir::Access* a) { usage_worklist.Push(a); },
-                    [&](core::ir::Let* l) { usage_worklist.Push(l); },  //
+                    [&](core::ir::Let* l) { usage_worklist.Push(l); },
+                    [&](core::ir::CoreBuiltinCall* call) {
+                        TINT_ASSERT(call->Func() == core::BuiltinFn::kArrayLength);
+                        usage_worklist.Push(call);
+                    },
+                    //
                     TINT_ICE_ON_NO_MATCH);
             }
 
@@ -125,6 +130,9 @@
                         let->Result(0)->ReplaceAllUsesWith(result);
                         let->Destroy();
                     },
+                    [&](core::ir::CoreBuiltinCall* call) {
+                        ArrayLength(var, call, var_ty->StoreType(), 0);
+                    },  //
                     TINT_ICE_ON_NO_MATCH);
             }
 
@@ -133,6 +141,35 @@
         }
     }
 
+    void ArrayLength(core::ir::Var* var,
+                     core::ir::CoreBuiltinCall* call,
+                     const core::type::Type* type,
+                     uint32_t offset) {
+        auto* arr_ty = type->As<core::type::Array>();
+        // If the `arrayLength` was called directly on the storage buffer then
+        // it _must_ be a runtime array.
+        TINT_ASSERT(arr_ty && arr_ty->Count()->As<core::type::RuntimeArrayCount>());
+
+        b.InsertBefore(call, [&] {
+            // The `GetDimensions` call uses out parameters for all return values, there is no
+            // return value. This ends up being the result value we care about.
+            //
+            // This creates a var with an access which means that when we emit the HLSL we'll emit
+            // the correct `var` name.
+            core::ir::Instruction* inst = b.Var(ty.ptr(function, ty.u32()));
+            b.MemberCall<hlsl::ir::MemberBuiltinCall>(ty.void_(), BuiltinFn::kGetDimensions, var,
+                                                      inst->Result(0));
+
+            inst = b.Load(inst);
+            if (offset > 0) {
+                inst = b.Subtract(ty.u32(), inst, u32(offset));
+            }
+            auto* div = b.Divide(ty.u32(), inst, u32(arr_ty->Stride()));
+            call->Result(0)->ReplaceAllUsesWith(div->Result(0));
+        });
+        call->Destroy();
+    }
+
     struct OffsetData {
         uint32_t byte_offset = 0;
         Vector<core::ir::Value*, 4> expr{};
@@ -446,26 +483,7 @@
                     // If this access chain is being used in an `arrayLength` call then the access
                     // chain _must_ have resolved to the runtime array member of the structure. So,
                     // we _must_ have set `obj` to the array member which is a runtime array.
-                    auto* arr_ty = obj->As<core::type::Array>();
-                    TINT_ASSERT(arr_ty && arr_ty->Count()->Is<core::type::RuntimeArrayCount>());
-
-                    b.InsertBefore(a, [&] {
-                        auto* val = b.Let(ty.u32());
-                        val->SetValue(b.Zero<u32>());
-
-                        b.MemberCall<hlsl::ir::MemberBuiltinCall>(
-                            ty.void_(), BuiltinFn::kGetDimensions, var, val);
-
-                        // Because the `runtime_array` must be the last element of the outer most
-                        // structure and we're calling `arrayLength` on the array then the access
-                        // chain must have only had a single item in it and that item must have been
-                        // a constant offset.
-                        auto* div =
-                            b.Divide(ty.u32(), b.Subtract(ty.u32(), val, u32(offset->byte_offset)),
-                                     u32(arr_ty->Stride()));
-                        call->Result(0)->ReplaceAllUsesWith(div->Result(0));
-                    });
-                    call->Destroy();
+                    ArrayLength(var, call, obj, offset->byte_offset);
                 },  //
                 TINT_ICE_ON_NO_MATCH);
         }
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_memory_access_test.cc b/src/tint/lang/hlsl/writer/raise/decompose_memory_access_test.cc
index c6b1662..22fe773 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_memory_access_test.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_memory_access_test.cc
@@ -3581,5 +3581,403 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(HlslWriterDecomposeMemoryAccessTest, ArrayLengthDirect) {
+    auto* sb = b.Var("sb", ty.ptr<storage, array<i32>>());
+    sb->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("len", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Return(func);
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %sb:ptr<storage, array<i32>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %3:u32 = arrayLength %sb
+    %len:u32 = let %3
+    ret
+  }
+}
+)";
+    ASSERT_EQ(src, str());
+
+    auto* expect = R"(
+$B1: {  # root
+  %sb:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %3:ptr<function, u32, read_write> = var
+    %4:void = %sb.GetDimensions %3
+    %5:u32 = load %3
+    %6:u32 = div %5, 4u
+    %len:u32 = let %6
+    ret
+  }
+}
+)";
+    Run(DecomposeMemoryAccess);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterDecomposeMemoryAccessTest, ArrayLengthInStruct) {
+    auto* SB =
+        ty.Struct(mod.symbols.New("SB"), {
+                                             {mod.symbols.New("x"), ty.i32()},
+                                             {mod.symbols.New("arr"), ty.runtime_array(ty.i32())},
+                                         });
+
+    auto* sb = b.Var("sb", ty.ptr(storage, SB));
+    sb->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("len", b.Call(ty.u32(), core::BuiltinFn::kArrayLength,
+                            b.Access(ty.ptr<storage, array<i32>>(), sb, 1_u)));
+        b.Return(func);
+    });
+
+    auto* src = R"(
+SB = struct @align(4) {
+  x:i32 @offset(0)
+  arr:array<i32> @offset(4)
+}
+
+$B1: {  # root
+  %sb:ptr<storage, SB, read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %3:ptr<storage, array<i32>, read_write> = access %sb, 1u
+    %4:u32 = arrayLength %3
+    %len:u32 = let %4
+    ret
+  }
+}
+)";
+    ASSERT_EQ(src, str());
+
+    auto* expect = R"(
+SB = struct @align(4) {
+  x:i32 @offset(0)
+  arr:array<i32> @offset(4)
+}
+
+$B1: {  # root
+  %sb:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %3:ptr<function, u32, read_write> = var
+    %4:void = %sb.GetDimensions %3
+    %5:u32 = load %3
+    %6:u32 = sub %5, 4u
+    %7:u32 = div %6, 4u
+    %len:u32 = let %7
+    ret
+  }
+}
+)";
+    Run(DecomposeMemoryAccess);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterDecomposeMemoryAccessTest, ArrayLengthOfStruct) {
+    auto* SB = ty.Struct(mod.symbols.New("SB"), {
+                                                    {mod.symbols.New("f"), ty.f32()},
+                                                });
+
+    auto* sb = b.Var("sb", ty.ptr(storage, ty.runtime_array(SB), core::Access::kRead));
+    sb->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("len", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Return(func);
+    });
+
+    auto* src = R"(
+SB = struct @align(4) {
+  f:f32 @offset(0)
+}
+
+$B1: {  # root
+  %sb:ptr<storage, array<SB>, read> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %3:u32 = arrayLength %sb
+    %len:u32 = let %3
+    ret
+  }
+}
+)";
+    ASSERT_EQ(src, str());
+
+    auto* expect = R"(
+SB = struct @align(4) {
+  f:f32 @offset(0)
+}
+
+$B1: {  # root
+  %sb:hlsl.byte_address_buffer<read> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %3:ptr<function, u32, read_write> = var
+    %4:void = %sb.GetDimensions %3
+    %5:u32 = load %3
+    %6:u32 = div %5, 4u
+    %len:u32 = let %6
+    ret
+  }
+}
+)";
+    Run(DecomposeMemoryAccess);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterDecomposeMemoryAccessTest, ArrayLengthArrayOfArrayOfStruct) {
+    auto* SB = ty.Struct(mod.symbols.New("SB"), {
+                                                    {mod.symbols.New("f"), ty.f32()},
+                                                });
+    auto* sb = b.Var("sb", ty.ptr(storage, ty.runtime_array(ty.array(SB, 4))));
+    sb->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("len", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Return(func);
+    });
+
+    auto* src = R"(
+SB = struct @align(4) {
+  f:f32 @offset(0)
+}
+
+$B1: {  # root
+  %sb:ptr<storage, array<array<SB, 4>>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %3:u32 = arrayLength %sb
+    %len:u32 = let %3
+    ret
+  }
+}
+)";
+    ASSERT_EQ(src, str());
+
+    auto* expect = R"(
+SB = struct @align(4) {
+  f:f32 @offset(0)
+}
+
+$B1: {  # root
+  %sb:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %3:ptr<function, u32, read_write> = var
+    %4:void = %sb.GetDimensions %3
+    %5:u32 = load %3
+    %6:u32 = div %5, 16u
+    %len:u32 = let %6
+    ret
+  }
+}
+)";
+    Run(DecomposeMemoryAccess);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterDecomposeMemoryAccessTest, ArrayLengthMultiple) {
+    auto* sb = b.Var("sb", ty.ptr<storage, array<i32>>());
+    sb->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("a", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Let("b", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Let("c", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb));
+        b.Return(func);
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %sb:ptr<storage, array<i32>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %3:u32 = arrayLength %sb
+    %a:u32 = let %3
+    %5:u32 = arrayLength %sb
+    %b:u32 = let %5
+    %7:u32 = arrayLength %sb
+    %c:u32 = let %7
+    ret
+  }
+}
+)";
+    ASSERT_EQ(src, str());
+
+    auto* expect = R"(
+$B1: {  # root
+  %sb:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %3:ptr<function, u32, read_write> = var
+    %4:void = %sb.GetDimensions %3
+    %5:u32 = load %3
+    %6:u32 = div %5, 4u
+    %a:u32 = let %6
+    %8:ptr<function, u32, read_write> = var
+    %9:void = %sb.GetDimensions %8
+    %10:u32 = load %8
+    %11:u32 = div %10, 4u
+    %b:u32 = let %11
+    %13:ptr<function, u32, read_write> = var
+    %14:void = %sb.GetDimensions %13
+    %15:u32 = load %13
+    %16:u32 = div %15, 4u
+    %c:u32 = let %16
+    ret
+  }
+}
+)";
+    Run(DecomposeMemoryAccess);
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterDecomposeMemoryAccessTest, ArrayLengthMultipleStorageBuffers) {
+    auto* SB1 =
+        ty.Struct(mod.symbols.New("SB1"), {
+                                              {mod.symbols.New("x"), ty.i32()},
+                                              {mod.symbols.New("arr1"), ty.runtime_array(ty.i32())},
+                                          });
+    auto* SB2 = ty.Struct(mod.symbols.New("SB2"),
+                          {
+                              {mod.symbols.New("x"), ty.i32()},
+                              {mod.symbols.New("arr2"), ty.runtime_array(ty.vec4<f32>())},
+                          });
+    auto* sb1 = b.Var("sb1", ty.ptr(storage, SB1));
+    sb1->SetBindingPoint(0, 0);
+    b.ir.root_block->Append(sb1);
+
+    auto* sb2 = b.Var("sb2", ty.ptr(storage, SB2));
+    sb2->SetBindingPoint(0, 1);
+    b.ir.root_block->Append(sb2);
+
+    auto* sb3 = b.Var("sb3", ty.ptr(storage, ty.runtime_array(ty.i32())));
+    sb3->SetBindingPoint(0, 2);
+    b.ir.root_block->Append(sb3);
+
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        b.Let("len1", b.Call(ty.u32(), core::BuiltinFn::kArrayLength,
+                             b.Access(ty.ptr<storage, array<i32>>(), sb1, 1_u)));
+        b.Let("len2", b.Call(ty.u32(), core::BuiltinFn::kArrayLength,
+                             b.Access(ty.ptr<storage, array<vec4<f32>>>(), sb2, 1_u)));
+        b.Let("len3", b.Call(ty.u32(), core::BuiltinFn::kArrayLength, sb3));
+        b.Return(func);
+    });
+
+    auto* src = R"(
+SB1 = struct @align(4) {
+  x:i32 @offset(0)
+  arr1:array<i32> @offset(4)
+}
+
+SB2 = struct @align(16) {
+  x_1:i32 @offset(0)
+  arr2:array<vec4<f32>> @offset(16)
+}
+
+$B1: {  # root
+  %sb1:ptr<storage, SB1, read_write> = var @binding_point(0, 0)
+  %sb2:ptr<storage, SB2, read_write> = var @binding_point(0, 1)
+  %sb3:ptr<storage, array<i32>, read_write> = var @binding_point(0, 2)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %5:ptr<storage, array<i32>, read_write> = access %sb1, 1u
+    %6:u32 = arrayLength %5
+    %len1:u32 = let %6
+    %8:ptr<storage, array<vec4<f32>>, read_write> = access %sb2, 1u
+    %9:u32 = arrayLength %8
+    %len2:u32 = let %9
+    %11:u32 = arrayLength %sb3
+    %len3:u32 = let %11
+    ret
+  }
+}
+)";
+    ASSERT_EQ(src, str());
+
+    auto* expect = R"(
+SB1 = struct @align(4) {
+  x:i32 @offset(0)
+  arr1:array<i32> @offset(4)
+}
+
+SB2 = struct @align(16) {
+  x_1:i32 @offset(0)
+  arr2:array<vec4<f32>> @offset(16)
+}
+
+$B1: {  # root
+  %sb1:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+  %sb2:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 1)
+  %sb3:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 2)
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    %5:ptr<function, u32, read_write> = var
+    %6:void = %sb1.GetDimensions %5
+    %7:u32 = load %5
+    %8:u32 = sub %7, 4u
+    %9:u32 = div %8, 4u
+    %len1:u32 = let %9
+    %11:ptr<function, u32, read_write> = var
+    %12:void = %sb2.GetDimensions %11
+    %13:u32 = load %11
+    %14:u32 = sub %13, 16u
+    %15:u32 = div %14, 16u
+    %len2:u32 = let %15
+    %17:ptr<function, u32, read_write> = var
+    %18:void = %sb3.GetDimensions %17
+    %19:u32 = load %17
+    %20:u32 = div %19, 4u
+    %len3:u32 = let %20
+    ret
+  }
+}
+)";
+    Run(DecomposeMemoryAccess);
+    EXPECT_EQ(expect, str());
+}
+
 }  // namespace
 }  // namespace tint::hlsl::writer::raise