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:
Ben Clayton
2021-06-04 22:17:37 +00:00
committed by Tint LUCI CQ
parent 3db1820f0b
commit 1858854f7e
248 changed files with 7332 additions and 2027 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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

View File

@@ -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>

View File

@@ -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));
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()) + "'",

View File

@@ -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);

View File

@@ -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";
}

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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();