Files
swift-mirror/test/SILOptimizer/access_enforcement_selection.sil
Erik Eckstein 18063707b5 Optimizer: enable complete OSSA lifetimes throughout the pass pipeline
This new OSSA invariant simplifies many optimizations because they don't have to take care of the corner case of incomplete lifetimes in dead-end blocks.

The implementation basically consists of these changes:
* add the lifetime completion utility
* add a flag in SILFunction which tells optimization that they need to run the lifetime completion utility
* let all optimizations complete lifetimes if necessary
* enable the ownership verifier to check complete lifetimes
2026-01-22 17:41:48 +01:00

349 lines
16 KiB
Plaintext

// RUN: %target-sil-opt -sil-print-types -access-enforcement-selection -enforce-exclusivity=checked %s | %FileCheck %s
import Builtin
import Swift
sil_stage raw
// Test undef begin_access operands.
// CHECK-LABEL: sil hidden [ossa] @undefStack : $@convention(thin) (Builtin.Int64) -> Builtin.Int64 {
// CHECK: bb0(%0 : $Builtin.Int64):
// CHECK: bb1:
// CHECK: [[WRITE:%.*]] = begin_access [modify] [static] undef : $*Builtin.Int64
// CHECK: store %{{.*}} to [trivial] [[WRITE]] : $*Builtin.Int64
// CHECK: end_access [[WRITE]] : $*Builtin.Int64
// CHECK: br
// CHECK: bb2:
// CHECK: [[READ:%.*]] = begin_access [read] [static] undef : $*Builtin.Int64
// CHECK: %{{.*}} = load [trivial] [[READ]] : $*Builtin.Int64
// CHECK: end_access [[READ]] : $*Builtin.Int64
// CHECK-LABEL: } // end sil function 'undefStack'
sil hidden [ossa] @undefStack : $@convention(thin) (Builtin.Int64) -> Builtin.Int64 {
bb0(%0 : $Builtin.Int64):
br bb1
bb1:
%23 = integer_literal $Builtin.Int64, 42
%25 = begin_access [modify] [unknown] undef : $*Builtin.Int64
store %23 to [trivial] %25 : $*Builtin.Int64
end_access %25 : $*Builtin.Int64
br bb2
bb2:
%29 = begin_access [read] [unknown] undef : $*Builtin.Int64
%30 = load [trivial] %29 : $*Builtin.Int64
end_access %29 : $*Builtin.Int64
return %30 : $Builtin.Int64
}
// Test static enforcement selection in the presence of mark_function_escape.
// This really isn't really a special case, but depends on pass pipeline.
//
// CHECK-LABEL: sil hidden [ossa] @markFuncEscape : $@convention(thin) () -> () {
// CHECK: bb0:
// CHECK: [[BOX:%.*]] = alloc_box ${ var Builtin.Int64 }, var, name "x"
// CHECK: [[ADR:%.*]] = project_box [[BOX]] : ${ var Builtin.Int64 }, 0
// CHECK: mark_function_escape [[ADR]] : $*Builtin.Int64
// CHECK: [[READ:%.*]] = begin_access [read] [static] [[ADR]] : $*Builtin.Int64
// CHECK: %{{.*}} = load [trivial] [[READ]] : $*Builtin.Int64
// CHECK: end_access [[READ]] : $*Builtin.Int64
// CHECK: destroy_value [[BOX]] : ${ var Builtin.Int64 }
// CHECK: return %{{.*}} : $()
// CHECK-LABEL:} // end sil function 'markFuncEscape'
sil hidden [ossa] @markFuncEscape : $@convention(thin) () -> () {
%2 = alloc_box ${ var Builtin.Int64 }, var, name "x"
%3 = project_box %2 : ${ var Builtin.Int64 }, 0
mark_function_escape %3 : $*Builtin.Int64
%39 = begin_access [read] [unknown] %3 : $*Builtin.Int64
%40 = load [trivial] %39 : $*Builtin.Int64
end_access %39 : $*Builtin.Int64
destroy_value %2 : ${ var Builtin.Int64 }
%98 = tuple ()
return %98 : $()
}
sil [ossa] @takesInoutAndClosure : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
sil [ossa] @closureCapturingByStorageAddress : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
// Test static enforcement of box addresses that escape via closure
// partial_applys.
// application.
// CHECK-LABEL: sil hidden [ossa] @escapeAsArgumentToPartialApply : $@convention(thin) () -> () {
// CHECK: bb0:
// CHECK: [[BOX:%.*]] = alloc_box ${ var Builtin.Int64 }, var, name "x"
// CHECK: [[ADR:%.*]] = project_box [[BOX]] : ${ var Builtin.Int64 }, 0
// CHECK: [[FUNC:%.*]] = function_ref @takesInoutAndClosure : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
// CHECK: [[CLOSURE:%.*]] = function_ref @closureCapturingByStorageAddress : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
// CHECK: [[PA:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE]]([[ADR]]) : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
// CHECK: [[MODIFY:%.*]] = begin_access [modify] [static] [[ADR]] : $*Builtin.Int64
// CHECK: %{{.*}} = apply [[FUNC]]([[MODIFY]], [[PA]]) : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
// CHECK: end_access [[MODIFY]] : $*Builtin.Int64
// CHECK: destroy_value [[PA]]
// CHECK: destroy_value [[BOX]] : ${ var Builtin.Int64 }
// CHECK: return %{{.*}} : $()
// CHECK-LABEL:} // end sil function 'escapeAsArgumentToPartialApply'
sil hidden [ossa] @escapeAsArgumentToPartialApply : $@convention(thin) () -> () {
bb0:
%2 = alloc_box ${ var Builtin.Int64 }, var, name "x"
%3 = project_box %2 : ${ var Builtin.Int64 }, 0
%4 = function_ref @takesInoutAndClosure : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
%5 = function_ref @closureCapturingByStorageAddress : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
%6 = partial_apply [callee_guaranteed] %5(%3) : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
%7 = begin_access [modify] [unknown] %3 : $*Builtin.Int64
%8 = apply %4(%7, %6) : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
end_access %7 : $*Builtin.Int64
destroy_value %6 : $@callee_guaranteed () -> ()
destroy_value %2 : ${ var Builtin.Int64 }
%9 = tuple ()
return %9 : $()
}
sil [dynamically_replacable] [ossa] @closureCapturingByStorageAddress2 : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
// Make sure that we handle dynamic_function_ref.
sil hidden [ossa] @escapeAsArgumentToPartialApplyDynamic : $@convention(thin) () -> () {
bb0:
%2 = alloc_box ${ var Builtin.Int64 }, var, name "x"
%3 = project_box %2 : ${ var Builtin.Int64 }, 0
%4 = function_ref @takesInoutAndClosure : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
%5 = dynamic_function_ref @closureCapturingByStorageAddress2 : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
%6 = partial_apply [callee_guaranteed] %5(%3) : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
%7 = begin_access [modify] [unknown] %3 : $*Builtin.Int64
%8 = apply %4(%7, %6) : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
end_access %7 : $*Builtin.Int64
destroy_value %6 : $@callee_guaranteed () -> ()
destroy_value %2 : ${ var Builtin.Int64 }
%9 = tuple ()
return %9 : $()
}
// Test static enforcement of copied boxes.
// FIXME: Oops... We make this dynamic.
//
// CHECK-LABEL: sil hidden [ossa] @copyBox : $@convention(thin) () -> () {
// CHECK: bb0:
// CHECK: [[BOX:%.*]] = alloc_box ${ var Builtin.Int64 }, var, name "x"
// CHECK: [[ADR1:%.*]] = project_box [[BOX]] : ${ var Builtin.Int64 }, 0
// CHECK: [[CPY:%.*]] = copy_value [[BOX]] : ${ var Builtin.Int64 }
// CHECK: [[ADR2:%.*]] = project_box [[CPY]] : ${ var Builtin.Int64 }, 0
// CHECK: [[READ:%.*]] = begin_access [read] [dynamic] [[ADR2]] : $*Builtin.Int64
// CHECK: %{{.*}} = load [trivial] [[READ]] : $*Builtin.Int64
// CHECK: end_access [[READ]] : $*Builtin.Int64
// CHECK: [[READ:%.*]] = begin_access [read] [dynamic] [[ADR1]] : $*Builtin.Int64
// CHECK: %{{.*}} = load [trivial] [[READ]] : $*Builtin.Int64
// CHECK: end_access [[READ]] : $*Builtin.Int64
// CHECK: destroy_value [[CPY]] : ${ var Builtin.Int64 }
// CHECK: destroy_value [[BOX]] : ${ var Builtin.Int64 }
// CHECK: return %{{.*}} : $()
// CHECK-LABEL: } // end sil function 'copyBox'
sil hidden [ossa] @copyBox : $@convention(thin) () -> () {
%2 = alloc_box ${ var Builtin.Int64 }, var, name "x"
%3 = project_box %2 : ${ var Builtin.Int64 }, 0
%16 = copy_value %2 : ${ var Builtin.Int64 }
%17 = project_box %16 : ${ var Builtin.Int64 }, 0
%18 = begin_access [read] [unknown] %17 : $*Builtin.Int64
%19 = load [trivial] %18 : $*Builtin.Int64
end_access %18 : $*Builtin.Int64
%39 = begin_access [read] [unknown] %3 : $*Builtin.Int64
%40 = load [trivial] %39 : $*Builtin.Int64
end_access %39 : $*Builtin.Int64
destroy_value %16 : ${ var Builtin.Int64 }
destroy_value %2 : ${ var Builtin.Int64 }
%98 = tuple ()
return %98 : $()
}
sil [ossa] @closure : $@convention(thin) (@owned { var Builtin.Int64 }) -> () {
bb0(%0 : @owned ${ var Builtin.Int64 }):
destroy_value %0 : ${ var Builtin.Int64 }
%empty = tuple ()
return %empty : $()
}
// An access that escapes on an unreachable path must be dynamic.
//
// CHECK-LABEL: sil [ossa] @partialUnreachable : $@convention(thin) () -> () {
// CHECK: %[[ACCESS:.*]] = begin_access [modify] [dynamic] %{{.*}} : $*Builtin.Int64
// CHECK: bb1:
// CHECK: end_access %[[ACCESS]] : $*Builtin.Int64
// CHECK: return
// CHECK: bb2:
// CHECK: partial_apply
// CHECK: unreachable
sil [ossa] @partialUnreachable : $@convention(thin) () -> () {
bb0:
%box = alloc_box ${ var Builtin.Int64 }, var, name "x"
%addr = project_box %box : ${ var Builtin.Int64 }, 0
%write = begin_access [modify] [unknown] %addr : $*Builtin.Int64
cond_br undef, bb1, bb2
bb1:
end_access %write : $*Builtin.Int64
destroy_value %box : ${ var Builtin.Int64 }
%empty = tuple ()
return %empty : $()
bb2:
%f = function_ref @closure : $@convention(thin) (@owned { var Builtin.Int64 }) -> ()
%closure = partial_apply %f(%box) : $@convention(thin) (@owned { var Builtin.Int64 }) -> ()
destroy_value [dead_end] %closure
unreachable
}
// An access that refers to mark_uninitialized.
//
// CHECK-LABEL: sil [ossa] @markUninitSource : $@convention(thin) () -> () {
sil [ossa] @markUninitSource : $@convention(thin) () -> () {
bb0:
%stk = alloc_stack $Builtin.Int64, let, name "x"
%var = mark_uninitialized [var] %stk : $*Builtin.Int64
%f = function_ref @closureCapturingByStorageAddress : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
%closure = partial_apply %f(%var) : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
destroy_value [dead_end] %closure
unreachable
}
// Borrowed closure can be statically checked.
//
// CHECK-LABEL: sil [ossa] @borrowedClosure : $@convention(thin) (@inout_aliasable Builtin.Int64) -> () {
// CHECK: begin_access [read] [static]
// CHECK-LABEL: } // end sil function 'borrowedClosure'
sil [ossa] @borrowedClosure : $@convention(thin) (@inout_aliasable Builtin.Int64) -> () {
bb0(%0 : $*Builtin.Int64):
%access = begin_access [read] [unknown] %0 : $*Builtin.Int64
%val = load [trivial] %access : $*Builtin.Int64
end_access %access : $*Builtin.Int64
%empty = tuple ()
return %empty : $()
}
// Borrow an escaping closure. The closure body will be dynamically checked.
sil [ossa] @borrowClosure : $@convention(thin) () -> () {
%box = alloc_box ${ var Builtin.Int64 }, var, name "x"
%addr = project_box %box : ${ var Builtin.Int64 }, 0
// box escapes.
%copy = copy_value %box : ${ var Builtin.Int64 }
%f = function_ref @borrowedClosure : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
%closure = partial_apply [callee_guaranteed] %f(%addr) : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
// closure is borrowed.
%borrow = begin_borrow %closure : $@callee_guaranteed () -> ()
%closure_copy = copy_value %borrow : $@callee_guaranteed () -> ()
end_borrow %borrow : $@callee_guaranteed () -> ()
destroy_value %closure_copy : $@callee_guaranteed () -> ()
destroy_value %closure : $@callee_guaranteed () -> ()
destroy_value %copy : ${ var Builtin.Int64 }
destroy_value %box : ${ var Builtin.Int64 }
%empty = tuple ()
return %empty : $()
}
sil [ossa] @dontAssert : $@convention(thin) (Builtin.Int64) -> (@out Builtin.Int64) {
bb0(%0 : $*Builtin.Int64, %1 : $Builtin.Int64):
store %1 to [trivial] %0 : $*Builtin.Int64
%f = function_ref @closureCapturingByStorageAddress : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
%closure = partial_apply %f(%0) : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
destroy_value [dead_end] %closure
unreachable
}
sil [canonical] [ossa] @serializedClosureCapturingByStorageAddress : $@convention(thin) (@inout_aliasable Builtin.Int64) -> () {
bb0(%0 : $*Builtin.Int64):
%2 = begin_access [read] [unknown] %0 : $*Builtin.Int64
%3 = load [trivial] %2 : $*Builtin.Int64
end_access %2 : $*Builtin.Int64
%10 = tuple ()
return %10 : $()
}
// A begin_access may not be used by a partial_apply or a nested
// begin_access prior to mandatory inlining. Nonetheless, this does
// occur in deserialized SIL. This SIL is only well-formed because the
// function is marked [canonical].
sil [canonical] [ossa] @accessAroundClosure : $@convention(thin) () -> () {
bb0:
%1 = alloc_box ${ var Builtin.Int64 }, var, name "x"
%2 = copy_value %1 : ${ var Builtin.Int64 }
%3 = project_box %1 : ${ var Builtin.Int64 }, 0
// outer access (presumably its uses were mandatory inlined)
%4 = begin_access [modify] [static] %3 : $*Builtin.Int64
%5 = function_ref @serializedClosureCapturingByStorageAddress : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
%6 = partial_apply [callee_guaranteed] %5(%4) : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
%7 = begin_access [modify] [static] %4 : $*Builtin.Int64
%8 = function_ref @takesInoutAndClosure : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
%9 = apply %8(%7, %6) : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
end_access %7 : $*Builtin.Int64
end_access %4 : $*Builtin.Int64
destroy_value %6 : $@callee_guaranteed () -> ()
destroy_value %2 : ${ var Builtin.Int64 }
destroy_value %1 : ${ var Builtin.Int64 }
%10 = tuple ()
return %10 : $()
}
// -----------------------------------------------------------------------------
// <rdar://problem/43122715> [Exclusivity] failure to diagnose escaping closures
// called within directly applied noescape closures.
//
// The top level function, testDirectApplyNoescapeDynamic, has no
// access markers, but it does define a box that escapes into two
// closures. Since one of those captures is a box, they both need
// dynamic enforcement. The unique thing about this case is that the
// non-escaping closure is directly applied, so has no partial_apply
// to indicate the presence of a captured argument.
sil [noinline] @takeInoutAndPerform : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
// CHECK-LABEL: sil [ossa] @testDirectApplyNoescapeDynamic : $@convention(thin) (Builtin.Int64) -> () {
// CHECK-LABEL: // end sil function 'testDirectApplyNoescapeDynamic'
sil [ossa] @testDirectApplyNoescapeDynamic : $@convention(thin) (Builtin.Int64) -> () {
bb0(%0 : $Builtin.Int64):
%box = alloc_box ${ var Builtin.Int64 }, var, name "localVal"
%boxproj = project_box %box : ${ var Builtin.Int64 }, 0
store %0 to [trivial] %boxproj : $*Builtin.Int64
%closureF = function_ref @directApplyNoescapeDynamicClosure : $@convention(thin) (@guaranteed { var Builtin.Int64 }) -> ()
%boxCopy = copy_value %box : ${ var Builtin.Int64 }
%closure = partial_apply [callee_guaranteed] %closureF(%boxCopy) : $@convention(thin) (@guaranteed { var Builtin.Int64 }) -> ()
%directF = function_ref @directApplyNoescapeDynamicAppliedClosure : $@convention(thin) (@guaranteed @callee_guaranteed () -> (), @inout_aliasable Builtin.Int64) -> ()
%call = apply %directF(%closure, %boxproj) : $@convention(thin) (@guaranteed @callee_guaranteed () -> (), @inout_aliasable Builtin.Int64) -> ()
destroy_value %closure : $@callee_guaranteed () -> ()
destroy_value %box : ${ var Builtin.Int64 }
%v = tuple ()
return %v : $()
}
// CHECK-LABEL: sil private [ossa] @directApplyNoescapeDynamicClosure : $@convention(thin) (@guaranteed { var Builtin.Int64 }) -> () {
// CHECK: [[BOX:%.*]] = project_box %0 : ${ var Builtin.Int64 }, 0
// CHECK: begin_access [modify] [dynamic] [[BOX]] : $*Builtin.Int64
// CHECK-LABEL: // end sil function 'directApplyNoescapeDynamicClosure'
sil private [ossa] @directApplyNoescapeDynamicClosure : $@convention(thin) (@guaranteed { var Builtin.Int64 }) -> () {
bb0(%0 : @guaranteed ${ var Builtin.Int64 }):
%1 = project_box %0 : ${ var Builtin.Int64 }, 0
%4 = integer_literal $Builtin.Int64, 3
%7 = begin_access [modify] [unknown] %1 : $*Builtin.Int64
assign %4 to %7 : $*Builtin.Int64
end_access %7 : $*Builtin.Int64
%10 = tuple ()
return %10 : $()
}
// CHECK-LABEL: sil private [ossa] @directApplyNoescapeDynamicAppliedClosure : $@convention(thin) (@guaranteed @callee_guaranteed () -> (), @inout_aliasable Builtin.Int64) -> () {
// CHECK: begin_access [modify] [dynamic] %1 : $*Builtin.Int64
// CHECK-LABEL: end sil function 'directApplyNoescapeDynamicAppliedClosure'
sil private [ossa] @directApplyNoescapeDynamicAppliedClosure : $@convention(thin) (@guaranteed @callee_guaranteed () -> (), @inout_aliasable Builtin.Int64) -> () {
bb0(%0 : @guaranteed $@callee_guaranteed () -> (), %1 : $*Builtin.Int64):
%4 = begin_access [modify] [unknown] %1 : $*Builtin.Int64
%5 = function_ref @takesInoutAndClosure : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
%6 = apply %5(%4, %0) : $@convention(thin) (@inout Builtin.Int64, @guaranteed @callee_guaranteed () -> ()) -> ()
end_access %4 : $*Builtin.Int64
%8 = tuple ()
return %8 : $()
}