[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>
diff --git a/src/ast/type/access_control_type.h b/src/ast/type/access_control_type.h
index 9bd04c5..cf802b6 100644
--- a/src/ast/type/access_control_type.h
+++ b/src/ast/type/access_control_type.h
@@ -44,6 +44,8 @@
   /// @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_; }
 
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 6f4184c..1d7ad02 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -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 @@
   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 @@
     //    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 @@
     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 @@
   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 @@
       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::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 @@
       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));
   }
 
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index 29b5711..d0bc9b2 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -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 @@
   /// @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 @@
   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
diff --git a/src/writer/spirv/builder_function_variable_test.cc b/src/writer/spirv/builder_function_variable_test.cc
index e462ce1..0f39a2e 100644
--- a/src/writer/spirv/builder_function_variable_test.cc
+++ b/src/writer/spirv/builder_function_variable_test.cc
@@ -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"
diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc
index 14106b9..62e9b16 100644
--- a/src/writer/spirv/builder_global_variable_test.cc
+++ b/src/writer/spirv/builder_global_variable_test.cc
@@ -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 @@
         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
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index 92d6719..fe8710e 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -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"