transform: Handle arrayLength for non-struct buffers

These two transforms previously assumed that the argument to
arrayLength had the form `&struct_var.array_member`. We now also need
to handle the case where it is just `&array_var`.

Bug: tint:1372
Change-Id: I173a84bd32c324445573a295b281a51e291c2ae2
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/76163
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/transform/array_length_from_uniform.cc b/src/transform/array_length_from_uniform.cc
index 5084a54..32289fe 100644
--- a/src/transform/array_length_from_uniform.cc
+++ b/src/transform/array_length_from_uniform.cc
@@ -59,30 +59,26 @@
     }
 
     // Get the storage buffer that contains the runtime array.
-    // We assume that the argument to `arrayLength` has the form
-    // `&resource.array`, which requires that `SimplifyPointers` have been run
-    // before this transform.
+    // Since we require SimplifyPointers, we can assume that the arrayLength()
+    // call has one of two forms:
+    //   arrayLength(&struct_var.array_member)
+    //   arrayLength(&array_var)
     auto* param = call_expr->args[0]->As<ast::UnaryOpExpression>();
     if (!param || param->op != ast::UnaryOp::kAddressOf) {
       TINT_ICE(Transform, ctx.dst->Diagnostics())
-          << "expected form of arrayLength argument to be "
-             "&resource.array";
+          << "expected form of arrayLength argument to be &array_var or "
+             "&struct_var.array_member";
       break;
     }
-    auto* accessor = param->expr->As<ast::MemberAccessorExpression>();
-    if (!accessor) {
-      TINT_ICE(Transform, ctx.dst->Diagnostics())
-          << "expected form of arrayLength argument to be "
-             "&resource.array";
-      break;
+    auto* storage_buffer_expr = param->expr;
+    if (auto* accessor = param->expr->As<ast::MemberAccessorExpression>()) {
+      storage_buffer_expr = accessor->structure;
     }
-    auto* storage_buffer_expr = accessor->structure;
-    auto* storage_buffer_sem =
-        sem.Get(storage_buffer_expr)->As<sem::VariableUser>();
+    auto* storage_buffer_sem = sem.Get<sem::VariableUser>(storage_buffer_expr);
     if (!storage_buffer_sem) {
       TINT_ICE(Transform, ctx.dst->Diagnostics())
-          << "expected form of arrayLength argument to be "
-             "&resource.array";
+          << "expected form of arrayLength argument to be &array_var or "
+             "&struct_var.array_member";
       break;
     }
 
@@ -183,14 +179,25 @@
         //                total_storage_buffer_size - array_offset
         // array_length = ----------------------------------------
         //                             array_stride
-        auto* storage_buffer_type =
-            storage_buffer_sem->Type()->UnwrapRef()->As<sem::Struct>();
-        auto* array_member_sem = storage_buffer_type->Members().back();
-        uint32_t array_offset = array_member_sem->Offset();
-        uint32_t array_stride = array_member_sem->Size();
-        auto* array_length =
-            ctx.dst->Div(ctx.dst->Sub(total_storage_buffer_size, array_offset),
-                         array_stride);
+        const ast::Expression* total_size = total_storage_buffer_size;
+        auto* storage_buffer_type = storage_buffer_sem->Type()->UnwrapRef();
+        const sem::Array* array_type = nullptr;
+        if (auto* str = storage_buffer_type->As<sem::Struct>()) {
+          // The variable is a struct, so subtract the byte offset of the array
+          // member.
+          auto* array_member_sem = str->Members().back();
+          array_type = array_member_sem->Type()->As<sem::Array>();
+          total_size = ctx.dst->Sub(total_storage_buffer_size,
+                                    array_member_sem->Offset());
+        } else if (auto* arr = storage_buffer_type->As<sem::Array>()) {
+          array_type = arr;
+        } else {
+          TINT_ICE(Transform, ctx.dst->Diagnostics())
+              << "expected form of arrayLength argument to be &array_var or "
+                 "&struct_var.array_member";
+          return;
+        }
+        auto* array_length = ctx.dst->Div(total_size, array_type->Stride());
 
         ctx.Replace(call_expr, array_length);
       });
