Skip to content

Commit be55564

Browse files
committed
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 (cherry picked from commit 425c190)
1 parent cedfa64 commit be55564

21 files changed

+894
-78
lines changed

docs/SIL.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2325,6 +2325,7 @@ mark_uninitialized
23252325
mu_kind ::= 'derivedself'
23262326
mu_kind ::= 'derivedselfonly'
23272327
mu_kind ::= 'delegatingself'
2328+
mu_kind ::= 'delegatingselfallocated'
23282329

23292330
%2 = mark_uninitialized [var] %1 : $*T
23302331
// $T must be an address
@@ -2345,6 +2346,7 @@ the mark_uninitialized instruction refers to:
23452346
- ``derivedself``: designates ``self`` in a derived (non-root) class
23462347
- ``derivedselfonly``: designates ``self`` in a derived (non-root) class whose stored properties have already been initialized
23472348
- ``delegatingself``: designates ``self`` on a struct, enum, or class in a delegating constructor (one that calls self.init)
2349+
- ``delegatingselfallocated``: designates ``self`` on a class convenience initializer's initializing entry point
23482350

23492351
The purpose of the ``mark_uninitialized`` instruction is to enable
23502352
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
/// MarkUninitializedBehaviorInst - Indicates that a logical property

lib/ParseSIL/ParseSIL.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3355,10 +3355,12 @@ bool SILParser::parseSILInstruction(SILBuilder &B) {
33553355
Kind = MarkUninitializedInst::DerivedSelfOnly;
33563356
else if (KindId.str() == "delegatingself")
33573357
Kind = MarkUninitializedInst::DelegatingSelf;
3358+
else if (KindId.str() == "delegatingselfallocated")
3359+
Kind = MarkUninitializedInst::DelegatingSelfAllocated;
33583360
else {
33593361
P.diagnose(KindLoc, diag::expected_tok_in_sil_instr,
33603362
"var, rootself, crossmodulerootself, derivedself, "
3361-
"derivedselfonly, or delegatingself");
3363+
"derivedselfonly, delegatingself, or delegatingselfallocated");
33623364
return true;
33633365
}
33643366

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/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
@@ -1390,7 +1390,7 @@ void SILGenFunction::emitNativeToForeignThunk(SILDeclRef thunk) {
13901390
bool isInitializingToAllocatingInitThunk = false;
13911391
if (native.kind == SILDeclRef::Kind::Initializer) {
13921392
if (auto ctor = dyn_cast<ConstructorDecl>(native.getDecl())) {
1393-
if (!ctor->isDesignatedInit()) {
1393+
if (!ctor->isDesignatedInit() && !ctor->isObjC()) {
13941394
isInitializingToAllocatingInitThunk = true;
13951395
native = SILDeclRef(ctor, SILDeclRef::Kind::Allocator);
13961396
}

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/DIMemoryUseCollectorOwnership.cpp

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

18121812
static bool shouldPerformClassInitSelf(const DIMemoryObjectInfo &MemoryInfo) {
1813-
assert(!MemoryInfo.isDelegatingInit());
1813+
if (MemoryInfo.MemoryInst->isDelegatingSelfAllocated())
1814+
return true;
18141815

18151816
return MemoryInfo.isNonDelegatingInit() &&
18161817
MemoryInfo.getType()->getClassOrBoundGenericClass() != nullptr &&
@@ -1824,6 +1825,12 @@ void swift::ownership::collectDIElementUsesFrom(
18241825
const DIMemoryObjectInfo &MemoryInfo, DIElementUseInfo &UseInfo,
18251826
bool isDIFinished, bool TreatAddressToPointerAsInout) {
18261827

1828+
if (shouldPerformClassInitSelf(MemoryInfo)) {
1829+
ClassInitElementUseCollector UseCollector(MemoryInfo, UseInfo);
1830+
UseCollector.collectClassInitSelfUses();
1831+
return;
1832+
}
1833+
18271834
if (MemoryInfo.isDelegatingInit()) {
18281835
// When we're analyzing a delegating constructor, we aren't field sensitive
18291836
// at all. Just treat all members of self as uses of the single
@@ -1833,12 +1840,6 @@ void swift::ownership::collectDIElementUsesFrom(
18331840
return;
18341841
}
18351842

1836-
if (shouldPerformClassInitSelf(MemoryInfo)) {
1837-
ClassInitElementUseCollector UseCollector(MemoryInfo, UseInfo);
1838-
UseCollector.collectClassInitSelfUses();
1839-
return;
1840-
}
1841-
18421843
ElementUseCollector(MemoryInfo, UseInfo, isDIFinished,
18431844
TreatAddressToPointerAsInout)
18441845
.collectFrom();

lib/SILOptimizer/Mandatory/DIMemoryUseCollectorOwnership.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;

0 commit comments

Comments
 (0)