Skip to content

Commit 044477e

Browse files
committed
[SourceKit/CodeCompletion] Use callback function to run the second pass
To controls the lifetime of CompilerInstance within CompletionIntance. - Prevent from using the same CompilerInstance from multiple completion requests - Separate the stacktrace between "fast" and "normal" completions - Further code consolidation between various completion-like requests
1 parent fcb50d6 commit 044477e

File tree

9 files changed

+260
-283
lines changed

9 files changed

+260
-283
lines changed

include/swift/IDE/CompletionInstance.h

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,47 +37,56 @@ makeCodeCompletionMemoryBuffer(const llvm::MemoryBuffer *origBuf,
3737

3838
/// Manages \c CompilerInstance for completion like operations.
3939
class CompletionInstance {
40-
std::unique_ptr<CompilerInstance> CachedCI = nullptr;
41-
llvm::hash_code CachedArgsHash;
42-
bool EnableASTCaching = false;
4340
unsigned MaxASTReuseCount = 100;
41+
bool EnableASTCaching = false;
42+
43+
std::shared_ptr<CompilerInstance> CachedCI = nullptr;
44+
llvm::hash_code CachedArgsHash;
4445
unsigned CurrentASTReuseCount = 0;
4546

46-
/// Returns cached \c CompilerInstance if it's usable for the specified
47-
/// completion request.
48-
/// Returns \c nullptr if the functionality is disabled, compiler argument has
47+
/// Calls \p Callback with cached \c CompilerInstance if it's usable for the
48+
/// specified completion request.
49+
/// Returns \c if the callback was called. Returns \c false if the
50+
/// functionality is disabled, compiler argument has
4951
/// changed, primary file is not the same, the \c Offset is not in function
5052
/// bodies, or the interface hash of the file has changed.
51-
swift::CompilerInstance *
52-
getCachedCompilerInstance(const swift::CompilerInvocation &Invocation,
53-
llvm::hash_code ArgsHash,
54-
llvm::MemoryBuffer *completionBuffer,
55-
unsigned int Offset, DiagnosticConsumer *DiagC);
53+
bool performCachedOperaitonIfPossible(
54+
const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
55+
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
56+
DiagnosticConsumer *DiagC,
57+
llvm::function_ref<void(CompilerInstance &)> Callback);
5658

57-
/// Returns new \c CompilerInstance for the completion request. Users still
58-
/// have to call \c performParseAndResolveImportsOnly() , and perform the
59-
/// second pass on it.
60-
swift::CompilerInstance *renewCompilerInstance(
59+
/// Calls \p Callback with new \c CompilerInstance for the completion
60+
/// request. The \c CompilerInstace passed to the callback already performed
61+
/// the first pass.
62+
/// Returns \c false if it fails to setup the \c CompilerInstance.
63+
bool performNewOperation(
6164
swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
6265
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
6366
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
64-
std::string &Error, DiagnosticConsumer *DiagC);
67+
std::string &Error, DiagnosticConsumer *DiagC,
68+
llvm::function_ref<void(CompilerInstance &)> Callback);
6569

6670
public:
6771
void setEnableASTCaching(bool Flag) { EnableASTCaching = Flag; }
6872

69-
/// Returns \C CompilerInstance for the completion request. Users can check if
70-
/// it's cached or not by 'hasPersistentParserState()'.
73+
/// Calls \p Callback with a \c CompilerInstance which is prepared for the
74+
/// second pass. \p Callback is resposible to perform the second pass on it.
75+
/// The \c CompilerInstance may be reused from the previous completions,
76+
/// and may be cached for the next completion.
77+
/// Return \c true if \p is successfully called, \c it fails. In failure
78+
/// cases \p Error is populated with an error message.
7179
///
7280
/// NOTE: \p Args is only used for checking the equaity of the invocation.
7381
/// Since this function assumes that it is already normalized, exact the same
7482
/// arguments including their order is considered as the same invocation.
75-
swift::CompilerInstance *getCompilerInstance(
76-
swift::CompilerInvocation &Invocation,
77-
llvm::ArrayRef<const char *> Args,
78-
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
79-
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
80-
std::string &Error, DiagnosticConsumer *DiagC);
83+
bool
84+
performOperation(swift::CompilerInvocation &Invocation,
85+
llvm::ArrayRef<const char *> Args,
86+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
87+
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
88+
std::string &Error, DiagnosticConsumer *DiagC,
89+
llvm::function_ref<void(CompilerInstance &)> Callback);
8190
};
8291

8392
} // namespace ide

lib/IDE/CompletionInstance.cpp

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -129,38 +129,40 @@ static DeclContext *getEquivalentDeclContextFromSourceFile(DeclContext *DC,
129129

130130
} // namespace
131131

132-
CompilerInstance *CompletionInstance::getCachedCompilerInstance(
132+
bool CompletionInstance::performCachedOperaitonIfPossible(
133133
const swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
134134
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
135-
DiagnosticConsumer *DiagC) {
136-
if (!EnableASTCaching)
137-
return nullptr;
135+
DiagnosticConsumer *DiagC,
136+
llvm::function_ref<void(CompilerInstance &)> Callback) {
138137

139-
if (CurrentASTReuseCount >= MaxASTReuseCount) {
140-
CurrentASTReuseCount = 0;
141-
return nullptr;
142-
}
138+
if (!EnableASTCaching)
139+
return false;
143140

144-
if (!CachedCI)
145-
return nullptr;
141+
// Temporary move the CI so other threads don't use the same instance.
142+
std::shared_ptr<CompilerInstance> CI;
143+
CI.swap(CachedCI);
146144

145+
if (!CI)
146+
return false;
147+
if (CurrentASTReuseCount >= MaxASTReuseCount)
148+
return false;
147149
if (ArgsHash != CachedArgsHash)
148-
return nullptr;
150+
return false;
149151

150-
auto &oldState = CachedCI->getPersistentParserState();
152+
auto &oldState = CI->getPersistentParserState();
151153
if (!oldState.hasCodeCompletionDelayedDeclState())
152-
return nullptr;
154+
return false;
153155

154-
auto &SM = CachedCI->getSourceMgr();
156+
auto &SM = CI->getSourceMgr();
155157
if (SM.getIdentifierForBuffer(SM.getCodeCompletionBufferID()) !=
156158
completionBuffer->getBufferIdentifier())
157-
return nullptr;
159+
return false;
158160

159161
auto &oldInfo = oldState.getCodeCompletionDelayedDeclState();
160162

161163
// Currently, only completions within a function body is supported.
162164
if (oldInfo.Kind != CodeCompletionDelayedDeclKind::FunctionBody)
163-
return nullptr;
165+
return false;
164166

165167
auto newBufferID = SM.addMemBufferCopy(completionBuffer);
166168
SM.setCodeCompletionPoint(newBufferID, Offset);
@@ -185,13 +187,13 @@ CompilerInstance *CompletionInstance::getCachedCompilerInstance(
185187
parseIntoSourceFileFull(*newSF, BufferID, &newState);
186188
// Couldn't find any completion token?
187189
if (!newState.hasCodeCompletionDelayedDeclState())
188-
return nullptr;
190+
return false;
189191

190192
auto &newInfo = newState.getCodeCompletionDelayedDeclState();
191193

192194
// The new completion must happens in function body too.
193195
if (newInfo.Kind != CodeCompletionDelayedDeclKind::FunctionBody)
194-
return nullptr;
196+
return false;
195197

196198
auto *oldSF = oldInfo.ParentContext->getParentSourceFile();
197199

@@ -201,12 +203,12 @@ CompilerInstance *CompletionInstance::getCachedCompilerInstance(
201203
oldSF->getInterfaceHash(oldInterfaceHash);
202204
newSF->getInterfaceHash(newInterfaceHash);
203205
if (oldInterfaceHash != newInterfaceHash)
204-
return nullptr;
206+
return false;
205207

206208
DeclContext *DC =
207209
getEquivalentDeclContextFromSourceFile(newInfo.ParentContext, oldSF);
208210
if (!DC)
209-
return nullptr;
211+
return false;
210212

211213
// OK, we can perform fast completion for this. Update the orignal delayed
212214
// decl state.
@@ -228,28 +230,33 @@ CompilerInstance *CompletionInstance::getCachedCompilerInstance(
228230
if (AFD->isBodySkipped())
229231
AFD->setBodyDelayed(AFD->getBodySourceRange());
230232
if (DiagC)
231-
CachedCI->addDiagnosticConsumer(DiagC);
233+
CI->addDiagnosticConsumer(DiagC);
232234

233-
CachedCI->getDiags().diagnose(
234-
SM.getLocForOffset(BufferID, newInfo.StartOffset),
235-
diag::completion_reusing_astcontext);
235+
CI->getDiags().diagnose(SM.getLocForOffset(BufferID, newInfo.StartOffset),
236+
diag::completion_reusing_astcontext);
236237

238+
Callback(*CI);
239+
240+
if (DiagC)
241+
CI->removeDiagnosticConsumer(DiagC);
242+
243+
CachedCI.swap(CI);
244+
CachedArgsHash = ArgsHash;
237245
CurrentASTReuseCount += 1;
238246

239-
return CachedCI.get();
247+
return true;
240248
}
241249

242-
CompilerInstance *CompletionInstance::renewCompilerInstance(
250+
bool CompletionInstance::performNewOperation(
243251
swift::CompilerInvocation &Invocation, llvm::hash_code ArgsHash,
244252
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
245253
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
246-
std::string &Error, DiagnosticConsumer *DiagC) {
254+
std::string &Error, DiagnosticConsumer *DiagC,
255+
llvm::function_ref<void(CompilerInstance &)> Callback) {
247256
CachedCI.reset();
248-
CachedCI = std::make_unique<CompilerInstance>();
249-
CachedArgsHash = ArgsHash;
250-
auto *CI = CachedCI.get();
257+
auto CI = std::make_shared<CompilerInstance>();
251258
if (DiagC)
252-
CachedCI->addDiagnosticConsumer(DiagC);
259+
CI->addDiagnosticConsumer(DiagC);
253260

254261
if (FileSystem != llvm::vfs::getRealFileSystem())
255262
CI->getSourceMgr().setFileSystem(FileSystem);
@@ -258,18 +265,31 @@ CompilerInstance *CompletionInstance::renewCompilerInstance(
258265

259266
if (CI->setup(Invocation)) {
260267
Error = "failed to setup compiler instance";
261-
return nullptr;
268+
return false;
262269
}
263270
registerIDERequestFunctions(CI->getASTContext().evaluator);
264271

265-
return CI;
272+
CI->performParseAndResolveImportsOnly();
273+
Callback(*CI);
274+
275+
if (DiagC)
276+
CI->removeDiagnosticConsumer(DiagC);
277+
278+
if (EnableASTCaching) {
279+
CachedCI.swap(CI);
280+
CachedArgsHash = ArgsHash;
281+
CurrentASTReuseCount = 0;
282+
}
283+
284+
return true;
266285
}
267286

268-
CompilerInstance *swift::ide::CompletionInstance::getCompilerInstance(
287+
bool swift::ide::CompletionInstance::performOperation(
269288
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
270289
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
271290
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
272-
std::string &Error, DiagnosticConsumer *DiagC) {
291+
std::string &Error, DiagnosticConsumer *DiagC,
292+
llvm::function_ref<void(CompilerInstance &)> Callback) {
273293

274294
// Always disable source location resolutions from .swiftsourceinfo file
275295
// because they're somewhat heavy operations and aren't needed for completion.
@@ -287,15 +307,14 @@ CompilerInstance *swift::ide::CompletionInstance::getCompilerInstance(
287307
for (auto arg : Args)
288308
ArgsHash = llvm::hash_combine(ArgsHash, StringRef(arg));
289309

290-
if (auto *cached = getCachedCompilerInstance(Invocation, ArgsHash,
291-
completionBuffer, Offset, DiagC))
292-
return cached;
310+
if (performCachedOperaitonIfPossible(Invocation, ArgsHash, completionBuffer,
311+
Offset, DiagC, Callback))
312+
return true;
293313

294-
if (auto *renewed =
295-
renewCompilerInstance(Invocation, ArgsHash, FileSystem,
296-
completionBuffer, Offset, Error, DiagC))
297-
return renewed;
314+
if (performNewOperation(Invocation, ArgsHash, FileSystem, completionBuffer,
315+
Offset, Error, DiagC, Callback))
316+
return true;
298317

299318
assert(!Error.empty());
300-
return nullptr;
319+
return false;
301320
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
class Foo {
2+
var x: Int
3+
var y: Int
4+
func fooMethod() {}
5+
}
6+
struct Bar {
7+
var a: Int
8+
var b: Int
9+
func barMethod() {}
10+
}
11+
func foo(arg: Foo) {
12+
_ = arg.
13+
}
14+
func bar(arg: Bar) {
15+
_ = arg.
16+
}
17+
18+
// NOTE: Test for simultaneous completion requests don't cause compiler crashes.
19+
20+
// ReuseASTContext disabled.
21+
// RUN: %sourcekitd-test \
22+
// RUN: -req=complete -pos=12:11 %s -async -- %s == \
23+
// RUN: -req=complete -pos=15:11 %s -async -- %s == \
24+
// RUN: -req=complete -pos=12:11 %s -async -- %s == \
25+
// RUN: -req=complete -pos=15:11 %s -async -- %s == \
26+
// RUN: -req=complete -pos=12:11 %s -async -- %s == \
27+
// RUN: -req=complete -pos=15:11 %s -async -- %s == \
28+
// RUN: -req=complete -pos=12:11 %s -async -- %s == \
29+
// RUN: -req=complete -pos=15:11 %s -async -- %s == \
30+
// RUN: -req=complete -pos=12:11 %s -async -- %s == \
31+
// RUN: -req=complete -pos=15:11 %s -async -- %s
32+
33+
// ReuseASTContext enabled.
34+
// RUN: %sourcekitd-test \
35+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:11 %s -async -- %s == \
36+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=15:11 %s -async -- %s == \
37+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:11 %s -async -- %s == \
38+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=15:11 %s -async -- %s == \
39+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:11 %s -async -- %s == \
40+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=15:11 %s -async -- %s == \
41+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:11 %s -async -- %s == \
42+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=15:11 %s -async -- %s == \
43+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=12:11 %s -async -- %s == \
44+
// RUN: -req=complete -req-opts=reuseastcontext=1 -pos=15:11 %s -async -- %s

0 commit comments

Comments
 (0)