Skip to content

Commit 6d55ecd

Browse files
authored
Merge pull request #41459 from drexin/wip-dealloc-recur
[SILGen] Optimize generated dealloc for linearly recursive data struc…
2 parents e737770 + ba0de89 commit 6d55ecd

File tree

6 files changed

+348
-5
lines changed

6 files changed

+348
-5
lines changed

lib/SILGen/SILGenDestructor.cpp

Lines changed: 159 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#include "ArgumentScope.h"
14+
#include "RValue.h"
1315
#include "SILGenFunction.h"
1416
#include "SILGenFunctionBuilder.h"
15-
#include "RValue.h"
16-
#include "ArgumentScope.h"
17+
#include "SwitchEnumBuilder.h"
1718
#include "swift/AST/GenericSignature.h"
1819
#include "swift/AST/SubstitutionMap.h"
1920
#include "swift/SIL/TypeLowering.h"
21+
#include "llvm/ADT/SmallSet.h"
2022

2123
using namespace swift;
2224
using namespace Lowering;
@@ -219,6 +221,142 @@ void SILGenFunction::destroyClassMember(SILLocation cleanupLoc,
219221
}
220222
}
221223

224+
/// Finds stored properties that have the same type as `cd` and thus form
225+
/// a recursive structure.
226+
///
227+
/// Example:
228+
///
229+
/// class Node<T> {
230+
/// let element: T
231+
/// let next: Node<T>?
232+
/// }
233+
///
234+
/// In the above example `next` is a recursive link and would be recognized
235+
/// by this function and added to the result set.
236+
static void findRecursiveLinks(ClassDecl *cd,
237+
llvm::SmallSetVector<VarDecl *, 4> &result) {
238+
auto selfTy = cd->getDeclaredInterfaceType();
239+
240+
// Collect all stored properties that would form a recursive structure,
241+
// so we can remove the recursion and prevent the call stack from
242+
// overflowing.
243+
for (VarDecl *vd : cd->getStoredProperties()) {
244+
auto Ty = vd->getInterfaceType()->getOptionalObjectType();
245+
if (Ty && Ty->getCanonicalType() == selfTy->getCanonicalType()) {
246+
result.insert(vd);
247+
}
248+
}
249+
250+
// NOTE: Right now we only optimize linear recursion, so if there is more
251+
// than one stored property of the same type, clear out the set and don't
252+
// perform any recursion optimization.
253+
if (result.size() > 1) {
254+
result.clear();
255+
}
256+
}
257+
258+
void SILGenFunction::emitRecursiveChainDestruction(ManagedValue selfValue,
259+
ClassDecl *cd,
260+
VarDecl *recursiveLink,
261+
CleanupLocation cleanupLoc) {
262+
auto selfTy = F.mapTypeIntoContext(cd->getDeclaredInterfaceType());
263+
264+
auto selfTyLowered = getTypeLowering(selfTy).getLoweredType();
265+
266+
SILBasicBlock *cleanBB = createBasicBlock();
267+
SILBasicBlock *noneBB = createBasicBlock();
268+
SILBasicBlock *notUniqueBB = createBasicBlock();
269+
SILBasicBlock *uniqueBB = createBasicBlock();
270+
SILBasicBlock *someBB = createBasicBlock();
271+
SILBasicBlock *loopBB = createBasicBlock();
272+
273+
// var iter = self.link
274+
// self.link = nil
275+
auto Ty = getTypeLowering(F.mapTypeIntoContext(recursiveLink->getInterfaceType())).getLoweredType();
276+
auto optionalNone = B.createOptionalNone(cleanupLoc, Ty);
277+
SILValue varAddr =
278+
B.createRefElementAddr(cleanupLoc, selfValue.getValue(), recursiveLink,
279+
Ty.getAddressType());
280+
auto *iterAddr = B.createAllocStack(cleanupLoc, Ty);
281+
SILValue addr = B.createBeginAccess(
282+
cleanupLoc, varAddr, SILAccessKind::Modify, SILAccessEnforcement::Static,
283+
true /*noNestedConflict*/, false /*fromBuiltin*/);
284+
SILValue iter = B.createLoad(cleanupLoc, addr, LoadOwnershipQualifier::Take);
285+
B.createStore(cleanupLoc, optionalNone, addr, StoreOwnershipQualifier::Init);
286+
B.createEndAccess(cleanupLoc, addr, false /*is aborting*/);
287+
B.createStore(cleanupLoc, iter, iterAddr, StoreOwnershipQualifier::Init);
288+
289+
B.createBranch(cleanupLoc, loopBB);
290+
291+
// while iter != nil {
292+
{
293+
B.emitBlock(loopBB);
294+
auto iterBorrow =
295+
ManagedValue::forUnmanaged(iterAddr).borrow(*this, cleanupLoc);
296+
SwitchEnumBuilder switchBuilder(B, cleanupLoc, iterBorrow);
297+
switchBuilder.addOptionalSomeCase(someBB);
298+
switchBuilder.addOptionalNoneCase(noneBB);
299+
std::move(switchBuilder).emit();
300+
}
301+
302+
// if isKnownUniquelyReferenced(&iter) {
303+
{
304+
B.emitBlock(someBB);
305+
auto isUnique = B.createIsUnique(cleanupLoc, iterAddr);
306+
B.createCondBranch(cleanupLoc, isUnique, uniqueBB, notUniqueBB);
307+
}
308+
309+
// we have a uniquely referenced link, so we need to deinit
310+
{
311+
B.emitBlock(uniqueBB);
312+
313+
// let tail = iter.unsafelyUnwrapped.next
314+
// iter = tail
315+
SILValue iterBorrow = B.createLoadBorrow(cleanupLoc, iterAddr);
316+
auto *link = B.createUncheckedEnumData(
317+
cleanupLoc, iterBorrow, getASTContext().getOptionalSomeDecl(),
318+
selfTyLowered);
319+
320+
varAddr = B.createRefElementAddr(cleanupLoc, link, recursiveLink,
321+
Ty.getAddressType());
322+
323+
addr = B.createBeginAccess(
324+
cleanupLoc, varAddr, SILAccessKind::Read, SILAccessEnforcement::Static,
325+
true /* noNestedConflict */, false /*fromBuiltin*/);
326+
327+
// The deinit of `iter` will decrement the ref count of the field
328+
// containing the next element and potentially leading to its
329+
// deinitialization, causing the recursion. The prevent that,
330+
// we `load [copy]` here to ensure the object stays alive until
331+
// we explicitly release it in the next step of the iteration.
332+
iter = B.createLoad(cleanupLoc, addr, LoadOwnershipQualifier::Copy);
333+
B.createEndAccess(cleanupLoc, addr, false /*is aborting*/);
334+
B.createEndBorrow(cleanupLoc, iterBorrow);
335+
336+
B.createStore(cleanupLoc, iter, iterAddr, StoreOwnershipQualifier::Assign);
337+
338+
B.createBranch(cleanupLoc, loopBB);
339+
}
340+
341+
// the next link in the chain is not unique, so we are done here
342+
{
343+
B.emitBlock(notUniqueBB);
344+
B.createBranch(cleanupLoc, cleanBB);
345+
}
346+
347+
// we reached the end of the chain
348+
{
349+
B.emitBlock(noneBB);
350+
B.createBranch(cleanupLoc, cleanBB);
351+
}
352+
353+
{
354+
B.emitBlock(cleanBB);
355+
B.createDestroyAddr(cleanupLoc, iterAddr);
356+
B.createDeallocStack(cleanupLoc, iterAddr);
357+
}
358+
}
359+
222360
void SILGenFunction::emitClassMemberDestruction(ManagedValue selfValue,
223361
ClassDecl *cd,
224362
CleanupLocation cleanupLoc) {
@@ -231,10 +369,10 @@ void SILGenFunction::emitClassMemberDestruction(ManagedValue selfValue,
231369
/// For other cases, the basic blocks are not necessary and the destructor
232370
/// can just emit all the normal destruction code right into the current block.
233371
// If set, used as the basic block for the destroying of all members.
234-
SILBasicBlock* normalMemberDestroyBB = nullptr;
372+
SILBasicBlock *normalMemberDestroyBB = nullptr;
235373
// If set, used as the basic block after members have been destroyed,
236374
// and we're ready to perform final cleanups before returning.
237-
SILBasicBlock* finishBB = nullptr;
375+
SILBasicBlock *finishBB = nullptr;
238376

239377
/// A distributed actor may be 'remote' in which case there is no need to
240378
/// destroy "all" members, because they never had storage to begin with.
@@ -247,13 +385,29 @@ void SILGenFunction::emitClassMemberDestruction(ManagedValue selfValue,
247385
finishBB);
248386
}
249387

388+
// Before we destroy all fields, we check if any of them are
389+
// recursively the same type as `self`, so we can iteratively
390+
// deinitialize them, to prevent deep recursion and potential
391+
// stack overflows.
392+
393+
llvm::SmallSetVector<VarDecl *, 4> recursiveLinks;
394+
findRecursiveLinks(cd, recursiveLinks);
395+
250396
/// Destroy all members.
251397
{
252398
if (normalMemberDestroyBB)
253399
B.emitBlock(normalMemberDestroyBB);
254400

255-
for (VarDecl *vd : cd->getStoredProperties())
401+
for (VarDecl *vd : cd->getStoredProperties()) {
402+
if (recursiveLinks.contains(vd))
403+
continue;
256404
destroyClassMember(cleanupLoc, selfValue, vd);
405+
}
406+
407+
if (!recursiveLinks.empty()) {
408+
assert(recursiveLinks.size() == 1 && "Only linear recursion supported.");
409+
emitRecursiveChainDestruction(selfValue, cd, recursiveLinks[0], cleanupLoc);
410+
}
257411

258412
if (finishBB)
259413
B.createBranch(cleanupLoc, finishBB);

lib/SILGen/SILGenFunction.h

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

683+
/// Generates code to destroy linearly recursive data structures, without
684+
/// building up the call stack.
685+
///
686+
/// E.x.: In the following we want to deinit next without recursing into next.
687+
///
688+
/// class Node<A> {
689+
/// let value: A
690+
/// let next: Node<A>?
691+
/// }
692+
///
693+
/// \param selfValue The 'self' value.
694+
/// \param cd The class declaration whose members are being destroyed.
695+
/// \param recursiveLink The property that forms the recursive structure.
696+
void emitRecursiveChainDestruction(ManagedValue selfValue,
697+
ClassDecl *cd,
698+
VarDecl* recursiveLink,
699+
CleanupLocation cleanupLoc);
700+
683701
/// Generates a thunk from a foreign function to the native Swift convention.
684702
void emitForeignToNativeThunk(SILDeclRef thunk);
685703
/// Generates a thunk from a native function to foreign conventions.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: %target-run-simple-swift
2+
3+
// REQUIRES: executable_test
4+
5+
class Node {
6+
var next: Node?
7+
}
8+
9+
var first: Node? = nil
10+
for _ in 1...3_000_000 {
11+
let next = Node()
12+
next.next = first
13+
first = next
14+
}
15+
16+
@inline(never)
17+
func deallocList() {
18+
first = nil
19+
}
20+
deallocList()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %target-swift-emit-silgen %s | %FileCheck %s
2+
3+
// Non-linearly recursive structures should not get optimized
4+
class Tree {
5+
var left: Tree?
6+
var right: Tree?
7+
}
8+
9+
// CHECK: sil hidden [ossa] @$s26deinit_recursive_branching4TreeCfd : $@convention(method) (@guaranteed Tree) -> @owned Builtin.NativeObject {
10+
// CHECK: // [[SELF:%.*]] "self"
11+
// CHECK: bb0([[SELF]] : @guaranteed $Tree):
12+
// CHECK: [[LEFT:%.*]] = ref_element_addr [[SELF]] : $Tree, #Tree.left
13+
// CHECK: [[LEFT_ACCESS:%.*]] = begin_access [deinit] [static] [[LEFT]] : $*Optional<Tree>
14+
// CHECK: destroy_addr [[LEFT_ACCESS]] : $*Optional<Tree>
15+
// CHECK: end_access [[LEFT_ACCESS]] : $*Optional<Tree>
16+
// CHECK: [[RIGHT:%.*]] = ref_element_addr [[SELF]] : $Tree, #Tree.right
17+
// CHECK: [[RIGHT_ACCESS:%.*]] = begin_access [deinit] [static] [[RIGHT]] : $*Optional<Tree>
18+
// CHECK: destroy_addr [[RIGHT_ACCESS]] : $*Optional<Tree>
19+
// CHECK: end_access [[RIGHT_ACCESS]] : $*Optional<Tree> // id: %9
20+
// CHECK: [[SELF_NATIVE:%.*]] = unchecked_ref_cast [[SELF]] : $Tree to $Builtin.NativeObject
21+
// CHECK: [[SELF_NATIVE_OWNED:%.*]] = unchecked_ownership_conversion [[SELF_NATIVE]] : $Builtin.NativeObject, @guaranteed to @owned
22+
// CHECK: return [[SELF_NATIVE_OWNED]] : $Builtin.NativeObject
23+
// CHECK: } // end sil function '$s26deinit_recursive_branching4TreeCfd'
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// RUN: %target-swift-emit-silgen %s | %FileCheck %s
2+
3+
class Node {
4+
var elem: [Int64] = []
5+
var next: Node?
6+
}
7+
8+
// CHECK: sil hidden [ossa] @$s23deinit_recursive_linear4NodeCfd : $@convention(method) (@guaranteed Node) -> @owned Builtin.NativeObject {
9+
// CHECK: [[SELF:%.*]] "self"
10+
// CHECK: bb0([[SELF]] : @guaranteed $Node):
11+
// CHECK: [[ELEM:%.*]] = ref_element_addr [[SELF]] : $Node, #Node.elem
12+
// CHECK: [[ELEM_ACCESS:%.*]] = begin_access [deinit] [static] [[ELEM]] : $*Array<Int64>
13+
// CHECK: destroy_addr [[ELEM_ACCESS]] : $*Array<Int64>
14+
// CHECK: end_access [[ELEM_ACCESS]] : $*Array<Int64>
15+
// CHECK: [[NIL:%.*]] = enum $Optional<Node>, #Optional.none!enumelt
16+
// CHECK: [[SELF_NEXT:%.*]] = ref_element_addr [[SELF]] : $Node, #Node.next
17+
// CHECK: [[ITER:%.*]] = alloc_stack $Optional<Node>
18+
// CHECK: [[SELF_NEXT_ACCESS:%.*]] = begin_access [modify] [static] [no_nested_conflict] [[SELF_NEXT]] : $*Optional<Node>
19+
// CHECK: [[SELF_NEXT_COPY:%.*]] = load [take] [[SELF_NEXT_ACCESS]] : $*Optional<Node>
20+
// CHECK: store [[NIL]] to [init] [[SELF_NEXT_ACCESS]] : $*Optional<Node>
21+
// CHECK: end_access [[SELF_NEXT_ACCESS]] : $*Optional<Node>
22+
// CHECK: store [[SELF_NEXT_COPY]] to [init] [[ITER]] : $*Optional<Node>
23+
// CHECK: br [[LOOPBB:bb.*]] //
24+
25+
// CHECK: [[LOOPBB]]:
26+
// CHECK: [[ITER_COPY:%.*]] = load [copy] [[ITER]] : $*Optional<Node>
27+
// CHECK: switch_enum [[ITER_COPY]] : $Optional<Node>, case #Optional.some!enumelt: [[IS_SOME_BB:bb.*]], case #Optional.none!enumelt: [[IS_NONE_BB:bb[0-9]+]]
28+
29+
// CHECK: [[IS_SOME_BB]]([[NODE:%.*]] : @owned $Node):
30+
// CHECK: destroy_value [[NODE]] : $Node
31+
// CHECK: [[IS_UNIQUE:%.*]] = is_unique [[ITER]] : $*Optional<Node>
32+
// CHECK: cond_br [[IS_UNIQUE]], [[IS_UNIQUE_BB:bb.*]], [[NOT_UNIQUE_BB:bb[0-9]*]]
33+
34+
// CHECK: [[IS_UNIQUE_BB]]:
35+
// CHECK: [[ITER_BORROW:%.*]] = load_borrow [[ITER]] : $*Optional<Node>
36+
// CHECK: [[ITER_UNWRAPPED:%.*]] = unchecked_enum_data [[ITER_BORROW]] : $Optional<Node>, #Optional.some!enumelt
37+
// CHECK: [[NEXT_ADDR:%.*]] = ref_element_addr [[ITER_UNWRAPPED]] : $Node, #Node.next
38+
// CHECK: [[NEXT_ADDR_ACCESS:%.*]] = begin_access [read] [static] [no_nested_conflict] [[NEXT_ADDR]] : $*Optional<Node>
39+
// CHECK: [[NEXT_COPY:%.*]] = load [copy] [[NEXT_ADDR_ACCESS]] : $*Optional<Node>
40+
// CHECK: end_access [[NEXT_ADDR_ACCESS]] : $*Optional<Node>
41+
// CHECK: end_borrow [[ITER_BORROW]] : $Optional<Node>
42+
// CHECK: store [[NEXT_COPY]] to [assign] [[ITER]] : $*Optional<Node>
43+
// CHECK: br [[LOOPBB]]
44+
45+
// CHECK: [[NOT_UNIQUE_BB]]:
46+
// CHECK: br bb6
47+
48+
// CHECK: [[IS_NONE_BB]]:
49+
// CHECK: br [[CLEAN_BB:bb[0-9]+]]
50+
51+
// CHECK: [[CLEAN_BB]]:
52+
// CHECK: destroy_addr [[ITER]] : $*Optional<Node>
53+
// CHECK: dealloc_stack [[ITER]] : $*Optional<Node>
54+
// CHECK: [[SELF_NATIVE:%.*]] = unchecked_ref_cast [[SELF]] : $Node to $Builtin.NativeObject
55+
// CHECK: [[SELF_NATIVE_OWNED:%.*]] = unchecked_ownership_conversion [[SELF_NATIVE]] : $Builtin.NativeObject, @guaranteed to @owned
56+
// CHECK: return [[SELF_NATIVE_OWNED]] : $Builtin.NativeObject
57+
// CHECK: } // end sil function '$s23deinit_recursive_linear4NodeCfd'

0 commit comments

Comments
 (0)