Skip to content

Commit 3d4a9cf

Browse files
Merge pull request #32300 from aschwaighofer/dyn_repl_objc_method_generic_class_5.3
[5.3] Mark non-foreign entry points of `@objc dynamic` methods in generic classes `dynamically_replaceable`
2 parents b0340f0 + 3a23cac commit 3d4a9cf

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
@@ -2711,6 +2711,7 @@ class ValueDecl : public Decl {
27112711
/// Is this declaration marked with 'dynamic'?
27122712
bool isDynamic() const;
27132713

2714+
private:
27142715
bool isObjCDynamic() const {
27152716
return isObjC() && isDynamic();
27162717
}
@@ -2719,6 +2720,37 @@ class ValueDecl : public Decl {
27192720
return !isObjC() && isDynamic();
27202721
}
27212722

2723+
bool isObjCDynamicInGenericClass() const;
2724+
2725+
public:
2726+
/// Should we use Objective-C method dispatch for this decl.
2727+
bool shouldUseObjCDispatch() const {
2728+
return isObjCDynamic();
2729+
}
2730+
2731+
/// Should we use native dynamic function replacement dispatch for this decl.
2732+
bool shouldUseNativeDynamicDispatch() const {
2733+
return isNativeDynamic();
2734+
}
2735+
2736+
/// Should we use Objective-C category based function replacement for this
2737+
/// decl.
2738+
/// This is all `@objc dynamic` methods except for such methods in native
2739+
/// generic classes. We can't use a category for generic classes so we use
2740+
/// native replacement instead (this behavior is only enabled with
2741+
/// -enable-implicit-dynamic).
2742+
bool shouldUseObjCMethodReplacement() const;
2743+
2744+
/// Should we use native dynamic function replacement mechanism for this decl.
2745+
/// This is all native dynamic methods except for `@objc dynamic` methods in
2746+
/// generic classes (see above).
2747+
bool shouldUseNativeMethodReplacement() const;
2748+
2749+
/// Is this a native dynamic function replacement based replacement.
2750+
/// This is all @_dynamicReplacement(for:) of native functions and @objc
2751+
/// dynamic methods on generic classes (see above).
2752+
bool isNativeMethodReplacement() const;
2753+
27222754
bool isEffectiveLinkageMoreVisibleThan(ValueDecl *other) const {
27232755
return (std::min(getEffectiveAccess(), AccessLevel::Public) >
27242756
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
@@ -3111,12 +3111,12 @@ class Verifier : public ASTWalker {
31113111
storageDecl->getWriteImpl() ==
31123112
WriteImplKind::StoredWithObservers ||
31133113
storageDecl->getWriteImpl() == WriteImplKind::MutableAddress) &&
3114-
storageDecl->isNativeDynamic()) &&
3114+
storageDecl->shouldUseNativeDynamicDispatch()) &&
31153115
// We allow a non dynamic getter if there is a dynamic read.
31163116
!(FD->isGetter() &&
31173117
(storageDecl->getReadImpl() == ReadImplKind::Read ||
31183118
storageDecl->getReadImpl() == ReadImplKind::Address) &&
3119-
storageDecl->isNativeDynamic())) {
3119+
storageDecl->shouldUseNativeDynamicDispatch())) {
31203120
Out << "Property and accessor do not match for 'dynamic'\n";
31213121
abort();
31223122
}

lib/AST/Decl.cpp

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

19521952
static bool isPolymorphic(const AbstractStorageDecl *storage) {
1953-
if (storage->isObjCDynamic())
1953+
if (storage->shouldUseObjCDispatch())
19541954
return true;
19551955

19561956

@@ -2083,7 +2083,7 @@ getDirectReadWriteAccessStrategy(const AbstractStorageDecl *storage) {
20832083
return AccessStrategy::getStorage();
20842084
case ReadWriteImplKind::Stored: {
20852085
// If the storage isDynamic (and not @objc) use the accessors.
2086-
if (storage->isNativeDynamic())
2086+
if (storage->shouldUseNativeDynamicDispatch())
20872087
return AccessStrategy::getMaterializeToTemporary(
20882088
getOpaqueReadAccessStrategy(storage, false),
20892089
getOpaqueWriteAccessStrategy(storage, false));
@@ -2168,7 +2168,7 @@ AbstractStorageDecl::getAccessStrategy(AccessSemantics semantics,
21682168
if (isPolymorphic(this))
21692169
return getOpaqueAccessStrategy(this, accessKind, /*dispatch*/ true);
21702170

2171-
if (isNativeDynamic())
2171+
if (shouldUseNativeDynamicDispatch())
21722172
return getOpaqueAccessStrategy(this, accessKind, /*dispatch*/ false);
21732173

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

2929+
bool ValueDecl::isObjCDynamicInGenericClass() const {
2930+
if (!isObjCDynamic())
2931+
return false;
2932+
2933+
auto *DC = this->getDeclContext();
2934+
auto *classDecl = DC->getSelfClassDecl();
2935+
if (!classDecl)
2936+
return false;
2937+
2938+
return classDecl->isGenericContext() && !classDecl->usesObjCGenericsModel();
2939+
}
2940+
2941+
bool ValueDecl::shouldUseObjCMethodReplacement() const {
2942+
if (isNativeDynamic())
2943+
return false;
2944+
2945+
if (getModuleContext()->isImplicitDynamicEnabled() &&
2946+
isObjCDynamicInGenericClass())
2947+
return false;
2948+
2949+
return isObjCDynamic();
2950+
}
2951+
2952+
bool ValueDecl::shouldUseNativeMethodReplacement() const {
2953+
if (isNativeDynamic())
2954+
return true;
2955+
2956+
if (!isObjCDynamicInGenericClass())
2957+
return false;
2958+
2959+
auto *replacedDecl = getDynamicallyReplacedDecl();
2960+
if (replacedDecl)
2961+
return false;
2962+
2963+
return getModuleContext()->isImplicitDynamicEnabled();
2964+
}
2965+
2966+
bool ValueDecl::isNativeMethodReplacement() const {
2967+
// Is this a @_dynamicReplacement(for:) that use the native dynamic function
2968+
// replacement mechanism.
2969+
auto *replacedDecl = getDynamicallyReplacedDecl();
2970+
if (!replacedDecl)
2971+
return false;
2972+
2973+
if (isNativeDynamic())
2974+
return true;
2975+
2976+
if (isObjCDynamicInGenericClass())
2977+
return replacedDecl->getModuleContext()->isImplicitDynamicEnabled();
2978+
2979+
return false;
2980+
}
2981+
29292982
void ValueDecl::setIsDynamic(bool value) {
29302983
assert(!LazySemanticInfo.isDynamicComputed ||
29312984
LazySemanticInfo.isDynamic == value);
@@ -5143,7 +5196,7 @@ bool AbstractStorageDecl::hasDidSetOrWillSetDynamicReplacement() const {
51435196

51445197
bool AbstractStorageDecl::hasAnyNativeDynamicAccessors() const {
51455198
for (auto accessor : getAllAccessors()) {
5146-
if (accessor->isNativeDynamic())
5199+
if (accessor->shouldUseNativeDynamicDispatch())
51475200
return true;
51485201
}
51495202
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
@@ -2539,7 +2539,7 @@ void IRGenModule::emitOpaqueTypeDescriptorAccessor(OpaqueTypeDecl *opaque) {
25392539
// Don't emit accessors for functions that are not dynamic or dynamic
25402540
// replacements.
25412541
if (!abstractStorage) {
2542-
isNativeDynamic = namingDecl->isNativeDynamic();
2542+
isNativeDynamic = namingDecl->shouldUseNativeDynamicDispatch();
25432543
if (!isNativeDynamic && !isDynamicReplacement)
25442544
return;
25452545
}

lib/IRGen/GenMeta.cpp

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

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

15571557
// Include the pointer-auth discriminator.

lib/IRGen/GenObjC.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,12 +1430,22 @@ void irgen::emitObjCSetterDescriptor(IRGenModule &IGM,
14301430
emitObjCDescriptor(IGM, descriptors, descriptor);
14311431
}
14321432

1433+
static bool isObjCGenericClassExtension(ValueDecl *decl) {
1434+
// Don't emit category entries for @objc methods in extensions they would
1435+
// normally be disallowed except for @_dynamicReplacement(for:) methods that
1436+
// use the native dynamic replacement mechanism instead of objc categories.
1437+
auto *DC = decl->getDeclContext();
1438+
if (!isa<ExtensionDecl>(DC))
1439+
return false;
1440+
return decl->isNativeMethodReplacement();
1441+
}
1442+
14331443
bool irgen::requiresObjCMethodDescriptor(FuncDecl *method) {
14341444
// Property accessors should be generated alongside the property.
14351445
if (isa<AccessorDecl>(method))
14361446
return false;
14371447

1438-
return method->isObjC();
1448+
return method->isObjC() && !isObjCGenericClassExtension(method);
14391449
}
14401450

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

14531464
bool irgen::requiresObjCSubscriptDescriptor(IRGenModule &IGM,
14541465
SubscriptDecl *subscript) {
1455-
return subscript->isObjC();
1466+
return subscript->isObjC() && !isObjCGenericClassExtension(subscript);
14561467
}
14571468

14581469
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

@@ -867,15 +867,15 @@ SILDeclRef SILDeclRef::getNextOverriddenVTableEntry() const {
867867
}
868868

869869
// Overrides of @objc dynamic declarations are not in the vtable.
870-
if (overridden.getDecl()->isObjCDynamic()) {
870+
if (overridden.getDecl()->shouldUseObjCDispatch()) {
871871
return SILDeclRef();
872872
}
873-
873+
874874
if (auto *accessor = dyn_cast<AccessorDecl>(overridden.getDecl())) {
875875
auto *asd = accessor->getStorage();
876876
if (asd->hasClangNode())
877877
return SILDeclRef();
878-
if (asd->isObjCDynamic()) {
878+
if (asd->shouldUseObjCDispatch()) {
879879
return SILDeclRef();
880880
}
881881
}
@@ -1106,6 +1106,10 @@ static bool isDesignatedConstructorForClass(ValueDecl *decl) {
11061106
}
11071107

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

11191123
bool SILDeclRef::isDynamicallyReplaceable() const {
1124+
// The non-foreign entry of a @dynamicReplacement(for:) of @objc method in a
1125+
// generic class can't be a dynamically replaced.
1126+
if (!isForeign && hasDecl() && getDecl()->isNativeMethodReplacement())
1127+
return false;
1128+
11201129
if (kind == SILDeclRef::Kind::DefaultArgGenerator)
11211130
return false;
11221131
if (isStoredPropertyInitializer() || isPropertyWrapperBackingInitializer())
@@ -1138,5 +1147,15 @@ bool SILDeclRef::isDynamicallyReplaceable() const {
11381147
return false;
11391148

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

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
}

lib/SILGen/SILGenApply.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1005,7 +1005,7 @@ class SILGenApply : public Lowering::ExprVisitor<SILGenApply> {
10051005
// @objc dynamic initializers are statically dispatched (we're
10061006
// calling the allocating entry point, which is a thunk that
10071007
// does the dynamic dispatch for us).
1008-
if (ctor->isObjCDynamic())
1008+
if (ctor->shouldUseObjCDispatch())
10091009
return false;
10101010

10111011
// Required constructors are statically dispatched when the 'self'

lib/SILGen/SILGenBridging.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1466,7 +1466,7 @@ void SILGenFunction::emitNativeToForeignThunk(SILDeclRef thunk) {
14661466
// If @objc was inferred based on the Swift 3 @objc inference rules, emit
14671467
// a call to Builtin.swift3ImplicitObjCEntrypoint() to enable runtime
14681468
// logging of the uses of such entrypoints.
1469-
if (attr->isSwift3Inferred() && !decl->isObjCDynamic()) {
1469+
if (attr->isSwift3Inferred() && !decl->shouldUseObjCDispatch()) {
14701470
// Get the starting source location of the declaration so we can say
14711471
// exactly where to stick '@objc'.
14721472
SourceLoc objcInsertionLoc =

lib/SILGen/SILGenConstructor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ void SILGenFunction::emitClassConstructorAllocator(ConstructorDecl *ctor) {
571571
bool useObjCAllocation = usesObjCAllocator(selfClassDecl);
572572

573573
if (ctor->hasClangNode() ||
574-
ctor->isObjCDynamic() ||
574+
ctor->shouldUseObjCDispatch() ||
575575
ctor->isConvenienceInit()) {
576576
assert(ctor->hasClangNode() || ctor->isObjC());
577577
// For an allocator thunk synthesized for an @objc convenience initializer

lib/SILGen/SILGenFunction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ SILGenFunction::emitSiblingMethodRef(SILLocation loc,
154154
// If the method is dynamic, access it through runtime-hookable virtual
155155
// dispatch (viz. objc_msgSend for now).
156156
if (methodConstant.hasDecl()
157-
&& methodConstant.getDecl()->isObjCDynamic()) {
157+
&& methodConstant.getDecl()->shouldUseObjCDispatch()) {
158158
methodValue =
159159
emitDynamicMethodRef(
160160
loc, methodConstant,

lib/SILGen/SILGenLValue.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,7 @@ namespace {
14631463
auto setterInfo =
14641464
SGF.getConstantInfo(SGF.getTypeExpansionContext(), setter);
14651465
SILValue setterFRef;
1466-
if (setter.hasDecl() && setter.getDecl()->isObjCDynamic()) {
1466+
if (setter.hasDecl() && setter.getDecl()->shouldUseObjCDispatch()) {
14671467
// Emit a thunk we might have to bridge arguments.
14681468
auto foreignSetterThunk = setter.asForeign(false);
14691469
setterFRef =

lib/SILGen/SILGenPoly.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4493,7 +4493,7 @@ static WitnessDispatchKind getWitnessDispatchKind(SILDeclRef witness,
44934493
}
44944494

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

0 commit comments

Comments
 (0)