this isn’t a new problem, but i am surprised it persists to this day, and remains resistant to all of the remedies that were predicted to mitigate it.
i did an experiment on Swift 6.2.4 creating a minimal server application that depends on SwiftNIO and AsyncAlgorithms, and compared the size of the compiled binary when using AsyncAlgorithms, and when literally copy-pasting the source files of AsyncChannel into a vendored copy of the module within the project.
aside from the strange situation where --experimental-lto-mode full actually makes the binary larger on macOS, it is apparent that vendoring the source files of AsyncChannel is still the clear winner by a considerable margin.
when i first encountered this problem years ago, i was advised that dead code/symbols stripping and the (at the time, upcoming) swift build --experimental-lto-mode full feature would resolve the problem by design. but that has not really panned out.
The only solution I've found to reduce binary size of projects is to use package traits for every offending piece of code. That is the main reason why I wrote my own networking library without SwiftNIO and Foundation, and the minimal server I wrote using it came under 1 MB (without explicit flags or special linking; could shave it down to < 500 KiB if you disabled some package traits, suitable for embedded).
With Swift 6.3 around the corner and 6.1 becoming the last 'supported' minor release, I really hope more packages utilize package traits. Some of the popular libraries already require 6.1 or later yet they don't use them...
With Xcode 26.4 finally adding support for them (though I don't know how good it is), they should be more viable in the near future. Biggest issue right now is that using traits in packages randomly breaks stuff in Xcode, so I've generally avoided them in my packages, and packages that use them.
It seems like this is a high-impact effort that would help all Swift platforms, such as @compnerd with Windows too if employing the right Windows-specific tools there, would be good to set up some knowledge-sharing and collaboration on this important issue @taylorswift brings up.
I think this is a good problem to tackle. In my opinion, we should start off with creating tooling similar to bloaty that helps us analyze binaries and understand where the binary size is coming from. Potentially also writing a documentation article about this.
FWIW I added this to support a very early variant of embedded swift. It needs to be combined with --internalize-at-link and a couple other flags to really have an effect. Desktop swift today isn't able to dead strip as effectively as embedded due to things like reflection metadata. I'll avoid speaking more concretely as I don't understand the full details.
cc @Douglas_Gregor@Slava_Pestov might be able to explain what type metadata keeps "dead" code around. (or if thats even correct).
Overall this would be good - but given that bloaty works (good enough?!) - perhaps worth checking status and upstreaming any missing pieces? Not sure it helps for android/windows though…
Bloaty 1.1 is ~6years old so I recommend using the latest bloaty from source. A number of fixes for bloaty to work better with Swift binaries have landed recently, e.g. improved dwarf5 support, improved handling of dsyms, support for demangling Swift symbols. There’s a few pending pull requests as well that also improve bloaty support on macOS, such as support for archives, and fixes for vm addressing in universal binaries.