Skip to content

Commit df7ac0e

Browse files
authored
[clang][modules] Virtualize module cache pruning (llvm#149113)
This PR virtualizes module cache pruning via the new `ModuleCache` interface. Currently this is an NFC, but I left a FIXME in `InProcessModuleCache` to make this more efficient for the dependency scanner.
1 parent 72dafa1 commit df7ac0e

File tree

4 files changed

+105
-90
lines changed

4 files changed

+105
-90
lines changed

clang/include/clang/Serialization/ModuleCache.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,15 @@ class ModuleCache : public RefCountedBase<ModuleCache> {
4545
/// were validated.
4646
virtual void updateModuleTimestamp(StringRef ModuleFilename) = 0;
4747

48+
/// Prune module files that haven't been accessed in a long time.
49+
virtual void maybePrune(StringRef Path, time_t PruneInterval,
50+
time_t PruneAfter) = 0;
51+
4852
/// Returns this process's view of the module cache.
4953
virtual InMemoryModuleCache &getInMemoryModuleCache() = 0;
5054
virtual const InMemoryModuleCache &getInMemoryModuleCache() const = 0;
5155

52-
// TODO: Virtualize writing/reading PCM files, pruning, etc.
56+
// TODO: Virtualize writing/reading PCM files, etc.
5357

5458
virtual ~ModuleCache() = default;
5559
};
@@ -59,6 +63,9 @@ class ModuleCache : public RefCountedBase<ModuleCache> {
5963
/// \c CompilerInstance instances participating in building modules for single
6064
/// translation unit in order to share the same \c InMemoryModuleCache.
6165
IntrusiveRefCntPtr<ModuleCache> createCrossProcessModuleCache();
66+
67+
/// Shared implementation of `ModuleCache::maybePrune()`.
68+
void maybePruneImpl(StringRef Path, time_t PruneInterval, time_t PruneAfter);
6269
} // namespace clang
6370

6471
#endif

clang/lib/Frontend/CompilerInstance.cpp

Lines changed: 4 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,90 +1598,6 @@ static void checkConfigMacros(Preprocessor &PP, Module *M,
15981598
}
15991599
}
16001600

1601-
/// Write a new timestamp file with the given path.
1602-
static void writeTimestampFile(StringRef TimestampFile) {
1603-
std::error_code EC;
1604-
llvm::raw_fd_ostream Out(TimestampFile.str(), EC, llvm::sys::fs::OF_None);
1605-
}
1606-
1607-
/// Prune the module cache of modules that haven't been accessed in
1608-
/// a long time.
1609-
static void pruneModuleCache(const HeaderSearchOptions &HSOpts) {
1610-
llvm::sys::fs::file_status StatBuf;
1611-
llvm::SmallString<128> TimestampFile;
1612-
TimestampFile = HSOpts.ModuleCachePath;
1613-
assert(!TimestampFile.empty());
1614-
llvm::sys::path::append(TimestampFile, "modules.timestamp");
1615-
1616-
// Try to stat() the timestamp file.
1617-
if (std::error_code EC = llvm::sys::fs::status(TimestampFile, StatBuf)) {
1618-
// If the timestamp file wasn't there, create one now.
1619-
if (EC == std::errc::no_such_file_or_directory) {
1620-
writeTimestampFile(TimestampFile);
1621-
}
1622-
return;
1623-
}
1624-
1625-
// Check whether the time stamp is older than our pruning interval.
1626-
// If not, do nothing.
1627-
time_t TimeStampModTime =
1628-
llvm::sys::toTimeT(StatBuf.getLastModificationTime());
1629-
time_t CurrentTime = time(nullptr);
1630-
if (CurrentTime - TimeStampModTime <= time_t(HSOpts.ModuleCachePruneInterval))
1631-
return;
1632-
1633-
// Write a new timestamp file so that nobody else attempts to prune.
1634-
// There is a benign race condition here, if two Clang instances happen to
1635-
// notice at the same time that the timestamp is out-of-date.
1636-
writeTimestampFile(TimestampFile);
1637-
1638-
// Walk the entire module cache, looking for unused module files and module
1639-
// indices.
1640-
std::error_code EC;
1641-
for (llvm::sys::fs::directory_iterator Dir(HSOpts.ModuleCachePath, EC),
1642-
DirEnd;
1643-
Dir != DirEnd && !EC; Dir.increment(EC)) {
1644-
// If we don't have a directory, there's nothing to look into.
1645-
if (!llvm::sys::fs::is_directory(Dir->path()))
1646-
continue;
1647-
1648-
// Walk all of the files within this directory.
1649-
for (llvm::sys::fs::directory_iterator File(Dir->path(), EC), FileEnd;
1650-
File != FileEnd && !EC; File.increment(EC)) {
1651-
// We only care about module and global module index files.
1652-
StringRef Extension = llvm::sys::path::extension(File->path());
1653-
if (Extension != ".pcm" && Extension != ".timestamp" &&
1654-
llvm::sys::path::filename(File->path()) != "modules.idx")
1655-
continue;
1656-
1657-
// Look at this file. If we can't stat it, there's nothing interesting
1658-
// there.
1659-
if (llvm::sys::fs::status(File->path(), StatBuf))
1660-
continue;
1661-
1662-
// If the file has been used recently enough, leave it there.
1663-
time_t FileAccessTime = llvm::sys::toTimeT(StatBuf.getLastAccessedTime());
1664-
if (CurrentTime - FileAccessTime <=
1665-
time_t(HSOpts.ModuleCachePruneAfter)) {
1666-
continue;
1667-
}
1668-
1669-
// Remove the file.
1670-
llvm::sys::fs::remove(File->path());
1671-
1672-
// Remove the timestamp file.
1673-
std::string TimpestampFilename = File->path() + ".timestamp";
1674-
llvm::sys::fs::remove(TimpestampFilename);
1675-
}
1676-
1677-
// If we removed all of the files in the directory, remove the directory
1678-
// itself.
1679-
if (llvm::sys::fs::directory_iterator(Dir->path(), EC) ==
1680-
llvm::sys::fs::directory_iterator() && !EC)
1681-
llvm::sys::fs::remove(Dir->path());
1682-
}
1683-
}
1684-
16851601
void CompilerInstance::createASTReader() {
16861602
if (TheASTReader)
16871603
return;
@@ -1692,11 +1608,10 @@ void CompilerInstance::createASTReader() {
16921608
// If we're implicitly building modules but not currently recursively
16931609
// building a module, check whether we need to prune the module cache.
16941610
if (getSourceManager().getModuleBuildStack().empty() &&
1695-
!getPreprocessor().getHeaderSearchInfo().getModuleCachePath().empty() &&
1696-
getHeaderSearchOpts().ModuleCachePruneInterval > 0 &&
1697-
getHeaderSearchOpts().ModuleCachePruneAfter > 0) {
1698-
pruneModuleCache(getHeaderSearchOpts());
1699-
}
1611+
!getPreprocessor().getHeaderSearchInfo().getModuleCachePath().empty())
1612+
ModCache->maybePrune(getHeaderSearchOpts().ModuleCachePath,
1613+
getHeaderSearchOpts().ModuleCachePruneInterval,
1614+
getHeaderSearchOpts().ModuleCachePruneAfter);
17001615

17011616
HeaderSearchOptions &HSOpts = getHeaderSearchOpts();
17021617
std::string Sysroot = HSOpts.Sysroot;

clang/lib/Serialization/ModuleCache.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,87 @@
1616

1717
using namespace clang;
1818

19+
/// Write a new timestamp file with the given path.
20+
static void writeTimestampFile(StringRef TimestampFile) {
21+
std::error_code EC;
22+
llvm::raw_fd_ostream Out(TimestampFile.str(), EC, llvm::sys::fs::OF_None);
23+
}
24+
25+
void clang::maybePruneImpl(StringRef Path, time_t PruneInterval,
26+
time_t PruneAfter) {
27+
if (PruneInterval <= 0 || PruneAfter <= 0)
28+
return;
29+
30+
llvm::SmallString<128> TimestampFile(Path);
31+
llvm::sys::path::append(TimestampFile, "modules.timestamp");
32+
33+
// Try to stat() the timestamp file.
34+
llvm::sys::fs::file_status StatBuf;
35+
if (std::error_code EC = llvm::sys::fs::status(TimestampFile, StatBuf)) {
36+
// If the timestamp file wasn't there, create one now.
37+
if (EC == std::errc::no_such_file_or_directory)
38+
writeTimestampFile(TimestampFile);
39+
return;
40+
}
41+
42+
// Check whether the time stamp is older than our pruning interval.
43+
// If not, do nothing.
44+
time_t TimestampModTime =
45+
llvm::sys::toTimeT(StatBuf.getLastModificationTime());
46+
time_t CurrentTime = time(nullptr);
47+
if (CurrentTime - TimestampModTime <= PruneInterval)
48+
return;
49+
50+
// Write a new timestamp file so that nobody else attempts to prune.
51+
// There is a benign race condition here, if two Clang instances happen to
52+
// notice at the same time that the timestamp is out-of-date.
53+
writeTimestampFile(TimestampFile);
54+
55+
// Walk the entire module cache, looking for unused module files and module
56+
// indices.
57+
std::error_code EC;
58+
for (llvm::sys::fs::directory_iterator Dir(Path, EC), DirEnd;
59+
Dir != DirEnd && !EC; Dir.increment(EC)) {
60+
// If we don't have a directory, there's nothing to look into.
61+
if (!llvm::sys::fs::is_directory(Dir->path()))
62+
continue;
63+
64+
// Walk all the files within this directory.
65+
for (llvm::sys::fs::directory_iterator File(Dir->path(), EC), FileEnd;
66+
File != FileEnd && !EC; File.increment(EC)) {
67+
// We only care about module and global module index files.
68+
StringRef Extension = llvm::sys::path::extension(File->path());
69+
if (Extension != ".pcm" && Extension != ".timestamp" &&
70+
llvm::sys::path::filename(File->path()) != "modules.idx")
71+
continue;
72+
73+
// Look at this file. If we can't stat it, there's nothing interesting
74+
// there.
75+
if (llvm::sys::fs::status(File->path(), StatBuf))
76+
continue;
77+
78+
// If the file has been used recently enough, leave it there.
79+
time_t FileAccessTime = llvm::sys::toTimeT(StatBuf.getLastAccessedTime());
80+
if (CurrentTime - FileAccessTime <= PruneAfter)
81+
continue;
82+
83+
// Remove the file.
84+
llvm::sys::fs::remove(File->path());
85+
86+
// Remove the timestamp file.
87+
std::string TimpestampFilename = File->path() + ".timestamp";
88+
llvm::sys::fs::remove(TimpestampFilename);
89+
}
90+
91+
// If we removed all the files in the directory, remove the directory
92+
// itself.
93+
if (llvm::sys::fs::directory_iterator(Dir->path(), EC) ==
94+
llvm::sys::fs::directory_iterator() &&
95+
!EC)
96+
llvm::sys::fs::remove(Dir->path());
97+
}
98+
}
99+
19100
namespace {
20101
class CrossProcessModuleCache : public ModuleCache {
21102
InMemoryModuleCache InMemory;
@@ -53,6 +134,11 @@ class CrossProcessModuleCache : public ModuleCache {
53134
OS.clear_error(); // Avoid triggering a fatal error.
54135
}
55136

137+
void maybePrune(StringRef Path, time_t PruneInterval,
138+
time_t PruneAfter) override {
139+
maybePruneImpl(Path, PruneInterval, PruneAfter);
140+
}
141+
56142
InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
57143
const InMemoryModuleCache &getInMemoryModuleCache() const override {
58144
return InMemory;

clang/lib/Tooling/DependencyScanning/InProcessModuleCache.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ class InProcessModuleCache : public ModuleCache {
100100
Timestamp.store(llvm::sys::toTimeT(std::chrono::system_clock::now()));
101101
}
102102

103+
void maybePrune(StringRef Path, time_t PruneInterval,
104+
time_t PruneAfter) override {
105+
// FIXME: This only needs to be ran once per build, not in every
106+
// compilation. Call it once per service.
107+
maybePruneImpl(Path, PruneInterval, PruneAfter);
108+
}
109+
103110
InMemoryModuleCache &getInMemoryModuleCache() override { return InMemory; }
104111
const InMemoryModuleCache &getInMemoryModuleCache() const override {
105112
return InMemory;

0 commit comments

Comments
 (0)