mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
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:
@@ -29,6 +29,7 @@ swift_compiler_sources(Optimizer
|
||||
LoopInvariantCodeMotion.swift
|
||||
ObjectOutliner.swift
|
||||
ObjCBridgingOptimization.swift
|
||||
MandatoryDestroyHoisting.swift
|
||||
MergeCondFails.swift
|
||||
NamedReturnValueOptimization.swift
|
||||
RedundantLoadElimination.swift
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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) })
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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 : $()
|
||||
}
|
||||
|
||||
@@ -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 : $()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ()
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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]]
|
||||
|
||||
447
test/SILOptimizer/mandatory-destroy-hoisting.sil
Normal file
447
test/SILOptimizer/mandatory-destroy-hoisting.sil
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user