diff --git a/src/transform/array_length_from_uniform_test.cc b/src/transform/array_length_from_uniform_test.cc
index 59f5a4d..77fb0c3 100644
--- a/src/transform/array_length_from_uniform_test.cc
+++ b/src/transform/array_length_from_uniform_test.cc
@@ -52,6 +52,44 @@
 
 TEST_F(ArrayLengthFromUniformTest, Basic) {
   auto* src = R"(
+[[group(0), binding(0)]] var<storage, read> sb : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var len : u32 = arrayLength(&sb);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  buffer_size : array<vec4<u32>, 1u>;
+};
+
+[[group(0), binding(30)]] var<uniform> tint_symbol_1 : tint_symbol;
+
+[[group(0), binding(0)]] var<storage, read> sb : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var len : u32 = (tint_symbol_1.buffer_size[0u][0u] / 4u);
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
+            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
+TEST_F(ArrayLengthFromUniformTest, BasicInStruct) {
+  auto* src = R"(
 struct SB {
   x : i32;
   arr : array<i32>;
@@ -100,6 +138,44 @@
 
 TEST_F(ArrayLengthFromUniformTest, WithStride) {
   auto* src = R"(
+[[group(0), binding(0)]] var<storage, read> sb : [[stride(64)]] array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var len : u32 = arrayLength(&sb);
+}
+)";
+
+  auto* expect = R"(
+struct tint_symbol {
+  buffer_size : array<vec4<u32>, 1u>;
+};
+
+[[group(0), binding(30)]] var<uniform> tint_symbol_1 : tint_symbol;
+
+[[group(0), binding(0)]] var<storage, read> sb : [[stride(64)]] array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var len : u32 = (tint_symbol_1.buffer_size[0u][0u] / 64u);
+}
+)";
+
+  ArrayLengthFromUniform::Config cfg({0, 30u});
+  cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0);
+
+  DataMap data;
+  data.Add<ArrayLengthFromUniform::Config>(std::move(cfg));
+
+  auto got = Run<Unshadow, SimplifyPointers, ArrayLengthFromUniform>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+  EXPECT_EQ(std::unordered_set<uint32_t>({0}),
+            got.data.Get<ArrayLengthFromUniform::Result>()->used_size_indices);
+}
+
+TEST_F(ArrayLengthFromUniformTest, WithStride_InStruct) {
+  auto* src = R"(
 struct SB {
   x : i32;
   y : f32;
@@ -158,32 +234,24 @@
   x : i32;
   arr2 : array<vec4<f32>>;
 };
-struct SB3 {
-  x : i32;
-  arr3 : array<vec4<f32>>;
-};
 struct SB4 {
   x : i32;
   arr4 : array<vec4<f32>>;
 };
-struct SB5 {
-  x : i32;
-  arr5 : array<vec4<f32>>;
-};
 
 [[group(0), binding(2)]] var<storage, read> sb1 : SB1;
 [[group(1), binding(2)]] var<storage, read> sb2 : SB2;
-[[group(2), binding(2)]] var<storage, read> sb3 : SB3;
+[[group(2), binding(2)]] var<storage, read> sb3 : array<vec4<f32>>;
 [[group(3), binding(2)]] var<storage, read> sb4 : SB4;
-[[group(4), binding(2)]] var<storage, read> sb5 : SB5;
+[[group(4), binding(2)]] var<storage, read> sb5 : array<vec4<f32>>;
 
 [[stage(compute), workgroup_size(1)]]
 fn main() {
   var len1 : u32 = arrayLength(&(sb1.arr1));
   var len2 : u32 = arrayLength(&(sb2.arr2));
-  var len3 : u32 = arrayLength(&(sb3.arr3));
+  var len3 : u32 = arrayLength(&sb3);
   var len4 : u32 = arrayLength(&(sb4.arr4));
-  var len5 : u32 = arrayLength(&(sb5.arr5));
+  var len5 : u32 = arrayLength(&sb5);
   var x : u32 = (len1 + len2 + len3 + len4 + len5);
 }
 )";
@@ -205,38 +273,28 @@
   arr2 : array<vec4<f32>>;
 };
 
-struct SB3 {
-  x : i32;
-  arr3 : array<vec4<f32>>;
-};
-
 struct SB4 {
   x : i32;
   arr4 : array<vec4<f32>>;
 };
 
-struct SB5 {
-  x : i32;
-  arr5 : array<vec4<f32>>;
-};
-
 [[group(0), binding(2)]] var<storage, read> sb1 : SB1;
 
 [[group(1), binding(2)]] var<storage, read> sb2 : SB2;
 
-[[group(2), binding(2)]] var<storage, read> sb3 : SB3;
+[[group(2), binding(2)]] var<storage, read> sb3 : array<vec4<f32>>;
 
 [[group(3), binding(2)]] var<storage, read> sb4 : SB4;
 
-[[group(4), binding(2)]] var<storage, read> sb5 : SB5;
+[[group(4), binding(2)]] var<storage, read> sb5 : array<vec4<f32>>;
 
 [[stage(compute), workgroup_size(1)]]
 fn main() {
   var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
   var len2 : u32 = ((tint_symbol_1.buffer_size[0u][1u] - 16u) / 16u);
-  var len3 : u32 = ((tint_symbol_1.buffer_size[0u][2u] - 16u) / 16u);
+  var len3 : u32 = (tint_symbol_1.buffer_size[0u][2u] / 16u);
   var len4 : u32 = ((tint_symbol_1.buffer_size[0u][3u] - 16u) / 16u);
-  var len5 : u32 = ((tint_symbol_1.buffer_size[1u][0u] - 16u) / 16u);
+  var len5 : u32 = (tint_symbol_1.buffer_size[1u][0u] / 16u);
   var x : u32 = ((((len1 + len2) + len3) + len4) + len5);
 }
 )";
