tint: Support @diagnostic on loop and loop body Bug: tint:1809 Change-Id: Ib3ccfd823f9cccb67bebbf04927d54f193a4e281 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/124321 Commit-Queue: Ben Clayton <bclayton@google.com> Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/ast/loop_statement.cc b/src/tint/ast/loop_statement.cc index 731dee7..cd946cb 100644 --- a/src/tint/ast/loop_statement.cc +++ b/src/tint/ast/loop_statement.cc
@@ -14,6 +14,8 @@ #include "src/tint/ast/loop_statement.h" +#include <utility> + #include "src/tint/program_builder.h" TINT_INSTANTIATE_TYPEINFO(tint::ast::LoopStatement); @@ -24,11 +26,16 @@ NodeID nid, const Source& src, const BlockStatement* b, - const BlockStatement* cont) - : Base(pid, nid, src), body(b), continuing(cont) { + const BlockStatement* cont, + utils::VectorRef<const ast::Attribute*> attrs) + : Base(pid, nid, src), body(b), continuing(cont), attributes(std::move(attrs)) { TINT_ASSERT(AST, body); TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id); TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, continuing, program_id); + for (auto* attr : attributes) { + TINT_ASSERT(AST, attr); + TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id); + } } LoopStatement::~LoopStatement() = default; @@ -38,7 +45,8 @@ auto src = ctx->Clone(source); auto* b = ctx->Clone(body); auto* cont = ctx->Clone(continuing); - return ctx->dst->create<LoopStatement>(src, b, cont); + auto attrs = ctx->Clone(attributes); + return ctx->dst->create<LoopStatement>(src, b, cont, std::move(attrs)); } } // namespace tint::ast
diff --git a/src/tint/ast/loop_statement.h b/src/tint/ast/loop_statement.h index c1e5cc0..fc764e2 100644 --- a/src/tint/ast/loop_statement.h +++ b/src/tint/ast/loop_statement.h
@@ -28,12 +28,13 @@ /// @param source the loop statement source /// @param body the body statements /// @param continuing the continuing statements + /// @param attributes the while statement attributes LoopStatement(ProgramID pid, NodeID nid, const Source& source, const BlockStatement* body, - const BlockStatement* continuing); - + const BlockStatement* continuing, + utils::VectorRef<const ast::Attribute*> attributes); /// Destructor ~LoopStatement() override; @@ -48,6 +49,9 @@ /// The continuing statements const BlockStatement* const continuing; + + /// The attribute list + const utils::Vector<const Attribute*, 1> attributes; }; } // namespace tint::ast
diff --git a/src/tint/ast/loop_statement_test.cc b/src/tint/ast/loop_statement_test.cc index caa995d..e0fc0f1 100644 --- a/src/tint/ast/loop_statement_test.cc +++ b/src/tint/ast/loop_statement_test.cc
@@ -14,6 +14,7 @@ #include "src/tint/ast/loop_statement.h" +#include "gmock/gmock.h" #include "gtest/gtest-spi.h" #include "src/tint/ast/discard_statement.h" #include "src/tint/ast/if_statement.h" @@ -30,7 +31,7 @@ auto* continuing = Block(create<DiscardStatement>()); - auto* l = create<LoopStatement>(body, continuing); + auto* l = create<LoopStatement>(body, continuing, utils::Empty); ASSERT_EQ(l->body->statements.Length(), 1u); EXPECT_EQ(l->body->statements[0], b); ASSERT_EQ(l->continuing->statements.Length(), 1u); @@ -42,21 +43,32 @@ auto* continuing = Block(create<DiscardStatement>()); - auto* l = create<LoopStatement>(Source{Source::Location{20, 2}}, body, continuing); + auto* l = + create<LoopStatement>(Source{Source::Location{20, 2}}, body, continuing, utils::Empty); auto src = l->source; EXPECT_EQ(src.range.begin.line, 20u); EXPECT_EQ(src.range.begin.column, 2u); } +TEST_F(LoopStatementTest, Creation_WithAttributes) { + auto* attr1 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "foo"); + auto* attr2 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "bar"); + + auto* body = Block(Return()); + auto* l = create<LoopStatement>(body, nullptr, utils::Vector{attr1, attr2}); + + EXPECT_THAT(l->attributes, testing::ElementsAre(attr1, attr2)); +} + TEST_F(LoopStatementTest, IsLoop) { - auto* l = create<LoopStatement>(Block(), Block()); + auto* l = create<LoopStatement>(Block(), Block(), utils::Empty); EXPECT_TRUE(l->Is<LoopStatement>()); } TEST_F(LoopStatementTest, HasContinuing_WithoutContinuing) { auto* body = Block(create<DiscardStatement>()); - auto* l = create<LoopStatement>(body, nullptr); + auto* l = create<LoopStatement>(body, nullptr, utils::Empty); EXPECT_FALSE(l->continuing); } @@ -65,7 +77,7 @@ auto* continuing = Block(create<DiscardStatement>()); - auto* l = create<LoopStatement>(body, continuing); + auto* l = create<LoopStatement>(body, continuing, utils::Empty); EXPECT_TRUE(l->continuing); } @@ -73,7 +85,7 @@ EXPECT_FATAL_FAILURE( { ProgramBuilder b; - b.create<LoopStatement>(nullptr, nullptr); + b.create<LoopStatement>(nullptr, nullptr, utils::Empty); }, "internal compiler error"); } @@ -83,7 +95,7 @@ { ProgramBuilder b1; ProgramBuilder b2; - b1.create<LoopStatement>(b2.Block(), b1.Block()); + b1.create<LoopStatement>(b2.Block(), b1.Block(), utils::Empty); }, "internal compiler error"); } @@ -93,7 +105,7 @@ { ProgramBuilder b1; ProgramBuilder b2; - b1.create<LoopStatement>(b1.Block(), b2.Block()); + b1.create<LoopStatement>(b1.Block(), b2.Block(), utils::Empty); }, "internal compiler error"); }
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h index 31f566b..bcfaf6e 100644 --- a/src/tint/program_builder.h +++ b/src/tint/program_builder.h
@@ -3264,20 +3264,26 @@ /// @param source the source information /// @param body the loop body /// @param continuing the optional continuing block + /// @param attributes optional attributes /// @returns the loop statement pointer - const ast::LoopStatement* Loop(const Source& source, - const ast::BlockStatement* body, - const ast::BlockStatement* continuing = nullptr) { - return create<ast::LoopStatement>(source, body, continuing); + const ast::LoopStatement* Loop( + const Source& source, + const ast::BlockStatement* body, + const ast::BlockStatement* continuing = nullptr, + utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) { + return create<ast::LoopStatement>(source, body, continuing, std::move(attributes)); } /// Creates a ast::LoopStatement with input body and optional continuing /// @param body the loop body /// @param continuing the optional continuing block + /// @param attributes optional attributes /// @returns the loop statement pointer - const ast::LoopStatement* Loop(const ast::BlockStatement* body, - const ast::BlockStatement* continuing = nullptr) { - return create<ast::LoopStatement>(body, continuing); + const ast::LoopStatement* Loop( + const ast::BlockStatement* body, + const ast::BlockStatement* continuing = nullptr, + utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) { + return create<ast::LoopStatement>(body, continuing, std::move(attributes)); } /// Creates a ast::ForLoopStatement with input body and optional initializer, condition,
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc index ff0d721..413e58b 100644 --- a/src/tint/reader/spirv/function.cc +++ b/src/tint/reader/spirv/function.cc
@@ -743,7 +743,7 @@ /// @param builder the program builder /// @returns the built ast::LoopStatement ast::LoopStatement* Build(ProgramBuilder* builder) const override { - return builder->create<ast::LoopStatement>(Source{}, body, continuing); + return builder->create<ast::LoopStatement>(Source{}, body, continuing, utils::Empty); } /// Loop-statement block body
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc index 38d9aa2..f6e324ba 100644 --- a/src/tint/reader/wgsl/parser_impl.cc +++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -1281,7 +1281,7 @@ return sw.value; } - auto loop = loop_statement(); + auto loop = loop_statement(attrs.value); if (loop.errored) { return Failure::kErrored; } @@ -1731,13 +1731,18 @@ } // loop_statement -// : LOOP BRACKET_LEFT statements continuing_statement? BRACKET_RIGHT -Maybe<const ast::LoopStatement*> ParserImpl::loop_statement() { +// : attribute* LOOP attribute* BRACKET_LEFT statements continuing_statement? BRACKET_RIGHT +Maybe<const ast::LoopStatement*> ParserImpl::loop_statement(AttributeList& attrs) { Source source; if (!match(Token::Type::kLoop, &source)) { return Failure::kNoMatch; } + auto body_attrs = attribute_list(); + if (body_attrs.errored) { + return Failure::kErrored; + } + Maybe<const ast::BlockStatement*> continuing(Failure::kErrored); auto body_start = peek().source(); auto body = expect_brace_block("loop", [&]() -> Maybe<StatementList> { @@ -1757,11 +1762,12 @@ } auto body_end = last_source(); + TINT_DEFER(attrs.Clear()); return create<ast::LoopStatement>( source, create<ast::BlockStatement>(Source::Combine(body_start, body_end), body.value, - utils::Empty), - continuing.value); + std::move(body_attrs.value)), + continuing.value, std::move(attrs)); } ForHeader::ForHeader(const ast::Statement* init,
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h index 7ad2e6f..9789b4d 100644 --- a/src/tint/reader/wgsl/parser_impl.h +++ b/src/tint/reader/wgsl/parser_impl.h
@@ -512,9 +512,10 @@ /// Parses a `func_call_statement` grammar element /// @returns the parsed function call or nullptr Maybe<const ast::CallStatement*> func_call_statement(); - /// Parses a `loop_statement` grammar element + /// Parses a `loop_statement` grammar element, with the attribute list provided as `attrs`. + /// @param attrs the list of attributes for the statement /// @returns the parsed loop or nullptr - Maybe<const ast::LoopStatement*> loop_statement(); + Maybe<const ast::LoopStatement*> loop_statement(AttributeList& attrs); /// Parses a `for_header` grammar element, erroring on parse failure. /// @returns the parsed for header or nullptr Expect<std::unique_ptr<ForHeader>> expect_for_header();
diff --git a/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc index 43cf219..c8ad2b4 100644 --- a/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc +++ b/src/tint/reader/wgsl/parser_impl_loop_stmt_test.cc
@@ -20,7 +20,8 @@ TEST_F(ParserImplTest, LoopStmt_BodyNoContinuing) { auto p = parser("loop { discard; }"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_TRUE(e.matched); EXPECT_FALSE(e.errored); EXPECT_FALSE(p->has_error()) << p->error(); @@ -39,7 +40,8 @@ TEST_F(ParserImplTest, LoopStmt_BodyWithContinuing) { auto p = parser("loop { discard; continuing { discard; }}"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_TRUE(e.matched); EXPECT_FALSE(e.errored); EXPECT_FALSE(p->has_error()) << p->error(); @@ -64,7 +66,8 @@ TEST_F(ParserImplTest, LoopStmt_NoBodyNoContinuing) { auto p = parser("loop { }"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_TRUE(e.matched); EXPECT_FALSE(e.errored); EXPECT_FALSE(p->has_error()) << p->error(); @@ -75,7 +78,8 @@ TEST_F(ParserImplTest, LoopStmt_NoBodyWithContinuing) { auto p = parser("loop { continuing { discard; }}"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_TRUE(e.matched); EXPECT_FALSE(e.errored); EXPECT_FALSE(p->has_error()) << p->error(); @@ -85,9 +89,35 @@ EXPECT_TRUE(e->continuing->statements[0]->Is<ast::DiscardStatement>()); } +TEST_F(ParserImplTest, LoopStmt_StmtAttributes) { + auto p = parser("@diagnostic(off, derivative_uniformity) loop { }"); + auto attrs = p->attribute_list(); + auto l = p->loop_statement(attrs.value); + EXPECT_FALSE(p->has_error()) << p->error(); + EXPECT_FALSE(l.errored); + ASSERT_TRUE(l.matched); + + EXPECT_TRUE(attrs->IsEmpty()); + ASSERT_EQ(l->attributes.Length(), 1u); + EXPECT_TRUE(l->attributes[0]->Is<ast::DiagnosticAttribute>()); +} + +TEST_F(ParserImplTest, LoopStmt_BodyAttributes) { + auto p = parser("loop @diagnostic(off, derivative_uniformity) { }"); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); + EXPECT_TRUE(e.matched); + EXPECT_FALSE(e.errored); + EXPECT_FALSE(p->has_error()) << p->error(); + ASSERT_NE(e.value, nullptr); + ASSERT_EQ(e->body->attributes.Length(), 1u); + EXPECT_TRUE(e->body->attributes[0]->Is<ast::DiagnosticAttribute>()); +} + TEST_F(ParserImplTest, LoopStmt_MissingBracketLeft) { auto p = parser("loop discard; }"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_FALSE(e.matched); EXPECT_TRUE(e.errored); EXPECT_EQ(e.value, nullptr); @@ -97,7 +127,8 @@ TEST_F(ParserImplTest, LoopStmt_MissingBracketRight) { auto p = parser("loop { discard; "); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_FALSE(e.matched); EXPECT_TRUE(e.errored); EXPECT_EQ(e.value, nullptr); @@ -107,7 +138,8 @@ TEST_F(ParserImplTest, LoopStmt_InvalidStatements) { auto p = parser("loop { discard }"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_FALSE(e.matched); EXPECT_TRUE(e.errored); EXPECT_EQ(e.value, nullptr); @@ -117,7 +149,8 @@ TEST_F(ParserImplTest, LoopStmt_InvalidContinuing) { auto p = parser("loop { continuing { discard }}"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_FALSE(e.matched); EXPECT_TRUE(e.errored); EXPECT_EQ(e.value, nullptr); @@ -127,7 +160,8 @@ TEST_F(ParserImplTest, LoopStmt_Continuing_BreakIf) { auto p = parser("loop { continuing { break if 1 + 2 < 5; }}"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_TRUE(e.matched); EXPECT_FALSE(e.errored); EXPECT_FALSE(p->has_error()) << p->error(); @@ -139,7 +173,8 @@ TEST_F(ParserImplTest, LoopStmt_Continuing_BreakIf_MissingExpr) { auto p = parser("loop { continuing { break if; }}"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_FALSE(e.matched); EXPECT_TRUE(e.errored); EXPECT_TRUE(p->has_error()); @@ -149,7 +184,8 @@ TEST_F(ParserImplTest, LoopStmt_Continuing_BreakIf_InvalidExpr) { auto p = parser("loop { continuing { break if switch; }}"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_FALSE(e.matched); EXPECT_TRUE(e.errored); EXPECT_TRUE(p->has_error()); @@ -159,7 +195,8 @@ TEST_F(ParserImplTest, LoopStmt_NoContinuing_BreakIf) { auto p = parser("loop { break if true; }"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_FALSE(e.matched); EXPECT_TRUE(e.errored); EXPECT_TRUE(p->has_error()); @@ -169,7 +206,8 @@ TEST_F(ParserImplTest, LoopStmt_Continuing_BreakIf_MissingSemicolon) { auto p = parser("loop { continuing { break if 1 + 2 < 5 }}"); - auto e = p->loop_statement(); + ParserImpl::AttributeList attrs; + auto e = p->loop_statement(attrs); EXPECT_FALSE(e.matched); EXPECT_TRUE(e.errored); EXPECT_TRUE(p->has_error());
diff --git a/src/tint/reader/wgsl/parser_impl_statement_test.cc b/src/tint/reader/wgsl/parser_impl_statement_test.cc index 8c920a7b..9651fa0 100644 --- a/src/tint/reader/wgsl/parser_impl_statement_test.cc +++ b/src/tint/reader/wgsl/parser_impl_statement_test.cc
@@ -350,6 +350,18 @@ EXPECT_EQ(s->attributes.Length(), 1u); } +TEST_F(ParserImplTest, Statement_ConsumedAttributes_Loop) { + auto p = parser("@diagnostic(off, derivative_uniformity) loop {}"); + auto e = p->statement(); + ASSERT_FALSE(p->has_error()) << p->error(); + EXPECT_TRUE(e.matched); + EXPECT_FALSE(e.errored); + + auto* s = As<ast::LoopStatement>(e.value); + ASSERT_NE(s, nullptr); + EXPECT_EQ(s->attributes.Length(), 1u); +} + TEST_F(ParserImplTest, Statement_ConsumedAttributes_Switch) { auto p = parser("@diagnostic(off, derivative_uniformity) switch (0) { default{} }"); auto e = p->statement();
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc index 23f82a2..1e123f6 100644 --- a/src/tint/resolver/attribute_validation_test.cc +++ b/src/tint/resolver/attribute_validation_test.cc
@@ -1144,6 +1144,39 @@ TestParams{AttributeKind::kWorkgroup, false}, TestParams{AttributeKind::kBindingAndGroup, false})); +using LoopStatementAttributeTest = TestWithParams; +TEST_P(LoopStatementAttributeTest, IsValid) { + auto& params = GetParam(); + + WrapInFunction( + Loop(Block(Return()), Block(), createAttributes(Source{{12, 34}}, *this, params.kind))); + + if (params.should_pass) { + EXPECT_TRUE(r()->Resolve()) << r()->error(); + } else { + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for loop statements"); + } +} +INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest, + LoopStatementAttributeTest, + testing::Values(TestParams{AttributeKind::kAlign, false}, + TestParams{AttributeKind::kBinding, false}, + TestParams{AttributeKind::kBuiltin, false}, + TestParams{AttributeKind::kDiagnostic, true}, + TestParams{AttributeKind::kGroup, false}, + TestParams{AttributeKind::kId, false}, + TestParams{AttributeKind::kInterpolate, false}, + TestParams{AttributeKind::kInvariant, false}, + TestParams{AttributeKind::kLocation, false}, + TestParams{AttributeKind::kMustUse, false}, + TestParams{AttributeKind::kOffset, false}, + TestParams{AttributeKind::kSize, false}, + TestParams{AttributeKind::kStage, false}, + TestParams{AttributeKind::kStride, false}, + TestParams{AttributeKind::kWorkgroup, false}, + TestParams{AttributeKind::kBindingAndGroup, false})); + using WhileStatementAttributeTest = TestWithParams; TEST_P(WhileStatementAttributeTest, IsValid) { auto& params = GetParam(); @@ -1225,6 +1258,13 @@ }); Check(); } +TEST_P(BlockStatementTest, LoopStatementBody) { + Func("foo", utils::Empty, ty.void_(), + utils::Vector{ + Loop(Block(utils::Vector{Break()}, createAttributes({}, *this, GetParam().kind))), + }); + Check(); +} TEST_P(BlockStatementTest, WhileStatementBody) { Func("foo", utils::Empty, ty.void_(), utils::Vector{
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc index 2cd40e9..5158aed 100644 --- a/src/tint/resolver/resolver.cc +++ b/src/tint/resolver/resolver.cc
@@ -4281,6 +4281,9 @@ return handle_attributes(f, sem, "for statements"); }, [&](const ast::IfStatement* i) { return handle_attributes(i, sem, "if statements"); }, + [&](const ast::LoopStatement* l) { + return handle_attributes(l, sem, "loop statements"); + }, [&](const ast::SwitchStatement* s) { return handle_attributes(s, sem, "switch statements"); },
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc index e166a06..ee952f2 100644 --- a/src/tint/resolver/uniformity_test.cc +++ b/src/tint/resolver/uniformity_test.cc
@@ -8547,6 +8547,114 @@ } } +TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnLoopStatement_CallInBody) { + auto& param = GetParam(); + utils::StringStream ss; + ss << R"( +@group(0) @binding(0) var<storage, read_write> non_uniform : i32; +fn foo() { + )" + << "@diagnostic(" << param << ", derivative_uniformity)" + << R"(loop { + _ = dpdx(1.0); + continuing { + 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, AttributeOnLoopStatement_CallInContinuing) { + auto& param = GetParam(); + utils::StringStream ss; + ss << R"( +@group(0) @binding(0) var<storage, read_write> non_uniform : i32; +fn foo() { + )" + << "@diagnostic(" << param << ", derivative_uniformity)" + << R"(loop { + continuing { + _ = 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, AttributeOnLoopBody_CallInBody) { + auto& param = GetParam(); + utils::StringStream ss; + ss << R"( +@group(0) @binding(0) var<storage, read_write> non_uniform : i32; +fn foo() { + loop )" + << "@diagnostic(" << param << ", derivative_uniformity)" + << R"( { + _ = dpdx(1.0); + continuing { + 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, AttributeOnLoopBody_CallInContinuing) { + auto& param = GetParam(); + utils::StringStream ss; + ss << R"( +@group(0) @binding(0) var<storage, read_write> non_uniform : i32; +fn foo() { + loop )" + << "@diagnostic(" << param << ", derivative_uniformity)" + << R"( { + continuing { + _ = 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 a22b43e..43187a7 100644 --- a/src/tint/sem/diagnostic_severity_test.cc +++ b/src/tint/sem/diagnostic_severity_test.cc
@@ -55,7 +55,11 @@ // for (var i = 0; false; i++) @diagnostic(warning, chromium_unreachable_code) { // return; // } - // } + // + // @diagnostic(warning, chromium_unreachable_code) + // loop @diagnostic(off, chromium_unreachable_code) { + // return; + // } // // @diagnostic(error, chromium_unreachable_code) // while (false) @diagnostic(warning, chromium_unreachable_code) { @@ -77,6 +81,8 @@ auto case_severity = builtin::DiagnosticSeverity::kWarning; auto for_severity = builtin::DiagnosticSeverity::kError; auto for_body_severity = builtin::DiagnosticSeverity::kWarning; + auto loop_severity = builtin::DiagnosticSeverity::kWarning; + auto loop_body_severity = builtin::DiagnosticSeverity::kOff; auto while_severity = builtin::DiagnosticSeverity::kError; auto while_body_severity = builtin::DiagnosticSeverity::kWarning; auto attr = [&](auto severity) { @@ -90,6 +96,7 @@ auto* return_foo_case = Return(); auto* return_foo_default = Return(); auto* return_foo_for = Return(); + auto* return_foo_loop = Return(); auto* return_foo_while = Return(); 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)); @@ -102,10 +109,12 @@ 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(), + attr(loop_severity)); auto* wl = While(false, Block(utils::Vector{return_foo_while}, attr(while_body_severity)), attr(while_severity)); auto* block_1 = - Block(utils::Vector{if_foo, return_foo_block, swtch, fl, wl}, attr(block_severity)); + Block(utils::Vector{if_foo, return_foo_block, swtch, fl, l, wl}, attr(block_severity)); auto* func_attr = DiagnosticAttribute(func_severity, "chromium_unreachable_code"); auto* foo = Func("foo", {}, ty.void_(), utils::Vector{block_1}, utils::Vector{func_attr}); @@ -139,6 +148,9 @@ EXPECT_EQ(p.Sem().DiagnosticSeverity(fl->continuing, rule), for_severity); EXPECT_EQ(p.Sem().DiagnosticSeverity(fl->body, rule), for_body_severity); 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(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); EXPECT_EQ(p.Sem().DiagnosticSeverity(wl->body, rule), while_body_severity);
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc index 2b5f474..352b7e0 100644 --- a/src/tint/writer/wgsl/generator_impl.cc +++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -1025,7 +1025,21 @@ } bool GeneratorImpl::EmitLoop(const ast::LoopStatement* stmt) { - line() << "loop {"; + { + auto out = line(); + + if (!stmt->attributes.IsEmpty()) { + if (!EmitAttributes(out, stmt->attributes)) { + return false; + } + out << " "; + } + + out << "loop "; + if (!EmitBlockHeader(out, stmt->body)) { + return false; + } + } increment_indent(); if (!EmitStatements(stmt->body->statements)) {