tint: Handle @diagnostic on block statements

Use expect_compound_statement() in all the places that use
compound_statement in the WGSL grammar.

Handle attributes on statements inside Resolver::StatementScope, so
that the logic can be reused for the various places where block
statements are used. This will also make it easier to reuse this logic
when we allow these attributes on other types of statement in the
future.

Add an `EmitBlockHeader()` helper to the WGSL writer to reuse the
logic for emitting attributes on block statements for all the places
that use them.

Bug: tint:1809
Change-Id: Iac3bb01f5031e6134c1798ddafdad080412c8bef
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/118000
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: James Price <jrprice@google.com>
This commit is contained in:
James Price
2023-02-01 23:14:10 +00:00
committed by Dawn LUCI CQ
parent e60a579c19
commit d9f659670d
84 changed files with 2506 additions and 112 deletions

View File

@@ -1030,6 +1030,104 @@ TEST_F(OverrideAttributeTest, DuplicateAttribute) {
12:34 note: first attribute declared here)");
}
namespace BlockStatementTests {
class BlockStatementTest : public TestWithParams {
protected:
void Check() {
if (GetParam().should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "error: attribute is not valid for block statements");
}
}
};
TEST_P(BlockStatementTest, CompoundStatement) {
Func("foo", utils::Empty, ty.void_(),
utils::Vector{
Block(utils::Vector{Return()}, createAttributes({}, *this, GetParam().kind)),
});
Check();
}
TEST_P(BlockStatementTest, FunctionBody) {
Func("foo", utils::Empty, ty.void_(),
utils::Vector{
Block(utils::Vector{Return()}),
},
utils::Empty, utils::Empty, createAttributes({}, *this, GetParam().kind));
Check();
}
TEST_P(BlockStatementTest, IfStatementBody) {
Func("foo", utils::Empty, ty.void_(),
utils::Vector{
If(Expr(true),
Block(utils::Vector{Return()}, createAttributes({}, *this, GetParam().kind))),
});
Check();
}
TEST_P(BlockStatementTest, ElseStatementBody) {
Func("foo", utils::Empty, ty.void_(),
utils::Vector{
If(Expr(true), Block(utils::Vector{Return()}),
Else(Block(utils::Vector{Return()}, createAttributes({}, *this, GetParam().kind)))),
});
Check();
}
TEST_P(BlockStatementTest, ForStatementBody) {
Func("foo", utils::Empty, ty.void_(),
utils::Vector{
For(nullptr, Expr(true), nullptr,
Block(utils::Vector{Break()}, createAttributes({}, *this, GetParam().kind))),
});
Check();
}
TEST_P(BlockStatementTest, WhileStatementBody) {
Func("foo", utils::Empty, ty.void_(),
utils::Vector{
While(Expr(true),
Block(utils::Vector{Break()}, createAttributes({}, *this, GetParam().kind))),
});
Check();
}
TEST_P(BlockStatementTest, CaseStatementBody) {
Func("foo", utils::Empty, ty.void_(),
utils::Vector{
Switch(1_a,
Case(CaseSelector(1_a), Block(utils::Vector{Break()},
createAttributes({}, *this, GetParam().kind))),
DefaultCase(Block({}))),
});
Check();
}
TEST_P(BlockStatementTest, DefaultStatementBody) {
Func("foo", utils::Empty, ty.void_(),
utils::Vector{
Switch(1_a, Case(CaseSelector(1_a), Block()),
DefaultCase(Block(utils::Vector{Break()},
createAttributes({}, *this, GetParam().kind)))),
});
Check();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
BlockStatementTest,
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::kOffset, false},
TestParams{AttributeKind::kSize, false},
TestParams{AttributeKind::kStage, false},
TestParams{AttributeKind::kStride, false},
TestParams{AttributeKind::kWorkgroup, false},
TestParams{AttributeKind::kBindingAndGroup, false}));
} // namespace BlockStatementTests
} // namespace
} // namespace AttributeTests

View File

@@ -181,6 +181,66 @@ TEST_F(ResolverDiagnosticControlTest, FunctionAttributeScope) {
89:10 note: code is unreachable)");
}
TEST_F(ResolverDiagnosticControlTest, BlockAttributeScope) {
// fn foo() @diagnostic(off, chromium_unreachable_code) {
// {
// return;
// return; // Should not produce a diagnostic
// }
// @diagnostic(warning, chromium_unreachable_code) {
// if (true) @diagnostic(info, chromium_unreachable_code) {
// return;
// return; // Should produce an info
// } else {
// while (true) @diagnostic(off, chromium_unreachable_code) {
// return;
// return; // Should not produce a diagnostic
// }
// return;
// return; // Should produce an warning
// }
// }
// }
auto attr = [&](auto severity) {
return utils::Vector{DiagnosticAttribute(severity, Expr("chromium_unreachable_code"))};
};
Func("foo", {}, ty.void_(),
utils::Vector{
Return(),
Return(Source{{12, 21}}),
Block(utils::Vector{
Block(
utils::Vector{
If(Expr(true),
Block(
utils::Vector{
Return(),
Return(Source{{34, 43}}),
},
attr(ast::DiagnosticSeverity::kInfo)),
Else(Block(utils::Vector{
While(
Expr(true), Block(
utils::Vector{
Return(),
Return(Source{{56, 65}}),
},
attr(ast::DiagnosticSeverity::kOff))),
Return(),
Return(Source{{78, 87}}),
}))),
},
attr(ast::DiagnosticSeverity::kWarning)),
}),
},
attr(ast::DiagnosticSeverity::kOff));
EXPECT_TRUE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), R"(34:43 note: code is unreachable
78:87 warning: code is unreachable)");
}
TEST_F(ResolverDiagnosticControlTest, UnrecognizedRuleName_Directive) {
DiagnosticDirective(ast::DiagnosticSeverity::kError,
Expr(Source{{12, 34}}, "chromium_unreachable_cod"));

View File

@@ -3845,6 +3845,43 @@ SEM* Resolver::StatementScope(const ast::Statement* ast, SEM* sem, F&& callback)
auto* as_compound = As<sem::CompoundStatement, CastFlags::kDontErrorOnImpossibleCast>(sem);
// Helper to handle attributes that are supported on certain types of statement.
auto handle_attributes = [&](auto* stmt, sem::Statement* sem_stmt, const char* use) {
for (auto* attr : stmt->attributes) {
Mark(attr);
if (auto* dc = attr->template As<ast::DiagnosticAttribute>()) {
Mark(dc->control);
if (!DiagnosticControl(dc->control)) {
return false;
}
} else {
std::ostringstream ss;
ss << "attribute is not valid for " << use;
AddError(ss.str(), attr->source);
return false;
}
}
if (!validator_.NoDuplicateAttributes(stmt->attributes)) {
return false;
}
ApplyDiagnosticSeverities(sem_stmt);
return true;
};
// Handle attributes, if necessary.
// Some statements can take diagnostic filtering attributes, so push a new diagnostic filter
// scope to capture them.
validator_.DiagnosticFilters().Push();
TINT_DEFER(validator_.DiagnosticFilters().Pop());
if (!Switch(
ast, //
[&](const ast::BlockStatement* block) {
return handle_attributes(block, sem, "block statements");
},
[&](Default) { return true; })) {
return nullptr;
}
TINT_SCOPED_ASSIGNMENT(current_statement_, sem);
TINT_SCOPED_ASSIGNMENT(current_compound_statement_,
as_compound ? as_compound : current_compound_statement_);

View File

@@ -7942,6 +7942,32 @@ TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnFunction) {
}
}
TEST_P(UniformityAnalysisDiagnosticFilterTest, AttributeOnBlock) {
auto& param = GetParam();
std::ostringstream 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() {
if (non_uniform == 42))"
<< "@diagnostic(" << param << ", derivative_uniformity)"
<< R"({
let color = textureSample(t, s, vec2(0, 0));
}
}
)";
RunTest(ss.str(), param != ast::DiagnosticSeverity::kError);
if (param == ast::DiagnosticSeverity::kOff) {
EXPECT_TRUE(error_.empty());
} else {
std::ostringstream err;
err << ToStr(param) << ": 'textureSample' must only be called";
EXPECT_THAT(error_, ::testing::HasSubstr(err.str()));
}
}
INSTANTIATE_TEST_SUITE_P(UniformityAnalysisTest,
UniformityAnalysisDiagnosticFilterTest,
::testing::Values(ast::DiagnosticSeverity::kError,