Import Tint changes from Dawn
Changes:
- 79195ca42a7712ce671faff3bc9f96010874cbd3 tint/uniformity: implement analysis for full and partial ... by Antonio Maiorano <amaiorano@google.com>
- 33a090f90f73277e5ea9959c21d35f7586688148 Remove unused SwitchStatement method. by dan sinclair <dsinclair@chromium.org>
- 29fb8f8eef24ea3c80d3c2545a07e798e7f8e199 tint: optimize compile time for const_eval_*_test files by Antonio Maiorano <amaiorano@google.com>
- 3fd42ae042e0a97de8af43061b739f3b492629a1 Convert the location attribute to expressions. by dan sinclair <dsinclair@chromium.org>
- 155165cd52df5692d7af7f6158fb21d7490a4c88 Convert the id attribute to expressions. by dan sinclair <dsinclair@chromium.org>
- f50ad7f63d89d8ba07076856924e2bbd7f53b1a0 tint/resolver: Make member attribute diagnostics consistent by Ben Clayton <bclayton@google.com>
- df3a0462ad69db351d84ab24ce23279f566f6e78 tint/sem: Remove 'sem_' prefix from array / struct tests by Ben Clayton <bclayton@google.com>
- c574151e7293f5ab2961162d7b134d70990e3bd6 tint: Remove junk from copyright header by Ben Clayton <bclayton@google.com>
- cd4b6c147912a1f0d3a198667a31e32e96950e51 tint/sem: Add missing 'const' to static const char* by Ben Clayton <bclayton@google.com>
- 4e0689b6653c276b626c958081786b36aee0e8ce tint/sem: Move variable decls to CompoundStatement by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: 79195ca42a7712ce671faff3bc9f96010874cbd3
Change-Id: I4f27d61d0452e06b2be66ee8c03605f7b7e59074
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/106240
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Copybara Prod <copybara-worker-blackhole@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index e87e7cd..5ef8252 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1163,6 +1163,7 @@
tint_unittests_source_set("tint_unittests_sem_src") {
sources = [
+ "sem/array_test.cc",
"sem/atomic_test.cc",
"sem/bool_test.cc",
"sem/builtin_test.cc",
@@ -1179,9 +1180,8 @@
"sem/reference_test.cc",
"sem/sampled_texture_test.cc",
"sem/sampler_test.cc",
- "sem/sem_array_test.cc",
- "sem/sem_struct_test.cc",
"sem/storage_texture_test.cc",
+ "sem/struct_test.cc",
"sem/texture_test.cc",
"sem/type_manager_test.cc",
"sem/type_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index b3dd6ce..0811598 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -838,6 +838,7 @@
resolver/variable_test.cc
resolver/variable_validation_test.cc
scope_stack_test.cc
+ sem/array_test.cc
sem/atomic.cc
sem/bool_test.cc
sem/builtin_test.cc
@@ -854,9 +855,8 @@
sem/reference_test.cc
sem/sampled_texture_test.cc
sem/sampler_test.cc
- sem/sem_array_test.cc
- sem/sem_struct_test.cc
sem/storage_texture_test.cc
+ sem/struct_test.cc
sem/texture_test.cc
sem/type_manager_test.cc
sem/type_test.cc
diff --git a/src/tint/ast/storage_texture_test.cc b/src/tint/ast/storage_texture_test.cc
index 8f1b221..41f3d65 100644
--- a/src/tint/ast/storage_texture_test.cc
+++ b/src/tint/ast/storage_texture_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2020 The Tint Authors->
+// Copyright 2020 The Tint Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/src/tint/ast/switch_statement.h b/src/tint/ast/switch_statement.h
index 82a9aa4..eb083f0 100644
--- a/src/tint/ast/switch_statement.h
+++ b/src/tint/ast/switch_statement.h
@@ -38,9 +38,6 @@
SwitchStatement(SwitchStatement&&);
~SwitchStatement() override;
- /// @returns true if this is a default statement
- bool IsDefault() const { return condition == nullptr; }
-
/// Clones this node and all transitive child nodes using the `CloneContext`
/// `ctx`.
/// @param ctx the clone context
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 7c06020..8d6040d 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -3495,15 +3495,16 @@
if (t == "id") {
const char* use = "id attribute";
return expect_paren_block(use, [&]() -> Result {
- auto val = expect_positive_sint(use);
- if (val.errored) {
+ auto expr = expression();
+ if (expr.errored) {
return Failure::kErrored;
}
+ if (!expr.matched) {
+ return add_error(peek(), "expected id expression");
+ }
match(Token::Type::kComma);
- return create<ast::IdAttribute>(
- t.source(), create<ast::IntLiteralExpression>(
- val.value, ast::IntLiteralExpression::Suffix::kNone));
+ return create<ast::IdAttribute>(t.source(), expr.value);
});
}
@@ -3538,15 +3539,16 @@
if (t == "location") {
const char* use = "location attribute";
return expect_paren_block(use, [&]() -> Result {
- auto val = expect_positive_sint(use);
- if (val.errored) {
+ auto expr = expression();
+ if (expr.errored) {
return Failure::kErrored;
}
+ if (!expr.matched) {
+ return add_error(peek(), "expected location expression");
+ }
match(Token::Type::kComma);
- return builder_.Location(t.source(),
- create<ast::IntLiteralExpression>(
- val.value, ast::IntLiteralExpression::Suffix::kNone));
+ return builder_.Location(t.source(), expr.value);
});
}
diff --git a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
index 0fc27dd..9416a4d 100644
--- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
@@ -930,7 +930,7 @@
)");
}
-TEST_F(ParserImplErrorTest, GlobalDeclVarAttrListMissingComma) {
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrListMissingAt) {
EXPECT("@location(1) group(2) var i : i32;",
R"(test.wgsl:1:14 error: expected declaration after attributes
@location(1) group(2) var i : i32;
@@ -959,10 +959,34 @@
}
TEST_F(ParserImplErrorTest, GlobalDeclVarAttrLocationInvalidValue) {
- EXPECT("@location(x) var i : i32;",
- R"(test.wgsl:1:11 error: expected signed integer literal for location attribute
-@location(x) var i : i32;
- ^
+ EXPECT("@location(if) var i : i32;",
+ R"(test.wgsl:1:11 error: expected location expression
+@location(if) var i : i32;
+ ^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrIdMissingLParen) {
+ EXPECT("@id 1) var i : i32;",
+ R"(test.wgsl:1:5 error: expected '(' for id attribute
+@id 1) var i : i32;
+ ^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrIdMissingRParen) {
+ EXPECT("@id (1 var i : i32;",
+ R"(test.wgsl:1:8 error: expected ')' for id attribute
+@id (1 var i : i32;
+ ^^^
+)");
+}
+
+TEST_F(ParserImplErrorTest, GlobalDeclVarAttrIdInvalidValue) {
+ EXPECT("@id(if) var i : i32;",
+ R"(test.wgsl:1:5 error: expected id expression
+@id(if) var i : i32;
+ ^^
)");
}
diff --git a/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc
index 01ed4d9..0531066 100644
--- a/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_global_constant_decl_test.cc
@@ -263,37 +263,5 @@
ASSERT_EQ(id_attr, nullptr);
}
-TEST_F(ParserImplTest, GlobalOverrideDecl_MissingId) {
- auto p = parser("@id() override a : f32 = 1.");
- auto attrs = p->attribute_list();
- EXPECT_TRUE(attrs.errored);
- EXPECT_FALSE(attrs.matched);
-
- auto e = p->global_constant_decl(attrs.value);
- EXPECT_TRUE(e.matched);
- EXPECT_FALSE(e.errored);
- auto* override = e.value->As<ast::Override>();
- ASSERT_NE(override, nullptr);
-
- EXPECT_TRUE(p->has_error());
- EXPECT_EQ(p->error(), "1:5: expected signed integer literal for id attribute");
-}
-
-TEST_F(ParserImplTest, GlobalOverrideDecl_InvalidId) {
- auto p = parser("@id(-7) override a : f32 = 1.");
- auto attrs = p->attribute_list();
- EXPECT_TRUE(attrs.errored);
- EXPECT_FALSE(attrs.matched);
-
- auto e = p->global_constant_decl(attrs.value);
- EXPECT_TRUE(e.matched);
- EXPECT_FALSE(e.errored);
- auto* override = e.value->As<ast::Override>();
- ASSERT_NE(override, nullptr);
-
- EXPECT_TRUE(p->has_error());
- EXPECT_EQ(p->error(), "1:5: id attribute must be positive");
-}
-
} // namespace
} // namespace tint::reader::wgsl
diff --git a/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc b/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc
index e015033..051104b 100644
--- a/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_struct_attribute_decl_test.cc
@@ -46,7 +46,7 @@
EXPECT_TRUE(attrs.errored);
EXPECT_FALSE(attrs.matched);
EXPECT_TRUE(attrs.value.IsEmpty());
- EXPECT_EQ(p->error(), "1:11: expected signed integer literal for location attribute");
+ EXPECT_EQ(p->error(), "1:11: expected location expression");
}
TEST_F(ParserImplTest, AttributeDecl_MissingParenRight) {
diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
index 86e5487..1b538a2 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
@@ -17,6 +17,105 @@
namespace tint::reader::wgsl {
namespace {
+TEST_F(ParserImplTest, Attribute_Id) {
+ auto p = parser("id(4)");
+ auto attr = p->attribute();
+ EXPECT_TRUE(attr.matched);
+ EXPECT_FALSE(attr.errored);
+ ASSERT_NE(attr.value, nullptr);
+ auto* var_attr = attr.value->As<ast::Attribute>();
+ ASSERT_NE(var_attr, nullptr);
+ ASSERT_FALSE(p->has_error());
+ ASSERT_TRUE(var_attr->Is<ast::IdAttribute>());
+
+ auto* loc = var_attr->As<ast::IdAttribute>();
+ ASSERT_TRUE(loc->expr->Is<ast::IntLiteralExpression>());
+ auto* exp = loc->expr->As<ast::IntLiteralExpression>();
+ EXPECT_EQ(exp->value, 4u);
+}
+
+TEST_F(ParserImplTest, Attribute_Id_Expression) {
+ auto p = parser("id(4 + 5)");
+ auto attr = p->attribute();
+ EXPECT_TRUE(attr.matched);
+ EXPECT_FALSE(attr.errored);
+ ASSERT_NE(attr.value, nullptr);
+ auto* var_attr = attr.value->As<ast::Attribute>();
+ ASSERT_NE(var_attr, nullptr);
+ ASSERT_FALSE(p->has_error());
+ ASSERT_TRUE(var_attr->Is<ast::IdAttribute>());
+
+ auto* loc = var_attr->As<ast::IdAttribute>();
+ ASSERT_TRUE(loc->expr->Is<ast::BinaryExpression>());
+ auto* expr = loc->expr->As<ast::BinaryExpression>();
+
+ EXPECT_EQ(ast::BinaryOp::kAdd, expr->op);
+ auto* v = expr->lhs->As<ast::IntLiteralExpression>();
+ ASSERT_NE(nullptr, v);
+ EXPECT_EQ(v->value, 4u);
+
+ v = expr->rhs->As<ast::IntLiteralExpression>();
+ ASSERT_NE(nullptr, v);
+ EXPECT_EQ(v->value, 5u);
+}
+
+TEST_F(ParserImplTest, Attribute_Id_TrailingComma) {
+ auto p = parser("id(4,)");
+ auto attr = p->attribute();
+ EXPECT_TRUE(attr.matched);
+ EXPECT_FALSE(attr.errored);
+ ASSERT_NE(attr.value, nullptr);
+ auto* var_attr = attr.value->As<ast::Attribute>();
+ ASSERT_NE(var_attr, nullptr);
+ ASSERT_FALSE(p->has_error());
+ ASSERT_TRUE(var_attr->Is<ast::IdAttribute>());
+
+ auto* loc = var_attr->As<ast::IdAttribute>();
+ ASSERT_TRUE(loc->expr->Is<ast::IntLiteralExpression>());
+ auto* exp = loc->expr->As<ast::IntLiteralExpression>();
+ EXPECT_EQ(exp->value, 4u);
+}
+
+TEST_F(ParserImplTest, Attribute_Id_MissingLeftParen) {
+ auto p = parser("id 4)");
+ auto attr = p->attribute();
+ EXPECT_FALSE(attr.matched);
+ EXPECT_TRUE(attr.errored);
+ EXPECT_EQ(attr.value, nullptr);
+ EXPECT_TRUE(p->has_error());
+ EXPECT_EQ(p->error(), "1:4: expected '(' for id attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Id_MissingRightParen) {
+ auto p = parser("id(4");
+ auto attr = p->attribute();
+ EXPECT_FALSE(attr.matched);
+ EXPECT_TRUE(attr.errored);
+ EXPECT_EQ(attr.value, nullptr);
+ EXPECT_TRUE(p->has_error());
+ EXPECT_EQ(p->error(), "1:5: expected ')' for id attribute");
+}
+
+TEST_F(ParserImplTest, Attribute_Id_MissingValue) {
+ auto p = parser("id()");
+ auto attr = p->attribute();
+ EXPECT_FALSE(attr.matched);
+ EXPECT_TRUE(attr.errored);
+ EXPECT_EQ(attr.value, nullptr);
+ EXPECT_TRUE(p->has_error());
+ EXPECT_EQ(p->error(), "1:4: expected id expression");
+}
+
+TEST_F(ParserImplTest, Attribute_Id_MissingInvalid) {
+ auto p = parser("id(if)");
+ auto attr = p->attribute();
+ EXPECT_FALSE(attr.matched);
+ EXPECT_TRUE(attr.errored);
+ EXPECT_EQ(attr.value, nullptr);
+ EXPECT_TRUE(p->has_error());
+ EXPECT_EQ(p->error(), "1:4: expected id expression");
+}
+
TEST_F(ParserImplTest, Attribute_Location) {
auto p = parser("location(4)");
auto attr = p->attribute();
@@ -34,6 +133,31 @@
EXPECT_EQ(exp->value, 4u);
}
+TEST_F(ParserImplTest, Attribute_Location_Expression) {
+ auto p = parser("location(4 + 5)");
+ auto attr = p->attribute();
+ EXPECT_TRUE(attr.matched);
+ EXPECT_FALSE(attr.errored);
+ ASSERT_NE(attr.value, nullptr);
+ auto* var_attr = attr.value->As<ast::Attribute>();
+ ASSERT_NE(var_attr, nullptr);
+ ASSERT_FALSE(p->has_error());
+ ASSERT_TRUE(var_attr->Is<ast::LocationAttribute>());
+
+ auto* loc = var_attr->As<ast::LocationAttribute>();
+ ASSERT_TRUE(loc->expr->Is<ast::BinaryExpression>());
+ auto* expr = loc->expr->As<ast::BinaryExpression>();
+
+ EXPECT_EQ(ast::BinaryOp::kAdd, expr->op);
+ auto* v = expr->lhs->As<ast::IntLiteralExpression>();
+ ASSERT_NE(nullptr, v);
+ EXPECT_EQ(v->value, 4u);
+
+ v = expr->rhs->As<ast::IntLiteralExpression>();
+ ASSERT_NE(nullptr, v);
+ EXPECT_EQ(v->value, 5u);
+}
+
TEST_F(ParserImplTest, Attribute_Location_TrailingComma) {
auto p = parser("location(4,)");
auto attr = p->attribute();
@@ -78,17 +202,17 @@
EXPECT_TRUE(attr.errored);
EXPECT_EQ(attr.value, nullptr);
EXPECT_TRUE(p->has_error());
- EXPECT_EQ(p->error(), "1:10: expected signed integer literal for location attribute");
+ EXPECT_EQ(p->error(), "1:10: expected location expression");
}
TEST_F(ParserImplTest, Attribute_Location_MissingInvalid) {
- auto p = parser("location(nan)");
+ auto p = parser("location(if)");
auto attr = p->attribute();
EXPECT_FALSE(attr.matched);
EXPECT_TRUE(attr.errored);
EXPECT_EQ(attr.value, nullptr);
EXPECT_TRUE(p->has_error());
- EXPECT_EQ(p->error(), "1:10: expected signed integer literal for location attribute");
+ EXPECT_EQ(p->error(), "1:10: expected location expression");
}
struct BuiltinData {
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 552b470..212f594 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -673,7 +673,7 @@
"a", ty.f32(), utils::Vector{MemberAlign(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
- R"(12:34 error: 'align' value must be a positive, power-of-two integer)");
+ R"(12:34 error: @align value must be a positive, power-of-two integer)");
}
TEST_F(StructMemberAttributeTest, Align_Attribute_ConstPowerOfTwo) {
@@ -683,7 +683,7 @@
"a", ty.f32(), utils::Vector{MemberAlign(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
- R"(12:34 error: 'align' value must be a positive, power-of-two integer)");
+ R"(12:34 error: @align value must be a positive, power-of-two integer)");
}
TEST_F(StructMemberAttributeTest, Align_Attribute_ConstF32) {
@@ -692,7 +692,7 @@
Structure("mystruct", utils::Vector{Member(
"a", ty.f32(), utils::Vector{MemberAlign(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), R"(12:34 error: 'align' must be an i32 or u32 value)");
+ EXPECT_EQ(r()->error(), R"(12:34 error: @align must be an i32 or u32 value)");
}
TEST_F(StructMemberAttributeTest, Align_Attribute_ConstU32) {
@@ -717,7 +717,7 @@
Structure("mystruct", utils::Vector{Member(
"a", ty.f32(), utils::Vector{MemberAlign(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), R"(12:34 error: 'align' must be an i32 or u32 value)");
+ EXPECT_EQ(r()->error(), R"(12:34 error: @align must be an i32 or u32 value)");
}
TEST_F(StructMemberAttributeTest, Align_Attribute_Var) {
@@ -757,7 +757,7 @@
Structure("mystruct", utils::Vector{Member(
"a", ty.f32(), utils::Vector{MemberSize(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), R"(12:34 error: 'size' attribute must be positive)");
+ EXPECT_EQ(r()->error(), R"(12:34 error: @size must be a positive integer)");
}
TEST_F(StructMemberAttributeTest, Size_Attribute_ConstF32) {
@@ -766,7 +766,7 @@
Structure("mystruct", utils::Vector{Member(
"a", ty.f32(), utils::Vector{MemberSize(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), R"(12:34 error: 'size' must be an i32 or u32 value)");
+ EXPECT_EQ(r()->error(), R"(12:34 error: @size must be an i32 or u32 value)");
}
TEST_F(StructMemberAttributeTest, Size_Attribute_ConstU32) {
@@ -791,7 +791,7 @@
Structure("mystruct", utils::Vector{Member(
"a", ty.f32(), utils::Vector{MemberSize(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), R"(12:34 error: 'size' must be an i32 or u32 value)");
+ EXPECT_EQ(r()->error(), R"(12:34 error: @size must be an i32 or u32 value)");
}
TEST_F(StructMemberAttributeTest, Size_Attribute_Var) {
@@ -1640,6 +1640,41 @@
EXPECT_EQ(r()->error(), R"(12:34 error: 'group' must be an i32 or u32 value)");
}
+using IdTest = ResolverTest;
+
+TEST_F(IdTest, Const_I32) {
+ Override("val", ty.f32(), utils::Vector{Id(1_i)});
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(IdTest, Const_U32) {
+ Override("val", ty.f32(), utils::Vector{Id(1_u)});
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(IdTest, Const_AInt) {
+ Override("val", ty.f32(), utils::Vector{Id(1_a)});
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(IdTest, Negative) {
+ Override("val", ty.f32(), utils::Vector{Id(Source{{12, 34}}, -1_i)});
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(), R"(12:34 error: 'id' value must be non-negative)");
+}
+
+TEST_F(IdTest, F32) {
+ Override("val", ty.f32(), utils::Vector{Id(Source{{12, 34}}, 1_f)});
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(), R"(12:34 error: 'id' must be an i32 or u32 value)");
+}
+
+TEST_F(IdTest, AFloat) {
+ Override("val", ty.f32(), utils::Vector{Id(Source{{12, 34}}, 1.0_a)});
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(), R"(12:34 error: 'id' must be an i32 or u32 value)");
+}
+
} // namespace
} // namespace InterpolateTests
diff --git a/src/tint/resolver/const_eval_binary_op_test.cc b/src/tint/resolver/const_eval_binary_op_test.cc
index 18b28da..5f43499 100644
--- a/src/tint/resolver/const_eval_binary_op_test.cc
+++ b/src/tint/resolver/const_eval_binary_op_test.cc
@@ -54,47 +54,39 @@
auto op = std::get<0>(GetParam());
auto& c = std::get<1>(GetParam());
- std::visit(
- [&](auto&& expected) {
- using T = typename std::decay_t<decltype(expected)>::ElementType;
- if constexpr (std::is_same_v<T, AInt> || std::is_same_v<T, AFloat>) {
- if (c.overflow) {
- // Overflow is not allowed for abstract types. This is tested separately.
- return;
- }
- }
+ auto* expected = ToValueBase(c.expected);
+ if (expected->IsAbstract() && c.overflow) {
+ // Overflow is not allowed for abstract types. This is tested separately.
+ return;
+ }
- auto* lhs_expr = std::visit([&](auto&& value) { return value.Expr(*this); }, c.lhs);
- auto* rhs_expr = std::visit([&](auto&& value) { return value.Expr(*this); }, c.rhs);
- auto* expr = create<ast::BinaryExpression>(op, lhs_expr, rhs_expr);
+ auto* lhs = ToValueBase(c.lhs);
+ auto* rhs = ToValueBase(c.rhs);
- GlobalConst("C", expr);
- auto* expected_expr = expected.Expr(*this);
- GlobalConst("E", expected_expr);
- ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* lhs_expr = lhs->Expr(*this);
+ auto* rhs_expr = rhs->Expr(*this);
+ auto* expr = create<ast::BinaryExpression>(op, lhs_expr, rhs_expr);
+ GlobalConst("C", expr);
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
- auto* sem = Sem().Get(expr);
- const sem::Constant* value = sem->ConstantValue();
- ASSERT_NE(value, nullptr);
- EXPECT_TYPE(value->Type(), sem->Type());
+ auto* sem = Sem().Get(expr);
+ const sem::Constant* value = sem->ConstantValue();
+ ASSERT_NE(value, nullptr);
+ EXPECT_TYPE(value->Type(), sem->Type());
- auto* expected_sem = Sem().Get(expected_expr);
- const sem::Constant* expected_value = expected_sem->ConstantValue();
- ASSERT_NE(expected_value, nullptr);
- EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
-
- ForEachElemPair(value, expected_value,
- [&](const sem::Constant* a, const sem::Constant* b) {
- EXPECT_EQ(a->As<T>(), b->As<T>());
- if constexpr (IsIntegral<T>) {
- // Check that the constant's integer doesn't contain unexpected
- // data in the MSBs that are outside of the bit-width of T.
- EXPECT_EQ(a->As<AInt>(), b->As<AInt>());
- }
- return HasFailure() ? Action::kStop : Action::kContinue;
- });
- },
- c.expected);
+ auto values_flat = ScalarArgsFrom(value);
+ auto expected_values_flat = expected->Args();
+ ASSERT_EQ(values_flat.values.Length(), expected_values_flat.values.Length());
+ for (size_t i = 0; i < values_flat.values.Length(); ++i) {
+ auto& a = values_flat.values[i];
+ auto& b = expected_values_flat.values[i];
+ EXPECT_EQ(a, b);
+ if (expected->IsIntegral()) {
+ // Check that the constant's integer doesn't contain unexpected
+ // data in the MSBs that are outside of the bit-width of T.
+ EXPECT_EQ(builder::As<AInt>(a), builder::As<AInt>(b));
+ }
+ }
}
INSTANTIATE_TEST_SUITE_P(MixedAbstractArgs,
@@ -658,21 +650,15 @@
TEST_P(ResolverConstEvalBinaryOpTest_Overflow, Test) {
Enable(ast::Extension::kF16);
auto& c = GetParam();
- auto* lhs_expr = std::visit([&](auto&& value) { return value.Expr(*this); }, c.lhs);
- auto* rhs_expr = std::visit([&](auto&& value) { return value.Expr(*this); }, c.rhs);
+ auto* lhs = ToValueBase(c.lhs);
+ auto* rhs = ToValueBase(c.rhs);
+ auto* lhs_expr = lhs->Expr(*this);
+ auto* rhs_expr = rhs->Expr(*this);
auto* expr = create<ast::BinaryExpression>(Source{{1, 1}}, c.op, lhs_expr, rhs_expr);
GlobalConst("C", expr);
ASSERT_FALSE(r()->Resolve());
-
- std::string type_name = std::visit(
- [&](auto&& value) {
- using ValueType = std::decay_t<decltype(value)>;
- return builder::FriendlyName<ValueType>();
- },
- c.lhs);
-
EXPECT_THAT(r()->error(), HasSubstr("1:1 error: '"));
- EXPECT_THAT(r()->error(), HasSubstr("' cannot be represented as '" + type_name + "'"));
+ EXPECT_THAT(r()->error(), HasSubstr("' cannot be represented as '" + lhs->TypeName() + "'"));
}
INSTANTIATE_TEST_SUITE_P(
Test,
@@ -854,10 +840,8 @@
using ResolverConstEvalShiftLeftConcreteGeqBitWidthError =
ResolverTestWithParam<std::tuple<Types, Types>>;
TEST_P(ResolverConstEvalShiftLeftConcreteGeqBitWidthError, Test) {
- auto* lhs_expr =
- std::visit([&](auto&& value) { return value.Expr(*this); }, std::get<0>(GetParam()));
- auto* rhs_expr =
- std::visit([&](auto&& value) { return value.Expr(*this); }, std::get<1>(GetParam()));
+ auto* lhs_expr = ToValueBase(std::get<0>(GetParam()))->Expr(*this);
+ auto* rhs_expr = ToValueBase(std::get<1>(GetParam()))->Expr(*this);
GlobalConst("c", Shl(Source{{1, 1}}, lhs_expr, rhs_expr));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
@@ -880,10 +864,8 @@
// AInt left shift results in sign change error
using ResolverConstEvalShiftLeftSignChangeError = ResolverTestWithParam<std::tuple<Types, Types>>;
TEST_P(ResolverConstEvalShiftLeftSignChangeError, Test) {
- auto* lhs_expr =
- std::visit([&](auto&& value) { return value.Expr(*this); }, std::get<0>(GetParam()));
- auto* rhs_expr =
- std::visit([&](auto&& value) { return value.Expr(*this); }, std::get<1>(GetParam()));
+ auto* lhs_expr = ToValueBase(std::get<0>(GetParam()))->Expr(*this);
+ auto* rhs_expr = ToValueBase(std::get<1>(GetParam()))->Expr(*this);
GlobalConst("c", Shl(Source{{1, 1}}, lhs_expr, rhs_expr));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "1:1 error: shift left operation results in sign change");
diff --git a/src/tint/resolver/const_eval_builtin_test.cc b/src/tint/resolver/const_eval_builtin_test.cc
index 3936fba..86223ba 100644
--- a/src/tint/resolver/const_eval_builtin_test.cc
+++ b/src/tint/resolver/const_eval_builtin_test.cc
@@ -83,54 +83,57 @@
std::visit([&](auto&& v) { args.Push(v.Expr(*this)); }, a);
}
- std::visit(
- [&](auto&& expected) {
- using T = typename std::decay_t<decltype(expected)>::ElementType;
- auto* expr = Call(sem::str(builtin), std::move(args));
+ auto* expected = ToValueBase(c.expected);
+ auto* expr = Call(sem::str(builtin), std::move(args));
- GlobalConst("C", expr);
- auto* expected_expr = expected.Expr(*this);
- GlobalConst("E", expected_expr);
+ GlobalConst("C", expr);
+ auto* expected_expr = expected->Expr(*this);
+ GlobalConst("E", expected_expr);
- EXPECT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
- auto* sem = Sem().Get(expr);
- const sem::Constant* value = sem->ConstantValue();
- ASSERT_NE(value, nullptr);
- EXPECT_TYPE(value->Type(), sem->Type());
+ auto* sem = Sem().Get(expr);
+ const sem::Constant* value = sem->ConstantValue();
+ ASSERT_NE(value, nullptr);
+ EXPECT_TYPE(value->Type(), sem->Type());
- auto* expected_sem = Sem().Get(expected_expr);
- const sem::Constant* expected_value = expected_sem->ConstantValue();
- ASSERT_NE(expected_value, nullptr);
- EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
+ auto* expected_sem = Sem().Get(expected_expr);
+ const sem::Constant* expected_value = expected_sem->ConstantValue();
+ ASSERT_NE(expected_value, nullptr);
+ EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
- ForEachElemPair(value, expected_value,
- [&](const sem::Constant* a, const sem::Constant* b) {
- auto v = a->As<T>();
- auto e = b->As<T>();
- if constexpr (std::is_same_v<bool, T>) {
- EXPECT_EQ(v, e);
- } else if constexpr (IsFloatingPoint<T>) {
- if (std::isnan(e)) {
- EXPECT_TRUE(std::isnan(v));
- } else {
- auto vf = (c.expected_pos_or_neg ? Abs(v) : v);
- if (c.float_compare) {
- EXPECT_FLOAT_EQ(vf, e);
- } else {
- EXPECT_EQ(vf, e);
- }
- }
- } else {
- EXPECT_EQ((c.expected_pos_or_neg ? Abs(v) : v), e);
- // Check that the constant's integer doesn't contain unexpected
- // data in the MSBs that are outside of the bit-width of T.
- EXPECT_EQ(a->As<AInt>(), b->As<AInt>());
- }
- return HasFailure() ? Action::kStop : Action::kContinue;
- });
- },
- c.expected);
+ // @TODO(amaiorano): Rewrite using ScalarArgsFrom()
+ ForEachElemPair(value, expected_value, [&](const sem::Constant* a, const sem::Constant* b) {
+ std::visit(
+ [&](auto&& ct_expected) {
+ using T = typename std::decay_t<decltype(ct_expected)>::ElementType;
+
+ auto v = a->As<T>();
+ auto e = b->As<T>();
+ if constexpr (std::is_same_v<bool, T>) {
+ EXPECT_EQ(v, e);
+ } else if constexpr (IsFloatingPoint<T>) {
+ if (std::isnan(e)) {
+ EXPECT_TRUE(std::isnan(v));
+ } else {
+ auto vf = (c.expected_pos_or_neg ? Abs(v) : v);
+ if (c.float_compare) {
+ EXPECT_FLOAT_EQ(vf, e);
+ } else {
+ EXPECT_EQ(vf, e);
+ }
+ }
+ } else {
+ EXPECT_EQ((c.expected_pos_or_neg ? Abs(v) : v), e);
+ // Check that the constant's integer doesn't contain unexpected
+ // data in the MSBs that are outside of the bit-width of T.
+ EXPECT_EQ(a->As<AInt>(), b->As<AInt>());
+ }
+ },
+ c.expected);
+
+ return HasFailure() ? Action::kStop : Action::kContinue;
+ });
}
INSTANTIATE_TEST_SUITE_P( //
diff --git a/src/tint/resolver/const_eval_conversion_test.cc b/src/tint/resolver/const_eval_conversion_test.cc
index 35657ee..7e6a6fc 100644
--- a/src/tint/resolver/const_eval_conversion_test.cc
+++ b/src/tint/resolver/const_eval_conversion_test.cc
@@ -29,20 +29,7 @@
builder::Value<bool>>;
static std::ostream& operator<<(std::ostream& o, const Scalar& scalar) {
- std::visit(
- [&](auto&& v) {
- using ValueType = std::decay_t<decltype(v)>;
- o << ValueType::DataType::Name() << "(";
- for (auto& a : v.args.values) {
- o << std::get<typename ValueType::ElementType>(a);
- if (&a != &v.args.values.Back()) {
- o << ", ";
- }
- }
- o << ")";
- },
- scalar);
- return o;
+ return ToValueBase(scalar)->Print(o);
}
enum class Kind {
@@ -96,7 +83,7 @@
const auto& type = std::get<1>(GetParam()).type;
const auto unrepresentable = std::get<1>(GetParam()).unrepresentable;
- auto* input_val = std::visit([&](auto val) { return val.Expr(*this); }, input);
+ auto* input_val = ToValueBase(input)->Expr(*this);
auto* expr = Construct(type.ast(*this), input_val);
if (kind == Kind::kVector) {
expr = Construct(ty.vec(nullptr, 3), expr);
@@ -120,7 +107,7 @@
ASSERT_NE(sem->ConstantValue(), nullptr);
EXPECT_TYPE(sem->ConstantValue()->Type(), target_sem_ty);
- auto expected_values = std::visit([&](auto&& val) { return val.args; }, expected);
+ auto expected_values = ToValueBase(expected)->Args();
if (kind == Kind::kVector) {
expected_values.values.Push(expected_values.values[0]);
expected_values.values.Push(expected_values.values[0]);
diff --git a/src/tint/resolver/const_eval_test.h b/src/tint/resolver/const_eval_test.h
index 3840540..2761daf 100644
--- a/src/tint/resolver/const_eval_test.h
+++ b/src/tint/resolver/const_eval_test.h
@@ -41,6 +41,8 @@
inline void CollectScalarArgs(const sem::Constant* c, builder::ScalarArgs& args) {
Switch(
c->Type(), //
+ [&](const sem::AbstractInt*) { args.values.Push(c->As<AInt>()); },
+ [&](const sem::AbstractFloat*) { args.values.Push(c->As<AFloat>()); },
[&](const sem::Bool*) { args.values.Push(c->As<bool>()); },
[&](const sem::I32*) { args.values.Push(c->As<i32>()); },
[&](const sem::U32*) { args.values.Push(c->As<u32>()); },
@@ -136,6 +138,7 @@
using builder::Mat;
using builder::Val;
using builder::Value;
+using builder::ValueBase;
using builder::Vec;
using Types = std::variant< //
@@ -188,21 +191,18 @@
//
>;
+/// Returns the current Value<T> in the `types` variant as a `ValueBase` pointer to use the
+/// polymorphic API. This trades longer compile times using std::variant for longer runtime via
+/// virtual function calls.
+template <typename ValueVariant>
+inline const ValueBase* ToValueBase(const ValueVariant& types) {
+ return std::visit(
+ [](auto&& t) -> const ValueBase* { return static_cast<const ValueBase*>(&t); }, types);
+}
+
+/// Prints Types to ostream
inline std::ostream& operator<<(std::ostream& o, const Types& types) {
- std::visit(
- [&](auto&& v) {
- using ValueType = std::decay_t<decltype(v)>;
- o << ValueType::DataType::Name() << "(";
- for (auto& a : v.args.values) {
- o << std::get<typename ValueType::ElementType>(a);
- if (&a != &v.args.values.Back()) {
- o << ", ";
- }
- }
- o << ")";
- },
- types);
- return o;
+ return ToValueBase(types)->Print(o);
}
// Calls `f` on deepest elements of both `a` and `b`. If function returns Action::kStop, it stops
diff --git a/src/tint/resolver/const_eval_unary_op_test.cc b/src/tint/resolver/const_eval_unary_op_test.cc
index 80b8caa..fced490 100644
--- a/src/tint/resolver/const_eval_unary_op_test.cc
+++ b/src/tint/resolver/const_eval_unary_op_test.cc
@@ -51,40 +51,34 @@
auto op = std::get<0>(GetParam());
auto& c = std::get<1>(GetParam());
- std::visit(
- [&](auto&& expected) {
- using T = typename std::decay_t<decltype(expected)>::ElementType;
- auto* input_expr = std::visit([&](auto&& value) { return value.Expr(*this); }, c.input);
- auto* expr = create<ast::UnaryOpExpression>(op, input_expr);
+ auto* expected = ToValueBase(c.expected);
+ auto* input = ToValueBase(c.input);
- GlobalConst("C", expr);
- auto* expected_expr = expected.Expr(*this);
- GlobalConst("E", expected_expr);
- ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* input_expr = input->Expr(*this);
+ auto* expr = create<ast::UnaryOpExpression>(op, input_expr);
- auto* sem = Sem().Get(expr);
- const sem::Constant* value = sem->ConstantValue();
- ASSERT_NE(value, nullptr);
- EXPECT_TYPE(value->Type(), sem->Type());
+ GlobalConst("C", expr);
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
- auto* expected_sem = Sem().Get(expected_expr);
- const sem::Constant* expected_value = expected_sem->ConstantValue();
- ASSERT_NE(expected_value, nullptr);
- EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
+ auto* sem = Sem().Get(expr);
+ const sem::Constant* value = sem->ConstantValue();
+ ASSERT_NE(value, nullptr);
+ EXPECT_TYPE(value->Type(), sem->Type());
- ForEachElemPair(value, expected_value,
- [&](const sem::Constant* a, const sem::Constant* b) {
- EXPECT_EQ(a->As<T>(), b->As<T>());
- if constexpr (IsIntegral<T>) {
- // Check that the constant's integer doesn't contain unexpected
- // data in the MSBs that are outside of the bit-width of T.
- EXPECT_EQ(a->As<AInt>(), b->As<AInt>());
- }
- return HasFailure() ? Action::kStop : Action::kContinue;
- });
- },
- c.expected);
+ auto values_flat = ScalarArgsFrom(value);
+ auto expected_values_flat = expected->Args();
+ ASSERT_EQ(values_flat.values.Length(), expected_values_flat.values.Length());
+ for (size_t i = 0; i < values_flat.values.Length(); ++i) {
+ auto& a = values_flat.values[i];
+ auto& b = expected_values_flat.values[i];
+ EXPECT_EQ(a, b);
+ if (expected->IsIntegral()) {
+ // Check that the constant's integer doesn't contain unexpected
+ // data in the MSBs that are outside of the bit-width of T.
+ EXPECT_EQ(builder::As<AInt>(a), builder::As<AInt>(b));
+ }
+ }
}
INSTANTIATE_TEST_SUITE_P(Complement,
ResolverConstEvalUnaryOpTest,
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index 514e0cb..e84eec5 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -427,6 +427,14 @@
TraverseExpression(group->expr);
return true;
},
+ [&](const ast::IdAttribute* id) {
+ TraverseExpression(id->expr);
+ return true;
+ },
+ [&](const ast::LocationAttribute* loc) {
+ TraverseExpression(loc->expr);
+ return true;
+ },
[&](const ast::StructMemberAlignAttribute* align) {
TraverseExpression(align->expr);
return true;
@@ -445,9 +453,8 @@
return;
}
- if (attr->IsAnyOf<ast::BuiltinAttribute, ast::IdAttribute, ast::InternalAttribute,
- ast::InterpolateAttribute, ast::InvariantAttribute,
- ast::LocationAttribute, ast::StageAttribute, ast::StrideAttribute,
+ if (attr->IsAnyOf<ast::BuiltinAttribute, ast::InternalAttribute, ast::InterpolateAttribute,
+ ast::InvariantAttribute, ast::StageAttribute, ast::StrideAttribute,
ast::StructMemberOffsetAttribute>()) {
return;
}
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
index 150d65a..724a527 100644
--- a/src/tint/resolver/dependency_graph_test.cc
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -1291,6 +1291,8 @@
GlobalVar(Sym(), ty.sampler(ast::SamplerKind::kSampler));
GlobalVar(Sym(), ty.i32(), utils::Vector{Binding(V), Group(V)});
+ GlobalVar(Sym(), ty.i32(), utils::Vector{Location(V)});
+ Override(Sym(), ty.i32(), utils::Vector{Id(V)});
Func(Sym(), utils::Empty, ty.void_(), utils::Empty);
#undef V
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index eebd5ea..c252473 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -449,18 +449,24 @@
sem->SetConstructor(rhs);
if (auto* id_attr = ast::GetAttribute<ast::IdAttribute>(v->attributes)) {
- auto* materialize = Materialize(Expression(id_attr->expr));
- if (!materialize) {
+ ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@id"};
+ TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
+
+ auto* materialized = Materialize(Expression(id_attr->expr));
+ if (!materialized) {
return nullptr;
}
- auto* c = materialize->ConstantValue();
- if (!c) {
- // TODO(crbug.com/tint/1633): Handle invalid materialization when expressions are
- // supported.
+ if (!materialized->Type()->IsAnyOf<sem::I32, sem::U32>()) {
+ AddError("'id' must be an i32 or u32 value", id_attr->source);
return nullptr;
}
- auto value = c->As<uint32_t>();
+ auto const_value = materialized->ConstantValue();
+ auto value = const_value->As<AInt>();
+ if (value < 0) {
+ AddError("'id' value must be non-negative", id_attr->source);
+ return nullptr;
+ }
if (value > std::numeric_limits<decltype(OverrideId::value)>::max()) {
AddError("override IDs must be between 0 and " +
std::to_string(std::numeric_limits<decltype(OverrideId::value)>::max()),
@@ -669,17 +675,22 @@
std::optional<uint32_t> location;
if (auto* attr = ast::GetAttribute<ast::LocationAttribute>(var->attributes)) {
- auto* materialize = Materialize(Expression(attr->expr));
- if (!materialize) {
+ auto* materialized = Materialize(Expression(attr->expr));
+ if (!materialized) {
return nullptr;
}
- auto* c = materialize->ConstantValue();
- if (!c) {
- // TODO(crbug.com/tint/1633): Add error message about invalid materialization
- // when location can be an expression.
+ if (!materialized->Type()->IsAnyOf<sem::I32, sem::U32>()) {
+ AddError("'location' must be an i32 or u32 value", attr->source);
return nullptr;
}
- location = c->As<uint32_t>();
+
+ auto const_value = materialized->ConstantValue();
+ auto value = const_value->As<AInt>();
+ if (value < 0) {
+ AddError("'location' value must be non-negative", attr->source);
+ return nullptr;
+ }
+ location = u32(value);
}
sem = builder_->create<sem::GlobalVariable>(
@@ -2268,21 +2279,16 @@
current_statement_->FindFirstParent<sem::LoopContinuingBlockStatement>()) {
auto* loop_block = continuing_block->FindFirstParent<sem::LoopBlockStatement>();
if (loop_block->FirstContinue()) {
- auto& decls = loop_block->Decls();
// If our identifier is in loop_block->decls, make sure its index is
// less than first_continue
- auto iter = std::find_if(decls.begin(), decls.end(),
- [&symbol](auto* v) { return v->symbol == symbol; });
- if (iter != decls.end()) {
- auto var_decl_index =
- static_cast<size_t>(std::distance(decls.begin(), iter));
- if (var_decl_index >= loop_block->NumDeclsAtFirstContinue()) {
+ if (auto* decl = loop_block->Decls().Find(symbol)) {
+ if (decl->order >= loop_block->NumDeclsAtFirstContinue()) {
AddError("continue statement bypasses declaration of '" +
builder_->Symbols().NameFor(symbol) + "'",
loop_block->FirstContinue()->source);
AddNote("identifier '" + builder_->Symbols().NameFor(symbol) +
"' declared here",
- (*iter)->source);
+ decl->variable->Declaration()->source);
AddNote("identifier '" + builder_->Symbols().NameFor(symbol) +
"' referenced in continuing block here",
expr->source);
@@ -2842,112 +2848,128 @@
std::optional<uint32_t> location;
for (auto* attr : member->attributes) {
Mark(attr);
- if (auto* o = attr->As<ast::StructMemberOffsetAttribute>()) {
- // Offset attributes are not part of the WGSL spec, but are emitted
- // by the SPIR-V reader.
+ bool ok = Switch(
+ attr, //
+ [&](const ast::StructMemberOffsetAttribute* o) {
+ // Offset attributes are not part of the WGSL spec, but are emitted
+ // by the SPIR-V reader.
+ ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant,
+ "@offset value"};
+ TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
- ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant,
- "@offset value"};
- TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
-
- auto* materialized = Materialize(Expression(o->expr));
- if (!materialized) {
- return nullptr;
- }
- auto const_value = materialized->ConstantValue();
- if (!const_value) {
- AddError("'offset' must be constant expression", o->expr->source);
- return nullptr;
- }
- offset = const_value->As<uint64_t>();
-
- if (offset < struct_size) {
- AddError("offsets must be in ascending order", o->source);
- return nullptr;
- }
- align = 1;
- has_offset_attr = true;
- } else if (auto* a = attr->As<ast::StructMemberAlignAttribute>()) {
- ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@align"};
- TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
-
- auto* materialized = Materialize(Expression(a->expr));
- if (!materialized) {
- return nullptr;
- }
- if (!materialized->Type()->IsAnyOf<sem::I32, sem::U32>()) {
- AddError("'align' must be an i32 or u32 value", a->source);
- return nullptr;
- }
-
- auto const_value = materialized->ConstantValue();
- if (!const_value) {
- AddError("'align' must be constant expression", a->source);
- return nullptr;
- }
- auto value = const_value->As<AInt>();
-
- if (value <= 0 || !utils::IsPowerOfTwo(value)) {
- AddError("'align' value must be a positive, power-of-two integer", a->source);
- return nullptr;
- }
- align = u32(value);
- has_align_attr = true;
- } else if (auto* s = attr->As<ast::StructMemberSizeAttribute>()) {
- ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@size"};
- TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
-
- auto* materialized = Materialize(Expression(s->expr));
- if (!materialized) {
- return nullptr;
- }
- if (!materialized->Type()->IsAnyOf<sem::U32, sem::I32>()) {
- AddError("'size' must be an i32 or u32 value", s->source);
- return nullptr;
- }
-
- auto const_value = materialized->ConstantValue();
- if (!const_value) {
- AddError("'size' must be constant expression", s->expr->source);
- return nullptr;
- }
- {
- auto value = const_value->As<AInt>();
- if (value <= 0) {
- AddError("'size' attribute must be positive", s->source);
- return nullptr;
+ auto* materialized = Materialize(Expression(o->expr));
+ if (!materialized) {
+ return false;
}
- }
- auto value = const_value->As<uint64_t>();
- if (value < size) {
- AddError("'size' must be at least as big as the type's size (" +
- std::to_string(size) + ")",
- s->source);
- return nullptr;
- }
- size = u32(value);
- has_size_attr = true;
- } else if (auto* l = attr->As<ast::LocationAttribute>()) {
- ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@location"};
- TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
+ auto const_value = materialized->ConstantValue();
+ if (!const_value) {
+ AddError("@offset must be constant expression", o->expr->source);
+ return false;
+ }
+ offset = const_value->As<uint64_t>();
- auto* materialize = Materialize(Expression(l->expr));
- if (!materialize) {
- return nullptr;
- }
- auto* c = materialize->ConstantValue();
- if (!c) {
- // TODO(crbug.com/tint/1633): Add error message about invalid materialization
- // when location can be an expression.
- return nullptr;
- }
- location = c->As<uint32_t>();
+ if (offset < struct_size) {
+ AddError("offsets must be in ascending order", o->source);
+ return false;
+ }
+ align = 1;
+ has_offset_attr = true;
+ return true;
+ },
+ [&](const ast::StructMemberAlignAttribute* a) {
+ ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@align"};
+ TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
+
+ auto* materialized = Materialize(Expression(a->expr));
+ if (!materialized) {
+ return false;
+ }
+ if (!materialized->Type()->IsAnyOf<sem::I32, sem::U32>()) {
+ AddError("@align must be an i32 or u32 value", a->source);
+ return false;
+ }
+
+ auto const_value = materialized->ConstantValue();
+ if (!const_value) {
+ AddError("@align must be constant expression", a->source);
+ return false;
+ }
+ auto value = const_value->As<AInt>();
+
+ if (value <= 0 || !utils::IsPowerOfTwo(value)) {
+ AddError("@align value must be a positive, power-of-two integer",
+ a->source);
+ return false;
+ }
+ align = u32(value);
+ has_align_attr = true;
+ return true;
+ },
+ [&](const ast::StructMemberSizeAttribute* s) {
+ ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@size"};
+ TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
+
+ auto* materialized = Materialize(Expression(s->expr));
+ if (!materialized) {
+ return false;
+ }
+ if (!materialized->Type()->IsAnyOf<sem::U32, sem::I32>()) {
+ AddError("@size must be an i32 or u32 value", s->source);
+ return false;
+ }
+
+ auto const_value = materialized->ConstantValue();
+ if (!const_value) {
+ AddError("@size must be constant expression", s->expr->source);
+ return false;
+ }
+ {
+ auto value = const_value->As<AInt>();
+ if (value <= 0) {
+ AddError("@size must be a positive integer", s->source);
+ return false;
+ }
+ }
+ auto value = const_value->As<uint64_t>();
+ if (value < size) {
+ AddError("@size must be at least as big as the type's size (" +
+ std::to_string(size) + ")",
+ s->source);
+ return false;
+ }
+ size = u32(value);
+ has_size_attr = true;
+ return true;
+ },
+ [&](const ast::LocationAttribute* l) {
+ ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant,
+ "@location"};
+ TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
+
+ auto* materialize = Materialize(Expression(l->expr));
+ if (!materialize) {
+ return false;
+ }
+ auto* c = materialize->ConstantValue();
+ if (!c) {
+ // TODO(crbug.com/tint/1633): Add error message about invalid
+ // materialization when location can be an expression.
+ return false;
+ }
+ location = c->As<uint32_t>();
+ return true;
+ },
+ [&](Default) {
+ // The validator will check attributes can be applied to the struct member.
+ return true;
+ });
+ if (!ok) {
+ return nullptr;
}
}
if (has_offset_attr && (has_align_attr || has_size_attr)) {
- AddError("offset attributes cannot be used with align or size attributes",
- member->source);
+ AddError("@offset cannot be used with @align or @size", member->source);
return nullptr;
}
@@ -3128,9 +3150,7 @@
}
}
- if (current_block_) { // Not all statements are inside a block
- current_block_->AddDecl(stmt->variable);
- }
+ current_compound_statement_->AddDecl(variable->As<sem::LocalVariable>());
if (auto* ctor = variable->Constructor()) {
sem->Behaviors() = ctor->Behaviors();
@@ -3232,7 +3252,7 @@
if (auto* block = sem->FindFirstParent<sem::LoopBlockStatement>()) {
if (!block->FirstContinue()) {
const_cast<sem::LoopBlockStatement*>(block)->SetFirstContinue(
- stmt, block->Decls().size());
+ stmt, block->Decls().Count());
}
}
@@ -3337,12 +3357,10 @@
builder_->Sem().Add(ast, sem);
auto* as_compound = As<sem::CompoundStatement, CastFlags::kDontErrorOnImpossibleCast>(sem);
- auto* as_block = As<sem::BlockStatement, CastFlags::kDontErrorOnImpossibleCast>(sem);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem);
TINT_SCOPED_ASSIGNMENT(current_compound_statement_,
as_compound ? as_compound : current_compound_statement_);
- TINT_SCOPED_ASSIGNMENT(current_block_, as_block ? as_block : current_block_);
if (!callback()) {
return nullptr;
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index f699996..c25b48e 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -375,11 +375,8 @@
/// * Assigns `sem` to #current_statement_
/// * Assigns `sem` to #current_compound_statement_ if `sem` derives from
/// sem::CompoundStatement.
- /// * Assigns `sem` to #current_block_ if `sem` derives from
- /// sem::BlockStatement.
/// * Then calls `callback`.
- /// * Before returning #current_statement_, #current_compound_statement_, and
- /// #current_block_ are restored to their original values.
+ /// * Before returning #current_statement_ and #current_compound_statement_ are restored to their original values.
/// @returns `sem` if `callback` returns true, otherwise `nullptr`.
template <typename SEM, typename F>
SEM* StatementScope(const ast::Statement* ast, SEM* sem, F&& callback);
@@ -441,7 +438,6 @@
sem::Function* current_function_ = nullptr;
sem::Statement* current_statement_ = nullptr;
sem::CompoundStatement* current_compound_statement_ = nullptr;
- sem::BlockStatement* current_block_ = nullptr;
};
} // namespace tint::resolver
diff --git a/src/tint/resolver/resolver_test_helper.h b/src/tint/resolver/resolver_test_helper.h
index 923f3ff..57fe14a 100644
--- a/src/tint/resolver/resolver_test_helper.h
+++ b/src/tint/resolver/resolver_test_helper.h
@@ -206,6 +206,12 @@
utils::Vector<Storage, 16> values;
};
+/// Returns current variant value in `s` cast to type `T`
+template <typename T>
+T As(ScalarArgs::Storage& s) {
+ return std::visit([](auto&& v) { return static_cast<T>(v); }, s);
+}
+
/// @param o the std::ostream to write to
/// @param args the ScalarArgs
/// @return the std::ostream so calls can be chained
@@ -750,10 +756,45 @@
DataType<T>::Name};
}
+/// Base class for Value<T>
+struct ValueBase {
+ /// Constructor
+ ValueBase() = default;
+ /// Destructor
+ virtual ~ValueBase() = default;
+ /// Move constructor
+ ValueBase(ValueBase&&) = default;
+ /// Copy constructor
+ ValueBase(const ValueBase&) = default;
+ /// Copy assignment operator
+ /// @returns this instance
+ ValueBase& operator=(const ValueBase&) = default;
+ /// Creates an `ast::Expression` for the type T passing in previously stored args
+ /// @param b the ProgramBuilder
+ /// @returns an expression node
+ virtual const ast::Expression* Expr(ProgramBuilder& b) const = 0;
+ /// @returns args used to create expression via `Expr`
+ virtual const ScalarArgs& Args() const = 0;
+ /// @returns true if element type is abstract
+ virtual bool IsAbstract() const = 0;
+ /// @returns true if element type is an integral
+ virtual bool IsIntegral() const = 0;
+ /// @returns element type name
+ virtual std::string TypeName() const = 0;
+ /// Prints this value to the output stream
+ /// @param o the output stream
+ /// @returns input argument `o`
+ virtual std::ostream& Print(std::ostream& o) const = 0;
+};
+
/// Value<T> is an instance of a value of type DataType<T>. Useful for storing values to create
/// expressions with.
template <typename T>
-struct Value {
+struct Value : ValueBase {
+ /// Constructor
+ /// @param a the scalar args
+ explicit Value(ScalarArgs a) : args(std::move(a)) {}
+
/// Alias to T
using Type = T;
/// Alias to DataType<T>
@@ -764,15 +805,43 @@
/// Creates a Value<T> with `args`
/// @param args the args that will be passed to the expression
/// @returns a Value<T>
- static Value Create(ScalarArgs args) { return Value{CreatePtrsFor<T>(), std::move(args)}; }
+ static Value Create(ScalarArgs args) { return Value{std::move(args)}; }
/// Creates an `ast::Expression` for the type T passing in previously stored args
/// @param b the ProgramBuilder
/// @returns an expression node
- const ast::Expression* Expr(ProgramBuilder& b) const { return (*create.expr)(b, args); }
+ const ast::Expression* Expr(ProgramBuilder& b) const override {
+ auto create = CreatePtrsFor<T>();
+ return (*create.expr)(b, args);
+ }
- /// functions to create values / types of the value
- CreatePtrs create;
+ /// @returns args used to create expression via `Expr`
+ const ScalarArgs& Args() const override { return args; }
+
+ /// @returns true if element type is abstract
+ bool IsAbstract() const override { return tint::IsAbstract<ElementType>; }
+
+ /// @returns true if element type is an integral
+ bool IsIntegral() const override { return tint::IsIntegral<ElementType>; }
+
+ /// @returns element type name
+ std::string TypeName() const override { return tint::FriendlyName<ElementType>(); }
+
+ /// Prints this value to the output stream
+ /// @param o the output stream
+ /// @returns input argument `o`
+ std::ostream& Print(std::ostream& o) const override {
+ o << TypeName() << "(";
+ for (auto& a : args.values) {
+ o << std::get<ElementType>(a);
+ if (&a != &args.values.Back()) {
+ o << ", ";
+ }
+ }
+ o << ")";
+ return o;
+ }
+
/// args to create expression with
ScalarArgs args;
};
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index f75536b..84c42da 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -47,6 +47,23 @@
namespace {
+/// Unwraps `u->expr`'s chain of indirect (*) and address-of (&) expressions, returning the first
+/// expression that is neither of these.
+/// E.g. If `u` is `*(&(*(&p)))`, returns `p`.
+const ast::Expression* UnwrapIndirectAndAddressOfChain(const ast::UnaryOpExpression* u) {
+ auto* e = u->expr;
+ while (true) {
+ auto* unary = e->As<ast::UnaryOpExpression>();
+ if (unary &&
+ (unary->op == ast::UnaryOp::kIndirection || unary->op == ast::UnaryOp::kAddressOf)) {
+ e = unary->expr;
+ } else {
+ break;
+ }
+ }
+ return e;
+}
+
/// CallSiteTag describes the uniformity requirements on the call sites of a function.
enum CallSiteTag {
CallSiteRequiredToBeUniform,
@@ -203,6 +220,10 @@
/// Includes pointer parameters.
std::unordered_set<const sem::Variable*> local_var_decls;
+ /// The set of partial pointer variables - pointers that point to a subobject (into an array or
+ /// struct).
+ std::unordered_set<const sem::Variable*> partial_ptrs;
+
/// LoopSwitchInfo tracks information about the value of variables for a control flow construct.
struct LoopSwitchInfo {
/// The type of this control flow construct.
@@ -481,8 +502,8 @@
}
// Remove any variables declared in this scope from the set of in-scope variables.
- for (auto* d : sem_.Get<sem::BlockStatement>(b)->Decls()) {
- current_function_->local_var_decls.erase(sem_.Get<sem::LocalVariable>(d));
+ for (auto decl : sem_.Get<sem::BlockStatement>(b)->Decls()) {
+ current_function_->local_var_decls.erase(decl.value.variable);
}
return cf;
@@ -943,14 +964,27 @@
[&](const ast::VariableDeclStatement* decl) {
Node* node;
+ auto* sem_var = sem_.Get(decl->variable);
if (decl->variable->constructor) {
auto [cf1, v] = ProcessExpression(cf, decl->variable->constructor);
cf = cf1;
node = v;
+
+ // Store if lhs is a partial pointer
+ if (sem_var->Type()->Is<sem::Pointer>()) {
+ auto* init = sem_.Get(decl->variable->constructor);
+ if (auto* unary_init = init->Declaration()->As<ast::UnaryOpExpression>()) {
+ auto* e = UnwrapIndirectAndAddressOfChain(unary_init);
+ if (e->IsAnyOf<ast::IndexAccessorExpression,
+ ast::MemberAccessorExpression>()) {
+ current_function_->partial_ptrs.insert(sem_var);
+ }
+ }
+ }
} else {
node = cf;
}
- current_function_->variables.Set(sem_.Get(decl->variable), node);
+ current_function_->variables.Set(sem_var, node);
if (decl->variable->Is<ast::Var>()) {
current_function_->local_var_decls.insert(
@@ -1126,11 +1160,37 @@
});
}
+ /// @param u unary expression with op == kIndirection
+ /// @returns true if `u` is an indirection unary expression that ultimately dereferences a
+ /// partial pointer, false otherwise.
+ bool IsDerefOfPartialPointer(const ast::UnaryOpExpression* u) {
+ TINT_ASSERT(Resolver, u->op == ast::UnaryOp::kIndirection);
+
+ // To determine if we're dereferencing a partial pointer, unwrap *&
+ // chains; if the final expression is an identifier, see if it's a
+ // partial pointer. If it's not an identifier, then it must be an
+ // index/acessor expression, and thus a partial pointer.
+ auto* e = UnwrapIndirectAndAddressOfChain(u);
+ if (auto* var_user = sem_.Get<sem::VariableUser>(e)) {
+ if (current_function_->partial_ptrs.count(var_user->Variable())) {
+ return true;
+ }
+ } else {
+ TINT_ASSERT(
+ Resolver,
+ (e->IsAnyOf<ast::IndexAccessorExpression, ast::MemberAccessorExpression>()));
+ return true;
+ }
+ return false;
+ }
+
/// Process an LValue expression.
/// @param cf the input control flow node
/// @param expr the expression to process
/// @returns a pair of (control flow node, variable node)
- std::pair<Node*, Node*> ProcessLValueExpression(Node* cf, const ast::Expression* expr) {
+ std::pair<Node*, Node*> ProcessLValueExpression(Node* cf,
+ const ast::Expression* expr,
+ bool is_partial_reference = false) {
return Switch(
expr,
@@ -1144,9 +1204,11 @@
auto* value = CreateNode(name + "_lvalue");
auto* old_value = current_function_->variables.Set(local, value);
- // Aggregate values link back to their previous value, as they can never become
- // uniform again.
- if (!local->Type()->UnwrapRef()->is_scalar() && old_value) {
+ // If i is part of an expression that is a partial reference to a variable (e.g.
+ // index or member access), we link back to the variable's previous value. If
+ // the previous value was non-uniform, a partial assignment will not make it
+ // uniform.
+ if (is_partial_reference && old_value) {
value->AddEdge(old_value);
}
@@ -1160,14 +1222,15 @@
},
[&](const ast::IndexAccessorExpression* i) {
- auto [cf1, l1] = ProcessLValueExpression(cf, i->object);
+ auto [cf1, l1] =
+ ProcessLValueExpression(cf, i->object, /*is_partial_reference*/ true);
auto [cf2, v2] = ProcessExpression(cf1, i->index);
l1->AddEdge(v2);
return std::pair<Node*, Node*>(cf2, l1);
},
[&](const ast::MemberAccessorExpression* m) {
- return ProcessLValueExpression(cf, m->structure);
+ return ProcessLValueExpression(cf, m->structure, /*is_partial_reference*/ true);
},
[&](const ast::UnaryOpExpression* u) {
@@ -1179,15 +1242,17 @@
auto* deref = CreateNode(name + "_deref");
auto* old_value = current_function_->variables.Set(source_var, deref);
- // Aggregate values link back to their previous value, as they can never become
- // uniform again.
- if (!source_var->Type()->UnwrapRef()->UnwrapPtr()->is_scalar() && old_value) {
- deref->AddEdge(old_value);
+ if (old_value) {
+ // If derefercing a partial reference or partial pointer, we link back to
+ // the variable's previous value. If the previous value was non-uniform, a
+ // partial assignment will not make it uniform.
+ if (is_partial_reference || IsDerefOfPartialPointer(u)) {
+ deref->AddEdge(old_value);
+ }
}
-
return std::pair<Node*, Node*>(cf, deref);
}
- return ProcessLValueExpression(cf, u->expr);
+ return ProcessLValueExpression(cf, u->expr, is_partial_reference);
},
[&](Default) {
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
index 52cccbe..efff223 100644
--- a/src/tint/resolver/uniformity_test.cc
+++ b/src/tint/resolver/uniformity_test.cc
@@ -5450,7 +5450,7 @@
}
TEST_F(UniformityAnalysisTest, VectorElement_ElementBecomesUniform) {
- // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // For aggregate types, we conservatively consider them to be non-uniform once they
// become non-uniform. Test that after assigning a uniform value to an element, that element is
// still considered to be non-uniform.
std::string src = R"(
@@ -5482,8 +5482,156 @@
)");
}
+TEST_F(UniformityAnalysisTest, VectorElement_VectorBecomesUniform_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v[1] = rw;
+ v = vec4(1, 2, 3, 4);
+ if (v[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, VectorElementViaMember_VectorBecomesUniform_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v.y = rw;
+ v = vec4(1, 2, 3, 4);
+ if (v.y == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, VectorElement_VectorBecomesUniform_ThroughPointer_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v[1] = rw;
+ *(&v) = vec4(1, 2, 3, 4);
+ if (v[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest,
+ VectorElement_VectorBecomesUniform_ThroughPointerChain_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v[1] = rw;
+ *(&(*(&(*(&v))))) = vec4(1, 2, 3, 4);
+ if (v[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest,
+ VectorElement_VectorBecomesUniform_ThroughCapturedPointer_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v[1] = rw;
+ let p = &v;
+ *p = vec4(1, 2, 3, 4);
+ if (v[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, VectorElement_VectorBecomesUniform_PartialAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v[1] = rw;
+ v = vec4(1, 2, 3, v[3]);
+ if (v[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (v[1] == 0) {
+ ^^
+
+test:6:10 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ v[1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, VectorElementViaMember_VectorBecomesUniform_PartialAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var v : vec4<i32>;
+ v.y = rw;
+ v = vec4(1, 2, 3, v.w);
+ if (v.y == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (v.y == 0) {
+ ^^
+
+test:6:9 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ v.y = rw;
+ ^^
+)");
+}
+
TEST_F(UniformityAnalysisTest, VectorElement_DifferentElementBecomesUniform) {
- // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // For aggregate types, we conservatively consider them to be non-uniform once they
// become non-uniform. Test that after assigning a uniform value to an element, the whole vector
// is still considered to be non-uniform.
std::string src = R"(
@@ -5544,6 +5692,467 @@
)");
}
+TEST_F(UniformityAnalysisTest, MatrixElement_ElementBecomesUniform) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ m[1][1] = 42.0;
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:6:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, MatrixElement_ElementBecomesUniform_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ m = mat3x3<f32>(vec3(1.0, 2.0, 3.0), vec3(4.0, 5.0, 6.0), vec3(7.0, 8.0, 9.0));
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, MatrixElement_ElementBecomesUniform_ThroughPointer_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ *(&m) = mat3x3<f32>(vec3(1.0, 2.0, 3.0), vec3(4.0, 5.0, 6.0), vec3(7.0, 8.0, 9.0));
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest,
+ MatrixElement_ElementBecomesUniform_ThroughPointerChain_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ *(&(*(&(*(&m))))) = mat3x3<f32>(vec3(1.0, 2.0, 3.0), vec3(4.0, 5.0, 6.0), vec3(7.0, 8.0, 9.0));
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest,
+ MatrixElement_ElementBecomesUniform_ThroughCapturedPointer_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ let p = &m;
+ *p = mat3x3<f32>(vec3(1.0, 2.0, 3.0), vec3(4.0, 5.0, 6.0), vec3(7.0, 8.0, 9.0));
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, MatrixElement_ColumnBecomesUniform) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ m[1] = vec3(0.0, 42.0, 0.0);
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:6:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, MatrixElement_ColumnBecomesUniform_ThroughPartialPointer) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ *(&(m[1])) = vec3(0.0, 42.0, 0.0);
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:6:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, MatrixElement_ColumnBecomesUniform_ThroughPartialPointerChain) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ *(&(*(&(m[1])))) = vec3(0.0, 42.0, 0.0);
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:6:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, MatrixElement_ColumnBecomesUniform_ThroughCapturedPartialPointer) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ let p = &m[1];
+ m[1][1] = rw;
+ *p = vec3(0.0, 42.0, 0.0);
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:7:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ MatrixElement_ColumnBecomesUniform_ThroughCapturedPartialPointerChain) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ let p = &m[1];
+ m[1][1] = rw;
+ *(&(*p)) = vec3(0.0, 42.0, 0.0);
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:7:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, MatrixElement_ColumnBecomesUniform_ThroughCapturedPointer) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element, that element is
+ // still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ let p = &m;
+ m[1][1] = rw;
+ (*p)[1] = vec3(0.0, 42.0, 0.0);
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:7:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, MatrixElement_MatrixBecomesUniform_PartialAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ m = mat3x3<f32>(vec3(1.0, 2.0, 3.0), vec3(4.0, 5.0, 6.0), m[2]);
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:6:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ MatrixElement_MatrixBecomesUniform_PartialAssignment_ThroughPointer) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ *(&m) = mat3x3<f32>(vec3(1.0, 2.0, 3.0), vec3(4.0, 5.0, 6.0), m[2]);
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:6:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ MatrixElement_MatrixBecomesUniform_PartialAssignment_ThroughCapturedPointer) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ let p = &m;
+ m[1][1] = rw;
+ *p = mat3x3<f32>(vec3(1.0, 2.0, 3.0), vec3(4.0, 5.0, 6.0), (*p)[2]);
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:7:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ MatrixElement_MatrixBecomesUniform_PartialAssignment_ThroughCapturedPointerChain) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ let p = &(*(&m));
+ m[1][1] = rw;
+ *p = mat3x3<f32>(vec3(1.0, 2.0, 3.0), vec3(4.0, 5.0, 6.0), (*p)[2]);
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:7:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, MatrixElement_DifferentElementBecomesUniform) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : f32;
+
+fn foo() {
+ var m : mat3x3<f32>;
+ m[1][1] = rw;
+ m[2][2] = 42.0;
+ if (m[1][1] == 0.0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (m[1][1] == 0.0) {
+ ^^
+
+test:6:13 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ m[1][1] = rw;
+ ^^
+)");
+}
+
TEST_F(UniformityAnalysisTest, StructMember_Uniform) {
std::string src = R"(
struct S {
@@ -5680,7 +6289,7 @@
}
TEST_F(UniformityAnalysisTest, StructMember_MemberBecomesUniform) {
- // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // For aggregate types, we conservatively consider them to be non-uniform once they
// become non-uniform. Test that after assigning a uniform value to a member, that member is
// still considered to be non-uniform.
std::string src = R"(
@@ -5716,8 +6325,312 @@
)");
}
+TEST_F(UniformityAnalysisTest, StructMember_MemberBecomesUniformThroughCapturedPointer) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to a member, that member is
+ // still considered to be non-uniform.
+ std::string src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ let p = &s;
+ s.a = rw;
+ (*p).a = 0;
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:13:3 note: control flow depends on non-uniform value
+ if (s.a == 0) {
+ ^^
+
+test:11:9 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ s.a = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_MemberBecomesUniformThroughCapturedPartialPointer) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to a member, that member is
+ // still considered to be non-uniform.
+ std::string src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ let p = &s.a;
+ s.a = rw;
+ (*p) = 0;
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:13:3 note: control flow depends on non-uniform value
+ if (s.a == 0) {
+ ^^
+
+test:11:9 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ s.a = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_StructBecomesUniform_FullAssignment) {
+ std::string src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ s.a = rw;
+ s = S(1, 2);
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_StructBecomesUniform_PartialAssignment) {
+ std::string src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ s.a = rw;
+ s = S(1, s.b);
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:13:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:12:3 note: control flow depends on non-uniform value
+ if (s.a == 0) {
+ ^^
+
+test:10:9 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ s.a = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_StructBecomesUniform_FullAssignment_ThroughPointer) {
+ std::string src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ s.a = rw;
+ *(&s) = S(1, 2);
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest,
+ StructMember_StructBecomesUniform_FullAssignment_ThroughCapturedPointer) {
+ std::string src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ let p = &s;
+ s.a = rw;
+ *p = S(1, 2);
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest,
+ StructMember_StructBecomesUniform_FullAssignment_ThroughCapturedPointerChain) {
+ std::string src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ let p = &(*(&s));
+ s.a = rw;
+ *p = S(1, 2);
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, StructMember_StructBecomesUniform_PartialAssignment_ThroughPointer) {
+ std::string src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ s.a = rw;
+ *(&s) = S(1, (*(&s)).b);
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:13:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:12:3 note: control flow depends on non-uniform value
+ if (s.a == 0) {
+ ^^
+
+test:10:9 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ s.a = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ StructMember_StructBecomesUniform_PartialAssignment_ThroughCapturedPointer) {
+ std::string src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ let p = &s;
+ s.a = rw;
+ *p = S(1, (*p).b);
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:13:3 note: control flow depends on non-uniform value
+ if (s.a == 0) {
+ ^^
+
+test:11:9 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ s.a = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest,
+ StructMember_StructBecomesUniform_PartialAssignment_ThroughCapturedPointerChain) {
+ std::string src = R"(
+struct S {
+ a : i32,
+ b : i32,
+}
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var s : S;
+ let p = &(*(&s));
+ s.a = rw;
+ *p = S(1, (*p).b);
+ if (s.a == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:14:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:13:3 note: control flow depends on non-uniform value
+ if (s.a == 0) {
+ ^^
+
+test:11:9 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ s.a = rw;
+ ^^
+)");
+}
+
TEST_F(UniformityAnalysisTest, StructMember_DifferentMemberBecomesUniform) {
- // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // For aggregate types, we conservatively consider them to be non-uniform once they
// become non-uniform. Test that after assigning a uniform value to a member, the whole struct
// is still considered to be non-uniform.
std::string src = R"(
@@ -5868,7 +6781,8 @@
)");
}
-TEST_F(UniformityAnalysisTest, ArrayElement_DifferentElementBecomesNonUniformThroughPointer) {
+TEST_F(UniformityAnalysisTest,
+ ArrayElement_DifferentElementBecomesNonUniformThroughPartialPointer) {
std::string src = R"(
@group(0) @binding(0) var<storage, read_write> rw : i32;
@@ -5931,8 +6845,55 @@
)");
}
+TEST_F(UniformityAnalysisTest, ArrayElement_ElementBecomesUniform_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ arr[1] = rw;
+ arr = array<i32, 4>(1, 2, 3, 4);
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_ElementBecomesUniform_PartialAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ arr[1] = rw;
+ arr = array<i32, 4>(1, 2, 3, arr[3]);
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (arr[1] == 0) {
+ ^^
+
+test:6:12 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ arr[1] = rw;
+ ^^
+)");
+}
+
TEST_F(UniformityAnalysisTest, ArrayElement_DifferentElementBecomesUniform) {
- // For aggregate types, we conservatively consider them to be forever non-uniform once they
+ // For aggregate types, we conservatively consider them to be non-uniform once they
// become non-uniform. Test that after assigning a uniform value to an element, the whole array
// is still considered to be non-uniform.
std::string src = R"(
@@ -5964,8 +6925,74 @@
)");
}
-TEST_F(UniformityAnalysisTest, ArrayElement_ElementBecomesUniformThroughPointer) {
- // For aggregate types, we conservatively consider them to be forever non-uniform once they
+TEST_F(UniformityAnalysisTest, ArrayElement_ElementBecomesUniform_ThroughPartialPointer) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element through a
+ // pointer, the whole array is still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ arr[1] = rw;
+ *(&(arr[2])) = 42;
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (arr[1] == 0) {
+ ^^
+
+test:6:12 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ arr[1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_ElementBecomesUniform_ThroughPartialPointerChain) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element through a
+ // pointer, the whole array is still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ arr[1] = rw;
+ *(&(*(&(*(&(arr[2])))))) = 42;
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:9:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:8:3 note: control flow depends on non-uniform value
+ if (arr[1] == 0) {
+ ^^
+
+test:6:12 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ arr[1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_ElementBecomesUniform_ThroughCapturedPartialPointer) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
// become non-uniform. Test that after assigning a uniform value to an element through a
// pointer, the whole array is still considered to be non-uniform.
std::string src = R"(
@@ -5998,6 +7025,148 @@
)");
}
+TEST_F(UniformityAnalysisTest,
+ ArrayElement_ElementBecomesUniform_ThroughCapturedPartialPointerChain) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element through a
+ // pointer, the whole array is still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ let pa = &(*(&arr[2]));
+ arr[1] = rw;
+ *pa = 42;
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on non-uniform value
+ if (arr[1] == 0) {
+ ^^
+
+test:7:12 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ arr[1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_ElementBecomesUniform_ThroughCapturedPointer) {
+ // For aggregate types, we conservatively consider them to be non-uniform once they
+ // become non-uniform. Test that after assigning a uniform value to an element through a
+ // pointer, the whole array is still considered to be non-uniform.
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ let pa = &arr;
+ arr[1] = rw;
+ (*pa)[2] = 42;
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, false);
+ EXPECT_EQ(error_,
+ R"(test:10:5 error: 'workgroupBarrier' must only be called from uniform control flow
+ workgroupBarrier();
+ ^^^^^^^^^^^^^^^^
+
+test:9:3 note: control flow depends on non-uniform value
+ if (arr[1] == 0) {
+ ^^
+
+test:7:12 note: reading from read_write storage buffer 'rw' may result in a non-uniform value
+ arr[1] = rw;
+ ^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, ArrayElement_ArrayBecomesUniform_ThroughPointer_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ arr[1] = rw;
+ *(&arr) = array<i32, 4>();
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest,
+ ArrayElement_ArrayBecomesUniform_ThroughPointerChain_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ arr[1] = rw;
+ *(&(*(&(*(&arr))))) = array<i32, 4>();
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest,
+ ArrayElement_ArrayBecomesUniform_ThroughCapturedPointer_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ let pa = &arr;
+ arr[1] = rw;
+ *pa = array<i32, 4>();
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
+TEST_F(UniformityAnalysisTest,
+ ArrayElement_ArrayBecomesUniform_ThroughCapturedPointerChain_FullAssignment) {
+ std::string src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+fn foo() {
+ var arr : array<i32, 4>;
+ let pa = &(*(&arr));
+ arr[1] = rw;
+ *pa = array<i32, 4>();
+ if (arr[1] == 0) {
+ workgroupBarrier();
+ }
+}
+)";
+
+ RunTest(src, true);
+}
+
////////////////////////////////////////////////////////////////////////////////
/// Miscellaneous statement and expression tests.
////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/resolver/validation_test.cc b/src/tint/resolver/validation_test.cc
index edce7eb..fb210c0 100644
--- a/src/tint/resolver/validation_test.cc
+++ b/src/tint/resolver/validation_test.cc
@@ -1243,7 +1243,7 @@
});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:34 error: 'align' value must be a positive, power-of-two integer");
+ EXPECT_EQ(r()->error(), "12:34 error: @align value must be a positive, power-of-two integer");
}
TEST_F(ResolverValidationTest, NonPOTStructMemberAlignAttribute) {
@@ -1252,7 +1252,7 @@
});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:34 error: 'align' value must be a positive, power-of-two integer");
+ EXPECT_EQ(r()->error(), "12:34 error: @align value must be a positive, power-of-two integer");
}
TEST_F(ResolverValidationTest, ZeroStructMemberAlignAttribute) {
@@ -1261,7 +1261,7 @@
});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:34 error: 'align' value must be a positive, power-of-two integer");
+ EXPECT_EQ(r()->error(), "12:34 error: @align value must be a positive, power-of-two integer");
}
TEST_F(ResolverValidationTest, ZeroStructMemberSizeAttribute) {
@@ -1270,7 +1270,7 @@
});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:34 error: 'size' must be at least as big as the type's size (4)");
+ EXPECT_EQ(r()->error(), "12:34 error: @size must be at least as big as the type's size (4)");
}
TEST_F(ResolverValidationTest, OffsetAndSizeAttribute) {
@@ -1280,9 +1280,7 @@
});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: offset attributes cannot be used with align or size "
- "attributes");
+ EXPECT_EQ(r()->error(), "12:34 error: @offset cannot be used with @align or @size");
}
TEST_F(ResolverValidationTest, OffsetAndAlignAttribute) {
@@ -1292,9 +1290,7 @@
});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: offset attributes cannot be used with align or size "
- "attributes");
+ EXPECT_EQ(r()->error(), "12:34 error: @offset cannot be used with @align or @size");
}
TEST_F(ResolverValidationTest, OffsetAndAlignAndSizeAttribute) {
@@ -1304,9 +1300,7 @@
});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: offset attributes cannot be used with align or size "
- "attributes");
+ EXPECT_EQ(r()->error(), "12:34 error: @offset cannot be used with @align or @size");
}
TEST_F(ResolverTest, Expr_Constructor_Cast_Pointer) {
diff --git a/src/tint/sem/array.cc b/src/tint/sem/array.cc
index 31827cb..74ef382 100644
--- a/src/tint/sem/array.cc
+++ b/src/tint/sem/array.cc
@@ -26,7 +26,7 @@
namespace tint::sem {
-const char* Array::kErrExpectedConstantCount =
+const char* const Array::kErrExpectedConstantCount =
"array size is an override-expression, when expected a constant-expression.\n"
"Was the SubstituteOverride transform run?";
diff --git a/src/tint/sem/array.h b/src/tint/sem/array.h
index 693549d..ca66a16 100644
--- a/src/tint/sem/array.h
+++ b/src/tint/sem/array.h
@@ -111,7 +111,7 @@
public:
/// An error message string stating that the array count was expected to be a constant
/// expression. Used by multiple writers and transforms.
- static const char* kErrExpectedConstantCount;
+ static const char* const kErrExpectedConstantCount;
/// Constructor
/// @param element the array element type
diff --git a/src/tint/sem/sem_array_test.cc b/src/tint/sem/array_test.cc
similarity index 100%
rename from src/tint/sem/sem_array_test.cc
rename to src/tint/sem/array_test.cc
diff --git a/src/tint/sem/block_statement.cc b/src/tint/sem/block_statement.cc
index 51bad0f..12b8318 100644
--- a/src/tint/sem/block_statement.cc
+++ b/src/tint/sem/block_statement.cc
@@ -35,10 +35,6 @@
return Base::Declaration()->As<ast::BlockStatement>();
}
-void BlockStatement::AddDecl(const ast::Variable* var) {
- decls_.push_back(var);
-}
-
FunctionBlockStatement::FunctionBlockStatement(const sem::Function* function)
: Base(function->Declaration()->body, nullptr, function) {
TINT_ASSERT(Semantic, function);
diff --git a/src/tint/sem/block_statement.h b/src/tint/sem/block_statement.h
index 4f12122..e5d4969 100644
--- a/src/tint/sem/block_statement.h
+++ b/src/tint/sem/block_statement.h
@@ -47,16 +47,6 @@
/// @returns the AST block statement associated with this semantic block
/// statement
const ast::BlockStatement* Declaration() const;
-
- /// @returns the declarations associated with this block
- const std::vector<const ast::Variable*>& Decls() const { return decls_; }
-
- /// Associates a declaration with this block.
- /// @param var a variable declaration to be added to the block
- void AddDecl(const ast::Variable* var);
-
- private:
- std::vector<const ast::Variable*> decls_;
};
/// The root block statement for a function
diff --git a/src/tint/sem/statement.cc b/src/tint/sem/statement.cc
index fb8e557..685f0d6 100644
--- a/src/tint/sem/statement.cc
+++ b/src/tint/sem/statement.cc
@@ -17,8 +17,10 @@
#include "src/tint/ast/block_statement.h"
#include "src/tint/ast/loop_statement.h"
#include "src/tint/ast/statement.h"
+#include "src/tint/ast/variable.h"
#include "src/tint/sem/block_statement.h"
#include "src/tint/sem/statement.h"
+#include "src/tint/sem/variable.h"
TINT_INSTANTIATE_TYPEINFO(tint::sem::Statement);
TINT_INSTANTIATE_TYPEINFO(tint::sem::CompoundStatement);
@@ -43,4 +45,8 @@
CompoundStatement::~CompoundStatement() = default;
+void CompoundStatement::AddDecl(const sem::LocalVariable* var) {
+ decls_.Add(var->Declaration()->symbol, OrderedLocalVariable{decls_.Count(), var});
+}
+
} // namespace tint::sem
diff --git a/src/tint/sem/statement.h b/src/tint/sem/statement.h
index bdcb55c..09112d5 100644
--- a/src/tint/sem/statement.h
+++ b/src/tint/sem/statement.h
@@ -17,6 +17,8 @@
#include "src/tint/sem/behavior.h"
#include "src/tint/sem/node.h"
+#include "src/tint/symbol.h"
+#include "src/tint/utils/hashmap.h"
// Forward declarations
namespace tint::ast {
@@ -26,6 +28,7 @@
class BlockStatement;
class CompoundStatement;
class Function;
+class LocalVariable;
} // namespace tint::sem
namespace tint::sem {
@@ -128,6 +131,25 @@
/// Destructor
~CompoundStatement() override;
+
+ /// OrderedLocalVariable describes a local variable declaration, and order of declaration.
+ struct OrderedLocalVariable {
+ /// The 0-based declaration order index of the variable
+ size_t order;
+ /// The variable
+ const LocalVariable* variable;
+ };
+
+ /// @returns a map of variable name to variable declarations associated with this block
+ const utils::Hashmap<Symbol, OrderedLocalVariable, 4>& Decls() const { return decls_; }
+
+ /// Associates a declaration with this block.
+ /// @note this method must be called in variable declaration order
+ /// @param var a variable declaration to be added to the block
+ void AddDecl(const LocalVariable* var);
+
+ private:
+ utils::Hashmap<Symbol, OrderedLocalVariable, 4> decls_;
};
template <typename Pred>
diff --git a/src/tint/sem/storage_texture_test.cc b/src/tint/sem/storage_texture_test.cc
index 8a9351e..23d6964 100644
--- a/src/tint/sem/storage_texture_test.cc
+++ b/src/tint/sem/storage_texture_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2020 The Tint Authors->
+// Copyright 2020 The Tint Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/src/tint/sem/sem_struct_test.cc b/src/tint/sem/struct_test.cc
similarity index 100%
rename from src/tint/sem/sem_struct_test.cc
rename to src/tint/sem/struct_test.cc