Skip to content

Commit b768a8c

Browse files
authored
[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
1 parent 5d18789 commit b768a8c

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
@@ -242,6 +242,8 @@ class EntryRef {
242242
/// The underlying cached entry.
243243
const CachedFileSystemEntry &Entry;
244244

245+
friend class DependencyScanningWorkerFilesystem;
246+
245247
public:
246248
EntryRef(StringRef Name, const CachedFileSystemEntry &Entry)
247249
: Filename(Name), Entry(Entry) {}
@@ -300,14 +302,15 @@ class DependencyScanningWorkerFilesystem
300302
///
301303
/// Attempts to use the local and shared caches first, then falls back to
302304
/// using the underlying filesystem.
303-
llvm::ErrorOr<EntryRef>
304-
getOrCreateFileSystemEntry(StringRef Filename,
305-
bool DisableDirectivesScanning = false);
305+
llvm::ErrorOr<EntryRef> getOrCreateFileSystemEntry(StringRef Filename);
306306

307-
private:
308-
/// Check whether the file should be scanned for preprocessor directives.
309-
bool shouldScanForDirectives(StringRef Filename);
307+
/// Ensure the directive tokens are populated for this file entry.
308+
///
309+
/// Returns true if the directive tokens are populated for this file entry,
310+
/// false if not (i.e. this entry is not a file or its scan fails).
311+
bool ensureDirectiveTokensArePopulated(EntryRef Entry);
310312

313+
private:
311314
/// For a filename that's not yet associated with any entry in the caches,
312315
/// uses the underlying filesystem to either look up the entry based in the
313316
/// shared cache indexed by unique ID, or creates new entry from scratch.
@@ -317,11 +320,6 @@ class DependencyScanningWorkerFilesystem
317320
computeAndStoreResult(StringRef OriginalFilename,
318321
StringRef FilenameForLookup);
319322

320-
/// Scan for preprocessor directives for the given entry if necessary and
321-
/// returns a wrapper object with reference semantics.
322-
EntryRef scanForDirectivesIfNecessary(const CachedFileSystemEntry &Entry,
323-
StringRef Filename, bool Disable);
324-
325323
/// Represents a filesystem entry that has been stat-ed (and potentially read)
326324
/// and that's about to be inserted into the cache as `CachedFileSystemEntry`.
327325
struct TentativeEntry {

clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp

+16-47
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,25 @@ DependencyScanningWorkerFilesystem::readFile(StringRef Filename) {
4141
return TentativeEntry(Stat, std::move(Buffer));
4242
}
4343

44-
EntryRef DependencyScanningWorkerFilesystem::scanForDirectivesIfNecessary(
45-
const CachedFileSystemEntry &Entry, StringRef Filename, bool Disable) {
46-
if (Entry.isError() || Entry.isDirectory() || Disable ||
47-
!shouldScanForDirectives(Filename))
48-
return EntryRef(Filename, Entry);
44+
bool DependencyScanningWorkerFilesystem::ensureDirectiveTokensArePopulated(
45+
EntryRef Ref) {
46+
auto &Entry = Ref.Entry;
47+
48+
if (Entry.isError() || Entry.isDirectory())
49+
return false;
4950

5051
CachedFileContents *Contents = Entry.getCachedContents();
5152
assert(Contents && "contents not initialized");
5253

5354
// Double-checked locking.
5455
if (Contents->DepDirectives.load())
55-
return EntryRef(Filename, Entry);
56+
return true;
5657

5758
std::lock_guard<std::mutex> GuardLock(Contents->ValueLock);
5859

5960
// Double-checked locking.
6061
if (Contents->DepDirectives.load())
61-
return EntryRef(Filename, Entry);
62+
return true;
6263

6364
SmallVector<dependency_directives_scan::Directive, 64> Directives;
6465
// Scan the file for preprocessor directives that might affect the
@@ -69,16 +70,16 @@ EntryRef DependencyScanningWorkerFilesystem::scanForDirectivesIfNecessary(
6970
Contents->DepDirectiveTokens.clear();
7071
// FIXME: Propagate the diagnostic if desired by the client.
7172
Contents->DepDirectives.store(new std::optional<DependencyDirectivesTy>());
72-
return EntryRef(Filename, Entry);
73+
return false;
7374
}
7475

7576
// This function performed double-checked locking using `DepDirectives`.
7677
// Assigning it must be the last thing this function does, otherwise other
77-
// threads may skip the
78-
// critical section (`DepDirectives != nullptr`), leading to a data race.
78+
// threads may skip the critical section (`DepDirectives != nullptr`), leading
79+
// to a data race.
7980
Contents->DepDirectives.store(
8081
new std::optional<DependencyDirectivesTy>(std::move(Directives)));
81-
return EntryRef(Filename, Entry);
82+
return true;
8283
}
8384

8485
DependencyScanningFilesystemSharedCache::
@@ -161,34 +162,11 @@ DependencyScanningFilesystemSharedCache::CacheShard::
161162
return *EntriesByFilename.insert({Filename, &Entry}).first->getValue();
162163
}
163164

164-
/// Whitelist file extensions that should be minimized, treating no extension as
165-
/// a source file that should be minimized.
166-
///
167-
/// This is kinda hacky, it would be better if we knew what kind of file Clang
168-
/// was expecting instead.
169-
static bool shouldScanForDirectivesBasedOnExtension(StringRef Filename) {
170-
StringRef Ext = llvm::sys::path::extension(Filename);
171-
if (Ext.empty())
172-
return true; // C++ standard library
173-
return llvm::StringSwitch<bool>(Ext)
174-
.CasesLower(".c", ".cc", ".cpp", ".c++", ".cxx", true)
175-
.CasesLower(".h", ".hh", ".hpp", ".h++", ".hxx", true)
176-
.CasesLower(".m", ".mm", true)
177-
.CasesLower(".i", ".ii", ".mi", ".mmi", true)
178-
.CasesLower(".def", ".inc", true)
179-
.Default(false);
180-
}
181-
182165
static bool shouldCacheStatFailures(StringRef Filename) {
183166
StringRef Ext = llvm::sys::path::extension(Filename);
184167
if (Ext.empty())
185168
return false; // This may be the module cache directory.
186-
// Only cache stat failures on files that are not expected to change during
187-
// the build.
188-
StringRef FName = llvm::sys::path::filename(Filename);
189-
if (FName == "module.modulemap" || FName == "module.map")
190-
return true;
191-
return shouldScanForDirectivesBasedOnExtension(Filename);
169+
return true;
192170
}
193171

194172
DependencyScanningWorkerFilesystem::DependencyScanningWorkerFilesystem(
@@ -201,11 +179,6 @@ DependencyScanningWorkerFilesystem::DependencyScanningWorkerFilesystem(
201179
updateWorkingDirForCacheLookup();
202180
}
203181

204-
bool DependencyScanningWorkerFilesystem::shouldScanForDirectives(
205-
StringRef Filename) {
206-
return shouldScanForDirectivesBasedOnExtension(Filename);
207-
}
208-
209182
const CachedFileSystemEntry &
210183
DependencyScanningWorkerFilesystem::getOrEmplaceSharedEntryForUID(
211184
TentativeEntry TEntry) {
@@ -259,7 +232,7 @@ DependencyScanningWorkerFilesystem::computeAndStoreResult(
259232

260233
llvm::ErrorOr<EntryRef>
261234
DependencyScanningWorkerFilesystem::getOrCreateFileSystemEntry(
262-
StringRef OriginalFilename, bool DisableDirectivesScanning) {
235+
StringRef OriginalFilename) {
263236
StringRef FilenameForLookup;
264237
SmallString<256> PathBuf;
265238
if (llvm::sys::path::is_absolute_gnu(OriginalFilename)) {
@@ -276,15 +249,11 @@ DependencyScanningWorkerFilesystem::getOrCreateFileSystemEntry(
276249
assert(llvm::sys::path::is_absolute_gnu(FilenameForLookup));
277250
if (const auto *Entry =
278251
findEntryByFilenameWithWriteThrough(FilenameForLookup))
279-
return scanForDirectivesIfNecessary(*Entry, OriginalFilename,
280-
DisableDirectivesScanning)
281-
.unwrapError();
252+
return EntryRef(OriginalFilename, *Entry).unwrapError();
282253
auto MaybeEntry = computeAndStoreResult(OriginalFilename, FilenameForLookup);
283254
if (!MaybeEntry)
284255
return MaybeEntry.getError();
285-
return scanForDirectivesIfNecessary(*MaybeEntry, OriginalFilename,
286-
DisableDirectivesScanning)
287-
.unwrapError();
256+
return EntryRef(OriginalFilename, *MaybeEntry).unwrapError();
288257
}
289258

290259
llvm::ErrorOr<llvm::vfs::Status>

clang/lib/Tooling/DependencyScanning/DependencyScanningWorker.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,8 @@ class DependencyScanningAction : public tooling::ToolAction {
372372
-> std::optional<ArrayRef<dependency_directives_scan::Directive>> {
373373
if (llvm::ErrorOr<EntryRef> Entry =
374374
LocalDepFS->getOrCreateFileSystemEntry(File.getName()))
375-
return Entry->getDirectiveTokens();
375+
if (LocalDepFS->ensureDirectiveTokensArePopulated(*Entry))
376+
return Entry->getDirectiveTokens();
376377
return std::nullopt;
377378
};
378379
}
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)