jansvoboda11 updated this revision to Diff 391369.
jansvoboda11 added a comment.

Add unit test, IWYU.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D114966/new/

https://reviews.llvm.org/D114966

Files:
  clang/include/clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h
  clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp
  clang/unittests/Tooling/DependencyScannerTest.cpp
  llvm/include/llvm/Support/VirtualFileSystem.h
  llvm/lib/Support/VirtualFileSystem.cpp

Index: llvm/lib/Support/VirtualFileSystem.cpp
===================================================================
--- llvm/lib/Support/VirtualFileSystem.cpp
+++ llvm/lib/Support/VirtualFileSystem.cpp
@@ -75,6 +75,12 @@
     : Name(Name.str()), UID(UID), MTime(MTime), User(User), Group(Group),
       Size(Size), Type(Type), Perms(Perms) {}
 
+Status Status::copyWithNewSize(const Status &In, uint64_t NewSize) {
+  return Status(In.getName(), In.getUniqueID(), In.getLastModificationTime(),
+                In.getUser(), In.getGroup(), NewSize, In.getType(),
+                In.getPermissions());
+}
+
 Status Status::copyWithNewName(const Status &In, const Twine &NewName) {
   return Status(NewName, In.getUniqueID(), In.getLastModificationTime(),
                 In.getUser(), In.getGroup(), In.getSize(), In.getType(),
Index: llvm/include/llvm/Support/VirtualFileSystem.h
===================================================================
--- llvm/include/llvm/Support/VirtualFileSystem.h
+++ llvm/include/llvm/Support/VirtualFileSystem.h
@@ -64,6 +64,8 @@
          uint64_t Size, llvm::sys::fs::file_type Type,
          llvm::sys::fs::perms Perms);
 
+  /// Get a copy of a Status with a different size.
+  static Status copyWithNewSize(const Status &In, uint64_t NewSize);
   /// Get a copy of a Status with a different name.
   static Status copyWithNewName(const Status &In, const Twine &NewName);
   static Status copyWithNewName(const llvm::sys::fs::file_status &In,
Index: clang/unittests/Tooling/DependencyScannerTest.cpp
===================================================================
--- clang/unittests/Tooling/DependencyScannerTest.cpp
+++ clang/unittests/Tooling/DependencyScannerTest.cpp
@@ -205,9 +205,11 @@
 }
 
 namespace dependencies {
-TEST(DependencyScanningFilesystem, IgnoredFilesHaveSeparateCache) {
+TEST(DependencyScanningFilesystem, IgnoredFilesAreCachedSeparately1) {
   auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
-  VFS->addFile("/mod.h", 0, llvm::MemoryBuffer::getMemBuffer("// hi there!\n"));
+  VFS->addFile("/mod.h", 0,
+               llvm::MemoryBuffer::getMemBuffer("#include <foo.h>\n"
+                                                "// hi there!\n"));
 
   DependencyScanningFilesystemSharedCache SharedCache;
   auto Mappings = std::make_unique<ExcludedPreprocessorDirectiveSkipMapping>();
@@ -223,14 +225,35 @@
   auto StatusFull3 = DepFS.status("/mod.h");
 
   EXPECT_TRUE(StatusMinimized0);
-  EXPECT_EQ(StatusMinimized0->getSize(), 0u);
+  EXPECT_EQ(StatusMinimized0->getSize(), 17u);
   EXPECT_TRUE(StatusFull1);
-  EXPECT_EQ(StatusFull1->getSize(), 13u);
+  EXPECT_EQ(StatusFull1->getSize(), 30u);
 
   EXPECT_TRUE(StatusMinimized2);
-  EXPECT_EQ(StatusMinimized2->getSize(), 0u);
+  EXPECT_EQ(StatusMinimized2->getSize(), 17u);
   EXPECT_TRUE(StatusFull3);
-  EXPECT_EQ(StatusFull3->getSize(), 13u);
+  EXPECT_EQ(StatusFull3->getSize(), 30u);
+}
+
+TEST(DependencyScanningFilesystem, IgnoredFilesAreCachedSeparately2) {
+  auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
+  VFS->addFile("/mod.h", 0,
+               llvm::MemoryBuffer::getMemBuffer("#include <foo.h>\n"
+                                                "// hi there!\n"));
+
+  DependencyScanningFilesystemSharedCache SharedCache;
+  auto Mappings = std::make_unique<ExcludedPreprocessorDirectiveSkipMapping>();
+  DependencyScanningWorkerFilesystem DepFS(SharedCache, VFS, Mappings.get());
+
+  DepFS.disableMinimization("/mod.h");
+  auto StatusFull0 = DepFS.status("/mod.h");
+  DepFS.enableMinimizationOfAllFiles();
+  auto StatusMinimized1 = DepFS.status("/mod.h");
+
+  EXPECT_TRUE(StatusFull0);
+  EXPECT_TRUE(StatusMinimized1);
+  EXPECT_EQ(StatusFull0->getSize(), 30u);
+  EXPECT_EQ(StatusMinimized1->getSize(), 17u);
 }
 
 } // end namespace dependencies
Index: clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp
===================================================================
--- clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp
+++ clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp
@@ -15,9 +15,9 @@
 using namespace tooling;
 using namespace dependencies;
 
-CachedFileSystemEntry CachedFileSystemEntry::createFileEntry(
-    StringRef Filename, llvm::vfs::FileSystem &FS, bool Minimize) {
-  // Load the file and its content from the file system.
+static llvm::ErrorOr<llvm::vfs::Status>
+readFile(StringRef Filename, llvm::vfs::FileSystem &FS,
+         llvm::SmallString<1> &SharedOriginalContents) {
   llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> MaybeFile =
       FS.openFileForRead(Filename);
   if (!MaybeFile)
@@ -32,31 +32,27 @@
   if (!MaybeBuffer)
     return MaybeBuffer.getError();
 
+  const auto &Buffer = *MaybeBuffer;
+  SharedOriginalContents.reserve(Buffer->getBufferSize() + 1);
+  SharedOriginalContents.append(Buffer->getBufferStart(),
+                                Buffer->getBufferEnd());
+  // Implicitly null terminate the contents for Clang's lexer.
+  SharedOriginalContents.push_back('\0');
+  SharedOriginalContents.pop_back();
+  return *Stat;
+}
+
+static bool
+minimizeFile(const SmallString<1> &OriginalContents,
+             SmallString<1> &MinimizedContents,
+             PreprocessorSkippedRangeMapping &PPSkippedRangeMapping) {
   llvm::SmallString<1024> MinimizedFileContents;
   // Minimize the file down to directives that might affect the dependencies.
-  const auto &Buffer = *MaybeBuffer;
   SmallVector<minimize_source_to_dependency_directives::Token, 64> Tokens;
-  if (!Minimize || minimizeSourceToDependencyDirectives(
-                       Buffer->getBuffer(), MinimizedFileContents, Tokens)) {
-    // Use the original file unless requested otherwise, or
-    // if the minimization failed.
-    // FIXME: Propage the diagnostic if desired by the client.
-    CachedFileSystemEntry Result;
-    Result.MaybeStat = std::move(*Stat);
-    Result.Contents.reserve(Buffer->getBufferSize() + 1);
-    Result.Contents.append(Buffer->getBufferStart(), Buffer->getBufferEnd());
-    // Implicitly null terminate the contents for Clang's lexer.
-    Result.Contents.push_back('\0');
-    Result.Contents.pop_back();
-    return Result;
-  }
+  if (minimizeSourceToDependencyDirectives(OriginalContents.str(),
+                                           MinimizedFileContents, Tokens))
+    return true;
 
-  CachedFileSystemEntry Result;
-  size_t Size = MinimizedFileContents.size();
-  Result.MaybeStat = llvm::vfs::Status(Stat->getName(), Stat->getUniqueID(),
-                                       Stat->getLastModificationTime(),
-                                       Stat->getUser(), Stat->getGroup(), Size,
-                                       Stat->getType(), Stat->getPermissions());
   // The contents produced by the minimizer must be null terminated.
   assert(MinimizedFileContents.data()[MinimizedFileContents.size()] == '\0' &&
          "not null terminated contents");
@@ -65,10 +61,10 @@
   // std::move will preserve it even if it needs to do a copy if the
   // SmallString still has the small capacity.
   MinimizedFileContents.push_back('\0');
-  Result.Contents = std::move(MinimizedFileContents);
+  MinimizedContents = std::move(MinimizedFileContents);
   // Now make the null terminator implicit again, so that Clang's lexer can find
   // it right where the buffer ends.
-  Result.Contents.pop_back();
+  MinimizedContents.pop_back();
 
   // Compute the skipped PP ranges that speedup skipping over inactive
   // preprocessor blocks.
@@ -86,20 +82,13 @@
     }
     Mapping[Range.Offset] = Range.Length;
   }
-  Result.PPSkippedRangeMapping = std::move(Mapping);
+  PPSkippedRangeMapping = std::move(Mapping);
 
-  return Result;
+  return false;
 }
 
-CachedFileSystemEntry
-CachedFileSystemEntry::createDirectoryEntry(llvm::vfs::Status &&Stat) {
-  assert(Stat.isDirectory() && "not a directory!");
-  auto Result = CachedFileSystemEntry();
-  Result.MaybeStat = std::move(Stat);
-  return Result;
-}
-
-DependencyScanningFilesystemSharedCache::SingleCache::SingleCache() {
+DependencyScanningFilesystemSharedCache::
+    DependencyScanningFilesystemSharedCache() {
   // This heuristic was chosen using a empirical testing on a
   // reasonably high core machine (iMacPro 18 cores / 36 threads). The cache
   // sharding gives a performance edge by reducing the lock contention.
@@ -110,18 +99,67 @@
   CacheShards = std::make_unique<CacheShard[]>(NumShards);
 }
 
-DependencyScanningFilesystemSharedCache::SharedFileSystemEntry &
-DependencyScanningFilesystemSharedCache::SingleCache::get(StringRef Key) {
-  CacheShard &Shard = CacheShards[llvm::hash_value(Key) % NumShards];
-  std::unique_lock<std::mutex> LockGuard(Shard.CacheLock);
-  auto It = Shard.Cache.try_emplace(Key);
-  return It.first->getValue();
+SharedStat &
+DependencyScanningFilesystemSharedCache::getStat(StringRef Filename) {
+  CacheShard &Shard = CacheShards[llvm::hash_value(Filename) % NumShards];
+  std::unique_lock<std::mutex> Lock(Shard.CacheLock);
+  return Shard.StatCache.try_emplace(Filename).first->second;
+}
+
+InsertResult<OriginalContents>
+DependencyScanningFilesystemSharedCache::getOriginalContents(
+    StringRef Filename, llvm::sys::fs::UniqueID UID) {
+  CacheShard &Shard = CacheShards[llvm::hash_value(Filename) % NumShards];
+  std::unique_lock<std::mutex> Lock(Shard.CacheLock);
+  auto InsertRes = Shard.OriginalContentsCache.insert({UID, OriginalContents{}});
+  return makeInsertResult(InsertRes.first->second, InsertRes.second);
+}
+
+InsertResult<MinimizedContents>
+DependencyScanningFilesystemSharedCache::getMinimizedContents(
+    StringRef Filename, llvm::sys::fs::UniqueID UID) {
+  CacheShard &Shard = CacheShards[llvm::hash_value(Filename) % NumShards];
+  std::unique_lock<std::mutex> Lock(Shard.CacheLock);
+  auto InsertRes = Shard.MinimizedContentsCache.insert({UID, MinimizedContents{}});
+  return makeInsertResult(InsertRes.first->second, InsertRes.second);
+}
+
+const MaybeStat *
+DependencyScanningFilesystemLocalCache::findStat(StringRef Filename) {
+  auto It = StatCache.find(Filename);
+  return It != StatCache.end() ? It->getValue() : nullptr;
+}
+
+const MaybeStat *
+DependencyScanningFilesystemLocalCache::insertStat(StringRef Filename,
+                                                   const MaybeStat *Stat) {
+  return StatCache.insert({Filename, Stat}).first->second;
+}
+
+const OriginalContents *
+DependencyScanningFilesystemLocalCache::findOriginalContents(
+    llvm::sys::fs::UniqueID UID) {
+  auto It = OriginalContentsCache.find(UID);
+  return It != OriginalContentsCache.end() ? It->second : nullptr;
+}
+
+const OriginalContents *
+DependencyScanningFilesystemLocalCache::insertOriginalContents(
+    llvm::sys::fs::UniqueID UID, const OriginalContents *Contents) {
+  return OriginalContentsCache.insert({UID, Contents}).first->second;
 }
 
-DependencyScanningFilesystemSharedCache::SharedFileSystemEntry &
-DependencyScanningFilesystemSharedCache::get(StringRef Key, bool Minimized) {
-  SingleCache &Cache = Minimized ? CacheMinimized : CacheOriginal;
-  return Cache.get(Key);
+const MinimizedContents *
+DependencyScanningFilesystemLocalCache::findMinimizedContents(
+    llvm::sys::fs::UniqueID UID) {
+  auto It = MinimizedContentsCache.find(UID);
+  return It != MinimizedContentsCache.end() ? It->second : nullptr;
+}
+
+const MinimizedContents *
+DependencyScanningFilesystemLocalCache::insertMinimizedContents(
+    llvm::sys::fs::UniqueID UID, const MinimizedContents *Contents) {
+  return MinimizedContentsCache.insert({UID, Contents}).first->second;
 }
 
 /// Whitelist file extensions that should be minimized, treating no extension as
@@ -167,66 +205,126 @@
   return !NotToBeMinimized.contains(Filename);
 }
 
-CachedFileSystemEntry DependencyScanningWorkerFilesystem::createFileSystemEntry(
-    llvm::ErrorOr<llvm::vfs::Status> &&MaybeStatus, StringRef Filename,
-    bool ShouldMinimize) {
-  if (!MaybeStatus)
-    return CachedFileSystemEntry(MaybeStatus.getError());
+void DependencyScanningWorkerFilesystem::ensureLocked(
+    StringRef Path, SharedStat *&SS,
+    llvm::Optional<std::unique_lock<std::mutex>> &Lock) {
+  if (!SS)
+    SS = &SharedCache.getStat(Path);
+  if (!Lock)
+    Lock.emplace(SS->Mutex);
+  assert(Lock->mutex() == &SS->Mutex && "Locked the wrong mutex");
+}
+
+const MaybeStat *DependencyScanningWorkerFilesystem::getOrCreateLocalStat(
+    StringRef Path, SharedStat *&SS,
+    llvm::Optional<std::unique_lock<std::mutex>> &Lock) {
+  if (const MaybeStat *LocalStat = Cache.findStat(Path))
+    return LocalStat;
+
+  bool CacheStatFailures = shouldCacheStatFailures(Path);
 
-  if (MaybeStatus->isDirectory())
-    return CachedFileSystemEntry::createDirectoryEntry(std::move(*MaybeStatus));
+  ensureLocked(Path, SS, Lock);
 
-  return CachedFileSystemEntry::createFileEntry(Filename, getUnderlyingFS(),
-                                                ShouldMinimize);
+  // Call stat if necessary.
+  if (!SS->isValid() || (!SS->Stat && !CacheStatFailures)) {
+    // HACK: We need to always restat non source files if the stat fails.
+    //   This is because Clang first looks up the module cache and module
+    //   files before building them, and then looks for them again. If we
+    //   cache the stat failure, it won't see them the second time.
+    auto MaybeStatus = getUnderlyingFS().status(Path);
+    SS->Stat = std::move(MaybeStatus);
+  }
+  // Update local cache if possible.
+  return CacheStatFailures ? Cache.insertStat(Path, &SS->Stat) : &SS->Stat;
 }
 
-llvm::ErrorOr<const CachedFileSystemEntry *>
-DependencyScanningWorkerFilesystem::getOrCreateFileSystemEntry(
-    const StringRef Filename) {
-  bool ShouldMinimize = shouldMinimize(Filename);
-
-  if (const auto *Entry = Cache.getCachedEntry(Filename, ShouldMinimize))
-    return Entry;
-
-  // FIXME: Handle PCM/PCH files.
-  // FIXME: Handle module map files.
-
-  DependencyScanningFilesystemSharedCache::SharedFileSystemEntry
-      &SharedCacheEntry = SharedCache.get(Filename, ShouldMinimize);
-  const CachedFileSystemEntry *Result;
-  {
-    std::unique_lock<std::mutex> LockGuard(SharedCacheEntry.ValueLock);
-    CachedFileSystemEntry &CacheEntry = SharedCacheEntry.Value;
-
-    if (!CacheEntry.isValid()) {
-      auto MaybeStatus = getUnderlyingFS().status(Filename);
-      if (!MaybeStatus && !shouldCacheStatFailures(Filename))
-        // HACK: We need to always restat non source files if the stat fails.
-        //   This is because Clang first looks up the module cache and module
-        //   files before building them, and then looks for them again. If we
-        //   cache the stat failure, it won't see them the second time.
-        return MaybeStatus.getError();
-      CacheEntry = createFileSystemEntry(std::move(MaybeStatus), Filename,
-                                         ShouldMinimize);
-    }
+const OriginalContents *
+DependencyScanningWorkerFilesystem::getOrCreateLocalOriginalContents(
+    StringRef Filename, llvm::sys::fs::UniqueID UID, SharedStat *&SharedStat,
+    llvm::Optional<std::unique_lock<std::mutex>> &Lock) {
+  if (const OriginalContents *LocalOriginalContents =
+          Cache.findOriginalContents(UID))
+    return LocalOriginalContents;
+
+  ensureLocked(Filename, SharedStat, Lock);
 
-    Result = &CacheEntry;
+  auto SharedOriginalContents = SharedCache.getOriginalContents(Filename, UID);
+  // Read the file if necessary.
+  if (SharedOriginalContents.WasInserted)
+    SharedStat->Stat =
+        readFile(Filename, getUnderlyingFS(), SharedOriginalContents.Value);
+
+  return Cache.insertOriginalContents(UID, &SharedOriginalContents.Value);
+}
+
+const MinimizedContents *
+DependencyScanningWorkerFilesystem::getOrCreateLocalMinimizedContents(
+    StringRef Filename, llvm::sys::fs::UniqueID UID, SharedStat *&SharedStat,
+    llvm::Optional<std::unique_lock<std::mutex>> &Lock) {
+  if (const MinimizedContents *LocalMinimizedContents =
+          Cache.findMinimizedContents(UID))
+    return LocalMinimizedContents;
+
+  const OriginalContents *LocalOriginalContents =
+      getOrCreateLocalOriginalContents(Filename, UID, SharedStat, Lock);
+
+  ensureLocked(Filename, SharedStat, Lock);
+
+  auto SharedMinimizedContents =
+      SharedCache.getMinimizedContents(Filename, UID);
+  if (SharedMinimizedContents.WasInserted)
+    if (minimizeFile(*LocalOriginalContents,
+                     SharedMinimizedContents.Value.Contents,
+                     SharedMinimizedContents.Value.PPSkippedRangeMapping))
+      // Use the original file contents if minimization failed.
+      SharedMinimizedContents.Value.Contents = *LocalOriginalContents;
+  return &SharedMinimizedContents.Value;
+}
+
+static llvm::ErrorOr<llvm::vfs::Status>
+withNewSize(const llvm::ErrorOr<llvm::vfs::Status> &Status, uint64_t NewSize) {
+  if (!Status)
+    return Status;
+  return llvm::vfs::Status::copyWithNewSize(*Status, NewSize);
+}
+
+llvm::ErrorOr<FileSystemEntryResult>
+DependencyScanningWorkerFilesystem::getOrCreateFileSystemEntry(
+    StringRef Filename) {
+  SharedStat *SharedStat = nullptr;
+  llvm::Optional<std::unique_lock<std::mutex>> GuardLock;
+
+  const MaybeStat *LocalStat =
+      getOrCreateLocalStat(Filename, SharedStat, GuardLock);
+  if (!(*LocalStat))
+    return LocalStat->getError();
+  if ((*LocalStat)->isDirectory())
+    return FileSystemEntryResult(*LocalStat, nullptr, nullptr);
+
+  llvm::sys::fs::UniqueID UID = (*LocalStat)->getUniqueID();
+
+  if (!shouldMinimize(Filename)) {
+    const OriginalContents *LocalOriginalContents =
+        getOrCreateLocalOriginalContents(Filename, UID, SharedStat, GuardLock);
+    return FileSystemEntryResult(*LocalStat, LocalOriginalContents, nullptr);
   }
 
-  // Store the result in the local cache.
-  Cache.setCachedEntry(Filename, ShouldMinimize, Result);
-  return Result;
+  const MinimizedContents *LocalMinimizedContents =
+      getOrCreateLocalMinimizedContents(Filename, UID, SharedStat, GuardLock);
+  return FileSystemEntryResult(
+      withNewSize(*LocalStat, LocalMinimizedContents->Contents.size()),
+      &LocalMinimizedContents->Contents,
+      &LocalMinimizedContents->PPSkippedRangeMapping);
 }
 
 llvm::ErrorOr<llvm::vfs::Status>
 DependencyScanningWorkerFilesystem::status(const Twine &Path) {
   SmallString<256> OwnedFilename;
   StringRef Filename = Path.toStringRef(OwnedFilename);
-  const llvm::ErrorOr<const CachedFileSystemEntry *> Result =
-      getOrCreateFileSystemEntry(Filename);
+  auto Result = getOrCreateFileSystemEntry(Filename);
   if (!Result)
     return Result.getError();
-  return (*Result)->getStatus();
+  return Result->Status;
 }
 
 namespace {
@@ -240,7 +338,7 @@
       : Buffer(std::move(Buffer)), Stat(std::move(Stat)) {}
 
   static llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
-  create(const CachedFileSystemEntry *Entry,
+  create(const FileSystemEntryResult &Entry,
          ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings);
 
   llvm::ErrorOr<llvm::vfs::Status> status() override { return Stat; }
@@ -261,23 +359,21 @@
 } // end anonymous namespace
 
 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> MinimizedVFSFile::create(
-    const CachedFileSystemEntry *Entry,
+    const FileSystemEntryResult &Entry,
     ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings) {
-  if (Entry->isDirectory())
+  if (Entry.Status->isDirectory())
     return llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>(
         std::make_error_code(std::errc::is_a_directory));
-  llvm::ErrorOr<StringRef> Contents = Entry->getContents();
-  if (!Contents)
-    return Contents.getError();
   auto Result = std::make_unique<MinimizedVFSFile>(
-      llvm::MemoryBuffer::getMemBuffer(*Contents, Entry->getName(),
+      llvm::MemoryBuffer::getMemBuffer(*Entry.Contents, Entry.Status->getName(),
                                        /*RequiresNullTerminator=*/false),
-      *Entry->getStatus());
-  if (!Entry->getPPSkippedRangeMapping().empty() && PPSkipMappings)
+      *Entry.Status);
+
+  if (Entry.PPSkippedRangeMapping && !Entry.PPSkippedRangeMapping->empty() &&
+      PPSkipMappings)
     (*PPSkipMappings)[Result->Buffer->getBufferStart()] =
-        &Entry->getPPSkippedRangeMapping();
-  return llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>(
-      std::unique_ptr<llvm::vfs::File>(std::move(Result)));
+        Entry.PPSkippedRangeMapping;
+  return llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>(std::move(Result));
 }
 
 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
@@ -285,9 +381,10 @@
   SmallString<256> OwnedFilename;
   StringRef Filename = Path.toStringRef(OwnedFilename);
 
-  const llvm::ErrorOr<const CachedFileSystemEntry *> Result =
-      getOrCreateFileSystemEntry(Filename);
+  auto Result = getOrCreateFileSystemEntry(Filename);
   if (!Result)
     return Result.getError();
+  if (!Result->Status)
+    return Result->Status.getError();
   return MinimizedVFSFile::create(Result.get(), PPSkipMappings);
 }
Index: clang/include/clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h
===================================================================
--- clang/include/clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h
+++ clang/include/clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h
@@ -11,97 +11,92 @@
 
 #include "clang/Basic/LLVM.h"
 #include "clang/Lex/PreprocessorExcludedConditionalDirectiveSkipMapping.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/Optional.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/Support/Allocator.h"
 #include "llvm/Support/ErrorOr.h"
 #include "llvm/Support/VirtualFileSystem.h"
 #include <mutex>
+#include <map>
 
 namespace clang {
 namespace tooling {
 namespace dependencies {
 
-/// An in-memory representation of a file system entity that is of interest to
-/// the dependency scanning filesystem.
-///
-/// It represents one of the following:
-/// - an opened source file with minimized contents and a stat value.
-/// - an opened source file with original contents and a stat value.
-/// - a directory entry with its stat value.
-/// - an error value to represent a file system error.
-/// - a placeholder with an invalid stat indicating a not yet initialized entry.
-class CachedFileSystemEntry {
-public:
-  /// Default constructor creates an entry with an invalid stat.
-  CachedFileSystemEntry() : MaybeStat(llvm::vfs::Status()) {}
-
-  CachedFileSystemEntry(std::error_code Error) : MaybeStat(std::move(Error)) {}
-
-  /// Create an entry that represents an opened source file with minimized or
-  /// original contents.
-  ///
-  /// The filesystem opens the file even for `stat` calls open to avoid the
-  /// issues with stat + open of minimized files that might lead to a
-  /// mismatching size of the file. If file is not minimized, the full file is
-  /// read and copied into memory to ensure that it's not memory mapped to avoid
-  /// running out of file descriptors.
-  static CachedFileSystemEntry createFileEntry(StringRef Filename,
-                                               llvm::vfs::FileSystem &FS,
-                                               bool Minimize = true);
-
-  /// Create an entry that represents a directory on the filesystem.
-  static CachedFileSystemEntry createDirectoryEntry(llvm::vfs::Status &&Stat);
-
-  /// \returns True if the entry is valid.
-  bool isValid() const { return !MaybeStat || MaybeStat->isStatusKnown(); }
-
-  /// \returns True if the current entry points to a directory.
-  bool isDirectory() const { return MaybeStat && MaybeStat->isDirectory(); }
-
-  /// \returns The error or the file's contents.
-  llvm::ErrorOr<StringRef> getContents() const {
-    if (!MaybeStat)
-      return MaybeStat.getError();
-    assert(!MaybeStat->isDirectory() && "not a file");
-    assert(isValid() && "not initialized");
-    return Contents.str();
-  }
-
-  /// \returns The error or the status of the entry.
-  llvm::ErrorOr<llvm::vfs::Status> getStatus() const {
-    assert(isValid() && "not initialized");
-    return MaybeStat;
-  }
-
-  /// \returns the name of the file.
-  StringRef getName() const {
-    assert(isValid() && "not initialized");
-    return MaybeStat->getName();
-  }
-
-  /// Return the mapping between location -> distance that is used to speed up
-  /// the block skipping in the preprocessor.
-  const PreprocessorSkippedRangeMapping &getPPSkippedRangeMapping() const {
-    return PPSkippedRangeMapping;
-  }
-
-  CachedFileSystemEntry(CachedFileSystemEntry &&) = default;
-  CachedFileSystemEntry &operator=(CachedFileSystemEntry &&) = default;
-
-  CachedFileSystemEntry(const CachedFileSystemEntry &) = delete;
-  CachedFileSystemEntry &operator=(const CachedFileSystemEntry &) = delete;
+/// The result type that represents one of:
+/// - a file system error,
+/// - status of a directory,
+/// - status of a file,
+/// - status of an opened file and its original contents,
+/// - status of an opened file and its minimized contents.
+struct FileSystemEntryResult {
+  /// The filesystem error, or status.
+  llvm::ErrorOr<llvm::vfs::Status> Status;
+  /// The (potentially minimized) file contents.
+  const llvm::SmallString<1> *Contents;
+  /// The skipped range mappings produced during minimization.
+  const PreprocessorSkippedRangeMapping *PPSkippedRangeMapping;
+
+  FileSystemEntryResult(
+      llvm::ErrorOr<llvm::vfs::Status> Status,
+      const llvm::SmallString<1> *Contents,
+      const PreprocessorSkippedRangeMapping *PPSkippedRangeMapping)
+      : Status(std::move(Status)), Contents(Contents),
+        PPSkippedRangeMapping(PPSkippedRangeMapping) {}
+};
 
-private:
-  llvm::ErrorOr<llvm::vfs::Status> MaybeStat;
-  // Store the contents in a small string to allow a
-  // move from the small string for the minimized contents.
-  // Note: small size of 1 allows us to store an empty string with an implicit
-  // null terminator without any allocations.
-  llvm::SmallString<1> Contents;
+/// The value type for stat cache.
+using MaybeStat = llvm::ErrorOr<llvm::vfs::Status>;
+
+/// The value type for shared stat cache.
+struct SharedStat {
+  /// The wrapped status value.
+  MaybeStat Stat;
+
+  /// The mutex that must be locked when accessing this shared stat cache entry
+  /// and shared original/minimized content caches for the status' UniqueID.
+  std::mutex Mutex;
+
+  SharedStat() : Stat(std::error_code()) {}
+
+  /// Check whether this cache entry is valid.
+  bool isValid() const { return Stat || Stat.getError() != std::error_code(); }
+};
+
+// Small size of 1 allows us to store an empty string with an implicit null
+// terminator without any allocations.
+using Contents = llvm::SmallString<1>;
+
+/// The value type for original contents cache.
+using OriginalContents = Contents;
+
+/// The value type for minimized contents cache.
+struct MinimizedContents {
+  /// The minimized contents itself.
+  Contents Contents;
   PreprocessorSkippedRangeMapping PPSkippedRangeMapping;
+
+  MinimizedContents() : Contents(), PPSkippedRangeMapping(0) {}
 };
 
+/// Result of the "insert" operation on map and set containers that exposes
+/// members with more obvious names compared to \c std::pair::{first,second}.
+template <typename T> struct InsertResult {
+  T &Value;
+  bool WasInserted;
+
+  InsertResult(T &Value, bool WasInserted)
+      : Value(Value), WasInserted(WasInserted) {}
+};
+
+/// A factory function for deducing the template parameter of \c InsertResult.
+template <typename T>
+InsertResult<T> makeInsertResult(T &Value, bool WasInserted) {
+  return InsertResult<T>(Value, WasInserted);
+}
+
 /// This class is a shared cache, that caches the 'stat' and 'open' calls to the
 /// underlying real file system. It distinguishes between minimized and original
 /// files.
@@ -110,67 +105,71 @@
 /// the worker threads.
 class DependencyScanningFilesystemSharedCache {
 public:
-  struct SharedFileSystemEntry {
-    std::mutex ValueLock;
-    CachedFileSystemEntry Value;
-  };
+  DependencyScanningFilesystemSharedCache();
+
+  /// Returns the cached stat entry for directory entry. Creates invalid status
+  /// if not found. This is a thread safe call.
+  SharedStat &getStat(StringRef Path);
 
-  /// Returns a cache entry for the corresponding key.
-  ///
-  /// A new cache entry is created if the key is not in the cache. This is a
-  /// thread safe call.
-  SharedFileSystemEntry &get(StringRef Key, bool Minimized);
+  /// Returns the cached original contents for file. Creates empty contents if
+  /// not found. This is a thread safe call.
+  InsertResult<OriginalContents>
+  getOriginalContents(StringRef Filename, llvm::sys::fs::UniqueID UID);
+
+  /// Returns the cached minimized contents for file. Creates empty contents if
+  /// not found. This is a thread safe call.
+  InsertResult<MinimizedContents>
+  getMinimizedContents(StringRef Filename, llvm::sys::fs::UniqueID UID);
 
 private:
-  class SingleCache {
-  public:
-    SingleCache();
-
-    SharedFileSystemEntry &get(StringRef Key);
-
-  private:
-    struct CacheShard {
-      std::mutex CacheLock;
-      llvm::StringMap<SharedFileSystemEntry, llvm::BumpPtrAllocator> Cache;
-    };
-    std::unique_ptr<CacheShard[]> CacheShards;
-    unsigned NumShards;
+  struct CacheShard {
+    /// The mutex that's locked whenever insertion into any of the caches takes
+    /// place.
+    std::mutex CacheLock;
+    /// Cache of stat call results.
+    llvm::StringMap<SharedStat, llvm::BumpPtrAllocator> StatCache;
+    /// Cache of original file contents. Using \c std::map to ensure references
+    /// are not invalidated on insertion. 
+    std::map<llvm::sys::fs::UniqueID, OriginalContents> OriginalContentsCache;
+    /// Cache of minimized file contents.
+    std::map<llvm::sys::fs::UniqueID, MinimizedContents> MinimizedContentsCache;
   };
-
-  SingleCache CacheMinimized;
-  SingleCache CacheOriginal;
+  std::unique_ptr<CacheShard[]> CacheShards;
+  unsigned NumShards;
 };
 
 /// This class is a local cache, that caches the 'stat' and 'open' calls to the
 /// underlying real file system. It distinguishes between minimized and original
 /// files.
 class DependencyScanningFilesystemLocalCache {
-private:
-  using SingleCache =
-      llvm::StringMap<const CachedFileSystemEntry *, llvm::BumpPtrAllocator>;
-
-  SingleCache CacheMinimized;
-  SingleCache CacheOriginal;
-
-  SingleCache &selectCache(bool Minimized) {
-    return Minimized ? CacheMinimized : CacheOriginal;
-  }
+  /// Cache of stat call results, pointing into the shared cache.
+  llvm::StringMap<const MaybeStat *, llvm::BumpPtrAllocator> StatCache;
+  /// Cache of original file contents, pointing into the shared cache.
+  llvm::DenseMap<llvm::sys::fs::UniqueID, const OriginalContents *>
+      OriginalContentsCache;
+  /// Cache of minimized file contents, pointing into the shared cache.
+  llvm::DenseMap<llvm::sys::fs::UniqueID, const MinimizedContents *>
+      MinimizedContentsCache;
 
 public:
-  void setCachedEntry(StringRef Filename, bool Minimized,
-                      const CachedFileSystemEntry *Entry) {
-    SingleCache &Cache = selectCache(Minimized);
-    bool IsInserted = Cache.try_emplace(Filename, Entry).second;
-    (void)IsInserted;
-    assert(IsInserted && "local cache is updated more than once");
-  }
-
-  const CachedFileSystemEntry *getCachedEntry(StringRef Filename,
-                                              bool Minimized) {
-    SingleCache &Cache = selectCache(Minimized);
-    auto It = Cache.find(Filename);
-    return It == Cache.end() ? nullptr : It->getValue();
-  }
+  /// Returns the status result for the path, or nullptr when not found.
+  const MaybeStat *findStat(StringRef Path);
+  /// Inserts the given status result into the cache for the path.
+  const MaybeStat *insertStat(StringRef Path, const MaybeStat *Status);
+
+  /// Returns the original contents for the file, or nullptr when not found.
+  const OriginalContents *findOriginalContents(llvm::sys::fs::UniqueID UID);
+  /// Inserts the given original contents into the cache for the file.
+  const OriginalContents *
+  insertOriginalContents(llvm::sys::fs::UniqueID UID,
+                         const OriginalContents *Contents);
+
+  /// Returns the minimized contents for the file, or nullptr when not found.
+  const MinimizedContents *findMinimizedContents(llvm::sys::fs::UniqueID UID);
+  /// Inserts the given minimized contents into the cache for the file.
+  const MinimizedContents *
+  insertMinimizedContents(llvm::sys::fs::UniqueID UID,
+                          const MinimizedContents *Contents);
 };
 
 /// A virtual file system optimized for the dependency discovery.
@@ -204,13 +203,32 @@
   /// Check whether the file should be minimized.
   bool shouldMinimize(StringRef Filename);
 
-  llvm::ErrorOr<const CachedFileSystemEntry *>
-  getOrCreateFileSystemEntry(const StringRef Filename);
-
-  /// Create a cached file system entry based on the initial status result.
-  CachedFileSystemEntry
-  createFileSystemEntry(llvm::ErrorOr<llvm::vfs::Status> &&MaybeStatus,
-                        StringRef Filename, bool ShouldMinimize);
+  /// Ensure \p Lock is locked with the mutex for entry at \p Path.
+  /// Also assigns to \p SS if null.
+  void ensureLocked(StringRef Path, SharedStat *&SS,
+                    llvm::Optional<std::unique_lock<std::mutex>> &Lock);
+
+  /// Get the status for \p Path from local cache, populating it if necessary.
+  const MaybeStat *
+  getOrCreateLocalStat(StringRef Path, SharedStat *&SS,
+                       llvm::Optional<std::unique_lock<std::mutex>> &Lock);
+
+  /// Get the original contents for \p Filename from local cache, populating it
+  /// if necessary.
+  const OriginalContents *getOrCreateLocalOriginalContents(
+      StringRef Filename, llvm::sys::fs::UniqueID UID, SharedStat *&SharedStat,
+      llvm::Optional<std::unique_lock<std::mutex>> &Lock);
+
+  /// Get the minimized contents for \p Filename from local cache, populating it
+  /// if necessary.
+  const MinimizedContents *getOrCreateLocalMinimizedContents(
+      StringRef Filename, llvm::sys::fs::UniqueID UID, SharedStat *&SharedStat,
+      llvm::Optional<std::unique_lock<std::mutex>> &Lock);
+
+  /// Get the full filesystem entry for \p Path from local cache, populating it
+  /// if necessary.
+  llvm::ErrorOr<FileSystemEntryResult>
+  getOrCreateFileSystemEntry(StringRef Path);
 
   /// The global cache shared between worker threads.
   DependencyScanningFilesystemSharedCache &SharedCache;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to