spirv-diff: Basic support for OpTypeForwardPointer (#4761)

Currently, the diff tool matches types bottom up, so on every
instruction it expects to know if its operands are already matched or
not.  With cyclical references, it cannot know that.  Type matching
would need significant rework to be able to support such a use case; for
example, it may need to maintain a set of plausable matches between type
pointers that are forward-referenced, and potentially back track when
later the types turn out to be incompatible.

In this change, OpTypeForwardPointer is supported in the more common and
trivial case.  Firstly, forwarded type pointers are only matched if they
have they have the same storage class and point to the same type opcode:

- In the presence of debug info, matching is done only if the names are
  unique in both src and dst.
- In the absence of debug info, matching is done only if there is only
  one possible matching.

Fixes: #4754
diff --git a/source/diff/diff.cpp b/source/diff/diff.cpp
index 4e6fadd..4ddbbd3 100644
--- a/source/diff/diff.cpp
+++ b/source/diff/diff.cpp
@@ -44,6 +44,8 @@
 // different implementations produce identical results.
 using IdGroupMapByName = std::map<std::string, IdGroup>;
 using IdGroupMapByTypeId = std::map<uint32_t, IdGroup>;
+using IdGroupMapByOp = std::map<SpvOp, IdGroup>;
+using IdGroupMapByStorageClass = std::map<SpvStorageClass, IdGroup>;
 
 // A set of potential id mappings that haven't been resolved yet.  Any id in src
 // may map in any id in dst.  Note that ids are added in the same order as they
@@ -177,7 +179,8 @@
   IdInstructions(const opt::Module* module)
       : inst_map_(module->IdBound(), nullptr),
         name_map_(module->IdBound()),
-        decoration_map_(module->IdBound()) {
+        decoration_map_(module->IdBound()),
+        forward_pointer_map_(module->IdBound()) {
     // Map ids from all sections to instructions that define them.
     MapIdsToInstruction(module->ext_inst_imports());
     MapIdsToInstruction(module->types_values());
@@ -195,6 +198,7 @@
     // between src and dst modules.
     MapIdsToInfos(module->debugs2());
     MapIdsToInfos(module->annotations());
+    MapIdsToInfos(module->types_values());
   }
 
   void MapIdToInstruction(uint32_t id, const opt::Instruction* inst);
@@ -207,6 +211,7 @@
   IdToInstructionMap inst_map_;
   IdToInfoMap name_map_;
   IdToInfoMap decoration_map_;
+  IdToInstructionMap forward_pointer_map_;
 };
 
 class Differ {
@@ -234,6 +239,7 @@
   void MatchMemoryModel();
   void MatchEntryPointIds();
   void MatchExecutionModes();
+  void MatchTypeForwardPointers();
   void MatchTypeIds();
   void MatchConstants();
   void MatchVariableIds();
@@ -295,6 +301,10 @@
   // Get various properties from an id.  These Helper functions are passed to
   // `GroupIds` and `GroupIdsAndMatch` below (as the `get_group` argument).
   uint32_t GroupIdsHelperGetTypeId(const IdInstructions& id_to, uint32_t id);
+  SpvStorageClass GroupIdsHelperGetTypePointerStorageClass(
+      const IdInstructions& id_to, uint32_t id);
+  SpvOp GroupIdsHelperGetTypePointerTypeOp(const IdInstructions& id_to,
+                                           uint32_t id);
 
   // Given a list of ids, groups them based on some value.  The `get_group`
   // function extracts a piece of information corresponding to each id, and the
@@ -361,6 +371,10 @@
   bool MatchPerVertexVariable(const opt::Instruction* src_inst,
                               const opt::Instruction* dst_inst);
 
+  // Helper functions for matching OpTypeForwardPointer
+  void MatchTypeForwardPointersByName(const IdGroup& src, const IdGroup& dst);
+  void MatchTypeForwardPointersByTypeOp(const IdGroup& src, const IdGroup& dst);
+
   // Helper functions for function matching.
   using FunctionMap = std::map<uint32_t, const opt::Function*>;
 
@@ -402,6 +416,7 @@
   uint32_t GetConstantUint(const IdInstructions& id_to, uint32_t constant_id);
   SpvExecutionModel GetExecutionModel(const opt::Module* module,
                                       uint32_t entry_point_id);
+  bool HasName(const IdInstructions& id_to, uint32_t id);
   // Get the OpName associated with an id
   std::string GetName(const IdInstructions& id_to, uint32_t id, bool* has_name);
   // Get the OpName associated with an id, with argument types stripped for
@@ -412,6 +427,8 @@
                         SpvStorageClass* storage_class);
   bool GetDecorationValue(const IdInstructions& id_to, uint32_t id,
                           SpvDecoration decoration, uint32_t* decoration_value);
+  const opt::Instruction* GetForwardPointerInst(const IdInstructions& id_to,
+                                                uint32_t id);
   bool IsIntType(const IdInstructions& id_to, uint32_t type_id);
   bool IsFloatType(const IdInstructions& id_to, uint32_t type_id);
   bool IsConstantUint(const IdInstructions& id_to, uint32_t id);
@@ -556,6 +573,14 @@
       case SpvOpMemberDecorate:
         info_map = &decoration_map_;
         break;
+      case SpvOpTypeForwardPointer: {
+        uint32_t id = inst.GetSingleWordOperand(0);
+        assert(id != 0);
+
+        assert(id < forward_pointer_map_.size());
+        forward_pointer_map_[id] = &inst;
+        continue;
+      }
       default:
         // Currently unsupported instruction, don't attempt to use it for
         // matching.
@@ -793,6 +818,25 @@
   return GetInst(id_to, id)->type_id();
 }
 
+SpvStorageClass Differ::GroupIdsHelperGetTypePointerStorageClass(
+    const IdInstructions& id_to, uint32_t id) {
+  const opt::Instruction* inst = GetInst(id_to, id);
+  assert(inst && inst->opcode() == SpvOpTypePointer);
+  return SpvStorageClass(inst->GetSingleWordInOperand(0));
+}
+
+SpvOp Differ::GroupIdsHelperGetTypePointerTypeOp(const IdInstructions& id_to,
+                                                 uint32_t id) {
+  const opt::Instruction* inst = GetInst(id_to, id);
+  assert(inst && inst->opcode() == SpvOpTypePointer);
+
+  const uint32_t type_id = inst->GetSingleWordInOperand(1);
+  const opt::Instruction* type_inst = GetInst(id_to, type_id);
+  assert(type_inst);
+
+  return type_inst->opcode();
+}
+
 template <typename T>
 void Differ::GroupIds(const IdGroup& ids, bool is_src,
                       std::map<T, IdGroup>* groups,
@@ -1356,6 +1400,52 @@
   return src_storage_class == dst_storage_class;
 }
 
+void Differ::MatchTypeForwardPointersByName(const IdGroup& src,
+                                            const IdGroup& dst) {
+  // Given two sets of compatible groups of OpTypeForwardPointer instructions,
+  // attempts to match them by name.
+
+  // Group them by debug info and loop over them.
+  GroupIdsAndMatch<std::string>(
+      src, dst, "", &Differ::GetSanitizedName,
+      [this](const IdGroup& src_group, const IdGroup& dst_group) {
+
+        // Match only if there's a unique forward declaration with this debug
+        // name.
+        if (src_group.size() == 1 && dst_group.size() == 1) {
+          id_map_.MapIds(src_group[0], dst_group[0]);
+        }
+      });
+}
+
+void Differ::MatchTypeForwardPointersByTypeOp(const IdGroup& src,
+                                              const IdGroup& dst) {
+  // Given two sets of compatible groups of OpTypeForwardPointer instructions,
+  // attempts to match them by type op.  Must be called after
+  // MatchTypeForwardPointersByName to match as many as possible by debug info.
+
+  // Remove ids that are matched with debug info in
+  // MatchTypeForwardPointersByName.
+  IdGroup src_unmatched_ids;
+  IdGroup dst_unmatched_ids;
+
+  std::copy_if(src.begin(), src.end(), std::back_inserter(src_unmatched_ids),
+               [this](uint32_t id) { return !id_map_.IsSrcMapped(id); });
+  std::copy_if(dst.begin(), dst.end(), std::back_inserter(dst_unmatched_ids),
+               [this](uint32_t id) { return !id_map_.IsDstMapped(id); });
+
+  // Match only if there's a unique forward declaration with this
+  // storage class and type opcode.  If both have debug info, they
+  // must not have been matchable.
+  if (src_unmatched_ids.size() == 1 && dst_unmatched_ids.size() == 1) {
+    uint32_t src_id = src_unmatched_ids[0];
+    uint32_t dst_id = dst_unmatched_ids[0];
+    if (!HasName(src_id_to_, src_id) || !HasName(dst_id_to_, dst_id)) {
+      id_map_.MapIds(src_id, dst_id);
+    }
+  }
+}
+
 InstructionList Differ::GetFunctionBody(opt::IRContext* context,
                                         opt::Function& function) {
   // Canonicalize the blocks of the function to produce better diff, for example
@@ -1649,6 +1739,19 @@
   return SpvExecutionModel(0xFFF);
 }
 
+bool Differ::HasName(const IdInstructions& id_to, uint32_t id) {
+  assert(id != 0);
+  assert(id < id_to.name_map_.size());
+
+  for (const opt::Instruction* inst : id_to.name_map_[id]) {
+    if (inst->opcode() == SpvOpName) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 std::string Differ::GetName(const IdInstructions& id_to, uint32_t id,
                             bool* has_name) {
   assert(id != 0);
@@ -1710,6 +1813,13 @@
   return false;
 }
 
+const opt::Instruction* Differ::GetForwardPointerInst(
+    const IdInstructions& id_to, uint32_t id) {
+  assert(id != 0);
+  assert(id < id_to.forward_pointer_map_.size());
+  return id_to.forward_pointer_map_[id];
+}
+
 bool Differ::IsIntType(const IdInstructions& id_to, uint32_t type_id) {
   return IsOp(id_to, type_id, SpvOpTypeInt);
 }
@@ -1968,6 +2078,80 @@
   MatchPreambleInstructions(src_->execution_modes(), dst_->execution_modes());
 }
 
+void Differ::MatchTypeForwardPointers() {
+  // Bunch all of type forward pointers as potential matches.
+  PotentialIdMap potential_id_map;
+  auto get_pointer_type_id = [](const opt::Instruction& inst) {
+    return inst.GetSingleWordOperand(0);
+  };
+  auto accept_type_forward_pointer_ops = [](const opt::Instruction& inst) {
+    return inst.opcode() == SpvOpTypeForwardPointer;
+  };
+
+  PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
+                   accept_type_forward_pointer_ops, get_pointer_type_id);
+  PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
+                   accept_type_forward_pointer_ops, get_pointer_type_id);
+
+  // Matching types with cyclical references (i.e. in the style of linked lists)
+  // can get very complex.  Currently, the diff tool matches types bottom up, so
+  // on every instruction it expects to know if its operands are already matched
+  // or not.  With cyclical references, it cannot know that.  Type matching may
+  // need significant modifications to be able to support this use case.
+  //
+  // Currently, forwarded types are only matched by storage class and debug
+  // info, with minimal matching of the type being forwarded:
+  //
+  // - Group by class
+  //   - Group by OpType being pointed to
+  //     - Group by debug info
+  //       - If same name and unique, match
+  //     - If leftover is unique, match
+
+  // Group forwarded pointers by storage class first and loop over them.
+  GroupIdsAndMatch<SpvStorageClass>(
+      potential_id_map.src_ids, potential_id_map.dst_ids, SpvStorageClassMax,
+      &Differ::GroupIdsHelperGetTypePointerStorageClass,
+      [this](const IdGroup& src_group_by_storage_class,
+             const IdGroup& dst_group_by_storage_class) {
+
+        // Group them further by the type they are pointing to and loop over
+        // them.
+        GroupIdsAndMatch<SpvOp>(
+            src_group_by_storage_class, dst_group_by_storage_class, SpvOpMax,
+            &Differ::GroupIdsHelperGetTypePointerTypeOp,
+            [this](const IdGroup& src_group_by_type_op,
+                   const IdGroup& dst_group_by_type_op) {
+
+              // Group them even further by debug info, if possible and match by
+              // debug name.
+              MatchTypeForwardPointersByName(src_group_by_type_op,
+                                             dst_group_by_type_op);
+
+              // Match the leftovers only if they lack debug info and there is
+              // only one instance of them.
+              MatchTypeForwardPointersByTypeOp(src_group_by_type_op,
+                                               dst_group_by_type_op);
+            });
+      });
+
+  // Match the instructions that forward declare the same type themselves
+  for (uint32_t src_id : potential_id_map.src_ids) {
+    uint32_t dst_id = id_map_.MappedDstId(src_id);
+    if (dst_id == 0) continue;
+
+    const opt::Instruction* src_forward_inst =
+        GetForwardPointerInst(src_id_to_, src_id);
+    const opt::Instruction* dst_forward_inst =
+        GetForwardPointerInst(dst_id_to_, dst_id);
+
+    assert(src_forward_inst);
+    assert(dst_forward_inst);
+
+    id_map_.MapInsts(src_forward_inst, dst_forward_inst);
+  }
+}
+
 void Differ::MatchTypeIds() {
   // Bunch all of type ids as potential matches.
   PotentialIdMap potential_id_map;
@@ -2648,6 +2832,7 @@
   differ.MatchMemoryModel();
   differ.MatchEntryPointIds();
   differ.MatchExecutionModes();
+  differ.MatchTypeForwardPointers();
   differ.MatchTypeIds();
   differ.MatchConstants();
   differ.MatchVariableIds();
diff --git a/test/diff/diff_files/OpTypeForwardPointer_basic_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_basic_autogen.cpp
new file mode 100644
index 0000000..af252b1
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_basic_autogen.cpp
@@ -0,0 +1,136 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Basic test that OpTypeForwardPointer is matched
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpName %structptr2 "structptr2"
+               OpTypeForwardPointer %structptr UniformConstant
+               OpTypeForwardPointer %structptr2 Function
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+  %structptr2 = OpTypePointer Function %structt1
+)";
+
+TEST(DiffTest, OptypeforwardpointerBasic) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 7
++; Bound: 8
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpName %1 "structptr"
++OpName %7 "structptr2"
+ OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %7 Function
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2
+ %4 = OpTypeStruct %2 %1
+ %5 = OpTypeStruct %2 %2 %1
+ %6 = OpTypeStruct %2 %2 %2 %1
+ %1 = OpTypePointer UniformConstant %3
++%7 = OpTypePointer Function %3
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerBasicNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+               OpTypeForwardPointer %structptr2 Function
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+  %structptr2 = OpTypePointer Function %structt1
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 7
++; Bound: 8
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %7 Function
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2
+ %4 = OpTypeStruct %2 %1
+ %5 = OpTypeStruct %2 %2 %1
+ %6 = OpTypeStruct %2 %2 %2 %1
+ %1 = OpTypePointer UniformConstant %3
++%7 = OpTypePointer Function %3
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_basic_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_basic_dst.spvasm
new file mode 100644
index 0000000..0c6e0cb
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_basic_dst.spvasm
@@ -0,0 +1,15 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpName %structptr2 "structptr2"
+               OpTypeForwardPointer %structptr UniformConstant
+               OpTypeForwardPointer %structptr2 Function
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+  %structptr2 = OpTypePointer Function %structt1
diff --git a/test/diff/diff_files/OpTypeForwardPointer_basic_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_basic_src.spvasm
new file mode 100644
index 0000000..408ec98
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_basic_src.spvasm
@@ -0,0 +1,13 @@
+;; Basic test that OpTypeForwardPointer is matched
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
diff --git a/test/diff/diff_files/OpTypeForwardPointer_intertwined_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_intertwined_autogen.cpp
new file mode 100644
index 0000000..f2c9008
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_intertwined_autogen.cpp
@@ -0,0 +1,138 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that two forwarded types whose declarations are intertwined match
+// correctly
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpName %Bptr "Bptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+               OpTypeForwardPointer %Bptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint %Bptr
+          %B = OpTypeStruct %uint %Aptr %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpName %Bptr "Bptr"
+               OpTypeForwardPointer %Bptr UniformConstant
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %B = OpTypeStruct %uint %Aptr %Bptr %uint
+          %A = OpTypeStruct %Aptr %uint %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B
+)";
+
+TEST(DiffTest, OptypeforwardpointerIntertwined) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 6
++; Bound: 7
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpName %1 "Aptr"
+ OpName %2 "Bptr"
+ OpTypeForwardPointer %1 UniformConstant
+ OpTypeForwardPointer %2 UniformConstant
+ %3 = OpTypeInt 32 0
++%6 = OpTypeStruct %3 %1 %2 %3
+ %4 = OpTypeStruct %1 %3 %2
+-%5 = OpTypeStruct %3 %1 %2
+ %1 = OpTypePointer UniformConstant %4
+-%2 = OpTypePointer UniformConstant %5
++%2 = OpTypePointer UniformConstant %6
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerIntertwinedNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+               OpTypeForwardPointer %Bptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint %Bptr
+          %B = OpTypeStruct %uint %Aptr %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Bptr UniformConstant
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %B = OpTypeStruct %uint %Aptr %Bptr %uint
+          %A = OpTypeStruct %Aptr %uint %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 6
++; Bound: 10
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpTypeForwardPointer %1 UniformConstant
+-OpTypeForwardPointer %2 UniformConstant
++OpTypeForwardPointer %6 UniformConstant
++OpTypeForwardPointer %7 UniformConstant
+ %3 = OpTypeInt 32 0
+-%4 = OpTypeStruct %1 %3 %2
+-%5 = OpTypeStruct %3 %1 %2
+-%1 = OpTypePointer UniformConstant %4
+-%2 = OpTypePointer UniformConstant %5
++%8 = OpTypeStruct %3 %7 %6 %3
++%9 = OpTypeStruct %7 %3 %6
++%7 = OpTypePointer UniformConstant %9
++%6 = OpTypePointer UniformConstant %8
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_intertwined_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_intertwined_dst.spvasm
new file mode 100644
index 0000000..bd73501
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_intertwined_dst.spvasm
@@ -0,0 +1,13 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpName %Bptr "Bptr"
+               OpTypeForwardPointer %Bptr UniformConstant
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %B = OpTypeStruct %uint %Aptr %Bptr %uint
+          %A = OpTypeStruct %Aptr %uint %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B
diff --git a/test/diff/diff_files/OpTypeForwardPointer_intertwined_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_intertwined_src.spvasm
new file mode 100644
index 0000000..8fdaf28
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_intertwined_src.spvasm
@@ -0,0 +1,15 @@
+;; Tests that two forwarded types whose declarations are intertwined match
+;; correctly
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpName %Bptr "Bptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+               OpTypeForwardPointer %Bptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint %Bptr
+          %B = OpTypeStruct %uint %Aptr %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_autogen.cpp
new file mode 100644
index 0000000..0a59be3
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_autogen.cpp
@@ -0,0 +1,116 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that two forwarded type pointers with mismatching storage classes
+// aren't matched
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr Function
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer Function %A
+)";
+
+TEST(DiffTest, OptypeforwardpointerMismatchingClass) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 4
++; Bound: 6
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpName %1 "Aptr"
++OpName %4 "Aptr"
+-OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %4 Function
+ %2 = OpTypeInt 32 0
+-%3 = OpTypeStruct %1 %2
+-%1 = OpTypePointer UniformConstant %3
++%5 = OpTypeStruct %4 %2
++%4 = OpTypePointer Function %5
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerMismatchingClassNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr Function
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer Function %A
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 4
++; Bound: 6
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %4 Function
+ %2 = OpTypeInt 32 0
+-%3 = OpTypeStruct %1 %2
+-%1 = OpTypePointer UniformConstant %3
++%5 = OpTypeStruct %4 %2
++%4 = OpTypePointer Function %5
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_dst.spvasm
new file mode 100644
index 0000000..e874a0c
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_dst.spvasm
@@ -0,0 +1,9 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr Function
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer Function %A
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_src.spvasm
new file mode 100644
index 0000000..8a33933
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_src.spvasm
@@ -0,0 +1,11 @@
+;; Tests that two forwarded type pointers with mismatching storage classes
+;; aren't matched
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_autogen.cpp
new file mode 100644
index 0000000..0067cdf
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_autogen.cpp
@@ -0,0 +1,111 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that two forwarded type pointers with mismatching types aren't matched
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+  %Aptr = OpTypePointer UniformConstant %uint
+)";
+
+TEST(DiffTest, OptypeforwardpointerMismatchingType) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 4
++; Bound: 5
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpName %1 "Aptr"
++OpName %4 "Aptr"
+-OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %4 UniformConstant
+ %2 = OpTypeInt 32 0
+-%3 = OpTypeStruct %1 %2
+-%1 = OpTypePointer UniformConstant %3
++%4 = OpTypePointer UniformConstant %2
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerMismatchingTypeNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+  %Aptr = OpTypePointer UniformConstant %uint
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 4
++; Bound: 5
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %4 UniformConstant
+ %2 = OpTypeInt 32 0
+-%3 = OpTypeStruct %1 %2
+-%1 = OpTypePointer UniformConstant %3
++%4 = OpTypePointer UniformConstant %2
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_dst.spvasm
new file mode 100644
index 0000000..ee3d35c
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_dst.spvasm
@@ -0,0 +1,8 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+  %Aptr = OpTypePointer UniformConstant %uint
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_src.spvasm
new file mode 100644
index 0000000..a4596a0
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_src.spvasm
@@ -0,0 +1,10 @@
+;; Tests that two forwarded type pointers with mismatching types aren't matched
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A
diff --git a/test/diff/diff_files/OpTypeForwardPointer_nested_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_nested_autogen.cpp
new file mode 100644
index 0000000..d66c28a
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_nested_autogen.cpp
@@ -0,0 +1,127 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that two forwarded declarations match even if the type pointer is used
+// in a nested struct declaration, and in multiple places
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr %uint
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A
+)";
+
+TEST(DiffTest, OptypeforwardpointerNested) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 6
++; Bound: 8
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpName %1 "Aptr"
+ OpTypeForwardPointer %1 UniformConstant
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2 %1
+-%4 = OpTypeStruct %3 %1 %2
+-%5 = OpTypeStruct %4 %3 %4
++%6 = OpTypeStruct %3 %1
++%7 = OpTypeStruct %6 %3 %6
+-%1 = OpTypePointer UniformConstant %5
++%1 = OpTypePointer UniformConstant %7
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerNestedNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr %uint
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 6
++; Bound: 8
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpTypeForwardPointer %1 UniformConstant
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2 %1
+-%4 = OpTypeStruct %3 %1 %2
+-%5 = OpTypeStruct %4 %3 %4
++%6 = OpTypeStruct %3 %1
++%7 = OpTypeStruct %6 %3 %6
+-%1 = OpTypePointer UniformConstant %5
++%1 = OpTypePointer UniformConstant %7
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_nested_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_nested_dst.spvasm
new file mode 100644
index 0000000..e248355
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_nested_dst.spvasm
@@ -0,0 +1,11 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A
diff --git a/test/diff/diff_files/OpTypeForwardPointer_nested_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_nested_src.spvasm
new file mode 100644
index 0000000..035410e
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_nested_src.spvasm
@@ -0,0 +1,13 @@
+;; Tests that two forwarded declarations match even if the type pointer is used
+;; in a nested struct declaration, and in multiple places
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr %uint
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A
diff --git a/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_autogen.cpp
new file mode 100644
index 0000000..df86fef
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_autogen.cpp
@@ -0,0 +1,124 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test that OpTypeForwardPointer is matched when one SPIR-V doesn't have debug
+// info
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+)";
+
+TEST(DiffTest, OptypeforwardpointerOnesidedDebug) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 7
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpName %1 "structptr"
+ OpTypeForwardPointer %1 UniformConstant
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2
+ %4 = OpTypeStruct %2 %1
+ %5 = OpTypeStruct %2 %2 %1
+ %6 = OpTypeStruct %2 %2 %2 %1
+ %1 = OpTypePointer UniformConstant %3
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerOnesidedDebugNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 7
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpTypeForwardPointer %1 UniformConstant
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2
+ %4 = OpTypeStruct %2 %1
+ %5 = OpTypeStruct %2 %2 %1
+ %6 = OpTypeStruct %2 %2 %2 %1
+ %1 = OpTypePointer UniformConstant %3
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_dst.spvasm
new file mode 100644
index 0000000..7e25710
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_dst.spvasm
@@ -0,0 +1,11 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
diff --git a/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_src.spvasm
new file mode 100644
index 0000000..e949b27
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_src.spvasm
@@ -0,0 +1,14 @@
+;; Test that OpTypeForwardPointer is matched when one SPIR-V doesn't have debug
+;; info
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
diff --git a/test/diff/diff_files/diff_test_files_autogen.cmake b/test/diff/diff_files/diff_test_files_autogen.cmake
index f472480..c64eaab 100644
--- a/test/diff/diff_files/diff_test_files_autogen.cmake
+++ b/test/diff/diff_files/diff_test_files_autogen.cmake
@@ -18,6 +18,12 @@
 list(APPEND DIFF_TEST_FILES
 "diff_files/OpExtInst_in_dst_only_autogen.cpp"
 "diff_files/OpExtInst_in_src_only_autogen.cpp"
+"diff_files/OpTypeForwardPointer_basic_autogen.cpp"
+"diff_files/OpTypeForwardPointer_intertwined_autogen.cpp"
+"diff_files/OpTypeForwardPointer_mismatching_class_autogen.cpp"
+"diff_files/OpTypeForwardPointer_mismatching_type_autogen.cpp"
+"diff_files/OpTypeForwardPointer_nested_autogen.cpp"
+"diff_files/OpTypeForwardPointer_onesided_debug_autogen.cpp"
 "diff_files/basic_autogen.cpp"
 "diff_files/constant_array_size_autogen.cpp"
 "diff_files/different_decorations_fragment_autogen.cpp"
diff --git a/test/diff/diff_files/generate_tests.py b/test/diff/diff_files/generate_tests.py
index 1c380c9..cc3175d 100755
--- a/test/diff/diff_files/generate_tests.py
+++ b/test/diff/diff_files/generate_tests.py
@@ -114,7 +114,7 @@
     (in_basename, in_ext) = os.path.splitext(in_path)
     out_name = in_basename + '_no_dbg' + in_ext
     out_path = os.path.join(tmp_dir, out_name)
-    
+
     with open(in_path, 'r') as fin:
         with open(out_path, 'w') as fout:
             for line in fin: