usaxena95 created this revision.
Herald added a subscriber: cfe-commits.

Added support of creating a hardlink from one file to another file.
After a hardlink is added between two files, both file will have the same:

1. UniqueID (inode)
2. Size
3. Buffer

This will bring replay of compilation closer to the actual compilation. There 
are instances where clang checks for the UniqueID of the file/header to be 
loaded which leads to a different behavior during replay as all files have 
different UniqueIDs.


Repository:
  rC Clang

https://reviews.llvm.org/D51359

Files:
  include/clang/Basic/VirtualFileSystem.h
  lib/Basic/VirtualFileSystem.cpp
  unittests/Basic/VirtualFileSystemTest.cpp

Index: unittests/Basic/VirtualFileSystemTest.cpp
===================================================================
--- unittests/Basic/VirtualFileSystemTest.cpp
+++ unittests/Basic/VirtualFileSystemTest.cpp
@@ -17,6 +17,7 @@
 #include "llvm/Support/SourceMgr.h"
 #include "gtest/gtest.h"
 #include <map>
+#include <string>
 
 using namespace clang;
 using namespace llvm;
@@ -695,6 +696,20 @@
   InMemoryFileSystemTest()
       : FS(/*UseNormalizedPaths=*/false),
         NormalizedFS(/*UseNormalizedPaths=*/true) {}
+
+public:
+  void ExpectHardLink(Twine From, Twine To) {
+    auto OpenedFrom = FS.openFileForRead(From);
+    ASSERT_FALSE(OpenedFrom.getError());
+    auto OpenedTo = FS.openFileForRead(To);
+    ASSERT_FALSE(OpenedTo.getError());
+    ASSERT_EQ((*OpenedFrom)->status()->getSize(),
+              (*OpenedTo)->status()->getSize());
+    ASSERT_EQ((*OpenedFrom)->status()->getUniqueID(),
+              (*OpenedTo)->status()->getUniqueID());
+    ASSERT_EQ((*OpenedFrom)->getBuffer(From)->get()->getBuffer(),
+              (*OpenedTo)->getBuffer(To)->get()->getBuffer());
+  }
 };
 
 TEST_F(InMemoryFileSystemTest, IsEmpty) {
@@ -958,6 +973,96 @@
   ASSERT_EQ("../b/c", getPosixPath(It->getName()));
 }
 
+TEST_F(InMemoryFileSystemTest, AddHardLinkWithToFileNotAddedBefore) {
+  std::pair<llvm::Twine, StringRef> From = {"/path/to/FROM/file",
+                                            "content of FROM file"};
+  std::pair<llvm::Twine, StringRef> To = {"/path/to/TO/file",
+                                          "content of TO file"};
+  FS.addFile(From.first, 0, MemoryBuffer::getMemBuffer(From.second));
+  FS.addHardlink(From.first, To.first, /*CopyBuffer=*/true);
+  ExpectHardLink(From.first, To.first);
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkWithToFileThatWasAlreadyAdded) {
+  std::pair<llvm::Twine, StringRef> From = {"/path/to/FROM/file",
+                                            "content of FROM file"};
+  std::pair<llvm::Twine, StringRef> To = {"/path/to/TO/file",
+                                          "content of TO file"};
+  FS.addFile(From.first, 0, MemoryBuffer::getMemBuffer(From.second));
+  FS.addFile(To.first, 0, MemoryBuffer::getMemBuffer(To.second));
+  FS.addHardlink(From.first, To.first, /*CopyBuffer=*/true);
+  ExpectHardLink(From.first, To.first);
+}
+
+TEST_F(InMemoryFileSystemTest, AddMoreThanOneHardLinkToSameFile) {
+  std::pair<llvm::Twine, StringRef> From = {"/path/to/FROM/file",
+                                            "content of FROM file"};
+  std::pair<llvm::Twine, StringRef> To = {"/path/to/TO/file",
+                                          "content of TO file"};
+  FS.addFile(From.first, 0, MemoryBuffer::getMemBuffer(From.second));
+  FS.addFile(To.first, 0, MemoryBuffer::getMemBuffer(To.second));
+  for (int i = 0; i < 5; ++i)
+    FS.addHardlink(From.first, To.first, /*CopyBuffer=*/true);
+  ExpectHardLink(From.first, To.first);
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkToAlreadyLinkedFiles) {
+  std::pair<Twine, StringRef> From = {"/path/to/FROM/file",
+                                      "content of FROM file"};
+  FS.addFile(From.first, 0, MemoryBuffer::getMemBuffer(From.second.data()));
+  std::vector<std::pair<string, StringRef>> ToFiles;
+  ToFiles.reserve(5);
+  for (int i = 0; i < 5; ++i) {
+    ToFiles.push_back(
+        {"/path/to/TO/file_" + std::to_string(i), "content of to file"});
+  }
+  for (const auto &to : ToFiles) {
+    FS.addFile(to.first.data(), 0,
+               MemoryBuffer::getMemBuffer(to.second.data()));
+  }
+  for (int i = 1; i < 5; ++i) {
+    FS.addHardlink(ToFiles[i - 1].first.data(), ToFiles[i].first.data(),
+                   /*CopyBuffer=*/false);
+  }
+  FS.addHardlink(From.first, ToFiles[0].first.data(), /*CopyBuffer=*/true);
+  for (const auto &to : ToFiles) {
+    ExpectHardLink(From.first, to.first.data());
+  }
+}
+
+TEST_F(InMemoryFileSystemTest, AddHardLinkBetweenTwoGroups) {
+  std::vector<std::pair<string, string>> FileGroup1, FileGroup2;
+  for (int i = 0; i < 5; ++i) {
+    string str_i = std::to_string(i);
+    FileGroup1.push_back(
+        {"/path/to/group/1/file_" + str_i, "group1, file =" + str_i});
+    FileGroup2.push_back(
+        {"/path/to/group/2/file_" + str_i, "group2, file =" + str_i});
+  }
+  // Create files for both groups.
+  for (int i = 0; i < 5; ++i) {
+    FS.addFile(FileGroup1[i].first.data(), 0,
+               MemoryBuffer::getMemBuffer(FileGroup1[i].second.data()));
+    FS.addFile(FileGroup2[i].first.data(), 0,
+               MemoryBuffer::getMemBuffer(FileGroup2[i].second.data()));
+  }
+  // Link files that belong to the same group.
+  for (int i = 1; i < 5; ++i) {
+    FS.addHardlink(FileGroup1[i - 1].first.data(), FileGroup1[i].first.data(),
+                   /*CopyBuffer=*/true);
+    FS.addHardlink(FileGroup2[i - 1].first.data(), FileGroup2[i].first.data(),
+                   /*CopyBuffer=*/true);
+  }
+  // Link 1st files of both groups together. Copy buffers also.
+  FS.addHardlink(FileGroup1[0].first.data(), FileGroup2[0].first.data(),
+                 /*CopyBuffer=*/true);
+  // Expect all files to be linked with content as of 1st file of 1st group.
+  for (int i = 0; i < 5; ++i) {
+    ExpectHardLink(FileGroup1[0].first.data(), FileGroup1[i].first.data());
+    ExpectHardLink(FileGroup1[0].first.data(), FileGroup2[i].first.data());
+  }
+}
+
 // NOTE: in the tests below, we use '//root/' as our root directory, since it is
 // a legal *absolute* path on Windows as well as *nix.
 class VFSFromYAMLTest : public ::testing::Test {
@@ -1500,3 +1605,4 @@
 
   EXPECT_EQ(3, NumDiagnostics);
 }
