Skip to content

Commit 825a2a2

Browse files
committed
Mark non-foreign entry points of @objc dynamic methods in generic classes dynamically_replaceable
``` class Generic<T> { @objc dynamic func method() {} } extension Generic { @_dynamicReplacement(for:method()) func replacement() {} } ``` The standard mechanism of using Objective-C categories for dynamically replacing @objc methods in generic classes does not work. Instead we mark the native entry point as replaceable. Because this affects all @objc methods in generic classes (whether there is a replacement or not) by making the native entry point `[dynamically_replaceable]` (regardless of optimization mode) we guard this by the -enable-implicit-dynamic flag because we are late in the release cycle. * Replace isNativeDynamic and isObjcDynamic by calls to shouldUse*Dispatch and shouldUse*Replacement This disambiguates between which dispatch method we should use at call sites and how these methods should implement dynamic function replacement. * Don't emit the method entry for @_dynamicReplacement(for:) of generic class methods There is not way to call this entry point since we can't generate an objective-c category for generic classes. rdar://63679357
1 parent 8f0569d commit 825a2a2

31 files changed

+298
-43
lines changed

include/swift/AST/Decl.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,7 @@ class ValueDecl : public Decl {
27152715
/// Is this declaration marked with 'dynamic'?
27162716
bool isDynamic() const;
27172717

2718+
private:
27182719
bool isObjCDynamic() const {
27192720
return isObjC() && isDynamic();
27202721
}
@@ -2723,6 +2724,37 @@ class ValueDecl : public Decl {
27232724
return !isObjC() && isDynamic();
27242725
}
27252726

2727+
bool isObjCDynamicInGenericClass() const;
2728+
2729+
public:
2730+
/// Should we use Objective-C method dispatch for this decl.
2731+
bool shouldUseObjCDispatch() const {
2732+
return isObjCDynamic();
2733+
}
2734+
2735+
/// Should we use native dynamic function replacement dispatch for this decl.
2736+
bool shouldUseNativeDynamicDispatch() const {
2737+
return isNativeDynamic();
2738+
}
2739+
2740+
/// Should we use Objective-C category based function replacement for this
2741+
/// decl.
2742+
/// This is all `@objc dynamic` methods except for such methods in native
2743+
/// generic classes. We can't use a category for generic classes so we use
2744+
/// native replacement instead (this behavior is only enabled with
2745+
/// -enable-implicit-dynamic).
2746+
bool shouldUseObjCMethodReplacement() const;
2747+
2748+
/// Should we use native dynamic function replacement mechanism for this decl.
2749+
/// This is all native dynamic methods except for `@objc dynamic` methods in
2750+
/// generic classes (see above).
2751+
bool shouldUseNativeMethodReplacement() const;
2752+
2753+
/// Is this a native dynamic function replacement based replacement.
2754+
/// This is all @_dynamicReplacement(for:) of native functions and @objc
2755+
/// dynamic methods on generic classes (see above).
2756+
bool isNativeMethodReplacement() const;
2757+
27262758
bool isEffectiveLinkageMoreVisibleThan(ValueDecl *other) const {
27272759
return (std::min(getEffectiveAccess(), AccessLevel::Public) >
27282760
std::min(other->getEffectiveAccess(), AccessLevel::Public));

include/swift/Serialization/Validation.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class ExtendedValidationInfo {
9898
unsigned IsSIB : 1;
9999
unsigned IsTestable : 1;
100100
unsigned ResilienceStrategy : 2;
101+
unsigned IsImplicitDynamicEnabled: 1;
101102
} Bits;
102103
public:
103104
ExtendedValidationInfo() : Bits() {}
@@ -123,6 +124,10 @@ class ExtendedValidationInfo {
123124
void setPrivateImportsEnabled(bool enabled) {
124125
Bits.ArePrivateImportsEnabled = enabled;
125126
}
127+
bool isImplicitDynamicEnabled() { return Bits.IsImplicitDynamicEnabled; }
128+
void setImplicitDynamicEnabled(bool val) {
129+
Bits.IsImplicitDynamicEnabled = val;
130+
}
126131
bool isTestable() const { return Bits.IsTestable; }
127132
void setIsTestable(bool val) {
128133
Bits.IsTestable = val;

lib/AST/ASTVerifier.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3120,12 +3120,12 @@ class Verifier : public ASTWalker {
31203120
storageDecl->getWriteImpl() ==
31213121
WriteImplKind::StoredWithObservers ||
31223122
storageDecl->getWriteImpl() == WriteImplKind::MutableAddress) &&
3123-
storageDecl->isNativeDynamic()) &&
3123+
storageDecl->shouldUseNativeDynamicDispatch()) &&
31243124
// We allow a non dynamic getter if there is a dynamic read.
31253125
!(FD->isGetter() &&
31263126
(storageDecl->getReadImpl() == ReadImplKind::Read ||
31273127
storageDecl->getReadImpl() == ReadImplKind::Address) &&
3128-
storageDecl->isNativeDynamic())) {
3128+
storageDecl->shouldUseNativeDynamicDispatch())) {
31293129
Out << "Property and accessor do not match for 'dynamic'\n";
31303130
abort();
31313131
}

