diff --git a/clang/include/clang/Basic/DirectoryEntry.h b/clang/include/clang/Basic/DirectoryEntry.h --- 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 @@ 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>; 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 @@ DirectoryEntryRef(optional_none_tag) : ME(nullptr) {} bool hasOptionalValue() const { return ME; } + friend struct llvm::DenseMapInfo; + struct dense_map_empty_tag {}; + struct dense_map_tombstone_tag {}; + + // Private constructors for use by DenseMapInfo. + DirectoryEntryRef(dense_map_empty_tag) + : ME(llvm::DenseMapInfo::getEmptyKey()) {} + DirectoryEntryRef(dense_map_tombstone_tag) + : ME(llvm::DenseMapInfo::getTombstoneKey()) {} + bool isSpecialDenseMapKey() const { + return isSameRef(DirectoryEntryRef(dense_map_empty_tag())) || + isSameRef(DirectoryEntryRef(dense_map_tombstone_tag())); + } + const MapEntry *ME; }; @@ -164,6 +189,38 @@ "Optional should be trivially copyable"); } // end namespace optional_detail + +/// Specialisation of DenseMapInfo for DirectoryEntryRef. +template <> struct DenseMapInfo { + 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 --- 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 @@ 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 @@ FileEntryRef(optional_none_tag) : ME(nullptr) {} bool hasOptionalValue() const { return ME; } + friend struct llvm::DenseMapInfo; + struct dense_map_empty_tag {}; + struct dense_map_tombstone_tag {}; + + // Private constructors for use by DenseMapInfo. + FileEntryRef(dense_map_empty_tag) + : ME(llvm::DenseMapInfo::getEmptyKey()) {} + FileEntryRef(dense_map_tombstone_tag) + : ME(llvm::DenseMapInfo::getTombstoneKey()) {} + bool isSpecialDenseMapKey() const { + return isSameRef(FileEntryRef(dense_map_empty_tag())) || + isSameRef(FileEntryRef(dense_map_tombstone_tag())); + } + const MapEntry *ME; }; @@ -197,6 +219,35 @@ "Optional should be trivially copyable"); } // end namespace optional_detail + +/// Specialisation of DenseMapInfo for FileEntryRef. +template <> struct DenseMapInfo { + 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 --- 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 @@ FileMap Files; DirMap Dirs; - DirectoryEntry D; - DirectoryEntryRef DR; SmallVector, 5> FEs; + SmallVector, 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()); + return DirectoryEntryRef(*Dirs.insert({Name, *DEs.back()}).first); + } + DirectoryEntryRef addDirectoryAlias(StringRef Name, DirectoryEntryRef Base) { + return DirectoryEntryRef( + *Dirs.insert({Name, const_cast(Base.getDirEntry())}) + .first); + } FileEntryRef addFile(StringRef Name) { FEs.push_back(std::make_unique()); @@ -112,4 +123,74 @@ 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 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 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 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 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