spirv-fuzz: Add support for BuiltIn decoration (#3736)

Fixes #3676.
diff --git a/source/fuzz/fuzzer_pass_add_composite_types.cpp b/source/fuzz/fuzzer_pass_add_composite_types.cpp
index 653d784..c4d8d1c 100644
--- a/source/fuzz/fuzzer_pass_add_composite_types.cpp
+++ b/source/fuzz/fuzzer_pass_add_composite_types.cpp
@@ -120,10 +120,15 @@
       case SpvOpTypeFloat:
       case SpvOpTypeInt:
       case SpvOpTypeMatrix:
-      case SpvOpTypeStruct:
       case SpvOpTypeVector:
         candidates.push_back(inst.result_id());
         break;
+      case SpvOpTypeStruct: {
+        if (!fuzzerutil::MembersHaveBuiltInDecoration(GetIRContext(),
+                                                      inst.result_id())) {
+          candidates.push_back(inst.result_id());
+        }
+      } break;
       default:
         break;
     }
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 3883cbe..aa45d66 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -1270,6 +1270,15 @@
     const auto* type = ir_context->get_type_mgr()->GetType(type_id);
     (void)type;  // Make compiler happy in release mode.
     assert(type && !type->AsFunction() && "Component's type id is invalid");
+
+    if (type->AsStruct()) {
+      // From the spec for the BuiltIn decoration:
+      // - When applied to a structure-type member, that structure type cannot
+      //   be contained as a member of another structure type.
+      assert(!MembersHaveBuiltInDecoration(ir_context, type_id) &&
+             "A member struct has BuiltIn members");
+    }
+
     operands.push_back({SPV_OPERAND_TYPE_ID, {type_id}});
   }
 
@@ -1452,6 +1461,31 @@
   return true;
 }
 
+bool MembersHaveBuiltInDecoration(opt::IRContext* ir_context,
+                                  uint32_t struct_type_id) {
+  const auto* type_inst = ir_context->get_def_use_mgr()->GetDef(struct_type_id);
+  assert(type_inst && type_inst->opcode() == SpvOpTypeStruct &&
+         "|struct_type_id| is not a result id of an OpTypeStruct");
+
+  uint32_t builtin_count = 0;
+  ir_context->get_def_use_mgr()->ForEachUser(
+      type_inst,
+      [struct_type_id, &builtin_count](const opt::Instruction* user) {
+        if (user->opcode() == SpvOpMemberDecorate &&
+            user->GetSingleWordInOperand(0) == struct_type_id &&
+            static_cast<SpvDecoration>(user->GetSingleWordInOperand(2)) ==
+                SpvDecorationBuiltIn) {
+          ++builtin_count;
+        }
+      });
+
+  assert((builtin_count == 0 || builtin_count == type_inst->NumInOperands()) &&
+         "The module is invalid: either none or all of the members of "
+         "|struct_type_id| may be builtin");
+
+  return builtin_count != 0;
+}
+
 }  // namespace fuzzerutil
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index c9adfd7..865c1a0 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -468,7 +468,8 @@
 
 // Creates a new OpTypeStruct instruction in the module. Updates module's id
 // bound to accommodate for |result_id|. |component_type_ids| may not contain
-// a result id of an OpTypeFunction.
+// a result id of an OpTypeFunction. if |component_type_ids| contains a result
+// of an OpTypeStruct instruction, that struct may not have BuiltIn members.
 void AddStructType(opt::IRContext* ir_context, uint32_t result_id,
                    const std::vector<uint32_t>& component_type_ids);
 
@@ -516,6 +517,13 @@
                         opt::Instruction* use_instruction,
                         uint32_t use_in_operand_index);
 
+// Requires that |struct_type_id| is the id of a struct type, and (as per the
+// SPIR-V spec) that either all or none of the members of |struct_type_id| have
+// the BuiltIn decoration. Returns true if and only if all members have the
+// BuiltIn decoration.
+bool MembersHaveBuiltInDecoration(opt::IRContext* ir_context,
+                                  uint32_t struct_type_id);
+
 }  // namespace fuzzerutil
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_struct.cpp b/source/fuzz/transformation_add_type_struct.cpp
index a7345a1..e8adefd 100644
--- a/source/fuzz/transformation_add_type_struct.cpp
+++ b/source/fuzz/transformation_add_type_struct.cpp
@@ -44,6 +44,14 @@
       // function type; both are illegal.
       return false;
     }
+
+    // From the spec for the BuiltIn decoration:
+    // - When applied to a structure-type member, that structure type cannot
+    //   be contained as a member of another structure type.
+    if (type->AsStruct() &&
+        fuzzerutil::MembersHaveBuiltInDecoration(ir_context, member_type)) {
+      return false;
+    }
   }
   return true;
 }
diff --git a/source/fuzz/transformation_add_type_struct.h b/source/fuzz/transformation_add_type_struct.h
index 86a532d..c9d0cfd 100644
--- a/source/fuzz/transformation_add_type_struct.h
+++ b/source/fuzz/transformation_add_type_struct.h
@@ -35,6 +35,9 @@
 
   // - |message_.fresh_id| must be a fresh id
   // - |message_.member_type_id| must be a sequence of non-function type ids
+  // - |message_.member_type_id| may not contain a result id of an OpTypeStruct
+  //   instruction with BuiltIn members (i.e. members of the struct are
+  //   decorated via OpMemberDecorate with BuiltIn decoration).
   bool IsApplicable(
       opt::IRContext* ir_context,
       const TransformationContext& transformation_context) const override;
diff --git a/test/fuzz/transformation_add_type_struct_test.cpp b/test/fuzz/transformation_add_type_struct_test.cpp
index 06f78cd..61e87a5 100644
--- a/test/fuzz/transformation_add_type_struct_test.cpp
+++ b/test/fuzz/transformation_add_type_struct_test.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_type_struct.h"
+
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -109,6 +110,49 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationAddTypeStructTest, HandlesBuiltInMembers) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %2 "main"
+               OpMemberDecorate %4 0 BuiltIn Position
+               OpMemberDecorate %4 1 BuiltIn PointSize
+               OpMemberDecorate %4 2 BuiltIn ClipDistance
+          %6 = OpTypeFloat 32
+          %5 = OpTypeVector %6 4
+          %9 = OpTypeInt 32 1
+          %8 = OpConstant %9 1
+          %7 = OpTypeArray %6 %8
+          %4 = OpTypeStruct %5 %6 %7
+         %27 = OpTypeVoid
+         %28 = OpTypeFunction %27
+          %2 = OpFunction %27 None %28
+         %29 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // From the spec for the BuiltIn decoration:
+  // - When applied to a structure-type member, that structure type cannot
+  //   be contained as a member of another structure type.
+  //
+  // OpTypeStruct with id %4 has BuiltIn members.
+  ASSERT_FALSE(TransformationAddTypeStruct(50, {6, 5, 4, 6, 7})
+                   .IsApplicable(context.get(), transformation_context));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools