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);
@@ -263,8 +382,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
263
382
completionBuffer->getBuffer ().slice (startOffset, endOffset);
264
383
auto newOffset = Offset - startOffset;
265
384
266
- newBufferID = SM.addMemBufferCopy (sourceText,
267
- completionBuffer->getBufferIdentifier ());
385
+ newBufferID = SM.addMemBufferCopy (sourceText, bufferName);
268
386
SM.openVirtualFile (SM.getLocForBufferStart (newBufferID),
269
387
tmpSM.getDisplayNameForLoc (startLoc),
270
388
tmpSM.getLineAndColumn (startLoc).first - 1 );
@@ -310,8 +428,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
310
428
endOffset = tmpSM.getLocOffsetInBuffer (endLoc, tmpBufferID);
311
429
sourceText = sourceText.slice (0 , endOffset);
312
430
}
313
- newBufferID = SM.addMemBufferCopy (sourceText,
314
- completionBuffer->getBufferIdentifier ());
431
+ newBufferID = SM.addMemBufferCopy (sourceText, bufferName);
315
432
SM.setCodeCompletionPoint (newBufferID, Offset);
316
433
317
434
// Create a new module and a source file using the current AST context.
@@ -331,6 +448,7 @@ bool CompletionInstance::performCachedOperationIfPossible(
331
448
performImportResolution (*newSF);
332
449
bindExtensions (*newSF);
333
450
451
+ CurrentModule = newM;
334
452
traceDC = newM;
335
453
#ifndef NDEBUG
336
454
const auto *reparsedState = newSF->getDelayedParserState ();
@@ -357,6 +475,8 @@ bool CompletionInstance::performCachedOperationIfPossible(
357
475
}
358
476
359
477
CachedReuseCount += 1 ;
478
+ cacheDependencyHashIfNeeded (CI, CurrentModule, SM.getCodeCompletionBufferID (),
479
+ InMemoryDependencyHash);
360
480
361
481
return true ;
362
482
}
@@ -369,7 +489,15 @@ bool CompletionInstance::performNewOperation(
369
489
llvm::function_ref<void (CompilerInstance &, bool )> Callback) {
370
490
llvm::PrettyStackTraceString trace (" While performing new completion" );
371
491
492
+ auto isCachedCompletionRequested = ArgsHash.hasValue ();
493
+
372
494
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
+
373
501
{
374
502
auto &CI = *TheInstance;
375
503
if (DiagC)
@@ -407,15 +535,41 @@ bool CompletionInstance::performNewOperation(
407
535
Callback (CI, /* reusingASTContext=*/ false );
408
536
}
409
537
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);
415
541
416
542
return true ;
417
543
}
418
544
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
+
419
573
bool swift::ide::CompletionInstance::performOperation (
420
574
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
421
575
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
@@ -451,8 +605,9 @@ bool swift::ide::CompletionInstance::performOperation(
451
605
// the cached completion instance.
452
606
std::lock_guard<std::mutex> lock (mtx);
453
607
454
- if (performCachedOperationIfPossible (Invocation, ArgsHash, completionBuffer,
455
- Offset, DiagC, Callback))
608
+ if (performCachedOperationIfPossible (Invocation, ArgsHash, FileSystem,
609
+ completionBuffer, Offset, DiagC,
610
+ Callback))
456
611
return true ;
457
612
458
613
if (performNewOperation (ArgsHash, Invocation, FileSystem, completionBuffer,
0 commit comments