Optimizer: add simplifications for destroy_value

Attempt to optimize by forwarding the destroy to operands of forwarding instructions.

```
  %3 = struct $S (%1, %2)
  destroy_value %3         // the only use of %3
```
->
```
  destroy_value %1
  destroy_value %2
```

The benefit of this transformation is that the forwarding instruction can be removed.

Also, handle `destroy_value` for phi arguments.
This is a more complex case where the destroyed value comes from different predecessors via a phi argument.
The optimization moves the `destroy_value` to each predecessor block.

```
bb1:
  br bb3(%0)
bb2:
  br bb3(%1)
bb3(%3 : @owned T):
  ...                // no deinit-barriers
  destroy_value %3   // the only use of %3
```
->
```
bb1:
  destroy_value %0
  br bb3
bb2:
  destroy_value %1
  br bb3
bb3:
  ...
```
This commit is contained in:
Erik Eckstein
2025-12-03 13:01:02 +01:00
parent fbb420f38d
commit 8aa911ba2f
6 changed files with 627 additions and 60 deletions

View File

@@ -14,8 +14,147 @@ import SIL
extension DestroyValueInst : OnoneSimplifiable, SILCombineSimplifiable {
func simplify(_ context: SimplifyContext) {
// If the value has `.none` ownership, the destroy is a no-op. Note that a value can have `.none`
// ownership even if it's type is not trivial, e.g.
//
// ```
// %1 = enum $NonTrivialEnum, #NonTrivialEnum.trivialCase!enumelt // ownership: none
// %2 = destroy_value %1
// ```
//
if destroyedValue.ownership == .none {
context.erase(instruction: self)
return
}
tryRemoveForwardingOperandInstruction(context)
}
/// Attempt to optimize by forwarding the destroy to operands of forwarding instructions.
///
/// ```
/// %3 = struct $S (%1, %2)
/// destroy_value %3 // the only use of %3
/// ```
/// ->
/// ```
/// destroy_value %1
/// destroy_value %2
/// ```
///
/// The benefit of this transformation is that the forwarding instruction can be removed.
///
private func tryRemoveForwardingOperandInstruction(_ context: SimplifyContext) {
guard context.preserveDebugInfo ? destroyedValue.uses.isSingleUse
: destroyedValue.uses.ignoreDebugUses.isSingleUse
else {
return
}
let destroyedInst: Instruction
switch destroyedValue {
case is StructInst,
is EnumInst:
if destroyedValue.type.nominal!.valueTypeDestructor != nil {
// Moving the destroy to a non-copyable struct/enum's operands would drop the deinit call!
return
}
destroyedInst = destroyedValue as! SingleValueInstruction
// Handle various "forwarding" instructions that simply pass through values
// without performing operations that would affect destruction semantics.
//
// We are intentionally _not_ handling `unchecked_enum_data`, because that would not necessarily be
// a simplification, because destroying the whole enum is more effort than to destroy an enum payload.
// We are also not handling `destructure_struct` and `destructure_tuple`. That would end up in
// an infinite simplification loop in MandatoryPerformanceOptimizations because there we "split" such
// destroys again when de-virtualizing deinits of non-copyable types.
//
case is TupleInst,
is RefToBridgeObjectInst,
is ConvertFunctionInst,
is ThinToThickFunctionInst,
is UpcastInst,
is UncheckedRefCastInst,
is UnconditionalCheckedCastInst,
is BridgeObjectToRefInst,
is InitExistentialRefInst,
is OpenExistentialRefInst:
destroyedInst = destroyedValue as! SingleValueInstruction
case let arg as Argument:
tryRemovePhiArgument(arg, context)
return
default:
return
}
let builder = Builder(before: self, context)
for op in destroyedInst.definedOperands where op.value.ownership == .owned {
builder.createDestroyValue(operand: op.value)
}
// Users include `debug_value` instructions and this `destroy_value`
context.erase(instructionIncludingAllUsers: destroyedInst)
}
/// Handles the optimization of `destroy_value` instructions for phi arguments.
/// This is a more complex case where the destroyed value comes from different predecessors
/// via a phi argument. The optimization moves the `destroy_value` to each predecessor block.
///
/// ```
/// bb1:
/// br bb3(%0)
/// bb2:
/// br bb3(%1)
/// bb3(%3 : @owned T):
/// ... // no deinit-barriers
/// destroy_value %3 // the only use of %3
/// ```
/// ->
/// ```
/// bb1:
/// destroy_value %0
/// br bb3
/// bb2:
/// destroy_value %1
/// br bb3
/// bb3:
/// ...
/// ```
///
private func tryRemovePhiArgument(_ arg: Argument, _ context: SimplifyContext) {
guard let phi = Phi(arg),
arg.parentBlock == parentBlock,
!isDeinitBarrierInBlock(before: self, context)
else {
return
}
for incomingOp in phi.incomingOperands {
let oldBranch = incomingOp.instruction as! BranchInst
let builder = Builder(before: oldBranch, context)
builder.createDestroyValue(operand: incomingOp.value)
builder.createBranch(to: parentBlock, arguments: oldBranch.arguments(excluding: incomingOp))
context.erase(instruction: oldBranch)
}
// Users of `arg` include `debug_value` instructions and this `destroy_value`
context.erase(instructions: arg.uses.users)
arg.parentBlock.eraseArgument(at: arg.index, context)
}
}
private func isDeinitBarrierInBlock(before instruction: Instruction, _ context: SimplifyContext) -> Bool {
return ReverseInstructionList(first: instruction.previous).contains(where: {
$0.isDeinitBarrier(context.calleeAnalysis)
})
}
private extension BranchInst {
func arguments(excluding excludeOp: Operand) -> [Value] {
return Array(operands.filter{ $0 != excludeOp }.values)
}
}

View File

@@ -140,11 +140,8 @@ bb0(%0 : @owned $Klass):
return %2 : $Klass
}
// We get ARC traffic here today since we do not get rid of PhiArguments kept
// alive only by destroys/end_borrows. We will eventually though.
//
// CHECK-LABEL: sil [ossa] @owned_to_owned_consuming : $@convention(thin) (@owned FakeOptional<Klass>) -> () {
// CHECK: copy_value
// CHECK-NOT: copy_value
// CHECK-NOT: enum $FakeOptional<Klass>, #FakeOptional.some!enumelt
// CHECK: } // end sil function 'owned_to_owned_consuming'
sil [ossa] @owned_to_owned_consuming : $@convention(thin) (@owned FakeOptional<Klass>) -> () {

View File

@@ -36,9 +36,12 @@ public class MyGizmo {
// CHECK: [[FUN:%.*]] = function_ref @$sSo5GizmoC14stringPropertySSSgvsToTembnn_
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@owned String, Gizmo) -> ()
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@owned String, Gizmo) -> ()
// CHECK: [[FUN:%.*]] = function_ref @$sSo5GizmoC12modifyString_10withNumber0D6FoobarSSSgAF_SiypSgtFToTembnnnb_
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@owned String, Int, Optional<AnyObject>, Gizmo) -> @owned Optional<String>
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@owned String, Int, Optional<AnyObject>, Gizmo) -> @owned Optional<String>
// TODO: check why this code is not outlined
// xCHECK: [[FUN:%.*]] = function_ref @$sSo5GizmoC12modifyString_10withNumber0D6FoobarSSSgAF_SiypSgtFToTembnnnb_
// xCHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@owned String, Int, Optional<AnyObject>, Gizmo) -> @owned Optional<String>
// xCHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@owned String, Int, Optional<AnyObject>, Gizmo) -> @owned Optional<String>
// CHECK: [[FUN:%.*]] = function_ref @$sSo5GizmoC11doSomethingyypSgSaySSGSgFToTembgnn_
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@guaranteed Array<String>, Gizmo) -> @owned Optional<AnyObject>
// CHECK: [[FUN:%.*]] = function_ref @$sSo5GizmoC11doSomethingyypSgSaySSGSgFToTembnn_
@@ -126,32 +129,32 @@ public func testOutlining() {
// CHECK: return %7 : $()
// CHECK: } // end sil function '$sSo5GizmoC14stringPropertySSSgvsToTembnn_'
// CHECK-LABEL: sil shared [noinline] @$sSo5GizmoC12modifyString_10withNumber0D6FoobarSSSgAF_SiypSgtFToTembnnnb_ : $@convention(thin) (@owned String, Int, Optional<AnyObject>, Gizmo) -> @owned Optional<String> {
// CHECK: bb0(%0 : $String, %1 : $Int, %2 : $Optional<AnyObject>, %3 : $Gizmo):
// CHECK: %4 = objc_method %3 : $Gizmo, #Gizmo.modifyString!foreign : (Gizmo) -> (String?, Int, Any?) -> String?
// CHECK: %5 = function_ref @$sSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF : $@convention(method) (@guaranteed String) -> @owned NSString
// CHECK: %6 = apply %5(%0) : $@convention(method) (@guaranteed String) -> @owned NSString
// CHECK: release_value %0 : $String
// CHECK: %8 = enum $Optional<NSString>, #Optional.some!enumelt, %6 : $NSString
// CHECK: %9 = apply %4(%8, %1, %2, %3) : $@convention(objc_method) (Optional<NSString>, Int, Optional<AnyObject>, Gizmo) -> @autoreleased Optional<NSString>
// CHECK: strong_release %6 : $NSString
// CHECK: switch_enum %9 : $Optional<NSString>, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb1
// xCHECK-LABEL: sil shared [noinline] @$sSo5GizmoC12modifyString_10withNumber0D6FoobarSSSgAF_SiypSgtFToTembnnnb_ : $@convention(thin) (@owned String, Int, Optional<AnyObject>, Gizmo) -> @owned Optional<String> {
// xCHECK: bb0(%0 : $String, %1 : $Int, %2 : $Optional<AnyObject>, %3 : $Gizmo):
// xCHECK: %4 = objc_method %3 : $Gizmo, #Gizmo.modifyString!foreign : (Gizmo) -> (String?, Int, Any?) -> String?
// xCHECK: %5 = function_ref @$sSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF : $@convention(method) (@guaranteed String) -> @owned NSString
// xCHECK: %6 = apply %5(%0) : $@convention(method) (@guaranteed String) -> @owned NSString
// xCHECK: release_value %0 : $String
// xCHECK: %8 = enum $Optional<NSString>, #Optional.some!enumelt, %6 : $NSString
// xCHECK: %9 = apply %4(%8, %1, %2, %3) : $@convention(objc_method) (Optional<NSString>, Int, Optional<AnyObject>, Gizmo) -> @autoreleased Optional<NSString>
// xCHECK: strong_release %6 : $NSString
// xCHECK: switch_enum %9 : $Optional<NSString>, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb1
//
// CHECK: bb1:
// CHECK: %12 = enum $Optional<String>, #Optional.none!enumelt
// CHECK: br bb3(%12 : $Optional<String>)
// xCHECK: bb1:
// xCHECK: %14 = enum $Optional<String>, #Optional.none!enumelt
// xCHECK: br bb3(%14 : $Optional<String>)
//
// CHECK: bb2(%14 : $NSString):
// CHECK: %15 = function_ref @$sSS10FoundationE36_unconditionallyBridgeFromObjectiveCySSSo8NSStringCSgFZ : $@convention(method) (@guaranteed Optional<NSString>, @thin String.Type) -> @owned String
// CHECK: %16 = metatype $@thin String.Type
// CHECK: %17 = apply %15(%9, %16) : $@convention(method) (@guaranteed Optional<NSString>, @thin String.Type) -> @owned String
// CHECK: release_value %9 : $Optional<NSString>
// CHECK: %19 = enum $Optional<String>, #Optional.some!enumelt, %17 : $String
// CHECK: br bb3(%19 : $Optional<String>)
// xCHECK: bb2(%16 : $NSString):
// xCHECK: %18 = function_ref @$sSS10FoundationE36_unconditionallyBridgeFromObjectiveCySSSo8NSStringCSgFZ : $@convention(method) (@guaranteed Optional<NSString>, @thin String.Type) -> @owned String
// xCHECK: %19 = metatype $@thin String.Type
// xCHECK: %20 = apply %18(%9, %19) : $@convention(method) (@guaranteed Optional<NSString>, @thin String.Type) -> @owned String
// xCHECK: release_value %9 : $Optional<NSString>
// xCHECK: %22 = enum $Optional<String>, #Optional.some!enumelt, %20 : $String
// xCHECK: br bb3(%22 : $Optional<String>)
//
// CHECK: bb3(%21 : $Optional<String>):
// CHECK: return %21 : $Optional<String>
// CHECK: } // end sil function '$sSo5GizmoC12modifyString_10withNumber0D6FoobarSSSgAF_SiypSgtFToTembnnnb_'
// xCHECK: bb3(%24 : $Optional<String>):
// xCHECK: return %24 : $Optional<String>
// xCHECK: } // end sil function '$sSo5GizmoC12modifyString_10withNumber0D6FoobarSSSgAF_SiypSgtFToTembnnnb_'
// CHECK-LABEL: sil shared [noinline] @$sSo5GizmoC11doSomethingyypSgSaySSGSgFToTembgnn_ : $@convention(thin) (@guaranteed Array<String>, Gizmo) -> @owned Optional<AnyObject> {
// CHECK: bb0(%0 : $Array<String>, %1 : $Gizmo):

View File

@@ -77,8 +77,7 @@ bb0(%0 : @guaranteed $Klass):
// CHECK-LABEL: sil [ossa] @ref_to_raw_pointer_unchecked_ref_cast_composition_ossa_owned : $@convention(thin) (@owned Klass) -> Builtin.RawPointer
// CHECK: bb0([[ARG:%.*]] : @owned
// CHECK-NEXT: [[RESULT:%.*]] = ref_to_raw_pointer [[ARG]]
// CHECK-NEXT: [[CAST:%.*]] = unchecked_ref_cast [[ARG]]
// CHECK-NEXT: destroy_value [[CAST]]
// CHECK-NEXT: destroy_value [[ARG]]
// CHECK-NEXT: return [[RESULT]]
// CHECK: } // end sil function 'ref_to_raw_pointer_unchecked_ref_cast_composition_ossa_owned'
sil [ossa] @ref_to_raw_pointer_unchecked_ref_cast_composition_ossa_owned : $@convention(thin) (@owned Klass) -> Builtin.RawPointer {

View File

@@ -1,6 +1,7 @@
// RUN: %target-sil-opt -enable-sil-verify-all %s -onone-simplification -simplify-instruction=destroy_value | %FileCheck %s
// RUN: %target-sil-opt -enable-experimental-feature MoveOnlyEnumDeinits %s -onone-simplification -simplify-instruction=destroy_value | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ONONE
// RUN: %target-sil-opt -enable-experimental-feature MoveOnlyEnumDeinits %s -simplification -simplify-instruction=destroy_value | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-O
// REQUIRES: swift_in_compiler
// REQUIRES: swift_feature_MoveOnlyEnumDeinits
sil_stage canonical
@@ -8,20 +9,448 @@ import Builtin
import Swift
import SwiftShims
sil @cl : $@convention(thin) () -> Int
// CHECK-LABEL: sil [ossa] @remove_copy :
// CHECK-NOT: destroy_value
// CHECK: } // end sil function 'remove_copy'
sil [ossa] @remove_copy : $@convention(thin) () -> () {
bb0:
%0 = function_ref @cl : $@convention(thin) () -> Int
%1 = thin_to_thick_function %0 : $@convention(thin) () -> Int to $@callee_guaranteed () -> Int
destroy_value %1 : $@callee_guaranteed () -> Int
%4 = tuple ()
return %4 : $()
struct S {
var a: C
var b: C
var c: Int
}
struct S2 {
var a: C
}
struct NCS: ~Copyable {
var a: C
deinit
}
enum E {
case A(Int)
case B(C)
}
enum NCE: ~Copyable {
case A(Int)
case B(C)
deinit
}
protocol P: C {}
class C: P {}
class D: C {}
sil @cl : $@convention(thin) () -> Int
// CHECK-LABEL: sil [ossa] @remove_destroy :
// CHECK-NOT: destroy_value
// CHECK: } // end sil function 'remove_destroy'
sil [ossa] @remove_destroy : $@convention(thin) () -> () {
bb0:
%0 = function_ref @cl : $@convention(thin) () -> Int
%1 = thin_to_thick_function %0 to $@callee_guaranteed () -> Int
destroy_value %1
%4 = tuple ()
return %4
}
// CHECK-LABEL: sil [ossa] @remove_struct :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: destroy_value %1
// CHECK-NEXT: return %2
// CHECK: } // end sil function 'remove_struct'
sil [ossa] @remove_struct : $@convention(thin) (@owned C, @owned C, Int) -> Int {
bb0(%0 : @owned $C, %1 : @owned $C, %2 : $Int):
%3 = struct $S (%0, %1, %2)
destroy_value %3
return %2
}
// CHECK-LABEL: sil [ossa] @remove_tuple :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: destroy_value %1
// CHECK-NEXT: return %2
// CHECK: } // end sil function 'remove_tuple'
sil [ossa] @remove_tuple : $@convention(thin) (@owned C, @owned C, Int) -> Int {
bb0(%0 : @owned $C, %1 : @owned $C, %2 : $Int):
%3 = tuple (%0, %1, %2)
destroy_value %3
return %2
}
// CHECK-LABEL: sil [ossa] @remove_enum_a :
// CHECK: bb0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_enum_a'
sil [ossa] @remove_enum_a : $@convention(thin) (Int) -> () {
bb0(%0 : $Int):
%1 = enum $E, #E.A!enumelt, %0, forwarding: @owned
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @remove_enum_b :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_enum_b'
sil [ossa] @remove_enum_b : $@convention(thin) (@owned C) -> () {
bb0(%0 : @owned $C):
%1 = enum $E, #E.B!enumelt, %0
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @remove_ref_to_bridged :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_ref_to_bridged'
sil [ossa] @remove_ref_to_bridged : $@convention(thin) (@owned C, Builtin.Word) -> () {
bb0(%0 : @owned $C, %1 : $Builtin.Word):
%2 = ref_to_bridge_object %0, %1
destroy_value %2
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @remove_bridged_to_ref :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_bridged_to_ref'
sil [ossa] @remove_bridged_to_ref : $@convention(thin) (@owned Builtin.BridgeObject) -> () {
bb0(%0 : @owned $Builtin.BridgeObject):
%1 = bridge_object_to_ref %0 to $C
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @remove_convert_function :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_convert_function'
sil [ossa] @remove_convert_function : $@convention(thin) (@owned @convention(thick) (C) -> ()) -> () {
bb0(%0 : @owned $@convention(thick) (C) -> ()):
%1 = convert_function %0 to $@convention(thick) (D) -> ()
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @remove_thin_to_thick_function :
// CHECK: bb0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_thin_to_thick_function'
sil [ossa] @remove_thin_to_thick_function : $@convention(thin) () -> () {
bb0:
%0 = function_ref @cl : $@convention(thin) () -> Int
%1 = thin_to_thick_function %0 to $@callee_guaranteed () -> Int, forwarding: @owned
destroy_value %1
%4 = tuple ()
return %4
}
// CHECK-LABEL: sil [ossa] @remove_upcast :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_upcast'
sil [ossa] @remove_upcast : $@convention(thin) (@owned D) -> () {
bb0(%0 : @owned $D):
%1 = upcast %0 to $C
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @remove_unchecked_ref_cast :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_unchecked_ref_cast'
sil [ossa] @remove_unchecked_ref_cast : $@convention(thin) (@owned C) -> () {
bb0(%0 : @owned $C):
%1 = unchecked_ref_cast %0 to $D
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @remove_unconditional_checked_cast :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_unconditional_checked_cast'
sil [ossa] @remove_unconditional_checked_cast : $@convention(thin) (@owned any P) -> () {
bb0(%0 : @owned $any P):
%1 = unconditional_checked_cast %0 to C
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @remove_init_existential_ref :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_init_existential_ref'
sil [ossa] @remove_init_existential_ref : $@convention(thin) (@owned C) -> () {
bb0(%0 : @owned $C):
%1 = init_existential_ref %0 : $C : $C, $P
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @remove_open_existential_ref :
// CHECK: bb0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'remove_open_existential_ref'
sil [ossa] @remove_open_existential_ref : $@convention(thin) (@owned any P) -> () {
bb0(%0 : @owned $any P):
%1 = open_existential_ref %0 to $@opened("01234567-89ab-cdef-0123-111111111111", P) Self
destroy_value %1
%r = tuple ()
return %r
}
// Doing this wouldn't be a simplification
// CHECK-LABEL: sil [ossa] @dont_remove_unchecked_enum_data :
// CHECK: bb0
// CHECK-NEXT: %1 = unchecked_enum_data
// CHECK: destroy_value %1
// CHECK: } // end sil function 'dont_remove_unchecked_enum_data'
sil [ossa] @dont_remove_unchecked_enum_data : $@convention(thin) (@owned E) -> () {
bb0(%0 : @owned $E):
%1 = unchecked_enum_data %0, #E.B!enumelt
destroy_value %1
%r = tuple ()
return %r
}
// Doing this might undo an opposite simplification
// CHECK-LABEL: sil [ossa] @dont_remove_destructure_struct :
// CHECK: bb0
// CHECK-NEXT: %1 = destructure_struct
// CHECK: destroy_value %1
// CHECK: } // end sil function 'dont_remove_destructure_struct'
sil [ossa] @dont_remove_destructure_struct : $@convention(thin) (@owned S2) -> () {
bb0(%0 : @owned $S2):
%1 = destructure_struct %0
destroy_value %1
%r = tuple ()
return %r
}
// Doing this might undo an opposite simplification
// CHECK-LABEL: sil [ossa] @dont_remove_destructure_tuple :
// CHECK: bb0
// CHECK: %2 = destructure_tuple
// CHECK: destroy_value %2
// CHECK: } // end sil function 'dont_remove_destructure_tuple'
sil [ossa] @dont_remove_destructure_tuple : $@convention(thin) (@owned C) -> () {
bb0(%0 : @owned $C):
%1 = tuple (%0)
%2 = destructure_tuple %1
destroy_value %2
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @dont_remove_struct_with_deinit :
// CHECK: bb0
// CHECK-NEXT: %1 = struct $NCS
// CHECK-NEXT: destroy_value %1
// CHECK: } // end sil function 'dont_remove_struct_with_deinit'
sil [ossa] @dont_remove_struct_with_deinit : $@convention(thin) (@owned C) -> () {
bb0(%0 : @owned $C):
%1 = struct $NCS (%0)
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @dont_remove_enum_with_deinit :
// CHECK: bb0
// CHECK-NEXT: %1 = enum $NCE
// CHECK-NEXT: destroy_value %1
// CHECK: } // end sil function 'dont_remove_enum_with_deinit'
sil [ossa] @dont_remove_enum_with_deinit : $@convention(thin) (@owned C) -> () {
bb0(%0 : @owned $C):
%1 = enum $NCE, #NCE.B!enumelt, %0
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @dont_remove_with_additional_user :
// CHECK: bb0
// CHECK-NEXT: %1 = upcast
// CHECK: destroy_value %1
// CHECK: } // end sil function 'dont_remove_with_additional_user'
sil [ossa] @dont_remove_with_additional_user : $@convention(thin) (@owned D) -> () {
bb0(%0 : @owned $D):
%1 = upcast %0 to $C
%2 = begin_borrow %1
fix_lifetime %2
end_borrow %2
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @dont_remove_with_debug_user :
// CHECK: bb0
// CHECK-O-NEXT: destroy_value %0
// CHECK-O-NEXT: tuple
// CHECK-ONONE-NEXT: %1 = upcast
// CHECK-ONONE-NEXT: debug_value %1
// CHECK-ONONE-NEXT: destroy_value %1
// CHECK: } // end sil function 'dont_remove_with_debug_user'
sil [ossa] @dont_remove_with_debug_user : $@convention(thin) (@owned D) -> () {
bb0(%0 : @owned $D):
%1 = upcast %0 to $C
debug_value %1, let, name "x"
destroy_value %1
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @hoist_over_phi :
// CHECK: bb1:
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: br bb3
// CHECK: bb2:
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: br bb3
// CHECK: bb3:
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'hoist_over_phi'
sil [ossa] @hoist_over_phi : $@convention(thin) (@owned C) -> () {
bb0(%0 : @owned $C):
cond_br undef, bb1, bb2
bb1:
br bb3(%0)
bb2:
br bb3(%0)
bb3(%4 : @owned $C):
destroy_value %4
%10 = tuple ()
return %10
}
// CHECK-LABEL: sil [ossa] @hoist_over_phi_with_additional_args :
// CHECK: bb1:
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: br bb3(%1, %2)
// CHECK: bb2:
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: br bb3(%1, %2)
// CHECK: bb3({{%[0-9]+}} : $Int, {{%[0-9]+}} : $Int):
// CHECK-NEXT: debug_step
// CHECK-NEXT: tuple
// CHECK: } // end sil function 'hoist_over_phi_with_additional_args'
sil [ossa] @hoist_over_phi_with_additional_args : $@convention(thin) (@owned C, Int, Int) -> () {
bb0(%0 : @owned $C, %1 : $Int, %2 : $Int):
cond_br undef, bb1, bb2
bb1:
br bb3(%1, %0, %2)
bb2:
br bb3(%1, %0, %2)
bb3(%6 : $Int, %7 : @owned $C, %8 : $Int):
debug_step // not a deinit barrier
destroy_value %7
%10 = tuple ()
return %10
}
// CHECK-LABEL: sil [ossa] @dont_hoist_over_non_phi_arg :
// CHECK: bb2([[A:%.*]] : @owned $C):
// CHECK-NEXT: destroy_value [[A]]
// CHECK-NEXT: br bb3
// CHECK: } // end sil function 'dont_hoist_over_non_phi_arg'
sil [ossa] @dont_hoist_over_non_phi_arg : $@convention(thin) (@owned E) -> () {
bb0(%0 : @owned $E):
switch_enum %0, case #E.A!enumelt: bb1, case #E.B!enumelt: bb2
bb1(%2 : $Int):
br bb3
bb2(%4 : @owned $C):
destroy_value %4
br bb3
bb3:
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @dont_hoist_over_deinit_barrier :
// CHECK-NOT: destroy_value
// CHECK: bb3([[A:%.*]] : @owned $C):
// CHECK-NEXT: apply
// CHECK-NEXT: destroy_value [[A]]
// CHECK: } // end sil function 'dont_hoist_over_deinit_barrier'
sil [ossa] @dont_hoist_over_deinit_barrier : $@convention(thin) (@owned C) -> () {
bb0(%0 : @owned $C):
cond_br undef, bb1, bb2
bb1:
br bb3(%0)
bb2:
br bb3(%0)
bb3(%4 : @owned $C):
apply undef() : $() -> ()
destroy_value %4
%10 = tuple ()
return %10
}
// CHECK-LABEL: sil [ossa] @dont_hoist_destroy_in_different_block :
// CHECK-NOT: destroy_value
// CHECK: bb4:
// CHECK-NEXT: destroy_value
// CHECK: bb5:
// CHECK-NEXT: destroy_value
// CHECK: } // end sil function 'dont_hoist_destroy_in_different_block'
sil [ossa] @dont_hoist_destroy_in_different_block : $@convention(thin) (@owned C) -> () {
bb0(%0 : @owned $C):
cond_br undef, bb1, bb2
bb1:
br bb3(%0)
bb2:
br bb3(%0)
bb3(%4 : @owned $C):
cond_br undef, bb4, bb5
bb4:
destroy_value %4
br bb6
bb5:
destroy_value %4
br bb6
bb6:
%r = tuple ()
return %r
}

View File

@@ -19,47 +19,47 @@
/// Public import or non-resilient modules
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t \
// RUN: -swift-version 5 -enable-library-evolution \
// RUN: -swift-version 5 -enable-library-evolution -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/inlinable-public.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-OPTIMIZED --input-file %t/main.sil %s
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/inlinable-public.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-OPTIMIZED --input-file %t/main.sil %s
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/inlinable-internal.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-OPTIMIZED --input-file %t/main.sil %s
/// Foundation is imported from a different file
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -module-name main \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -module-name main -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/inlinable-not-imported-fileA.swift \
// RUN: %t/inlinable-not-imported-fileB.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-OPTIMIZED --input-file %t/main.sil %s
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -module-name main \
// RUN: -swift-version 5 -enable-library-evolution \
// RUN: -swift-version 5 -enable-library-evolution -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/inlinable-not-imported-fileA.swift \
// RUN: %t/inlinable-not-imported-fileB.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-OPTIMIZED --input-file %t/main.sil %s
/// Foundation is imported via a transitive dependency
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -module-name main \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -module-name main -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/inlinable-imported-transitive.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-OPTIMIZED --input-file %t/main.sil %s
/// Any non-inlinable uses
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/non-inlinable-public.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-OPTIMIZED --input-file %t/main.sil %s
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/non-inlinable-ioi.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-OPTIMIZED --input-file %t/main.sil %s
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/non-inlinable-internal.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-OPTIMIZED --input-file %t/main.sil %s
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -module-name main \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -module-name main -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/non-inlinable-not-imported-fileA.swift \
// RUN: %t/non-inlinable-not-imported-fileB.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-OPTIMIZED --input-file %t/main.sil %s
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -module-name main \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -module-name main -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: -swift-version 5 -enable-library-evolution \
// RUN: %t/non-inlinable-not-imported-fileA.swift \
// RUN: %t/non-inlinable-not-imported-fileB.swift > %t/main.sil
@@ -69,16 +69,16 @@
// CHECK-NOT-OPTIMIZED-NOT: $NSError
/// Implementation-only import
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: %t/inlinable-ioi.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-NOT-OPTIMIZED --input-file %t/main.sil %s
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: -swift-version 5 -enable-library-evolution \
// RUN: %t/inlinable-ioi.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-NOT-OPTIMIZED --input-file %t/main.sil %s
/// Internal import from resilient module
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t \
// RUN: %target-swift-frontend -emit-module -emit-sil -I%t -Xllvm -sil-disable-pass=simplify-destroy_value \
// RUN: -swift-version 5 -enable-library-evolution \
// RUN: %t/inlinable-internal.swift > %t/main.sil
// RUN: %FileCheck --check-prefix CHECK-NOT-OPTIMIZED --input-file %t/main.sil %s