Files
swift-mirror/test/SILOptimizer/destroy-hoisting.sil
Erik Eckstein 18063707b5 Optimizer: enable complete OSSA lifetimes throughout the pass pipeline
This new OSSA invariant simplifies many optimizations because they don't have to take care of the corner case of incomplete lifetimes in dead-end blocks.

The implementation basically consists of these changes:
* add the lifetime completion utility
* add a flag in SILFunction which tells optimization that they need to run the lifetime completion utility
* let all optimizations complete lifetimes if necessary
* enable the ownership verifier to check complete lifetimes
2026-01-22 17:41:48 +01:00

500 lines
15 KiB
Plaintext

// RUN: %target-sil-opt -destroy-hoisting %s | %FileCheck %s
sil_stage canonical
import Builtin
import Swift
class Klass {
}
class C {
@_hasStorage var k: Klass
}
struct S {
var k: Klass
var i: Int
}
sil [ossa] @deinit_barrier : $@convention(thin) () -> ()
sil [ossa] @createKlass : $@convention(thin) () -> @owned Klass
sil [ossa] @createC : $@convention(thin) () -> @owned C
sil [ossa] @useKlass : $@convention(thin) (@guaranteed Klass) -> ()
// CHECK-LABEL: sil [ossa] @extend_forwarded_lifetime :
// CHECK: %2 = copy_value %0
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'extend_forwarded_lifetime'
sil [ossa] @extend_forwarded_lifetime : $@convention(thin) (@owned Klass, Int) -> @owned S {
bb0(%0 : @owned $Klass, %1 : $Int):
%2 = copy_value %0
%3 = move_value [lexical] %2
%4 = function_ref @deinit_barrier : $@convention(thin) () -> ()
%5 = apply %4() : $@convention(thin) () -> ()
destroy_value %0
%7 = struct $S (%3, %1)
return %7
}
// CHECK-LABEL: sil [ossa] @extend_forwarded_lifetime2 :
// CHECK: %4 = copy_value %3
// CHECK-NEXT: destroy_value %3
// CHECK: } // end sil function 'extend_forwarded_lifetime2'
sil [ossa] @extend_forwarded_lifetime2 : $@convention(thin) (Int) -> @owned S {
bb0(%0 : $Int):
%1 = function_ref @createKlass : $@convention(thin) () -> @owned Klass
%2 = apply %1() : $@convention(thin) () -> @owned Klass
%3 = move_value [lexical] [var_decl] %2
%4 = copy_value %3
%5 = move_value [lexical] %4
%6 = struct $S (%5, %0)
%7 = move_value [lexical] [var_decl] %6
%8 = function_ref @deinit_barrier : $@convention(thin) () -> ()
%9 = apply %8() : $@convention(thin) () -> ()
destroy_value %3
return %7
} // end sil function '$s3nix6testitAA1SVyF'
// CHECK-LABEL: sil [ossa] @hoisting_into_non_lexical_lifetime :
// CHECK: %2 = copy_value
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'hoisting_into_non_lexical_lifetime'
sil [ossa] @hoisting_into_non_lexical_lifetime : $@convention(thin) (@owned Klass, Int) -> @owned S {
bb0(%0 : @owned $Klass, %1 : $Int):
%2 = copy_value %0
%4 = struct $S (%2, %1)
%5 = function_ref @deinit_barrier : $@convention(thin) () -> ()
apply %5() : $@convention(thin) () -> ()
destroy_value %0
return %4
}
// CHECK-LABEL: sil [ossa] @degenerated_liferange :
// CHECK: bb0(%0 : @owned $Klass):
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'degenerated_liferange'
sil [ossa] @degenerated_liferange : $@convention(method) (@owned Klass) -> () {
bb0(%0 : @owned $Klass):
destroy_value %0
%2 = tuple ()
return %2
}
// CHECK-LABEL: sil [ossa] @hoist_multi_block :
// CHECK: %4 = copy_value %0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: destroy_value %4
// CHECK: bb2:
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'hoist_multi_block'
sil [ossa] @hoist_multi_block : $@convention(thin) (@owned Klass, Int) -> @owned S {
bb0(%0 : @owned $Klass, %1 : $Int):
%2 = copy_value %0
cond_br undef, bb1, bb2
bb1:
%4 = copy_value %0
destroy_value %4
destroy_value %0
br bb3
bb2:
%8 = function_ref @deinit_barrier : $@convention(thin) () -> ()
apply %8() : $@convention(thin) () -> ()
destroy_value %0
br bb3
bb3:
%12 = move_value [lexical] %2
%13 = struct $S (%12, %1)
return %13
}
// CHECK-LABEL: sil [ossa] @partial_hoist_multi_block :
// CHECK: %6 = copy_value %0
// CHECK-NEXT: destroy_value %6
// CHECK-NEXT: destroy_value %0
// CHECK: bb2:
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'partial_hoist_multi_block'
sil [ossa] @partial_hoist_multi_block : $@convention(thin) (@owned Klass, Int) -> () {
bb0(%0 : @owned $Klass, %1 : $Int):
%2 = copy_value %0
%3 = move_value [lexical] %2
cond_br undef, bb1, bb2
bb1:
destroy_value %3
%6 = copy_value %0
destroy_value %6
destroy_value %0
br bb3
bb2:
%9 = function_ref @deinit_barrier : $@convention(thin) () -> ()
apply %9() : $@convention(thin) () -> ()
destroy_value %0
destroy_value %3
br bb3
bb3:
%14 = tuple ()
return %14
}
// CHECK-LABEL: sil [ossa] @already_at_lifetime_end :
// CHECK: %4 = copy_value %0
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: destroy_value %4
// CHECK: bb2:
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'already_at_lifetime_end'
sil [ossa] @already_at_lifetime_end : $@convention(thin) (@owned Klass, Int) -> @owned S {
bb0(%0 : @owned $Klass, %1 : $Int):
%2 = copy_value %0
cond_br undef, bb1, bb2
bb1:
%4 = copy_value %0
destroy_value %0
destroy_value %4
br bb3
bb2:
destroy_value %0
br bb3
bb3:
%10 = move_value [lexical] %2
%11 = struct $S (%10, %1)
return %11
}
// CHECK-LABEL: sil [ossa] @reborrow_phi :
// CHECK: end_borrow
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'reborrow_phi'
sil [ossa] @reborrow_phi : $@convention(thin) (@owned Klass, Int) -> @owned S {
bb0(%0 : @owned $Klass, %1 : $Int):
%2 = copy_value %0
cond_br undef, bb1, bb2
bb1:
%4 = begin_borrow %0
br bb3(%4)
bb2:
%6 = begin_borrow %0
br bb3(%6)
bb3(%8 : @reborrow $Klass):
%9 = borrowed %8 from (%0)
end_borrow %9
%11 = function_ref @deinit_barrier : $@convention(thin) () -> ()
apply %11() : $@convention(thin) () -> ()
destroy_value %0
%14 = move_value [lexical] %2
%15 = struct $S (%14, %1)
return %15
}
// CHECK-LABEL: sil [ossa] @store :
// CHECK: %2 = copy_value %0
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'store'
sil [ossa] @store : $@convention(method) (@owned Klass, @guaranteed C) -> () {
bb0(%0 : @owned $Klass, %1 : @guaranteed $C):
%2 = copy_value %0
%3 = ref_element_addr %1, #C.k
%4 = begin_access [modify] [dynamic] %3
store %2 to [assign] %4
end_access %4
destroy_value %0
%8 = tuple ()
return %8
}
// CHECK-LABEL: sil [ossa] @dominating_store :
// CHECK: %2 = copy_value %0
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'dominating_store'
sil [ossa] @dominating_store : $@convention(method) (@owned Klass, @guaranteed C) -> () {
bb0(%0 : @owned $Klass, %1 : @guaranteed $C):
%2 = copy_value %0
%3 = ref_element_addr %1, #C.k
%4 = begin_access [modify] [dynamic] %3
store %2 to [assign] %4
end_access %4
cond_br undef, bb1, bb2
bb1:
destroy_value %0
br bb3
bb2:
destroy_value %0
br bb3
bb3:
%8 = tuple ()
return %8
}
// CHECK-LABEL: sil [ossa] @not_dominating_store :
// CHECK: bb3:
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'not_dominating_store'
sil [ossa] @not_dominating_store : $@convention(method) (@owned Klass, @guaranteed C) -> () {
bb0(%0 : @owned $Klass, %1 : @guaranteed $C):
cond_br undef, bb1, bb2
bb1:
%2 = copy_value %0
%3 = ref_element_addr %1, #C.k
%4 = begin_access [modify] [dynamic] %3
store %2 to [assign] %4
end_access %4
br bb3
bb2:
br bb3
bb3:
destroy_value %0
%8 = tuple ()
return %8
}
// CHECK-LABEL: sil [ossa] @write_to_memory :
// CHECK: bb3:
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'write_to_memory'
sil [ossa] @write_to_memory : $@convention(method) (@owned Klass, @guaranteed C) -> () {
bb0(%0 : @owned $Klass, %1 : @guaranteed $C):
%2 = copy_value %0
%3 = ref_element_addr %1, #C.k
%4 = begin_access [modify] [dynamic] %3
store %2 to [assign] %4
end_access %4
cond_br undef, bb1, bb2
bb1:
destroy_addr %4
br bb3
bb2:
br bb3
bb3:
destroy_value %0
%8 = tuple ()
return %8
}
// CHECK-LABEL: sil [ossa] @not_lexical_access_base :
// CHECK: %1 = copy_value %0
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'not_lexical_access_base'
sil [ossa] @not_lexical_access_base : $@convention(method) (@owned Klass) -> () {
bb0(%0 : @owned $Klass):
%1 = copy_value %0
%2 = function_ref @createC : $@convention(thin) () -> @owned C
%3 = apply %2() : $@convention(thin) () -> @owned C
%4 = begin_borrow %3
%5 = ref_element_addr %4, #C.k
store %1 to [assign] %5
end_borrow %4
destroy_value %0
destroy_value %3
%11 = tuple ()
return %11
}
// CHECK-LABEL: sil [ossa] @lexical_access_base :
// CHECK: %1 = copy_value %0
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'lexical_access_base'
sil [ossa] @lexical_access_base : $@convention(method) (@owned Klass) -> () {
bb0(%0 : @owned $Klass):
%1 = copy_value %0
%2 = function_ref @createC : $@convention(thin) () -> @owned C
%3 = apply %2() : $@convention(thin) () -> @owned C
%4 = move_value [lexical] %3
%5 = begin_borrow %4
%6 = ref_element_addr %5, #C.k
store %1 to [assign] %6
end_borrow %5
destroy_value %0
destroy_value %4
%11 = tuple ()
return %11
}
// CHECK-LABEL: sil [ossa] @destroy_of_access_base :
// CHECK: end_borrow
// CHECK-NEXT: destroy_value
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'destroy_of_access_base'
sil [ossa] @destroy_of_access_base : $@convention(method) (@owned Klass) -> () {
bb0(%0 : @owned $Klass):
%1 = copy_value %0
%2 = function_ref @createC : $@convention(thin) () -> @owned C
%3 = apply %2() : $@convention(thin) () -> @owned C
%4 = move_value [lexical] %3
%5 = begin_borrow %4
%6 = ref_element_addr %5, #C.k
store %1 to [assign] %6
end_borrow %5
destroy_value %4
destroy_value %0
%11 = tuple ()
return %11
}
// CHECK-LABEL: sil [ossa] @incomplete_liferange_at_unreachable :
// CHECK: bb2:
// CHECK-NOT: destroy_value %0
// CHECK: } // end sil function 'incomplete_liferange_at_unreachable'
sil [ossa] @incomplete_liferange_at_unreachable : $@convention(thin) (@owned Klass, Int) -> @owned S {
bb0(%0 : @owned $Klass, %1 : $Int):
%2 = copy_value %0
%3 = alloc_stack $Klass
%4 = store_borrow %0 to %3
cond_br undef, bb1, bb2
bb1:
end_borrow %4
dealloc_stack %3
destroy_value %0
%14 = move_value %2
%15 = struct $S (%14, %1)
return %15
bb2:
end_borrow %4
dealloc_stack %3
destroy_value [dead_end] %2
destroy_value [dead_end] %0
unreachable
}
// CHECK-LABEL: sil [ossa] @partial_apply_liferange :
// CHECK: %3 = convert_function %2
// CHECK-NEXT: destroy_value %3
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'partial_apply_liferange'
sil [ossa] @partial_apply_liferange : $@convention(thin) (@owned Klass) -> () {
bb0(%0 : @owned $Klass):
%1 = function_ref @useKlass : $@convention(thin) (@guaranteed Klass) -> ()
%2 = partial_apply [callee_guaranteed] [on_stack] %1(%0) : $@convention(thin) (@guaranteed Klass) -> ()
%3 = convert_function %2 to $@noescape @callee_guaranteed () -> ()
destroy_value %3
destroy_value %0
%r = tuple ()
return %r
}
// CHECK-LABEL: sil [ossa] @borrow_in_deadend_block :
// CHECK: bb1:
// CHECK-NEXT: destroy_value %1
// CHECK-NEXT: destroy_value %2
// CHECK: } // end sil function 'borrow_in_deadend_block'
sil [ossa] @borrow_in_deadend_block : $@convention(thin) (@in Klass) -> () {
bb0(%0 : $*Klass):
%1 = load [take] %0
%2 = copy_value %1
%3 = begin_borrow %1
end_borrow %3
cond_br undef, bb1, bb2
bb1:
destroy_value %1
destroy_value %2
%8 = tuple ()
return %8
bb2:
%10 = begin_borrow %1
end_borrow %10
debug_step
destroy_value [dead_end] %2
destroy_value [dead_end] %1
unreachable
}
// CHECK-LABEL: sil [ossa] @handle_dead_end :
// CHECK: %1 = copy_value %0
// CHECK-NEXT: destroy_value %0
// CHECK: } // end sil function 'handle_dead_end'
sil [ossa] @handle_dead_end : $@convention(thin) (@owned Klass) -> @owned Klass {
bb0(%0 : @owned $Klass):
%1 = copy_value %0
cond_br undef, bb1, bb2
bb1:
destroy_value %0
return %1
bb2:
destroy_value [dead_end] %0
destroy_value [dead_end] %1
unreachable
}
// CHECK-LABEL: sil [ossa] @deadend_vs_not_deadend :
// CHECK: bb1:
// CHECK-NEXT: fix_lifetime
// CHECK-NEXT: destroy_value %0
// CHECK-NEXT: debug_step
// CHECK: bb2:
// CHECK-NEXT: destroy_value [dead_end] %0
// CHECK-NEXT: debug_step
// CHECK: } // end sil function 'deadend_vs_not_deadend'
sil [ossa] @deadend_vs_not_deadend : $@convention(thin) (@owned Klass) -> @owned Klass {
bb0(%0 : @owned $Klass):
%1 = copy_value %0
cond_br undef, bb1, bb2
bb1:
fix_lifetime %0
debug_step
destroy_value %0
return %1
bb2:
debug_step
destroy_value [dead_end] %0
destroy_value [dead_end] %1
unreachable
}
public struct Wrapper {
var _k: Klass
var i: Int
}
sil @get_wrapper : $@convention(thin) () -> @owned Wrapper
sil @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
sil [ossa] @borrow_loadable_prop : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass {
bb0(%0 : @guaranteed $Wrapper):
%2 = struct_extract %0, #Wrapper._k
return %2
}
// CHECK-LABEL: sil [ossa] @test_borrow_accessor_noopt1 :
// CHECK: [[F:%.*]] = function_ref @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
// CHECK: [[A:%.*]] = apply [[F]]({{.*}}) : $@convention(thin) (@guaranteed Klass) -> ()
// CHECK: end_borrow {{.*}}
// CHECK: destroy_value %0
// CHECK-LABEL: } // end sil function 'test_borrow_accessor_noopt1'
sil [ossa] @test_borrow_accessor_noopt1 : $@convention(thin) (@owned Wrapper) -> () {
bb0(%0 : @owned $Wrapper):
%1 = begin_borrow %0
%2 = function_ref @borrow_loadable_prop : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
%3 = apply %2(%1) : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
%4 = function_ref @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
%5 = apply %4(%3) : $@convention(thin) (@guaranteed Klass) -> ()
end_borrow %1
destroy_value %0
%6 = tuple ()
return %6
}
// CHECK-LABEL: sil [ossa] @test_borrow_accessor_noopt2 :
// CHECK: [[F:%.*]] = function_ref @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
// CHECK: [[A:%.*]] = apply [[F]]({{.*}}) : $@convention(thin) (@guaranteed Klass) -> ()
// CHECK: end_borrow {{.*}}
// CHECK: destroy_value {{.*}}
// CHECK-LABEL: } // end sil function 'test_borrow_accessor_noopt2'
sil [ossa] @test_borrow_accessor_noopt2 : $@convention(thin) () -> () {
bb0:
%1 = function_ref @get_wrapper : $@convention(thin) () -> @owned Wrapper
%2 = apply %1() : $@convention(thin) () -> @owned Wrapper
%3 = begin_borrow %2
%4 = function_ref @borrow_loadable_prop : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
%5 = apply %4(%3) : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass
%6 = function_ref @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
%7 = apply %6(%5) : $@convention(thin) (@guaranteed Klass) -> ()
end_borrow %3
destroy_value %2
%10 = tuple ()
return %10
}