Skip to content

Commit 5f9297a

Browse files
committed
Restore initializing entry points for @objc convenience initializers
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
1 parent 129f39d commit 5f9297a

22 files changed

+891
-81
lines changed

docs/SIL.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2345,6 +2345,7 @@ mark_uninitialized
23452345
mu_kind ::= 'derivedself'
23462346
mu_kind ::= 'derivedselfonly'
23472347
mu_kind ::= 'delegatingself'
2348+
mu_kind ::= 'delegatingselfallocated'
23482349

23492350
%2 = mark_uninitialized [var] %1 : $*T
23502351
// $T must be an address
@@ -2365,6 +2366,7 @@ the mark_uninitialized instruction refers to:
23652366
- ``derivedself``: designates ``self`` in a derived (non-root) class
23662367
- ``derivedselfonly``: designates ``self`` in a derived (non-root) class whose stored properties have already been initialized
23672368
- ``delegatingself``: designates ``self`` on a struct, enum, or class in a delegating constructor (one that calls self.init)
2369+
- ``delegatingselfallocated``: designates ``self`` on a class convenience initializer's initializing entry point
23682370

23692371
The purpose of the ``mark_uninitialized`` instruction is to enable
23702372
definitive initialization analysis for global variables (when marked as

include/swift/SIL/SILInstruction.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3654,6 +3654,10 @@ class MarkUninitializedInst
36543654
/// DelegatingSelf designates "self" on a struct, enum, or class
36553655
/// in a delegating constructor (one that calls self.init).
36563656
DelegatingSelf,
3657+
3658+
/// DelegatingSelfAllocated designates "self" in a delegating class
3659+
/// initializer where memory has already been allocated.
3660+
DelegatingSelfAllocated,
36573661
};
36583662
private:
36593663
Kind ThisKind;
@@ -3683,6 +3687,9 @@ class MarkUninitializedInst
36833687
bool isDelegatingSelf() const {
36843688
return ThisKind == DelegatingSelf;
36853689
}
3690+
bool isDelegatingSelfAllocated() const {
3691+
return ThisKind == DelegatingSelfAllocated;
3692+
}
36863693
};
36873694

36883695
/// MarkFunctionEscape - Represents the escape point of set of variables due to

lib/ParseSIL/ParseSIL.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3397,10 +3397,12 @@ bool SILParser::parseSILInstruction(SILBuilder &B) {
33973397
Kind = MarkUninitializedInst::DerivedSelfOnly;
33983398
else if (KindId.str() == "delegatingself")
33993399
Kind = MarkUninitializedInst::DelegatingSelf;
3400+
else if (KindId.str() == "delegatingselfallocated")
3401+
Kind = MarkUninitializedInst::DelegatingSelfAllocated;
34003402
else {
34013403
P.diagnose(KindLoc, diag::expected_tok_in_sil_instr,
34023404
"var, rootself, crossmodulerootself, derivedself, "
3403-
"derivedselfonly, or delegatingself");
3405+
"derivedselfonly, delegatingself, or delegatingselfallocated");
34043406
return true;
34053407
}
34063408

lib/SIL/SILPrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,9 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
12971297
*this << "[derivedselfonly] ";
12981298
break;
12991299
case MarkUninitializedInst::DelegatingSelf: *this << "[delegatingself] ";break;
1300+
case MarkUninitializedInst::DelegatingSelfAllocated:
1301+
*this << "[delegatingselfallocated] ";
1302+
break;
13001303
}
13011304

13021305
*this << getIDAndType(MU->getOperand());

lib/SIL/SILVerifier.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2287,9 +2287,6 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
22872287
->getInstanceType()->getClassOrBoundGenericClass();
22882288
require(class2,
22892289
"Second operand of dealloc_partial_ref must be a class metatype");
2290-
require(!class2->isResilient(F.getModule().getSwiftModule(),
2291-
F.getResilienceExpansion()),
2292-
"cannot directly deallocate resilient class");
22932290
while (class1 != class2) {
22942291
class1 = class1->getSuperclassDecl();
22952292
require(class1, "First operand not superclass of second instance type");

lib/SILGen/SILGen.cpp

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -854,33 +854,18 @@ void SILGenModule::emitConstructor(ConstructorDecl *decl) {
854854

855855
bool ForCoverageMapping = doesASTRequireProfiling(M, decl);
856856

857-
auto emitClassAllocatorThunk = [&]{
858-
emitOrDelayFunction(
859-
*this, constant, [this, constant, decl](SILFunction *f) {
860-
preEmitFunction(constant, decl, f, decl);
861-
PrettyStackTraceSILFunction X("silgen emitConstructor", f);
862-
SILGenFunction(*this, *f, decl).emitClassConstructorAllocator(decl);
863-
postEmitFunction(constant, f);
864-
});
865-
};
866-
auto emitValueConstructorIfHasBody = [&]{
867-
if (decl->hasBody()) {
857+
if (declCtx->getSelfClassDecl()) {
858+
// Designated initializers for classes, as well as @objc convenience
859+
// initializers, have have separate entry points for allocation and
860+
// initialization.
861+
if (decl->isDesignatedInit() || decl->isObjC()) {
868862
emitOrDelayFunction(
869-
*this, constant, [this, constant, decl, declCtx](SILFunction *f) {
863+
*this, constant, [this, constant, decl](SILFunction *f) {
870864
preEmitFunction(constant, decl, f, decl);
871865
PrettyStackTraceSILFunction X("silgen emitConstructor", f);
872-
f->setProfiler(getOrCreateProfilerForConstructors(declCtx, decl));
873-
SILGenFunction(*this, *f, decl).emitValueConstructor(decl);
866+
SILGenFunction(*this, *f, decl).emitClassConstructorAllocator(decl);
874867
postEmitFunction(constant, f);
875868
});
876-
}
877-
};
878-
879-
if (declCtx->getSelfClassDecl()) {
880-
// Designated initializers for classes have have separate entry points for
881-
// allocation and initialization.
882-
if (decl->isDesignatedInit()) {
883-
emitClassAllocatorThunk();
884869

885870
// Constructors may not have bodies if they've been imported, or if they've
886871
// been parsed from a parseable interface.
@@ -900,21 +885,21 @@ void SILGenModule::emitConstructor(ConstructorDecl *decl) {
900885
},
901886
/*forceEmission=*/ForCoverageMapping);
902887
}
903-
// Convenience initializers for classes behave more like value constructors
904-
// in that there's only an allocating entry point that effectively
905-
// "constructs" the self reference by invoking another initializer.
906-
} else {
907-
emitValueConstructorIfHasBody();
908-
909-
// If the convenience initializer was imported from ObjC, we still have to
910-
// emit the allocator thunk.
911-
if (decl->hasClangNode()) {
912-
emitClassAllocatorThunk();
913-
}
888+
return;
914889
}
915-
} else {
916-
// Struct and enum constructors do everything in a single function.
917-
emitValueConstructorIfHasBody();
890+
}
891+
892+
// Struct and enum constructors do everything in a single function, as do
893+
// non-@objc convenience initializers for classes.
894+
if (decl->hasBody()) {
895+
emitOrDelayFunction(
896+
*this, constant, [this, constant, decl, declCtx](SILFunction *f) {
897+
preEmitFunction(constant, decl, f, decl);
898+
PrettyStackTraceSILFunction X("silgen emitConstructor", f);
899+
f->setProfiler(getOrCreateProfilerForConstructors(declCtx, decl));
900+
SILGenFunction(*this, *f, decl).emitValueConstructor(decl);
901+
postEmitFunction(constant, f);
902+
});
918903
}
919904
}
920905

lib/SILGen/SILGenApply.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,14 +1397,21 @@ class SILGenApply : public Lowering::ExprVisitor<SILGenApply> {
13971397
// protocols, which only witness initializing initializers.
13981398
else if (auto proto = dyn_cast<ProtocolDecl>(nominal)) {
13991399
useAllocatingCtor = !proto->isObjC();
1400-
// Similarly, class initializers self.init-delegate to each other via
1401-
// their allocating entry points, unless delegating to an ObjC-only,
1402-
// non-factory initializer.
1400+
// Factory initializers are effectively "allocating" initializers with no
1401+
// corresponding initializing entry point.
1402+
} else if (ctorRef->getDecl()->isFactoryInit()) {
1403+
useAllocatingCtor = true;
1404+
// If we're emitting a class initializer's non-allocating entry point and
1405+
// delegating to an initializer exposed to Objective-C, use the initializing
1406+
// entry point to avoid replacing an existing allocated object.
1407+
} else if (!SGF.AllocatorMetatype && ctorRef->getDecl()->isObjC()) {
1408+
useAllocatingCtor = false;
1409+
// In general, though, class initializers self.init-delegate to each other
1410+
// via their allocating entry points.
14031411
} else {
14041412
assert(isa<ClassDecl>(nominal)
14051413
&& "some new kind of init context we haven't implemented");
1406-
useAllocatingCtor = ctorRef->getDecl()->isFactoryInit()
1407-
|| !requiresForeignEntryPoint(ctorRef->getDecl());
1414+
useAllocatingCtor = !requiresForeignEntryPoint(ctorRef->getDecl());
14081415
}
14091416

14101417
// Load the 'self' argument.
@@ -1472,9 +1479,16 @@ class SILGenApply : public Lowering::ExprVisitor<SILGenApply> {
14721479
arg->isSelfExprOf(
14731480
cast<AbstractFunctionDecl>(SGF.FunctionDC->getAsDecl()), false);
14741481

1475-
constant =
1476-
constant.asForeign(!isObjCReplacementSelfCall &&
1477-
requiresForeignEntryPoint(ctorRef->getDecl()));
1482+
if (!isObjCReplacementSelfCall) {
1483+
if (useAllocatingCtor) {
1484+
constant =
1485+
constant.asForeign(requiresForeignEntryPoint(ctorRef->getDecl()));
1486+
} else {
1487+
// Note: if we ever implement delegating from one designated initializer
1488+
// to another, this won't be correct; that should do a direct dispatch.
1489+
constant = constant.asForeign(ctorRef->getDecl()->isObjC());
1490+
}
1491+
}
14781492

14791493
// Determine the callee. This is normally the allocating
14801494
// entry point, unless we're delegating to an ObjC initializer.
@@ -1485,7 +1499,8 @@ class SILGenApply : public Lowering::ExprVisitor<SILGenApply> {
14851499
constant, subs, expr));
14861500
} else if ((useAllocatingCtor || constant.isForeign) &&
14871501
!isSelfCallToReplacedInDynamicReplacement &&
1488-
getMethodDispatch(ctorRef->getDecl()) == MethodDispatch::Class) {
1502+
((constant.isForeign && !useAllocatingCtor) ||
1503+
getMethodDispatch(ctorRef->getDecl()) == MethodDispatch::Class)) {
14891504
// Dynamic dispatch to the initializer.
14901505
Scope S(SGF, expr);
14911506
setCallee(Callee::forClassMethod(

lib/SILGen/SILGenBridging.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,7 @@ void SILGenFunction::emitNativeToForeignThunk(SILDeclRef thunk) {
13881388
bool isInitializingToAllocatingInitThunk = false;
13891389
if (native.kind == SILDeclRef::Kind::Initializer) {
13901390
if (auto ctor = dyn_cast<ConstructorDecl>(native.getDecl())) {
1391-
if (!ctor->isDesignatedInit()) {
1391+
if (!ctor->isDesignatedInit() && !ctor->isObjC()) {
13921392
isInitializingToAllocatingInitThunk = true;
13931393
native = SILDeclRef(ctor, SILDeclRef::Kind::Allocator);
13941394
}

lib/SILGen/SILGenConstructor.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -481,9 +481,10 @@ void SILGenFunction::emitClassConstructorAllocator(ConstructorDecl *ctor) {
481481
// Allocate the 'self' value.
482482
bool useObjCAllocation = usesObjCAllocator(selfClassDecl);
483483

484-
if (ctor->hasClangNode()) {
485-
// For an allocator thunk synthesized for an Objective-C init method,
486-
// allocate using the metatype.
484+
if (ctor->hasClangNode() || ctor->isConvenienceInit()) {
485+
assert(ctor->hasClangNode() || ctor->isObjC());
486+
// For an allocator thunk synthesized for an @objc convenience initializer
487+
// or imported Objective-C init method, allocate using the metatype.
487488
SILValue allocArg = selfMetaValue;
488489

489490
// When using Objective-C allocation, convert the metatype
@@ -572,10 +573,13 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) {
572573
// enforce its DI properties on stored properties.
573574
MarkUninitializedInst::Kind MUKind;
574575

575-
if (isDelegating)
576-
MUKind = MarkUninitializedInst::DelegatingSelf;
577-
else if (selfClassDecl->requiresStoredPropertyInits() &&
578-
usesObjCAllocator) {
576+
if (isDelegating) {
577+
if (ctor->isObjC())
578+
MUKind = MarkUninitializedInst::DelegatingSelfAllocated;
579+
else
580+
MUKind = MarkUninitializedInst::DelegatingSelf;
581+
} else if (selfClassDecl->requiresStoredPropertyInits() &&
582+
usesObjCAllocator) {
579583
// Stored properties will be initialized in a separate
580584
// .cxx_construct method called by the Objective-C runtime.
581585
assert(selfClassDecl->hasSuperclass() &&

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,7 +1806,8 @@ collectClassInitSelfLoadUses(MarkUninitializedInst *MUI,
18061806
//===----------------------------------------------------------------------===//
18071807

18081808
static bool shouldPerformClassInitSelf(const DIMemoryObjectInfo &MemoryInfo) {
1809-
assert(!MemoryInfo.isDelegatingInit());
1809+
if (MemoryInfo.MemoryInst->isDelegatingSelfAllocated())
1810+
return true;
18101811

18111812
return MemoryInfo.isNonDelegatingInit() &&
18121813
MemoryInfo.getType()->getClassOrBoundGenericClass() != nullptr &&
@@ -1820,6 +1821,12 @@ void swift::ownership::collectDIElementUsesFrom(
18201821
const DIMemoryObjectInfo &MemoryInfo, DIElementUseInfo &UseInfo,
18211822
bool isDIFinished, bool TreatAddressToPointerAsInout) {
18221823

1824+
if (shouldPerformClassInitSelf(MemoryInfo)) {
1825+
ClassInitElementUseCollector UseCollector(MemoryInfo, UseInfo);
1826+
UseCollector.collectClassInitSelfUses();
1827+
return;
1828+
}
1829+
18231830
if (MemoryInfo.isDelegatingInit()) {
18241831
// When we're analyzing a delegating constructor, we aren't field sensitive
18251832
// at all. Just treat all members of self as uses of the single
@@ -1829,12 +1836,6 @@ void swift::ownership::collectDIElementUsesFrom(
18291836
return;
18301837
}
18311838

1832-
if (shouldPerformClassInitSelf(MemoryInfo)) {
1833-
ClassInitElementUseCollector UseCollector(MemoryInfo, UseInfo);
1834-
UseCollector.collectClassInitSelfUses();
1835-
return;
1836-
}
1837-
18381839
ElementUseCollector(MemoryInfo, UseInfo, isDIFinished,
18391840
TreatAddressToPointerAsInout)
18401841
.collectFrom();

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ class DIMemoryObjectInfo {
119119
/// True if the memory object is the 'self' argument of a class designated
120120
/// initializer.
121121
bool isClassInitSelf() const {
122-
if (isDelegatingInit())
122+
if (MemoryInst->isDelegatingSelf())
123123
return false;
124-
124+
125125
if (!MemoryInst->isVar()) {
126126
if (auto decl = getType()->getAnyNominal()) {
127127
if (isa<ClassDecl>(decl)) {
@@ -153,14 +153,13 @@ class DIMemoryObjectInfo {
153153
return isClassInitSelf() && !MemoryInst->isRootSelf();
154154
}
155155

156-
/// isDelegatingInit - True if this is a delegating initializer, one that
157-
/// calls 'self.init'.
156+
/// True if this is a delegating initializer, one that calls 'self.init'.
158157
bool isDelegatingInit() const {
159-
return MemoryInst->isDelegatingSelf();
158+
return MemoryInst->isDelegatingSelf() ||
159+
MemoryInst->isDelegatingSelfAllocated();
160160
}
161161

162-
/// isNonDelegatingInit - True if this is an initializer that initializes
163-
/// stored properties.
162+
/// True if this is an initializer that initializes stored properties.
164163
bool isNonDelegatingInit() const {
165164
switch (MemoryInst->getKind()) {
166165
case MarkUninitializedInst::Var:
@@ -171,6 +170,7 @@ class DIMemoryObjectInfo {
171170
case MarkUninitializedInst::DerivedSelfOnly:
172171
return true;
173172
case MarkUninitializedInst::DelegatingSelf:
173+
case MarkUninitializedInst::DelegatingSelfAllocated:
174174
return false;
175175
}
176176
return false;

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,13 +1927,12 @@ void LifetimeChecker::processUninitializedRelease(SILInstruction *Release,
19271927
auto SILMetatypeTy = SILType::getPrimitiveObjectType(MetatypeTy);
19281928
SILValue Metatype;
19291929

1930-
// A convenience initializer should never deal in partially allocated
1931-
// objects.
1932-
assert(!TheMemory.isDelegatingInit());
1933-
1934-
// In a designated initializer, we know the class of the thing
1935-
// we're cleaning up statically.
1936-
Metatype = B.createMetatype(Loc, SILMetatypeTy);
1930+
// In an inherited convenience initializer, we must use the dynamic
1931+
// type of the object since nothing is initialized yet.
1932+
if (TheMemory.isDelegatingInit())
1933+
Metatype = B.createValueMetatype(Loc, SILMetatypeTy, Pointer);
1934+
else
1935+
Metatype = B.createMetatype(Loc, SILMetatypeTy);
19371936

19381937
// We've already destroyed any instance variables initialized by this
19391938
// constructor, now destroy instance variables initialized by subclass

test/Interpreter/SDK/Foundation_test.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,36 @@ if #available(OSX 10.11, iOS 9.0, *) {
433433

434434
KU.finishDecoding()
435435
}
436+
437+
FoundationTestSuite.test("NSKeyedUnarchiver/CycleWithConvenienceInit") {
438+
@objc(HasACyclicReference)
439+
class HasACyclicReference: NSObject, NSCoding {
440+
var ref: AnyObject?
441+
override init() {
442+
super.init()
443+
ref = self
444+
}
445+
446+
required convenience init?(coder: NSCoder) {
447+
let decodedRef = coder.decodeObject(forKey: "ref") as AnyObject?
448+
self.init(ref: decodedRef)
449+
}
450+
@objc init(ref: AnyObject?) {
451+
self.ref = ref
452+
super.init()
453+
}
454+
455+
func encode(with coder: NSCoder) {
456+
coder.encode(ref, forKey: "ref")
457+
}
458+
}
459+
460+
let data = NSKeyedArchiver.archivedData(withRootObject: HasACyclicReference())
461+
let decodedObj = NSKeyedUnarchiver.unarchiveObject(with: data) as! HasACyclicReference
462+
463+
expectEqual(ObjectIdentifier(decodedObj), ObjectIdentifier(decodedObj.ref!),
464+
"cycle was not preserved (due to object replacement?)")
465+
}
436466
}
437467

438468
FoundationTestSuite.test("NotificationCenter/addObserver(_:selector:name:object:)") {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@import Foundation;
2+
3+
extern NSInteger baseCounter;
4+
extern NSInteger subCounter;
5+
6+
@interface Base : NSObject
7+
- (nonnull instancetype)init __attribute__((objc_designated_initializer));
8+
- (nonnull instancetype)initConveniently;
9+
+ (nonnull instancetype)baseWithConvenientFactory:(_Bool)unused;
10+
+ (nonnull Base *)baseWithNormalFactory:(_Bool)unused;
11+
@end
12+
13+
@interface Sub : Base
14+
@end

0 commit comments

Comments
 (0)