spirv-reader: ignore PointSize builtin declared at module-scope

Fixed: tint:434
Change-Id: Ia29d0f7227e838fc5f9dd4ca521b5fd6b9a88637
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/36761
Auto-Submit: David Neto <dneto@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index b72b55a..9c78615 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -976,6 +976,9 @@
     return false;
   }
 
+  if (!RegisterIgnoredBuiltInVariables()) {
+    return false;
+  }
   if (!RegisterLocallyDefinedValues()) {
     return false;
   }
@@ -2880,6 +2883,9 @@
 }
 
 bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
+  if (failed()) {
+    return false;
+  }
   const auto result_id = inst.result_id();
   const auto type_id = inst.type_id();
 
@@ -2994,14 +3000,9 @@
       // a new named constant definition.
       auto value_id = inst.GetSingleWordInOperand(0);
       const auto skip = GetSkipReason(value_id);
-      switch (skip) {
-        case SkipReason::kDontSkip:
-          break;
-        case SkipReason::kOpaqueObject:
-        case SkipReason::kPointSizeBuiltinPointer:
-        case SkipReason::kPointSizeBuiltinValue:
-          GetDefInfo(inst.result_id())->skip = skip;
-          return true;
+      if (skip != SkipReason::kDontSkip) {
+        GetDefInfo(inst.result_id())->skip = skip;
+        return true;
       }
       auto expr = MakeExpression(value_id);
       expr.type = RemapStorageClass(expr.type, result_id);
@@ -3235,6 +3236,14 @@
     return {};
   }
 
+  const auto base_id = inst.GetSingleWordInOperand(0);
+  const auto base_skip = GetSkipReason(base_id);
+  if (base_skip != SkipReason::kDontSkip) {
+    // This can occur for AccessChain with no indices.
+    GetDefInfo(inst.result_id())->skip = base_skip;
+    return {};
+  }
+
   // A SPIR-V access chain is a single instruction with multiple indices
   // walking down into composites.  The Tint AST represents this as
   // ever-deeper nested indexing expressions. Start off with an expression
@@ -3242,7 +3251,6 @@
   TypedExpression current_expr(MakeOperand(inst, 0));
   const auto constants = constant_mgr_->GetOperandConstants(&inst);
 
-  const auto base_id = inst.GetSingleWordInOperand(0);
   auto ptr_ty_id = def_use_mgr_->GetDef(base_id)->type_id();
   uint32_t first_index = 1;
   const auto num_in_operands = inst.NumInOperands();
@@ -3592,9 +3600,29 @@
           create<ast::TypeConstructorExpression>(source, result_type, values)};
 }
 
+bool FunctionEmitter::RegisterIgnoredBuiltInVariables() {
+  size_t index = def_info_.size();
+  for (auto& ignored_var : parser_impl_.ignored_builtins()) {
+    const auto id = ignored_var.first;
+    const auto builtin = ignored_var.second;
+    def_info_[id] =
+        std::make_unique<DefInfo>(*(def_use_mgr_->GetDef(id)), 0, index);
+    ++index;
+    auto& def = def_info_[id];
+    switch (builtin) {
+      case SpvBuiltInPointSize:
+        def->skip = SkipReason::kPointSizeBuiltinPointer;
+        break;
+      default:
+        return Fail() << "unrecognized ignored builtin: " << int(builtin);
+    }
+  }
+  return true;
+}
+
 bool FunctionEmitter::RegisterLocallyDefinedValues() {
   // Create a DefInfo for each value definition in this function.
-  size_t index = 0;
+  size_t index = def_info_.size();
   for (auto block_id : block_order_) {
     const auto* block_info = GetBlockInfo(block_id);
     const auto block_pos = block_info->pos;
@@ -3604,7 +3632,7 @@
         continue;
       }
       def_info_[result_id] = std::make_unique<DefInfo>(inst, block_pos, index);
-      index++;
+      ++index;
       auto& info = def_info_[result_id];
 
       // Determine storage class for pointer values. Do this in order because
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index c27cde5..9002ea8 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -223,10 +223,13 @@
   kPointSizeBuiltinValue,
 };
 
