Files
swift-mirror/test/SILOptimizer/sil_combine_casts.sil
Erik Eckstein 9aff288be4 Optimizer: re-implement the pointer_to_address SILCombine peephole optimizations in swift
Which consists of
* removing redundant `address_to_pointer`-`pointer_to_address` pairs
* optimize `index_raw_pointer` of a manually computed stride to `index_addr`
* remove or increase the alignment based on a "assumeAlignment" builtin

This is a big code cleanup but also has some functional differences for the `address_to_pointer`-`pointer_to_address` pair removal:

* It's not done if the resulting SIL would result in a (detectable) use-after-dealloc_stack memory lifetime failure.
* It's not done if `copy_value`s must be inserted or borrow-scopes must be extended to comply with ownership rules (this was the task of the OwnershipRAUWHelper).

Inserting copies is bad anyway.
Extending borrow-scopes would only be required if the original lifetime of the pointer extends a borrow scope - which shouldn't happen in save code. Therefore this is a very rare case which is not worth handling.
2024-12-21 08:28:22 +01:00

354 lines
18 KiB
Plaintext

// RUN: %target-sil-opt -sil-print-types -enable-objc-interop -enforce-exclusivity=none -enable-sil-verify-all %s -sil-combine -sil-combine-disable-alloc-stack-opts | %FileCheck %s
sil_stage canonical
import Builtin
import Swift
class Klass {}
sil @returnInt : $@convention(thin) () -> Builtin.Int32
sil @use_anyobject : $@convention(thin) (@guaranteed AnyObject) -> ()
sil @use_nativeobject : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
// CHECK-LABEL: sil @optimize_convert_escape_to_noescape :
// CHECK: [[FN:%.*]] = function_ref @returnInt
// CHECK: apply [[FN]]()
// CHECK: } // end sil function 'optimize_convert_escape_to_noescape'
sil @optimize_convert_escape_to_noescape : $@convention(thin) () -> Builtin.Int32 {
bb0:
%0 = function_ref @returnInt : $@convention(thin) () -> Builtin.Int32
%1 = thin_to_thick_function %0 : $@convention(thin) () -> Builtin.Int32 to $@callee_guaranteed () -> Builtin.Int32
%2 = convert_escape_to_noescape %1 : $@callee_guaranteed () -> Builtin.Int32 to $@noescape @callee_guaranteed () -> Builtin.Int32
%4 = apply %2() : $@noescape @callee_guaranteed () -> Builtin.Int32
return %4 : $Builtin.Int32
}
// We have an extra thin_to_thick_function here since we do not yet have the
// optimization for eliminating it enabled in OSSA yet.
//
// CHECK-LABEL: sil [ossa] @optimize_convert_escape_to_noescape_ossa :
// CHECK: [[FN:%.*]] = function_ref @returnInt
// CHECK: apply [[FN]]()
// CHECK: } // end sil function 'optimize_convert_escape_to_noescape_ossa'
sil [ossa] @optimize_convert_escape_to_noescape_ossa : $@convention(thin) () -> Builtin.Int32 {
bb0:
%0 = function_ref @returnInt : $@convention(thin) () -> Builtin.Int32
%1 = thin_to_thick_function %0 : $@convention(thin) () -> Builtin.Int32 to $@callee_guaranteed () -> Builtin.Int32
%2 = convert_escape_to_noescape %1 : $@callee_guaranteed () -> Builtin.Int32 to $@noescape @callee_guaranteed () -> Builtin.Int32
%4 = apply %2() : $@noescape @callee_guaranteed () -> Builtin.Int32
destroy_value %2 : $@noescape @callee_guaranteed () -> Builtin.Int32
return %4 : $Builtin.Int32
}
//////////////////////////////
// ref_to_raw_pointer tests //
//////////////////////////////
// RefToRawPointer pointer consumption.
//
// (ref_to_raw_pointer (unchecked_ref_cast x))
// -> (ref_to_raw_pointer x)
//
// CHECK-LABEL: sil @ref_to_raw_pointer_unchecked_ref_cast_composition : $@convention(thin) (@guaranteed Klass) -> Builtin.RawPointer
// CHECK: bb0
// CHECK-NEXT: ref_to_raw_pointer
// CHECK-NEXT: return
// CHECK: } // end sil function 'ref_to_raw_pointer_unchecked_ref_cast_composition'
sil @ref_to_raw_pointer_unchecked_ref_cast_composition : $@convention(thin) (@guaranteed Klass) -> Builtin.RawPointer {
bb0(%0 : $Klass):
%1 = unchecked_ref_cast %0 : $Klass to $Builtin.NativeObject
%2 = ref_to_raw_pointer %1 : $Builtin.NativeObject to $Builtin.RawPointer
return %2 : $Builtin.RawPointer
}
// CHECK-LABEL: sil [ossa] @ref_to_raw_pointer_unchecked_ref_cast_composition_ossa_guaranteed : $@convention(thin) (@guaranteed Klass) -> Builtin.RawPointer
// CHECK: bb0
// CHECK-NEXT: ref_to_raw_pointer
// CHECK-NEXT: return
// CHECK: } // end sil function 'ref_to_raw_pointer_unchecked_ref_cast_composition_ossa_guaranteed'
sil [ossa] @ref_to_raw_pointer_unchecked_ref_cast_composition_ossa_guaranteed : $@convention(thin) (@guaranteed Klass) -> Builtin.RawPointer {
bb0(%0 : @guaranteed $Klass):
%1 = unchecked_ref_cast %0 : $Klass to $Builtin.NativeObject
%2 = ref_to_raw_pointer %1 : $Builtin.NativeObject to $Builtin.RawPointer
return %2 : $Builtin.RawPointer
}
// CHECK-LABEL: sil [ossa] @ref_to_raw_pointer_unchecked_ref_cast_composition_ossa_owned : $@convention(thin) (@owned Klass) -> Builtin.RawPointer
// CHECK: bb0([[ARG:%.*]] : @owned
// CHECK-NEXT: [[RESULT:%.*]] = ref_to_raw_pointer [[ARG]]
// CHECK-NEXT: [[CAST:%.*]] = unchecked_ref_cast [[ARG]]
// CHECK-NEXT: destroy_value [[CAST]]
// CHECK-NEXT: return [[RESULT]]
// CHECK: } // end sil function 'ref_to_raw_pointer_unchecked_ref_cast_composition_ossa_owned'
sil [ossa] @ref_to_raw_pointer_unchecked_ref_cast_composition_ossa_owned : $@convention(thin) (@owned Klass) -> Builtin.RawPointer {
bb0(%0 : @owned $Klass):
%1 = unchecked_ref_cast %0 : $Klass to $Builtin.NativeObject
%2 = ref_to_raw_pointer %1 : $Builtin.NativeObject to $Builtin.RawPointer
destroy_value %1 : $Builtin.NativeObject
return %2 : $Builtin.RawPointer
}
// CHECK-LABEL: sil @collapse_existential_pack_unpack_ref_to_raw_pointer :
// CHECK: bb0([[Ref:%.*]] : $Klass):
// CHECK-NOT: init_existential_ref
// CHECK-NOT: open_existential_ref
// CHECK: ref_to_raw_pointer [[Ref]]
// CHECK: } // end sil function 'collapse_existential_pack_unpack_ref_to_raw_pointer'
sil @collapse_existential_pack_unpack_ref_to_raw_pointer : $@convention(thin) (@guaranteed Klass) -> () {
bb0(%0 : $Klass):
%1 = init_existential_ref %0 : $Klass : $Klass, $AnyObject
%2 = open_existential_ref %1 : $AnyObject to $@opened("2CAE06CE-5F10-11E4-AF13-C82A1428F987", AnyObject) Self
%3 = ref_to_raw_pointer %2 : $@opened("2CAE06CE-5F10-11E4-AF13-C82A1428F987", AnyObject) Self to $Builtin.RawPointer
%4 = pointer_to_address %3 : $Builtin.RawPointer to [strict] $*Builtin.Word
%5 = integer_literal $Builtin.Word, 5
store %5 to %4: $*Builtin.Word
%6 = tuple ()
return %6 : $()
}
// CHECK-LABEL: sil [ossa] @collapse_existential_pack_unpack_ref_to_raw_pointer_ossa_guaranteed :
// CHECK: bb0([[Ref:%.*]] : @guaranteed $Klass):
// CHECK-NOT: init_existential_ref
// CHECK-NOT: open_existential_ref
// CHECK: ref_to_raw_pointer [[Ref]]
// CHECK: } // end sil function 'collapse_existential_pack_unpack_ref_to_raw_pointer_ossa_guaranteed'
sil [ossa] @collapse_existential_pack_unpack_ref_to_raw_pointer_ossa_guaranteed : $@convention(thin) (@guaranteed Klass) -> () {
bb0(%0 : @guaranteed $Klass):
%1 = init_existential_ref %0 : $Klass : $Klass, $AnyObject
%2 = open_existential_ref %1 : $AnyObject to $@opened("3CAE06CE-5F10-11E4-AF13-C82A1428F987", AnyObject) Self
%3 = ref_to_raw_pointer %2 : $@opened("3CAE06CE-5F10-11E4-AF13-C82A1428F987", AnyObject) Self to $Builtin.RawPointer
%4 = pointer_to_address %3 : $Builtin.RawPointer to [strict] $*Builtin.Word
%5 = integer_literal $Builtin.Word, 5
store %5 to [trivial] %4: $*Builtin.Word
%6 = tuple ()
return %6 : $()
}
// We need to hoist the ref_to_raw_pointer above the init_existential_ref since
// that is where %0 is live.
//
// CHECK-LABEL: sil [ossa] @collapse_existential_pack_unpack_ref_to_raw_pointer_ossa_owned :
// CHECK: bb0([[ARG:%.*]] : @owned $Klass):
// CHECK-NOT: init_existential_ref
// CHECK-NOT: open_existential_ref
// CHECK: [[PTR:%.*]] = ref_to_raw_pointer [[ARG]]
// CHECK: [[FORWARDED_ARG:%.*]] = init_existential_ref [[ARG]]
// CHECK: destroy_value [[FORWARDED_ARG]]
// CHECK: } // end sil function 'collapse_existential_pack_unpack_ref_to_raw_pointer_ossa_owned'
sil [ossa] @collapse_existential_pack_unpack_ref_to_raw_pointer_ossa_owned : $@convention(thin) (@owned Klass) -> () {
bb0(%0 : @owned $Klass):
%1 = init_existential_ref %0 : $Klass : $Klass, $AnyObject
%f = function_ref @use_anyobject : $@convention(thin) (@guaranteed AnyObject) -> ()
apply %f(%1) : $@convention(thin) (@guaranteed AnyObject) -> ()
%2 = open_existential_ref %1 : $AnyObject to $@opened("4CAE06CE-5F10-11E4-AF13-C82A1428F987", AnyObject) Self
%3 = ref_to_raw_pointer %2 : $@opened("4CAE06CE-5F10-11E4-AF13-C82A1428F987", AnyObject) Self to $Builtin.RawPointer
%4 = pointer_to_address %3 : $Builtin.RawPointer to [strict] $*Builtin.Word
%5 = integer_literal $Builtin.Word, 5
store %5 to [trivial] %4: $*Builtin.Word
destroy_value %2 : $@opened("4CAE06CE-5F10-11E4-AF13-C82A1428F987", AnyObject) Self
%6 = tuple ()
return %6 : $()
}
// (ref_to_raw_pointer (unchecked_ref_cast x)) -> (unchecked_trivial_bit_cast x)
//
// CHECK-LABEL: sil @collapse_to_unchecked_trivial_bit_cast :
// CHECK: bb0([[Ref:%.*]] : $Optional<Klass>):
// CHECK: unchecked_trivial_bit_cast [[Ref]]
// CHECK-NOT: unchecked_ref_cast
// CHECK-NOT: ref_to_raw_pointer
// CHECK: } // end sil function 'collapse_to_unchecked_trivial_bit_cast'
sil @collapse_to_unchecked_trivial_bit_cast : $@convention(thin) (Optional<Klass>) -> (Builtin.RawPointer) {
bb0(%0 : $Optional<Klass>):
%1 = unchecked_ref_cast %0 : $Optional<Klass> to $Builtin.NativeObject
%2 = ref_to_raw_pointer %1 : $Builtin.NativeObject to $Builtin.RawPointer
return %2 : $Builtin.RawPointer
}
// CHECK-LABEL: sil [ossa] @collapse_to_unchecked_trivial_bit_cast_ossa_guaranteed :
// CHECK: bb0([[Ref:%.*]] : @guaranteed $Optional<Klass>):
// CHECK: unchecked_trivial_bit_cast [[Ref]]
// CHECK-NOT: unchecked_ref_cast
// CHECK-NOT: ref_to_raw_pointer
// CHECK: } // end sil function 'collapse_to_unchecked_trivial_bit_cast_ossa_guaranteed'
sil [ossa] @collapse_to_unchecked_trivial_bit_cast_ossa_guaranteed : $@convention(thin) (@guaranteed Optional<Klass>) -> (Builtin.RawPointer) {
bb0(%0 : @guaranteed $Optional<Klass>):
%1 = unchecked_ref_cast %0 : $Optional<Klass> to $Builtin.NativeObject
%2 = ref_to_raw_pointer %1 : $Builtin.NativeObject to $Builtin.RawPointer
return %2 : $Builtin.RawPointer
}
// We need to make sure that we hoist unchecked_trivial_bit_cast above
// unchecked_ref_cast since we are not eliminating it here due to an additional
// user.
//
// CHECK-LABEL: sil [ossa] @collapse_to_unchecked_trivial_bit_cast_ossa_owned :
// CHECK: bb0([[ARG:%.*]] : @owned $Optional<Klass>):
// CHECK: [[RESULT:%.*]] = unchecked_trivial_bit_cast [[ARG]]
// CHECK: [[FORWARDED_ARG:%.*]] = unchecked_ref_cast [[ARG]]
// CHECK: destroy_value [[FORWARDED_ARG]]
// CHECK: return [[RESULT]]
// CHECK: } // end sil function 'collapse_to_unchecked_trivial_bit_cast_ossa_owned'
sil [ossa] @collapse_to_unchecked_trivial_bit_cast_ossa_owned : $@convention(thin) (@owned Optional<Klass>) -> (Builtin.RawPointer) {
bb0(%0 : @owned $Optional<Klass>):
%1 = unchecked_ref_cast %0 : $Optional<Klass> to $Builtin.NativeObject
%f = function_ref @use_nativeobject : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
apply %f(%1) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
%2 = ref_to_raw_pointer %1 : $Builtin.NativeObject to $Builtin.RawPointer
destroy_value %1 : $Builtin.NativeObject
return %2 : $Builtin.RawPointer
}
// -----------------------------------------------------------------------------
// SILCombiner::optimizeAlignment
//
// Optimize Builtin.assumeAlignment -> pointer_to_address
// -----------------------------------------------------------------------------
// Case #1. Literal zero = natural alignment
// %1 = integer_literal $Builtin.Int64, 0
// %2 = builtin "assumeAlignment"
// (%0 : $Builtin.RawPointer, %1 : $Builtin.Int64) : $Builtin.RawPointer
// %3 = pointer_to_address %2 : $Builtin.RawPointer to [align=1] $*Int
//
// Erases the `pointer_to_address` `[align=]` attribute:
//
// CHECK-LABEL: sil @test_zero_alignment : $@convention(thin) <T> (Builtin.RawPointer, @thick T.Type) -> @out T {
// CHECK: bb0(%0 : $*T, %1 : $Builtin.RawPointer, %2 : $@thick T.Type):
// CHECK: [[PTR:%.*]] = pointer_to_address %1 : $Builtin.RawPointer to $*T
// CHECK: copy_addr [[PTR]] to [init] %0 : $*T
// CHECK-LABEL: } // end sil function 'test_zero_alignment'
sil @test_zero_alignment : $@convention(thin) <T> (Builtin.RawPointer, @thick T.Type) -> @out T {
bb0(%0 : $*T, %1 : $Builtin.RawPointer, %2 : $@thick T.Type):
debug_value %1 : $Builtin.RawPointer, let, name "rawPointer", argno 1
debug_value %2 : $@thick T.Type, let, name "_1", argno 2
%5 = integer_literal $Builtin.Word, 0
%6 = builtin "assumeAlignment"(%1 : $Builtin.RawPointer, %5 : $Builtin.Word) : $Builtin.RawPointer
debug_value %6 : $Builtin.RawPointer, let, name "alignedPointer"
%8 = pointer_to_address %6 : $Builtin.RawPointer to [align=1] $*T
copy_addr %8 to [init] %0 : $*T
%10 = tuple ()
return %10 : $()
}
// Case #2. Literal nonzero = forced alignment.
//
// %1 = integer_literal $Builtin.Int64, 16
// %2 = builtin "assumeAlignment"
// (%0 : $Builtin.RawPointer, %1 : $Builtin.Int64) : $Builtin.RawPointer
// %3 = pointer_to_address %2 : $Builtin.RawPointer to [align=1] $*Int
//
// Promotes the `pointer_to_address` `[align=]` attribute to a higher value.
//
// CHECK-LABEL: sil @test_nonzero_alignment : $@convention(thin) <T> (Builtin.RawPointer, @thick T.Type) -> @out T {
// CHECK: bb0(%0 : $*T, %1 : $Builtin.RawPointer, %2 : $@thick T.Type):
// CHECK: [[PTR:%.*]] = pointer_to_address %1 : $Builtin.RawPointer to [align=8] $*T
// CHECK: copy_addr [[PTR]] to [init] %0 : $*T
// CHECK-LABEL: } // end sil function 'test_nonzero_alignment'
sil @test_nonzero_alignment : $@convention(thin) <T> (Builtin.RawPointer, @thick T.Type) -> @out T {
bb0(%0 : $*T, %1 : $Builtin.RawPointer, %2 : $@thick T.Type):
debug_value %1 : $Builtin.RawPointer, let, name "rawPointer", argno 1
debug_value %2 : $@thick T.Type, let, name "_1", argno 2
%5 = integer_literal $Builtin.Word, 8
%6 = builtin "assumeAlignment"(%1 : $Builtin.RawPointer, %5 : $Builtin.Word) : $Builtin.RawPointer
debug_value %6 : $Builtin.RawPointer, let, name "alignedPointer"
%8 = pointer_to_address %6 : $Builtin.RawPointer to [align=1] $*T
copy_addr %8 to [init] %0 : $*T
%10 = tuple ()
return %10 : $()
}
// Case #3. Folded dynamic alignment
//
// %1 = builtin "alignof"<T>(%0 : $@thin T.Type) : $Builtin.Word
// %2 = builtin "assumeAlignment"
// (%0 : $Builtin.RawPointer, %1 : $Builtin.Int64) : $Builtin.RawPointer
// %3 = pointer_to_address %2 : $Builtin.RawPointer to [align=1] $*T
//
// Erases the `pointer_to_address` `[align=]` attribute.
//
// CHECK-LABEL: sil @test_dynamic_alignment : $@convention(thin) <T> (Builtin.RawPointer, @thick T.Type) -> @out T {
// CHECK: bb0(%0 : $*T, %1 : $Builtin.RawPointer, %2 : $@thick T.Type):
// CHECK-NOT: metatype
// CHECK-NOT: builtin "alignOf"
// CHECK-NOT: builtin "assumeAlignment"
// CHECK: [[PTR:%.*]] = pointer_to_address %1 : $Builtin.RawPointer to $*T
// CHECK: copy_addr [[PTR]] to [init] %0 : $*T
// CHECK-LABEL: } // end sil function 'test_dynamic_alignment'
sil @test_dynamic_alignment : $@convention(thin) <T> (Builtin.RawPointer, @thick T.Type) -> @out T {
bb0(%0 : $*T, %1 : $Builtin.RawPointer, %2 : $@thick T.Type):
debug_value %1 : $Builtin.RawPointer, let, name "rawPointer", argno 1
debug_value %2 : $@thick T.Type, let, name "_1", argno 2
%5 = metatype $@thick T.Type
%6 = builtin "alignof"<T>(%5 : $@thick T.Type) : $Builtin.Word
%7 = builtin "sextOrBitCast_Word_Int64"(%6 : $Builtin.Word) : $Builtin.Int64
%8 = struct $Int64 (%7 : $Builtin.Int64)
debug_value %8 : $Int64, let, name "align"
%10 = builtin "truncOrBitCast_Int64_Word"(%7 : $Builtin.Int64) : $Builtin.Word
%11 = builtin "assumeAlignment"(%1 : $Builtin.RawPointer, %10 : $Builtin.Word) : $Builtin.RawPointer
debug_value %11 : $Builtin.RawPointer, let, name "alignedPointer"
%13 = pointer_to_address %11 : $Builtin.RawPointer to [align=1] $*T
copy_addr %13 to [init] %0 : $*T
%15 = tuple ()
return %15 : $()
}
// Case #3b. Folded dynamic alignment; 32-bit
//
// %1 = builtin "alignof"<T>(%0 : $@thin T.Type) : $Builtin.Word
// %2 = builtin "assumeAlignment"
// (%0 : $Builtin.RawPointer, %1 : $Builtin.Int64) : $Builtin.RawPointer
// %3 = pointer_to_address %2 : $Builtin.RawPointer to [align=1] $*T
//
// Erases the `pointer_to_address` `[align=]` attribute.
//
// CHECK-LABEL: sil @test_dynamic_alignment32 : $@convention(thin) <T> (Builtin.RawPointer, @thick T.Type) -> @out T {
// CHECK: bb0(%0 : $*T, %1 : $Builtin.RawPointer, %2 : $@thick T.Type):
// CHECK-NOT: metatype
// CHECK-NOT: builtin "alignOf"
// CHECK-NOT: builtin "assumeAlignment"
// CHECK: [[PTR:%.*]] = pointer_to_address %1 : $Builtin.RawPointer to $*T
// CHECK: copy_addr [[PTR]] to [init] %0 : $*T
// CHECK-LABEL: } // end sil function 'test_dynamic_alignment32'
sil @test_dynamic_alignment32 : $@convention(thin) <T> (Builtin.RawPointer, @thick T.Type) -> @out T {
bb0(%0 : $*T, %1 : $Builtin.RawPointer, %2 : $@thick T.Type):
debug_value %1 : $Builtin.RawPointer, let, name "rawPointer", argno 1
debug_value %2 : $@thick T.Type, let, name "_1", argno 2
%5 = metatype $@thick T.Type
%6 = builtin "alignof"<T>(%5 : $@thick T.Type) : $Builtin.Word
%7 = builtin "truncOrBitCast_Word_Int32"(%6 : $Builtin.Word) : $Builtin.Int32
%8 = struct $Int32 (%7 : $Builtin.Int32)
debug_value %8 : $Int32, let, name "align"
%10 = builtin "sextOrBitCast_Int32_Word"(%7 : $Builtin.Int32) : $Builtin.Word
%11 = builtin "assumeAlignment"(%1 : $Builtin.RawPointer, %10 : $Builtin.Word) : $Builtin.RawPointer
debug_value %11 : $Builtin.RawPointer, let, name "alignedPointer"
%13 = pointer_to_address %11 : $Builtin.RawPointer to [align=1] $*T
copy_addr %13 to [init] %0 : $*T
%15 = tuple ()
return %15 : $()
}
public protocol AnyAction {
}
public struct MyAction : AnyAction {
}
// Make sure that we do not crash on this test case!
//
// TODO: Maybe we could handle this?
sil shared @cast_optimizer_metatype_crasher : $@convention(thin) (@thick MyAction.Type) -> @thick AnyAction.Type {
bb0(%0 : $@thick MyAction.Type):
%1 = alloc_stack $@thick AnyAction.Type
%2 = alloc_stack $@thick MyAction.Type
store %0 to %2 : $*@thick MyAction.Type
unconditional_checked_cast_addr MyAction.Type in %2 : $*@thick MyAction.Type to AnyAction.Type in %1 : $*@thick AnyAction.Type
dealloc_stack %2 : $*@thick MyAction.Type
%6 = tuple ()
%7 = load %1 : $*@thick AnyAction.Type
dealloc_stack %1 : $*@thick AnyAction.Type
return %7 : $@thick AnyAction.Type
}