Forum: Mikrocontroller und Digitale Elektronik avr-gcc: toten Code eliminieren


von Moriz (untertaucher)


Lesenswert?

Wie kann man mit dem avr-gcc toten Code eliminieren?

https://stackoverflow.com/questions/14737641/have-linker-remove-unused-object-files-for-avr-gcc 
empfielt die Linker-Option -gc-sections und die Compiler-Optionen 
-ffunction-sections und -fdata-sections – nur leider bewirken die 
nichts.

Ein anderer Tipp aus derselben Quelle sagt, mit den Compiler-Optionen 
-Wl,-gc-sections würde es gehen – stimmt für den avr-gcc 5.4.0 aber auch 
nicht.

Wie bekommt man es hin?

von Achim M. (minifloat)


Lesenswert?

Hast du toten Code?
Hat vielleicht der Compiler diesen toten Code bereits entfernt?

Auch eine Idee wäre, eine statische Codeanalyse drüber laufen zu lassen 
und sich die Stellen anzusehen, die als toter Code identifiziert wurden.

mfg mf

von Moriz (untertaucher)


Lesenswert?

Achim M. schrieb:
> Hat vielleicht der Compiler diesen toten Code bereits entfernt?

Nein, hat er nicht.

Ich habe eine Funktion zu einem Modul zugefügt, die nirgends aufgerufen 
wird – also toter Code ist. Trotzdem steht sie – egal, wie 
Compiler/Linker optioniert werden – im .lss-File.

von MaWin O. (mawin_original)


Lesenswert?

Mit LinkTimeOptimization -flto kann man toten Code eigentlich ganz 
zuverlässig entfernen lassen. -flto muss dem Compiler und dem Linker 
angegeben werden.

von Wilhelm M. (wimalopaan)


Lesenswert?

Moriz schrieb:
> Ich habe eine Funktion zu einem Modul zugefügt, die nirgends aufgerufen
> wird – also toter Code ist.

Die wird wohl programm-global sein und Du übersetzt die TUs einzeln. 
Dann kann / darf der Compiler die nicht eliminieren.

Also entweder TU-lokal oder auf LTO umsteigen.

von Falk S. (db8fs)


Lesenswert?

Gibt vermutlich mehrere Wege, das zu erreichen - paar sehr gute sind 
schon genannt worden.

Persönlich würde ich auch mit der Link-Time-Optimization rangehen. 
Eventuell machts sogar Sinn, wenn du dein Projekt bereits frei von 
Warnungen hast, Warnungen als Fehler und dich dann mit der LTO auf 
unreferenzierte Funktionen/Variablen hinweisen zu lassen, eventuell auch 
nur pro einzelnem File, wenn's zuviel wird.

Rausmachen musste das dann selber - wegoptimieren sollte der die aber 
normalerweise so oder so ab genügend hohem Optimierungslevel.

von Moriz (untertaucher)


Lesenswert?

Wilhelm M. schrieb:
> Dann kann / darf der Compiler die nicht eliminieren.

Der Compiler nicht, aber der Linker. Mit den Optionen, die ich oben 
genannt hatte, sollte das passierten, aber offenbar funktioniert das 
nicht.

Falk S. schrieb:
> wenn du dein Projekt bereits frei von Warnungen hast

Das ist es.

> Rausmachen musste das dann selber

Das möchte ich vermeiden, denn es handelt sich um eine 
Quelltextbibliothek – die will man nicht für jeden Anwendungsfall 
zerpflucken…

> wegoptimieren sollte der die aber normalerweise so oder so ab genügend
> hohem Optimierungslevel.

Dafür prädestiniert sein sollte -Os – die habe ich aktiviert. Hilft aber 
nix.

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Moriz schrieb:
> Dafür prädestiniert sein sollte -Os

Nöö...
LTO

von MaWin O. (mawin_original)


Lesenswert?

Moriz schrieb:
> Der Compiler nicht, aber der Linker. Mit den Optionen, die ich oben
> genannt hatte, sollte das passierten, aber offenbar funktioniert das
> nicht.

Nein, das ist nicht ausreichend. -fwhole-program fehlt.
Der Linker kann sonst nicht wissen, ob du nicht später doch noch vor 
hast etwas hinzuzulinken. (Der Linker wird nicht nur auf Embedded 
verwendet.)

von MaWin O. (mawin_original)


Lesenswert?

Arduino F. schrieb:
> LTO

Ja ich würde auch unbedingt LTO empfehlen. Das optimiert auch Code 
innerhalb von Funktionen raus, wenn einzelne Ausführungspfade in einer 
tatsächlichen Konfiguration nicht betreten werden können. Ganz ohne 
hässliche #ifdefs. Du wirst überrascht sein, was LTO leisten kann.

von Wilhelm M. (wimalopaan)


Lesenswert?

Wenn

CFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections

nicht den gewünschten Effekt hat, dann machst Du etwas falsch.

: Bearbeitet durch User
von Moriz (untertaucher)


Lesenswert?

MaWin O. schrieb:
> -fwhole-program fehlt.

Das hat das Bild verändert: Der Linker mault jetzt massenweise Symbole 
an, die jeweils in anderen Modulen liegen. Beispiel:
1
obj/Release/libraries/watchdog.o: In function `__vector_6':
2
watchdog.c:(.text.__vector_6+0x18): undefined reference to `errorShutdown'

errorShutdown liegt in main.c Sie wird in dem Fall zwar aus einer ISR 
referiert, aber darauf scheint es nicht anzukommen:
1
obj/Release/main.o: In function `sensorError':
2
main.c:(.text.sensorError+0xe): undefined reference to `uart_puts_P'

uart_puts_P liegt in uart.c

Die unerfüllten Links sind jeweils einer Funktion zugeordnet, es sind 
schlichtweg alle Funktionen, die darin aufgerufen werden, sogar 
mehrfach.

von Moriz (untertaucher)


Lesenswert?

Super. Mit der Compiler-Optionierung funktioniert es:

-Wl,-gc-sections
-fdata-sections
-ffunction-sections
-fwhole-program
-std=gnu99

Allerdings kotzt er jetzt noch einen Sack voll
1
warning: MPFR header version 4.0.1-rc1 differs from library version 4.0.1.

auf die ich mir keinen Reim machen kann.

von MaWin O. (mawin_original)


Lesenswert?

Moriz schrieb:
> Das hat das Bild verändert: Der Linker mault jetzt massenweise Symbole
> an, die jeweils in anderen Modulen liegen.

Die Option darfst du natürlich nur dem Linkerlauf mitgeben. Nicht dem 
Compiler.

von Oliver S. (oliverso)


Lesenswert?

-fwhole-program macht halt nicht das, was du erwartest.

-Os zusammen mit -flto wurde ja nun schon mehrfach genannt, besser wird 
es nicht. Allerdings ist dein gcc jetzt nicht der jüngste, es kann 
durchaus sein, daß neuere das besser hinbekommen.

Oliver

von Moriz (untertaucher)


Lesenswert?

MaWin O. schrieb:
> Moriz schrieb:
>> Das hat das Bild verändert: Der Linker mault jetzt massenweise Symbole
>> an, die jeweils in anderen Modulen liegen.
>
> Die Option darfst du natürlich nur dem Linkerlauf mitgeben. Nicht dem
> Compiler.

Die -f…-Optionen werden mit -Wl vom Compiler-Driver an den Linker 
durchgereicht.

Der Effekt: der Code ist von 21300 auf 15030 Byte geschrumpft.

-fwhole-program kann man weglassen, die ändert nichts.

Mein Fehler war, -Wl in eine eigene Zeile unter Compiler-Optionen in 
Codeblocks zu schreiben.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Moriz schrieb:
> Mein Fehler war, -Wl in eine eigene Zeile unter Compiler-Optionen in
> Codeblocks zu schreiben.

Typisch Salami.

von Moriz (untertaucher)


Lesenswert?

Und hier nochmal für die Nachwelt die Erkenntnisse aus diesem Thread:

Um toten Code zu eliminieren, muss man dem avr-gcc folgende Optionen 
mitgeben:
1
-flto
2
-fdata-sections
3
-ffunction-sections
4
-Wl,-gc-sections

-Wl,-gc-sections wird dabei an den Linker durchgereicht.

Bei dieser Optimierung werden Module wegoptimiert, für die es im Code 
keine Referenzen gibt. Das führt zu Linkerfehlern. Hier ein Beispiel:

Der Optiboot Bootlader gibt in R2 den Wert des MCUSR-Registers, den es 
direkt nach dem Start hatte, an die Anwendung weiter. Ich hatte dieses 
Feature durch folgendes Codefragment genutzt:
1
    uint8_t mcusr __attribute__ ((section (".noinit")));
2
3
    void init_MCUSR_copy(void) __attribute__ ((section (".init0")));
4
5
    void init_MCUSR_copy(void) {
6
        __asm__ __volatile__ ("sts %0, r2\n" : "=m" (mcusr) );      // save r2 to mcusr. r2 has been setup by optiboot
7
    }

Der Code bewirkt, dass der Linker den Maschinenbefehl
1
   sts mcusr, r2
direkt vor den C-Startcode der Anwendung plaziert, also an die Stelle, 
an die der Interruptvektor 0 der Anwendung zeigt. Auf init_MCUSR_copy 
gibt es in der Anwendung keine Referenz, weshalb der Linker den Befehl 
als toten Code erkennt und raus schmeißt.

Man kann das dadurch beheben, dass man irgendwo in einem if-Statement, 
das nie erfüllt ist, init_MCUSR_copy aufruft – der Linker findet eine 
Referenz und baut den Modul ein.

Etwas eleganter geht es, wenn man das Codefragment als Interruptroutine 
für den Reset deklariert:
1
    #define RESET_VECT  _VECTOR(0)
2
3
    uint8_t mcusr __attribute__ ((section (".noinit")));
4
5
    void RESET_VECT(void) __attribute__ ((naked)) __attribute__ ((section (".init0")));
6
7
    ISR(RESET_VECT, ISR_NAKED) {
8
        __asm__ __volatile__ ("sts %0, r2\n" : "=m" (mcusr) );      // save r2 to mcusr. r2 has been setup by optiboot
9
    }

Die Variable mcusr, in die der Inhalt von R2 gespeichert wird, wird 
durch das _attribute_ ((section (".noinit"))) in einen Speicherbereich 
gelegt, der vom Startup-Code der Anwendung nicht überschrieben wird.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Moriz schrieb:
> Man kann das dadurch beheben, dass man irgendwo in einem if-Statement,
> das nie erfüllt ist, init_MCUSR_copy aufruft – der Linker findet eine
> Referenz und baut den Modul ein.
1
#define NOINIT __attribute__((section(".noinit")))
2
#define INIT0  __attribute__((section(".init0")))
3
#define NAKED  __attribute__((naked)) 
4
#define USED   __attribute__((used))
5
6
7
byte mcusr NOINIT;
8
9
void init_MCUSR_copy() INIT0 USED  NAKED;

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Moriz schrieb:
> Auf init_MCUSR_copy gibt es in der Anwendung keine Referenz,
> weshalb der Linker den Befehl als toten Code erkennt und raus schmeißt.

Im originalen Linker-Skript sind die .initN Startup-Sections als KEEP 
gekennzeichnet.  Ergo: Es wird ein fehlerhaftes ld-Skript verwendet, 
oder die Funktion wird früher (vom Compiler) entsorgt.

Damit letzteres nicht geschieht:
1
__attribute__((__naked__, __used__, __unused__, __section__(".init0")))
2
static void init_MCUSR_copy (void)
3
{
4
    __asm __volatile ("sts %0, r2" :: "i" (&mcusr) : "memory");
5
}

> -Wl,-gc-sections wird dabei an den Linker durchgereicht.
> Bei dieser Optimierung werden Module wegoptimiert,

Es werden (rekursiv) Input-Sections gelöscht, die nicht referenziert 
werden, nicht KEEP sind, und die kein Entry-Symbol enthalten.

> Das führt zu Linkerfehlern. Hier ein Beispiel:

Bug in Optiboot, siehe oben.

: Bearbeitet durch User
von Moriz (untertaucher)


Lesenswert?

Johann L. schrieb:
> Bug in Optiboot, siehe oben.

Nein, mit Optiboot hat das überhaupt nichts zu tun – der lädt nur ganz 
zu Beginn R2 mit dem Inhalt von MCUSR, das er anschließend löscht. Der 
gesamte Code von Optiboot rührt R2 anschließend nicht mehr an.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Moriz schrieb:
> Johann L. schrieb:
>> Bug in Optiboot, siehe oben.
>
> Nein, mit Optiboot hat das überhaupt nichts zu tun – der lädt nur ganz
> zu Beginn R2 mit dem Inhalt von MCUSR, das er anschließend löscht. Der
> gesamte Code von Optiboot rührt R2 anschließend nicht mehr an.

Jedenfalls ein Bug in den Attributen von init_MCUSR_copy und / oder dem 
verwendeten ld-Skript.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.