-/// Bookkeeping info for a SPIR-V ID defined in the function.
-/// This will be valid for result IDs for:
-/// - instructions that are not OpLabel, and not OpFunctionParameter
-/// - are defined in a basic block visited in the block-order for the function.
+/// Bookkeeping info for a SPIR-V ID defined in the function, or some
+/// module-scope variables. This will be valid for result IDs that are:
+/// - defined in the function and:
+///    - instructions that are not OpLabel, and not OpFunctionParameter
+///    - are defined in a basic block visited in the block-order for the
+///    function.
+/// - certain module-scope builtin variables.
 struct DefInfo {
   /// Constructor.
   /// @param def_inst the SPIR-V instruction defining the ID
@@ -240,8 +243,11 @@
 
   /// The SPIR-V instruction that defines the ID.
   const spvtools::opt::Instruction& inst;
-  /// The position of the block that defines this ID, in the function block
-  /// order.  See method `FunctionEmitter::ComputeBlockOrderAndPositions`
+  /// The position of the first block in which this ID is visible, in function
+  /// block order.  For IDs defined outside of the function, it is 0.
+  /// For IDs defined in the function, it is the position of the block
+  /// containing the definition of the ID.
+  /// See method `FunctionEmitter::ComputeBlockOrderAndPositions`
   const uint32_t block_pos = 0;
 
   /// An index for uniquely and deterministically ordering all DefInfo records
@@ -290,8 +296,8 @@
   /// This is kNone for non-pointers.
   ast::StorageClass storage_class = ast::StorageClass::kNone;
 
-  /// The reason, if any, that this value should not be generated.
-  /// Normally all values are generated.  This field can be updated while
+  /// The reason, if any, that this value should be ignored.
+  /// Normally no values are ignored.  This field can be updated while
   /// generating code because sometimes we only discover necessary facts
   /// in the middle of generating code.
   SkipReason skip = SkipReason::kDontSkip;
@@ -456,8 +462,14 @@
   /// @returns false if bad nesting has been detected.
   bool FindIfSelectionInternalHeaders();
 
+  /// Creates a DefInfo record for each module-scope builtin variable
+  /// that should be ignored.
+  /// Populates the `def_info_` mapping for such IDs.
+  /// @returns false on failure
+  bool RegisterIgnoredBuiltInVariables();
+
   /// Creates a DefInfo record for each locally defined SPIR-V ID.
-  /// Populates the `def_info_` mapping with basic results.
+  /// Populates the `def_info_` mapping with basic results for such IDs.
   /// @returns false on failure
   bool RegisterLocallyDefinedValues();
 
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 3cb8646..ee91183 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1092,8 +1092,10 @@
       auto* ast_var =
           MakeVariable(inst.result_id(), ast::StorageClass::kNone, ast_type,
                        true, ast_expr, std::move(spec_id_decos));
-      ast_module_.AddGlobalVariable(ast_var);
-      scalar_spec_constants_.insert(inst.result_id());
+      if (ast_var) {
+        ast_module_.AddGlobalVariable(ast_var);
+        scalar_spec_constants_.insert(inst.result_id());
+      }
     }
   }
   return success_;
@@ -1209,7 +1211,9 @@
         MakeVariable(var.result_id(), ast_storage_class, ast_store_type, false,
                      ast_constructor, ast::VariableDecorationList{});
     // TODO(dneto): initializers (a.k.a. constructor expression)
-    ast_module_.AddGlobalVariable(ast_var);
+    if (ast_var) {
+      ast_module_.AddGlobalVariable(ast_var);
+    }
   }
 
   // Emit gl_Position instead of gl_PerVertex
@@ -1261,8 +1265,15 @@
                << ": has no operand";
         return nullptr;
       }
-      auto ast_builtin =
-          enum_converter_.ToBuiltin(static_cast<SpvBuiltIn>(deco[1]));
+      const auto spv_builtin = static_cast<SpvBuiltIn>(deco[1]);
+      switch (spv_builtin) {
+        case SpvBuiltInPointSize:
+          ignored_builtins_[id] = spv_builtin;
+          return nullptr;
+        default:
+          break;
+      }
+      auto ast_builtin = enum_converter_.ToBuiltin(spv_builtin);
       if (ast_builtin == ast::Builtin::kNone) {
         return nullptr;
       }
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index b34177c..579d681 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -287,14 +287,15 @@
   bool EmitFunction(const spvtools::opt::Function& f);
 
   /// Creates an AST Variable node for a SPIR-V ID, including any attached
-  /// decorations.
+  /// decorations, unless it's an ignorable builtin variable.
   /// @param id the SPIR-V result ID
   /// @param sc the storage class, which cannot be ast::StorageClass::kNone
   /// @param type the type
   /// @param is_const if true, the variable is const
   /// @param constructor the variable constructor
   /// @param decorations the variable decorations
