spirv-reader: track access mode for ptr/ref
Fixed: tint:1041 tint:1103
Change-Id: Ief5f3da73c65700fe904e76683b9b25f4eca2169
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/104900
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index 79c55de..9c38818 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -2593,7 +2593,6 @@
// Construct the reference type, mapping storage class correctly.
const auto* type =
RemapPointerProperties(parser_impl_.ConvertType(inst->type_id(), PtrAs::Ref), id);
- // TODO(crbug.com/tint/1041): Fix access mode
return TypedExpression{type, create<ast::IdentifierExpression>(
Source{}, builder_.Symbols().Register(name))};
}
@@ -4859,10 +4858,15 @@
}
case SpvOpFunctionParameter: {
const auto* type = As<Pointer>(parser_impl_.ConvertType(inst.type_id()));
- // TODO(crbug.com/tint/1041): Add access mode.
- // Using kUndefined is ok for now, since the only non-default access mode
- // on a pointer would be for a storage buffer, and baseline SPIR-V doesn't
- // allow passing pointers to buffers as function parameters.
+ // For access mode, kUndefined is ok for now, since the
+ // only non-default access mode on a pointer would be for a storage
+ // buffer, and baseline SPIR-V doesn't allow passing pointers to
+ // buffers as function parameters.
+ // If/when the SPIR-V path supports variable pointers, then we
+ // can pointers to read-only storage buffers passed as
+ // parameters. In that case we need to do a global analysis to
+ // determine what the formal argument parameter type should be,
+ // whether it has read_only or read_write access mode.
return DefInfo::Pointer{type->address_space, ast::Access::kUndefined};
}
default:
@@ -4899,13 +4903,11 @@
const Type* FunctionEmitter::RemapPointerProperties(const Type* type, uint32_t result_id) {
if (auto* ast_ptr_type = As<Pointer>(type)) {
const auto pi = GetPointerInfo(result_id);
- // TODO(crbug.com/tin/t1041): also do access mode
- return ty_.Pointer(ast_ptr_type->type, pi.address_space);
+ return ty_.Pointer(ast_ptr_type->type, pi.address_space, pi.access);
}
if (auto* ast_ptr_type = As<Reference>(type)) {
const auto pi = GetPointerInfo(result_id);
- // TODO(crbug.com/tin/t1041): also do access mode
- return ty_.Reference(ast_ptr_type->type, pi.address_space);
+ return ty_.Reference(ast_ptr_type->type, pi.address_space, pi.access);
}
return type;
}
diff --git a/src/tint/reader/spirv/function_memory_test.cc b/src/tint/reader/spirv/function_memory_test.cc
index a64aac5..66979ae 100644
--- a/src/tint/reader/spirv/function_memory_test.cc
+++ b/src/tint/reader/spirv/function_memory_test.cc
@@ -858,7 +858,52 @@
EXPECT_EQ(got, expected) << got;
}
-std::string OldStorageBufferPreamble() {
+std::string NewStorageBufferPreamble(bool nonwritable = false) {
+ // Declare a buffer with
+ // StorageBuffer storage class
+ // Block struct decoration
+ return std::string(R"(
+ OpCapability Shader
+ OpExtension "SPV_KHR_storage_buffer_storage_class"
+ OpMemoryModel Logical Simple
+ OpEntryPoint Fragment %100 "main"
+ OpExecutionMode %100 OriginUpperLeft
+ OpName %myvar "myvar"
+
+ OpDecorate %myvar DescriptorSet 0
+ OpDecorate %myvar Binding 0
+
+ OpDecorate %struct Block
+ OpMemberDecorate %struct 0 Offset 0
+ OpMemberDecorate %struct 1 Offset 4
+ OpDecorate %arr ArrayStride 4
+ )") +
+ (nonwritable ? R"(
+ OpMemberDecorate %struct 0 NonWritable
+ OpMemberDecorate %struct 1 NonWritable)"
+ : "") +
+ R"(
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+
+ %uint_0 = OpConstant %uint 0
+ %uint_1 = OpConstant %uint 1
+
+ %arr = OpTypeRuntimeArray %uint
+ %struct = OpTypeStruct %uint %arr
+ %ptr_struct = OpTypePointer StorageBuffer %struct
+ %ptr_uint = OpTypePointer StorageBuffer %uint
+
+ %myvar = OpVariable %ptr_struct StorageBuffer
+ )";
+}
+
+std::string OldStorageBufferPreamble(bool nonwritable = false) {
+ // Declare a buffer with
+ // Unifrom storage class
+ // BufferBlock struct decoration
+ // This is the deprecated way to declare a storage buffer.
return Preamble() + R"(
OpName %myvar "myvar"
@@ -869,7 +914,12 @@
OpMemberDecorate %struct 0 Offset 0
OpMemberDecorate %struct 1 Offset 4
OpDecorate %arr ArrayStride 4
-
+ )" +
+ (nonwritable ? R"(
+ OpMemberDecorate %struct 0 NonWritable
+ OpMemberDecorate %struct 1 NonWritable)"
+ : "") +
+ R"(
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%uint = OpTypeInt 32 0
@@ -938,7 +988,7 @@
)"));
}
-TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughAccessChain_NonCascaded_UsedTwice) {
+TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughAccessChain_NonCascaded_UsedTwice_ReadWrite) {
// Use the pointer value twice, which provokes the spirv-reader
// to make a let declaration for the pointer. The storage class
// must be 'storage', not 'uniform'.
@@ -966,15 +1016,130 @@
EXPECT_TRUE(fe.EmitBody()) << p->error();
auto ast_body = fe.ast_body();
const auto got = test::ToString(p->program(), ast_body);
- EXPECT_THAT(got, HasSubstr(R"(let x_1 : ptr<storage, u32> = &(myvar.field0);
+ EXPECT_THAT(got, HasSubstr(R"(let x_1 : ptr<storage, u32, read_write> = &(myvar.field0);
*(x_1) = 0u;
*(x_1) = 0u;
-let x_2 : ptr<storage, u32> = &(myvar.field1[1u]);
+let x_2 : ptr<storage, u32, read_write> = &(myvar.field1[1u]);
let x_3 : u32 = *(x_2);
*(x_2) = 0u;
)"));
}
+TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughAccessChain_NonCascaded_UsedTwice_ReadOnly) {
+ // Like the previous test, but make the storage buffer read_only.
+ // The pointer type must also be read-only.
+ const auto assembly = OldStorageBufferPreamble(true) + R"(
+ %100 = OpFunction %void None %voidfn
+ %entry = OpLabel
+
+ ; the scalar element
+ %1 = OpAccessChain %ptr_uint %myvar %uint_0
+ OpStore %1 %uint_0
+ OpStore %1 %uint_0
+
+ ; element in the runtime array
+ %2 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1
+ ; Use the pointer twice
+ %3 = OpLoad %uint %2
+ OpStore %2 %uint_0
+
+ OpReturn
+ OpFunctionEnd
+)";
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+ auto fe = p->function_emitter(100);
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ auto ast_body = fe.ast_body();
+ const auto got = test::ToString(p->program(), ast_body);
+ EXPECT_THAT(got, HasSubstr(R"(let x_1 : ptr<storage, u32, read> = &(myvar.field0);
+*(x_1) = 0u;
+*(x_1) = 0u;
+let x_2 : ptr<storage, u32, read> = &(myvar.field1[1u]);
+let x_3 : u32 = *(x_2);
+*(x_2) = 0u;
+)")) << got
+ << assembly;
+}
+
+TEST_F(SpvParserMemoryTest, StorageBuffer_ThroughAccessChain_NonCascaded_UsedTwice_ReadWrite) {
+ // Use new style storage buffer declaration:
+ // StorageBuffer storage class,
+ // Block decoration
+ // The pointer type must use 'storage' address space, and should use defaulted access
+ const auto assembly = NewStorageBufferPreamble() + R"(
+ %100 = OpFunction %void None %voidfn
+ %entry = OpLabel
+
+ ; the scalar element
+ %1 = OpAccessChain %ptr_uint %myvar %uint_0
+ OpStore %1 %uint_0
+ OpStore %1 %uint_0
+
+ ; element in the runtime array
+ %2 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1
+ ; Use the pointer twice
+ %3 = OpLoad %uint %2
+ OpStore %2 %uint_0
+
+ OpReturn
+ OpFunctionEnd
+)";
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+ auto fe = p->function_emitter(100);
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ auto ast_body = fe.ast_body();
+ const auto got = test::ToString(p->program(), ast_body);
+ EXPECT_THAT(got, HasSubstr(R"(let x_1 : ptr<storage, u32, read_write> = &(myvar.field0);
+*(x_1) = 0u;
+*(x_1) = 0u;
+let x_2 : ptr<storage, u32, read_write> = &(myvar.field1[1u]);
+let x_3 : u32 = *(x_2);
+*(x_2) = 0u;
+)")) << got;
+}
+
+TEST_F(SpvParserMemoryTest, StorageBuffer_ThroughAccessChain_NonCascaded_UsedTwice_ReadOnly) {
+ // Like the previous test, but make the storage buffer read_only.
+ // Use new style storage buffer declaration:
+ // StorageBuffer storage class,
+ // Block decoration
+ // The pointer type must use 'storage' address space, and must use read_only
+ // access
+ const auto assembly = NewStorageBufferPreamble(true) + R"(
+ %100 = OpFunction %void None %voidfn
+ %entry = OpLabel
+
+ ; the scalar element
+ %1 = OpAccessChain %ptr_uint %myvar %uint_0
+ OpStore %1 %uint_0
+ OpStore %1 %uint_0
+
+ ; element in the runtime array
+ %2 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1
+ ; Use the pointer twice
+ %3 = OpLoad %uint %2
+ OpStore %2 %uint_0
+
+ OpReturn
+ OpFunctionEnd
+)";
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+ auto fe = p->function_emitter(100);
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ auto ast_body = fe.ast_body();
+ const auto got = test::ToString(p->program(), ast_body);
+ EXPECT_THAT(got, HasSubstr(R"(let x_1 : ptr<storage, u32, read> = &(myvar.field0);
+*(x_1) = 0u;
+*(x_1) = 0u;
+let x_2 : ptr<storage, u32, read> = &(myvar.field1[1u]);
+let x_3 : u32 = *(x_2);
+*(x_2) = 0u;
+)")) << got;
+}
+
TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain) {
// Like the previous test, but using OpInBoundsAccessChain.
const auto assembly = OldStorageBufferPreamble() + R"(
@@ -1050,7 +1215,7 @@
EXPECT_TRUE(fe.EmitBody()) << p->error();
auto ast_body = fe.ast_body();
EXPECT_THAT(test::ToString(p->program(), ast_body),
- HasSubstr(R"(let x_2 : ptr<storage, u32> = &(myvar.field1[1u]);
+ HasSubstr(R"(let x_2 : ptr<storage, u32, read_write> = &(myvar.field1[1u]);
*(x_2) = 0u;
)")) << p->error();
@@ -1128,7 +1293,7 @@
EXPECT_TRUE(fe.EmitBody()) << p->error();
auto ast_body = fe.ast_body();
const auto body_str = test::ToString(p->program(), ast_body);
- EXPECT_THAT(body_str, HasSubstr(R"(let x_2 : ptr<storage, S> = &(myvar);
+ EXPECT_THAT(body_str, HasSubstr(R"(let x_2 : ptr<storage, S, read_write> = &(myvar);
let x_1 : u32 = arrayLength(&((*(x_2)).rtarr));
)")) << body_str;
diff --git a/src/tint/reader/spirv/parser_type.cc b/src/tint/reader/spirv/parser_type.cc
index 256444a..ff4b96e 100644
--- a/src/tint/reader/spirv/parser_type.cc
+++ b/src/tint/reader/spirv/parser_type.cc
@@ -50,11 +50,15 @@
namespace {
struct PointerHasher {
- size_t operator()(const Pointer& t) const { return utils::Hash(t.type, t.address_space); }
+ size_t operator()(const Pointer& t) const {
+ return utils::Hash(t.type, t.address_space, t.access);
+ }
};
struct ReferenceHasher {
- size_t operator()(const Reference& t) const { return utils::Hash(t.type, t.address_space); }
+ size_t operator()(const Reference& t) const {
+ return utils::Hash(t.type, t.address_space, t.access);
+ }
};
struct VectorHasher {
@@ -107,10 +111,10 @@
// Equality operators
//! @cond Doxygen_Suppress
static bool operator==(const Pointer& a, const Pointer& b) {
- return a.type == b.type && a.address_space == b.address_space;
+ return a.type == b.type && a.address_space == b.address_space && a.access == b.access;
}
static bool operator==(const Reference& a, const Reference& b) {
- return a.type == b.type && a.address_space == b.address_space;
+ return a.type == b.type && a.address_space == b.address_space && a.access == b.access;
}
static bool operator==(const Vector& a, const Vector& b) {
return a.type == b.type && a.size == b.size;
@@ -170,14 +174,16 @@
Texture::~Texture() = default;
-Pointer::Pointer(const Type* t, ast::AddressSpace s) : type(t), address_space(s) {}
+Pointer::Pointer(const Type* t, ast::AddressSpace s, ast::Access a)
+ : type(t), address_space(s), access(a) {}
Pointer::Pointer(const Pointer&) = default;
const ast::Type* Pointer::Build(ProgramBuilder& b) const {
- return b.ty.pointer(type->Build(b), address_space);
+ return b.ty.pointer(type->Build(b), address_space, access);
}
-Reference::Reference(const Type* t, ast::AddressSpace s) : type(t), address_space(s) {}
+Reference::Reference(const Type* t, ast::AddressSpace s, ast::Access a)
+ : type(t), address_space(s), access(a) {}
Reference::Reference(const Reference&) = default;
const ast::Type* Reference::Build(ProgramBuilder& b) const {
@@ -438,12 +444,16 @@
return state->i32_;
}
-const spirv::Pointer* TypeManager::Pointer(const Type* el, ast::AddressSpace address_space) {
- return state->pointers_.Get(el, address_space);
+const spirv::Pointer* TypeManager::Pointer(const Type* el,
+ ast::AddressSpace address_space,
+ ast::Access access) {
+ return state->pointers_.Get(el, address_space, access);
}
-const spirv::Reference* TypeManager::Reference(const Type* el, ast::AddressSpace address_space) {
- return state->references_.Get(el, address_space);
+const spirv::Reference* TypeManager::Reference(const Type* el,
+ ast::AddressSpace address_space,
+ ast::Access access) {
+ return state->references_.Get(el, address_space, access);
}
const spirv::Vector* TypeManager::Vector(const Type* el, uint32_t size) {
diff --git a/src/tint/reader/spirv/parser_type.h b/src/tint/reader/spirv/parser_type.h
index 4c67f7a..48b6f3f 100644
--- a/src/tint/reader/spirv/parser_type.h
+++ b/src/tint/reader/spirv/parser_type.h
@@ -157,12 +157,13 @@
#endif // NDEBUG
};
-/// `ptr<SC, T>` type
+/// `ptr<SC, T, AM>` type
struct Pointer final : public Castable<Pointer, Type> {
/// Constructor
/// @param ty the store type
/// @param sc the pointer address space
- Pointer(const Type* ty, ast::AddressSpace sc);
+ /// @param access the declared access mode
+ Pointer(const Type* ty, ast::AddressSpace sc, ast::Access access);
/// Copy constructor
/// @param other the other type to copy
@@ -181,16 +182,19 @@
Type const* const type;
/// the pointer address space
ast::AddressSpace const address_space;
+ /// the pointer declared access mode
+ ast::Access const access;
};
-/// `ref<SC, T>` type
+/// `ref<SC, T, AM>` type
/// Note this has no AST representation, but is used for type tracking in the
/// reader.
struct Reference final : public Castable<Reference, Type> {
/// Constructor
/// @param ty the referenced type
/// @param sc the reference address space
- Reference(const Type* ty, ast::AddressSpace sc);
+ /// @param access the reference declared access mode
+ Reference(const Type* ty, ast::AddressSpace sc, ast::Access access);
/// Copy constructor
/// @param other the other type to copy
@@ -209,6 +213,8 @@
Type const* const type;
/// the pointer address space
ast::AddressSpace const address_space;
+ /// the pointer declared access mode
+ ast::Access const access;
};
/// `vecN<T>` type
@@ -535,14 +541,20 @@
const spirv::I32* I32();
/// @param ty the store type
/// @param address_space the pointer address space
+ /// @param access the declared access mode
/// @return a Pointer type. Repeated calls with the same arguments will return
/// the same pointer.
- const spirv::Pointer* Pointer(const Type* ty, ast::AddressSpace address_space);
+ const spirv::Pointer* Pointer(const Type* ty,
+ ast::AddressSpace address_space,
+ ast::Access access = ast::Access::kUndefined);
/// @param ty the referenced type
/// @param address_space the reference address space
+ /// @param access the declared access mode
/// @return a Reference type. Repeated calls with the same arguments will
/// return the same pointer.
- const spirv::Reference* Reference(const Type* ty, ast::AddressSpace address_space);
+ const spirv::Reference* Reference(const Type* ty,
+ ast::AddressSpace address_space,
+ ast::Access access = ast::Access::kUndefined);
/// @param ty the element type
/// @param sz the number of elements in the vector
/// @return a Vector type. Repeated calls with the same arguments will return