+
Index: lib/Basic/VirtualFileSystem.cpp
===================================================================
--- lib/Basic/VirtualFileSystem.cpp
+++ lib/Basic/VirtualFileSystem.cpp
@@ -12,6 +12,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/Basic/VirtualFileSystem.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/MemoryBuffer.h"
 #include "clang/Basic/LLVM.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
@@ -25,9 +27,9 @@
 #include "llvm/ADT/Twine.h"
 #include "llvm/ADT/iterator_range.h"
 #include "llvm/Config/llvm-config.h"
-#include "llvm/Support/Compiler.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Chrono.h"
+#include "llvm/Support/Compiler.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/Errc.h"
 #include "llvm/Support/ErrorHandling.h"
@@ -485,6 +487,8 @@
       : Stat(std::move(Stat)), Kind(Kind) {}
   virtual ~InMemoryNode() = default;
 
+  void setUniqueId(UniqueID unique_id) { Stat.setUniqueID(unique_id); }
+  void setSize(uint64_t Size) { Stat.setSize(Size); }
   /// Return the \p Status for this node. \p RequestedName should be the name
   /// through which the caller referred to this node. It will override
   /// \p Status::Name in the return value, to mimic the behavior of \p RealFile.
@@ -509,6 +513,13 @@
   InMemoryFile(Status Stat, std::unique_ptr<llvm::MemoryBuffer> Buffer)
       : InMemoryNode(std::move(Stat), IME_File), Buffer(std::move(Buffer)) {}
 
+  void setBuffer(std::unique_ptr<llvm::MemoryBuffer> buffer) {
+    this->Buffer = std::move(buffer);
+    setSize(this->Buffer->getBufferSize());
+  }
+  void setBuffer(StringRef buffer) {
+    setBuffer(llvm::MemoryBuffer::getMemBuffer(buffer));
+  }
   llvm::MemoryBuffer *getBuffer() { return Buffer.get(); }
 
   std::string toString(unsigned Indent) const override {
@@ -601,15 +612,51 @@
   return Root->toString(/*Indent=*/0);
 }
 
