OSDN Git Service

Basic: Add hashing support for FileEntryRef and DirectoryEntryRef
authorDuncan P. N. Exon Smith <dexonsmith@apple.com>
Fri, 30 Oct 2020 22:25:44 +0000 (18:25 -0400)
committerDuncan P. N. Exon Smith <dexonsmith@apple.com>
Wed, 9 Dec 2020 02:10:53 +0000 (18:10 -0800)
Allow hashing FileEntryRef and DirectoryEntryRef via `hash_value`, and
use that to implement `DenseMapInfo`. This hash should be equal whenever
the entry is the same (the name used to reference it is not relevant).

Also add `DirectoryEntryRef::isSameRef` to simplify the implementation
and facilitate testing.

Differential Revision: https://reviews.llvm.org/D92627

clang/include/clang/Basic/DirectoryEntry.h
clang/include/clang/Basic/FileEntry.h
clang/unittests/Basic/FileEntryTest.cpp

index 4e22996..e0f4ae2 100644 (file)
@@ -15,6 +15,8 @@
 #define LLVM_CLANG_BASIC_DIRECTORYENTRY_H
 
 #include "clang/Basic/LLVM.h"
+#include "llvm/ADT/DenseMapInfo.h"
+#include "llvm/ADT/Hashing.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/ErrorOr.h"
@@ -46,10 +48,19 @@ public:
 
   StringRef getName() const { return ME->getKey(); }
 
+  /// Hash code is based on the DirectoryEntry, not the specific named
+  /// reference.
+  friend llvm::hash_code hash_value(DirectoryEntryRef Ref) {
+    return llvm::hash_value(&Ref.getDirEntry());
+  }
+
   using MapEntry = llvm::StringMapEntry<llvm::ErrorOr<DirectoryEntry &>>;
 
   const MapEntry &getMapEntry() const { return *ME; }
 
+  /// Check if RHS referenced the file in exactly the same way.
+  bool isSameRef(DirectoryEntryRef RHS) const { return ME == RHS.ME; }
+
   DirectoryEntryRef() = delete;
   DirectoryEntryRef(const MapEntry &ME) : ME(&ME) {}
 
@@ -80,6 +91,20 @@ private:
   DirectoryEntryRef(optional_none_tag) : ME(nullptr) {}
   bool hasOptionalValue() const { return ME; }
 
+  friend struct llvm::DenseMapInfo<DirectoryEntryRef>;
+  struct dense_map_empty_tag {};
+  struct dense_map_tombstone_tag {};
+
+  // Private constructors for use by DenseMapInfo.
+  DirectoryEntryRef(dense_map_empty_tag)
+      : ME(llvm::DenseMapInfo<const MapEntry *>::getEmptyKey()) {}
+  DirectoryEntryRef(dense_map_tombstone_tag)
+      : ME(llvm::DenseMapInfo<const MapEntry *>::getTombstoneKey()) {}
+  bool isSpecialDenseMapKey() const {
+    return isSameRef(DirectoryEntryRef(dense_map_empty_tag())) ||
+           isSameRef(DirectoryEntryRef(dense_map_tombstone_tag()));
+  }
+
   const MapEntry *ME;
 };
 
@@ -164,6 +189,38 @@ static_assert(
     "Optional<DirectoryEntryRef> should be trivially copyable");
 
 } // end namespace optional_detail
+
+/// Specialisation of DenseMapInfo for DirectoryEntryRef.
+template <> struct DenseMapInfo<clang::DirectoryEntryRef> {
+  static inline clang::DirectoryEntryRef getEmptyKey() {
+    return clang::DirectoryEntryRef(
+        clang::DirectoryEntryRef::dense_map_empty_tag());
+  }
+
+  static inline clang::DirectoryEntryRef getTombstoneKey() {
+    return clang::DirectoryEntryRef(
+        clang::DirectoryEntryRef::dense_map_tombstone_tag());
+  }
+
+  static unsigned getHashValue(clang::DirectoryEntryRef Val) {
+    return hash_value(Val);
+  }
+
+  static bool isEqual(clang::DirectoryEntryRef LHS,
+                      clang::DirectoryEntryRef RHS) {
+    // Catch the easy cases: both empty, both tombstone, or the same ref.
+    if (LHS.isSameRef(RHS))
+      return true;
+
+    // Confirm LHS and RHS are valid.
+    if (LHS.isSpecialDenseMapKey() || RHS.isSpecialDenseMapKey())
+      return false;
+
+    // It's safe to use operator==.
+    return LHS == RHS;
+  }
+};
+
 } // end namespace llvm
 
 namespace clang {
index 75158d4..8db5446 100644 (file)
@@ -16,6 +16,8 @@
 
 #include "clang/Basic/DirectoryEntry.h"
 #include "clang/Basic/LLVM.h"
+#include "llvm/ADT/DenseMapInfo.h"
+#include "llvm/ADT/Hashing.h"
 #include "llvm/ADT/PointerUnion.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
@@ -88,6 +90,12 @@ public:
     return !(LHS == RHS);
   }
 
+  /// Hash code is based on the FileEntry, not the specific named reference,
+  /// just like operator==.
+  friend llvm::hash_code hash_value(FileEntryRef Ref) {
+    return llvm::hash_value(&Ref.getFileEntry());
+  }
+
   struct MapValue;
 
   /// Type used in the StringMap.
@@ -154,6 +162,20 @@ private:
   FileEntryRef(optional_none_tag) : ME(nullptr) {}
   bool hasOptionalValue() const { return ME; }
 
+  friend struct llvm::DenseMapInfo<FileEntryRef>;
+  struct dense_map_empty_tag {};
+  struct dense_map_tombstone_tag {};
+
+  // Private constructors for use by DenseMapInfo.
+  FileEntryRef(dense_map_empty_tag)
+      : ME(llvm::DenseMapInfo<const MapEntry *>::getEmptyKey()) {}
+  FileEntryRef(dense_map_tombstone_tag)
+      : ME(llvm::DenseMapInfo<const MapEntry *>::getTombstoneKey()) {}
+  bool isSpecialDenseMapKey() const {
+    return isSameRef(FileEntryRef(dense_map_empty_tag())) ||
+           isSameRef(FileEntryRef(dense_map_tombstone_tag()));
+  }
+
   const MapEntry *ME;
 };
 
@@ -197,6 +219,35 @@ static_assert(std::is_trivially_copyable<Optional<clang::FileEntryRef>>::value,
               "Optional<FileEntryRef> should be trivially copyable");
 
 } // end namespace optional_detail
+
+/// Specialisation of DenseMapInfo for FileEntryRef.
+template <> struct DenseMapInfo<clang::FileEntryRef> {
+  static inline clang::FileEntryRef getEmptyKey() {
+    return clang::FileEntryRef(clang::FileEntryRef::dense_map_empty_tag());
+  }
+
+  static inline clang::FileEntryRef getTombstoneKey() {
+    return clang::FileEntryRef(clang::FileEntryRef::dense_map_tombstone_tag());
+  }
+
+  static unsigned getHashValue(clang::FileEntryRef Val) {
+    return hash_value(Val);
+  }
+
+  static bool isEqual(clang::FileEntryRef LHS, clang::FileEntryRef RHS) {
+    // Catch the easy cases: both empty, both tombstone, or the same ref.
+    if (LHS.isSameRef(RHS))
+      return true;
+
+    // Confirm LHS and RHS are valid.
+    if (LHS.isSpecialDenseMapKey() || RHS.isSpecialDenseMapKey())
+      return false;
+
+    // It's safe to use operator==.
+    return LHS == RHS;
+  }
+};
+
 } // end namespace llvm
 
 namespace clang {
index f2619a2..3cc0187 100644 (file)
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/Basic/FileEntry.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/StringMap.h"
 #include "gtest/gtest.h"
 
@@ -22,11 +23,21 @@ struct RefMaps {
   FileMap Files;
   DirMap Dirs;
 
-  DirectoryEntry D;
-  DirectoryEntryRef DR;
   SmallVector<std::unique_ptr<FileEntry>, 5> FEs;
+  SmallVector<std::unique_ptr<DirectoryEntry>, 5> DEs;
+  DirectoryEntryRef DR;
 
-  RefMaps() : DR(*Dirs.insert({"dir", D}).first) {}
+  RefMaps() : DR(addDirectory("dir")) {}
+
+  DirectoryEntryRef addDirectory(StringRef Name) {
+    DEs.push_back(std::make_unique<DirectoryEntry>());
+    return DirectoryEntryRef(*Dirs.insert({Name, *DEs.back()}).first);
+  }
+  DirectoryEntryRef addDirectoryAlias(StringRef Name, DirectoryEntryRef Base) {
+    return DirectoryEntryRef(
+        *Dirs.insert({Name, const_cast<DirectoryEntry &>(Base.getDirEntry())})
+             .first);
+  }
 
   FileEntryRef addFile(StringRef Name) {
     FEs.push_back(std::make_unique<FileEntry>());
@@ -112,4 +123,74 @@ TEST(FileEntryTest, isSameRef) {
   EXPECT_FALSE(R1.isSameRef(R1Also));
 }
 
+TEST(FileEntryTest, DenseMapInfo) {
+  RefMaps Refs;
+  FileEntryRef R1 = Refs.addFile("1");
+  FileEntryRef R2 = Refs.addFile("2");
+  FileEntryRef R1Also = Refs.addFileAlias("1-also", R1);
+
+  // Insert R1Also first and confirm it "wins".
+  {
+    SmallDenseSet<FileEntryRef, 8> Set;
+    Set.insert(R1Also);
+    Set.insert(R1);
+    Set.insert(R2);
+    EXPECT_TRUE(Set.find(R1Also)->isSameRef(R1Also));
+    EXPECT_TRUE(Set.find(R1)->isSameRef(R1Also));
+    EXPECT_TRUE(Set.find(R2)->isSameRef(R2));
+  }
+
+  // Insert R1Also second and confirm R1 "wins".
+  {
+    SmallDenseSet<FileEntryRef, 8> Set;
+    Set.insert(R1);
+    Set.insert(R1Also);
+    Set.insert(R2);
+    EXPECT_TRUE(Set.find(R1Also)->isSameRef(R1));
+    EXPECT_TRUE(Set.find(R1)->isSameRef(R1));
+    EXPECT_TRUE(Set.find(R2)->isSameRef(R2));
+  }
+}
+
+TEST(DirectoryEntryTest, isSameRef) {
+  RefMaps Refs;
+  DirectoryEntryRef R1 = Refs.addDirectory("1");
+  DirectoryEntryRef R2 = Refs.addDirectory("2");
+  DirectoryEntryRef R1Also = Refs.addDirectoryAlias("1-also", R1);
+
+  EXPECT_TRUE(R1.isSameRef(DirectoryEntryRef(R1)));
+  EXPECT_TRUE(R1.isSameRef(DirectoryEntryRef(R1.getMapEntry())));
+  EXPECT_FALSE(R1.isSameRef(R2));
+  EXPECT_FALSE(R1.isSameRef(R1Also));
+}
+
+TEST(DirectoryEntryTest, DenseMapInfo) {
+  RefMaps Refs;
+  DirectoryEntryRef R1 = Refs.addDirectory("1");
+  DirectoryEntryRef R2 = Refs.addDirectory("2");
+  DirectoryEntryRef R1Also = Refs.addDirectoryAlias("1-also", R1);
+
+  // Insert R1Also first and confirm it "wins".
+  {
+    SmallDenseSet<DirectoryEntryRef, 8> Set;
+    Set.insert(R1Also);
+    Set.insert(R1);
+    Set.insert(R2);
+    EXPECT_TRUE(Set.find(R1Also)->isSameRef(R1Also));
+    EXPECT_TRUE(Set.find(R1)->isSameRef(R1Also));
+    EXPECT_TRUE(Set.find(R2)->isSameRef(R2));
+  }
+
+  // Insert R1Also second and confirm R1 "wins".
+  {
+    SmallDenseSet<DirectoryEntryRef, 8> Set;
+    Set.insert(R1);
+    Set.insert(R1Also);
+    Set.insert(R2);
+    EXPECT_TRUE(Set.find(R1Also)->isSameRef(R1));
+    EXPECT_TRUE(Set.find(R1)->isSameRef(R1));
+    EXPECT_TRUE(Set.find(R2)->isSameRef(R2));
+  }
+}
+
 } // end namespace