| // RUN: %target-sil-opt -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 is_escaping_closure check. |
| // |
| // 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: is_escaping_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 = is_escaping_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 : $() |
| } |