@@ -268,29 +326,21 @@
   x : i32;
   arr2 : array<vec4<f32>>;
 };
-struct SB3 {
-  x : i32;
-  arr3 : array<vec4<f32>>;
-};
 struct SB4 {
   x : i32;
   arr4 : array<vec4<f32>>;
 };
-struct SB5 {
-  x : i32;
-  arr5 : array<vec4<f32>>;
-};
 
 [[group(0), binding(2)]] var<storage, read> sb1 : SB1;
 [[group(1), binding(2)]] var<storage, read> sb2 : SB2;
-[[group(2), binding(2)]] var<storage, read> sb3 : SB3;
+[[group(2), binding(2)]] var<storage, read> sb3 : array<vec4<f32>>;
 [[group(3), binding(2)]] var<storage, read> sb4 : SB4;
-[[group(4), binding(2)]] var<storage, read> sb5 : SB5;
+[[group(4), binding(2)]] var<storage, read> sb5 : array<vec4<f32>>;
 
 [[stage(compute), workgroup_size(1)]]
 fn main() {
   var len1 : u32 = arrayLength(&(sb1.arr1));
-  var len3 : u32 = arrayLength(&(sb3.arr3));
+  var len3 : u32 = arrayLength(&sb3);
   var x : u32 = (len1 + len3);
 }
 )";
@@ -312,35 +362,25 @@
   arr2 : array<vec4<f32>>;
 };
 
-struct SB3 {
-  x : i32;
-  arr3 : array<vec4<f32>>;
-};
-
 struct SB4 {
   x : i32;
   arr4 : array<vec4<f32>>;
 };
 
-struct SB5 {
-  x : i32;
-  arr5 : array<vec4<f32>>;
-};
-
 [[group(0), binding(2)]] var<storage, read> sb1 : SB1;
 
 [[group(1), binding(2)]] var<storage, read> sb2 : SB2;
 
-[[group(2), binding(2)]] var<storage, read> sb3 : SB3;
+[[group(2), binding(2)]] var<storage, read> sb3 : array<vec4<f32>>;
 
 [[group(3), binding(2)]] var<storage, read> sb4 : SB4;
 
-[[group(4), binding(2)]] var<storage, read> sb5 : SB5;
+[[group(4), binding(2)]] var<storage, read> sb5 : array<vec4<f32>>;
 
 [[stage(compute), workgroup_size(1)]]
 fn main() {
   var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u);
-  var len3 : u32 = ((tint_symbol_1.buffer_size[0u][2u] - 16u) / 16u);
+  var len3 : u32 = (tint_symbol_1.buffer_size[0u][2u] / 16u);
   var x : u32 = (len1 + len3);
 }
 )";
diff --git a/src/transform/calculate_array_length.cc b/src/transform/calculate_array_length.cc
index e06b959..6c6c6ff 100644
--- a/src/transform/calculate_array_length.cc
+++ b/src/transform/calculate_array_length.cc
@@ -42,7 +42,7 @@
 /// It is used as a key by the array_length_by_usage map.
 struct ArrayUsage {
   ast::BlockStatement const* const block;
-  sem::Node const* const buffer;
+  sem::Variable const* const buffer;
   bool operator==(const ArrayUsage& rhs) const {
     return block == rhs.block && buffer == rhs.buffer;
   }
@@ -80,12 +80,11 @@
   // get_buffer_size_intrinsic() emits the function decorated with
   // BufferSizeIntrinsic that is transformed by the HLSL writer into a call to
   // [RW]ByteAddressBuffer.GetDimensions().
-  std::unordered_map<const sem::Struct*, Symbol> buffer_size_intrinsics;
-  auto get_buffer_size_intrinsic = [&](const sem::Struct* buffer_type) {
+  std::unordered_map<const sem::Type*, Symbol> buffer_size_intrinsics;
+  auto get_buffer_size_intrinsic = [&](const sem::Type* buffer_type) {
     return utils::GetOrCreate(buffer_size_intrinsics, buffer_type, [&] {
       auto name = ctx.dst->Sym();
-      auto* buffer_typename =
-          ctx.dst->ty.type_name(ctx.Clone(buffer_type->Declaration()->name));
+      auto* type = CreateASTTypeFor(ctx, buffer_type);
       auto* disable_validation = ctx.dst->Disable(
           ast::DisabledValidation::kIgnoreConstructibleFunctionParameter);
       auto* func = ctx.dst->create<ast::Function>(
@@ -95,7 +94,7 @@
               // in order for HLSL to emit this as a ByteAddressBuffer.
               ctx.dst->create<ast::Variable>(
                   ctx.dst->Sym("buffer"), ast::StorageClass::kStorage,
-                  ast::Access::kUndefined, buffer_typename, true, nullptr,
+                  ast::Access::kUndefined, type, true, nullptr,
                   ast::DecorationList{disable_validation}),
               ctx.dst->Param("result",
                              ctx.dst->ty.pointer(ctx.dst->ty.u32(),
@@ -106,8 +105,12 @@
               ctx.dst->ASTNodes().Create<BufferSizeIntrinsic>(ctx.dst->ID()),
           },
           ast::DecorationList{});
-      ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(),
-                      buffer_type->Declaration(), func);
+      if (auto* str = buffer_type->As<sem::Struct>()) {
+        ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(), str->Declaration(),
+                        func);
+      } else {
+        ctx.InsertFront(ctx.src->AST().GlobalDeclarations(), func);
+      }
       return name;
     });
   };
@@ -123,71 +126,47 @@
         if (intrinsic->Type() == sem::IntrinsicType::kArrayLength) {
           // We're dealing with an arrayLength() call
 
-          // https://gpuweb.github.io/gpuweb/wgsl/#array-types states:
-          //
-          // * The last member of the structure type defining the store type for
-          //   a variable in the storage storage class may be a runtime-sized
-          //   array.
-          // * A runtime-sized array must not be used as the store type or
-          //   contained within a store type in any other cases.
-          // * An expression must not evaluate to a runtime-sized array type.
-          //
-          // We can assume that the arrayLength() call has a single argument of
-          // the form: arrayLength(&X.Y) where X is an expression that resolves
-          // to the storage buffer structure, and Y is the runtime sized array.
+          // A runtime-sized array can only appear as the store type of a
+          // variable, or the last element of a structure (which cannot itself
+          // be nested). Given that we require SimplifyPointers, we can assume
+          // that the arrayLength() call has one of two forms:
+          //   arrayLength(&struct_var.array_member)
+          //   arrayLength(&array_var)
           auto* arg = call_expr->args[0];
           auto* address_of = arg->As<ast::UnaryOpExpression>();
           if (!address_of || address_of->op != ast::UnaryOp::kAddressOf) {
             TINT_ICE(Transform, ctx.dst->Diagnostics())
-                << "arrayLength() expected pointer to member access, got "
-                << address_of->TypeInfo().name;
+                << "arrayLength() expected address-of, got "
+                << arg->TypeInfo().name;
           }
-          auto* array_expr = address_of->expr;
-
-          auto* accessor = array_expr->As<ast::MemberAccessorExpression>();
-          if (!accessor) {
+          auto* storage_buffer_expr = address_of->expr;
+          if (auto* accessor =
+                  storage_buffer_expr->As<ast::MemberAccessorExpression>()) {
+            storage_buffer_expr = accessor->structure;
+          }
+          auto* storage_buffer_sem =
+              sem.Get<sem::VariableUser>(storage_buffer_expr);
+          if (!storage_buffer_sem) {
             TINT_ICE(Transform, ctx.dst->Diagnostics())
-                << "arrayLength() expected pointer to member access, got "
-                   "pointer to "
-                << array_expr->TypeInfo().name;
+                << "expected form of arrayLength argument to be &array_var or "
+                   "&struct_var.array_member";
             break;
           }
-          auto* storage_buffer_expr = accessor->structure;
-          auto* storage_buffer_sem = sem.Get(storage_buffer_expr);
-          auto* storage_buffer_type =
-              storage_buffer_sem->Type()->UnwrapRef()->As<sem::Struct>();
+          auto* storage_buffer_var = storage_buffer_sem->Variable();
+          auto* storage_buffer_type = storage_buffer_sem->Type()->UnwrapRef();
 
           // Generate BufferSizeIntrinsic for this storage type if we haven't
           // already
           auto buffer_size = get_buffer_size_intrinsic(storage_buffer_type);
 
-          if (!storage_buffer_type) {
-            TINT_ICE(Transform, ctx.dst->Diagnostics())
-                << "arrayLength(X.Y) expected X to be sem::Struct, got "
-                << storage_buffer_type->FriendlyName(ctx.src->Symbols());
-            break;
-          }
-
           // Find the current statement block
           auto* block = call->Stmt()->Block()->Declaration();
 
-          // If the storage_buffer_expr is resolves to a variable (typically
-          // true) then key the array_length from the variable. If not, key off
-          // the expression semantic node, which will be unique per call to
-          // arrayLength().
-          const sem::Node* storage_buffer_usage = storage_buffer_sem;
-          if (auto* user = storage_buffer_sem->As<sem::VariableUser>()) {
-            storage_buffer_usage = user->Variable();
-          }
-
           auto array_length = utils::GetOrCreate(
-              array_length_by_usage, {block, storage_buffer_usage}, [&] {
+              array_length_by_usage, {block, storage_buffer_var}, [&] {
                 // First time this array length is used for this block.
                 // Let's calculate it.
 
-                // Semantic info for the runtime array structure member
-                auto* array_member_sem = storage_buffer_type->Members().back();
-
                 // Construct the variable that'll hold the result of
                 // RWByteAddressBuffer.GetDimensions()
                 auto* buffer_size_result = ctx.dst->Decl(
@@ -208,14 +187,28 @@
                 // array_length = ----------------------------------------
                 //                             array_stride
                 auto name = ctx.dst->Sym();
-                uint32_t array_offset = array_member_sem->Offset();
-                uint32_t array_stride = array_member_sem->Size();
-                auto* array_length_var = ctx.dst->Decl(ctx.dst->Const(
-                    name, ctx.dst->ty.u32(),
-                    ctx.dst->Div(
-                        ctx.dst->Sub(buffer_size_result->variable->symbol,
-                                     array_offset),
-                        array_stride)));
+                const ast::Expression* total_size =
+                    ctx.dst->Expr(buffer_size_result->variable);
+                const sem::Array* array_type = nullptr;
+                if (auto* str = storage_buffer_type->As<sem::Struct>()) {
+                  // The variable is a struct, so subtract the byte offset of
+                  // the array member.
+                  auto* array_member_sem = str->Members().back();
+                  array_type = array_member_sem->Type()->As<sem::Array>();
+                  total_size =
+                      ctx.dst->Sub(total_size, array_member_sem->Offset());
+                } else if (auto* arr = storage_buffer_type->As<sem::Array>()) {
+                  array_type = arr;
+                } else {
+                  TINT_ICE(Transform, ctx.dst->Diagnostics())
+                      << "expected form of arrayLength argument to be "
+                         "&array_var or &struct_var.array_member";
+                  return name;
+                }
+                uint32_t array_stride = array_type->Size();
+                auto* array_length_var = ctx.dst->Decl(
+                    ctx.dst->Const(name, ctx.dst->ty.u32(),
+                                   ctx.dst->Div(total_size, array_stride)));
 
                 // Insert the array length calculations at the top of the block
                 ctx.InsertBefore(block->statements, block->statements[0],
diff --git a/src/transform/calculate_array_length_test.cc b/src/transform/calculate_array_length_test.cc
index 4522768..4b42bf9 100644
--- a/src/transform/calculate_array_length_test.cc
+++ b/src/transform/calculate_array_length_test.cc
@@ -38,6 +38,36 @@
 
 TEST_F(CalculateArrayLengthTest, Basic) {
   auto* src = R"(
+[[group(0), binding(0)]] var<storage, read> sb : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var len : u32 = arrayLength(&sb);
+}
+)";
+
+  auto* expect = R"(
+[[internal(intrinsic_buffer_size)]]
+fn tint_symbol([[internal(disable_validation__ignore_constructible_function_parameter)]] buffer : array<i32>, result : ptr<function, u32>)
+
+[[group(0), binding(0)]] var<storage, read> sb : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = (tint_symbol_1 / 4u);
+  var len : u32 = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, BasicInStruct) {
+  auto* src = R"(
 struct SB {
   x : i32;
   arr : array<i32>;
@@ -78,6 +108,40 @@
 
 TEST_F(CalculateArrayLengthTest, InSameBlock) {
   auto* src = R"(
+[[group(0), binding(0)]] var<storage, read> sb : array<i32>;;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var a : u32 = arrayLength(&sb);
+  var b : u32 = arrayLength(&sb);
+  var c : u32 = arrayLength(&sb);
+}
+)";
+
+  auto* expect = R"(
+[[internal(intrinsic_buffer_size)]]
+fn tint_symbol([[internal(disable_validation__ignore_constructible_function_parameter)]] buffer : array<i32>, result : ptr<function, u32>)
+
+[[group(0), binding(0)]] var<storage, read> sb : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = (tint_symbol_1 / 4u);
+  var a : u32 = tint_symbol_2;
+  var b : u32 = tint_symbol_2;
+  var c : u32 = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, InSameBlock_Struct) {
+  auto* src = R"(
 struct SB {
   x : i32;
   arr : array<i32>;
@@ -122,6 +186,36 @@
 
 TEST_F(CalculateArrayLengthTest, WithStride) {
   auto* src = R"(
+[[group(0), binding(0)]] var<storage, read> sb : [[stride(64)]] array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var len : u32 = arrayLength(&sb);
+}
+)";
+
+  auto* expect = R"(
+[[internal(intrinsic_buffer_size)]]
+fn tint_symbol([[internal(disable_validation__ignore_constructible_function_parameter)]] buffer : [[stride(64)]] array<i32>, result : ptr<function, u32>)
+
+[[group(0), binding(0)]] var<storage, read> sb : [[stride(64)]] array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  var tint_symbol_1 : u32 = 0u;
+  tint_symbol(sb, &(tint_symbol_1));
+  let tint_symbol_2 : u32 = (tint_symbol_1 / 64u);
+  var len : u32 = tint_symbol_2;
+}
+)";
+
+  auto got = Run<Unshadow, SimplifyPointers, CalculateArrayLength>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(CalculateArrayLengthTest, WithStride_InStruct) {
+  auto* src = R"(
 struct SB {
   x : i32;
   y : f32;
@@ -233,15 +327,21 @@
 
 [[group(0), binding(1)]] var<storage, read> sb2 : SB2;
 
+[[group(0), binding(2)]] var<storage, read> sb3 : array<i32>;
+
 [[stage(compute), workgroup_size(1)]]
 fn main() {
   var len1 : u32 = arrayLength(&(sb1.arr1));
   var len2 : u32 = arrayLength(&(sb2.arr2));
-  var x : u32 = (len1 + len2);
+  var len3 : u32 = arrayLength(&sb3);
+  var x : u32 = (len1 + len2 + len3);
 }
 )";
 
   auto* expect = R"(
+[[internal(intrinsic_buffer_size)]]
+fn tint_symbol_6([[internal(disable_validation__ignore_constructible_function_parameter)]] buffer : array<i32>, result : ptr<function, u32>)
+
 struct SB1 {
   x : i32;
   arr1 : array<i32>;
@@ -262,6 +362,8 @@
 
 [[group(0), binding(1)]] var<storage, read> sb2 : SB2;
 
+[[group(0), binding(2)]] var<storage, read> sb3 : array<i32>;
+
 [[stage(compute), workgroup_size(1)]]
 fn main() {
   var tint_symbol_1 : u32 = 0u;
@@ -270,9 +372,13 @@
   var tint_symbol_4 : u32 = 0u;
   tint_symbol_3(sb2, &(tint_symbol_4));
   let tint_symbol_5 : u32 = ((tint_symbol_4 - 16u) / 16u);
+  var tint_symbol_7 : u32 = 0u;
+  tint_symbol_6(sb3, &(tint_symbol_7));
+  let tint_symbol_8 : u32 = (tint_symbol_7 / 4u);
   var len1 : u32 = tint_symbol_2;
   var len2 : u32 = tint_symbol_5;
-  var x : u32 = (len1 + len2);
+  var len3 : u32 = tint_symbol_8;
+  var x : u32 = ((len1 + len2) + len3);
 }
 )";
 
diff --git a/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl b/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl
new file mode 100644
index 0000000..2e2966d
--- /dev/null
+++ b/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl
@@ -0,0 +1,8 @@
+[[group(0), binding(0)]] var<storage, read> G : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+    let p = &G;
+    let p2 = &(*p);
+    let l1 : u32 = arrayLength(p2);
+}
diff --git a/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.hlsl b/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.hlsl
new file mode 100644
index 0000000..d5cdca5
--- /dev/null
+++ b/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.hlsl
@@ -0,0 +1,10 @@
+ByteAddressBuffer G : register(t0, space0);
+
+[numthreads(1, 1, 1)]
+void main() {
+  uint tint_symbol_1 = 0u;
+  G.GetDimensions(tint_symbol_1);
+  const uint tint_symbol_2 = (tint_symbol_1 / 4u);
+  const uint l1 = tint_symbol_2;
+  return;
+}
diff --git a/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.msl b/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.msl
new file mode 100644
index 0000000..0ab65d1
--- /dev/null
+++ b/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_1 {
+  /* 0x0000 */ uint4 buffer_size[1];
+};
+
+kernel void tint_symbol(const constant tint_symbol_1* tint_symbol_3 [[buffer(30)]]) {
+  uint const l1 = ((*(tint_symbol_3)).buffer_size[0u][0u] / 4u);
+  return;
+}
+
diff --git a/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.spvasm b/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..23ceb5f
--- /dev/null
+++ b/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 12
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %G_block "G_block"
+               OpMemberName %G_block 0 "inner"
+               OpName %G "G"
+               OpName %main "main"
+               OpDecorate %G_block Block
+               OpMemberDecorate %G_block 0 Offset 0
+               OpDecorate %_runtimearr_int ArrayStride 4
+               OpDecorate %G NonWritable
+               OpDecorate %G DescriptorSet 0
+               OpDecorate %G Binding 0
+        %int = OpTypeInt 32 1
+%_runtimearr_int = OpTypeRuntimeArray %int
+    %G_block = OpTypeStruct %_runtimearr_int
+%_ptr_StorageBuffer_G_block = OpTypePointer StorageBuffer %G_block
+          %G = OpVariable %_ptr_StorageBuffer_G_block StorageBuffer
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+       %main = OpFunction %void None %6
+          %9 = OpLabel
+         %10 = OpArrayLength %uint %G 0
+               OpReturn
+               OpFunctionEnd
diff --git a/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.wgsl b/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..56b0e0a
--- /dev/null
+++ b/test/intrinsics/arrayLength/complex_via_let_no_struct.wgsl.expected.wgsl
@@ -0,0 +1,8 @@
+[[group(0), binding(0)]] var<storage, read> G : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  let p = &(G);
+  let p2 = &(*(p));
+  let l1 : u32 = arrayLength(p2);
+}
diff --git a/test/intrinsics/arrayLength/simple_no_struct.wgsl b/test/intrinsics/arrayLength/simple_no_struct.wgsl
new file mode 100644
index 0000000..0fcbfbe
--- /dev/null
+++ b/test/intrinsics/arrayLength/simple_no_struct.wgsl
@@ -0,0 +1,6 @@
+[[group(0), binding(0)]] var<storage, read> G : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+    let l1 : u32 = arrayLength(&G);
+}
diff --git a/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.hlsl b/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.hlsl
new file mode 100644
index 0000000..d5cdca5
--- /dev/null
+++ b/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.hlsl
@@ -0,0 +1,10 @@
+ByteAddressBuffer G : register(t0, space0);
+
+[numthreads(1, 1, 1)]
+void main() {
+  uint tint_symbol_1 = 0u;
+  G.GetDimensions(tint_symbol_1);
+  const uint tint_symbol_2 = (tint_symbol_1 / 4u);
+  const uint l1 = tint_symbol_2;
+  return;
+}
diff --git a/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.msl b/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.msl
new file mode 100644
index 0000000..0ab65d1
--- /dev/null
+++ b/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_1 {
+  /* 0x0000 */ uint4 buffer_size[1];
+};
+
+kernel void tint_symbol(const constant tint_symbol_1* tint_symbol_3 [[buffer(30)]]) {
+  uint const l1 = ((*(tint_symbol_3)).buffer_size[0u][0u] / 4u);
+  return;
+}
+
diff --git a/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.spvasm b/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..23ceb5f
--- /dev/null
+++ b/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 12
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %G_block "G_block"
+               OpMemberName %G_block 0 "inner"
+               OpName %G "G"
+               OpName %main "main"
+               OpDecorate %G_block Block
+               OpMemberDecorate %G_block 0 Offset 0
+               OpDecorate %_runtimearr_int ArrayStride 4
+               OpDecorate %G NonWritable
+               OpDecorate %G DescriptorSet 0
+               OpDecorate %G Binding 0
+        %int = OpTypeInt 32 1
+%_runtimearr_int = OpTypeRuntimeArray %int
+    %G_block = OpTypeStruct %_runtimearr_int
+%_ptr_StorageBuffer_G_block = OpTypePointer StorageBuffer %G_block
+          %G = OpVariable %_ptr_StorageBuffer_G_block StorageBuffer
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+       %main = OpFunction %void None %6
+          %9 = OpLabel
+         %10 = OpArrayLength %uint %G 0
+               OpReturn
+               OpFunctionEnd
diff --git a/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.wgsl b/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..cbe8d72
--- /dev/null
+++ b/test/intrinsics/arrayLength/simple_no_struct.wgsl.expected.wgsl
@@ -0,0 +1,6 @@
+[[group(0), binding(0)]] var<storage, read> G : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  let l1 : u32 = arrayLength(&(G));
+}
diff --git a/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl b/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl
new file mode 100644
index 0000000..4e8a6c5
--- /dev/null
+++ b/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl
@@ -0,0 +1,9 @@
+[[group(0), binding(0)]] var<storage, read> G : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  let p = &*&G;
+  let p2 = &*p;
+  let p3 = &(*p);
+  let l1 : u32 = arrayLength(&*p3);
+}
diff --git a/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.hlsl b/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.hlsl
new file mode 100644
index 0000000..d5cdca5
--- /dev/null
+++ b/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.hlsl
@@ -0,0 +1,10 @@
+ByteAddressBuffer G : register(t0, space0);
+
+[numthreads(1, 1, 1)]
+void main() {
+  uint tint_symbol_1 = 0u;
+  G.GetDimensions(tint_symbol_1);
+  const uint tint_symbol_2 = (tint_symbol_1 / 4u);
+  const uint l1 = tint_symbol_2;
+  return;
+}
diff --git a/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.msl b/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.msl
new file mode 100644
index 0000000..0ab65d1
--- /dev/null
+++ b/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_1 {
+  /* 0x0000 */ uint4 buffer_size[1];
+};
+
+kernel void tint_symbol(const constant tint_symbol_1* tint_symbol_3 [[buffer(30)]]) {
+  uint const l1 = ((*(tint_symbol_3)).buffer_size[0u][0u] / 4u);
+  return;
+}
+
diff --git a/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.spvasm b/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..23ceb5f
--- /dev/null
+++ b/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 12
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %G_block "G_block"
+               OpMemberName %G_block 0 "inner"
+               OpName %G "G"
+               OpName %main "main"
+               OpDecorate %G_block Block
+               OpMemberDecorate %G_block 0 Offset 0
+               OpDecorate %_runtimearr_int ArrayStride 4
+               OpDecorate %G NonWritable
+               OpDecorate %G DescriptorSet 0
+               OpDecorate %G Binding 0
+        %int = OpTypeInt 32 1
+%_runtimearr_int = OpTypeRuntimeArray %int
+    %G_block = OpTypeStruct %_runtimearr_int
+%_ptr_StorageBuffer_G_block = OpTypePointer StorageBuffer %G_block
+          %G = OpVariable %_ptr_StorageBuffer_G_block StorageBuffer
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+       %main = OpFunction %void None %6
+          %9 = OpLabel
+         %10 = OpArrayLength %uint %G 0
+               OpReturn
+               OpFunctionEnd
diff --git a/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.wgsl b/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..97964a4
--- /dev/null
+++ b/test/intrinsics/arrayLength/via_let_complex_no_struct.wgsl.expected.wgsl
@@ -0,0 +1,9 @@
+[[group(0), binding(0)]] var<storage, read> G : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  let p = &(*(&(G)));
+  let p2 = &(*(p));
+  let p3 = &(*(p));
+  let l1 : u32 = arrayLength(&(*(p3)));
+}
diff --git a/test/intrinsics/arrayLength/via_let_no_struct.wgsl b/test/intrinsics/arrayLength/via_let_no_struct.wgsl
new file mode 100644
index 0000000..83ab810
--- /dev/null
+++ b/test/intrinsics/arrayLength/via_let_no_struct.wgsl
@@ -0,0 +1,8 @@
+[[group(0), binding(0)]] var<storage, read> G : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+    let p = &G;
+    let p2 = p;
+    let l1 : u32 = arrayLength(p2);
+}
diff --git a/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.hlsl b/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.hlsl
new file mode 100644
index 0000000..d5cdca5
--- /dev/null
+++ b/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.hlsl
@@ -0,0 +1,10 @@
+ByteAddressBuffer G : register(t0, space0);
+
+[numthreads(1, 1, 1)]
+void main() {
+  uint tint_symbol_1 = 0u;
+  G.GetDimensions(tint_symbol_1);
+  const uint tint_symbol_2 = (tint_symbol_1 / 4u);
+  const uint l1 = tint_symbol_2;
+  return;
+}
diff --git a/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.msl b/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.msl
new file mode 100644
index 0000000..0ab65d1
--- /dev/null
+++ b/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_1 {
+  /* 0x0000 */ uint4 buffer_size[1];
+};
+
+kernel void tint_symbol(const constant tint_symbol_1* tint_symbol_3 [[buffer(30)]]) {
+  uint const l1 = ((*(tint_symbol_3)).buffer_size[0u][0u] / 4u);
+  return;
+}
+
diff --git a/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.spvasm b/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..23ceb5f
--- /dev/null
+++ b/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 12
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %G_block "G_block"
+               OpMemberName %G_block 0 "inner"
+               OpName %G "G"
+               OpName %main "main"
+               OpDecorate %G_block Block
+               OpMemberDecorate %G_block 0 Offset 0
+               OpDecorate %_runtimearr_int ArrayStride 4
+               OpDecorate %G NonWritable
+               OpDecorate %G DescriptorSet 0
+               OpDecorate %G Binding 0
+        %int = OpTypeInt 32 1
+%_runtimearr_int = OpTypeRuntimeArray %int
+    %G_block = OpTypeStruct %_runtimearr_int
+%_ptr_StorageBuffer_G_block = OpTypePointer StorageBuffer %G_block
+          %G = OpVariable %_ptr_StorageBuffer_G_block StorageBuffer
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+       %main = OpFunction %void None %6
+          %9 = OpLabel
+         %10 = OpArrayLength %uint %G 0
+               OpReturn
+               OpFunctionEnd
diff --git a/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.wgsl b/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..731ba0c
--- /dev/null
+++ b/test/intrinsics/arrayLength/via_let_no_struct.wgsl.expected.wgsl
@@ -0,0 +1,8 @@
+[[group(0), binding(0)]] var<storage, read> G : array<i32>;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+  let p = &(G);
+  let p2 = p;
+  let l1 : u32 = arrayLength(p2);
+}