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_tstrlen(constchar*text){
2
size_tlen=0;
3
while(*text){
4
text++;
5
len++;
6
}
7
returnlen;
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).
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.
Schiebe die Funktion in ein eigenes C-File und kompilier es mit
-ffreestanding. Dann weis GCC, das er für die Datei keine libc
vorrausetzen darf und lässt diese Optimierung weg.
Ggf. funktioniert auch -fno-builtin-strlen in deinem Fall.
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.
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.
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:
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).
Motopick schrieb:> GCC scheint halt "Bastlersoftware" zu sein.
Das beschriebene Problem ist eins der Distribution, die den gcc mit
abstrusen Optionen gebaut haben.
Selber mit den passen Optionen compilieren, und alles wird gut. Wenn du
Linux nutzt, gehört das zum Spiel.
Oliver
> 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.
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:
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ß!
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 .-)
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.
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
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.
Harald K. schrieb:> Das sind nicht Programmierer, das ist das Management und das Marketing
Ja ich weiss, hab da natuerlich etwas vereinfacht. .-)
Vanye
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.
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
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.
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