Files
swift-mirror/test/SILOptimizer/silgen_cleanup.sil
Erik Eckstein 98805c9141 Optimizer: let the InstructionDeleter respect deinit-barriers by default
The InstructionDeleter can remove instructions including their destroys and then insert compensating destroys at a new place.
This is effectively destroy-hoisting which doesn't respect deinit-barriers. Therefore it's not done for lexical lifetimes.
However, since https://github.com/swiftlang/swift/pull/85334, the optimizer should treat _all_ lifetimes as fixed and not only lexical lifetimes.

This change adds a `assumeFixedLifetimes` flag to InstructionDeleter which is on by default.
Only mandatory passes (like OSLogOptimization) should turn this off.
2025-12-03 15:53:56 +01:00

407 lines
18 KiB
Plaintext

// RUN: %target-sil-opt -sil-print-types -opt-mode=none -silgen-cleanup -sil-verify-all %s | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECKDEB
// RUN: %target-sil-opt -sil-print-types -opt-mode=speed -silgen-cleanup -sil-verify-all %s | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECKOPT
import Builtin
sil_stage raw
class Klass {
var property: Builtin.Int64
}
class SubKlass : Klass {}
sil @use_klass_guaranteed : $@convention(thin) (@guaranteed Klass) -> ()
sil @use_klass_owned : $@convention(thin) (@owned Klass) -> ()
sil @use_klass_unowned : $@convention(thin) (Klass) -> ()
enum FakeOptional<T> {
case none
case some(T)
}
sil @use_fakeoptional_klass_guaranteed : $@convention(thin) (@guaranteed FakeOptional<Klass>) -> ()
struct Int {
var _value : Builtin.Int32
}
struct UInt8 {
var _value : Builtin.Int8
}
// =============================================================================
// Test splitAggregateLoad
//
// Required for exclusivity diagnostics that are consistent across -Onone / -O
// =============================================================================
// CHECK-LABEL: sil [ossa] @struct_extract_load_to_load_struct_element_addr
// CHECKOPT: bb0([[IN:%[0-9]+]] : $*UInt8):
// CHECKOPT-NEXT: [[IN_GEP:%[0-9]+]] = struct_element_addr [[IN]] : $*UInt8, #UInt8._value
// CHECKOPT-NEXT: [[IN_LOADED:%[0-9]+]] = load [trivial] [[IN_GEP]] : $*Builtin.Int8
// CHECKOPT-NEXT: [[LITERAL:%[0-9]+]] = integer_literal $Builtin.Int8, 1
// CHECKOPT-NEXT: [[UINT8:%.*]] = struct $UInt8 ([[LITERAL]] : $Builtin.Int8)
// CHECKOPT-NEXT: store [[UINT8]] to [trivial] [[IN]] : $*UInt8
// CHECKOPT-NEXT: return [[IN_LOADED]] : $Builtin.Int8
sil [ossa] @struct_extract_load_to_load_struct_element_addr : $@convention(thin) (@inout UInt8) -> (Builtin.Int8) {
bb0(%0 : $*UInt8):
%1 = load [trivial] %0 : $*UInt8
%2 = integer_literal $Builtin.Int8, 1
%3 = struct_element_addr %0 : $*UInt8, #UInt8._value
store %2 to [trivial] %3 : $*Builtin.Int8
%5 = struct_extract %1 : $UInt8, #UInt8._value
return %5 : $Builtin.Int8
}
// CHECK-LABEL: sil [ossa] @tuple_extract_load_to_load_tuple_element_addr
// CHECKOPT: bb0([[IN:%[0-9]+]] : $*(Builtin.Int8, Builtin.Int8)):
// CHECKOPT-NEXT: [[IN_GEP:%[0-9]+]] = tuple_element_addr [[IN]] : $*(Builtin.Int8, Builtin.Int8), 0
// CHECKOPT-NEXT: [[IN_LOADED:%[0-9]+]] = load [trivial] [[IN_GEP]] : $*Builtin.Int8
// CHECKOPT-NEXT: [[LITERAL:%[0-9]+]] = integer_literal $Builtin.Int8, 1
// CHECKOPT-NEXT: [[IN_STORE_GEP:%[0-9]+]] = tuple_element_addr %0 : $*(Builtin.Int8, Builtin.Int8), 0
// CHECKOPT-NEXT: store [[LITERAL]] to [trivial] [[IN_STORE_GEP]] : $*Builtin.Int8
// CHECKOPT-NEXT: return [[IN_LOADED]] : $Builtin.Int8
sil [ossa] @tuple_extract_load_to_load_tuple_element_addr : $@convention(thin) (@inout (Builtin.Int8, Builtin.Int8)) -> (Builtin.Int8) {
bb0(%0 : $*(Builtin.Int8, Builtin.Int8)):
%1 = load [trivial] %0 : $*(Builtin.Int8, Builtin.Int8)
%2 = integer_literal $Builtin.Int8, 1
%3 = tuple_element_addr %0 : $*(Builtin.Int8, Builtin.Int8), 0
store %2 to [trivial] %3 : $*Builtin.Int8
%5 = tuple_extract %1 : $(Builtin.Int8, Builtin.Int8), 0
return %5 : $Builtin.Int8
}
// Do not perform the optimization of the input load has multiple uses.
//
// CHECK-LABEL: sil [ossa] @multiple_use_struct_extract_load_to_load_struct_element_addr
// CHECK: bb0([[IN:%[0-9]+]] : $*UInt8):
// CHECK-NEXT: load
// CHECK-NEXT: integer_literal
// CHECK-NEXT: struct
// CHECK-NEXT: store
// CHECK-NEXT: struct_extract
// CHECK-NEXT: tuple
// CHECK-NEXT: return
sil [ossa] @multiple_use_struct_extract_load_to_load_struct_element_addr : $@convention(thin) (@inout UInt8) -> (UInt8, Builtin.Int8) {
bb0(%0 : $*UInt8):
%1 = load [trivial] %0 : $*UInt8
%2 = integer_literal $Builtin.Int8, 1
%3 = struct_element_addr %0 : $*UInt8, #UInt8._value
store %2 to [trivial] %3 : $*Builtin.Int8
%5 = struct_extract %1 : $UInt8, #UInt8._value
%6 = tuple (%1 : $UInt8, %5 : $Builtin.Int8)
return %6 : $(UInt8, Builtin.Int8)
}
// Do not perform the optimization of the input load has multiple uses.
//
// CHECK-LABEL: sil [ossa] @multiple_use_tuple_extract_load_to_load_tuple_element_addr
// CHECK: bb0
// CHECK-NEXT: load
// CHECK-NEXT: integer_literal
// CHECK-NEXT: tuple_element_addr
// CHECK-NEXT: store
// CHECK-NEXT: tuple_extract
// CHECK-NEXT: tuple
// CHECK-NEXT: return
// CHECK-LABEL: } // end sil function 'multiple_use_tuple_extract_load_to_load_tuple_element_addr'
sil [ossa] @multiple_use_tuple_extract_load_to_load_tuple_element_addr : $@convention(thin) (@inout (Builtin.Int8, Builtin.Int8)) -> ((Builtin.Int8, Builtin.Int8), Builtin.Int8) {
bb0(%0 : $*(Builtin.Int8, Builtin.Int8)):
%1 = load [trivial] %0 : $*(Builtin.Int8, Builtin.Int8)
%2 = integer_literal $Builtin.Int8, 1
%3 = tuple_element_addr %0 : $*(Builtin.Int8, Builtin.Int8), 0
store %2 to [trivial] %3 : $*Builtin.Int8
%5 = tuple_extract %1 : $(Builtin.Int8, Builtin.Int8), 0
%6 = tuple (%1 : $(Builtin.Int8, Builtin.Int8), %5 : $Builtin.Int8)
return %6 : $((Builtin.Int8, Builtin.Int8), Builtin.Int8)
}
// Handle a combination of trivial and nontrivial elements.
struct X1 {
@_hasStorage @_hasInitialValue let a: Int { get }
@_hasStorage @_hasInitialValue var obj1: Builtin.NativeObject { get set }
@_hasStorage @_hasInitialValue var obj2: Builtin.NativeObject { get set }
init(a: Int, obj1: Builtin.NativeObject, obj2: Builtin.NativeObject)
}
// CHECK-LABEL: sil private [ossa] @testLoadNontrivial : $@convention(thin) (@inout_aliasable X1) -> (Int, @owned Builtin.NativeObject, @owned Builtin.NativeObject) {
// CHECKOPT-LABEL: bb0(%0 : $*X1):
// CHECKOPT: [[ACCESS:%.*]] = begin_access [read] [unknown] %0 : $*X1
// CHECKOPT: [[AA:%.*]] = struct_element_addr [[ACCESS]] : $*X1, #X1.a
// CHECKOPT: load [trivial] [[AA]] : $*Int
// CHECKOPT: [[OA1:%.*]] = struct_element_addr [[ACCESS]] : $*X1, #X1.obj1
// CHECKOPT: [[OV1:%.*]] = load [copy] [[OA1]] : $*Builtin.NativeObject
// CHECKOPT: [[OA2:%.*]] = struct_element_addr [[ACCESS]] : $*X1, #X1.obj2
// CHECKOPT: [[OV2:%.*]] = load [copy] [[OA2]] : $*Builtin.NativeObject
// CHECKOPT: end_access [[ACCESS]] : $*X1
// CHECKOPT: copy_value [[OV1]] : $Builtin.NativeObject
// CHECKOPT: copy_value [[OV2]] : $Builtin.NativeObject
// CHECKOPT: return
// CHECK-LABEL: } // end sil function 'testLoadNontrivial'
sil private [ossa] @testLoadNontrivial : $@convention(thin) (@inout_aliasable X1) -> (Int, @owned Builtin.NativeObject, @owned Builtin.NativeObject) {
bb0(%0 : $*X1):
%access = begin_access [read] [unknown] %0 : $*X1
%load = load [copy] %access : $*X1
end_access %access : $*X1
%borrowa = begin_borrow %load : $X1
%a = struct_extract %borrowa : $X1, #X1.a
end_borrow %borrowa : $X1
%borrow1 = begin_borrow %load : $X1
%o1 = struct_extract %borrow1 : $X1, #X1.obj1
%copy1 = copy_value %o1 : $Builtin.NativeObject
end_borrow %borrow1 : $X1
%borrow2 = begin_borrow %load : $X1
%o2 = struct_extract %borrow2 : $X1, #X1.obj2
%copy2 = copy_value %o2 : $Builtin.NativeObject
end_borrow %borrow2 : $X1
destroy_value %load : $X1
%result = tuple (%a : $Int, %copy1 : $Builtin.NativeObject, %copy2 : $Builtin.NativeObject)
return %result : $(Int, Builtin.NativeObject, Builtin.NativeObject)
}
// CHECK-LABEL: sil private [ossa] @testLoadBorrowNontrivial : $@convention(thin) (@in_guaranteed X1) -> (Int, @owned Builtin.NativeObject, @owned Builtin.NativeObject) {
// CHECKOPT: bb0([[ADDRESS:%.*]] : $*X1):
// CHECKOPT: [[AA:%.*]] = struct_element_addr [[ADDRESS]] : $*X1, #X1.a
// CHECKOPT: load [trivial] [[AA]] : $*Int
// CHECKOPT: [[OA1:%.*]] = struct_element_addr [[ADDRESS]] : $*X1, #X1.obj1
// CHECKOPT: [[OV1:%.*]] = load_borrow [[OA1]] : $*Builtin.NativeObject
// CHECKOPT: [[OA2:%.*]] = struct_element_addr [[ADDRESS]] : $*X1, #X1.obj2
// CHECKOPT: [[OV2:%.*]] = load_borrow [[OA2]] : $*Builtin.NativeObject
// CHECKOPT: copy_value [[OV1]] : $Builtin.NativeObject
// CHECKOPT: copy_value [[OV2]] : $Builtin.NativeObject
// CHECKOPT: end_borrow [[OV1]]
// CHECKOPT: end_borrow [[OV2]]
// CHECKOPT: return
// CHECK-LABEL: } // end sil function 'testLoadBorrowNontrivial'
sil private [ossa] @testLoadBorrowNontrivial : $@convention(thin) (@in_guaranteed X1) -> (Int, @owned Builtin.NativeObject, @owned Builtin.NativeObject) {
bb0(%0 : $*X1):
%load = load_borrow %0 : $*X1
%a = struct_extract %load : $X1, #X1.a
%o1 = struct_extract %load : $X1, #X1.obj1
%copy1 = copy_value %o1 : $Builtin.NativeObject
%o2 = struct_extract %load : $X1, #X1.obj2
%copy2 = copy_value %o2 : $Builtin.NativeObject
end_borrow %load : $X1
%result = tuple (%a : $Int, %copy1 : $Builtin.NativeObject, %copy2 : $Builtin.NativeObject)
return %result : $(Int, Builtin.NativeObject, Builtin.NativeObject)
}
// FIXME rdar85638376: At -Onone, either preserve the original load
// with its debug_value. But somehow prevent exclusivity diagnostics
// from seeing the original load. At -O, generate the following fragments after the loads:
// HECKOPT: debug_value [[V1]] : $Int, let, name "x1", type $X1, expr op_fragment:#X1.a
// HECKOPT: debug_value [[V2]] : $Builtin.NativeObject, let, name "x1", type $X1, expr op_fragment:#X1.obj1
// HECKOPT: debug_value [[V3]] : $Builtin.NativeObject, let, name "x1", type $X1, expr op_fragment:#X1.obj2
//
// CHECK-LABEL: sil private [ossa] @testLoadWithDebugInfo : $@convention(thin) (@inout_aliasable X1) -> (Int, @owned Builtin.NativeObject, @owned Builtin.NativeObject) {
// CHECK: bb0(%0 : $*X1):
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] %0 : $*X1
// CHECK: [[A1:%.*]] = struct_element_addr [[ACCESS]] : $*X1, #X1.a
// CHECK: [[V1:%.*]] = load [trivial] [[A1]] : $*Int
// CHECK: [[A2:%.*]] = struct_element_addr [[ACCESS]] : $*X1, #X1.obj1
// CHECK: [[V2:%.*]] = load [copy] [[A2]] : $*Builtin.NativeObject
// CHECK: [[A3:%.*]] = struct_element_addr [[ACCESS]] : $*X1, #X1.obj2
// CHECK: [[V3:%.*]] = load [copy] [[A3]] : $*Builtin.NativeObject
// CHECK: end_access [[ACCESS]] : $*X1
// CHECK-LABEL: } // end sil function 'testLoadWithDebugInfo'
sil private [ossa] @testLoadWithDebugInfo : $@convention(thin) (@inout_aliasable X1) -> (Int, @owned Builtin.NativeObject, @owned Builtin.NativeObject) {
bb0(%0 : $*X1):
%access = begin_access [read] [unknown] %0 : $*X1
%load = load [copy] %access : $*X1
debug_value %load : $X1, let, name "x1"
end_access %access : $*X1
%borrowa = begin_borrow %load : $X1
%a = struct_extract %borrowa : $X1, #X1.a
end_borrow %borrowa : $X1
%borrow1 = begin_borrow %load : $X1
%o1 = struct_extract %borrow1 : $X1, #X1.obj1
%copy1 = copy_value %o1 : $Builtin.NativeObject
end_borrow %borrow1 : $X1
%borrow2 = begin_borrow %load : $X1
%o2 = struct_extract %borrow2 : $X1, #X1.obj2
%copy2 = copy_value %o2 : $Builtin.NativeObject
end_borrow %borrow2 : $X1
destroy_value %load : $X1
%result = tuple (%a : $Int, %copy1 : $Builtin.NativeObject, %copy2 : $Builtin.NativeObject)
return %result : $(Int, Builtin.NativeObject, Builtin.NativeObject)
}
// =============================================================================
// Test broadenSingleElementStores
// =============================================================================
struct X2 {
@_hasStorage @_hasInitialValue var obj: Builtin.NativeObject { get set }
}
struct X3 {
@_hasStorage @_hasInitialValue var x2: X2 { get set }
}
// CHECK-LABEL: sil private [ossa] @testStoreNontrivial : $@convention(thin) (@inout X3, @guaranteed Builtin.NativeObject) -> () {
// CHECK: bb0(%0 : $*X3, %1 : @guaranteed $Builtin.NativeObject):
// CHECK: [[CP:%.*]] = copy_value %1 : $Builtin.NativeObject
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] %0 : $*X3
// CHECK: [[X2:%.*]] = struct $X2 ([[CP]] : $Builtin.NativeObject)
// CHECK: [[X3:%.*]] = struct $X3 ([[X2]] : $X2)
// CHECK: store [[X3]] to [assign] [[ACCESS]] : $*X3
// CHECK: end_access [[ACCESS]] : $*X3
// CHECK: } // end sil function 'testStoreNontrivial'
sil private [ossa] @testStoreNontrivial : $@convention(thin) (@inout X3, @guaranteed Builtin.NativeObject) -> () {
bb0(%0 : $*X3, %1 : @guaranteed $Builtin.NativeObject):
%4 = copy_value %1 : $Builtin.NativeObject
%5 = begin_access [modify] [unknown] %0 : $*X3
%6 = struct_element_addr %5 : $*X3, #X3.x2
%7 = struct_element_addr %6 : $*X2, #X2.obj
store %4 to [assign] %7 : $*Builtin.NativeObject
end_access %5 : $*X3
%12 = tuple ()
return %12 : $()
}
// =============================================================================
// Test simple copy and borrow elimination
// =============================================================================
// We used to hit a memory error on this test.
//
// CHECK-LABEL: sil [ossa] @testDestructureTupleNoCrash : $@convention(thin) (@owned (Builtin.NativeObject, Builtin.NativeObject)) -> () {
// CHECKOPT: bb0(
// CHECKOPT-NEXT: (%1, %2) = destructure_tuple
// CHECKOPT: destroy_value %2
// CHECKOPT-NEXT: destroy_value %1
// CHECK: } // end sil function 'testDestructureTupleNoCrash'
sil [ossa] @testDestructureTupleNoCrash : $@convention(thin) (@owned (Builtin.NativeObject, Builtin.NativeObject)) -> () {
bb0(%0 : @owned $(Builtin.NativeObject, Builtin.NativeObject)):
(%1, %2) = destructure_tuple %0 : $(Builtin.NativeObject, Builtin.NativeObject)
debug_value %1 : $Builtin.NativeObject, let, name "key"
debug_value %2 : $Builtin.NativeObject, let, name "value"
%3 = begin_borrow %1 : $Builtin.NativeObject
end_borrow %3 : $Builtin.NativeObject
%4 = begin_borrow %2 : $Builtin.NativeObject
end_borrow %4 : $Builtin.NativeObject
destroy_value %2 : $Builtin.NativeObject
destroy_value %1 : $Builtin.NativeObject
%9999 = tuple()
return %9999 : $()
}
// debug_value must be preserved after eliminating the borrow.
//
// CHECK-LABEL: sil [ossa] @testBorrowElimination : $@convention(thin) (@guaranteed Builtin.NativeObject) -> @owned Builtin.NativeObject {
// CHECK: bb0(%0 : @guaranteed $Builtin.NativeObject):
// CHECK: debug_value %0 : $Builtin.NativeObject, let, name "var"
// CHECK: [[CP:%.*]] = copy_value %0 : $Builtin.NativeObject
// CHECK: return [[CP]] : $Builtin.NativeObject
// CHECK-LABEL: } // end sil function 'testBorrowElimination'
sil [ossa] @testBorrowElimination : $@convention(thin) (@guaranteed Builtin.NativeObject) -> @owned Builtin.NativeObject {
bb0(%0 : @guaranteed $Builtin.NativeObject):
%1 = begin_borrow %0 : $Builtin.NativeObject
debug_value %1 : $Builtin.NativeObject, let, name "var"
%3 = copy_value %1 : $Builtin.NativeObject
end_borrow %1 : $Builtin.NativeObject
return %3 : $Builtin.NativeObject
}
// debug_value must be preserved after eliminating the copy.
//
// FIXME: eliminateSimpleCopies removes all debug_values. Instead,
// debug_value should be canonicalized first, before SILGenCleanup
// eliminates copies.
//
// CHECK-LABEL: sil [ossa] @testCopyElimination : $@convention(thin) (@guaranteed Builtin.NativeObject) -> @owned Builtin.NativeObject {
// CHECK: bb0(%0 : @guaranteed $Builtin.NativeObject):
// CHECK: [[CP:%.*]] = copy_value %0 : $Builtin.NativeObject
// CHECKDEB: [[CP2:%.*]] = copy_value %0 : $Builtin.NativeObject
// CHECKDEB: debug_value [[CP2]] : $Builtin.NativeObject, let, name "var"
// CHECK: return [[CP]] : $Builtin.NativeObject
// CHECK-LABEL: } // end sil function 'testCopyElimination'
sil [ossa] @testCopyElimination : $@convention(thin) (@guaranteed Builtin.NativeObject) -> @owned Builtin.NativeObject {
bb0(%0 : @guaranteed $Builtin.NativeObject):
%1 = copy_value %0 : $Builtin.NativeObject
%2 = copy_value %0 : $Builtin.NativeObject
debug_value %2 : $Builtin.NativeObject, let, name "var"
destroy_value %2 : $Builtin.NativeObject
return %1 : $Builtin.NativeObject
}
// Test SemanticARCOpts eliminateSimpleCopies. At -O, remove the
// debug_value with the copy to avoid a lifetime violation. -Onone, do nothing.
//
// CHECK-LABEL: sil [ossa] @testSimpleCopyWithDebug : $@convention(thin) (@owned Klass) -> Builtin.Int64 {
// CHECKOPT-NOT: copy_value
// CHECKOPT-NOT: debug_value
// CHECKDEB: copy_value
// CHECKDEB: debug_value
// CHECK-LABEL: } // end sil function 'testSimpleCopyWithDebug'
sil [ossa] @testSimpleCopyWithDebug : $@convention(thin) (@owned Klass) -> Builtin.Int64 {
bb9(%0 : @owned $Klass):
%borrow = begin_borrow %0 : $Klass
%p = ref_element_addr %borrow : $Klass, #Klass.property
%v = load [trivial] %p : $*Builtin.Int64
%copy = copy_value %borrow : $Klass
end_borrow %borrow : $Klass
debug_value %copy : $Klass, let, name "c"
destroy_value %copy : $Klass
destroy_value %0 : $Klass
return %v : $Builtin.Int64
}
struct Outer {
var middle: Middle
}
struct Middle {
var inner: Inner
}
struct Inner {
var guts: Builtin.AnyObject
}
sil @getOuter : $@convention(thin) () -> @owned Outer
sil @takeInner : $@convention(thin) (@owned Inner) -> ()
// CHECK-LABEL: sil [ossa] @narrowLoadThroughProjectionSequence : {{.*}} {
// CHECK: [[OUTER_ADDR:%[^,]+]] = project_box
// CHECK: [[MIDDLE_ADDR:%[^,]+]] = struct_element_addr [[OUTER_ADDR]]
// CHECK: [[INNER_ADDR:%[^,]+]] = struct_element_addr [[MIDDLE_ADDR]]
// CHECK: [[INNER_BORROW:%[^,]+]] = load_borrow [[INNER_ADDR]]
// CHECK: copy_value [[INNER_BORROW]]
// CHECK-LABEL: } // end sil function 'narrowLoadThroughProjectionSequence'
sil [ossa] @narrowLoadThroughProjectionSequence : $@convention(thin) () -> () {
%box = alloc_box ${ let Outer }
%box_borrow = begin_borrow [lexical] [var_decl] %box : ${ let Outer }
%outer_addr = project_box %box_borrow : ${ let Outer }, 0
%getOuter = function_ref @getOuter : $@convention(thin) () -> @owned Outer
%outer = apply %getOuter() : $@convention(thin) () -> @owned Outer
store %outer to [init] %outer_addr : $*Outer
%outer_borrow = load_borrow %outer_addr : $*Outer
%middle = struct_extract %outer_borrow : $Outer, #Outer.middle
%inner = struct_extract %middle : $Middle, #Middle.inner
%inner_copy = copy_value %inner : $Inner
%takeInner = function_ref @takeInner : $@convention(thin) (@owned Inner) -> ()
apply %takeInner(%inner_copy) : $@convention(thin) (@owned Inner) -> ()
end_borrow %outer_borrow : $Outer
end_borrow %box_borrow : ${ let Outer }
dealloc_box %box : ${ let Outer }
%retval = tuple ()
return %retval : $()
}