[spirv-reader][ir] Support `ArrayStride` annotation

Add support for processing the `ArrayStride` annotation. This creates an
`ExplicitLayoutArray` which will need to be cleaned up with a transform.

Bug: 391482902
Change-Id: I61ee0e3fcbfe3d074cec7cfa658ce9ab52923369
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/247774
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/spirv/reader/parser/composite_test.cc b/src/tint/lang/spirv/reader/parser/composite_test.cc
index 9327adf..b68ee7f 100644
--- a/src/tint/lang/spirv/reader/parser/composite_test.cc
+++ b/src/tint/lang/spirv/reader/parser/composite_test.cc
@@ -143,6 +143,155 @@
 )");
 }
 
+TEST_F(SpirvParserTest, CompositeConstruct_Array_ArrayStride_EqualsElementSize) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpDecorate %arr_ty ArrayStride 4
+       %void = OpTypeVoid
+        %u32 = OpTypeInt 32 0
+    %ep_type = OpTypeFunction %void
+      %u32_1 = OpConstant %u32 1
+      %u32_2 = OpConstant %u32 2
+      %u32_3 = OpConstant %u32 3
+      %u32_4 = OpConstant %u32 4
+     %arr_ty = OpTypeArray %u32 %u32_4
+    %fn_type = OpTypeFunction %arr_ty
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+        %foo = OpFunction %arr_ty None %fn_type
+  %foo_start = OpLabel
+        %arr = OpCompositeConstruct %arr_ty %u32_1 %u32_2 %u32_3 %u32_4
+               OpReturnValue %arr
+               OpFunctionEnd
+)",
+              R"(
+%2 = func():array<u32, 4> {
+  $B2: {
+    %3:array<u32, 4> = construct 1u, 2u, 3u, 4u
+    ret %3
+  }
+)");
+}
+
+TEST_F(SpirvParserTest, CompositeConstruct_Array_ArrayStride_EqualsElementSize_ArrayVec3) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpDecorate %arr_ty ArrayStride 12
+       %void = OpTypeVoid
+        %u32 = OpTypeInt 32 0
+    %ep_type = OpTypeFunction %void
+      %u32_1 = OpConstant %u32 1
+      %u32_2 = OpConstant %u32 2
+      %u32_3 = OpConstant %u32 3
+      %u32_4 = OpConstant %u32 4
+      %vec3u = OpTypeVector %u32 3
+     %arr_ty = OpTypeArray %vec3u %u32_4
+        %ptr = OpTypePointer Private %arr_ty
+         %vs = OpVariable %ptr Private
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+          %2 = OpCopyObject %ptr %vs
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+$B1: {  # root
+  %1:ptr<private, spirv.explicit_layout_array<vec3<u32>, 4>, read_write> = var undef
+}
+
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B2: {
+    %3:ptr<private, spirv.explicit_layout_array<vec3<u32>, 4>, read_write> = let %1
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest,
+       CompositeConstruct_Array_ArrayStride_EqualsElementSize_ArrayVec3_MatchTint) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpDecorate %arr_ty ArrayStride 16
+       %void = OpTypeVoid
+        %u32 = OpTypeInt 32 0
+    %ep_type = OpTypeFunction %void
+      %u32_1 = OpConstant %u32 1
+      %u32_2 = OpConstant %u32 2
+      %u32_3 = OpConstant %u32 3
+      %u32_4 = OpConstant %u32 4
+      %vec3u = OpTypeVector %u32 3
+     %arr_ty = OpTypeArray %vec3u %u32_4
+        %ptr = OpTypePointer Private %arr_ty
+         %vs = OpVariable %ptr Private
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+          %2 = OpCopyObject %ptr %vs
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+$B1: {  # root
+  %1:ptr<private, array<vec3<u32>, 4>, read_write> = var undef
+}
+
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B2: {
+    %3:ptr<private, array<vec3<u32>, 4>, read_write> = let %1
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, CompositeConstruct_Array_ArrayStride) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpDecorate %arr_ty ArrayStride 16
+       %void = OpTypeVoid
+        %u32 = OpTypeInt 32 0
+    %ep_type = OpTypeFunction %void
+      %u32_1 = OpConstant %u32 1
+      %u32_2 = OpConstant %u32 2
+      %u32_3 = OpConstant %u32 3
+      %u32_4 = OpConstant %u32 4
+     %arr_ty = OpTypeArray %u32 %u32_4
+    %fn_type = OpTypeFunction %arr_ty
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+        %foo = OpFunction %arr_ty None %fn_type
+  %foo_start = OpLabel
+        %arr = OpCompositeConstruct %arr_ty %u32_1 %u32_2 %u32_3 %u32_4
+               OpReturnValue %arr
+               OpFunctionEnd
+)",
+              R"(
+%2 = func():spirv.explicit_layout_array<u32, 4> {
+  $B2: {
+    %3:spirv.explicit_layout_array<u32, 4> = construct 1u, 2u, 3u, 4u
+    ret %3
+  }
+)");
+}
+
 TEST_F(SpirvParserTest, CompositeConstruct_ArrayOfVec) {
     EXPECT_IR(R"(
                OpCapability Shader
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 5aeaee9..6890743 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -56,6 +56,7 @@
 #include "src/tint/lang/core/type/builtin_structs.h"
 #include "src/tint/lang/spirv/builtin_fn.h"
 #include "src/tint/lang/spirv/ir/builtin_call.h"
+#include "src/tint/lang/spirv/type/explicit_layout_array.h"
 #include "src/tint/lang/spirv/type/image.h"
 #include "src/tint/lang/spirv/type/sampled_image.h"
 #include "src/tint/lang/spirv/validate/validate.h"
@@ -478,10 +479,11 @@
         }
 
         // TODO(crbug.com/1907): Handle decorations that affect the type
+        uint32_t array_stride = 0;
         for (auto& deco : type->decorations()) {
             switch (spv::Decoration(deco[0])) {
-                case spv::Decoration::SpecId: {
-                    // TODO(dsinclair): Trick clang, remove when actual decorations added
+                case spv::Decoration::ArrayStride: {
+                    array_stride = deco[1];
                     break;
                 }
                 default: {
@@ -530,7 +532,7 @@
                                    mat_ty->element_count());
                 }
                 case spvtools::opt::analysis::Type::kArray: {
-                    return EmitArray(type->AsArray());
+                    return EmitArray(type->AsArray(), array_stride);
                 }
                 case spvtools::opt::analysis::Type::kRuntimeArray: {
                     auto* arr_ty = type->AsRuntimeArray();
@@ -650,7 +652,8 @@
 
     /// @param arr_ty a SPIR-V array object
     /// @returns a Tint array object
-    const core::type::Type* EmitArray(const spvtools::opt::analysis::Array* arr_ty) {
+    const core::type::Type* EmitArray(const spvtools::opt::analysis::Array* arr_ty,
+                                      uint32_t array_stride) {
         const auto& length = arr_ty->length_info();
         TINT_ASSERT(!length.words.empty());
         if (length.words[0] != spvtools::opt::analysis::Array::LengthInfo::kConstant) {
@@ -664,7 +667,15 @@
         const uint64_t count_val = count_const->GetZeroExtendedValue();
         TINT_ASSERT(count_val <= UINT32_MAX);
 
-        return ty_.array(Type(arr_ty->element_type()), static_cast<uint32_t>(count_val));
+        auto* elem_ty = Type(arr_ty->element_type());
+        uint32_t implicit_stride = tint::RoundUp(elem_ty->Align(), elem_ty->Size());
+        if (array_stride == 0 || array_stride == implicit_stride) {
+            return ty_.array(elem_ty, static_cast<uint32_t>(count_val));
+        }
+
+        return ty_.Get<spirv::type::ExplicitLayoutArray>(
+            elem_ty, ty_.Get<core::type::ConstantArrayCount>(static_cast<uint32_t>(count_val)),
+            elem_ty->Align(), static_cast<uint32_t>(array_stride * count_val), array_stride);
     }
 
     /// @param struct_ty a SPIR-V struct object
diff --git a/src/tint/lang/spirv/reader/reader_test.cc b/src/tint/lang/spirv/reader/reader_test.cc
index 5b297f2..e406e90 100644
--- a/src/tint/lang/spirv/reader/reader_test.cc
+++ b/src/tint/lang/spirv/reader/reader_test.cc
@@ -264,8 +264,7 @@
 )");
 }
 
-// TODO(dsinclair): Requires `ArrayStride` decoration support
-TEST_F(SpirvReaderTest, DISABLED_ClipDistances) {
+TEST_F(SpirvReaderTest, ClipDistances) {
     auto got = Run(R"(
                OpCapability Shader
                OpCapability ClipDistance