writer/msl: Wrap each array type in a struct

This allows them to be used in various places that WGSL allows, such
as function return types and parameters, and as the type of the RHS of
an assignment.

Fixed: tint:814
Fixed: tint:820
Change-Id: Idb6a901b9a34e96bb9733cc158191e7b3bafaa0e
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/52844
Auto-Submit: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 6d0a523..2c5c51d 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -94,7 +94,18 @@
       }
     }
   }
-  if (!program_->AST().ConstructedTypes().empty()) {
+
+  // Generate wrappers for array types.
+  for (auto* ty : program_->Types()) {
+    auto* arr = ty->As<sem::Array>();
+    if (arr && !arr->IsRuntimeSized()) {
+      if (!EmitArrayWrapper(arr)) {
+        return false;
+      }
+    }
+  }
+
+  if (!program_->AST().ConstructedTypes().empty() || array_wrappers_.size()) {
     out_ << std::endl;
   }
 
@@ -179,6 +190,14 @@
   if (paren_lhs) {
     out_ << ")";
   }
+
+  // If the array has been wrapped in a struct, emit a member accessor to get to
+  // the actual array.
+  auto* sem_ty = program_->Sem().Get(expr->array())->Type()->UnwrapRef();
+  if (array_wrappers_.count(sem_ty)) {
+    out_ << ".array";
+  }
+
   out_ << "[";
 
   if (!EmitExpression(expr->idx_expr())) {
@@ -1892,6 +1911,16 @@
 
 bool GeneratorImpl::EmitType(const sem::Type* type, const std::string& name) {
   if (auto* ary = type->As<sem::Array>()) {
+    if (array_wrappers_.count(type)) {
+      // If the array has been wrapped in a struct, emit the struct name instead
+      // of the array type.
+      out_ << array_wrappers_[type];
+      if (!name.empty()) {
+        out_ << " " << name;
+      }
+      return true;
+    }
+
     const sem::Type* base_type = ary;
     std::vector<uint32_t> sizes;
     while (auto* arr = base_type->As<sem::Array>()) {
@@ -1940,7 +1969,8 @@
       default:
         TINT_ICE(diagnostics_) << "unhandled storage class for pointer";
     }
-    if (ptr->StoreType()->Is<sem::Array>()) {
+    if (ptr->StoreType()->Is<sem::Array>() &&
+        !array_wrappers_.count(ptr->StoreType())) {
       std::string inner = "(*" + name + ")";
       if (!EmitType(ptr->StoreType(), inner)) {
         return false;
@@ -2055,6 +2085,16 @@
 }
 
 bool GeneratorImpl::EmitStructType(const sem::Struct* str) {
+  // Generate wrappers for any struct members that have an array type.
+  for (auto* mem : str->Members()) {
+    auto* arr = mem->Type()->As<sem::Array>();
+    if (arr && !arr->IsRuntimeSized()) {
+      if (!EmitArrayWrapper(arr)) {
+        return false;
+      }
+    }
+  }
+
   // TODO(dsinclair): Block decoration?
   // if (str->impl()->decoration() != ast::Decoration::kNone) {
   // }
@@ -2376,6 +2416,36 @@
   return {};
 }
 
+bool GeneratorImpl::EmitArrayWrapper(const sem::Array* arr) {
+  // We may have created a wrapper for this array while emitting structs.
+  if (array_wrappers_.count(arr)) {
+    return true;
+  }
+
+  // Find a unique name for the new structure.
+  uint32_t i = static_cast<uint32_t>(array_wrappers_.size());
+  std::string struct_name;
+  do {
+    struct_name = "tint_array_wrapper_" + std::to_string(i++);
+    if (i == 0) {
+      TINT_ICE(diagnostics_) << "unable to allocate name for array wrapper";
+    }
+  } while (program_->Symbols().Get(struct_name).IsValid());
+
+  // Generate a struct with a single member with the same type as the array.
+  out_ << "struct " << struct_name << " {" << std::endl;
+  out_ << "  ";
+  if (!EmitType(arr->ElemType(), "")) {
+    return false;
+  }
+  out_ << " array[" << arr->Count() << "];" << std::endl;
+  out_ << "};" << std::endl;
+
+  array_wrappers_.emplace(arr, struct_name);
+
+  return true;
+}
+
 }  // namespace msl
 }  // namespace writer
 }  // namespace tint
diff --git a/src/writer/msl/generator_impl.h b/src/writer/msl/generator_impl.h
index 3608dc6..c0ada95 100644
--- a/src/writer/msl/generator_impl.h
+++ b/src/writer/msl/generator_impl.h
@@ -42,6 +42,7 @@
 
 // Forward declarations
 namespace sem {
+class Array;
 class Call;
 class Intrinsic;
 }  // namespace sem
@@ -289,12 +290,18 @@
   /// type.
   SizeAndAlign MslPackedTypeSizeAndAlign(const sem::Type* ty);
 
+  /// Generate a struct wrapper for an array type.
+  bool EmitArrayWrapper(const sem::Array* arr);
+
   ScopeStack<const sem::Variable*> global_variables_;
   Symbol current_ep_sym_;
   bool generating_entry_point_ = false;
   const Program* program_ = nullptr;
   uint32_t loop_emission_counter_ = 0;
 
+  // Map from an array type to the name of a struct which wraps it.
+  std::unordered_map<const sem::Type*, std::string> array_wrappers_;
+
   std::unordered_map<Symbol, EntryPointData> ep_sym_to_in_data_;
   std::unordered_map<Symbol, EntryPointData> ep_sym_to_out_data_;
 
diff --git a/src/writer/msl/generator_impl_function_test.cc b/src/writer/msl/generator_impl_function_test.cc
index befaca2..3a529afc 100644
--- a/src/writer/msl/generator_impl_function_test.cc
+++ b/src/writer/msl/generator_impl_function_test.cc
@@ -776,7 +776,11 @@
   EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
 
 using namespace metal;
-  void my_func(float const a[5]) {
+struct tint_array_wrapper_0 {
+  float array[5];
+};
+
+  void my_func(tint_array_wrapper_0 const a) {
     return;
   }
 
diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc
index 2a0079f..7eaf660 100644
--- a/src/writer/msl/generator_impl_type_test.cc
+++ b/src/writer/msl/generator_impl_type_test.cc
@@ -451,20 +451,27 @@
 
   // ALL_FIELDS() calls the macro FIELD(ADDR, TYPE, NAME, SUFFIX)
   // for each field of the structure s.
-#define ALL_FIELDS()                       \
-  FIELD(0x0000, int, a, /*NO SUFFIX*/)     \
-  FIELD(0x0004, float, b, [7])             \
-  FIELD(0x0020, float, c, /*NO SUFFIX*/)   \
-  FIELD(0x0024, int8_t, tint_pad_0, [476]) \
-  FIELD(0x0200, inner, d, [4])             \
-  FIELD(0x1200, float, e, /*NO SUFFIX*/)   \
-  FIELD(0x1204, float, f, [1])             \
+#define ALL_FIELDS()                                    \
+  FIELD(0x0000, int, a, /*NO SUFFIX*/)                  \
+  FIELD(0x0004, tint_array_wrapper_0, b, /*NO SUFFIX*/) \
+  FIELD(0x0020, float, c, /*NO SUFFIX*/)                \
+  FIELD(0x0024, int8_t, tint_pad_0, [476])              \
+  FIELD(0x0200, tint_array_wrapper_1, d, /*NO SUFFIX*/) \
+  FIELD(0x1200, float, e, /*NO SUFFIX*/)                \
+  FIELD(0x1204, float, f, [1])                          \
   FIELD(0x1208, int8_t, tint_pad_1, [504])
 
   // Check that the generated string is as expected.
 #define FIELD(ADDR, TYPE, NAME, SUFFIX) \
   "  /* " #ADDR " */ " #TYPE " " #NAME #SUFFIX ";\n"
-  auto* expect = "struct S {\n" ALL_FIELDS() "};\n";
+  auto* expect =
+      "struct tint_array_wrapper_0 {\n"
+      "  float array[7];\n"
+      "};\n"
+      "struct tint_array_wrapper_1 {\n"
+      "  inner array[4];\n"
+      "};\n"
+      "struct S {\n" ALL_FIELDS() "};\n";
 #undef FIELD
   EXPECT_EQ(gen.result(), expect);
 
@@ -484,12 +491,12 @@
     CHECK_TYPE_SIZE_AND_ALIGN(inner, 1024, 512);
 
     // array_x: size(28), align(4)
-    using array_x = std::array<float, 7>;
-    CHECK_TYPE_SIZE_AND_ALIGN(array_x, 28, 4);
+    using tint_array_wrapper_0 = std::array<float, 7>;
+    CHECK_TYPE_SIZE_AND_ALIGN(tint_array_wrapper_0, 28, 4);
 
     // array_y: size(4096), align(512)
-    using array_y = std::array<inner, 4>;
-    CHECK_TYPE_SIZE_AND_ALIGN(array_y, 4096, 512);
+    using tint_array_wrapper_1 = std::array<inner, 4>;
+    CHECK_TYPE_SIZE_AND_ALIGN(tint_array_wrapper_1, 4096, 512);
 
     // array_z: size(4), align(4)
     using array_z = std::array<float, 1>;
diff --git a/test/array/assign_to_function_var.wgsl b/test/array/assign_to_function_var.wgsl
new file mode 100644
index 0000000..043fb8c
--- /dev/null
+++ b/test/array/assign_to_function_var.wgsl
@@ -0,0 +1,53 @@
+type ArrayType = [[stride(16)]] array<i32, 4>;
+
+[[block]]
+struct S {
+  arr : ArrayType;
+};
+
+var<private> src_private : ArrayType;
+var<workgroup> src_workgroup : ArrayType;
+[[group(0), binding(0)]] var<uniform> src_uniform : S;
+[[group(0), binding(1)]] var<storage> src_storage : [[access(read_write)]] S;
+
+fn ret_arr() -> ArrayType {
+  return ArrayType();
+}
+
+fn ret_struct_arr() -> S {
+  return S();
+}
+
+fn foo(src_param : ArrayType) {
+  var src_function : ArrayType;
+
+  var dst : ArrayType;
+
+  // Assign from type constructor.
+  dst = ArrayType(1, 2, 3, 3);
+
+  // Assign from parameter.
+  dst = src_param;
+
+  // Assign from function call.
+  dst = ret_arr();
+
+  // Assign from constant.
+  let src_let : ArrayType = ArrayType();
+  dst = src_let;
+
+  // Assign from var, various storage classes.
+  dst = src_function;
+  dst = src_private;
+  dst = src_workgroup;
+
+  // Assign from struct.arr, various storage classes.
+  dst = ret_struct_arr().arr;
+  dst = src_uniform.arr;
+  dst = src_storage.arr;
+
+  // Nested assignment.
+  var dst_nested : array<array<array<i32, 2>, 3>, 4>;
+  var src_nested : array<array<array<i32, 2>, 3>, 4>;
+  dst_nested = src_nested;
+}
diff --git a/test/array/assign_to_function_var.wgsl.expected.hlsl b/test/array/assign_to_function_var.wgsl.expected.hlsl
new file mode 100644
index 0000000..7d6165e
--- /dev/null
+++ b/test/array/assign_to_function_var.wgsl.expected.hlsl
@@ -0,0 +1 @@
+SKIP: crbug.com/tint/845
diff --git a/test/array/assign_to_function_var.wgsl.expected.msl b/test/array/assign_to_function_var.wgsl.expected.msl
new file mode 100644
index 0000000..0ae6e42
--- /dev/null
+++ b/test/array/assign_to_function_var.wgsl.expected.msl
@@ -0,0 +1 @@
+SKIP: crbug.com/tint/649
diff --git a/test/array/assign_to_function_var.wgsl.expected.spvasm b/test/array/assign_to_function_var.wgsl.expected.spvasm
new file mode 100644
index 0000000..806064b
--- /dev/null
+++ b/test/array/assign_to_function_var.wgsl.expected.spvasm
@@ -0,0 +1,112 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 60
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %src_private "src_private"
+               OpName %src_workgroup "src_workgroup"
+               OpName %S "S"
+               OpMemberName %S 0 "arr"
+               OpName %src_uniform "src_uniform"
+               OpName %src_storage "src_storage"
+               OpName %unused_entry_point "unused_entry_point"
+               OpName %ret_arr "ret_arr"
+               OpName %ret_struct_arr "ret_struct_arr"
+               OpName %foo "foo"
+               OpName %src_param "src_param"
+               OpName %src_function "src_function"
+               OpName %dst "dst"
+               OpName %dst_nested "dst_nested"
+               OpName %src_nested "src_nested"
+               OpDecorate %_arr_int_uint_4 ArrayStride 16
+               OpDecorate %S Block
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %src_uniform DescriptorSet 0
+               OpDecorate %src_uniform Binding 0
+               OpDecorate %src_storage DescriptorSet 0
+               OpDecorate %src_storage Binding 1
+               OpDecorate %_arr_int_uint_2 ArrayStride 4
+               OpDecorate %_arr__arr_int_uint_2_uint_3 ArrayStride 8
+               OpDecorate %_arr__arr__arr_int_uint_2_uint_3_uint_4 ArrayStride 24
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+%_ptr_Private__arr_int_uint_4 = OpTypePointer Private %_arr_int_uint_4
+          %7 = OpConstantNull %_arr_int_uint_4
+%src_private = OpVariable %_ptr_Private__arr_int_uint_4 Private %7
+%_ptr_Workgroup__arr_int_uint_4 = OpTypePointer Workgroup %_arr_int_uint_4
+%src_workgroup = OpVariable %_ptr_Workgroup__arr_int_uint_4 Workgroup
+          %S = OpTypeStruct %_arr_int_uint_4
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+%src_uniform = OpVariable %_ptr_Uniform_S Uniform
+%_ptr_StorageBuffer_S = OpTypePointer StorageBuffer %S
+%src_storage = OpVariable %_ptr_StorageBuffer_S StorageBuffer
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+         %19 = OpTypeFunction %_arr_int_uint_4
+         %22 = OpTypeFunction %S
+         %25 = OpConstantNull %S
+         %26 = OpTypeFunction %void %_arr_int_uint_4
+%_ptr_Function__arr_int_uint_4 = OpTypePointer Function %_arr_int_uint_4
+      %int_1 = OpConstant %int 1
+      %int_2 = OpConstant %int 2
+      %int_3 = OpConstant %int 3
+         %36 = OpConstantComposite %_arr_int_uint_4 %int_1 %int_2 %int_3 %int_3
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform__arr_int_uint_4 = OpTypePointer Uniform %_arr_int_uint_4
+%_ptr_StorageBuffer__arr_int_uint_4 = OpTypePointer StorageBuffer %_arr_int_uint_4
+     %uint_2 = OpConstant %uint 2
+%_arr_int_uint_2 = OpTypeArray %int %uint_2
+     %uint_3 = OpConstant %uint 3
+%_arr__arr_int_uint_2_uint_3 = OpTypeArray %_arr_int_uint_2 %uint_3
+%_arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypeArray %_arr__arr_int_uint_2_uint_3 %uint_4
+%_ptr_Function__arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypePointer Function %_arr__arr__arr_int_uint_2_uint_3_uint_4
+         %57 = OpConstantNull %_arr__arr__arr_int_uint_2_uint_3_uint_4
+%unused_entry_point = OpFunction %void None %15
+         %18 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    %ret_arr = OpFunction %_arr_int_uint_4 None %19
+         %21 = OpLabel
+               OpReturnValue %7
+               OpFunctionEnd
+%ret_struct_arr = OpFunction %S None %22
+         %24 = OpLabel
+               OpReturnValue %25
+               OpFunctionEnd
+        %foo = OpFunction %void None %26
+  %src_param = OpFunctionParameter %_arr_int_uint_4
+         %29 = OpLabel
+%src_function = OpVariable %_ptr_Function__arr_int_uint_4 Function %7
+        %dst = OpVariable %_ptr_Function__arr_int_uint_4 Function %7
+ %dst_nested = OpVariable %_ptr_Function__arr__arr__arr_int_uint_2_uint_3_uint_4 Function %57
+ %src_nested = OpVariable %_ptr_Function__arr__arr__arr_int_uint_2_uint_3_uint_4 Function %57
+               OpStore %dst %36
+               OpStore %dst %src_param
+         %37 = OpFunctionCall %_arr_int_uint_4 %ret_arr
+               OpStore %dst %37
+               OpStore %dst %7
+         %38 = OpLoad %_arr_int_uint_4 %src_function
+               OpStore %dst %38
+         %39 = OpLoad %_arr_int_uint_4 %src_private
+               OpStore %dst %39
+         %40 = OpLoad %_arr_int_uint_4 %src_workgroup
+               OpStore %dst %40
+         %41 = OpFunctionCall %S %ret_struct_arr
+         %42 = OpCompositeExtract %_arr_int_uint_4 %41 0
+               OpStore %dst %42
+         %45 = OpAccessChain %_ptr_Uniform__arr_int_uint_4 %src_uniform %uint_0
+         %46 = OpLoad %_arr_int_uint_4 %45
+               OpStore %dst %46
+         %48 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %src_storage %uint_0
+         %49 = OpLoad %_arr_int_uint_4 %48
+               OpStore %dst %49
+         %59 = OpLoad %_arr__arr__arr_int_uint_2_uint_3_uint_4 %src_nested
+               OpStore %dst_nested %59
+               OpReturn
+               OpFunctionEnd
diff --git a/test/array/assign_to_function_var.wgsl.expected.wgsl b/test/array/assign_to_function_var.wgsl.expected.wgsl
new file mode 100644
index 0000000..7b23ed6
--- /dev/null
+++ b/test/array/assign_to_function_var.wgsl.expected.wgsl
@@ -0,0 +1,41 @@
+type ArrayType = [[stride(16)]] array<i32, 4>;
+
+[[block]]
+struct S {
+  arr : ArrayType;
+};
+
+var<private> src_private : ArrayType;
+
+var<workgroup> src_workgroup : ArrayType;
+
+[[group(0), binding(0)]] var<uniform> src_uniform : S;
+
+[[group(0), binding(1)]] var<storage> src_storage : [[access(read_write)]] S;
+
+fn ret_arr() -> ArrayType {
+  return ArrayType();
+}
+
+fn ret_struct_arr() -> S {
+  return S();
+}
+
+fn foo(src_param : ArrayType) {
+  var src_function : ArrayType;
+  var dst : ArrayType;
+  dst = ArrayType(1, 2, 3, 3);
+  dst = src_param;
+  dst = ret_arr();
+  let src_let : ArrayType = ArrayType();
+  dst = src_let;
+  dst = src_function;
+  dst = src_private;
+  dst = src_workgroup;
+  dst = ret_struct_arr().arr;
+  dst = src_uniform.arr;
+  dst = src_storage.arr;
+  var dst_nested : array<array<array<i32, 2>, 3>, 4>;
+  var src_nested : array<array<array<i32, 2>, 3>, 4>;
+  dst_nested = src_nested;
+}
diff --git a/test/array/assign_to_private_var.wgsl b/test/array/assign_to_private_var.wgsl
new file mode 100644
index 0000000..1c32ee3
--- /dev/null
+++ b/test/array/assign_to_private_var.wgsl
@@ -0,0 +1,53 @@
+type ArrayType = [[stride(16)]] array<i32, 4>;
+
+[[block]]
+struct S {
+  arr : ArrayType;
+};
+
+var<private> src_private : ArrayType;
+var<workgroup> src_workgroup : ArrayType;
+[[group(0), binding(0)]] var<uniform> src_uniform : S;
+[[group(0), binding(1)]] var<storage> src_storage : [[access(read_write)]] S;
+
+var<private> dst : ArrayType;
+var<private> dst_nested : array<array<array<i32, 2>, 3>, 4>;
+
+fn ret_arr() -> ArrayType {
+  return ArrayType();
+}
+
+fn ret_struct_arr() -> S {
+  return S();
+}
+
+fn foo(src_param : ArrayType) {
+  var src_function : ArrayType;
+
+  // Assign from type constructor.
+  dst = ArrayType(1, 2, 3, 3);
+
+  // Assign from parameter.
+  dst = src_param;
+
+  // Assign from function call.
+  dst = ret_arr();
+
+  // Assign from constant.
+  let src_let : ArrayType = ArrayType();
+  dst = src_let;
+
+  // Assign from var, various storage classes.
+  dst = src_function;
+  dst = src_private;
+  dst = src_workgroup;
+
+  // Assign from struct.arr, various storage classes.
+  dst = ret_struct_arr().arr;
+  dst = src_uniform.arr;
+  dst = src_storage.arr;
+
+  // Nested assignment.
+  var src_nested : array<array<array<i32, 2>, 3>, 4>;
+  dst_nested = src_nested;
+}
diff --git a/test/array/assign_to_private_var.wgsl.expected.hlsl b/test/array/assign_to_private_var.wgsl.expected.hlsl
new file mode 100644
index 0000000..7d6165e
--- /dev/null
+++ b/test/array/assign_to_private_var.wgsl.expected.hlsl
@@ -0,0 +1 @@
+SKIP: crbug.com/tint/845
diff --git a/test/array/assign_to_private_var.wgsl.expected.msl b/test/array/assign_to_private_var.wgsl.expected.msl
new file mode 100644
index 0000000..0ae6e42
--- /dev/null
+++ b/test/array/assign_to_private_var.wgsl.expected.msl
@@ -0,0 +1 @@
+SKIP: crbug.com/tint/649
diff --git a/test/array/assign_to_private_var.wgsl.expected.spvasm b/test/array/assign_to_private_var.wgsl.expected.spvasm
new file mode 100644
index 0000000..3ef5aa7
--- /dev/null
+++ b/test/array/assign_to_private_var.wgsl.expected.spvasm
@@ -0,0 +1,113 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 61
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %src_private "src_private"
+               OpName %src_workgroup "src_workgroup"
+               OpName %S "S"
+               OpMemberName %S 0 "arr"
+               OpName %src_uniform "src_uniform"
+               OpName %src_storage "src_storage"
+               OpName %dst "dst"
+               OpName %dst_nested "dst_nested"
+               OpName %unused_entry_point "unused_entry_point"
+               OpName %ret_arr "ret_arr"
+               OpName %ret_struct_arr "ret_struct_arr"
+               OpName %foo "foo"
+               OpName %src_param "src_param"
+               OpName %src_function "src_function"
+               OpName %src_nested "src_nested"
+               OpDecorate %_arr_int_uint_4 ArrayStride 16
+               OpDecorate %S Block
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %src_uniform DescriptorSet 0
+               OpDecorate %src_uniform Binding 0
+               OpDecorate %src_storage DescriptorSet 0
+               OpDecorate %src_storage Binding 1
+               OpDecorate %_arr_int_uint_2 ArrayStride 4
+               OpDecorate %_arr__arr_int_uint_2_uint_3 ArrayStride 8
+               OpDecorate %_arr__arr__arr_int_uint_2_uint_3_uint_4 ArrayStride 24
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+%_ptr_Private__arr_int_uint_4 = OpTypePointer Private %_arr_int_uint_4
+          %7 = OpConstantNull %_arr_int_uint_4
+%src_private = OpVariable %_ptr_Private__arr_int_uint_4 Private %7
+%_ptr_Workgroup__arr_int_uint_4 = OpTypePointer Workgroup %_arr_int_uint_4
+%src_workgroup = OpVariable %_ptr_Workgroup__arr_int_uint_4 Workgroup
+          %S = OpTypeStruct %_arr_int_uint_4
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+%src_uniform = OpVariable %_ptr_Uniform_S Uniform
+%_ptr_StorageBuffer_S = OpTypePointer StorageBuffer %S
+%src_storage = OpVariable %_ptr_StorageBuffer_S StorageBuffer
+        %dst = OpVariable %_ptr_Private__arr_int_uint_4 Private %7
+     %uint_2 = OpConstant %uint 2
+%_arr_int_uint_2 = OpTypeArray %int %uint_2
+     %uint_3 = OpConstant %uint 3
+%_arr__arr_int_uint_2_uint_3 = OpTypeArray %_arr_int_uint_2 %uint_3
+%_arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypeArray %_arr__arr_int_uint_2_uint_3 %uint_4
+%_ptr_Private__arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypePointer Private %_arr__arr__arr_int_uint_2_uint_3_uint_4
+         %23 = OpConstantNull %_arr__arr__arr_int_uint_2_uint_3_uint_4
+ %dst_nested = OpVariable %_ptr_Private__arr__arr__arr_int_uint_2_uint_3_uint_4 Private %23
+       %void = OpTypeVoid
+         %24 = OpTypeFunction %void
+         %28 = OpTypeFunction %_arr_int_uint_4
+         %31 = OpTypeFunction %S
+         %34 = OpConstantNull %S
+         %35 = OpTypeFunction %void %_arr_int_uint_4
+%_ptr_Function__arr_int_uint_4 = OpTypePointer Function %_arr_int_uint_4
+      %int_1 = OpConstant %int 1
+      %int_2 = OpConstant %int 2
+      %int_3 = OpConstant %int 3
+         %44 = OpConstantComposite %_arr_int_uint_4 %int_1 %int_2 %int_3 %int_3
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform__arr_int_uint_4 = OpTypePointer Uniform %_arr_int_uint_4
+%_ptr_StorageBuffer__arr_int_uint_4 = OpTypePointer StorageBuffer %_arr_int_uint_4
+%_ptr_Function__arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypePointer Function %_arr__arr__arr_int_uint_2_uint_3_uint_4
+%unused_entry_point = OpFunction %void None %24
+         %27 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    %ret_arr = OpFunction %_arr_int_uint_4 None %28
+         %30 = OpLabel
+               OpReturnValue %7
+               OpFunctionEnd
+%ret_struct_arr = OpFunction %S None %31
+         %33 = OpLabel
+               OpReturnValue %34
+               OpFunctionEnd
+        %foo = OpFunction %void None %35
+  %src_param = OpFunctionParameter %_arr_int_uint_4
+         %38 = OpLabel
+%src_function = OpVariable %_ptr_Function__arr_int_uint_4 Function %7
+ %src_nested = OpVariable %_ptr_Function__arr__arr__arr_int_uint_2_uint_3_uint_4 Function %23
+               OpStore %dst %44
+               OpStore %dst %src_param
+         %45 = OpFunctionCall %_arr_int_uint_4 %ret_arr
+               OpStore %dst %45
+               OpStore %dst %7
+         %46 = OpLoad %_arr_int_uint_4 %src_function
+               OpStore %dst %46
+         %47 = OpLoad %_arr_int_uint_4 %src_private
+               OpStore %dst %47
+         %48 = OpLoad %_arr_int_uint_4 %src_workgroup
+               OpStore %dst %48
+         %49 = OpFunctionCall %S %ret_struct_arr
+         %50 = OpCompositeExtract %_arr_int_uint_4 %49 0
+               OpStore %dst %50
+         %53 = OpAccessChain %_ptr_Uniform__arr_int_uint_4 %src_uniform %uint_0
+         %54 = OpLoad %_arr_int_uint_4 %53
+               OpStore %dst %54
+         %56 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %src_storage %uint_0
+         %57 = OpLoad %_arr_int_uint_4 %56
+               OpStore %dst %57
+         %60 = OpLoad %_arr__arr__arr_int_uint_2_uint_3_uint_4 %src_nested
+               OpStore %dst_nested %60
+               OpReturn
+               OpFunctionEnd
diff --git a/test/array/assign_to_private_var.wgsl.expected.wgsl b/test/array/assign_to_private_var.wgsl.expected.wgsl
new file mode 100644
index 0000000..af9c38e
--- /dev/null
+++ b/test/array/assign_to_private_var.wgsl.expected.wgsl
@@ -0,0 +1,43 @@
+type ArrayType = [[stride(16)]] array<i32, 4>;
+
+[[block]]
+struct S {
+  arr : ArrayType;
+};
+
+var<private> src_private : ArrayType;
+
+var<workgroup> src_workgroup : ArrayType;
+
+[[group(0), binding(0)]] var<uniform> src_uniform : S;
+
+[[group(0), binding(1)]] var<storage> src_storage : [[access(read_write)]] S;
+
+var<private> dst : ArrayType;
+
+var<private> dst_nested : array<array<array<i32, 2>, 3>, 4>;
+
+fn ret_arr() -> ArrayType {
+  return ArrayType();
+}
+
+fn ret_struct_arr() -> S {
+  return S();
+}
+
+fn foo(src_param : ArrayType) {
+  var src_function : ArrayType;
+  dst = ArrayType(1, 2, 3, 3);
+  dst = src_param;
+  dst = ret_arr();
+  let src_let : ArrayType = ArrayType();
+  dst = src_let;
+  dst = src_function;
+  dst = src_private;
+  dst = src_workgroup;
+  dst = ret_struct_arr().arr;
+  dst = src_uniform.arr;
+  dst = src_storage.arr;
+  var src_nested : array<array<array<i32, 2>, 3>, 4>;
+  dst_nested = src_nested;
+}
diff --git a/test/array/assign_to_storage_var.wgsl b/test/array/assign_to_storage_var.wgsl
new file mode 100644
index 0000000..5754a22
--- /dev/null
+++ b/test/array/assign_to_storage_var.wgsl
@@ -0,0 +1,58 @@
+type ArrayType = [[stride(16)]] array<i32, 4>;
+
+[[block]]
+struct S {
+  arr : ArrayType;
+};
+
+[[block]]
+struct S_nested {
+  arr : array<array<array<i32, 2>, 3>, 4>;
+};
+
+var<private> src_private : ArrayType;
+var<workgroup> src_workgroup : ArrayType;
+[[group(0), binding(0)]] var<uniform> src_uniform : S;
+[[group(0), binding(1)]] var<storage> src_storage : [[access(read_write)]] S;
+
+[[group(0), binding(2)]] var<storage> dst : [[access(read_write)]] S;
+[[group(0), binding(3)]] var<storage> dst_nested : [[access(read_write)]] S_nested;
+
+fn ret_arr() -> ArrayType {
+  return ArrayType();
+}
+
+fn ret_struct_arr() -> S {
+  return S();
+}
+
+fn foo(src_param : ArrayType) {
+  var src_function : ArrayType;
+
+  // Assign from type constructor.
+  dst.arr = ArrayType(1, 2, 3, 3);
+
+  // Assign from parameter.
+  dst.arr = src_param;
+
+  // Assign from function call.
+  dst.arr = ret_arr();
+
+  // Assign from constant.
+  let src_let : ArrayType = ArrayType();
+  dst.arr = src_let;
+
+  // Assign from var, various storage classes.
+  dst.arr = src_function;
+  dst.arr = src_private;
+  dst.arr = src_workgroup;
+
+  // Assign from struct.arr, various storage classes.
+  dst.arr = ret_struct_arr().arr;
+  dst.arr = src_uniform.arr;
+  dst.arr = src_storage.arr;
+
+  // Nested assignment.
+  var src_nested : array<array<array<i32, 2>, 3>, 4>;
+  dst_nested.arr = src_nested;
+}
diff --git a/test/array/assign_to_storage_var.wgsl.expected.hlsl b/test/array/assign_to_storage_var.wgsl.expected.hlsl
new file mode 100644
index 0000000..7d6165e
--- /dev/null
+++ b/test/array/assign_to_storage_var.wgsl.expected.hlsl
@@ -0,0 +1 @@
+SKIP: crbug.com/tint/845
diff --git a/test/array/assign_to_storage_var.wgsl.expected.msl b/test/array/assign_to_storage_var.wgsl.expected.msl
new file mode 100644
index 0000000..0ae6e42
--- /dev/null
+++ b/test/array/assign_to_storage_var.wgsl.expected.msl
@@ -0,0 +1 @@
+SKIP: crbug.com/tint/649
diff --git a/test/array/assign_to_storage_var.wgsl.expected.spvasm b/test/array/assign_to_storage_var.wgsl.expected.spvasm
new file mode 100644
index 0000000..67fa25e
--- /dev/null
+++ b/test/array/assign_to_storage_var.wgsl.expected.spvasm
@@ -0,0 +1,134 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 74
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %src_private "src_private"
+               OpName %src_workgroup "src_workgroup"
+               OpName %S "S"
+               OpMemberName %S 0 "arr"
+               OpName %src_uniform "src_uniform"
+               OpName %src_storage "src_storage"
+               OpName %dst "dst"
+               OpName %S_nested "S_nested"
+               OpMemberName %S_nested 0 "arr"
+               OpName %dst_nested "dst_nested"
+               OpName %unused_entry_point "unused_entry_point"
+               OpName %ret_arr "ret_arr"
+               OpName %ret_struct_arr "ret_struct_arr"
+               OpName %foo "foo"
+               OpName %src_param "src_param"
+               OpName %src_function "src_function"
+               OpName %src_nested "src_nested"
+               OpDecorate %_arr_int_uint_4 ArrayStride 16
+               OpDecorate %S Block
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %src_uniform DescriptorSet 0
+               OpDecorate %src_uniform Binding 0
+               OpDecorate %src_storage DescriptorSet 0
+               OpDecorate %src_storage Binding 1
+               OpDecorate %dst DescriptorSet 0
+               OpDecorate %dst Binding 2
+               OpDecorate %S_nested Block
+               OpMemberDecorate %S_nested 0 Offset 0
+               OpDecorate %_arr_int_uint_2 ArrayStride 4
+               OpDecorate %_arr__arr_int_uint_2_uint_3 ArrayStride 8
+               OpDecorate %_arr__arr__arr_int_uint_2_uint_3_uint_4 ArrayStride 24
+               OpDecorate %dst_nested DescriptorSet 0
+               OpDecorate %dst_nested Binding 3
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+%_ptr_Private__arr_int_uint_4 = OpTypePointer Private %_arr_int_uint_4
+          %7 = OpConstantNull %_arr_int_uint_4
+%src_private = OpVariable %_ptr_Private__arr_int_uint_4 Private %7
+%_ptr_Workgroup__arr_int_uint_4 = OpTypePointer Workgroup %_arr_int_uint_4
+%src_workgroup = OpVariable %_ptr_Workgroup__arr_int_uint_4 Workgroup
+          %S = OpTypeStruct %_arr_int_uint_4
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+%src_uniform = OpVariable %_ptr_Uniform_S Uniform
+%_ptr_StorageBuffer_S = OpTypePointer StorageBuffer %S
+%src_storage = OpVariable %_ptr_StorageBuffer_S StorageBuffer
+        %dst = OpVariable %_ptr_StorageBuffer_S StorageBuffer
+     %uint_2 = OpConstant %uint 2
+%_arr_int_uint_2 = OpTypeArray %int %uint_2
+     %uint_3 = OpConstant %uint 3
+%_arr__arr_int_uint_2_uint_3 = OpTypeArray %_arr_int_uint_2 %uint_3
+%_arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypeArray %_arr__arr_int_uint_2_uint_3 %uint_4
+   %S_nested = OpTypeStruct %_arr__arr__arr_int_uint_2_uint_3_uint_4
+%_ptr_StorageBuffer_S_nested = OpTypePointer StorageBuffer %S_nested
+ %dst_nested = OpVariable %_ptr_StorageBuffer_S_nested StorageBuffer
+       %void = OpTypeVoid
+         %24 = OpTypeFunction %void
+         %28 = OpTypeFunction %_arr_int_uint_4
+         %31 = OpTypeFunction %S
+         %34 = OpConstantNull %S
+         %35 = OpTypeFunction %void %_arr_int_uint_4
+%_ptr_Function__arr_int_uint_4 = OpTypePointer Function %_arr_int_uint_4
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer__arr_int_uint_4 = OpTypePointer StorageBuffer %_arr_int_uint_4
+      %int_1 = OpConstant %int 1
+      %int_2 = OpConstant %int 2
+      %int_3 = OpConstant %int 3
+         %47 = OpConstantComposite %_arr_int_uint_4 %int_1 %int_2 %int_3 %int_3
+%_ptr_Uniform__arr_int_uint_4 = OpTypePointer Uniform %_arr_int_uint_4
+%_ptr_Function__arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypePointer Function %_arr__arr__arr_int_uint_2_uint_3_uint_4
+         %70 = OpConstantNull %_arr__arr__arr_int_uint_2_uint_3_uint_4
+%_ptr_StorageBuffer__arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypePointer StorageBuffer %_arr__arr__arr_int_uint_2_uint_3_uint_4
+%unused_entry_point = OpFunction %void None %24
+         %27 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    %ret_arr = OpFunction %_arr_int_uint_4 None %28
+         %30 = OpLabel
+               OpReturnValue %7
+               OpFunctionEnd
+%ret_struct_arr = OpFunction %S None %31
+         %33 = OpLabel
+               OpReturnValue %34
+               OpFunctionEnd
+        %foo = OpFunction %void None %35
+  %src_param = OpFunctionParameter %_arr_int_uint_4
+         %38 = OpLabel
+%src_function = OpVariable %_ptr_Function__arr_int_uint_4 Function %7
+ %src_nested = OpVariable %_ptr_Function__arr__arr__arr_int_uint_2_uint_3_uint_4 Function %70
+         %43 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %dst %uint_0
+               OpStore %43 %47
+         %48 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %dst %uint_0
+               OpStore %48 %src_param
+         %49 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %dst %uint_0
+         %50 = OpFunctionCall %_arr_int_uint_4 %ret_arr
+               OpStore %49 %50
+         %51 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %dst %uint_0
+               OpStore %51 %7
+         %52 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %dst %uint_0
+         %53 = OpLoad %_arr_int_uint_4 %src_function
+               OpStore %52 %53
+         %54 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %dst %uint_0
+         %55 = OpLoad %_arr_int_uint_4 %src_private
+               OpStore %54 %55
+         %56 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %dst %uint_0
+         %57 = OpLoad %_arr_int_uint_4 %src_workgroup
+               OpStore %56 %57
+         %58 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %dst %uint_0
+         %59 = OpFunctionCall %S %ret_struct_arr
+         %60 = OpCompositeExtract %_arr_int_uint_4 %59 0
+               OpStore %58 %60
+         %61 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %dst %uint_0
+         %63 = OpAccessChain %_ptr_Uniform__arr_int_uint_4 %src_uniform %uint_0
+         %64 = OpLoad %_arr_int_uint_4 %63
+               OpStore %61 %64
+         %65 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %dst %uint_0
+         %66 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %src_storage %uint_0
+         %67 = OpLoad %_arr_int_uint_4 %66
+               OpStore %65 %67
+         %72 = OpAccessChain %_ptr_StorageBuffer__arr__arr__arr_int_uint_2_uint_3_uint_4 %dst_nested %uint_0
+         %73 = OpLoad %_arr__arr__arr_int_uint_2_uint_3_uint_4 %src_nested
+               OpStore %72 %73
+               OpReturn
+               OpFunctionEnd
diff --git a/test/array/assign_to_storage_var.wgsl.expected.wgsl b/test/array/assign_to_storage_var.wgsl.expected.wgsl
new file mode 100644
index 0000000..6508595
--- /dev/null
+++ b/test/array/assign_to_storage_var.wgsl.expected.wgsl
@@ -0,0 +1,48 @@
+type ArrayType = [[stride(16)]] array<i32, 4>;
+
+[[block]]
+struct S {
+  arr : ArrayType;
+};
+
+[[block]]
+struct S_nested {
+  arr : array<array<array<i32, 2>, 3>, 4>;
+};
+
+var<private> src_private : ArrayType;
+
+var<workgroup> src_workgroup : ArrayType;
+
+[[group(0), binding(0)]] var<uniform> src_uniform : S;
+
+[[group(0), binding(1)]] var<storage> src_storage : [[access(read_write)]] S;
+
+[[group(0), binding(2)]] var<storage> dst : [[access(read_write)]] S;
+
+[[group(0), binding(3)]] var<storage> dst_nested : [[access(read_write)]] S_nested;
+
+fn ret_arr() -> ArrayType {
+  return ArrayType();
+}
+
+fn ret_struct_arr() -> S {
+  return S();
+}
+
+fn foo(src_param : ArrayType) {
+  var src_function : ArrayType;
+  dst.arr = ArrayType(1, 2, 3, 3);
+  dst.arr = src_param;
+  dst.arr = ret_arr();
+  let src_let : ArrayType = ArrayType();
+  dst.arr = src_let;
+  dst.arr = src_function;
+  dst.arr = src_private;
+  dst.arr = src_workgroup;
+  dst.arr = ret_struct_arr().arr;
+  dst.arr = src_uniform.arr;
+  dst.arr = src_storage.arr;
+  var src_nested : array<array<array<i32, 2>, 3>, 4>;
+  dst_nested.arr = src_nested;
+}
diff --git a/test/array/assign_to_subexpr.wgsl b/test/array/assign_to_subexpr.wgsl
new file mode 100644
index 0000000..c633eb7
--- /dev/null
+++ b/test/array/assign_to_subexpr.wgsl
@@ -0,0 +1,27 @@
+type ArrayType = array<i32, 4>;
+
+struct S {
+  arr : array<i32, 4>;
+};
+
+fn foo() {
+  let src : ArrayType = ArrayType();
+
+  var dst : ArrayType;
+  var dst_struct : S;
+  var dst_array : array<ArrayType, 2>;
+  let dst_ptr : ptr<function, ArrayType> = &dst;
+  let dst_struct_ptr : ptr<function, S> = &dst_struct;
+  let dst_array_ptr : ptr<function, array<ArrayType, 2>> = &dst_array;
+
+  // Assign to struct.member.
+  dst_struct.arr = src;
+
+  // Assign to array[index].
+  dst_array[1] = src;
+
+  // Assign via pointers.
+  *dst_ptr = src;
+  (*dst_struct_ptr).arr = src;
+  (*dst_array_ptr)[0] = src;
+}
diff --git a/test/array/assign_to_subexpr.wgsl.expected.hlsl b/test/array/assign_to_subexpr.wgsl.expected.hlsl
new file mode 100644
index 0000000..4d2b005
--- /dev/null
+++ b/test/array/assign_to_subexpr.wgsl.expected.hlsl
@@ -0,0 +1,21 @@
+struct S {
+  int arr[4];
+};
+
+void foo() {
+  const int src[4] = {0, 0, 0, 0};
+  int tint_symbol[4] = {0, 0, 0, 0};
+  S dst_struct = {{0, 0, 0, 0}};
+  int dst_array[2][4] = {{0, 0, 0, 0}, {0, 0, 0, 0}};
+  dst_struct.arr = src;
+  dst_array[1] = src;
+  tint_symbol = src;
+  dst_struct.arr = src;
+  dst_array[0] = src;
+}
+
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
diff --git a/test/array/assign_to_subexpr.wgsl.expected.msl b/test/array/assign_to_subexpr.wgsl.expected.msl
new file mode 100644
index 0000000..1d09d97
--- /dev/null
+++ b/test/array/assign_to_subexpr.wgsl.expected.msl
@@ -0,0 +1,28 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_array_wrapper_0 {
+  int array[4];
+};
+struct S {
+  tint_array_wrapper_0 arr;
+};
+struct tint_array_wrapper_1 {
+  tint_array_wrapper_0 array[2];
+};
+
+void foo() {
+  tint_array_wrapper_0 const src = {};
+  tint_array_wrapper_0 dst = {0};
+  S dst_struct = {};
+  tint_array_wrapper_1 dst_array = {{0}};
+  thread tint_array_wrapper_0* const dst_ptr = &(dst);
+  thread S* const dst_struct_ptr = &(dst_struct);
+  thread tint_array_wrapper_1* const dst_array_ptr = &(dst_array);
+  dst_struct.arr = src;
+  dst_array.array[1] = src;
+  *(dst_ptr) = src;
+  (*(dst_struct_ptr)).arr = src;
+  (*(dst_array_ptr)).array[0] = src;
+}
+
diff --git a/test/array/assign_to_subexpr.wgsl.expected.spvasm b/test/array/assign_to_subexpr.wgsl.expected.spvasm
new file mode 100644
index 0000000..7c4cd74
--- /dev/null
+++ b/test/array/assign_to_subexpr.wgsl.expected.spvasm
@@ -0,0 +1,57 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 36
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %unused_entry_point "unused_entry_point"
+               OpName %foo "foo"
+               OpName %dst "dst"
+               OpName %S "S"
+               OpMemberName %S 0 "arr"
+               OpName %dst_struct "dst_struct"
+               OpName %dst_array "dst_array"
+               OpDecorate %_arr_int_uint_4 ArrayStride 4
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %_arr__arr_int_uint_4_uint_2 ArrayStride 16
+       %void = OpTypeVoid
+          %1 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+         %11 = OpConstantNull %_arr_int_uint_4
+%_ptr_Function__arr_int_uint_4 = OpTypePointer Function %_arr_int_uint_4
+          %S = OpTypeStruct %_arr_int_uint_4
+%_ptr_Function_S = OpTypePointer Function %S
+         %17 = OpConstantNull %S
+     %uint_2 = OpConstant %uint 2
+%_arr__arr_int_uint_4_uint_2 = OpTypeArray %_arr_int_uint_4 %uint_2
+%_ptr_Function__arr__arr_int_uint_4_uint_2 = OpTypePointer Function %_arr__arr_int_uint_4_uint_2
+         %22 = OpConstantNull %_arr__arr_int_uint_4_uint_2
+     %uint_0 = OpConstant %uint 0
+      %int_1 = OpConstant %int 1
+      %int_0 = OpConstant %int 0
+%unused_entry_point = OpFunction %void None %1
+          %4 = OpLabel
+               OpReturn
+               OpFunctionEnd
+        %foo = OpFunction %void None %1
+          %6 = OpLabel
+        %dst = OpVariable %_ptr_Function__arr_int_uint_4 Function %11
+ %dst_struct = OpVariable %_ptr_Function_S Function %17
+  %dst_array = OpVariable %_ptr_Function__arr__arr_int_uint_4_uint_2 Function %22
+         %27 = OpAccessChain %_ptr_Function__arr_int_uint_4 %dst_struct %uint_0
+               OpStore %27 %11
+         %29 = OpAccessChain %_ptr_Function__arr_int_uint_4 %dst_array %int_1
+               OpStore %29 %11
+               OpStore %dst %11
+         %32 = OpAccessChain %_ptr_Function__arr_int_uint_4 %dst_struct %uint_0
+               OpStore %32 %11
+         %35 = OpAccessChain %_ptr_Function__arr_int_uint_4 %dst_array %int_0
+               OpStore %35 %11
+               OpReturn
+               OpFunctionEnd
diff --git a/test/array/assign_to_subexpr.wgsl.expected.wgsl b/test/array/assign_to_subexpr.wgsl.expected.wgsl
new file mode 100644
index 0000000..6ab4099
--- /dev/null
+++ b/test/array/assign_to_subexpr.wgsl.expected.wgsl
@@ -0,0 +1,20 @@
+type ArrayType = array<i32, 4>;
+
+struct S {
+  arr : array<i32, 4>;
+};
+
+fn foo() {
+  let src : ArrayType = ArrayType();
+  var dst : ArrayType;
+  var dst_struct : S;
+  var dst_array : array<ArrayType, 2>;
+  let dst_ptr : ptr<function, ArrayType> = &(dst);
+  let dst_struct_ptr : ptr<function, S> = &(dst_struct);
+  let dst_array_ptr : ptr<function, array<ArrayType, 2>> = &(dst_array);
+  dst_struct.arr = src;
+  dst_array[1] = src;
+  *(dst_ptr) = src;
+  (*(dst_struct_ptr)).arr = src;
+  (*(dst_array_ptr))[0] = src;
+}
diff --git a/test/array/assign_to_workgroup_var.wgsl b/test/array/assign_to_workgroup_var.wgsl
new file mode 100644
index 0000000..675daf6
--- /dev/null
+++ b/test/array/assign_to_workgroup_var.wgsl
@@ -0,0 +1,53 @@
+type ArrayType = [[stride(16)]] array<i32, 4>;
+
+[[block]]
+struct S {
+  arr : ArrayType;
+};
+
+var<private> src_private : ArrayType;
+var<workgroup> src_workgroup : ArrayType;
+[[group(0), binding(0)]] var<uniform> src_uniform : S;
+[[group(0), binding(1)]] var<storage> src_storage : [[access(read_write)]] S;
+
+var<workgroup> dst : ArrayType;
+var<workgroup> dst_nested : array<array<array<i32, 2>, 3>, 4>;
+
+fn ret_arr() -> ArrayType {
+  return ArrayType();
+}
+
+fn ret_struct_arr() -> S {
+  return S();
+}
+
+fn foo(src_param : ArrayType) {
+  var src_function : ArrayType;
+
+  // Assign from type constructor.
+  dst = ArrayType(1, 2, 3, 3);
+
+  // Assign from parameter.
+  dst = src_param;
+
+  // Assign from function call.
+  dst = ret_arr();
+
+  // Assign from constant.
+  let src_let : ArrayType = ArrayType();
+  dst = src_let;
+
+  // Assign from var, various storage classes.
+  dst = src_function;
+  dst = src_private;
+  dst = src_workgroup;
+
+  // Assign from struct.arr, various storage classes.
+  dst = ret_struct_arr().arr;
+  dst = src_uniform.arr;
+  dst = src_storage.arr;
+
+  // Nested assignment.
+  var src_nested : array<array<array<i32, 2>, 3>, 4>;
+  dst_nested = src_nested;
+}
diff --git a/test/array/assign_to_workgroup_var.wgsl.expected.hlsl b/test/array/assign_to_workgroup_var.wgsl.expected.hlsl
new file mode 100644
index 0000000..7d6165e
--- /dev/null
+++ b/test/array/assign_to_workgroup_var.wgsl.expected.hlsl
@@ -0,0 +1 @@
+SKIP: crbug.com/tint/845
diff --git a/test/array/assign_to_workgroup_var.wgsl.expected.msl b/test/array/assign_to_workgroup_var.wgsl.expected.msl
new file mode 100644
index 0000000..0ae6e42
--- /dev/null
+++ b/test/array/assign_to_workgroup_var.wgsl.expected.msl
@@ -0,0 +1 @@
+SKIP: crbug.com/tint/649
diff --git a/test/array/assign_to_workgroup_var.wgsl.expected.spvasm b/test/array/assign_to_workgroup_var.wgsl.expected.spvasm
new file mode 100644
index 0000000..e491f49
--- /dev/null
+++ b/test/array/assign_to_workgroup_var.wgsl.expected.spvasm
@@ -0,0 +1,113 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 61
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %src_private "src_private"
+               OpName %src_workgroup "src_workgroup"
+               OpName %S "S"
+               OpMemberName %S 0 "arr"
+               OpName %src_uniform "src_uniform"
+               OpName %src_storage "src_storage"
+               OpName %dst "dst"
+               OpName %dst_nested "dst_nested"
+               OpName %unused_entry_point "unused_entry_point"
+               OpName %ret_arr "ret_arr"
+               OpName %ret_struct_arr "ret_struct_arr"
+               OpName %foo "foo"
+               OpName %src_param "src_param"
+               OpName %src_function "src_function"
+               OpName %src_nested "src_nested"
+               OpDecorate %_arr_int_uint_4 ArrayStride 16
+               OpDecorate %S Block
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %src_uniform DescriptorSet 0
+               OpDecorate %src_uniform Binding 0
+               OpDecorate %src_storage DescriptorSet 0
+               OpDecorate %src_storage Binding 1
+               OpDecorate %_arr_int_uint_2 ArrayStride 4
+               OpDecorate %_arr__arr_int_uint_2_uint_3 ArrayStride 8
+               OpDecorate %_arr__arr__arr_int_uint_2_uint_3_uint_4 ArrayStride 24
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+%_ptr_Private__arr_int_uint_4 = OpTypePointer Private %_arr_int_uint_4
+          %7 = OpConstantNull %_arr_int_uint_4
+%src_private = OpVariable %_ptr_Private__arr_int_uint_4 Private %7
+%_ptr_Workgroup__arr_int_uint_4 = OpTypePointer Workgroup %_arr_int_uint_4
+%src_workgroup = OpVariable %_ptr_Workgroup__arr_int_uint_4 Workgroup
+          %S = OpTypeStruct %_arr_int_uint_4
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+%src_uniform = OpVariable %_ptr_Uniform_S Uniform
+%_ptr_StorageBuffer_S = OpTypePointer StorageBuffer %S
+%src_storage = OpVariable %_ptr_StorageBuffer_S StorageBuffer
+        %dst = OpVariable %_ptr_Workgroup__arr_int_uint_4 Workgroup
+     %uint_2 = OpConstant %uint 2
+%_arr_int_uint_2 = OpTypeArray %int %uint_2
+     %uint_3 = OpConstant %uint 3
+%_arr__arr_int_uint_2_uint_3 = OpTypeArray %_arr_int_uint_2 %uint_3
+%_arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypeArray %_arr__arr_int_uint_2_uint_3 %uint_4
+%_ptr_Workgroup__arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypePointer Workgroup %_arr__arr__arr_int_uint_2_uint_3_uint_4
+ %dst_nested = OpVariable %_ptr_Workgroup__arr__arr__arr_int_uint_2_uint_3_uint_4 Workgroup
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+         %27 = OpTypeFunction %_arr_int_uint_4
+         %30 = OpTypeFunction %S
+         %33 = OpConstantNull %S
+         %34 = OpTypeFunction %void %_arr_int_uint_4
+%_ptr_Function__arr_int_uint_4 = OpTypePointer Function %_arr_int_uint_4
+      %int_1 = OpConstant %int 1
+      %int_2 = OpConstant %int 2
+      %int_3 = OpConstant %int 3
+         %43 = OpConstantComposite %_arr_int_uint_4 %int_1 %int_2 %int_3 %int_3
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform__arr_int_uint_4 = OpTypePointer Uniform %_arr_int_uint_4
+%_ptr_StorageBuffer__arr_int_uint_4 = OpTypePointer StorageBuffer %_arr_int_uint_4
+%_ptr_Function__arr__arr__arr_int_uint_2_uint_3_uint_4 = OpTypePointer Function %_arr__arr__arr_int_uint_2_uint_3_uint_4
+         %59 = OpConstantNull %_arr__arr__arr_int_uint_2_uint_3_uint_4
+%unused_entry_point = OpFunction %void None %23
+         %26 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    %ret_arr = OpFunction %_arr_int_uint_4 None %27
+         %29 = OpLabel
+               OpReturnValue %7
+               OpFunctionEnd
+%ret_struct_arr = OpFunction %S None %30
+         %32 = OpLabel
+               OpReturnValue %33
+               OpFunctionEnd
+        %foo = OpFunction %void None %34
+  %src_param = OpFunctionParameter %_arr_int_uint_4
+         %37 = OpLabel
+%src_function = OpVariable %_ptr_Function__arr_int_uint_4 Function %7
+ %src_nested = OpVariable %_ptr_Function__arr__arr__arr_int_uint_2_uint_3_uint_4 Function %59
+               OpStore %dst %43
+               OpStore %dst %src_param
+         %44 = OpFunctionCall %_arr_int_uint_4 %ret_arr
+               OpStore %dst %44
+               OpStore %dst %7
+         %45 = OpLoad %_arr_int_uint_4 %src_function
+               OpStore %dst %45
+         %46 = OpLoad %_arr_int_uint_4 %src_private
+               OpStore %dst %46
+         %47 = OpLoad %_arr_int_uint_4 %src_workgroup
+               OpStore %dst %47
+         %48 = OpFunctionCall %S %ret_struct_arr
+         %49 = OpCompositeExtract %_arr_int_uint_4 %48 0
+               OpStore %dst %49
+         %52 = OpAccessChain %_ptr_Uniform__arr_int_uint_4 %src_uniform %uint_0
+         %53 = OpLoad %_arr_int_uint_4 %52
+               OpStore %dst %53
+         %55 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %src_storage %uint_0
+         %56 = OpLoad %_arr_int_uint_4 %55
+               OpStore %dst %56
+         %60 = OpLoad %_arr__arr__arr_int_uint_2_uint_3_uint_4 %src_nested
+               OpStore %dst_nested %60
+               OpReturn
+               OpFunctionEnd
diff --git a/test/array/assign_to_workgroup_var.wgsl.expected.wgsl b/test/array/assign_to_workgroup_var.wgsl.expected.wgsl
new file mode 100644
index 0000000..8816948
--- /dev/null
+++ b/test/array/assign_to_workgroup_var.wgsl.expected.wgsl
@@ -0,0 +1,43 @@
+type ArrayType = [[stride(16)]] array<i32, 4>;
+
+[[block]]
+struct S {
+  arr : ArrayType;
+};
+
+var<private> src_private : ArrayType;
+
+var<workgroup> src_workgroup : ArrayType;
+
+[[group(0), binding(0)]] var<uniform> src_uniform : S;
+
+[[group(0), binding(1)]] var<storage> src_storage : [[access(read_write)]] S;
+
+var<workgroup> dst : ArrayType;
+
+var<workgroup> dst_nested : array<array<array<i32, 2>, 3>, 4>;
+
+fn ret_arr() -> ArrayType {
+  return ArrayType();
+}
+
+fn ret_struct_arr() -> S {
+  return S();
+}
+
+fn foo(src_param : ArrayType) {
+  var src_function : ArrayType;
+  dst = ArrayType(1, 2, 3, 3);
+  dst = src_param;
+  dst = ret_arr();
+  let src_let : ArrayType = ArrayType();
+  dst = src_let;
+  dst = src_function;
+  dst = src_private;
+  dst = src_workgroup;
+  dst = ret_struct_arr().arr;
+  dst = src_uniform.arr;
+  dst = src_storage.arr;
+  var src_nested : array<array<array<i32, 2>, 3>, 4>;
+  dst_nested = src_nested;
+}
diff --git a/test/array/function_parameter.wgsl b/test/array/function_parameter.wgsl
new file mode 100644
index 0000000..0b301e7
--- /dev/null
+++ b/test/array/function_parameter.wgsl
@@ -0,0 +1,21 @@
+fn f1(a : array<f32, 4>) -> f32 {
+  return a[3];
+}
+
+fn f2(a : array<array<f32, 4>, 3>) -> f32 {
+  return a[2][3];
+}
+
+fn f3(a : array<array<array<f32, 4>, 3>, 2>) -> f32 {
+  return a[1][2][3];
+}
+
+[[stage(compute)]]
+fn main() {
+  let a1 : array<f32, 4> = array<f32, 4>();
+  let a2 : array<array<f32, 4>, 3> = array<array<f32, 4>, 3>();
+  let a3 : array<array<array<f32, 4>, 3>, 2> = array<array<array<f32, 4>, 3>, 2>();
+  let v1 : f32 = f1(a1);
+  let v2 : f32 = f2(a2);
+  let v3 : f32 = f3(a3);
+}
diff --git a/test/array/function_parameter.wgsl.expected.hlsl b/test/array/function_parameter.wgsl.expected.hlsl
new file mode 100644
index 0000000..028cce9
--- /dev/null
+++ b/test/array/function_parameter.wgsl.expected.hlsl
@@ -0,0 +1,23 @@
+float f1(float a[4]) {
+  return a[3];
+}
+
+float f2(float a[3][4]) {
+  return a[2][3];
+}
+
+float f3(float a[2][3][4]) {
+  return a[1][2][3];
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float a1[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+  const float a2[3][4] = {{0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}};
+  const float a3[2][3][4] = {{{0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}}, {{0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}}};
+  const float v1 = f1(a1);
+  const float v2 = f2(a2);
+  const float v3 = f3(a3);
+  return;
+}
+
diff --git a/test/array/function_parameter.wgsl.expected.msl b/test/array/function_parameter.wgsl.expected.msl
new file mode 100644
index 0000000..5c999da
--- /dev/null
+++ b/test/array/function_parameter.wgsl.expected.msl
@@ -0,0 +1,35 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_array_wrapper_0 {
+  float array[4];
+};
+struct tint_array_wrapper_1 {
+  tint_array_wrapper_0 array[3];
+};
+struct tint_array_wrapper_2 {
+  tint_array_wrapper_1 array[2];
+};
+
+float f1(tint_array_wrapper_0 const a) {
+  return a.array[3];
+}
+
+float f2(tint_array_wrapper_1 const a) {
+  return a.array[2].array[3];
+}
+
+float f3(tint_array_wrapper_2 const a) {
+  return a.array[1].array[2].array[3];
+}
+
+kernel void tint_symbol() {
+  tint_array_wrapper_0 const a1 = {};
+  tint_array_wrapper_1 const a2 = {};
+  tint_array_wrapper_2 const a3 = {};
+  float const v1 = f1(a1);
+  float const v2 = f2(a2);
+  float const v3 = f3(a3);
+  return;
+}
+
diff --git a/test/array/function_parameter.wgsl.expected.spvasm b/test/array/function_parameter.wgsl.expected.spvasm
new file mode 100644
index 0000000..406d4b0
--- /dev/null
+++ b/test/array/function_parameter.wgsl.expected.spvasm
@@ -0,0 +1,67 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 41
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %f1 "f1"
+               OpName %a "a"
+               OpName %f2 "f2"
+               OpName %a_0 "a"
+               OpName %f3 "f3"
+               OpName %a_1 "a"
+               OpName %main "main"
+               OpDecorate %_arr_float_uint_4 ArrayStride 4
+               OpDecorate %_arr__arr_float_uint_4_uint_3 ArrayStride 16
+               OpDecorate %_arr__arr__arr_float_uint_4_uint_3_uint_2 ArrayStride 48
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_float_uint_4 = OpTypeArray %float %uint_4
+          %1 = OpTypeFunction %float %_arr_float_uint_4
+        %int = OpTypeInt 32 1
+      %int_3 = OpConstant %int 3
+     %uint_3 = OpConstant %uint 3
+%_arr__arr_float_uint_4_uint_3 = OpTypeArray %_arr_float_uint_4 %uint_3
+         %12 = OpTypeFunction %float %_arr__arr_float_uint_4_uint_3
+      %int_2 = OpConstant %int 2
+     %uint_2 = OpConstant %uint 2
+%_arr__arr__arr_float_uint_4_uint_3_uint_2 = OpTypeArray %_arr__arr_float_uint_4_uint_3 %uint_2
+         %21 = OpTypeFunction %float %_arr__arr__arr_float_uint_4_uint_3_uint_2
+      %int_1 = OpConstant %int 1
+       %void = OpTypeVoid
+         %31 = OpTypeFunction %void
+         %35 = OpConstantNull %_arr_float_uint_4
+         %36 = OpConstantNull %_arr__arr_float_uint_4_uint_3
+         %37 = OpConstantNull %_arr__arr__arr_float_uint_4_uint_3_uint_2
+         %f1 = OpFunction %float None %1
+          %a = OpFunctionParameter %_arr_float_uint_4
+          %8 = OpLabel
+         %11 = OpCompositeExtract %float %a 3
+               OpReturnValue %11
+               OpFunctionEnd
+         %f2 = OpFunction %float None %12
+        %a_0 = OpFunctionParameter %_arr__arr_float_uint_4_uint_3
+         %17 = OpLabel
+         %19 = OpCompositeExtract %_arr_float_uint_4 %a_0 2
+         %20 = OpCompositeExtract %float %19 3
+               OpReturnValue %20
+               OpFunctionEnd
+         %f3 = OpFunction %float None %21
+        %a_1 = OpFunctionParameter %_arr__arr__arr_float_uint_4_uint_3_uint_2
+         %26 = OpLabel
+         %28 = OpCompositeExtract %_arr__arr_float_uint_4_uint_3 %a_1 1
+         %29 = OpCompositeExtract %_arr_float_uint_4 %28 2
+         %30 = OpCompositeExtract %float %29 3
+               OpReturnValue %30
+               OpFunctionEnd
+       %main = OpFunction %void None %31
+         %34 = OpLabel
+         %38 = OpFunctionCall %float %f1 %35
+         %39 = OpFunctionCall %float %f2 %36
+         %40 = OpFunctionCall %float %f3 %37
+               OpReturn
+               OpFunctionEnd
diff --git a/test/array/function_parameter.wgsl.expected.wgsl b/test/array/function_parameter.wgsl.expected.wgsl
new file mode 100644
index 0000000..0b301e7
--- /dev/null
+++ b/test/array/function_parameter.wgsl.expected.wgsl
@@ -0,0 +1,21 @@
+fn f1(a : array<f32, 4>) -> f32 {
+  return a[3];
+}
+
+fn f2(a : array<array<f32, 4>, 3>) -> f32 {
+  return a[2][3];
+}
+
+fn f3(a : array<array<array<f32, 4>, 3>, 2>) -> f32 {
+  return a[1][2][3];
+}
+
+[[stage(compute)]]
+fn main() {
+  let a1 : array<f32, 4> = array<f32, 4>();
+  let a2 : array<array<f32, 4>, 3> = array<array<f32, 4>, 3>();
+  let a3 : array<array<array<f32, 4>, 3>, 2> = array<array<array<f32, 4>, 3>, 2>();
+  let v1 : f32 = f1(a1);
+  let v2 : f32 = f2(a2);
+  let v3 : f32 = f3(a3);
+}
diff --git a/test/array/function_return_type.wgsl b/test/array/function_return_type.wgsl
new file mode 100644
index 0000000..ba2a3a9
--- /dev/null
+++ b/test/array/function_return_type.wgsl
@@ -0,0 +1,18 @@
+fn f1() -> array<f32, 4> {
+  return array<f32, 4>();
+}
+
+fn f2() -> array<array<f32, 4>, 3> {
+  return array<array<f32, 4>, 3>(f1(), f1(), f1());
+}
+
+fn f3() -> array<array<array<f32, 4>, 3>, 2> {
+  return array<array<array<f32, 4>, 3>, 2>(f2(), f2());
+}
+
+[[stage(compute)]]
+fn main() {
+  let a1 : array<f32, 4> = f1();
+  let a2 : array<array<f32, 4>, 3> = f2();
+  let a3 : array<array<array<f32, 4>, 3>, 2> = f3();
+}
diff --git a/test/array/function_return_type.wgsl.expected.hlsl b/test/array/function_return_type.wgsl.expected.hlsl
new file mode 100644
index 0000000..7d6165e
--- /dev/null
+++ b/test/array/function_return_type.wgsl.expected.hlsl
@@ -0,0 +1 @@
+SKIP: crbug.com/tint/845
diff --git a/test/array/function_return_type.wgsl.expected.msl b/test/array/function_return_type.wgsl.expected.msl
new file mode 100644
index 0000000..709f410
--- /dev/null
+++ b/test/array/function_return_type.wgsl.expected.msl
@@ -0,0 +1,35 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_array_wrapper_0 {
+  float array[4];
+};
+struct tint_array_wrapper_1 {
+  tint_array_wrapper_0 array[3];
+};
+struct tint_array_wrapper_2 {
+  tint_array_wrapper_1 array[2];
+};
+
+tint_array_wrapper_0 f1() {
+  tint_array_wrapper_0 const tint_symbol_1 = {};
+  return tint_symbol_1;
+}
+
+tint_array_wrapper_1 f2() {
+  tint_array_wrapper_1 const tint_symbol_2 = {f1(), f1(), f1()};
+  return tint_symbol_2;
+}
+
+tint_array_wrapper_2 f3() {
+  tint_array_wrapper_2 const tint_symbol_3 = {f2(), f2()};
+  return tint_symbol_3;
+}
+
+kernel void tint_symbol() {
+  tint_array_wrapper_0 const a1 = f1();
+  tint_array_wrapper_1 const a2 = f2();
+  tint_array_wrapper_2 const a3 = f3();
+  return;
+}
+
diff --git a/test/array/function_return_type.wgsl.expected.spvasm b/test/array/function_return_type.wgsl.expected.spvasm
new file mode 100644
index 0000000..a1a6115
--- /dev/null
+++ b/test/array/function_return_type.wgsl.expected.spvasm
@@ -0,0 +1,56 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 33
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %f1 "f1"
+               OpName %f2 "f2"
+               OpName %f3 "f3"
+               OpName %main "main"
+               OpDecorate %_arr_float_uint_4 ArrayStride 4
+               OpDecorate %_arr__arr_float_uint_4_uint_3 ArrayStride 16
+               OpDecorate %_arr__arr__arr_float_uint_4_uint_3_uint_2 ArrayStride 48
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_float_uint_4 = OpTypeArray %float %uint_4
+          %1 = OpTypeFunction %_arr_float_uint_4
+          %8 = OpConstantNull %_arr_float_uint_4
+     %uint_3 = OpConstant %uint 3
+%_arr__arr_float_uint_4_uint_3 = OpTypeArray %_arr_float_uint_4 %uint_3
+          %9 = OpTypeFunction %_arr__arr_float_uint_4_uint_3
+     %uint_2 = OpConstant %uint 2
+%_arr__arr__arr_float_uint_4_uint_3_uint_2 = OpTypeArray %_arr__arr_float_uint_4_uint_3 %uint_2
+         %18 = OpTypeFunction %_arr__arr__arr_float_uint_4_uint_3_uint_2
+       %void = OpTypeVoid
+         %26 = OpTypeFunction %void
+         %f1 = OpFunction %_arr_float_uint_4 None %1
+          %7 = OpLabel
+               OpReturnValue %8
+               OpFunctionEnd
+         %f2 = OpFunction %_arr__arr_float_uint_4_uint_3 None %9
+         %13 = OpLabel
+         %14 = OpFunctionCall %_arr_float_uint_4 %f1
+         %15 = OpFunctionCall %_arr_float_uint_4 %f1
+         %16 = OpFunctionCall %_arr_float_uint_4 %f1
+         %17 = OpCompositeConstruct %_arr__arr_float_uint_4_uint_3 %14 %15 %16
+               OpReturnValue %17
+               OpFunctionEnd
+         %f3 = OpFunction %_arr__arr__arr_float_uint_4_uint_3_uint_2 None %18
+         %22 = OpLabel
+         %23 = OpFunctionCall %_arr__arr_float_uint_4_uint_3 %f2
+         %24 = OpFunctionCall %_arr__arr_float_uint_4_uint_3 %f2
+         %25 = OpCompositeConstruct %_arr__arr__arr_float_uint_4_uint_3_uint_2 %23 %24
+               OpReturnValue %25
+               OpFunctionEnd
+       %main = OpFunction %void None %26
+         %29 = OpLabel
+         %30 = OpFunctionCall %_arr_float_uint_4 %f1
+         %31 = OpFunctionCall %_arr__arr_float_uint_4_uint_3 %f2
+         %32 = OpFunctionCall %_arr__arr__arr_float_uint_4_uint_3_uint_2 %f3
+               OpReturn
+               OpFunctionEnd
diff --git a/test/array/function_return_type.wgsl.expected.wgsl b/test/array/function_return_type.wgsl.expected.wgsl
new file mode 100644
index 0000000..ba2a3a9
--- /dev/null
+++ b/test/array/function_return_type.wgsl.expected.wgsl
@@ -0,0 +1,18 @@
+fn f1() -> array<f32, 4> {
+  return array<f32, 4>();
+}
+
+fn f2() -> array<array<f32, 4>, 3> {
+  return array<array<f32, 4>, 3>(f1(), f1(), f1());
+}
+
+fn f3() -> array<array<array<f32, 4>, 3>, 2> {
+  return array<array<array<f32, 4>, 3>, 2>(f2(), f2());
+}
+
+[[stage(compute)]]
+fn main() {
+  let a1 : array<f32, 4> = f1();
+  let a2 : array<array<f32, 4>, 3> = f2();
+  let a3 : array<array<array<f32, 4>, 3>, 2> = f3();
+}
diff --git a/test/array/type_constructor.wgsl.expected.msl b/test/array/type_constructor.wgsl.expected.msl
index 77696be..791d6a6 100644
--- a/test/array/type_constructor.wgsl.expected.msl
+++ b/test/array/type_constructor.wgsl.expected.msl
@@ -1 +1,53 @@
-SKIP: crbug.com/tint/814
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_array_wrapper_0 {
+  int array[4];
+};
+struct tint_array_wrapper_1 {
+  tint_array_wrapper_0 array[3];
+};
+struct tint_array_wrapper_2 {
+  tint_array_wrapper_1 array[2];
+};
+struct tint_array_wrapper_3 {
+  tint_array_wrapper_0 array[2];
+};
+
+kernel void tint_symbol() {
+  int const x = 42;
+  tint_array_wrapper_0 const empty = {};
+  tint_array_wrapper_0 const nonempty = {1, 2, 3, 4};
+  tint_array_wrapper_0 const nonempty_with_expr = {1, x, (x + 1), nonempty.array[3]};
+  tint_array_wrapper_2 const nested_empty = {};
+  tint_array_wrapper_0 const tint_symbol_1 = {1, 2, 3, 4};
+  tint_array_wrapper_0 const tint_symbol_2 = {5, 6, 7, 8};
+  tint_array_wrapper_0 const tint_symbol_3 = {9, 10, 11, 12};
+  tint_array_wrapper_1 const tint_symbol_4 = {tint_symbol_1, tint_symbol_2, tint_symbol_3};
+  tint_array_wrapper_0 const tint_symbol_5 = {13, 14, 15, 16};
+  tint_array_wrapper_0 const tint_symbol_6 = {17, 18, 19, 20};
+  tint_array_wrapper_0 const tint_symbol_7 = {21, 22, 23, 24};
+  tint_array_wrapper_1 const tint_symbol_8 = {tint_symbol_5, tint_symbol_6, tint_symbol_7};
+  tint_array_wrapper_2 const nested_nonempty = {tint_symbol_4, tint_symbol_8};
+  tint_array_wrapper_0 const tint_symbol_9 = {1, 2, x, (x + 1)};
+  tint_array_wrapper_0 const tint_symbol_10 = {5, 6, nonempty.array[2], (nonempty.array[3] + 1)};
+  tint_array_wrapper_1 const tint_symbol_11 = {tint_symbol_9, tint_symbol_10, nonempty};
+  tint_array_wrapper_2 const nested_nonempty_with_expr = {tint_symbol_11, nested_nonempty.array[1]};
+  tint_array_wrapper_0 const tint_symbol_12 = {};
+  int const subexpr_empty = tint_symbol_12.array[1];
+  tint_array_wrapper_0 const tint_symbol_13 = {1, 2, 3, 4};
+  int const subexpr_nonempty = tint_symbol_13.array[2];
+  tint_array_wrapper_0 const tint_symbol_14 = {1, x, (x + 1), nonempty.array[3]};
+  int const subexpr_nonempty_with_expr = tint_symbol_14.array[2];
+  tint_array_wrapper_3 const tint_symbol_15 = {};
+  tint_array_wrapper_0 const subexpr_nested_empty = tint_symbol_15.array[1];
+  tint_array_wrapper_0 const tint_symbol_16 = {1, 2, 3, 4};
+  tint_array_wrapper_0 const tint_symbol_17 = {5, 6, 7, 8};
+  tint_array_wrapper_3 const tint_symbol_18 = {tint_symbol_16, tint_symbol_17};
+  tint_array_wrapper_0 const subexpr_nested_nonempty = tint_symbol_18.array[1];
+  tint_array_wrapper_0 const tint_symbol_19 = {1, x, (x + 1), nonempty.array[3]};
+  tint_array_wrapper_3 const tint_symbol_20 = {tint_symbol_19, nested_nonempty.array[1].array[2]};
+  tint_array_wrapper_0 const subexpr_nested_nonempty_with_expr = tint_symbol_20.array[1];
+  return;
+}
+
diff --git a/test/bug/tint/782.wgsl.expected.msl b/test/bug/tint/782.wgsl.expected.msl
index 638f5fd..9bec463 100644
--- a/test/bug/tint/782.wgsl.expected.msl
+++ b/test/bug/tint/782.wgsl.expected.msl
@@ -1,12 +1,13 @@
-SKIP: crbug.com/tint/814
-
 #include <metal_stdlib>
 
 using namespace metal;
+struct tint_array_wrapper_0 {
+  int array[2];
+};
 
 void foo() {
-  int tint_symbol[2] = {0};
-  int implict[2] = {0};
+  tint_array_wrapper_0 tint_symbol = {0};
+  tint_array_wrapper_0 implict = {0};
   implict = tint_symbol;
 }
 
diff --git a/test/struct/type_constructor.wgsl.expected.msl b/test/struct/type_constructor.wgsl.expected.msl
index 77696be..b80fe95 100644
--- a/test/struct/type_constructor.wgsl.expected.msl
+++ b/test/struct/type_constructor.wgsl.expected.msl
@@ -1 +1,67 @@
-SKIP: crbug.com/tint/814
+#include <metal_stdlib>
+
+using namespace metal;
+struct S1 {
+  int a;
+  int b;
+  int c;
+  int d;
+};
+struct S2 {
+  int e;
+  S1 f;
+};
+struct S3 {
+  int g;
+  S1 h;
+  S2 i;
+};
+struct tint_array_wrapper_0 {
+  int array[2];
+};
+struct T {
+  tint_array_wrapper_0 a;
+};
+struct tint_array_wrapper_1 {
+  T array[2];
+};
+
+kernel void tint_symbol() {
+  int const x = 42;
+  S1 const empty = {};
+  S1 const nonempty = {1, 2, 3, 4};
+  S1 const nonempty_with_expr = {1, x, (x + 1), nonempty.d};
+  S3 const nested_empty = {};
+  S1 const tint_symbol_1 = {2, 3, 4, 5};
+  S1 const tint_symbol_2 = {7, 8, 9, 10};
+  S2 const tint_symbol_3 = {6, tint_symbol_2};
+  S3 const nested_nonempty = {1, tint_symbol_1, tint_symbol_3};
+  S1 const tint_symbol_4 = {2, x, (x + 1), nested_nonempty.i.f.d};
+  S2 const tint_symbol_5 = {6, nonempty};
+  S3 const nested_nonempty_with_expr = {1, tint_symbol_4, tint_symbol_5};
+  S1 const tint_symbol_6 = {};
+  int const subexpr_empty = tint_symbol_6.a;
+  S1 const tint_symbol_7 = {1, 2, 3, 4};
+  int const subexpr_nonempty = tint_symbol_7.b;
+  S1 const tint_symbol_8 = {1, x, (x + 1), nonempty.d};
+  int const subexpr_nonempty_with_expr = tint_symbol_8.c;
+  S2 const tint_symbol_9 = {};
+  S1 const subexpr_nested_empty = tint_symbol_9.f;
+  S1 const tint_symbol_10 = {2, 3, 4, 5};
+  S2 const tint_symbol_11 = {1, tint_symbol_10};
+  S1 const subexpr_nested_nonempty = tint_symbol_11.f;
+  S1 const tint_symbol_12 = {2, x, (x + 1), nested_nonempty.i.f.d};
+  S2 const tint_symbol_13 = {1, tint_symbol_12};
+  S1 const subexpr_nested_nonempty_with_expr = tint_symbol_13.f;
+  tint_array_wrapper_1 const aosoa_empty = {};
+  tint_array_wrapper_0 const tint_symbol_14 = {1, 2};
+  T const tint_symbol_15 = {tint_symbol_14};
+  tint_array_wrapper_0 const tint_symbol_16 = {3, 4};
+  T const tint_symbol_17 = {tint_symbol_16};
+  tint_array_wrapper_1 const aosoa_nonempty = {tint_symbol_15, tint_symbol_17};
+  tint_array_wrapper_0 const tint_symbol_18 = {1, (aosoa_nonempty.array[0].a.array[0] + 1)};
+  T const tint_symbol_19 = {tint_symbol_18};
+  tint_array_wrapper_1 const aosoa_nonempty_with_expr = {tint_symbol_19, aosoa_nonempty.array[1]};
+  return;
+}
+
diff --git a/test/types/function_scope_declarations.wgsl.expected.msl b/test/types/function_scope_declarations.wgsl.expected.msl
index 8c0e766..92f688f 100644
--- a/test/types/function_scope_declarations.wgsl.expected.msl
+++ b/test/types/function_scope_declarations.wgsl.expected.msl
@@ -3,6 +3,9 @@
 using namespace metal;
 struct S {
 };
+struct tint_array_wrapper_0 {
+  float array[4];
+};
 
 kernel void tint_symbol() {
   bool bool_var = bool();
@@ -21,13 +24,13 @@
   float4 const v4f32_let = float4();
   float2x3 m2x3_var = float2x3();
   float3x4 const m3x4_let = float3x4();
-  float arr_var[4] = {};
-  float const arr_let[4] = {};
+  tint_array_wrapper_0 arr_var = {};
+  tint_array_wrapper_0 const arr_let = {};
   S struct_var = {};
   S const struct_let = {};
   thread float* const ptr_f32 = &(f32_var);
   thread float4* const ptr_vec = &(v4f32_var);
-  thread float (*const ptr_arr)[4] = &(arr_var);
+  thread tint_array_wrapper_0* const ptr_arr = &(arr_var);
   return;
 }
 
diff --git a/test/types/module_scope_let.wgsl.expected.msl b/test/types/module_scope_let.wgsl.expected.msl
index e3a2d4e..af6f441 100644
--- a/test/types/module_scope_let.wgsl.expected.msl
+++ b/test/types/module_scope_let.wgsl.expected.msl
@@ -3,6 +3,9 @@
 using namespace metal;
 struct S {
 };
+struct tint_array_wrapper_0 {
+  float array[4];
+};
 
 constant bool bool_let = bool();
 constant int i32_let = int();
@@ -12,7 +15,7 @@
 constant uint3 v3u32_let = uint3();
 constant float4 v4f32_let = float4();
 constant float3x4 m3x4_let = float3x4();
-constant float arr_let[4] = {};
+constant tint_array_wrapper_0 arr_let = {};
 constant S struct_let = {};
 kernel void tint_symbol() {
   return;
diff --git a/test/types/module_scope_var.wgsl.expected.msl b/test/types/module_scope_var.wgsl.expected.msl
index 77696be..b857fe8 100644
--- a/test/types/module_scope_var.wgsl.expected.msl
+++ b/test/types/module_scope_var.wgsl.expected.msl
@@ -1 +1,45 @@
-SKIP: crbug.com/tint/814
+#include <metal_stdlib>
+
+using namespace metal;
+struct S {
+};
+struct tint_array_wrapper_0 {
+  float array[4];
+};
+
+kernel void tint_symbol() {
+  thread bool tint_symbol_4 = false;
+  thread bool* const tint_symbol_3 = &(tint_symbol_4);
+  thread int tint_symbol_6 = 0;
+  thread int* const tint_symbol_5 = &(tint_symbol_6);
+  thread uint tint_symbol_8 = 0u;
+  thread uint* const tint_symbol_7 = &(tint_symbol_8);
+  thread float tint_symbol_10 = 0.0f;
+  thread float* const tint_symbol_9 = &(tint_symbol_10);
+  thread int2 tint_symbol_12 = 0;
+  thread int2* const tint_symbol_11 = &(tint_symbol_12);
+  thread uint3 tint_symbol_14 = 0u;
+  thread uint3* const tint_symbol_13 = &(tint_symbol_14);
+  thread float4 tint_symbol_16 = 0.0f;
+  thread float4* const tint_symbol_15 = &(tint_symbol_16);
+  thread float2x3 tint_symbol_18 = float2x3(0.0f);
+  thread float2x3* const tint_symbol_17 = &(tint_symbol_18);
+  thread tint_array_wrapper_0 tint_symbol_20 = {0.0f};
+  thread tint_array_wrapper_0* const tint_symbol_19 = &(tint_symbol_20);
+  thread S tint_symbol_22 = {};
+  thread S* const tint_symbol_21 = &(tint_symbol_22);
+  *(tint_symbol_3) = bool();
+  *(tint_symbol_5) = int();
+  *(tint_symbol_7) = uint();
+  *(tint_symbol_9) = float();
+  *(tint_symbol_11) = int2();
+  *(tint_symbol_13) = uint3();
+  *(tint_symbol_15) = float4();
+  *(tint_symbol_17) = float2x3();
+  tint_array_wrapper_0 const tint_symbol_1 = {};
+  *(tint_symbol_19) = tint_symbol_1;
+  S const tint_symbol_2 = {};
+  *(tint_symbol_21) = tint_symbol_2;
+  return;
+}
+
diff --git a/test/types/module_scope_var_initializers.wgsl.expected.msl b/test/types/module_scope_var_initializers.wgsl.expected.msl
index 77696be..af7d470 100644
--- a/test/types/module_scope_var_initializers.wgsl.expected.msl
+++ b/test/types/module_scope_var_initializers.wgsl.expected.msl
@@ -1 +1,45 @@
-SKIP: crbug.com/tint/814
+#include <metal_stdlib>
+
+using namespace metal;
+struct S {
+};
+struct tint_array_wrapper_0 {
+  float array[4];
+};
+
+kernel void tint_symbol() {
+  thread bool tint_symbol_4 = bool();
+  thread bool* const tint_symbol_3 = &(tint_symbol_4);
+  thread int tint_symbol_6 = int();
+  thread int* const tint_symbol_5 = &(tint_symbol_6);
+  thread uint tint_symbol_8 = uint();
+  thread uint* const tint_symbol_7 = &(tint_symbol_8);
+  thread float tint_symbol_10 = float();
+  thread float* const tint_symbol_9 = &(tint_symbol_10);
+  thread int2 tint_symbol_12 = int2();
+  thread int2* const tint_symbol_11 = &(tint_symbol_12);
+  thread uint3 tint_symbol_14 = uint3();
+  thread uint3* const tint_symbol_13 = &(tint_symbol_14);
+  thread float4 tint_symbol_16 = float4();
+  thread float4* const tint_symbol_15 = &(tint_symbol_16);
+  thread float2x3 tint_symbol_18 = float2x3();
+  thread float2x3* const tint_symbol_17 = &(tint_symbol_18);
+  thread tint_array_wrapper_0 tint_symbol_20 = {};
+  thread tint_array_wrapper_0* const tint_symbol_19 = &(tint_symbol_20);
+  thread S tint_symbol_22 = {};
+  thread S* const tint_symbol_21 = &(tint_symbol_22);
+  *(tint_symbol_3) = bool();
+  *(tint_symbol_5) = int();
+  *(tint_symbol_7) = uint();
+  *(tint_symbol_9) = float();
+  *(tint_symbol_11) = int2();
+  *(tint_symbol_13) = uint3();
+  *(tint_symbol_15) = float4();
+  *(tint_symbol_17) = float2x3();
+  tint_array_wrapper_0 const tint_symbol_1 = {};
+  *(tint_symbol_19) = tint_symbol_1;
+  S const tint_symbol_2 = {};
+  *(tint_symbol_21) = tint_symbol_2;
+  return;
+}
+
diff --git a/test/types/parameters.wgsl.expected.msl b/test/types/parameters.wgsl.expected.msl
index 81ca7b3..3eab2c5 100644
--- a/test/types/parameters.wgsl.expected.msl
+++ b/test/types/parameters.wgsl.expected.msl
@@ -3,8 +3,11 @@
 using namespace metal;
 struct S {
 };
+struct tint_array_wrapper_0 {
+  float array[4];
+};
 
-void foo(bool param_bool, int param_i32, uint param_u32, float param_f32, int2 param_v2i32, uint3 param_v3u32, float4 param_v4f32, float2x3 param_m2x3, float const param_arr[4], S param_struct, thread float* const param_ptr_f32, thread float4* const param_ptr_vec, thread float (*const param_ptr_arr)[4]) {
+void foo(bool param_bool, int param_i32, uint param_u32, float param_f32, int2 param_v2i32, uint3 param_v3u32, float4 param_v4f32, float2x3 param_m2x3, tint_array_wrapper_0 const param_arr, S param_struct, thread float* const param_ptr_f32, thread float4* const param_ptr_vec, thread tint_array_wrapper_0* const param_ptr_arr) {
 }
 
 kernel void tint_symbol() {
diff --git a/test/types/return_types.wgsl.expected.hlsl b/test/types/return_types.wgsl.expected.hlsl
index 9c9d96e..933d5ab 100644
--- a/test/types/return_types.wgsl.expected.hlsl
+++ b/test/types/return_types.wgsl.expected.hlsl
@@ -1 +1 @@
-SKIP: crbug.com/tint/820: arrays as function return types
+SKIP: crbug.com/tint/848: arrays as function return types
diff --git a/test/types/return_types.wgsl.expected.msl b/test/types/return_types.wgsl.expected.msl
index 9c9d96e..e196054 100644
--- a/test/types/return_types.wgsl.expected.msl
+++ b/test/types/return_types.wgsl.expected.msl
@@ -1 +1,55 @@
-SKIP: crbug.com/tint/820: arrays as function return types
+#include <metal_stdlib>
+
+using namespace metal;
+struct S {
+};
+struct tint_array_wrapper_0 {
+  float array[4];
+};
+
+bool ret_bool() {
+  return bool();
+}
+
+int ret_i32() {
+  return int();
+}
+
+uint ret_u32() {
+  return uint();
+}
+
+float ret_f32() {
+  return float();
+}
+
+int2 ret_v2i32() {
+  return int2();
+}
+
+uint3 ret_v3u32() {
+  return uint3();
+}
+
+float4 ret_v4f32() {
+  return float4();
+}
+
+float2x3 ret_m2x3() {
+  return float2x3();
+}
+
+tint_array_wrapper_0 ret_arr() {
+  tint_array_wrapper_0 const tint_symbol_1 = {};
+  return tint_symbol_1;
+}
+
+S ret_struct() {
+  S const tint_symbol_2 = {};
+  return tint_symbol_2;
+}
+
+kernel void tint_symbol() {
+  return;
+}
+
diff --git a/test/types/struct_members.wgsl.expected.msl b/test/types/struct_members.wgsl.expected.msl
index 8f7c579..74bf77e 100644
--- a/test/types/struct_members.wgsl.expected.msl
+++ b/test/types/struct_members.wgsl.expected.msl
@@ -3,6 +3,9 @@
 using namespace metal;
 struct S_inner {
 };
+struct tint_array_wrapper_0 {
+  float array[4];
+};
 struct S {
   bool member_bool;
   int member_i32;
@@ -12,7 +15,7 @@
   uint3 member_v3u32;
   float4 member_v4f32;
   float2x3 member_m2x3;
-  float member_arr[4];
+  tint_array_wrapper_0 member_arr;
   S_inner member_struct;
 };