mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
The problem with `is_escaping_closure` was that it didn't consume its operand and therefore reference count checks were unreliable. For example, copy-propagation could break it. As this instruction was always used together with an immediately following `destroy_value` of the closure, it makes sense to combine both into a `destroy_not_escaped_closure`. It 1. checks the reference count and returns true if it is 1 2. consumes and destroys the operand
283 lines
12 KiB
Plaintext
283 lines
12 KiB
Plaintext
// RUN: %target-sil-opt -sil-print-types -access-enforcement-release %s -enable-sil-verify-all | %FileCheck %s
|
|
//
|
|
// Test the AccessEnforcementReleaseSinking pass in isolation.
|
|
// This ensures that no upstream passes have removed SIL-level access markers
|
|
// that are required to ensure the pass is not overly optimistic.
|
|
|
|
sil_stage canonical
|
|
|
|
import Builtin
|
|
import Swift
|
|
import SwiftShims
|
|
|
|
struct X {
|
|
@_hasStorage var i: Int64 { get set }
|
|
init(i: Int64)
|
|
init()
|
|
}
|
|
|
|
var globalX: X
|
|
|
|
sil_global hidden @globalX : $X
|
|
|
|
sil hidden_external [global_init] @globalAddressor : $@convention(thin) () -> Builtin.RawPointer
|
|
|
|
// public func testSimpleRelease() {
|
|
// Checks the simple case of release sinking
|
|
//
|
|
// CHECK-LABEL: sil @testSimpleRelease : $@convention(thin) () -> () {
|
|
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
|
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
|
|
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
|
// CHECK-NEXT: release_value [[LOADED]]
|
|
// CHECK-LABEL: } // end sil function 'testSimpleRelease'
|
|
sil @testSimpleRelease : $@convention(thin) () -> () {
|
|
bb0:
|
|
%0 = global_addr @globalX: $*X
|
|
%1 = begin_access [modify] [dynamic] %0 : $*X
|
|
%2 = load %1 : $*X
|
|
release_value %2 : $X
|
|
end_access %1 : $*X
|
|
%ret = tuple ()
|
|
return %ret : $()
|
|
}
|
|
|
|
// public func testMultiBlocklSimpleRelease() {
|
|
// Checks the simple case of release sinking with the begin_access in a different block
|
|
//
|
|
// CHECK-LABEL: sil @testMultiBlocklSimpleRelease : $@convention(thin) () -> () {
|
|
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
|
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
|
|
// CHECK-NEXT: br bb1
|
|
// CHECK: bb1
|
|
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
|
// CHECK-NEXT: release_value [[LOADED]]
|
|
// CHECK-LABEL: } // end sil function 'testMultiBlocklSimpleRelease'
|
|
sil @testMultiBlocklSimpleRelease : $@convention(thin) () -> () {
|
|
bb0:
|
|
%0 = global_addr @globalX: $*X
|
|
%1 = begin_access [modify] [dynamic] %0 : $*X
|
|
%2 = load %1 : $*X
|
|
br bb1
|
|
|
|
bb1:
|
|
release_value %2 : $X
|
|
end_access %1 : $*X
|
|
%ret = tuple ()
|
|
return %ret : $()
|
|
}
|
|
|
|
// public func testMultiBlocklBailOnRelease() {
|
|
// Checks bailing (for now) on the simple case due to the release being in a different block
|
|
//
|
|
// CHECK-LABEL: sil @testMultiBlocklBailOnRelease : $@convention(thin) () -> () {
|
|
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
|
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
|
|
// CHECK-NEXT: release_value [[LOADED]]
|
|
// CHECK-NEXT: br bb1
|
|
// CHECK: bb1
|
|
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
|
// CHECK-LABEL: } // end sil function 'testMultiBlocklBailOnRelease'
|
|
sil @testMultiBlocklBailOnRelease : $@convention(thin) () -> () {
|
|
bb0:
|
|
%0 = global_addr @globalX: $*X
|
|
%1 = begin_access [modify] [dynamic] %0 : $*X
|
|
%2 = load %1 : $*X
|
|
release_value %2 : $X
|
|
br bb1
|
|
|
|
bb1:
|
|
end_access %1 : $*X
|
|
%ret = tuple ()
|
|
return %ret : $()
|
|
}
|
|
|
|
// public func testApplyBarrier() {
|
|
// Checks we don't sink across apply-site barrier
|
|
//
|
|
// CHECK-LABEL: sil @testApplyBarrier : $@convention(thin) () -> () {
|
|
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
|
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
|
|
// CHECK-NEXT: release_value [[LOADED]]
|
|
// CHECK: apply
|
|
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
|
// CHECK-LABEL: } // end sil function 'testApplyBarrier'
|
|
sil @testApplyBarrier : $@convention(thin) () -> () {
|
|
bb0:
|
|
%0 = global_addr @globalX: $*X
|
|
%1 = begin_access [modify] [dynamic] %0 : $*X
|
|
%2 = load %1 : $*X
|
|
release_value %2 : $X
|
|
%u0 = function_ref @globalAddressor : $@convention(thin) () -> Builtin.RawPointer
|
|
%u1 = apply %u0() : $@convention(thin) () -> Builtin.RawPointer
|
|
end_access %1 : $*X
|
|
%ret = tuple ()
|
|
return %ret : $()
|
|
}
|
|
|
|
// public func testUniquenessBarrier() {
|
|
// Checks we don't sink across a uniqueness check
|
|
//
|
|
// CHECK-LABEL: sil @testUniquenessBarrier : $@convention(thin) () -> () {
|
|
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
|
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
|
|
// CHECK-NEXT: release_value [[LOADED]]
|
|
// CHECK-NEXT: is_unique
|
|
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
|
// CHECK-LABEL: } // end sil function 'testUniquenessBarrier'
|
|
sil @testUniquenessBarrier : $@convention(thin) () -> () {
|
|
bb0:
|
|
%0 = global_addr @globalX: $*X
|
|
%1 = begin_access [modify] [dynamic] %0 : $*X
|
|
%2 = load %1 : $*X
|
|
release_value %2 : $X
|
|
is_unique %1 : $*X
|
|
end_access %1 : $*X
|
|
%ret = tuple ()
|
|
return %ret : $()
|
|
}
|
|
|
|
// public func testBeginBarrier() {
|
|
// Checks we don't sink across begin_access barrier
|
|
//
|
|
// CHECK-LABEL: sil @testBeginBarrier : $@convention(thin) () -> () {
|
|
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
|
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
|
|
// CHECK-NEXT: release_value [[LOADED]]
|
|
// CHECK-NEXT: [[BEGIN2:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
|
|
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
|
// CHECK-LABEL: } // end sil function 'testBeginBarrier'
|
|
sil @testBeginBarrier : $@convention(thin) () -> () {
|
|
bb0:
|
|
%0 = global_addr @globalX: $*X
|
|
%1 = begin_access [modify] [dynamic] %0 : $*X
|
|
%2 = load %1 : $*X
|
|
release_value %2 : $X
|
|
%b0 = begin_access [modify] [dynamic] %0 : $*X
|
|
end_access %b0 : $*X
|
|
end_access %1 : $*X
|
|
%ret = tuple ()
|
|
return %ret : $()
|
|
}
|
|
|
|
// public func testSinkCrossMultiEnds() {
|
|
// Checks that we choose the *bottom* end_access when sinking
|
|
//
|
|
// CHECK-LABEL: sil @testSinkCrossMultiEnds : $@convention(thin) () -> () {
|
|
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
|
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-NEXT: [[BEGIN2:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
|
|
// CHECK-NEXT: end_access [[BEGIN2]] : $*X
|
|
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
|
// CHECK-NEXT: release_value [[LOADED]]
|
|
// CHECK-LABEL: } // end sil function 'testSinkCrossMultiEnds'
|
|
sil @testSinkCrossMultiEnds : $@convention(thin) () -> () {
|
|
bb0:
|
|
%0 = global_addr @globalX: $*X
|
|
%1 = begin_access [modify] [dynamic] %0 : $*X
|
|
%b0 = begin_access [modify] [dynamic] %0 : $*X
|
|
%2 = load %1 : $*X
|
|
release_value %2 : $X
|
|
end_access %b0 : $*X
|
|
end_access %1 : $*X
|
|
%ret = tuple ()
|
|
return %ret : $()
|
|
}
|
|
|
|
// public func testSinkAfterBarrierEncounter() {
|
|
// Checks that we sink after barrier resetting
|
|
//
|
|
// CHECK-LABEL: sil @testSinkAfterBarrierEncounter : $@convention(thin) () -> () {
|
|
// CHECK: [[GLOBAL:%.*]] = global_addr @globalX : $*X
|
|
// CHECK-NEXT: [[BEGIN:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-NEXT: [[LOADED:%.*]] = load [[BEGIN]] : $*X
|
|
// CHECK-NEXT: end_access [[BEGIN]] : $*X
|
|
// CHECK-NEXT: release_value [[LOADED]]
|
|
// CHECK-NEXT: [[BEGIN2:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] : $*X
|
|
// CHECK-LABEL: } // end sil function 'testSinkAfterBarrierEncounter'
|
|
sil @testSinkAfterBarrierEncounter : $@convention(thin) () -> () {
|
|
bb0:
|
|
%0 = global_addr @globalX: $*X
|
|
%1 = begin_access [modify] [dynamic] %0 : $*X
|
|
%2 = load %1 : $*X
|
|
release_value %2 : $X
|
|
end_access %1 : $*X
|
|
%b0 = begin_access [modify] [dynamic] %0 : $*X
|
|
end_access %b0 : $*X
|
|
%ret = tuple ()
|
|
return %ret : $()
|
|
}
|
|
|
|
// testSinkAfterEscapingClosureCheck:
|
|
// <rdar://problem/45846920> TestFoundation, TestProcess, closure
|
|
// argument passed as @noescape to Objective-C has escaped
|
|
class IntWrapper {
|
|
var value: Builtin.Int64
|
|
|
|
init(v: Builtin.Int64) {
|
|
value = v
|
|
}
|
|
}
|
|
|
|
sil @takesNoEscapeBlockClosure : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
|
|
|
|
sil @closureThatModifiesCapture: $@convention(thin) ({ var Builtin.Int64 }) -> ()
|
|
|
|
sil [reabstraction_thunk] @thunkForCalleeGuaranteed : $@convention(c) (@inout_aliasable @block_storage @callee_guaranteed () -> ()) -> ()
|
|
sil [reabstraction_thunk] @withoutActuallyEscapingThunk : $@convention(thin) (@noescape @callee_guaranteed () -> ()) -> ()
|
|
|
|
// The Copy release cannot be moved below the destroy_not_escaped_closure.
|
|
//
|
|
// CHECK-LABEL: sil @testSinkAfterEscapingClosureCheck : $@convention(thin) (@guaranteed IntWrapper) -> () {
|
|
// CHECK: bb0(%0 : $IntWrapper):
|
|
// CHECK: [[BA:%.*]] = begin_access [read] [dynamic] %{{.*}} : $*Builtin.Int64
|
|
// CHECK: [[COPY:%.*]] = copy_block %{{.*}} : $@convention(block) @noescape () -> ()
|
|
// CHECK: [[F:%.*]] = function_ref @takesNoEscapeBlockClosure : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
|
|
// CHECK: apply [[F]]([[COPY]]) : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
|
|
// CHECK: strong_release [[COPY]] : $@convention(block) @noescape () -> ()
|
|
// CHECK: destroy_not_escaped_closure
|
|
// CHECK: cond_fail
|
|
// CHECK: end_access [[BA]] : $*Builtin.Int64
|
|
// CHECK-LABEL: } // end sil function 'testSinkAfterEscapingClosureCheck'
|
|
sil @testSinkAfterEscapingClosureCheck : $@convention(thin) (@guaranteed IntWrapper) -> () {
|
|
bb0(%0 : $IntWrapper):
|
|
%va = ref_element_addr %0 : $IntWrapper, #IntWrapper.value
|
|
%ba = begin_access [read] [dynamic] %va : $*Builtin.Int64
|
|
%value = load %ba : $*Builtin.Int64
|
|
%box = alloc_box ${ var Builtin.Int64 }
|
|
%boxaddr = project_box %box : ${ var Builtin.Int64 }, 0
|
|
store %value to %boxaddr : $*Builtin.Int64
|
|
%closureF = function_ref @closureThatModifiesCapture : $@convention(thin) ({ var Builtin.Int64 }) -> ()
|
|
%closure = partial_apply [callee_guaranteed] %closureF(%box) : $@convention(thin) ({ var Builtin.Int64 }) -> ()
|
|
%conv = convert_escape_to_noescape %closure : $@callee_guaranteed () -> () to $@noescape @callee_guaranteed () -> ()
|
|
%thunk = function_ref @withoutActuallyEscapingThunk : $@convention(thin) (@noescape @callee_guaranteed () -> ()) -> ()
|
|
%pathunk = partial_apply [callee_guaranteed] %thunk(%conv) : $@convention(thin) (@noescape @callee_guaranteed () -> ()) -> ()
|
|
%md = mark_dependence %pathunk : $@callee_guaranteed () -> () on %conv : $@noescape @callee_guaranteed () -> ()
|
|
strong_retain %md : $@callee_guaranteed () -> ()
|
|
%allocblock = alloc_stack $@block_storage @callee_guaranteed () -> ()
|
|
%blockaddr = project_block_storage %allocblock : $*@block_storage @callee_guaranteed () -> ()
|
|
store %md to %blockaddr : $*@callee_guaranteed () -> ()
|
|
%blockF = function_ref @thunkForCalleeGuaranteed : $@convention(c) (@inout_aliasable @block_storage @callee_guaranteed () -> ()) -> ()
|
|
%initblock = init_block_storage_header %allocblock : $*@block_storage @callee_guaranteed () -> (), invoke %blockF : $@convention(c) (@inout_aliasable @block_storage @callee_guaranteed () -> ()) -> (), type $@convention(block) @noescape () -> ()
|
|
%copyblock = copy_block %initblock : $@convention(block) @noescape () -> ()
|
|
%f = function_ref @takesNoEscapeBlockClosure : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
|
|
%call = apply %f(%copyblock) : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
|
|
strong_release %copyblock : $@convention(block) @noescape () -> ()
|
|
%isescape = destroy_not_escaped_closure %md : $@callee_guaranteed () -> ()
|
|
cond_fail %isescape : $Builtin.Int1
|
|
end_access %ba : $*Builtin.Int64
|
|
destroy_addr %blockaddr : $*@callee_guaranteed() -> ()
|
|
dealloc_stack %allocblock : $*@block_storage @callee_guaranteed () -> ()
|
|
strong_release %box : ${ var Builtin.Int64 }
|
|
%ret = tuple ()
|
|
return %ret : $()
|
|
}
|