Skip to content

Commit 55323ca

Browse files
authored
[clang][deps] Only bypass scanning VFS for the module cache (#88800)
The scanning VFS doesn't cache stat failures of paths with no extension. This was originally implemented to avoid caching the non-existence of the modules cache directory that the modular scanner will eventually create if it does not exist. However, this prevents caching of the non-existence of all directories and notably also header files from the standard C++ library, which can lead to sub-par performance. This patch adds an API to the scanning VFS that allows clients to configure path prefix for which to bypass the scanning VFS and use the underlying VFS directly.
1 parent 17dc43d commit 55323ca

File tree

4 files changed

+72
-21
lines changed

4 files changed

+72
-21
lines changed

clang/include/clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,12 @@ class DependencyScanningWorkerFilesystem
353353

354354
std::error_code setCurrentWorkingDirectory(const Twine &Path) override;
355355

356+
/// Make it so that no paths bypass this VFS.
357+
void resetBypassedPathPrefix() { BypassedPathPrefix.reset(); }
358+
/// Set the prefix for paths that should bypass this VFS and go straight to
359+
/// the underlying VFS.
360+
void setBypassedPathPrefix(StringRef Prefix) { BypassedPathPrefix = Prefix; }
361+
356362
/// Returns entry for the given filename.
357363
///
358364
/// Attempts to use the local and shared caches first, then falls back to
@@ -450,12 +456,19 @@ class DependencyScanningWorkerFilesystem
450456
getUnderlyingFS().print(OS, Type, IndentLevel + 1);
451457
}
452458

459+
/// Whether this path should bypass this VFS and go straight to the underlying
460+
/// VFS.
461+
bool shouldBypass(StringRef Path) const;
462+
453463
/// The global cache shared between worker threads.
454464
DependencyScanningFilesystemSharedCache &SharedCache;
455465
/// The local cache is used by the worker thread to cache file system queries
456466
/// locally instead of querying the global cache every time.
457467
DependencyScanningFilesystemLocalCache LocalCache;
458468

469+
/// Prefix of paths that should go straight to the underlying VFS.
470+
std::optional<std::string> BypassedPathPrefix;
471+
459472
/// The working directory to use for making relative paths absolute before
460473
/// using them for cache lookups.
461474
llvm::ErrorOr<std::string> WorkingDirForCacheLookup;

clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,8 @@ const CachedRealPath &DependencyScanningFilesystemSharedCache::CacheShard::
201201
return *StoredRealPath;
202202
}
203203

