Forum: Compiler & IDEs GCC-Option -mint8 vermeiden?


von J. W. (jw-lighting)


Lesenswert?

Hallo,

ohne wirklich viel Ahnung von Programmierung in Assembler und der 
Funktionsweise des GCC sowie seiner Hilfsprogramme zu haben, habe ich 
ein testweises Programm geschrieben und mir mal mit verschiedenen 
Arbeitsweisen im Programm sowie verschiedenen Optionen übersetzen 
lassen.

Allgemein ist ja bekannt, dass der GCC (Optimierungsstufe -Os) hieraus
1
uint8_t v;
2
3
// ...
4
5
if(v & 1){
6
 // tu was
7
}
8
9
// ...

gerne einen 16-bit Vergleich macht:
1
; ...
2
3
movw r20,r24
4
andi r20,lo8(1)
5
andi r21,hi8(1)
6
cp r20,__zero_reg__
7
cpc r21,__zero_reg__
8
breq .L2
9
10
; tu was
11
12
.L2:
13
14
; ...

Lässt man den GCC das hier übersetzen, sieht es schon besser aus:
1
uint8_t temp;
2
uint8_t v;
3
4
// ...
5
6
temp = 1;
7
if(v & temp){
8
 // tu was
9
}
10
11
// ...
1
mov r20, r24
2
andi r20,lo8(1)
3
tst r20
4
breq .L2
5
6
; tu was
7
8
.L2:
9
10
; ...

Das selbe Ergebnis bekomme ich mit der GCC-Option -mint8. Mit einem Cast 
auf der 1 ...
1
if(v & ((uint8_t) 1)){ //...
... ist lustigerweise alles mit 16bit verarbeitet!?

Das dies Verhalten nicht erwünscht ist steht außer Frage. Ich habe 
mehrfach gelesen, dass man -mint8 vermeiden soll, da es nicht ausgereift 
sei und einige Bibliotheken dann nicht mehr kompatibel sind.
Was kann man dann am besten dagegen tuen? Wie händelt ihr das Problem?
Ein ständiges kopieren in eine temporäre Variable finde ich nervig und 
ein (funktionierender) Cast ist auf Dauer auch nicht mehr 
schön/leserlich.

LG :)

von (prx) A. K. (prx)


Lesenswert?

J. W. schrieb:

> Was kann man dann am besten dagegen tuen? Wie händelt ihr das Problem?

Ignorieren. Wenn man sich über jeden Kleinkram an verpasster Optimierung 
aufregt kriegt man nur Magengeschwüre.

Nur wenns an bestimmten Stellen wirklich auf jeden einzelnen Takt 
ankommt mache ich mir über solche Kleinigkeiten wirklich Gedanken.

von J. W. (jw-lighting)


Lesenswert?

A. K. schrieb:
> Ignorieren. Wenn man sich über jeden Kleinkram an verpasster Optimierung
> aufregt kriegt man nur Magengeschwüre.

Ja. Ich kriege auch so schon "Magengeschwüre", nur weil ich mir den 
Umstand mal genauer angeschaut habe.
Ignorieren ist natürlich eine Möglichkeit. Aber in manchen Fällen kann 
man den Controller nicht weiter hoch skalieren um mehr Speicher zu haben 
und bekommt dann wegen sowas gewünschte Funktionen nicht mehr in den 
Flash. Und da fängt es an, mich aufzuregen.

Das man das Ignorieren kann (und das teilweise auch nicht interessiert) 
ist mir durchaus klar. Aber das hilft mir in diesem Fall leider gar 
nicht.
Daher wäre ich sehr dankbar, wenn ihr mir in euren Antworten noch andere 
Möglichkeiten darstellen könntet, die das Problem wirklich beheben. ;)

LG :)

von Yalu X. (yalu) (Moderator)


Lesenswert?

Welche GCC-version hast du?
1
#include <stdint.h>
2
3
void tuwas(void);
4
5
uint8_t v;
6
7
void test(void) {
8
  if(v & 1)
9
    tuwas();
10
}

ergibt bei mir mit den GCC-Versionen 4.2 bis 4.6 folgendes Ergebnis:
1
test:
2
  lds r24,v
3
  sbrc r24,0
4
  rcall tuwas
5
  ret

Besser geht's nicht.

Ab GCC 4.3 darf v sogar ein beliebiger Integer-Typ sein (bspw. auch
uint64_t), und der erzeugte Code ist immer noch der gleiche.

von (prx) A. K. (prx)


Lesenswert?

Habs grad mit gcc 4.5.3 ausprobiert und es kommt raus:
        sbrs r24,0
        rjmp .L2

von J. W. (jw-lighting)


Lesenswert?

Ich steh grad auch etwas dumm da. :p Ich habe gerade meine Testdatei 
etwas ausgeschlachtet (waren noch diverse andere Sachen/Vergleiche 
drin). Der Include von stdint.h fehlte, dafür war avr/io.h includiert.

avr-gcc Aufruf (Version 4.3.3):
> avr-gcc -save-temps -Os -mmcu=attiny2313 test.c

Leider ist das Phänomen oben seit dem Ausschlachten weg. Egal ob mit 
oder ohne stdint inkludiert.

Ich versuche mal das wieder herzustellen und geb euch dann mal das 
komplette Test- und Assemblerfile.

LG :)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:
> Habs grad mit gcc 4.5.3 ausprobiert und es kommt raus:
>         sbrs r24,0
>         rjmp .L2

Ja, weil du es für ein Device mit mehr als 8 KiB Flash übersetzt hast.

von Oliver (Gast)


Lesenswert?

J. W. schrieb:
> Egal ob mit
> oder ohne stdint inkludiert.

Das hat ja nun auch keinerlei Einfluß auf den erzeugten Code.

Oliver

von J. W. (jw-lighting)


Angehängte Dateien:

Lesenswert?

So,

das Phänomen hat noch ganz andere Ausmaße :p
Ansich geht es nicht darum, ob die logische Operation (AND + Vergleich) 
mit 16-bit ausgeführt wird, sondern dass v selbst als 16-bit behandelt 
wird.

Zumindest solange es selbst nicht verändert oder initialisiert wird.

Ich habe zwei Versionen der C- und AVRASM-Dateien angehängt. In der 
einen, die "vernünftigen Code" erzeugt, wird v am Ende der Hauptschleife 
inkrementiert (oder besser gesagt, es wir -1 abgezogen ;)).
In der anderen fehlt dieser Inkrement.

Ich habe v bewusst nicht initialisiert, sonst wird alles weg optimiert.

GCC Version 4.3.3, GCC Aufruf mit
> avr-gcc -mmcu=attiny2313 -save-temps -Os test_buggy(16).c

Das erklär mir jetzt mal einer :p


Yalu X. schrieb:
> ergibt bei mir mit den GCC-Versionen 4.2 bis 4.6 folgendes Ergebnis:
> test:
>   lds r24,v
>   sbrc r24,0
>   rcall tuwas
>   ret
>
> Besser geht's nicht.

Resultiert daraus, dass -mint8 überflüsig ist, da das Problem in neueren 
GCC-Versionen nicht mehr besteht?

LG :)

PS: Sehe grad, das in test_buggy.c das v++ auch drin steht. Bitte 
wegdenken ;)

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> Ja, weil du es für ein Device mit mehr als 8 KiB Flash übersetzt hast.

Ich habs für garnix übersetzt, keine Ahnung was der Compiler dann nimmt.
Sieht aber bei -mmcu=attiny2313 auch nicht anders aus.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Also was ist jetzt falsch? Du verwendest nicht-initialisierte Variablen.

garbage in -- garbage out

Ansonsten sieht der Code korrekt aus und entspricht dem, wenn das v++ 
entfernt wird.

von (prx) A. K. (prx)


Lesenswert?

J. W. schrieb:

> Ich habe zwei Versionen der C- und AVRASM-Dateien angehängt.

Die C Versionen sehen für mich ziemlich identisch aus.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> Ja, weil du es für ein Device mit mehr als 8 KiB Flash übersetzt hast.

NB: Da du das sicherlich nicht grundlos schreibst: Wie entsteht so ein 
Zusammenhang?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Er meint solchen Code:
1
typedef unsigned char uint8_t;
2
3
#define PINB (*(volatile uint8_t*) 0x36)
4
5
void foo (uint8_t v)
6
{
7
    while (1)
8
    {
9
        if (v & (1<<0))
10
        {
11
            PINB = 8;
12
            PINB = 4;
13
        }
14
15
        if(v & (1<<2))
16
        {
17
            PINB = 10;
18
            PINB = 5;
19
        }
20
    }
21
}
v ist schleifeninvariant und der Compiler zieht die &-Operationen aus 
der Schleife.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> v ist schleifeninvariant und der Compiler zieht die &-Operationen aus
> der Schleife.

Ok, allerdings kriege ich keinen Unterschied bei der 8K Grenze. 
Ausserdem zieht er nur eine der Operationen raus.

von J. W. (jw-lighting)


Lesenswert?

Johann L. schrieb:
> Also was ist jetzt falsch? Du verwendest nicht-initialisierte Variablen.

