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
167277bool 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+
422576bool 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,
0 commit comments