+void InMemoryFileSystem::addHardlink(const Twine &FromPath, const Twine &ToPath,
+                                     bool CopyBuffer = true) {
+  llvm::ErrorOr<Status> FromStat = this->status(FromPath.str());
+  llvm::ErrorOr<Status> ToStat = this->status(ToPath.str());
+  assert(FromStat && FromStat->exists() &&
+         "From File must be added before adding hardlink");
+  std::set<detail::InMemoryNode *> &FromIDSet =
+      this->Files_by_ID[FromStat->getUniqueID()];
+  StringRef FromBuffer = static_cast<detail::InMemoryFile *>(*FromIDSet.begin())
+                             ->getBuffer()
+                             ->getBuffer();
+  // If ToFile does not exist, create one containing buffer of From file.
+  if (!ToStat || !ToStat->exists()) {
+    this->addFile(ToPath, 0, llvm::MemoryBuffer::getMemBuffer(FromBuffer));
+    ToStat = this->status(ToPath.str());
+  }
+  // Check if link already exists.
+  if (ToStat->getUniqueID() == FromStat->getUniqueID()) {
+    return;
+  }
+  std::set<detail::InMemoryNode *> &ToIDSet =
+      this->Files_by_ID[ToStat->getUniqueID()];
+  if (CopyBuffer) {
+    // Copy Buffer from From to all To nodes.
+    for (detail::InMemoryNode *node : ToIDSet) {
+      detail::InMemoryFile *ToFile = static_cast<detail::InMemoryFile *>(node);
+      ToFile->setBuffer(FromBuffer);
+    }
+  }
+  // Merging ToIDSet into FromIDSet.
+  for (detail::InMemoryNode *to_node : ToIDSet) {
+    to_node->setUniqueId(FromStat->getUniqueID());
+    FromIDSet.insert(to_node);
+  }
+  this->Files_by_ID.erase(ToStat->getUniqueID());
+}
+
 bool InMemoryFileSystem::addFile(const Twine &P, time_t ModificationTime,
                                  std::unique_ptr<llvm::MemoryBuffer> Buffer,
                                  Optional<uint32_t> User,
                                  Optional<uint32_t> Group,
                                  Optional<llvm::sys::fs::file_type> Type,
                                  Optional<llvm::sys::fs::perms> Perms) {
   SmallString<128> Path;
   P.toVector(Path);
-
   // Fix up relative paths. This just prepends the current working directory.
   std::error_code EC = makeAbsolute(Path);
   assert(!EC);
@@ -645,8 +692,9 @@
         if (ResolvedType == sys::fs::file_type::directory_file) {
           Child.reset(new detail::InMemoryDirectory(std::move(Stat)));
         } else {
-          Child.reset(new detail::InMemoryFile(std::move(Stat),
+          Child.reset(new detail::InMemoryFile(Stat,
                                                std::move(Buffer)));
+          this->Files_by_ID[Stat.getUniqueID()].insert(Child.get());
         }
         Dir->addChild(Name, std::move(Child));
         return true;
@@ -2064,3 +2112,4 @@
 
   return *this;
 }
+
Index: include/clang/Basic/VirtualFileSystem.h
===================================================================
--- include/clang/Basic/VirtualFileSystem.h
+++ include/clang/Basic/VirtualFileSystem.h
@@ -29,7 +29,9 @@
 #include <cassert>
 #include <cstdint>
 #include <ctime>
+#include <map>
 #include <memory>
+#include <set>
 #include <stack>
 #include <string>
 #include <system_error>
@@ -75,6 +77,9 @@
   /// Returns the name that should be used for this file or directory.
   StringRef getName() const { return Name; }
 
+  void setUniqueID(llvm::sys::fs::UniqueID UID) { this->UID = UID; }
+  void setSize(uint64_t Size) { this->Size = Size; }
+
   /// @name Status interface from llvm::sys::fs
   /// @{
   llvm::sys::fs::file_type getType() const { return Type; }
@@ -323,12 +328,15 @@
 namespace detail {
 
 class InMemoryDirectory;
+class InMemoryNode;
 
 } // namespace detail
 
 /// An in-memory file system.
 class InMemoryFileSystem : public FileSystem {
   std::unique_ptr<detail::InMemoryDirectory> Root;
+  std::map<llvm::sys::fs::UniqueID, std::set<detail::InMemoryNode *>>
+      Files_by_ID;
   std::string WorkingDirectory;
   bool UseNormalizedPaths = true;
 
@@ -348,6 +356,11 @@
                Optional<llvm::sys::fs::file_type> Type = None,
                Optional<llvm::sys::fs::perms> Perms = None);
 
+  /// Add a HardLink from one file to another.
+  /// Makes the UniqueID of To file same as that of From file. If CopyBuffer is
+  /// true then contents of from buffer is copied into the buffer of To file.
+  void addHardlink(const Twine &From, const Twine &To, bool CopyBuffer);
+
   /// Add a buffer to the VFS with a path. The VFS does not own the buffer.
   /// If present, User, Group, Type and Perms apply to the newly-created file
   /// or directory.
@@ -452,3 +465,4 @@
 } // namespace clang
 
 #endif // LLVM_CLANG_BASIC_VIRTUALFILESYSTEM_H
+
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to