Search code examples
swiftstatic-librariesstatic-linkingstrip

Swift -dead_strip doesn't remove unused classes


I create simple static library with only one source file for test -dead_strip option

public class UsedClass {
public init() {)
public func function() { print("print") }
}
public class PublicClas {
func private_func() { print("print") }
}
class SimpleClass {
func private_func() { print("print") }
}
public struct PublicStruct {
func private_func() { print("print") }
}
struct SimpleStruct {
func private_func() { print("print") }
}

And dynamic library witch use only one class from static library

public class MyClass {
public func callModule() {
let qq = UsedClass()
qq.function()
}
}

Link dynamic library with option -dead_strip

I see SimpleClass artifact in output binary, use command nm -m test_lib | grep SimpleClass

0000000100003d54 (\__TEXT,\__const) non-external (was a private external) \_$s13TestFramework11SimpleClassC11public_funcyyFTq
0000000100003d5c (\__TEXT,\__const) non-external (was a private external) \_$s13TestFramework11SimpleClassC12private_funcyyFTq
0000000100003d64 (\__TEXT,\__const) non-external (was a private external) \_$s13TestFramework11SimpleClassCACycfCTq
0000000100003e58 (\__TEXT,\__swift5_fieldmd) non-external \_$s13TestFramework11SimpleClassCMF
0000000100003a38 (\__TEXT,\__text) non-external (was a private external) \_$s13TestFramework11SimpleClassCMa
00000001000083e0 (\__DATA,\__data) non-external \_$s13TestFramework11SimpleClassCMf
00000001000083b8 (\__DATA,\__data) non-external (was a private external) \_$s13TestFramework11SimpleClassCMm
0000000100003d20 (\__TEXT,\__const) non-external (was a private external) \_$s13TestFramework11SimpleClassCMn
00000001000083f0 (\__DATA,\__data) non-external (was a private external) \_$s13TestFramework11SimpleClassCN
0000000100003a6c (\__TEXT,\__text) non-external (was a private external) \_$s13TestFramework11SimpleClassCfD
0000000100008198 (\__DATA,\__objc_const) non-external \__DATA__TtC13TestFramework11SimpleClass
0000000100008150 (\__DATA,\__objc_const) non-external \__METACLASS_DATA__TtC13TestFramework11SimpleClass
0000000100003e24 (\__TEXT,\__swift5_typeref) non-external (was a private external) \_symbolic \____\_ 13TestFramework11SimpleClassC

After googling a found that Apple static library is not same with Unix static library, but whats different? I think my example is very easy and all C++ linkers can understand it and remove unused classes

I remove -ObjC option, and try add -internalize-at-link option( from https://forums.swift.org/t/why-public-symbols-are-always-no-dead-strip/47264 ), but always have same result, only private struct removed.


Solution

  • Classes in Swift work with the Objective-C runtime (even if you don’t use @objc). They each create a record of an objc class in the app binary. You can find them with code like this:

    NSString *executablePath = [[NSBundle mainBundle] executablePath];
    unsigned int classNamesInMainBinaryCount = 0;
    const char **classNamesInMainBinary = objc_copyClassNamesForImage([executablePath UTF8String], &classNamesInMainBinaryCount);
    

    Since objc supports dynamic features such as NSClassFromString() these classes need to stay in the binary and can’t be stripped at compile time.

    I work on a tool that detects these kinds of unused classes at runtime, called Reaper. This is the only way to really know if they are not used by any of the dynamic runtime features.