[wgsl-reader] Add support for read only storage buffers.

This Cl adds the necessary infrastructure to parse the access decoration
for storage buffers.

This CL incorporates changes from bclayton@ from
https://dawn-review.googlesource.com/c/tint/+/33202 and
https://dawn-review.googlesource.com/c/tint/+/33201

Bug: tint:287
Change-Id: I7479f2cf7ab794b24c682b9927c4c68f6d325839
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/33161
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
This commit is contained in:
dan sinclair
2020-11-19 18:55:01 +00:00
committed by Commit Bot service account
parent d2f73226bc
commit 7214f407dc
15 changed files with 573 additions and 30 deletions

View File

@@ -17,6 +17,7 @@
#include <memory>
#include <vector>
#include "src/ast/access_decoration.h"
#include "src/ast/array_accessor_expression.h"
#include "src/ast/binary_expression.h"
#include "src/ast/binding_decoration.h"
@@ -45,6 +46,7 @@
#include "src/ast/struct_block_decoration.h"
#include "src/ast/struct_member_offset_decoration.h"
#include "src/ast/switch_statement.h"
#include "src/ast/type/access_control_type.h"
#include "src/ast/type/alias_type.h"
#include "src/ast/type/array_type.h"
#include "src/ast/type/bool_type.h"
@@ -61,6 +63,7 @@
#include "src/ast/type/vector_type.h"
#include "src/ast/type/void_type.h"
#include "src/ast/type_constructor_expression.h"
#include "src/ast/type_decoration.h"
#include "src/ast/uint_literal.h"
#include "src/ast/unary_op.h"
#include "src/ast/unary_op_expression.h"
@@ -797,7 +800,7 @@ Expect<ast::type::ImageFormat> ParserImpl::expect_image_storage_type(
}
// variable_ident_decl
// : IDENT COLON type_decl
// : IDENT COLON variable_decoration_list* type_decl
Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_variable_ident_decl(
const std::string& use) {
auto ident = expect_ident(use);
@@ -807,14 +810,47 @@ Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_variable_ident_decl(
if (!expect(use, Token::Type::kColon))
return Failure::kErrored;
auto decos = decoration_list();
if (decos.errored)
return Failure::kErrored;
auto access_decos = take_decorations<ast::AccessDecoration>(decos.value);
auto t = peek();
auto type = type_decl();
auto type = type_decl(decos.value);
if (type.errored)
return Failure::kErrored;
if (!type.matched)
return add_error(t.source(), "invalid type", use);
return TypedIdentifier{type.value, ident.value, ident.source};
if (!expect_decorations_consumed(decos.value))
return Failure::kErrored;
if (access_decos.size() > 1)
return add_error(ident.source, "multiple access decorations not allowed");
auto* ty = type.value;
for (auto* deco : access_decos) {
// If we have an access control decoration then we take it and wrap our
// type up with that decoration
ty = ctx_.type_mgr().Get(std::make_unique<ast::type::AccessControlType>(
deco->AsAccess()->value(), ty));
}
return TypedIdentifier{ty, ident.value, ident.source};
}
Expect<ast::AccessControl> ParserImpl::expect_access_type() {
auto ident = expect_ident("access_type");
if (ident.errored)
return Failure::kErrored;
if (ident.value == "read")
return {ast::AccessControl::kReadOnly, ident.source};
if (ident.value == "read_write")
return {ast::AccessControl::kReadWrite, ident.source};
return add_error(ident.source, "invalid value for access decoration");
}
// variable_storage_decoration
@@ -889,6 +925,23 @@ Maybe<ast::type::Type*> ParserImpl::type_alias() {
// | MAT4x4 LESS_THAN type_decl GREATER_THAN
// | texture_sampler_types
Maybe<ast::type::Type*> ParserImpl::type_decl() {
auto decos = decoration_list();
if (decos.errored)
return Failure::kErrored;
auto type = type_decl(decos.value);
if (type.errored)
return Failure::kErrored;
if (!type.matched)
return Failure::kNoMatch;
if (!expect_decorations_consumed(decos.value))
return Failure::kErrored;
return type.value;
}
Maybe<ast::type::Type*> ParserImpl::type_decl(ast::DecorationList& decos) {
auto t = peek();
if (match(Token::Type::kIdentifier)) {
auto* ty = get_constructed(t.to_str());
@@ -918,21 +971,14 @@ Maybe<ast::type::Type*> ParserImpl::type_decl() {
if (match(Token::Type::kPtr))
return expect_type_decl_pointer();
auto decos = decoration_list();
if (decos.errored)
return Failure::kErrored;
if (match(Token::Type::kArray)) {
auto array_decos = cast_decorations<ast::ArrayDecoration>(decos.value);
auto array_decos = cast_decorations<ast::ArrayDecoration>(decos);
if (array_decos.errored)
return Failure::kErrored;
return expect_type_decl_array(std::move(array_decos.value));
}
if (!expect_decorations_consumed(decos.value))
return Failure::kErrored;
if (t.IsMat2x2() || t.IsMat2x3() || t.IsMat2x4() || t.IsMat3x2() ||
t.IsMat3x3() || t.IsMat3x4() || t.IsMat4x2() || t.IsMat4x3() ||
t.IsMat4x4()) {
@@ -2714,6 +2760,16 @@ Expect<ast::Decoration*> ParserImpl::expect_decoration() {
Maybe<ast::Decoration*> ParserImpl::decoration() {
using Result = Maybe<ast::Decoration*>;
auto t = next();
if (t.IsIdentifier() && t.to_str() == "access") {
const char* use = "access decoration";
return expect_paren_block(use, [&]() -> Result {
auto val = expect_access_type();
if (val.errored)
return Failure::kErrored;
return create<ast::AccessDecoration>(val.value, val.source);
});
}
if (t.IsLocation()) {
const char* use = "location decoration";
return expect_paren_block(use, [&]() -> Result {
@@ -2817,23 +2873,37 @@ Maybe<ast::Decoration*> ParserImpl::decoration() {
}
template <typename T>
Expect<std::vector<T*>> ParserImpl::cast_decorations(ast::DecorationList& in) {
bool ok = true;
std::vector<T*> ParserImpl::take_decorations(ast::DecorationList& in) {
ast::DecorationList remaining;
std::vector<T*> out;
out.reserve(in.size());
for (auto* deco : in) {
if (!deco->Is<T>()) {
std::stringstream msg;
msg << deco->GetKind() << " decoration type cannot be used for "
<< T::Kind;
add_error(deco->source(), msg.str());
ok = false;
continue;
if (deco->Is<T>()) {
out.emplace_back(ast::As<T>(deco));
} else {
remaining.emplace_back(deco);
}
out.emplace_back(ast::As<T>(deco));
}
// clear in so that we can verify decorations were consumed with
// expect_decorations_consumed()
in = std::move(remaining);
return out;
}
template <typename T>
Expect<std::vector<T*>> ParserImpl::cast_decorations(ast::DecorationList& in) {
auto out = take_decorations<T>(in);
bool ok = true;
for (auto* deco : in) {
std::stringstream msg;
msg << deco->GetKind() << " decoration type cannot be used for " << T::Kind;
add_error(deco->source(), msg.str());
ok = false;
}
// clear in so that expect_decorations_consumed() doesn't error again on the
// decorations we've already errored on.
in.clear();
if (!ok)

View File

@@ -24,6 +24,7 @@
#include <utility>
#include <vector>
#include "src/ast/access_control.h"
#include "src/ast/array_decoration.h"
#include "src/ast/assignment_statement.h"
#include "src/ast/builtin.h"
@@ -320,6 +321,11 @@ class ParserImpl {
/// Parses a `type_decl` grammar element
/// @returns the parsed Type or nullptr if none matched.
Maybe<ast::type::Type*> type_decl();
/// Parses a `type_decl` grammar element with the given pre-parsed
/// decorations.
/// @param decos the list of decorations for the type.
/// @returns the parsed Type or nullptr if none matched.
Maybe<ast::type::Type*> type_decl(ast::DecorationList& decos);
/// Parses a `storage_class` grammar element, erroring on parse failure.
/// @param use a description of what was being parsed if an error was raised.
/// @returns the storage class or StorageClass::kNone if none matched
@@ -383,6 +389,10 @@ class ParserImpl {
/// not match a stage name.
/// @returns the pipeline stage.
Expect<ast::PipelineStage> expect_pipeline_stage();
/// Parses an access type identifier, erroring if the next token does not
/// match a valid access type name.
/// @returns the parsed access control.
Expect<ast::AccessControl> expect_access_type();
/// Parses a builtin identifier, erroring if the next token does not match a
/// valid builtin name.
/// @returns the parsed builtin.
@@ -720,10 +730,16 @@ class ParserImpl {
template <typename F, typename T = ReturnType<F>>
T without_error(F&& func);
/// Returns all the decorations taken from |list| that matches the type |T|.
/// Those that do not match are kept in |list|.
template <typename T>
std::vector<T*> take_decorations(ast::DecorationList& list);
/// Downcasts all the decorations in |list| to the type |T|, raising a parser
/// error if any of the decorations aren't of the type |T|.
template <typename T>
Expect<std::vector<T*>> cast_decorations(ast::DecorationList& in);
Expect<std::vector<T*>> cast_decorations(ast::DecorationList& list);
/// Reports an error if the decoration list |list| is not empty.
/// Used to ensure that all decorations are consumed.
bool expect_decorations_consumed(const ast::DecorationList& list);

View File

@@ -13,6 +13,11 @@
// limitations under the License.
#include "gtest/gtest.h"
#include "src/ast/struct.h"
#include "src/ast/struct_block_decoration.h"
#include "src/ast/type/access_control_type.h"
#include "src/ast/type/i32_type.h"
#include "src/ast/type/struct_type.h"
#include "src/reader/wgsl/parser_impl.h"
#include "src/reader/wgsl/parser_impl_test_helper.h"
@@ -24,7 +29,7 @@ namespace {
TEST_F(ParserImplTest, VariableIdentDecl_Parses) {
auto p = parser("my_var : f32");
auto decl = p->expect_variable_ident_decl("test");
ASSERT_FALSE(p->has_error());
ASSERT_FALSE(p->has_error()) << p->error();
ASSERT_FALSE(decl.errored);
ASSERT_EQ(decl->name, "my_var");
ASSERT_NE(decl->type, nullptr);
@@ -76,6 +81,175 @@ TEST_F(ParserImplTest, VariableIdentDecl_InvalidType) {
ASSERT_EQ(p->error(), "1:10: unknown constructed type 'invalid'");
}
TEST_F(ParserImplTest, VariableIdentDecl_ParsesWithAccessDeco_Read) {
ast::type::I32Type i32;
ast::StructMember mem("a", &i32, ast::StructMemberDecorationList{});
ast::StructMemberList members;
members.push_back(&mem);
ast::StructBlockDecoration block_deco(Source{});
ast::StructDecorationList decos;
decos.push_back(&block_deco);
ast::Struct str(decos, members);
ast::type::StructType s("S", &str);
auto p = parser("my_var : [[access(read)]] S");
p->register_constructed("S", &s);
auto decl = p->expect_variable_ident_decl("test");
ASSERT_FALSE(p->has_error()) << p->error();
ASSERT_FALSE(decl.errored);
ASSERT_EQ(decl->name, "my_var");
ASSERT_NE(decl->type, nullptr);
ASSERT_TRUE(decl->type->IsAccessControl());
EXPECT_TRUE(decl->type->AsAccessControl()->IsReadOnly());
}
TEST_F(ParserImplTest, VariableIdentDecl_ParsesWithAccessDeco_ReadWrite) {
ast::type::I32Type i32;
ast::StructMember mem("a", &i32, ast::StructMemberDecorationList{});
ast::StructMemberList members;
members.push_back(&mem);
ast::StructBlockDecoration block_deco(Source{});
ast::StructDecorationList decos;
decos.push_back(&block_deco);
ast::Struct str(decos, members);
ast::type::StructType s("S", &str);
auto p = parser("my_var : [[access(read_write)]] S");
p->register_constructed("S", &s);
auto decl = p->expect_variable_ident_decl("test");
ASSERT_FALSE(p->has_error()) << p->error();
ASSERT_FALSE(decl.errored);
ASSERT_EQ(decl->name, "my_var");
ASSERT_NE(decl->type, nullptr);
ASSERT_TRUE(decl->type->IsAccessControl());
EXPECT_TRUE(decl->type->AsAccessControl()->IsReadWrite());
}
TEST_F(ParserImplTest, VariableIdentDecl_MultipleAccessDecoFail) {
ast::type::I32Type i32;
ast::StructMember mem("a", &i32, ast::StructMemberDecorationList{});
ast::StructMemberList members;
members.push_back(&mem);
ast::StructBlockDecoration block_deco(Source{});
ast::StructDecorationList decos;
decos.push_back(&block_deco);
ast::Struct str(decos, members);
ast::type::StructType s("S", &str);
auto p = parser("my_var : [[access(read), access(read_write)]] S");
p->register_constructed("S", &s);
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);
ASSERT_EQ(p->error(), "1:1: multiple access decorations not allowed");
}
TEST_F(ParserImplTest, VariableIdentDecl_MultipleAccessDeco_MultiBlock_Fail) {
ast::type::I32Type i32;
ast::StructMember mem("a", &i32, ast::StructMemberDecorationList{});
ast::StructMemberList members;
members.push_back(&mem);
ast::StructBlockDecoration block_deco(Source{});
ast::StructDecorationList decos;
decos.push_back(&block_deco);
ast::Struct str(decos, members);
ast::type::StructType s("S", &str);
auto p = parser("my_var : [[access(read)]][[access(read_write)]] S");
p->register_constructed("S", &s);
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);
ASSERT_EQ(p->error(), "1:1: multiple access decorations not allowed");
}
TEST_F(ParserImplTest, VariableIdentDecl_AccessDecoBadValue) {
auto p = parser("my_var : [[access(unknown)]] S");
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);
ASSERT_EQ(p->error(), "1:19: invalid value for access decoration");
}
TEST_F(ParserImplTest, VariableIdentDecl_AccessDecoIllegalValue) {
auto p = parser("my_var : [[access(1)]] S");
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);
ASSERT_EQ(p->error(), "1:19: expected identifier for access_type");
}
TEST_F(ParserImplTest, VariableIdentDecl_NonAccessDecoFail) {
ast::type::I32Type i32;
ast::StructMember mem("a", &i32, ast::StructMemberDecorationList{});
ast::StructMemberList members;
members.push_back(&mem);
ast::StructBlockDecoration block_deco(Source{});
ast::StructDecorationList decos;
decos.push_back(&block_deco);
ast::Struct str(decos, members);
ast::type::StructType s("S", &str);
auto p = parser("my_var : [[stride(1)]] S");
p->register_constructed("S", &s);
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);
ASSERT_EQ(p->error(), "1:12: unexpected decorations");
}
TEST_F(ParserImplTest, VariableIdentDecl_DecorationMissingRightBlock) {
auto p = parser("my_var : [[access(read) S");
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);
ASSERT_EQ(p->error(), "1:25: expected ']]' for decoration list");
}
TEST_F(ParserImplTest, VariableIdentDecl_DecorationMissingRightParen) {
auto p = parser("my_var : [[access(read]] S");
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);
ASSERT_EQ(p->error(), "1:23: expected ')' for access decoration");
}
TEST_F(ParserImplTest, VariableIdentDecl_DecorationMissingLeftParen) {
auto p = parser("my_var : [[access read)]] S");
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);
ASSERT_EQ(p->error(), "1:19: expected '(' for access decoration");
}
TEST_F(ParserImplTest, VariableIdentDecl_DecorationEmpty) {
auto p = parser("my_var : [[]] S");
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);
ASSERT_EQ(p->error(), "1:12: empty decoration list");
}
} // namespace
} // namespace wgsl
} // namespace reader