Neuer gcc compiler für ARM und Optimierungen

OP Persönliche Seite #7561480
Lesenswert?

Mit dem Wechsel von Debian 11 auf Debian 12 wurde auch der gcc für ARM aktualisiert (jetzt arm-none-eabi-gcc (15:12.2.rel1-1) 12.2.1 20221205). Leider liefen damit einige meiner Projekte nicht mehr. Also hab ich die Ursache eingegrenzt und bin auf eine Optimierung gestoßen, die dafür sorgt, dass meine eigene strlen Implementierung mit der Optimierung von Os durch eine Endlosschleife ersetzt wird:

Aus

1
size_t strlen(const char * text) {
2
  size_t len = 0;
3
  while(*text) {
4
    text++;
5
    len++;
6
  }
7
  return len;
8
}

wird mit -Os:

1
strlen:
2
b strlen

Benenne ich meine Funktion um, so wird daraus ebenfalls b strlen. Der Compiler erkennt also dass meine Implementierung das selbe liefert wie strlen und will meine Funktion durch einen Aufruf der Standardlib ersetzen. Wie schalte ich das ab? Die Makefile fürs Beispiel habe ich angehängt. Mit O1 sieht der generierte Code gut aus. Mit O2 wird ein rekursiver Aufruf mit fehlerhaften Rückgabewert draus.

Bevor jetzt jemand fragt warum ich die Standard strlen Funktion ersetze: Meine Implementierung ist zwar nicht unbedingt schneller, aber deutlich Programmspeicher sparender als die strlen der newlib (28byte vs 220byte).

Angehängte Dateien:
#7561496
Lesenswert?

Mein Vertrauen in den GCC und die Maintainer ist schon lange gestoert.

Ein AVR-GCC verweigerte, ohne weitere Fehlermeldung die Compileroption -Os. Nach vielem Suchen stellte sich heraus, dass dieser Teil eine bessere CPU im Host erwartete... Eine Qualitaetskontrolle hat da sicher nicht stattgefunden. GCC scheint halt "Bastlersoftware" zu sein.

Oder das fuer eine Compilerversion X valider Code, in der Compilerversion X+1, sich nur noch als "grosser Haufen von Fehlern" praesentiert. Und man bevor man weitere Zeit verschendet, die Distribution mit Version X in einen Virtualisierer installiert, und das was man braucht dann dort kompilieren muss?

Fuer ARM gibt es, Gott sei es gedankt, ja genug kommerzielle Alternativen.

#7561585
Lesenswert?

Malte _. schrieb:

Aus size_t strlen(const char * text) { size_t len = 0; while(*text) { text++; len++; } return len; }

wird mit -Os:strlen: b strlen

Benenne ich meine Funktion um, so wird daraus ebenfalls b strlen. Der Compiler erkennt also dass meine Implementierung das selbe liefert wie strlen und will meine Funktion durch einen Aufruf der Standardlib ersetzen.

Rein nach C-Standard ist das korrekt. Das definieren eigener Funktionen, deren Name mit str gefolgt von einem Kleinbuchstaben beginnt, führt bei einer "hosted"-Impelementation zu undefiniertem Verhalten.

Wie schalte ich das ab?

Mit -ffreestanding. Das gilt dann aber für alle Standard-Funktionen.

Bevor jetzt jemand fragt warum ich die Standard strlen Funktion ersetze: Meine Implementierung ist zwar nicht unbedingt schneller, aber deutlich Programmspeicher sparender als die strlen der newlib (28byte vs 220byte).

Wenn du strlen aufrufst, nutzt er dann überhaupt die Implementation der newlib? Solche Funktionen sind bei gcc normalerweise bereits im Compiler selbst umgesetzt. Das hätte ich gerade bei so einer einfachen Funktion wie strlen erwartet.

Motopick schrieb:

Oder das fuer eine Compilerversion X valider Code, in der Compilerversion X+1, sich nur noch als "grosser Haufen von Fehlern" praesentiert.

Oft ist der Code nicht valide, sondern der Fehler ist nur nicht aufgefallen. Viele Fehler im Code fallen einem erst bei Optimierungen auf die Füße. Neue Optimierungen führen daher dazu, dass diese Fehler erst in einer neueren Compiler-Version auffallen. Der Fehler liegt dann aber trotzdem im Code und nicht im Compiler.

OP Persönliche Seite #7561591
Lesenswert?

Max H. schrieb:

Schiebe die Funktion in ein eigenes C-File und kompilier es mit -ffreestanding.

Danke, damit sieht der Code so aus wie er soll :) Und mit 14 byte dürfte es kürzer nicht gehen.

Ggf. funktioniert auch -fno-builtin-strlen in deinem Fall.

Nein, der erzeugt den selben Code. Ist ja auch zu erwarten. Bei nicht-buildin soll er ja die Lib aufrufen, statt gegebenenfalls inline Code einzufügen.

Motopick schrieb:

Ein AVR-GCC verweigerte, ohne weitere Fehlermeldung die Compileroption -Os.

Hmm, ja es dürfte einen Grund haben warum Debian für den AVR weiterhin gcc version 5 ausliefert.

Mein Vertrauen in den GCC und die Maintainer ist schon lange gestoert.

In einer älteren gcc version (mit ARM) war mir mal aufgefallen, dass bei einer Funktion, die ein float zurück gibt und wo das return vergessen wurde der program counter einfach in die nächste Funktion reinläuft, weil auch kein Funktionsreturn generiert wird. Das scheint aber in der aktuellen Version inzwischen behoben zu sein.

Wirklich gruselig finde ich eher die newlib für ARM. Erst dadurch habe ich zu schätzen gelernt wie gut und kompakt die avr-libc geschrieben ist.

Persönliche Seite #7561607
Lesenswert?

Malte _. schrieb:

wird mit -Os:

1
> strlen:
2
> b strlen

Das ist aber nicht der Code aus dem Beispiel, das du angehangen hast. Der wäre

1
> mystrlen:
2
> b strlen

-fno-builtin-strlen wurde schon genannt, funktioniert aber leider nicht:

https://godbolt.org/z/En3j6Gvda

Evtl https://gcc.gnu.org/PR102725

-fno-builtin und -ffreestanding funktionieren zwar, aber die will man nicht verwenden, auch nicht auf bare-metal.

Als Work-Around geht zum Beispiel:

1
size_t strlen (const char *text)
2
{
3
  size_t len = 0;
4

5
  while (*text)
6
  {
7
    text++;
8
    len++;
9
    __asm ("");
10
  }
11

12
  return len;
13
}
OP Persönliche Seite #7561609
Lesenswert?

Rolf M. schrieb:

Wenn du strlen aufrufst, nutzt er dann überhaupt die Implementation der newlib? Solche Funktionen sind bei gcc normalerweise bereits im Compiler selbst umgesetzt. Das hätte ich gerade bei so einer einfachen Funktion wie strlen erwartet.

Jup, tut er. Egal welche Optimierung ich wähle, es gibt immer ein bl strlen wenn ich strlen aufrufe (deswegen hab ich die ja auch in der Map Datei gefunden und durch meine kurze Implementierung ersetzt). Das macht ja auch Sinn. Bei -Os dürfte Platz gespart werden werden wenn strlen mehr als 1x im Programm verwendet wird. Bei -O3 könnte die Implementierung für lange strings schneller sein, wenn dann auf einem 32Bitter immer 4 Byte auf einmal gelesen und verglichen werden (erfordert natürlich mehr Code).

#7561645
Lesenswert?

Selber mit den passen Optionen compilieren, und alles wird gut. Wenn du Linux nutzt, gehört das zum Spiel.

Das habe ich vor einiger Zeit tatsaechlich mal wieder probiert. Von frueher™ kenne ich noch selbst das "Bootstrapping" fuer eine neue Version. Das ist mir auch immer als recht problemlos in eigener Erinnerung.

Diesmal sollte es aber der RISCV-GCC fuer den Test eines Softcore werden. Da braucht der Source dann noch droellfzig Patche. Als Linux haette ein Oracle Linux bereitgestanden. Einige Stunden spaeter: Schlussendlich habe ich das "Selberbauen" dann gelassen, und mein recht kurzes Testprogramm mit dem IAR-Compiler kompiliert. :) Das dann erwartungsgemaess auch funktionierte.

#7561663
Lesenswert?

Motopick schrieb:

Schlussendlich habe ich das "Selberbauen" dann gelassen, und mein recht kurzes Testprogramm mit dem IAR-Compiler kompiliert. :) Das dann erwartungsgemaess auch funktionierte.

Beim 'Versuch' ein IAR-Projekt auf Segger ES (GCC) umzustellen, mache ich leider gerade eine ähnliche Erfahrung. Beim RP2040 soll auf das Ende einer DMA-Übertragung gewartet werden, was bei IAR problemlos läuft und im Fehlerfall einfach zu debuggen wäre. Eigentlich ganz simpel:

1
void DMA11_transfer_abwarten(void)
2
{
3
  while(DMA->CH11_CTRL_TRIG & DMA_CH11_CTRL_TRIG_BUSY_Msk);
4
}

aber es wird nicht beachtet. Erst eine zusätzliche Warteschleife wirkt als notwendige Bremse. Insofern fand ich die hiesigen Beiträge sehr interessant, daß ich da nicht ganz alleine bin. Irgendwann werde ich weiter 'forschen', trete das Zeug aber zunächst in die Tonne. Macht absolut keinen Spaß!

#7561688
Lesenswert?

Oliver S. schrieb:

Selber mit den passen Optionen compilieren, und alles wird gut. Wenn du Linux nutzt, gehört das zum Spiel

Das ist doch wohl eine Selbstverstaendlichkeit. Ich hab alle meins Crosscompiler selber uebersetzt. Ich will doch schliesslich auch alle meine Cores auf demselben Revisionslevel haben.

Also manchmal denke ich der durchnittliche Linuxuser wird immer bloeder.

Vanye .-)

OP Persönliche Seite #7562074
Lesenswert?

Johann L. schrieb:

__asm ("");

Danke für den Tipp. Das werde ich mir merken, auch wenn ich denke dass ich im jetzigen Fall mit -ffreestanding besser bedient bin.

Mi N. schrieb:

while(DMA->CH11_CTRL_TRIG & DMA_CH11_CTRL_TRIG_BUSY_Msk);

Bei solchen einfachen Fällen lohnt sich meist ein Blick in den generierten Assembler um zu erkennen ob es ein Compiler (falscher Assembler) oder Hardware Problem (korrekter Assembler) ist.

Johann L. schrieb:

Das ist aber nicht der Code aus dem Beispiel, das du angehangen hast. Der wäre

Ja sorry. Ich hatte in dem kurzen Beispiel zunächst strlen stehen. Und dachte mir das kann einfach nicht sein, dass der gcc an so einer simplen Funktion scheitert. Also war mir kurz vor dem Abschicken noch die Idee gekommen diese umzubenennen. Das hat mich dann auf die Spur mit dem ersetzen des Musters durch strlen gebracht.

Oliver S. schrieb:

Selber mit den passen Optionen compilieren, und alles wird gut. Wenn du Linux nutzt, gehört das zum Spiel.

Das ist bei machen Softwarepaketen aber alles andere als einfach. https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads -> arm-gnu-toolchain-src-snapshot-13.2.rel1.tar.xz -> Entpackt 289065 Dateien. Vor Jahren hatte ich mal eine Anleitung gefunden wie es dann weiter geht. Aber aktuell finde ich nichts. Ist echt gruselig. Von daher bin ich glücklich dass die Distri Maintainer mir diese Arbeit abnehmen. Dass es dabei einen Zielkonflikt gibt "läufts auf einem Cortex-M0, M4 oder M7 und möchte der Nutzer kleinen oder schnellen Code" ist mir bewusst. Die Newlib in Debian ist definitiv für "so schnell wie möglich auf einem M7" übersetzt, wenn man sich das Disassembly anschaut.

#7562137
Lesenswert?

Malte _. schrieb:

Das ist bei machen Softwarepaketen aber alles andere als einfach

Korrekt. Daher habe ich mich oben auch zu einem Smiley hinreissen lassen. In den 90ern war auch fuer Anfaenger vollkommen normal und schaffbar Software von Source zu installieren. Mittlerweile hat es aber eine Level erreicht wo schon jeden Tag 8h beruflich so was treiben muss. Ich mach das jetzt seit 30jahren und komme gelegentlich an Grenzen wo ich aufgebe. Wieso? Weil jeder Programierer sein Waesche nur noch gewaschen bekommt in dem er hunderte vom Libaries nutzt die natuerlich in der richtigen version vorliegen muessen und Abhaengigkeiten zum Systen habe. Das ganze natuerlich Distributionsabhaengig und gerne mit den unterschiedlichsten build-tools. Beim Gcc darf man noch vermuten das Programmier die Spass dran haben den neusten Spielkram einer Sprache einzubauen natuerlich den Kram vom letzen Jahr nutzen. Da sollte man selber nicht mit einem Compiler vom vorletzten Jahr ankommen.

Es wundert mich wie lange das noch beherschbar bleibt. Irgendwann bekommen die alten Knacker das nicht mehr gebacken und der Nachwuchs schafft es nicht mehr einzusteigen.

Und schuld ist das Unfaehigkeitstheorem der Informatik. Ein Programierer ist grundsaetzich nicht in der Lage ein Projekt abzuschliessen und als fertig zu erklaeren. Stellt euch mal vor Ikea kommt an und will am vor einem Jahr gekauften Stuhl ein Bein verlaenger und in einer neuen Farbe streiche und eine geaenderte sitzhoehe ist auch kein Problem weil ihr euch ja einen neuen Tisch kaufen koennt.

Programmierer bemerken ihre Fehle(=Feature) nicht sonder machem aus ihre Unfaehigkeit ein Geschaeftsmodel. (software nur noch im Abo)

Vanye

#7562220
Lesenswert?

Vanye R. schrieb:

Programmierer bemerken ihre Fehle(=Feature) nicht sonder machem aus ihre Unfaehigkeit ein Geschaeftsmodel. (software nur noch im Abo)

Das sind nicht Programmierer, das ist das Management und das Marketing. Da der "dernier cri" der Softwareeentwicklung nun mal CI/CD ist, /muss/ ständig neue Software produziert werden, immer wieder, ständig neu. Egal, ob es tatsächlich nötig ist, man kann sich immer damit rausreden, daß irgendeine Abhängigkeit (die x-te Library, die wegen irgendeines längst vergessenen Features "gezogen" wird) das so nötig macht.

Und offenbar ist schon wieder vergessen worden, was das blinde Verlassen auf irgendwelche Libraries und deren Abhängigkeiten zur Folge haben kann - lib4j, erinnert sich noch jemand daran? Ich hab' vor ein paar Jahren mal ein so strukturiertes Projekt genauer untersucht (das war irgendwas, was SVG renderte), darin steckten gleich drei konkurrierende XML-Parser. Mit ihrem jeweiligen Rattenschwanz an Abhängigkeiten, natürlich.

Gäbe man Softwareenwicklern oder Programmierern etwas mehr Zeit, könnten sie sich, statt für jeden Furz /irgendwas/ von irgendwem irgendwo geschriebenes zu "ziehen", sich intensiver damit beschäftigen, was sie eigentlich erreichen wollen, und erkennen, daß a) sie oft den fremden Code gar nicht benötigen, weil das Problem auch anders lösbar ist oder b) sie den fremden Code auch mit einer halben Stunde Eigenleistung ersetzen könnten, nur daß die dann nicht noch drölfzig Abhängigkeiten hat.

Aber dazu müssten die Jungs halt nicht nur hippe Architekturkonzepte mit bunten Namen kennen, sondern sich mit Grundlagen beschäftigen. Ich habe mal einen Java-Programmierer dabei erlebt, wie er in einem 32-Bit-Wert nach gesetzten Bits suchte. Nein, er nutzte keinen Schiebeoberator, sondern die Funktion pow(). Macht ja nichts, PCs haben ja fett Rechenleistung.

#7562312
Lesenswert?

Mi N. schrieb:

void DMA11_transfer_abwarten(void) { while(DMA->CH11_CTRL_TRIG & DMA_CH11_CTRL_TRIG_BUSY_Msk); } aber es wird nicht beachtet. Erst eine zusätzliche Warteschleife wirkt als notwendige Bremse.

Das optimiert der gcc weg weil sich die Bedingung im while Block nicht ändert. Die DMA struct oder den Member volatile deklarieren und es wird funktionieren. Sowas ist doch kein Fehler. Über den IAR und die Tools haben hier auch schon einige fürchterlich geschimpft, die kochen auch nur mit Wasser. Und auch Keil, kennt der mittlerweile aligned_alloc()? Ist ja erst neu im Standard seit C11. Bei gcc gibt es ein altes issue das die Optimierung für M0 schlecht ist, ich weiß nicht ob das schon besser geworden ist. Für M0 hatte der Keil mal deutlich kürzeren Code erzeugt, das lag aber auch an der schlechteren LTO, die bei gcc mittlerweile auch deutlich besser funktioniert.

Und wo braucht die newlib mehr Code für das strlen? Newlib ist reentrant, das ist mir mehr Wert als ein paar gesparte Byte. Für MCU mit weniger Speicher gibt es ja noch die Newlib nano die für strlen evtl. auch weniger braucht.

Persönliche Seite #7562337
Lesenswert?

Malte _. schrieb:

Johann L. schrieb:

__asm ("");

Danke für den Tipp. Das werde ich mir merken, auch wenn ich denke dass ich im jetzigen Fall mit -ffreestanding besser bedient bin.

Die dafür gedachte Option is -fno-tree-loop-distribute-patterns:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113049#c6

Was allerdings weder aus dem Namen der Option hervorgeht noch aus ihrer Dokumentation.

-ffreestanding will man eigentlich nicht verwenden, auch nicht auf bare-metal. Zum Beispiel wird dann strlen("Hallo") nicht mehr zu 5 optimiert, sondern die Länge des bekannten Strings wird zur Laufzeit berechnet.

-fno-builtin-strlen wiederum bewirkt, dass explizte Aufrufe von strlen immer zu einem Aufruf von strlen führen. Ist also auch nicht, was man braucht; erklärt aber, warum diese Option nicht wirkt — warum -fno-builtin wirkt ist dann wieder schleierhaft...

Aber zurück zum eigentlichen "Problem":

Malte _. schrieb:

Bevor jetzt jemand fragt warum ich die Standard strlen Funktion ersetze: Meine Implementierung ist zwar nicht unbedingt schneller, aber deutlich Programmspeicher sparender als die strlen der newlib (28byte vs 220byte).

Newlib implementiert eine Speed-optimierte Version von strlen. Zuerst wird bis zu einem bestimmten Alignment byteweise untersucht, danach geht's dann in Word-Happen weiter, zumindest für ARM v7:

https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/machine/arm/strlen-armv7.S;h=6aa122c075bbd395c1c1b53925b0a28c3be7294e;hb=HEAD

Für Thumb2 ist der Code dann kürzer:

https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/machine/arm/strlen-thumb2-Os.S;h=4adbc61d2e032ca7593db6b39e938202058583ba;hb=HEAD

Dein Problem ist also, dass -Os keine Multilib-Option ist, und man für kleinen Code die Newlib mit speziellen Optionen generieren muss wie -Os bzw. -D PREFER_SIZE_OVER_SPEED.

Wenn du die Newlib selbst generiert hast, dann liegt da der Hase im Pfeffer. Bzw. beim Distributor, der wenn es wirklich auf die paar Bytes ankommt (tut es das überhaupt?) die Newlib mit entsprechenden Optionen generiert haben sollte.

Dann gibt es diese Implementierung, die je nach Gegebenheit die o.g. includiert bzw. was eigenes macht:

https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/machine/arm/strlen-stub.c;h=fc2daf16fa30cb755079f2f00f18b1eb643fbcad;hb=HEAD

#7562391
Lesenswert?

J. S. schrieb:

Das optimiert der gcc weg weil sich die Bedingung im while Block nicht ändert. Die DMA struct oder den Member volatile deklarieren und es wird funktionieren. Sowas ist doch kein Fehler.

Nicht wegoptimiert, sondern sogar doppelt vorhanden, wobei jeder Aufruf noch einmal inline und etwas anders nachgebildet wird. Einmal wird auf Bit24 getestet und einandermal 7 x nach links geschoben und auf negativ getestet. Egal, ich bekomme das in den Griff.

#7562453
Lesenswert?

J. S. schrieb:

Das optimiert der gcc weg weil sich die Bedingung im while Block nicht ändert. Die DMA struct oder den Member volatile deklarieren und es wird funktionieren. Sowas ist doch kein Fehler.

Das DMA-Struct stammt aus den vom Hersteller für den Prozessor bereitgestellten Headern. Wenn das nicht volatile definiert sein sollte, wäre das ein signifikanter Fehler da drin. Das sollte aber nicht der Fall sein.

Oliver

OP Persönliche Seite #7569739
Lesenswert?

Johann L. schrieb:

Die dafür gedachte Option is -fno-tree-loop-distribute-patterns:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113049#c6

Was allerdings weder aus dem Namen der Option hervorgeht noch aus ihrer Dokumentation.

Vielen dank, das hätte ich wohl nicht gefunden. :) Da war ja ein Bug Reporter genau 1 Tag schneller als ich. Schön die Hintergründe zu lesen. Ich hab jetzt wie empfohlen -fno-tree-loop-distribute-patterns als Lösung genommen: https://github.com/Solartraveler/UniversalboxArm/commit/dba37e42acd4dd493cb93c3016aef0d5abeb90f1

Johann L. schrieb:

Für Thumb2 ist der Code dann kürzer:

https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/machine/arm/strlen-thumb2-Os.S;h=4adbc61d2e032ca7593db6b39e938202058583ba;hb=HEAD

Ok, dass es auch eine größenoptimierte Version gibt, wusste ich nicht. Wie oben schon geschrieben verlasse ich mich auf die vorkompilierte Version.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren