Files
swift-mirror/test/DebugInfo/simplify_alloc_stack.sil
Erik Eckstein 1501acc7d8 SimplifyAllocStack: don't retype the debug_value of an existential alloc_stack
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
2026-10-12 09:51:48 +02:00

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
}