Skip to content

Commit eec0fcd

Browse files
committed
IRGen: When available, use ClassMetadataStrategy::Update even if we don't have legacy type info
If we know our deployment target has a new Objective-C runtime, we can emit fixed metadata for classes with resilient types even if those types do not appear in the YAML legacy type info. Fixes <rdar://problem/47649465>.
1 parent 9a6126b commit eec0fcd

8 files changed

+473
-42
lines changed

lib/IRGen/ClassLayout.h

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,31 @@ class ClassLayout {
157157
Options.contains(ClassMetadataFlags::ClassHasObjCAncestry));
158158
}
159159

160-
bool doesMetadataRequireInitialization() const {
161-
return (Options.contains(ClassMetadataFlags::ClassHasResilientAncestry) ||
162-
Options.contains(ClassMetadataFlags::ClassHasGenericAncestry) ||
163-
Options.contains(ClassMetadataFlags::ClassHasResilientMembers) ||
160+
/// Returns true iff everything about the class metadata layout is statically
161+
/// known except field offsets and the instance size and alignment.
162+
///
163+
/// Will assert if the class metadata is "more" dynamic; you must check
164+
/// doesMetadataRequireRelocation() and doesMetadataRequireInitialization()
165+
/// first.
166+
bool doesMetadataRequireUpdate() const {
167+
assert(!doesMetadataRequireInitialization());
168+
return (Options.contains(ClassMetadataFlags::ClassHasResilientMembers) ||
164169
Options.contains(ClassMetadataFlags::ClassHasMissingMembers));
165170
}
166171

172+
/// Returns true iff everything about the class metadata layout is statically
173+
/// known except the superclass field must be instantiated at runtime because
174+
/// it is a generic class type.
175+
///
176+
/// Will assert if the class metadata is "more" dynamic; you must check
177+
/// doesMetadataRequireRelocation() first.
178+
bool doesMetadataRequireInitialization() const {
179+
assert(!doesMetadataRequireRelocation());
180+
return Options.contains(ClassMetadataFlags::ClassHasGenericAncestry);
181+
}
182+
183+
/// Returns true if the class metadata must be built at runtime because its
184+
/// size is not known at compile time. This is the most general case.
167185
bool doesMetadataRequireRelocation() const {
168186
return (Options.contains(ClassMetadataFlags::ClassHasResilientAncestry) ||
169187
Options.contains(ClassMetadataFlags::ClassIsGeneric));

lib/IRGen/GenClass.cpp

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2237,21 +2237,47 @@ IRGenModule::getClassMetadataStrategy(const ClassDecl *theClass) {
22372237

22382238
auto &resilientLayout = selfTI.getClassLayout(*this, selfType,
22392239
/*forBackwardDeployment=*/false);
2240-
auto &fragileLayout = selfTI.getClassLayout(*this, selfType,
2241-
/*forBackwardDeployment=*/true);
22422240

22432241
if (resilientLayout.doesMetadataRequireRelocation())
22442242
return ClassMetadataStrategy::Resilient;
22452243

2246-
if (fragileLayout.doesMetadataRequireInitialization())
2244+
// On Windows, we want to force singleton metadata initialization, since
2245+
// fixed class metadata emission requires an absolute global reference to the
2246+
// Builtin.NativeObject value witness table in the runtime, which is something
2247+
// the PE executable format does not support.
2248+
if (IRGen.Opts.LazyInitializeClassMetadata)
22472249
return ClassMetadataStrategy::Singleton;
22482250

2249-
// If the legacy type info was sufficient to produce a fixed fragile layout,
2250-
// but our resilient layout requires initialization, we can use the fixed
2251-
// layout on older Objective-C runtimes, and emit an update callback for
2252-
// newer Objective-C runtimes.
2251+
// If we have generic ancestry, we have to use the singleton pattern.
22532252
if (resilientLayout.doesMetadataRequireInitialization())
2253+
return ClassMetadataStrategy::Singleton;
2254+
2255+
// If we have resiliently-sized fields, we might be able to use the
2256+
// update pattern.
2257+
if (resilientLayout.doesMetadataRequireUpdate()) {
2258+
// The update pattern only benefits us on platforms with an Objective-C
2259+
// runtime, otherwise just use the singleton pattern.
2260+
if (!Context.LangOpts.EnableObjCInterop)
2261+
return ClassMetadataStrategy::Singleton;
2262+
2263+
// If the Objective-C runtime is new enough, we can just use the update
2264+
// pattern unconditionally.
2265+
if (Types.doesPlatformSupportObjCMetadataUpdateCallback())
2266+
return ClassMetadataStrategy::Update;
2267+
2268+
// Otherwise, check if we have legacy type info for backward deployment.
2269+
auto &fragileLayout = selfTI.getClassLayout(*this, selfType,
2270+
/*forBackwardDeployment=*/true);
2271+
2272+
// If we still have resiliently-sized fields even when using the legacy
2273+
// type info, fall back to the singleton pattern.
2274+
if (fragileLayout.doesMetadataRequireUpdate())
2275+
return ClassMetadataStrategy::Singleton;
2276+
2277+
// We're going to use the legacy type info on older Objective-C runtimes,
2278+
// and the update callback on newer runtimes.
22542279
return ClassMetadataStrategy::FixedOrUpdate;
2280+
}
22552281

22562282
return ClassMetadataStrategy::Fixed;
22572283
}

lib/IRGen/GenType.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,12 @@ TypeConverter::getLegacyTypeInfo(NominalTypeDecl *decl) const {
11161116
return found->second;
11171117
}
11181118

1119+
// The following Apple platforms support backward deployment of Swift
1120+
// code built with Swift 5.0 running on an old Objective-C runtime
1121+
// that does not support the class metadata update hook.
1122+
//
1123+
// We ship a YAML legacy type info file for these platforms as part
1124+
// of the toolchain.
11191125
static llvm::StringLiteral platformsWithLegacyLayouts[][2] = {
11201126
{"appletvos", "arm64"},
11211127
{"appletvsimulator", "x86_64"},
@@ -1140,6 +1146,26 @@ static bool doesPlatformUseLegacyLayouts(StringRef platformName,
11401146

11411147
return false;
11421148
}
1149+
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+
11431169
TypeConverter::TypeConverter(IRGenModule &IGM)
11441170
: IGM(IGM),
11451171
FirstType(invalidTypeInfo()) {
@@ -1153,6 +1179,15 @@ TypeConverter::TypeConverter(IRGenModule &IGM)
11531179

11541180
const auto &Triple = IGM.Context.LangOpts.Target;
11551181

1182+
SupportsObjCMetadataUpdateCallback =
1183+
::doesPlatformSupportObjCMetadataUpdateCallback(Triple);
1184+
1185+
// If our deployment target allows us to rely on the metadata update
1186+
// callback being called, we don't have to emit a legacy layout for a
1187+
// class with resiliently-sized fields.
1188+
if (SupportsObjCMetadataUpdateCallback)
1189+
return;
1190+
11561191
// We have a bunch of -parse-stdlib tests that pass a -target in the test
11571192
// suite. To prevent these from failing when the user hasn't build the
11581193
// standard library for that target, we pass -disable-legacy-type-info to

lib/IRGen/GenType.h

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

117+
bool SupportsObjCMetadataUpdateCallback = false;
117118
llvm::StringMap<YAMLTypeInfoNode> LegacyTypeInfos;
118119
llvm::DenseMap<NominalTypeDecl *, std::string> DeclMangledNames;
119120

@@ -159,6 +160,10 @@ class TypeConverter {
159160
return LoweringMode;
160161
}
161162

163+
bool doesPlatformSupportObjCMetadataUpdateCallback() const {
164+
return SupportsObjCMetadataUpdateCallback;
165+
}
166+
162167
const TypeInfo *getTypeEntry(CanType type);
163168
const TypeInfo &getCompleteTypeInfo(CanType type);
164169
const LoadableTypeInfo &getNativeObjectTypeInfo();

0 commit comments

Comments
 (0)