tint: Support @diagnostic on continuing statements

Bug: tint:1809
Change-Id: I01725dc3b5aa3a91f7a3d3b0077667a1072debce
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/124360
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index f6e324ba..3ec5416 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -1975,8 +1975,13 @@
 }
 
 // continuing_compound_statement:
-//   brace_left statement* break_if_statement? brace_right
+//   attribute* BRACE_LEFT statement* break_if_statement? BRACE_RIGHT
 Maybe<const ast::BlockStatement*> ParserImpl::continuing_compound_statement() {
+    auto attrs = attribute_list();
+    if (attrs.errored) {
+        return Failure::kErrored;
+    }
+
     auto source_start = peek().source();
     auto body = expect_brace_block("", [&]() -> Expect<StatementList> {
         StatementList stmts;
@@ -2010,7 +2015,7 @@
     auto source_end = last_source();
 
     return create<ast::BlockStatement>(Source::Combine(source_start, source_end), body.value,
-                                       utils::Empty);
+                                       std::move(attrs.value));
 }
 
 // continuing_statement
diff --git a/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc
index d2113af..3354399 100644
--- a/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_continuing_stmt_test.cc
@@ -28,6 +28,16 @@
     ASSERT_TRUE(e->statements[0]->Is<ast::DiscardStatement>());
 }
 
+TEST_F(ParserImplTest, ContinuingStmt_WithAttributes) {
+    auto p = parser("continuing @diagnostic(off, derivative_uniformity) { discard; }");
+    auto e = p->continuing_statement();
+    EXPECT_TRUE(e.matched);
+    EXPECT_FALSE(e.errored);
+    EXPECT_FALSE(p->has_error()) << p->error();
+    ASSERT_EQ(e->attributes.Length(), 1u);
+    EXPECT_TRUE(e->attributes[0]->Is<ast::DiagnosticAttribute>());
+}
+
 TEST_F(ParserImplTest, ContinuingStmt_InvalidBody) {
     auto p = parser("continuing { discard }");
     auto e = p->continuing_statement();
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
index ee952f2..4cb95dd 100644
--- a/src/tint/resolver/uniformity_test.cc
+++ b/src/tint/resolver/uniformity_test.cc
@@ -8655,6 +8655,33 @@
     }
 }
 
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnLoopContinuing_CallInContinuing) {
+    auto& param = GetParam();
+    utils::StringStream ss;
+    ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+fn foo() {
+  loop {
+    continuing )"
+       << "@diagnostic(" << param << ", derivative_uniformity)"
+       << R"( {
+      _ = dpdx(1.0);
+      break if non_uniform == 0;
+    }
+  }
+}
+)";
+
+    RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+    if (param == builtin::DiagnosticSeverity::kOff) {
+        EXPECT_TRUE(error_.empty());
+    } else {
+        utils::StringStream err;
+        err << ToStr(param) << ": 'dpdx' must only be called";
+        EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+    }
+}
+
 TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnSwitchStatement_CallInCondition) {
     auto& param = GetParam();
     utils::StringStream ss;
diff --git a/src/tint/sem/diagnostic_severity_test.cc b/src/tint/sem/diagnostic_severity_test.cc
index 43187a7..06d5a7e 100644
--- a/src/tint/sem/diagnostic_severity_test.cc
+++ b/src/tint/sem/diagnostic_severity_test.cc
@@ -59,6 +59,9 @@
         //     @diagnostic(warning, chromium_unreachable_code)
         //     loop @diagnostic(off, chromium_unreachable_code) {
         //       return;
+        //       continuing @diagnostic(info, chromium_unreachable_code) {
+        //         break if true;
+        //       }
         //     }
         //
         //     @diagnostic(error, chromium_unreachable_code)
@@ -83,6 +86,7 @@
         auto for_body_severity = builtin::DiagnosticSeverity::kWarning;
         auto loop_severity = builtin::DiagnosticSeverity::kWarning;
         auto loop_body_severity = builtin::DiagnosticSeverity::kOff;
+        auto continuing_severity = builtin::DiagnosticSeverity::kInfo;
         auto while_severity = builtin::DiagnosticSeverity::kError;
         auto while_body_severity = builtin::DiagnosticSeverity::kWarning;
         auto attr = [&](auto severity) {
@@ -98,6 +102,7 @@
         auto* return_foo_for = Return();
         auto* return_foo_loop = Return();
         auto* return_foo_while = Return();
+        auto* breakif_foo_continuing = BreakIf(Expr(true));
         auto* else_stmt = Block(utils::Vector{return_foo_else}, attr(else_body_severity));
         auto* elseif = If(Expr(false), Block(return_foo_elseif), Else(else_stmt));
         auto* if_foo = If(Expr(true), Block(utils::Vector{return_foo_if}, attr(if_body_severity)),
@@ -109,7 +114,8 @@
         auto* fl =
             For(Decl(Var("i", ty.i32())), false, Increment("i"),
                 Block(utils::Vector{return_foo_for}, attr(for_body_severity)), attr(for_severity));
-        auto* l = Loop(Block(utils::Vector{return_foo_loop}, attr(loop_body_severity)), Block(),
+        auto* l = Loop(Block(utils::Vector{return_foo_loop}, attr(loop_body_severity)),
+                       Block(utils::Vector{breakif_foo_continuing}, attr(continuing_severity)),
                        attr(loop_severity));
         auto* wl = While(false, Block(utils::Vector{return_foo_while}, attr(while_body_severity)),
                          attr(while_severity));
@@ -150,6 +156,8 @@
         EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_for, rule), for_body_severity);
         EXPECT_EQ(p.Sem().DiagnosticSeverity(l, rule), loop_severity);
         EXPECT_EQ(p.Sem().DiagnosticSeverity(l->body, rule), loop_body_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(l->continuing, rule), continuing_severity);
+        EXPECT_EQ(p.Sem().DiagnosticSeverity(breakif_foo_continuing, rule), continuing_severity);
         EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_loop, rule), loop_body_severity);
         EXPECT_EQ(p.Sem().DiagnosticSeverity(wl, rule), while_severity);
         EXPECT_EQ(p.Sem().DiagnosticSeverity(wl->condition, rule), while_severity);
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index 352b7e0..52df265 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -1048,7 +1048,17 @@
 
     if (stmt->continuing && !stmt->continuing->Empty()) {
         line();
-        line() << "continuing {";
+        {
+            auto out = line();
+            out << "continuing ";
+            if (!stmt->continuing->attributes.IsEmpty()) {
+                if (!EmitAttributes(out, stmt->continuing->attributes)) {
+                    return false;
+                }
+                out << " ";
+            }
+            out << "{";
+        }
         if (!EmitStatementsWithIndent(stmt->continuing->statements)) {
             return false;
         }
diff --git a/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl
new file mode 100644
index 0000000..8466651
--- /dev/null
+++ b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl
@@ -0,0 +1,9 @@
+@fragment
+fn main(@location(0) x : f32) {
+  loop {
+    continuing @diagnostic(warning, derivative_uniformity) {
+      _ = dpdx(1.0);
+      break if x > 0.0;
+    }
+  }
+}
diff --git a/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.dxc.hlsl b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..f1d8d13
--- /dev/null
+++ b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.dxc.hlsl
@@ -0,0 +1,28 @@
+diagnostic_filtering/loop_continuing_attribute.wgsl:5:11 warning: 'dpdx' must only be called from uniform control flow
+      _ = dpdx(1.0);
+          ^^^^^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:7 note: control flow depends on possibly non-uniform value
+      break if x > 0.0;
+      ^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:16 note: user-defined input 'x' of 'main' may be non-uniform
+      break if x > 0.0;
+               ^
+
+struct tint_symbol_1 {
+  float x : TEXCOORD0;
+};
+
+void main_inner(float x) {
+  while (true) {
+    {
+      if ((x > 0.0f)) { break; }
+    }
+  }
+}
+
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.x);
+  return;
+}
diff --git a/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.fxc.hlsl b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..f1d8d13
--- /dev/null
+++ b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.fxc.hlsl
@@ -0,0 +1,28 @@
+diagnostic_filtering/loop_continuing_attribute.wgsl:5:11 warning: 'dpdx' must only be called from uniform control flow
+      _ = dpdx(1.0);
+          ^^^^^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:7 note: control flow depends on possibly non-uniform value
+      break if x > 0.0;
+      ^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:16 note: user-defined input 'x' of 'main' may be non-uniform
+      break if x > 0.0;
+               ^
+
+struct tint_symbol_1 {
+  float x : TEXCOORD0;
+};
+
+void main_inner(float x) {
+  while (true) {
+    {
+      if ((x > 0.0f)) { break; }
+    }
+  }
+}
+
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.x);
+  return;
+}
diff --git a/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.glsl b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.glsl
new file mode 100644
index 0000000..4794a47
--- /dev/null
+++ b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.glsl
@@ -0,0 +1,28 @@
+diagnostic_filtering/loop_continuing_attribute.wgsl:5:11 warning: 'dpdx' must only be called from uniform control flow
+      _ = dpdx(1.0);
+          ^^^^^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:7 note: control flow depends on possibly non-uniform value
+      break if x > 0.0;
+      ^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:16 note: user-defined input 'x' of 'main' may be non-uniform
+      break if x > 0.0;
+               ^
+
+#version 310 es
+precision highp float;
+
+layout(location = 0) in float x_1;
+void tint_symbol(float x) {
+  while (true) {
+    {
+      if ((x > 0.0f)) { break; }
+    }
+  }
+}
+
+void main() {
+  tint_symbol(x_1);
+  return;
+}
diff --git a/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.msl b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.msl
new file mode 100644
index 0000000..5eadb5d
--- /dev/null
+++ b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.msl
@@ -0,0 +1,32 @@
+diagnostic_filtering/loop_continuing_attribute.wgsl:5:11 warning: 'dpdx' must only be called from uniform control flow
+      _ = dpdx(1.0);
+          ^^^^^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:7 note: control flow depends on possibly non-uniform value
+      break if x > 0.0;
+      ^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:16 note: user-defined input 'x' of 'main' may be non-uniform
+      break if x > 0.0;
+               ^
+
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_2 {
+  float x [[user(locn0)]];
+};
+
+void tint_symbol_inner(float x) {
+  while (true) {
+    {
+      if ((x > 0.0f)) { break; }
+    }
+  }
+}
+
+fragment void tint_symbol(tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  tint_symbol_inner(tint_symbol_1.x);
+  return;
+}
+
diff --git a/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.spvasm b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.spvasm
new file mode 100644
index 0000000..b4837c0
--- /dev/null
+++ b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.spvasm
@@ -0,0 +1,55 @@
+diagnostic_filtering/loop_continuing_attribute.wgsl:5:11 warning: 'dpdx' must only be called from uniform control flow
+      _ = dpdx(1.0);
+          ^^^^^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:7 note: control flow depends on possibly non-uniform value
+      break if x > 0.0;
+      ^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:16 note: user-defined input 'x' of 'main' may be non-uniform
+      break if x > 0.0;
+               ^
+
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 21
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %x_1
+               OpExecutionMode %main OriginUpperLeft
+               OpName %x_1 "x_1"
+               OpName %main_inner "main_inner"
+               OpName %x "x"
+               OpName %main "main"
+               OpDecorate %x_1 Location 0
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+        %x_1 = OpVariable %_ptr_Input_float Input
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void %float
+         %13 = OpConstantNull %float
+       %bool = OpTypeBool
+         %16 = OpTypeFunction %void
+ %main_inner = OpFunction %void None %4
+          %x = OpFunctionParameter %float
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+         %14 = OpFOrdGreaterThan %bool %x %13
+               OpBranchConditional %14 %10 %9
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %16
+         %18 = OpLabel
+         %20 = OpLoad %float %x_1
+         %19 = OpFunctionCall %void %main_inner %20
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.wgsl b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.wgsl
new file mode 100644
index 0000000..b2f284c
--- /dev/null
+++ b/test/tint/diagnostic_filtering/loop_continuing_attribute.wgsl.expected.wgsl
@@ -0,0 +1,22 @@
+diagnostic_filtering/loop_continuing_attribute.wgsl:5:11 warning: 'dpdx' must only be called from uniform control flow
+      _ = dpdx(1.0);
+          ^^^^^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:7 note: control flow depends on possibly non-uniform value
+      break if x > 0.0;
+      ^^^^^
+
+diagnostic_filtering/loop_continuing_attribute.wgsl:6:16 note: user-defined input 'x' of 'main' may be non-uniform
+      break if x > 0.0;
+               ^
+
+@fragment
+fn main(@location(0) x : f32) {
+  loop {
+
+    continuing @diagnostic(warning, derivative_uniformity) {
+      _ = dpdx(1.0);
+      break if (x > 0.0);
+    }
+  }
+}