Skip to content

Commit 20dd808

Browse files
authored
Merge pull request #23413 from slavapestov/phase-out-nscoding-hack-deployment-target
IRGen: Don't eagerly initialize NSCoding conformers on startup on new deployment targets
2 parents 830eebb + 6d7d13f commit 20dd808

File tree

10 files changed

+129
-99
lines changed

10 files changed

+129
-99
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,24 @@ namespace swift {
385385
return EffectiveLanguageVersion.isVersionAtLeast(major, minor);
386386
}
387387

388+
// The following deployment targets ship an Objective-C runtime supporting
389+
// the class metadata update callback mechanism:
390+
//
391+
// - macOS 10.14.4
392+
// - iOS 12.2
393+
// - tvOS 12.2
394+
// - watchOS 5.2
395+
bool doesTargetSupportObjCMetadataUpdateCallback() const;
396+
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+
388406
/// Returns true if the given platform condition argument represents
389407
/// a supported target operating system.
390408
///

lib/Basic/LangOptions.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,20 @@ std::pair<bool, bool> LangOptions::setTarget(llvm::Triple triple) {
275275

276276
return { false, false };
277277
}
278+
279+
bool LangOptions::doesTargetSupportObjCMetadataUpdateCallback() const {
280+
if (Target.isMacOSX())
281+
return !Target.isMacOSXVersionLT(10, 14, 4);
282+
if (Target.isiOS()) // also returns true on tvOS
283+
return !Target.isOSVersionLT(12, 2);
284+
if (Target.isWatchOS())
285+
return !Target.isOSVersionLT(5, 2);
286+
287+
// If we're running on a non-Apple platform, we still want to allow running
288+
// tests that -enable-objc-interop.
289+
return false;
290+
}
291+
292+
bool LangOptions::doesTargetSupportObjCGetClassHook() const {
293+
return doesTargetSupportObjCMetadataUpdateCallback();
294+
}

lib/IRGen/GenClass.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2327,7 +2327,7 @@ IRGenModule::getClassMetadataStrategy(const ClassDecl *theClass) {
23272327

23282328
// If the Objective-C runtime is new enough, we can just use the update
23292329
// pattern unconditionally.
2330-
if (Types.doesPlatformSupportObjCMetadataUpdateCallback())
2330+
if (Context.LangOpts.doesTargetSupportObjCMetadataUpdateCallback())
23312331
return ClassMetadataStrategy::Update;
23322332

23332333
// Otherwise, check if we have legacy type info for backward deployment.

lib/IRGen/GenDecl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,8 +1483,8 @@ void IRGenerator::emitEagerClassInitialization() {
14831483
RegisterFn->setCallingConv(IGM->DefaultCC);
14841484

14851485
for (ClassDecl *CD : ClassesForEagerInitialization) {
1486-
Type Ty = CD->getDeclaredType();
1487-
llvm::Value *MetaData = RegisterIGF.emitTypeMetadataRef(getAsCanType(Ty));
1486+
auto Ty = CD->getDeclaredType()->getCanonicalType();
1487+
llvm::Value *MetaData = RegisterIGF.emitTypeMetadataRef(Ty);
14881488
assert(CD->getAttrs().hasAttribute<StaticInitializeObjCMetadataAttr>());
14891489

14901490
// Get the metadata to make sure that the class is registered. We need to

lib/IRGen/GenType.cpp

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,37 +1147,19 @@ static bool doesPlatformUseLegacyLayouts(StringRef platformName,
11471147
return false;
11481148
}
11491149

1150-
// The following Apple platforms ship an Objective-C runtime supporting
1151-
// the class metadata update hook:
1152-
//
1153-
// - macOS 10.14.4
1154-
// - iOS 12.2
1155-
// - tvOS 12.2
1156-
// - watchOS 5.2
1157-
static bool doesPlatformSupportObjCMetadataUpdateCallback(
1158-
const llvm::Triple &triple) {
1159-
if (triple.isMacOSX())
1160-
return !triple.isMacOSXVersionLT(10, 14, 4);
1161-
if (triple.isiOS()) // also returns true on tvOS
1162-
return !triple.isOSVersionLT(12, 2);
1163-
if (triple.isWatchOS())
1164-
return !triple.isOSVersionLT(5, 2);
1165-
1166-
return false;
1167-
}
1168-
11691150
TypeConverter::TypeConverter(IRGenModule &IGM)
11701151
: IGM(IGM),
11711152
FirstType(invalidTypeInfo()) {
1172-
const auto &Triple = IGM.Context.LangOpts.Target;
1173-
1174-
SupportsObjCMetadataUpdateCallback =
1175-
::doesPlatformSupportObjCMetadataUpdateCallback(Triple);
1153+
// Whether the Objective-C runtime is guaranteed to invoke the class
1154+
// metadata update callback when realizing a Swift class referenced from
1155+
// Objective-C.
1156+
bool supportsObjCMetadataUpdateCallback =
1157+
IGM.Context.LangOpts.doesTargetSupportObjCMetadataUpdateCallback();
11761158

11771159
// If our deployment target allows us to rely on the metadata update
11781160
// callback being called, we don't have to emit a legacy layout for a
11791161
// class with resiliently-sized fields.
1180-
if (SupportsObjCMetadataUpdateCallback)
1162+
if (supportsObjCMetadataUpdateCallback)
11811163
return;
11821164

11831165
// We have a bunch of -parse-stdlib tests that pass a -target in the test
@@ -1191,6 +1173,8 @@ TypeConverter::TypeConverter(IRGenModule &IGM)
11911173

11921174
StringRef path = IGM.IRGen.Opts.ReadLegacyTypeInfoPath;
11931175
if (path.empty()) {
1176+
const auto &Triple = IGM.Context.LangOpts.Target;
1177+
11941178
// If the flag was not explicitly specified, look for a file in a
11951179
// platform-specific location, if this platform is known to require
11961180
// one.

lib/IRGen/GenType.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ class TypeConverter {
114114
const LoadableTypeInfo *SwiftRetainablePointerBoxTI = nullptr,
115115
*UnknownObjectRetainablePointerBoxTI = nullptr;
116116

117-
bool SupportsObjCMetadataUpdateCallback = false;
118117
llvm::StringMap<YAMLTypeInfoNode> LegacyTypeInfos;
119118
llvm::DenseMap<NominalTypeDecl *, std::string> DeclMangledNames;
120119

@@ -160,10 +159,6 @@ class TypeConverter {
160159
return LoweringMode;
161160
}
162161

163-
bool doesPlatformSupportObjCMetadataUpdateCallback() const {
164-
return SupportsObjCMetadataUpdateCallback;
165-
}
166-
167162
const TypeInfo *getTypeEntry(CanType type);
168163
const TypeInfo &getCompleteTypeInfo(CanType type);
169164
const LoadableTypeInfo &getNativeObjectTypeInfo();

lib/IRGen/IRGenModule.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -814,15 +814,8 @@ void IRGenerator::addClassForEagerInitialization(ClassDecl *ClassDecl) {
814814
if (!ClassDecl->getAttrs().hasAttribute<StaticInitializeObjCMetadataAttr>())
815815
return;
816816

817-
// Exclude some classes where those attributes make no sense but could be set
818-
// for some reason. Just to be on the safe side.
819-
Type ClassTy = ClassDecl->getDeclaredType();
820-
if (ClassTy->is<UnboundGenericType>())
821-
return;
822-
if (ClassTy->hasArchetype())
823-
return;
824-
if (ClassDecl->hasClangNode())
825-
return;
817+
assert(!ClassDecl->isGenericContext());
818+
assert(!ClassDecl->hasClangNode());
826819

827820
ClassesForEagerInitialization.push_back(ClassDecl);
828821
}

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4756,11 +4756,16 @@ static bool isNSCoding(ProtocolDecl *protocol) {
47564756

47574757
/// Whether the given class has an explicit '@objc' name.
47584758
static bool hasExplicitObjCName(ClassDecl *classDecl) {
4759+
// FIXME: Turn this function into a request instead of computing this
4760+
// as part of the @objc request.
4761+
(void) classDecl->isObjC();
4762+
47594763
if (classDecl->getAttrs().hasAttribute<ObjCRuntimeNameAttr>())
47604764
return true;
47614765

47624766
auto objcAttr = classDecl->getAttrs().getAttribute<ObjCAttr>();
4763-
if (!objcAttr) return false;
4767+
if (!objcAttr)
4768+
return false;
47644769

47654770
return objcAttr->hasName() && !objcAttr->isNameImplicit();
47664771
}
@@ -4834,6 +4839,12 @@ static void inferStaticInitializeObjCMetadata(TypeChecker &tc,
48344839
if (classDecl->getAttrs().hasAttribute<StaticInitializeObjCMetadataAttr>())
48354840
return;
48364841

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+
48374848
// If we know that the Objective-C metadata will be statically registered,
48384849
// there's nothing to do.
48394850
if (!classDecl->checkAncestry(AncestryFlags::Generic)) {
@@ -4843,7 +4854,7 @@ static void inferStaticInitializeObjCMetadata(TypeChecker &tc,
48434854
// If this class isn't always available on the deployment target, don't
48444855
// mark it as statically initialized.
48454856
// FIXME: This is a workaround. The proper solution is for IRGen to
4846-
// only statically initializae the Objective-C metadata when running on
4857+
// only statically initialize the Objective-C metadata when running on
48474858
// a new-enough OS.
48484859
if (auto sourceFile = classDecl->getParentSourceFile()) {
48494860
AvailabilityContext availableInfo = AvailabilityContext::alwaysAvailable();
@@ -4993,7 +5004,8 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
49935004
if (auto classDecl = dc->getSelfClassDecl()) {
49945005
if (Context.LangOpts.EnableObjCInterop &&
49955006
isNSCoding(conformance->getProtocol()) &&
4996-
!classDecl->isGenericContext()) {
5007+
!classDecl->isGenericContext() &&
5008+
!classDecl->hasClangNode()) {
49975009
diagnoseUnstableName(*this, conformance, classDecl);
49985010
// Infer @_staticInitializeObjCMetadata if needed.
49995011
inferStaticInitializeObjCMetadata(*this, classDecl);
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)