Merge pull request #85334 from eeckstein/mandatory-destroy-hoisting

Optimizer: make destroy hoisting a mandatory pass
This commit is contained in:
eeckstein
2025-11-07 06:45:03 +01:00
committed by GitHub
31 changed files with 809 additions and 79 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

@@ -72,10 +72,10 @@ extension Value {
switch v {
case let fw as ForwardingInstruction:
worklist.pushIfNotVisited(contentsOf: fw.definedOperands.values)
case let ot as OwnershipTransitionInstruction where !(ot is CopyingInstruction):
worklist.pushIfNotVisited(ot.operand.value)
case let bf as BorrowedFromInst:
worklist.pushIfNotVisited(bf.borrowedValue)
case let bb as BeginBorrowInst:
worklist.pushIfNotVisited(bb.borrowedValue)
case let arg as Argument:
if let phi = Phi(arg) {
worklist.pushIfNotVisited(contentsOf: phi.incomingValues)

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

@@ -480,11 +480,16 @@ private:
}
bool respectsDeinitBarriers() const {
if (!currentDef->isLexical())
auto &module = currentDef->getFunction()->getModule();
// The move-only checker (which runs in raw SIL) relies on ignoring deinit
// barriers for non-lexical lifetimes.
// Optimizations, on the other hand, should always respect deinit barriers.
if (module.getStage() == SILStage::Raw && !currentDef->isLexical())
return false;
if (currentDef->getFunction()->forceEnableLexicalLifetimes())
return true;
auto &module = currentDef->getFunction()->getModule();
return module.getASTContext().SILOpts.supportsLexicalLifetimes(module);
}

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

@@ -408,19 +408,16 @@ static bool tryJoinIfDestroyConsumingUseInSameBlock(
return true;
}
// The lifetime of the original ends after the lifetime of the copy. If the
// original is lexical, its lifetime must not be shortened through deinit
// barriers.
if (cvi->getOperand()->isLexical()) {
// At this point, visitedInsts contains all the instructions between the
// consuming use of the copy and the destroy. If any of those instructions
// is a deinit barrier, it would be illegal to shorten the original lexical
// value's lifetime to end at that consuming use. Bail if any are.
if (llvm::any_of(visitedInsts, [](auto *inst) {
return mayBeDeinitBarrierNotConsideringSideEffects(inst);
}))
return false;
}
// The lifetime of the original ends after the lifetime of the copy.
// Its lifetime must not be shortened through deinit barriers.
// At this point, visitedInsts contains all the instructions between the
// consuming use of the copy and the destroy. If any of those instructions
// is a deinit barrier, it would be illegal to shorten the original lexical
// value's lifetime to end at that consuming use. Bail if any are.
if (llvm::any_of(visitedInsts, [](auto *inst) {
return mayBeDeinitBarrierNotConsideringSideEffects(inst);
}))
return false;
// If we reached this point, isUseBetweenInstAndBlockEnd succeeded implying
// that we found destroy_value to be after our consuming use. Noting that

View File

@@ -990,7 +990,7 @@ void DestroyAddrHoisting::hoistDestroys(
if (!continueWithNextSubpassRun(asi))
return;
changed |= ::hoistDestroys(asi,
/*ignoreDeinitBarriers=*/!asi->isLexical(),
/*ignoreDeinitBarriers=*/false,
remainingDestroyAddrs, deleter, calleeAnalysis);
}
// Arguments enclose everything.

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

@@ -3,6 +3,10 @@
// REQUIRES: CPU=arm64 || CPU=x86_64 || CPU=arm64e
// The test currently fails because various optimizations optimize away most parts of the SIL.
// TODO: re-write this test so that it's testing what it's intended to test.
// REQUIRES: fix_this_test
protocol External {
func use(str: String);
func decode<T>(_: T.Type) -> T

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

@@ -175,9 +175,9 @@ int main() {
// CHECK-NEXT: take shared frt x 2
UseCxx::consumeSharedFRT(&sfrt);
// CHECK-NEXT: retainShared
// CHECK-NEXT: releaseShared
// CHECK-NEXT: consume shared frt x 2
SharedFRT *sfrtptr = UseCxx::returnSharedFRT(&sfrt);
// CHECK-NEXT: releaseShared
// CHECK-NEXT: retainShared
// CHECK-NEXT: return shared frt x 2
SharedFRT *sfrtptr2 = UseCxx::returnSharedFRT2();
@@ -191,9 +191,9 @@ int main() {
UseCxx::consumeValueWrapper(wrapper);
// CHECK-NEXT: retainShared
// CHECK-NEXT: retainShared
// CHECK-NEXT: releaseShared
// CHECK-NEXT: return shared frt x 4
// CHECK-NEXT: releaseShared
// CHECK-NEXT: releaseShared
}
{
SharedFRT sfrt;

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

@@ -4,11 +4,11 @@
// CHECK-LABEL: sil [ossa] @async_dead_arg_call : {{.*}} {
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] : @noImplicitCopy @_eagerMove @owned
// CHECK: destroy_value [[INSTANCE]]
// CHECK: [[EXECUTOR:%[^,]+]] = enum $Optional<any Actor>, #Optional.none!enumelt
// CHECK: [[CALLEE:%[^,]+]] = function_ref @async_callee
// CHECK: apply [[CALLEE]]()
// CHECK: hop_to_executor [[EXECUTOR]]
// CHECK: destroy_value [[INSTANCE]]
// CHECK-LABEL: } // end sil function 'async_dead_arg_call'
@_silgen_name("async_dead_arg_call")
public func async_dead_arg_call(o: consuming AnyObject) async {
@@ -46,11 +46,11 @@ extension C {
public class C {
// CHECK-LABEL: sil [ossa] @async_dead_arg_call_method : {{.*}} {
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] : @noImplicitCopy @_eagerMove @owned
// CHECK: destroy_value [[INSTANCE]]
// CHECK: [[EXECUTOR:%[^,]+]] = enum $Optional<any Actor>, #Optional.none!enumelt
// CHECK: [[CALLEE:%[^,]+]] = function_ref @async_callee : $@convention(thin) @async () -> ()
// CHECK: apply [[CALLEE]]() : $@convention(thin) @async () -> ()
// CHECK: hop_to_executor [[EXECUTOR]]
// CHECK: destroy_value [[INSTANCE]]
// CHECK-LABEL: } // end sil function 'async_dead_arg_call_method'
@_silgen_name("async_dead_arg_call_method")
consuming

View File

@@ -20,9 +20,13 @@ struct MOS : ~Copyable {
deinit {}
}
sil [ossa] @dummy : $@convention(thin) () -> ()
sil [ossa] @dummy : $@convention(thin) () -> () {
[global:]
}
sil [ossa] @barrier : $@convention(thin) () -> ()
sil [ossa] @getOwnedC : $@convention(thin) () -> (@owned C)
sil [ossa] @getOwnedC : $@convention(thin) () -> (@owned C) {
[global:]
}
sil [ossa] @getOwnedB : $@convention(thin) () -> (@owned B)
sil [ossa] @takeOwnedC : $@convention(thin) (@owned C) -> ()
sil [ossa] @takeOwnedCTwice : $@convention(thin) (@owned C, @owned C) -> ()
@@ -1076,7 +1080,7 @@ sil [ossa] @dontShortenDeadMoveOnlyLifetime : $@convention(thin) () -> () {
// CHECK-LABEL: sil [ossa] @look_through_end_init_let_ref :
// CHECK: [[E:%.*]] = end_init_let_ref
// CHECK: [[D:%.*]] = function_ref @dummy
// CHECK: [[D:%.*]] = function_ref @barrier
// CHECK: apply [[D]]
// CHECK: destroy_value [[E]]
// CHECK-LABEL: } // end sil function 'look_through_end_init_let_ref'
@@ -1087,7 +1091,7 @@ bb0:
%2 = end_init_let_ref %1
%4 = function_ref @takeGuaranteedC : $@convention(thin) (@guaranteed C) -> ()
apply %4(%2) : $@convention(thin) (@guaranteed C) -> ()
%6 = function_ref @dummy : $@convention(thin) () -> ()
%6 = function_ref @barrier : $@convention(thin) () -> ()
apply %6() : $@convention(thin) () -> ()
destroy_value %2
%3 = tuple ()

View File

@@ -41,7 +41,9 @@ class C {
sil [ossa] @dummy : $@convention(thin) () -> ()
sil [ossa] @getOwnedC : $@convention(thin) () -> (@owned C)
sil [ossa] @takeOwnedC : $@convention(thin) (@owned C) -> ()
sil [ossa] @takeOwnedC : $@convention(thin) (@owned C) -> () {
[global:]
}
sil [ossa] @getOwnedWrapper : $@convention(thin) () -> (@owned Wrapper)
sil [ossa] @getOwnedMultiWrapper : $@convention(thin) () -> (@owned MultiWrapper)
sil [ossa] @getOwnedHasObjectAndInt : $@convention(thin) () -> (@owned HasObjectAndInt)

View File

@@ -10,7 +10,9 @@ enum FakeOptional<T> {
}
sil [ossa] @getX : $@convention(thin) () -> (@owned X)
sil [ossa] @holdX : $@convention(thin) (@guaranteed X) -> ()
sil [ossa] @holdX : $@convention(thin) (@guaranteed X) -> () {
[global:]
}
// CHECK-LABEL: sil [ossa] @nohoist_destroy_over_reborrow_endborrow : {{.*}} {
// CHECK: {{bb[0-9]+}}([[REBORROW:%[^,]+]] : @reborrow $X, [[VALUE:%[^,]+]] : @owned $X):

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

@@ -89,6 +89,9 @@ struct UnsafeMutablePointer<T> {
}
sil @unknown : $@convention(thin) () -> ()
sil @nobarrier : $@convention(thin) () -> () {
[global:]
}
sil @use_S : $@convention(thin) (@in_guaranteed S) -> ()
// This function is not a synchronization point.
@@ -99,6 +102,9 @@ sil @empty : $@convention(thin) () -> () {
sil @f_out : $@convention(thin) <T> () -> @out T
sil @f_bool : $@convention(thin) () -> Builtin.Int1
sil @f_bool_no_barrier : $@convention(thin) () -> Builtin.Int1 {
[global:]
}
sil [ossa] @take_trivial_struct : $@convention(thin) (TrivialStruct) -> ()
sil [ossa] @get_change_out : $@convention(thin) () -> @out Change
sil [ossa] @coro : $@yield_once @convention(thin) (@inout X) -> @yields ()
@@ -283,7 +289,7 @@ bb0(%0 : $*T, %1 : $Builtin.Int1):
cond_br %1, bb1, bb2
bb1:
%8 = function_ref @unknown : $@convention(thin) () -> ()
%8 = function_ref @nobarrier : $@convention(thin) () -> ()
%9 = apply %8() : $@convention(thin) () -> ()
br bb3
@@ -356,7 +362,7 @@ bb0(%0 : $*T):
br bb1
bb1:
%f = function_ref @f_bool : $@convention(thin) () -> Builtin.Int1
%f = function_ref @f_bool_no_barrier : $@convention(thin) () -> Builtin.Int1
%c = apply %f() : $@convention(thin) () -> Builtin.Int1
cond_br %c, bb2, bb3
@@ -492,7 +498,8 @@ entry(%instance : @owned $S):
br applier
applier:
apply undef() : $@convention(thin) () -> ()
%f = function_ref @nobarrier : $@convention(thin) () -> ()
apply %f() : $@convention(thin) () -> ()
br good
good:
@@ -731,7 +738,7 @@ entry(%instance : @owned $S):
end_access %store_scope : $*S
%load_scope = begin_access [modify] [static] %addr : $*S
%value = load [copy] %load_scope : $*S
%unknown = function_ref @unknown : $@convention(thin) () -> ()
%unknown = function_ref @nobarrier : $@convention(thin) () -> ()
apply %unknown() : $@convention(thin) () -> ()
end_access %load_scope : $*S
destroy_addr %addr : $*S

View File

@@ -11,6 +11,9 @@ class X {
}
sil [ossa] @get_owned_X : $@convention(thin) () -> @owned X
sil [ossa] @get_owned_X_no_barrier : $@convention(thin) () -> @owned X {
[global:]
}
// Hoist the destroy_addr of an inout over a loop and over a deinit barrier to
// the top of the enclosing function.
@@ -83,7 +86,7 @@ bb0(%instance : @owned $X, %second: $*X):
%addr = alloc_stack $X
store %instance to [init] %addr : $*X
%scope = begin_access [modify] [static] %second : $*X
%1 = function_ref @get_owned_X : $@convention(thin) () -> @owned X
%1 = function_ref @get_owned_X_no_barrier : $@convention(thin) () -> @owned X
%14 = apply %1() : $@convention(thin) () -> @owned X
destroy_value %14 : $X
end_access %scope : $*X

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

View File

@@ -805,8 +805,9 @@ bb3:
return %9999 : $()
}
// TODO: check why the copy cannot be eliminated here
// CHECK-LABEL: sil [ossa] @join_live_range_with_borrowscopes_multipledestroys_succeed_8 : $@convention(thin) () -> () {
// CHECK-NOT: copy_value
// xCHECK-NOT: copy_value
// CHECK: } // end sil function 'join_live_range_with_borrowscopes_multipledestroys_succeed_8'
sil [ossa] @join_live_range_with_borrowscopes_multipledestroys_succeed_8 : $@convention(thin) () -> () {
bb0: