Skip to content

Commit 394d6a8

Browse files
committed
Back-deploy @objc actor types.
@objc actors implicitly inherit from the new, hidden `SwiftNativeNSObject` class that inherits from `NSObject` yet provides Swift-native reference counting, which is important for the actor runtime's handling of zombies. However, `SwiftNativeNSObject` is only available in the Swift runtime in newer OS versions (e.g., macOS 12.0/iOS 15.0), and is available in the back-deployed _Concurrency library, but there is no stable place to link against for back-deployed code. Tricky, tricky. When back-deploying @objc actors, record `NSObject` as the superclass in the metadata in the binary, because we cannot reference `SwiftNativeNSObject`. Then, emit a static initializer to dynamically look up `SwiftNativeNSObject` by name (which will find it in either the back-deployment library, on older systems, or in the runtime for newer systems), then swizzle that in as the superclass of the @objc actor. Fixes rdar://83919973.
1 parent ecbca24 commit 394d6a8

File tree

9 files changed

+154
-10
lines changed

9 files changed

+154
-10
lines changed

include/swift/Runtime/RuntimeFunctions.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,12 @@ FUNCTION(LookUpClass, objc_lookUpClass, C_CC, AlwaysAvailable,
11241124
ARGS(Int8PtrTy),
11251125
ATTRS(NoUnwind, ReadNone))
11261126

1127+
// Class objc_setSuperclass(Class cls, Class newSuper);
1128+
FUNCTION(SetSuperclass, class_setSuperclass, C_CC, AlwaysAvailable,
1129+
RETURNS(ObjCClassPtrTy),
1130+
ARGS(ObjCClassPtrTy, ObjCClassPtrTy),
1131+
ATTRS(NoUnwind))
1132+
11271133
// Metadata *swift_getObjectType(id object);
11281134
FUNCTION(GetObjectType, swift_getObjectType, C_CC, AlwaysAvailable,
11291135
RETURNS(TypeMetadataPtrTy),

lib/IRGen/GenClass.cpp

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,7 @@ void IRGenModule::emitClassDecl(ClassDecl *D) {
960960
emitFieldDescriptor(D);
961961

962962
IRGen.addClassForEagerInitialization(D);
963+
IRGen.addBackDeployedObjCActorInitialization(D);
963964

964965
emitNestedTypeDecls(D->getMembers());
965966
}
@@ -2527,15 +2528,24 @@ ClassDecl *irgen::getRootClassForMetaclass(IRGenModule &IGM, ClassDecl *C) {
25272528

25282529
ClassDecl *
25292530
irgen::getSuperclassDeclForMetadata(IRGenModule &IGM, ClassDecl *C) {
2530-
if (C->isNativeNSObjectSubclass())
2531+
if (C->isNativeNSObjectSubclass()) {
2532+
// When concurrency isn't available in the OS, use NSObject instead.
2533+
if (!IGM.isConcurrencyAvailable()) {
2534+
return IGM.getObjCRuntimeBaseClass(
2535+
IGM.Context.getSwiftId(KnownFoundationEntity::NSObject),
2536+
IGM.Context.getIdentifier("NSObject"));
2537+
}
2538+
25312539
return IGM.getSwiftNativeNSObjectDecl();
2540+
}
25322541
return C->getSuperclassDecl();
25332542
}
25342543

25352544
CanType irgen::getSuperclassForMetadata(IRGenModule &IGM, ClassDecl *C) {
2536-
if (C->isNativeNSObjectSubclass())
2537-
return IGM.getSwiftNativeNSObjectDecl()->getDeclaredInterfaceType()
2538-
->getCanonicalType();
2545+
if (C->isNativeNSObjectSubclass()) {
2546+
return getSuperclassDeclForMetadata(IGM, C)->getDeclaredInterfaceType()
2547+
->getCanonicalType();
2548+
}
25392549
if (auto superclass = C->getSuperclass())
25402550
return superclass->getCanonicalType();
25412551
return CanType();
@@ -2544,9 +2554,10 @@ CanType irgen::getSuperclassForMetadata(IRGenModule &IGM, ClassDecl *C) {
25442554
CanType irgen::getSuperclassForMetadata(IRGenModule &IGM, CanType type,
25452555
bool useArchetypes) {
25462556
auto cls = type->getClassOrBoundGenericClass();
2547-
if (cls->isNativeNSObjectSubclass())
2548-
return IGM.getSwiftNativeNSObjectDecl()->getDeclaredInterfaceType()
2549-
->getCanonicalType();
2557+
if (cls->isNativeNSObjectSubclass()) {
2558+
return getSuperclassDeclForMetadata(IGM, cls)->getDeclaredInterfaceType()
2559+
->getCanonicalType();
2560+
}
25502561
if (auto superclass = type->getSuperclass(useArchetypes))
25512562
return superclass->getCanonicalType();
25522563
return CanType();

lib/IRGen/GenDecl.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,6 +1861,50 @@ void IRGenerator::emitEagerClassInitialization() {
18611861
llvm::appendToGlobalCtors(IGM->Module, RegisterFn, 60000, nullptr);
18621862
}
18631863

1864+
void IRGenerator::emitObjCActorsNeedingSuperclassSwizzle() {
1865+
if (ObjCActorsNeedingSuperclassSwizzle.empty())
1866+
return;
1867+
1868+
// Emit the register function in the primary module.
1869+
IRGenModule *IGM = getPrimaryIGM();
1870+
1871+
llvm::Function *RegisterFn = llvm::Function::Create(
1872+
llvm::FunctionType::get(IGM->VoidTy, false),
1873+
llvm::GlobalValue::PrivateLinkage,
1874+
"_swift_objc_actor_initialization");
1875+
IGM->Module.getFunctionList().push_back(RegisterFn);
1876+
IRGenFunction RegisterIGF(*IGM, RegisterFn);
1877+
RegisterFn->setAttributes(IGM->constructInitialAttributes());
1878+
RegisterFn->setCallingConv(IGM->DefaultCC);
1879+
1880+
// Look up the SwiftNativeNSObject class.
1881+
auto swiftNativeNSObjectName =
1882+
IGM->getAddrOfGlobalString("SwiftNativeNSObject");
1883+
auto swiftNativeNSObjectClass = RegisterIGF.Builder.CreateCall(
1884+
RegisterIGF.IGM.getLookUpClassFn(), swiftNativeNSObjectName);
1885+
1886+
for (ClassDecl *CD : ObjCActorsNeedingSuperclassSwizzle) {
1887+
// The @objc actor class.
1888+
llvm::Value *classRef = RegisterIGF.emitTypeMetadataRef(
1889+
CD->getDeclaredInterfaceType()->getCanonicalType());
1890+
classRef = RegisterIGF.Builder.CreateBitCast(classRef, IGM->ObjCClassPtrTy);
1891+
classRef = RegisterIGF.Builder.CreateCall(
1892+
IGM->getFixedClassInitializationFn(), classRef);
1893+
1894+
// Set its superclass to SwiftNativeNSObject.
1895+
RegisterIGF.Builder.CreateCall(
1896+
RegisterIGF.IGM.getSetSuperclassFn(),
1897+
{ classRef, swiftNativeNSObjectClass});
1898+
}
1899+
RegisterIGF.Builder.CreateRetVoid();
1900+
1901+
// Add the registration function as a static initializer. We use a priority
1902+
// slightly lower than used for C++ global constructors, so that the code is
1903+
// executed before C++ global constructors (in case someone uses archives
1904+
// from a C++ global constructor).
1905+
llvm::appendToGlobalCtors(IGM->Module, RegisterFn, 60000, nullptr);
1906+
}
1907+
18641908
/// Emit symbols for eliminated dead methods, which can still be referenced
18651909
/// from other modules. This happens e.g. if a public class contains a (dead)
18661910
/// private method.

lib/IRGen/IRGen.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,7 @@ GeneratedModule IRGenRequest::evaluate(Evaluator &evaluator,
11131113
IGM.emitBuiltinReflectionMetadata();
11141114
IGM.emitReflectionMetadataVersion();
11151115
irgen.emitEagerClassInitialization();
1116+
irgen.emitObjCActorsNeedingSuperclassSwizzle();
11161117
irgen.emitDynamicReplacements();
11171118
}
11181119

@@ -1353,6 +1354,7 @@ static void performParallelIRGeneration(IRGenDescriptor desc) {
13531354
irgen.emitReflectionMetadataVersion();
13541355

13551356
irgen.emitEagerClassInitialization();
1357+
irgen.emitObjCActorsNeedingSuperclassSwizzle();
13561358

13571359
// Emit reflection metadata for builtin and imported types.
13581360
irgen.emitBuiltinReflectionMetadata();

lib/IRGen/IRGenModule.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,22 @@ void IRGenerator::addClassForEagerInitialization(ClassDecl *ClassDecl) {
11141114
ClassesForEagerInitialization.push_back(ClassDecl);
11151115
}
11161116

1117+
void IRGenerator::addBackDeployedObjCActorInitialization(ClassDecl *ClassDecl) {
1118+
if (!ClassDecl->isActor())
1119+
return;
1120+
1121+
if (!ClassDecl->isObjC())
1122+
return;
1123+
1124+
// If we are not back-deploying concurrency, there's nothing to do.
1125+
ASTContext &ctx = ClassDecl->getASTContext();
1126+
auto deploymentAvailability = AvailabilityContext::forDeploymentTarget(ctx);
1127+
if (deploymentAvailability.isContainedIn(ctx.getConcurrencyAvailability()))
1128+
return;
1129+
1130+
ObjCActorsNeedingSuperclassSwizzle.push_back(ClassDecl);
1131+
}
1132+
11171133
llvm::AttributeList IRGenModule::getAllocAttrs() {
11181134
if (AllocAttrs.isEmpty()) {
11191135
AllocAttrs =

lib/IRGen/IRGenModule.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@ class IRGenerator {
319319

320320
llvm::SmallVector<ClassDecl *, 4> ClassesForEagerInitialization;
321321

322+
llvm::SmallVector<ClassDecl *, 4> ObjCActorsNeedingSuperclassSwizzle;
323+
322324
/// The order in which all the SIL function definitions should
323325
/// appear in the translation unit.
324326
llvm::DenseMap<SILFunction*, unsigned> FunctionOrder;
@@ -407,6 +409,7 @@ class IRGenerator {
407409
void emitReflectionMetadataVersion();
408410

409411
void emitEagerClassInitialization();
412+
void emitObjCActorsNeedingSuperclassSwizzle();
410413

411414
// Emit the code to replace dynamicReplacement(for:) functions.
412415
void emitDynamicReplacements();
@@ -499,6 +502,7 @@ class IRGenerator {
499502

500503

501504
void addClassForEagerInitialization(ClassDecl *ClassDecl);
505+
void addBackDeployedObjCActorInitialization(ClassDecl *ClassDecl);
502506

503507
unsigned getFunctionOrder(SILFunction *F) {
504508
auto it = FunctionOrder.find(F);

test/Concurrency/Backdeploy/objc_actor.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// RUN: %empty-directory(%t)
2-
// RUN: %target-build-swift -target x86_64-apple-macosx10.15 %s -o %t/test_mangling -Xfrontend -disable-availability-checking -parse-as-library
3-
// RUN: %target-run %t/test_mangling
2+
// RUN: %target-build-swift -target x86_64-apple-macosx10.15 %s -o %t/test_backdeploy -Xfrontend -parse-as-library
3+
// RUN: %target-run %t/test_backdeploy
44

55
// REQUIRES: CPU=x86_64
66
// REQUIRES: OS=macosx

test/IRGen/actor_class_objc.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// RUN: %target-swift-frontend -emit-ir %s -swift-version 5 -enable-experimental-concurrency -disable-availability-checking | %IRGenFileCheck %s
1+
// RUN: %target-swift-frontend -emit-ir %s -swift-version 5 -target %target-cpu-apple-macosx12.0 | %IRGenFileCheck %s
22
// REQUIRES: concurrency
33
// REQUIRES: objc_interop
4+
// REQUIRES: OS=macosx
45

56
import Foundation
67

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %target-swift-frontend -emit-ir %s -swift-version 5 -target %target-cpu-apple-macosx11.0 -module-name actor_class_objc | %IRGenFileCheck %s
2+
// REQUIRES: concurrency
3+
// REQUIRES: objc_interop
4+
// REQUIRES: OS=macosx
5+
6+
import Foundation
7+
8+
// CHECK: %T16actor_class_objc7MyClassC = type <{ %swift.refcounted, %swift.defaultactor, %TSi }>
9+
// CHECK: %swift.defaultactor = type { [12 x i8*] }
10+
11+
// CHECK-LABEL: @"OBJC_METACLASS_$__TtC16actor_class_objc7MyClass" = global
12+
// Metaclass is an instance of the root class.
13+
// CHECK-SAME: %objc_class* {{.*}}@"OBJC_METACLASS_$_NSObject{{(.ptrauth)?}}"
14+
15+
// CHECK: @"$s16actor_class_objc7MyClassCMf" = internal global
16+
// CHECK-SAME: @"$s16actor_class_objc7MyClassCfD{{(.ptrauth)?}}"
17+
// CHECK-SAME: @"OBJC_METACLASS_$__TtC16actor_class_objc7MyClass{{(.ptrauth)?}}"
18+
// CHECK-SAME: @"OBJC_CLASS_$_NSObject{{(.ptrauth)?}}"
19+
// Flags: uses Swift refcounting
20+
// CHECK-SAME: i32 2,
21+
// Instance size
22+
// CHECK-64-SAME: i32 120,
23+
// CHECK-32-SAME: i32 60,
24+
// Alignment mask
25+
// CHECK-64-SAME: i16 15,
26+
// CHECK-32-SAME: i16 7,
27+
// Field offset for 'x'
28+
// CHECK-64-SAME: i64 112,
29+
// CHECK-32-SAME: i32 56,
30+
31+
@objc public actor MyClass {
32+
public var x: Int
33+
public init() { self.x = 0 }
34+
}
35+
36+
// CHECK: [[SWIFT_NATIVE_NSOBJECT_NAME:@.*]] = private unnamed_addr constant [20 x i8] c"SwiftNativeNSObject\00"
37+
38+
// CHECK: @llvm.global_ctors = appending global
39+
// CHECK-SAME: _swift_objc_actor_initialization
40+
41+
42+
// CHECK-LABEL: define {{.*}} @"$s16actor_class_objc7MyClassC1xSivg"
43+
// CHECK: [[T0:%.*]] = getelementptr inbounds %T16actor_class_objc7MyClassC, %T16actor_class_objc7MyClassC* %0, i32 0, i32 2
44+
// CHECK: [[T1:%.*]] = getelementptr inbounds %TSi, %TSi* [[T0]], i32 0, i32 0
45+
// CHECK: load [[INT]], [[INT]]* [[T1]], align
46+
47+
// CHECK-LABEL: define {{.*}}swiftcc %T16actor_class_objc7MyClassC* @"$s16actor_class_objc7MyClassCACycfc"
48+
// CHECK: swift_defaultActor_initialize
49+
// CHECK-LABEL: ret %T16actor_class_objc7MyClassC*
50+
51+
// CHECK: swift_defaultActor_destroy
52+
53+
// CHECK-LABEL: define private void @_swift_objc_actor_initialization()
54+
// CHECK: [[SWIFT_NATIVE_NSOBJECT_CLASS:%.*]] = call %objc_class* @objc_lookUpClass(i8* getelementptr inbounds ([20 x i8], [20 x i8]* [[SWIFT_NATIVE_NSOBJECT_NAME]]
55+
// CHECK: [[ACTOR_RESPONSE:%.*]] = call swiftcc %swift.metadata_response @"$s16actor_class_objc7MyClassCMa"(
56+
// CHECK: [[ACTOR_METADATA:%.*]] = extractvalue %swift.metadata_response [[ACTOR_RESPONSE]], 0
57+
// CHECK: [[ACTOR_CLASS_RAW:%.*]] = bitcast %swift.type* [[ACTOR_METADATA]] to %objc_class*
58+
// CHECK: [[ACTOR_CLASS:%.*]] = call %objc_class* @objc_opt_self(%objc_class* [[ACTOR_CLASS_RAW]])
59+
// CHECK: call %objc_class* @class_setSuperclass(%objc_class* [[ACTOR_CLASS]], %objc_class* [[SWIFT_NATIVE_NSOBJECT_CLASS]])
60+

0 commit comments

Comments
 (0)