Skip to content

Dispatch initializers by their allocating entry point #19151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -6463,9 +6463,8 @@ class MissingMemberDecl : public Decl {

static MissingMemberDecl *
forInitializer(ASTContext &ctx, DeclContext *DC, DeclName name,
bool hasNormalVTableEntry,
bool hasAllocatingVTableEntry) {
unsigned entries = hasNormalVTableEntry + hasAllocatingVTableEntry;
bool hasVTableEntry) {
unsigned entries = hasVTableEntry ? 1 : 0;
return new (ctx) MissingMemberDecl(DC, name, entries, 0);
}

Expand Down
17 changes: 6 additions & 11 deletions include/swift/SIL/SILVTableVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,12 @@ template <class T> class SILVTableVisitor {
void maybeAddConstructor(ConstructorDecl *cd) {
assert(!cd->hasClangNode());

// Required constructors (or overrides thereof) have their allocating entry
// point in the vtable.
if (cd->isRequired()) {
SILDeclRef constant(cd, SILDeclRef::Kind::Allocator);
maybeAddEntry(constant, constant.requiresNewVTableEntry());
}

// All constructors have their initializing constructor in the
// vtable, which can be used by a convenience initializer.
SILDeclRef constant(cd, SILDeclRef::Kind::Initializer);
maybeAddEntry(constant, constant.requiresNewVTableEntry());
// The allocating entry point is what is used for dynamic dispatch.
// The initializing entry point for designated initializers is only
// necessary for super.init chaining, which is sufficiently constrained
// to never need dynamic dispatch.
SILDeclRef constant(cd, SILDeclRef::Kind::Allocator);
maybeAddEntry(constant, constant.requiresNewVTableEntry());
}

void maybeAddEntry(SILDeclRef declRef, bool needsNewEntry) {
Expand Down
22 changes: 22 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5251,6 +5251,18 @@ static bool requiresNewVTableEntry(const AbstractFunctionDecl *decl) {
// Dynamic methods are always accessed by objc_msgSend().
if (decl->isFinal() || decl->isDynamic() || decl->hasClangNode())
return false;

// Initializers are not normally inherited, but required initializers can
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by this "inherited". Requiring a new vtable entry also happens when the type changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A convenience initializer is always effectively final, though, so even if it overrides a designated init while changing type, it'll override an existing vtable entry, but never need a new one of its own.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sure, but this comment is about all initializers. I would have expected something like "Only designated initializers and required convenience initializers appear in the vtable; bail out if this is an initializer that is neither designated nor required."

// be overridden for invocation from dynamic types, and convenience initializers
// are conditionally inherited when all designated initializers are available,
// working by dynamically invoking the designated initializer implementation
// from the subclass. Convenience initializers can also override designated
// initializer implementations from their superclass.
if (auto ctor = dyn_cast<ConstructorDecl>(decl)) {
if (!ctor->isRequired() && !ctor->isDesignatedInit()) {
return false;
}
}

if (auto *accessor = dyn_cast<AccessorDecl>(decl)) {
// Check to see if it's one of the opaque accessors for the declaration.
Expand All @@ -5262,6 +5274,16 @@ static bool requiresNewVTableEntry(const AbstractFunctionDecl *decl) {
auto base = decl->getOverriddenDecl();
if (!base || base->hasClangNode() || base->isDynamic())
return true;

// As above, convenience initializers are not formally overridable in Swift
// vtables, although same-named initializers are modeled as overriding for
// various QoI and objc interop reasons. Even if we "override" a non-required
// convenience init, we still need a distinct vtable entry.
if (auto baseCtor = dyn_cast<ConstructorDecl>(base)) {
if (!baseCtor->isRequired() && !baseCtor->isDesignatedInit()) {
return true;
}
}

// If the method overrides something, we only need a new entry if the
// override has a more general AST type. However an abstraction
Expand Down
54 changes: 34 additions & 20 deletions lib/SIL/SILDeclRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,21 @@ swift::getMethodDispatch(AbstractFunctionDecl *method) {
if (method->isFinal())
return MethodDispatch::Static;

// Members defined directly inside a class are dynamically dispatched.
if (isa<ClassDecl>(dc))
return MethodDispatch::Class;

// Imported class methods are dynamically dispatched.
if (method->isObjC() && method->hasClangNode())
return MethodDispatch::Class;

// Members defined directly inside a class are dynamically dispatched.
if (isa<ClassDecl>(dc)) {
// Native convenience initializers are not dynamically dispatched unless
// required.
if (auto ctor = dyn_cast<ConstructorDecl>(method)) {
if (!ctor->isRequired() && !ctor->isDesignatedInit()
&& !requiresForeignEntryPoint(ctor))
return MethodDispatch::Static;
}
return MethodDispatch::Class;
}
}

// Otherwise, it can be referenced statically.
Expand Down Expand Up @@ -715,16 +723,6 @@ std::string SILDeclRef::mangle(ManglingKind MKind) const {
bool SILDeclRef::requiresNewVTableEntry() const {
if (cast<AbstractFunctionDecl>(getDecl())->needsNewVTableEntry())
return true;
if (kind == SILDeclRef::Kind::Allocator) {
auto *cd = cast<ConstructorDecl>(getDecl());
if (cd->isRequired()) {
auto *baseCD = cd->getOverriddenDecl();
if(!baseCD ||
!baseCD->isRequired() ||
baseCD->hasClangNode())
return true;
}
}
return false;
}

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

SILDeclRef SILDeclRef::getNextOverriddenVTableEntry() const {
if (auto overridden = getOverridden()) {
// If we overrode a foreign decl, a dynamic method, this is an
// If we overrode a foreign decl or dynamic method, if this is an
// accessor for a property that overrides an ObjC decl, or if it is an
// @NSManaged property, then it won't be in the vtable.
if (overridden.getDecl()->hasClangNode())
return SILDeclRef();

// If we overrode a non-required initializer, there won't be a vtable
// slot for the allocator.

// An @objc convenience initializer can be "overridden" in the sense that
// its selector is reclaimed by a subclass's convenience init with the
// same name. The AST models this as an override for the purposes of
// ObjC selector validation, but it isn't for Swift method dispatch
// purposes.
if (overridden.kind == SILDeclRef::Kind::Allocator) {
if (!cast<ConstructorDecl>(overridden.getDecl())->isRequired())
auto overriddenCtor = cast<ConstructorDecl>(overridden.getDecl());
if (!overriddenCtor->isDesignatedInit()
&& !overriddenCtor->isRequired())
return SILDeclRef();
} else if (overridden.getDecl()->isDynamic()) {
}

// Initializing entry points for initializers won't be in the vtable.
// For Swift designated initializers, they're only used in super.init
// chains, which can always be statically resolved. Other native Swift
// initializers only have allocating entry points. ObjC initializers always
// have the initializing entry point (corresponding to the -init method)
// but those are never in the vtable.
if (overridden.kind == SILDeclRef::Kind::Initializer) {
return SILDeclRef();
}
if (overridden.getDecl()->isDynamic()) {
return SILDeclRef();
}

Expand Down
68 changes: 44 additions & 24 deletions lib/SILGen/SILGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -757,37 +757,16 @@ void SILGenModule::emitConstructor(ConstructorDecl *decl) {

bool ForCoverageMapping = doesASTRequireProfiling(M, decl);

if (declCtx->getSelfClassDecl()) {
// Class constructors have separate entry points for allocation and
// initialization.
auto emitClassAllocatorThunk = [&]{
emitOrDelayFunction(
*this, constant, [this, constant, decl](SILFunction *f) {
preEmitFunction(constant, decl, f, decl);
PrettyStackTraceSILFunction X("silgen emitConstructor", f);
SILGenFunction(*this, *f, decl).emitClassConstructorAllocator(decl);
postEmitFunction(constant, f);
});

// Constructors may not have bodies if they've been imported, or if they've
// been parsed from a textual interface.
if (decl->hasBody()) {
SILDeclRef initConstant(decl, SILDeclRef::Kind::Initializer);
emitOrDelayFunction(
*this, initConstant,
[this, initConstant, decl, declCtx](SILFunction *initF) {
preEmitFunction(initConstant, decl, initF, decl);
PrettyStackTraceSILFunction X("silgen constructor initializer",
initF);
initF->setProfiler(
getOrCreateProfilerForConstructors(declCtx, decl));
SILGenFunction(*this, *initF, decl)
.emitClassConstructorInitializer(decl);
postEmitFunction(initConstant, initF);
},
/*forceEmission=*/ForCoverageMapping);
}
} else {
// Struct and enum constructors do everything in a single function.
};
auto emitValueConstructorIfHasBody = [&]{
if (decl->hasBody()) {
emitOrDelayFunction(
*this, constant, [this, constant, decl, declCtx](SILFunction *f) {
Expand All @@ -798,6 +777,47 @@ void SILGenModule::emitConstructor(ConstructorDecl *decl) {
postEmitFunction(constant, f);
});
}
};

if (declCtx->getSelfClassDecl()) {
// Designated initializers for classes have have separate entry points for
// allocation and initialization.
if (decl->isDesignatedInit()) {
emitClassAllocatorThunk();

// Constructors may not have bodies if they've been imported, or if they've
// been parsed from a textual interface.
if (decl->hasBody()) {
SILDeclRef initConstant(decl, SILDeclRef::Kind::Initializer);
emitOrDelayFunction(
*this, initConstant,
[this, initConstant, decl, declCtx](SILFunction *initF) {
preEmitFunction(initConstant, decl, initF, decl);
PrettyStackTraceSILFunction X("silgen constructor initializer",
initF);
initF->setProfiler(
getOrCreateProfilerForConstructors(declCtx, decl));
SILGenFunction(*this, *initF, decl)
.emitClassConstructorInitializer(decl);
postEmitFunction(initConstant, initF);
},
/*forceEmission=*/ForCoverageMapping);
}
// Convenience initializers for classes behave more like value constructors
// in that there's only an allocating entry point that effectively
// "constructs" the self reference by invoking another initializer.
} else {
emitValueConstructorIfHasBody();

// If the convenience initializer was imported from ObjC, we still have to
// emit the allocator thunk.
if (decl->hasClangNode()) {
emitClassAllocatorThunk();
}
}
} else {
// Struct and enum constructors do everything in a single function.
emitValueConstructorIfHasBody();
}
}

Expand Down
Loading