tint: Support @diagnostic on while statements
Bug: tint:1809
Change-Id: I9593539d258d16a68c9ff1cfd2cad52e28fad65a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/124100
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/ast/while_statement.cc b/src/tint/ast/while_statement.cc
index 160af4b..47cf2bb 100644
--- a/src/tint/ast/while_statement.cc
+++ b/src/tint/ast/while_statement.cc
@@ -14,6 +14,8 @@
#include "src/tint/ast/while_statement.h"
+#include <utility>
+
#include "src/tint/program_builder.h"
TINT_INSTANTIATE_TYPEINFO(tint::ast::WhileStatement);
@@ -24,13 +26,18 @@
NodeID nid,
const Source& src,
const Expression* cond,
- const BlockStatement* b)
- : Base(pid, nid, src), condition(cond), body(b) {
+ const BlockStatement* b,
+ utils::VectorRef<const ast::Attribute*> attrs)
+ : Base(pid, nid, src), condition(cond), body(b), attributes(std::move(attrs)) {
TINT_ASSERT(AST, cond);
TINT_ASSERT(AST, body);
TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, condition, program_id);
TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, body, program_id);
+ for (auto* attr : attributes) {
+ TINT_ASSERT(AST, attr);
+ TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(AST, attr, program_id);
+ }
}
WhileStatement::WhileStatement(WhileStatement&&) = default;
@@ -43,7 +50,8 @@
auto* cond = ctx->Clone(condition);
auto* b = ctx->Clone(body);
- return ctx->dst->create<WhileStatement>(src, cond, b);
+ auto attrs = ctx->Clone(attributes);
+ return ctx->dst->create<WhileStatement>(src, cond, b, std::move(attrs));
}
} // namespace tint::ast
diff --git a/src/tint/ast/while_statement.h b/src/tint/ast/while_statement.h
index 4e8dd7e..b2b91dc 100644
--- a/src/tint/ast/while_statement.h
+++ b/src/tint/ast/while_statement.h
@@ -30,11 +30,13 @@
/// @param source the for loop statement source
/// @param condition the optional loop condition expression
/// @param body the loop body
+ /// @param attributes the while statement attributes
WhileStatement(ProgramID pid,
NodeID nid,
const Source& source,
const Expression* condition,
- const BlockStatement* body);
+ const BlockStatement* body,
+ utils::VectorRef<const ast::Attribute*> attributes);
/// Move constructor
WhileStatement(WhileStatement&&);
~WhileStatement() override;
@@ -50,6 +52,9 @@
/// The loop body block
const BlockStatement* const body;
+
+ /// The attribute list
+ const utils::Vector<const Attribute*, 1> attributes;
};
} // namespace tint::ast
diff --git a/src/tint/ast/while_statement_test.cc b/src/tint/ast/while_statement_test.cc
index 73c9e56..ef287fc 100644
--- a/src/tint/ast/while_statement_test.cc
+++ b/src/tint/ast/while_statement_test.cc
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "gmock/gmock.h"
#include "gtest/gtest-spi.h"
#include "src/tint/ast/binary_expression.h"
#include "src/tint/ast/test_helper.h"
@@ -41,6 +42,16 @@
EXPECT_EQ(src.range.begin.column, 2u);
}
+TEST_F(WhileStatementTest, Creation_WithAttributes) {
+ auto* attr1 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "foo");
+ auto* attr2 = DiagnosticAttribute(builtin::DiagnosticSeverity::kOff, "bar");
+ auto* cond = create<BinaryExpression>(BinaryOp::kLessThan, Expr("i"), Expr(5_u));
+ auto* body = Block(Return());
+ auto* l = While(cond, body, utils::Vector{attr1, attr2});
+
+ EXPECT_THAT(l->attributes, testing::ElementsAre(attr1, attr2));
+}
+
TEST_F(WhileStatementTest, Assert_Null_Cond) {
EXPECT_FATAL_FAILURE(
{
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index a2d4f48..dc50acf 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -3311,25 +3311,34 @@
return create<ast::ForLoopStatement>(init, Expr(std::forward<COND>(cond)), cont, body);
}
- /// Creates a ast::WhileStatement with input body and condition.
+ /// Creates a ast::WhileStatement with input body, condition, and optional attributes.
/// @param source the source information
/// @param cond the loop condition
/// @param body the loop body
+ /// @param attributes optional attributes
/// @returns the while statement pointer
template <typename COND>
- const ast::WhileStatement* While(const Source& source,
- COND&& cond,
- const ast::BlockStatement* body) {
- return create<ast::WhileStatement>(source, Expr(std::forward<COND>(cond)), body);
+ const ast::WhileStatement* While(
+ const Source& source,
+ COND&& cond,
+ const ast::BlockStatement* body,
+ utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
+ return create<ast::WhileStatement>(source, Expr(std::forward<COND>(cond)), body,
+ std::move(attributes));
}
- /// Creates a ast::WhileStatement with given condition and body.
+ /// Creates a ast::WhileStatement with input body, condition, and optional attributes.
/// @param cond the condition
/// @param body the loop body
+ /// @param attributes optional attributes
/// @returns the while loop statement pointer
template <typename COND>
- const ast::WhileStatement* While(COND&& cond, const ast::BlockStatement* body) {
- return create<ast::WhileStatement>(Expr(std::forward<COND>(cond)), body);
+ const ast::WhileStatement* While(
+ COND&& cond,
+ const ast::BlockStatement* body,
+ utils::VectorRef<const ast::Attribute*> attributes = utils::Empty) {
+ return create<ast::WhileStatement>(Expr(std::forward<COND>(cond)), body,
+ std::move(attributes));
}
/// Creates a ast::VariableDeclStatement for the input variable
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 5f901e0..7936d65 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -1296,7 +1296,7 @@
return stmt_for.value;
}
- auto stmt_while = while_statement();
+ auto stmt_while = while_statement(attrs.value);
if (stmt_while.errored) {
return Failure::kErrored;
}
@@ -1902,8 +1902,8 @@
}
// while_statement
-// : WHILE expression compound_statement
-Maybe<const ast::WhileStatement*> ParserImpl::while_statement() {
+// : attribute* WHILE expression compound_statement
+Maybe<const ast::WhileStatement*> ParserImpl::while_statement(AttributeList& attrs) {
Source source;
if (!match(Token::Type::kWhile, &source)) {
return Failure::kNoMatch;
@@ -1922,7 +1922,8 @@
return Failure::kErrored;
}
- return create<ast::WhileStatement>(source, condition.value, body.value);
+ TINT_DEFER(attrs.Clear());
+ return create<ast::WhileStatement>(source, condition.value, body.value, std::move(attrs));
}
// func_call_statement
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index dd0a96c..57550e0 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -520,9 +520,10 @@
/// Parses a `for_statement` grammar element
/// @returns the parsed for loop or nullptr
Maybe<const ast::ForLoopStatement*> for_statement();
- /// Parses a `while_statement` grammar element
+ /// Parses a `while_statement` grammar element, with the attribute list provided as `attrs`.
+ /// @param attrs the list of attributes for the statement
/// @returns the parsed while loop or nullptr
- Maybe<const ast::WhileStatement*> while_statement();
+ Maybe<const ast::WhileStatement*> while_statement(AttributeList& attrs);
/// Parses a `break_if_statement` grammar element
/// @returns the parsed statement or nullptr
Maybe<const ast::Statement*> break_if_statement();
diff --git a/src/tint/reader/wgsl/parser_impl_statement_test.cc b/src/tint/reader/wgsl/parser_impl_statement_test.cc
index 8b50d10..d10e549 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_While) {
+ auto p = parser("@diagnostic(off, derivative_uniformity) while (false) {}");
+ auto e = p->statement();
+ ASSERT_FALSE(p->has_error()) << p->error();
+ EXPECT_TRUE(e.matched);
+ EXPECT_FALSE(e.errored);
+
+ auto* s = As<ast::WhileStatement>(e.value);
+ ASSERT_NE(s, nullptr);
+ EXPECT_EQ(s->attributes.Length(), 1u);
+}
+
TEST_F(ParserImplTest, Statement_UnexpectedAttributes) {
auto p = parser("@diagnostic(off, derivative_uniformity) return;");
auto e = p->statement();
diff --git a/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc
index d61843e..dcbb59b 100644
--- a/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_while_stmt_test.cc
@@ -24,7 +24,8 @@
// Test an empty while loop.
TEST_F(WhileStmtTest, Empty) {
auto p = parser("while true { }");
- auto wl = p->while_statement();
+ ParserImpl::AttributeList attrs;
+ auto wl = p->while_statement(attrs);
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(wl.errored);
ASSERT_TRUE(wl.matched);
@@ -35,7 +36,8 @@
// Test an empty while loop with parentheses.
TEST_F(WhileStmtTest, EmptyWithParentheses) {
auto p = parser("while (true) { }");
- auto wl = p->while_statement();
+ ParserImpl::AttributeList attrs;
+ auto wl = p->while_statement(attrs);
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(wl.errored);
ASSERT_TRUE(wl.matched);
@@ -46,7 +48,8 @@
// Test a while loop with non-empty body.
TEST_F(WhileStmtTest, Body) {
auto p = parser("while (true) { discard; }");
- auto wl = p->while_statement();
+ ParserImpl::AttributeList attrs;
+ auto wl = p->while_statement(attrs);
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(wl.errored);
ASSERT_TRUE(wl.matched);
@@ -58,7 +61,8 @@
// Test a while loop with complex condition.
TEST_F(WhileStmtTest, ComplexCondition) {
auto p = parser("while (a + 1 - 2) == 3 { }");
- auto wl = p->while_statement();
+ ParserImpl::AttributeList attrs;
+ auto wl = p->while_statement(attrs);
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(wl.errored);
ASSERT_TRUE(wl.matched);
@@ -69,7 +73,8 @@
// Test a while loop with complex condition, with parentheses.
TEST_F(WhileStmtTest, ComplexConditionWithParentheses) {
auto p = parser("while ((a + 1 - 2) == 3) { }");
- auto wl = p->while_statement();
+ ParserImpl::AttributeList attrs;
+ auto wl = p->while_statement(attrs);
EXPECT_FALSE(p->has_error()) << p->error();
EXPECT_FALSE(wl.errored);
ASSERT_TRUE(wl.matched);
@@ -77,11 +82,26 @@
EXPECT_TRUE(wl->body->Empty());
}
+// Test a while loop with attributes.
+TEST_F(WhileStmtTest, WithAttributes) {
+ auto p = parser("@diagnostic(off, derivative_uniformity) while true { }");
+ auto attrs = p->attribute_list();
+ auto wl = p->while_statement(attrs.value);
+ EXPECT_FALSE(p->has_error()) << p->error();
+ EXPECT_FALSE(wl.errored);
+ ASSERT_TRUE(wl.matched);
+
+ EXPECT_TRUE(attrs->IsEmpty());
+ ASSERT_EQ(wl->attributes.Length(), 1u);
+ EXPECT_TRUE(wl->attributes[0]->Is<ast::DiagnosticAttribute>());
+}
+
class WhileStmtErrorTest : public ParserImplTest {
public:
void TestWhileWithError(std::string for_str, std::string error_str) {
auto p_for = parser(for_str);
- auto e_for = p_for->while_statement();
+ ParserImpl::AttributeList attrs;
+ auto e_for = p_for->while_statement(attrs);
EXPECT_FALSE(e_for.matched);
EXPECT_TRUE(e_for.errored);
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 1a8de3b..0b80300 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -1111,6 +1111,39 @@
TestParams{AttributeKind::kWorkgroup, false},
TestParams{AttributeKind::kBindingAndGroup, false}));
+using WhileStatementAttributeTest = TestWithParams;
+TEST_P(WhileStatementAttributeTest, IsValid) {
+ auto& params = GetParam();
+
+ WrapInFunction(
+ While(Expr(false), 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 while statements");
+ }
+}
+INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
+ WhileStatementAttributeTest,
+ 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}));
+
namespace BlockStatementTests {
class BlockStatementTest : public TestWithParams {
protected:
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 1b5c71a..2425f51 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -4278,6 +4278,9 @@
[&](const ast::SwitchStatement* s) {
return handle_attributes(s, sem, "switch statements");
},
+ [&](const ast::WhileStatement* w) {
+ return handle_attributes(w, sem, "while statements");
+ },
[&](Default) { return true; })) {
return nullptr;
}
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
index e7ee8cd..274e5ad 100644
--- a/src/tint/resolver/uniformity_test.cc
+++ b/src/tint/resolver/uniformity_test.cc
@@ -8504,6 +8504,55 @@
}
}
+TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnWhileStatement_CallInCondition) {
+ 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"(while (non_uniform == 42 && dpdx(1.0) > 0.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, AttributeOnWhileStatement_CallInBody) {
+ auto& param = GetParam();
+ utils::StringStream ss;
+ ss << R"(
+@group(0) @binding(0) var<storage, read_write> non_uniform : i32;
+@group(0) @binding(1) var t : texture_2d<f32>;
+@group(0) @binding(2) var s : sampler;
+fn foo() {
+ )"
+ << "@diagnostic(" << param << ", derivative_uniformity)"
+ << R"(while (non_uniform == 42) {
+ let color = textureSample(t, s, vec2(0, 0));
+ }
+}
+)";
+
+ RunTest(ss.str(), param != builtin::DiagnosticSeverity::kError);
+ if (param == builtin::DiagnosticSeverity::kOff) {
+ EXPECT_TRUE(error_.empty());
+ } else {
+ utils::StringStream err;
+ err << ToStr(param) << ": 'textureSample' must only be called";
+ EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
+ }
+}
+
INSTANTIATE_TEST_SUITE_P(UniformityAnalysisTest,
UniformityAnalysisDiagnosticFilterTest,
::testing::Values(builtin::DiagnosticSeverity::kError,
diff --git a/src/tint/sem/diagnostic_severity_test.cc b/src/tint/sem/diagnostic_severity_test.cc
index 328e589..b487663 100644
--- a/src/tint/sem/diagnostic_severity_test.cc
+++ b/src/tint/sem/diagnostic_severity_test.cc
@@ -50,6 +50,11 @@
// return;
// }
// }
+ //
+ // @diagnostic(error, chromium_unreachable_code)
+ // while (false) @diagnostic(warning, chromium_unreachable_code) {
+ // return;
+ // }
// }
// }
//
@@ -64,6 +69,8 @@
auto else_body_severity = builtin::DiagnosticSeverity::kInfo;
auto switch_severity = builtin::DiagnosticSeverity::kError;
auto case_severity = builtin::DiagnosticSeverity::kWarning;
+ auto while_severity = builtin::DiagnosticSeverity::kError;
+ auto while_body_severity = builtin::DiagnosticSeverity::kWarning;
auto attr = [&](auto severity) {
return utils::Vector{DiagnosticAttribute(severity, "chromium_unreachable_code")};
};
@@ -74,6 +81,7 @@
auto* return_foo_block = Return();
auto* return_foo_case = Return();
auto* return_foo_default = 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));
auto* if_foo = If(Expr(true), Block(utils::Vector{return_foo_if}, attr(if_body_severity)),
@@ -82,7 +90,10 @@
Case(CaseSelector(0_a), Block(utils::Vector{return_foo_case}, attr(case_severity)));
auto* swtch = Switch(42_a, utils::Vector{case_stmt, DefaultCase(Block(return_foo_default))},
attr(switch_severity));
- auto* block_1 = Block(utils::Vector{if_foo, return_foo_block, swtch}, attr(block_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, 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});
@@ -110,6 +121,10 @@
EXPECT_EQ(p.Sem().DiagnosticSeverity(case_stmt->body, rule), case_severity);
EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_case, rule), case_severity);
EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_default, rule), switch_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);
+ EXPECT_EQ(p.Sem().DiagnosticSeverity(return_foo_while, rule), while_body_severity);
EXPECT_EQ(p.Sem().DiagnosticSeverity(bar, rule), global_severity);
EXPECT_EQ(p.Sem().DiagnosticSeverity(return_bar, rule), global_severity);
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index 9b47f10..7446d35 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -1120,6 +1120,14 @@
bool GeneratorImpl::EmitWhile(const ast::WhileStatement* stmt) {
{
auto out = line();
+
+ if (!stmt->attributes.IsEmpty()) {
+ if (!EmitAttributes(out, stmt->attributes)) {
+ return false;
+ }
+ out << " ";
+ }
+
out << "while";
{
ScopedParen sp(out);
diff --git a/test/tint/diagnostic_filtering/while_loop_attribute.wgsl b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl
new file mode 100644
index 0000000..0c3458a
--- /dev/null
+++ b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl
@@ -0,0 +1,7 @@
+@fragment
+fn main(@location(0) x : f32) {
+ var v = vec4<f32>(0);
+ @diagnostic(warning, derivative_uniformity)
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ }
+}
diff --git a/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.dxc.hlsl b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..9d6319b
--- /dev/null
+++ b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.dxc.hlsl
@@ -0,0 +1,31 @@
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 warning: 'dpdx' must only be called from uniform control flow
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:3 note: control flow depends on possibly non-uniform value
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 note: return value of 'dpdx' may be non-uniform
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+struct tint_symbol_1 {
+ float x : TEXCOORD0;
+};
+
+void main_inner(float x) {
+ float4 v = (0.0f).xxxx;
+ while (true) {
+ bool tint_tmp = (x > 0.0f);
+ if (tint_tmp) {
+ tint_tmp = (ddx(1.0f) > 0.0f);
+ }
+ if (!((tint_tmp))) { break; }
+ }
+}
+
+void main(tint_symbol_1 tint_symbol) {
+ main_inner(tint_symbol.x);
+ return;
+}
diff --git a/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.fxc.hlsl b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..9d6319b
--- /dev/null
+++ b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.fxc.hlsl
@@ -0,0 +1,31 @@
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 warning: 'dpdx' must only be called from uniform control flow
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:3 note: control flow depends on possibly non-uniform value
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 note: return value of 'dpdx' may be non-uniform
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+struct tint_symbol_1 {
+ float x : TEXCOORD0;
+};
+
+void main_inner(float x) {
+ float4 v = (0.0f).xxxx;
+ while (true) {
+ bool tint_tmp = (x > 0.0f);
+ if (tint_tmp) {
+ tint_tmp = (ddx(1.0f) > 0.0f);
+ }
+ if (!((tint_tmp))) { break; }
+ }
+}
+
+void main(tint_symbol_1 tint_symbol) {
+ main_inner(tint_symbol.x);
+ return;
+}
diff --git a/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.glsl b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.glsl
new file mode 100644
index 0000000..a37e0fb6
--- /dev/null
+++ b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.glsl
@@ -0,0 +1,31 @@
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 warning: 'dpdx' must only be called from uniform control flow
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:3 note: control flow depends on possibly non-uniform value
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 note: return value of 'dpdx' may be non-uniform
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+#version 310 es
+precision highp float;
+
+layout(location = 0) in float x_1;
+void tint_symbol(float x) {
+ vec4 v = vec4(0.0f);
+ while (true) {
+ bool tint_tmp = (x > 0.0f);
+ if (tint_tmp) {
+ tint_tmp = (dFdx(1.0f) > 0.0f);
+ }
+ if (!((tint_tmp))) { break; }
+ }
+}
+
+void main() {
+ tint_symbol(x_1);
+ return;
+}
diff --git a/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.msl b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.msl
new file mode 100644
index 0000000..2549ca8
--- /dev/null
+++ b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.msl
@@ -0,0 +1,30 @@
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 warning: 'dpdx' must only be called from uniform control flow
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:3 note: control flow depends on possibly non-uniform value
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 note: return value of 'dpdx' may be non-uniform
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_2 {
+ float x [[user(locn0)]];
+};
+
+void tint_symbol_inner(float x) {
+ float4 v = float4(0.0f);
+ while(((x > 0.0f) && (dfdx(1.0f) > 0.0f))) {
+ }
+}
+
+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/while_loop_attribute.wgsl.expected.spvasm b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.spvasm
new file mode 100644
index 0000000..b4ed2fc
--- /dev/null
+++ b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.spvasm
@@ -0,0 +1,76 @@
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 warning: 'dpdx' must only be called from uniform control flow
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:3 note: control flow depends on possibly non-uniform value
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 note: return value of 'dpdx' may be non-uniform
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 34
+; 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 %v "v"
+ 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
+ %v4float = OpTypeVector %float 4
+ %10 = OpConstantNull %v4float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+ %18 = OpConstantNull %float
+ %bool = OpTypeBool
+ %float_1 = OpConstant %float 1
+ %29 = OpTypeFunction %void
+ %main_inner = OpFunction %void None %4
+ %x = OpFunctionParameter %float
+ %8 = OpLabel
+ %v = OpVariable %_ptr_Function_v4float Function %10
+ OpStore %v %10
+ OpBranch %13
+ %13 = OpLabel
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ %19 = OpFOrdGreaterThan %bool %x %18
+ OpSelectionMerge %21 None
+ OpBranchConditional %19 %22 %21
+ %22 = OpLabel
+ %23 = OpDPdx %float %float_1
+ %25 = OpFOrdGreaterThan %bool %23 %18
+ OpBranch %21
+ %21 = OpLabel
+ %26 = OpPhi %bool %19 %16 %25 %22
+ %17 = OpLogicalNot %bool %26
+ OpSelectionMerge %27 None
+ OpBranchConditional %17 %28 %27
+ %28 = OpLabel
+ OpBranch %14
+ %27 = OpLabel
+ OpBranch %15
+ %15 = OpLabel
+ OpBranch %13
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %29
+ %31 = OpLabel
+ %33 = OpLoad %float %x_1
+ %32 = OpFunctionCall %void %main_inner %33
+ OpReturn
+ OpFunctionEnd
diff --git a/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.wgsl b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.wgsl
new file mode 100644
index 0000000..fb738ce
--- /dev/null
+++ b/test/tint/diagnostic_filtering/while_loop_attribute.wgsl.expected.wgsl
@@ -0,0 +1,18 @@
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 warning: 'dpdx' must only be called from uniform control flow
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:3 note: control flow depends on possibly non-uniform value
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^
+
+diagnostic_filtering/while_loop_attribute.wgsl:5:21 note: return value of 'dpdx' may be non-uniform
+ while (x > 0.0 && dpdx(1.0) > 0.0) {
+ ^^^^^^^^^
+
+@fragment
+fn main(@location(0) x : f32) {
+ var v = vec4<f32>(0);
+ @diagnostic(warning, derivative_uniformity) while(((x > 0.0) && (dpdx(1.0) > 0.0))) {
+ }
+}