Support Dual Source Blending in SPIR-V reader with Tint AST

This patch adds the support of parsing `spv::Decoration::Index` into
the attribute `@blend_src` in the SPIR-V reader with Tint AST.

Fixed: chromium:371367697, chromium:341973423
Test: tint_unittests
Change-Id: I927d95b523e599be6cd2962087d39d081ad2aa5d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/210174
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: James Price <jrprice@google.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 54d54b9..58c9ef9 100644
--- a/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
@@ -270,6 +270,7 @@
     }
     switch (static_cast<spv::Decoration>(deco[0])) {
         case spv::Decoration::Location:
+        case spv::Decoration::Index:
         case spv::Decoration::Flat:
         case spv::Decoration::NoPerspective:
         case spv::Decoration::Centroid:
@@ -589,6 +590,7 @@
     deco_mgr_ = nullptr;
 
     glsl_std_450_imports_.clear();
+    enabled_extensions_.Clear();
 }
 
 bool ASTParser::ParseInternalModule() {
@@ -1805,7 +1807,23 @@
     }
     // The list didn't have a location. Add it.
     attributes.Add(replacement);
-    return;
+}
+
+void ASTParser::SetBlendSrc(Attributes& attributes, const ast::Attribute* replacement) {
+    if (!replacement) {
+        return;
+    }
+    for (auto*& attribute : attributes.list) {
+        if (attribute->Is<ast::BlendSrcAttribute>()) {
+            // Replace this BlendSrc attribute with the replacement.
+            // The old one doesn't leak because it's kept in the builder's AST node
+            // list.
+            attribute = replacement;
+            return;  // Assume there is only one such decoration.
+        }
+    }
+    // The list didn't have a BlendSrc. Add it.
+    attributes.Add(replacement);
 }
 
 bool ASTParser::ConvertPipelineDecorations(const Type* store_type,
@@ -1853,6 +1871,14 @@
                 }
                 sampling = core::InterpolationSampling::kSample;
                 break;
+            case spv::Decoration::Index:
+                if (deco.size() != 2) {
+                    return Fail()
+                           << "malformed Index decoration on ID requires one literal operand";
+                }
+                Enable(wgsl::Extension::kDualSourceBlending);
+                SetBlendSrc(attributes, builder_.BlendSrc(AInt(deco[1])));
+                break;
             default:
                 break;
         }
diff --git a/src/tint/lang/spirv/reader/ast_parser/ast_parser.h b/src/tint/lang/spirv/reader/ast_parser/ast_parser.h
index 4174965..760f31b 100644
--- a/src/tint/lang/spirv/reader/ast_parser/ast_parser.h
+++ b/src/tint/lang/spirv/reader/ast_parser/ast_parser.h
@@ -296,6 +296,14 @@
     /// @param replacement the location decoration to place into the list
     void SetLocation(Attributes& attributes, const ast::Attribute* replacement);
 
+    /// Updates the attribute list, placing a non-null BlendSrc decoration into
+    /// the list, replacing an existing one if it exists. Does nothing if the
+    /// replacement is nullptr.
+    /// Assumes the list contains at most one BlendSrc decoration.
+    /// @param attributes the attribute list to modify
+    /// @param replacement the BlendSrc decoration to place into the list
+    void SetBlendSrc(Attributes& attributes, const ast::Attribute* replacement);
+
     /// Converts a SPIR-V struct member decoration into a number of AST
     /// decorations. If the decoration is recognized but deliberately dropped,
     /// then returns an empty list without a diagnostic. On failure, emits a
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 89758c5..3aaf1f9 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
@@ -27,7 +27,9 @@
 
 #include "gmock/gmock.h"
 #include "src/tint/lang/spirv/reader/ast_parser/helper_test.h"
+#include "src/tint/lang/spirv/reader/ast_parser/parse.h"
 #include "src/tint/lang/spirv/reader/ast_parser/spirv_tools_helpers_test.h"
