Skip to content

Commit cc92319

Browse files
committed
Optimizer: support partial_apply of thunks in the copy_block simplification.
If the block is passed to another function - either as closure argument or as closure capture - it's "converted" to a swift closure with the help of a thunk. The thunk just calls the block. If this is done with a non-escaping partial_apply, the block does not escape. rdar://149095630
1 parent 2f34dae commit cc92319

File tree

3 files changed

+178
-5
lines changed

3 files changed

+178
-5
lines changed

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyCopyBlock.swift

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ extension CopyBlockInst : Simplifiable, SILCombineSimplifiable {
3434
}
3535

3636
if hasValidUses(block: self) {
37-
replaceBlock( self, with: operand.value, context)
37+
replaceBlock(self, with: operand.value, context)
3838
context.erase(instruction: self)
3939
}
4040
}
@@ -49,6 +49,13 @@ private func hasValidUses(block: Value) -> Bool {
4949
}
5050
case let apply as FullApplySite where apply.isCallee(operand: use):
5151
break
52+
case let partialApply as PartialApplyInst:
53+
// If the block is passed to another function - either as closure argument or as closure capture -
54+
// it's "converted" to a swift closure with the help of a thunk. The thunk just calls the block.
55+
// If this is a non-escaping partial_apply and it's such a thunk, the block does not escape.
56+
if partialApply.canClosureArgumentEscape(closure: use) {
57+
return false
58+
}
5259
case is EndBorrowInst, is DestroyValueInst:
5360
break
5461
default:
@@ -66,10 +73,40 @@ private func replaceBlock(_ block: Value, with original: Value, _ context: Simpl
6673
context.erase(instruction: beginBorrow)
6774
case is FullApplySite:
6875
use.set(to: original, context)
76+
case let partialApply as PartialApplyInst:
77+
if original.ownership == .unowned {
78+
let builder = Builder(before: partialApply, context)
79+
let conv = builder.createUncheckedOwnershipConversion(operand: original, resultOwnership: .guaranteed)
80+
use.set(to: conv, context)
81+
} else {
82+
use.set(to: original, context)
83+
}
6984
case is EndBorrowInst, is DestroyValueInst:
7085
context.erase(instruction: use.instruction)
7186
default:
7287
fatalError("unhandled use")
7388
}
7489
}
7590
}
91+
92+
private extension PartialApplyInst {
93+
func canClosureArgumentEscape(closure: Operand) -> Bool {
94+
guard isOnStack,
95+
let callee = referencedFunction,
96+
callee.isDefinition,
97+
let argIdx = calleeArgumentIndex(of: closure),
98+
// If the callee only _calls_ the closure argument, it does not escape.
99+
callee.arguments[argIdx].uses.allSatisfy(isCalleeOperandOfApply)
100+
else {
101+
return true
102+
}
103+
return false
104+
}
105+
}
106+
107+
private func isCalleeOperandOfApply(_ operand: Operand) -> Bool {
108+
if let apply = operand.instruction as? FullApplySite, apply.isCallee(operand: operand) {
109+
return true
110+
}
111+
return false
112+
}

