mirror of
https://github.com/apple/swift.git
synced 2026-11-04 00:12:11 +01:00
When replacing an existential alloc_stack with its concrete type, the debug_value of the alloc_stack was retyped to the concrete type. But all debug_values of a variable must have the same type, and there can be other debug_values of the variable in the function, e.g. a `debug_value undef` which SILGen emits when a variable is consumed. This resulted in a SIL verifier error: "Two variables with different type but same scope". Finding those other debug_values would require iterating over the whole function, which is not possible in an instruction simplification. Therefore kill the debug_value's operand instead. The debug_value of an existential archetype alloc_stack is still retyped, because an existential archetype cannot be the type of a source variable. rdar://189754686
178 lines
6.8 KiB
Plaintext
178 lines
6.8 KiB
Plaintext
// RUN: %target-sil-opt %s -simplification -simplify-instruction=alloc_stack | %FileCheck %s
|
|
|
|
import Swift
|
|
import Builtin
|
|
|
|
protocol P {
|
|
func foo()
|
|
}
|
|
|
|
public class C {}
|
|
|
|
public struct T: P {
|
|
let c: C
|
|
func foo()
|
|
}
|
|
|
|
// CHECK-LABEL: sil [ossa] @expand_enum_with_debug_value :
|
|
// CHECK: [[A:%[0-9]+]] = alloc_stack $Int
|
|
// CHECK: debug_value [[A]], var, name "x", type $Optional<Int>, transform {
|
|
// CHECK: bb0(%[[ARG:[0-9]+]] : $*Int):
|
|
// CHECK: %[[LOAD:[0-9]+]] = load %[[ARG]]
|
|
// CHECK: %[[ENUM:[0-9]+]] = enum $Optional<Int>, #Optional.some!enumelt, %[[LOAD]]
|
|
// CHECK: return %[[ENUM]]
|
|
// CHECK: }
|
|
// CHECK: } // end sil function 'expand_enum_with_debug_value'
|
|
sil [ossa] @expand_enum_with_debug_value : $@convention(thin) (Int) -> () {
|
|
bb0(%0 : $Int):
|
|
%1 = alloc_stack $Optional<Int>, var, name "x"
|
|
%2 = init_enum_data_addr %1, #Optional.some!enumelt
|
|
store %0 to [trivial] %2 : $*Int
|
|
inject_enum_addr %1, #Optional.some!enumelt
|
|
%5 = unchecked_take_enum_data_addr %1, #Optional.some!enumelt
|
|
destroy_addr %5
|
|
dealloc_stack %1
|
|
%r = tuple ()
|
|
return %r : $()
|
|
}
|
|
|
|
// When the enum payload is an opaque (existential) type, the debug_value
|
|
// cannot be salvaged with a load (IRGen can't emit it). It must be killed.
|
|
// CHECK-LABEL: sil [ossa] @expand_enum_with_opaque_payload :
|
|
// CHECK: alloc_stack $any P
|
|
// CHECK: debug_value undef : $*Optional<any P>, var, name "x", type $Optional<any P>, expr op_deref
|
|
// CHECK-NOT: transform
|
|
// CHECK: } // end sil function 'expand_enum_with_opaque_payload'
|
|
sil [ossa] @expand_enum_with_opaque_payload : $@convention(thin) (@in any P) -> () {
|
|
bb0(%0 : $*any P):
|
|
%1 = alloc_stack $Optional<any P>, var, name "x"
|
|
%2 = init_enum_data_addr %1 : $*Optional<any P>, #Optional.some!enumelt
|
|
copy_addr [take] %0 to [init] %2 : $*any P
|
|
inject_enum_addr %1 : $*Optional<any P>, #Optional.some!enumelt
|
|
%5 = unchecked_take_enum_data_addr %1 : $*Optional<any P>, #Optional.some!enumelt
|
|
destroy_addr %5 : $*any P
|
|
dealloc_stack %1 : $*Optional<any P>
|
|
%r = tuple ()
|
|
return %r : $()
|
|
}
|
|
|
|
struct Pair {
|
|
@_hasStorage var i: Int { get set }
|
|
@_hasStorage var o: Optional<Int> { get set }
|
|
init(i: Int, o: Optional<Int>)
|
|
}
|
|
|
|
// Test that multi-operand debug values are rewritten correctly.
|
|
// CHECK-LABEL: sil [ossa] @expand_enum_multi_operand :
|
|
// CHECK: [[A:%[0-9]+]] = alloc_stack $Int
|
|
// CHECK: debug_value (%0, [[A]]), var, name "x", type $Pair, transform {
|
|
// CHECK: bb0(%0 : $Int, %1 : $*Int):
|
|
// CHECK: %2 = load %1
|
|
// CHECK: %3 = enum $Optional<Int>, #Optional.some!enumelt, %2
|
|
// CHECK: %4 = struct $Pair (%0, %3)
|
|
// CHECK: return %4
|
|
// CHECK: }
|
|
// CHECK: } // end sil function 'expand_enum_multi_operand'
|
|
sil [ossa] @expand_enum_multi_operand : $@convention(thin) (Int, Int) -> () {
|
|
bb0(%0 : $Int, %1 : $Int):
|
|
%2 = alloc_stack $Optional<Int>
|
|
%3 = init_enum_data_addr %2, #Optional.some!enumelt
|
|
store %1 to [trivial] %3 : $*Int
|
|
inject_enum_addr %2, #Optional.some!enumelt
|
|
debug_value (%0 : $Int, %2 : $*Optional<Int>), var, name "x", type $Pair, transform {
|
|
bb0(%0 : $Int, %1 : $*Optional<Int>):
|
|
%2 = load %1 : $*Optional<Int>
|
|
%3 = struct $Pair (%0 : $Int, %2 : $Optional<Int>)
|
|
return %3 : $Pair
|
|
}
|
|
%5 = unchecked_take_enum_data_addr %2, #Optional.some!enumelt
|
|
destroy_addr %5
|
|
dealloc_stack %2
|
|
%r = tuple ()
|
|
return %r : $()
|
|
}
|
|
|
|
enum TwoCase {
|
|
case a(Int)
|
|
case b(Int)
|
|
}
|
|
|
|
// When the enum has multiple different case indices, we can't reconstruct
|
|
// the enum in debug info. The debug_value must be killed.
|
|
// CHECK-LABEL: sil [ossa] @expand_enum_mismatching_cases :
|
|
// CHECK: alloc_stack $Int
|
|
// CHECK: debug_value undef : $TwoCase, var, name "x"
|
|
// CHECK-NOT: transform
|
|
// CHECK: } // end sil function 'expand_enum_mismatching_cases'
|
|
sil [ossa] @expand_enum_mismatching_cases : $@convention(thin) (Int) -> () {
|
|
bb0(%0 : $Int):
|
|
%1 = alloc_stack $TwoCase, var, name "x"
|
|
cond_br undef, bb1, bb2
|
|
bb1:
|
|
%2 = init_enum_data_addr %1 : $*TwoCase, #TwoCase.a!enumelt
|
|
store %0 to [trivial] %2 : $*Int
|
|
inject_enum_addr %1 : $*TwoCase, #TwoCase.a!enumelt
|
|
br bb3
|
|
bb2:
|
|
%3 = init_enum_data_addr %1 : $*TwoCase, #TwoCase.b!enumelt
|
|
store %0 to [trivial] %3 : $*Int
|
|
inject_enum_addr %1 : $*TwoCase, #TwoCase.b!enumelt
|
|
br bb3
|
|
bb3:
|
|
%5 = unchecked_take_enum_data_addr %1 : $*TwoCase, #TwoCase.a!enumelt
|
|
destroy_addr %5 : $*Int
|
|
dealloc_stack %1 : $*TwoCase
|
|
%r = tuple ()
|
|
return %r : $()
|
|
}
|
|
|
|
// When an existential alloc_stack is replaced by its concrete type, the
|
|
// existential value cannot be recreated from a reconstruction block.
|
|
// Debug values which have a reconstruction block are killed as unsalvageable.
|
|
// CHECK-LABEL: sil [ossa] @kill_transform_operand_of_existential :
|
|
// CHECK: alloc_stack $T
|
|
// CHECK: debug_value undef : $*any P, var, name "x", type $Builtin.RawPointer, transform {
|
|
// CHECK-NEXT: bb0(%0 : $*any P):
|
|
// CHECK-NEXT: = address_to_pointer undef
|
|
// CHECK: }
|
|
// CHECK: } // end sil function 'kill_transform_operand_of_existential'
|
|
sil [ossa] @kill_transform_operand_of_existential : $@convention(thin) (@owned T) -> () {
|
|
bb0(%0 : @owned $T):
|
|
%1 = alloc_stack $any P
|
|
%2 = init_existential_addr %1, $T
|
|
store %0 to [init] %2
|
|
debug_value %1, var, name "x", type $Builtin.RawPointer, transform {
|
|
bb0(%0 : $*any P):
|
|
%p = address_to_pointer %0 : $*any P to $Builtin.RawPointer
|
|
return %p : $Builtin.RawPointer
|
|
}
|
|
%4 = open_existential_addr mutable_access %1 to $*@opened(1, P) Self
|
|
destroy_addr %4
|
|
dealloc_stack %1
|
|
%r = tuple ()
|
|
return %r : $()
|
|
}
|
|
|
|
// The debug_value of an existential alloc_stack which is replaced by its
|
|
// concrete type is killed and not retyped, because other debug_values of the
|
|
// variable, like the `undef` marker, would still have the existential type.
|
|
// CHECK-LABEL: sil [ossa] @kill_debug_value_of_existential :
|
|
// CHECK: alloc_stack $T
|
|
// CHECK: debug_value undef : $*any P, var, name "x", expr op_deref
|
|
// CHECK: debug_value undef : $*any P, var, name "x", expr op_deref
|
|
// CHECK: } // end sil function 'kill_debug_value_of_existential'
|
|
sil_scope 10 { loc "a.swift":1:1 parent @kill_debug_value_of_existential : $@convention(thin) (@owned T) -> () }
|
|
sil [ossa] @kill_debug_value_of_existential : $@convention(thin) (@owned T) -> () {
|
|
bb0(%0 : @owned $T):
|
|
%1 = alloc_stack $any P, scope 10
|
|
%2 = init_existential_addr %1, $T, scope 10
|
|
store %0 to [init] %2, scope 10
|
|
debug_value %1, var, name "x", expr op_deref, loc "a.swift":2:7, scope 10
|
|
debug_value undef : $*any P, var, name "x", expr op_deref, loc "a.swift":2:7, scope 10
|
|
%4 = open_existential_addr mutable_access %1 to $*@opened(10, P) Self, scope 10
|
|
destroy_addr %4, scope 10
|
|
dealloc_stack %1, scope 10
|
|
%r = tuple (), scope 10
|
|
return %r, scope 10
|
|
}
|