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
voidtest(unsignedintval)
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
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.
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.
#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.
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.
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.
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.
@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
@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
@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?
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
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. ;)
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?
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 ;-)
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
voidtest(unsignedintval)
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.
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".
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
externunsignedivar;
2
3
#pragma GCC push_options
4
#pragma GCC optimize("no-tree-ter")
5
6
voidtest2(unsignedval)
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
voidtest3(unsignedval)
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.
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.
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.
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/
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.
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.
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
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!
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".
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
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.
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
Ε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.
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.
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
voidtest(unsignedintval)
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.
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.
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.
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
voidtest(unsignedintval)
3
{
4
val=1535/val;
5
cli();
6
ivar=val;
7
sei();;
8
}
9
#pragma GCC optimize("tree-ter")
10
11
12
voidtest2(unsignedintval)
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
voidtest(unsignedintval)
3
{
4
7c:bc01movwr22,r24
5
val=1535/val;
6
7e:8fefldir24,0xFF;255
7
80:95e0ldir25,0x05;5
8
82:0e945b00call0xb6;0xb6<__udivmodhi4>
9
cli();
10
86:f894cli
11
ivar=val;
12
88:60936000sts0x0060,r22
13
8c:70936100sts0x0061,r23
14
sei();;
15
90:7894sei
16
}
17
92:0895ret
18
19
00000094<test2>:
20
#pragma GCC optimize("tree-ter")
21
22
23
voidtest2(unsignedintval)
24
{
25
94:bc01movwr22,r24
26
val=1535/val;
27
cli();
28
96:f894cli
29
#pragma GCC optimize("tree-ter")
30
31
32
voidtest2(unsignedintval)
33
{
34
val=1535/val;
35
98:8fefldir24,0xFF;255
36
9a:95e0ldir25,0x05;5
37
9c:0e945b00call0xb6;0xb6<__udivmodhi4>
38
cli();
39
ivar=val;
40
a0:70936100sts0x0061,r23
41
a4:60936000sts0x0060,r22
42
sei();;
43
a8:7894sei
44
}
45
aa:0895ret
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
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.
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...
@ 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.
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.
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...
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.
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
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...
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...