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

@ -448,6 +448,7 @@ libtint_source_set("libtint_core_all_src") {
"sem/access_control_type.cc", "sem/access_control_type.cc",
"sem/access_control_type.h", "sem/access_control_type.h",
"sem/array.h", "sem/array.h",
"sem/binding_point.h",
"sem/bool_type.cc", "sem/bool_type.cc",
"sem/bool_type.h", "sem/bool_type.h",
"sem/call.h", "sem/call.h",
@ -496,7 +497,6 @@ libtint_source_set("libtint_core_all_src") {
"symbol_table.cc", "symbol_table.cc",
"symbol_table.h", "symbol_table.h",
"traits.h", "traits.h",
"transform/binding_point.h",
"transform/binding_remapper.cc", "transform/binding_remapper.cc",
"transform/binding_remapper.h", "transform/binding_remapper.h",
"transform/bound_array_accessors.cc", "transform/bound_array_accessors.cc",

View File

@ -232,6 +232,7 @@ set(TINT_LIB_SRCS
scope_stack.h scope_stack.h
sem/array.cc sem/array.cc
sem/array.h sem/array.h
sem/binding_point.h
sem/call_target.cc sem/call_target.cc
sem/call_target.h sem/call_target.h
sem/call.cc sem/call.cc
@ -258,7 +259,6 @@ set(TINT_LIB_SRCS
symbol.h symbol.h
traits.h traits.h
typepair.h typepair.h
transform/binding_point.h
transform/binding_remapper.cc transform/binding_remapper.cc
transform/binding_remapper.h transform/binding_remapper.h
transform/bound_array_accessors.cc transform/bound_array_accessors.cc

View File

@ -41,16 +41,16 @@ type t1 = array<vec4<f32>>;
var<private> g0 : u32 = 20u; var<private> g0 : u32 = 20u;
var<private> g1 : f32 = 123.0; var<private> g1 : f32 = 123.0;
var g2 : texture_2d<f32>; [[group(0), binding(0)]] var g2 : texture_2d<f32>;
var g3 : [[access(read)]] texture_storage_2d<r32uint>; [[group(1), binding(0)]] var g3 : [[access(read)]] texture_storage_2d<r32uint>;
var g4 : [[access(write)]] texture_storage_2d<rg32float>; [[group(2), binding(0)]] var g4 : [[access(write)]] texture_storage_2d<rg32float>;
var g5 : [[access(read)]] texture_storage_2d<r32uint>; [[group(3), binding(0)]] var g5 : [[access(read)]] texture_storage_2d<r32uint>;
var g6 : [[access(write)]] texture_storage_2d<rg32float>; [[group(4), binding(0)]] var g6 : [[access(write)]] texture_storage_2d<rg32float>;
var<private> g7 : vec3<f32>; var<private> g7 : vec3<f32>;
[[group(10), binding(20)]] var<storage> g8 : [[access(write)]] S; [[group(0), binding(1)]] var<storage> g8 : [[access(write)]] S;
[[group(10), binding(20)]] var<storage> g9 : [[access(read)]] S; [[group(1), binding(1)]] var<storage> g9 : [[access(read)]] S;
[[group(10), binding(20)]] var<storage> g10 : [[access(read_write)]] S; [[group(2), binding(1)]] var<storage> g10 : [[access(read_write)]] S;
fn f0(p0 : bool) -> f32 { fn f0(p0 : bool) -> f32 {
if (p0) { if (p0) {

View File

@ -17,53 +17,35 @@
namespace tint { namespace tint {
namespace ast { namespace ast {
std::ostream& operator<<(std::ostream& out, StorageClass sc) { const char* str(StorageClass sc) {
switch (sc) { switch (sc) {
case StorageClass::kInvalid: { case StorageClass::kInvalid:
out << "invalid"; return "invalid";
break; case StorageClass::kNone:
} return "none";
case StorageClass::kNone: { case StorageClass::kInput:
out << "none"; return "in";
break; case StorageClass::kOutput:
} return "out";
case StorageClass::kInput: { case StorageClass::kUniform:
out << "in"; return "uniform";
break; case StorageClass::kWorkgroup:
} return "workgroup";
case StorageClass::kOutput: { case StorageClass::kUniformConstant:
out << "out"; return "uniform_constant";
break; case StorageClass::kStorage:
} return "storage";
case StorageClass::kUniform: { case StorageClass::kImage:
out << "uniform"; return "image";
break; case StorageClass::kPrivate:
} return "private";
case StorageClass::kWorkgroup: { case StorageClass::kFunction:
out << "workgroup"; return "function";
break;
}
case StorageClass::kUniformConstant: {
out << "uniform_constant";
break;
}
case StorageClass::kStorage: {
out << "storage";
break;
}
case StorageClass::kImage: {
out << "image";
break;
}
case StorageClass::kPrivate: {
out << "private";
break;
}
case StorageClass::kFunction: {
out << "function";
break;
}
} }
return "<unknown>";
}
std::ostream& operator<<(std::ostream& out, StorageClass sc) {
out << str(sc);
return out; return out;
} }

View File

@ -42,6 +42,10 @@ inline bool IsHostShareable(StorageClass sc) {
return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage; return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage;
} }
/// @param sc the StorageClass
/// @return the name of the given storage class
const char* str(StorageClass sc);
/// @param out the std::ostream to write to /// @param out the std::ostream to write to
/// @param sc the StorageClass /// @param sc the StorageClass
/// @return the std::ostream so calls can be chained /// @return the std::ostream so calls can be chained

View File

@ -381,7 +381,7 @@ class InspectorHelper : public ProgramBuilder {
} }
void AddGlobalVariable(const std::string& name, ast::Type* type) { void AddGlobalVariable(const std::string& name, ast::Type* type) {
Global(name, type, ast::StorageClass::kUniformConstant); Global(name, type, ast::StorageClass::kPrivate);
} }
/// Adds a depth texture variable to the program /// Adds a depth texture variable to the program

View File

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

View File

@ -43,46 +43,63 @@ enum class DecorationKind {
kStride, kStride,
kStructBlock, kStructBlock,
kWorkgroup, kWorkgroup,
kBindingAndGroup,
}; };
bool IsBindingDecoration(DecorationKind kind) {
switch (kind) {
case DecorationKind::kBinding:
case DecorationKind::kGroup:
case DecorationKind::kBindingAndGroup:
return true;
default:
return false;
}
}
struct TestParams { struct TestParams {
DecorationKind kind; DecorationKind kind;
bool should_pass; bool should_pass;
}; };
struct TestWithParams : ResolverTestWithParam<TestParams> {}; struct TestWithParams : ResolverTestWithParam<TestParams> {};
static ast::Decoration* createDecoration(const Source& source, static ast::DecorationList createDecorations(const Source& source,
ProgramBuilder& builder, ProgramBuilder& builder,
DecorationKind kind) { DecorationKind kind) {
switch (kind) { switch (kind) {
case DecorationKind::kAccess: case DecorationKind::kAccess:
return builder.create<ast::AccessDecoration>( return {builder.create<ast::AccessDecoration>(
source, ast::AccessControl::kReadOnly); source, ast::AccessControl::kReadOnly)};
case DecorationKind::kAlign: case DecorationKind::kAlign:
return builder.create<ast::StructMemberAlignDecoration>(source, 4u); return {builder.create<ast::StructMemberAlignDecoration>(source, 4u)};
case DecorationKind::kBinding: case DecorationKind::kBinding:
return builder.create<ast::BindingDecoration>(source, 1); return {builder.create<ast::BindingDecoration>(source, 1u)};
case DecorationKind::kBuiltin: case DecorationKind::kBuiltin:
return builder.Builtin(source, ast::Builtin::kPosition); return {builder.Builtin(source, ast::Builtin::kPosition)};
case DecorationKind::kGroup: case DecorationKind::kGroup:
return builder.create<ast::GroupDecoration>(source, 1u); return {builder.create<ast::GroupDecoration>(source, 1u)};
case DecorationKind::kLocation: case DecorationKind::kLocation:
return builder.Location(source, 1); return {builder.Location(source, 1)};
case DecorationKind::kOverride: case DecorationKind::kOverride:
return builder.create<ast::OverrideDecoration>(source, 0u); return {builder.create<ast::OverrideDecoration>(source, 0u)};
case DecorationKind::kOffset: case DecorationKind::kOffset:
return builder.create<ast::StructMemberOffsetDecoration>(source, 4u); return {builder.create<ast::StructMemberOffsetDecoration>(source, 4u)};
case DecorationKind::kSize: case DecorationKind::kSize:
return builder.create<ast::StructMemberSizeDecoration>(source, 4u); return {builder.create<ast::StructMemberSizeDecoration>(source, 4u)};
case DecorationKind::kStage: case DecorationKind::kStage:
return builder.Stage(source, ast::PipelineStage::kCompute); return {builder.Stage(source, ast::PipelineStage::kCompute)};
case DecorationKind::kStride: case DecorationKind::kStride:
return builder.create<ast::StrideDecoration>(source, 4u); return {builder.create<ast::StrideDecoration>(source, 4u)};
case DecorationKind::kStructBlock: case DecorationKind::kStructBlock:
return builder.create<ast::StructBlockDecoration>(source); return {builder.create<ast::StructBlockDecoration>(source)};
case DecorationKind::kWorkgroup: 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; using FunctionReturnTypeDecorationTest = TestWithParams;
@ -91,7 +108,7 @@ TEST_P(FunctionReturnTypeDecorationTest, IsValid) {
Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)}, Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
ast::DecorationList{Stage(ast::PipelineStage::kCompute)}, ast::DecorationList{Stage(ast::PipelineStage::kCompute)},
ast::DecorationList{createDecoration({}, *this, params.kind)}); createDecorations({}, *this, params.kind));
if (params.should_pass) { if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();
@ -116,17 +133,15 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false})); TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using ArrayDecorationTest = TestWithParams; using ArrayDecorationTest = TestWithParams;
TEST_P(ArrayDecorationTest, IsValid) { TEST_P(ArrayDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
auto* arr = auto* arr = ty.array(ty.f32(), 0,
ty.array(ty.f32(), 0, createDecorations(Source{{12, 34}}, *this, params.kind));
{
createDecoration(Source{{12, 34}}, *this, params.kind),
});
Structure("mystruct", Structure("mystruct",
{ {
Member("a", arr), Member("a", arr),
@ -158,14 +173,15 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, true}, TestParams{DecorationKind::kStride, true},
TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false})); TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using StructDecorationTest = TestWithParams; using StructDecorationTest = TestWithParams;
TEST_P(StructDecorationTest, IsValid) { TEST_P(StructDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
Structure("mystruct", {}, Structure("mystruct", {},
{createDecoration(Source{{12, 34}}, *this, params.kind)}); createDecorations(Source{{12, 34}}, *this, params.kind));
WrapInFunction(); WrapInFunction();
@ -192,16 +208,15 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, true}, TestParams{DecorationKind::kStructBlock, true},
TestParams{DecorationKind::kWorkgroup, false})); TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using StructMemberDecorationTest = TestWithParams; using StructMemberDecorationTest = TestWithParams;
TEST_P(StructMemberDecorationTest, IsValid) { TEST_P(StructMemberDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
ast::StructMemberList members{ ast::StructMemberList members{Member(
Member("a", ty.i32(), "a", ty.i32(), createDecorations(Source{{12, 34}}, *this, params.kind))};
ast::DecorationList{
createDecoration(Source{{12, 34}}, *this, params.kind)})};
Structure("mystruct", members); Structure("mystruct", members);
@ -230,15 +245,21 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false})); TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using VariableDecorationTest = TestWithParams; using VariableDecorationTest = TestWithParams;
TEST_P(VariableDecorationTest, IsValid) { TEST_P(VariableDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
Global("a", ty.f32(), ast::StorageClass::kInput, nullptr, if (IsBindingDecoration(params.kind)) {
ast::DecorationList{ Global("a", ty.sampler(ast::SamplerKind::kSampler),
createDecoration(Source{{12, 34}}, *this, params.kind)}); 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(); WrapInFunction();
@ -246,8 +267,10 @@ TEST_P(VariableDecorationTest, IsValid) {
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();
} else { } else {
EXPECT_FALSE(r()->Resolve()) << r()->error(); EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), if (!IsBindingDecoration(params.kind)) {
"12:34 error: decoration is not valid for variables"); EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for variables");
}
} }
} }
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(
@ -255,9 +278,9 @@ INSTANTIATE_TEST_SUITE_P(
VariableDecorationTest, VariableDecorationTest,
testing::Values(TestParams{DecorationKind::kAccess, false}, testing::Values(TestParams{DecorationKind::kAccess, false},
TestParams{DecorationKind::kAlign, false}, TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, true}, TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, true}, TestParams{DecorationKind::kBuiltin, true},
TestParams{DecorationKind::kGroup, true}, TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kLocation, true}, TestParams{DecorationKind::kLocation, true},
TestParams{DecorationKind::kOverride, false}, TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false}, TestParams{DecorationKind::kOffset, false},
@ -265,15 +288,15 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false})); TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, true}));
using ConstantDecorationTest = TestWithParams; using ConstantDecorationTest = TestWithParams;
TEST_P(ConstantDecorationTest, IsValid) { TEST_P(ConstantDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
GlobalConst("a", ty.f32(), nullptr, GlobalConst("a", ty.f32(), Expr(1.23f),
ast::DecorationList{ createDecorations(Source{{12, 34}}, *this, params.kind));
createDecoration(Source{{12, 34}}, *this, params.kind)});
WrapInFunction(); WrapInFunction();
@ -300,16 +323,17 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false})); TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using FunctionDecorationTest = TestWithParams; using FunctionDecorationTest = TestWithParams;
TEST_P(FunctionDecorationTest, IsValid) { TEST_P(FunctionDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
Func("foo", ast::VariableList{}, ty.void_(), ast::StatementList{}, ast::DecorationList decos =
ast::DecorationList{ createDecorations(Source{{12, 34}}, *this, params.kind);
Stage(ast::PipelineStage::kCompute), decos.emplace_back(Stage(ast::PipelineStage::kCompute));
createDecoration(Source{{12, 34}}, *this, params.kind)}); Func("foo", ast::VariableList{}, ty.void_(), ast::StatementList{}, decos);
if (params.should_pass) { if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();
@ -334,7 +358,8 @@ INSTANTIATE_TEST_SUITE_P(
// Skip kStage as we always apply it in this test // Skip kStage as we always apply it in this test
TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, true})); TestParams{DecorationKind::kWorkgroup, true},
TestParams{DecorationKind::kBindingAndGroup, false}));
} // namespace } // namespace
} // namespace DecorationTests } // namespace DecorationTests
@ -480,5 +505,158 @@ TEST_F(StructBlockTest, StructUsedAsArrayElement) {
} // namespace } // namespace
} // namespace StructBlockTests } // 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 resolver
} // namespace tint } // namespace tint

View File

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

View File

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

View File

@ -466,20 +466,10 @@ bool Resolver::GlobalVariable(ast::Variable* var) {
for (auto* deco : var->decorations()) { for (auto* deco : var->decorations()) {
Mark(deco); Mark(deco);
if (var->is_const()) { }
if (!deco->Is<ast::OverrideDecoration>()) {
diagnostics_.add_error("decoration is not valid for constants", if (auto bp = var->binding_point()) {
deco->source()); info->binding_point = {bp.group->value(), bp.binding->value()};
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 (var->has_constructor()) { if (var->has_constructor()) {
@ -512,6 +502,53 @@ bool Resolver::GlobalVariable(ast::Variable* var) {
} }
bool Resolver::ValidateGlobalVariable(const VariableInfo* info) { 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) { switch (info->storage_class) {
case ast::StorageClass::kStorage: { case ast::StorageClass::kStorage: {
// https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration // 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; return true;
} }

View File

@ -24,6 +24,7 @@
#include "src/intrinsic_table.h" #include "src/intrinsic_table.h"
#include "src/program_builder.h" #include "src/program_builder.h"
#include "src/scope_stack.h" #include "src/scope_stack.h"
#include "src/sem/binding_point.h"
#include "src/sem/struct.h" #include "src/sem/struct.h"
#include "src/utils/unique_vector.h" #include "src/utils/unique_vector.h"
@ -97,6 +98,7 @@ class Resolver {
std::string const type_name; std::string const type_name;
ast::StorageClass storage_class; ast::StorageClass storage_class;
std::vector<ast::IdentifierExpression*> users; std::vector<ast::IdentifierExpression*> users;
sem::BindingPoint binding_point;
}; };
/// Structure holding semantic information about a function. /// 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* my_var_b = Expr("my_var");
auto* assign = Assign(my_var_a, my_var_b); 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_(), Func("my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{ ast::StatementList{
@ -770,7 +774,11 @@ TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput); auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput); 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* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate); 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* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput);
auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput); 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* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup);
auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate); auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
@ -1447,7 +1459,11 @@ INSTANTIATE_TEST_SUITE_P(ResolverTest,
ast::UnaryOp::kNot)); ast::UnaryOp::kNot));
TEST_F(ResolverTest, StorageClass_SetsIfMissing) { 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); auto* stmt = Decl(var);
Func("func", ast::VariableList{}, ty.void_(), ast::StatementList{stmt}, Func("func", ast::VariableList{}, ty.void_(), ast::StatementList{stmt},
@ -1460,7 +1476,11 @@ TEST_F(ResolverTest, StorageClass_SetsIfMissing) {
TEST_F(ResolverTest, StorageClass_SetForSampler) { TEST_F(ResolverTest, StorageClass_SetForSampler) {
auto t = ty.sampler(ast::SamplerKind::kSampler); 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(); EXPECT_TRUE(r()->Resolve()) << r()->error();
@ -1471,7 +1491,11 @@ TEST_F(ResolverTest, StorageClass_SetForSampler) {
TEST_F(ResolverTest, StorageClass_SetForTexture) { TEST_F(ResolverTest, StorageClass_SetForTexture) {
auto t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); auto t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32());
auto ac = ty.access(ast::AccessControl::Access::kReadOnly, t); 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(); EXPECT_TRUE(r()->Resolve()) << r()->error();

View File

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

View File

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

View File

@ -377,7 +377,12 @@ TEST_F(ResolverValidationTest, StorageClass_NonFunctionClassError) {
TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) { TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) {
auto t = ty.sampler(ast::SamplerKind::kSampler); 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()); EXPECT_FALSE(r()->Resolve());
@ -388,7 +393,12 @@ TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) {
TEST_F(ResolverValidationTest, StorageClass_TextureExplicitStorageClass) { TEST_F(ResolverValidationTest, StorageClass_TextureExplicitStorageClass) {
auto t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); 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(); EXPECT_FALSE(r()->Resolve()) << r()->error();

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#ifndef SRC_TRANSFORM_BINDING_POINT_H_ #ifndef SRC_SEM_BINDING_POINT_H_
#define SRC_TRANSFORM_BINDING_POINT_H_ #define SRC_SEM_BINDING_POINT_H_
#include <stdint.h> #include <stdint.h>
@ -22,7 +22,7 @@
#include "src/utils/hash.h" #include "src/utils/hash.h"
namespace tint { namespace tint {
namespace transform { namespace sem {
/// BindingPoint holds a group and binding index. /// BindingPoint holds a group and binding index.
struct BindingPoint { struct BindingPoint {
@ -46,25 +46,25 @@ struct BindingPoint {
} }
}; };
} // namespace transform } // namespace sem
} // namespace tint } // namespace tint
namespace std { namespace std {
/// Custom std::hash specialization for tint::transform::BindingPoint so /// Custom std::hash specialization for tint::sem::BindingPoint so
/// BindingPoints can be used as keys for std::unordered_map and /// BindingPoints can be used as keys for std::unordered_map and
/// std::unordered_set. /// std::unordered_set.
template <> template <>
class hash<tint::transform::BindingPoint> { class hash<tint::sem::BindingPoint> {
public: public:
/// @param binding_point the binding point to create a hash for /// @param binding_point the binding point to create a hash for
/// @return the hash value /// @return the hash value
inline std::size_t operator()( inline std::size_t operator()(
const tint::transform::BindingPoint& binding_point) const { const tint::sem::BindingPoint& binding_point) const {
return tint::utils::Hash(binding_point.group, binding_point.binding); return tint::utils::Hash(binding_point.group, binding_point.binding);
} }
}; };
} // namespace std } // namespace std
#endif // SRC_TRANSFORM_BINDING_POINT_H_ #endif // SRC_SEM_BINDING_POINT_H_

View File

@ -18,12 +18,15 @@
#include <unordered_map> #include <unordered_map>
#include "src/ast/access_control.h" #include "src/ast/access_control.h"
#include "src/transform/binding_point.h" #include "src/sem/binding_point.h"
#include "src/transform/transform.h" #include "src/transform/transform.h"
namespace tint { namespace tint {
namespace transform { namespace transform {
/// BindingPoint is an alias to sem::BindingPoint
using BindingPoint = sem::BindingPoint;
/// BindingRemapper is a transform used to remap resource binding points and /// BindingRemapper is a transform used to remap resource binding points and
/// access controls. /// access controls.
class BindingRemapper : public Transform { class BindingRemapper : public Transform {

View File

@ -540,7 +540,7 @@ struct S {
a : f32; a : f32;
b : array<f32>; b : array<f32>;
}; };
var<storage> s : [[access(read)]] S; [[group(0), binding(0)]] var<storage> s : [[access(read)]] S;
fn f() { fn f() {
var d : f32 = s.b[25]; var d : f32 = s.b[25];
@ -554,7 +554,7 @@ struct S {
b : array<f32>; b : array<f32>;
}; };
var<storage> s : [[access(read)]] S; [[group(0), binding(0)]] var<storage> s : [[access(read)]] S;
fn f() { fn f() {
var d : f32 = s.b[min(u32(25), (arrayLength(s.b) - 1u))]; var d : f32 = s.b[min(u32(25), (arrayLength(s.b) - 1u))];
@ -601,7 +601,7 @@ struct S {
b : array<f32>; b : array<f32>;
}; };
var<storage> s : [[access(read)]] S; [[group(0), binding(0)]] var<storage> s : [[access(read)]] S;
let c : u32 = 1u; let c : u32 = 1u;
@ -619,7 +619,7 @@ struct S {
b : array<f32>; b : array<f32>;
}; };
var<storage> s : [[access(read)]] S; [[group(0), binding(0)]] var<storage> s : [[access(read)]] S;
let c : u32 = 1u; let c : u32 = 1u;

View File

@ -30,7 +30,7 @@ struct SB {
arr : array<i32>; arr : array<i32>;
}; };
var<storage> sb : [[access(read)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -48,7 +48,7 @@ struct SB {
[[internal(intrinsic_buffer_size)]] [[internal(intrinsic_buffer_size)]]
fn tint_symbol(buffer : SB, result : ptr<function, u32>) fn tint_symbol(buffer : SB, result : ptr<function, u32>)
var<storage> sb : [[access(read)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -72,7 +72,7 @@ struct SB {
arr : array<i32>; arr : array<i32>;
}; };
var<storage> sb : [[access(read)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -92,7 +92,7 @@ struct SB {
[[internal(intrinsic_buffer_size)]] [[internal(intrinsic_buffer_size)]]
fn tint_symbol(buffer : SB, result : ptr<function, u32>) fn tint_symbol(buffer : SB, result : ptr<function, u32>)
var<storage> sb : [[access(read)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -119,7 +119,7 @@ struct SB {
arr : [[stride(64)]] array<i32>; arr : [[stride(64)]] array<i32>;
}; };
var<storage> sb : [[access(read)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -138,7 +138,7 @@ struct SB {
[[internal(intrinsic_buffer_size)]] [[internal(intrinsic_buffer_size)]]
fn tint_symbol(buffer : SB, result : ptr<function, u32>) fn tint_symbol(buffer : SB, result : ptr<function, u32>)
var<storage> sb : [[access(read)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -162,7 +162,7 @@ struct SB {
arr : array<i32>; arr : array<i32>;
}; };
var<storage> sb : [[access(read)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -186,7 +186,7 @@ struct SB {
[[internal(intrinsic_buffer_size)]] [[internal(intrinsic_buffer_size)]]
fn tint_symbol(buffer : SB, result : ptr<function, u32>) fn tint_symbol(buffer : SB, result : ptr<function, u32>)
var<storage> sb : [[access(read)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -225,9 +225,9 @@ struct SB2 {
arr2 : array<vec4<f32>>; arr2 : array<vec4<f32>>;
}; };
var<storage> sb1 : [[access(read)]] SB1; [[group(0), binding(0)]] var<storage> sb1 : [[access(read)]] SB1;
var<storage> sb2 : [[access(read)]] SB2; [[group(0), binding(1)]] var<storage> sb2 : [[access(read)]] SB2;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -256,9 +256,9 @@ struct SB2 {
[[internal(intrinsic_buffer_size)]] [[internal(intrinsic_buffer_size)]]
fn tint_symbol_3(buffer : SB2, result : ptr<function, u32>) fn tint_symbol_3(buffer : SB2, result : ptr<function, u32>)
var<storage> sb1 : [[access(read)]] SB1; [[group(0), binding(0)]] var<storage> sb1 : [[access(read)]] SB1;
var<storage> sb2 : [[access(read)]] SB2; [[group(0), binding(1)]] var<storage> sb2 : [[access(read)]] SB2;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {

View File

@ -50,7 +50,7 @@ struct SB {
v : array<vec3<f32>, 2>; v : array<vec3<f32>, 2>;
}; };
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -182,7 +182,7 @@ fn tint_symbol_21(buffer : [[access(read_write)]] SB, offset : u32) -> array<vec
return array<vec3<f32>, 2>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u))); return array<vec3<f32>, 2>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
} }
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -244,7 +244,7 @@ struct SB {
v : array<vec3<f32>, 2>; v : array<vec3<f32>, 2>;
}; };
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -395,7 +395,7 @@ fn tint_symbol_21(buffer : [[access(read_write)]] SB, offset : u32, value : arra
tint_symbol_8(buffer, (offset + 16u), value[1u]); tint_symbol_8(buffer, (offset + 16u), value[1u]);
} }
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -457,7 +457,7 @@ struct SB {
v : array<vec3<f32>, 2>; v : array<vec3<f32>, 2>;
}; };
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -572,7 +572,7 @@ fn tint_symbol_22(buffer : [[access(read_write)]] SB, offset : u32) -> SB {
return SB(tint_symbol(buffer, (offset + 0u)), tint_symbol_1(buffer, (offset + 4u)), tint_symbol_2(buffer, (offset + 8u)), tint_symbol_3(buffer, (offset + 16u)), tint_symbol_4(buffer, (offset + 24u)), tint_symbol_5(buffer, (offset + 32u)), tint_symbol_6(buffer, (offset + 48u)), tint_symbol_7(buffer, (offset + 64u)), tint_symbol_8(buffer, (offset + 80u)), tint_symbol_9(buffer, (offset + 96u)), tint_symbol_10(buffer, (offset + 112u)), tint_symbol_11(buffer, (offset + 128u)), tint_symbol_12(buffer, (offset + 144u)), tint_symbol_13(buffer, (offset + 160u)), tint_symbol_14(buffer, (offset + 192u)), tint_symbol_15(buffer, (offset + 224u)), tint_symbol_16(buffer, (offset + 256u)), tint_symbol_17(buffer, (offset + 304u)), tint_symbol_18(buffer, (offset + 352u)), tint_symbol_19(buffer, (offset + 384u)), tint_symbol_20(buffer, (offset + 448u)), tint_symbol_21(buffer, (offset + 512u))); return SB(tint_symbol(buffer, (offset + 0u)), tint_symbol_1(buffer, (offset + 4u)), tint_symbol_2(buffer, (offset + 8u)), tint_symbol_3(buffer, (offset + 16u)), tint_symbol_4(buffer, (offset + 24u)), tint_symbol_5(buffer, (offset + 32u)), tint_symbol_6(buffer, (offset + 48u)), tint_symbol_7(buffer, (offset + 64u)), tint_symbol_8(buffer, (offset + 80u)), tint_symbol_9(buffer, (offset + 96u)), tint_symbol_10(buffer, (offset + 112u)), tint_symbol_11(buffer, (offset + 128u)), tint_symbol_12(buffer, (offset + 144u)), tint_symbol_13(buffer, (offset + 160u)), tint_symbol_14(buffer, (offset + 192u)), tint_symbol_15(buffer, (offset + 224u)), tint_symbol_16(buffer, (offset + 256u)), tint_symbol_17(buffer, (offset + 304u)), tint_symbol_18(buffer, (offset + 352u)), tint_symbol_19(buffer, (offset + 384u)), tint_symbol_20(buffer, (offset + 448u)), tint_symbol_21(buffer, (offset + 512u)));
} }
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -613,7 +613,7 @@ struct SB {
v : array<vec3<f32>, 2>; v : array<vec3<f32>, 2>;
}; };
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -768,7 +768,7 @@ fn tint_symbol_22(buffer : [[access(read_write)]] SB, offset : u32, value : SB)
tint_symbol_21(buffer, (offset + 512u), value.v); tint_symbol_21(buffer, (offset + 512u), value.v);
} }
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -802,7 +802,7 @@ struct SB {
b : [[stride(256)]] array<S2>; b : [[stride(256)]] array<S2>;
}; };
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -840,7 +840,7 @@ struct SB {
[[internal(intrinsic_load_f32)]] [[internal(intrinsic_load_f32)]]
fn tint_symbol(buffer : [[access(read_write)]] SB, offset : u32) -> f32 fn tint_symbol(buffer : [[access(read_write)]] SB, offset : u32) -> f32
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -874,7 +874,7 @@ struct SB {
b : [[stride(256)]] array<S2>; b : [[stride(256)]] array<S2>;
}; };
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -908,7 +908,7 @@ struct SB {
[[internal(intrinsic_load_f32)]] [[internal(intrinsic_load_f32)]]
fn tint_symbol(buffer : [[access(read_write)]] SB, offset : u32) -> f32 fn tint_symbol(buffer : [[access(read_write)]] SB, offset : u32) -> f32
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -953,7 +953,7 @@ struct SB {
b : A2_Array; b : A2_Array;
}; };
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {
@ -995,7 +995,7 @@ struct SB {
[[internal(intrinsic_load_f32)]] [[internal(intrinsic_load_f32)]]
fn tint_symbol(buffer : [[access(read_write)]] SB, offset : u32) -> f32 fn tint_symbol(buffer : [[access(read_write)]] SB, offset : u32) -> f32
var<storage> sb : [[access(read_write)]] SB; [[group(0), binding(0)]] var<storage> sb : [[access(read_write)]] SB;
[[stage(compute)]] [[stage(compute)]]
fn main() { fn main() {

View File

@ -183,7 +183,11 @@ TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl_OmittedIfStorageBuffer) {
}, },
{create<ast::StructBlockDecoration>()}); {create<ast::StructBlockDecoration>()});
Global("g", ty.access(ast::AccessControl::kReadWrite, s), Global("g", ty.access(ast::AccessControl::kReadWrite, s),
ast::StorageClass::kStorage); ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
GeneratorImpl& gen = Build(); GeneratorImpl& gen = Build();

View File

@ -232,7 +232,11 @@ TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_NonComposites) {
{create<ast::StructBlockDecoration>()}); {create<ast::StructBlockDecoration>()});
Global("G", ty.access(ast::AccessControl::kReadOnly, s), Global("G", ty.access(ast::AccessControl::kReadOnly, s),
ast::StorageClass::kStorage); ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
GeneratorImpl& gen = Build(); GeneratorImpl& gen = Build();
@ -338,7 +342,11 @@ TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_Structures) {
{create<ast::StructBlockDecoration>()}); {create<ast::StructBlockDecoration>()});
Global("G", ty.access(ast::AccessControl::kReadOnly, s), Global("G", ty.access(ast::AccessControl::kReadOnly, s),
ast::StorageClass::kStorage); ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
GeneratorImpl& gen = Build(); GeneratorImpl& gen = Build();
@ -429,7 +437,11 @@ TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayDefaultStride) {
ast::DecorationList{create<ast::StructBlockDecoration>()}); ast::DecorationList{create<ast::StructBlockDecoration>()});
Global("G", ty.access(ast::AccessControl::kReadOnly, s), Global("G", ty.access(ast::AccessControl::kReadOnly, s),
ast::StorageClass::kStorage); ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
GeneratorImpl& gen = Build(); GeneratorImpl& gen = Build();
@ -532,7 +544,11 @@ TEST_F(MslGeneratorImplTest, AttemptTintPadSymbolCollision) {
{create<ast::StructBlockDecoration>()}); {create<ast::StructBlockDecoration>()});
Global("G", ty.access(ast::AccessControl::kReadOnly, s), Global("G", ty.access(ast::AccessControl::kReadOnly, s),
ast::StorageClass::kStorage); ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
GeneratorImpl& gen = Build(); GeneratorImpl& gen = Build();
@ -594,7 +610,11 @@ TEST_F(MslGeneratorImplTest, DISABLED_EmitType_Struct_WithDecoration) {
{create<ast::StructBlockDecoration>()}); {create<ast::StructBlockDecoration>()});
Global("G", ty.access(ast::AccessControl::kReadOnly, s), Global("G", ty.access(ast::AccessControl::kReadOnly, s),
ast::StorageClass::kStorage); ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
GeneratorImpl& gen = Build(); GeneratorImpl& gen = Build();
@ -745,7 +765,11 @@ TEST_P(MslStorageTexturesTest, Emit) {
auto ac = ty.access(params.ro ? ast::AccessControl::kReadOnly auto ac = ty.access(params.ro ? ast::AccessControl::kReadOnly
: ast::AccessControl::kWriteOnly, : ast::AccessControl::kWriteOnly,
s); s);
Global("test_var", ac, ast::StorageClass::kNone); Global("test_var", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
GeneratorImpl& gen = Build(); GeneratorImpl& gen = Build();

View File

@ -162,7 +162,8 @@ TEST_F(BuilderTest, GlobalVar_WithLocation) {
} }
TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) { TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) {
auto* v = Global("var", ty.f32(), ast::StorageClass::kOutput, nullptr, auto* v = Global("var", ty.sampler(ast::SamplerKind::kSampler),
ast::StorageClass::kNone, nullptr,
ast::DecorationList{ ast::DecorationList{
create<ast::BindingDecoration>(2), create<ast::BindingDecoration>(2),
create<ast::GroupDecoration>(3), create<ast::GroupDecoration>(3),
@ -176,10 +177,9 @@ TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) {
EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 Binding 2 EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 Binding 2
OpDecorate %1 DescriptorSet 3 OpDecorate %1 DescriptorSet 3
)"); )");
EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32 EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeSampler
%2 = OpTypePointer Output %3 %2 = OpTypePointer UniformConstant %3
%4 = OpConstantNull %3 %1 = OpVariable %2 UniformConstant
%1 = OpVariable %2 Output %4
)"); )");
} }
@ -384,7 +384,11 @@ TEST_F(BuilderTest, GlobalVar_DeclReadOnly) {
{create<ast::StructBlockDecoration>()}); {create<ast::StructBlockDecoration>()});
auto ac = ty.access(ast::AccessControl::kReadOnly, A); auto ac = ty.access(ast::AccessControl::kReadOnly, A);
auto* var = Global("b", ac, ast::StorageClass::kStorage); auto* var = Global("b", ac, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -395,6 +399,8 @@ OpMemberDecorate %3 0 Offset 0
OpMemberDecorate %3 0 NonWritable OpMemberDecorate %3 0 NonWritable
OpMemberDecorate %3 1 Offset 4 OpMemberDecorate %3 1 Offset 4
OpMemberDecorate %3 1 NonWritable OpMemberDecorate %3 1 NonWritable
OpDecorate %1 Binding 0
OpDecorate %1 DescriptorSet 0
)"); )");
EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A" EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
OpMemberName %3 0 "a" OpMemberName %3 0 "a"
@ -420,7 +426,11 @@ TEST_F(BuilderTest, GlobalVar_TypeAliasDeclReadOnly) {
auto* B = ty.alias("B", A); auto* B = ty.alias("B", A);
AST().AddConstructedType(B); AST().AddConstructedType(B);
auto ac = ty.access(ast::AccessControl::kReadOnly, B); auto ac = ty.access(ast::AccessControl::kReadOnly, B);
auto* var = Global("b", ac, ast::StorageClass::kStorage); auto* var = Global("b", ac, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -429,6 +439,8 @@ TEST_F(BuilderTest, GlobalVar_TypeAliasDeclReadOnly) {
EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
OpMemberDecorate %3 0 Offset 0 OpMemberDecorate %3 0 Offset 0
OpMemberDecorate %3 0 NonWritable OpMemberDecorate %3 0 NonWritable
OpDecorate %1 Binding 0
OpDecorate %1 DescriptorSet 0
)"); )");
EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A" EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
OpMemberName %3 0 "a" OpMemberName %3 0 "a"
@ -453,7 +465,11 @@ TEST_F(BuilderTest, GlobalVar_TypeAliasAssignReadOnly) {
auto ac = ty.access(ast::AccessControl::kReadOnly, A); auto ac = ty.access(ast::AccessControl::kReadOnly, A);
auto* B = ty.alias("B", ac); auto* B = ty.alias("B", ac);
AST().AddConstructedType(B); AST().AddConstructedType(B);
auto* var = Global("b", B, ast::StorageClass::kStorage); auto* var = Global("b", B, ast::StorageClass::kStorage, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -462,6 +478,8 @@ TEST_F(BuilderTest, GlobalVar_TypeAliasAssignReadOnly) {
EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block
OpMemberDecorate %3 0 Offset 0 OpMemberDecorate %3 0 Offset 0
OpMemberDecorate %3 0 NonWritable OpMemberDecorate %3 0 NonWritable
OpDecorate %1 Binding 0
OpDecorate %1 DescriptorSet 0
)"); )");
EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A" EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
OpMemberName %3 0 "a" OpMemberName %3 0 "a"
@ -486,8 +504,16 @@ TEST_F(BuilderTest, GlobalVar_TwoVarDeclReadOnly) {
auto read = ty.access(ast::AccessControl::kReadOnly, A); auto read = ty.access(ast::AccessControl::kReadOnly, A);
auto rw = ty.access(ast::AccessControl::kReadWrite, A); auto rw = ty.access(ast::AccessControl::kReadWrite, A);
auto* var_b = Global("b", read, ast::StorageClass::kStorage); auto* var_b = Global("b", read, ast::StorageClass::kStorage, nullptr,
auto* var_c = Global("c", rw, ast::StorageClass::kStorage); {
create<ast::GroupDecoration>(0),
create<ast::BindingDecoration>(0),
});
auto* var_c = Global("c", rw, ast::StorageClass::kStorage, nullptr,
{
create<ast::GroupDecoration>(1),
create<ast::BindingDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -498,8 +524,12 @@ TEST_F(BuilderTest, GlobalVar_TwoVarDeclReadOnly) {
R"(OpDecorate %3 Block R"(OpDecorate %3 Block
OpMemberDecorate %3 0 Offset 0 OpMemberDecorate %3 0 Offset 0
OpMemberDecorate %3 0 NonWritable OpMemberDecorate %3 0 NonWritable
OpDecorate %1 DescriptorSet 0
OpDecorate %1 Binding 0
OpDecorate %7 Block OpDecorate %7 Block
OpMemberDecorate %7 0 Offset 0 OpMemberDecorate %7 0 Offset 0
OpDecorate %5 DescriptorSet 1
OpDecorate %5 Binding 0
)"); )");
EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A" EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
OpMemberName %3 0 "a" OpMemberName %3 0 "a"
@ -526,13 +556,19 @@ TEST_F(BuilderTest, GlobalVar_TextureStorageReadOnly) {
auto ac = ty.access(ast::AccessControl::kReadOnly, type); auto ac = ty.access(ast::AccessControl::kReadOnly, type);
auto* var_a = Global("a", ac, ast::StorageClass::kNone); auto* var_a = Global("a", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error(); EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonWritable EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonWritable
OpDecorate %1 Binding 0
OpDecorate %1 DescriptorSet 0
)"); )");
EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0 EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
%3 = OpTypeImage %4 2D 0 0 0 2 R32ui %3 = OpTypeImage %4 2D 0 0 0 2 R32ui
@ -549,13 +585,19 @@ TEST_F(BuilderTest, GlobalVar_TextureStorageWriteOnly) {
auto ac = ty.access(ast::AccessControl::kWriteOnly, type); auto ac = ty.access(ast::AccessControl::kWriteOnly, type);
auto* var_a = Global("a", ac, ast::StorageClass::kNone); auto* var_a = Global("a", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error(); EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error();
EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonReadable EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonReadable
OpDecorate %1 Binding 0
OpDecorate %1 DescriptorSet 0
)"); )");
EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0 EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0
%3 = OpTypeImage %4 2D 0 0 0 2 R32ui %3 = OpTypeImage %4 2D 0 0 0 2 R32ui
@ -573,12 +615,20 @@ TEST_F(BuilderTest, GlobalVar_TextureStorageWithDifferentAccess) {
auto type_a = ty.access(ast::AccessControl::kReadOnly, auto type_a = ty.access(ast::AccessControl::kReadOnly,
ty.storage_texture(ast::TextureDimension::k2d, ty.storage_texture(ast::TextureDimension::k2d,
ast::ImageFormat::kR32Uint)); ast::ImageFormat::kR32Uint));
auto* var_a = Global("a", type_a, ast::StorageClass::kNone); auto* var_a = Global("a", type_a, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
auto type_b = ty.access(ast::AccessControl::kWriteOnly, auto type_b = ty.access(ast::AccessControl::kWriteOnly,
ty.storage_texture(ast::TextureDimension::k2d, ty.storage_texture(ast::TextureDimension::k2d,
ast::ImageFormat::kR32Uint)); ast::ImageFormat::kR32Uint));
auto* var_b = Global("b", type_b, ast::StorageClass::kNone); auto* var_b = Global("b", type_b, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(1),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -586,7 +636,11 @@ TEST_F(BuilderTest, GlobalVar_TextureStorageWithDifferentAccess) {
EXPECT_TRUE(b.GenerateGlobalVariable(var_b)) << b.error(); EXPECT_TRUE(b.GenerateGlobalVariable(var_b)) << b.error();
EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonWritable EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonWritable
OpDecorate %1 Binding 0
OpDecorate %1 DescriptorSet 0
OpDecorate %5 NonReadable OpDecorate %5 NonReadable
OpDecorate %5 Binding 1
OpDecorate %5 DescriptorSet 0
)"); )");
// There must only be one OpTypeImage declaration with the same // There must only be one OpTypeImage declaration with the same
// arguments // arguments

View File

@ -373,9 +373,17 @@ TEST_F(IntrinsicBuilderTest, Call_TextureSampleCompare_Twice) {
auto s = ty.sampler(ast::SamplerKind::kComparisonSampler); auto s = ty.sampler(ast::SamplerKind::kComparisonSampler);
auto t = ty.depth_texture(ast::TextureDimension::k2d); auto t = ty.depth_texture(ast::TextureDimension::k2d);
auto* tex = Global("texture", t, ast::StorageClass::kNone); auto* tex = Global("texture", t, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
auto* sampler = Global("sampler", s, ast::StorageClass::kNone); auto* sampler = Global("sampler", s, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(1),
create<ast::GroupDecoration>(0),
});
auto* expr1 = Call("textureSampleCompare", "texture", "sampler", auto* expr1 = Call("textureSampleCompare", "texture", "sampler",
vec2<f32>(1.0f, 2.0f), 2.0f); vec2<f32>(1.0f, 2.0f), 2.0f);

View File

@ -31,7 +31,11 @@ TEST_F(BuilderTest_Type, GenerateRuntimeArray) {
auto* str = Structure("S", {Member("x", ary)}, auto* str = Structure("S", {Member("x", ary)},
{create<ast::StructBlockDecoration>()}); {create<ast::StructBlockDecoration>()});
auto ac = ty.access(ast::AccessControl::kReadOnly, str); 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),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -49,7 +53,11 @@ TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) {
auto* str = Structure("S", {Member("x", ary)}, auto* str = Structure("S", {Member("x", ary)},
{create<ast::StructBlockDecoration>()}); {create<ast::StructBlockDecoration>()});
auto ac = ty.access(ast::AccessControl::kReadOnly, str); 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),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -816,7 +824,11 @@ TEST_F(BuilderTest_Type, StorageTexture_Generate_1d) {
ast::ImageFormat::kR32Float); ast::ImageFormat::kR32Float);
auto ac = ty.access(ast::AccessControl::kReadOnly, s); auto ac = ty.access(ast::AccessControl::kReadOnly, s);
Global("test_var", ac, ast::StorageClass::kNone); Global("test_var", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -832,7 +844,11 @@ TEST_F(BuilderTest_Type, StorageTexture_Generate_2d) {
ast::ImageFormat::kR32Float); ast::ImageFormat::kR32Float);
auto ac = ty.access(ast::AccessControl::kReadOnly, s); auto ac = ty.access(ast::AccessControl::kReadOnly, s);
Global("test_var", ac, ast::StorageClass::kNone); Global("test_var", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -848,7 +864,11 @@ TEST_F(BuilderTest_Type, StorageTexture_Generate_2dArray) {
ast::ImageFormat::kR32Float); ast::ImageFormat::kR32Float);
auto ac = ty.access(ast::AccessControl::kReadOnly, s); auto ac = ty.access(ast::AccessControl::kReadOnly, s);
Global("test_var", ac, ast::StorageClass::kNone); Global("test_var", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -864,7 +884,11 @@ TEST_F(BuilderTest_Type, StorageTexture_Generate_3d) {
ast::ImageFormat::kR32Float); ast::ImageFormat::kR32Float);
auto ac = ty.access(ast::AccessControl::kReadOnly, s); auto ac = ty.access(ast::AccessControl::kReadOnly, s);
Global("test_var", ac, ast::StorageClass::kNone); Global("test_var", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -881,7 +905,11 @@ TEST_F(BuilderTest_Type,
ast::ImageFormat::kR32Float); ast::ImageFormat::kR32Float);
auto ac = ty.access(ast::AccessControl::kReadOnly, s); auto ac = ty.access(ast::AccessControl::kReadOnly, s);
Global("test_var", ac, ast::StorageClass::kNone); Global("test_var", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -898,7 +926,11 @@ TEST_F(BuilderTest_Type,
ast::ImageFormat::kR32Sint); ast::ImageFormat::kR32Sint);
auto ac = ty.access(ast::AccessControl::kReadOnly, s); auto ac = ty.access(ast::AccessControl::kReadOnly, s);
Global("test_var", ac, ast::StorageClass::kNone); Global("test_var", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();
@ -915,7 +947,11 @@ TEST_F(BuilderTest_Type,
ast::ImageFormat::kR32Uint); ast::ImageFormat::kR32Uint);
auto ac = ty.access(ast::AccessControl::kReadOnly, s); auto ac = ty.access(ast::AccessControl::kReadOnly, s);
Global("test_var", ac, ast::StorageClass::kNone); Global("test_var", ac, ast::StorageClass::kNone, nullptr,
{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
spirv::Builder& b = Build(); spirv::Builder& b = Build();