Skip to content

Commit 6d7d13f

Browse files
committed
IRGen: Disable eager initialization of NSCoding adopters on newer targets
If a class does not have a custom @objc name, objc_getClass() can find it at runtime by calling the Swift runtime's metadata demangler hook. This avoids the static initializer on startup. If the class has a custom runtime name we still need the static initializer unfortunately. Fixes <rdar://problem/49660515>.
1 parent 3b6ec6c commit 6d7d13f

File tree

5 files changed

+85
-55
lines changed

5 files changed

+85
-55
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,15 @@ namespace swift {
394394
// - watchOS 5.2
395395
bool doesTargetSupportObjCMetadataUpdateCallback() const;
396396

397+
// The following deployment targets ship an Objective-C runtime supporting
398+
// the objc_getClass() hook:
399+
//
400+
// - macOS 10.14.4
401+
// - iOS 12.2
402+
// - tvOS 12.2
403+
// - watchOS 5.2
404+
bool doesTargetSupportObjCGetClassHook() const;
405+
397406
/// Returns true if the given platform condition argument represents
398407
/// a supported target operating system.
399408
///

lib/Basic/LangOptions.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,7 @@ bool LangOptions::doesTargetSupportObjCMetadataUpdateCallback() const {
288288
// tests that -enable-objc-interop.
289289
return false;
290290
}
291+
292+
bool LangOptions::doesTargetSupportObjCGetClassHook() const {
293+
return doesTargetSupportObjCMetadataUpdateCallback();
294+
}

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4839,6 +4839,12 @@ static void inferStaticInitializeObjCMetadata(TypeChecker &tc,
48394839
if (classDecl->getAttrs().hasAttribute<StaticInitializeObjCMetadataAttr>())
48404840
return;
48414841

4842+
// If the class does not have a custom @objc name and the deployment target
4843+
// supports the objc_getClass() hook, the workaround is unnecessary.
4844+
if (tc.Context.LangOpts.doesTargetSupportObjCGetClassHook() &&
4845+
!hasExplicitObjCName(classDecl))
4846+
return;
4847+
48424848
// If we know that the Objective-C metadata will be statically registered,
48434849
// there's nothing to do.
48444850
if (!classDecl->checkAncestry(AncestryFlags::Generic)) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %build-irgen-test-overlays
3+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) %s -emit-ir | %FileCheck %s -DINT=i%target-ptrsize --check-prefix=CHECK --check-prefix=CHECK-OLD
4+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) %s -target x86_64-apple-macosx10.14.4 -emit-ir | %FileCheck %s -DINT=i%target-ptrsize --check-prefix=CHECK
5+
// REQUIRES: CPU=x86_64
6+
// REQUIRES: OS=macosx
7+
8+
import Foundation
9+
10+
class BaseWithCoding : NSObject, NSCoding {
11+
required init(coder: NSCoder) { fatalError() }
12+
13+
func encode(coder: NSCoder) { fatalError() }
14+
}
15+
16+
class Generic<T> : BaseWithCoding {}
17+
18+
class GenericAncestry : Generic<Int> {}
19+
20+
@objc(custom_name)
21+
class GenericAncestryWithCustomName : Generic<Double> {}
22+
23+
// CHECK-LABEL: define {{.*}} @_swift_eager_class_initialization
24+
// CHECK-NEXT: entry:
25+
// CHECK-OLD-NEXT: [[RESPONSE:%.*]] = call swiftcc %swift.metadata_response @"$s4main15GenericAncestryCMa"([[INT]] 0)
26+
// CHECK-OLD-NEXT: [[METADATA:%.*]] = extractvalue %swift.metadata_response [[RESPONSE]], 0
27+
// CHECK-OLD-NEXT: call void asm sideeffect "", "r"(%swift.type* [[METADATA]])
28+
// CHECK-NEXT: [[RESPONSE:%.*]] = call swiftcc %swift.metadata_response @"$s4main29GenericAncestryWithCustomNameCMa"([[INT]] 0)
29+
// CHECK-NEXT: [[METADATA:%.*]] = extractvalue %swift.metadata_response [[RESPONSE]], 0
30+
// CHECK-NEXT: call void asm sideeffect "", "r"(%swift.type* [[METADATA]])
31+
// CHECK-NEXT: ret

test/Interpreter/SDK/archive_attributes.swift

Lines changed: 35 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
// RUN: %empty-directory(%t)
22
// RUN: %target-build-swift %s -module-name=test -DENCODE -o %t/encode
3-
// RUN: %target-build-swift %s -module-name=test -o %t/decode
4-
// RUN: %target-build-swift %s -module-name=test -Xfrontend -disable-llvm-optzns -emit-ir | %FileCheck -check-prefix=CHECK-IR %s
53
// RUN: %target-run %t/encode %t/test.arc
64
// RUN: plutil -p %t/test.arc | %FileCheck -check-prefix=CHECK-ARCHIVE %s
7-
// RUN: %target-run %t/decode %t/test.arc | %FileCheck %s
5+
6+
// RUN: %target-build-swift %s -module-name=test -o %t/decode
7+
// RUN: %target-run %t/decode %t/test.arc --stdlib-unittest-in-process
8+
9+
// RUN: %target-build-swift %s -module-name=test -o %t/decode -target x86_64-apple-macosx10.14.4
10+
// RUN: %target-run %t/decode %t/test.arc NEW --stdlib-unittest-in-process
811

912
// REQUIRES: executable_test
1013
// REQUIRES: objc_interop
11-
// REQUIRES: CPU=i386 || CPU=x86_64
12-
// UNSUPPORTED: OS=tvos
13-
// UNSUPPORTED: OS=watchos
14+
// REQUIRES: OS=macosx
1415

1516
import Foundation
17+
import StdlibUnittest
1618

1719
struct ABC {
1820
// CHECK-ARCHIVE-DAG: "$classname" => "nested_class_coding"
@@ -133,59 +135,37 @@ class TopLevel : NSObject, NSCoding {
133135
}
134136
}
135137

136-
func main() {
137-
138-
let args = CommandLine.arguments
139-
140138
#if ENCODE
141-
let c = TopLevel(27)
142-
c.nested = ABC.NestedClass(28)
143-
c.priv = PrivateClass(29)
144-
c.intc = IntClass(ii: 42)
145-
c.doublec = DoubleClass(dd: 3.14)
139+
let c = TopLevel(27)
140+
c.nested = ABC.NestedClass(28)
141+
c.priv = PrivateClass(29)
142+
c.intc = IntClass(ii: 42)
143+
c.doublec = DoubleClass(dd: 3.14)
146144

147-
NSKeyedArchiver.archiveRootObject(c, toFile: args[1])
145+
NSKeyedArchiver.archiveRootObject(c, toFile: CommandLine.arguments[1])
148146
#else
149-
if let u = NSKeyedUnarchiver.unarchiveObject(withFile: args[1]) {
150-
if let x = u as? TopLevel {
151-
// CHECK: top-level: 27
152-
print("top-level: \(x.tli)")
153-
if let n = x.nested {
154-
// CHECK: nested: 28
155-
print("nested: \(n.i)")
156-
}
157-
if let p = x.priv {
158-
// CHECK: private: 29
159-
print("private: \(p.pi)")
160-
}
161-
if let g = x.intc {
162-
// CHECK: int: 42
163-
print("int: \(g.gi!)")
164-
}
165-
if let d = x.doublec {
166-
// CHECK: double: 3.14
167-
print("double: \(d.gi!)")
168-
}
169-
} else {
170-
print(u)
147+
var DecodeTestSuite = TestSuite("Decode")
148+
149+
DecodeTestSuite.test("Decode") {
150+
func doIt() {
151+
let u = NSKeyedUnarchiver.unarchiveObject(withFile: CommandLine.arguments[1])!
152+
let x = u as! TopLevel
153+
expectEqual(27, x.tli)
154+
expectEqual(28, x.nested!.i)
155+
expectEqual(29, x.priv!.pi)
156+
expectEqual(42, x.intc!.gi!)
157+
expectEqual(3.14, x.doublec!.gi!)
158+
}
159+
160+
if CommandLine.arguments[2] == "NEW" {
161+
if #available(macOS 10.14.4, *) {
162+
doIt()
171163
}
172-
} else {
173-
print("nil")
164+
return
174165
}
175-
#endif
176-
}
177-
178-
main()
179166

180-
// Check that we eagerly create metadata of generic classes, but not for nested classes.
181-
182-
// CHECK-IR-LABEL: define {{.*}} @_swift_eager_class_initialization
183-
// CHECK-IR-NEXT: entry:
184-
// CHECK-IR-NEXT: call {{.*}}IntClassCMa
185-
// CHECK-IR-NEXT: extractvalue
186-
// CHECK-IR-NEXT: call void asm
187-
// CHECK-IR-NEXT: call {{.*}}DoubleClassCMa
188-
// CHECK-IR-NEXT: extractvalue
189-
// CHECK-IR-NEXT: call void asm
190-
// CHECK-IR-NEXT: ret
167+
doIt()
168+
}
191169

170+
runAllTests()
171+
#endif

0 commit comments

Comments
 (0)