Files
swift-mirror/test/SILOptimizer/lifetime_resolution_storage_unit.sil
Kavon Farvardin 5f41ff9fae LifetimeResolution: emit per-field assign undef's
This should help simplify the diagnostics emission.
2026-10-01 18:13:22 -07:00

1733 lines
66 KiB
Plaintext

// RUN: %target-sil-opt %s -test-runner -sil-print-types -sil-disable-input-verify | %FileCheck %s --implicit-check-not diagnose
sil_stage raw
import Builtin
import Swift
class K {}
struct C { var k: K }
struct NC: ~Copyable { var k: K }
struct B { var c: C }
struct A { var b: B }
struct Two { var a: K; var b: K }
struct NCTwo: ~Copyable { var a: K; var b: K }
struct Nested { var p: Two; var k: K }
sil @getK : $@convention(thin) () -> @owned K
sil @useK : $@convention(thin) (@guaranteed K) -> ()
sil @consumeK : $@convention(thin) (@owned K) -> ()
sil @consumeTwo : $@convention(thin) (@owned Two) -> ()
sil @getNC : $@convention(thin) () -> @owned NC
sil @useNC : $@convention(thin) (@guaranteed NC) -> ()
sil @consumeNC : $@convention(thin) (@owned NC) -> ()
sil @getNC_addr : $@convention(thin) () -> @out NC
sil @consumeNC_addr : $@convention(thin) (@in NC) -> ()
sil @barrier : $@convention(thin) () -> ()
sil @useC_addr : $@convention(thin) (@in_guaranteed C) -> ()
sil @consumeC_addr : $@convention(thin) (@in C) -> ()
sil @consumeK_addr : $@convention(thin) (@in K) -> ()
sil @produceC : $@convention(thin) () -> @out C
// CHECK-LABEL: sil [ossa] @basic_take_of_uninit_box :
// CHECK: [[ROOT:%.*]] = project_box {{.*}} : ${ var K }, 0
// CHECK: assign undef to [init] [[ROOT]]
// CHECK: [[BA:%.*]] = begin_access [deinit] [unknown] [[ROOT]]
// CHECK-NEXT: [[LD:%.*]] = load [copy] [[BA]]
// CHECK-NEXT: diagnose [unpermitted_copy] [[LD]]
// CHECK: } // end sil function
sil [ossa] @basic_take_of_uninit_box : $@convention(thin) () -> () {
bb0:
%0 = alloc_box ${ var K }, var, name "a"
%1 = mark_uninitialized [var] %0 : ${ var K }
%2 = begin_borrow [lexical] [var_decl] %1 : ${ var K }
%root = project_box %2 : ${ var K }, 0
specify_test "lifetime_resolution_resolve %0"
%d1 = begin_access [deinit] [unknown] %root : $*K
%l1 = load [take] %d1 : $*K
end_access %d1 : $*K
ignored_use %l1
destroy_value %l1
end_borrow %2
destroy_value %1
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @basic_take_of_uninit :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: assign undef to [init] [[ROOT]]
// CHECK: [[BA:%.*]] = begin_access [deinit] [unknown] [[ROOT]]
// CHECK-NEXT: load [take] [[BA]]
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @basic_take_of_uninit : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $K, var, name "x", type $K
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%d1 = begin_access [deinit] [unknown] %root : $*K
%l1 = load [take] %d1 : $*K
end_access %d1 : $*K
ignored_use %l1
destroy_value %l1
destroy_addr %root
dealloc_stack %0
%t = tuple ()
return %t
}
// FIXME: the `begin_access [deinit]`` should be corrected by LifetimeResolution into a `[read]`
// CHECK-LABEL: sil [ossa] @basic_double_take :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[BA:%.*]] = begin_access [deinit] [unknown] [[ROOT]] : $*K
// CHECK-NEXT: [[LD:%.*]] = load [copy] [[BA]] : $*K
// CHECK-NEXT: diagnose [unpermitted_copy] [[LD]]
// CHECK-NEXT: end_access [[BA]] : $*K
// CHECK-NEXT: ignored_use [[LD]]
// CHECK: load [take]
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @basic_double_take : $@convention(thin) () -> () {
bb0:
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%0 = alloc_stack [lexical] [var_decl] $K, var, name "a", type $K
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%val = apply %getFn() : $@convention(thin) () -> @owned K
assign %val to %root
%d1 = begin_access [deinit] [unknown] %root : $*K
%l1 = load [take] %d1 : $*K
end_access %d1 : $*K
ignored_use %l1
destroy_value %l1
%r1 = begin_access [deinit] [unknown] %root : $*K
%l2 = load [take] %r1 : $*K
destroy_value %l2 : $K
end_access %r1 : $*K
destroy_addr %root : $*K
dealloc_stack %0 : $*K
%t = tuple ()
return %t
}
// FIXME: the `begin_access [read]` should be corrected by LifetimeResolution into a `[deinit]` or `[modify]`
// CHECK-LABEL: sil [ossa] @basic_last_use_is_copy :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[BA:%.*]] = begin_access [read] [unknown] [[ROOT]] : $*K
// CHECK-NEXT: [[LD:%.*]] = load [take] [[BA]] : $*K
// CHECK-NEXT: end_access [[BA]] : $*K
// CHECK-NEXT: ignored_use [[LD]]
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @basic_last_use_is_copy : $@convention(thin) () -> () {
bb0:
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%0 = alloc_stack [lexical] [var_decl] $K, var, name "a", type $K
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%val = apply %getFn() : $@convention(thin) () -> @owned K
assign %val to %root
%d1 = begin_access [read] [unknown] %root : $*K
%l1 = load [copy] %d1 : $*K
end_access %d1 : $*K
ignored_use %l1
destroy_value %l1
destroy_addr %root : $*K
dealloc_stack %0 : $*K
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @nc_borrow_within_access_before_consume :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[BA:%.*]] = begin_access [read] [unknown] [[ROOT]] : $*NC
// CHECK-NEXT: [[LD:%.*]] = load_borrow [[BA]] : $*NC
// CHECK-NEXT: apply {{.*}}([[LD]]) : $@convention(thin) (@guaranteed NC) -> ()
// CHECK-NEXT: end_borrow [[LD]]
// CHECK-NEXT: end_access [[BA]]
// CHECK-NOT: load_borrow
// CHECK: [[BA2:%.*]] = begin_access [read] [unknown] [[ROOT]] : $*NC
// CHECK-NEXT: [[LD2:%.*]] = load_borrow [[BA]] : $*NC
// CHECK-NEXT: apply {{.*}}([[LD2]]) : $@convention(thin) (@guaranteed NC) -> ()
// CHECK-NEXT: apply {{.*}}() : $@convention(thin) () -> ()
// CHECK-NEXT: end_borrow [[LD2]]
// CHECK-NEXT: end_access [[BA2]]
// CHECK-NOT: load_borrow
// CHECK: load [take]
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @nc_borrow_within_access_before_consume : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $NC, var, name "a", type $NC
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getNC : $@convention(thin) () -> @owned NC
%val = apply %getFn() : $@convention(thin) () -> @owned NC
assign %val to %root
// Basic load-and-use within access
%useFn = function_ref @useNC : $@convention(thin) (@guaranteed NC) -> ()
%r1 = begin_access [read] [unknown] %root : $*NC
%l1 = load [copy] %r1 : $*NC
apply %useFn(%l1) : $@convention(thin) (@guaranteed NC) -> ()
destroy_value %l1
end_access %r1 : $*NC
// Same as above, but with a deinit barrier after the apply.
%barrierFn = function_ref @barrier : $@convention(thin) () -> ()
%r2 = begin_access [read] [unknown] %root : $*NC
%l2 = load [copy] %r1 : $*NC
apply %useFn(%l2) : $@convention(thin) (@guaranteed NC) -> ()
apply %barrierFn() : $@convention(thin) () -> ()
destroy_value %l2
end_access %r2 : $*NC
%consumeFn = function_ref @consumeNC : $@convention(thin) (@owned NC) -> ()
%c1 = begin_access [deinit] [unknown] %root : $*NC
%c2 = load [take] %c1 : $*NC
end_access %c1 : $*NC
apply %consumeFn(%c2) : $@convention(thin) (@owned NC) -> ()
destroy_addr %root : $*NC
dealloc_stack %0 : $*NC
%t = tuple ()
return %t
}
//===----------------------------------------------------------------------===//
// Field sensitivity: demand is tracked per leaf subelement, so consumes and uses
// of distinct fields are independent.
//===----------------------------------------------------------------------===//
// Two independent leaves. Taking `a` and then `b` are each the last use of their
// own field, so both remain `load [take]`.
// CHECK-LABEL: sil [ossa] @fs_independent_field_takes :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: store {{%.*}} to [init] [[ROOT]] : $*Two
// CHECK: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: load [take] [[AA]] : $*K
// CHECK: [[AB:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: load [take] [[AB]] : $*K
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @fs_independent_field_takes : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t", type $Two
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%consFn = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
%va = apply %getFn() : $@convention(thin) () -> @owned K
%vb = apply %getFn() : $@convention(thin) () -> @owned K
%whole = struct $Two (%va : $K, %vb : $K)
store %whole to [init] %root : $*Two
%aa = struct_element_addr %root : $*Two, #Two.a
%la = load [take] %aa : $*K
apply %consFn(%la) : $@convention(thin) (@owned K) -> ()
%ab = struct_element_addr %root : $*Two, #Two.b
%lb = load [take] %ab : $*K
apply %consFn(%lb) : $@convention(thin) (@owned K) -> ()
destroy_addr %root : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// take-then-copy --> copy-then-take + diagnostic
// CHECK-LABEL: sil [ossa] @fs_use_after_consume_same_field :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[A1:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: [[L1:%.*]] = load [copy] [[A1]] : $*K
// CHECK-NEXT: diagnose [unpermitted_copy] [[L1]] : $K
// CHECK: [[A2:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: load [take] [[A2]] : $*K
// CHECK: [[AB:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: load [take] [[AB]] : $*K
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @fs_use_after_consume_same_field : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t", type $Two
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%consFn = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
%va = apply %getFn() : $@convention(thin) () -> @owned K
%vb = apply %getFn() : $@convention(thin) () -> @owned K
%whole = struct $Two (%va : $K, %vb : $K)
store %whole to [init] %root : $*Two
%aa1 = struct_element_addr %root : $*Two, #Two.a
%la1 = load [take] %aa1 : $*K
apply %consFn(%la1) : $@convention(thin) (@owned K) -> ()
%aa2 = struct_element_addr %root : $*Two, #Two.a
%la2 = load [copy] %aa2 : $*K
apply %consFn(%la2) : $@convention(thin) (@owned K) -> ()
%ab = struct_element_addr %root : $*Two, #Two.b
%lb = load [copy] %ab : $*K
apply %consFn(%lb) : $@convention(thin) (@owned K) -> ()
destroy_addr %root : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// Consuming the whole aggregate, and then a field `b`.
// Since the whole take is interior it is legalized as a copy, leaving leaf `a`
// live, so the trailing destroy_addr is narrowed to `a`.
// CHECK-LABEL: sil [ossa] @fs_take_whole_then_field :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[LW:%.*]] = load [copy] [[ROOT]] : $*Two
// CHECK-NEXT: diagnose [unpermitted_copy] [[LW]] : $Two
// CHECK: [[AB:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: load [take] [[AB]] : $*K
// CHECK: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: destroy_addr [[AA]] : $*K
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @fs_take_whole_then_field : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t", type $Two
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%consFn = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
%va = apply %getFn() : $@convention(thin) () -> @owned K
%vb = apply %getFn() : $@convention(thin) () -> @owned K
%whole = struct $Two (%va : $K, %vb : $K)
store %whole to [init] %root : $*Two
%lw = load [take] %root : $*Two
destroy_value %lw : $Two
%ab = struct_element_addr %root : $*Two, #Two.b
%lb = load [take] %ab : $*K
apply %consFn(%lb) : $@convention(thin) (@owned K) -> ()
destroy_addr %root : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// The dual of the above: consuming field `a` and then the whole aggregate. The
// whole take demands leaf `a` below, so the field-`a` take is interior and
// legalized as a copy.
// CHECK-LABEL: sil [ossa] @fs_take_field_then_whole :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: [[LA:%.*]] = load [copy] [[AA]] : $*K
// CHECK-NEXT: diagnose [unpermitted_copy] [[LA]] : $K
// CHECK: load [take] [[ROOT]] : $*Two
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @fs_take_field_then_whole : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t", type $Two
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%consFn = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
%consTwoFn = function_ref @consumeTwo : $@convention(thin) (@owned Two) -> ()
%va = apply %getFn() : $@convention(thin) () -> @owned K
%vb = apply %getFn() : $@convention(thin) () -> @owned K
%whole = struct $Two (%va : $K, %vb : $K)
store %whole to [init] %root : $*Two
%aa = struct_element_addr %root : $*Two, #Two.a
%la = load [take] %aa : $*K
apply %consFn(%la) : $@convention(thin) (@owned K) -> ()
%lw = load [take] %root : $*Two
apply %consTwoFn(%lw) : $@convention(thin) (@owned Two) -> ()
destroy_addr %root : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// Tuple elements are leaves too: taking `.0` and `.1` are independent last uses,
// so both stay takes.
// CHECK-LABEL: sil [ossa] @fs_tuple_independent_takes :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[E0:%.*]] = tuple_element_addr [[ROOT]] : $*(K, K), 0
// CHECK-NEXT: load [take] [[E0]] : $*K
// CHECK: [[E1:%.*]] = tuple_element_addr [[ROOT]] : $*(K, K), 1
// CHECK-NEXT: load [take] [[E1]] : $*K
// CHECK-NOT: load [copy]
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @fs_tuple_independent_takes : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $(K, K), var, name "t", type $(K, K)
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%consFn = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
%va = apply %getFn() : $@convention(thin) () -> @owned K
%vb = apply %getFn() : $@convention(thin) () -> @owned K
%whole = tuple (%va : $K, %vb : $K)
store %whole to [init] %root : $*(K, K)
%e0 = tuple_element_addr %root : $*(K, K), 0
%l0 = load [take] %e0 : $*K
apply %consFn(%l0) : $@convention(thin) (@owned K) -> ()
%e1 = tuple_element_addr %root : $*(K, K), 1
%l1 = load [take] %e1 : $*K
apply %consFn(%l1) : $@convention(thin) (@owned K) -> ()
destroy_addr %root : $*(K, K)
dealloc_stack %0 : $*(K, K)
%t = tuple ()
return %t
}
// Nested aggregate: `Nested` linearizes to three leaves [p.a, p.b, k]. Consuming
// each once through a chained projection is three independent last uses that are
// converted to [take]'s
// CHECK-LABEL: sil [ossa] @fs_nested_leaves :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: struct_element_addr [[ROOT]] : $*Nested, #Nested.p
// CHECK-NEXT: struct_element_addr {{.*}} : $*Two, #Two.a
// CHECK-NEXT: load [take]
// CHECK: struct_element_addr [[ROOT]] : $*Nested, #Nested.p
// CHECK-NEXT: struct_element_addr {{.*}} : $*Two, #Two.b
// CHECK-NEXT: load [take]
// CHECK: struct_element_addr [[ROOT]] : $*Nested, #Nested.k
// CHECK-NEXT: load [take]
// CHECK-NOT: load [copy]
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @fs_nested_leaves : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Nested, var, name "n", type $Nested
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%consFn = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
%va = apply %getFn() : $@convention(thin) () -> @owned K
%vb = apply %getFn() : $@convention(thin) () -> @owned K
%vk = apply %getFn() : $@convention(thin) () -> @owned K
%two = struct $Two (%va : $K, %vb : $K)
%n = struct $Nested (%two : $Two, %vk : $K)
store %n to [init] %root : $*Nested
%pa = struct_element_addr %root : $*Nested, #Nested.p
%aa = struct_element_addr %pa : $*Two, #Two.a
%la = load [copy] %aa : $*K
apply %consFn(%la) : $@convention(thin) (@owned K) -> ()
%pb = struct_element_addr %root : $*Nested, #Nested.p
%ab = struct_element_addr %pb : $*Two, #Two.b
%lb = load [copy] %ab : $*K
apply %consFn(%lb) : $@convention(thin) (@owned K) -> ()
%ka = struct_element_addr %root : $*Nested, #Nested.k
%lk = load [copy] %ka : $*K
apply %consFn(%lk) : $@convention(thin) (@owned K) -> ()
destroy_addr %root : $*Nested
dealloc_stack %0 : $*Nested
%t = tuple ()
return %t
}
// Noncopyable field sensitivity: consuming field `a` (last use) stays a take.
// Field `b` is read while still demanded below, so it becomes a `load_borrow`.
// CHECK-LABEL: sil [ossa] @fs_nc_take_field_borrow_other :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*NCTwo, #NCTwo.a
// CHECK-NEXT: load [take] [[AA]] : $*K
// CHECK: [[AB:%.*]] = struct_element_addr [[ROOT]] : $*NCTwo, #NCTwo.b
// CHECK-NEXT: [[LB:%.*]] = load_borrow [[AB]] : $*K
// CHECK: end_borrow [[LB]] : $K
// CHECK: [[AB2:%.*]] = struct_element_addr [[ROOT]] : $*NCTwo, #NCTwo.b
// CHECK-NEXT: load [take] [[AB2]] : $*K
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @fs_nc_take_field_borrow_other : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $NCTwo, var, name "t", type $NCTwo
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%useFn = function_ref @useK : $@convention(thin) (@guaranteed K) -> ()
%consFn = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
%va = apply %getFn() : $@convention(thin) () -> @owned K
%vb = apply %getFn() : $@convention(thin) () -> @owned K
%whole = struct $NCTwo (%va : $K, %vb : $K)
store %whole to [init] %root : $*NCTwo
%aa = struct_element_addr %root : $*NCTwo, #NCTwo.a
%la = load [take] %aa : $*K
apply %consFn(%la) : $@convention(thin) (@owned K) -> ()
%ab = struct_element_addr %root : $*NCTwo, #NCTwo.b
%lb = load [copy] %ab : $*K
apply %useFn(%lb) : $@convention(thin) (@guaranteed K) -> ()
destroy_value %lb : $K
%ab2 = struct_element_addr %root : $*NCTwo, #NCTwo.b
%lb2 = load [take] %ab2 : $*K
apply %consFn(%lb2) : $@convention(thin) (@owned K) -> ()
destroy_addr %root : $*NCTwo
dealloc_stack %0 : $*NCTwo
%t = tuple ()
return %t
}
// Field sensitivity is preserved through a `begin_access` scope: the field-`a`
// take within the access is the last use of `a` and stays a take; the field-`b`
// take is independent.
// CHECK-LABEL: sil [ossa] @fs_field_access_scope :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[ACC:%.*]] = begin_access [deinit] [unknown] [[ROOT]] : $*Two
// CHECK-NEXT: [[AA:%.*]] = struct_element_addr [[ACC]] : $*Two, #Two.a
// CHECK-NEXT: load [take] [[AA]] : $*K
// CHECK-NEXT: end_access [[ACC]] : $*Two
// CHECK: [[AB:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: load [take] [[AB]] : $*K
// CHECK-NOT: load [copy]
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @fs_field_access_scope : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t", type $Two
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%consFn = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
%va = apply %getFn() : $@convention(thin) () -> @owned K
%vb = apply %getFn() : $@convention(thin) () -> @owned K
%whole = struct $Two (%va : $K, %vb : $K)
store %whole to [init] %root : $*Two
%acc = begin_access [deinit] [unknown] %root : $*Two
%aa = struct_element_addr %acc : $*Two, #Two.a
%la = load [copy] %aa : $*K
end_access %acc : $*Two
apply %consFn(%la) : $@convention(thin) (@owned K) -> ()
%ab = struct_element_addr %root : $*Two, #Two.b
%lb = load [copy] %ab : $*K
apply %consFn(%lb) : $@convention(thin) (@owned K) -> ()
destroy_addr %root : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// Only the field that is both demanded and uninitialized is repaired with an undef.
// Field `a` is stored but never consumed, so the trailing destroy_addr is narrowed to `a`.
// CHECK-LABEL: sil [ossa] @fs_partial_use_before_init :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK-NEXT: [[UB:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: assign undef to [init] [[UB]] : $*K
// CHECK-NOT: assign undef
// CHECK: [[AB:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: load [take] [[AB]] : $*K
// CHECK: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: destroy_addr [[AA]] : $*K
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @fs_partial_use_before_init : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t", type $Two
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%useFn = function_ref @useK : $@convention(thin) (@guaranteed K) -> ()
%va = apply %getFn() : $@convention(thin) () -> @owned K
%aa = struct_element_addr %root : $*Two, #Two.a
store %va to [init] %aa : $*K
%ab = struct_element_addr %root : $*Two, #Two.b
%lb = load [copy] %ab : $*K
apply %useFn(%lb) : $@convention(thin) (@guaranteed K) -> ()
destroy_value %lb : $K
destroy_addr %root : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// Each demanded, uninitialized leaf gets its own undef, in leaf order, even through nested fields.
// Leaf `p.a` is initialized, so it receives none.
// CHECK-LABEL: sil [ossa] @fs_nested_use_before_init :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK-NEXT: [[P:%.*]] = struct_element_addr [[ROOT]] : $*Nested, #Nested.p
// CHECK-NEXT: [[PB:%.*]] = struct_element_addr [[P]] : $*Two, #Two.b
// CHECK-NEXT: assign undef to [init] [[PB]] : $*K
// CHECK-NEXT: [[K:%.*]] = struct_element_addr [[ROOT]] : $*Nested, #Nested.k
// CHECK-NEXT: assign undef to [init] [[K]] : $*K
// CHECK-NOT: assign undef
// CHECK: } // end sil function
sil [ossa] @fs_nested_use_before_init : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Nested, var, name "n", type $Nested
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%useFn = function_ref @useK : $@convention(thin) (@guaranteed K) -> ()
%va = apply %getFn() : $@convention(thin) () -> @owned K
%p = struct_element_addr %root : $*Nested, #Nested.p
%pa = struct_element_addr %p : $*Two, #Two.a
store %va to [init] %pa : $*K
%pb = struct_element_addr %p : $*Two, #Two.b
%lb = load [copy] %pb : $*K
apply %useFn(%lb) : $@convention(thin) (@guaranteed K) -> ()
destroy_value %lb : $K
%k = struct_element_addr %root : $*Nested, #Nested.k
%lk = load [copy] %k : $*K
apply %useFn(%lk) : $@convention(thin) (@guaranteed K) -> ()
destroy_value %lk : $K
destroy_addr %root : $*Nested
dealloc_stack %0 : $*Nested
%t = tuple ()
return %t
}
//===----------------------------------------------------------------------===//
// destroy_addr removal: a `load [take]` that consumes the storage leaves a
// dominated `destroy_addr` as a redundant, so it is deleted.
//===----------------------------------------------------------------------===//
// CHECK-LABEL: sil [ossa] @stack_final_take_deletes_destroy_addr :
// CHECK: load [take] {{.*}} : $*C
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @stack_final_take_deletes_destroy_addr : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $C, let, name "x"
%1 = mark_uninitialized [var] %0 : $*C
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%vk = apply %getFn() : $@convention(thin) () -> @owned K
%c = struct $C (%vk : $K)
assign %c to %1 : $*C
%ld = load [copy] %1 : $*C // gets turned into a [take]
ignored_use %ld
destroy_value %ld
destroy_addr %1 : $*C // gets deleted
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @stack_take_dominates_branch_destroys :
// CHECK: load [take] {{.*}} : $*C
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @stack_take_dominates_branch_destroys : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $C, let, name "x"
%1 = mark_uninitialized [var] %0 : $*C
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%vk = apply %getFn() : $@convention(thin) () -> @owned K
%c = struct $C (%vk : $K)
assign %c to %1 : $*C
%ld = load [copy] %1 : $*C // gets turned into a [take]; dominates both destroys
ignored_use %ld
destroy_value %ld
cond_br undef, bb1, bb2
bb1:
destroy_addr %1 : $*C // gets deleted
br bb3
bb2:
destroy_addr %1 : $*C // gets deleted
br bb3
bb3:
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
//===----------------------------------------------------------------------===//
// copy_addr: a copy out of the source is a use demanding ownership, so a final
// such use becomes a [take]. The destination address views this as a def.
//===----------------------------------------------------------------------===//
// CHECK-LABEL: sil [ossa] @cpa_last_use_becomes_take :
// CHECK: bb0([[OUT:%.*]] : $*C, [[ARG:%.*]] : $*C):
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $C
// CHECK-NEXT: copy_addr [[ARG]] to [init] [[ROOT]] : $*C
// CHECK-NEXT: copy_addr [take] [[ROOT]] to [init] [[OUT]] : $*C
// CHECK-NEXT: dealloc_stack [[ROOT]]
// CHECK: } // end sil function
sil [ossa] @cpa_last_use_becomes_take : $@convention(thin) (@in_guaranteed C) -> @out C {
bb0(%out : $*C, %arg : $*C):
%0 = alloc_stack [lexical] [var_decl] $C, let, name "x"
specify_test "lifetime_resolution_resolve %0"
copy_addr %arg to [init] %0 : $*C
copy_addr %0 to [init] %out : $*C
destroy_addr %0 : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @cpa_interior_copy_stays_copy :
// CHECK: bb0([[OUT:%.*]] : $*C, [[ARG:%.*]] : $*C):
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $C
// CHECK: copy_addr [[ROOT]] to [init] [[OUT]] : $*C
// CHECK-NEXT: [[TMP:%.*]] = alloc_stack $C
// CHECK-NEXT: copy_addr [take] [[ROOT]] to [init] [[TMP]] : $*C
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @cpa_interior_copy_stays_copy : $@convention(thin) (@in_guaranteed C) -> @out C {
bb0(%out : $*C, %arg : $*C):
%0 = alloc_stack [lexical] [var_decl] $C, let, name "x"
specify_test "lifetime_resolution_resolve %0"
copy_addr %arg to [init] %0 : $*C
copy_addr %0 to [init] %out : $*C
%tmp = alloc_stack $C
copy_addr %0 to [init] %tmp : $*C
%f = function_ref @consumeC_addr : $@convention(thin) (@in C) -> ()
apply %f(%tmp) : $@convention(thin) (@in C) -> ()
dealloc_stack %tmp : $*C
destroy_addr %0 : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// An `@in_guaranteed` argument is not yet understood, so it conservatively demands
// the storage: the copy before it stays a copy, and the destroy is kept.
// CHECK-LABEL: sil [ossa] @cpa_unknown_use_keeps_copy_and_destroy :
// CHECK: bb0([[OUT:%.*]] : $*C, [[ARG:%.*]] : $*C):
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $C
// CHECK: copy_addr [[ROOT]] to [init] [[OUT]] : $*C
// CHECK: apply {{%.*}}([[ROOT]]) : $@convention(thin) (@in_guaranteed C) -> ()
// CHECK-NEXT: destroy_addr [[ROOT]] : $*C
// CHECK: } // end sil function
sil [ossa] @cpa_unknown_use_keeps_copy_and_destroy : $@convention(thin) (@in_guaranteed C) -> @out C {
bb0(%out : $*C, %arg : $*C):
%0 = alloc_stack [lexical] [var_decl] $C, let, name "x"
specify_test "lifetime_resolution_resolve %0"
copy_addr %arg to [init] %0 : $*C
copy_addr %0 to [init] %out : $*C
%f = function_ref @useC_addr : $@convention(thin) (@in_guaranteed C) -> ()
apply %f(%0) : $@convention(thin) (@in_guaranteed C) -> ()
destroy_addr %0 : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// An `@out` argument initializes the storage.
// CHECK-LABEL: sil [ossa] @cpa_indirect_out_apply_is_def :
// CHECK: bb0([[OUT:%.*]] : $*C):
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $C
// CHECK: apply {{%.*}}([[ROOT]]) : $@convention(thin) () -> @out C
// CHECK: apply {{%.*}}([[ROOT]]) : $@convention(thin) (@in_guaranteed C) -> ()
// CHECK-NEXT: copy_addr [take] [[ROOT]] to [init] [[OUT]] : $*C
// CHECK-NOT: assign undef
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @cpa_indirect_out_apply_is_def : $@convention(thin) () -> @out C {
bb0(%out : $*C):
%0 = alloc_stack [lexical] [var_decl] $C, let, name "x"
specify_test "lifetime_resolution_resolve %0"
%f = function_ref @produceC : $@convention(thin) () -> @out C
apply %f(%0) : $@convention(thin) () -> @out C
%u = function_ref @useC_addr : $@convention(thin) (@in_guaranteed C) -> ()
apply %u(%0) : $@convention(thin) (@in_guaranteed C) -> ()
copy_addr %0 to [init] %out : $*C
destroy_addr %0 : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// An `@in` argument takes the storage.
// CHECK-LABEL: sil [ossa] @cpa_indirect_in_apply_is_take :
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $C
// CHECK: apply {{%.*}}([[ROOT]]) : $@convention(thin) () -> @out C
// CHECK-NEXT: apply {{%.*}}([[ROOT]]) : $@convention(thin) (@in C) -> ()
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @cpa_indirect_in_apply_is_take : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $C, let, name "x"
specify_test "lifetime_resolution_resolve %0"
%gen = function_ref @produceC : $@convention(thin) () -> @out C
%take = function_ref @consumeC_addr : $@convention(thin) (@in C) -> ()
apply %gen(%0) : $@convention(thin) () -> @out C
apply %take(%0) : $@convention(thin) (@in C) -> ()
destroy_addr %0 : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// For an @in use that has subsequent demand, we introduce a copy.
//
// CHECK-LABEL: sil [ossa] @cpa_indirect_in_apply_is_take_twice :
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $C
// CHECK: apply {{%.*}}([[ROOT]]) : $@convention(thin) () -> @out C
// CHECK: [[ALLOC:%.*]] = alloc_stack $C
// CHECK-NEXT: copy_addr [[ROOT]] to [init] [[ALLOC]] : $*C
// CHECK-NEXT: apply {{%.*}}([[ALLOC]]) : $@convention(thin) (@in C) -> ()
// CHECK-NEXT: dealloc_stack [[ALLOC]]
// CHECK: apply {{%.*}}([[ROOT]]) : $@convention(thin) (@in C) -> ()
// CHECK-NOT: destroy_addr
// CHECK: dealloc_stack [[ROOT]]
// CHECK: } // end sil function
sil [ossa] @cpa_indirect_in_apply_is_take_twice : $@convention(thin) () -> () {
bb0:
%x = alloc_stack [lexical] [var_decl] $C, let, name "x"
specify_test "lifetime_resolution_resolve %x"
%genC = function_ref @produceC : $@convention(thin) () -> @out C
%takeC = function_ref @consumeC_addr : $@convention(thin) (@in C) -> ()
apply %genC(%x) : $@convention(thin) () -> @out C
apply %takeC(%x) : $@convention(thin) (@in C) -> ()
apply %takeC(%x) : $@convention(thin) (@in C) -> ()
destroy_addr %x : $*C
dealloc_stack %x : $*C
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @cpa_indirect_in_apply_is_take_twice_nc :
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $NC
// CHECK: apply {{%.*}}([[ROOT]]) : $@convention(thin) () -> @out NC
// CHECK: [[ALLOC:%.*]] = alloc_stack $NC
// CHECK-NEXT: copy_addr [[ROOT]] to [init] [[ALLOC]] : $*NC
// CHECK-NEXT: diagnose [unpermitted_copy] [[ROOT]]
// CHECK-NEXT: apply {{%.*}}([[ALLOC]]) : $@convention(thin) (@in NC) -> ()
// CHECK-NEXT: dealloc_stack [[ALLOC]]
// CHECK: apply {{%.*}}([[ROOT]]) : $@convention(thin) (@in NC) -> ()
// CHECK-NOT: destroy_addr
// CHECK: dealloc_stack [[ROOT]]
// CHECK: } // end sil function
sil [ossa] @cpa_indirect_in_apply_is_take_twice_nc : $@convention(thin) () -> () {
bb0:
%y = alloc_stack [lexical] [var_decl] $NC, let, name "y"
specify_test "lifetime_resolution_resolve %y"
%genNC = function_ref @getNC_addr : $@convention(thin) () -> @out NC
%takeNC = function_ref @consumeNC_addr : $@convention(thin) (@in NC) -> ()
apply %genNC(%y) : $@convention(thin) () -> @out NC
apply %takeNC(%y) : $@convention(thin) (@in NC) -> ()
apply %takeNC(%y) : $@convention(thin) (@in NC) -> ()
destroy_addr %y : $*NC
dealloc_stack %y : $*NC
%t = tuple ()
return %t
}
// SILGen's shape for a generic argument `var x = t; consume(x); x = t; return x`.
// when not using opaque values. Since `x` was consumed, the reassignment is an initialization.
//
// CHECK-LABEL: sil [ossa] @cpa_reassign_kills_demand :
// CHECK: bb0([[OUT:%.*]] : $*C, [[ARG:%.*]] : $*C):
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK-NEXT: copy_addr [[ARG]] to [init] [[ROOT]] : $*C
// CHECK: [[R1:%.*]] = begin_access [read] [unknown] [[ROOT]] : $*C
// CHECK-NEXT: copy_addr [take] [[R1]] to [init] {{%.*}} : $*C
// CHECK: [[M:%.*]] = begin_access [modify] [unknown] [[ROOT]] : $*C
// CHECK-NEXT: copy_addr [take] {{%.*}} to [init] [[M]] : $*C
// CHECK: [[R2:%.*]] = begin_access [read] [unknown] [[ROOT]] : $*C
// CHECK-NEXT: copy_addr [take] [[R2]] to [init] [[OUT]] : $*C
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @cpa_reassign_kills_demand : $@convention(thin) (@in_guaranteed C) -> @out C {
bb0(%out : $*C, %arg : $*C):
%0 = alloc_stack [lexical] [var_decl] $C, var, name "x"
%mu = mark_uninitialized [var] %0 : $*C
specify_test "lifetime_resolution_resolve %0"
copy_addr %arg to [init] %mu : $*C
%tmp = alloc_stack $C
%r = begin_access [read] [unknown] %mu : $*C
copy_addr %r to [init] %tmp : $*C
end_access %r : $*C
%f = function_ref @consumeC_addr : $@convention(thin) (@in C) -> ()
apply %f(%tmp) : $@convention(thin) (@in C) -> ()
dealloc_stack %tmp : $*C
%tmp2 = alloc_stack $C
copy_addr %arg to [init] %tmp2 : $*C
%m = begin_access [modify] [unknown] %mu : $*C
copy_addr [take] %tmp2 to %m : $*C
end_access %m : $*C
dealloc_stack %tmp2 : $*C
%r2 = begin_access [read] [unknown] %mu : $*C
copy_addr %r2 to [init] %out : $*C
end_access %r2 : $*C
destroy_addr %mu : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// Consumed on every path, so the destroy in the merge block is deleted.
// CHECK-LABEL: sil [ossa] @cpa_consume_both_branches :
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $C
// CHECK: bb1:
// CHECK-NEXT: copy_addr [take] [[ROOT]] to [init] {{%.*}} : $*C
// CHECK: bb2:
// CHECK-NEXT: copy_addr [take] [[ROOT]] to [init] {{%.*}} : $*C
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @cpa_consume_both_branches : $@convention(thin) (@in_guaranteed C) -> () {
bb0(%arg : $*C):
%0 = alloc_stack [lexical] [var_decl] $C, let, name "x"
specify_test "lifetime_resolution_resolve %0"
copy_addr %arg to [init] %0 : $*C
%f = function_ref @consumeC_addr : $@convention(thin) (@in C) -> ()
%tmp = alloc_stack $C
cond_br undef, bb1, bb2
bb1:
copy_addr %0 to [init] %tmp : $*C
br bb3
bb2:
copy_addr %0 to [init] %tmp : $*C
br bb3
bb3:
apply %f(%tmp) : $@convention(thin) (@in C) -> ()
dealloc_stack %tmp : $*C
destroy_addr %0 : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// The next iteration still demands the storage, so the copy in the loop stays a copy.
// CHECK-LABEL: sil [ossa] @cpa_loop_consume_stays_copy :
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $C
// CHECK: bb1:
// CHECK: copy_addr [[ROOT]] to [init] {{%.*}} : $*C
// CHECK: bb3:
// CHECK-NEXT: destroy_addr [[ROOT]] : $*C
// CHECK: } // end sil function
sil [ossa] @cpa_loop_consume_stays_copy : $@convention(thin) (@in_guaranteed C) -> () {
bb0(%arg : $*C):
%0 = alloc_stack [lexical] [var_decl] $C, let, name "x"
specify_test "lifetime_resolution_resolve %0"
copy_addr %arg to [init] %0 : $*C
%f = function_ref @consumeC_addr : $@convention(thin) (@in C) -> ()
br bb1
bb1:
%tmp = alloc_stack $C
copy_addr %0 to [init] %tmp : $*C
apply %f(%tmp) : $@convention(thin) (@in C) -> ()
dealloc_stack %tmp : $*C
cond_br undef, bb2, bb3
bb2:
br bb1
bb3:
destroy_addr %0 : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// Reinitializing within the loop body permits the copy before it to be a take.
// CHECK-LABEL: sil [ossa] @cpa_loop_reinit_is_take :
// CHECK: bb0([[ARG:%.*]] : $*C):
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $C
// CHECK: bb1:
// CHECK: copy_addr [take] [[ROOT]] to [init] {{%.*}} : $*C
// CHECK: copy_addr [[ARG]] to [init] [[ROOT]] : $*C
// CHECK: bb3:
// CHECK-NEXT: destroy_addr [[ROOT]] : $*C
// CHECK: } // end sil function
sil [ossa] @cpa_loop_reinit_is_take : $@convention(thin) (@in_guaranteed C) -> () {
bb0(%arg : $*C):
%0 = alloc_stack [lexical] [var_decl] $C, var, name "x"
specify_test "lifetime_resolution_resolve %0"
copy_addr %arg to [init] %0 : $*C
%f = function_ref @consumeC_addr : $@convention(thin) (@in C) -> ()
br bb1
bb1:
%tmp = alloc_stack $C
copy_addr %0 to [init] %tmp : $*C
apply %f(%tmp) : $@convention(thin) (@in C) -> ()
dealloc_stack %tmp : $*C
copy_addr %arg to [init] %0 : $*C
cond_br undef, bb2, bb3
bb2:
br bb1
bb3:
destroy_addr %0 : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// A copy_addr of a single field is tracked independently of its sibling.
// CHECK-LABEL: sil [ossa] @cpa_field_source :
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $Two
// CHECK: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: copy_addr [take] [[AA]] to [init] {{%.*}} : $*K
// CHECK: [[AB:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: load [take] [[AB]] : $*K
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @cpa_field_source : $@convention(thin) (@owned Two) -> () {
bb0(%w : @owned $Two):
%0 = alloc_stack [lexical] [var_decl] $Two, let, name "t"
specify_test "lifetime_resolution_resolve %0"
store %w to [init] %0 : $*Two
%kt = alloc_stack $K
%aa = struct_element_addr %0 : $*Two, #Two.a
copy_addr %aa to [init] %kt : $*K
%f = function_ref @consumeK_addr : $@convention(thin) (@in K) -> ()
apply %f(%kt) : $@convention(thin) (@in K) -> ()
dealloc_stack %kt : $*K
%ab = struct_element_addr %0 : $*Two, #Two.b
%lb = load [copy] %ab : $*K
%c = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
apply %c(%lb) : $@convention(thin) (@owned K) -> ()
destroy_addr %0 : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// A whole-value copy_addr demands every field, so an earlier take of a field is
// legalized into a copy.
// CHECK-LABEL: sil [ossa] @cpa_field_take_then_whole :
// CHECK: bb0([[OUT:%.*]] : $*Two, {{%.*}} : @owned $Two):
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $Two
// CHECK: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: [[LA:%.*]] = load [copy] [[AA]] : $*K
// CHECK-NEXT: diagnose [unpermitted_copy] [[LA]] : $K
// CHECK: copy_addr [take] [[ROOT]] to [init] [[OUT]] : $*Two
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @cpa_field_take_then_whole : $@convention(thin) (@owned Two) -> @out Two {
bb0(%out : $*Two, %w : @owned $Two):
%0 = alloc_stack [lexical] [var_decl] $Two, let, name "t"
specify_test "lifetime_resolution_resolve %0"
store %w to [init] %0 : $*Two
%aa = struct_element_addr %0 : $*Two, #Two.a
%la = load [take] %aa : $*K
%c = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
apply %c(%la) : $@convention(thin) (@owned K) -> ()
copy_addr %0 to [init] %out : $*Two
destroy_addr %0 : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// Initializing each field separately, one of them with a copy_addr, defines the whole.
// CHECK-LABEL: sil [ossa] @cpa_field_defs_then_whole :
// CHECK: bb0([[OUT:%.*]] : $*Two, [[KA:%.*]] : $*K, {{%.*}} : @owned $K):
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $Two
// CHECK-NEXT: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: copy_addr [[KA]] to [init] [[AA]] : $*K
// CHECK: copy_addr [take] [[ROOT]] to [init] [[OUT]] : $*Two
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @cpa_field_defs_then_whole : $@convention(thin) (@in_guaranteed K, @owned K) -> @out Two {
bb0(%out : $*Two, %ka : $*K, %kb : @owned $K):
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t"
specify_test "lifetime_resolution_resolve %0"
%aa = struct_element_addr %0 : $*Two, #Two.a
copy_addr %ka to [init] %aa : $*K
%ab = struct_element_addr %0 : $*Two, #Two.b
store %kb to [init] %ab : $*K
copy_addr %0 to [init] %out : $*Two
destroy_addr %0 : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// A copy_addr whose source and destination are both fields of the storage: `b` is
// consumed then reinitialized from `a`, which the whole-value copy_addr still demands.
// CHECK-LABEL: sil [ossa] @cpa_field_to_field_same_root :
// CHECK: bb0([[OUT:%.*]] : $*Two, {{%.*}} : @owned $Two):
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $Two
// CHECK: [[AB:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: load [take] [[AB]] : $*K
// CHECK: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: [[AB2:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: copy_addr [[AA]] to [init] [[AB2]] : $*K
// CHECK-NEXT: copy_addr [take] [[ROOT]] to [init] [[OUT]] : $*Two
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @cpa_field_to_field_same_root : $@convention(thin) (@owned Two) -> @out Two {
bb0(%out : $*Two, %w : @owned $Two):
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t"
specify_test "lifetime_resolution_resolve %0"
store %w to [init] %0 : $*Two
%ab = struct_element_addr %0 : $*Two, #Two.b
%lb = load [take] %ab : $*K
%c = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
apply %c(%lb) : $@convention(thin) (@owned K) -> ()
%aa = struct_element_addr %0 : $*Two, #Two.a
%ab2 = struct_element_addr %0 : $*Two, #Two.b
copy_addr %aa to [init] %ab2 : $*K
copy_addr %0 to [init] %out : $*Two
destroy_addr %0 : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @cpa_nc_last_use :
// CHECK: bb0([[OUT:%.*]] : $*NC, {{%.*}} : @owned $NC):
// CHECK: [[ROOT:%.*]] = alloc_stack [lexical] [var_decl] $NC
// CHECK: copy_addr [take] [[ROOT]] to [init] [[OUT]] : $*NC
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @cpa_nc_last_use : $@convention(thin) (@owned NC) -> @out NC {
bb0(%out : $*NC, %v : @owned $NC):
%0 = alloc_stack [lexical] [var_decl] $NC, let, name "x"
specify_test "lifetime_resolution_resolve %0"
store %v to [init] %0 : $*NC
copy_addr %0 to [init] %out : $*NC
destroy_addr %0 : $*NC
dealloc_stack %0 : $*NC
%t = tuple ()
return %t
}
// The box's own lifetime end conservatively demands its contents.
// CHECK-LABEL: sil [ossa] @cpa_box_copy_stays_copy :
// CHECK: bb0([[OUT:%.*]] : $*C, {{%.*}} : $*C):
// CHECK: [[PB:%.*]] = project_box {{%.*}} : ${ var C }, 0
// CHECK: [[R:%.*]] = begin_access [read] [unknown] [[PB]] : $*C
// CHECK-NEXT: copy_addr [[R]] to [init] [[OUT]] : $*C
// CHECK: } // end sil function
sil [ossa] @cpa_box_copy_stays_copy : $@convention(thin) (@in_guaranteed C) -> @out C {
bb0(%out : $*C, %arg : $*C):
%b = alloc_box ${ var C }, var, name "x"
%bb = begin_borrow [lexical] [var_decl] %b : ${ var C }
%pb = project_box %bb : ${ var C }, 0
specify_test "lifetime_resolution_resolve %b"
copy_addr %arg to [init] %pb : $*C
%r = begin_access [read] [unknown] %pb : $*C
copy_addr %r to [init] %out : $*C
end_access %r : $*C
end_borrow %bb : ${ var C }
destroy_value %b : ${ var C }
%t = tuple ()
return %t
}
// `var a = ...; var b = a; return b`: resolving both `a` and `b` turns this into all takes.
// CHECK-LABEL: sil [ossa] @cpa_chain_two_roots :
// CHECK: [[A:%.*]] = alloc_stack [lexical] [var_decl] $C, var, name "a"
// CHECK-NEXT: [[B:%.*]] = alloc_stack [lexical] [var_decl] $C, var, name "b"
// CHECK: [[RA:%.*]] = begin_access [read] [unknown] [[A]] : $*C
// CHECK-NEXT: copy_addr [take] [[RA]] to [init] [[B]] : $*C
// CHECK: [[RB:%.*]] = begin_access [read] [unknown] [[B]] : $*C
// CHECK-NEXT: [[L:%.*]] = load [take] [[RB]] : $*C
// CHECK-NOT: destroy_addr
// CHECK: return [[L]]
// CHECK: } // end sil function
sil [ossa] @cpa_chain_two_roots : $@convention(thin) (@owned C) -> @owned C {
bb0(%v : @owned $C):
%0 = alloc_stack [lexical] [var_decl] $C, var, name "a"
%1 = alloc_stack [lexical] [var_decl] $C, var, name "b"
specify_test "lifetime_resolution_resolve %1"
specify_test "lifetime_resolution_resolve %0"
store %v to [init] %0 : $*C
%r = begin_access [read] [unknown] %0 : $*C
copy_addr %r to [init] %1 : $*C
end_access %r : $*C
%r2 = begin_access [read] [unknown] %1 : $*C
%l = load [copy] %r2 : $*C
end_access %r2 : $*C
destroy_addr %1 : $*C
dealloc_stack %1 : $*C
destroy_addr %0 : $*C
dealloc_stack %0 : $*C
return %l
}
//===----------------------------------------------------------------------===//
// Partial destroys: a destroy_addr of partially available storage is replaced
// by destroys of only its available fields, testing a control variable for
// those that are conditionally available.
//===----------------------------------------------------------------------===//
// Statically known per-field availability needs no control variable.
// CHECK-LABEL: sil [ossa] @pd_static_field :
// CHECK-NOT: $Builtin.Int
// CHECK: [[STK:%.*]] = alloc_stack [lexical] [var_decl] $Two
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var] [[STK]]
// CHECK: store {{%.*}} to [init] {{%.*}} : $*K
// CHECK-NEXT: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: destroy_addr [[AA]] : $*K
// CHECK-NEXT: dealloc_stack [[STK]]
// CHECK: } // end sil function
sil [ossa] @pd_static_field : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t", type $Two
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%va = apply %getFn() : $@convention(thin) () -> @owned K
%aa = struct_element_addr %root : $*Two, #Two.a
store %va to [init] %aa : $*K
destroy_addr %root : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @pd_conditional_field :
// CHECK: [[CV:%.*]] = alloc_stack $Builtin.Int2
// CHECK: [[STK:%.*]] = alloc_stack [dynamic_lifetime] [lexical] [var_decl] $Two
// CHECK: [[Z:%.*]] = integer_literal $Builtin.Int2, 0
// CHECK-NEXT: store [[Z]] to [trivial] [[CV]]
// CHECK-NEXT: [[ROOT:%.*]] = mark_uninitialized [var] [[STK]]
// CHECK: bb1:
// CHECK: [[OLD:%.*]] = load [trivial] [[CV]]
// CHECK-NEXT: [[BIT:%.*]] = integer_literal $Builtin.Int2, 1
// CHECK-NEXT: [[NEW:%.*]] = builtin "or_Int2"([[OLD]] : $Builtin.Int2, [[BIT]] : $Builtin.Int2)
// CHECK-NEXT: store [[NEW]] to [trivial] [[CV]]
// CHECK-NEXT: store {{%.*}} to [init] {{%.*}} : $*K
// CHECK: bb3:
// CHECK-NEXT: [[V:%.*]] = load [trivial] [[CV]]
// CHECK-NEXT: [[M:%.*]] = integer_literal $Builtin.Int2, 1
// CHECK-NEXT: [[AND:%.*]] = builtin "and_Int2"([[V]] : $Builtin.Int2, [[M]] : $Builtin.Int2)
// CHECK-NEXT: [[Z2:%.*]] = integer_literal $Builtin.Int2, 0
// CHECK-NEXT: [[TEST:%.*]] = builtin "cmp_ne_Int2"([[AND]] : $Builtin.Int2, [[Z2]] : $Builtin.Int2) : $Builtin.Int1
// CHECK-NEXT: cond_br [[TEST]], [[YES:bb[0-9]+]], [[NO:bb[0-9]+]]
// CHECK: [[YES]]:
// CHECK-NEXT: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: destroy_addr [[AA]] : $*K
// CHECK-NEXT: br [[CONT:bb[0-9]+]]
// CHECK: [[NO]]:
// CHECK-NEXT: br [[CONT]]
// CHECK: [[CONT]]:
// CHECK-NOT: destroy_addr
// CHECK: dealloc_stack [[STK]]
// CHECK: dealloc_stack [[CV]]
// CHECK-NEXT: return
// CHECK: } // end sil function
sil [ossa] @pd_conditional_field : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t", type $Two
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
cond_br undef, bb1, bb2
bb1:
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%va = apply %getFn() : $@convention(thin) () -> @owned K
%aa = struct_element_addr %root : $*Two, #Two.a
store %va to [init] %aa : $*K
br bb3
bb2:
br bb3
bb3:
destroy_addr %root : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// A single-field control variable is tested directly.
// CHECK-LABEL: sil [ossa] @pd_conditional_whole :
// CHECK: [[CV:%.*]] = alloc_stack $Builtin.Int1
// CHECK: [[STK:%.*]] = alloc_stack [dynamic_lifetime] [lexical] [var_decl] $K
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var] [[STK]]
// CHECK: bb1:
// CHECK: [[ONE:%.*]] = integer_literal $Builtin.Int1, -1
// CHECK-NEXT: store [[ONE]] to [trivial] [[CV]]
// CHECK-NEXT: store {{%.*}} to [init] [[ROOT]] : $*K
// CHECK: bb3:
// CHECK-NEXT: [[V:%.*]] = load [trivial] [[CV]]
// CHECK-NEXT: cond_br [[V]], [[YES:bb[0-9]+]], {{bb[0-9]+}}
// CHECK: [[YES]]:
// CHECK-NEXT: destroy_addr [[ROOT]] : $*K
// CHECK: } // end sil function
sil [ossa] @pd_conditional_whole : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $K, var, name "k", type $K
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
cond_br undef, bb1, bb2
bb1:
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%v = apply %getFn() : $@convention(thin) () -> @owned K
store %v to [init] %root : $*K
br bb3
bb2:
br bb3
bb3:
destroy_addr %root : $*K
dealloc_stack %0 : $*K
%t = tuple ()
return %t
}
// A take on one path clears its field's bit; the other field is destroyed unconditionally.
// CHECK-LABEL: sil [ossa] @pd_init_then_conditional_take :
// CHECK: [[CV:%.*]] = alloc_stack $Builtin.Int2
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[ALL:%.*]] = integer_literal $Builtin.Int2, -1
// CHECK-NEXT: store [[ALL]] to [trivial] [[CV]]
// CHECK-NEXT: store {{%.*}} to [init] [[ROOT]] : $*Two
// CHECK: bb1:
// CHECK: [[AB:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: [[OLD:%.*]] = load [trivial] [[CV]]
// CHECK-NEXT: [[KEEP:%.*]] = integer_literal $Builtin.Int2, 1
// CHECK-NEXT: [[NEW:%.*]] = builtin "and_Int2"([[OLD]] : $Builtin.Int2, [[KEEP]] : $Builtin.Int2)
// CHECK-NEXT: store [[NEW]] to [trivial] [[CV]]
// CHECK-NEXT: load [take] [[AB]] : $*K
// CHECK: bb3:
// CHECK-NEXT: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: destroy_addr [[AA]] : $*K
// CHECK-NEXT: [[V:%.*]] = load [trivial] [[CV]]
// CHECK-NEXT: [[M:%.*]] = integer_literal $Builtin.Int2, -2
// CHECK-NEXT: builtin "and_Int2"([[V]] : $Builtin.Int2, [[M]] : $Builtin.Int2)
// CHECK: cond_br {{%.*}}, [[YES:bb[0-9]+]], {{bb[0-9]+}}
// CHECK: [[YES]]:
// CHECK-NEXT: [[AB2:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.b
// CHECK-NEXT: destroy_addr [[AB2]] : $*K
// CHECK: } // end sil function
sil [ossa] @pd_init_then_conditional_take : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t", type $Two
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%va = apply %getFn() : $@convention(thin) () -> @owned K
%vb = apply %getFn() : $@convention(thin) () -> @owned K
%whole = struct $Two (%va : $K, %vb : $K)
store %whole to [init] %root : $*Two
cond_br undef, bb1, bb2
bb1:
%consFn = function_ref @consumeK : $@convention(thin) (@owned K) -> ()
%ab = struct_element_addr %root : $*Two, #Two.b
%lb = load [take] %ab : $*K
apply %consFn(%lb) : $@convention(thin) (@owned K) -> ()
br bb3
bb2:
br bb3
bb3:
destroy_addr %root : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @pd_nested_projection :
// CHECK: [[CV:%.*]] = alloc_stack $Builtin.Int3
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: bb3:
// CHECK-NEXT: [[V:%.*]] = load [trivial] [[CV]]
// CHECK-NEXT: [[M:%.*]] = integer_literal $Builtin.Int3, 2
// CHECK-NEXT: builtin "and_Int3"([[V]] : $Builtin.Int3, [[M]] : $Builtin.Int3)
// CHECK: cond_br {{%.*}}, [[YES:bb[0-9]+]], {{bb[0-9]+}}
// CHECK: [[YES]]:
// CHECK-NEXT: [[NP:%.*]] = struct_element_addr [[ROOT]] : $*Nested, #Nested.p
// CHECK-NEXT: [[NPB:%.*]] = struct_element_addr [[NP]] : $*Two, #Two.b
// CHECK-NEXT: destroy_addr [[NPB]] : $*K
// CHECK: [[NK:%.*]] = struct_element_addr [[ROOT]] : $*Nested, #Nested.k
// CHECK-NEXT: destroy_addr [[NK]] : $*K
// CHECK-NOT: destroy_addr
// CHECK: } // end sil function
sil [ossa] @pd_nested_projection : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Nested, var, name "n", type $Nested
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%vk = apply %getFn() : $@convention(thin) () -> @owned K
%nk = struct_element_addr %root : $*Nested, #Nested.k
store %vk to [init] %nk : $*K
cond_br undef, bb1, bb2
bb1:
%vb = apply %getFn() : $@convention(thin) () -> @owned K
%np = struct_element_addr %root : $*Nested, #Nested.p
%npb = struct_element_addr %np : $*Two, #Two.b
store %vb to [init] %npb : $*K
br bb3
bb2:
br bb3
bb3:
destroy_addr %root : $*Nested
dealloc_stack %0 : $*Nested
%t = tuple ()
return %t
}
// The control variable is reset at the root each time it's reached.
// CHECK-LABEL: sil [ossa] @pd_loop_resets_at_root :
// CHECK: bb0:
// CHECK-NEXT: [[CV:%.*]] = alloc_stack $Builtin.Int1
// CHECK-NEXT: br bb1
// CHECK: bb1:
// CHECK-NEXT: [[STK:%.*]] = alloc_stack [dynamic_lifetime] [lexical] [var_decl] $K
// CHECK-NEXT: [[Z:%.*]] = integer_literal $Builtin.Int1, 0
// CHECK-NEXT: store [[Z]] to [trivial] [[CV]]
// CHECK-NEXT: mark_uninitialized [var] [[STK]]
// CHECK: } // end sil function
sil [ossa] @pd_loop_resets_at_root : $@convention(thin) () -> () {
bb0:
br bb1
bb1:
%0 = alloc_stack [lexical] [var_decl] $K, var, name "k", type $K
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
cond_br undef, bb2, bb3
bb2:
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%v = apply %getFn() : $@convention(thin) () -> @owned K
store %v to [init] %root : $*K
br bb4
bb3:
br bb4
bb4:
destroy_addr %root : $*K
dealloc_stack %0 : $*K
cond_br undef, bb5, bb6
bb5:
br bb1
bb6:
%t = tuple ()
return %t
}
//===----------------------------------------------------------------------===//
// Conditional init/assign: every def becomes either an initialization or an
// assignment of its destination, based on the availability of the fields it
// overwrites. Partially available fields are destroyed before an initialization.
//===----------------------------------------------------------------------===//
// CHECK-LABEL: sil [ossa] @ca_assign_uninit_is_init :
// CHECK-NOT: $Builtin.Int
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: assign {{%.*}} to [init] [[ROOT]] : $*K
// CHECK-NEXT: destroy_addr [[ROOT]] : $*K
// CHECK: } // end sil function
sil [ossa] @ca_assign_uninit_is_init : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $K, var, name "k", type $K
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%v = apply %getFn() : $@convention(thin) () -> @owned K
assign %v to %root : $*K
destroy_addr %root : $*K
dealloc_stack %0 : $*K
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @ca_assign_init_is_reassign :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: store {{%.*}} to [init] [[ROOT]] : $*K
// CHECK: assign {{%.*}} to [reassign] [[ROOT]] : $*K
// CHECK-NEXT: destroy_addr [[ROOT]] : $*K
// CHECK: } // end sil function
sil [ossa] @ca_assign_init_is_reassign : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $K, var, name "k", type $K
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%v1 = apply %getFn() : $@convention(thin) () -> @owned K
store %v1 to [init] %root : $*K
%v2 = apply %getFn() : $@convention(thin) () -> @owned K
assign %v2 to %root : $*K
destroy_addr %root : $*K
dealloc_stack %0 : $*K
%t = tuple ()
return %t
}
// The conditionally initialized value is destroyed before the assign becomes an init.
// Its bit is only set after being tested.
// CHECK-LABEL: sil [ossa] @ca_conditional_assign :
// CHECK: [[CV:%.*]] = alloc_stack $Builtin.Int1
// CHECK: alloc_stack [dynamic_lifetime] [lexical] [var_decl] $K
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: bb1:
// CHECK: assign {{%.*}} to [init] [[ROOT]] : $*K
// CHECK: bb3:
// CHECK: [[V:%.*]] = load [trivial] [[CV]]
// CHECK-NEXT: cond_br [[V]], [[YES:bb[0-9]+]], {{bb[0-9]+}}
// CHECK: [[YES]]:
// CHECK-NEXT: destroy_addr [[ROOT]] : $*K
// CHECK: bb6:
// CHECK-NEXT: [[ONE:%.*]] = integer_literal $Builtin.Int1, -1
// CHECK-NEXT: store [[ONE]] to [trivial] [[CV]]
// CHECK-NEXT: assign {{%.*}} to [init] [[ROOT]] : $*K
// CHECK: destroy_addr [[ROOT]] : $*K
// CHECK: } // end sil function
sil [ossa] @ca_conditional_assign : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $K, var, name "k", type $K
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
cond_br undef, bb1, bb2
bb1:
%v1 = apply %getFn() : $@convention(thin) () -> @owned K
assign %v1 to %root : $*K
br bb3
bb2:
br bb3
bb3:
%v2 = apply %getFn() : $@convention(thin) () -> @owned K
assign %v2 to %root : $*K
destroy_addr %root : $*K
dealloc_stack %0 : $*K
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @ca_store_whole_over_field :
// CHECK-NOT: $Builtin.Int
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: [[WHOLE:%.*]] = struct $Two
// CHECK-NEXT: [[AA:%.*]] = struct_element_addr [[ROOT]] : $*Two, #Two.a
// CHECK-NEXT: destroy_addr [[AA]] : $*K
// CHECK-NEXT: store [[WHOLE]] to [init] [[ROOT]] : $*Two
// CHECK-NEXT: destroy_addr [[ROOT]] : $*Two
// CHECK: } // end sil function
sil [ossa] @ca_store_whole_over_field : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $Two, var, name "t", type $Two
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%va = apply %getFn() : $@convention(thin) () -> @owned K
%aa = struct_element_addr %root : $*Two, #Two.a
store %va to [init] %aa : $*K
%wa = apply %getFn() : $@convention(thin) () -> @owned K
%wb = apply %getFn() : $@convention(thin) () -> @owned K
%whole = struct $Two (%wa : $K, %wb : $K)
store %whole to [assign] %root : $*Two
destroy_addr %root : $*Two
dealloc_stack %0 : $*Two
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @ca_copy_addr_onto_conditional :
// CHECK: [[CV:%.*]] = alloc_stack $Builtin.Int1
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: bb3:
// CHECK-NEXT: [[V:%.*]] = load [trivial] [[CV]]
// CHECK-NEXT: cond_br [[V]], [[YES:bb[0-9]+]], {{bb[0-9]+}}
// CHECK: [[YES]]:
// CHECK-NEXT: [[CK:%.*]] = struct_element_addr [[ROOT]] : $*C, #C.k
// CHECK-NEXT: destroy_addr [[CK]] : $*K
// CHECK: bb6:
// CHECK: copy_addr {{%.*}} to [init] [[ROOT]] : $*C
// CHECK: } // end sil function
sil [ossa] @ca_copy_addr_onto_conditional : $@convention(thin) (@in_guaranteed C) -> () {
bb0(%arg : $*C):
%0 = alloc_stack [lexical] [var_decl] $C, var, name "c", type $C
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
cond_br undef, bb1, bb2
bb1:
copy_addr %arg to [init] %root : $*C
br bb3
bb2:
br bb3
bb3:
copy_addr %arg to %root : $*C
destroy_addr %root : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @ca_store_init_onto_live :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: store {{%.*}} to [init] [[ROOT]] : $*K
// CHECK: store {{%.*}} to [assign] [[ROOT]] : $*K
// CHECK-NEXT: destroy_addr [[ROOT]] : $*K
// CHECK: } // end sil function
sil [ossa] @ca_store_init_onto_live : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $K, var, name "k", type $K
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
%v1 = apply %getFn() : $@convention(thin) () -> @owned K
store %v1 to [init] %root : $*K
%v2 = apply %getFn() : $@convention(thin) () -> @owned K
store %v2 to [init] %root : $*K
destroy_addr %root : $*K
dealloc_stack %0 : $*K
%t = tuple ()
return %t
}
// An @out apply can't assign, so the live value is destroyed first.
// CHECK-LABEL: sil [ossa] @ca_indirect_out_onto_live :
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: apply {{%.*}}([[ROOT]])
// CHECK-NEXT: [[CK:%.*]] = struct_element_addr [[ROOT]] : $*C, #C.k
// CHECK-NEXT: destroy_addr [[CK]] : $*K
// CHECK-NEXT: apply {{%.*}}([[ROOT]])
// CHECK-NEXT: destroy_addr [[ROOT]] : $*C
// CHECK: } // end sil function
sil [ossa] @ca_indirect_out_onto_live : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $C, var, name "c", type $C
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%f = function_ref @produceC : $@convention(thin) () -> @out C
apply %f(%root) : $@convention(thin) () -> @out C
apply %f(%root) : $@convention(thin) () -> @out C
destroy_addr %root : $*C
dealloc_stack %0 : $*C
%t = tuple ()
return %t
}
// CHECK-LABEL: sil [ossa] @ca_tuple_addr_constructor :
// CHECK: [[CV:%.*]] = alloc_stack $Builtin.Int2
// CHECK: [[ROOT:%.*]] = mark_uninitialized [var]
// CHECK: bb3:
// CHECK: cond_br {{%.*}}, [[YES:bb[0-9]+]], {{bb[0-9]+}}
// CHECK: [[YES]]:
// CHECK-NEXT: [[T0:%.*]] = tuple_element_addr [[ROOT]] : $*(K, K), 0
// CHECK-NEXT: destroy_addr [[T0]] : $*K
// CHECK: bb6:
// CHECK-NEXT: [[ALL:%.*]] = integer_literal $Builtin.Int2, -1
// CHECK-NEXT: store [[ALL]] to [trivial] [[CV]]
// CHECK-NEXT: tuple_addr_constructor [init] [[ROOT]] : $*(K, K)
// CHECK: } // end sil function
sil [ossa] @ca_tuple_addr_constructor : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack [lexical] [var_decl] $(K, K), var, name "t", type $(K, K)
%root = mark_uninitialized [var] %0
specify_test "lifetime_resolution_resolve %0"
%getFn = function_ref @getK : $@convention(thin) () -> @owned K
cond_br undef, bb1, bb2
bb1:
%v0 = apply %getFn() : $@convention(thin) () -> @owned K
%t0 = tuple_element_addr %root : $*(K, K), 0
store %v0 to [init] %t0 : $*K
br bb3
bb2:
br bb3
bb3:
%a = apply %getFn() : $@convention(thin) () -> @owned K
%b = apply %getFn() : $@convention(thin) () -> @owned K
tuple_addr_constructor [assign] %root : $*(K, K) with (%a : $K, %b : $K)
destroy_addr %root : $*(K, K)
dealloc_stack %0 : $*(K, K)
%t = tuple ()
return %t
}