Why public symbols are always no_dead_strip?

I noticed Swift public symbols are always marked as no_dead_strip, so the public symbols defined in a static library won't be stripped from the executable even they're not used, which results in larger binary size.

I'm wondering why public symbols are default to no_dead_strip. Is there a way to control this behavior?


Here is the sample code,

// lib.swift
public struct PublicStruct {
    public func f1() { print("public print") }
}

struct InternalStruct {
    func f2() { print("internal print") }
}
> swiftc -parse-as-library -c -o lib.o -Xfrontend -disable-reflection-metadata lib.swift
> nm -m lib.o | grep "no dead strip"
0000000000000000 (__TEXT,__text) external [no dead strip] _$s3lib12PublicStructV2f1yyF
00000000000001c0 (__TEXT,__text) external [no dead strip] _$s3lib12PublicStructVMa
000000000000021c (__TEXT,__const) external [no dead strip] _$s3lib12PublicStructVMn
0000000000000270 (__DATA,__const) external [no dead strip] [alt entry] _$s3lib12PublicStructVN
0000000000000264 (__TEXT,__const) weak private external [no dead strip] ___swift_reflection_version
00000000000002a0 (__LLVM,__swift_modhash) non-external [no dead strip] l_llvm.swift_module_hash
0000000000000298 (__TEXT,__swift5_types) non-external [no dead strip] l_type_metadata_table

As you can see, public symbols are no dead strip while internal symbols are not. I also verified that the string "internal print" can be stripped away by the linker while "public print" cannot.

4 Likes

It's not strictly necessary; it's a legacy of Swift's original priorities being to support dynamic libraries with stable ABIs, and we're working to make it unnecessary when building static libraries. See:

9 Likes

Thank you for the quick response. It's good to know that there is some good progress on that already.

I'm currently thinking for some prebuilt 3rd party libraries, maybe I can write a tool to scrub the symbol table and mark off N_NO_DEAD_STRIP in nlist.n_desc field. Anything obviously wrong with this approach?

2 Likes

You'll want to be careful, because there are things that might rely on runtime lookup that won't be obviously alive to the linker, like metadata for types that are looked up by name, or protocol conformances for types that are dynamically cast using as? Protocol casts. For most other things, that should be safe, though.

2 Likes

We need to decide whether "reflection on types you don't use" is the right default. I think it would be much better to have a "don't strip" attribute you can opt into.

5 Likes

This is a pretty big problem for getting (e.g.) the stdlib to scale down right now. It isn't a matter of just LTO'ing your app and the stdlib together, it is a language issue that all those protocols (and their metadata etc) aren't strippable.

1 Like

In fact there is an active thread about this right now.

Also this one:

I agree that having types be reflectable by default is unnecessary and it should ultimately be opt-in.

1 Like

Coming back to give an update. With -internalize-at-link, the Swift public symbols are no longer marked as no_dead_strip. We're also seeing significant app size saving after applying that flag.

Using the example in my original post, this is what I'm seeing now.

> swiftc -parse-as-library -c -o lib.o -Xfrontend -disable-reflection-metadata -Xfrontend -internalize-at-link lib.swift
> nm -m lib.o | grep "no dead strip"
00000000000002f4 (__TEXT,__const) weak private external [no dead strip] ___swift_reflection_version
0000000000000328 (__TEXT,__swift5_types) non-external [no dead strip] l_$s3lib12PublicStructVHn
000000000000032c (__TEXT,__swift5_types) non-external [no dead strip] l_$s3lib14InternalStructVHn
0000000000000330 (__LLVM,__swift_modhash) non-external [no dead strip] l_llvm.swift_module_hash
3 Likes

Do you have any issues using this flag? We have large library of proto objects with all classes public and most of them are not referenced from main app, so looks like this flag can be useful.

We enabled this flag for a while. No issue has been surfaced.

2 Likes