Skip to content

Commit ec7a91d

Browse files
authored
Merge pull request swiftlang#78162 from eeckstein/improve-release-devirtualizer
ReleaseDevirtualizer: improve finding the last release of an allocation
2 parents b2ae07d + 9cdd9fa commit ec7a91d

File tree

2 files changed

+154
-68
lines changed

2 files changed

+154
-68
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ReleaseDevirtualizer.swift

Lines changed: 72 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -34,54 +34,26 @@ import SIL
3434
let releaseDevirtualizerPass = FunctionPass(name: "release-devirtualizer") {
3535
(function: Function, context: FunctionPassContext) in
3636

37-
for block in function.blocks {
38-
// The last `release_value`` or `strong_release`` instruction before the
39-
// deallocation.
40-
var lastRelease: RefCountingInst?
41-
42-
for instruction in block.instructions {
43-
switch instruction {
44-
case let dealloc as DeallocStackRefInst:
45-
if let lastRel = lastRelease {
46-
// We only do the optimization for stack promoted object, because for
47-
// these we know that they don't have associated objects, which are
48-
// _not_ released by the deinit method.
49-
if !context.continueWithNextSubpassRun(for: lastRel) {
50-
return
51-
}
52-
tryDevirtualizeRelease(of: dealloc.allocRef, lastRelease: lastRel, context)
53-
lastRelease = nil
54-
}
55-
case let strongRelease as StrongReleaseInst:
56-
lastRelease = strongRelease
57-
case let releaseValue as ReleaseValueInst where releaseValue.value.type.containsSingleReference(in: function):
58-
lastRelease = releaseValue
59-
case is DeallocRefInst, is BeginDeallocRefInst:
60-
lastRelease = nil
61-
default:
62-
if instruction.mayRelease {
63-
lastRelease = nil
64-
}
37+
for inst in function.instructions {
38+
if let dealloc = inst as? DeallocStackRefInst {
39+
if !context.continueWithNextSubpassRun(for: dealloc) {
40+
return
6541
}
42+
tryDevirtualizeRelease(of: dealloc, context)
6643
}
6744
}
6845
}
6946

70-
/// Tries to de-virtualize the final release of a stack-promoted object.
71-
private func tryDevirtualizeRelease(
72-
of allocRef: AllocRefInstBase,
73-
lastRelease: RefCountingInst,
74-
_ context: FunctionPassContext
75-
) {
76-
var downWalker = FindReleaseWalker(release: lastRelease)
77-
guard let pathToRelease = downWalker.getPathToRelease(from: allocRef) else {
47+
private func tryDevirtualizeRelease(of dealloc: DeallocStackRefInst, _ context: FunctionPassContext) {
48+
guard let (lastRelease, pathToRelease) = findLastRelease(of: dealloc, context) else {
7849
return
7950
}
8051

8152
if !pathToRelease.isMaterializable {
8253
return
8354
}
8455

56+
let allocRef = dealloc.allocRef
8557
var upWalker = FindAllocationWalker(allocation: allocRef)
8658
if upWalker.walkUp(value: lastRelease.operand.value, path: pathToRelease) == .abortWalk {
8759
return
@@ -120,23 +92,62 @@ private func tryDevirtualizeRelease(
12092
context.erase(instruction: lastRelease)
12193
}
12294

95+
private func findLastRelease(
96+
of dealloc: DeallocStackRefInst,
97+
_ context: FunctionPassContext
98+
) -> (lastRelease: RefCountingInst, pathToRelease: SmallProjectionPath)? {
99+
let allocRef = dealloc.allocRef
100+
101+
// Search for the final release in the same basic block of the dealloc.
102+
for instruction in ReverseInstructionList(first: dealloc.previous) {
103+
switch instruction {
104+
case let strongRelease as StrongReleaseInst:
105+
if let pathToRelease = getPathToRelease(from: allocRef, to: strongRelease) {
106+
return (strongRelease, pathToRelease)
107+
}
108+
case let releaseValue as ReleaseValueInst:
109+
if releaseValue.value.type.containsSingleReference(in: dealloc.parentFunction) {
110+
if let pathToRelease = getPathToRelease(from: allocRef, to: releaseValue) {
111+
return (releaseValue, pathToRelease)
112+
}
113+
}
114+
case is BeginDeallocRefInst, is DeallocRefInst:
115+
// Check if the last release was already de-virtualized.
116+
if allocRef.escapes(to: instruction, context) {
117+
return nil
118+
}
119+
default:
120+
break
121+
}
122+
if instruction.mayRelease && allocRef.escapes(to: instruction, context) {
123+
// This instruction may release the allocRef, which means that any release we find
124+
// earlier in the block is not guaranteed to be the final release.
125+
return nil
126+
}
127+
}
128+
return nil
129+
}
130+
131+
// If the release is a release_value it might release a struct which _contains_ the allocated object.
132+
// Return a projection path to the contained object in this case.
133+
private func getPathToRelease(from allocRef: AllocRefInstBase, to release: RefCountingInst) -> SmallProjectionPath? {
134+
var downWalker = FindReleaseWalker(release: release)
135+
if downWalker.walkDownUses(ofValue: allocRef, path: SmallProjectionPath()) == .continueWalk {
136+
return downWalker.result
137+
}
138+
return nil
139+
}
140+
123141
private struct FindReleaseWalker : ValueDefUseWalker {
124142
private let release: RefCountingInst
125-
private var result: SmallProjectionPath? = nil
143+
private(set) var result: SmallProjectionPath? = nil
126144

127145
var walkDownCache = WalkerCache<SmallProjectionPath>()
128146

129147
init(release: RefCountingInst) {
130148
self.release = release
131149
}
132150

133-
mutating func getPathToRelease(from allocRef: AllocRefInstBase) -> SmallProjectionPath? {
134-
if walkDownUses(ofValue: allocRef, path: SmallProjectionPath()) == .continueWalk {
135-
return result
136-
}
137-
return nil
138-
}
139-
140151
mutating func leafUse(value: Operand, path: SmallProjectionPath) -> WalkResult {
141152
if value.instruction == release {
142153
if let existingResult = result {
@@ -149,6 +160,23 @@ private struct FindReleaseWalker : ValueDefUseWalker {
149160
}
150161
}
151162

163+
private extension AllocRefInstBase {
164+
func escapes(to instruction: Instruction, _ context: FunctionPassContext) -> Bool {
165+
return self.isEscaping(using: EscapesToInstructionVisitor(target: instruction), context)
166+
}
167+
}
168+
169+
private struct EscapesToInstructionVisitor : EscapeVisitor {
170+
let target: Instruction
171+
172+
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
173+
if operand.instruction == target {
174+
return .abort
175+
}
176+
return .continueWalk
177+
}
178+
}
179+
152180
// Up-walker to find the root of a release instruction.
153181
private struct FindAllocationWalker : ValueUseDefWalker {
154182
private let allocInst: AllocRefInstBase

test/SILOptimizer/devirt_release.sil

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ struct Str {
1818
@_hasStorage var b: B
1919
}
2020

21+
sil @unknown_func : $@convention(thin) () -> ()
22+
sil @unknown_releaseing_func : $@convention(thin) (@owned B) -> ()
23+
24+
sil @$s4test1BCfD : $@convention(method) (@owned B) -> () {
25+
// Note that the destructor is not destroying, but it must block the optimization.
26+
[global: ]
27+
bb0(%0 : $B):
28+
dealloc_ref %0
29+
%r = tuple ()
30+
return %r
31+
}
32+
2133
// CHECK-LABEL: sil @devirtualize_object
2234
// CHECK: [[A:%[0-9]+]] = alloc_ref
2335
// CHECK-NEXT: [[BD:%.*]] = begin_dealloc_ref [[A]] : $B of [[A]]
@@ -71,37 +83,69 @@ bb0:
7183
return %r : $()
7284
}
7385

74-
// CHECK-LABEL: sil @dont_devirtualize_wrong_release
75-
// CHECK: alloc_ref
76-
// CHECK-NEXT: strong_release
77-
// CHECK-NEXT: strong_release
78-
// CHECK-NEXT: dealloc_ref
79-
// CHECK: return
80-
// CHECK: } // end sil function 'dont_devirtualize_wrong_release'
81-
sil @dont_devirtualize_wrong_release : $@convention(thin) (@owned B) -> () {
86+
// CHECK-LABEL: sil @dont_devirtualize_devirtualized_not_inlined :
87+
// CHECK: alloc_ref
88+
// CHECK-NEXT: strong_retain
89+
// CHECK-NEXT: strong_release
90+
// CHECK-NEXT: begin_dealloc_ref
91+
// CHECK: } // end sil function 'dont_devirtualize_devirtualized_not_inlined'
92+
sil @dont_devirtualize_devirtualized_not_inlined : $@convention(thin) () -> () {
93+
bb0:
94+
%1 = alloc_ref [stack] $B
95+
strong_retain %1 : $B
96+
strong_release %1 : $B
97+
%6 = begin_dealloc_ref %1 : $B of %1 : $B
98+
%7 = function_ref @$s4test1BCfD : $@convention(method) (@owned B) -> ()
99+
%8 = apply %7(%6) : $@convention(method) (@owned B) -> ()
100+
dealloc_stack_ref %1 : $B
101+
%r = tuple ()
102+
return %r : $()
103+
}
104+
105+
// CHECK-LABEL: sil @devirtualize_with_other_release_in_between :
106+
// CHECK: %1 = alloc_ref [stack] $B
107+
// CHECK-NOT: release
108+
// CHECK: apply
109+
// CHECK-NEXT: strong_release %0
110+
// CHECK-NEXT: dealloc_stack_ref %1
111+
// CHECK: } // end sil function 'devirtualize_with_other_release_in_between'
112+
sil @devirtualize_with_other_release_in_between : $@convention(thin) (@owned B) -> () {
82113
bb0(%0 : $B):
83-
%1 = alloc_ref $B
114+
%1 = alloc_ref [stack] $B
84115
strong_release %1 : $B
85116
strong_release %0 : $B
86-
dealloc_ref %1 : $B
117+
dealloc_stack_ref %1 : $B
87118
%r = tuple ()
88119
return %r : $()
89120
}
90121

91-
// CHECK-LABEL: sil @dont_devirtualize_unknown_release
92-
// CHECK: alloc_ref
93-
// CHECK-NEXT: strong_release
94-
// CHECK: [[F:%[0-9]+]] = function_ref @unknown_func
95-
// CHECK-NEXT: apply [[F]]()
96-
// CHECK-NEXT: dealloc_ref
97-
// CHECK: } // end sil function 'dont_devirtualize_unknown_release'
98-
sil @dont_devirtualize_unknown_release : $@convention(thin) (@owned B) -> () {
122+
// CHECK-LABEL: sil @devirtualize_with_apply_in_between :
123+
// CHECK: alloc_ref
124+
// CHECK-NEXT: begin_dealloc_ref
125+
// CHECK-NOT: release
126+
// CHECK: } // end sil function 'devirtualize_with_apply_in_between'
127+
sil @devirtualize_with_apply_in_between : $@convention(thin) (@owned B) -> () {
99128
bb0(%0 : $B):
100-
%1 = alloc_ref $B
129+
%1 = alloc_ref [stack] $B
101130
strong_release %1 : $B
102131
%f = function_ref @unknown_func : $@convention(thin) () -> ()
103132
%a = apply %f() : $@convention(thin) () -> ()
104-
dealloc_ref %1 : $B
133+
dealloc_stack_ref %1 : $B
134+
%r = tuple ()
135+
return %r : $()
136+
}
137+
138+
// CHECK-LABEL: sil @dont_devirtualize_unknown_release :
139+
// CHECK: alloc_ref
140+
// CHECK-NEXT: strong_release
141+
// CHECK: } // end sil function 'dont_devirtualize_unknown_release'
142+
sil @dont_devirtualize_unknown_release : $@convention(thin) (@owned B) -> () {
143+
bb0(%0 : $B):
144+
%1 = alloc_ref [stack] $B
145+
strong_release %1 : $B
146+
%f = function_ref @unknown_releaseing_func : $@convention(thin) (@owned B) -> ()
147+
%a = apply %f(%1) : $@convention(thin) (@owned B) -> ()
148+
dealloc_stack_ref %1 : $B
105149
%r = tuple ()
106150
return %r : $()
107151
}
@@ -205,10 +249,24 @@ bb3(%a : $B):
205249
return %r : $()
206250
}
207251

208-
209-
sil @unknown_func : $@convention(thin) () -> ()
210-
211-
sil @$s4test1BCfD : $@convention(method) (@owned B) -> ()
252+
// CHECK-LABEL: sil @devirtualize_double_release :
253+
// CHECK-NOT: release
254+
// CHECK: } // end sil function 'devirtualize_double_release'
255+
sil @devirtualize_double_release : $@convention(thin) () -> () {
256+
bb0:
257+
%0 = alloc_ref [stack] $B
258+
%1 = end_init_let_ref %0
259+
%2 = alloc_ref [stack] $B
260+
%3 = end_init_let_ref %2
261+
strong_release %1
262+
strong_release %3
263+
%4 = function_ref @unknown_func : $@convention(thin) () -> ()
264+
%5 = apply %4() : $@convention(thin) () -> ()
265+
dealloc_stack_ref %2
266+
dealloc_stack_ref %0
267+
%r = tuple ()
268+
return %r
269+
}
212270

213271
sil_vtable B {
214272
#B.deinit!deallocator: @$s4test1BCfD

0 commit comments

Comments
 (0)