-  /// @returns a new Variable node, or null in the error case
+  /// @returns a new Variable node, or null in the ignorable variable case and
+  /// in the error case
   ast::Variable* MakeVariable(uint32_t id,
                               ast::StorageClass sc,
                               ast::type::Type* type,
@@ -473,6 +474,9 @@
   /// @returns the instruction, or nullptr on error
   const spvtools::opt::Instruction* GetInstructionForTest(uint32_t id) const;
 
+  using BuiltInsMap = std::unordered_map<uint32_t, SpvBuiltIn>;
+  const BuiltInsMap& ignored_builtins() const { return ignored_builtins_; }
+
  private:
   /// Converts a specific SPIR-V type to a Tint type. Integer case
   ast::type::Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);
@@ -626,6 +630,10 @@
   // The inferred pointer type for the given handle variable.
   std::unordered_map<const spvtools::opt::Instruction*, ast::type::Pointer*>
       handle_type_;
+
+  /// Maps the SPIR-V ID of a module-scope builtin variable that should be
+  /// ignored, to its builtin kind.
+  BuiltInsMap ignored_builtins_;
 };
 
 }  // namespace spirv
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index 1c568eb..b39f219 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -629,6 +629,160 @@
 )") << module_str;
 }
 
+std::string LoosePointSizePreamble() {
+  return R"(
+    OpCapability Shader
+    OpCapability Linkage ; so we don't have to declare an entry point
+    OpMemoryModel Logical Simple
+
+    OpDecorate %1 BuiltIn PointSize
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %uint_0 = OpConstant %uint 0
+    %uint_1 = OpConstant %uint 1
+    %11 = OpTypePointer Output %float
+    %1 = OpVariable %11 Output
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_Loose_Write1_IsErased) {
+  const std::string assembly = LoosePointSizePreamble() + R"(
+  %ptr = OpTypePointer Output %float
+  %one = OpConstant %float 1.0
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpStore %1 %one
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str =
+      Demangler().Demangle(p->get_module(), p->get_module().to_str());
+  EXPECT_EQ(module_str, R"(Module{
+  Function x_500 -> __void
+  ()
+  {
+    Return{}
+  }
+}
+)") << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_Loose_WriteNon1_IsError) {
+  const std::string assembly = LoosePointSizePreamble() + R"(
+  %ptr = OpTypePointer Output %float
+  %999 = OpConstant %float 2.0
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  OpStore %1 %999
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->error(),
+              HasSubstr("cannot store a value other than constant 1.0 to "
+                        "PointSize builtin: OpStore %1 %999"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_Loose_ReadReplaced) {
+  const std::string assembly = LoosePointSizePreamble() + R"(
+  %ptr = OpTypePointer Private %float
+  %900 = OpVariable %ptr Private
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %99 = OpLoad %float %1
+  OpStore %900 %99
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str =
+      Demangler().Demangle(p->get_module(), p->get_module().to_str());
+  EXPECT_EQ(module_str, R"(Module{
+  Variable{
+    x_900
+    private
+    __f32
+  }
+  Function x_500 -> __void
+  ()
+  {
+    Assignment{
+      Identifier[not set]{x_900}
+      ScalarConstructor[not set]{1.000000}
+    }
+    Return{}
+  }
+}
+)") << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPointSize_Loose_WriteViaCopyObjectPriorAccess_Erased) {
+  const std::string assembly = LoosePointSizePreamble() + R"(
+  %one = OpConstant %float 1.0
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %20 = OpCopyObject %11 %1
+  %100 = OpAccessChain %11 %20
+  OpStore %100 %one
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str =
+      Demangler().Demangle(p->get_module(), p->get_module().to_str());
+  EXPECT_EQ(module_str, R"(Module{
+  Function x_500 -> __void
+  ()
+  {
+    Return{}
+  }
+}
+)") << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest,
+       BuiltinPointSize_Loose_WriteViaCopyObjectPostAccessChainErased) {
+  const std::string assembly = LoosePointSizePreamble() + R"(
+  %one = OpConstant %float 1.0
+
+  %500 = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %100 = OpAccessChain %11 %1
+  %101 = OpCopyObject %11 %100
+  OpStore %101 %one
+  OpReturn
+  OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
+  EXPECT_TRUE(p->error().empty()) << p->error();
+  const auto module_str =
+      Demangler().Demangle(p->get_module(), p->get_module().to_str());
+  EXPECT_EQ(module_str, R"(Module{
+  Function x_500 -> __void
+  ()
+  {
+    Return{}
+  }
+}
+)") << module_str;
+}
+
 TEST_F(SpvModuleScopeVarParserTest, BuiltinClipDistance_NotSupported) {
   const std::string assembly = PerVertexPreamble() + R"(
   %ptr_float = OpTypePointer Output %float