21
21
#include " swift/Basic/LangOptions.h"
22
22
#include " swift/Basic/PrettyStackTrace.h"
23
23
#include " swift/Basic/SourceManager.h"
24
+ #include " swift/ClangImporter/ClangModule.h"
24
25
#include " swift/Driver/FrontendUtil.h"
25
26
#include " swift/Frontend/Frontend.h"
26
27
#include " swift/Parse/Lexer.h"
27
28
#include " swift/Parse/PersistentParserState.h"
29
+ #include " swift/Serialization/SerializedModuleLoader.h"
28
30
#include " swift/Subsystems.h"
31
+ #include " clang/AST/ASTContext.h"
29
32
#include " llvm/ADT/Hashing.h"
30
33
#include " llvm/Support/MemoryBuffer.h"
31
34
@@ -162,10 +165,118 @@ static DeclContext *getEquivalentDeclContextFromSourceFile(DeclContext *DC,
162
165
return newDC;
163
166
}
164
167
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
+
165
275
} // namespace
166
276
167
277
bool CompletionInstance::performCachedOperationIfPossible (
168
278
const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
279
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
169
280
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
170
281
DiagnosticConsumer *DiagC,
171
282
llvm::function_ref<void (CompilerInstance &, bool )> Callback) {
@@ -187,10 +298,18 @@ bool CompletionInstance::performCachedOperationIfPossible(
187
298
auto &oldInfo = oldState->getCodeCompletionDelayedDeclState ();
188
299
189
300
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 )
192
303
return false ;
193
304
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
+
194
313
// Parse the new buffer into temporary SourceFile.
195
314
SourceManager tmpSM;
196
315
auto tmpBufferID = tmpSM.addMemBufferCopy (completionBuffer);
@@ -265,8 +384,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
265
384
completionBuffer->getBuffer ().slice (startOffset, endOffset);
266
385
auto newOffset = Offset - startOffset;
267
386
268
- newBufferID = SM.addMemBufferCopy (sourceText,
269
- completionBuffer->getBufferIdentifier ());
387
+ newBufferID = SM.addMemBufferCopy (sourceText, bufferName);
270
388
SM.openVirtualFile (SM.getLocForBufferStart (newBufferID),
271
389
tmpSM.getDisplayNameForLoc (startLoc),
272
390
tmpSM.getLineAndColumn (startLoc).first - 1 );
@@ -312,8 +430,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
312
430
endOffset = tmpSM.getLocOffsetInBuffer (endLoc, tmpBufferID);
313
431
sourceText = sourceText.slice (0 , endOffset);
314
432
}
315
- newBufferID = SM.addMemBufferCopy (sourceText,
316
- completionBuffer->getBufferIdentifier ());
433
+ newBufferID = SM.addMemBufferCopy (sourceText, bufferName);
317
434
SM.setCodeCompletionPoint (newBufferID, Offset);
318
435
319
436
// Create a new module and a source file using the current AST context.
@@ -334,6 +451,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
334
451
performImportResolution (*newSF);
335
452
bindExtensions (*newSF);
336
453
454
+ CurrentModule = newM;
337
455
traceDC = newM;
338
456
#ifndef NDEBUG
339
457
const auto *reparsedState = newSF->getDelayedParserState ();
@@ -360,6 +478,8 @@ bool CompletionInstance::performCachedOperationIfPossible(
360
478
}
361
479
362
480
CachedReuseCount += 1 ;
481
+ cacheDependencyHashIfNeeded (CI, CurrentModule, SM.getCodeCompletionBufferID (),
482
+ InMemoryDependencyHash);
363
483
364
484
return true ;
365
485
}
@@ -372,7 +492,15 @@ bool CompletionInstance::performNewOperation(
372
492
llvm::function_ref<void (CompilerInstance &, bool )> Callback) {
373
493
llvm::PrettyStackTraceString trace (" While performing new completion" );
374
494
495
+ auto isCachedCompletionRequested = ArgsHash.hasValue ();
496
+
375
497
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
+
376
504
{
377
505
auto &CI = *TheInstance;
378
506
if (DiagC)
@@ -410,15 +538,41 @@ bool CompletionInstance::performNewOperation(
410
538
Callback (CI, /* reusingASTContext=*/ false );
411
539
}
412
540
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);
418
544
419
545
return true ;
420
546
}
421
547
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
+
422
576
bool swift::ide::CompletionInstance::performOperation (
423
577
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
424
578
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
@@ -454,8 +608,9 @@ bool swift::ide::CompletionInstance::performOperation(
454
608
// the cached completion instance.
455
609
std::lock_guard<std::mutex> lock (mtx);
456
610
457
- if (performCachedOperationIfPossible (Invocation, ArgsHash, completionBuffer,
458
- Offset, DiagC, Callback))
611
+ if (performCachedOperationIfPossible (Invocation, ArgsHash, FileSystem,
612
+ completionBuffer, Offset, DiagC,
613
+ Callback))
459
614
return true ;
460
615
461
616
if (performNewOperation (ArgsHash, Invocation, FileSystem, completionBuffer,
0 commit comments