Skip to content

Commit 8665342

Browse files
authored
Merge pull request #19151 from jckarter/allocating-convenience-initializers
Dispatch initializers by their allocating entry point
2 parents 6aae40c + 91bf80f commit 8665342

File tree

62 files changed

+782
-827
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+782
-827
lines changed

include/swift/AST/Decl.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6482,9 +6482,8 @@ class MissingMemberDecl : public Decl {
64826482

64836483
static MissingMemberDecl *
64846484
forInitializer(ASTContext &ctx, DeclContext *DC, DeclName name,
6485-
bool hasNormalVTableEntry,
6486-
bool hasAllocatingVTableEntry) {
6487-
unsigned entries = hasNormalVTableEntry + hasAllocatingVTableEntry;
6485+
bool hasVTableEntry) {
6486+
unsigned entries = hasVTableEntry ? 1 : 0;
64886487
return new (ctx) MissingMemberDecl(DC, name, entries, 0);
64896488
}
64906489

include/swift/SIL/SILVTableVisitor.h

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,12 @@ template <class T> class SILVTableVisitor {
9393
void maybeAddConstructor(ConstructorDecl *cd) {
9494
assert(!cd->hasClangNode());
9595

96-
// Required constructors (or overrides thereof) have their allocating entry
97-
// point in the vtable.
98-
if (cd->isRequired()) {
99-
SILDeclRef constant(cd, SILDeclRef::Kind::Allocator);
100-
maybeAddEntry(constant, constant.requiresNewVTableEntry());
101-
}
102-
103-
// All constructors have their initializing constructor in the
104-
// vtable, which can be used by a convenience initializer.
105-
SILDeclRef constant(cd, SILDeclRef::Kind::Initializer);
106-
maybeAddEntry(constant, constant.requiresNewVTableEntry());
96+
// The allocating entry point is what is used for dynamic dispatch.
97+
// The initializing entry point for designated initializers is only
98+
// necessary for super.init chaining, which is sufficiently constrained
99+
// to never need dynamic dispatch.
100+
SILDeclRef constant(cd, SILDeclRef::Kind::Allocator);
101+
maybeAddEntry(constant, constant.requiresNewVTableEntry());
107102
}
108103

109104
void maybeAddEntry(SILDeclRef declRef, bool needsNewEntry) {

lib/AST/Decl.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5253,6 +5253,18 @@ static bool requiresNewVTableEntry(const AbstractFunctionDecl *decl) {
52535253
// Dynamic methods are always accessed by objc_msgSend().
52545254
if (decl->isFinal() || decl->isDynamic() || decl->hasClangNode())
52555255
return false;
5256+
5257+
// Initializers are not normally inherited, but required initializers can
5258+
// be overridden for invocation from dynamic types, and convenience initializers
5259+
// are conditionally inherited when all designated initializers are available,
5260+
// working by dynamically invoking the designated initializer implementation
5261+
// from the subclass. Convenience initializers can also override designated
5262+
// initializer implementations from their superclass.
5263+
if (auto ctor = dyn_cast<ConstructorDecl>(decl)) {
5264+
if (!ctor->isRequired() && !ctor->isDesignatedInit()) {
5265+
return false;
5266+
}
5267+
}
52565268

52575269
if (auto *accessor = dyn_cast<AccessorDecl>(decl)) {
52585270
// Check to see if it's one of the opaque accessors for the declaration.
@@ -5264,6 +5276,16 @@ static bool requiresNewVTableEntry(const AbstractFunctionDecl *decl) {
52645276
auto base = decl->getOverriddenDecl();
52655277
if (!base || base->hasClangNode() || base->isDynamic())
52665278
return true;
5279+
5280+
// As above, convenience initializers are not formally overridable in Swift
5281+
// vtables, although same-named initializers are modeled as overriding for
5282+
// various QoI and objc interop reasons. Even if we "override" a non-required
5283+
// convenience init, we still need a distinct vtable entry.
5284+
if (auto baseCtor = dyn_cast<ConstructorDecl>(base)) {
5285+
if (!baseCtor->isRequired() && !baseCtor->isDesignatedInit()) {
5286+
return true;
5287+
}
5288+
}
52675289

52685290
// If the method overrides something, we only need a new entry if the
52695291
// override has a more general AST type. However an abstraction

lib/SIL/SILDeclRef.cpp

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,21 @@ swift::getMethodDispatch(AbstractFunctionDecl *method) {
4848
if (method->isFinal())
4949
return MethodDispatch::Static;
5050

51-
// Members defined directly inside a class are dynamically dispatched.
52-
if (isa<ClassDecl>(dc))
53-
return MethodDispatch::Class;
54-
5551
// Imported class methods are dynamically dispatched.
5652
if (method->isObjC() && method->hasClangNode())
5753
return MethodDispatch::Class;
54+
55+
// Members defined directly inside a class are dynamically dispatched.
56+
if (isa<ClassDecl>(dc)) {
57+
// Native convenience initializers are not dynamically dispatched unless
58+
// required.
59+
if (auto ctor = dyn_cast<ConstructorDecl>(method)) {
60+
if (!ctor->isRequired() && !ctor->isDesignatedInit()
61+
&& !requiresForeignEntryPoint(ctor))
62+
return MethodDispatch::Static;
63+
}
64+
return MethodDispatch::Class;
65+
}
5866
}
5967

6068
// Otherwise, it can be referenced statically.
@@ -715,16 +723,6 @@ std::string SILDeclRef::mangle(ManglingKind MKind) const {
715723
bool SILDeclRef::requiresNewVTableEntry() const {
716724
if (cast<AbstractFunctionDecl>(getDecl())->needsNewVTableEntry())
717725
return true;
718-
if (kind == SILDeclRef::Kind::Allocator) {
719-
auto *cd = cast<ConstructorDecl>(getDecl());
720-
if (cd->isRequired()) {
721-
auto *baseCD = cd->getOverriddenDecl();
722-
if(!baseCD ||
723-
!baseCD->isRequired() ||
724-
baseCD->hasClangNode())
725-
return true;
726-
}
727-
}
728726
return false;
729727
}
730728

@@ -748,18 +746,34 @@ SILDeclRef SILDeclRef::getOverridden() const {
748746

749747
SILDeclRef SILDeclRef::getNextOverriddenVTableEntry() const {
750748
if (auto overridden = getOverridden()) {
751-
// If we overrode a foreign decl, a dynamic method, this is an
749+
// If we overrode a foreign decl or dynamic method, if this is an
752750
// accessor for a property that overrides an ObjC decl, or if it is an
753751
// @NSManaged property, then it won't be in the vtable.
754752
if (overridden.getDecl()->hasClangNode())
755753
return SILDeclRef();
756-
757-
// If we overrode a non-required initializer, there won't be a vtable
758-
// slot for the allocator.
754+
755+
// An @objc convenience initializer can be "overridden" in the sense that
756+
// its selector is reclaimed by a subclass's convenience init with the
757+
// same name. The AST models this as an override for the purposes of
758+
// ObjC selector validation, but it isn't for Swift method dispatch
759+
// purposes.
759760
if (overridden.kind == SILDeclRef::Kind::Allocator) {
760-
if (!cast<ConstructorDecl>(overridden.getDecl())->isRequired())
761+
auto overriddenCtor = cast<ConstructorDecl>(overridden.getDecl());
762+
if (!overriddenCtor->isDesignatedInit()
763+
&& !overriddenCtor->isRequired())
761764
return SILDeclRef();
762-
} else if (overridden.getDecl()->isDynamic()) {
765+
}
766+
767+
// Initializing entry points for initializers won't be in the vtable.
768+
// For Swift designated initializers, they're only used in super.init
769+
// chains, which can always be statically resolved. Other native Swift
770+
// initializers only have allocating entry points. ObjC initializers always
771+
// have the initializing entry point (corresponding to the -init method)
772+
// but those are never in the vtable.
773+
if (overridden.kind == SILDeclRef::Kind::Initializer) {
774+
return SILDeclRef();
775+
}
776+
if (overridden.getDecl()->isDynamic()) {
763777
return SILDeclRef();
764778
}
765779

lib/SILGen/SILGen.cpp

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -757,37 +757,16 @@ void SILGenModule::emitConstructor(ConstructorDecl *decl) {
757757

758758
bool ForCoverageMapping = doesASTRequireProfiling(M, decl);
759759

760-
if (declCtx->getSelfClassDecl()) {
761-
// Class constructors have separate entry points for allocation and
762-
// initialization.
760+
auto emitClassAllocatorThunk = [&]{
763761
emitOrDelayFunction(
764762
*this, constant, [this, constant, decl](SILFunction *f) {
765763
preEmitFunction(constant, decl, f, decl);
766764
PrettyStackTraceSILFunction X("silgen emitConstructor", f);
767765
SILGenFunction(*this, *f, decl).emitClassConstructorAllocator(decl);
768766
postEmitFunction(constant, f);
769767
});
770-
771-
// Constructors may not have bodies if they've been imported, or if they've
772-
// been parsed from a textual interface.
773-
if (decl->hasBody()) {
774-
SILDeclRef initConstant(decl, SILDeclRef::Kind::Initializer);
775-
emitOrDelayFunction(
776-
*this, initConstant,
777-
[this, initConstant, decl, declCtx](SILFunction *initF) {
778-
preEmitFunction(initConstant, decl, initF, decl);
779-
PrettyStackTraceSILFunction X("silgen constructor initializer",
780-
initF);
781-
initF->setProfiler(
782-
getOrCreateProfilerForConstructors(declCtx, decl));
783-
SILGenFunction(*this, *initF, decl)
784-
.emitClassConstructorInitializer(decl);
785-
postEmitFunction(initConstant, initF);
786-
},
787-
/*forceEmission=*/ForCoverageMapping);
788-
}
789-
} else {
790-
// Struct and enum constructors do everything in a single function.
768+
};
769+
auto emitValueConstructorIfHasBody = [&]{
791770
if (decl->hasBody()) {
792771
emitOrDelayFunction(
793772
*this, constant, [this, constant, decl, declCtx](SILFunction *f) {
@@ -798,6 +777,47 @@ void SILGenModule::emitConstructor(ConstructorDecl *decl) {
798777
postEmitFunction(constant, f);
799778
});
800779
}
780+
};
781+
782+
if (declCtx->getSelfClassDecl()) {
783+
// Designated initializers for classes have have separate entry points for
784+
// allocation and initialization.
785+
if (decl->isDesignatedInit()) {
786+
emitClassAllocatorThunk();
787+
788+
// Constructors may not have bodies if they've been imported, or if they've
789+
// been parsed from a textual interface.
790+
if (decl->hasBody()) {
791+
SILDeclRef initConstant(decl, SILDeclRef::Kind::Initializer);
792+
emitOrDelayFunction(
793+
*this, initConstant,
794+
[this, initConstant, decl, declCtx](SILFunction *initF) {
795+
preEmitFunction(initConstant, decl, initF, decl);
796+
PrettyStackTraceSILFunction X("silgen constructor initializer",
797+
initF);
798+
initF->setProfiler(
799+
getOrCreateProfilerForConstructors(declCtx, decl));
800+
SILGenFunction(*this, *initF, decl)
801+
.emitClassConstructorInitializer(decl);
802+
postEmitFunction(initConstant, initF);
803+
},
804+
/*forceEmission=*/ForCoverageMapping);
805+
}
806+
// Convenience initializers for classes behave more like value constructors
807+
// in that there's only an allocating entry point that effectively
808+
// "constructs" the self reference by invoking another initializer.
809+
} else {
810+
emitValueConstructorIfHasBody();
811+
812+
// If the convenience initializer was imported from ObjC, we still have to
813+
// emit the allocator thunk.
814+
if (decl->hasClangNode()) {
815+
emitClassAllocatorThunk();
816+
}
817+
}
818+
} else {
819+
// Struct and enum constructors do everything in a single function.
820+
emitValueConstructorIfHasBody();
801821
}
802822
}
803823

0 commit comments

Comments
 (0)