Skip to content

Method dispatch cleanup #13742

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 11 commits into from
Jan 5, 2018
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
8 changes: 0 additions & 8 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4248,14 +4248,6 @@ class AbstractStorageDecl : public ValueDecl {
/// only valid on a declaration with Observing storage.
FuncDecl *getDidSetFunc() const { return getDidSetInfo().DidSet; }

/// Return true if this storage can (but doesn't have to) be accessed with
/// Objective-C-compatible getters and setters.
bool hasForeignGetterAndSetter() const;

/// Return true if this storage *must* be accessed with Objective-C-compatible
/// getters and setters.
bool requiresForeignGetterAndSetter() const;

/// Given that this is an Objective-C property or subscript declaration,
/// produce its getter selector.
ObjCSelector getObjCGetterSelector(LazyResolver *resolver = nullptr,
Expand Down
67 changes: 14 additions & 53 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1260,34 +1260,25 @@ SourceRange IfConfigDecl::getSourceRange() const {
}

static bool isPolymorphic(const AbstractStorageDecl *storage) {
auto nominal = storage->getDeclContext()
->getAsNominalTypeOrNominalTypeExtensionContext();
if (!nominal) return false;

switch (nominal->getKind()) {
#define DECL(ID, BASE) case DeclKind::ID:
#define NOMINAL_TYPE_DECL(ID, BASE)
#include "swift/AST/DeclNodes.def"
llvm_unreachable("not a nominal type!");

case DeclKind::Struct:
case DeclKind::Enum:
return false;
if (storage->isDynamic())
return true;

case DeclKind::Protocol:
return !storage->getDeclContext()->isExtensionContext();
// Imported declarations behave like they are dynamic, even if they're
// not marked as such explicitly.
if (storage->isObjC() && storage->hasClangNode())
return true;

case DeclKind::Class:
// Final properties can always be direct, even in classes.
if (storage->isFinal())
return false;
// Extension properties are statically dispatched, unless they're @objc.
if (storage->getDeclContext()->isExtensionContext()
&& !storage->isObjC())
if (auto *classDecl = dyn_cast<ClassDecl>(storage->getDeclContext())) {
if (storage->isFinal() || classDecl->isFinal())
return false;

return true;
}
llvm_unreachable("bad DeclKind");

if (auto *protoDecl = dyn_cast<ProtocolDecl>(storage->getDeclContext()))
return true;

return false;
}

/// Determines the access semantics to use in a DeclRefExpr or
Expand Down Expand Up @@ -4186,36 +4177,6 @@ bool VarDecl::isSelfParameter() const {
return false;
}

/// Return true if this stored property has a getter and
/// setter that are accessible from Objective-C.
bool AbstractStorageDecl::hasForeignGetterAndSetter() const {
if (auto override = getOverriddenDecl())
return override->hasForeignGetterAndSetter();

if (!isObjC())
return false;

return true;
}

bool AbstractStorageDecl::requiresForeignGetterAndSetter() const {
if (isFinal())
return false;
if (hasAccessorFunctions() && getGetter()->isImportAsMember())
return true;
if (!hasForeignGetterAndSetter())
return false;
// Imported accessors are foreign and only have objc entry points.
if (hasClangNode())
return true;
// Otherwise, we only dispatch by @objc if the declaration is dynamic,
// NSManaged, or dispatched through an ObjC protocol.
return isDynamic()
|| getAttrs().hasAttribute<NSManagedAttr>()
|| (isa<ProtocolDecl>(getDeclContext()) && isProtocolRequirement());
}


bool VarDecl::isAnonClosureParam() const {
auto name = getName();
if (name.empty())
Expand Down
174 changes: 25 additions & 149 deletions lib/SIL/SILDeclRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ using namespace swift;
/// Get the method dispatch mechanism for a method.
MethodDispatch
swift::getMethodDispatch(AbstractFunctionDecl *method) {
// Final methods can be statically referenced.
if (method->isFinal())
return MethodDispatch::Static;
// Some methods are forced to be statically dispatched.
if (method->hasForcedStaticDispatch())
return MethodDispatch::Static;
Expand All @@ -41,23 +38,23 @@ swift::getMethodDispatch(AbstractFunctionDecl *method) {
if (method->isImportAsMember())
return MethodDispatch::Static;

// If this declaration is in a class but not marked final, then it is
// always dynamically dispatched.
auto dc = method->getDeclContext();
if (isa<ClassDecl>(dc))
return MethodDispatch::Class;

// Class extension methods are only dynamically dispatched if they're
// dispatched by objc_msgSend, which happens if they're foreign or dynamic.
if (dc->getAsClassOrClassExtensionContext()) {
if (method->hasClangNode())
return MethodDispatch::Class;
if (auto fd = dyn_cast<FuncDecl>(method)) {
if (fd->isAccessor() && fd->getAccessorStorageDecl()->hasClangNode())
return MethodDispatch::Class;
}
if (method->isDynamic())
return MethodDispatch::Class;

// Final methods can be statically referenced.
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;
}

// Otherwise, it can be referenced statically.
Expand All @@ -79,151 +76,30 @@ bool swift::requiresForeignToNativeThunk(ValueDecl *vd) {
return false;
}

/// FIXME: merge requiresForeignEntryPoint() into getMethodDispatch() and add
/// an ObjectiveC case to the MethodDispatch enum.
bool swift::requiresForeignEntryPoint(ValueDecl *vd) {
if (vd->isImportAsMember())
assert(!isa<AbstractStorageDecl>(vd));

if (vd->isDynamic())
return true;

// Final functions never require ObjC dispatch.
if (vd->isFinal())
return false;
if (vd->isObjC() && isa<ProtocolDecl>(vd->getDeclContext()))
return true;

if (vd->isImportAsMember())
return true;

if (requiresForeignToNativeThunk(vd))
if (vd->hasClangNode())
return true;

if (auto *fd = dyn_cast<FuncDecl>(vd)) {

// Property accessors should be generated alongside the property.
if (fd->isGetterOrSetter())
return requiresForeignEntryPoint(fd->getAccessorStorageDecl());

return fd->isDynamic();
}

if (auto *cd = dyn_cast<ConstructorDecl>(vd)) {
if (cd->hasClangNode())
return true;

return cd->isDynamic();
}

if (auto *asd = dyn_cast<AbstractStorageDecl>(vd))
return asd->requiresForeignGetterAndSetter();

return vd->isDynamic();
}

/// TODO: We should consult the cached LoweredLocalCaptures the SIL
/// TypeConverter calculates, but that would require plumbing SILModule&
/// through every SILDeclRef constructor. Since this is only used to determine
/// "natural uncurry level", and "uncurry level" is a concept we'd like to
/// phase out, it's not worth it.
static bool hasLoweredLocalCaptures(AnyFunctionRef AFR,
llvm::DenseSet<AnyFunctionRef> &visited) {
if (!AFR.getCaptureInfo().hasLocalCaptures())
return false;

// Scan for local, non-function captures.
bool functionCapturesToRecursivelyCheck = false;
auto addFunctionCapture = [&](AnyFunctionRef capture) {
if (visited.find(capture) == visited.end())
functionCapturesToRecursivelyCheck = true;
};
for (auto &capture : AFR.getCaptureInfo().getCaptures()) {
if (!capture.getDecl()->getDeclContext()->isLocalContext())
continue;
// We transitively capture a local function's captures.
if (auto func = dyn_cast<AbstractFunctionDecl>(capture.getDecl())) {
addFunctionCapture(func);
continue;
}
// We may either directly capture properties, or capture through their
// accessors.
if (auto var = dyn_cast<VarDecl>(capture.getDecl())) {
switch (var->getStorageKind()) {
case VarDecl::StoredWithTrivialAccessors:
llvm_unreachable("stored local variable with trivial accessors?");

case VarDecl::InheritedWithObservers:
llvm_unreachable("inherited local variable?");

case VarDecl::StoredWithObservers:
case VarDecl::Addressed:
case VarDecl::AddressedWithTrivialAccessors:
case VarDecl::AddressedWithObservers:
case VarDecl::ComputedWithMutableAddress:
// Directly capture storage if we're supposed to.
if (capture.isDirect())
return true;

// Otherwise, transitively capture the accessors.
LLVM_FALLTHROUGH;

case VarDecl::Computed:
addFunctionCapture(var->getGetter());
if (auto setter = var->getSetter())
addFunctionCapture(setter);
continue;

case VarDecl::Stored:
if (fd->isGetterOrSetter()) {
auto *asd = fd->getAccessorStorageDecl();
if (asd->isObjC() && asd->hasClangNode())
return true;
}
}
// Anything else is directly captured.
return true;
}

// Recursively consider function captures, since we didn't have any direct
// captures.
auto captureHasLocalCaptures = [&](AnyFunctionRef capture) -> bool {
if (visited.insert(capture).second)
return hasLoweredLocalCaptures(capture, visited);
return false;
};

if (functionCapturesToRecursivelyCheck) {
for (auto &capture : AFR.getCaptureInfo().getCaptures()) {
if (!capture.getDecl()->getDeclContext()->isLocalContext())
continue;
if (auto func = dyn_cast<AbstractFunctionDecl>(capture.getDecl())) {
if (captureHasLocalCaptures(func))
return true;
continue;
}
if (auto var = dyn_cast<VarDecl>(capture.getDecl())) {
switch (var->getStorageKind()) {
case VarDecl::StoredWithTrivialAccessors:
llvm_unreachable("stored local variable with trivial accessors?");

case VarDecl::InheritedWithObservers:
llvm_unreachable("inherited local variable?");

case VarDecl::StoredWithObservers:
case VarDecl::Addressed:
case VarDecl::AddressedWithTrivialAccessors:
case VarDecl::AddressedWithObservers:
case VarDecl::ComputedWithMutableAddress:
assert(!capture.isDirect() && "should have short circuited out");
// Otherwise, transitively capture the accessors.
LLVM_FALLTHROUGH;

case VarDecl::Computed:
if (captureHasLocalCaptures(var->getGetter()))
return true;
if (auto setter = var->getSetter())
if (captureHasLocalCaptures(setter))
return true;
continue;

case VarDecl::Stored:
llvm_unreachable("should have short circuited out");
}
}
llvm_unreachable("should have short circuited out");
}
}


return false;
}

Expand Down
42 changes: 11 additions & 31 deletions lib/SILGen/SILGenApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4736,7 +4736,7 @@ static Callee getBaseAccessorFunctionRef(SILGenFunction &SGF,
}

// Dispatch in a struct/enum or to a final method is always direct.
if (!isClassDispatch || decl->isFinal())
if (!isClassDispatch)
return Callee::forDirect(SGF, constant, subs, loc);

// Otherwise, if we have a non-final class dispatch to a normal method,
Expand Down Expand Up @@ -4974,28 +4974,10 @@ ArgumentSource SILGenFunction::prepareAccessorBaseArg(SILLocation loc,
return Preparer.prepare();
}

static bool shouldReferenceForeignAccessor(AbstractStorageDecl *storage,
bool isDirectUse) {
// Members of Objective-C protocols should be dynamically dispatched.
if (auto *protoDecl = dyn_cast<ProtocolDecl>(storage->getDeclContext()))
return protoDecl->isObjC();

// C functions imported as members should be referenced as C functions.
if (storage->getGetter()->isImportAsMember())
return true;

// Otherwise, favor native entry points for direct accesses.
if (isDirectUse)
return false;

return storage->requiresForeignGetterAndSetter();
}

SILDeclRef SILGenFunction::getGetterDeclRef(AbstractStorageDecl *storage,
bool isDirectUse) {
// Use the ObjC entry point
return SILDeclRef(storage->getGetter(), SILDeclRef::Kind::Func)
.asForeign(shouldReferenceForeignAccessor(storage, isDirectUse));
SILDeclRef SILGenFunction::getGetterDeclRef(AbstractStorageDecl *storage) {
auto *getter = storage->getGetter();
return SILDeclRef(getter, SILDeclRef::Kind::Func)
.asForeign(requiresForeignEntryPoint(getter));
}

/// Emit a call to a getter.
Expand Down Expand Up @@ -5031,10 +5013,10 @@ emitGetAccessor(SILLocation loc, SILDeclRef get,
return emission.apply(c);
}

SILDeclRef SILGenFunction::getSetterDeclRef(AbstractStorageDecl *storage,
bool isDirectUse) {
return SILDeclRef(storage->getSetter(), SILDeclRef::Kind::Func)
.asForeign(shouldReferenceForeignAccessor(storage, isDirectUse));
SILDeclRef SILGenFunction::getSetterDeclRef(AbstractStorageDecl *storage) {
auto *setter = storage->getSetter();
return SILDeclRef(setter, SILDeclRef::Kind::Func)
.asForeign(requiresForeignEntryPoint(setter));
}

void SILGenFunction::emitSetAccessor(SILLocation loc, SILDeclRef set,
Expand Down Expand Up @@ -5097,8 +5079,7 @@ void SILGenFunction::emitSetAccessor(SILLocation loc, SILDeclRef set,
}

SILDeclRef
SILGenFunction::getMaterializeForSetDeclRef(AbstractStorageDecl *storage,
bool isDirectUse) {
SILGenFunction::getMaterializeForSetDeclRef(AbstractStorageDecl *storage) {
return SILDeclRef(storage->getMaterializeForSetFunc(),
SILDeclRef::Kind::Func);
}
Expand Down Expand Up @@ -5176,8 +5157,7 @@ emitMaterializeForSetAccessor(SILLocation loc, SILDeclRef materializeForSet,
}

SILDeclRef SILGenFunction::getAddressorDeclRef(AbstractStorageDecl *storage,
AccessKind accessKind,
bool isDirectUse) {
AccessKind accessKind) {
FuncDecl *addressorFunc = storage->getAddressorForAccess(accessKind);
return SILDeclRef(addressorFunc, SILDeclRef::Kind::Func);
}
Expand Down
Loading