Skip to content

Commit 9d76be8

Browse files
authored
Merge pull request #66933 from eeckstein/stack-promotion
StackPromotion: support promoting allocations in dead-end control flow regions
2 parents 7aa6cbb + d0fb49e commit 9d76be8

File tree

2 files changed

+84
-12
lines changed

2 files changed

+84
-12
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/StackPromotion.swift

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,9 @@ let stackPromotion = FunctionPass(name: "stack-promotion") {
4646
var needFixStackNesting = false
4747
for inst in function.instructions {
4848
if let allocRef = inst as? AllocRefInstBase {
49-
if deadEndBlocks.isDeadEnd(allocRef.parentBlock) {
50-
// Don't stack promote any allocation inside a code region which ends up
51-
// in a no-return block. Such allocations may missing their final release.
52-
// We would insert the deallocation too early, which may result in a
53-
// use-after-free problem.
54-
continue
55-
}
5649
if !context.continueWithNextSubpassRun(for: allocRef) {
5750
break
5851
}
59-
6052
if tryPromoteAlloc(allocRef, deadEndBlocks, context) {
6153
needFixStackNesting = true
6254
}
@@ -75,6 +67,41 @@ private func tryPromoteAlloc(_ allocRef: AllocRefInstBase,
7567
if allocRef.isObjC || allocRef.canAllocOnStack {
7668
return false
7769
}
70+
71+
if deadEndBlocks.isDeadEnd(allocRef.parentBlock) {
72+
73+
// Allocations inside a code region which ends up in a no-return block may missing their
74+
// final release. Therefore we extend their lifetime indefinitely, e.g.
75+
//
76+
// %k = alloc_ref $Klass
77+
// ...
78+
// unreachable // The end of %k's lifetime
79+
//
80+
// Also, such an allocation cannot escape the function, because the function does not
81+
// return after the point of allocation. So we can stack-promote it unconditionally.
82+
//
83+
// There is one exception: if it's in a loop (within the dead-end region) we must not
84+
// extend its lifetime. On the other hand we can be sure that its final release is not
85+
// missing, because otherwise the object would be leaking. For example:
86+
//
87+
// bb1:
88+
// %k = alloc_ref $Klass
89+
// ... // %k's lifetime must end somewhere here
90+
// cond_br %c, bb1, bb2
91+
// bb2:
92+
// unreachable
93+
//
94+
// Therefore, if the allocation is inside a loop, we can treat it like allocations in
95+
// non dead-end regions.
96+
if !isInLoop(block: allocRef.parentBlock, context),
97+
// TODO: for some reason this doesn't work for aysnc functions.
98+
// Maybe a problem with co-routine splitting in LLVM?
99+
!allocRef.parentFunction.isAsync {
100+
allocRef.setIsStackAllocatable(context)
101+
return true
102+
}
103+
}
104+
78105
// The most important check: does the object escape the current function?
79106
if allocRef.isEscaping(context) {
80107
return false
@@ -293,3 +320,16 @@ private extension BasicBlockRange {
293320
}
294321
}
295322

323+
private func isInLoop(block startBlock: BasicBlock, _ context: FunctionPassContext) -> Bool {
324+
var worklist = BasicBlockWorklist(context)
325+
defer { worklist.deinitialize() }
326+
327+
worklist.pushIfNotVisited(contentsOf: startBlock.successors)
328+
while let block = worklist.pop() {
329+
if block == startBlock {
330+
return true
331+
}
332+
worklist.pushIfNotVisited(contentsOf: block.successors)
333+
}
334+
return false
335+
}

test/SILOptimizer/stack_promotion.sil

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,14 @@ bb0:
7474
return %n1 : $XX
7575
}
7676

77-
// CHECK-LABEL: sil @dont_promote_in_unreachable
78-
// CHECK: bb1:
79-
// CHECK-NEXT: alloc_ref $XX
80-
sil @dont_promote_in_unreachable : $@convention(thin) () -> () {
77+
// CHECK-LABEL: sil @promote_in_unreachable :
78+
// CHECK: bb1:
79+
// CHECK-NEXT: alloc_ref [stack] $XX
80+
// CHECK: apply
81+
// CHECK: apply
82+
// CHECK-NEXT: unreachable
83+
// CHECK: } // end sil function 'promote_in_unreachable'
84+
sil @promote_in_unreachable : $@convention(thin) () -> () {
8185
bb0:
8286
cond_br undef, bb1, bb2
8387

@@ -97,6 +101,34 @@ bb2:
97101
return %r : $()
98102
}
99103

104+
// CHECK-LABEL: sil @promote_in_unreachable_loop :
105+
// CHECK: bb1:
106+
// CHECK-NEXT: alloc_ref [stack] $XX
107+
// CHECK: apply
108+
// CHECK: apply
109+
// CHECK-NEXT: strong_release
110+
// CHECK-NEXT: dealloc_stack_ref
111+
// CHECK-NEXT: cond_br
112+
// CHECK: } // end sil function 'promote_in_unreachable_loop'
113+
sil @promote_in_unreachable_loop : $@convention(thin) () -> () {
114+
bb0:
115+
br bb1
116+
117+
bb1:
118+
%o1 = alloc_ref $XX
119+
%f1 = function_ref @xx_init : $@convention(thin) (@guaranteed XX) -> XX
120+
%n1 = apply %f1(%o1) : $@convention(thin) (@guaranteed XX) -> XX
121+
%l1 = ref_element_addr %n1 : $XX, #XX.x
122+
%l2 = load %l1 : $*Int32
123+
%f2 = function_ref @consume_int : $@convention(thin) (Int32) -> ()
124+
%a = apply %f2(%l2) : $@convention(thin) (Int32) -> ()
125+
strong_release %o1 : $XX
126+
cond_br undef, bb1, bb2
127+
128+
bb2:
129+
unreachable
130+
}
131+
100132
// CHECK-LABEL: sil @promote_nested
101133
// CHECK: [[X:%[0-9]+]] = alloc_ref [stack] $XX
102134
// CHECK: [[Y:%[0-9]+]] = alloc_ref [stack] $YY

0 commit comments

Comments
 (0)