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);
@@ -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+
419573bool 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,
0 commit comments