204-
static bool shouldCacheStatFailures(StringRef Filename) {
205-
StringRef Ext = llvm::sys::path::extension(Filename);
206-
if (Ext.empty())
207-
return false; // This may be the module cache directory.
208-
return true;
204+
bool DependencyScanningWorkerFilesystem::shouldBypass(StringRef Path) const {
205+
return BypassedPathPrefix && Path.starts_with(*BypassedPathPrefix);
209206
}
210207

211208
DependencyScanningWorkerFilesystem::DependencyScanningWorkerFilesystem(
@@ -244,8 +241,6 @@ DependencyScanningWorkerFilesystem::computeAndStoreResult(
244241
llvm::ErrorOr<llvm::vfs::Status> Stat =
245242
getUnderlyingFS().status(OriginalFilename);
246243
if (!Stat) {
247-
if (!shouldCacheStatFailures(OriginalFilename))
248-
return Stat.getError();
249244
const auto &Entry =
250245
getOrEmplaceSharedEntryForFilename(FilenameForLookup, Stat.getError());
251246
return insertLocalEntryForFilename(FilenameForLookup, Entry);
@@ -291,7 +286,7 @@ DependencyScanningWorkerFilesystem::status(const Twine &Path) {
291286
SmallString<256> OwnedFilename;
292287
StringRef Filename = Path.toStringRef(OwnedFilename);
293288

294-
if (Filename.ends_with(".pcm"))
289+
if (shouldBypass(Filename))
295290
return getUnderlyingFS().status(Path);
296291

297292
llvm::ErrorOr<EntryRef> Result = getOrCreateFileSystemEntry(Filename);
@@ -362,7 +357,7 @@ DependencyScanningWorkerFilesystem::openFileForRead(const Twine &Path) {
362357
SmallString<256> OwnedFilename;
363358
StringRef Filename = Path.toStringRef(OwnedFilename);
364359

365-
if (Filename.ends_with(".pcm"))
360+
if (shouldBypass(Filename))
366361
return getUnderlyingFS().openFileForRead(Path);
367362

368363
llvm::ErrorOr<EntryRef> Result = getOrCreateFileSystemEntry(Filename);
@@ -377,6 +372,9 @@ DependencyScanningWorkerFilesystem::getRealPath(const Twine &Path,
377372
SmallString<256> OwnedFilename;
378373
StringRef OriginalFilename = Path.toStringRef(OwnedFilename);
379374

375+
if (shouldBypass(OriginalFilename))
376+
return getUnderlyingFS().getRealPath(Path, Output);
377+
380378
SmallString<256> PathBuf;
381379
auto FilenameForLookup = tryGetFilenameForLookup(OriginalFilename, PathBuf);
382380
if (!FilenameForLookup)

clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,26 @@ class DependencyScanningAction : public tooling::ToolAction {
345345
ScanInstance.getInvocation(), ScanInstance.getDiagnostics(),
346346
DriverFileMgr->getVirtualFileSystemPtr());
347347

348+
// Use the dependency scanning optimized file system if requested to do so.
349+
if (DepFS) {
350+
StringRef ModulesCachePath =
351+
ScanInstance.getHeaderSearchOpts().ModuleCachePath;
352+
353+
DepFS->resetBypassedPathPrefix();
354+
if (!ModulesCachePath.empty())
355+
DepFS->setBypassedPathPrefix(ModulesCachePath);
356+
357+
ScanInstance.getPreprocessorOpts().DependencyDirectivesForFile =
358+
[LocalDepFS = DepFS](FileEntryRef File)
359+
-> std::optional<ArrayRef<dependency_directives_scan::Directive>> {
360+
if (llvm::ErrorOr<EntryRef> Entry =
361+
LocalDepFS->getOrCreateFileSystemEntry(File.getName()))
362+
if (LocalDepFS->ensureDirectiveTokensArePopulated(*Entry))
363+
return Entry->getDirectiveTokens();
364+
return std::nullopt;
365+
};
366+
}
367+
348368
// Create a new FileManager to match the invocation's FileSystemOptions.
349369
auto *FileMgr = ScanInstance.createFileManager(FS);
350370
ScanInstance.createSourceManager(*FileMgr);
@@ -361,18 +381,6 @@ class DependencyScanningAction : public tooling::ToolAction {
361381
PrebuiltModuleVFSMap, ScanInstance.getDiagnostics()))
362382
return false;
363383

364-
// Use the dependency scanning optimized file system if requested to do so.
365-
if (DepFS)
366-
ScanInstance.getPreprocessorOpts().DependencyDirectivesForFile =
367-
[LocalDepFS = DepFS](FileEntryRef File)
368-
-> std::optional<ArrayRef<dependency_directives_scan::Directive>> {
369-
if (llvm::ErrorOr<EntryRef> Entry =
370-
LocalDepFS->getOrCreateFileSystemEntry(File.getName()))
371-
if (LocalDepFS->ensureDirectiveTokensArePopulated(*Entry))
372-
return Entry->getDirectiveTokens();
373-
return std::nullopt;
374-
};
375-
376384
// Create the dependency collector that will collect the produced
377385
// dependencies.
378386
//

clang/unittests/Tooling/DependencyScanning/DependencyScanningFilesystemTest.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,35 @@ TEST(DependencyScanningFilesystem, CacheStatOnExists) {
174174
EXPECT_EQ(InstrumentingFS->NumStatusCalls, 2u);
175175
EXPECT_EQ(InstrumentingFS->NumExistsCalls, 0u);
176176
}
177+
178+
TEST(DependencyScanningFilesystem, CacheStatFailures) {
179+
auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
180+
InMemoryFS->setCurrentWorkingDirectory("/");
181+
InMemoryFS->addFile("/dir/vector", 0, llvm::MemoryBuffer::getMemBuffer(""));
182+
InMemoryFS->addFile("/cache/a.pcm", 0, llvm::MemoryBuffer::getMemBuffer(""));
183+
184+
auto InstrumentingFS =
185+
llvm::makeIntrusiveRefCnt<InstrumentingFilesystem>(InMemoryFS);
186+
187+
DependencyScanningFilesystemSharedCache SharedCache;
188+
DependencyScanningWorkerFilesystem DepFS(SharedCache, InstrumentingFS);
189+
190+
DepFS.status("/dir");
191+
DepFS.status("/dir");
192+
EXPECT_EQ(InstrumentingFS->NumStatusCalls, 1u);
193+
194+
DepFS.status("/dir/vector");
195+
DepFS.status("/dir/vector");
196+
EXPECT_EQ(InstrumentingFS->NumStatusCalls, 2u);
197+
198+
DepFS.setBypassedPathPrefix("/cache");
199+
DepFS.exists("/cache/a.pcm");
200+
EXPECT_EQ(InstrumentingFS->NumStatusCalls, 3u);
201+
DepFS.exists("/cache/a.pcm");
202+
EXPECT_EQ(InstrumentingFS->NumStatusCalls, 4u);
203+
204+
DepFS.resetBypassedPathPrefix();
205+
DepFS.exists("/cache/a.pcm");
206+
DepFS.exists("/cache/a.pcm");
207+
EXPECT_EQ(InstrumentingFS->NumStatusCalls, 5u);
208+
}

0 commit comments

Comments
 (0)