-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[SourceKit] ⚡️Fast code completion within function bodies #28727
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SourceKit] ⚡️Fast code completion within function bodies #28727
Conversation
@swift-ci Please test |
Build failed |
@swift-ci Please test Linux |
Forgot to add a test file. |
Build failed |
Build failed |
efbd9d6
to
d36ae5d
Compare
@swift-ci Please test |
Build failed |
Build failed |
@swift-ci Please test Linux |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to have some additional tests:
- Compiler argument changed
- Source changed in a compatible way -> reuse
- Source changed in an incompatible way -> new AST
You can modify the text that is passed without changing the compiler arguments by using -text-input
option to sourcekitd-test
I think.
0e2a41b
to
6cd54e9
Compare
@@ -970,6 +972,8 @@ static int handleTestInvocation(TestOptions Opts, TestOptions &InitOpts) { | |||
if (Opts.SourceText) { | |||
sourcekitd_request_dictionary_set_string(Req, KeySourceText, | |||
Opts.SourceText->c_str()); | |||
sourcekitd_request_dictionary_set_string(Req, KeySourceFile, | |||
SemaName.c_str()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change was needed to use -name
as the filename (key.sourcefile
).
@swift-ci Please smoke test |
Invocation.setCodeCompletionPoint(NewBuffer.get(), CodeCompletionOffset); | ||
// Pin completion instance. | ||
auto CompletionInst = Lang.getCompletionInstance(); | ||
CompilerInstance *CI = CompletionInst->getCompilerInstance( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh hmm, this probably isn't thread safe. I don't think anything prevents you from making multiple code-completion requests (or other completion-like requests) at the same time. In sourcekitd-test, for example if you add -async
and then make a bunch of requests in a row they probably overlap.
Perhaps we should use shared_ptr<CompilerInstance>
so that if we renew it can't affect an ongoing completion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I confirmed the crashes with -async
. Will fix.
f353894
to
5f7090a
Compare
@benlangmuir The last commit changes the structure of the processing. So
What do you think? |
@swift-ci Please smoke test |
Modified the way of storing the cached information. It should prevent |
Ah good point, a shared_ptr is wrong since you actually can't share this safely across threads. Note: your current code is still technically racy because the So if you have racing completions, one will win and get to reuse the AST, and the next one will go through the slow path. Is that better than blocking on the first one? If you're reusing ASTs it seems like it would usually be better to wait since you have a chance to hit the fast path on every completion. Assuming the reuse case is 10x faster and that the number of concurrent completions is usually very small, you would be better off waiting. If you're not reusing ASTs, then I guess running the completions in parallel might be better in some instances - or at least it matches the current behaviour. |
a4eee1a
to
23f4416
Compare
Fair enough. I update the PR to block other threads using |
Align with code completion.
Specifically, align with swiftTypeContextInfoImpl() and swiftConformingMethodListImpl()
So that we can use the different the buffer for the second pass from the first pass.
- Introduce ide::CompletionInstance to manage CompilerInstance - `CompletionInstance` vends the cached CompilerInstance when: -- The compiler arguments (i.e. CompilerInvocation) has has not changed -- The primary file is the same -- The completion happens inside function bodies in both previous and current completion -- The interface hash of the primary file has not changed - Otherwise, it vends a fresh CompilerInstance and cache it for the next completion rdar://problem/20787086
to getCachedCompilerInstance()
So we can pin it on the threads.
…ation Checking "selected" properties of CompilerInvocation is hard to keep it up to date. If the arguments are the same, the invocations are the same.
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
To prevent them from out-of-sync.
if fast-completion is enabled. So they have higher chance to use the cached completion instance. If it's disabled, don't block, use an ephemeral instance so we can peform multiple completions simultaneously.
lib/IDE/CompletionInstance.cpp
Outdated
CachedInst.swap(TheInstance); | ||
CachedCI = std::move(TheInstance); | ||
CachedArgHash = ArgsHash; | ||
CachedReuseCound = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: "Cound" vs "Count"
llvm::function_ref<void(CompilerInstance &)> Callback); | ||
|
||
public: | ||
void setEnableASTCaching(bool Flag) { EnableASTCaching = Flag; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if this were done atomically it would not be correct for racing completions. Can we pass EnableASTCaching
in as an argument to performOperation
so that is is associated with a specific completion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! How does it look now?
lib/IDE/CompletionInstance.cpp
Outdated
|
||
// If AST caching is enabled, block completions in other threads. So they | ||
// have higher chance to use the cached completion instance. | ||
Optional<std::lock_guard<std::mutex>> lock; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fun, but maybe too magical? Could we instead separate these code-paths, like
if (EnableASTCaching) {
// Compute the signature of the invocation.
llvm::hash_code ArgsHash(0);
for (auto arg : Args)
ArgsHash = llvm::hash_combine(ArgsHash, StringRef(arg));
// Concurrent completions will block so that they have a higher chance to use the cached completion instance.
std::lock_guard<std::mutex> lock(mtx);
if (performCachedOperaitonIfPossible(...))
return true;
if (performNewOperation(...))
return true;
} else {
// Concurrent completions may happen in parallel when caching is disabled.
if (performNewOperation(Invocation, ArgsHash, FileSystem, completionBuffer,
Offset, Error, DiagC, Callback))
return true;
}
23f4416
to
4327111
Compare
so that it is associated with a specific completion.
4327111
to
eebcbf6
Compare
@swift-ci Please smoke test |
Reuse
ASTContext
(CompilerInstance
) from the previous completions when possible.ide::CompletionInstance
managesCompilerInstance
for completion like requests (i.e. code completion, type context info, and conforming method list).It vends the cached
CompilerInstance
if:CompilerInvocation
) has not changed, andSince cached instance already have
AST
including imported modules, we just run the second pass.Otherwise,
CompletionInstance
vends newCompilerInstance
, so we run both first and second pass using that instance. This instance is cached for the next completion request.NOTE: currently it's disabled by default.
For example, using this simple example:
rdar://problem/20787086