Skip to content

[5.3] Mark non-foreign entry points of @objc dynamic methods in generic classes dynamically_replaceable #32300

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
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
32 changes: 32 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2711,6 +2711,7 @@ class ValueDecl : public Decl {
/// Is this declaration marked with 'dynamic'?
bool isDynamic() const;

private:
bool isObjCDynamic() const {
return isObjC() && isDynamic();
}
Expand All @@ -2719,6 +2720,37 @@ class ValueDecl : public Decl {
return !isObjC() && isDynamic();
}

bool isObjCDynamicInGenericClass() const;

public:
/// Should we use Objective-C method dispatch for this decl.
bool shouldUseObjCDispatch() const {
return isObjCDynamic();
}

/// Should we use native dynamic function replacement dispatch for this decl.
bool shouldUseNativeDynamicDispatch() const {
return isNativeDynamic();
}

/// Should we use Objective-C category based function replacement for this
/// decl.
/// This is all `@objc dynamic` methods except for such methods in native
/// generic classes. We can't use a category for generic classes so we use
/// native replacement instead (this behavior is only enabled with
/// -enable-implicit-dynamic).
bool shouldUseObjCMethodReplacement() const;

/// Should we use native dynamic function replacement mechanism for this decl.
/// This is all native dynamic methods except for `@objc dynamic` methods in
/// generic classes (see above).
bool shouldUseNativeMethodReplacement() const;

/// Is this a native dynamic function replacement based replacement.
/// This is all @_dynamicReplacement(for:) of native functions and @objc
/// dynamic methods on generic classes (see above).
bool isNativeMethodReplacement() const;

bool isEffectiveLinkageMoreVisibleThan(ValueDecl *other) const {
return (std::min(getEffectiveAccess(), AccessLevel::Public) >
std::min(other->getEffectiveAccess(), AccessLevel::Public));
Expand Down
5 changes: 5 additions & 0 deletions include/swift/Serialization/Validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class ExtendedValidationInfo {
unsigned IsSIB : 1;
unsigned IsTestable : 1;
unsigned ResilienceStrategy : 2;
unsigned IsImplicitDynamicEnabled: 1;
} Bits;
public:
ExtendedValidationInfo() : Bits() {}
Expand All @@ -123,6 +124,10 @@ class ExtendedValidationInfo {
void setPrivateImportsEnabled(bool enabled) {
Bits.ArePrivateImportsEnabled = enabled;
}
bool isImplicitDynamicEnabled() { return Bits.IsImplicitDynamicEnabled; }
void setImplicitDynamicEnabled(bool val) {
Bits.IsImplicitDynamicEnabled = val;
}
bool isTestable() const { return Bits.IsTestable; }
void setIsTestable(bool val) {
Bits.IsTestable = val;
Expand Down
4 changes: 2 additions & 2 deletions lib/AST/ASTVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3111,12 +3111,12 @@ class Verifier : public ASTWalker {
storageDecl->getWriteImpl() ==
WriteImplKind::StoredWithObservers ||
storageDecl->getWriteImpl() == WriteImplKind::MutableAddress) &&
storageDecl->isNativeDynamic()) &&
storageDecl->shouldUseNativeDynamicDispatch()) &&
// We allow a non dynamic getter if there is a dynamic read.
!(FD->isGetter() &&
(storageDecl->getReadImpl() == ReadImplKind::Read ||
storageDecl->getReadImpl() == ReadImplKind::Address) &&
storageDecl->isNativeDynamic())) {
storageDecl->shouldUseNativeDynamicDispatch())) {
Out << "Property and accessor do not match for 'dynamic'\n";
abort();
}
Expand Down
61 changes: 57 additions & 4 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1950,7 +1950,7 @@ SourceRange IfConfigDecl::getSourceRange() const {
}

static bool isPolymorphic(const AbstractStorageDecl *storage) {
if (storage->isObjCDynamic())
if (storage->shouldUseObjCDispatch())
return true;


Expand Down Expand Up @@ -2083,7 +2083,7 @@ getDirectReadWriteAccessStrategy(const AbstractStorageDecl *storage) {
return AccessStrategy::getStorage();
case ReadWriteImplKind::Stored: {
// If the storage isDynamic (and not @objc) use the accessors.
if (storage->isNativeDynamic())
if (storage->shouldUseNativeDynamicDispatch())
return AccessStrategy::getMaterializeToTemporary(
getOpaqueReadAccessStrategy(storage, false),
getOpaqueWriteAccessStrategy(storage, false));
Expand Down Expand Up @@ -2168,7 +2168,7 @@ AbstractStorageDecl::getAccessStrategy(AccessSemantics semantics,
if (isPolymorphic(this))
return getOpaqueAccessStrategy(this, accessKind, /*dispatch*/ true);

if (isNativeDynamic())
if (shouldUseNativeDynamicDispatch())
return getOpaqueAccessStrategy(this, accessKind, /*dispatch*/ false);

// If the storage is resilient from the given module and resilience
Expand Down Expand Up @@ -2926,6 +2926,59 @@ bool ValueDecl::isDynamic() const {
getAttrs().hasAttribute<DynamicAttr>());
}

bool ValueDecl::isObjCDynamicInGenericClass() const {
if (!isObjCDynamic())
return false;

auto *DC = this->getDeclContext();
auto *classDecl = DC->getSelfClassDecl();
if (!classDecl)
return false;

return classDecl->isGenericContext() && !classDecl->usesObjCGenericsModel();
}

bool ValueDecl::shouldUseObjCMethodReplacement() const {
if (isNativeDynamic())
return false;

if (getModuleContext()->isImplicitDynamicEnabled() &&
isObjCDynamicInGenericClass())
return false;

return isObjCDynamic();
}

bool ValueDecl::shouldUseNativeMethodReplacement() const {
if (isNativeDynamic())
return true;

if (!isObjCDynamicInGenericClass())
return false;

auto *replacedDecl = getDynamicallyReplacedDecl();
if (replacedDecl)
return false;

return getModuleContext()->isImplicitDynamicEnabled();
}

bool ValueDecl::isNativeMethodReplacement() const {
// Is this a @_dynamicReplacement(for:) that use the native dynamic function
// replacement mechanism.
auto *replacedDecl = getDynamicallyReplacedDecl();
if (!replacedDecl)
return false;

if (isNativeDynamic())
return true;

if (isObjCDynamicInGenericClass())
return replacedDecl->getModuleContext()->isImplicitDynamicEnabled();

return false;
}

void ValueDecl::setIsDynamic(bool value) {
assert(!LazySemanticInfo.isDynamicComputed ||
LazySemanticInfo.isDynamic == value);
Expand Down Expand Up @@ -5143,7 +5196,7 @@ bool AbstractStorageDecl::hasDidSetOrWillSetDynamicReplacement() const {

bool AbstractStorageDecl::hasAnyNativeDynamicAccessors() const {
for (auto accessor : getAllAccessors()) {
if (accessor->isNativeDynamic())
if (accessor->shouldUseNativeDynamicDispatch())
return true;
}
return false;
Expand Down
2 changes: 1 addition & 1 deletion lib/IRGen/GenArchetype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ bool shouldUseOpaqueTypeDescriptorAccessor(OpaqueTypeDecl *opaque) {

// Don't emit accessors for functions that are not dynamic or dynamic
// replacements.
return namingDecl->isNativeDynamic() ||
return namingDecl->shouldUseNativeDynamicDispatch() ||
(bool)namingDecl->getDynamicallyReplacedDecl();
}

Expand Down
2 changes: 1 addition & 1 deletion lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2539,7 +2539,7 @@ void IRGenModule::emitOpaqueTypeDescriptorAccessor(OpaqueTypeDecl *opaque) {
// Don't emit accessors for functions that are not dynamic or dynamic
// replacements.
if (!abstractStorage) {
isNativeDynamic = namingDecl->isNativeDynamic();
isNativeDynamic = namingDecl->shouldUseNativeDynamicDispatch();
if (!isNativeDynamic && !isDynamicReplacement)
return;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/IRGen/GenMeta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1551,7 +1551,7 @@ namespace {
auto flags = getMethodDescriptorFlags<Flags>(func);

// Remember if the declaration was dynamic.
if (func->isObjCDynamic())
if (func->shouldUseObjCDispatch())
flags = flags.withIsDynamic(true);

// Include the pointer-auth discriminator.
Expand Down
17 changes: 14 additions & 3 deletions lib/IRGen/GenObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1430,12 +1430,22 @@ void irgen::emitObjCSetterDescriptor(IRGenModule &IGM,
emitObjCDescriptor(IGM, descriptors, descriptor);
}

static bool isObjCGenericClassExtension(ValueDecl *decl) {
// Don't emit category entries for @objc methods in extensions they would
// normally be disallowed except for @_dynamicReplacement(for:) methods that
// use the native dynamic replacement mechanism instead of objc categories.
auto *DC = decl->getDeclContext();
if (!isa<ExtensionDecl>(DC))
return false;
return decl->isNativeMethodReplacement();
}

bool irgen::requiresObjCMethodDescriptor(FuncDecl *method) {
// Property accessors should be generated alongside the property.
if (isa<AccessorDecl>(method))
return false;

return method->isObjC();
return method->isObjC() && !isObjCGenericClassExtension(method);
}

bool irgen::requiresObjCMethodDescriptor(ConstructorDecl *constructor) {
Expand All @@ -1447,12 +1457,13 @@ bool irgen::requiresObjCPropertyDescriptor(IRGenModule &IGM,
// Don't generate a descriptor for a property without any accessors.
// This is only possible in SIL files because Sema will normally
// implicitly synthesize accessors for @objc properties.
return property->isObjC() && property->requiresOpaqueAccessors();
return property->isObjC() && property->requiresOpaqueAccessors() &&
!isObjCGenericClassExtension(property);
}

bool irgen::requiresObjCSubscriptDescriptor(IRGenModule &IGM,
SubscriptDecl *subscript) {
return subscript->isObjC();
return subscript->isObjC() && !isObjCGenericClassExtension(subscript);
}

llvm::Value *IRGenFunction::emitBlockCopyCall(llvm::Value *value) {
Expand Down
31 changes: 25 additions & 6 deletions lib/SIL/IR/SILDeclRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ swift::getMethodDispatch(AbstractFunctionDecl *method) {
auto dc = method->getDeclContext();

if (dc->getSelfClassDecl()) {
if (method->isObjCDynamic()) {
if (method->shouldUseObjCDispatch()) {
return MethodDispatch::Class;
}

Expand Down Expand Up @@ -88,7 +88,7 @@ bool swift::requiresForeignToNativeThunk(ValueDecl *vd) {
bool swift::requiresForeignEntryPoint(ValueDecl *vd) {
assert(!isa<AbstractStorageDecl>(vd));

if (vd->isObjCDynamic()) {
if (vd->shouldUseObjCDispatch()) {
return true;
}

Expand Down Expand Up @@ -867,15 +867,15 @@ SILDeclRef SILDeclRef::getNextOverriddenVTableEntry() const {
}

// Overrides of @objc dynamic declarations are not in the vtable.
if (overridden.getDecl()->isObjCDynamic()) {
if (overridden.getDecl()->shouldUseObjCDispatch()) {
return SILDeclRef();
}

if (auto *accessor = dyn_cast<AccessorDecl>(overridden.getDecl())) {
auto *asd = accessor->getStorage();
if (asd->hasClangNode())
return SILDeclRef();
if (asd->isObjCDynamic()) {
if (asd->shouldUseObjCDispatch()) {
return SILDeclRef();
}
}
Expand Down Expand Up @@ -1106,6 +1106,10 @@ static bool isDesignatedConstructorForClass(ValueDecl *decl) {
}

bool SILDeclRef::canBeDynamicReplacement() const {
// The foreign entry of a @dynamicReplacement(for:) of @objc method in a
// generic class can't be a dynamic replacement.
if (isForeign && hasDecl() && getDecl()->isNativeMethodReplacement())
return false;
if (kind == SILDeclRef::Kind::Destroyer ||
kind == SILDeclRef::Kind::DefaultArgGenerator)
return false;
Expand All @@ -1117,6 +1121,11 @@ bool SILDeclRef::canBeDynamicReplacement() const {
}

bool SILDeclRef::isDynamicallyReplaceable() const {
// The non-foreign entry of a @dynamicReplacement(for:) of @objc method in a
// generic class can't be a dynamically replaced.
if (!isForeign && hasDecl() && getDecl()->isNativeMethodReplacement())
return false;

if (kind == SILDeclRef::Kind::DefaultArgGenerator)
return false;
if (isStoredPropertyInitializer() || isPropertyWrapperBackingInitializer())
Expand All @@ -1138,5 +1147,15 @@ bool SILDeclRef::isDynamicallyReplaceable() const {
return false;

auto decl = getDecl();
return decl->isNativeDynamic();

if (isForeign)
return false;

// We can't generate categories for generic classes. So the standard mechanism
// for replacing @objc dynamic methods in generic classes does not work.
// Instead we mark the non @objc entry dynamically replaceable and replace
// that.
// For now, we only support this behavior if -enable-implicit-dynamic is
// enabled.
return decl->shouldUseNativeMethodReplacement();
}
10 changes: 8 additions & 2 deletions lib/SIL/IR/SILFunctionBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ void SILFunctionBuilder::addFunctionAttributes(
auto *decl = constant.getDecl();

// Only emit replacements for the objc entry point of objc methods.
if (decl->isObjC() &&
// There is one exception: @_dynamicReplacement(for:) of @objc methods in
// generic classes. In this special case we use native replacement instead of
// @objc categories.
if (decl->isObjC() && !decl->isNativeMethodReplacement() &&
F->getLoweredFunctionType()->getExtInfo().getRepresentation() !=
SILFunctionTypeRepresentation::ObjCMethod)
return;
Expand All @@ -103,7 +106,10 @@ void SILFunctionBuilder::addFunctionAttributes(
if (!replacedDecl)
return;

if (decl->isObjC()) {
// For @objc method replacement we normally use categories to perform the
// replacement. Except for methods in generic class where we can't. Instead,
// we special case this and use the native swift replacement mechanism.
if (decl->isObjC() && !decl->isNativeMethodReplacement()) {
F->setObjCReplacement(replacedDecl);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,7 @@ class SILGenApply : public Lowering::ExprVisitor<SILGenApply> {
// @objc dynamic initializers are statically dispatched (we're
// calling the allocating entry point, which is a thunk that
// does the dynamic dispatch for us).
if (ctor->isObjCDynamic())
if (ctor->shouldUseObjCDispatch())
return false;

// Required constructors are statically dispatched when the 'self'
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenBridging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1466,7 +1466,7 @@ void SILGenFunction::emitNativeToForeignThunk(SILDeclRef thunk) {
// If @objc was inferred based on the Swift 3 @objc inference rules, emit
// a call to Builtin.swift3ImplicitObjCEntrypoint() to enable runtime
// logging of the uses of such entrypoints.
if (attr->isSwift3Inferred() && !decl->isObjCDynamic()) {
if (attr->isSwift3Inferred() && !decl->shouldUseObjCDispatch()) {
// Get the starting source location of the declaration so we can say
// exactly where to stick '@objc'.
SourceLoc objcInsertionLoc =
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ void SILGenFunction::emitClassConstructorAllocator(ConstructorDecl *ctor) {
bool useObjCAllocation = usesObjCAllocator(selfClassDecl);

if (ctor->hasClangNode() ||
ctor->isObjCDynamic() ||
ctor->shouldUseObjCDispatch() ||
ctor->isConvenienceInit()) {
assert(ctor->hasClangNode() || ctor->isObjC());
// For an allocator thunk synthesized for an @objc convenience initializer
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ SILGenFunction::emitSiblingMethodRef(SILLocation loc,
// If the method is dynamic, access it through runtime-hookable virtual
// dispatch (viz. objc_msgSend for now).
if (methodConstant.hasDecl()
&& methodConstant.getDecl()->isObjCDynamic()) {
&& methodConstant.getDecl()->shouldUseObjCDispatch()) {
methodValue =
emitDynamicMethodRef(
loc, methodConstant,
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenLValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1463,7 +1463,7 @@ namespace {
auto setterInfo =
SGF.getConstantInfo(SGF.getTypeExpansionContext(), setter);
SILValue setterFRef;
if (setter.hasDecl() && setter.getDecl()->isObjCDynamic()) {
if (setter.hasDecl() && setter.getDecl()->shouldUseObjCDispatch()) {
// Emit a thunk we might have to bridge arguments.
auto foreignSetterThunk = setter.asForeign(false);
setterFRef =
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenPoly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4493,7 +4493,7 @@ static WitnessDispatchKind getWitnessDispatchKind(SILDeclRef witness,
}

// If the witness is dynamic, go through dynamic dispatch.
if (decl->isObjCDynamic()) {
if (decl->shouldUseObjCDispatch()) {
// For initializers we still emit a static allocating thunk around
// the dynamic initializing entry point.
if (witness.kind == SILDeclRef::Kind::Allocator)
Expand Down
Loading