Skip to content

Commit 377a05a

Browse files
authored
Merge pull request #41081 from rintaro/sourcekit-compile-dependency
[SourceKit] Add automatic dependency check to 'compile' request
2 parents 8d709fd + 6424135 commit 377a05a

File tree

7 files changed

+239
-126
lines changed

7 files changed

+239
-126
lines changed

include/swift/IDE/CompileInstance.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,20 @@ class CompileInstance {
3636

3737
struct Options {
3838
unsigned MaxASTReuseCount = 100;
39+
unsigned DependencyCheckIntervalSecond = 5;
3940
} Opts;
4041

4142
std::mutex mtx;
4243

4344
std::unique_ptr<CompilerInstance> CI;
4445
llvm::hash_code CachedArgHash;
4546
std::atomic<bool> CachedCIInvalidated;
47+
llvm::sys::TimePoint<> DependencyCheckedTimestamp;
48+
llvm::StringMap<llvm::hash_code> InMemoryDependencyHash;
4649
unsigned CachedReuseCount;
4750

51+
bool shouldCheckDependencies() const;
52+
4853
/// Perform cached sema. Returns \c true if the CI is not reusable.
4954
bool performCachedSemaIfPossible(DiagnosticConsumer *DiagC);
5055

@@ -54,8 +59,8 @@ class CompileInstance {
5459
DiagnosticConsumer *DiagC);
5560

5661
/// Perform Parse and Sema, potentially CI from previous compilation is
57-
/// reused.
58-
void performSema(llvm::ArrayRef<const char *> Args,
62+
/// reused. Returns \c true if there was any error.
63+
bool performSema(llvm::ArrayRef<const char *> Args,
5964
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
6065
DiagnosticConsumer *DiagC,
6166
std::shared_ptr<std::atomic<bool>> CancellationFlag);

lib/IDE/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_swift_host_library(swiftIDE STATIC
88
CompileInstance.cpp
99
CompletionInstance.cpp
1010
ConformingMethodList.cpp
11+
DependencyChecking.cpp
1112
ExprContextAnalysis.cpp
1213
Formatting.cpp
1314
FuzzyStringMatcher.cpp

lib/IDE/CompileInstance.cpp

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "swift/IDE/CompileInstance.h"
1414

15+
#include "DependencyChecking.h"
1516
#include "swift/AST/ASTContext.h"
1617
#include "swift/AST/DiagnosticEngine.h"
1718
#include "swift/AST/Module.h"
@@ -204,6 +205,16 @@ bool CompileInstance::performCachedSemaIfPossible(DiagnosticConsumer *DiagC) {
204205

205206
SourceManager &SM = CI->getSourceMgr();
206207
auto FS = SM.getFileSystem();
208+
209+
if (shouldCheckDependencies()) {
210+
if (areAnyDependentFilesInvalidated(*CI, *FS, /*excludeBufferID=*/None,
211+
DependencyCheckedTimestamp,
212+
InMemoryDependencyHash)) {
213+
return true;
214+
}
215+
DependencyCheckedTimestamp = std::chrono::system_clock::now();
216+
}
217+
207218
SourceManager tmpSM(FS);
208219

209220
// Collect modified function body.
@@ -230,9 +241,6 @@ bool CompileInstance::setupCI(
230241
llvm::ArrayRef<const char *> origArgs,
231242
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fileSystem,
232243
DiagnosticConsumer *diagC) {
233-
CI->addDiagnosticConsumer(diagC);
234-
SWIFT_DEFER { CI->removeDiagnosticConsumer(diagC); };
235-
236244
auto &Diags = CI->getDiags();
237245

238246
SmallVector<const char *, 16> args;
@@ -271,6 +279,11 @@ bool CompileInstance::setupCI(
271279
/// Declare the frontend to be used for multiple compilations.
272280
invocation.getFrontendOptions().ReuseFrontendForMutipleCompilations = true;
273281

282+
// Enable dependency trakcing (excluding system modules) to invalidate the
283+
// compiler instance if any dependent files are modified.
284+
invocation.getFrontendOptions().IntermoduleDependencyTracking =
285+
IntermoduleDepTrackingMode::ExcludeSystem;
286+
274287
std::string InstanceSetupError;
275288
if (CI->setup(invocation, InstanceSetupError)) {
276289
assert(Diags.hadAnyError());
@@ -280,7 +293,7 @@ bool CompileInstance::setupCI(
280293
return true;
281294
}
282295

283-
void CompileInstance::performSema(
296+
bool CompileInstance::performSema(
284297
llvm::ArrayRef<const char *> Args,
285298
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fileSystem,
286299
DiagnosticConsumer *DiagC,
@@ -297,30 +310,33 @@ void CompileInstance::performSema(
297310
if (!performCachedSemaIfPossible(DiagC)) {
298311
// If we compileted cacehd Sema operation. We're done.
299312
++CachedReuseCount;
300-
return;
313+
return CI->getDiags().hadAnyError();
301314
}
302315
}
303316

304317
// Performing a new operation. Reset the compiler instance.
305-
CachedArgHash = hash_code();
306-
CachedReuseCount = 0;
307318
CI = std::make_unique<CompilerInstance>();
319+
CI->addDiagnosticConsumer(DiagC);
308320

309321
if (!setupCI(Args, fileSystem, DiagC)) {
310322
// Failed to setup the CI.
311323
CI.reset();
312-
return;
324+
return true;
313325
}
314326

315-
CI->addDiagnosticConsumer(DiagC);
316-
SWIFT_DEFER { CI->removeDiagnosticConsumer(DiagC); };
317-
318-
// CI is potentially reusable.
327+
// Remember cache related information.
328+
DependencyCheckedTimestamp = std::chrono::system_clock::now();
319329
CachedArgHash = ArgsHash;
330+
CachedReuseCount = 0;
331+
InMemoryDependencyHash.clear();
332+
cacheDependencyHashIfNeeded(*CI, /*excludeBufferID=*/None,
333+
InMemoryDependencyHash);
320334

321335
// Perform!
322336
CI->getASTContext().CancellationFlag = CancellationFlag;
323337
CI->performSema();
338+
CI->removeDiagnosticConsumer(DiagC);
339+
return CI->getDiags().hadAnyError();
324340
}
325341

326342
bool CompileInstance::performCompile(
@@ -334,14 +350,24 @@ bool CompileInstance::performCompile(
334350
if (CancellationFlag && CancellationFlag->load(std::memory_order_relaxed))
335351
return true;
336352

337-
performSema(Args, fileSystem, DiagC, CancellationFlag);
338-
if (CI->getDiags().hadAnyError())
353+
if (performSema(Args, fileSystem, DiagC, CancellationFlag))
339354
return true;
340355

341356
// Cancellation check after Sema.
342357
if (CI->isCancellationRequested())
343358
return true;
344359

360+
CI->addDiagnosticConsumer(DiagC);
361+
SWIFT_DEFER { CI->removeDiagnosticConsumer(DiagC); };
345362
int ReturnValue = 0;
346363
return performCompileStepsPostSema(*CI, ReturnValue, /*observer=*/nullptr);
347364
}
365+
366+
bool CompileInstance::shouldCheckDependencies() const {
367+
assert(CI);
368+
using namespace std::chrono;
369+
auto now = system_clock::now();
370+
auto threshold =
371+
DependencyCheckedTimestamp + seconds(Opts.DependencyCheckIntervalSecond);
372+
return threshold <= now;
373+
}

lib/IDE/CompletionInstance.cpp

Lines changed: 1 addition & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "swift/IDE/CompletionInstance.h"
1414

15+
#include "DependencyChecking.h"
1516
#include "swift/AST/ASTContext.h"
1617
#include "swift/AST/DiagnosticEngine.h"
1718
#include "swift/AST/DiagnosticsFrontend.h"
@@ -170,115 +171,6 @@ static DeclContext *getEquivalentDeclContextFromSourceFile(DeclContext *DC,
170171
return newDC;
171172
}
172173

173-
/// For each dependency file in \p CI, run \p callback until the callback
174-
/// returns \c true. Returns \c true if any callback call returns \c true, \c
175-
/// false otherwise.
176-
static bool
177-
forEachDependencyUntilTrue(CompilerInstance &CI, unsigned excludeBufferID,
178-
llvm::function_ref<bool(StringRef)> callback) {
179-
// Check files in the current module.
180-
for (FileUnit *file : CI.getMainModule()->getFiles()) {
181-
StringRef filename;
182-
if (auto SF = dyn_cast<SourceFile>(file)) {
183-
if (SF->getBufferID() == excludeBufferID)
184-
continue;
185-
filename = SF->getFilename();
186-
} else if (auto LF = dyn_cast<LoadedFile>(file))
187-
filename = LF->getFilename();
188-
else
189-
continue;
190-
191-
// Ignore synthesized files.
192-
if (filename.empty() || filename.front() == '<')
193-
continue;
194-
195-
if (callback(filename))
196-
return true;
197-
}
198-
199-
// Check other non-system depenencies (e.g. modules, headers).
200-
for (auto &dep : CI.getDependencyTracker()->getDependencies()) {
201-
if (callback(dep))
202-
return true;
203-
}
204-
for (auto dep : CI.getDependencyTracker()->getIncrementalDependencyPaths()) {
205-
if (callback(dep))
206-
return true;
207-
}
208-
209-
return false;
210-
}
211-
212-
/// Collect hash codes of the dependencies into \c Map.
213-
static void cacheDependencyHashIfNeeded(CompilerInstance &CI,
214-
unsigned excludeBufferID,
215-
llvm::StringMap<llvm::hash_code> &Map) {
216-
auto &FS = CI.getFileSystem();
217-
forEachDependencyUntilTrue(
218-
CI, excludeBufferID, [&](StringRef filename) {
219-
if (Map.count(filename))
220-
return false;
221-
222-
auto stat = FS.status(filename);
223-
if (!stat)
224-
return false;
225-
226-
// We will check the hash only if the modification time of the dependecy
227-
// is zero. See 'areAnyDependentFilesInvalidated() below'.
228-
if (stat->getLastModificationTime() != llvm::sys::TimePoint<>())
229-
return false;
230-
231-
auto buf = FS.getBufferForFile(filename);
232-
Map[filename] = llvm::hash_value(buf.get()->getBuffer());
233-
return false;
234-
});
235-
}
236-
237-
/// Check if any dependent files are modified since \p timestamp.
238-
static bool areAnyDependentFilesInvalidated(
239-
CompilerInstance &CI, llvm::vfs::FileSystem &FS,
240-
unsigned excludeBufferID, llvm::sys::TimePoint<> timestamp,
241-
llvm::StringMap<llvm::hash_code> &Map) {
242-
243-
return forEachDependencyUntilTrue(
244-
CI, excludeBufferID, [&](StringRef filePath) {
245-
auto stat = FS.status(filePath);
246-
if (!stat)
247-
// Missing.
248-
return true;
249-
250-
auto lastModTime = stat->getLastModificationTime();
251-
if (lastModTime > timestamp)
252-
// Modified.
253-
return true;
254-
255-
// If the last modification time is zero, this file is probably from a
256-
// virtual file system. We need to check the content.
257-
if (lastModTime == llvm::sys::TimePoint<>()) {
258-
// Get the hash code of the last content.
259-
auto oldHashEntry = Map.find(filePath);
260-
if (oldHashEntry == Map.end())
261-
// Unreachable? Not virtual in old filesystem, but virtual in new
262-
// one.
263-
return true;
264-
auto oldHash = oldHashEntry->second;
265-
266-
// Calculate the hash code of the current content.
267-
auto newContent = FS.getBufferForFile(filePath);
268-
if (!newContent)
269-
// Unreachable? stat succeeded, but coundn't get the content.
270-
return true;
271-
272-
auto newHash = llvm::hash_value(newContent.get()->getBuffer());
273-
274-
if (oldHash != newHash)
275-
return true;
276-
}
277-
278-
return false;
279-
});
280-
}
281-
282174
} // namespace
283175

284176
bool CompletionInstance::performCachedOperationIfPossible(

0 commit comments

Comments
 (0)