Skip to content

Commit 8f38105

Browse files
committed
[CodeCompletion] Reduce leaking memory per fast completion
For each fast completion, the memory grows for the source buffer and the AST of the function body in memory. They are kept until the next slow completion. To mitigate the memory consumption per completion, use sliced source text which is actually required for the second pass. rdar://problem/58119719
1 parent 9cd3072 commit 8f38105

File tree

1 file changed

+38
-10
lines changed

1 file changed

+38
-10
lines changed

lib/IDE/CompletionInstance.cpp

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/Basic/SourceManager.h"
2222
#include "swift/Driver/FrontendUtil.h"
2323
#include "swift/Frontend/Frontend.h"
24+
#include "swift/Parse/Lexer.h"
2425
#include "swift/Parse/PersistentParserState.h"
2526
#include "swift/Subsystems.h"
2627
#include "llvm/ADT/Hashing.h"
@@ -159,27 +160,27 @@ bool CompletionInstance::performCachedOperaitonIfPossible(
159160
if (oldInfo.Kind != CodeCompletionDelayedDeclKind::FunctionBody)
160161
return false;
161162

162-
auto newBufferID = SM.addMemBufferCopy(completionBuffer);
163-
SM.setCodeCompletionPoint(newBufferID, Offset);
164-
165163
// Parse the new buffer into temporary SourceFile.
164+
SourceManager tmpSM;
165+
auto tmpBufferID = tmpSM.addMemBufferCopy(completionBuffer);
166+
tmpSM.setCodeCompletionPoint(tmpBufferID, Offset);
167+
166168
LangOptions langOpts;
167169
langOpts.DisableParserLookup = true;
168170
TypeCheckerOptions typeckOpts;
169171
SearchPathOptions searchPathOpts;
170-
DiagnosticEngine Diags(SM);
172+
DiagnosticEngine tmpDiags(tmpSM);
171173
std::unique_ptr<ASTContext> Ctx(
172-
ASTContext::get(langOpts, typeckOpts, searchPathOpts, SM, Diags));
174+
ASTContext::get(langOpts, typeckOpts, searchPathOpts, tmpSM, tmpDiags));
173175
registerIDERequestFunctions(Ctx->evaluator);
174176
registerTypeCheckerRequestFunctions(Ctx->evaluator);
175177
ModuleDecl *M = ModuleDecl::create(Identifier(), *Ctx);
176-
unsigned BufferID = SM.getCodeCompletionBufferID();
177178
PersistentParserState newState;
178179
SourceFile *newSF =
179-
new (*Ctx) SourceFile(*M, SourceFileKind::Library, BufferID,
180+
new (*Ctx) SourceFile(*M, SourceFileKind::Library, tmpBufferID,
180181
SourceFile::ImplicitModuleImportKind::None);
181182
newSF->enableInterfaceHash();
182-
parseIntoSourceFileFull(*newSF, BufferID, &newState);
183+
parseIntoSourceFileFull(*newSF, tmpBufferID, &newState);
183184
// Couldn't find any completion token?
184185
if (!newState.hasCodeCompletionDelayedDeclState())
185186
return false;
@@ -208,6 +209,33 @@ bool CompletionInstance::performCachedOperaitonIfPossible(
208209
// OK, we can perform fast completion for this. Update the orignal delayed
209210
// decl state.
210211

212+
// Fast completion keeps the buffer in memory for multiple completions.
213+
// To reduce the consumption, slice the source buffer so it only holds
214+
// the portion that is needed for the second pass.
215+
auto startOffset = newInfo.StartOffset;
216+
if (newInfo.PrevOffset != ~0u)
217+
startOffset = newInfo.PrevOffset;
218+
auto startLoc = tmpSM.getLocForOffset(tmpBufferID, startOffset);
219+
startLoc = Lexer::getLocForStartOfLine(tmpSM, startLoc);
220+
startOffset = tmpSM.getLocOffsetInBuffer(startLoc, tmpBufferID);
221+
222+
auto endOffset = newInfo.EndOffset;
223+
auto endLoc = tmpSM.getLocForOffset(tmpBufferID, endOffset);
224+
endLoc = Lexer::getLocForEndOfToken(tmpSM, endLoc);
225+
endOffset = tmpSM.getLocOffsetInBuffer(endLoc, tmpBufferID);
226+
227+
newInfo.StartOffset -= startOffset;
228+
newInfo.EndOffset -= startOffset;
229+
if (newInfo.PrevOffset != ~0u)
230+
newInfo.PrevOffset -= startOffset;
231+
232+
auto sourceText = completionBuffer->getBuffer().slice(startOffset, endOffset);
233+
auto newOffset = Offset - startOffset;
234+
235+
auto newBufferID =
236+
SM.addMemBufferCopy(sourceText, completionBuffer->getBufferIdentifier());
237+
SM.setCodeCompletionPoint(newBufferID, newOffset);
238+
211239
// Construct dummy scopes. We don't need to restore the original scope
212240
// because they are probably not 'isResolvable()' anyway.
213241
auto &SI = oldState.getScopeInfo();
@@ -227,8 +255,8 @@ bool CompletionInstance::performCachedOperaitonIfPossible(
227255
if (DiagC)
228256
CI.addDiagnosticConsumer(DiagC);
229257

230-
CI.getDiags().diagnose(SM.getLocForOffset(BufferID, newInfo.StartOffset),
231-
diag::completion_reusing_astcontext);
258+
CI.getDiags().diagnose(SM.getLocForOffset(newBufferID, newInfo.StartOffset),
259+
diag::completion_reusing_astcontext);
232260

233261
Callback(CI);
234262

0 commit comments

Comments
 (0)