Skip to content

[SR-12180] Crash in app runtime due to Swift compiler optimization -O (Optimize for speed) #54605

@swift-ci

Description

@swift-ci
Previous ID SR-12180
Radar rdar://problem/59496027
Original Reporter kamils (JIRA User)
Type Bug
Status Resolved
Resolution Done
Environment

Apple Swift version 5.1 (swiftlang-1100.0.270.13)

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, CompilerCrash, OptimizedOnly
Assignee None
Priority Medium

md5: 0edb6e6fee44600a3b53619a01df402a

Issue Description:

On production AppStore build we did face a 100% reproducible deterministic app crash which I was able reduce to be caused by following code. This happens only with `-O (Optimize for speed)`, `-Onone` is unaffected.

swiftc --version                   
Apple Swift version 5.1 (swiftlang-1100.0.270.13 clang-1100.0.33.7)
Target: x86_64-apple-darwin19.0.0

This happens both on X86-64 and ARM so clearly it's a Swift compiler issue.

import Foundation

public class UserDefaultsStore<T>: Storing where T: Codable {
    public typealias Element = T
    private var userDefaults: UserDefaults

    @inline(__always) //this will be inlined by default, will crash
    public func read(for url: String) -> T? {
        print(userDefaults) //� Crash will happen here
        return nil
    }
    //This code is essential for crash to happen (even though it's not called)
    public func save(_ element: T, for url: String) {
    }
    public init(userDefaults: UserDefaults) {
        self.userDefaults = userDefaults
    }
}


public protocol Storing {
    associatedtype Element
    func read(for url: String) -> Element?
    func save(_ element: Element, for key: String)
}


public struct AnyStoring<T>: Storing {
    public typealias Element = T
    private let readHandler: (String) -> T?
    private let saveHandler: (T, String) -> Void
    
    //This is essential to cause the crash
    @inline(never)
    public func read(for key: String) -> T? {
        return readHandler(key)
    }
    
    public func save(_ element: T, for key: String) {
        saveHandler(element, key)
    }


    public init<C: Storing>(_ cache: C) where C.Element == Element {
        readHandler = cache.read
        saveHandler = cache.save
    }
}


//This can be class as well
public struct SomeModel: Codable {
}

//Call to crash
AnyStoring<SomeModel>(UserDefaultsStore<SomeModel>(userDefaults: UserDefaults.standard)).read(for: "")

We found four possible workarounds not to trigger the crash:

  1. Define both `AnyStoring` & `UserDefaultsStore` as a class
  2. Define both `AnyStoring` & `UserDefaultsStore` as a struct
  3. Define `AnyStoring` as a class & `UserDefaultsStore` as a struct (so effectively exchanging their definition types)
  4. Mark the crashing method
    public func read(for url: String) -> T? {
    with `@inline(never)`

The SIL of inlined and nonline variant for crashing `public func read(for url: String) -> T? {` looks identical to me (apart from the mangled signature).

// UserDefaultsStore.read(for:)
sil [always_inline] [ossa] @$s11main_always17UserDefaultsStoreC4read3forxSgSS_tF : $@convention(method) <T where T : Decodable, T : Encodable> (@guaranteed String, @guaranteed UserDefaultsStore<T>) -> @out Optional<T> {
// %0                                             // user: %25
// %1                                             // user: %3
// %2                                             // users: %12, %11, %4
bb0(%0 : $*Optional<T>, %1 : @guaranteed $String, %2 : @guaranteed $UserDefaultsStore<T>):
  debug_value %1 : $String, let, name "url", argno 1 // id: %3
  debug_value %2 : $UserDefaultsStore<T>, let, name "self", argno 2 // id: %4
  %5 = integer_literal $Builtin.Word, 1           // user: %7
  // function_ref _allocateUninitializedArray<A>(_:)
  %6 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %7
  %7 = apply %6<Any>(%5) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %8
  (%8, %9) = destructure_tuple %7 : $(Array<Any>, Builtin.RawPointer) // users: %23, %20, %10
  %10 = pointer_to_address %9 : $Builtin.RawPointer to [strict] $*Any // user: %13
  %11 = class_method %2 : $UserDefaultsStore<T>, #UserDefaultsStore.userDefaults!getter.1 : <T where T : Decodable, T : Encodable> (UserDefaultsStore<T>) -> () -> UserDefaults, $@convention(method) <τ_0_0 where τ_0_0 : Decodable, τ_0_0 : Encodable> (@guaranteed UserDefaultsStore<τ_0_0>) -> @owned UserDefaults // user: %12
  %12 = apply %11<T>(%2) : $@convention(method) <τ_0_0 where τ_0_0 : Decodable, τ_0_0 : Encodable> (@guaranteed UserDefaultsStore<τ_0_0>) -> @owned UserDefaults // user: %14
  %13 = init_existential_addr %10 : $*Any, $UserDefaults // user: %14
  store %12 to [init] %13 : $*UserDefaults        // id: %14
  // function_ref default argument 1 of print(_:separator:terminator:)
  %15 = function_ref @$ss5print_9separator10terminatoryypd_S2StFfA0_ : $@convention(thin) () -> @owned String // user: %16
  %16 = apply %15() : $@convention(thin) () -> @owned String // users: %22, %20
  // function_ref default argument 2 of print(_:separator:terminator:)
  %17 = function_ref @$ss5print_9separator10terminatoryypd_S2StFfA1_ : $@convention(thin) () -> @owned String // user: %18
  %18 = apply %17() : $@convention(thin) () -> @owned String // users: %21, %20
  // function_ref print(_:separator:terminator:)
  %19 = function_ref @$ss5print_9separator10terminatoryypd_S2StF : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> () // user: %20
  %20 = apply %19(%8, %16, %18) : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> ()
  destroy_value %18 : $String                     // id: %21
  destroy_value %16 : $String                     // id: %22
  destroy_value %8 : $Array<Any>                  // id: %23
  %24 = metatype $@thin Optional<T>.Type
  inject_enum_addr %0 : $*Optional<T>, #Optional.none!enumelt // id: %25
  %26 = tuple ()                                  // user: %27
  return %26 : $()                                // id: %27
} // end sil function '$s11main_always17UserDefaultsStoreC4read3forxSgSS_tF'
// UserDefaultsStore.read(for:)
sil [noinline] [ossa] @$s4main17UserDefaultsStoreC4read3forxSgSS_tF : $@convention(method) <T where T : Decodable, T : Encodable> (@guaranteed String, @guaranteed UserDefaultsStore<T>) -> @out Optional<T> {
// %0                                             // user: %25
// %1                                             // user: %3
// %2                                             // users: %12, %11, %4
bb0(%0 : $*Optional<T>, %1 : @guaranteed $String, %2 : @guaranteed $UserDefaultsStore<T>):
  debug_value %1 : $String, let, name "url", argno 1 // id: %3
  debug_value %2 : $UserDefaultsStore<T>, let, name "self", argno 2 // id: %4
  %5 = integer_literal $Builtin.Word, 1           // user: %7
  // function_ref _allocateUninitializedArray<A>(_:)
  %6 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %7
  %7 = apply %6<Any>(%5) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %8
  (%8, %9) = destructure_tuple %7 : $(Array<Any>, Builtin.RawPointer) // users: %23, %20, %10
  %10 = pointer_to_address %9 : $Builtin.RawPointer to [strict] $*Any // user: %13
  %11 = class_method %2 : $UserDefaultsStore<T>, #UserDefaultsStore.userDefaults!getter.1 : <T where T : Decodable, T : Encodable> (UserDefaultsStore<T>) -> () -> UserDefaults, $@convention(method) <τ_0_0 where τ_0_0 : Decodable, τ_0_0 : Encodable> (@guaranteed UserDefaultsStore<τ_0_0>) -> @owned UserDefaults // user: %12
  %12 = apply %11<T>(%2) : $@convention(method) <τ_0_0 where τ_0_0 : Decodable, τ_0_0 : Encodable> (@guaranteed UserDefaultsStore<τ_0_0>) -> @owned UserDefaults // user: %14
  %13 = init_existential_addr %10 : $*Any, $UserDefaults // user: %14
  store %12 to [init] %13 : $*UserDefaults        // id: %14
  // function_ref default argument 1 of print(_:separator:terminator:)
  %15 = function_ref @$ss5print_9separator10terminatoryypd_S2StFfA0_ : $@convention(thin) () -> @owned String // user: %16
  %16 = apply %15() : $@convention(thin) () -> @owned String // users: %22, %20
  // function_ref default argument 2 of print(_:separator:terminator:)
  %17 = function_ref @$ss5print_9separator10terminatoryypd_S2StFfA1_ : $@convention(thin) () -> @owned String // user: %18
  %18 = apply %17() : $@convention(thin) () -> @owned String // users: %21, %20
  // function_ref print(_:separator:terminator:)
  %19 = function_ref @$ss5print_9separator10terminatoryypd_S2StF : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> () // user: %20
  %20 = apply %19(%8, %16, %18) : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> ()
  destroy_value %18 : $String                     // id: %21
  destroy_value %16 : $String                     // id: %22
  destroy_value %8 : $Array<Any>                  // id: %23
  %24 = metatype $@thin Optional<T>.Type
  inject_enum_addr %0 : $*Optional<T>, #Optional.none!enumelt // id: %25
  %26 = tuple ()                                  // user: %27
  return %26 : $()                                // id: %27
} // end sil function '$s4main17UserDefaultsStoreC4read3forxSgSS_tF'

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.compilerThe Swift compiler itselfcrashBug: A crash, i.e., an abnormal termination of softwareoptimized onlyFlag: An issue whose reproduction requires optimized compilation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions