Skip to content

Commit ec0750c

Browse files
committed
[SILGen] Optimize generated dealloc for linearly recursive data structures
Adds detection of linearly recursive data structures by finding stored properties that share the type of the class the dealloc is being generated for. Each link will then be deallocated in a loop, while ensuring to keep the next link alive to prevent the recursion. This prevents stack overflows for long chains while also improving performance. rdar://89162954
1 parent b0c2dbe commit ec0750c

File tree

2 files changed

+157
-3
lines changed

2 files changed

+157
-3
lines changed

lib/SILGen/SILGenDestructor.cpp

Lines changed: 145 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "SILGenFunctionBuilder.h"
1515
#include "RValue.h"
1616
#include "ArgumentScope.h"
17+
#include "llvm/ADT/SmallSet.h"
1718
#include "swift/AST/GenericSignature.h"
1819
#include "swift/AST/SubstitutionMap.h"
1920
#include "swift/SIL/TypeLowering.h"
@@ -219,6 +220,140 @@ void SILGenFunction::destroyClassMember(SILLocation cleanupLoc,
219220
}
220221
}
221222

223+
llvm::SmallSetVector<VarDecl*, 4> findRecursiveLinks(DeclContext* DC, ClassDecl *cd) {
224+
auto SelfTy = DC->mapTypeIntoContext(cd->getDeclaredType());
225+
226+
// Collect all stored properties that would form a recursive structure,
227+
// so we can remove the recursion and prevent the call stack from
228+
// overflowing.
229+
llvm::SmallSetVector<VarDecl*, 4> recursiveLinks;
230+
for (VarDecl *vd : cd->getStoredProperties()) {
231+
auto Ty = vd->getType()->getOptionalObjectType();
232+
if (Ty && Ty->getCanonicalType() == SelfTy->getCanonicalType()) {
233+
recursiveLinks.insert(vd);
234+
}
235+
}
236+
237+
// NOTE: Right now we only optimize linear recursion, so if there is more than one link,
238+
// clear out the set and don't perform any recursion optimization.
239+
if (recursiveLinks.size() < 1) {
240+
recursiveLinks.clear();
241+
}
242+
243+
return recursiveLinks;
244+
}
245+
246+
247+
void SILGenFunction::emitRecursiveChainDestruction(ManagedValue selfValue,
248+
ClassDecl *cd,
249+
SmallSetVector<VarDecl*, 4> recursiveLinks,
250+
CleanupLocation cleanupLoc) {
251+
auto SelfTy = F.mapTypeIntoContext(cd->getDeclaredType());
252+
253+
assert(recursiveLinks.size() <= 1 && "Only linear recursion supported.");
254+
255+
auto SelfTyLowered = getTypeLowering(SelfTy).getLoweredType();
256+
for (VarDecl* vd : recursiveLinks) {
257+
SILBasicBlock* cleanBB = createBasicBlock();
258+
SILBasicBlock* noneBB = createBasicBlock();
259+
SILBasicBlock* notUniqueBB = createBasicBlock();
260+
SILBasicBlock* uniqueBB = createBasicBlock();
261+
SILBasicBlock* someBB = createBasicBlock();
262+
SILBasicBlock* loopBB = createBasicBlock();
263+
264+
// var iter = self.link
265+
// self.link = nil
266+
auto Ty = getTypeLowering(vd->getType()).getLoweredType();
267+
auto optionalNone = B.createOptionalNone(cleanupLoc, Ty);
268+
SILValue varAddr =
269+
B.createRefElementAddr(cleanupLoc, selfValue.getValue(), vd,
270+
Ty.getAddressType());
271+
auto iterAddr = B.createAllocStack(cleanupLoc, Ty);
272+
SILValue addr = B.createBeginAccess(
273+
cleanupLoc, varAddr, SILAccessKind::Modify, SILAccessEnforcement::Static,
274+
false /*noNestedConflict*/, false /*fromBuiltin*/);
275+
SILValue iter = B.createLoad(cleanupLoc, addr, LoadOwnershipQualifier::Copy);
276+
B.createStore(cleanupLoc, optionalNone, addr, StoreOwnershipQualifier::Assign);
277+
B.createEndAccess(cleanupLoc, addr, false /*is aborting*/);
278+
B.createStore(cleanupLoc, iter, iterAddr, StoreOwnershipQualifier::Init);
279+
280+
B.createBranch(cleanupLoc, loopBB);
281+
282+
// while iter != nil {
283+
B.emitBlock(loopBB);
284+
SILValue operand = B.createLoad(cleanupLoc, iterAddr, LoadOwnershipQualifier::Copy);
285+
auto operandCopy = B.createCopyValue(cleanupLoc, operand);
286+
auto operandAddr = B.createAllocStack(cleanupLoc, Ty);
287+
B.createStore(cleanupLoc, operandCopy, operandAddr, StoreOwnershipQualifier::Init);
288+
B.createDestroyValue(cleanupLoc, operand);
289+
B.createSwitchEnumAddr(
290+
cleanupLoc, operandAddr, nullptr,
291+
{{getASTContext().getOptionalSomeDecl(), someBB},
292+
{std::make_pair(getASTContext().getOptionalNoneDecl(), noneBB)}});
293+
294+
// if isKnownUniquelyReferenced(&iter) {
295+
B.emitBlock(someBB);
296+
B.createDestroyAddr(cleanupLoc, operandAddr);
297+
B.createDeallocStack(cleanupLoc, operandAddr);
298+
auto isUnique = B.createIsUnique(cleanupLoc, iterAddr);
299+
B.createCondBranch(cleanupLoc, isUnique, uniqueBB, notUniqueBB);
300+
301+
302+
// we have a uniquely referenced link, so we need to deinit
303+
B.emitBlock(uniqueBB);
304+
305+
// NOTE: We increment the ref count of the tail instead of unlinking it,
306+
// because custom deinit implementations of subclasses may access
307+
// it and it would be semantically wrong to unset it before that.
308+
// Making the tail non-uniquely referenced prevents the recursion.
309+
310+
// let tail = iter.unsafelyUnwrapped.next
311+
// iter = tail
312+
SILValue _iter = B.createLoad(cleanupLoc, iterAddr, LoadOwnershipQualifier::Copy);
313+
auto iterBorrow = B.createBeginBorrow(cleanupLoc, _iter);
314+
auto iterBorrowAddr = B.createAllocStack(cleanupLoc, Ty);
315+
B.createStoreBorrow(cleanupLoc, iterBorrow, iterBorrowAddr);
316+
auto xx = B.createLoadBorrow(cleanupLoc, iterBorrowAddr);
317+
auto* link = B.createUncheckedEnumData(cleanupLoc,
318+
xx,
319+
getASTContext().getOptionalSomeDecl(),
320+
SelfTyLowered);
321+
varAddr = B.createRefElementAddr(cleanupLoc,
322+
link,
323+
vd,
324+
Ty.getAddressType());
325+
326+
addr = B.createBeginAccess(
327+
cleanupLoc, varAddr, SILAccessKind::Read, SILAccessEnforcement::Static,
328+
false /* noNestedConflict */, false /*fromBuiltin*/);
329+
iter = B.createLoad(cleanupLoc, addr, LoadOwnershipQualifier::Copy);
330+
B.createEndAccess(cleanupLoc, addr, false /*is aborting*/);
331+
B.createStore(cleanupLoc, iter, iterAddr, StoreOwnershipQualifier::Assign);
332+
333+
B.createEndBorrow(cleanupLoc, xx);
334+
B.createEndBorrow(cleanupLoc, iterBorrow);
335+
336+
B.createDestroyValue(cleanupLoc, _iter);
337+
B.createDeallocStack(cleanupLoc, iterBorrowAddr);
338+
339+
B.createBranch(cleanupLoc, loopBB);
340+
341+
// the next link in the chain is not unique, so we are done here
342+
B.emitBlock(notUniqueBB);
343+
B.createBranch(cleanupLoc, cleanBB);
344+
345+
// we reached the end of the chain
346+
B.emitBlock(noneBB);
347+
B.createDeallocStack(cleanupLoc, operandAddr);
348+
B.createBranch(cleanupLoc, cleanBB);
349+
350+
351+
B.emitBlock(cleanBB);
352+
B.createDestroyAddr(cleanupLoc, iterAddr);
353+
B.createDeallocStack(cleanupLoc, iterAddr);
354+
}
355+
}
356+
222357
void SILGenFunction::emitClassMemberDestruction(ManagedValue selfValue,
223358
ClassDecl *cd,
224359
CleanupLocation cleanupLoc) {
@@ -239,21 +374,28 @@ void SILGenFunction::emitClassMemberDestruction(ManagedValue selfValue,
239374
/// A distributed actor may be 'remote' in which case there is no need to
240375
/// destroy "all" members, because they never had storage to begin with.
241376
if (cd->isDistributedActor()) {
242-
finishBB = createBasicBlock();
243377
normalMemberDestroyBB = createBasicBlock();
244-
378+
finishBB = createBasicBlock();
245379
emitDistributedActorClassMemberDestruction(cleanupLoc, selfValue, cd,
246380
normalMemberDestroyBB,
247381
finishBB);
248382
}
249383

384+
auto recursiveLinks = findRecursiveLinks(F.getDeclContext(), cd);
385+
250386
/// Destroy all members.
251387
{
252388
if (normalMemberDestroyBB)
253389
B.emitBlock(normalMemberDestroyBB);
254390

255-
for (VarDecl *vd : cd->getStoredProperties())
391+
for (VarDecl *vd : cd->getStoredProperties()) {
392+
if (recursiveLinks.contains(vd))
393+
continue;
256394
destroyClassMember(cleanupLoc, selfValue, vd);
395+
}
396+
397+
if (!recursiveLinks.empty())
398+
emitRecursiveChainDestruction(selfValue, cd, recursiveLinks, cleanupLoc);
257399

258400
if (finishBB)
259401
B.createBranch(cleanupLoc, finishBB);

lib/SILGen/SILGenFunction.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,18 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
680680
void emitClassMemberDestruction(ManagedValue selfValue, ClassDecl *cd,
681681
CleanupLocation cleanupLoc);
682682

683+
/// Generates code to destroy recursive data structures, without building
684+
/// up the call stack.
685+
///
686+
/// \param selfValue The 'self' value.
687+
/// \param cd The class declaration whose members are being destroyed.
688+
/// \param recursiveLinks The set of stored properties that form the
689+
/// recursive data structure.
690+
void emitRecursiveChainDestruction(ManagedValue selfValue,
691+
ClassDecl *cd,
692+
llvm::SmallSetVector<VarDecl*, 4> recursiveLinks,
693+
CleanupLocation cleanupLoc);
694+
683695
/// Generates a thunk from a foreign function to the native Swift convention.
684696
void emitForeignToNativeThunk(SILDeclRef thunk);
685697
/// Generates a thunk from a native function to foreign conventions.

0 commit comments

Comments
 (0)