mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-18 01:15:39 +00:00
Add optional access to ptr<>
This also completes the work to resolve the access controls for each storage type. Fixed: tint:846 Change-Id: Iab24057ec14620a2978ec63c4a91ba12d1bc6e9b Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/53381 Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Ben Clayton <bclayton@google.com> Reviewed-by: James Price <jrprice@google.com>
This commit is contained in:
committed by
Tint LUCI CQ
parent
3db1820f0b
commit
1858854f7e
@@ -67,7 +67,7 @@ TEST_F(AstAliasTest, UnwrapAll_TwiceAliasPointerTwiceAlias) {
|
||||
auto* u32 = create<U32>();
|
||||
auto* a = create<Alias>(Sym("a_type"), u32);
|
||||
auto* aa = create<Alias>(Sym("aa_type"), a);
|
||||
auto* paa = create<Pointer>(aa, StorageClass::kUniform);
|
||||
auto* paa = create<Pointer>(aa, StorageClass::kUniform, Access::kUndefined);
|
||||
auto* apaa = create<Alias>(Sym("paa_type"), paa);
|
||||
auto* aapaa = create<Alias>(Sym("aapaa_type"), apaa);
|
||||
|
||||
|
||||
@@ -24,14 +24,19 @@ namespace ast {
|
||||
Pointer::Pointer(ProgramID program_id,
|
||||
const Source& source,
|
||||
Type* const subtype,
|
||||
ast::StorageClass storage_class)
|
||||
ast::StorageClass storage_class,
|
||||
ast::Access access)
|
||||
: Base(program_id, source),
|
||||
subtype_(subtype),
|
||||
storage_class_(storage_class) {}
|
||||
storage_class_(storage_class),
|
||||
access_(access) {}
|
||||
|
||||
std::string Pointer::type_name() const {
|
||||
std::ostringstream out;
|
||||
out << "__ptr_" << storage_class_ << subtype_->type_name();
|
||||
if (access_ != ast::Access::kUndefined) {
|
||||
out << "_" << access_;
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
@@ -41,7 +46,11 @@ std::string Pointer::FriendlyName(const SymbolTable& symbols) const {
|
||||
if (storage_class_ != ast::StorageClass::kNone) {
|
||||
out << storage_class_ << ", ";
|
||||
}
|
||||
out << subtype_->FriendlyName(symbols) << ">";
|
||||
out << subtype_->FriendlyName(symbols);
|
||||
if (access_ != ast::Access::kUndefined) {
|
||||
out << ", " << access_;
|
||||
}
|
||||
out << ">";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
@@ -53,7 +62,7 @@ Pointer* Pointer::Clone(CloneContext* ctx) const {
|
||||
// Clone arguments outside of create() call to have deterministic ordering
|
||||
auto src = ctx->Clone(source());
|
||||
auto* ty = ctx->Clone(type());
|
||||
return ctx->dst->create<Pointer>(src, ty, storage_class_);
|
||||
return ctx->dst->create<Pointer>(src, ty, storage_class_, access_);
|
||||
}
|
||||
|
||||
} // namespace ast
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "src/ast/access.h"
|
||||
#include "src/ast/storage_class.h"
|
||||
#include "src/ast/type.h"
|
||||
|
||||
@@ -31,19 +32,25 @@ class Pointer : public Castable<Pointer, Type> {
|
||||
/// @param source the source of this node
|
||||
/// @param subtype the pointee type
|
||||
/// @param storage_class the storage class of the pointer
|
||||
/// @param access the access control of the pointer
|
||||
Pointer(ProgramID program_id,
|
||||
const Source& source,
|
||||
Type* const subtype,
|
||||
ast::StorageClass storage_class);
|
||||
ast::StorageClass storage_class,
|
||||
ast::Access access);
|
||||
/// Move constructor
|
||||
Pointer(Pointer&&);
|
||||
~Pointer() override;
|
||||
|
||||
/// @returns the pointee type
|
||||
Type* type() const { return const_cast<Type*>(subtype_); }
|
||||
|
||||
/// @returns the storage class of the pointer
|
||||
ast::StorageClass storage_class() const { return storage_class_; }
|
||||
|
||||
/// @returns the access control of the pointer
|
||||
ast::Access access() const { return access_; }
|
||||
|
||||
/// @returns the name for this type
|
||||
std::string type_name() const override;
|
||||
|
||||
@@ -60,6 +67,7 @@ class Pointer : public Castable<Pointer, Type> {
|
||||
private:
|
||||
Type const* const subtype_;
|
||||
ast::StorageClass const storage_class_;
|
||||
ast::Access const access_;
|
||||
};
|
||||
|
||||
} // namespace ast
|
||||
|
||||
@@ -25,27 +25,37 @@ using AstPointerTest = TestHelper;
|
||||
|
||||
TEST_F(AstPointerTest, Creation) {
|
||||
auto* i32 = create<I32>();
|
||||
auto* p = create<Pointer>(i32, ast::StorageClass::kStorage);
|
||||
auto* p = create<Pointer>(i32, ast::StorageClass::kStorage, Access::kRead);
|
||||
EXPECT_EQ(p->type(), i32);
|
||||
EXPECT_EQ(p->storage_class(), ast::StorageClass::kStorage);
|
||||
EXPECT_EQ(p->access(), Access::kRead);
|
||||
}
|
||||
|
||||
TEST_F(AstPointerTest, TypeName) {
|
||||
auto* i32 = create<I32>();
|
||||
auto* p = create<Pointer>(i32, ast::StorageClass::kWorkgroup);
|
||||
auto* p =
|
||||
create<Pointer>(i32, ast::StorageClass::kWorkgroup, Access::kUndefined);
|
||||
EXPECT_EQ(p->type_name(), "__ptr_workgroup__i32");
|
||||
}
|
||||
|
||||
TEST_F(AstPointerTest, FriendlyNameWithStorageClass) {
|
||||
TEST_F(AstPointerTest, TypeNameWithAccess) {
|
||||
auto* i32 = create<I32>();
|
||||
auto* p = create<Pointer>(i32, ast::StorageClass::kWorkgroup);
|
||||
auto* p = create<Pointer>(i32, ast::StorageClass::kWorkgroup, Access::kRead);
|
||||
EXPECT_EQ(p->type_name(), "__ptr_workgroup__i32_read");
|
||||
}
|
||||
|
||||
TEST_F(AstPointerTest, FriendlyName) {
|
||||
auto* i32 = create<I32>();
|
||||
auto* p =
|
||||
create<Pointer>(i32, ast::StorageClass::kWorkgroup, Access::kUndefined);
|
||||
EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<workgroup, i32>");
|
||||
}
|
||||
|
||||
TEST_F(AstPointerTest, FriendlyNameWithoutStorageClass) {
|
||||
TEST_F(AstPointerTest, FriendlyNameWithAccess) {
|
||||
auto* i32 = create<I32>();
|
||||
auto* p = create<Pointer>(i32, ast::StorageClass::kNone);
|
||||
EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<i32>");
|
||||
auto* p =
|
||||
create<Pointer>(i32, ast::StorageClass::kStorage, Access::kReadWrite);
|
||||
EXPECT_EQ(p->FriendlyName(Symbols()), "ptr<storage, i32, read_write>");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -431,24 +431,30 @@ const sem::Array* build_array(MatchState& state, const sem::Type* el) {
|
||||
/* stride_implicit */ 0);
|
||||
}
|
||||
|
||||
bool match_ptr(const sem::Type* ty, Number& S, const sem::Type*& T) {
|
||||
bool match_ptr(const sem::Type* ty, Number& S, const sem::Type*& T, Number& A) {
|
||||
if (ty->Is<Any>()) {
|
||||
S = Number::any;
|
||||
T = ty;
|
||||
A = Number::any;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto* p = ty->As<sem::Pointer>()) {
|
||||
S = Number(static_cast<uint32_t>(p->StorageClass()));
|
||||
T = p->StoreType();
|
||||
A = Number(static_cast<uint32_t>(p->Access()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const sem::Pointer* build_ptr(MatchState& state, Number S, const sem::Type* T) {
|
||||
const sem::Pointer* build_ptr(MatchState& state,
|
||||
Number S,
|
||||
const sem::Type* T,
|
||||
Number& A) {
|
||||
return state.builder.create<sem::Pointer>(
|
||||
T, static_cast<ast::StorageClass>(S.Value()));
|
||||
T, static_cast<ast::StorageClass>(S.Value()),
|
||||
static_cast<ast::Access>(A.Value()));
|
||||
}
|
||||
|
||||
bool match_sampler(const sem::Type* ty) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -196,7 +196,8 @@ TEST_F(IntrinsicTableTest, MismatchBool) {
|
||||
|
||||
TEST_F(IntrinsicTableTest, MatchPointer) {
|
||||
auto* f32 = create<sem::F32>();
|
||||
auto* ptr = create<sem::Pointer>(f32, ast::StorageClass::kNone);
|
||||
auto* ptr = create<sem::Pointer>(f32, ast::StorageClass::kFunction,
|
||||
ast::Access::kReadWrite);
|
||||
auto* result = table->Lookup(IntrinsicType::kModf, {f32, ptr}, Source{});
|
||||
ASSERT_NE(result, nullptr) << Diagnostics().str();
|
||||
ASSERT_EQ(Diagnostics().str(), "");
|
||||
@@ -386,9 +387,11 @@ TEST_F(IntrinsicTableTest, MismatchTexture) {
|
||||
|
||||
TEST_F(IntrinsicTableTest, ImplicitLoadOnReference) {
|
||||
auto* f32 = create<sem::F32>();
|
||||
auto* result = table->Lookup(
|
||||
IntrinsicType::kCos,
|
||||
{create<sem::Reference>(f32, ast::StorageClass::kNone)}, Source{});
|
||||
auto* result =
|
||||
table->Lookup(IntrinsicType::kCos,
|
||||
{create<sem::Reference>(f32, ast::StorageClass::kFunction,
|
||||
ast::Access::kReadWrite)},
|
||||
Source{});
|
||||
ASSERT_NE(result, nullptr) << Diagnostics().str();
|
||||
ASSERT_EQ(Diagnostics().str(), "");
|
||||
EXPECT_THAT(result->Type(), IntrinsicType::kCos);
|
||||
|
||||
@@ -74,7 +74,7 @@ type vec3<T>
|
||||
type vec4<T>
|
||||
[[display("vec{N}<{T}>")]] type vec<N: num, T>
|
||||
[[display("mat{N}x{M}<{T}>")]] type mat<N: num, M: num, T>
|
||||
[[display("ptr<{T}>")]] type ptr<S: storage_class, T> // TODO(crbug.com/tint/846): Add access control
|
||||
[[display("ptr<{T}>")]] type ptr<S: storage_class, T, A: access>
|
||||
type array<T>
|
||||
type sampler
|
||||
type sampler_comparison
|
||||
@@ -305,8 +305,8 @@ fn fma(f32, f32, f32) -> f32
|
||||
fn fma<N: num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32>
|
||||
fn fract(f32) -> f32
|
||||
fn fract<N: num>(vec<N, f32>) -> vec<N, f32>
|
||||
fn frexp<T: iu32, S: storage_class>(f32, ptr<S, T>) -> f32
|
||||
fn frexp<N: num, T: iu32, S: storage_class>(vec<N, f32>, ptr<S, vec<N, T>>) -> vec<N, f32>
|
||||
fn frexp<T: iu32, S: storage_class, A: access>(f32, ptr<S, T, A>) -> f32
|
||||
fn frexp<N: num, T: iu32, S: storage_class, A: access>(vec<N, f32>, ptr<S, vec<N, T>, A>) -> vec<N, f32>
|
||||
fn fwidth(f32) -> f32
|
||||
fn fwidth<N: num>(vec<N, f32>) -> vec<N, f32>
|
||||
fn fwidthCoarse(f32) -> f32
|
||||
@@ -337,8 +337,8 @@ fn min<T: fiu32>(T, T) -> T
|
||||
fn min<N: num, T: fiu32>(vec<N, T>, vec<N, T>) -> vec<N, T>
|
||||
fn mix(f32, f32, f32) -> f32
|
||||
fn mix<N: num>(vec<N, f32>, vec<N, f32>, vec<N, f32>) -> vec<N, f32>
|
||||
fn modf<S: storage_class>(f32, ptr<S, f32>) -> f32
|
||||
fn modf<N: num, S: storage_class>(vec<N, f32>, ptr<S, vec<N, f32>>) -> vec<N, f32>
|
||||
fn modf<S: storage_class, A: access>(f32, ptr<S, f32, A>) -> f32
|
||||
fn modf<N: num, S: storage_class, A: access>(vec<N, f32>, ptr<S, vec<N, f32>, A>) -> vec<N, f32>
|
||||
fn normalize<N: num>(vec<N, f32>) -> vec<N, f32>
|
||||
fn pack2x16float(vec2<f32>) -> u32
|
||||
fn pack2x16snorm(vec2<f32>) -> u32
|
||||
|
||||
@@ -712,29 +712,35 @@ class ProgramBuilder {
|
||||
|
||||
/// @param type the type of the pointer
|
||||
/// @param storage_class the storage class of the pointer
|
||||
/// @param access the optional access control of the pointer
|
||||
/// @return the pointer to `type` with the given ast::StorageClass
|
||||
ast::Pointer* pointer(ast::Type* type,
|
||||
ast::StorageClass storage_class) const {
|
||||
ast::StorageClass storage_class,
|
||||
ast::Access access = ast::Access::kUndefined) const {
|
||||
type = MaybeCreateTypename(type);
|
||||
return builder->create<ast::Pointer>(type, storage_class);
|
||||
return builder->create<ast::Pointer>(type, storage_class, access);
|
||||
}
|
||||
|
||||
/// @param source the Source of the node
|
||||
/// @param type the type of the pointer
|
||||
/// @param storage_class the storage class of the pointer
|
||||
/// @param access the optional access control of the pointer
|
||||
/// @return the pointer to `type` with the given ast::StorageClass
|
||||
ast::Pointer* pointer(const Source& source,
|
||||
ast::Type* type,
|
||||
ast::StorageClass storage_class) const {
|
||||
ast::StorageClass storage_class,
|
||||
ast::Access access = ast::Access::kUndefined) const {
|
||||
type = MaybeCreateTypename(type);
|
||||
return builder->create<ast::Pointer>(source, type, storage_class);
|
||||
return builder->create<ast::Pointer>(source, type, storage_class, access);
|
||||
}
|
||||
|
||||
/// @param storage_class the storage class of the pointer
|
||||
/// @param access the optional access control of the pointer
|
||||
/// @return the pointer to type `T` with the given ast::StorageClass.
|
||||
template <typename T>
|
||||
ast::Pointer* pointer(ast::StorageClass storage_class) const {
|
||||
return pointer(Of<T>(), storage_class);
|
||||
ast::Pointer* pointer(ast::StorageClass storage_class,
|
||||
ast::Access access = ast::Access::kUndefined) const {
|
||||
return pointer(Of<T>(), storage_class, access);
|
||||
}
|
||||
|
||||
/// @param kind the kind of sampler
|
||||
|
||||
@@ -971,7 +971,7 @@ Expect<ast::Access> ParserImpl::expect_access(const std::string& use) {
|
||||
if (ident.value == kReadWriteAccess)
|
||||
return {ast::Access::kReadWrite, ident.source};
|
||||
|
||||
return add_error(ident.source, "invalid value for access decoration");
|
||||
return add_error(ident.source, "invalid value for access control");
|
||||
}
|
||||
|
||||
// variable_qualifier
|
||||
@@ -1045,7 +1045,7 @@ Maybe<ast::Alias*> ParserImpl::type_alias() {
|
||||
// | VEC2 LESS_THAN type_decl GREATER_THAN
|
||||
// | VEC3 LESS_THAN type_decl GREATER_THAN
|
||||
// | VEC4 LESS_THAN type_decl GREATER_THAN
|
||||
// | PTR LESS_THAN storage_class, type_decl GREATER_THAN
|
||||
// | PTR LESS_THAN storage_class, type_decl (COMMA access_mode)? GREATER_THAN
|
||||
// | array_decoration_list* ARRAY LESS_THAN type_decl COMMA
|
||||
// INT_LITERAL GREATER_THAN
|
||||
// | array_decoration_list* ARRAY LESS_THAN type_decl
|
||||
@@ -1142,20 +1142,32 @@ Expect<ast::Type*> ParserImpl::expect_type(const std::string& use) {
|
||||
Expect<ast::Type*> ParserImpl::expect_type_decl_pointer(Token t) {
|
||||
const char* use = "ptr declaration";
|
||||
|
||||
ast::StorageClass storage_class = ast::StorageClass::kNone;
|
||||
auto storage_class = ast::StorageClass::kNone;
|
||||
auto access = ast::Access::kUndefined;
|
||||
|
||||
auto subtype = expect_lt_gt_block(use, [&]() -> Expect<ast::Type*> {
|
||||
auto sc = expect_storage_class(use);
|
||||
if (sc.errored)
|
||||
if (sc.errored) {
|
||||
return Failure::kErrored;
|
||||
}
|
||||
storage_class = sc.value;
|
||||
|
||||
if (!expect(use, Token::Type::kComma))
|
||||
if (!expect(use, Token::Type::kComma)) {
|
||||
return Failure::kErrored;
|
||||
}
|
||||
|
||||
auto type = expect_type(use);
|
||||
if (type.errored)
|
||||
if (type.errored) {
|
||||
return Failure::kErrored;
|
||||
}
|
||||
|
||||
if (match(Token::Type::kComma)) {
|
||||
auto ac = expect_access("access control");
|
||||
if (ac.errored) {
|
||||
return Failure::kErrored;
|
||||
}
|
||||
access = ac.value;
|
||||
}
|
||||
|
||||
return type.value;
|
||||
});
|
||||
@@ -1165,7 +1177,7 @@ Expect<ast::Type*> ParserImpl::expect_type_decl_pointer(Token t) {
|
||||
}
|
||||
|
||||
return builder_.ty.pointer(make_source_range_from(t.source()), subtype.value,
|
||||
storage_class);
|
||||
storage_class, access);
|
||||
}
|
||||
|
||||
Expect<ast::Type*> ParserImpl::expect_type_decl_vector(Token t) {
|
||||
|
||||
@@ -314,7 +314,7 @@ TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidAccess) {
|
||||
EXPECT_EQ(t.value, nullptr);
|
||||
EXPECT_FALSE(t.matched);
|
||||
EXPECT_TRUE(t.errored);
|
||||
EXPECT_EQ(p->error(), "1:30: invalid value for access decoration");
|
||||
EXPECT_EQ(p->error(), "1:30: invalid value for access control");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingType) {
|
||||
|
||||
@@ -223,6 +223,22 @@ TEST_F(ParserImplTest, TypeDecl_Ptr) {
|
||||
EXPECT_EQ(t.value->source().range, (Source::Range{{1u, 1u}, {1u, 19u}}));
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_WithAccess) {
|
||||
auto p = parser("ptr<function, f32, read>");
|
||||
auto t = p->type_decl();
|
||||
EXPECT_TRUE(t.matched);
|
||||
EXPECT_FALSE(t.errored);
|
||||
ASSERT_NE(t.value, nullptr) << p->error();
|
||||
ASSERT_FALSE(p->has_error());
|
||||
ASSERT_TRUE(t.value->Is<ast::Pointer>());
|
||||
|
||||
auto* ptr = t.value->As<ast::Pointer>();
|
||||
ASSERT_TRUE(ptr->type()->Is<ast::F32>());
|
||||
ASSERT_EQ(ptr->storage_class(), ast::StorageClass::kFunction);
|
||||
ASSERT_EQ(ptr->access(), ast::Access::kRead);
|
||||
EXPECT_EQ(t.value->source().range, (Source::Range{{1u, 1u}, {1u, 25u}}));
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_ToVec) {
|
||||
auto p = parser("ptr<function, vec2<f32>>");
|
||||
auto t = p->type_decl();
|
||||
@@ -252,7 +268,7 @@ TEST_F(ParserImplTest, TypeDecl_Ptr_MissingLessThan) {
|
||||
ASSERT_EQ(p->error(), "1:5: expected '<' for ptr declaration");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingGreaterThan) {
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingGreaterThanAfterType) {
|
||||
auto p = parser("ptr<function, f32");
|
||||
auto t = p->type_decl();
|
||||
EXPECT_TRUE(t.errored);
|
||||
@@ -262,7 +278,17 @@ TEST_F(ParserImplTest, TypeDecl_Ptr_MissingGreaterThan) {
|
||||
ASSERT_EQ(p->error(), "1:18: expected '>' for ptr declaration");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingComma) {
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingGreaterThanAfterAccess) {
|
||||
auto p = parser("ptr<function, f32, read");
|
||||
auto t = p->type_decl();
|
||||
EXPECT_TRUE(t.errored);
|
||||
EXPECT_FALSE(t.matched);
|
||||
ASSERT_EQ(t.value, nullptr);
|
||||
ASSERT_TRUE(p->has_error());
|
||||
ASSERT_EQ(p->error(), "1:24: expected '>' for ptr declaration");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingCommaAfterStorageClass) {
|
||||
auto p = parser("ptr<function f32>");
|
||||
auto t = p->type_decl();
|
||||
EXPECT_TRUE(t.errored);
|
||||
@@ -272,18 +298,18 @@ TEST_F(ParserImplTest, TypeDecl_Ptr_MissingComma) {
|
||||
ASSERT_EQ(p->error(), "1:14: expected ',' for ptr declaration");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingStorageClass) {
|
||||
auto p = parser("ptr<, f32>");
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingCommaAfterAccess) {
|
||||
auto p = parser("ptr<function, f32 read>");
|
||||
auto t = p->type_decl();
|
||||
EXPECT_TRUE(t.errored);
|
||||
EXPECT_FALSE(t.matched);
|
||||
ASSERT_EQ(t.value, nullptr);
|
||||
ASSERT_TRUE(p->has_error());
|
||||
ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
|
||||
ASSERT_EQ(p->error(), "1:19: expected '>' for ptr declaration");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingParams) {
|
||||
auto p = parser("ptr<>");
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingStorageClass) {
|
||||
auto p = parser("ptr<, f32>");
|
||||
auto t = p->type_decl();
|
||||
EXPECT_TRUE(t.errored);
|
||||
EXPECT_FALSE(t.matched);
|
||||
@@ -302,6 +328,26 @@ TEST_F(ParserImplTest, TypeDecl_Ptr_MissingType) {
|
||||
ASSERT_EQ(p->error(), "1:14: invalid type for ptr declaration");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingAccess) {
|
||||
auto p = parser("ptr<function, i32, >");
|
||||
auto t = p->type_decl();
|
||||
EXPECT_TRUE(t.errored);
|
||||
EXPECT_FALSE(t.matched);
|
||||
ASSERT_EQ(t.value, nullptr);
|
||||
ASSERT_TRUE(p->has_error());
|
||||
ASSERT_EQ(p->error(), "1:20: expected identifier for access control");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_MissingParams) {
|
||||
auto p = parser("ptr<>");
|
||||
auto t = p->type_decl();
|
||||
EXPECT_TRUE(t.errored);
|
||||
EXPECT_FALSE(t.matched);
|
||||
ASSERT_EQ(t.value, nullptr);
|
||||
ASSERT_TRUE(p->has_error());
|
||||
ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_BadStorageClass) {
|
||||
auto p = parser("ptr<unknown, f32>");
|
||||
auto t = p->type_decl();
|
||||
@@ -322,6 +368,16 @@ TEST_F(ParserImplTest, TypeDecl_Ptr_BadType) {
|
||||
ASSERT_EQ(p->error(), "1:15: unknown constructed type 'unknown'");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Ptr_BadAccess) {
|
||||
auto p = parser("ptr<function, i32, unknown>");
|
||||
auto t = p->type_decl();
|
||||
EXPECT_TRUE(t.errored);
|
||||
EXPECT_FALSE(t.matched);
|
||||
ASSERT_EQ(t.value, nullptr);
|
||||
ASSERT_TRUE(p->has_error());
|
||||
ASSERT_EQ(p->error(), "1:20: invalid value for access control");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, TypeDecl_Array) {
|
||||
auto p = parser("array<f32, 5>");
|
||||
auto t = p->type_decl();
|
||||
|
||||
@@ -234,7 +234,7 @@ TEST_F(ParserImplTest, VariableIdentDecl_AccessDecoBadValue_DEPRECATED) {
|
||||
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");
|
||||
ASSERT_EQ(p->error(), "1:19: invalid value for access control");
|
||||
}
|
||||
|
||||
// TODO(crbug.com/tint/846): Remove
|
||||
|
||||
@@ -849,12 +849,14 @@ TEST_F(ResolverIntrinsicDataTest, Frexp_Error_FirstParamInt) {
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: no matching call to frexp(i32, ptr<workgroup, i32>)\n\n"
|
||||
"2 candidate functions:\n"
|
||||
" frexp(f32, ptr<T>) -> f32 where: T is i32 or u32\n"
|
||||
" frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32> "
|
||||
"where: T is i32 or u32\n");
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: no matching call to frexp(i32, ptr<workgroup, i32, read_write>)
|
||||
|
||||
2 candidate functions:
|
||||
frexp(f32, ptr<T>) -> f32 where: T is i32 or u32
|
||||
frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32> where: T is i32 or u32
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIntrinsicDataTest, Frexp_Error_SecondParamFloatPtr) {
|
||||
@@ -864,12 +866,14 @@ TEST_F(ResolverIntrinsicDataTest, Frexp_Error_SecondParamFloatPtr) {
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: no matching call to frexp(f32, ptr<workgroup, f32>)\n\n"
|
||||
"2 candidate functions:\n"
|
||||
" frexp(f32, ptr<T>) -> f32 where: T is i32 or u32\n"
|
||||
" frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32> "
|
||||
"where: T is i32 or u32\n");
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: no matching call to frexp(f32, ptr<workgroup, f32, read_write>)
|
||||
|
||||
2 candidate functions:
|
||||
frexp(f32, ptr<T>) -> f32 where: T is i32 or u32
|
||||
frexp(vecN<f32>, ptr<vecN<T>>) -> vecN<f32> where: T is i32 or u32
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIntrinsicDataTest, Frexp_Error_SecondParamNotAPointer) {
|
||||
@@ -895,7 +899,7 @@ TEST_F(ResolverIntrinsicDataTest, Frexp_Error_VectorSizesDontMatch) {
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: no matching call to frexp(vec2<f32>, ptr<workgroup, vec4<i32>>)
|
||||
R"(error: no matching call to frexp(vec2<f32>, ptr<workgroup, vec4<i32>, read_write>)
|
||||
|
||||
2 candidate functions:
|
||||
frexp(f32, ptr<T>) -> f32 where: T is i32 or u32
|
||||
@@ -933,11 +937,14 @@ TEST_F(ResolverIntrinsicDataTest, Modf_Error_FirstParamInt) {
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: no matching call to modf(i32, ptr<workgroup, f32>)\n\n"
|
||||
"2 candidate functions:\n"
|
||||
" modf(f32, ptr<f32>) -> f32\n"
|
||||
" modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>\n");
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: no matching call to modf(i32, ptr<workgroup, f32, read_write>)
|
||||
|
||||
2 candidate functions:
|
||||
modf(f32, ptr<f32>) -> f32
|
||||
modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIntrinsicDataTest, Modf_Error_SecondParamIntPtr) {
|
||||
@@ -947,11 +954,14 @@ TEST_F(ResolverIntrinsicDataTest, Modf_Error_SecondParamIntPtr) {
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: no matching call to modf(f32, ptr<workgroup, i32>)\n\n"
|
||||
"2 candidate functions:\n"
|
||||
" modf(f32, ptr<f32>) -> f32\n"
|
||||
" modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>\n");
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: no matching call to modf(f32, ptr<workgroup, i32, read_write>)
|
||||
|
||||
2 candidate functions:
|
||||
modf(f32, ptr<f32>) -> f32
|
||||
modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIntrinsicDataTest, Modf_Error_SecondParamNotAPointer) {
|
||||
@@ -976,7 +986,7 @@ TEST_F(ResolverIntrinsicDataTest, Modf_Error_VectorSizesDontMatch) {
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: no matching call to modf(vec2<f32>, ptr<workgroup, vec4<f32>>)
|
||||
R"(error: no matching call to modf(vec2<f32>, ptr<workgroup, vec4<f32>, read_write>)
|
||||
|
||||
2 candidate functions:
|
||||
modf(vecN<f32>, ptr<vecN<f32>>) -> vecN<f32>
|
||||
|
||||
@@ -87,8 +87,8 @@ TEST_F(ResolverIsHostShareable, Matrix) {
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsHostShareable, Pointer) {
|
||||
auto* ptr =
|
||||
create<sem::Pointer>(create<sem::I32>(), ast::StorageClass::kPrivate);
|
||||
auto* ptr = create<sem::Pointer>(
|
||||
create<sem::I32>(), ast::StorageClass::kPrivate, ast::Access::kReadWrite);
|
||||
EXPECT_FALSE(r()->IsHostShareable(ptr));
|
||||
}
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ TEST_F(ResolverIsStorableTest, Matrix) {
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Pointer) {
|
||||
auto* ptr =
|
||||
create<sem::Pointer>(create<sem::I32>(), ast::StorageClass::kPrivate);
|
||||
auto* ptr = create<sem::Pointer>(
|
||||
create<sem::I32>(), ast::StorageClass::kPrivate, ast::Access::kReadWrite);
|
||||
EXPECT_FALSE(r()->IsStorable(ptr));
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ TEST_F(ResolverIsStorableTest, Struct_SomeMembersNonStorable) {
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: ptr<private, i32> cannot be used as the type of a structure member)");
|
||||
R"(error: ptr<private, i32, read_write> cannot be used as the type of a structure member)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Struct_NestedStorable) {
|
||||
@@ -126,7 +126,7 @@ TEST_F(ResolverIsStorableTest, Struct_NestedNonStorable) {
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: ptr<private, i32> cannot be used as the type of a structure member)");
|
||||
R"(error: ptr<private, i32, read_write> cannot be used as the type of a structure member)");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/ast/struct_block_decoration.h"
|
||||
#include "src/resolver/resolver.h"
|
||||
#include "src/resolver/resolver_test_helper.h"
|
||||
#include "src/sem/reference_type.h"
|
||||
@@ -57,6 +58,111 @@ TEST_F(ResolverPtrRefTest, AddressOfThenDeref) {
|
||||
EXPECT_TRUE(TypeOf(expr)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefTest, DefaultStorageClass) {
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#storage-class
|
||||
|
||||
auto* buf = Structure("S", {Member("m", ty.i32())},
|
||||
{create<ast::StructBlockDecoration>()});
|
||||
auto* function = Var("f", ty.i32());
|
||||
auto* private_ = Global("p", ty.i32(), ast::StorageClass::kPrivate);
|
||||
auto* workgroup = Global("w", ty.i32(), ast::StorageClass::kWorkgroup);
|
||||
auto* uniform = Global("ub", buf, ast::StorageClass::kUniform,
|
||||
ast::DecorationList{
|
||||
create<ast::BindingDecoration>(0),
|
||||
create<ast::GroupDecoration>(0),
|
||||
});
|
||||
auto* storage = Global("sb", buf, ast::StorageClass::kStorage,
|
||||
ast::DecorationList{
|
||||
create<ast::BindingDecoration>(1),
|
||||
create<ast::GroupDecoration>(0),
|
||||
});
|
||||
auto* handle = Global("h", ty.depth_texture(ast::TextureDimension::k2d),
|
||||
ast::DecorationList{
|
||||
create<ast::BindingDecoration>(2),
|
||||
create<ast::GroupDecoration>(0),
|
||||
});
|
||||
|
||||
WrapInFunction(function);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_TRUE(TypeOf(function)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(private_)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(workgroup)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(uniform)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(storage)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(handle)->Is<sem::Reference>());
|
||||
|
||||
EXPECT_EQ(TypeOf(function)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(private_)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(workgroup)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(uniform)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kRead);
|
||||
EXPECT_EQ(TypeOf(storage)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kRead);
|
||||
EXPECT_EQ(TypeOf(handle)->As<sem::Reference>()->Access(), ast::Access::kRead);
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefTest, ExplicitStorageClass) {
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#storage-class
|
||||
|
||||
auto* buf = Structure("S", {Member("m", ty.i32())},
|
||||
{create<ast::StructBlockDecoration>()});
|
||||
auto* storage =
|
||||
Global("sb", buf, ast::StorageClass::kStorage, ast::Access::kReadWrite,
|
||||
ast::DecorationList{
|
||||
create<ast::BindingDecoration>(1),
|
||||
create<ast::GroupDecoration>(0),
|
||||
});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_TRUE(TypeOf(storage)->Is<sem::Reference>());
|
||||
|
||||
EXPECT_EQ(TypeOf(storage)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefTest, InheritsAccessFromOriginatingVariable) {
|
||||
// struct Inner {
|
||||
// arr: array<i32, 4>;
|
||||
// }
|
||||
// [[block]] struct S {
|
||||
// inner: Inner;
|
||||
// }
|
||||
// [[group(0), binding(0)]] var<storage, read_write> s : S;
|
||||
// fn f() {
|
||||
// let p = &s.inner.arr[2];
|
||||
// }
|
||||
auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
|
||||
auto* buf = Structure("S", {Member("inner", inner)},
|
||||
{create<ast::StructBlockDecoration>()});
|
||||
auto* storage =
|
||||
Global("s", buf, ast::StorageClass::kStorage, ast::Access::kReadWrite,
|
||||
ast::DecorationList{
|
||||
create<ast::BindingDecoration>(0),
|
||||
create<ast::GroupDecoration>(0),
|
||||
});
|
||||
|
||||
auto* expr =
|
||||
IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
|
||||
auto* ptr = Const("p", nullptr, AddressOf(expr));
|
||||
|
||||
WrapInFunction(ptr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_TRUE(TypeOf(expr)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(ptr)->Is<sem::Pointer>());
|
||||
|
||||
EXPECT_EQ(TypeOf(expr)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(ptr)->As<sem::Pointer>()->Access(), ast::Access::kReadWrite);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/ast/struct_block_decoration.h"
|
||||
#include "src/resolver/resolver.h"
|
||||
#include "src/resolver/resolver_test_helper.h"
|
||||
#include "src/sem/reference_type.h"
|
||||
@@ -77,6 +78,42 @@ TEST_F(ResolverPtrRefValidationTest, DerefOfVar) {
|
||||
"12:34 error: cannot dereference expression of type 'i32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefValidationTest, InferredAccessMismatch) {
|
||||
// struct Inner {
|
||||
// arr: array<i32, 4>;
|
||||
// }
|
||||
// [[block]] struct S {
|
||||
// inner: Inner;
|
||||
// }
|
||||
// [[group(0), binding(0)]] var<storage> s : S;
|
||||
// fn f() {
|
||||
// let p : pointer<storage, i32, read_write> = &s.inner.arr[2];
|
||||
// }
|
||||
auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
|
||||
auto* buf = Structure("S", {Member("inner", inner)},
|
||||
{create<ast::StructBlockDecoration>()});
|
||||
auto* storage = Global("s", buf, ast::StorageClass::kStorage,
|
||||
ast::DecorationList{
|
||||
create<ast::BindingDecoration>(0),
|
||||
create<ast::GroupDecoration>(0),
|
||||
});
|
||||
|
||||
auto* expr =
|
||||
IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
|
||||
auto* ptr = Const(
|
||||
Source{{12, 34}}, "p",
|
||||
ty.pointer<i32>(ast::StorageClass::kStorage, ast::Access::kReadWrite),
|
||||
AddressOf(expr));
|
||||
|
||||
WrapInFunction(ptr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot initialize let of type "
|
||||
"'ptr<storage, i32, read_write>' with value of type "
|
||||
"'ptr<storage, i32, read>'");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
|
||||
@@ -307,8 +307,12 @@ sem::Type* Resolver::Type(const ast::Type* ty) {
|
||||
}
|
||||
if (auto* t = ty->As<ast::Pointer>()) {
|
||||
if (auto* el = Type(t->type())) {
|
||||
auto access = t->access();
|
||||
if (access == ast::kUndefined) {
|
||||
access = DefaultAccessForStorageClass(t->storage_class());
|
||||
}
|
||||
return builder_->create<sem::Pointer>(const_cast<sem::Type*>(el),
|
||||
t->storage_class());
|
||||
t->storage_class(), access);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -477,11 +481,17 @@ Resolver::VariableInfo* Resolver::Variable(ast::Variable* var,
|
||||
}
|
||||
}
|
||||
|
||||
auto access = var->declared_access();
|
||||
if (access == ast::Access::kUndefined) {
|
||||
access = DefaultAccessForStorageClass(storage_class);
|
||||
}
|
||||
|
||||
auto* type = storage_type;
|
||||
if (!var->is_const()) {
|
||||
// Variable declaration. Unlike `let`, `var` has storage.
|
||||
// Variables are always of a reference type to the declared storage type.
|
||||
type = builder_->create<sem::Reference>(storage_type, storage_class);
|
||||
type =
|
||||
builder_->create<sem::Reference>(storage_type, storage_class, access);
|
||||
}
|
||||
|
||||
if (rhs_type && !ValidateVariableConstructor(var, storage_type, type_name,
|
||||
@@ -489,15 +499,6 @@ Resolver::VariableInfo* Resolver::Variable(ast::Variable* var,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto access = var->declared_access();
|
||||
if (access == ast::Access::kUndefined &&
|
||||
storage_class == ast::StorageClass::kStorage) {
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#access-mode-defaults
|
||||
// For the storage storage class, the access mode is optional, and defaults
|
||||
// to read.
|
||||
access = ast::Access::kRead;
|
||||
}
|
||||
|
||||
auto* info = variable_infos_.Create(var, const_cast<sem::Type*>(type),
|
||||
type_name, storage_class, access);
|
||||
variable_to_info_.emplace(var, info);
|
||||
@@ -505,6 +506,21 @@ Resolver::VariableInfo* Resolver::Variable(ast::Variable* var,
|
||||
return info;
|
||||
}
|
||||
|
||||
ast::AccessControl Resolver::DefaultAccessForStorageClass(
|
||||
ast::StorageClass storage_class) {
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#storage-class
|
||||
switch (storage_class) {
|
||||
case ast::StorageClass::kStorage:
|
||||
return ast::Access::kRead;
|
||||
case ast::StorageClass::kUniform:
|
||||
case ast::StorageClass::kUniformConstant:
|
||||
return ast::Access::kRead;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ast::Access::kReadWrite;
|
||||
}
|
||||
|
||||
bool Resolver::ValidateVariableConstructor(const ast::Variable* var,
|
||||
const sem::Type* storage_type,
|
||||
const std::string& type_name,
|
||||
@@ -1603,7 +1619,8 @@ bool Resolver::ArrayAccessor(ast::ArrayAccessorExpression* expr) {
|
||||
|
||||
// If we're extracting from a reference, we return a reference.
|
||||
if (auto* ref = res->As<sem::Reference>()) {
|
||||
ret = builder_->create<sem::Reference>(ret, ref->StorageClass());
|
||||
ret = builder_->create<sem::Reference>(ret, ref->StorageClass(),
|
||||
ref->Access());
|
||||
}
|
||||
SetType(expr, ret);
|
||||
|
||||
@@ -1959,7 +1976,8 @@ bool Resolver::MemberAccessor(ast::MemberAccessorExpression* expr) {
|
||||
|
||||
// If we're extracting from a reference, we return a reference.
|
||||
if (auto* ref = structure->As<sem::Reference>()) {
|
||||
ret = builder_->create<sem::Reference>(ret, ref->StorageClass());
|
||||
ret = builder_->create<sem::Reference>(ret, ref->StorageClass(),
|
||||
ref->Access());
|
||||
}
|
||||
|
||||
builder_->Sem().Add(expr, builder_->create<sem::StructMemberAccess>(
|
||||
@@ -2028,7 +2046,8 @@ bool Resolver::MemberAccessor(ast::MemberAccessorExpression* expr) {
|
||||
ret = vec->type();
|
||||
// If we're extracting from a reference, we return a reference.
|
||||
if (auto* ref = structure->As<sem::Reference>()) {
|
||||
ret = builder_->create<sem::Reference>(ret, ref->StorageClass());
|
||||
ret = builder_->create<sem::Reference>(ret, ref->StorageClass(),
|
||||
ref->Access());
|
||||
}
|
||||
} else {
|
||||
// The vector will have a number of components equal to the length of
|
||||
@@ -2291,8 +2310,8 @@ bool Resolver::UnaryOp(ast::UnaryOpExpression* unary) {
|
||||
|
||||
case ast::UnaryOp::kAddressOf:
|
||||
if (auto* ref = expr_type->As<sem::Reference>()) {
|
||||
type = builder_->create<sem::Pointer>(ref->StoreType(),
|
||||
ref->StorageClass());
|
||||
type = builder_->create<sem::Pointer>(
|
||||
ref->StoreType(), ref->StorageClass(), ref->Access());
|
||||
} else {
|
||||
diagnostics_.add_error("cannot take the address of expression",
|
||||
unary->expr()->source());
|
||||
@@ -2302,8 +2321,8 @@ bool Resolver::UnaryOp(ast::UnaryOpExpression* unary) {
|
||||
|
||||
case ast::UnaryOp::kIndirection:
|
||||
if (auto* ptr = expr_type->As<sem::Pointer>()) {
|
||||
type = builder_->create<sem::Reference>(ptr->StoreType(),
|
||||
ptr->StorageClass());
|
||||
type = builder_->create<sem::Reference>(
|
||||
ptr->StoreType(), ptr->StorageClass(), ptr->Access());
|
||||
} else {
|
||||
diagnostics_.add_error("cannot dereference expression of type '" +
|
||||
TypeNameOf(unary->expr()) + "'",
|
||||
|
||||
@@ -319,6 +319,11 @@ class Resolver {
|
||||
uint32_t& align,
|
||||
uint32_t& size);
|
||||
|
||||
/// @param storage_class the storage class
|
||||
/// @returns the default access control for the given storage class
|
||||
ast::AccessControl DefaultAccessForStorageClass(
|
||||
ast::StorageClass storage_class);
|
||||
|
||||
/// @returns the resolved type of the ast::Expression `expr`
|
||||
/// @param expr the expression
|
||||
sem::Type* TypeOf(const ast::Expression* expr);
|
||||
|
||||
@@ -87,7 +87,8 @@ TEST_P(InferTypeTest_FromConstructorExpression, All) {
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* got = TypeOf(a_ident);
|
||||
auto* expected = create<sem::Reference>(params.create_rhs_sem_type(ty),
|
||||
ast::StorageClass::kFunction);
|
||||
ast::StorageClass::kFunction,
|
||||
ast::Access::kReadWrite);
|
||||
ASSERT_EQ(got, expected) << "got: " << FriendlyName(got) << "\n"
|
||||
<< "expected: " << FriendlyName(expected) << "\n";
|
||||
}
|
||||
@@ -141,7 +142,8 @@ TEST_P(InferTypeTest_FromArithmeticExpression, All) {
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* got = TypeOf(a_ident);
|
||||
auto* expected = create<sem::Reference>(params.create_rhs_sem_type(ty),
|
||||
ast::StorageClass::kFunction);
|
||||
ast::StorageClass::kFunction,
|
||||
ast::Access::kReadWrite);
|
||||
ASSERT_EQ(got, expected) << "got: " << FriendlyName(got) << "\n"
|
||||
<< "expected: " << FriendlyName(expected) << "\n";
|
||||
}
|
||||
@@ -190,7 +192,8 @@ TEST_P(InferTypeTest_FromCallExpression, All) {
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* got = TypeOf(a_ident);
|
||||
auto* expected = create<sem::Reference>(params.create_rhs_sem_type(ty),
|
||||
ast::StorageClass::kFunction);
|
||||
ast::StorageClass::kFunction,
|
||||
ast::Access::kReadWrite);
|
||||
ASSERT_EQ(got, expected) << "got: " << FriendlyName(got) << "\n"
|
||||
<< "expected: " << FriendlyName(expected) << "\n";
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ TEST_F(ResolverVarLetValidationTest, VarConstructorNotStorable) {
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: 'ptr<function, i32>' is not storable for assignment");
|
||||
"12:34 error: 'ptr<function, i32, read_write>' is not storable for "
|
||||
"assignment");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, LetConstructorWrongType) {
|
||||
|
||||
@@ -15,18 +15,24 @@
|
||||
#include "src/sem/pointer_type.h"
|
||||
|
||||
#include "src/program_builder.h"
|
||||
#include "src/sem/reference_type.h"
|
||||
|
||||
TINT_INSTANTIATE_TYPEINFO(tint::sem::Pointer);
|
||||
|
||||
namespace tint {
|
||||
namespace sem {
|
||||
|
||||
Pointer::Pointer(const Type* subtype, ast::StorageClass storage_class)
|
||||
: subtype_(subtype), storage_class_(storage_class) {}
|
||||
Pointer::Pointer(const Type* subtype,
|
||||
ast::StorageClass storage_class,
|
||||
ast::Access access)
|
||||
: subtype_(subtype), storage_class_(storage_class), access_(access) {
|
||||
TINT_ASSERT(!subtype->Is<Reference>());
|
||||
TINT_ASSERT(access != ast::Access::kUndefined);
|
||||
}
|
||||
|
||||
std::string Pointer::type_name() const {
|
||||
std::ostringstream out;
|
||||
out << "__ptr_" << storage_class_ << subtype_->type_name();
|
||||
out << "__ptr_" << storage_class_ << subtype_->type_name() << "__" << access_;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
@@ -36,7 +42,8 @@ std::string Pointer::FriendlyName(const SymbolTable& symbols) const {
|
||||
if (storage_class_ != ast::StorageClass::kNone) {
|
||||
out << storage_class_ << ", ";
|
||||
}
|
||||
out << subtype_->FriendlyName(symbols) << ">";
|
||||
out << subtype_->FriendlyName(symbols) << ", " << access_;
|
||||
out << ">";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "src/ast/access.h"
|
||||
#include "src/ast/storage_class.h"
|
||||
#include "src/sem/type.h"
|
||||
|
||||
@@ -29,16 +30,24 @@ class Pointer : public Castable<Pointer, Type> {
|
||||
/// Constructor
|
||||
/// @param subtype the pointee type
|
||||
/// @param storage_class the storage class of the pointer
|
||||
Pointer(const Type* subtype, ast::StorageClass storage_class);
|
||||
/// @param access the resolved access control of the reference
|
||||
Pointer(const Type* subtype,
|
||||
ast::StorageClass storage_class,
|
||||
ast::Access access);
|
||||
|
||||
/// Move constructor
|
||||
Pointer(Pointer&&);
|
||||
~Pointer() override;
|
||||
|
||||
/// @returns the pointee type
|
||||
const Type* StoreType() const { return subtype_; }
|
||||
|
||||
/// @returns the storage class of the pointer
|
||||
ast::StorageClass StorageClass() const { return storage_class_; }
|
||||
|
||||
/// @returns the access control of the reference
|
||||
ast::Access Access() const { return access_; }
|
||||
|
||||
/// @returns the name for this type
|
||||
std::string type_name() const override;
|
||||
|
||||
@@ -50,6 +59,7 @@ class Pointer : public Castable<Pointer, Type> {
|
||||
private:
|
||||
Type const* const subtype_;
|
||||
ast::StorageClass const storage_class_;
|
||||
ast::AccessControl const access_;
|
||||
};
|
||||
|
||||
} // namespace sem
|
||||
|
||||
@@ -22,24 +22,29 @@ namespace {
|
||||
using PointerTest = TestHelper;
|
||||
|
||||
TEST_F(PointerTest, Creation) {
|
||||
auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kStorage);
|
||||
auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kStorage,
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_TRUE(r->StoreType()->Is<sem::I32>());
|
||||
EXPECT_EQ(r->StorageClass(), ast::StorageClass::kStorage);
|
||||
EXPECT_EQ(r->Access(), ast::Access::kReadWrite);
|
||||
}
|
||||
|
||||
TEST_F(PointerTest, TypeName) {
|
||||
auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kWorkgroup);
|
||||
EXPECT_EQ(r->type_name(), "__ptr_workgroup__i32");
|
||||
auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kWorkgroup,
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(r->type_name(), "__ptr_workgroup__i32__read_write");
|
||||
}
|
||||
|
||||
TEST_F(PointerTest, FriendlyName) {
|
||||
auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kNone,
|
||||
ast::Access::kRead);
|
||||
EXPECT_EQ(r->FriendlyName(Symbols()), "ptr<i32, read>");
|
||||
}
|
||||
|
||||
TEST_F(PointerTest, FriendlyNameWithStorageClass) {
|
||||
auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kWorkgroup);
|
||||
EXPECT_EQ(r->FriendlyName(Symbols()), "ptr<workgroup, i32>");
|
||||
}
|
||||
|
||||
TEST_F(PointerTest, FriendlyNameWithoutStorageClass) {
|
||||
auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kNone);
|
||||
EXPECT_EQ(r->FriendlyName(Symbols()), "ptr<i32>");
|
||||
auto* r = create<Pointer>(create<I32>(), ast::StorageClass::kWorkgroup,
|
||||
ast::Access::kRead);
|
||||
EXPECT_EQ(r->FriendlyName(Symbols()), "ptr<workgroup, i32, read>");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -21,14 +21,17 @@ TINT_INSTANTIATE_TYPEINFO(tint::sem::Reference);
|
||||
namespace tint {
|
||||
namespace sem {
|
||||
|
||||
Reference::Reference(const Type* subtype, ast::StorageClass storage_class)
|
||||
: subtype_(subtype), storage_class_(storage_class) {
|
||||
Reference::Reference(const Type* subtype,
|
||||
ast::StorageClass storage_class,
|
||||
ast::Access access)
|
||||
: subtype_(subtype), storage_class_(storage_class), access_(access) {
|
||||
TINT_ASSERT(!subtype->Is<Reference>());
|
||||
TINT_ASSERT(access != ast::Access::kUndefined);
|
||||
}
|
||||
|
||||
std::string Reference::type_name() const {
|
||||
std::ostringstream out;
|
||||
out << "__ref_" << storage_class_ << subtype_->type_name();
|
||||
out << "__ref_" << storage_class_ << subtype_->type_name() << "__" << access_;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
@@ -38,7 +41,8 @@ std::string Reference::FriendlyName(const SymbolTable& symbols) const {
|
||||
if (storage_class_ != ast::StorageClass::kNone) {
|
||||
out << storage_class_ << ", ";
|
||||
}
|
||||
out << subtype_->FriendlyName(symbols) << ">";
|
||||
out << subtype_->FriendlyName(symbols) << ", " << access_;
|
||||
out << ">";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "src/ast/access.h"
|
||||
#include "src/ast/storage_class.h"
|
||||
#include "src/sem/type.h"
|
||||
|
||||
@@ -29,16 +30,24 @@ class Reference : public Castable<Reference, Type> {
|
||||
/// Constructor
|
||||
/// @param subtype the pointee type
|
||||
/// @param storage_class the storage class of the reference
|
||||
Reference(const Type* subtype, ast::StorageClass storage_class);
|
||||
/// @param access the resolved access control of the reference
|
||||
Reference(const Type* subtype,
|
||||
ast::StorageClass storage_class,
|
||||
ast::Access access);
|
||||
|
||||
/// Move constructor
|
||||
Reference(Reference&&);
|
||||
~Reference() override;
|
||||
|
||||
/// @returns the pointee type
|
||||
const Type* StoreType() const { return subtype_; }
|
||||
|
||||
/// @returns the storage class of the reference
|
||||
ast::StorageClass StorageClass() const { return storage_class_; }
|
||||
|
||||
/// @returns the resolved access control of the reference.
|
||||
ast::Access Access() const { return access_; }
|
||||
|
||||
/// @returns the name for this type
|
||||
std::string type_name() const override;
|
||||
|
||||
@@ -50,6 +59,7 @@ class Reference : public Castable<Reference, Type> {
|
||||
private:
|
||||
Type const* const subtype_;
|
||||
ast::StorageClass const storage_class_;
|
||||
ast::AccessControl const access_;
|
||||
};
|
||||
|
||||
} // namespace sem
|
||||
|
||||
@@ -22,24 +22,29 @@ namespace {
|
||||
using ReferenceTest = TestHelper;
|
||||
|
||||
TEST_F(ReferenceTest, Creation) {
|
||||
auto* r = create<Reference>(create<I32>(), ast::StorageClass::kStorage);
|
||||
auto* r = create<Reference>(create<I32>(), ast::StorageClass::kStorage,
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_TRUE(r->StoreType()->Is<sem::I32>());
|
||||
EXPECT_EQ(r->StorageClass(), ast::StorageClass::kStorage);
|
||||
EXPECT_EQ(r->Access(), ast::Access::kReadWrite);
|
||||
}
|
||||
|
||||
TEST_F(ReferenceTest, TypeName) {
|
||||
auto* r = create<Reference>(create<I32>(), ast::StorageClass::kWorkgroup);
|
||||
EXPECT_EQ(r->type_name(), "__ref_workgroup__i32");
|
||||
auto* r = create<Reference>(create<I32>(), ast::StorageClass::kWorkgroup,
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(r->type_name(), "__ref_workgroup__i32__read_write");
|
||||
}
|
||||
|
||||
TEST_F(ReferenceTest, FriendlyName) {
|
||||
auto* r = create<Reference>(create<I32>(), ast::StorageClass::kNone,
|
||||
ast::Access::kRead);
|
||||
EXPECT_EQ(r->FriendlyName(Symbols()), "ref<i32, read>");
|
||||
}
|
||||
|
||||
TEST_F(ReferenceTest, FriendlyNameWithStorageClass) {
|
||||
auto* r = create<Reference>(create<I32>(), ast::StorageClass::kWorkgroup);
|
||||
EXPECT_EQ(r->FriendlyName(Symbols()), "ref<workgroup, i32>");
|
||||
}
|
||||
|
||||
TEST_F(ReferenceTest, FriendlyNameWithoutStorageClass) {
|
||||
auto* r = create<Reference>(create<I32>(), ast::StorageClass::kNone);
|
||||
EXPECT_EQ(r->FriendlyName(Symbols()), "ref<i32>");
|
||||
auto* r = create<Reference>(create<I32>(), ast::StorageClass::kWorkgroup,
|
||||
ast::Access::kRead);
|
||||
EXPECT_EQ(r->FriendlyName(Symbols()), "ref<workgroup, i32, read>");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -154,7 +154,8 @@ TEST_F(HlslGeneratorImplTest_Type, EmitType_Matrix) {
|
||||
// TODO(dsinclair): How to annotate as workgroup?
|
||||
TEST_F(HlslGeneratorImplTest_Type, DISABLED_EmitType_Pointer) {
|
||||
auto* f32 = create<sem::F32>();
|
||||
auto* p = create<sem::Pointer>(f32, ast::StorageClass::kWorkgroup);
|
||||
auto* p = create<sem::Pointer>(f32, ast::StorageClass::kWorkgroup,
|
||||
ast::Access::kReadWrite);
|
||||
|
||||
GeneratorImpl& gen = Build();
|
||||
|
||||
|
||||
@@ -162,7 +162,8 @@ TEST_F(MslGeneratorImplTest, EmitType_Matrix) {
|
||||
|
||||
TEST_F(MslGeneratorImplTest, EmitType_Pointer) {
|
||||
auto* f32 = create<sem::F32>();
|
||||
auto* p = create<sem::Pointer>(f32, ast::StorageClass::kWorkgroup);
|
||||
auto* p = create<sem::Pointer>(f32, ast::StorageClass::kWorkgroup,
|
||||
ast::Access::kReadWrite);
|
||||
|
||||
GeneratorImpl& gen = Build();
|
||||
|
||||
|
||||
@@ -1730,8 +1730,8 @@ uint32_t Builder::GenerateShortCircuitBinaryExpression(
|
||||
uint32_t Builder::GenerateSplat(uint32_t scalar_id, const sem::Type* vec_type) {
|
||||
// Create a new vector to splat scalar into
|
||||
auto splat_vector = result_op();
|
||||
auto* splat_vector_type =
|
||||
builder_.create<sem::Pointer>(vec_type, ast::StorageClass::kFunction);
|
||||
auto* splat_vector_type = builder_.create<sem::Pointer>(
|
||||
vec_type, ast::StorageClass::kFunction, ast::Access::kReadWrite);
|
||||
push_function_var(
|
||||
{Operand::Int(GenerateTypeIfNeeded(splat_vector_type)), splat_vector,
|
||||
Operand::Int(ConvertStorageClass(ast::StorageClass::kFunction)),
|
||||
@@ -3228,13 +3228,22 @@ uint32_t Builder::GenerateTypeIfNeeded(const sem::Type* type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Pointers and references with differing accesses should not result in a
|
||||
// different SPIR-V types, so we explicitly ignore the access.
|
||||
// Pointers and References both map to a SPIR-V pointer type.
|
||||
// Transform a Reference to a Pointer to prevent these having duplicated
|
||||
// definitions in the generated SPIR-V. Note that nested references are not
|
||||
// legal, so only considering the top-level type is fine.
|
||||
// definitions in the generated SPIR-V. Note that nested pointers and
|
||||
// references are not legal in WGSL, so only considering the top-level type is
|
||||
// fine.
|
||||
std::string type_name;
|
||||
if (auto* ref = type->As<sem::Reference>()) {
|
||||
type_name = sem::Pointer(ref->StoreType(), ref->StorageClass()).type_name();
|
||||
if (auto* ptr = type->As<sem::Pointer>()) {
|
||||
type_name =
|
||||
sem::Pointer(ptr->StoreType(), ptr->StorageClass(), ast::kReadWrite)
|
||||
.type_name();
|
||||
} else if (auto* ref = type->As<sem::Reference>()) {
|
||||
type_name =
|
||||
sem::Pointer(ref->StoreType(), ref->StorageClass(), ast::kReadWrite)
|
||||
.type_name();
|
||||
} else {
|
||||
type_name = type->type_name();
|
||||
}
|
||||
|
||||
@@ -241,7 +241,8 @@ TEST_F(BuilderTest_Type, ReturnsGeneratedMatrix) {
|
||||
|
||||
TEST_F(BuilderTest_Type, GeneratePtr) {
|
||||
auto* i32 = create<sem::I32>();
|
||||
auto* ptr = create<sem::Pointer>(i32, ast::StorageClass::kOutput);
|
||||
auto* ptr = create<sem::Pointer>(i32, ast::StorageClass::kOutput,
|
||||
ast::Access::kReadWrite);
|
||||
|
||||
spirv::Builder& b = Build();
|
||||
|
||||
@@ -256,7 +257,8 @@ TEST_F(BuilderTest_Type, GeneratePtr) {
|
||||
|
||||
TEST_F(BuilderTest_Type, ReturnsGeneratedPtr) {
|
||||
auto* i32 = create<sem::I32>();
|
||||
auto* ptr = create<sem::Pointer>(i32, ast::StorageClass::kOutput);
|
||||
auto* ptr = create<sem::Pointer>(i32, ast::StorageClass::kOutput,
|
||||
ast::Access::kReadWrite);
|
||||
|
||||
spirv::Builder& b = Build();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user