Resolver: Validate resource binding decorations

Validate that:
* That resource variables have resource bindings
* Only resource variables have resource bindings
* That a [[binding]] decoration is paired with a [[group]]
* That binding points are not reused in the same entry point

Fixed: tint:235
Fixed: tint:645
Bug: tint:645
Change-Id: I2542934b4c6a2b4bbde48242932c04c796033a90
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/50500
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
Ben Clayton
2021-05-10 19:16:46 +00:00
committed by Commit Bot service account
parent 6ba446fa5e
commit 3f968e7b05
26 changed files with 730 additions and 229 deletions

View File

@@ -244,8 +244,16 @@ TEST_F(ResolverAssignmentValidationTest, AssignFromPointer_Fail) {
return ty.access(ast::AccessControl::kReadOnly, tex_type);
};
auto* var_a = Global("a", make_type(), ast::StorageClass::kNone);
auto* var_b = Global("b", make_type(), ast::StorageClass::kNone);
auto* var_a = Global("a", make_type(), ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
auto* var_b = Global("b", make_type(), ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(1),
create<ast::GroupDecoration>(0),
});
WrapInFunction(Assign(Source{{12, 34}}, var_a, var_b));

View File

@@ -43,46 +43,63 @@ enum class DecorationKind {
kStride,
kStructBlock,
kWorkgroup,
kBindingAndGroup,
};
bool IsBindingDecoration(DecorationKind kind) {
switch (kind) {
case DecorationKind::kBinding:
case DecorationKind::kGroup:
case DecorationKind::kBindingAndGroup:
return true;
default:
return false;
}
}
struct TestParams {
DecorationKind kind;
bool should_pass;
};
struct TestWithParams : ResolverTestWithParam<TestParams> {};
static ast::Decoration* createDecoration(const Source& source,
ProgramBuilder& builder,
DecorationKind kind) {
static ast::DecorationList createDecorations(const Source& source,
ProgramBuilder& builder,
DecorationKind kind) {
switch (kind) {
case DecorationKind::kAccess:
return builder.create<ast::AccessDecoration>(
source, ast::AccessControl::kReadOnly);
return {builder.create<ast::AccessDecoration>(
source, ast::AccessControl::kReadOnly)};
case DecorationKind::kAlign:
return builder.create<ast::StructMemberAlignDecoration>(source, 4u);
return {builder.create<ast::StructMemberAlignDecoration>(source, 4u)};
case DecorationKind::kBinding:
return builder.create<ast::BindingDecoration>(source, 1);
return {builder.create<ast::BindingDecoration>(source, 1u)};
case DecorationKind::kBuiltin:
return builder.Builtin(source, ast::Builtin::kPosition);
return {builder.Builtin(source, ast::Builtin::kPosition)};
case DecorationKind::kGroup:
return builder.create<ast::GroupDecoration>(source, 1u);
return {builder.create<ast::GroupDecoration>(source, 1u)};
case DecorationKind::kLocation:
return builder.Location(source, 1);
return {builder.Location(source, 1)};
case DecorationKind::kOverride:
return builder.create<ast::OverrideDecoration>(source, 0u);
return {builder.create<ast::OverrideDecoration>(source, 0u)};
case DecorationKind::kOffset:
return builder.create<ast::StructMemberOffsetDecoration>(source, 4u);
return {builder.create<ast::StructMemberOffsetDecoration>(source, 4u)};
case DecorationKind::kSize:
return builder.create<ast::StructMemberSizeDecoration>(source, 4u);
return {builder.create<ast::StructMemberSizeDecoration>(source, 4u)};
case DecorationKind::kStage:
return builder.Stage(source, ast::PipelineStage::kCompute);
return {builder.Stage(source, ast::PipelineStage::kCompute)};
case DecorationKind::kStride:
return builder.create<ast::StrideDecoration>(source, 4u);
return {builder.create<ast::StrideDecoration>(source, 4u)};
case DecorationKind::kStructBlock:
return builder.create<ast::StructBlockDecoration>(source);
return {builder.create<ast::StructBlockDecoration>(source)};
case DecorationKind::kWorkgroup:
return builder.create<ast::WorkgroupDecoration>(source, 1u, 1u, 1u);
return {builder.create<ast::WorkgroupDecoration>(source, 1u, 1u, 1u)};
case DecorationKind::kBindingAndGroup:
return {builder.create<ast::BindingDecoration>(source, 1u),
builder.create<ast::GroupDecoration>(source, 1u)};
}
return nullptr;
return {};
}
using FunctionReturnTypeDecorationTest = TestWithParams;
@@ -91,7 +108,7 @@ TEST_P(FunctionReturnTypeDecorationTest, IsValid) {
Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
ast::DecorationList{Stage(ast::PipelineStage::kCompute)},
ast::DecorationList{createDecoration({}, *this, params.kind)});
createDecorations({}, *this, params.kind));
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -116,17 +133,15 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false}));
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using ArrayDecorationTest = TestWithParams;
TEST_P(ArrayDecorationTest, IsValid) {
auto& params = GetParam();
auto* arr =
ty.array(ty.f32(), 0,
{
createDecoration(Source{{12, 34}}, *this, params.kind),
});
auto* arr = ty.array(ty.f32(), 0,
createDecorations(Source{{12, 34}}, *this, params.kind));
Structure("mystruct",
{
Member("a", arr),
@@ -158,14 +173,15 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, true},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false}));
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using StructDecorationTest = TestWithParams;
TEST_P(StructDecorationTest, IsValid) {
auto& params = GetParam();
Structure("mystruct", {},
{createDecoration(Source{{12, 34}}, *this, params.kind)});
createDecorations(Source{{12, 34}}, *this, params.kind));
WrapInFunction();
@@ -192,16 +208,15 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, true},
TestParams{DecorationKind::kWorkgroup, false}));
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using StructMemberDecorationTest = TestWithParams;
TEST_P(StructMemberDecorationTest, IsValid) {
auto& params = GetParam();
ast::StructMemberList members{
Member("a", ty.i32(),
ast::DecorationList{
createDecoration(Source{{12, 34}}, *this, params.kind)})};
ast::StructMemberList members{Member(
"a", ty.i32(), createDecorations(Source{{12, 34}}, *this, params.kind))};
Structure("mystruct", members);
@@ -230,15 +245,21 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false}));
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using VariableDecorationTest = TestWithParams;
TEST_P(VariableDecorationTest, IsValid) {
auto& params = GetParam();
Global("a", ty.f32(), ast::StorageClass::kInput, nullptr,
ast::DecorationList{
createDecoration(Source{{12, 34}}, *this, params.kind)});
if (IsBindingDecoration(params.kind)) {
Global("a", ty.sampler(ast::SamplerKind::kSampler),
ast::StorageClass::kNone, nullptr,
createDecorations(Source{{12, 34}}, *this, params.kind));
} else {
Global("a", ty.f32(), ast::StorageClass::kInput, nullptr,
createDecorations(Source{{12, 34}}, *this, params.kind));
}
WrapInFunction();
@@ -246,8 +267,10 @@ TEST_P(VariableDecorationTest, IsValid) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for variables");
if (!IsBindingDecoration(params.kind)) {
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for variables");
}
}
}
INSTANTIATE_TEST_SUITE_P(
@@ -255,9 +278,9 @@ INSTANTIATE_TEST_SUITE_P(
VariableDecorationTest,
testing::Values(TestParams{DecorationKind::kAccess, false},
TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, true},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, true},
TestParams{DecorationKind::kGroup, true},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kLocation, true},
TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false},
@@ -265,15 +288,15 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false}));
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, true}));
using ConstantDecorationTest = TestWithParams;
TEST_P(ConstantDecorationTest, IsValid) {
auto& params = GetParam();
GlobalConst("a", ty.f32(), nullptr,
ast::DecorationList{
createDecoration(Source{{12, 34}}, *this, params.kind)});
GlobalConst("a", ty.f32(), Expr(1.23f),
createDecorations(Source{{12, 34}}, *this, params.kind));
WrapInFunction();
@@ -300,16 +323,17 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false}));
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using FunctionDecorationTest = TestWithParams;
TEST_P(FunctionDecorationTest, IsValid) {
auto& params = GetParam();
Func("foo", ast::VariableList{}, ty.void_(), ast::StatementList{},
ast::DecorationList{
Stage(ast::PipelineStage::kCompute),
createDecoration(Source{{12, 34}}, *this, params.kind)});
ast::DecorationList decos =
createDecorations(Source{{12, 34}}, *this, params.kind);
decos.emplace_back(Stage(ast::PipelineStage::kCompute));
Func("foo", ast::VariableList{}, ty.void_(), ast::StatementList{}, decos);
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -334,7 +358,8 @@ INSTANTIATE_TEST_SUITE_P(
// Skip kStage as we always apply it in this test
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, true}));
TestParams{DecorationKind::kWorkgroup, true},
TestParams{DecorationKind::kBindingAndGroup, false}));
} // namespace
} // namespace DecorationTests
@@ -480,5 +505,158 @@ TEST_F(StructBlockTest, StructUsedAsArrayElement) {
} // namespace
} // namespace StructBlockTests
namespace ResourceTests {
namespace {
using ResourceDecorationTest = ResolverTest;
TEST_F(ResourceDecorationTest, UniformBufferMissingBinding) {
auto* s = Structure("S", {Member("x", ty.i32())},
{create<ast::StructBlockDecoration>()});
Global(Source{{12, 34}}, "G", s, ast::StorageClass::kUniform);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: resource variables require [[group]] and [[binding]] "
"decorations");
}
TEST_F(ResourceDecorationTest, StorageBufferMissingBinding) {
auto* s = Structure("S", {Member("x", ty.i32())},
{create<ast::StructBlockDecoration>()});
auto ac = ty.access(ast::AccessControl::kReadOnly, s);
Global(Source{{12, 34}}, "G", ac, ast::StorageClass::kStorage);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: resource variables require [[group]] and [[binding]] "
"decorations");
}
TEST_F(ResourceDecorationTest, TextureMissingBinding) {
Global(Source{{12, 34}}, "G", ty.depth_texture(ast::TextureDimension::k2d),
ast::StorageClass::kNone);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: resource variables require [[group]] and [[binding]] "
"decorations");
}
TEST_F(ResourceDecorationTest, SamplerMissingBinding) {
Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
ast::StorageClass::kNone);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: resource variables require [[group]] and [[binding]] "
"decorations");
}
TEST_F(ResourceDecorationTest, BindingPairMissingBinding) {
Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
ast::StorageClass::kNone, nullptr,
{
create<ast::GroupDecoration>(1),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: resource variables require [[group]] and [[binding]] "
"decorations");
}
TEST_F(ResourceDecorationTest, BindingPairMissingGroup) {
Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler),
ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(1),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: resource variables require [[group]] and [[binding]] "
"decorations");
}
TEST_F(ResourceDecorationTest, BindingPointUsedTwiceByEntryPoint) {
Global(Source{{12, 34}}, "A",
ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(1),
create<ast::GroupDecoration>(2),
});
Global(Source{{56, 78}}, "B",
ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(1),
create<ast::GroupDecoration>(2),
});
Func("F", {}, ty.void_(),
{
Decl(Var("a", ty.vec4<f32>(), ast::StorageClass::kFunction,
Call("textureLoad", "A", vec2<i32>(1, 2), 0))),
Decl(Var("b", ty.vec4<f32>(), ast::StorageClass::kFunction,
Call("textureLoad", "B", vec2<i32>(1, 2), 0))),
},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(56:78 error: entry point 'F' references multiple variables that use the same resource binding [[group(2), binding(1)]]
12:34 note: first resource binding usage declared here)");
}
TEST_F(ResourceDecorationTest, BindingPointUsedTwiceByDifferentEntryPoints) {
Global(Source{{12, 34}}, "A",
ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(1),
create<ast::GroupDecoration>(2),
});
Global(Source{{56, 78}}, "B",
ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(1),
create<ast::GroupDecoration>(2),
});
Func("F_A", {}, ty.void_(),
{
Decl(Var("a", ty.vec4<f32>(), ast::StorageClass::kFunction,
Call("textureLoad", "A", vec2<i32>(1, 2), 0))),
},
{Stage(ast::PipelineStage::kFragment)});
Func("F_B", {}, ty.void_(),
{
Decl(Var("b", ty.vec4<f32>(), ast::StorageClass::kFunction,
Call("textureLoad", "B", vec2<i32>(1, 2), 0))),
},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResourceDecorationTest, BindingPointOnNonResource) {
Global(Source{{12, 34}}, "G", ty.f32(), ast::StorageClass::kPrivate, nullptr,
{
create<ast::BindingDecoration>(1),
create<ast::GroupDecoration>(2),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: non-resource variables must not have [[group]] or "
"[[binding]] decorations");
}
} // namespace
} // namespace ResourceTests
} // namespace resolver
} // namespace tint

View File

@@ -30,7 +30,11 @@ TEST_F(ResolverHostShareableValidationTest, BoolMember) {
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())},
{create<ast::StructBlockDecoration>()});
auto a = ty.access(ast::AccessControl::kReadOnly, s);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -45,7 +49,11 @@ TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())},
{create<ast::StructBlockDecoration>()});
auto a = ty.access(ast::AccessControl::kReadOnly, s);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -64,7 +72,11 @@ TEST_F(ResolverHostShareableValidationTest, Aliases) {
auto ac = ty.access(ast::AccessControl::kReadOnly, s);
auto* a2 = ty.alias("a2", ac);
AST().AddConstructedType(a2);
Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage);
Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -83,7 +95,11 @@ TEST_F(ResolverHostShareableValidationTest, NestedStructures) {
auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
{create<ast::StructBlockDecoration>()});
auto a = ty.access(ast::AccessControl::kReadOnly, s);
Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage);
Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -122,7 +138,11 @@ TEST_F(ResolverHostShareableValidationTest, NoError) {
auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)},
{create<ast::StructBlockDecoration>()});
auto a = ty.access(ast::AccessControl::kReadOnly, s);
Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage);
Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_TRUE(r()->Resolve()) << r()->error();
}

View File

@@ -243,10 +243,17 @@ class ResolverIntrinsicTest_TextureOperation
void add_call_param(std::string name,
typ::Type type,
ast::ExpressionList* call_params) {
ast::StorageClass storage_class = type->UnwrapAll()->is_handle()
? ast::StorageClass::kNone
: ast::StorageClass::kPrivate;
Global(name, type, storage_class);
if (type->UnwrapAll()->is_handle()) {
Global(name, type, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
} else {
Global(name, type, ast::StorageClass::kPrivate);
}
call_params->push_back(Expr(name));
}
typ::Type subtype(Texture type) {
@@ -763,7 +770,11 @@ TEST_F(ResolverIntrinsicDataTest, ArrayLength_Vector) {
auto* str = Structure("S", {Member("x", ary)},
{create<ast::StructBlockDecoration>()});
auto ac = ty.access(ast::AccessControl::kReadOnly, str);
Global("a", ac, ast::StorageClass::kStorage);
Global("a", ac, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
auto* call = Call("arrayLength", MemberAccessor("a", "x"));
WrapInFunction(call);

View File

@@ -466,20 +466,10 @@ bool Resolver::GlobalVariable(ast::Variable* var) {
for (auto* deco : var->decorations()) {
Mark(deco);
if (var->is_const()) {
if (!deco->Is<ast::OverrideDecoration>()) {
diagnostics_.add_error("decoration is not valid for constants",
deco->source());
return false;
}
} else if (!(deco->Is<ast::BindingDecoration>() ||
deco->Is<ast::BuiltinDecoration>() ||
deco->Is<ast::GroupDecoration>() ||
deco->Is<ast::LocationDecoration>())) {
diagnostics_.add_error("decoration is not valid for variables",
deco->source());
return false;
}
}
if (auto bp = var->binding_point()) {
info->binding_point = {bp.group->value(), bp.binding->value()};
}
if (var->has_constructor()) {
@@ -512,6 +502,53 @@ bool Resolver::GlobalVariable(ast::Variable* var) {
}
bool Resolver::ValidateGlobalVariable(const VariableInfo* info) {
for (auto* deco : info->declaration->decorations()) {
if (info->declaration->is_const()) {
if (!deco->Is<ast::OverrideDecoration>()) {
diagnostics_.add_error("decoration is not valid for constants",
deco->source());
return false;
}
} else {
if (!(deco->Is<ast::BindingDecoration>() ||
deco->Is<ast::BuiltinDecoration>() ||
deco->Is<ast::GroupDecoration>() ||
deco->Is<ast::LocationDecoration>())) {
diagnostics_.add_error("decoration is not valid for variables",
deco->source());
return false;
}
}
}
auto binding_point = info->declaration->binding_point();
switch (info->storage_class) {
case ast::StorageClass::kUniform:
case ast::StorageClass::kStorage:
case ast::StorageClass::kUniformConstant: {
// https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
// Each resource variable must be declared with both group and binding
// attributes.
if (!binding_point) {
diagnostics_.add_error(
"resource variables require [[group]] and [[binding]] decorations",
info->declaration->source());
return false;
}
break;
}
default:
if (binding_point.binding || binding_point.group) {
// https://gpuweb.github.io/gpuweb/wgsl/#attribute-binding
// Must only be applied to a resource variable
diagnostics_.add_error(
"non-resource variables must not have [[group]] or [[binding]] "
"decorations",
info->declaration->source());
return false;
}
}
switch (info->storage_class) {
case ast::StorageClass::kStorage: {
// https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration
@@ -945,6 +982,33 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
}
}
// Validate there are no resource variable binding collisions
std::unordered_map<sem::BindingPoint, const ast::Variable*> binding_points;
for (auto* var_info : info->referenced_module_vars) {
if (!var_info->declaration->binding_point()) {
continue;
}
auto bp = var_info->binding_point;
auto res = binding_points.emplace(bp, var_info->declaration);
if (!res.second) {
// https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
// Bindings must not alias within a shader stage: two different
// variables in the resource interface of a given shader must not have
// the same group and binding values, when considered as a pair of
// values.
auto func_name = builder_->Symbols().NameFor(info->declaration->symbol());
diagnostics_.add_error("entry point '" + func_name +
"' references multiple variables that use the "
"same resource binding [[group(" +
std::to_string(bp.group) + "), binding(" +
std::to_string(bp.binding) + ")]]",
var_info->declaration->source());
diagnostics_.add_note("first resource binding usage declared here",
res.first->second->source());
return false;
}
}
return true;
}

View File

@@ -24,6 +24,7 @@
#include "src/intrinsic_table.h"
#include "src/program_builder.h"
#include "src/scope_stack.h"
#include "src/sem/binding_point.h"
#include "src/sem/struct.h"
#include "src/utils/unique_vector.h"
@@ -97,6 +98,7 @@ class Resolver {
std::string const type_name;
ast::StorageClass storage_class;
std::vector<ast::IdentifierExpression*> users;
sem::BindingPoint binding_point;
};
/// Structure holding semantic information about a function.

View File

@@ -665,7 +665,11 @@ TEST_F(ResolverTest, Expr_Identifier_FunctionVariable) {
auto* my_var_b = Expr("my_var");
auto* assign = Assign(my_var_a, my_var_b);
auto* var = Var("my_var", ty.f32(), ast::StorageClass::kNone);
auto* var = Var("my_var", ty.f32(), ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
Func("my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
@@ -770,7 +774,11 @@ TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage);
auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
@@ -806,7 +814,11 @@ TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput);
auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage);
auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
@@ -1447,7 +1459,11 @@ INSTANTIATE_TEST_SUITE_P(ResolverTest,
ast::UnaryOp::kNot));
TEST_F(ResolverTest, StorageClass_SetsIfMissing) {
auto* var = Var("var", ty.i32(), ast::StorageClass::kNone);
auto* var = Var("var", ty.i32(), ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
auto* stmt = Decl(var);
Func("func", ast::VariableList{}, ty.void_(), ast::StatementList{stmt},
@@ -1460,7 +1476,11 @@ TEST_F(ResolverTest, StorageClass_SetsIfMissing) {
TEST_F(ResolverTest, StorageClass_SetForSampler) {
auto t = ty.sampler(ast::SamplerKind::kSampler);
auto* var = Global("var", t, ast::StorageClass::kNone);
auto* var = Global("var", t, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -1471,7 +1491,11 @@ TEST_F(ResolverTest, StorageClass_SetForSampler) {
TEST_F(ResolverTest, StorageClass_SetForTexture) {
auto t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
auto ac = ty.access(ast::AccessControl::Access::kReadOnly, t);
auto* var = Global("var", ac, ast::StorageClass::kNone);
auto* var = Global("var", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();

View File

@@ -36,8 +36,12 @@ TEST_F(ResolverStorageClassValidationTest, GlobalVariableNoStorageClass_Fail) {
}
TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
// var<storage> g : bool;
Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage);
// var<storage> g : i32;
Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -49,7 +53,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
TEST_F(ResolverStorageClassValidationTest, StorageBufferPointer) {
// var<storage> g : ptr<i32, input>;
Global(Source{{56, 78}}, "g", ty.pointer<i32>(ast::StorageClass::kInput),
ast::StorageClass::kStorage);
ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -63,7 +71,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferArray) {
auto* s = Structure("S", {Member("a", ty.f32())});
auto* a = ty.array(s, 3);
auto ac = ty.access(ast::AccessControl::kReadOnly, a);
Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kStorage);
Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -77,7 +89,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferBoolAlias) {
// var<storage> g : [[access(read)]] a;
auto* a = ty.alias("a", ty.bool_());
AST().AddConstructedType(a);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -89,7 +105,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferBoolAlias) {
TEST_F(ResolverStorageClassValidationTest, StorageBufferNoAccessControl) {
// var<storage> g : S;
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())});
Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage);
Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -103,7 +123,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferNoBlockDecoration) {
// var<storage> g : [[access(read)]] S;
auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
auto a = ty.access(ast::AccessControl::kReadOnly, s);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -119,7 +143,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Basic) {
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
{create<ast::StructBlockDecoration>()});
auto a = ty.access(ast::AccessControl::kReadOnly, s);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_TRUE(r()->Resolve());
}
@@ -136,7 +164,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Aliases) {
auto ac = ty.access(ast::AccessControl::kReadOnly, a1);
auto* a2 = ty.alias("a2", ac);
AST().AddConstructedType(a2);
Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage);
Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_TRUE(r()->Resolve());
}
@@ -145,7 +177,12 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Aliases) {
TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) {
// var<uniform> g : bool;
Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform);
Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform,
nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -157,7 +194,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) {
TEST_F(ResolverStorageClassValidationTest, UniformBufferPointer) {
// var<uniform> g : ptr<i32, input>;
Global(Source{{56, 78}}, "g", ty.pointer<i32>(ast::StorageClass::kInput),
ast::StorageClass::kUniform);
ast::StorageClass::kUniform, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -171,7 +212,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferArray) {
auto* s = Structure("S", {Member("a", ty.f32())});
auto* a = ty.array(s, 3);
auto ac = ty.access(ast::AccessControl::kReadOnly, a);
Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kUniform);
Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kUniform, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -185,7 +230,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferBoolAlias) {
// var<uniform> g : [[access(read)]] a;
auto* a = ty.alias("a", ty.bool_());
AST().AddConstructedType(a);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform);
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -198,7 +247,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferNoBlockDecoration) {
// struct S { x : i32 };
// var<uniform> g : S;
auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())});
Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform);
Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_FALSE(r()->Resolve());
@@ -213,7 +266,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Basic) {
// var<uniform> g : S;
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
{create<ast::StructBlockDecoration>()});
Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform);
Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_TRUE(r()->Resolve());
}
@@ -226,7 +283,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Aliases) {
{create<ast::StructBlockDecoration>()});
auto* a1 = ty.alias("a1", s);
AST().AddConstructedType(a1);
Global(Source{{56, 78}}, "g", a1, ast::StorageClass::kUniform);
Global(Source{{56, 78}}, "g", a1, ast::StorageClass::kUniform, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
ASSERT_TRUE(r()->Resolve());
}

View File

@@ -173,8 +173,16 @@ TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
auto* s = Structure("S", {Member("a", ty.f32())},
{create<ast::StructBlockDecoration>()});
auto ac = ty.access(ast::AccessControl::kReadOnly, s);
Global("x", s, ast::StorageClass::kUniform);
Global("y", ac, ast::StorageClass::kStorage);
Global("x", s, ast::StorageClass::kUniform, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
Global("y", ac, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(1),
create<ast::GroupDecoration>(0),
});
WrapInFunction(Var("g", s, ast::StorageClass::kFunction));
ASSERT_TRUE(r()->Resolve()) << r()->error();

View File

@@ -377,7 +377,12 @@ TEST_F(ResolverValidationTest, StorageClass_NonFunctionClassError) {
TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) {
auto t = ty.sampler(ast::SamplerKind::kSampler);
Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant);
Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant,
nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
EXPECT_FALSE(r()->Resolve());
@@ -388,7 +393,12 @@ TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) {
TEST_F(ResolverValidationTest, StorageClass_TextureExplicitStorageClass) {
auto t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant);
Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant,
nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
EXPECT_FALSE(r()->Resolve()) << r()->error();