Skip to content

Commit d19cfe7

Browse files
committed
[CodeCompletion] Give up fast-completion if dependent files are modified
Check if dependencies are modified since the last checking. Dependencies: - Other source files in the current module - Dependent files collected by the dependency tracker When: - If the last dependency check was over N (defaults to 5) seconds ago Invalidate if: - The dependency file is missing - The modification time of the dependecy is greater than the last check - If the modification time is zero, compare the content using the file system from the previous completion and the current completion rdar://problem/62336432 (cherry picked from commit 05a87e8)
1 parent ec57300 commit d19cfe7

File tree

29 files changed

+527
-18
lines changed

29 files changed

+527
-18
lines changed

include/swift/IDE/CompletionInstance.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "llvm/ADT/IntrusiveRefCntPtr.h"
1919
#include "llvm/ADT/SmallString.h"
2020
#include "llvm/ADT/StringRef.h"
21+
#include "llvm/Support/Chrono.h"
2122
#include "llvm/Support/MemoryBuffer.h"
2223
#include "llvm/Support/VirtualFileSystem.h"
2324

@@ -38,20 +39,28 @@ makeCodeCompletionMemoryBuffer(const llvm::MemoryBuffer *origBuf,
3839
/// Manages \c CompilerInstance for completion like operations.
3940
class CompletionInstance {
4041
unsigned MaxASTReuseCount = 100;
42+
unsigned DependencyCheckIntervalSecond = 5;
4143

4244
std::mutex mtx;
4345

4446
std::unique_ptr<CompilerInstance> CachedCI;
4547
llvm::hash_code CachedArgHash;
48+
llvm::sys::TimePoint<> DependencyCheckedTimestamp;
4649
unsigned CachedReuseCount = 0;
4750

51+
void cacheCompilerInstance(std::unique_ptr<CompilerInstance> CI,
52+
llvm::hash_code ArgsHash);
53+
54+
bool shouldCheckDependencies() const;
55+
4856
/// Calls \p Callback with cached \c CompilerInstance if it's usable for the
4957
/// specified completion request.
5058
/// Returns \c if the callback was called. Returns \c false if the compiler
5159
/// argument has changed, primary file is not the same, the \c Offset is not
5260
/// in function bodies, or the interface hash of the file has changed.
5361
bool performCachedOperationIfPossible(
5462
const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
63+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
5564
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
5665
DiagnosticConsumer *DiagC,
5766
llvm::function_ref<void(CompilerInstance &, bool)> Callback);
@@ -69,6 +78,8 @@ class CompletionInstance {
6978
llvm::function_ref<void(CompilerInstance &, bool)> Callback);
7079

7180
public:
81+
void setDependencyCheckIntervalSecond(unsigned Value);
82+
7283
/// Calls \p Callback with a \c CompilerInstance which is prepared for the
7384
/// second pass. \p Callback is resposible to perform the second pass on it.
7485
/// The \c CompilerInstance may be reused from the previous completions,

lib/IDE/CompletionInstance.cpp

Lines changed: 116 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "swift/AST/Module.h"
1919
#include "swift/AST/PrettyStackTrace.h"
2020
#include "swift/AST/SourceFile.h"
21+
#include "swift/Serialization/SerializedModuleLoader.h"
22+
#include "swift/ClangImporter/ClangModule.h"
2123
#include "swift/Basic/LangOptions.h"
2224
#include "swift/Basic/PrettyStackTrace.h"
2325
#include "swift/Basic/SourceManager.h"
@@ -28,6 +30,7 @@
2830
#include "swift/Subsystems.h"
2931
#include "llvm/ADT/Hashing.h"
3032
#include "llvm/Support/MemoryBuffer.h"
33+
#include "clang/AST/ASTContext.h"
3134

3235
using namespace swift;
3336
using namespace ide;
@@ -162,10 +165,77 @@ static DeclContext *getEquivalentDeclContextFromSourceFile(DeclContext *DC,
162165
return newDC;
163166
}
164167

168+
/// Check if any dependent files are modified since \p timestamp.
169+
bool areAnyDependentFilesInvalidated(CompilerInstance &CI,
170+
llvm::vfs::FileSystem &FS,
171+
StringRef currentFileName,
172+
llvm::sys::TimePoint<> timestamp) {
173+
174+
auto isInvalidated = [&](StringRef filePath) -> bool {
175+
auto stat = FS.status(filePath);
176+
if (!stat)
177+
// Missing.
178+
return true;
179+
180+
auto lastModTime = stat->getLastModificationTime();
181+
if (lastModTime > timestamp)
182+
// Modified.
183+
return true;
184+
185+
// If the last modification time is zero, this file is probably from a
186+
// virtual file system. We need to check the content.
187+
if (lastModTime == llvm::sys::TimePoint<>()) {
188+
if (&CI.getFileSystem() == &FS)
189+
return false;
190+
191+
auto oldContent = CI.getFileSystem().getBufferForFile(filePath);
192+
auto newContent = FS.getBufferForFile(filePath);
193+
if (!oldContent || !newContent)
194+
// (unreachable?)
195+
return true;
196+
197+
if (oldContent.get()->getBuffer() != newContent.get()->getBuffer())
198+
// Different content.
199+
return true;
200+
}
201+
202+
return false;
203+
};
204+
205+
// Check files in the current module.
206+
for (FileUnit *file : CI.getMainModule()->getFiles()) {
207+
StringRef filename;
208+
if (auto SF = dyn_cast<SourceFile>(file))
209+
filename = SF->getFilename();
210+
else if (auto LF = dyn_cast<LoadedFile>(file))
211+
filename = LF->getFilename();
212+
else
213+
continue;
214+
215+
// Ignore the current file and synthesized files.
216+
if (filename.empty() || filename.front() == '<' ||
217+
filename.equals(currentFileName))
218+
continue;
219+
220+
if (isInvalidated(filename))
221+
return true;
222+
}
223+
224+
// Check other non-system depenencies (e.g. modules, headers).
225+
for (auto &dep : CI.getDependencyTracker()->getDependencies()) {
226+
if (isInvalidated(dep))
227+
return true;
228+
}
229+
230+
// All loaded module files are not modified since the timestamp.
231+
return false;
232+
}
233+
165234
} // namespace
166235

167236
bool CompletionInstance::performCachedOperationIfPossible(
168237
const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
238+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
169239
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
170240
DiagnosticConsumer *DiagC,
171241
llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
@@ -187,10 +257,17 @@ bool CompletionInstance::performCachedOperationIfPossible(
187257
auto &oldInfo = oldState->getCodeCompletionDelayedDeclState();
188258

189259
auto &SM = CI.getSourceMgr();
190-
if (SM.getIdentifierForBuffer(SM.getCodeCompletionBufferID()) !=
191-
completionBuffer->getBufferIdentifier())
260+
auto bufferName = completionBuffer->getBufferIdentifier();
261+
if (SM.getIdentifierForBuffer(SM.getCodeCompletionBufferID()) != bufferName)
192262
return false;
193263

264+
if (shouldCheckDependencies()) {
265+
if (areAnyDependentFilesInvalidated(CI, *FileSystem, bufferName,
266+
DependencyCheckedTimestamp))
267+
return false;
268+
DependencyCheckedTimestamp = std::chrono::system_clock::now();
269+
}
270+
194271
// Parse the new buffer into temporary SourceFile.
195272
SourceManager tmpSM;
196273
auto tmpBufferID = tmpSM.addMemBufferCopy(completionBuffer);
@@ -265,8 +342,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
265342
completionBuffer->getBuffer().slice(startOffset, endOffset);
266343
auto newOffset = Offset - startOffset;
267344

268-
newBufferID = SM.addMemBufferCopy(sourceText,
269-
completionBuffer->getBufferIdentifier());
345+
newBufferID = SM.addMemBufferCopy(sourceText, bufferName);
270346
SM.openVirtualFile(SM.getLocForBufferStart(newBufferID),
271347
tmpSM.getDisplayNameForLoc(startLoc),
272348
tmpSM.getLineAndColumn(startLoc).first - 1);
@@ -312,8 +388,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
312388
endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID);
313389
sourceText = sourceText.slice(0, endOffset);
314390
}
315-
newBufferID = SM.addMemBufferCopy(sourceText,
316-
completionBuffer->getBufferIdentifier());
391+
newBufferID = SM.addMemBufferCopy(sourceText, bufferName);
317392
SM.setCodeCompletionPoint(newBufferID, Offset);
318393

319394
// Create a new module and a source file using the current AST context.
@@ -372,7 +447,15 @@ bool CompletionInstance::performNewOperation(
372447
llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
373448
llvm::PrettyStackTraceString trace("While performing new completion");
374449

450+
auto isCachedCompletionRequested = ArgsHash.hasValue();
451+
375452
auto TheInstance = std::make_unique<CompilerInstance>();
453+
454+
// Track dependencies in fast-completion mode to invalidate the compiler
455+
// instance if any dependent files are modified.
456+
if (isCachedCompletionRequested)
457+
TheInstance->createDependencyTracker(false);
458+
376459
{
377460
auto &CI = *TheInstance;
378461
if (DiagC)
@@ -410,15 +493,34 @@ bool CompletionInstance::performNewOperation(
410493
Callback(CI, /*reusingASTContext=*/false);
411494
}
412495

413-
if (ArgsHash.hasValue()) {
414-
CachedCI = std::move(TheInstance);
415-
CachedArgHash = *ArgsHash;
416-
CachedReuseCount = 0;
417-
}
496+
// Cache the compiler instance if fast completion is enabled.
497+
if (isCachedCompletionRequested)
498+
cacheCompilerInstance(std::move(TheInstance), *ArgsHash);
418499

419500
return true;
420501
}
421502

503+
void CompletionInstance::cacheCompilerInstance(
504+
std::unique_ptr<CompilerInstance> CI, llvm::hash_code ArgsHash) {
505+
CachedCI = std::move(CI);
506+
CachedArgHash = ArgsHash;
507+
auto now = std::chrono::system_clock::now();
508+
DependencyCheckedTimestamp = now;
509+
CachedReuseCount = 0;
510+
}
511+
512+
bool CompletionInstance::shouldCheckDependencies() const {
513+
assert(CachedCI);
514+
using namespace std::chrono;
515+
auto now = system_clock::now();
516+
return DependencyCheckedTimestamp + seconds(DependencyCheckIntervalSecond) < now;
517+
}
518+
519+
void CompletionInstance::setDependencyCheckIntervalSecond(unsigned Value) {
520+
std::lock_guard<std::mutex> lock(mtx);
521+
DependencyCheckIntervalSecond = Value;
522+
}
523+
422524
bool swift::ide::CompletionInstance::performOperation(
423525
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
424526
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
@@ -454,8 +556,9 @@ bool swift::ide::CompletionInstance::performOperation(
454556
// the cached completion instance.
455557
std::lock_guard<std::mutex> lock(mtx);
456558

457-
if (performCachedOperationIfPossible(Invocation, ArgsHash, completionBuffer,
458-
Offset, DiagC, Callback))
559+
if (performCachedOperationIfPossible(Invocation, ArgsHash, FileSystem,
560+
completionBuffer, Offset, DiagC,
561+
Callback))
459562
return true;
460563

461564
if (performNewOperation(ArgsHash, Invocation, FileSystem, completionBuffer,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
#import <ClangFW/Funcs.h>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#ifndef CLANGFW_FUNCS_H
2+
#define CLANGFW_FUNCS_H
3+
int clangFWFunc();
4+
#endif
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
framework module ClangFW {
2+
umbrella header "ClangFW.h"
3+
export *
4+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#ifndef CLANGFW_FUNCS_H
2+
#define CLANGFW_FUNCS_H
3+
int clangFWFunc_mod();
4+
#endif
5+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#import "LocalCFunc.h"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
func localSwiftFunc() -> Int {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#ifndef MYPROJECT_LOCALCFUNC_H
2+
#define MYPROJECT_LOCALCFUNC_H
3+
4+
int localClangFunc();
5+
6+
#endif
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
func localSwiftFunc_mod() -> Int {}

0 commit comments

Comments
 (0)