Support ClipDistances in SPIR-V reader with Tint AST - Part I

This patch adds the support of parsing `spv::BuiltIn::ClipDistance`
into the built-in attribute `@builtin(clip_distances)` in the SPIR-V
reader with Tint AST.

Note that this CL doesn't implement parsing `ClipDistance` in
`gl_PerVertex` struct, which will be implemented in the next CL.

Bug: chromium:358408571
Test: tint_unittests
Change-Id: Ieba8e810789357ebd4ed15dcdba390ed44c663ba
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/210138
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc b/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
index 58c9ef9..de9615a 100644
--- a/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
@@ -1156,7 +1156,7 @@
                         builtin_position_.pointsize_member_index = member_index;
                         create_ast_member = false;  // Not part of the WGSL structure.
                         break;
-                    case spv::BuiltIn::ClipDistance:  // not supported in WGSL
+                    case spv::BuiltIn::ClipDistance:
                     case spv::BuiltIn::CullDistance:  // not supported in WGSL
                         create_ast_member = false;    // Not part of the WGSL structure.
                         break;
@@ -1555,6 +1555,7 @@
     }
 
     // Emit gl_Position instead of gl_PerVertex
+    // TODO(chromium:358408571): handle gl_ClipDistance[] in gl_PerVertex
     if (builtin_position_.per_vertex_var_id) {
         // Make sure the variable has a name.
         namer_.SuggestSanitizedName(builtin_position_.per_vertex_var_id, "gl_Position");
@@ -1737,6 +1738,9 @@
                     }
                     break;
                 }
+                case spv::BuiltIn::ClipDistance:
+                    Enable(wgsl::Extension::kClipDistances);
+                    break;
                 default:
                     break;
             }
diff --git a/src/tint/lang/spirv/reader/ast_parser/ast_parser_test.cc b/src/tint/lang/spirv/reader/ast_parser/ast_parser_test.cc
index 3aaf1f9..54548ef 100644
--- a/src/tint/lang/spirv/reader/ast_parser/ast_parser_test.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/ast_parser_test.cc
@@ -264,7 +264,7 @@
 
 TEST_F(SpirvASTParserTest, BlendSrc) {
     auto spv = test::Assemble(R"(
-OpCapability Shader
+               OpCapability Shader
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %frag_main "frag_main" %frag_main_loc0_idx0_Output %frag_main_loc0_idx1_Output
                OpExecutionMode %frag_main OriginUpperLeft
@@ -372,5 +372,205 @@
 )");
 }
 
+TEST_F(SpirvASTParserTest, ClipDistances_ArraySize_1) {
+    auto spv = test::Assemble(R"(
+               OpCapability Shader
+               OpCapability ClipDistance
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %main_position_Output %main_clip_distances_Output %main___point_size_Output
+               OpName %main_position_Output "main_position_Output"
+               OpName %main_clip_distances_Output "main_clip_distances_Output"
+               OpName %main___point_size_Output "main___point_size_Output"
+               OpName %main_inner "main_inner"
+               OpMemberName %VertexOutputs 0 "position"
+               OpMemberName %VertexOutputs 1 "clipDistance"
+               OpName %VertexOutputs "VertexOutputs"
+               OpName %main "main"
+               OpDecorate %main_position_Output BuiltIn Position
+               OpDecorate %_arr_float_uint_1 ArrayStride 4
+               OpDecorate %main_clip_distances_Output BuiltIn ClipDistance
+               OpDecorate %main___point_size_Output BuiltIn PointSize
+               OpMemberDecorate %VertexOutputs 0 Offset 0
+               OpMemberDecorate %VertexOutputs 1 Offset 16
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%main_position_Output = OpVariable %_ptr_Output_v4float Output
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%_ptr_Output__arr_float_uint_1 = OpTypePointer Output %_arr_float_uint_1
+%main_clip_distances_Output = OpVariable %_ptr_Output__arr_float_uint_1 Output
+%_ptr_Output_float = OpTypePointer Output %float
+%main___point_size_Output = OpVariable %_ptr_Output_float Output
+%VertexOutputs = OpTypeStruct %v4float %_arr_float_uint_1
+         %14 = OpTypeFunction %VertexOutputs
+         %16 = OpConstantNull %VertexOutputs
+       %void = OpTypeVoid
+         %19 = OpTypeFunction %void
+    %float_1 = OpConstant %float 1
+ %main_inner = OpFunction %VertexOutputs None %14
+         %15 = OpLabel
+               OpReturnValue %16
+               OpFunctionEnd
+       %main = OpFunction %void None %19
+         %20 = OpLabel
+         %21 = OpFunctionCall %VertexOutputs %main_inner
+         %22 = OpCompositeExtract %v4float %21 0
+               OpStore %main_position_Output %22 None
+         %23 = OpCompositeExtract %_arr_float_uint_1 %21 1
+               OpStore %main_clip_distances_Output %23 None
+               OpStore %main___point_size_Output %float_1 None
+               OpReturn
+               OpFunctionEnd
+)");
+    auto program = Parse(spv, {});
+    auto errs = program.Diagnostics().Str();
+    EXPECT_TRUE(program.IsValid()) << errs;
+    EXPECT_EQ(program.Diagnostics().Count(), 0u) << errs;
+    auto result = wgsl::writer::Generate(program, {});
+    EXPECT_EQ(result, Success);
+    EXPECT_EQ("\n" + result->wgsl, R"(
+enable clip_distances;
+
+alias Arr = array<f32, 1u>;
+
+struct VertexOutputs {
+  /* @offset(0) */
+  position : vec4f,
+  /* @offset(16) */
+  clipDistance : Arr,
+}
+
+var<private> main_position_Output : vec4f;
+
+var<private> main_clip_distances_Output : Arr;
+
+fn main_inner() -> VertexOutputs {
+  return VertexOutputs(vec4f(), array<f32, 1u>());
+}
+
+fn main_1() {
+  let x_21 = main_inner();
+  main_position_Output = x_21.position;
+  main_clip_distances_Output = x_21.clipDistance;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  main_position_Output_1 : vec4f,
+  @builtin(clip_distances)
+  main_clip_distances_Output_1 : Arr,
+}
+
+@vertex
+fn main() -> main_out {
+  main_1();
+  return main_out(main_position_Output, main_clip_distances_Output);
+}
+)");
+}
+
+TEST_F(SpirvASTParserTest, ClipDistances_ArraySize_4) {
+    auto spv = test::Assemble(R"(
+               OpCapability Shader
+               OpCapability ClipDistance
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %main_position_Output %main_clip_distances_Output %main___point_size_Output
+               OpName %main_position_Output "main_position_Output"
+               OpName %main_clip_distances_Output "main_clip_distances_Output"
+               OpName %main___point_size_Output "main___point_size_Output"
+               OpName %main_inner "main_inner"
+               OpMemberName %VertexOutputs 0 "position"
+               OpMemberName %VertexOutputs 1 "clipDistance"
+               OpName %VertexOutputs "VertexOutputs"
+               OpName %main "main"
+               OpDecorate %main_position_Output BuiltIn Position
+               OpDecorate %_arr_float_uint_1 ArrayStride 4
+               OpDecorate %main_clip_distances_Output BuiltIn ClipDistance
+               OpDecorate %main___point_size_Output BuiltIn PointSize
+               OpMemberDecorate %VertexOutputs 0 Offset 0
+               OpMemberDecorate %VertexOutputs 1 Offset 16
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%main_position_Output = OpVariable %_ptr_Output_v4float Output
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 4
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%_ptr_Output__arr_float_uint_1 = OpTypePointer Output %_arr_float_uint_1
+%main_clip_distances_Output = OpVariable %_ptr_Output__arr_float_uint_1 Output
+%_ptr_Output_float = OpTypePointer Output %float
+%main___point_size_Output = OpVariable %_ptr_Output_float Output
+%VertexOutputs = OpTypeStruct %v4float %_arr_float_uint_1
+         %14 = OpTypeFunction %VertexOutputs
+         %16 = OpConstantNull %VertexOutputs
+       %void = OpTypeVoid
+         %19 = OpTypeFunction %void
+    %float_1 = OpConstant %float 1
+ %main_inner = OpFunction %VertexOutputs None %14
+         %15 = OpLabel
+               OpReturnValue %16
+               OpFunctionEnd
+       %main = OpFunction %void None %19
+         %20 = OpLabel
+         %21 = OpFunctionCall %VertexOutputs %main_inner
+         %22 = OpCompositeExtract %v4float %21 0
+               OpStore %main_position_Output %22 None
+         %23 = OpCompositeExtract %_arr_float_uint_1 %21 1
+               OpStore %main_clip_distances_Output %23 None
+               OpStore %main___point_size_Output %float_1 None
+               OpReturn
+               OpFunctionEnd
+)");
+    auto program = Parse(spv, {});
+    auto errs = program.Diagnostics().Str();
+    EXPECT_TRUE(program.IsValid()) << errs;
+    EXPECT_EQ(program.Diagnostics().Count(), 0u) << errs;
+    auto result = wgsl::writer::Generate(program, {});
+    EXPECT_EQ(result, Success);
+    EXPECT_EQ("\n" + result->wgsl, R"(
+enable clip_distances;
+
+alias Arr = array<f32, 4u>;
+
+struct VertexOutputs {
+  /* @offset(0) */
+  position : vec4f,
+  /* @offset(16) */
+  clipDistance : Arr,
+}
+
+var<private> main_position_Output : vec4f;
+
+var<private> main_clip_distances_Output : Arr;
+
+fn main_inner() -> VertexOutputs {
+  return VertexOutputs(vec4f(), array<f32, 4u>());
+}
+
+fn main_1() {
+  let x_21 = main_inner();
+  main_position_Output = x_21.position;
+  main_clip_distances_Output = x_21.clipDistance;
+  return;
+}
+
+struct main_out {
+  @builtin(position)
+  main_position_Output_1 : vec4f,
+  @builtin(clip_distances)
+  main_clip_distances_Output_1 : Arr,
+}
+
+@vertex
+fn main() -> main_out {
+  main_1();
+  return main_out(main_position_Output, main_clip_distances_Output);
+}
+)");
+}
+
 }  // namespace
 }  // namespace tint::spirv::reader::ast_parser
diff --git a/src/tint/lang/spirv/reader/ast_parser/enum_converter.cc b/src/tint/lang/spirv/reader/ast_parser/enum_converter.cc
index b8c805b..5f6083b 100644
--- a/src/tint/lang/spirv/reader/ast_parser/enum_converter.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/enum_converter.cc
@@ -105,6 +105,8 @@
             return core::BuiltinValue::kSampleIndex;
         case spv::BuiltIn::SampleMask:
             return core::BuiltinValue::kSampleMask;
+        case spv::BuiltIn::ClipDistance:
+            return core::BuiltinValue::kClipDistances;
         default:
             break;
     }
diff --git a/src/tint/lang/spirv/reader/ast_parser/function.cc b/src/tint/lang/spirv/reader/ast_parser/function.cc
index 9eaea1e..6ce6314 100644
--- a/src/tint/lang/spirv/reader/ast_parser/function.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/function.cc
@@ -1163,6 +1163,19 @@
             if (array_type->size == 0) {
                 return Fail() << "runtime-size array not allowed on pipeline IO";
             }
+
+            const ast::BuiltinAttribute* builtin_attribute = attrs.Get<ast::BuiltinAttribute>();
+            if (builtin_attribute != nullptr &&
+                builtin_attribute->builtin == core::BuiltinValue::kClipDistances) {
+                const Type* member_type = forced_member_type;
+                const auto member_name = namer_.MakeDerivedName(var_name);
+                return_members.Push(
+                    builder_.Member(member_name, member_type->Build(builder_), attrs.list));
+                const ast::Expression* load_source = builder_.Expr(var_name);
+                return_exprs.Push(load_source);
+                return success();
+            }
+
             index_prefix.Push(0);
             const Type* elem_ty = array_type->type;
             for (int i = 0; i < static_cast<int>(array_type->size); i++) {
diff --git a/src/tint/lang/spirv/reader/ast_parser/parse.cc b/src/tint/lang/spirv/reader/ast_parser/parse.cc
index 8081c14..df00f2e 100644
--- a/src/tint/lang/spirv/reader/ast_parser/parse.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/parse.cc
@@ -101,6 +101,7 @@
 
     // Allow below WGSL extensions unconditionally but not enable them by default.
     allowed_features.extensions.insert(wgsl::Extension::kDualSourceBlending);
+    allowed_features.extensions.insert(wgsl::Extension::kClipDistances);
 
     // The SPIR-V parser can construct disjoint AST nodes, which is invalid for
     // the Resolver. Clone the Program to clean these up.