Tint: Support `clip_distances` on MSL

This patch implements the translation of `clip_distances` on MSL.
1. The struct member `clip_distances` will be translated into a
   C-style array.
2. The struct member `clip_distances` will be assigned once per
   array index.

Bug: chromium:358408571
Change-Id: I5ecd77b4827815fba830fd0bed39ecd17babb9ed
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/203156
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
index 91d4237..39aa061 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
@@ -291,6 +291,7 @@
                 wgsl::Extension::kChromiumExperimentalSubgroups,
                 wgsl::Extension::kChromiumInternalGraphite,
                 wgsl::Extension::kChromiumInternalRelaxedUniformLayout,
+                wgsl::Extension::kClipDistances,
                 wgsl::Extension::kF16,
                 wgsl::Extension::kDualSourceBlending,
                 wgsl::Extension::kSubgroups,
@@ -2994,23 +2995,40 @@
             add_byte_offset_comment(out, msl_offset);
         }
 
+        auto* ty = mem->Type();
+
+        // Emit attributes
+        auto& attributes = mem->Attributes();
+        std::string builtin_value_name;
+        if (auto builtin = attributes.builtin) {
+            builtin_value_name = BuiltinToAttribute(builtin.value());
+            if (builtin_value_name.empty()) {
+                diagnostics_.AddError(Source{}) << "unknown builtin";
+                return false;
+            }
+
+            // Emit `[[clip_distance]]` as a C-style f32 array
+            if (builtin == core::BuiltinValue::kClipDistances) {
+                const auto* arrayType = mem->Type()->As<core::type::Array>();
+                if (TINT_UNLIKELY(arrayType == nullptr ||
+                                  !arrayType->ConstantCount().has_value())) {
+                    TINT_ICE() << "The type of `clip_distances` is not a sized array";
+                } else {
+                    out << "float " << mem_name << " [[" << builtin_value_name << "]] ["
+                        << *arrayType->ConstantCount() << "];";
+                }
+                continue;
+            }
+        }
+
         if (!EmitType(out, mem->Type())) {
             return false;
         }
 
-        auto* ty = mem->Type();
-
         out << " " << mem_name;
-        // Emit attributes
-        auto& attributes = mem->Attributes();
 
-        if (auto builtin = attributes.builtin) {
-            auto name = BuiltinToAttribute(builtin.value());
-            if (name.empty()) {
-                diagnostics_.AddError(Source{}) << "unknown builtin";
-                return false;
-            }
-            out << " [[" << name << "]]";
+        if (!builtin_value_name.empty()) {
+            out << " [[" << builtin_value_name << "]]";
         }
 
         if (auto location = attributes.location) {
diff --git a/src/tint/lang/msl/writer/common/printer_support.cc b/src/tint/lang/msl/writer/common/printer_support.cc
index d3dbba0..ce92042 100644
--- a/src/tint/lang/msl/writer/common/printer_support.cc
+++ b/src/tint/lang/msl/writer/common/printer_support.cc
@@ -77,6 +77,8 @@
             return "thread_index_in_simdgroup";
         case core::BuiltinValue::kSubgroupSize:
             return "threads_per_simdgroup";
+        case core::BuiltinValue::kClipDistances:
+            return "clip_distance";
         default:
             break;
     }
diff --git a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
index c7847e8..eec3c26 100644
--- a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
@@ -165,6 +165,8 @@
     Hashmap<const BuiltinAttribute*, core::BuiltinValue, 16> builtin_attrs;
     /// A map of builtin values to HLSL wave intrinsic functions.
     Hashmap<core::BuiltinValue, Symbol, 2> wave_intrinsics;
+    /// The array length of the struct member with builtin attribute `clip_distances`.
+    uint32_t clip_distances_size = 0;
 
     /// Constructor
     /// @param context the clone context
@@ -407,6 +409,17 @@
                                      core::InterpolationSampling::kUndefined));
         }
 
+        if (cfg.shader_style == ShaderStyle::kMsl &&
+            func_ast->PipelineStage() == PipelineStage::kVertex &&
+            builtin_attr == core::BuiltinValue::kClipDistances) {
+            const auto* arrayType = type->As<core::type::Array>();
+            if (TINT_UNLIKELY(arrayType == nullptr || !arrayType->ConstantCount().has_value())) {
+                TINT_ICE() << "The type of `clip_distances` is not a sized array";
+            } else {
+                clip_distances_size = *arrayType->ConstantCount();
+            }
+        }
+
         // In GLSL, if it's a builtin, override the name with the
         // corresponding gl_ builtin name
         if (cfg.shader_style == ShaderStyle::kGlsl) {
@@ -682,13 +695,26 @@
             }
             member_names.insert(name.Name());
 
+            auto* builtin_attribute = GetAttribute<BuiltinAttribute>(outval.attributes);
+            if (cfg.shader_style == ShaderStyle::kMsl && builtin_attribute != nullptr &&
+                builtin_attribute->builtin == core::BuiltinValue::kClipDistances) {
+                Symbol clip_distances_inner_array = b.Symbols().New("tmp_inner_clip_distances");
+                assignments.Push(b.Decl(b.Let(clip_distances_inner_array, outval.value)));
+                for (uint32_t i = 0; i < clip_distances_size; ++i) {
+                    assignments.Push(
+                        b.Assign(b.IndexAccessor(b.MemberAccessor(wrapper_result, name), u32(i)),
+                                 b.IndexAccessor(clip_distances_inner_array, u32(i))));
+                }
+            } else {
+                assignments.Push(b.Assign(b.MemberAccessor(wrapper_result, name), outval.value));
+            }
+
             wrapper_struct_output_members.Push({
                 /* member */ b.Member(name, outval.type, std::move(outval.attributes)),
                 /* location */ outval.location,
                 /* blend_src */ outval.blend_src,
                 /* color */ std::nullopt,
             });
-            assignments.Push(b.Assign(b.MemberAccessor(wrapper_result, name), outval.value));
         }
 
         // Sort the struct members to satisfy HLSL interfacing matching rules.
diff --git a/test/tint/extensions/clip_distances/clip_distances_size_1.wgsl.expected.msl b/test/tint/extensions/clip_distances/clip_distances_size_1.wgsl.expected.msl
index 557ef30..fd5ca23 100644
--- a/test/tint/extensions/clip_distances/clip_distances_size_1.wgsl.expected.msl
+++ b/test/tint/extensions/clip_distances/clip_distances_size_1.wgsl.expected.msl
@@ -1,21 +1,40 @@
-SKIP: FAILED
+#include <metal_stdlib>
 
+using namespace metal;
 
-enable clip_distances;
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
 
 struct VertexOutputs {
-  @builtin(position)
-  position : vec4<f32>,
-  @builtin(clip_distances)
-  clipDistance : array<f32, 1>,
+  float4 position;
+  tint_array<float, 1> clipDistance;
+};
+
+struct tint_symbol_1 {
+  float4 position [[position]];
+  float clipDistance [[clip_distance]] [1];
+};
+
+VertexOutputs tint_symbol_inner() {
+  VertexOutputs const tint_symbol_2 = VertexOutputs{.position=float4(1.0f, 2.0f, 3.0f, 4.0f), .clipDistance=tint_array<float, 1>{}};
+  return tint_symbol_2;
 }
 
-@vertex
-fn tint_symbol() -> VertexOutputs {
-  return VertexOutputs(vec4<f32>(1.0, 2.0, 3.0, 4.0), array<f32, 1>(0.0));
+vertex tint_symbol_1 tint_symbol() {
+  VertexOutputs const inner_result = tint_symbol_inner();
+  tint_symbol_1 wrapper_result = {};
+  wrapper_result.position = inner_result.position;
+  tint_array<float, 1> const tmp_inner_clip_distances = inner_result.clipDistance;
+  wrapper_result.clipDistance[0u] = tmp_inner_clip_distances[0u];
+  return wrapper_result;
 }
 
-Failed to generate: <dawn>/test/tint/extensions/clip_distances/clip_distances_size_1.wgsl:1:8 error: MSL backend does not support extension 'clip_distances'
-enable clip_distances;
-       ^^^^^^^^^^^^^^
-
diff --git a/test/tint/extensions/clip_distances/clip_distances_size_4.wgsl.expected.msl b/test/tint/extensions/clip_distances/clip_distances_size_4.wgsl.expected.msl
index 668fc1f..8f88332 100644
--- a/test/tint/extensions/clip_distances/clip_distances_size_4.wgsl.expected.msl
+++ b/test/tint/extensions/clip_distances/clip_distances_size_4.wgsl.expected.msl
@@ -1,21 +1,43 @@
-SKIP: FAILED
+#include <metal_stdlib>
 
+using namespace metal;
 
-enable clip_distances;
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
 
 struct VertexOutputs {
-  @builtin(position)
-  position : vec4<f32>,
-  @builtin(clip_distances)
-  clipDistance : array<f32, 4>,
+  float4 position;
+  tint_array<float, 4> clipDistance;
+};
+
+struct tint_symbol_1 {
+  float4 position [[position]];
+  float clipDistance [[clip_distance]] [4];
+};
+
+VertexOutputs tint_symbol_inner() {
+  VertexOutputs const tint_symbol_2 = VertexOutputs{.position=float4(1.0f, 2.0f, 3.0f, 4.0f), .clipDistance=tint_array<float, 4>{}};
+  return tint_symbol_2;
 }
 
-@vertex
-fn tint_symbol() -> VertexOutputs {
-  return VertexOutputs(vec4<f32>(1.0, 2.0, 3.0, 4.0), array<f32, 4>(0.0, 0.0, 0.0, 0.0));
+vertex tint_symbol_1 tint_symbol() {
+  VertexOutputs const inner_result = tint_symbol_inner();
+  tint_symbol_1 wrapper_result = {};
+  wrapper_result.position = inner_result.position;
+  tint_array<float, 4> const tmp_inner_clip_distances = inner_result.clipDistance;
+  wrapper_result.clipDistance[0u] = tmp_inner_clip_distances[0u];
+  wrapper_result.clipDistance[1u] = tmp_inner_clip_distances[1u];
+  wrapper_result.clipDistance[2u] = tmp_inner_clip_distances[2u];
+  wrapper_result.clipDistance[3u] = tmp_inner_clip_distances[3u];
+  return wrapper_result;
 }
 
-Failed to generate: <dawn>/test/tint/extensions/clip_distances/clip_distances_size_4.wgsl:1:8 error: MSL backend does not support extension 'clip_distances'
-enable clip_distances;
-       ^^^^^^^^^^^^^^
-
diff --git a/test/tint/extensions/clip_distances/clip_distances_size_8.wgsl.expected.msl b/test/tint/extensions/clip_distances/clip_distances_size_8.wgsl.expected.msl
index 2592985..952455e 100644
--- a/test/tint/extensions/clip_distances/clip_distances_size_8.wgsl.expected.msl
+++ b/test/tint/extensions/clip_distances/clip_distances_size_8.wgsl.expected.msl
@@ -1,21 +1,47 @@
-SKIP: FAILED
+#include <metal_stdlib>
 
+using namespace metal;
 
-enable clip_distances;
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
 
 struct VertexOutputs {
-  @builtin(position)
-  position : vec4<f32>,
-  @builtin(clip_distances)
-  clipDistance : array<f32, 8>,
+  float4 position;
+  tint_array<float, 8> clipDistance;
+};
+
+struct tint_symbol_1 {
+  float4 position [[position]];
+  float clipDistance [[clip_distance]] [8];
+};
+
+VertexOutputs tint_symbol_inner() {
+  VertexOutputs const tint_symbol_2 = VertexOutputs{.position=float4(1.0f, 2.0f, 3.0f, 4.0f), .clipDistance=tint_array<float, 8>{}};
+  return tint_symbol_2;
 }
 
-@vertex
-fn tint_symbol() -> VertexOutputs {
-  return VertexOutputs(vec4<f32>(1.0, 2.0, 3.0, 4.0), array<f32, 8>(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+vertex tint_symbol_1 tint_symbol() {
+  VertexOutputs const inner_result = tint_symbol_inner();
+  tint_symbol_1 wrapper_result = {};
+  wrapper_result.position = inner_result.position;
+  tint_array<float, 8> const tmp_inner_clip_distances = inner_result.clipDistance;
+  wrapper_result.clipDistance[0u] = tmp_inner_clip_distances[0u];
+  wrapper_result.clipDistance[1u] = tmp_inner_clip_distances[1u];
+  wrapper_result.clipDistance[2u] = tmp_inner_clip_distances[2u];
+  wrapper_result.clipDistance[3u] = tmp_inner_clip_distances[3u];
+  wrapper_result.clipDistance[4u] = tmp_inner_clip_distances[4u];
+  wrapper_result.clipDistance[5u] = tmp_inner_clip_distances[5u];
+  wrapper_result.clipDistance[6u] = tmp_inner_clip_distances[6u];
+  wrapper_result.clipDistance[7u] = tmp_inner_clip_distances[7u];
+  return wrapper_result;
 }
 
-Failed to generate: <dawn>/test/tint/extensions/clip_distances/clip_distances_size_8.wgsl:1:8 error: MSL backend does not support extension 'clip_distances'
-enable clip_distances;
-       ^^^^^^^^^^^^^^
-