Skip to content

Commit 2551a04

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 5868ae9 commit 2551a04

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
@@ -1148,6 +1148,12 @@ FUNCTION(LookUpClass, objc_lookUpClass, C_CC, AlwaysAvailable,
11481148
ARGS(Int8PtrTy),
11491149
ATTRS(NoUnwind, ReadNone))
11501150

1151+
// Class objc_setSuperclass(Class cls, Class newSuper);
1152+
FUNCTION(SetSuperclass, class_setSuperclass, C_CC, AlwaysAvailable,
1153+
RETURNS(ObjCClassPtrTy),
1154+
ARGS(ObjCClassPtrTy, ObjCClassPtrTy),
1155+
ATTRS(NoUnwind))
1156+
11511157
// Metadata *swift_getObjectType(id object);
11521158
FUNCTION(GetObjectType, swift_getObjectType, C_CC, AlwaysAvailable,
11531159
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
@@ -1887,6 +1887,50 @@ void IRGenerator::emitEagerClassInitialization() {
18871887
llvm::appendToGlobalCtors(IGM->Module, RegisterFn, 60000, nullptr);
18881888
}
18891889

1890+
void IRGenerator::emitObjCActorsNeedingSuperclassSwizzle() {
1891+
if (ObjCActorsNeedingSuperclassSwizzle.empty())
1892+
return;
1893+
1894+
// Emit the register function in the primary module.
1895+
IRGenModule *IGM = getPrimaryIGM();
1896+
1897+
llvm::Function *RegisterFn = llvm::Function::Create(
1898+
llvm::FunctionType::get(IGM->VoidTy, false),
1899+
llvm::GlobalValue::PrivateLinkage,
1900+
"_swift_objc_actor_initialization");
1901+
IGM->Module.getFunctionList().push_back(RegisterFn);
1902+
IRGenFunction RegisterIGF(*IGM, RegisterFn);
1903+
RegisterFn->setAttributes(IGM->constructInitialAttributes());
1904+
RegisterFn->setCallingConv(IGM->DefaultCC);
1905+
1906+
// Look up the SwiftNativeNSObject class.
1907+
auto swiftNativeNSObjectName =
1908+
IGM->getAddrOfGlobalString("SwiftNativeNSObject");
1909+
auto swiftNativeNSObjectClass = RegisterIGF.Builder.CreateCall(
1910+
RegisterIGF.IGM.getLookUpClassFn(), swiftNativeNSObjectName);
1911+
1912+
for (ClassDecl *CD : ObjCActorsNeedingSuperclassSwizzle) {
1913+
// The @objc actor class.
1914+
llvm::Value *classRef = RegisterIGF.emitTypeMetadataRef(
1915+
CD->getDeclaredInterfaceType()->getCanonicalType());
1916+
classRef = RegisterIGF.Builder.CreateBitCast(classRef, IGM->ObjCClassPtrTy);
1917+
classRef = RegisterIGF.Builder.CreateCall(
1918+
IGM->getFixedClassInitializationFn(), classRef);
1919+
1920+
// Set its superclass to SwiftNativeNSObject.
1921+
RegisterIGF.Builder.CreateCall(
1922+
RegisterIGF.IGM.getSetSuperclassFn(),
1923+
{ classRef, swiftNativeNSObjectClass});
1924+
}
1925+
RegisterIGF.Builder.CreateRetVoid();
1926+
1927+
// Add the registration function as a static initializer. We use a priority
1928+
// slightly lower than used for C++ global constructors, so that the code is
1929+
// executed before C++ global constructors (in case someone uses archives
1930+
// from a C++ global constructor).
1931+
llvm::appendToGlobalCtors(IGM->Module, RegisterFn, 60000, nullptr);
1932+
}
1933+
18901934
/// Emit symbols for eliminated dead methods, which can still be referenced
18911935
/// from other modules. This happens e.g. if a public class contains a (dead)
18921936
/// private method.

lib/IRGen/IRGen.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,7 @@ GeneratedModule IRGenRequest::evaluate(Evaluator &evaluator,
11111111
IGM.emitBuiltinReflectionMetadata();
11121112
IGM.emitReflectionMetadataVersion();
11131113
irgen.emitEagerClassInitialization();
1114+
irgen.emitObjCActorsNeedingSuperclassSwizzle();
11141115
irgen.emitDynamicReplacements();
11151116
}
11161117

@@ -1351,6 +1352,7 @@ static void performParallelIRGeneration(IRGenDescriptor desc) {
13511352
irgen.emitReflectionMetadataVersion();
13521353

13531354
irgen.emitEagerClassInitialization();
1355+
irgen.emitObjCActorsNeedingSuperclassSwizzle();
13541356

13551357
// Emit reflection metadata for builtin and imported types.
13561358
irgen.emitBuiltinReflectionMetadata();

lib/IRGen/IRGenModule.cpp

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

1137+
void IRGenerator::addBackDeployedObjCActorInitialization(ClassDecl *ClassDecl) {
1138+
if (!ClassDecl->isActor())
1139+
return;
1140+
1141+
if (!ClassDecl->isObjC())
1142+
return;
1143+
1144+
// If we are not back-deploying concurrency, there's nothing to do.
1145+
ASTContext &ctx = ClassDecl->getASTContext();
1146+
auto deploymentAvailability = AvailabilityContext::forDeploymentTarget(ctx);
1147+
if (deploymentAvailability.isContainedIn(ctx.getConcurrencyAvailability()))
1148+
return;
1149+
1150+
ObjCActorsNeedingSuperclassSwizzle.push_back(ClassDecl);
1151+
}
1152+
11371153
llvm::AttributeList IRGenModule::getAllocAttrs() {
11381154
if (AllocAttrs.isEmpty()) {
11391155
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 -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)