Skip to content

Enable C LTO for firmware#1962

Merged
benma merged 1 commit into
BitBoxSwiss:masterfrom
benma:c-lto
May 17, 2026
Merged

Enable C LTO for firmware#1962
benma merged 1 commit into
BitBoxSwiss:masterfrom
benma:c-lto

Conversation

@benma
Copy link
Copy Markdown
Collaborator

@benma benma commented Apr 23, 2026

Compile and link the cross-compiled C firmware objects with GCC LTO. This lets the linker optimize across C object and static-library boundaries while keeping the existing section garbage collection and stack protector.

The ARM toolchain must use the GCC LTO-aware archive utilities; plain arm-none-eabi-ar/ranlib can archive LTO objects without the plugin and produces an invalid tiny image where live objects are not extracted. Select arm-none-eabi-gcc-ar, arm-none-eabi-gcc-nm, and arm-none-eabi-gcc-ranlib in the ARM toolchain file.

With LTO, bootloader stack-protector references are generated after the normal symbol liveness pass. Mark the local __stack_chk_fail and __stack_chk_guard definitions as used/externally visible so they are kept for those late-generated references. __stack_chk_fail is also marked no_stack_protector to avoid protecting the failure handler itself.

Measured with make -j firmware.
Saves 12040 bytes of firmware ROM.

Verification:

  • make -j firmware
  • make -j bootloader

@benma benma requested a review from NickeZ April 23, 2026 22:23
@NickeZ
Copy link
Copy Markdown
Collaborator

NickeZ commented Apr 24, 2026

Did you try the bootloader? I remember having to fix it because we have assembly instructions that don't name the variables they use.

https://github.com/NickeZ/bitbox02-firmware/commits/enable-lto/

@benma benma marked this pull request as draft April 24, 2026 08:19
@NickeZ
Copy link
Copy Markdown
Collaborator

NickeZ commented Apr 24, 2026

Let's only do it for firmware, not bootloader nor factory setup. And you can remove -ffucntion-sections and -fdata-sections and --gc-sections, because they only apply without LTO.

@benma
Copy link
Copy Markdown
Collaborator Author

benma commented Apr 27, 2026

Let's only do it for firmware, not bootloader nor factory setup.

Added a change to limit to firmware, and also to exclude asf4 drivers etc. Having the drivers do lto in firmware but not in bootloader/factorysetup was too involved, and we get most of the gains in the firmware already without doing lto on the drivers.

And you can remove -ffucntion-sections and -fdata-sections and --gc-sections, because they only apply without LTO.

It seems they still apply, codex:


No, that is too broad.

--gc-sections still applies with LTO. LTO may remove dead code earlier at IR level, so -ffunction-sections / -fdata-sections are often less important for LTO-compiled objects, but the linker still performs
section GC on the final linked image.

In our scoped setup this matters even more because firmware still links non-LTO code:

  • ASF4 drivers remain non-LTO.
  • samd51a-ds and embedded-swd remain non-LTO.
  • libc/libgcc/vendor blobs/Rust archives may still benefit from section GC.

For those inputs, -ffunction-sections -fdata-sections plus --gc-sections is still the normal mechanism that lets the linker drop unused functions/data at fine granularity.

So the accurate version is: for code fully optimized by LTO, these flags may be partially redundant; for mixed LTO/non-LTO firmware, they are still useful and should not be removed without measuring.

@benma benma marked this pull request as ready for review April 28, 2026 07:57
@cedwies
Copy link
Copy Markdown
Collaborator

cedwies commented May 12, 2026

I tried verifying codex, seems correct. After checking GCC -flto doc, GCC section flags and GNU ld --gc-sections, nothing says "disabled when LTO is enabled". GCC docs actually explicitly say LTO links can mix objects with LTO bytecode and normal final object code.

A current concern of me: cryptoauthlib and optiga get compiled with LTO notes, while firmware-setup gets compiled without LTO notes.

But in src/CMakeLists.txt (line 495) every target links the same libraries. If we want firmware-setup to not do LTO link (as comment in line 487 suggests), shouldn't we add -fno-lto for factory setup?

Like:

if(firmware STREQUAL "factory-setup")
  target_compile_options(${elf} PRIVATE -fno-lto)
  target_link_libraries(${elf} PRIVATE -fno-lto)
else()
  target_compile_options(${elf} PRIVATE -flto -ffat-lto-objects)
  target_link_libraries(${elf} PRIVATE -flto)
endif()

@benma
Copy link
Copy Markdown
Collaborator Author

benma commented May 12, 2026

I tried verifying codex, seems correct. After checking GCC -flto doc, GCC section flags and GNU ld --gc-sections, nothing says "disabled when LTO is enabled". GCC docs actually explicitly say LTO links can mix objects with LTO bytecode and normal final object code.

A current concern of me: cryptoauthlib and optiga get compiled with LTO notes, while firmware-setup gets compiled without LTO notes.

But in src/CMakeLists.txt (line 495) every target links the same libraries. If we want firmware-setup to not do LTO link (as comment in line 487 suggests), shouldn't we add -fno-lto for factory setup?

Like:

if(firmware STREQUAL "factory-setup")
  target_compile_options(${elf} PRIVATE -fno-lto)
  target_link_libraries(${elf} PRIVATE -fno-lto)
else()
  target_compile_options(${elf} PRIVATE -flto -ffat-lto-objects)
  target_link_libraries(${elf} PRIVATE -flto)
endif()

-flto was not enabled, so one does not need to disable it explicitly. I disabled it though for consistency with the compile option and for clarity.

@cedwies
Copy link
Copy Markdown
Collaborator

cedwies commented May 15, 2026

-flto was not enabled, so one does not need to disable it explicitly. I disabled it though for consistency with the compile option and for clarity.

My main concern was the following part of the GCC docs:

GCC automatically performs link-time optimization if any of the objects involved were compiled with the -flto command-line option. You can always override the automatic decision to do link-time optimization by passing -fno-lto to the link command.

Thanks for specifying -fno-lto :)

Copy link
Copy Markdown
Collaborator

@cedwies cedwies left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK

saves:

firmware: 8.856 bytes
firmware-btc: 9.008 bytes
factory-setup: 0 bytes

Enable scoped GCC LTO for firmware C code to reduce ROM size while preserving section GC and stack protector support. Build firmware C objects plus the optiga and cryptoauthlib static libraries with -flto -ffat-lto-objects, and link only firmware images with -flto, since useful size savings require LTO IR in the linked static libraries.

Switch the ARM toolchain to the LTO-aware archive utilities (arm-none-eabi-gcc-ar, arm-none-eabi-gcc-nm, arm-none-eabi-gcc-ranlib); plain ar/ranlib can archive LTO objects without the plugin and produce invalid tiny images by failing to extract live objects.

Keep bootloaders, factory-setup, ASF4, samd51a-ds, and embedded-swd
off the LTO path because startup, interrupt, MMIO, vector-table,
linker-script, assembly, callback-table, and section-name interactions
are not cheaply auditable for LTO safety. Mark local stack protector
symbols as kept/visible so late LTO-generated references to
__stack_chk_fail and __stack_chk_guard are retained;
no_stack_protector is intentionally not needed.
@benma benma merged commit 509f1ea into BitBoxSwiss:master May 17, 2026
13 of 37 checks passed
@benma benma deleted the c-lto branch May 17, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants