Skip to content

Commit 05a87e8

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
1 parent d9b1d8f commit 05a87e8

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);
@@ -263,8 +340,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
263340
completionBuffer->getBuffer().slice(startOffset, endOffset);
264341
auto newOffset = Offset - startOffset;
265342

266-
newBufferID = SM.addMemBufferCopy(sourceText,
267-
completionBuffer->getBufferIdentifier());
343+
newBufferID = SM.addMemBufferCopy(sourceText, bufferName);
268344
SM.openVirtualFile(SM.getLocForBufferStart(newBufferID),
269345
tmpSM.getDisplayNameForLoc(startLoc),
270346
tmpSM.getLineAndColumn(startLoc).first - 1);
@@ -310,8 +386,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
310386
endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID);
311387
sourceText = sourceText.slice(0, endOffset);
312388
}
313-
newBufferID = SM.addMemBufferCopy(sourceText,
314-
completionBuffer->getBufferIdentifier());
389+
newBufferID = SM.addMemBufferCopy(sourceText, bufferName);
315390
SM.setCodeCompletionPoint(newBufferID, Offset);
316391

317392
// Create a new module and a source file using the current AST context.
@@ -369,7 +444,15 @@ bool CompletionInstance::performNewOperation(
369444
llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
370445
llvm::PrettyStackTraceString trace("While performing new completion");
371446

447+
auto isCachedCompletionRequested = ArgsHash.hasValue();
448+
372449
auto TheInstance = std::make_unique<CompilerInstance>();
450+
451+
// Track dependencies in fast-completion mode to invalidate the compiler
452+
// instance if any dependent files are modified.
453+
if (isCachedCompletionRequested)
454+
TheInstance->createDependencyTracker(false);
455+
373456
{
374457
auto &CI = *TheInstance;
375458
if (DiagC)
@@ -407,15 +490,34 @@ bool CompletionInstance::performNewOperation(
407490
Callback(CI, /*reusingASTContext=*/false);
408491
}
409492

410-
if (ArgsHash.hasValue()) {
411-
CachedCI = std::move(TheInstance);
412-
CachedArgHash = *ArgsHash;
413-
CachedReuseCount = 0;
414-
}
493+
// Cache the compiler instance if fast completion is enabled.
494+
if (isCachedCompletionRequested)
495+
cacheCompilerInstance(std::move(TheInstance), *ArgsHash);
415496

416497
return true;
417498
}
418499

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

454-
if (performCachedOperationIfPossible(Invocation, ArgsHash, completionBuffer,
455-
Offset, DiagC, Callback))
556+
if (performCachedOperationIfPossible(Invocation, ArgsHash, FileSystem,
557+
completionBuffer, Offset, DiagC,
558+
Callback))
456559
return true;
457560

458561
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 {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#ifndef MYPROJECT_LOCALCFUNC_H
2+
#define MYPROJECT_LOCALCFUNC_H
3+
4+
int localClangFunc_mod();
5+
6+
#endif
7+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
public func swiftFWFunc() -> Int { return 1 }
2+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
public func swiftFWFunc_mod() -> Int { return 1 }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Foundation
2+
import ClangFW // < 500 headers framework
3+
func foo() {
4+
/* HERE */
5+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import ClangFW
2+
import SwiftFW
3+
4+
func foo() {
5+
/*HERE*/
6+
}
7+
8+
// RUN: %empty-directory(%t/Frameworks)
9+
// RUN: %empty-directory(%t/MyProject)
10+
11+
// RUN: COMPILER_ARGS=( \
12+
// RUN: -target %target-triple \
13+
// RUN: -module-name MyProject \
14+
// RUN: -F %t/Frameworks \
15+
// RUN: -I %t/MyProject \
16+
// RUN: -import-objc-header %t/MyProject/Bridging.h \
17+
// RUN: %t/MyProject/Library.swift \
18+
// RUN: %s \
19+
// RUN: )
20+
// RUN: INPUT_DIR=%S/Inputs/checkdeps
21+
// RUN: DEPCHECK_INTERVAL=1
22+
// RUN: SLEEP_TIME=2
23+
24+
// RUN: cp -R $INPUT_DIR/MyProject %t/
25+
// RUN: cp -R $INPUT_DIR/ClangFW.framework %t/Frameworks/
26+
// RUN: %empty-directory(%t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule)
27+
// RUN: %target-swift-frontend -emit-module -module-name SwiftFW -o %t/Frameworks/SwiftFW.framework/Modules/SwiftFW.swiftmodule/%target-swiftmodule-name $INPUT_DIR/SwiftFW_src/Funcs.swift
28+
29+
// RUN: %sourcekitd-test \
30+
// RUN: -req=global-config -completion-check-dependency-interval ${DEPCHECK_INTERVAL} == \
31+
32+
// RUN: -shell -- echo "### Initial" == \
33+
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} == \
34+
35+
// RUN: -shell -- echo '### Modify bridging header library file' == \
36+
// RUN: -shell -- cp -R $INPUT_DIR/MyProject_mod/LocalCFunc.h %t/MyProject/ == \
37+
// RUN: -shell -- sleep $SLEEP_TIME == \
38+
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} == \
39+
40+
// RUN: -shell -- echo '### Fast completion' == \
41+
// RUN: -shell -- sleep $SLEEP_TIME == \
42+
// RUN: -req=complete -pos=5:3 %s -- ${COMPILER_ARGS[@]} \
43+
44+
// RUN: | %FileCheck %s
45+
46+
47+
// CHECK-LABEL: ### Initial
48+
// CHECK: key.results: [
49+
// CHECK-DAG: key.description: "clangFWFunc()"
50+
// CHECK-DAG: key.description: "swiftFWFunc()"
51+
// CHECK-DAG: key.description: "localClangFunc()"
52+
// CHECK-DAG: key.description: "localSwiftFunc()"
53+
// CHECK: ]
54+
// CHECK-NOT: key.reusingastcontext: 1
55+
56+
// CHECK-LABEL: ### Modify bridging header library file
57+
// CHECK: key.results: [
58+
// CHECK-DAG: key.description: "clangFWFunc()"
59+
// CHECK-DAG: key.description: "swiftFWFunc()"
60+
// CHECK-DAG: key.description: "localClangFunc_mod()"
61+
// CHECK-DAG: key.description: "localSwiftFunc()"
62+
// CHECK: ]
63+
// CHECK-NOT: key.reusingastcontext: 1
64+
65+
// CHECK-LABEL: ### Fast completion
66+
// CHECK: key.results: [
67+
// CHECK-DAG: key.description: "clangFWFunc()"
68+
// CHECK-DAG: key.description: "swiftFWFunc()"
69+
// CHECK-DAG: key.description: "localClangFunc_mod()"
70+
// CHECK-DAG: key.description: "localSwiftFunc()"
71+
// CHECK: ]
72+
// CHECK: key.reusingastcontext: 1

0 commit comments

Comments
 (0)