Forum: Compiler & IDEs GCC Optimierer zerlegt Block (compound-statement)


von ReinerS (Gast)


Lesenswert?

Hallo,

laut K&R fasst ein Block mehrere Anweisungen zusammen und kann als eine 
einzelne Anweisung betrachtet werden. Dieses compound-statement kann 
auch lokale Variablen enthalten die außerhalb des Blocks nicht sichtbar 
sind. Soweit so gut. Leider wird beim optimieren die Blockstruktur 
zerstört. Hier ein Beispiel:
1
void test( unsigned int val )
2
{
3
    val = 1535 / val;
4
5
  {//start Block 
6
    cli();
7
    ivar = val;
8
    sei();
9
  }//ende Block
10
}

Und der Compiler macht folgendes daraus:

void test( unsigned int val )
{
  7c:  bc 01         movw  r22, r24
    val = 1535 / val;
  {  //start Block
    cli();
  7e:  f8 94         cli



void test( unsigned int val )
{
    val = 1535 / val;
  80:  8f ef         ldi  r24, 0xFF  ; 255
  82:  95 e0         ldi  r25, 0x05  ; 5
  84:  0e 94 4f 00   call  0x9e  ; 0x9e <__udivmodhi4>
  {  //start Block
    cli();
    ivar = val;
  88:  70 93 61 00   sts  0x0061, r23
  8c:  60 93 60 00   sts  0x0060, r22
    sei();
  90:  78 94         sei
  }  // ende Block
}
  92:  08 95         ret

Der Compiler GCC 4.5.1 verschiebt also die Anweisung val = 1535 / val; 
in den Block.
Das ist aber das was ich ich nicht will.

Das Problem tritt bei allen Optimierungen auf. Ich denke da geht die 
Registeroptimierung zu weit.

Gibt es eine GCC Version die den Fehler nicht hat?

Viele Grüße
Reiner

von 900ss (900ss)


Lesenswert?

Warum erinnert mich dein Codebeispiel exakt an diese Diskussion?
Beitrag "Schwerer Bug in AVR-GCC 4.1.1"

Zufall?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Die Klammern kannst du übrigens getrost weglassen, die ändern
semantisch rein gar nichts.

Und: es ist kein Compilerfehler, auch wenn du es nicht schön
findest.  Das hängt damit zusammen, dass sich das Verhalten der
formalen Maschine nicht ändert durch die Optimierung (was dem
Optimizer Recht gibt), und dass der C-Standard sowas wie "setze
eine Anweisung genau hier um" nicht kennt, ob du das nun magst
oder nicht.

Im von 900ss zitierten Thread waren wir zu dem Schluss gekommen,
dass man im Standard sowas wie "volatile für statements" benötigen
würde, gibt's aber halt nicht.

Du kannst nur mal versuchen, mit sowas wie inline-Funktionen
rumzuspielen, oder aber mit den Sachen aus <util/atomic.h>.  Zu
guter Letzt kannst du natürlich eine Reihenfolge erzwingen, indem
du "val" als volatile deklarierst.  Hat allerdings als Pessimierung
zur Folge, dass der Wert dann auch in den Speicher geschrieben und
wieder von dort gelesen wird, obwohl natürlich in deinem Sinne das
Halten des Zwischenergebnisses in Registern komplett ausreichend
wäre.

von ReinerS (Gast)


Lesenswert?

Weil cli() und sei() nicht weg optimiert werden.

von Yalu X. (yalu) (Moderator)


Lesenswert?

900ss D. schrieb:
> Warum erinnert mich dein Codebeispiel exakt an diese Diskussion?
> Beitrag "Schwerer Bug in AVR-GCC 4.1.1"
>
> Zufall?

Das Thema mit diesem oder ähnlichem Beispielcode ist schon an vielen
Stellen im Internet diskutiert worden:

  http://www.nongnu.org/avr-libc/user-manual/optimization.html
  http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=97929&start=0
  http://lists.nongnu.org/archive/html/avr-libc-dev/2010-06/msg00130.html

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

@ReinerS: Wie sind sei() und cli() denn definiert, d.h. was sieht der 
Compiler?

 sei() und cli() sind ja keine Intrinsics.

von ReinerS (Gast)


Lesenswert?

Habe ich auch mit util/atomic.h probiert mit dem gleichen Ergebnis.
void test( unsigned int val )
{
  7c:  bc 01         movw  r22, r24
    return 1;
}

static _inline_ uint8_t __iCliRetVal(void)
{
    cli();
  7e:  f8 94         cli
    val = 1535 / val;
  80:  8f ef         ldi  r24, 0xFF  ; 255
  82:  95 e0         ldi  r25, 0x05  ; 5
  84:  0e 94 4f 00   call  0x9e  ; 0x9e <__udivmodhi4>
  ATOMIC_BLOCK(ATOMIC_FORCEON) {
    ivar = val;
  88:  70 93 61 00   sts  0x0061, r23
  8c:  60 93 60 00   sts  0x0060, r22
    return 1;
}

static _inline_ void __iSeiParam(const uint8_t *__s)
{
    sei();
  90:  78 94         sei
  }
}
  92:  08 95         ret

Die Division wird auch in den Block verschoben.
Das der GCC die Registeroptimierung auf die Spitze treibt ist ja bei 
"normalen" CPUs in Ordnung aber bei MCUs, speziell 8 Bit, kommt es zu 
Problemen.

von ReinerS (Gast)


Lesenswert?

#Johann
Ich denke es ist nicht richtig nur vom Compiler zu reden, denn der 
Compiler besteht aus mehreren Modulen. Wenn ich mit -O0 compiliere 
bleibt mein Block erhalten. Das ganze passiert also beim optimieren oder 
bei der Codegenerierung. Ich finde es blöd das der GCC mein 
compound-statement einfach ignoriert und damit das Programm so verändert 
das auf einmal ein Funktionsaufruf in meinem Block ist, den ich auf 
keinen Fall dort haben will.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Und wie sehen sei() und cli() nun vor dem Compileren aus? Oben ist ja 
nur ein nichtssagendes Disassembly.

Eine Definition sieht man z.B. mit -P -g3 -save-temps im i-File.

ReinerS schrieb:

> Ich denke es ist nicht richtig nur vom Compiler zu reden,

Doch.

von (prx) A. K. (prx)


Lesenswert?

ReinerS schrieb:

> Ich finde es blöd das der GCC mein
> compound-statement einfach ignoriert und damit das Programm so verändert
> das auf einmal ein Funktionsaufruf in meinem Block ist, den ich auf
> keinen Fall dort haben will.

Niemand bestreitet, dass du es blöd findest. Nur ist GCC nicht für 
Mikrocontroller entworfen worden und diese Optimierungen sind 
allgemeiner natur, damit erwischt es jede Maschine.

Wobei schon allerlei Optimierungen im AVR Backend abgeschaltet werden, 
weil sie kontraproduktiv sind. Tendenz zunehmend. Bei dieser hier ist 
das freilich weniger simpel, denn die dürfte nur in einem solchen 
Kontext nachteilig und sonst eher vorteilhaft sein.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

ReinerS schrieb:
> Ich finde es blöd das der GCC mein
> compound-statement einfach ignoriert

Ein compound statement hat nichts, absolut gar nichts, mit der
Abarbeitungsreihenfolge zu tun, und es hat dir nie jemand (zumindest
nicht offiziell) versprochen, dass es das hätte.  Ein compound
statement ist ein rein syntaktischer Konstrukt, den man einerseits
benutzt, um den Scope von Variablen einzuschränken, und andererseits,
um an einer Stelle, an der nur eine einzige Anweisung statthaft ist
(bspw. nach einem "if" oder "else"), meherere Anweisungen unterbringen
zu können.  Nachdem die Syntax geparst worden ist, hat der Compiler
vergessen, dass da mal Klammern drum waren.

Wie ich schon schrieb, du kannst diese Klammern weglassen, ohne dass
es irgendwann irgendwie jemals was an dem generierten Code ändern
würde.

Dass es ohne Optimierung so läuft, wie du es dir vorstellst, ist
natürlich normal, denn dann wird der Code ganz formal in die
hypothetische Maschine umgesetzt, ohne auch nur das geringste
bisschen dran zu ändern.

Was dir da passiert, ist das Entfernen einer (aus Sicht des Compilers)
nutzlosen temporären Variablen, und das ist völlig selbstverständlich
eine Optimierung, die man an anderen Stellen (auch in einer Controller-
Umgebung!) durchaus haben möchte.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Setze -fno-tree-ter und der Code ist wie soll.

von ReinerS (Gast)


Lesenswert?

@Jörg Wunsch

Das Problem ist das wir bei der Programmierung von MCUs öfter mal die 
Takte zählen und Interrupts fein steuern. Die Optimierung des GCC für 
einen PC, Mainframe oder Supercomputer ist sehr gut, aber bei 
zeitkritischen Programmen auf einer MCU ist die Optimierung mach mal 
hinderlich.

Außerdem ist die 8 Bit AVR Architektur ein Problem, da sie kein atomaren 
Zugriff auf 16 Bit Register/Speicher zulässt.

Es wäre gut wenn beim optimieren bei allein stehende Blocks, also keine 
Blocks die in if, while, do-while oder for stehen, keine Anweisungen 
eingefügt oder raus gezogen werden. Dann sind folgende Anweisungen
{
  cli();
  mem16 = tmp;
  sei();
}
atomar ohne Tricks und sehr gut zu verstehen und rüttelt nicht an der 
Sprache C.

Mir ist klar der GCC kein 8 Bit Compiler ist und für einen ganz anderen 
Einsatz entwickelt wurde. Es ist schon erstaunlich wie leistungsfähig 
der GCC im 8 Bit Modus ist. Wie gesagt, es wäre schön wenn ein Block so 
compiliert wird.

Gruß Reiner

von ReinerS (Gast)


Lesenswert?

@Johann

Vielen Dank!!
Jetzt macht er was ich mir denke.
Werde mal nach lesen was die Option -fno-tree-ter im einzeln macht und 
mir mal die anderen Optionen genauer ansehen, da scheinen ja noch 
Schätze zu schlummern.

Gruß Reiner

von ReinerS (Gast)


Lesenswert?

@Johann

Ich kann die Beschreibung der Option -fno-tree-ter nicht finden.
Habe das Manual von GCC 4.5.3 und 4.7.0 durchsucht.
Wo kann man die Doku finden?

von (prx) A. K. (prx)


Lesenswert?

Siehe -ftree-ter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

ReinerS schrieb:

> Werde mal nach lesen was die Option -fno-tree-ter im einzeln macht

Es ist eine Optimierung, "Temporary Expression Replacement".

Mir ist es auch nur durch Debuggen des Compilers aufgefallen, und ich 
hab einen Bug-Report dafür gemacht:

http://gcc.gnu.org/PR53049

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

ReinerS schrieb:
> Es wäre gut wenn beim optimieren bei allein stehende Blocks

Vergiss doch endlich deine Blöcke!  Die gibt's (nach der syntaktischen
Analyse) für den Compiler einfach nicht mehr, folglich kann man die
Optimierung auch nicht so abändern, dass er die irgendwie nicht
antastet.

Du kannst die überflüssigen Klammern ruhig weglassen.

Mal sehen, ob Johanns PR was bringt, aber wie ich Johann kenne, wird
er sich selbst irgendwann wohl dahinterklemmen und sehen, ob man
das korrigieren kann. ;-)

(Ich glaube, wir AVR-GCC-Nutzer sollten allmählich mal sammeln und
Johann als Dankeschön für seine viele Arbeit 'n Kasten Bier spendieren
oder was auch immer für ihn als Dankeschön taugt. ;)

von (prx) A. K. (prx)


Lesenswert?

Ich denke das ist einmal mehr die Suche nach Barriers. Nach einer 
explizit codierbaren Barriere, die solche Verschiebebahnhöfe blockiert 
und zu cli/sei hinzugefügt werden kann.

Die Blöcke, die Reiner dafür hielt, helfen da freilich nicht.

Ist da im Linux Kernel oder den Device Drivern noch niemand drüber 
gestolpert?

von 900ss (900ss)


Lesenswert?

Jörg Wunsch schrieb:
> (Ich glaube, wir AVR-GCC-Nutzer sollten allmählich mal sammeln und
> Johann als Dankeschön für seine viele Arbeit 'n Kasten Bier spendieren
> oder was auch immer für ihn als Dankeschön taugt. ;)

Ich bin dabei. :-)
Vielleicht 'n neues besonders feinmaschiges Netz zum Käfer fangen ;-)

von (prx) A. K. (prx)


Lesenswert?

Für den hier präsentierten Fall gibts eine Lösung, mit der die 
Berechnung vor cli gezogen wird. Allerdings ist das eine 
Einzelfall-Lösung, d.h. man muss erst einmal merken, oder ahnen, dass 
man ein Problem kriegen könnte.
1
#define barrier(x) asm volatile ("" : : "r"(x))
2
3
void test( unsigned int val )
4
{
5
    val = 1535 / val;
6
    barrier(val);
7
    cli();
8
    ivar = val;
9
    sei();
10
}

Aber das wird jetzt wirklich zum Wiedergänger. Der gleiche Kram wie 
2007, identisch aufgewärmt.

von 900ss (900ss)


Lesenswert?

A. K. schrieb:
> Für den hier präsentierten Fall gibts eine Lösung, mit der die
> Berechnung vor cli gezogen wird. Allerdings ist das eine
> Einzelfall-Lösung, d.h. man muss erst einmal merken, oder ahnen, dass
> man ein Problem kriegen könnte.
>

Stimmt schon, man muss es vorher wissen. Allerdings sollte es nicht 
soviel Arbeit bereiten, alle Stellen die mit cli/sei gekapselt sind, im 
generierten Assembercode zu untersuchen.
Wenn man ganz sicher gehen will ein "muss".

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Ich denke das ist einmal mehr die Suche nach Barriers. Nach einer
> explizit codierbaren Barriere, die solche Verschiebebahnhöfe blockiert
> und zu cli/sei hinzugefügt werden kann.

Naja, die erste Antwort von Andrew Pinski (im PR) sieht nicht gerade
vielversprechend aus. :(

> Ist da im Linux Kernel oder den Device Drivern noch niemand drüber
> gestolpert?

Bei aktuellen CISC-CPUs wird man solche Probleme weniger haben, da
die viele Operationen natürlich in relativ kurzer Zeit in Hardware
ausführen.  Da wäre die Verschiebung der Division in die Interrupt-
sperre hinein kaum ein Beinbruch.

Was vielleicht Reiner noch helfen könnte:
1
extern unsigned ivar;
2
3
#pragma GCC push_options
4
#pragma GCC optimize("no-tree-ter")
5
6
void test2 (unsigned val)
7
{
8
  val = 65535U / val;
9
10
  __builtin_avr_cli();
11
  ivar = val;
12
  __builtin_avr_sei();
13
}
14
15
#pragma GCC pop_options
16
17
void test3 (unsigned val)
18
{
19
  val = 65535U / val;
20
21
  __builtin_avr_cli();
22
  ivar = val;
23
  __builtin_avr_sei();
24
}

Zwar bekomme ich beim "pop_options"-Pragma noch eine dubiose Warnung:
1
foo.c:15:9: warning: #pragma GCC target is not supported for this machine

Aber der generierte Code scheint mir OK:
1
        .file   "foo.c"
2
__SREG__ = 0x3f
3
__SP_H__ = 0x3e
4
__SP_L__ = 0x3d
5
__CCP__ = 0x34
6
__tmp_reg__ = 0
7
__zero_reg__ = 1
8
        .text
9
.global test2
10
        .type   test2, @function
11
test2:
12
/* prologue: function */
13
/* frame size = 0 */
14
/* stack size = 0 */
15
.L__stack_usage = 0
16
        movw r22,r24
17
        ldi r24,lo8(-1)
18
        ldi r25,hi8(-1)
19
        call __udivmodhi4
20
        cli
21
        sts ivar,r22
22
        sts ivar+1,r23
23
        sei
24
/* epilogue start */
25
        ret
26
        .size   test2, .-test2
27
.global test3
28
        .type   test3, @function
29
test3:
30
/* prologue: function */
31
/* frame size = 0 */
32
/* stack size = 0 */
33
.L__stack_usage = 0
34
        movw r22,r24
35
        cli
36
        ldi r24,lo8(-1)
37
        ldi r25,hi8(-1)
38
        call __udivmodhi4
39
        sts ivar+1,r23
40
        sts ivar,r22
41
        sei
42
/* epilogue start */
43
        ret
44
        .size   test3, .-test3

Auf diese Weise kann man also das temporary expression reordering
nur für diese eine Funktion ausschalten.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:

> #define barrier(x) asm volatile ("" : : "r"(x))

Vertippt ;-) Es muss heissen:

#define barrier(x) asm volatile ("" : "+r"(x))

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> Vertippt ;-) Es muss heissen:

Meine Variante funktionierte auch. Aber deine ist sicherer. Kommt er 
nicht auf dumme Gedanken.

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:

> Bei aktuellen CISC-CPUs wird man solche Probleme weniger haben, da
> die viele Operationen natürlich in relativ kurzer Zeit in Hardware
> ausführen.

Divisionen sind auch heute noch ausgesprochen kostspielig. Auf wohl 
jeder general purpose CPU.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Könnte doch ebenso nen libcall für gamma, j oder bessel sein, findet 
sich nur wohl kaum in Linux-Kernel :-/

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:

> Naja, die erste Antwort von Andrew Pinski (im PR) sieht nicht gerade
> vielversprechend aus. :(

Einfach mehr Futter liefern!!!

Das Ding lässt sich bestimmt auch auf ARM oder sonstige Maschinen 
portieren und dort explizit machen. Mit einklinken in den PR!


> warning: #pragma GCC target is not supported for this machine

[google Übersetzer]:
Warnung: #pragma GCC Ziel wird nicht für diese Maschine unterstützt

[Babel-Fisch]:
Warnung: #pragma GCC-Ziel wird nicht für diese Maschine gestützt

GCC push_options/push_options brauch backen-Unterstützung, da sich der 
Maschinenstatus ändern kann, zum Beispiel wenn man 
Scheduler-Einstellungen ändert.

von D++ (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Zwar bekomme ich beim "pop_options"-Pragma noch eine dubiose Warnung:
> foo.c:15:9: warning: #pragma GCC target is not supported for this machine

Sei doch froh, früher hat der gcc stumpf nethack gestartet wenn er ein 
pragma gefunden hat. http://www.feross.org/gcc-ownage/

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> GCC push_options/push_options brauch backen-Unterstützung, da sich der
> Maschinenstatus ändern kann, zum Beispiel wenn man
> Scheduler-Einstellungen ändert.

OK, das erklärt es natürlich.  Dennoch ist die Warnung irreführend:
man schreibt ein "GCC optimize"-Pragma und bekommt eine Warnung,
dass "GCC target"-Pragmas nicht implementiert sind ... (die gibt
es ja ansonsten auch noch).

> Das Ding lässt sich bestimmt auch auf ARM oder sonstige Maschinen
> portieren und dort explizit machen. Mit einklinken in den PR!

ARM wäre natürlich mal eine interessante Idee.  Dann wären wir wohl
bei einem "tier 1 target", denke ich.

von Peter D. (peda)


Lesenswert?

Hier noch ne Lösung:
1
#include <util/atomic.h>
2
3
// force access interrupt variables
4
#define IVAR(x)         (*(volatile typeof(x)*)&(x))
5
6
7
unsigned int ivar;
8
9
10
void test( unsigned int val )
11
{
12
  val = 1535 / val;
13
14
  ATOMIC_BLOCK(ATOMIC_FORCEON){
15
    IVAR(ivar) = val;
16
  }
17
}


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:

> Hier noch ne Lösung:

Nein, das ist auch keine Lösung. Es ändert nichts am Verhältnis zwischen 
der Division und dem CLI.

Ausserdem ist's Hack.

von Peter D. (peda)


Lesenswert?

Johann L. schrieb:
> Nein, das ist auch keine Lösung.

Also mit WINAVR (GCC 4.3.3) funktionierts:
1
void test( unsigned int val )
2
{
3
  98:   bc 01           movw    r22, r24
4
  val = 1535 / val;
5
  9a:   8f ef           ldi     r24, 0xFF       ; 255
6
  9c:   95 e0           ldi     r25, 0x05       ; 5
7
  9e:   0e 94 5b 00     call    0xb6    ; 0xb6 <__udivmodhi4>
8
    return 1;
9
}
10
11
static __inline__ uint8_t __iCliRetVal(void)
12
{
13
    cli();
14
  a2:   f8 94           cli
15
16
  ATOMIC_BLOCK(ATOMIC_FORCEON){
17
    IVAR(ivar) = val;
18
  a4:   70 93 01 01     sts     0x0101, r23
19
  a8:   60 93 00 01     sts     0x0100, r22
20
    return 1;
21
}
22
23
static __inline__ void __iSeiParam(const uint8_t *__s)
24
{
25
    sei();
26
  ac:   78 94           sei
27
  }
28
}
29
  ae:   08 95           ret


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:

> Also mit xxx funktionierts:

Das ist bekanntlich notwendig für eine korrekte Lösung, aber nicht 
hinreichend.

von Peter D. (peda)


Lesenswert?

Johann L. schrieb:
> Das ist bekanntlich notwendig für eine korrekte Lösung, aber nicht
> hinreichend.

Dann sag dochmal, mit welchem AVR-GCC es nicht geht.

Hab eben von AVRfreaks den 4.6.1 geladen und ausprobiert, geht auch.


Peter

von Konrad S. (maybee)


Lesenswert?

Jörg Wunsch schrieb:
> (Ich glaube, wir AVR-GCC-Nutzer sollten allmählich mal sammeln und
> Johann als Dankeschön für seine viele Arbeit 'n Kasten Bier spendieren
> oder was auch immer für ihn als Dankeschön taugt. ;)

Da bin ich auch dabei!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:

> Dann sag dochmal, mit welchem AVR-GCC es nicht geht.

Hatte es gestern mit 4.8.0 getestet und einer volatile-Variable als 
Ziel. Der Code war exakt der gleiche. Aber welbst wenn es damit nicht 
ginge ist das kein Beweis daß die IVAR-Hack funktioniert.

A => B

ist eben immer noch was anderes als

B => A

und hier haben wir
B = "Die Lösung ist korrek"
A = "Es wird für eine bestimmte
     Beispielcode/Compilerversion/Verschalterung
     korrekter Code erzeugt"

Diesen Fehlschluss liest man mindestens in 50% aller Threads, teilweise 
in etwas anderem Gewand wie "was macht der Code?" - "probier das aus" 
oder "wie lange dauert dieser Code" - "schau was ein Simulator macht".

von Εrnst B. (ernst)


Lesenswert?

Könnte man das Problem nicht elegant mit einem inline-asm-Makro 
erschlagen, das nur cli; sts; sts; sei enthält?

dann könnte man statt
1
  cli();
2
  a=b;
3
  sei():
oder
1
  ATOMIC_BLOCK(ATOMIC_FORCEON){
2
    a=b;
3
  }

z.B. schreiben
1
  ATOMIC_ASSIGN_16BIT(a,b);
ggfs mit "FORCEON" und "RESTORESTATE" Varianten.

auch
1
  ATOMIC_ASSIGN_16BIT(a, 12345/b);
wär dann kein Problem mehr.

Würde sich gut als Erweiterung für die util/atomic.h machen.

von Peter D. (peda)


Lesenswert?

Johann L. schrieb:
> Hatte es gestern mit 4.8.0 getestet und einer volatile-Variable als
> Ziel. Der Code war exakt der gleiche. Aber welbst wenn es damit nicht
> ginge ist das kein Beweis daß die IVAR-Hack funktioniert.

Der "Hack" ist identisch mit der Lösung von Jörg Wunsch:

Beitrag "Re: Schwerer Bug in AVR-GCC 4.1.1"

Ich hab ihn nur als Macro umgeschrieben und daß der Typ automatisch 
übernommen wird.
Bisher funktionierte er immer saugut.
Daß er mit 4.8.0 plötzlich nicht mehr geht, wundert mich schon.
Vielleicht geht er ja wieder, wenn AVR-GCC 4.8.x auch mal für Windows 
verfügbar ist.


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:

> Daß er mit 4.8.0 plötzlich nicht mehr geht, wundert mich schon.
> Vielleicht geht er ja wieder, wenn AVR-GCC 4.8.x auch mal für
> Windows verfügbar ist.

Die 4.8.0 ist ein avr-gcc. Auf welchem Host der Compiler läuft ist 
egal.

Da? die entsprechende Optimierung nicht durch eine Volatile-Variable 
beeinflusst wird ist bereits an den Compilerquellen ersichtlich.

von Peter D. (peda)


Lesenswert?

Johann L. schrieb:
> Die 4.8.0 ist ein avr-gcc. Auf welchem Host der Compiler läuft ist
> egal.

Ich meinte, sobald ihn mal jemand für Windows compiliert und ihn dann 
zum Download zur Verfügung stellt.
Daß die Linuxer da immer etwas fixer sind, ist mir klar. Ich hoffe aber, 
daß die nacheilenden Windows-Versionen dafür stabiler (besser 
ausgetestet) sind.
Daher bin ich garnicht so erpicht, immer die neueste Version zu haben.


Johann L. schrieb:
> Da? die entsprechende Optimierung nicht durch eine Volatile-Variable
> beeinflusst wird ist bereits an den Compilerquellen ersichtlich.

D.h. also, die Compilerquellen haben sich für diesen Fall von 4.6.1 auf 
4.8.0 entscheidend geändert?
Ist ja nicht so der Brüller. Hab mich doch langsam an das volatile 
gewöhnt, daß es der Superkleber gegen alle möglichen 
Compilerwidrigkeiten ist.



Peter

von (prx) A. K. (prx)


Lesenswert?

Εrnst B✶ schrieb:

> Könnte man das Problem nicht elegant mit einem inline-asm-Makro
> erschlagen, das nur cli; sts; sts; sei enthält?

Wird dann abhängig vom Datentyp. Die o.A. barrier() Lösung besitzt 
dieses Problem nicht und ist in Johanns Version funktional ansonsten 
gleichwertig.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:
> Εrnst B✶ schrieb:
>
>> Könnte man das Problem nicht elegant mit einem inline-asm-Makro
>> erschlagen, das nur cli; sts; sts; sei enthält?
>
> Wird dann abhängig vom Datentyp. Die o.A. barrier() Lösung besitzt
> dieses Problem nicht und ist in Johanns Version funktional ansonsten
> gleichwertig.

Doch, auch das barrier hat das Problem, weil ein normaler arithmetischer 
Ausdruck für die Barrier unsichtbar ist.

Tatsächlich ist ein Intrinsic oder Assembler der einzige Weg, ein 
Vertauschen sicher auszuschliessen.

Inline Assembler hat ds Problem, daß man Implikationen an die 
Adressierbarkeit der Variable machen muss. LDS/STS gehen eben nur mit 
Symbolen, und nicht was sonst noch an Adressierungsarten zur Verfügung 
steht.

In einer Allgemeinen Version müsste man also indirekt adressieren, weil 
das die allgemeinste Adressierung ist.


Peter Dannegger schrieb:
> Johann L. schrieb:
>> Die 4.8.0 ist ein avr-gcc. Auf welchem Host der Compiler läuft ist
>> egal.
>
> Ich hoffe aber, daß die nacheilenden Windows-Versionen dafür
> stabiler (besser ausgetestet) sind.
> Daher bin ich garnicht so erpicht, immer die neueste Version zu haben.

Genau aus solchen Gründen sind die Windows-Builds nicht besser getestet 
als die Linux-Version.

Wo es kein Feedback und keine Bugreports gibt, wird sich auch nix 
ändern.

Nicht durch Hoffen und Beten, nicht durch Gedankenübertragung oder durch 
Erdstrahlen weil das Problem bereits in 4.1 gesehen wurde und ins Wissen 
des Universums getunnelt ist, und auch nicht durch spotane 
Selbst-Implementierung oder dadurch, täglich einen magischen 
Compilertanz aufzuführen. Nein, nichts von alledem hilft.

Ohne aussagekräftiges Feedback ist die Qualität einer Software nicht 
über einen bestimmten Punkt zu steigern, insbesondere wenn es so 
komplexe Software wie GCC ist.

Aber solcherlei Trivialitäten sind ja hinlänglich bekannt.

> Johann L. schrieb:
>> Da? die entsprechende Optimierung nicht durch eine Volatile-Variable
>> beeinflusst wird ist bereits an den Compilerquellen ersichtlich.
>
> D.h. also, die Compilerquellen haben sich für diesen Fall von 4.6.1 auf
> 4.8.0 entscheidend geändert?

Die Quellen ändern sich ständig entscheidend. Hier ein hübsches Video, 
das das Wachsen der GCC-Quellen darstellt:

http://www.youtube.com/watch?v=ZEAlhVOZ8qQ

> Hab mich doch langsam an das volatile gewöhnt,
> daß es der Superkleber gegen alle möglichen Compilerwidrigkeiten ist.

An der besagten Stelle würd ich kein Groschen drauf wetten, eben weil es 
nix mit der Arithmetik zu tun hat.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> Doch, auch das barrier hat das Problem, weil ein normaler arithmetischer
> Ausdruck für die Barrier unsichtbar ist.

Reden wir vom gleichen Ding? Da die Variable per "+r" im Register landen 
muss kommt der Compiler doch kaum darum herum, den Wert vorher zu 
berechnen.

Ich beziehe mich auf:
1
#define barrier(x) asm volatile ("" : "+r"(x))
2
3
void test( unsigned int val )
4
{
5
    val = 1535 / val;
6
    barrier(val);
7
    cli();
8
    ivar = val;
9
    sei();
10
}

In dieser Variante ist barrier vom Datentyp und von der Art der 
Adressierung der Daten unabhängig. Muss nur ins constraint "r" passen.

Von der "sts" Variante halte ich nichts, denn da kriegt man eben diese 
beiden Probleme.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:
> Johann L. schrieb:
>
>> Doch, auch das barrier hat das Problem, weil ein normaler arithmetischer
>> Ausdruck für die Barrier unsichtbar ist.
>
> Reden wir vom gleichen Ding? Da die Variable per "+r" im Register landen
> muss kommt der Compiler doch kaum darum herum, den Wert vorher zu
> berechnen.

Achso, das meinst du...

"Normale" Scheduling-Barriers sind asm volatile("":::"memory") oder 
__builtin_barrier()

Das asm von oben wird eher dazu verwendet, Informationen zu vernichten; 
als "Barrier" hab ich es nicht angesehen.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> Das asm von oben wird eher dazu verwendet, Informationen zu vernichten;
> als "Barrier" hab ich es nicht angesehen.

Ok, der Name... Ja, es ist keine universelle Barriere, nur eine 
spezifische für "val". Sucht euch halt einen anderen Namen.

von ReinerS (Gast)


Lesenswert?

Ich habe heute einige Vorschläge getestet und bin zu folgender Lösung 
für mich gekommen:
1
#pragma GCC optimize("no-tree-ter")
2
void test( unsigned int val )
3
{
4
    val = 1535 / val;
5
  cli();
6
  ivar = val;
7
  sei();;
8
}
9
#pragma GCC optimize("tree-ter")
10
11
12
void test2( unsigned int val )
13
{
14
    val = 1535 / val;
15
  cli();
16
  ivar = val;
17
  sei();;
18
}

das ergibt folgenden ASM Code
1
#pragma GCC optimize("no-tree-ter")
2
void test( unsigned int val )
3
{
4
  7c:  bc 01         movw  r22, r24
5
    val = 1535 / val;
6
  7e:  8f ef         ldi  r24, 0xFF  ; 255
7
  80:  95 e0         ldi  r25, 0x05  ; 5
8
  82:  0e 94 5b 00   call  0xb6  ; 0xb6 <__udivmodhi4>
9
  cli();
10
  86:  f8 94         cli
11
  ivar = val;
12
  88:  60 93 60 00   sts  0x0060, r22
13
  8c:  70 93 61 00   sts  0x0061, r23
14
  sei();;
15
  90:  78 94         sei
16
}
17
  92:  08 95         ret
18
19
00000094 <test2>:
20
#pragma GCC optimize("tree-ter")
21
22
23
void test2( unsigned int val )
24
{
25
  94:  bc 01         movw  r22, r24
26
    val = 1535 / val;
27
  cli();
28
  96:  f8 94         cli
29
#pragma GCC optimize("tree-ter")
30
31
32
void test2( unsigned int val )
33
{
34
    val = 1535 / val;
35
  98:  8f ef         ldi  r24, 0xFF  ; 255
36
  9a:  95 e0         ldi  r25, 0x05  ; 5
37
  9c:  0e 94 5b 00   call  0xb6  ; 0xb6 <__udivmodhi4>
38
  cli();
39
  ivar = val;
40
  a0:  70 93 61 00   sts  0x0061, r23
41
  a4:  60 93 60 00   sts  0x0060, r22
42
  sei();;
43
  a8:  78 94         sei
44
}
45
  aa:  08 95         ret

Mit den Pragmas kann ich die Optimierung für die kritische Funktion 
verändern. Der restliche Code kann dann mit voller Optimierung übersetzt 
werden.

Herzlichen Dank für die vielen nützlichen Anregungen und Informationen.

Gruß Reiner

von (prx) A. K. (prx)


Lesenswert?

Vorsicht Falle:
1
#pragma GCC optimize("no-tree-ter")
2
void test( unsigned int val )
3
{
4
  val = 1535 / val;
5
  cli();
6
  ivar = val;
7
  sei();;
8
}
9
#pragma GCC optimize("tree-ter")
10
11
void f(int x)
12
{
13
  test(x);
14
}
führt bei avr-gcc 4.5.3 und -O2 aufgrund von Inlining zu
1
test:
2
        movw r22,r24
3
        ldi r24,lo8(1535)
4
        ldi r25,hi8(1535)
5
        rcall __udivmodhi4
6
        cli
7
        sts ivar+1,r23
8
        sts ivar,r22
9
        sei
10
        ret
11
12
f:
13
        movw r22,r24
14
        cli
15
        ldi r24,lo8(1535)
16
        ldi r25,hi8(1535)
17
        rcall __udivmodhi4
18
        sts ivar+1,r23
19
        sts ivar,r22
20
        sei
21
        ret

Pragmas sind eine etwas zweifelhafte Konstruktion, wenn sie sich nicht 
auf die lexikalische Interpretation des Codes, sondern erst auf die 
Codegenerierung beziehen. Denn die ist u.U. nicht auf die Zone 
beschränkt, in der das Pragma gilt.

von (prx) A. K. (prx)


Lesenswert?

Mit dem an sich besseren weil an die Funktion geklebten
1
void test( unsigned int val ) __attribute__((optimize("no-tree-ter")));
geht es genauso in die Hose. Besser also:
1
void test( unsigned int val ) __attribute__((optimize("no-tree-ter"),noinline));

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

TER ist ein Übrltäter, aber ich weiß nicht, ob es noch andere 
Optimierungen gibt, die ähnliche Effekte haben.

von ReinerS (Gast)


Lesenswert?

@ A. K.

Ich werde dein zweiten Vorschlag in Zukunft verwenden. Macht den Code 
sehr übersichtlich.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> TER ist ein Übrltäter, aber ich weiß nicht, ob es noch andere
> Optimierungen gibt, die ähnliche Effekte haben.

Das sehe ich auch als eher riskant an. Deshalb gefällt mir die 
barrier(var) Variante besser. Aber wenn er die nicht leiden mag...

von ReinerS (Gast)


Lesenswert?

@ A. K.
>Das sehe ich auch als eher riskant an. Deshalb gefällt mir die
>barrier(var) Variante besser. Aber wenn er die nicht leiden mag...

Er mag es schon leiden. Der Vorteil der Funktion mit Attribut ist, das 
es besser zu verstehen ist. Die barrier Lösung ist für nicht eingeweihte 
zwar schwerer nach zu vollziehen, arbeite aber hervorragend auch in 
Verbindung mit atomic.h.

Eure Aussage, das es die sicherste Methode ist, hat mich überzeugt.

von (prx) A. K. (prx)


Lesenswert?

ReinerS schrieb:

> Die barrier Lösung ist für nicht eingeweihte
> zwar schwerer nach zu vollziehen,

In aller Kürze:
  asm volatile ("" : "+r"(x))
zwingt den Compiler, den Wert in Register zu laden. Zusätzlich wird 
signalisiert, dass der (leere) Befehl das/diese Register und damit die 
Variable verändern kann, so dass der Compiler gezwungen ist, im weiteren 
Verlauf diese(s) Register an Stelle des ursprünglichen Ausdrucks zu 
verwenden. Da sowohl dieses Statement als auch cli() volatile sind 
werden sie auch nicht relativ zueinander umgeordnet.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:

>   asm volatile ("" : "+r"(x))

Das funktioniert allerdings nur dann, bzw. ist nur dann anwendbar, wenn 
der Wert in Register passt.

Hat man etwa einen Puffer, der atomar zu verwalten ist und mit 
memcpy/memclr gesetzt wird, ist dieses asm offenbar nicht anwendbar.

Wirklich wichtig ist der Fehler offensichtlich nicht. Immerhin wurde er 
für avr-gcc 4.1 beobachtet; inzwischen ist 4.7 aktuell und 4.8 wird 
entwickelt. Wenn es während 8 Releases kein Bugreport wert war (GCC wird 
1x pro Jahr releast), dann kann das nicht wichtig sein...

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> Das funktioniert allerdings nur dann, bzw. ist nur dann anwendbar, wenn
> der Wert in Register passt.

Allerdings dürfte das nur bei Strukturen und Arrays ein Problem werden, 
denn es passen bei AVRs m.W. alle Skalare in Register. Und bei 
Strukturen und Arrays entsteht das hier betrachtete Laufzeitproblem nur, 
wenn deren Adresse umständlich berechnet wird, d.h. man hat es mit einem 
Pointer zu tun und der ist wiederum ein Skalar.

Oder welches Szenario hattest Du im Auge?

Worst case ist bei Skalaren wohl, dass der Compiler zwischen 
barrier(var) und der weiteren Verwendung aufgrund Registerdrucks den 
Kram zeitweilig in Speicher auslagert. Aber da muss dann schon deutlich 
mehr Code dazwischen stehen als hier gezeigt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:

> [...] dazwischen [...]

Wobei — wie oben zu sehen — zu präzisieren ist, was "dazwischen" 
bedeuten soll ;-)

von Peter D. (peda)


Lesenswert?

Johann L. schrieb:
> Wirklich wichtig ist der Fehler offensichtlich nicht.

Das kann man so nicht sagen. Es ist ein Interruptfehler und die sind 
immer sehr wichtig.
Allerdings liegt es in der Natur von Interruptfehlern, daß sie sich nur 
selten auswirken. Z.B. könnte eine SW-PWM dadurch flackern, aber 
vielleicht nur alle 2 Tage. Wer da nicht die Idee hat, woran es liegen 
könnte, der wird nie dahinter kommen. Und nur die wenigsten gucken sich 
ab und zu das Asssemblerlisting an, bzw. verstehen es überhaupt.

Wie gesagt, in den 5 Jahren seitdem benutze ich die volatile Lösung von 
Jörg Wunsch und sie funktioniert einwandfrei (unter WINAVR).


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:
> Johann L. schrieb:
>> Wirklich wichtig ist der Fehler offensichtlich nicht.
>
> Das kann man so nicht sagen.

Wie gesagt: Wenn das Problem 5 Jahre rumliegt, ohne daß jemand einen PR 
aufmacht, kann es nicht wichtig sein...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Konrad S. schrieb:
> Jörg Wunsch schrieb:
>> (Ich glaube, wir AVR-GCC-Nutzer sollten allmählich mal sammeln und
>> Johann als Dankeschön für seine viele Arbeit 'n Kasten Bier spendieren
>> oder was auch immer für ihn als Dankeschön taugt. ;)
>
> Da bin ich auch dabei!

Hey, vielen Dank an die "vielen Nutzer des avr-gcc" für das "Bier"!

Hat mich echt gefreut, und ich war total baff...

von Konrad S. (maybee)


Lesenswert?

Ich war auch baff:
"Warum in die Ferne schweifen? Sieh, das Gute liegt so nah!"

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.