Allow array size to be a module-scope constant

Change ast::Array to use an ast::Expression for its `size` field. The
WGSL frontend now parses the array size as an `primary_expression`,
and the Resolver is responsible for validating the expression is a
signed or unsigned integer, and either a literal or a non-overridable
module-scope constant.

The Resolver evaluates the constant value of the size expression, and
so the resolved sem::Array type still has a constant size as before.

Fixed: tint:1068
Fixed: tint:1117

Change-Id: Icfa141482ea1e47ea8c21a25e9eb48221f176e9a
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/63061
Auto-Submit: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
James Price
2021-09-02 13:49:59 +00:00
committed by Tint LUCI CQ
parent 69ce5f74ed
commit 4cc4315d6c
41 changed files with 861 additions and 261 deletions

View File

@@ -62,6 +62,49 @@ TEST_F(ResolverAssignmentValidationTest, AssignIncompatibleTypes) {
EXPECT_EQ(r()->error(), "12:34 error: cannot assign 'f32' to 'i32'");
}
TEST_F(ResolverAssignmentValidationTest,
AssignArraysWithDifferentSizeExpressions_Pass) {
// let len = 4u;
// {
// var a : array<f32, 4>;
// var b : array<f32, len>;
// a = b;
// }
GlobalConst("len", nullptr, Expr(4u));
auto* a = Var("a", ty.array(ty.f32(), 4));
auto* b = Var("b", ty.array(ty.f32(), "len"));
auto* assign = Assign(Source{{12, 34}}, "a", "b");
WrapInFunction(a, b, assign);
ASSERT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverAssignmentValidationTest,
AssignArraysWithDifferentSizeExpressions_Fail) {
// let len = 5u;
// {
// var a : array<f32, 4>;
// var b : array<f32, len>;
// a = b;
// }
GlobalConst("len", nullptr, Expr(5u));
auto* a = Var("a", ty.array(ty.f32(), 4));
auto* b = Var("b", ty.array(ty.f32(), "len"));
auto* assign = Assign(Source{{12, 34}}, "a", "b");
WrapInFunction(a, b, assign);
ASSERT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: cannot assign 'array<f32, len>' to 'array<f32, 4>'");
}
TEST_F(ResolverAssignmentValidationTest,
AssignCompatibleTypesInBlockStatement_Pass) {
// {

View File

@@ -681,7 +681,7 @@ using ArrayDecorationTest = TestWithParams;
TEST_P(ArrayDecorationTest, IsValid) {
auto& params = GetParam();
auto* arr = ty.array(ty.f32(), 0,
auto* arr = ty.array(ty.f32(), nullptr,
createDecorations(Source{{12, 34}}, *this, params.kind));
Structure("mystruct",
{

View File

@@ -698,7 +698,7 @@ TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_StructOfAtomic) {
}
TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_RuntimeArray) {
auto* ret_type = ty.array(Source{{12, 34}}, ty.i32(), 0);
auto* ret_type = ty.array(Source{{12, 34}}, ty.i32());
Func("f", {}, ret_type, {});
EXPECT_FALSE(r()->Resolve());

View File

@@ -3868,11 +3868,68 @@ sem::Array* Resolver::Array(const ast::Array* arr) {
auto stride = explicit_stride ? explicit_stride : implicit_stride;
// WebGPU requires runtime arrays have at least one element, but the AST
// records an element count of 0 for it.
auto size = std::max<uint32_t>(arr->size(), 1) * stride;
auto* out = builder_->create<sem::Array>(elem_type, arr->size(), el_align,
size, stride, implicit_stride);
// Evaluate the constant array size expression.
// sem::Array uses a size of 0 for a runtime-sized array.
uint32_t count = 0;
if (auto* count_expr = arr->Size()) {
Mark(count_expr);
if (!Expression(count_expr)) {
return nullptr;
}
auto size_source = count_expr->source();
auto* ty = TypeOf(count_expr)->UnwrapRef();
if (!ty->is_integer_scalar()) {
AddError("array size must be integer scalar", size_source);
return nullptr;
}
if (auto* ident = count_expr->As<ast::IdentifierExpression>()) {
// Make sure the identifier is a non-overridable module-scope constant.
VariableInfo* var = nullptr;
bool is_global = false;
if (!variable_stack_.get(ident->symbol(), &var, &is_global) ||
!is_global || !var->declaration->is_const()) {
AddError("array size identifier must be a module-scope constant",
size_source);
return nullptr;
}
if (ast::HasDecoration<ast::OverrideDecoration>(
var->declaration->decorations())) {
AddError("array size expression must not be pipeline-overridable",
size_source);
return nullptr;
}
count_expr = var->declaration->constructor();
} else if (!count_expr->Is<ast::ScalarConstructorExpression>()) {
AddError(
"array size expression must be either a literal or a module-scope "
"constant",
size_source);
return nullptr;
}
auto count_val = ConstantValueOf(count_expr);
if (!count_val) {
TINT_ICE(Resolver, diagnostics_)
<< "could not resolve array size expression";
return nullptr;
}
if (ty->is_signed_integer_scalar() ? count_val.Elements()[0].i32 < 1
: count_val.Elements()[0].u32 < 1u) {
AddError("array size must be at least 1", size_source);
return nullptr;
}
count = count_val.Elements()[0].u32;
}
auto size = std::max<uint32_t>(count, 1) * stride;
auto* out = builder_->create<sem::Array>(elem_type, count, el_align, size,
stride, implicit_stride);
if (!ValidateArray(out, source)) {
return nullptr;

View File

@@ -431,6 +431,66 @@ TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScopeAfterFunctionScope) {
EXPECT_EQ(VarOf(fn_f32->constructor())->Declaration(), mod_f32);
}
TEST_F(ResolverTest, ArraySize_UnsignedLiteral) {
// var<private> a : array<f32, 10u>;
auto* a =
Global("a", ty.array(ty.f32(), Expr(10u)), ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_NE(TypeOf(a), nullptr);
auto* ref = TypeOf(a)->As<sem::Reference>();
ASSERT_NE(ref, nullptr);
auto* ary = ref->StoreType()->As<sem::Array>();
EXPECT_EQ(ary->Count(), 10u);
}
TEST_F(ResolverTest, ArraySize_SignedLiteral) {
// var<private> a : array<f32, 10>;
auto* a =
Global("a", ty.array(ty.f32(), Expr(10)), ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_NE(TypeOf(a), nullptr);
auto* ref = TypeOf(a)->As<sem::Reference>();
ASSERT_NE(ref, nullptr);
auto* ary = ref->StoreType()->As<sem::Array>();
EXPECT_EQ(ary->Count(), 10u);
}
TEST_F(ResolverTest, ArraySize_UnsignedConstant) {
// let size = 0u;
// var<private> a : array<f32, 10u>;
GlobalConst("size", nullptr, Expr(10u));
auto* a = Global("a", ty.array(ty.f32(), Expr("size")),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_NE(TypeOf(a), nullptr);
auto* ref = TypeOf(a)->As<sem::Reference>();
ASSERT_NE(ref, nullptr);
auto* ary = ref->StoreType()->As<sem::Array>();
EXPECT_EQ(ary->Count(), 10u);
}
TEST_F(ResolverTest, ArraySize_SignedConstant) {
// let size = 0;
// var<private> a : array<f32, 10>;
GlobalConst("size", nullptr, Expr(10));
auto* a = Global("a", ty.array(ty.f32(), Expr("size")),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_NE(TypeOf(a), nullptr);
auto* ref = TypeOf(a)->As<sem::Reference>();
ASSERT_NE(ref, nullptr);
auto* ary = ref->StoreType()->As<sem::Array>();
EXPECT_EQ(ary->Count(), 10u);
}
TEST_F(ResolverTest, Expr_ArrayAccessor_Array) {
auto* idx = Expr(2);
Global("my_var", ty.array<f32, 3>(), ast::StorageClass::kPrivate);

View File

@@ -584,8 +584,9 @@ TEST_F(ResolverTypeConstructorValidationTest,
}
TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_Runtime) {
// array<f32>(1);
auto* tc = array<i32>(
// array<i32>(1);
auto* tc = array(
ty.i32(), nullptr,
create<ast::ScalarConstructorExpression>(Source{{12, 34}}, Literal(1)));
WrapInFunction(tc);
@@ -595,8 +596,8 @@ TEST_F(ResolverTypeConstructorValidationTest, Expr_Constructor_Array_Runtime) {
TEST_F(ResolverTypeConstructorValidationTest,
Expr_Constructor_Array_RuntimeZeroValue) {
// array<f32>();
auto* tc = array<i32>();
// array<i32>();
auto* tc = array(ty.i32(), nullptr);
WrapInFunction(tc);
EXPECT_FALSE(r()->Resolve());

View File

@@ -201,6 +201,180 @@ TEST_F(ResolverTypeValidationTest,
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLiteral_Pass) {
// var<private> a : array<f32, 4u>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 4u)),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Pass) {
// var<private> a : array<f32, 4>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 4)),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConstant_Pass) {
// let size = 4u;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(4u));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Pass) {
// let size = 4;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(4));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLiteral_Zero) {
// var<private> a : array<f32, 0u>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0u)),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Zero) {
// var<private> a : array<f32, 0>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0)),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Negative) {
// var<private> a : array<f32, -10>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, -10)),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConstant_Zero) {
// let size = 0u;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(0u));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Zero) {
// let size = 0;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(0));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Negative) {
// let size = -10;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(-10));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_FloatLiteral) {
// var<private> a : array<f32, 10.0>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 10.f)),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
}
TEST_F(ResolverTypeValidationTest, ArraySize_IVecLiteral) {
// var<private> a : array<f32, vec2<i32>(10, 10)>;
Global(
"a",
ty.array(ty.f32(), Construct(Source{{12, 34}}, ty.vec2<i32>(), 10, 10)),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
}
TEST_F(ResolverTypeValidationTest, ArraySize_FloatConstant) {
// let size = 10.0;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(10.f));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
}
TEST_F(ResolverTypeValidationTest, ArraySize_IVecConstant) {
// let size = vec2<i32>(100, 100);
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Construct(ty.vec2<i32>(), 100, 100));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
}
TEST_F(ResolverTypeValidationTest, ArraySize_OverridableConstant) {
// [[override]] let size = 10;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(10), {Override()});
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error: array size expression must not be pipeline-overridable");
}
TEST_F(ResolverTypeValidationTest, ArraySize_ModuleVar) {
// var<private> size : i32 = 10;
// var<private> a : array<f32, size>;
Global("size", ty.i32(), Expr(10), ast::StorageClass::kPrivate);
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error: array size identifier must be a module-scope constant");
}
TEST_F(ResolverTypeValidationTest, ArraySize_FunctionConstant) {
// {
// let size = 10;
// var a : array<f32, size>;
// }
auto* size = Const("size", nullptr, Expr(10));
auto* a = Var("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")));
WrapInFunction(Block(Decl(size), Decl(a)));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error: array size identifier must be a module-scope constant");
}
TEST_F(ResolverTypeValidationTest, ArraySize_InvalidExpr) {
// var a : array<f32, i32(4)>;
auto* size = Const("size", nullptr, Expr(10));
auto* a =
Var("a", ty.array(ty.f32(), Construct(Source{{12, 34}}, ty.i32(), 4)));
WrapInFunction(Block(Decl(size), Decl(a)));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: array size expression must be either a literal or a "
"module-scope constant");
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayInFunction_Fail) {
/// [[stage(vertex)]]
// fn func() { var a : array<i32>; }