Skip to content

Commit

Permalink
Basic: Add hashing support for FileEntryRef and DirectoryEntryRef
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dexonsmith committed Dec 9, 2020
1 parent 66a03d1 commit 2878e96
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 3 deletions.
57 changes: 57 additions & 0 deletions clang/include/clang/Basic/DirectoryEntry.h
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {}

Expand Down Expand Up @@ -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;
};

Expand Down Expand Up @@ -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 {
Expand Down
51 changes: 51 additions & 0 deletions clang/include/clang/Basic/FileEntry.h
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
};

Expand Down Expand Up @@ -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 {
Expand Down
87 changes: 84 additions & 3 deletions clang/unittests/Basic/FileEntryTest.cpp
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include "clang/Basic/FileEntry.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/StringMap.h"
#include "gtest/gtest.h"

Expand All @@ -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>());
Expand Down Expand Up @@ -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

0 comments on commit 2878e96

Please sign in to comment.