Skip to content

cmake: add CURL_DROP_UNUSED option to reduce binary sizes#20357

Closed
vszakats wants to merge 29 commits intocurl:masterfrom
vszakats:cmdeadcode
Closed

cmake: add CURL_DROP_UNUSED option to reduce binary sizes#20357
vszakats wants to merge 29 commits intocurl:masterfrom
vszakats:cmdeadcode

Conversation

@vszakats
Copy link
Member

@vszakats vszakats commented Jan 19, 2026

To enable known linker options dropping unused, dead, code and data from
the executables built.

Useful to reduce binary sizes for curl, libcurl shared lib and apps
linking static libcurl. It's effective on both "unity" and non-unity
builds. Aligning "unity" build sizes with default, non-unity ones.

Supported platforms: Apple, MSVC, llvm/clang and GCC on all tested
platforms: Linux, BSDs, Windows, MSYS2/Cygwin, Android, MS-DOS.

Notes:

  • Static libraries grow 20-30% with non-Apple toolchains.
    This effect is controlled by separate, optional compiler flags on
    non-Apple. This patch enables them automatically for public binaries
    (libcurl and curl tool), and leaves them off for internal/test ones.
  • MSVC enables this option by default for 'Release' configurations.
    The curl build option has no effect on it.
  • Observed effect on VS2010 is negligible. VS2012+ is recommended.
  • Works with LTO, Fil-C.
  • No observed/conclusive effect on build speed.
  • On Windows with clang/gcc (mingw-w64/MSYS2/Cygwin) it also enables
    -fno-asynchronous-unwind-tables as a workaround to make
    the toolchain options actually work.
    Ref: https://sourceware.org/bugzilla/show_bug.cgi?id=11539
    Thanks-to: Andarwinux

Also:

  • GHA: enable in Linux and MinGW jobs to test it. Size changes:

    • linux aws-lc H3:
      curl: 2000000 -> 1937152, libcurl.a: 2065724 -> 2716532 bytes
    • macos clang HTTP-only:
      curl: 1364376 -> 128799 bytes, libcurl.a: unchanged
    • macos llvm MultiSSL:
      curl: 410056 -> 405720, libcurl.dylib: 1350336 -> 1348480 bytes
    • mingw schannel c-ares U:
      curl: 1588736 -> 1507328, libcurl-d.a: 3322040 -> 3884746 bytes
      bld: 34 -> 35MB
  • GHA: enable in MSVC and Apple jobs to reduce disk footprint, with no
    obvious downside. Size changes:

    • AppVeyor CI VS2019:
      curl: 2339840 -> 1295872, libcurl-d.dll: 3155968 -> 1900544 bytes
      bld: 161 -> 97MB
    • AppVeyor CI VS2022 clang-cl:
      curl: 2933248 -> 2332160, libcurl-d.lib: 4762688 -> 5511330 bytes
      bld: 133 -> 121MB
    • AppVeyor CI VS2022 HTTP-only:
      curl: 3514368 -> 2177024, libcurl-d.lib: 2538420 -> 3151740 bytes
      bld: 137 -> 83MB
    • GHA intel:
      curl: 2629120 -> 2023424, libcurl-d.lib: 4366652 -> 5350670 bytes
      bld: 86 -> 69MB
    • GHA arm64:
      curl: 2832896 -> 2063872, libcurl-d.lib: 4690616 -> 5597250 bytes
      bld: 82 -> 66MB

Refs:
https://maskray.me/blog/2021-02-28-linker-garbage-collection
https://web.archive.org/web/20110811230637/msdn.microsoft.com/en-us/library/bxwfs976.aspx (VS2010)
https://learn.microsoft.com/cpp/build/reference/opt-optimizations
https://learn.microsoft.com/cpp/build/reference/gy-enable-function-level-linking


CMake jobs only:

  • MSVC with sane build tools (Ninja): good results, overall reduced footprint, smaller executables.
  • MSVC with MSBuild (tested on AppVeyor): some jobs see good results, no change in some others.
  • mingw non-unity (6.4.0 dl-mingw): no effect (it wasn't enabled!). significant size reduction.
  • macOS: visible size improvements (but not earth-shattering in the CI examples, pbly pointless in CI or by default). (debug info included)
  • Linux: visible size improvements (pbly pointless in CI or by default), larger static libs, they include debug info. (including LTO)
  • Linux/Fil-C: no effect on curl exe, the static lib is larger by 52 bytes (doh, wasn't enabled!). It does have size improvements.
  • Cygwin/MSYS2/MinGW: binaries became bigger. Need to check if this is due to debug info. Checked, and the size increase remains after strip. Same thing works for curl-for-win (10x size curl.exe), and can't replicate for trurl.exe (5x smaller than CI curl.exe). mingw unity 686 build shows a tiny size improvement.
  • MS-DOS: larger static lib (normal), non-stripped executables increase in size a little → visible size improvements in stripped one.
  • Android: non-stripped executables increase in size a little → visible size improvements in stripped one.

  • implement it in autotools also, for clang/gcc and Apple. → ANOTHER TIME
  • perhaps avoid for test binaries. → Let's keep it simple by default and apply to all targets, the costs of this option seem minor.
  • decide how to enable in MSVC. VS2010 is weird. Enable both /Gy and /OPT:REF
    For VS2010 I found that only the first had an effect in CI.
  • perhaps avoid in debug-enabled builds? [to think about once enabled by default]
  • perhaps avoid in debug builds? [to think about once enabled by default]
  • drawbacks? Nothing in particular, besides larges static library sizes, and
    potentially more CPU use. No observable downside in CI or locally.
  • performance drawbacks? None observed.
  • test without in CI. or disable CI-wide and enable in a few jobs.

Before:
https://ci.appveyor.com/project/curlorg/curl/builds/53395645
https://github.com/curl/curl/actions/runs/21142250302 Windows (gcc/clang stripped)
https://github.com/curl/curl/actions/runs/21138785324 Linux
https://github.com/curl/curl/actions/runs/21138785297 Linux H3
https://github.com/curl/curl/actions/runs/21138785306 macOS
https://github.com/curl/curl/actions/runs/21142863929?pr=20359 non-native (Android/MS-DOS stripped)

After 1.:
https://github.com/curl/curl/actions/runs/21139220699/job/60788722271?pr=20357
https://github.com/curl/curl/actions/runs/21172575934/job/60892756697?pr=20357

@vszakats
Copy link
Member Author

vszakats commented Jan 20, 2026

  • MSVC + MSBuild knows better and ignores custom linker options (on AppVeyor). Some jobs have great improvements, but not all. Not sure why. → It seems that Release builds do this by default, so it only affects Debug builds. It also seems to require VS2012+, based on no change in the VS2010 Debug job.
  • MSVC + Ninja: works. Big gains, also in build disk footprint.
  • Linux, macOS, Android: works. Gains are modest with CI builds, but visible.
  • Static libs grow in size with non-Apple clang/gcc.

Open puzzle:

MinGW/MSYS2/Cygwin: Binary size grows. I haven't seen this before,
and couldn't replicate with curl-for-win, where this option reduces curl and
trurl size signficantly and consistently, with both big (curl) and small (trurl)
binaries. Need to investigate more.

ATM I think that enabling this automatically with unity is perhaps overkill.
Also, size improvement can also be significant for non-unity builds.
Once tested more, it can be bound to unity, or enabled by by default, and
if that works well, maybe even unity builds can be enabled by default,
alongside this option.

The same could probably be done for autotools.

@Andarwinux
Copy link

Andarwinux commented Jan 20, 2026

The problem with MinGW is that gcc and bfd only support ELF section groups and cannot handle COFF COMDAT correctly, so gc-sections won't work.

@vszakats
Copy link
Member Author

vszakats commented Jan 20, 2026

The problem with MinGW is that gcc and bfd only support ELF section groups and cannot handle COFF COMDAT correctly, so gc-sections won't work.

What could be the reason that it works in curl-for-win?:

gcc without --gc-sections:

+ stat -c %10s bytes: %n _x64-win-ucrt/usr/bin/curl.exe
   4528640 bytes: _x64-win-ucrt/usr/bin/curl.exe

https://github.com/curl/curl-for-win/actions/runs/21143631016/job/60803714998#step:3:6957

vs.

gcc with --gc-sections:

+ stat -c %10s bytes: %n _x64-win-ucrt/usr/bin/curl.exe
   3982336 bytes: _x64-win-ucrt/usr/bin/curl.exe

https://github.com/curl/curl-for-win/actions/runs/21169943310/job/60885143940#step:3:6959

same with clang: 4127232 bytes vs 3599872.

@Andarwinux
Copy link

The problem with MinGW is that gcc and bfd only support ELF section groups and cannot handle COFF COMDAT correctly, so gc-sections won't work.

What could be the reason that it works in curl-for-win?:

gcc without --gc-sections:

+ stat -c %10s bytes: %n _x64-win-ucrt/usr/bin/curl.exe
   4528640 bytes: _x64-win-ucrt/usr/bin/curl.exe

https://github.com/curl/curl-for-win/actions/runs/21143631016/job/60803714998#step:3:6957

vs.

gcc with --gc-sections:

+ stat -c %10s bytes: %n _x64-win-ucrt/usr/bin/curl.exe
   3982336 bytes: _x64-win-ucrt/usr/bin/curl.exe

https://github.com/curl/curl-for-win/actions/runs/21169943310/job/60885143940#step:3:6959

same with clang: 4127232 bytes vs 3599872.

https://sourceware.org/bugzilla/show_bug.cgi?id=11539#c19

https://github.com/curl/curl-for-win/blob/71bc883caa89b394171677df05c5376e64056a60/_build.sh#L1091

vszakats added a commit to curl/curl-for-win that referenced this pull request Jan 20, 2026
@vszakats
Copy link
Member Author

Nice! Thanks @Andarwinux!

With -fno-asynchronous-unwind-tables option added, sizes are reduced and
align with other platforms:
https://github.com/curl/curl/actions/runs/21171306589/job/60888482907?pr=20357
vs:
https://github.com/curl/curl/actions/runs/21142250302/job/60798980910

Is there any concerning property of this option? Would be safe to enable it
as part of CURL_PURGE_UNUSED on Windows?
Or by default, for unity builds?

Also perhaps these options should be applied to the curl binary and libcurl
only, to save the static lib size inflation for internal libs and test exes (and
thus not inflate footprint of a full build). Except for MSVC/Apple, which don't
have this downside and would be beneficial to apply everywhere.

@vszakats
Copy link
Member Author

vszakats commented Jan 20, 2026

correction: Fil-C and non-unity also get reduces sizes. (I forgot that I enabled the option only for unity builds initially!)

correction 2: MSVC MSBuild jobs also seem some great improvements, but not all of them. Sill looking for the explanation.

@Andarwinux
Copy link

Is there any concerning property of this option? Would be safe to enable it
as part of CURL_PURGE_UNUSED on Windows?
Or by default, for unity builds?

If there isn't anything that uses setjmp/longjmp it's safe, otherwise I'm not sure.

@vszakats
Copy link
Member Author

Is there any concerning property of this option? Would be safe to enable it
as part of CURL_PURGE_UNUSED on Windows?
Or by default, for unity builds?

If there isn't anything that uses setjmp/longjmp it's safe, otherwise I'm not sure.

curl uses sigsetjmp in two places (libcurl → hostip and tftpd in tests), does it count?

@Andarwinux
Copy link

curl uses sigsetjmp in two places (libcurl → hostip and tftpd in tests), does it count?

CI is green, so it should be fine? If there is a problem, curl is supposed to crash.

@vszakats
Copy link
Member Author

curl uses sigsetjmp in two places (libcurl → hostip and tftpd in tests), does it count?

CI is green, so it should be fine? If there is a problem, curl is supposed to crash.

curl-for-win had this enabled for 3 years with no issues. I'm rather just wondering
if this is an option someone might want to keep turned off for a reason. Useful to
know before thinking to enable CURL_PURGE_UNUSED bound to e.g. unity,
release builds, or as baseline default.

@vszakats
Copy link
Member Author

vszakats commented Jan 20, 2026

sigsetjmp is not supported by Windows.

vszakats added a commit that referenced this pull request Jan 21, 2026
Unstripped size hides effective binary sizes due to the added debug
information. E.g. `--gc-sections` may inflate unstripped binaries, while
their unstripped size decreases. To see if binary size optimization
options work, it's more useful to observe unstripped size.

Ref: #20357
Follow-up to 4cf4350 #20355

Closes #20359
@vszakats vszakats force-pushed the cmdeadcode branch 2 times, most recently from 754da61 to e04618e Compare January 21, 2026 15:39
@bagder
Copy link
Member

bagder commented Jan 21, 2026

curl uses sigsetjmp in two places (libcurl → hostip and tftpd in tests), does it count?

It is only used for synchronous name resolves on platforms with alarm(), which is not the regular Windows builds...

To enable known linker options dropping dead code and data from
the binaries built.

Enabled by default for "unity" builds.

Supported platforms: Apple, MSVC, llvm/clang and GCC.
@vszakats vszakats marked this pull request as ready for review January 22, 2026 10:58
to save build footprint and max out performance by avoiding them
for test libraries.
@vszakats vszakats changed the title cmake: add CURL_PURGE_UNUSED option cmake: add CURL_PURGE_UNUSED option to reduce binary sizes Jan 22, 2026
@vszakats vszakats changed the title cmake: add CURL_PURGE_UNUSED option to reduce binary sizes cmake: add CURL_STRIP_UNUSED option to reduce binary sizes Jan 22, 2026
@vszakats vszakats changed the title cmake: add CURL_STRIP_UNUSED option to reduce binary sizes cmake: add CURL_DROP_UNUSED option to reduce binary sizes Jan 22, 2026
@vszakats vszakats closed this in 66ad54e Jan 22, 2026
@vszakats vszakats deleted the cmdeadcode branch January 22, 2026 16:09
vszakats added a commit to vszakats/curl that referenced this pull request Feb 11, 2026
vszakats added a commit that referenced this pull request Feb 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants