mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-17 00:47:13 +00:00
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:
committed by
Tint LUCI CQ
parent
69ce5f74ed
commit
4cc4315d6c
@@ -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) {
|
||||
// {
|
||||
|
||||
@@ -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",
|
||||
{
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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>; }
|
||||
|
||||
Reference in New Issue
Block a user