Optimizer: add the mandatory destroy hoisting pass

It hoists `destroy_value` instructions for non-lexical values.

```
  %1 = some_ownedValue
  ...
  last_use(%1)
  ... // other instructions
  destroy_value %1
```
->
```
  %1 = some_ownedValue
  ...
  last_use(%1)
  destroy_value %1    // <- moved after the last use
  ... // other instructions
```

In contrast to non-mandatory optimization passes, this is the only pass which hoists destroys over deinit-barriers.
This ensures consistent behavior in -Onone and optimized builds.
This commit is contained in:
Erik Eckstein
2025-11-05 10:22:33 +01:00
parent eef769c83a
commit 62786b01e2
19 changed files with 751 additions and 46 deletions

View File

@@ -29,6 +29,7 @@ swift_compiler_sources(Optimizer
LoopInvariantCodeMotion.swift
ObjectOutliner.swift
ObjCBridgingOptimization.swift
MandatoryDestroyHoisting.swift
MergeCondFails.swift
NamedReturnValueOptimization.swift
RedundantLoadElimination.swift

View File

@@ -0,0 +1,263 @@
//===--- MandatoryDestroyHoisting.swift ------------------------------------==//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SIL
/// Hoists `destroy_value` instructions for non-lexical values.
///
/// ```
/// %1 = some_ownedValue
/// ...
/// last_use(%1)
/// ... // other instructions
/// destroy_value %1
/// ```
/// ->
/// ```
/// %1 = some_ownedValue
/// ...
/// last_use(%1)
/// destroy_value %1 // <- moved after the last use
/// ... // other instructions
/// ```
///
/// In contrast to non-mandatory optimization passes, this is the only pass which hoists destroys
/// over deinit-barriers. This ensures consistent behavior in -Onone and optimized builds.
///
///
let mandatoryDestroyHoisting = FunctionPass(name: "mandatory-destroy-hoisting") {
(function: Function, context: FunctionPassContext) in
var endAccesses = Stack<EndAccessInst>(context)
defer { endAccesses.deinitialize() }
endAccesses.append(contentsOf: function.instructions.compactMap{ $0 as? EndAccessInst })
for block in function.blocks {
for arg in block.arguments {
hoistDestroys(of: arg, endAccesses: endAccesses, context)
if !context.continueWithNextSubpassRun() {
return
}
}
for inst in block.instructions {
for result in inst.results {
hoistDestroys(of: result, endAccesses: endAccesses, context)
if !context.continueWithNextSubpassRun(for: inst) {
return
}
}
}
}
}
private func hoistDestroys(of value: Value, endAccesses: Stack<EndAccessInst>, _ context: FunctionPassContext) {
guard value.ownership == .owned,
// We must not violate side-effect dependencies of non-copyable deinits.
// Therefore we don't handle non-copyable values.
!value.type.isMoveOnly,
// Just a shortcut to avoid all the computations if there is no destroy at all.
!value.uses.users(ofType: DestroyValueInst.self).isEmpty,
// Hoisting destroys is only legal for non-lexical lifetimes.
!value.isInLexicalLiverange(context),
// Avoid compromimsing debug-info in Onone builds for source-level variables with non-lexical lifetimes.
// For example COW types, like Array, which are "eager-move" and therefore not lexical.
!needPreserveDebugInfo(of: value, context)
else {
return
}
guard var liverange = Liverange(of: value, context) else {
return
}
defer { liverange.deinitialize() }
// We must not move a destroy into an access scope, because the deinit can have an access scope as well.
// And that would cause a false exclusivite error at runtime.
liverange.extendWithAccessScopes(of: endAccesses)
var aliveDestroys = insertNewDestroys(of: value, in: liverange)
defer { aliveDestroys.deinitialize() }
removeOldDestroys(of: value, ignoring: aliveDestroys, context)
}
private func insertNewDestroys(of value: Value, in liverange: Liverange) -> InstructionSet {
var aliveDestroys = InstructionSet(liverange.context)
if liverange.nonDestroyingUsers.isEmpty {
// Handle the corner case where the value has no use at all (beside the destroy).
immediatelyDestroy(value: value, ifIn: liverange, &aliveDestroys)
return aliveDestroys
}
// Insert new destroys at the end of the pruned liverange.
for user in liverange.nonDestroyingUsers {
insertDestroy(of: value, after: user, ifIn: liverange, &aliveDestroys)
}
// Also, we need new destroys at exit edges from the pruned liverange.
for exitInst in liverange.prunedLiverange.exits {
insertDestroy(of: value, before: exitInst, ifIn: liverange, &aliveDestroys)
}
return aliveDestroys
}
private func removeOldDestroys(of value: Value, ignoring: InstructionSet, _ context: FunctionPassContext) {
for destroy in value.uses.users(ofType: DestroyValueInst.self) {
if !ignoring.contains(destroy) {
context.erase(instruction: destroy)
}
}
}
private func insertDestroy(of value: Value,
before insertionPoint: Instruction,
ifIn liverange: Liverange,
_ aliveDestroys: inout InstructionSet
) {
guard liverange.isOnlyInExtendedLiverange(insertionPoint) else {
return
}
if let existingDestroy = insertionPoint as? DestroyValueInst, existingDestroy.destroyedValue == value {
aliveDestroys.insert(existingDestroy)
return
}
let builder = Builder(before: insertionPoint, liverange.context)
let newDestroy = builder.createDestroyValue(operand: value)
aliveDestroys.insert(newDestroy)
}
private func insertDestroy(of value: Value,
after insertionPoint: Instruction,
ifIn liverange: Liverange,
_ aliveDestroys: inout InstructionSet
) {
if let next = insertionPoint.next {
insertDestroy(of: value, before: next, ifIn: liverange, &aliveDestroys)
} else {
for succ in insertionPoint.parentBlock.successors {
insertDestroy(of: value, before: succ.instructions.first!, ifIn: liverange, &aliveDestroys)
}
}
}
private func immediatelyDestroy(value: Value, ifIn liverange: Liverange, _ aliveDestroys: inout InstructionSet) {
if let arg = value as? Argument {
insertDestroy(of: value, before: arg.parentBlock.instructions.first!, ifIn: liverange, &aliveDestroys)
} else {
insertDestroy(of: value, after: value.definingInstruction!, ifIn: liverange, &aliveDestroys)
}
}
private func needPreserveDebugInfo(of value: Value, _ context: FunctionPassContext) -> Bool {
if value.parentFunction.shouldOptimize {
// No need to preserve debug info in optimized builds.
return false
}
// Check if the value is associated to a source-level variable.
if let inst = value.definingInstruction {
return inst.findVarDecl() != nil
}
if let arg = value as? Argument {
return arg.findVarDecl() != nil
}
return false
}
/// Represents the "extended" liverange of a value which is the range after the last uses until the
/// final destroys of the value.
///
/// ```
/// %1 = definition -+ -+
/// ... | pruned liverange |
/// last_use(%1) -+ -+ | full liverange
/// ... no uses of %1 | extended liverange |
/// destroy_value %1 -+ -+
/// ```
private struct Liverange {
var nonDestroyingUsers: Stack<Instruction>
var prunedLiverange: InstructionRange
var fullLiverange: InstructionRange
let context: FunctionPassContext
init?(of value: Value, _ context: FunctionPassContext) {
guard let users = Stack(usersOf: value, context) else {
return nil
}
self.nonDestroyingUsers = users
self.prunedLiverange = InstructionRange(for: value, context)
prunedLiverange.insert(contentsOf: nonDestroyingUsers)
self.fullLiverange = InstructionRange(for: value, context)
fullLiverange.insert(contentsOf: value.users)
self.context = context
}
func isOnlyInExtendedLiverange(_ instruction: Instruction) -> Bool {
fullLiverange.inclusiveRangeContains(instruction) && !prunedLiverange.inclusiveRangeContains(instruction)
}
mutating func extendWithAccessScopes(of endAccesses: Stack<EndAccessInst>) {
var changed: Bool
// We need to do this repeatedly because if access scopes are not nested properly, an overlapping scope
// can make a non-overlapping scope also overlapping, e.g.
// ```
// %1 = begin_access // overlapping
// last_use %value
// %2 = begin_access // initially not overlapping, but overlapping because of scope %1
// end_access %1
// end_access %2
// destroy_value %value
// ```
repeat {
changed = false
for endAccess in endAccesses {
if isOnlyInExtendedLiverange(endAccess), !isOnlyInExtendedLiverange(endAccess.beginAccess) {
prunedLiverange.insert(endAccess)
nonDestroyingUsers.append(endAccess)
changed = true
}
}
} while changed
}
mutating func deinitialize() {
fullLiverange.deinitialize()
prunedLiverange.deinitialize()
nonDestroyingUsers.deinitialize()
}
}
private extension Stack where Element == Instruction {
init?(usersOf value: Value, _ context: FunctionPassContext) {
var users = Stack<Instruction>(context)
var visitor = InteriorUseWalker(definingValue: value, ignoreEscape: false, visitInnerUses: true, context) {
if $0.instruction is DestroyValueInst, $0.value == value {
return .continueWalk
}
users.append($0.instruction)
return .continueWalk
}
defer { visitor.deinitialize() }
guard visitor.visitUses() == .continueWalk else {
users.deinitialize()
return nil
}
self = users
}
}

View File

@@ -80,6 +80,7 @@ private func registerSwiftPasses() {
registerPass(computeSideEffects, { computeSideEffects.run($0) })
registerPass(diagnoseInfiniteRecursion, { diagnoseInfiniteRecursion.run($0) })
registerPass(destroyHoisting, { destroyHoisting.run($0) })
registerPass(mandatoryDestroyHoisting, { mandatoryDestroyHoisting.run($0) })
registerPass(initializeStaticGlobalsPass, { initializeStaticGlobalsPass.run($0) })
registerPass(objCBridgingOptimization, { objCBridgingOptimization.run($0) })
registerPass(objectOutliner, { objectOutliner.run($0) })

View File

@@ -71,6 +71,8 @@ PASS(BooleanLiteralFolding, "boolean-literal-folding",
"Constant folds initializers of boolean literals")
PASS(DestroyHoisting, "destroy-hoisting",
"Hoist destroy_value instructions")
PASS(MandatoryDestroyHoisting, "mandatory-destroy-hoisting",
"Hoist destroy_value instructions for non-lexical values")
PASS(DeadEndBlockDumper, "dump-deadendblocks",
"Tests the DeadEndBlocks utility")
PASS(EscapeInfoDumper, "dump-escape-info",

View File

@@ -266,6 +266,8 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
P.addOnoneSimplification();
P.addInitializeStaticGlobals();
P.addMandatoryDestroyHoisting();
// MandatoryPerformanceOptimizations might create specializations that are not
// used, and by being unused they are might have unspecialized applies.
// Eliminate them via the DeadFunctionAndGlobalElimination in embedded Swift

View File

@@ -12,8 +12,7 @@ public func testPartialApply(_ obj: Test) {
// CHECK: dynamic_method_br [[CURRIED1_OBJ:%.+]] : $@opened([[CURRIED1_EXISTENTIAL:.+]], any Test) Self, #Test.normalObject!foreign, [[CURRIED1_TRUE:[^,]+]], [[CURRIED1_FALSE:[^,]+]]
// CHECK: [[CURRIED1_FALSE]]:
// CHECK: [[CURRIED1_TRUE]]([[CURRIED1_METHOD:%.+]] : $@convention(objc_method) (@opened([[CURRIED1_EXISTENTIAL]], any Test) Self) -> @autoreleased AnyObject):
// CHECK: [[CURRIED1_OBJECT_COPY:%.*]] = copy_value [[CURRIED1_OBJ]]
// CHECK: [[CURRIED1_PARTIAL:%.+]] = partial_apply [callee_guaranteed] [[CURRIED1_METHOD]]([[CURRIED1_OBJECT_COPY]]) : $@convention(objc_method) (@opened([[CURRIED1_EXISTENTIAL]], any Test) Self) -> @autoreleased AnyObject
// CHECK: [[CURRIED1_PARTIAL:%.+]] = partial_apply [callee_guaranteed] [[CURRIED1_METHOD]]([[CURRIED1_OBJ]]) : $@convention(objc_method) (@opened([[CURRIED1_EXISTENTIAL]], any Test) Self) -> @autoreleased AnyObject
// CHECK: [[CURRIED1_THUNK:%.+]] = function_ref @$syXlIego_ypIegr_TR : $@convention(thin) (@guaranteed @callee_guaranteed () -> @owned AnyObject) -> @out Any
// CHECK: = partial_apply [callee_guaranteed] [[CURRIED1_THUNK]]([[CURRIED1_PARTIAL]])
curried1()
@@ -22,16 +21,14 @@ public func testPartialApply(_ obj: Test) {
// CHECK: dynamic_method_br [[CURRIED2_OBJ:%.+]] : $@opened([[CURRIED2_EXISTENTIAL:.+]], any Test) Self, #Test.innerPointer!foreign, [[CURRIED2_TRUE:[^,]+]], [[CURRIED2_FALSE:[^,]+]]
// CHECK: [[CURRIED2_FALSE]]:
// CHECK: [[CURRIED2_TRUE]]([[CURRIED2_METHOD:%.+]] : $@convention(objc_method) (@opened([[CURRIED2_EXISTENTIAL]], any Test) Self) -> @unowned_inner_pointer UnsafeMutableRawPointer):
// CHECK: [[CURRIED2_OBJ_COPY:%.*]] = copy_value [[CURRIED2_OBJ]]
// CHECK: [[CURRIED2_PARTIAL:%.+]] = partial_apply [callee_guaranteed] [[CURRIED2_METHOD]]([[CURRIED2_OBJ_COPY]]) : $@convention(objc_method) (@opened([[CURRIED2_EXISTENTIAL]], any Test) Self) -> @unowned_inner_pointer UnsafeMutableRawPointer
// CHECK: [[CURRIED2_PARTIAL:%.+]] = partial_apply [callee_guaranteed] [[CURRIED2_METHOD]]([[CURRIED2_OBJ]]) : $@convention(objc_method) (@opened([[CURRIED2_EXISTENTIAL]], any Test) Self) -> @unowned_inner_pointer UnsafeMutableRawPointer
curried2()
}
if let prop1 = obj.normalObjectProp {
// CHECK: dynamic_method_br [[PROP1_OBJ:%.+]] : $@opened([[PROP1_EXISTENTIAL:.+]], any Test) Self, #Test.normalObjectProp!getter.foreign, [[PROP1_TRUE:[^,]+]], [[PROP1_FALSE:[^,]+]]
// CHECK: [[PROP1_FALSE]]:
// CHECK: [[PROP1_TRUE]]([[PROP1_METHOD:%.+]] : $@convention(objc_method) (@opened([[PROP1_EXISTENTIAL]], any Test) Self) -> @autoreleased AnyObject):
// CHECK: [[PROP1_OBJ_COPY:%.*]] = copy_value [[PROP1_OBJ]]
// CHECK: [[PROP1_PARTIAL:%.+]] = partial_apply [callee_guaranteed] [[PROP1_METHOD]]([[PROP1_OBJ_COPY]]) : $@convention(objc_method) (@opened([[PROP1_EXISTENTIAL]], any Test) Self) -> @autoreleased AnyObject
// CHECK: [[PROP1_PARTIAL:%.+]] = partial_apply [callee_guaranteed] [[PROP1_METHOD]]([[PROP1_OBJ]]) : $@convention(objc_method) (@opened([[PROP1_EXISTENTIAL]], any Test) Self) -> @autoreleased AnyObject
// CHECK: = apply [[PROP1_PARTIAL]]() : $@callee_guaranteed () -> @owned AnyObject
_ = prop1
}
@@ -39,8 +36,7 @@ public func testPartialApply(_ obj: Test) {
// CHECK: dynamic_method_br [[PROP2_OBJ:%.+]] : $@opened([[PROP2_EXISTENTIAL:.+]], any Test) Self, #Test.innerPointerProp!getter.foreign, [[PROP2_TRUE:[^,]+]], [[PROP2_FALSE:[^,]+]]
// CHECK: [[PROP2_FALSE]]:
// CHECK: [[PROP2_TRUE]]([[PROP2_METHOD:%.+]] : $@convention(objc_method) (@opened([[PROP2_EXISTENTIAL]], any Test) Self) -> @unowned_inner_pointer UnsafeMutableRawPointer):
// CHECK: [[PROP2_OBJ_COPY:%.*]] = copy_value [[PROP2_OBJ]]
// CHECK: [[PROP2_PARTIAL:%.+]] = partial_apply [callee_guaranteed] [[PROP2_METHOD]]([[PROP2_OBJ_COPY]]) : $@convention(objc_method) (@opened([[PROP2_EXISTENTIAL]], any Test) Self) -> @unowned_inner_pointer UnsafeMutableRawPointer
// CHECK: [[PROP2_PARTIAL:%.+]] = partial_apply [callee_guaranteed] [[PROP2_METHOD]]([[PROP2_OBJ]]) : $@convention(objc_method) (@opened([[PROP2_EXISTENTIAL]], any Test) Self) -> @unowned_inner_pointer UnsafeMutableRawPointer
// CHECK: = apply [[PROP2_PARTIAL]]() : $@callee_guaranteed () -> UnsafeMutableRawPointer
_ = prop2
}

View File

@@ -49,7 +49,6 @@ distributed actor MyDistActor {
// CHECK: [[TP_FIELD2:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.actorSystem
// CHECK: [[RELOADED_SYS1:%[0-9]+]] = load [[TP_FIELD2]] : $*FakeRoundtripActorSystem
// CHECK: [[SELF_METATYPE:%[0-9]+]] = metatype $@thick MyDistActor.Type
// CHECK: strong_retain [[RELOADED_SYS1]] : $FakeRoundtripActorSystem
// CHECK: [[ASSIGN_ID_FN:%[0-9]+]] = function_ref @$s27FakeDistributedActorSystems0a9RoundtripC6SystemC8assignIDyAA0C7AddressVxm0B00bC0RzlF
// CHECK: [[ID:%[0-9]+]] = apply [[ASSIGN_ID_FN]]<MyDistActor>([[SELF_METATYPE]], {{.*}})
// CHECK: strong_release {{.*}} : $FakeRoundtripActorSystem
@@ -72,7 +71,6 @@ distributed actor MyDistActor {
// CHECK: return [[SELF]] : $MyDistActor
// CHECK: [[SYSTEM_ERROR_BB]]([[ERROR_ARG:%[0-9]+]] : $any Error):
// CHECK: strong_retain [[ERROR_ARG]] : $any Error
// CHECK: function_ref @$s27FakeDistributedActorSystems0a9RoundtripC6SystemCACycfC
// CHECK: store {{.*}} to {{.*}} : $*FakeRoundtripActorSystem
// CHECK: store {{.*}} to {{.*}} : $*ActorAddress

View File

@@ -20,8 +20,8 @@ import Foundation
// CHECK-NEXT: %._value = getelementptr inbounds{{.*}} %TSi, ptr %[[T2]], i32 0, i32 0
// CHECK: %[[T7:.+]] = call swiftcc ptr @"$ss27_finalizeUninitializedArrayySayxGABnlF"(ptr %[[T1]], ptr @"$sSiN")
// CHECK: %[[T4:.+]] = call swiftcc ptr @"$sSa10FoundationE19_bridgeToObjectiveCSo7NSArrayCyF"(ptr %[[T7]], ptr @"$sSiN")
// CHECK-NEXT: store ptr %[[T4]]
// CHECK-NEXT: call void @swift_bridgeObjectRelease(ptr %{{[0-9]+}}) #{{[0-9]+}}
// CHECK-NEXT: store ptr %[[T4]]
// CHECK-NEXT: call void @llvm.objc.release(ptr %[[T4]])
// CHECK-NEXT: ret ptr %[[T4]]
let arr = [1] as CFArray
@@ -37,12 +37,14 @@ import Foundation
// CHECK: [[L2]]: ; preds = %entry
// CHECK-NEXT: %[[T4:.+]] = phi ptr [ %[[T0]], %entry ]
// CHECK-NEXT: call void @llvm.objc.release(ptr %{{.+}})
// CHECK-NEXT: %[[T5:.+]] = ptrtoint ptr %[[T4]] to i{{32|64}}
// CHECK-NEXT: br label %[[L3:.+]]
// CHECK: [[L1]]: ; preds = %entry
// CHECK-NEXT: %[[T6:.+]] = phi ptr [ %[[T2]], %entry ]
// CHECK-NEXT: store ptr null, ptr %swifterror, align {{[0-9]+}}
// CHECK-NEXT: call void @llvm.objc.release(ptr %{{.+}})
// CHECK-NEXT: %[[T7:.+]] = icmp eq i{{32|64}} %{{.+}}, 0
// CHECK-NEXT: br i1 %[[T7]], label %[[L4:.+]], label %[[L5:.+]]
@@ -54,8 +56,7 @@ import Foundation
// CHECK-NEXT: %[[T9:.+]] = phi ptr [ %[[T8]], %[[L5]] ]
// CHECK-NEXT: %[[T10:.+]] = call swiftcc ptr @"$s10Foundation22_convertErrorToNSErrorySo0E0Cs0C0_pF"(ptr %[[T6]]) #{{[0-9]+}}
// CHECK: call swiftcc void @"$sSA7pointeexvs"(ptr noalias %{{.+}}, ptr %[[T9]], ptr %{{.+}}) #{{[0-9]+}}
// CHECK: call void @swift_errorRelease(ptr %[[T6]]) #{{[0-9]+}}
// CHECK-NEXT: br label %[[L7:.+]]
// CHECK: br label %[[L7:.+]]
// CHECK: [[L4]]: ; preds = %[[L1]]
// CHECK-NEXT: call void @swift_errorRelease(ptr %[[T6]]) #{{[0-9]+}}
@@ -66,6 +67,5 @@ import Foundation
// CHECK: [[L3]]: ; preds = %[[L2]], %[[L7]]
// CHECK-NEXT: %[[T12:.+]] = phi i{{32|64}} [ 0, %[[L7]] ], [ %[[T5]], %[[L2]] ]
// CHECK-NEXT: call void @llvm.objc.release(ptr %{{.+}})
// CHECK-NEXT: %[[T14:.+]] = inttoptr i{{32|64}} %[[T12]] to ptr
// CHECK-NEXT: ret ptr %[[T14]]

View File

@@ -129,6 +129,14 @@ entry(%flag : $Builtin.Int1):
// CHECK-NEXT: [[R6:%.*]] = load ptr, ptr [[T0]], align
// CHECK-NEXT: [[T0:%.*]] = getelementptr inbounds{{.*}} [[BIGSUB]], ptr [[PTR]], i32 0, i32 7
// CHECK-NEXT: [[R7:%.*]] = load ptr, ptr [[T0]], align
// CHECK: call void @swift_release(ptr [[R0]])
// CHECK-NEXT: call void @swift_release(ptr [[R1]])
// CHECK-NEXT: call void @swift_release(ptr [[R2]])
// CHECK-NEXT: call void @swift_release(ptr [[R3]])
// CHECK: call void @swift_release(ptr [[R4]])
// CHECK-NEXT: call void @swift_release(ptr [[R5]])
// CHECK-NEXT: call void @swift_release(ptr [[R6]])
// CHECK-NEXT: call void @swift_release(ptr [[R7]])
// Branch.
// CHECK-NEXT: br i1 %0,
@@ -155,17 +163,9 @@ no:
br cont
cont:
// CHECK: call void @swift_release(ptr [[R0]])
// CHECK-NEXT: call void @swift_release(ptr [[R1]])
// CHECK-NEXT: call void @swift_release(ptr [[R2]])
// CHECK-NEXT: call void @swift_release(ptr [[R3]])
// CHECK: call void @swift_release(ptr [[R4]])
// CHECK-NEXT: call void @swift_release(ptr [[R5]])
// CHECK-NEXT: call void @swift_release(ptr [[R6]])
// CHECK-NEXT: call void @swift_release(ptr [[R7]])
destroy_value %value : $Big<SomeSubclass>
// CHECK-NEXT: ret void
// CHECK: ret void
%ret = tuple ()
return %ret : $()
}

View File

@@ -122,6 +122,10 @@ entry(%flag : $Builtin.Int1):
%0 = function_ref @test_simple : $@convention(thin) @yield_once <T: SomeClass> () -> (@yields @owned Biggish<T>)
(%value, %token) = begin_apply %0<SomeSubclass>() : $@convention(thin) @yield_once <T: SomeClass> () -> (@yields @owned Biggish<T>)
// CHECK: call void @swift_release(ptr [[R0_ORIG]])
// CHECK-NEXT: call void @swift_release(ptr [[R1_ORIG]])
// CHECK-NEXT: call void @swift_release(ptr [[R2_ORIG]])
// CHECK-NEXT: call void @swift_release(ptr [[R3_ORIG]])
// Branch.
// CHECK-NEXT: br i1 %0,
cond_br %flag, yes, no
@@ -147,13 +151,9 @@ no:
br cont
cont:
// CHECK: call void @swift_release(ptr [[R0_ORIG]])
// CHECK-NEXT: call void @swift_release(ptr [[R1_ORIG]])
// CHECK-NEXT: call void @swift_release(ptr [[R2_ORIG]])
// CHECK-NEXT: call void @swift_release(ptr [[R3_ORIG]])
destroy_value %value : $Biggish<SomeSubclass>
// CHECK-NEXT: ret void
// CHECK: ret void
%ret = tuple ()
return %ret : $()
}

View File

@@ -28,7 +28,6 @@ import Closure
// CHECK: apply %[[V3]](%[[V1]]) : $@callee_guaranteed (@in_guaranteed ARCWeak) -> ()
// CHECK: %[[V6:.*]] = tuple ()
// CHECK: destroy_addr %[[V1]] : $*ARCWeak
// CHECK: strong_release %[[V3]] : $@callee_guaranteed (@in_guaranteed ARCWeak) -> ()
// CHECK: return %[[V6]] : $()
// ARCWeak is destroyed by the callee.

View File

@@ -27,7 +27,6 @@ import Closure
// CHECK: strong_retain %[[V3]] : $@callee_guaranteed (@in_guaranteed NonTrivial) -> ()
// CHECK: apply %[[V3]](%[[V1]]) : $@callee_guaranteed (@in_guaranteed NonTrivial) -> ()
// CHECK: %[[V6:.*]] = tuple ()
// CHECK: strong_release %[[V3]] : $@callee_guaranteed (@in_guaranteed NonTrivial) -> ()
// CHECK: return %[[V6]] : $()
// NonTrivial is destroyed by the caller.

View File

@@ -54,8 +54,8 @@ func test0() {
// MANDATORY-NEXT: // function_ref
// MANDATORY-NEXT: [[T0:%.*]] = function_ref @$s10reabstract6takeFn{{[_0-9a-zA-Z]*}}F
// MANDATORY-NEXT: apply [[T0]]<Int>([[T5]])
// MANDATORY-NEXT: strong_release [[T2]]
// MANDATORY-NEXT: dealloc_stack [[T4]] : $@noescape @callee_guaranteed (@in_guaranteed Int) -> @out Optional<Int>
// MANDATORY-NEXT: strong_release [[T2]]
// MANDATORY-NEXT: tuple ()
// MANDATORY-NEXT: return
// MANDATORY-NEXT: } // end sil function '$s10reabstract5test0yyF'

View File

@@ -64,6 +64,7 @@ func test1() throws {
// CHECK-NEXT: [[RESULT:%.*]] = tuple ()
// CHECK-NEXT: return [[RESULT]]
// CHECK: [[ERROR]]([[T0:%.*]] : $any Error):
// CHECK-NEXT: strong_release [[T0]]
// CHECK-NEXT: unreachable
func test2() {
rethrower(nonthrower)

View File

@@ -88,7 +88,6 @@ public func couldActuallyEscapeWithLoop(_ closure: @escaping () -> (), _ villain
// CHECK: [[BLOCK_PROJ:%.*]] = project_block_storage [[BLOCK_SLOT]]
// CHECK: store [[MDI]] to [[BLOCK_PROJ]] :
// CHECK: [[BLOCK:%.*]] = init_block_storage_header [[BLOCK_SLOT]]
// CHECK: release_value [[LOOP_IND_VAR]]
// CHECK: [[SOME:%.*]] = enum $Optional<{{.*}}>, #Optional.some!enumelt, [[MDI]]
// CHECK: [[BLOCK_COPY:%.*]] = copy_block [[BLOCK]]
// CHECK: destroy_addr [[BLOCK_PROJ]]
@@ -100,7 +99,6 @@ public func couldActuallyEscapeWithLoop(_ closure: @escaping () -> (), _ villain
// CHECK: br [[LOOP_HEADER_BB]]([[NONE_FOR_BACKEDGE]] :
//
// CHECK: [[NONE_BB]]:
// CHECK: release_value [[LOOP_IND_VAR]]
// CHECK: return
// CHECK: } // end sil function '$s27closure_lifetime_fixup_objc9dontCrashyyF'
public func dontCrash() {
@@ -135,7 +133,6 @@ class C: NSObject {
// CHECK-LABEL: sil hidden @$s27closure_lifetime_fixup_objc9CWithLoopCfD : $@convention(method) (@owned CWithLoop) -> () {
// CHECK: [[METH:%.*]] = objc_super_method
// CHECK-NEXT: release_value
// CHECK-NEXT: release_value
// CHECK-NEXT: upcast {{%.*}} : $CWithLoop to $NSObject
// CHECK-NEXT: apply [[METH]]({{%.*}}) : $@convention(objc_method) (NSObject) -> ()
// CHECK-NEXT: tuple ()

View File

@@ -15,22 +15,22 @@ import Foundation
// CHECK: retain_value %0
// CHECK: retain_value %0
// CHECK: bb1
// CHECK: convert_escape_to_noescape %
// CHECK: strong_release
// CHECK: convert_escape_to_noescape %
// CHECK: bb5
// CHECK: retain_value %1
// CHECK: retain_value %1
// CHECK: bb6
// CHECK: convert_escape_to_noescape %
// CHECK: strong_release
// CHECK: convert_escape_to_noescape %
// CHECK: bb10
// CHECK: [[F:%.*]] = function_ref @$sSo14noescapeBlock3yyySSSgXESg_AcBtFTo
// CHECK: apply [[F]]
// CHECK: release_value {{.*}} : $Optional<NSString>
// CHECK: release_value %1 : $Optional<@callee_guaranteed (@guaranteed Optional<String>) -> ()>
// CHECK: release_value {{.*}} : $Optional<@convention(block) @noescape (Optional<NSString>) -> ()>
// CHECK: release_value %0 : $Optional<@callee_guaranteed (@guaranteed Optional<String>) -> ()>
// CHECK: release_value {{.*}} : $Optional<@convention(block) @noescape (Optional<NSString>)
// CHECK: release_value %1 : $Optional<@callee_guaranteed (@guaranteed Optional<String>) -> ()>
// CHECK: release_value %0 : $Optional<@callee_guaranteed (@guaranteed Optional<String>) -> ()>
public func bridgeNoescapeBlock( optFn: ((String?) -> ())?, optFn2: ((String?) -> ())?) {
noescapeBlock3(optFn, optFn2, "Foobar")
}
@@ -53,7 +53,6 @@ public func returnOptionalEscape() -> (() ->())?
// CHECK: [[V1_UNWRAPPED:%.*]] = unchecked_enum_data [[V1]]
// CHECK: [[CVT:%.*]] = convert_escape_to_noescape [[V1_UNWRAPPED]]
// CHECK: [[SOME:%.*]] = enum $Optional<{{.*}}>, #Optional.some!enumelt, [[CVT]]
// CHECK: strong_release [[V2]]
// CHECK: br [[NEXT_BB:bb[0-9]+]]([[SOME]] :
//
// CHECK: [[NEXT_BB]]([[SOME_PHI:%.*]] :
@@ -77,11 +76,11 @@ public func returnOptionalEscape() -> (() ->())?
// CHECK: br bb5([[NONE_BLOCK]] : {{.*}}, [[NONE]] :
//
// CHECK: bb5([[BLOCK_PHI:%.*]] : $Optional<{{.*}}>, [[SWIFT_CLOSURE_PHI:%.*]] :
// CHECK: release_value [[SWIFT_CLOSURE_PHI]]
// CHECK: [[F:%.*]] = function_ref @$sSo13noescapeBlockyyyyXESgFTo
// CHECK: apply [[F]]([[BLOCK_PHI]])
// CHECK: release_value [[BLOCK_PHI]]
// CHECK: release_value [[SWIFT_CLOSURE_PHI]]
// CHECK-NEXT: return
// CHECK: return
// NOPEEPHOLE-LABEL: sil @$s1A19bridgeNoescapeBlockyyF : $@convention(thin) () -> () {
// NOPEEPHOLE: bb0:
@@ -93,11 +92,11 @@ public func returnOptionalEscape() -> (() ->())?
//
// NOPEEPHOLE: [[SOME_BB]]([[V2:%.*]]: $@callee_guaranteed () -> ()):
// NOPEEPHOLE-NEXT: strong_retain [[V2]]
// NOPEEPHOLE-NEXT: strong_release [[V2]]
// NOPEEPHOLE-NEXT: [[CVT:%.*]] = convert_escape_to_noescape [[V2]]
// NOPEEPHOLE-NEXT: [[SOME:%.*]] = enum $Optional<{{.*}}>, #Optional.some!enumelt, [[V2]]
// NOPEEPHOLE-NEXT: [[MDI:%.*]] = mark_dependence [[CVT]] : $@noescape @callee_guaranteed () -> () on [[V2]]
// NOPEEPHOLE-NEXT: [[NOESCAPE_SOME:%.*]] = enum $Optional<{{.*}}>, #Optional.some!enumelt, [[MDI]]
// NOPEEPHOLE-NEXT: strong_release [[V2]]
// NOPEEPHOLE-NEXT: br bb2([[NOESCAPE_SOME]] : $Optional<{{.*}}>, [[SOME]] : $Optional<{{.*}}>, [[SOME]] : $Optional<{{.*}}>)
//
// NOPEEPHOLE: bb2([[NOESCAPE_SOME:%.*]] : $Optional<{{.*}}>, [[SOME1:%.*]] : $Optional<{{.*}}>, [[SOME:%.*]] : $Optional<{{.*}}>):
@@ -110,12 +109,12 @@ public func returnOptionalEscape() -> (() ->())?
// NOPEEPHOLE: br bb5
//
// NOPEEPHOLE: bb5([[BLOCK_PHI:%.*]] : $Optional<{{.*}}>, [[SWIFT_CLOSURE_PHI:%.*]] :
// NOPEEPHOLE-NEXT: release_value [[SWIFT_CLOSURE_PHI]]
// NOPEEPHOLE: [[F:%.*]] = function_ref @$sSo13noescapeBlockyyyyXESgFTo :
// NOPEEPHOLE-NEXT: apply [[F]]([[BLOCK_PHI]])
// NOPEEPHOLE-NEXT: release_value [[BLOCK_PHI]]
// NOPEEPHOLE-NEXT: tuple
// NOPEEPHOLE-NEXT: release_value [[SOME]]
// NOPEEPHOLE-NEXT: release_value [[SWIFT_CLOSURE_PHI]]
// NOPEEPHOLE-NEXT: return
// NOPEEPHOLE: } // end sil function '$s1A19bridgeNoescapeBlockyyF'
public func bridgeNoescapeBlock() {

View File

@@ -95,11 +95,11 @@ func use(_ o : borrowing View) {}
// CHECK: ([[VIEW:%.*]], [[TOKEN2:%.*]]) = begin_apply %{{.*}}([[MDI]]) : $@yield_once @convention(method) (@guaranteed Wrapper) -> @lifetime(copy 0) @yields @guaranteed View
// CHECK: retain_value [[VIEW]]
// CHECK: end_apply [[TOKEN2]] as $()
// CHECK: release_value [[MDI]]
// CHECK: debug_value [[VIEW]], let, name "view"
// use(view)
// CHECK: apply %{{.*}}([[VIEW]]) : $@convention(thin) (@guaranteed View) -> ()
// CHECK: release_value [[VIEW]]
// CHECK: release_value [[MDI]]
// CHECK: end_apply [[TOKEN1]] as $()
// CHECK: end_access [[ACCESS]]
// CHECK: destroy_addr [[NC]]

View File

@@ -0,0 +1,447 @@
// RUN: %target-sil-opt -mandatory-destroy-hoisting %s | %FileCheck %s
sil_stage canonical
import Builtin
import Swift
// CHECK-LABEL: sil [ossa] @simple :
// CHECK: fix_lifetime
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: debug_step
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'simple'
sil [ossa] @simple : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
fix_lifetime %0
debug_step
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @multi_block :
// CHECK: fix_lifetime
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'multi_block'
sil [ossa] @multi_block : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
fix_lifetime %0
cond_br undef, bb1, bb2
bb1:
br bb3
bb2:
br bb3
bb3:
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @two_uses_in_one_user :
// CHECK: apply
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: debug_step
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'two_uses_in_one_user'
sil [ossa] @two_uses_in_one_user : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
apply undef(%0, %0) : $(@guaranteed String, @guaranteed String) -> ()
debug_step
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @no_destroy :
// CHECK-NOT: destroy_value
// CHECK: } // end sil function 'no_destroy'
sil [ossa] @no_destroy : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
apply undef(%0) : $(@owned String) -> ()
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @no_arg_user :
// CHECK: bb0(%0 : @owned $String):
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: debug_step
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'no_arg_user'
sil [ossa] @no_arg_user : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
debug_step
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @no_inst_user :
// CHECK: %1 = move_value %0
// CHECK-NEXT: destroy_value %1
// CHECK-NEXT: debug_step
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'no_inst_user'
sil [ossa] @no_inst_user : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
%1 = move_value %0
debug_step
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @destroy_already_in_place :
// CHECK: fix_lifetime
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: debug_step
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'destroy_already_in_place'
sil [ossa] @destroy_already_in_place : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
fix_lifetime %0
destroy_value %0
debug_step
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @two_consecutive_users :
// CHECK: fix_lifetime
// CHECK-NEXT: fix_lifetime
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: debug_step
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'two_consecutive_users'
sil [ossa] @two_consecutive_users : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
fix_lifetime %0
fix_lifetime %0
debug_step
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @last_user_is_terminator :
// CHECK: bb1(%2 : $()):
// CHECK-NEXT: destroy_value %0
// CHECK: bb2(%5 : @owned $any Error):
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'last_user_is_terminator'
sil [ossa] @last_user_is_terminator : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
try_apply undef(%0) : $(@guaranteed String) -> @error Error, normal bb1, error bb2
bb1(%2 : $()):
br bb3
bb2(%3 : @owned $Error):
apply undef(%3) : $(@owned Error) -> ()
br bb3
bb3:
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @liverange_exit :
// CHECK: bb1:
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'liverange_exit'
sil [ossa] @liverange_exit : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
cond_br undef, bb1, bb2
bb1:
debug_step
destroy_value %0
br bb3
bb2:
apply undef(%0) : $(@owned String) -> ()
br bb3
bb3:
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @non_overlapping_access_scope :
// CHECK: fix_lifetime
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: begin_access
// CHECK: } // end sil function 'non_overlapping_access_scope'
sil [ossa] @non_overlapping_access_scope : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
fix_lifetime %0
%2 = begin_access [modify] [dynamic] undef : $*Int
end_access %2
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @overlapping_access_scope :
// CHECK: end_access
// CHECK-NEXT: destroy_value %2
// CHECK: } // end sil function 'overlapping_access_scope'
sil [ossa] @overlapping_access_scope : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
%1 = begin_access [modify] [dynamic] undef : $*Int
%2 = move_value %0
fix_lifetime %2
end_access %1
destroy_value %2
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @interleaved_overlapping_access_scope :
// CHECK: end_access %1
// CHECK-NEXT: end_access %4
// CHECK-NEXT: destroy_value %2
// CHECK: } // end sil function 'interleaved_overlapping_access_scope'
sil [ossa] @interleaved_overlapping_access_scope : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
%1 = begin_access [modify] [dynamic] undef : $*Int
%2 = move_value %0
fix_lifetime %2
%4 = begin_access [modify] [dynamic] undef : $*Int
end_access %1
end_access %4
destroy_value %2
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @interleaved_overlapping_access_scope_reverse_order :
// CHECK: bb1:
// CHECK-NEXT: end_access %4
// CHECK-NEXT: destroy_value %2
// CHECK: } // end sil function 'interleaved_overlapping_access_scope_reverse_order'
sil [ossa] @interleaved_overlapping_access_scope_reverse_order : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
%1 = begin_access [modify] [dynamic] undef : $*Int
%2 = move_value %0
fix_lifetime %2
%4 = begin_access [modify] [dynamic] undef : $*Int
br bb2
bb1:
end_access %4
destroy_value %2
%r = tuple ()
return %r
bb2:
end_access %1
br bb1
}
// CHECK-LABEL: sil [ossa] @dead_end_block :
// CHECK: bb0(%0 : @owned $String):
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'dead_end_block'
sil [ossa] @dead_end_block : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
cond_br undef, bb1, bb2
bb1:
debug_step
destroy_value %0
%r = tuple ()
return %r
bb2:
unreachable
}
// CHECK-LABEL: sil [ossa] @dead_end_block_with_use :
// CHECK: bb1:
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'dead_end_block_with_use'
sil [ossa] @dead_end_block_with_use : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
cond_br undef, bb1, bb2
bb1:
debug_step
destroy_value %0
%r = tuple ()
return %r
bb2:
fix_lifetime %0
unreachable
}
// CHECK-LABEL: sil [ossa] @dead_end_block_with_inner_use :
// CHECK: end_borrow %1
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'dead_end_block_with_inner_use'
sil [ossa] @dead_end_block_with_inner_use : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
%1 = begin_borrow %0
cond_br undef, bb1, bb2
bb1:
end_borrow %1
debug_step
destroy_value %0
%r = tuple ()
return %r
bb2:
fix_lifetime %1
unreachable
}
// CHECK-LABEL: sil [ossa] @dead_end_with_no_destroy :
// CHECK: bb0(%0 : @owned $String):
// CHECK-NEXT: fix_lifetime
// CHECK-NEXT: unreachable
// CHECK: } // end sil function 'dead_end_with_no_destroy'
sil [ossa] @dead_end_with_no_destroy : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
fix_lifetime %0
unreachable
}
// CHECK-LABEL: sil [ossa] @dead_end_with_no_use :
// CHECK: bb0(%0 : @owned $String):
// CHECK-NEXT: unreachable
// CHECK: } // end sil function 'dead_end_with_no_use'
sil [ossa] @dead_end_with_no_use : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
unreachable
}
// CHECK-LABEL: sil [ossa] @borrow_scope :
// CHECK: end_borrow
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'borrow_scope'
sil [ossa] @borrow_scope : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
%1 = begin_borrow %0
end_borrow %1
debug_step
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @partial_apply :
// CHECK: destroy_value %1
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'partial_apply'
sil [ossa] @partial_apply : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
%1 = partial_apply [on_stack] undef(%0) : $(@guaranteed String) -> ()
destroy_value %1
debug_step
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @mark_dependence :
// CHECK: destroy_value %2
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'mark_dependence'
sil [ossa] @mark_dependence : $@convention(thin) (@owned String, @owned String) -> () {
bb0(%0 : @owned $String, %1 : @owned $String):
%2 = mark_dependence [nonescaping] %1 on %0
destroy_value %2
debug_step
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @reborrow :
// CHECK: end_borrow
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: debug_step
// CHECK: } // end sil function 'reborrow'
sil [ossa] @reborrow : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
cond_br undef, bb1, bb2
bb1:
%1 = begin_borrow %0
br bb3(%1)
bb2:
%3 = begin_borrow %0
br bb3(%3)
bb3(%5 : @reborrow $String):
%6 = borrowed %5 from (%0)
end_borrow %6
debug_step
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @lexical :
// CHECK: debug_step
// CHECK-NEXT: destroy_value %1
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'lexical'
sil [ossa] @lexical : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
%1 = move_value [lexical] %0
debug_step
destroy_value %1
%r = tuple ()
return %r
}
class C {}
// CHECK-LABEL: sil [ossa] @lexical_in_class_initializer :
// CHECK: debug_step
// CHECK-NEXT: destroy_value %2
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'lexical_in_class_initializer'
sil [ossa] @lexical_in_class_initializer : $@convention(thin) () -> () {
bb0:
%0 = alloc_ref $C
%1 = move_value [lexical] %0
%2 = end_init_let_ref %1
debug_step
destroy_value %2
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @copy_of_lexical :
// CHECK: %2 = copy_value %1
// CHECK-NEXT: destroy_value %2
// CHECK: } // end sil function 'copy_of_lexical'
sil [ossa] @copy_of_lexical : $@convention(thin) (@owned String) -> () {
bb0(%0 : @owned $String):
%1 = move_value [lexical] %0
%2 = copy_value %1
destroy_value %1
debug_step
destroy_value %2
%r = tuple ()
return %r
}
struct NC: ~Copyable {}
// CHECK-LABEL: sil [ossa] @not_copyable_arg :
// CHECK: debug_step
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'not_copyable_arg'
sil [ossa] @not_copyable_arg : $@convention(thin) (@owned NC) -> () {
bb0(%0 : @owned $NC):
fix_lifetime %0
debug_step
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @not_copyable_struct :
// CHECK: debug_step
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'not_copyable_struct'
sil [ossa] @not_copyable_struct : $@convention(thin) () -> () {
bb0:
%0 = struct $NC ()
fix_lifetime %0
debug_step
destroy_value %0
%r = tuple ()
return %r
}

View File

@@ -1,5 +1,5 @@
// RUN: %target-swift-frontend -Osize -import-objc-header %S/Inputs/Outliner.h %s -Xllvm -sil-print-types -emit-sil -enforce-exclusivity=unchecked -enable-copy-propagation | %FileCheck %s
// RUN: %target-swift-frontend -Osize -g -import-objc-header %S/Inputs/Outliner.h %s -Xllvm -sil-print-types -emit-sil -enforce-exclusivity=unchecked -enable-copy-propagation | %FileCheck %s
// RUN: %target-swift-frontend -Xllvm -sil-disable-pass=mandatory-destroy-hoisting -Osize -import-objc-header %S/Inputs/Outliner.h %s -Xllvm -sil-print-types -emit-sil -enforce-exclusivity=unchecked -enable-copy-propagation | %FileCheck %s
// RUN: %target-swift-frontend -Xllvm -sil-disable-pass=mandatory-destroy-hoisting -Osize -g -import-objc-header %S/Inputs/Outliner.h %s -Xllvm -sil-print-types -emit-sil -enforce-exclusivity=unchecked -enable-copy-propagation | %FileCheck %s
// REQUIRES: objc_interop
// REQUIRES: optimized_stdlib