Author: Duncan P. N. Exon Smith Date: 2020-12-08T18:10:53-08:00 New Revision: 2878e965af27ce037378a4f0409e89039108c09f
URL: https://github.com/llvm/llvm-project/commit/2878e965af27ce037378a4f0409e89039108c09f DIFF: https://github.com/llvm/llvm-project/commit/2878e965af27ce037378a4f0409e89039108c09f.diff LOG: Basic: Add hashing support for FileEntryRef and DirectoryEntryRef 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 Added: Modified: clang/include/clang/Basic/DirectoryEntry.h clang/include/clang/Basic/FileEntry.h clang/unittests/Basic/FileEntryTest.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Basic/DirectoryEntry.h b/clang/include/clang/Basic/DirectoryEntry.h index 4e229962bdcc..e0f4ae28321a 100644 --- a/clang/include/clang/Basic/DirectoryEntry.h +++ b/clang/include/clang/Basic/DirectoryEntry.h @@ -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 @@ class DirectoryEntryRef { 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 @@ class DirectoryEntryRef { 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 { diff --git a/clang/include/clang/Basic/FileEntry.h b/clang/include/clang/Basic/FileEntry.h index 75158d44bf5a..8db5446aa8d4 100644 --- a/clang/include/clang/Basic/FileEntry.h +++ b/clang/include/clang/Basic/FileEntry.h @@ -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 @@ class FileEntryRef { 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 @@ class FileEntryRef { 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 { diff --git a/clang/unittests/Basic/FileEntryTest.cpp b/clang/unittests/Basic/FileEntryTest.cpp index f2619a21def7..3cc01870b800 100644 --- a/clang/unittests/Basic/FileEntryTest.cpp +++ b/clang/unittests/Basic/FileEntryTest.cpp @@ -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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits