Files
swift-mirror/test/Interpreter/SDK/convenience_init_peer_delegation.swift
Jordan Rose 425c190086 Restore initializing entry points for @objc convenience initializers (#21815)
This undoes some of Joe's work in 8665342 to add a guarantee: if an
@objc convenience initializer only calls other @objc initializers that
eventually call a designated initializer, it won't result in an extra
allocation. While Objective-C /allows/ returning a different object
from an initializer than the allocation you were given, doing so
doesn't play well with some very hairy implementation details of
compiled nib files (or NSCoding archives with cyclic references in
general).

This guarantee only applies to
(1) calling `self.init`
(2) where the delegated-to initializer is @objc
because convenience initializers must do dynamic dispatch when they
delegate, and Swift only stores allocating entry points for
initializers in a class's vtable. To dynamically find an initializing
entry point, ObjC dispatch must be used instead.

(It's worth noting that this patch does NOT check that the calling
initializer is a convenience initializer when deciding whether to use
ObjC dispatch for `self.init`. If we ever add peer delegation to
designated initializers, which is totally a valid feature, that should
use static dispatch and therefore should not go through objc_msgSend.)

This change doesn't /always/ result in fewer allocations; if the
delegated-to initializer ends up returning a different object after
all, the original allocation was wasted. Objective-C has the same
problem (one of the reasons why factory methods exist for things like
NSNumber and NSArray).

We do still get most of the benefits of Joe's original change. In
particular, vtables only ever contain allocating initializer entry
points, never the initializing ones, and never /both/ (which was a
thing that could happen with 'required' before).

rdar://problem/46823518
2019-01-14 13:06:50 -08:00

128 lines
4.3 KiB
Swift

// RUN: %empty-directory(%t)
// RUN: %target-clang -c %S/Inputs/convenience_init_peer_delegation.m -o %t/convenience_init_peer_delegation.objc.o -fmodules -fobjc-arc
// RUN: %target-build-swift -c -o %t/convenience_init_peer_delegation.swift.o -import-objc-header %S/Inputs/convenience_init_peer_delegation.h %s
// RUN: %target-swiftc_driver %t/convenience_init_peer_delegation.objc.o %t/convenience_init_peer_delegation.swift.o -o %t/main
// RUN: %target-codesign %t/main
// RUN: %target-run %t/main | %FileCheck %s
// REQUIRES: executable_test
// REQUIRES: objc_interop
extension Base {
convenience init(swiftToDesignated: ()) {
print("\(#function) \(type(of: self))")
self.init()
}
convenience init(swiftToConvenience: ()) {
print("\(#function) \(type(of: self))")
self.init(conveniently: ())
}
convenience init(swiftToConvenienceFactory: ()) {
print("\(#function) \(type(of: self))")
self.init(convenientFactory: false)
}
convenience init(swiftToNormalFactory: ()) {
// FIXME: This shouldn't be allowed, since the factory won't actually use
// the dynamic Self type.
print("\(#function) \(type(of: self))")
self.init(normalFactory: false)
}
@objc convenience init(objcToDesignated: ()) {
print("\(#function) \(type(of: self))")
self.init()
}
@objc convenience init(objcToConvenience: ()) {
print("\(#function) \(type(of: self))")
self.init(conveniently: ())
}
@objc convenience init(objcToConvenienceFactory: ()) {
print("\(#function) \(type(of: self))")
self.init(convenientFactory: false)
}
@objc convenience init(objcToNormalFactory: ()) {
// FIXME: This shouldn't be allowed, since the factory won't actually use
// the dynamic Self type.
print("\(#function) \(type(of: self))")
self.init(normalFactory: false)
}
}
/// Checks that `op` performs `base` allocations of Base and `sub` allocations
/// of Sub.
func check(base: Int = 0, sub: Int = 0,
file: StaticString = #file, line: UInt = #line,
op: () -> Void) {
baseCounter = 0
subCounter = 0
op()
precondition(baseCounter == base,
"expected \(base) Base instances, got \(baseCounter)",
file: file, line: line)
precondition(subCounter == sub,
"expected \(sub) Sub instances, got \(subCounter)",
file: file, line: line)
}
// CHECK: START
print("START")
// Check that this whole setup works.
// CHECK-NEXT: init Base
check(base: 1) { _ = Base() }
// CHECK-NEXT: init Sub
check(sub: 1) { _ = Sub() }
// CHECK-NEXT: init(swiftToDesignated:) Sub
// CHECK-NEXT: init Sub
check(sub: 1) { _ = Sub(swiftToDesignated: ()) }
// CHECK-NEXT: init(swiftToConvenience:) Sub
// CHECK-NEXT: -[Base initConveniently]
// CHECK-NEXT: init Sub
check(sub: 1) { _ = Sub(swiftToConvenience: ()) }
// CHECK-NEXT: init(swiftToConvenienceFactory:) Sub
// CHECK-NEXT: +[Base baseWithConvenientFactory:]
// CHECK-NEXT: init Sub
check(sub: 1) { _ = Sub(swiftToConvenienceFactory: ()) }
// FIXME: This shouldn't be allowed in the first place; see the definition
// above.
// CHECK-NEXT: init(swiftToNormalFactory:) Base
// CHECK-NEXT: +[Base baseWithNormalFactory:]
// CHECK-NEXT: init Base
check(base: 1) { _ = Base(swiftToNormalFactory: ()) }
// CHECK-NEXT: init(swiftToNormalFactory:) Sub
// CHECK-NEXT: +[Base baseWithNormalFactory:]
// CHECK-NEXT: init Base
check(base: 1) { _ = Sub(swiftToNormalFactory: ()) }
// CHECK-NEXT: init(objcToDesignated:) Sub
// CHECK-NEXT: init Sub
check(sub: 1) { _ = Sub(objcToDesignated: ()) }
// CHECK-NEXT: init(objcToConvenience:) Sub
// CHECK-NEXT: -[Base initConveniently]
// CHECK-NEXT: init Sub
check(sub: 1) { _ = Sub(objcToConvenience: ()) }
// CHECK-NEXT: init(objcToConvenienceFactory:) Sub
// CHECK-NEXT: +[Base baseWithConvenientFactory:]
// CHECK-NEXT: init Sub
check(sub: 2) { _ = Sub(objcToConvenienceFactory: ()) }
// FIXME: This shouldn't be allowed in the first place; see the definition
// above.
// CHECK-NEXT: init(objcToNormalFactory:) Base
// CHECK-NEXT: +[Base baseWithNormalFactory:]
// CHECK-NEXT: init Base
check(base: 2) { _ = Base(objcToNormalFactory: ()) }
// CHECK-NEXT: init(objcToNormalFactory:) Sub
// CHECK-NEXT: +[Base baseWithNormalFactory:]
// CHECK-NEXT: init Base
check(base: 1, sub: 1) { _ = Sub(objcToNormalFactory: ()) }
// CHECK-NEXT: END
print("END")