Hi,
ich habe lange nach einem Fehler bei mir gesucht,
aber mir scheint, avr-gcc (WinAVR 20090313) 4.3.2) macht es falsch.
Ich habe den Fehler mit einem kurzen Programmstück provozieren können
(s.u)
Das Stück
1
if (tmp.x.sekunden)
2
tmp.x.sekunden--;
wird falsch übersetzt - oder jedenfalls funktioniert es nicht so, wie es
soll, denn beim Decrement läuft was falsch.
1
do {
2
if (tmp.x.sekunden)
3
4c: cc 23 and r28, r28
4
4e: 21 f0 breq .+8 ; 0x58 <__SREG__+0x19>
5
tmp.x.sekunden--;
6
50: 2c 2f mov r18, r28
7
52: 21 50 subi r18, 0x01 ; 1
8
54: e9 01 movw r28, r18
9
56: 02 c0 rjmp .+4 ; 0x5c <__SREG__+0x1d>
10
else {
11
tmp.x.sekunden = 59;
12
58: cb e3 ldi r28, 0x3B ; 59
13
tmp.x.minuten--;
14
5a: d1 50 subi r29, 0x01 ; 1
15
}
Wie kommt das movw r28, r18 dorthin ?
Übrigens funktioniert es wenn man in der Union Sekunden und Minuten
vertauscht.
Hier der Sourcecode:
1
#include <avr/io.h>
2
3
typedef union
4
{
5
struct
6
{
7
8
uint8_t sekunden;
9
uint8_t minuten;
10
11
} x;
12
uint16_t sekmin;
13
} zeit_t;
14
15
16
void testmich2 (zeit_t tmp) {
17
GPIOR2 = tmp.x.sekunden;
18
}
19
20
21
void testmich (zeit_t zeit) {
22
23
zeit_t tmp;
24
25
for (;;) {
26
27
tmp.x = zeit.x;
28
29
testmich2(tmp);
30
31
do {
32
if (tmp.x.sekunden)
33
tmp.x.sekunden--;
34
else {
35
tmp.x.sekunden = 59;
36
tmp.x.minuten--;
37
}
38
39
}
40
41
while (tmp.sekmin);
42
}
43
44
}
45
46
int main (void) {
47
zeit_t zeit;
48
49
zeit.x.minuten = 1;
50
zeit.x.sekunden = 2;
51
52
testmich(zeit);
53
}
Aufruf :
avr-gcc -c -mmcu=attiny2313 -I. -gdwarf-2 -DF_CPU=8000000UL -Os
-funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall
-Wstrict-prototypes -mshort-calls -Wa,-adhlns=./bug.lst -std=gnu99
-fno-inline-small-functions -finline-limit=3 --param
inline-call-cost=2 -fno-if-conversion -fno-split-wide-types
-fno-tree-scev-cprop -MMD -MP -MF .dep/bug.o.d bug.c -o bug.o
Woran liegts ?
Ist das tatsächlich ein Bug, oder ist etwas anderes schuld ?
Hab ich was falsch gemacht ??
mfg,
Frank
Hallo Frank,
der Compiler hat nicht ganz konsequent optimiert!
Die Schleife
do { ... } while (tmp.sekmin);
läßt sich ersetzen durch
tmp.sekmin = 0;
Das hat der Compiler "natürlich" nicht erkannt, aber er hat eins
erkannt:
tmp.x.minuten wird durcgh die do-Schleife auf 0 gesetzt.
Genau das hat er generiert.
Bernhard
P.S. Der Fehler sitzt üblicherweise vor dem Bildschirm (des
Anwendungsprogrammierers, nicht des Compilerbauers).
Hi Bernhard,
wie ich schrieb, ist das Beispiel konstruiert, im echten Programm
passiert in der Schleife viel mehr.
Ist aber auch egal, denn in testmich2() schreibt er in GPIOR, und das
ist darf nicht wegoptimiert werden (ist glaube ich volatile).
Die Anzahl der Schleifendurchläufe stimmt so nicht. Egal ob er nun was
berechtigterweise was wegoptimiert nicht... das sieht man besser, wenn
in testmich2 z.b. eine Ausgabe auf LCD erfolgt. das mit GPIO ist auch
nur ein Beispiel um einen kurzen Testcode zu bekommen.
Außerdem ist das keine Erklärung dafür, daß es funktioniert, wenn man in
der Union Sekunden und Minuten vertauscht...
So richtig überzeugt mich Deine Erklärung noch nicht :-)
Lasse mich aber trotzdem immernoch gerne vom Gegenteil überzeugen.
lg,
Frank.
Hallo Frank,
Bernhard hat mit seiner Aussage recht:
---
Die Schleife
do { ... } while (tmp.sekmin);
läßt sich ersetzen durch
tmp.sekmin = 0;
---
Die Abbruchbedingung der Schleife hängt ja nicht von veränderlichen
Eingangsgrößen ab und enthält keine volatile-Zugriffe. Das Ergebnis der
Schleife ist hier immer konstant.
Die Anzahl der Schleifendurchläufe ändert ebenfalls nichts am Ergebnis.
Das Register GPIOR wird ebenfalls nicht angetastet.
Bevor du einen Compilerfehler suchst, kompiliere testweise mal
mit Option -O0 statt -Os oder verwende volatile für tmp.
Grüße
Daniel
Hallo Frank,
die do-Schleife führt genau folgendes aus (Pseudocode):
Do until tmp.x.sekunden == 0 and tmp.x.minuten == 0
Wenn tmp.x.sekunden != 0 dann
tmp.x.sekunden = tmp.x.sekunden - 1
sonst
tmp.x.sekunden = 59
tmp.x.minuten = tmp.x.minuten - 1
Ende Wenn
Ende Do
Diese Schleife tut nichts anderes als tmp.x.sekunden und tmp.x.minuten
geordnet herunterzuzählen, bis beide null sind. Dann terminiert sie.
Probiere es händisch aus.
Für den Compiler spielt es keine Rolle, was an Code produziert wird,
solange der generierte Code genau das tut, was er lt. source-code tun
soll. Und das ist hier mit ziemlicher Sicherheit der Fall.
Leider fehlt in dem Code-Schnipsel die Initialisierung von r19. Selbiges
dürfte in der Initialisierung auf 0 gelöscht worden sein, um
tmp.x.minuten auf 0 zu setzen.
Bernhard
fb schrieb:
> Wie kommt das movw r28, r18 dorthin ?
Der verminderte Wert aus R18 (und das unveränderte R19) wird zurück in
die struct bei R28 (und R29) geschrieben. Das MOV ist schonmal OK.
Ob das MOVW (d.h. der nicht sichtbare R19->R29 Teil) ein Fehler ist,
kann man an deinem Codefetzen nicht sehen.
Ok, schande auf mein Haupt, ihr habt Recht :-)
Für den oben geposteten Code stimmen Eure Argumente tatsächlich.
Habe den Code geändert (s.u.)
Tut mir mal torrtzdem den Gefallen, und probiert das im Debugger. Bitte
:-)
Setzt mal einen Breakpoint auf den NOP.
Ergebnis: GPIOR2 ist dort 0x02.
Jetzt vertauschen wir in der Union die Zeilen für Minuten und Sekunden
(also wirklich nur die Reihenfolge), und lassen das Programm nochmal
laufen.
Ergebnis: GPIOR2 ist 0x3E
Frage: Warum ?
Apropos Codefetzen: Bevor man sich die Mühe macht hier rumzupesten
sollte man das Ding mal eben durch den Compiler jagen und selbst
sehen... oder nach nach einem gößerem Ausschnitt fragen.
1
#include <avr/io.h>
2
3
typedef union
4
{
5
struct
6
{
7
8
uint8_t minuten;
9
uint8_t sekunden;
10
11
} x;
12
uint16_t sekmin;
13
} zeit_t;
14
15
16
void testmich2 (zeit_t tmp) {
17
GPIOR2++;
18
}
19
20
void testmich (zeit_t zeit) {
21
22
zeit_t tmp;
23
24
for (uint8_t i=1;i<2;i++) {
25
26
tmp.x = zeit.x;
27
28
do {
29
30
testmich2(tmp);
31
32
if (tmp.x.sekunden)
33
tmp.x.sekunden--;
34
else {
35
tmp.x.sekunden = 59;
36
tmp.x.minuten--;
37
}
38
39
}
40
41
while (tmp.sekmin);
42
}
43
44
}
45
46
int main (void) {
47
zeit_t zeit;
48
49
zeit.x.minuten = 1;
50
zeit.x.sekunden = 2;
51
52
GPIOR2 = 0;
53
54
testmich(zeit);
55
56
for (;;)
57
asm("nop");
58
59
}
Bei Bedarf kann ich auch die vollständigen LSS-Dateien beider Versionen
posten...
Sorry, ich bin da raus, nicht weil ich gleich ins Kino (TROPA DE ELITE)
gehe ;-) sondern weil ich ein älteres WinAVR einsetze. Der ASM-Code von
dem Stück oben sah bei mir schon ganz anders aus.
Ok... falls es jemanden interessieren sollte:
Es liegt an den beliebten Schaltern
-finline-limit=3 --param inline-call-cost=2
Warum auch immer.
Läßt man sie weg, funktioniert das Programm.
Da scheint ganz schön was im Argen zu sein...
Leider kann ich es nicht genauer eingrenzen, meine Möglichkeiten sind
damit erschöpft.
P.s. Darf ich jetzt davon ausgehen, daß es NICHT mein Fehler ist, oder
übersehe ich immer noch was ?
Die Schalter dürfen doch keinesfalls zu falschem Code führen.
Ja, ich denke auch, dass das ein Bug ist. Der Bug liegt an dieser
Stelle im Code:
1
mov r18,r28
2
subi r18,lo8(-(-1))
3
movw r28,r18
Warum auch immer, der Compiler schiebt für die Subtraktion von
tmp.x.sekunde den Wert nach r18. Anschließend benutzt er aber
eine Wortoperation, um ihn wieder nach tmp zurück zu speichern.
Dabei wird das (undefiniert gesetzte und offenbar auf 0 stehende)
Register r19 nach r29 kopiert, sodass das komplette tmp.x dann
auf { .minute = 0, .sekunde = 1 } steht. Danach bedarf es noch
eines einzelnen Schleifendurchlaufs, und alles wird beendet.
Ich habe dein Beispiel mal auf folgendes runtergebrochen, was
offenbar das Minimalbeispiel darstellt:
1
typedefunsignedcharuint8_t;
2
typedefunsignedshortuint16_t;
3
4
typedefunion
5
{
6
struct{
7
uint8_tsekunden;
8
uint8_tminuten;
9
}x;
10
uint16_tsekmin;
11
}zeit_t;
12
13
14
voidtestmich2(zeit_ttmp){
15
// just something that cannot be optimized out
16
asmvolatile("nop");
17
}
18
19
voidtestmich(zeit_tzeit){
20
21
zeit_ttmp;
22
23
tmp.x=zeit.x;
24
25
do{
26
testmich2(tmp);
27
28
if(tmp.x.sekunden)
29
tmp.x.sekunden--;
30
else{
31
tmp.x.sekunden=59;
32
tmp.x.minuten--;
33
}
34
}while(tmp.x.sekunden||tmp.x.minuten);
35
}
36
37
intmain(void){
38
zeit_tzeit;
39
40
zeit.x.minuten=1;
41
zeit.x.sekunden=2;
42
43
testmich(zeit);
44
45
return0;
46
}
Dieses Beispiel kannst du direkt in einem GCC-Bugreport benutzen, da
es keinerlei Abhängigkeiten mehr von einer konkreten AVR-Maschine
oder anderweitig vom Präprozessor besitzt, d. h. das sieht nach dem
Präprozessor immer noch genauso aus. Außerdem vermeidet es das
potenziell undefinierte Verhalten als Ursache, dass du in einer
union verschiedene Teile beschreibst und dann ausliest.
Außerdem habe ich die Kommandozeile auf folgendes Minimum herunter
gebrochen:
Offenbar liegt der Bug also darin, wie das -fno-split-wide-types
implementiert ist und ist aber nur reproduzierbar, wenn man das
inlining von testmich2() verhindert.
Hi Jörg,
ok, wenn Du mir verrätst wie das geht ? :-)
Achso, und da du Mod bist:
Ich habe hier auch einen Account, lange nicht benutzt, habe aber
inzwischen mein Passwort vergessen und meine eMail-Addy hat sich
geändert; kann man da was machen ?
Übrigens hatte ich damals auch schon in einem Thread auf einen Bug
hingewiesen. Der ist inwischen gefixt. :-)
lg,
Frank.
fb schrieb:
> ok, wenn Du mir verrätst wie das geht ? :-)
Du tippst "gcc bug reporting" bei Tante Gugel ein, und kannst
eigentlich "Auf gut Glück!" drücken, denn der erste Link ist:
http://gcc.gnu.org/bugs/> Achso, und da du Mod bist:
...aber ich bin nicht Andreas. ;-) Schreib' ihm eine Mail. Adresse
findest du im Impressum.
Jo :-)
wollte grade die verlangte .S Datei hochladen, da machte mein Outlook
"Ding" und jemand anderes ;-) war schon schneller.
Was meinst Du, ist der Bug was AVR-Spezifisches, oder kann das auch
andere Targets treffen ?
Danke.
Frank.
fb schrieb:
> Was meinst Du, ist der Bug was AVR-Spezifisches, oder kann das auch> andere Targets treffen ?
Das ist so schwer zu sagen. Da Andy Hutchinson schon drauf aufmerksam
geworden ist, vermute ich mal, dass er sich zumindest zu einer Analyse
aufraffen wird.
Eine letzte kleine Bitte noch :-)
Könnte einer der Mods bitte den Thread-Titel ändern ?
Mein halb editiertes Gestammel ist ja peinlich... :-)
lg,
Frank
Jörg Wunsch schrieb:
> Offenbar liegt der Bug also darin, wie das -fno-split-wide-types> implementiert ist und ist aber nur reproduzierbar, wenn man das> inlining von testmich2() verhindert.
Mir ist auch schon aufgefallen, daß -fno-split-wide-types den Code eines
gesamten Programms optimiert, aber bei einzelnen Funktionen mehr Code
erzeugt.
In der Regel verringert es aber deutlich die Zahl unnötiger MOVs.
Der Befehl MOVW wurde ja erst später von Atmel implementiert.
Es könnte also eine Art Copy&Paste Fehler sein, wo an einer Stelle
zuviel das MOV durch MOVW ersetzt wurde.
Peter
Andy Hutchinson scheint an der Sache dran zu sein.
Ich habe da noch eine Frage :
Ich schreibe in Sekmin einen Wert, und lese später x.Sekunden und
x.Minuten.
Ist das undefiniertes Verhalten ?
Falls nein, kann ich evtl noch einen Bugreport hinterherschieben...
Habe grade das Phänomen, daß (laut ASM-Listing) zwar sekmin geschrieben
wird, aber später anstelle von x.sekunden einfach 0 benutzt wird ?!?
:confused:
Derzeit macht er das aber nur in meinem "großem" Programm, das ich hier
niemandem zumuten möchte ;-)
fb schrieb:
> Ich schreibe in Sekmin einen Wert, und lese später x.Sekunden und> x.Minuten.> Ist das undefiniertes Verhalten ?
Ich habe keine klare Aussage im Standard dazu gefunden, aber ich
denke schon -- schon deshalb, weil zwischen sek und min ja noch
padding sein könnte.
> Habe grade das Phänomen, daß (laut ASM-Listing) zwar sekmin geschrieben> wird, aber später anstelle von x.sekunden einfach 0 benutzt wird ?!?
Wenn der Standard das nicht sanktioniert, dass man tatsächlich eine
union über verschiedene Pfade zugreifen kann, dann kann ich mir gut
vorstellen, dass der Compiler das halt optimieren darf, so nach dem
Motto: unionname.x.sek wurde nur mit 0 beschrieben, mehr hat sich
seither nicht geändert, also ist er noch 0.
Daher hatte ich dein Fehlerbeispiel auch so geändert, dass es auf
der sicheren Seite bezüglich des Standards liegt.
Es könnte noch sein, dass das von dir beschriebene Verhalten sich
durch -fno-strict-aliasing abändern lässt. Kannst dir ja mal die
Beschreibung zu -fstrict-aliasing in der GCC-Dokumentation durchlesen.
Ich wäre mittlerweile mit derartigen "historischen Missbräuchen"
recht vorsichtig. Es gibt meistens elegantere Methoden, das zu
lösen, und all dein Herum-Aliasen zwischen sekmin und sek/min ist
eine derartige Mikrooptimierung, dass ich nicht glaube, dass davon
die Benutzbarkeit deines Programms irgendwie abhängt. Ich würde das
einfach sein lassen und Minuten und Sekunden ganz normal separat
handhaben.
Peter Dannegger schrieb:
> Es könnte also eine Art Copy&Paste Fehler sein, wo an einer Stelle> zuviel das MOV durch MOVW ersetzt wurde.
:-)
Peter, ich mag Andy Hutchinsons Analyse hier nicht posten, weil die
Erklärung doch recht umfangreich ist. Schau einfach mal selbst rein,
damit du ein Gefühl bekommst, um welche Art Fehler es sich so
handelt...
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=41894
Habe da was gefunden, einen Ausschnitt aus der avr/eeprom.h :
union
{
uint16_t word;
struct
{
uint8_t lo;
uint8_t hi;
} byte;
} x;
x.word = __value;
eeprom_write_byte ((uint8_t *)__p, x.byte.lo);
eeprom_write_byte ((uint8_t *)__p + 1, x.byte.hi);
Jetzt gibts 2 Möglichkeiten:
Entweder das ist erlaubt, oder die eeprom.h funktioniert nur rein
zufällig.
Bei mir funktionierts nicht. Übertragen auf obiges Beispiel würde bei
mir immer eine 0 ins eeprom Geschrieben. Allerdings kann ich diese
"Erscheinung" derzeit aus Zeitgründen (noch) nicht erfolgreich auf ein
(kleines) Testprogramm runterbrechen.... zischen "x.word = __value;" und
dem lesen passiert bei mir noch eine ganze Menge. Evtl liegts daran.
Oder an mir ;-)
Ich bleib dran.
fb schrieb:
> Entweder das ist erlaubt, oder die eeprom.h funktioniert nur rein> zufällig.
Die <avr/eeprom.h> würde ich als Bestandteil der "implementation" im
Sinne des C-Standards sehen, denn die avr-libc (zu der sie gehört)
bildet ja den Teil der Standarbibliothek für die AVR-GCC-Implemen-
tierung.
Damit steht es dieser Datei frei, sich auf internes Verhalten des
Compilers zu verlassen, sofern es mit allen funktionierenden GCC-
Versionen funktioniert. Das kann ja durchaus gegeben sein, auch
wenn das entsprechende Verhalten vom Standard als undefiniert gilt.
Allerdings hätte die avr-libc ein Problem, falls sich das Compiler-
verhalten mal ändert, dann müsste man diese Stelle anfassen.
Wenn dich die Details interessieren, was bei unions tatsächlich vom
Standard abgedeckt wird und als portabel gelten kann, dann frage mal
in der Newsgruppe de.comp.lang.c nach. Dort sitzen die Sprach-
Rechtsanwälte der Sprache C. ;-)
Ok.. einverstanden..
Das ist wohl etwas Glatteis, auf das sich die libc da begibt, aber
solange es funktioniert.. :-)
Übrigens steht hier, dass es tatsächlich undefiniert ist:
http://openbook.galileocomputing.de/c_von_a_bis_z/015_c_strukturen_009.htm
Hab mein Programm geändert und verzichte auf die Union. Damit gehe ich
dann auch dem Bug oben aus dem Weg.
Nochmal Danke !
lg,
Frank.