abidiff: speed up with performance hack

The function types_have_similar_structure is used during comparisons.
It is called by and calls the main equality functions, possibly
resulting in a multiplication of the cost of comparison.

This is a convenient place to add some memoisation to avoid at least
some of the duplicate calls. This is safe only if pointer addresses
are never reused.

	* src/abg-ir.cc (types_have_similar_structure): In the wrapper
	overload, check for calls with the same (pointer) arguments.
	Assert there is no identical call in progress. Return any
	known result. Cache newly determined results.

Bug: 215577137
Change-Id: I91078cf052ebab5dce0a2d388d84e393aa704d67
Signed-off-by: Giuliano Procida <gprocida@google.com>
diff --git a/src/abg-ir.cc b/src/abg-ir.cc
index 203a7f3..23ee021 100644
--- a/src/abg-ir.cc
+++ b/src/abg-ir.cc
@@ -18,6 +18,7 @@
 #include <sstream>
 #include <typeinfo>
 #include <unordered_map>
+#include <tuple>
 #include <utility>
 #include <vector>
 
@@ -33,6 +34,7 @@
 ABG_END_EXPORT_DECLARATIONS
 // </headers defining libabigail's API>
 
+#include "abg-cxx-compat.h"  // for abg_compat::optional
 #include "abg-tools-utils.h"
 #include "abg-comp-filter.h"
 #include "abg-ir-priv.h"
@@ -25346,7 +25348,43 @@
 types_have_similar_structure(const type_base_sptr& first,
 			     const type_base_sptr& second,
 			     bool indirect_type)
-{return types_have_similar_structure(first.get(), second.get(), indirect_type);}
+{
+  // This wrapper function is used during comparisons. It is called by and its
+  // wrappee calls the main equality functions, possibly resulting in a
+  // multiplication of the cost of comparison.
+
+  // This is a convenient place to add some memoisation to avoid at
+  // least some of the duplicate calls. This is safe only if pointer
+  // addresses are never reused.
+  using Arguments = std::tuple<const type_base*, const type_base*, bool>;
+
+  struct Hasher {
+    size_t operator()(const Arguments& arguments) const {
+      const auto& [first, second, indirect] = arguments;
+      size_t seed = 0;
+      std::hash<const type_base*> h;
+      combine_hash(seed, h(first));
+      combine_hash(seed, h(second));
+      combine_hash(seed, indirect);
+      return seed;
+    }
+    static void combine_hash(size_t& seed, size_t hash) {
+      seed ^= hash + 0x9e3779b97f4a7c15 + (seed << 12) + (seed >> 4);
+    }
+  };
+
+  static std::unordered_map<Arguments, abg_compat::optional<bool>, Hasher> memo;
+
+  const Arguments t{first.get(), second.get(), indirect_type};
+  const auto& [it, inserted] = memo.insert({t, {}});
+  auto& result = it->second;
+  if (inserted)
+    result = types_have_similar_structure(
+        std::get<0>(t), std::get<1>(t), std::get<2>(t));
+  else
+    ABG_ASSERT(result.has_value());
+  return *result;
+}
 
 /// Test if two types have similar structures, even though they are
 /// (or can be) different.