Files
swift-mirror/test/SIL/cloning.sil
Erik Eckstein 0f0aa0c17b Optimizer: require that there are no unreachable blocks and infinite loops in OSSA
These two new invariants eliminate corner cases which caused bugs if optimization didn't handle them.
Also, it will significantly simplify lifetime completion.

The implementation basically consists of these changes:
* add a flag in SILFunction which tells optimization if they need to take care of infinite loops
* add a utility to break infinite loops
* let all optimizations remove unreachable blocks and break infinite loops if necessary
* add verification to check the new SIL invariants

The new `breakIfniniteLoops` utility breaks infinite loops in the control flow by inserting an "artificial" loop exit to a new dead-end block with an `unreachable`.
It inserts a `cond_br` with a `builtin "infinite_loop_true_condition"`:
```
bb0:
  br bb1
bb1:
  br bb1              // back-end branch
```
->
```
bb0:
  br bb1
bb1:
  %1 = builtin "infinite_loop_true_condition"() // always true, but the compiler doesn't know
  cond_br %1, bb2, bb3
bb2:                  // new back-end block
  br bb1
bb3:                  // new dead-end block
  unreachable
```
2026-01-22 17:41:23 +01:00

289 lines
12 KiB
Plaintext

// RUN: %target-sil-opt -sil-print-types -enable-sil-verify-all -inline %s | %FileCheck %s
// Check cloning of instructions.
sil_stage canonical
import Builtin
class X {
}
sil [heuristic_always_inline] @callee_alloc_ref_stack : $@convention(thin) () -> () {
bb0:
%0 = alloc_ref [stack] $X
dealloc_stack_ref %0 : $X
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil @caller_alloc_ref_stack : $@convention(thin) () -> ()
// CHECK: [[X:%[0-9]+]] = alloc_ref [stack] $X
// CHECK: dealloc_stack_ref [[X]] : $X
sil @caller_alloc_ref_stack : $@convention(thin) () -> () {
bb0:
%0 = function_ref @callee_alloc_ref_stack : $@convention(thin) () -> ()
%1 = apply %0() : $@convention(thin) () -> ()
%2 = tuple ()
return %2 : $()
}
sil [ossa] [heuristic_always_inline] @callee_begin_borrow : $@convention(thin) () -> () {
%instance = alloc_ref $X
%guaranteed_c = begin_borrow [lexical] %instance : $X
end_borrow %guaranteed_c : $X
%guaranteed_c2 = begin_borrow [pointer_escape] %instance : $X
end_borrow %guaranteed_c2 : $X
%guaranteed_c3 = begin_borrow [var_decl] %instance : $X
end_borrow %guaranteed_c3 : $X
destroy_value %instance : $X
%res = tuple ()
return %res : $()
}
// CHECK-LABEL: sil [ossa] @caller_begin_borrow_lexical
// CHECK: begin_borrow [lexical]
// CHECK: begin_borrow [pointer_escape]
// CHECK: begin_borrow [var_decl]
// CHECK-LABEL: } // end sil function 'caller_begin_borrow_lexical'
sil [ossa] @caller_begin_borrow_lexical : $@convention(thin) () -> () {
%callee_begin_borrow_lexical = function_ref @callee_begin_borrow : $@convention(thin) () -> ()
%res = apply %callee_begin_borrow_lexical() : $@convention(thin) () -> ()
return %res : $()
}
sil [ossa] [heuristic_always_inline] @callee_alloc_box : $@convention(thin) () -> () {
%b1 = alloc_box ${ var X }
dealloc_box %b1 : ${ var X }
%b2 = alloc_box [dynamic_lifetime] ${ var X }
dealloc_box %b2 : ${ var X }
%b3 = alloc_box [reflection] ${ var X }
dealloc_box %b3 : ${ var X }
%b4 = alloc_box [pointer_escape] ${ var X }
dealloc_box %b4 : ${ var X }
%b5 = alloc_box [moveable_value_debuginfo] ${ var X }
dealloc_box %b5 : ${ var X }
%b6 = alloc_box [dynamic_lifetime] [reflection] ${ var X }
dealloc_box %b6 : ${ var X }
%b7 = alloc_box [dynamic_lifetime] [pointer_escape] ${ var X }
dealloc_box %b7 : ${ var X }
%b8 = alloc_box [dynamic_lifetime] [moveable_value_debuginfo] ${ var X }
dealloc_box %b8 : ${ var X }
%b9 = alloc_box [reflection] [pointer_escape] ${ var X }
dealloc_box %b9 : ${ var X }
%b10 = alloc_box [reflection] [moveable_value_debuginfo] ${ var X }
dealloc_box %b10 : ${ var X }
%b11 = alloc_box [pointer_escape] [moveable_value_debuginfo] ${ var X }
dealloc_box %b11 : ${ var X }
%b12 = alloc_box [reflection] [pointer_escape] [moveable_value_debuginfo] ${ var X }
dealloc_box %b12 : ${ var X }
%b13 = alloc_box [dynamic_lifetime] [pointer_escape] [moveable_value_debuginfo] ${ var X }
dealloc_box %b13 : ${ var X }
%b14 = alloc_box [dynamic_lifetime] [reflection] [moveable_value_debuginfo] ${ var X }
dealloc_box %b14 : ${ var X }
%b15 = alloc_box [dynamic_lifetime] [reflection] [pointer_escape] ${ var X }
dealloc_box %b15 : ${ var X }
%b16 = alloc_box [dynamic_lifetime] [reflection] [pointer_escape] [moveable_value_debuginfo] ${ var X }
dealloc_box %b16 : ${ var X }
%res = tuple ()
return %res : $()
}
// CHECK-LABEL: sil [ossa] @caller_alloc_box{{.*}} {
// CHECK: alloc_box
// CHECK: alloc_box [dynamic_lifetime]
// CHECK: alloc_box [reflection]
// CHECK: alloc_box [pointer_escape]
// CHECK: alloc_box [moveable_value_debuginfo]
// CHECK: alloc_box [dynamic_lifetime] [reflection]
// CHECK: alloc_box [dynamic_lifetime] [pointer_escape]
// CHECK: alloc_box [dynamic_lifetime] [moveable_value_debuginfo]
// CHECK: alloc_box [reflection] [pointer_escape]
// CHECK: alloc_box [reflection] [moveable_value_debuginfo]
// CHECK: alloc_box [pointer_escape] [moveable_value_debuginfo]
// CHECK: alloc_box [reflection] [pointer_escape] [moveable_value_debuginfo]
// CHECK: alloc_box [dynamic_lifetime] [pointer_escape] [moveable_value_debuginfo]
// CHECK: alloc_box [dynamic_lifetime] [reflection] [moveable_value_debuginfo]
// CHECK: alloc_box [dynamic_lifetime] [reflection] [pointer_escape]
// CHECK: alloc_box [dynamic_lifetime] [reflection] [pointer_escape] [moveable_value_debuginfo]
// CHECK-LABEL: } // end sil function 'caller_alloc_box'
sil [ossa] @caller_alloc_box : $@convention(thin) () -> () {
%callee = function_ref @callee_alloc_box : $@convention(thin) () -> ()
%res = apply %callee() : $@convention(thin) () -> ()
return %res : $()
}
sil [ossa] @callee_alloc_stack : $@convention(thin) () -> () {
%instance = alloc_stack $Builtin.NativeObject
dealloc_stack %instance : $*Builtin.NativeObject
%instance2 = alloc_stack [dynamic_lifetime] $Builtin.NativeObject
dealloc_stack %instance2 : $*Builtin.NativeObject
%instance3 = alloc_stack [lexical] $Builtin.NativeObject
dealloc_stack %instance3 : $*Builtin.NativeObject
%instance5 = alloc_stack [var_decl] $Builtin.NativeObject
dealloc_stack %instance5 : $*Builtin.NativeObject
%instance4 = alloc_stack [lexical] [dynamic_lifetime] $Builtin.NativeObject
dealloc_stack %instance4 : $*Builtin.NativeObject
%instance6 = alloc_stack [dynamic_lifetime] [var_decl] $Builtin.NativeObject
dealloc_stack %instance6 : $*Builtin.NativeObject
%instance7 = alloc_stack [lexical] [var_decl] $Builtin.NativeObject
dealloc_stack %instance7 : $*Builtin.NativeObject
%instance8 = alloc_stack [lexical] [var_decl] [dynamic_lifetime] $Builtin.NativeObject
dealloc_stack %instance8 : $*Builtin.NativeObject
%res = tuple ()
return %res : $()
}
// CHECK-LABEL: sil [ossa] @caller_alloc_stack_lexical
// CHECK: alloc_stack
// CHECK: alloc_stack [dynamic_lifetime]
// CHECK: alloc_stack [lexical]
// CHECK: alloc_stack [var_decl]
// CHECK: alloc_stack [dynamic_lifetime] [lexical]
// CHECK: alloc_stack [dynamic_lifetime] [var_decl]
// CHECK: alloc_stack [lexical] [var_decl]
// CHECK: alloc_stack [dynamic_lifetime] [lexical] [var_decl]
// CHECK-LABEL: } // end sil function 'caller_alloc_stack_lexical'
sil [ossa] @caller_alloc_stack_lexical : $@convention(thin) () -> () {
%callee_alloc_stack = function_ref @callee_alloc_stack : $@convention(thin) () -> ()
%res = apply %callee_alloc_stack() : $@convention(thin) () -> ()
return %res : $()
}
sil [ossa] @callee_move_value : $@convention(thin) (@owned Builtin.NativeObject) -> @owned Builtin.NativeObject {
entry(%0 : @owned $Builtin.NativeObject):
%1 = move_value %0 : $Builtin.NativeObject
%2 = move_value [allows_diagnostics] %1 : $Builtin.NativeObject
%3 = move_value [lexical] %2 : $Builtin.NativeObject
%4 = move_value [allows_diagnostics] [lexical] %3 : $Builtin.NativeObject
%5 = move_value [pointer_escape] %4 : $Builtin.NativeObject
%6 = move_value [var_decl] %5 : $Builtin.NativeObject
return %6 : $Builtin.NativeObject
}
// CHECK-LABEL: sil [ossa] @caller_move_value {{.*}} {
// CHECK: {{bb[0-9]+}}([[REGISTER_0:%[^,]+]] :
// CHECK: [[REGISTER_1:%[^,]+]] = move_value [[REGISTER_0]]
// CHECK: [[REGISTER_2:%[^,]+]] = move_value [allows_diagnostics] [[REGISTER_1]]
// CHECK: [[REGISTER_3:%[^,]+]] = move_value [lexical] [[REGISTER_2]]
// CHECK: [[REGISTER_4:%[^,]+]] = move_value [allows_diagnostics] [lexical] [[REGISTER_3]]
// CHECK: [[REGISTER_5:%[^,]+]] = move_value [pointer_escape] [[REGISTER_4]]
// CHECK: [[REGISTER_6:%[^,]+]] = move_value [var_decl] [[REGISTER_5]]
// CHECK: return [[REGISTER_6]]
// CHECK-LABEL: } // end sil function 'caller_move_value'
sil [ossa] @caller_move_value : $@convention(thin) (@owned Builtin.NativeObject) -> @owned Builtin.NativeObject {
entry(%0 : @owned $Builtin.NativeObject):
%callee_move_value = function_ref @callee_move_value : $@convention(thin) (@owned Builtin.NativeObject) -> @owned Builtin.NativeObject
%res = apply %callee_move_value(%0) : $@convention(thin) (@owned Builtin.NativeObject) -> @owned Builtin.NativeObject
return %res : $Builtin.NativeObject
}
sil [ossa] @callee_debug_value : $@convention(thin) (@owned X) -> @owned X {
entry(%0 : @owned $X):
debug_value [trace] %0 : $X
return %0 : $X
}
sil [ossa] @caller_debug_value : $@convention(thin) (@owned X) -> @owned X {
entry(%0 : @owned $X):
%callee_debug_value = function_ref @callee_debug_value : $@convention(thin) (@owned X) -> @owned X
%res = apply %callee_debug_value(%0) : $@convention(thin) (@owned X) -> @owned X
return %res : $X
}
// CHECK-LABEL: sil [ossa] @caller_specify_test : {{.*}} {
// CHECK: specify_test "foo bar baz"
// CHECK-LABEL: } // end sil function 'caller_specify_test'
sil [heuristic_always_inline] [ossa] @callee_specify_test : $@convention(thin) () -> () {
entry:
specify_test "foo bar baz"
%retval = tuple ()
return %retval : $()
}
sil [ossa] @caller_specify_test : $@convention(thin) () -> () {
%callee = function_ref @callee_specify_test : $@convention(thin) () -> ()
%retval = apply %callee() : $@convention(thin) () -> ()
return %retval : $()
}
sil [ossa] [heuristic_always_inline] @callee_destroy_value : $@convention(thin) (@owned X) -> () {
entry(%x : @owned $X):
cond_br undef, good, bad
good:
cond_br undef, gleft, gright
gleft:
destroy_value %x : $X
br exit
gright:
destroy_value [poison] %x : $X
br exit
exit:
%retval = tuple()
return %retval : $()
bad:
cond_br undef, bleft, bright
bleft:
destroy_value [dead_end] %x : $X
unreachable
bright:
destroy_value [poison] [dead_end] %x : $X
unreachable
}
// CHECK-LABEL: sil [ossa] @caller_destroy_value : {{.*}} {
// CHECK: destroy_value
// CHECK: destroy_value [poison]
// CHECK: destroy_value [dead_end]
// CHECK: destroy_value [poison] [dead_end]
// CHECK-LABEL: } // end sil function 'caller_destroy_value'
sil [ossa] @caller_destroy_value : $@convention(thin) (@owned X) -> () {
entry(%x : @owned $X):
%callee = function_ref @callee_destroy_value : $@convention(thin) (@owned X) -> ()
%res = apply %callee(%x) : $@convention(thin) (@owned X) -> ()
return %res : $()
}
sil [ossa] [heuristic_always_inline] @callee_dealloc_box : $@convention(thin) () -> () {
entry:
%b = alloc_box ${ var X }
cond_br undef, exit, die
exit:
dealloc_box %b : ${ var X }
%retval = tuple()
return %retval : $()
die:
dealloc_box [dead_end] %b : ${ var X }
unreachable
}
// CHECK-LABEL: sil [ossa] @caller_dealloc_box : {{.*}} {
// CHECK: dealloc_box
// CHECK: dealloc_box [dead_end]
// CHECK-LABEL: } // end sil function 'caller_dealloc_box'
sil [ossa] @caller_dealloc_box : $@convention(thin) () -> () {
entry:
%callee = function_ref @callee_dealloc_box : $@convention(thin) () -> ()
%res = apply %callee() : $@convention(thin) () -> ()
return %res : $()
}
sil [transparent] [ossa] @getSize : $@convention(thin) <Type> (@thick Type.Type) -> Builtin.Word {
bb0(%ty : $@thick Type.Type):
%size = builtin "sizeof"<Type>() : $Builtin.Word
return %size : $Builtin.Word
}
// CHECK-LABEL: sil [ossa] @callee_builtin_type_dep_operand : {{.*}} {
// CHECK: open_existential_metatype
// CHECK-LABEL: } // end sil function 'callee_builtin_type_dep_operand'
sil [ossa] @callee_builtin_type_dep_operand : $@convention(thin) (@thick any Any.Type) -> Builtin.Word {
bb0(%0 : $@thick any Any.Type):
%ty = open_existential_metatype %0 : $@thick any Any.Type to $@thick (@opened("00000000-0000-0000-0000-000000000000", Any) Self).Type
%getSize = function_ref @getSize : $@convention(thin) <τ_0_0> (@thick τ_0_0.Type) -> Builtin.Word
%retval = apply %getSize<@opened("00000000-0000-0000-0000-000000000000", Any) Self>(%ty) : $@convention(thin) <τ_0_0> (@thick τ_0_0.Type) -> Builtin.Word
return %retval : $Builtin.Word
}