Seit wann soll das verboten sein? Tatsache ist doch, dass v eine 8-bit 
Variable ist. Wie kommt der GCC dann drauf (initialisierung hin oder 
her), da irgendwas mit 16-bit rechnen zu wollen?

A. K. schrieb:
> Die C Versionen sehen für mich ziemlich identisch aus.
Dann hättest du genauer lesen sollen. Tut mir Leid, dass ich da im 
Dateistrudel nen Fehler rein bekommen habe:

J. W. schrieb:
> PS: Sehe grad, das in test_buggy.c das v++ auch drin steht. Bitte
> wegdenken ;)

LG :)

von (prx) A. K. (prx)


Lesenswert?

J. W. schrieb:

> Seit wann soll das verboten sein?

Das Ergebnis von solchem Code ist undefiniert.

> Wie kommt der GCC dann drauf (initialisierung hin oder
> her), da irgendwas mit 16-bit rechnen zu wollen?

GCC ist nicht für 8-Bit Architekturen optimiert. Die Compilerbastler wie 
Johann tun einiges, um die dadurch entstehenden 16-Bit Artifakte 
wegzuoptimieren. Aber es wird immer gewisse Reste geben, in denen man 
erkennt, dass die Grundeinheit von GCC ein 16-Bit Register ist (sizeof 
int).

von J. W. (jw-lighting)


Lesenswert?

Das ist eine hinnehmbare Erklärung. Trotzdem irritiert mich, dass das 
bei einer 8-bit Variablen plötzlich 16-bit Berechnung auf einem 8-bit 
Controller erzwungen wird.

A. K. schrieb:
> Das Ergebnis von solchem Code ist undefiniert.
OK, das ist mir neu. Liegt das jetzt nur daran, dass er nicht 
initialisiert ist oder ist hinreichend Bedingung dafür auch, dass der 
Zustand von v nirgendwo gesetzt wird.

Kurz: Führt das hier auch zu einem undefinierten Ergebnis:
1
uint8_t v;
2
3
// ...
4
5
v = PINB;

LG :)

von (prx) A. K. (prx)


Lesenswert?

Der Compiler zieht hier ein Zwischenergebnis aus der Schleife raus und 
da alle Rechenoperationen mit mindestens sizeof(int) gerechnet werden 
ist diese Variable nun 16 Bits breit. Der Rest folgt daraus.

Diese Optimierung ist global, für alle Architekturen, und kümmert sich 
nicht um die Frage, ob eine Teilwortoperation möglicherweise billiger 
ist als eine Maschinenwortoperation. Für GCC hat das Maschinenwort auch 
bei AVRs 16 Bits. Viele Stellen, wo der erzeugte Code dennoch rein 
8-bittig rechnet, sind spezielle Optimierungen für AVRs. Das hat aber 
Grenzen.

von (prx) A. K. (prx)


Lesenswert?

J. W. schrieb:

> Kurz: Führt das hier auch zu einem undefinierten Ergebnis:

Nein. Schreibender Zugriff auf eine nicht intialisierte Variable ist 
kein Problem, sehr wohl aber lesender Zugriff.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:
> Johann L. schrieb:
>
>> Ja, weil du es für ein Device mit mehr als 8 KiB Flash übersetzt hast.
>
> Ich habs für garnix übersetzt, keine Ahnung was der Compiler dann nimmt.

Er nimmt dann avr2

Für alle Versionen, die ich angetestet habe, compiliert
1
unsigned char v;
2
3
void test (void)
4
{
5
    if (v & 1)
6
        tuwas();
7
}
mit
>> avr-gcc foo.c -Os -S
zu
1
test:
2
  lds r24,v
3
  sbrc r24,0
4
  rcall tuwas
5
.L1:
6
  ret

insbesondere auch 4.5.0 und 4.5.2, wobei in der 4.5.2 offenbar 
WinAVR-Patches drinne sind (__do_clear_bss steht am Dateiende wegen 
PR18145) und in der 4.5.0 nicht.

Einige Devices haben einen Silicon-Bug, so daß kein Skip über eine 
32-Bit Instruktione gemacht werden darf.

Ausserdem nimmt avr-gcc < 4.7 als Kriterium für einen Skip die 
Insn-Länge, die in 16-Bit Words angegeben ist. Ein Skip wird nur über 
Length=1 gemacht, denn nur anhand von Length=2 kann nicht entschieden 
werden, ob es sich um eine 32-Bit Instruktion handelt.

Diese Optimierung hat erst avr-gcc 4.7 (PR49939)

Beispiel:
1
char c;
2
3
void foo (char a, char b)
4
{
5
    if (a)
6
        c = b;
7
}

compiliert mit avr-gcc -S -Os -mmcu=at90s8515  zu
1
foo:
2
  tst r24
3
  breq .L1
4
  sts c,r22
5
.L1:
6
  ret

und mit avr-gcc -S -Os -mmcu=at90s8535  zu
1
foo:
2
  cpse r24,__zero_reg__
3
  sts c,r22
4
.L1:
5
  ret

von (prx) A. K. (prx)


Lesenswert?

Yep, das ergibt Sinn. Ich hatte das erst so verstanden, dass die 
Optimierung des schleifeninvarianten Ausdrucks von der Flashgrösse 
abhinge. Und das erschien mir etwas schräg.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:
> Yep, das ergibt Sinn. Ich hatte das erst so verstanden, dass die
> Optimierung des schleifeninvarianten Ausdrucks von der Flashgrösse
> abhinge. Und das erschien mir etwas schräg.

Ist aber so: Ein CALL gibt's eben nicht bei kleinem Flash, da kann immer 
ein RCLL stehen. Ausserdem ist nicht zu sehen, was hinter deiner 
Sprung-Sequenz noch kommt.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> Ist aber so: Ein CALL gibt's eben nicht bei kleinem Flash, da kann immer
> ein RCLL stehen. Ausserdem ist nicht zu sehen, was hinter deiner
> Sprung-Sequenz noch kommt.

Jo, als Folge der Längenabschätzung geht das noch an. Allerdings kam bei 
meinen Versuchen kein Unterschied in der Optimierung des Ausdrucks raus, 
sondern bloss in der Frage, obs einen Skip oder einen Sprung drüber 
gibt. Andererseits habe ich keinen kompletten Zoo aus GCCs parat, 
sondern in solchen Fällen bloss ein Debian-Package, was zudem 
tendentiell eher keine lebensnotwendigen Patches enthält.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Die LIM (Loop Invariant Motion) erweist sich für AVR eher als 
unvorteilhaft; in vielen meiner Projekte gehört daher 
-fno-move-loop-invariants zu den Optionen wie übrigens 
-fno-tree-loop-optimize auch.

Siehe
http://sourceforge.net/apps/mediawiki/mobilechessboar/index.php?title=Avr-gcc

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> Die LIM (Loop Invariant Motion) erweist sich für AVR eher als
> unvorteilhaft;

Ja, daran hatte ich in diesem Zusammenhang auch schon gedacht. Nicht die 
erste und sicher nicht die letzte Optimierung, die bei AVRs tendentiell 
in die Hose geht.

von Rolf Magnus (Gast)


Lesenswert?

J. W. schrieb:
> Das ist eine hinnehmbare Erklärung. Trotzdem irritiert mich, dass das
> bei einer 8-bit Variablen plötzlich 16-bit Berechnung auf einem 8-bit
> Controller erzwungen wird.

ISO C sieht das so vor. Dort wird vorgegeben, daß bei Berechnungen alle 
Operanden, die kleiner als int sind, erstmal auf int hochkonvertiert 
werden ("integer promotion"), bevor die Berechnung durchgeführt wird. 
Damit wird auch dein & per Default erstmal mit 16 Bit berechnet. Der 
Compiler darf das aber entsprechend optimieren, wenn er garantieren 
kann, daß das Ergebnis dadurch nicht verändert wird.
Beim gcc ist jetz das Problem, daß er nie dafür ausgelegt war, irgendwas 
mit weniger als int zu berechnen, weil das bei allen "normalen" 
Zielplattformen keinen Vorteil bringt. Daher fehlen einfach die 
entsprechenden Optimierungen. Und da AVR eher ein Nischen-Target ist, 
geht die Entwicklung da nicht so flott voran.

von Oliver (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Damit wird auch dein & per Default erstmal mit 16 Bit berechnet.

Sicher? Wenn ich das hier richtig verstehe (was nicht der Fall sein 
muß...), dann werden die Operanden beim Bit-Operatore & nicht vorher 
"promoted".

Oliver

von (prx) A. K. (prx)


Lesenswert?

Ganz sicher. Der Compiler muss sich so verhalten, als ob das so sei. Was 
er real macht ist dann seine Sache, es muss aber das Gleiche rauskommen.

Führt beispielsweise dazu, dass
  uint8_t i;
  if (i+1 < 100) ...
meist nicht in 8-Bit Rechnung durchgeführt werden kann. Geht sonst 
schief bei i=255.

Bei Bitoperationen & | ^ ist das zwar i.d.R. harmlos, aber GCC ist meist 
nicht darauf eingerichtet, Teilwortoperationen als möglicherweise 
effizienter als Wortoperationen zu betrachten. Bei den fast allen 
Targets ist es nämlich andersrum, oder zumindest egal.

von Rolf M. (rmagnus)


Lesenswert?

Oliver schrieb:
> Rolf Magnus schrieb:
>> Damit wird auch dein & per Default erstmal mit 16 Bit berechnet.
>
> Sicher?

Ja. Hab's nochmal nachgeschlagen. Beim bitweisen & (Kapitel 6.5.10) 
steht dort unter Semantics: "The usual arithmetic conversions are 
performed on the operands.", und zu diesen gehört nach Kapitel 6.3.1.8 
auch die integer promotion.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Hier mal anhand eines kleinen Beispiels:
1
char c;
2
3
void foo (char a)
4
{
5
    if (a & 4)
6
        c = 0;
7
}

Wie Rolf bereis mehrfach angemerkt hat, gelten die C-Regeln. Es ist ja 
auch ein C-Programm bzw. -Modul. Ubersetzt man es z.B. mit avr-gcc 4.3.3 
(zB WinAVR-20100110) mit

>> avr-gcc foo.c -Os -S -dP -fdump-tree-original -fdump-tree-optimized

Dann sieht das original-dump so aus:
1
;; Function foo
2
{
3
  if (((int) a & 4) != 0)
4
    {
5
      c = 0;
6
    }
7
}

und das optimized hat immer noch das "(int) a":
1
foo (a)
2
{
3
<bb 2>:
4
  if (((int) a & 4) != 0)
5
    goto <bb 3>;
6
  else
7
    goto <bb 4>;
8
9
<bb 3>:
10
  c = 0;
11
12
<bb 4>:
13
  return;
14
}

Schaut man dann ins erzeugt Assembler ist da zu finden
1
 ;(set (pc)
2
 ;     (if_then_else (eq (zero_extract:HI (reg:QI 24)
3
 ;                                        (const_int 1)
4
 ;                                        (const_int 2))
5
 ;                       (const_int 0))
6
 ;                   (label_ref:HI 17)
7
 ;                   (pc)))
8
  sbrs r24,2  ;  9 *sbrx_branch [length = 2]
9
  rjmp .L3

Im Kommentar steht die algebraische Darstellung der Instruktionssequenz 
darunter, welcher bedeutet:

>> Extrahiere ein Bitfeld der Länge 1 beginnend ab Bit 2 aus dem
>> 8-Bit Register R24, und mache einen zero-extend zu einem 16-Bit
>> Wert. Vergleiche diesen Wert mit 0, und falls der Vergleich "wahr"
>> ist, springe zu Label #17; ansonsten mache keinen Sprung.

Die reale Maschine (das Programm auf dem AVR) macht nun genau das, was 
die abstrakte Maschine (die durch die C-Spezifikation beschriebene) 
vorschreibt.

In 4.7 sieht die Sequenz übrigens anders aus:
1
 ; (set (pc)
2
 ;      (if_then_else (eq (zero_extract:QI (reg:QI 24)
3
 ;                                         (const_int 1)
4
 ;                                         (const_int 2))
5
 ;                        (const_int 0))
6
 ;                    (label_ref:HI 13)
7
 ;                    (pc)))
8
  sbrc r24,2  ;  8 *sbrx_branchqi [length = 2]

Hier steht sinngemäß das gleiche, ausser

>> ... und mache einen zero-extend zu einem 8-Bit Wert ...

Es wurde also schon vor der Ausgabe erkannt, daß das High-Byte nicht für 
diese Operation benötigt wird. Der Effekt ist aber immer noch der 
gleiche: Das reale Programm verhält sich so, wie es die abstrakte 
Maschine beschreibt.

Daß die Insn als SBRC und nicht als SBRS+RJMP ausgegeben wird ist 
übrigens eine andere Optimierung, die mit Promotion nix zu tun hat.

von (prx) A. K. (prx)


Lesenswert?

Und in der kaputtoptimierten Version darf man sich das dann wohl so 
vorstellen: Aus
1
void foo (uint8_t v)
2
{
3
    while (1)
4
    {
5
        if (v & (1<<0))
6
        {
7
            PINB = 8;
8
            PINB = 4;
9
        }
10
        ...
11
    }
12
}
wird ungefähr sowas wie
1
void foo (uint8_t v)
2
{
3
    int flag = v & (1<<0);
4
    while (1)
5
    {
6
        if (flag != 0)
7
        {
8
            PINB = 8;
9
            PINB = 4;
10
        }
11
        ...
12
    }
13
}

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.