test/SILOptimizer/optimize_copy_block.swift

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ module CModule {
1818
@import Foundation;
1919

2020
@interface TestClass : NSObject
21-
- (void)callHandlerInline: (NS_NOESCAPE _Nonnull dispatch_block_t)block;
21+
- (void)callHandler1: (NS_NOESCAPE _Nonnull dispatch_block_t)block;
22+
- (void)callHandler2: (NS_NOESCAPE _Nonnull dispatch_block_t)block;
23+
- (void)callHandler3: (NS_NOESCAPE _Nonnull dispatch_block_t)block;
2224
@end
2325

2426

@@ -28,13 +30,34 @@ import CModule
2830

2931
@objc @implementation
3032
extension TestClass {
31-
// CHECK-LABEL: sil private [thunk] @$sSo9TestClassC4testE17callHandlerInlineyyyyXEFTo :
33+
// CHECK-LABEL: sil private [thunk] @$sSo9TestClassC4testE12callHandler1yyyyXEFTo :
3234
// CHECK-NOT: copy_block
3335
// CHECK: apply %0
3436
// CHECK-NOT: destroy_value
35-
// CHECK: } // end sil function '$sSo9TestClassC4testE17callHandlerInlineyyyyXEFTo'
36-
func callHandlerInline(_ handler: () -> Void) {
37+
// CHECK: } // end sil function '$sSo9TestClassC4testE12callHandler1yyyyXEFTo'
38+
func callHandler1(_ handler: () -> Void) {
3739
handler()
3840
}
41+
42+
// CHECK-LABEL: sil private [thunk] @$sSo9TestClassC4testE12callHandler2yyyyXEFTo :
43+
// CHECK-NOT: copy_block
44+
// CHECK: } // end sil function '$sSo9TestClassC4testE12callHandler2yyyyXEFTo'
45+
func callHandler2(_ handler: () -> Void) {
46+
callee(handler)
47+
}
48+
49+
// CHECK-LABEL: sil private [thunk] @$sSo9TestClassC4testE12callHandler3yyyyXEFTo :
50+
// CHECK-NOT: copy_block
51+
// CHECK: } // end sil function '$sSo9TestClassC4testE12callHandler3yyyyXEFTo'
52+
func callHandler3(_ handler: () -> Void) {
53+
callClosure {
54+
handler()
55+
}
56+
}
3957
}
4058

59+
@_silgen_name("callee")
60+
func callee(_ handler: () -> Void)
61+
62+
@_silgen_name("callClosure")
63+
func callClosure(_ closure: () -> ())

test/SILOptimizer/simplify_copy_block.sil

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,119 @@ bb0(%0 : @unowned $@convention(block) @noescape () -> ()):
2727
return %7
2828
}
2929

30+
// CHECK-LABEL: sil [ossa] @remove_copy_block_with_partial_apply :
31+
// ENABLED-NOT: copy_block
32+
// ENABLED: [[B:%.*]] = unchecked_ownership_conversion %0, @unowned to @guaranteed
33+
// ENABLED: [[PA:%.*]] = partial_apply [callee_guaranteed] [on_stack] {{%[0-9]+}}([[B]])
34+
// ENABLED: destroy_value [[PA]]
35+
// ENABLED-NOT: destroy_value
36+
// CHECK: } // end sil function 'remove_copy_block_with_partial_apply'
37+
sil [ossa] @remove_copy_block_with_partial_apply : $@convention(thin) (@convention(block) @noescape () -> ()) -> () {
38+
bb0(%0 : @unowned $@convention(block) @noescape () -> ()):
39+
%2 = copy_block %0
40+
%3 = function_ref @thunk : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
41+
%4 = partial_apply [callee_guaranteed] [on_stack] %3(%2) : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
42+
destroy_value %4
43+
destroy_value %2
44+
%7 = tuple ()
45+
return %7
46+
}
47+
48+
// CHECK-LABEL: sil [ossa] @remove_copy_block_with_partial_apply_guaranteed :
49+
// ENABLED-NOT: copy_block
50+
// ENABLED: [[PA:%.*]] = partial_apply [callee_guaranteed] [on_stack] {{%[0-9]+}}(%0)
51+
// ENABLED: destroy_value [[PA]]
52+
// ENABLED-NOT: destroy_value
53+
// CHECK: } // end sil function 'remove_copy_block_with_partial_apply_guaranteed'
54+
sil [ossa] @remove_copy_block_with_partial_apply_guaranteed : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> () {
55+
bb0(%0 : @guaranteed $@convention(block) @noescape () -> ()):
56+
%2 = copy_block %0
57+
%3 = function_ref @thunk : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
58+
%4 = partial_apply [callee_guaranteed] [on_stack] %3(%2) : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
59+
destroy_value %4
60+
destroy_value %2
61+
%7 = tuple ()
62+
return %7
63+
}
64+
65+
// CHECK-LABEL: sil [ossa] @remove_copy_block_with_partial_apply_owned :
66+
// ENABLED-NOT: copy_block
67+
// ENABLED: [[PA:%.*]] = partial_apply [callee_guaranteed] [on_stack] {{%[0-9]+}}(%0)
68+
// CHECK: } // end sil function 'remove_copy_block_with_partial_apply_owned'
69+
sil [ossa] @remove_copy_block_with_partial_apply_owned : $@convention(thin) (@owned @convention(block) @noescape () -> ()) -> () {
70+
bb0(%0 : @owned $@convention(block) @noescape () -> ()):
71+
%2 = copy_block %0
72+
%3 = function_ref @thunk : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
73+
%4 = partial_apply [callee_guaranteed] [on_stack] %3(%2) : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
74+
destroy_value %4
75+
destroy_value %2
76+
destroy_value %0
77+
%7 = tuple ()
78+
return %7
79+
}
80+
81+
sil [ossa] @thunk : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> () {
82+
bb0(%0 : @guaranteed $@convention(block) @noescape () -> ()):
83+
%1 = apply %0() : $@convention(block) @noescape () -> ()
84+
%2 = tuple ()
85+
return %2
86+
}
87+
88+
sil @use_closure : $@convention(thin) (@guaranteed @callee_guaranteed () -> ()) -> ()
89+
90+
// CHECK-LABEL: sil [ossa] @dont_remove_copy_block_with_escaping_partial_apply :
91+
// CHECK: copy_block
92+
// CHECK: } // end sil function 'dont_remove_copy_block_with_escaping_partial_apply'
93+
sil [ossa] @dont_remove_copy_block_with_escaping_partial_apply : $@convention(thin) (@convention(block) @noescape () -> ()) -> () {
94+
bb0(%0 : @unowned $@convention(block) @noescape () -> ()):
95+
%2 = copy_block %0
96+
%3 = function_ref @thunk : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
97+
%4 = partial_apply [callee_guaranteed] %3(%2) : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
98+
%5 = function_ref @use_closure : $@convention(thin) (@guaranteed @callee_guaranteed () -> ()) -> ()
99+
apply %5(%4) : $@convention(thin) (@guaranteed @callee_guaranteed () -> ()) -> ()
100+
destroy_value %4
101+
%7 = tuple ()
102+
return %7
103+
}
104+
105+
sil [ossa] @unknown_thunk : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
106+
107+
// CHECK-LABEL: sil [ossa] @dont_remove_copy_block_with_unknown_partial_apply :
108+
// CHECK: copy_block
109+
// CHECK: } // end sil function 'dont_remove_copy_block_with_unknown_partial_apply'
110+
sil [ossa] @dont_remove_copy_block_with_unknown_partial_apply : $@convention(thin) (@convention(block) @noescape () -> ()) -> () {
111+
bb0(%0 : @unowned $@convention(block) @noescape () -> ()):
112+
%2 = copy_block %0
113+
%3 = function_ref @unknown_thunk : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
114+
%4 = partial_apply [callee_guaranteed] [on_stack] %3(%2) : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
115+
destroy_value %4
116+
destroy_value %2
117+
%7 = tuple ()
118+
return %7
119+
}
120+
121+
// CHECK-LABEL: sil [ossa] @dont_remove_copy_block_with_partial_apply_escaping :
122+
// CHECK: copy_block
123+
// CHECK: } // end sil function 'dont_remove_copy_block_with_partial_apply_escaping'
124+
sil [ossa] @dont_remove_copy_block_with_partial_apply_escaping : $@convention(thin) (@convention(block) @noescape () -> ()) -> () {
125+
bb0(%0 : @unowned $@convention(block) @noescape () -> ()):
126+
%2 = copy_block %0
127+
%3 = function_ref @thunk_escaping : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
128+
%4 = partial_apply [callee_guaranteed] [on_stack] %3(%2) : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
129+
destroy_value %4
130+
destroy_value %2
131+
%7 = tuple ()
132+
return %7
133+
}
134+
135+
sil [ossa] @thunk_escaping : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> () {
136+
bb0(%0 : @guaranteed $@convention(block) @noescape () -> ()):
137+
%1 = function_ref @use_block : $@convention(thin) (@convention(block) @noescape () -> ()) -> ()
138+
%2 = apply %1(%0) : $@convention(thin) (@convention(block) @noescape () -> ()) -> ()
139+
%3 = tuple ()
140+
return %3
141+
}
142+
30143
// CHECK-LABEL: sil [ossa] @dont_remove_copied_block :
31144
// CHECK: copy_block
32145
// CHECK: } // end sil function 'dont_remove_copied_block'

0 commit comments

Comments
 (0)