Skip to content

Commit 7cac40e

Browse files
authored
Merge pull request swiftlang#31547 from rintaro/5.3-ide-completion-fastcheckdep-rdar62336432
[5.3][CodeCompletion] Give up fast-completion if dependent files are modified
2 parents 96d6fcb + b00ef1b commit 7cac40e

File tree

33 files changed

+713
-19
lines changed

33 files changed

+713
-19
lines changed

include/swift/IDE/CompletionInstance.h

Lines changed: 13 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,30 @@ 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;
47+
ModuleDecl *CurrentModule = nullptr;
4548
llvm::hash_code CachedArgHash;
49+
llvm::sys::TimePoint<> DependencyCheckedTimestamp;
50+
llvm::StringMap<llvm::hash_code> InMemoryDependencyHash;
4651
unsigned CachedReuseCount = 0;
4752

53+
void cacheCompilerInstance(std::unique_ptr<CompilerInstance> CI,
54+
llvm::hash_code ArgsHash);
55+
56+
bool shouldCheckDependencies() const;
57+
4858
/// Calls \p Callback with cached \c CompilerInstance if it's usable for the
4959
/// specified completion request.
5060
/// Returns \c if the callback was called. Returns \c false if the compiler
5161
/// argument has changed, primary file is not the same, the \c Offset is not
5262
/// in function bodies, or the interface hash of the file has changed.
5363
bool performCachedOperationIfPossible(
5464
const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
65+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
5566
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
5667
DiagnosticConsumer *DiagC,
5768
llvm::function_ref<void(CompilerInstance &, bool)> Callback);
@@ -69,6 +80,8 @@ class CompletionInstance {
6980
llvm::function_ref<void(CompilerInstance &, bool)> Callback);
7081

7182
public:
83+
void setDependencyCheckIntervalSecond(unsigned Value);
84+
7285
/// Calls \p Callback with a \c CompilerInstance which is prepared for the
7386
/// second pass. \p Callback is resposible to perform the second pass on it.
7487
/// The \c CompilerInstance may be reused from the previous completions,

lib/IDE/CompletionInstance.cpp

Lines changed: 168 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
#include "swift/Basic/LangOptions.h"
2222
#include "swift/Basic/PrettyStackTrace.h"
2323
#include "swift/Basic/SourceManager.h"
24+
#include "swift/ClangImporter/ClangModule.h"
2425
#include "swift/Driver/FrontendUtil.h"
2526
#include "swift/Frontend/Frontend.h"
2627
#include "swift/Parse/Lexer.h"
2728
#include "swift/Parse/PersistentParserState.h"
29+
#include "swift/Serialization/SerializedModuleLoader.h"
2830
#include "swift/Subsystems.h"
31+
#include "clang/AST/ASTContext.h"
2932
#include "llvm/ADT/Hashing.h"
3033
#include "llvm/Support/MemoryBuffer.h"
3134

@@ -162,10 +165,118 @@ static DeclContext *getEquivalentDeclContextFromSourceFile(DeclContext *DC,
162165
return newDC;
163166
}
164167

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

167277
bool CompletionInstance::performCachedOperationIfPossible(
168278
const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
279+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
169280
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
170281
DiagnosticConsumer *DiagC,
171282
llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
@@ -187,10 +298,18 @@ bool CompletionInstance::performCachedOperationIfPossible(
187298
auto &oldInfo = oldState->getCodeCompletionDelayedDeclState();
188299

189300
auto &SM = CI.getSourceMgr();
190-
if (SM.getIdentifierForBuffer(SM.getCodeCompletionBufferID()) !=
191-
completionBuffer->getBufferIdentifier())
301+
auto bufferName = completionBuffer->getBufferIdentifier();
302+
if (SM.getIdentifierForBuffer(SM.getCodeCompletionBufferID()) != bufferName)
192303
return false;
193304

305+
if (shouldCheckDependencies()) {
306+
if (areAnyDependentFilesInvalidated(
307+
CI, CurrentModule, *FileSystem, SM.getCodeCompletionBufferID(),
308+
DependencyCheckedTimestamp, InMemoryDependencyHash))
309+
return false;
310+
DependencyCheckedTimestamp = std::chrono::system_clock::now();
311+
}
312+
194313
// Parse the new buffer into temporary SourceFile.
195314
SourceManager tmpSM;
196315
auto tmpBufferID = tmpSM.addMemBufferCopy(completionBuffer);
@@ -265,8 +384,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
265384
completionBuffer->getBuffer().slice(startOffset, endOffset);
266385
auto newOffset = Offset - startOffset;
267386

268-
newBufferID = SM.addMemBufferCopy(sourceText,
269-
completionBuffer->getBufferIdentifier());
387+
newBufferID = SM.addMemBufferCopy(sourceText, bufferName);
270388
SM.openVirtualFile(SM.getLocForBufferStart(newBufferID),
271389
tmpSM.getDisplayNameForLoc(startLoc),
272390
tmpSM.getLineAndColumn(startLoc).first - 1);
@@ -312,8 +430,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
312430
endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID);
313431
sourceText = sourceText.slice(0, endOffset);
314432
}
315-
newBufferID = SM.addMemBufferCopy(sourceText,
316-
completionBuffer->getBufferIdentifier());
433+
newBufferID = SM.addMemBufferCopy(sourceText, bufferName);
317434
SM.setCodeCompletionPoint(newBufferID, Offset);
318435

319436
// Create a new module and a source file using the current AST context.
@@ -334,6 +451,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
334451
performImportResolution(*newSF);
335452
bindExtensions(*newSF);
336453

454+
CurrentModule = newM;
337455
traceDC = newM;
338456
#ifndef NDEBUG
339457
const auto *reparsedState = newSF->getDelayedParserState();
@@ -360,6 +478,8 @@ bool CompletionInstance::performCachedOperationIfPossible(
360478
}
361479

362480
CachedReuseCount += 1;
481+
cacheDependencyHashIfNeeded(CI, CurrentModule, SM.getCodeCompletionBufferID(),
482+
InMemoryDependencyHash);
363483

364484
return true;
365485
}
@@ -372,7 +492,15 @@ bool CompletionInstance::performNewOperation(
372492
llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
373493
llvm::PrettyStackTraceString trace("While performing new completion");
374494

495+
auto isCachedCompletionRequested = ArgsHash.hasValue();
496+
375497
auto TheInstance = std::make_unique<CompilerInstance>();
498+
499+
// Track dependencies in fast-completion mode to invalidate the compiler
500+
// instance if any dependent files are modified.
501+
if (isCachedCompletionRequested)
502+
TheInstance->createDependencyTracker(false);
503+
376504
{
377505
auto &CI = *TheInstance;
378506
if (DiagC)
@@ -410,15 +538,41 @@ bool CompletionInstance::performNewOperation(
410538
Callback(CI, /*reusingASTContext=*/false);
411539
}
412540

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

419545
return true;
420546
}
421547

548+
void CompletionInstance::cacheCompilerInstance(
549+
std::unique_ptr<CompilerInstance> CI, llvm::hash_code ArgsHash) {
550+
CachedCI = std::move(CI);
551+
CurrentModule = CachedCI->getMainModule();
552+
CachedArgHash = ArgsHash;
553+
auto now = std::chrono::system_clock::now();
554+
DependencyCheckedTimestamp = now;
555+
CachedReuseCount = 0;
556+
InMemoryDependencyHash.clear();
557+
cacheDependencyHashIfNeeded(
558+
*CachedCI, CurrentModule,
559+
CachedCI->getASTContext().SourceMgr.getCodeCompletionBufferID(),
560+
InMemoryDependencyHash);
561+
}
562+
563+
bool CompletionInstance::shouldCheckDependencies() const {
564+
assert(CachedCI);
565+
using namespace std::chrono;
566+
auto now = system_clock::now();
567+
return DependencyCheckedTimestamp + seconds(DependencyCheckIntervalSecond) <
568+
now;
569+
}
570+
571+
void CompletionInstance::setDependencyCheckIntervalSecond(unsigned Value) {
572+
std::lock_guard<std::mutex> lock(mtx);
573+
DependencyCheckIntervalSecond = Value;
574+
}
575+
422576
bool swift::ide::CompletionInstance::performOperation(
423577
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
424578
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
@@ -454,8 +608,9 @@ bool swift::ide::CompletionInstance::performOperation(
454608
// the cached completion instance.
455609
std::lock_guard<std::mutex> lock(mtx);
456610

457-
if (performCachedOperationIfPossible(Invocation, ArgsHash, completionBuffer,
458-
Offset, DiagC, Callback))
611+
if (performCachedOperationIfPossible(Invocation, ArgsHash, FileSystem,
612+
completionBuffer, Offset, DiagC,
613+
Callback))
459614
return true;
460615

461616
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
func localSwiftFunc() -> Int {}
2+
3+
struct MyStruct {
4+
func myStructMethod() {}
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
extension MyStruct {
2+
func extensionMethod() {}
3+
}
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
func localSwiftFunc_mod() -> Int {}
2+
3+
struct MyStruct {
4+
func myStructMethod_mod() {}
5+
}
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+
}

0 commit comments

Comments
 (0)