GCC Link Time Optimization (LTO)

Ich arbeite auf dem Raspberry Pi und anderen Linux-Umgebung ja schon lange mit Debian 10 (Buster) und neuerdings auch auf Debian 11 (Bullseye).

Doch als ich spaßhalber einen GCC 6.3 Build des GATE Frameworks unter Debian 9 (Stretch) anfachte, tauchten jede Menge Fehler folgender Art auf:

plugin needed to handle lto object

Und dann meinte auch noch MinGW:

undefined reference to `strndup’


Zwei GCC Probleme

MinGW + libressl == missing strndup

Als ich vor einem halben Jahr auf Link-Time-Optimization (LTO) in den GCC Build umgestiegen bin, tat ich das nur im GATE Development Branch und der löst beim git push auf BitBucket keine Build-Pipeline aus.

Und seit dem ich neulich einen Pull-Request zur master Pipeline absetzte, krachte es beim Linken von einigen C++ Templates und immer wieder in der libressl, wenn mit MinGW unter Linux auch Windows-Binärdateien gebaut werden.

Offenbar werden einige Symbole durch die LTO entfernt, denn ist diese abgeschaltet, produziert MinGW brav Windows EXEn.

GCC kleiner 7 kann LTO nicht so richtig

Das “älteste” Linux, mit dem ich so arbeite ist Ubuntu 18.04 in WSL und das setzt den GCC 7.5 ein. Und meine Raspberry PIs mit Debian 10 kompilieren mit dem GCC 8.3.
Und alle konnten sie mit dem -flto Switch problemlos umgehen.

Doch da wir in der Firma auch mit Debian 8 und 9 arbeiteten und ich eher zufällig entdeckte, dass unter Windows 2022 ein wsl install -d debian plötzlich wieder Debian 9 ausrollt (während im Windows Store mit Debian 10 und neuer gehandelt wird), war klar, dass ich mit dem älteren Compiler experimentieren wollte.

Und bei jeder einzelnen .c oder .cpp Datei kam die Meldung:
plugin needed to handle lto object.

Es sieht also so aus, dass GCC 6.3 mit -flto nicht leben kann, denn ohne diesen Zusatz funktioniert alles problemlos.

Lösung: LTO abschalten

Oh Mann! Ich habe unzählige Kombinationen ausprobiert, doch keine konnte alle Probleme beseitigen und so blieb mir nichts anderes übrig, als folgendes in CMakeLists.txt einzutragen:

 1if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
 2  # standard GCC flags
 3  set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s -fdata-sections -ffunction-sections")
 4  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s -fdata-sections -ffunction-sections")
 5  set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -s -Wl,--gc-sections")
 6  set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} -s -Wl,--gc-sections")
 7  set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -s -Wl,--gc-sections")
 8
 9  if(NOT MINGW AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 7.0)
10    # special GCC LTO + optimization flags
11    string(REPLACE "-O3" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
12    string(REPLACE "-O3" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
13    set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto -Os")
14    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto -Os")
15    set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -flto")
16    set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} -flto")
17    set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -flto")
18  endif()
19endif()

Wenn also der MinGW oder ein anderer GCC kleiner 7 eingesetzt wird, dann soll keine Link Time Optimization stattfinden.
Ob diese Lösung korrekt ist, weiß ich nicht, sie entspricht nur meiner Beobachtung aus der Debian/Ubuntu Serie.

Es gibt Hinweise, dass in älteren Linux-Distributionen die bin-utilities LTO nicht nativ unterstützen und dafür andere Varianten von /usr/bin/ar und /usr/bin/ranlib bräuchten. Man könnte in CMake die Aufrufe durch gcc-ar und gcc-ranlib ersetzen, doch das hat bei mir nie vollständig funktioniert. Keine Ahnung warum, aber hier ist die Anleitung, die ich dazu gefunden habe.

Mein MinGW ist neuer als 7.0 und kann LTO, doch der stößt sich an anderen Flags wie z.B. -s, -Os oder -Wl,--gc-sections.

Ich konnte alle C++ Template Probleme durch die Entfernung von -s usw. beheben, wobei -flto bestehen blieb. Doch strndup blieb stets als “undefiniert” übrig und ließ den Übersetzungsvorgang abbrechen.

Folgende Pseudo-Erklärung reime ich mir dazu zusammen:
strndup ist eine Linux Erweiterung, die unter Windows fehlt. libressl erkennt das und fügt eine Kompatibilitäts-Implementierung hinzu. Dort wird strndup zwar implementiert, doch es gibt keine Deklaration in einem Header, die darauf verweise würde.
Und somit dürfte die Funktion auch als unsichtbar gelten, wenn LTO alle Module am Ende des Linkens zusammenfasst, schließlich wird das Symbol offiziell nicht in andere Module “importiert”, aber dort sehr wohl genutzt.

Fazit

Hmm … ich bin etwas angepisst.

Die Experimente haben mich einige Stunden gekostet und am Ende bin ich daran gescheitert. Vielleicht gibt es eine Kombination, die LTO in statischen Bibliotheken wie libressl unter MinGW gefügig machen und vielleicht kann man auch die Toolchain unter GCC kleiner 7 mit dem korrekten LTO Plugin füttern.

Aber ich fand es am Ende einfacher, diese Features auf jenen Plattformen auszuschalten, damit ich mich um wichtigere Dinge kümmern kann.

Dass LTO unter Linux mit dem GCC erst seit kurzem vernünftig funktioniert, finde ich etwas komisch. Schließlich nutzen Intel und der MSVC diese Technik schon seit 2 Jahrzehnten um die effizientesten Binaries zu erzeugen.

Tja, beim GCC war früher eben doch nicht alles besser …