[spirv-writer] Emit access control information.

This CL adds emission of the decorations for the access control flags.

Bug: tint:208 tint:108
Change-Id: I3286132dad8edd2586228dc6e87749ad49451739
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/31082
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
dan sinclair 2020-10-29 14:45:02 +00:00 committed by Commit Bot service account
parent 6857ed0b0b
commit c8b2d23e9d
6 changed files with 214 additions and 4 deletions

View File

@ -44,6 +44,8 @@ class AccessControlType : public Type {
/// @returns true if the access control is read/write
bool IsReadWrite() const { return access_ == AccessControl::kReadWrite; }
/// @returns the access control value
AccessControl access_control() const { return access_; }
/// @returns the subtype type
Type* type() const { return subtype_; }

View File

@ -49,6 +49,7 @@
#include "src/ast/struct_member.h"
#include "src/ast/struct_member_offset_decoration.h"
#include "src/ast/switch_statement.h"
#include "src/ast/type/access_control_type.h"
#include "src/ast/type/array_type.h"
#include "src/ast/type/depth_texture_type.h"
#include "src/ast/type/f32_type.h"
@ -695,11 +696,13 @@ bool Builder::GenerateGlobalVariable(ast::Variable* var) {
push_debug(spv::Op::OpName,
{Operand::Int(var_id), Operand::String(var->name())});
auto* type = var->type()->UnwrapAll();
OperandList ops = {Operand::Int(type_id), result,
Operand::Int(ConvertStorageClass(sc))};
if (var->has_constructor()) {
ops.push_back(Operand::Int(init_id));
} else if (!var->type()->IsTexture() && !var->type()->IsSampler()) {
} else if (!type->IsTexture() && !type->IsSampler()) {
// Certain cases require us to generate a constructor value.
//
// 1- ConstantId's must be attached to the OpConstant, if we have a
@ -707,7 +710,6 @@ bool Builder::GenerateGlobalVariable(ast::Variable* var) {
// one
// 2- If we don't have a constructor and we're an Output or Private variable
// then WGSL requires an initializer.
auto* type = var->type()->UnwrapPtrIfNeeded();
if (var->IsDecorated() && var->AsDecorated()->HasConstantIdDecoration()) {
if (type->IsF32()) {
ast::FloatLiteral l(type, 0.0f);
@ -2251,6 +2253,7 @@ uint32_t Builder::GenerateTypeIfNeeded(ast::type::Type* type) {
return 0;
}
// The alias is a wrapper around the subtype, so emit the subtype
if (type->IsAlias()) {
return GenerateTypeIfNeeded(type->AsAlias()->type());
}
@ -2263,7 +2266,18 @@ uint32_t Builder::GenerateTypeIfNeeded(ast::type::Type* type) {
auto result = result_op();
auto id = result.to_i();
if (type->IsArray()) {
if (type->IsAccessControl()) {
auto* ac = type->AsAccessControl();
auto* subtype = ac->type()->UnwrapIfNeeded();
if (!subtype->IsStruct()) {
error_ = "Access control attached to non-struct type.";
return 0;
}
if (!GenerateStructType(subtype->AsStruct(), ac->access_control(),
result)) {
return 0;
}
} else if (type->IsArray()) {
if (!GenerateArrayType(type->AsArray(), result)) {
return 0;
}
@ -2282,7 +2296,8 @@ uint32_t Builder::GenerateTypeIfNeeded(ast::type::Type* type) {
return 0;
}
} else if (type->IsStruct()) {
if (!GenerateStructType(type->AsStruct(), result)) {
if (!GenerateStructType(type->AsStruct(), ast::AccessControl::kReadWrite,
result)) {
return 0;
}
} else if (type->IsU32()) {
@ -2449,6 +2464,7 @@ bool Builder::GeneratePointerType(ast::type::PointerType* ptr,
}
bool Builder::GenerateStructType(ast::type::StructType* struct_type,
ast::AccessControl access_control,
const Operand& result) {
auto struct_id = result.to_i();
auto* impl = struct_type->impl();
@ -2473,6 +2489,19 @@ bool Builder::GenerateStructType(ast::type::StructType* struct_type,
return false;
}
// We're attaching the access control to the members of the struct instead
// of to the variable. The reason we do this is that WGSL models the access
// as part of the type. If we attach to the variable, it's no longer part
// of the type in the SPIR-V backend, but part of the variable. This differs
// from the modeling and other backends. Attaching to the struct members
// means the access control stays part of the type where it logically makes
// the most sense.
if (access_control == ast::AccessControl::kReadOnly) {
push_annot(spv::Op::OpMemberDecorate,
{Operand::Int(struct_id), Operand::Int(i),
Operand::Int(SpvDecorationNonWritable)});
}
ops.push_back(Operand::Int(mem_id));
}

View File

@ -26,6 +26,7 @@
#include "src/ast/literal.h"
#include "src/ast/module.h"
#include "src/ast/struct_member.h"
#include "src/ast/type/access_control_type.h"
#include "src/ast/type/storage_texture_type.h"
#include "src/ast/type_constructor_expression.h"
#include "src/scope_stack.h"
@ -238,6 +239,13 @@ class Builder {
/// @param func the function to generate for
/// @returns the ID to use for the function type. Returns 0 on failure.
uint32_t GenerateFunctionTypeIfNeeded(ast::Function* func);
/// Generates access control annotations if needed
/// @param type the type to generate for
/// @param struct_id the struct id
/// @param member_idx the member index
void GenerateMemberAccessControlIfNeeded(ast::type::Type* type,
uint32_t struct_id,
uint32_t member_idx);
/// Generates a function variable
/// @param var the variable
/// @returns true if the variable was generated
@ -409,9 +417,11 @@ class Builder {
bool GeneratePointerType(ast::type::PointerType* ptr, const Operand& result);
/// Generates a vector type declaration
/// @param struct_type the vector to generate
/// @param access_control the access controls to assign to the struct
/// @param result the result operand
/// @returns true if the vector was successfully generated
bool GenerateStructType(ast::type::StructType* struct_type,
ast::AccessControl access_control,
const Operand& result);
/// Generates a struct member
/// @param struct_id the id of the parent structure

View File

@ -25,7 +25,10 @@
#include "src/ast/scalar_constructor_expression.h"
#include "src/ast/set_decoration.h"
#include "src/ast/storage_class.h"
#include "src/ast/struct.h"
#include "src/ast/type/access_control_type.h"
#include "src/ast/type/f32_type.h"
#include "src/ast/type/struct_type.h"
#include "src/ast/type/vector_type.h"
#include "src/ast/type_constructor_expression.h"
#include "src/ast/variable.h"

View File

@ -27,9 +27,12 @@
#include "src/ast/scalar_constructor_expression.h"
#include "src/ast/set_decoration.h"
#include "src/ast/storage_class.h"
#include "src/ast/struct.h"
#include "src/ast/type/access_control_type.h"
#include "src/ast/type/bool_type.h"
#include "src/ast/type/f32_type.h"
#include "src/ast/type/i32_type.h"
#include "src/ast/type/struct_type.h"
#include "src/ast/type/u32_type.h"
#include "src/ast/type/vector_type.h"
#include "src/ast/type_constructor_expression.h"
@ -520,6 +523,168 @@ INSTANTIATE_TEST_SUITE_P(
BuiltinData{ast::Builtin::kGlobalInvocationId,
SpvBuiltInGlobalInvocationId}));
TEST_F(BuilderTest, GlobalVar_DeclReadOnly) {
// struct A {
// a : i32;
// };
// var b : [[access(read)]] A
ast::type::I32Type i32;
ast::StructMemberDecorationList decos;
ast::StructMemberList members;
members.push_back(
std::make_unique<ast::StructMember>("a", &i32, std::move(decos)));
members.push_back(
std::make_unique<ast::StructMember>("b", &i32, std::move(decos)));
ast::type::StructType A("A",
std::make_unique<ast::Struct>(std::move(members)));
ast::type::AccessControlType ac{ast::AccessControl::kReadOnly, &A};
ast::Variable var("b", ast::StorageClass::kStorageBuffer, &ac);
ast::Module mod;
Builder b(&mod);
EXPECT_TRUE(b.GenerateGlobalVariable(&var)) << b.error();
EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
OpMemberDecorate %3 1 NonWritable
)");
EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
OpMemberName %3 0 "a"
OpMemberName %3 1 "b"
OpName %1 "b"
)");
EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
%3 = OpTypeStruct %4 %4
%2 = OpTypePointer StorageBuffer %3
%1 = OpVariable %2 StorageBuffer
)");
}
TEST_F(BuilderTest, GlobalVar_TypeAliasDeclReadOnly) {
// struct A {
// a : i32;
// };
// type B = A;
// var b : [[access(read)]] B
ast::type::I32Type i32;
ast::StructMemberDecorationList decos;
ast::StructMemberList members;
members.push_back(
std::make_unique<ast::StructMember>("a", &i32, std::move(decos)));
ast::type::StructType A("A",
std::make_unique<ast::Struct>(std::move(members)));
ast::type::AliasType B("B", &A);
ast::type::AccessControlType ac{ast::AccessControl::kReadOnly, &B};
ast::Variable var("b", ast::StorageClass::kStorageBuffer, &ac);
ast::Module mod;
Builder b(&mod);
EXPECT_TRUE(b.GenerateGlobalVariable(&var)) << b.error();
EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
)");
EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
OpMemberName %3 0 "a"
OpName %1 "b"
)");
EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
%3 = OpTypeStruct %4
%2 = OpTypePointer StorageBuffer %3
%1 = OpVariable %2 StorageBuffer
)");
}
TEST_F(BuilderTest, GlobalVar_TypeAliasAssignReadOnly) {
// struct A {
// a : i32;
// };
// type B = [[access(read)]] A;
// var b : B
ast::type::I32Type i32;
ast::StructMemberDecorationList decos;
ast::StructMemberList members;
members.push_back(
std::make_unique<ast::StructMember>("a", &i32, std::move(decos)));
ast::type::StructType A("A",
std::make_unique<ast::Struct>(std::move(members)));
ast::type::AccessControlType ac{ast::AccessControl::kReadOnly, &A};
ast::type::AliasType B("B", &ac);
ast::Variable var("b", ast::StorageClass::kStorageBuffer, &B);
ast::Module mod;
Builder b(&mod);
EXPECT_TRUE(b.GenerateGlobalVariable(&var)) << b.error();
EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
)");
EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
OpMemberName %3 0 "a"
OpName %1 "b"
)");
EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
%3 = OpTypeStruct %4
%2 = OpTypePointer StorageBuffer %3
%1 = OpVariable %2 StorageBuffer
)");
}
TEST_F(BuilderTest, GlobalVar_TwoVarDeclReadOnly) {
// struct A {
// a : i32;
// };
// var b : [[access(read)]] A
// var c : [[access(read_write)]] A
ast::type::I32Type i32;
ast::StructMemberDecorationList decos;
ast::StructMemberList members;
members.push_back(
std::make_unique<ast::StructMember>("a", &i32, std::move(decos)));
ast::type::StructType A("A",
std::make_unique<ast::Struct>(std::move(members)));
ast::type::AccessControlType read{ast::AccessControl::kReadOnly, &A};
ast::type::AccessControlType rw{ast::AccessControl::kReadWrite, &A};
ast::Variable var_b("b", ast::StorageClass::kStorageBuffer, &read);
ast::Variable var_c("c", ast::StorageClass::kStorageBuffer, &rw);
ast::Module mod;
Builder b(&mod);
EXPECT_TRUE(b.GenerateGlobalVariable(&var_b)) << b.error();
EXPECT_TRUE(b.GenerateGlobalVariable(&var_c)) << b.error();
EXPECT_EQ(DumpInstructions(b.annots()), R"(OpMemberDecorate %3 0 NonWritable
)");
EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A"
OpMemberName %3 0 "a"
OpName %1 "b"
OpName %7 "A"
OpMemberName %7 0 "a"
OpName %5 "c"
)");
EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 1
%3 = OpTypeStruct %4
%2 = OpTypePointer StorageBuffer %3
%1 = OpVariable %2 StorageBuffer
%7 = OpTypeStruct %4
%6 = OpTypePointer StorageBuffer %7
%5 = OpVariable %6 StorageBuffer
)");
}
} // namespace
} // namespace spirv
} // namespace writer

View File

@ -20,6 +20,7 @@
#include "src/ast/struct.h"
#include "src/ast/struct_member.h"
#include "src/ast/struct_member_offset_decoration.h"
#include "src/ast/type/access_control_type.h"
#include "src/ast/type/alias_type.h"
#include "src/ast/type/array_type.h"
#include "src/ast/type/bool_type.h"