GCC Link Time Optimization (LTO)
« | 07 Nov 2021 | »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 …