Skip to content

Commit 1f6c3be

Browse files
authored
Merge pull request #31388 from rintaro/ide-completion-fastcheckdep-rdar62336432
[CodeCompletion] Give up fast-completion if dependent files are modified
2 parents 43587a6 + af5daed commit 1f6c3be

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);
@@ -263,8 +382,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
263382
completionBuffer->getBuffer().slice(startOffset, endOffset);
264383
auto newOffset = Offset - startOffset;
265384

266-
newBufferID = SM.addMemBufferCopy(sourceText,
267-
completionBuffer->getBufferIdentifier());
385+
newBufferID = SM.addMemBufferCopy(sourceText, bufferName);
268386
SM.openVirtualFile(SM.getLocForBufferStart(newBufferID),
269387
tmpSM.getDisplayNameForLoc(startLoc),
270388
tmpSM.getLineAndColumn(startLoc).first - 1);
@@ -310,8 +428,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
310428
endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID);
311429
sourceText = sourceText.slice(0, endOffset);
312430
}
313-
newBufferID = SM.addMemBufferCopy(sourceText,
314-
completionBuffer->getBufferIdentifier());
431+
newBufferID = SM.addMemBufferCopy(sourceText, bufferName);
315432
SM.setCodeCompletionPoint(newBufferID, Offset);
316433

317434
// Create a new module and a source file using the current AST context.
@@ -331,6 +448,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
331448
performImportResolution(*newSF);
332449
bindExtensions(*newSF);
333450

451+
CurrentModule = newM;
334452
traceDC = newM;
335453
#ifndef NDEBUG
336454
const auto *reparsedState = newSF->getDelayedParserState();
@@ -357,6 +475,8 @@ bool CompletionInstance::performCachedOperationIfPossible(
357475
}
358476

359477
CachedReuseCount += 1;
478+
cacheDependencyHashIfNeeded(CI, CurrentModule, SM.getCodeCompletionBufferID(),
479+
InMemoryDependencyHash);
360480

361481
return true;
362482
}
@@ -369,7 +489,15 @@ bool CompletionInstance::performNewOperation(
369489
llvm::function_ref<void(CompilerInstance &, bool)> Callback) {
370490
llvm::PrettyStackTraceString trace("While performing new completion");
371491

492+
auto isCachedCompletionRequested = ArgsHash.hasValue();
493+
372494
auto TheInstance = std::make_unique<CompilerInstance>();
495+
496+
// Track dependencies in fast-completion mode to invalidate the compiler
497+
// instance if any dependent files are modified.
498+
if (isCachedCompletionRequested)
499+
TheInstance->createDependencyTracker(false);
500+
373501
{
374502
auto &CI = *TheInstance;
375503
if (DiagC)
@@ -407,15 +535,41 @@ bool CompletionInstance::performNewOperation(
407535
Callback(CI, /*reusingASTContext=*/false);
408536
}
409537

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

416542
return true;
417543
}
418544

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

454-
if (performCachedOperationIfPossible(Invocation, ArgsHash, completionBuffer,
455-
Offset, DiagC, Callback))
608+
if (performCachedOperationIfPossible(Invocation, ArgsHash, FileSystem,
609+
completionBuffer, Offset, DiagC,
610+
Callback))
456611
return true;
457612

458613
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)