@@ -46,17 +46,9 @@ let stackPromotion = FunctionPass(name: "stack-promotion") {
46
46
var needFixStackNesting = false
47
47
for inst in function. instructions {
48
48
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
- }
56
49
if !context. continueWithNextSubpassRun ( for: allocRef) {
57
50
break
58
51
}
59
-
60
52
if tryPromoteAlloc ( allocRef, deadEndBlocks, context) {
61
53
needFixStackNesting = true
62
54
}
@@ -75,6 +67,41 @@ private func tryPromoteAlloc(_ allocRef: AllocRefInstBase,
75
67
if allocRef. isObjC || allocRef. canAllocOnStack {
76
68
return false
77
69
}
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
+
78
105
// The most important check: does the object escape the current function?
79
106
if allocRef. isEscaping ( context) {
80
107
return false
@@ -293,3 +320,16 @@ private extension BasicBlockRange {
293
320
}
294
321
}
295
322
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
+ }
0 commit comments