One of our teams has encountered some odd behavior involving Swift codegen for Objective-C generics that intersects with some efforts to avoid using the -ObjC
flag so the linker can do better pruning of things that are known to not be used only-dynamically. The issue seems to be the difference between when a direct reference to the _OBJC_CLASS_$_*
symbol is used vs. __swift_instantiateConcreteTypeFromMangledName
when accessing the metadata. I wasn't sure if this was intentional or a bug so I figured I'd start here.
I've uploaded a small repro example at GitHub - allevato/objc-generics-swift-metadata-accessors-weirdness.
First, let's look at a native Swift class as the baseline:
let obj = NativeSwift<NSString>()
Looking at the IR, we see that it calls __swift_instantiateConcreteTypeFromMangledName
using the mangled name of the symbol, and that name includes the bound generic parameters:
%0 = call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$s4main11NativeSwiftCySo8NSStringCGMD") #10
%1 = call swiftcc ptr @"$s4main11NativeSwiftCACyxGycfC"(ptr swiftself %0)
This works and doesn't pose any linker issues because there are references to other symbols from the class.
Next, the metadata for a non-generic Obj-C class:
let obj = NonGenericList()
Here, the compiler generates a direct reference to the _OBJC_CLASS_REF_$
symbol for the class, so the linker won't strip it.
%0 = call swiftcc %swift.metadata_response @"$sSo14NonGenericListCMa"(i64 0) #11
%1 = extractvalue %swift.metadata_response %0, 0
%2 = call swiftcc ptr @"$sSo14NonGenericListCABycfC"(ptr swiftself %1)
// ...
define linkonce_odr hidden swiftcc %swift.metadata_response @"$sSo14NonGenericListCMa"(i64 %0) #6 {
// ...
%3 = load ptr, ptr @"OBJC_CLASS_REF_$_NonGenericList", align 8
%4 = call ptr @objc_opt_self(ptr %3) #5
%5 = call ptr @swift_getObjCClassMetadata(ptr %4) #7
Where things get weird is when the Obj-C class is generic:
let obj = GenericList<NSString>()
%0 = call ptr @__swift_instantiateConcreteTypeFromMangledName(ptr @"$sSo11GenericListCMD") #10
%1 = call swiftcc %swift.metadata_response @"$sSo8NSStringCMa"(i64 0) #11
%2 = extractvalue %swift.metadata_response %1, 0
%3 = call swiftcc ptr @"$sSo11GenericListCAByxGycfC"(ptr %2, ptr swiftself %0)
This calls __swift_instantiateConcreteTypeFromMangledName
, but since the mangled name of the Objective-C class doesn't include the bound generic parameters, it still has to load them separately. Is this actually saving anything compared to just putting a direct reference to the _OBJC_CLASS_REF_$
in the binary?
But the real reason this is problematic is since the other methods invoked on the class boil down to objc_msgSend
, without that reference to the class symbol in the binary, the linker ends up stripping the whole thing. And if the initializer being called happens to be failable, the result is that it just returns nil
at runtime because the class isn't found.
Passing -Xfrontend -disable-concrete-type-metadata-mangled-name-accessors
does make the generic case work, but that's not something I really want teams to reach for.
Should this be considered a bug? Could we consider emitting the direct reference in that last case? It seems like it would be harmless to do so but I could certainly be missing something.