+#include "src/tint/lang/wgsl/writer/writer.h"
 
 namespace tint::spirv::reader::ast_parser {
 namespace {
@@ -260,5 +262,115 @@
     EXPECT_THAT(p->error(), HasSubstr("value cannot be represented as 'f32': -inf"));
 }
 
+TEST_F(SpirvASTParserTest, BlendSrc) {
+    auto spv = test::Assemble(R"(
+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
+               OpName %frag_main_loc0_idx0_Output "frag_main_loc0_idx0_Output"
+               OpName %frag_main_loc0_idx1_Output "frag_main_loc0_idx1_Output"
+               OpName %frag_main_inner "frag_main_inner"
+               OpMemberName %FragOutput 0 "color"
+               OpMemberName %FragOutput 1 "blend"
+               OpName %FragOutput "FragOutput"
+               OpName %output "output"
+               OpName %frag_main "frag_main"
+               OpDecorate %frag_main_loc0_idx0_Output Location 0
+               OpDecorate %frag_main_loc0_idx0_Output Index 0
+               OpDecorate %frag_main_loc0_idx1_Output Location 0
+               OpDecorate %frag_main_loc0_idx1_Output Index 1
+               OpMemberDecorate %FragOutput 0 Offset 0
+               OpMemberDecorate %FragOutput 1 Offset 16
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%frag_main_loc0_idx0_Output = OpVariable %_ptr_Output_v4float Output
+%frag_main_loc0_idx1_Output = OpVariable %_ptr_Output_v4float Output
+ %FragOutput = OpTypeStruct %v4float %v4float
+          %8 = OpTypeFunction %FragOutput
+%_ptr_Function_FragOutput = OpTypePointer Function %FragOutput
+         %12 = OpConstantNull %FragOutput
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+  %float_0_5 = OpConstant %float 0.5
+    %float_1 = OpConstant %float 1
+         %17 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_1
+     %uint_1 = OpConstant %uint 1
+       %void = OpTypeVoid
+         %25 = OpTypeFunction %void
+%frag_main_inner = OpFunction %FragOutput None %8
+          %9 = OpLabel
+     %output = OpVariable %_ptr_Function_FragOutput Function %12
+         %13 = OpAccessChain %_ptr_Function_v4float %output %uint_0
+               OpStore %13 %17 None
+         %20 = OpAccessChain %_ptr_Function_v4float %output %uint_1
+               OpStore %20 %17 None
+         %22 = OpLoad %FragOutput %output None
+               OpReturnValue %22
+               OpFunctionEnd
+  %frag_main = OpFunction %void None %25
+         %26 = OpLabel
+         %27 = OpFunctionCall %FragOutput %frag_main_inner
+         %28 = OpCompositeExtract %v4float %27 0
+               OpStore %frag_main_loc0_idx0_Output %28 None
+         %29 = OpCompositeExtract %v4float %27 1
+               OpStore %frag_main_loc0_idx1_Output %29 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 dual_source_blending;
+
+struct FragOutput {
+  /* @offset(0) */
+  color : vec4f,
+  /* @offset(16) */
+  blend : vec4f,
+}
+
+var<private> frag_main_loc0_idx0_Output : vec4f;
+
+var<private> frag_main_loc0_idx1_Output : vec4f;
+
+const x_17 = vec4f(0.5f, 0.5f, 0.5f, 1.0f);
+
+fn frag_main_inner() -> FragOutput {
+  var output = FragOutput(vec4f(), vec4f());
+  output.color = x_17;
+  output.blend = x_17;
+  let x_22 = output;
+  return x_22;
+}
+
+fn frag_main_1() {
+  let x_27 = frag_main_inner();
+  frag_main_loc0_idx0_Output = x_27.color;
+  frag_main_loc0_idx1_Output = x_27.blend;
+  return;
+}
+
+struct frag_main_out {
+  @location(0) @blend_src(0)
+  frag_main_loc0_idx0_Output_1 : vec4f,
+  @location(0) @blend_src(1)
+  frag_main_loc0_idx1_Output_1 : vec4f,
+}
+
+@fragment
+fn frag_main() -> frag_main_out {
+  frag_main_1();
+  return frag_main_out(frag_main_loc0_idx0_Output, frag_main_loc0_idx1_Output);
+}
+)");
+}
+
 }  // namespace
 }  // namespace tint::spirv::reader::ast_parser
diff --git a/src/tint/lang/spirv/reader/ast_parser/parse.cc b/src/tint/lang/spirv/reader/ast_parser/parse.cc
index 9106cbb..8081c14 100644
--- a/src/tint/lang/spirv/reader/ast_parser/parse.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/parse.cc
@@ -99,6 +99,9 @@
     allowed_features.extensions.insert(wgsl::Extension::kChromiumDisableUniformityAnalysis);
     builder.Enable(wgsl::Extension::kChromiumDisableUniformityAnalysis);
 
+    // Allow below WGSL extensions unconditionally but not enable them by default.
+    allowed_features.extensions.insert(wgsl::Extension::kDualSourceBlending);
+
     // The SPIR-V parser can construct disjoint AST nodes, which is invalid for
     // the Resolver. Clone the Program to clean these up.
     Program program_with_disjoint_ast(std::move(builder));
diff --git a/test/tint/bug/tint/1520.spvasm b/test/tint/bug/tint/1520.spvasm
index a9071c2..14811c3 100644
--- a/test/tint/bug/tint/1520.spvasm
+++ b/test/tint/bug/tint/1520.spvasm
@@ -35,7 +35,6 @@
 OpDecorate %4 DescriptorSet 0
 OpDecorate %sk_FragColor RelaxedPrecision
 OpDecorate %sk_FragColor Location 0
-OpDecorate %sk_FragColor Index 0
 OpDecorate %sk_Clockwise BuiltIn FrontFacing
 OpDecorate %vcolor_S0 RelaxedPrecision
 OpDecorate %vcolor_S0 Location 0