Skip to content

Commit 027777b

Browse files
committed
[clang][deps] Lazy dependency directives (llvm#86347)
Since b4c83a1, `Preprocessor` and `Lexer` are aware of the concept of scanning dependency directives. This makes it possible to scan for them on-demand rather than eagerly on the first filesystem operation (open, or even just stat). This might improve performance, but is also necessary for the "PCH as module" mode. Some precompiled header sources use the ".pch" file extension, which means they were not getting scanned for dependency directives. This was okay when the PCH was the main input file in a separate scan step, because there we just lex the file in a scanning-specific frontend action. But when such source gets treated as a module implicitly loaded from a TU, it will get compiled as any other module - with Sema - which will result in compilation errors. (See attached test case.) rdar://107663951 (cherry picked from commit b768a8c)
1 parent b7b175a commit 027777b

File tree

4 files changed

+60
-59
lines changed

4 files changed

+60
-59
lines changed

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

+9-11
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ class EntryRef {
255255
/// The underlying cached entry.
256256
const CachedFileSystemEntry &Entry;
257257

258+
friend class DependencyScanningWorkerFilesystem;
259+
258260
public:
259261
EntryRef(StringRef Name, const CachedFileSystemEntry &Entry)
260262
: Filename(Name), Entry(Entry) {}
@@ -316,14 +318,15 @@ class DependencyScanningWorkerFilesystem
316318
///
317319
/// Attempts to use the local and shared caches first, then falls back to
318320
/// using the underlying filesystem.
319-
llvm::ErrorOr<EntryRef>
320-
getOrCreateFileSystemEntry(StringRef Filename,
321-
bool DisableDirectivesScanning = false);
321+
llvm::ErrorOr<EntryRef> getOrCreateFileSystemEntry(StringRef Filename);
322322

323-
private:
324-
/// Check whether the file should be scanned for preprocessor directives.
325-
bool shouldScanForDirectives(StringRef Filename);
323+
/// Ensure the directive tokens are populated for this file entry.
324+
///
325+
/// Returns true if the directive tokens are populated for this file entry,
326+
/// false if not (i.e. this entry is not a file or its scan fails).
327+
bool ensureDirectiveTokensArePopulated(EntryRef Entry);
326328

329+
private:
327330
/// For a filename that's not yet associated with any entry in the caches,
328331
/// uses the underlying filesystem to either look up the entry based in the
329332
/// shared cache indexed by unique ID, or creates new entry from scratch.
@@ -333,11 +336,6 @@ class DependencyScanningWorkerFilesystem
333336
computeAndStoreResult(StringRef OriginalFilename,
334337
StringRef FilenameForLookup);
335338

336-
/// Scan for preprocessor directives for the given entry if necessary and
337-
/// returns a wrapper object with reference semantics.
338-
EntryRef scanForDirectivesIfNecessary(const CachedFileSystemEntry &Entry,
339-
StringRef Filename, bool Disable);
340-
341339
/// Represents a filesystem entry that has been stat-ed (and potentially read)
342340
/// and that's about to be inserted into the cache as `CachedFileSystemEntry`.
343341
struct TentativeEntry {

clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp

+16-47
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,25 @@ DependencyScanningWorkerFilesystem::readFile(StringRef Filename) {
4646
return TentativeEntry(Stat, std::move(Buffer), std::move(CASContents));
4747
}
4848

49-
EntryRef DependencyScanningWorkerFilesystem::scanForDirectivesIfNecessary(
50-
const CachedFileSystemEntry &Entry, StringRef Filename, bool Disable) {
51-
if (Entry.isError() || Entry.isDirectory() || Disable ||
52-
!shouldScanForDirectives(Filename))
53-
return EntryRef(Filename, Entry);
49+
bool DependencyScanningWorkerFilesystem::ensureDirectiveTokensArePopulated(
50+
EntryRef Ref) {
51+
auto &Entry = Ref.Entry;
52+
53+
if (Entry.isError() || Entry.isDirectory())
54+
return false;
5455

5556
CachedFileContents *Contents = Entry.getCachedContents();
5657
assert(Contents && "contents not initialized");
5758

5859
// Double-checked locking.
5960
if (Contents->DepDirectives.load())
60-
return EntryRef(Filename, Entry);
61+
return true;
6162

6263
std::lock_guard<std::mutex> GuardLock(Contents->ValueLock);
6364

6465
// Double-checked locking.
6566
if (Contents->DepDirectives.load())
66-
return EntryRef(Filename, Entry);
67+
return true;
6768

6869
SmallVector<dependency_directives_scan::Directive, 64> Directives;
6970
// Scan the file for preprocessor directives that might affect the
@@ -74,16 +75,16 @@ EntryRef DependencyScanningWorkerFilesystem::scanForDirectivesIfNecessary(
7475
Contents->DepDirectiveTokens.clear();
7576
// FIXME: Propagate the diagnostic if desired by the client.
7677
Contents->DepDirectives.store(new std::optional<DependencyDirectivesTy>());
77-
return EntryRef(Filename, Entry);
78+
return false;
7879
}
7980

8081
// This function performed double-checked locking using `DepDirectives`.
8182
// Assigning it must be the last thing this function does, otherwise other
82-
// threads may skip the
83-
// critical section (`DepDirectives != nullptr`), leading to a data race.
83+
// threads may skip the critical section (`DepDirectives != nullptr`), leading
84+
// to a data race.
8485
Contents->DepDirectives.store(
8586
new std::optional<DependencyDirectivesTy>(std::move(Directives)));
86-
return EntryRef(Filename, Entry);
87+
return true;
8788
}
8889

8990
DependencyScanningFilesystemSharedCache::
@@ -167,34 +168,11 @@ DependencyScanningFilesystemSharedCache::CacheShard::
167168
return *EntriesByFilename.insert({Filename, &Entry}).first->getValue();
168169
}
169170

170-
/// Whitelist file extensions that should be minimized, treating no extension as
171-
/// a source file that should be minimized.
172-
///
173-
/// This is kinda hacky, it would be better if we knew what kind of file Clang
174-
/// was expecting instead.
175-
static bool shouldScanForDirectivesBasedOnExtension(StringRef Filename) {
176-
StringRef Ext = llvm::sys::path::extension(Filename);
177-
if (Ext.empty())
178-
return true; // C++ standard library
179-
return llvm::StringSwitch<bool>(Ext)
180-
.CasesLower(".c", ".cc", ".cpp", ".c++", ".cxx", true)
181-
.CasesLower(".h", ".hh", ".hpp", ".h++", ".hxx", true)
182-
.CasesLower(".m", ".mm", true)
183-
.CasesLower(".i", ".ii", ".mi", ".mmi", true)
184-
.CasesLower(".def", ".inc", true)
185-
.Default(false);
186-
}
187-
188171
static bool shouldCacheStatFailures(StringRef Filename) {
189172
StringRef Ext = llvm::sys::path::extension(Filename);
190173
if (Ext.empty())
191174
return false; // This may be the module cache directory.
192-
// Only cache stat failures on files that are not expected to change during
193-
// the build.
194-
StringRef FName = llvm::sys::path::filename(Filename);
195-
if (FName == "module.modulemap" || FName == "module.map")
196-
return true;
197-
return shouldScanForDirectivesBasedOnExtension(Filename);
175+
return true;
198176
}
199177

200178
DependencyScanningWorkerFilesystem::DependencyScanningWorkerFilesystem(
@@ -207,11 +185,6 @@ DependencyScanningWorkerFilesystem::DependencyScanningWorkerFilesystem(
207185
updateWorkingDirForCacheLookup();
208186
}
209187

210-
bool DependencyScanningWorkerFilesystem::shouldScanForDirectives(
211-
StringRef Filename) {
212-
return shouldScanForDirectivesBasedOnExtension(Filename);
213-
}
214-
215188
const CachedFileSystemEntry &
216189
DependencyScanningWorkerFilesystem::getOrEmplaceSharedEntryForUID(
217190
TentativeEntry TEntry) {
@@ -265,7 +238,7 @@ DependencyScanningWorkerFilesystem::computeAndStoreResult(
265238

266239
llvm::ErrorOr<EntryRef>
267240
DependencyScanningWorkerFilesystem::getOrCreateFileSystemEntry(
268-
StringRef OriginalFilename, bool DisableDirectivesScanning) {
241+
StringRef OriginalFilename) {
269242
StringRef FilenameForLookup;
270243
SmallString<256> PathBuf;
271244
if (llvm::sys::path::is_absolute_gnu(OriginalFilename)) {
@@ -282,15 +255,11 @@ DependencyScanningWorkerFilesystem::getOrCreateFileSystemEntry(
282255
assert(llvm::sys::path::is_absolute_gnu(FilenameForLookup));
283256
if (const auto *Entry =
284257
findEntryByFilenameWithWriteThrough(FilenameForLookup))
285-
return scanForDirectivesIfNecessary(*Entry, OriginalFilename,
286-
DisableDirectivesScanning)
287-
.unwrapError();
258+
return EntryRef(OriginalFilename, *Entry).unwrapError();
288259
auto MaybeEntry = computeAndStoreResult(OriginalFilename, FilenameForLookup);
289260
if (!MaybeEntry)
290261
return MaybeEntry.getError();
291-
return scanForDirectivesIfNecessary(*MaybeEntry, OriginalFilename,
292-
DisableDirectivesScanning)
293-
.unwrapError();
262+
return EntryRef(OriginalFilename, *MaybeEntry).unwrapError();
294263
}
295264

296265
llvm::ErrorOr<llvm::vfs::Status>

clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,8 @@ class DependencyScanningAction : public tooling::ToolAction {
507507
-> std::optional<ArrayRef<dependency_directives_scan::Directive>> {
508508
if (llvm::ErrorOr<EntryRef> Entry =
509509
LocalDepFS->getOrCreateFileSystemEntry(File.getName()))
510-
return Entry->getDirectiveTokens();
510+
if (LocalDepFS->ensureDirectiveTokensArePopulated(*Entry))
511+
return Entry->getDirectiveTokens();
511512
return std::nullopt;
512513
};
513514
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// RUN: rm -rf %t
2+
// RUN: split-file %s %t
3+
4+
// This test checks that source files with uncommon extensions still undergo
5+
// dependency directives scan. If header.pch would not and b.h would, the scan
6+
// would fail when parsing `void function(B)` and not knowing the symbol B.
7+
8+
//--- module.modulemap
9+
module __PCH { header "header.pch" }
10+
module B { header "b.h" }
11+
12+
//--- header.pch
13+
#include "b.h"
14+
void function(B);
15+
16+
//--- b.h
17+
typedef int B;
18+
19+
//--- tu.c
20+
int main() {
21+
function(0);
22+
return 0;
23+
}
24+
25+
//--- cdb.json.in
26+
[{
27+
"directory": "DIR",
28+
"file": "DIR/tu.c",
29+
"command": "clang -c DIR/tu.c -fmodules -fmodules-cache-path=DIR/cache -fimplicit-module-maps -include DIR/header.pch"
30+
}]
31+
32+
// RUN: sed -e "s|DIR|%/t|g" %t/cdb.json.in > %t/cdb.json
33+
// RUN: clang-scan-deps -compilation-database %t/cdb.json -format experimental-full > %t/deps.json

0 commit comments

Comments
 (0)