lib/AST/Decl.cpp

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,7 +1912,7 @@ SourceRange IfConfigDecl::getSourceRange() const {
19121912
}
19131913

19141914
static bool isPolymorphic(const AbstractStorageDecl *storage) {
1915-
if (storage->isObjCDynamic())
1915+
if (storage->shouldUseObjCDispatch())
19161916
return true;
19171917

19181918

@@ -2063,7 +2063,7 @@ getDirectReadWriteAccessStrategy(const AbstractStorageDecl *storage) {
20632063
return AccessStrategy::getStorage();
20642064
case ReadWriteImplKind::Stored: {
20652065
// If the storage isDynamic (and not @objc) use the accessors.
2066-
if (storage->isNativeDynamic())
2066+
if (storage->shouldUseNativeDynamicDispatch())
20672067
return AccessStrategy::getMaterializeToTemporary(
20682068
getOpaqueReadAccessStrategy(storage, false),
20692069
getOpaqueWriteAccessStrategy(storage, false));
@@ -2148,7 +2148,7 @@ AbstractStorageDecl::getAccessStrategy(AccessSemantics semantics,
21482148
if (isPolymorphic(this))
21492149
return getOpaqueAccessStrategy(this, accessKind, /*dispatch*/ true);
21502150

2151-
if (isNativeDynamic())
2151+
if (shouldUseNativeDynamicDispatch())
21522152
return getOpaqueAccessStrategy(this, accessKind, /*dispatch*/ false);
21532153

21542154
// If the storage is resilient from the given module and resilience
@@ -2906,6 +2906,59 @@ bool ValueDecl::isDynamic() const {
29062906
getAttrs().hasAttribute<DynamicAttr>());
29072907
}
29082908

2909+
bool ValueDecl::isObjCDynamicInGenericClass() const {
2910+
if (!isObjCDynamic())
2911+
return false;
2912+
2913+
auto *DC = this->getDeclContext();
2914+
auto *classDecl = DC->getSelfClassDecl();
2915+
if (!classDecl)
2916+
return false;
2917+
2918+
return classDecl->isGenericContext() && !classDecl->usesObjCGenericsModel();
2919+
}
2920+
2921+
bool ValueDecl::shouldUseObjCMethodReplacement() const {
2922+
if (isNativeDynamic())
2923+
return false;
2924+
2925+
if (getModuleContext()->isImplicitDynamicEnabled() &&
2926+
isObjCDynamicInGenericClass())
2927+
return false;
2928+
2929+
return isObjCDynamic();
2930+
}
2931+
2932+
bool ValueDecl::shouldUseNativeMethodReplacement() const {
2933+
if (isNativeDynamic())
2934+
return true;
2935+
2936+
if (!isObjCDynamicInGenericClass())
2937+
return false;
2938+
2939+
auto *replacedDecl = getDynamicallyReplacedDecl();
2940+
if (replacedDecl)
2941+
return false;
2942+
2943+
return getModuleContext()->isImplicitDynamicEnabled();
2944+
}
2945+
2946+
bool ValueDecl::isNativeMethodReplacement() const {
2947+
// Is this a @_dynamicReplacement(for:) that use the native dynamic function
2948+
// replacement mechanism.
2949+
auto *replacedDecl = getDynamicallyReplacedDecl();
2950+
if (!replacedDecl)
2951+
return false;
2952+
2953+
if (isNativeDynamic())
2954+
return true;
2955+
2956+
if (isObjCDynamicInGenericClass())
2957+
return replacedDecl->getModuleContext()->isImplicitDynamicEnabled();
2958+
2959+
return false;
2960+
}
2961+
29092962
void ValueDecl::setIsDynamic(bool value) {
29102963
assert(!LazySemanticInfo.isDynamicComputed ||
29112964
LazySemanticInfo.isDynamic == value);
@@ -5132,7 +5185,7 @@ bool AbstractStorageDecl::hasDidSetOrWillSetDynamicReplacement() const {
51325185

51335186
bool AbstractStorageDecl::hasAnyNativeDynamicAccessors() const {
51345187
for (auto accessor : getAllAccessors()) {
5135-
if (accessor->isNativeDynamic())
5188+
if (accessor->shouldUseNativeDynamicDispatch())
51365189
return true;
51375190
}
51385191
return false;

lib/IRGen/GenArchetype.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ bool shouldUseOpaqueTypeDescriptorAccessor(OpaqueTypeDecl *opaque) {
468468

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

lib/IRGen/GenDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2566,7 +2566,7 @@ void IRGenModule::emitOpaqueTypeDescriptorAccessor(OpaqueTypeDecl *opaque) {
25662566
// Don't emit accessors for functions that are not dynamic or dynamic
25672567
// replacements.
25682568
if (!abstractStorage) {
2569-
isNativeDynamic = namingDecl->isNativeDynamic();
2569+
isNativeDynamic = namingDecl->shouldUseNativeDynamicDispatch();
25702570
if (!isNativeDynamic && !isDynamicReplacement)
25712571
return;
25722572
}

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1563,7 +1563,7 @@ namespace {
15631563
auto flags = getMethodDescriptorFlags<Flags>(func);
15641564

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

15691569
// Include the pointer-auth discriminator.

lib/IRGen/GenObjC.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,12 +1435,22 @@ void irgen::emitObjCSetterDescriptor(IRGenModule &IGM,
14351435
emitObjCDescriptor(IGM, descriptors, descriptor);
14361436
}
14371437

1438+
static bool isObjCGenericClassExtension(ValueDecl *decl) {
1439+
// Don't emit category entries for @objc methods in extensions they would
1440+
// normally be disallowed except for @_dynamicReplacement(for:) methods that
1441+
// use the native dynamic replacement mechanism instead of objc categories.
1442+
auto *DC = decl->getDeclContext();
1443+
if (!isa<ExtensionDecl>(DC))
1444+
return false;
1445+
return decl->isNativeMethodReplacement();
1446+
}
1447+
14381448
bool irgen::requiresObjCMethodDescriptor(FuncDecl *method) {
14391449
// Property accessors should be generated alongside the property.
14401450
if (isa<AccessorDecl>(method))
14411451
return false;
14421452

1443-
return method->isObjC();
1453+
return method->isObjC() && !isObjCGenericClassExtension(method);
14441454
}
14451455

14461456
bool irgen::requiresObjCMethodDescriptor(ConstructorDecl *constructor) {
@@ -1452,12 +1462,13 @@ bool irgen::requiresObjCPropertyDescriptor(IRGenModule &IGM,
14521462
// Don't generate a descriptor for a property without any accessors.
14531463
// This is only possible in SIL files because Sema will normally
14541464
// implicitly synthesize accessors for @objc properties.
1455-
return property->isObjC() && property->requiresOpaqueAccessors();
1465+
return property->isObjC() && property->requiresOpaqueAccessors() &&
1466+
!isObjCGenericClassExtension(property);
14561467
}
14571468

14581469
bool irgen::requiresObjCSubscriptDescriptor(IRGenModule &IGM,
14591470
SubscriptDecl *subscript) {
1460-
return subscript->isObjC();
1471+
return subscript->isObjC() && !isObjCGenericClassExtension(subscript);
14611472
}
14621473

14631474
llvm::Value *IRGenFunction::emitBlockCopyCall(llvm::Value *value) {

lib/SIL/IR/SILDeclRef.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ swift::getMethodDispatch(AbstractFunctionDecl *method) {
4141
auto dc = method->getDeclContext();
4242

4343
if (dc->getSelfClassDecl()) {
44-
if (method->isObjCDynamic()) {
44+
if (method->shouldUseObjCDispatch()) {
4545
return MethodDispatch::Class;
4646
}
4747

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

91-
if (vd->isObjCDynamic()) {
91+
if (vd->shouldUseObjCDispatch()) {
9292
return true;
9393
}
9494

@@ -872,15 +872,15 @@ SILDeclRef SILDeclRef::getNextOverriddenVTableEntry() const {
872872
}
873873

874874
// Overrides of @objc dynamic declarations are not in the vtable.
875-
if (overridden.getDecl()->isObjCDynamic()) {
875+
if (overridden.getDecl()->shouldUseObjCDispatch()) {
876876
return SILDeclRef();
877877
}
878-
878+
879879
if (auto *accessor = dyn_cast<AccessorDecl>(overridden.getDecl())) {
880880
auto *asd = accessor->getStorage();
881881
if (asd->hasClangNode())
882882
return SILDeclRef();
883-
if (asd->isObjCDynamic()) {
883+
if (asd->shouldUseObjCDispatch()) {
884884
return SILDeclRef();
885885
}
886886
}
@@ -1111,6 +1111,10 @@ static bool isDesignatedConstructorForClass(ValueDecl *decl) {
11111111
}
11121112

11131113
bool SILDeclRef::canBeDynamicReplacement() const {
1114+
// The foreign entry of a @dynamicReplacement(for:) of @objc method in a
1115+
// generic class can't be a dynamic replacement.
1116+
if (isForeign && hasDecl() && getDecl()->isNativeMethodReplacement())
1117+
return false;
11141118
if (kind == SILDeclRef::Kind::Destroyer ||
11151119
kind == SILDeclRef::Kind::DefaultArgGenerator)
11161120
return false;
@@ -1122,6 +1126,11 @@ bool SILDeclRef::canBeDynamicReplacement() const {
11221126
}
11231127

11241128
bool SILDeclRef::isDynamicallyReplaceable() const {
1129+
// The non-foreign entry of a @dynamicReplacement(for:) of @objc method in a
1130+
// generic class can't be a dynamically replaced.
1131+
if (!isForeign && hasDecl() && getDecl()->isNativeMethodReplacement())
1132+
return false;
1133+
11251134
if (kind == SILDeclRef::Kind::DefaultArgGenerator)
11261135
return false;
11271136
if (isStoredPropertyInitializer() || isPropertyWrapperBackingInitializer())
@@ -1143,5 +1152,15 @@ bool SILDeclRef::isDynamicallyReplaceable() const {
11431152
return false;
11441153

11451154
auto decl = getDecl();
1146-
return decl->isNativeDynamic();
1155+
1156+
if (isForeign)
1157+
return false;
1158+
1159+
// We can't generate categories for generic classes. So the standard mechanism
1160+
// for replacing @objc dynamic methods in generic classes does not work.
1161+
// Instead we mark the non @objc entry dynamically replaceable and replace
1162+
// that.
1163+
// For now, we only support this behavior if -enable-implicit-dynamic is
1164+
// enabled.
1165+
return decl->shouldUseNativeMethodReplacement();
11471166
}

lib/SIL/IR/SILFunctionBuilder.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ void SILFunctionBuilder::addFunctionAttributes(
9191
auto *decl = constant.getDecl();
9292

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

106-
if (decl->isObjC()) {
109+
// For @objc method replacement we normally use categories to perform the
110+
// replacement. Except for methods in generic class where we can't. Instead,
111+
// we special case this and use the native swift replacement mechanism.
112+
if (decl->isObjC() && !decl->isNativeMethodReplacement()) {
107113
F->setObjCReplacement(replacedDecl);
108114
return;
109115
}

0 commit comments

Comments
 (0)