Forum: Compiler & IDEs [AVR] Seltsame Optimierung


von Jan S. (eichhoernchen)


Lesenswert?

Hi,
ich habe ein Multitasking OS auf einem Atmega 644 implementiert, nun 
habe ich allerdings ein Problem an dem ich hänge, avr-gcc optimiert mir 
meine Tasks, die im System laufen sollen weg, bzw. teile davon.
1
unsigned char * shared;
2
3
unsigned char * control;
4
5
unsigned char * test;
6
7
8
9
10
void task1()
11
12
{
13
14
  control = os_calloc(7);
15
16
  shared  = os_calloc(20);
17
18
19
20
  while (1);
21
22
}
23
24
25
26
27
void task2()
28
29
{
30
31
32
  test = (unsigned char*)" Test ";
33
34
35
36
37
38
  while (!shared);
39
40
41
42
  while (1)
43
44
  {
45
46
    os_cWrite(shared, test   , 6);
47
48
    os_cRead (shared, control, 6);
49
50
    control[6]= 0;
51
52
     lcd_writeString((char *)control);
53
54
    delayMs(os_taskOutputDelay);
55
56
  }
57
58
}

Die Tasks werden vom OS Scheduler gestartet und gestoppt, nun das 
Problem, task2 soll so lange warten, bis Task1 gemeinsamen Speicher 
angefordert hat und anschließend immer wieder den Speicher beschreiben 
und auslesen
Leider macht die Codeoptimierung nur murks, das while(!shared) wird sehr 
seltsam behandelt:
1
while (!shared)
2
+000007B0:   91800177    LDS     R24,0x0177       Load direct from data space
3
+000007B2:   91900178    LDS     R25,0x0178       Load direct from data space
4
+000007B4:   9700        SBIW    R24,0x00         Subtract immediate from word
5
+000007B5:   F159        BREQ    PC+0x2C          Branch if equal
6
7
....
8
9
10
+000007E1:   CFFF        RJMP    PC-0x0000        Relative jump

Naja, also komm ich automatisch immer auf diese Stelle, wo er nicht 
wegspringt mehr in dem Task. Nun hab ich mir gedacht, okay machste mal 
die shared variable volatile, dann sollte es ja klappen... falsch 
gedacht, dass klappt auch nicht, der Code ist ähnlich toll. Nun 
erweitere ich den Code der while(!shared); um ein asm volatile("nop"); 
der Code wird fast wieder genauso gut, die Sprungbedinnung der Schleife 
ist nun nicht mehr zu einer Stelle wo nur wieder zu sich selbst 
gesprungen wird, sondern (die bed. wird 1 mal richtig geprüft (aber da 
ist in den seltesten Fällen schon die Speicherallokierung fertig) zu 
einer Abfolge von nop und rjmp pc-0x0001. Also wieder Endlosschleife 
ohne meine Schleifenbedinnung nochmals zu Prüfen.
1
27:         while (!shared)
2
+000007B0:   91800177    LDS     R24,0x0177       Load direct from data space
3
+000007B2:   91900000    LDS     R25,0x0000       Load direct from data space
4
+000007B4:   9700        SBIW    R24,0x00         Subtract immediate from word
5
+000007B5:   F411        BRNE    PC+0x03          Branch if not equal
6
28:           asm volatile("nop");
7
+000007B6:   0000        NOP                      No operation
8
+000007B7:   CFFE        RJMP    PC-0x0001        Relative jump
9
36:           delayMs(os_taskOutputDelay);
10
+000007B8:   91C0010A    LDS     R28,0x010A       Load direct from data space

Wieso kann es sein, dass avr-gcc denkt, dass die Variable niemals einen 
Wert bekommt (ich meine, wieso sollte er sonst den Sprung zum 
nachfolgendem Code wegoptimieren?). Stell ich die Optimierung aus, läuft 
der Code, wie er sollte, allerdings ist das nicht sinnvoll für den 
restlichen Code und irgendwie ist dass ja auch nicht der Sinn der Sache.
Kann es am os_calloc liegen? Das avr-gcc denkt, da kommt kein Wert 
zurück? Oder liegt es daran, weil er keine Codezeile findet wo die 
Funktionen explizit aufgerufen werden, diese werden nur durch eine ISR 
per reti aufgerufen.

Ich würde mich freuen, wenn ihr mir helfen könntet, btw, ich nutze AVR 
Studio mit WinAVR 20080610

Danke

von Skua C. (skua)


Lesenswert?

!shared
Geht so nicht(hat der compiler nicht gewarnt?).

Zeiger mit NUL initialisieren und dann (shared==NUL).

Oder so in der Art.
Bin nicht so der C Crack.

von Andreas W. (Gast)


Lesenswert?

ich finde das sollte gehen. Ich habe aber noch nie auf eine Zeiger 
abgefragt :-)
In meiner Verzweiflung würde ich ihn zu einen (int) casten.

von ... .. (docean) Benutzerseite


Lesenswert?

volatile könnte helfen... -> rest gidf ;)

von Skua C. (skua)


Lesenswert?

@... ...
Wer lesen kann ...

@Andreas
Versuchs.

von Peter D. (peda)


Lesenswert?

Dürfte ein volatile Problem sein.
Der GCC ist da sehr rabiat im Wegoptimieren, wenn die globale Variable 
nicht volatile ist.


Peter

von Jan S. (eichhoernchen)


Lesenswert?

Das Problem ist aber weiterhin existent, auch mit volatile, ich hab nun 
die ganze Nacht dran gesessen und versucht herauszufinden, wieso dieser 
blöde compiler mir den Code vermurkst und ich komm einfach nicht hinter 
das Geheimniss.

auf (int) casten bringt auch nichts, hab ich alles schon versucht.

von (prx) A. K. (prx)


Lesenswert?

Zeig mal den aktuellen Code und den Output (z.B. das .lss File).

von Jan S. (eichhoernchen)


Angehängte Dateien:

Lesenswert?

Ich hab das ganz jetzt mal im Aufwand etwas weniger gemacht, dass es 
übersichtlicher ist, der Fehler bleibt aber der gleiche!
1
volatile unsigned char * shared;
2
3
void task1()
4
{
5
  shared = os_malloc(20);
6
  while (1);
7
}
8
9
void task2()
10
{
11
  while (!((int)shared));
12
13
  while (1)
14
  {
15
    lcd_putByte((int)shared);
16
    lcd_writeString("wippy");
17
    delayMs(os_taskOutputDelay);
18
  }
19
}

Im anhang die .lss Datei

von Stefan E. (sternst)


Lesenswert?

1
volatile unsigned char * shared;

Argh, hier ist das Ziel volatile, nicht aber der Pointer selbst, und der 
wird doch schließlich getestet.
Also:
1
unsigned char * volatile shared;

> Wieso kann es sein, dass avr-gcc denkt, dass die Variable niemals einen
> Wert bekommt

Weil der Compiler nichts von Tasks weiß. Er sieht nur den linearen 
Codeablauf innerhalb der Funktion und dort wird shared in der 
while-Schleife nicht verändert.

> wieso dieser blöde compiler mir den Code vermurkst

Ja, ja, immer sind die anderen schuld.

von Jan S. (eichhoernchen)


Lesenswert?

Na klar sind die anderen schuld! so was blödes aber auch, ich wusste 
echt nicht, dass man volatile so benutzen kann, ich programmiere erst 
seit ein paar Wochen in C.

Nun, noch eine Frage, kann ich irgendwie erzwingen, dass die variablen 
die z.b. mit malloc benutzt werden das volatile richtig bekommen, auch, 
wenn sie falsch deklariert worden sind wie "unsigned char * shared", 
z.b. dass der Compiler trotzdem weiß, dass diese nicht wegzuoptimieren 
ist? Wieso könnte man fragen... z.b. damit das niemand falsch macht.

von Stefan E. (sternst)


Lesenswert?

Jan Sagichnicht wrote:

> ich programmiere erst seit ein paar Wochen in C.

Da ist ein Multitasking OS dann aber ein ziemlich ehrgeiziges Projekt. 
Wenn du dich da mal nicht übernimmst.

> Nun, noch eine Frage, kann ich irgendwie erzwingen, dass die variablen
> die z.b. mit malloc benutzt werden das volatile richtig bekommen, auch,
> wenn sie falsch deklariert worden sind wie "unsigned char * shared",
> z.b. dass der Compiler trotzdem weiß, dass diese nicht wegzuoptimieren
> ist? Wieso könnte man fragen... z.b. damit das niemand falsch macht.

Erzwingen kannst du es nicht. Du könntest aber z.B. in einer 
Header-Datei eigene Typen definieren, und dann den Benutzern deines OS 
sagen, dass sie die verwenden sollen.

von Jan S. (eichhoernchen)


Lesenswert?

^^ joar, aber wenn man muss, dann muss man das Programmieren.

Aber ich danke euch für eure Hilfestellung, nun ist mir das ganze viel 
klarer

von Tim (Gast)


Lesenswert?

Hallo,

ich arbeite am gleichen Projekt (Uni) und habe das gleiche Problem.

Mir ist durch eure Erklärungen klarer geworden, warum das ganze nicht 
funktioniert.

Die einzige Frage die wieso es andere Leute/Studenten gibt, die die 
gleichen Funktionen implementieren müssen (natürlich jeder wie er will 
und kann) und dieses Problem bei den gleichen Tasks nicht haben. (also 
ohne unsgn. char * volatile shared)

Wenn etwas "wegoptimiert" wird, dann doch überall und nicht mal hier 
oder mal da.....

Da das ganze aber auch bei uns/mir laufen sollte, frage ich mich was man 
den anders machen kann um diesen Fehler zu vermeiden.

Gruß Tim

von (prx) A. K. (prx)


Lesenswert?

Deklarationen mit "volatile" drin werden etwas intuitiver, wenn man bei 
der Deklaration das "*" in einem typedef versteckt:
  typedef unsigned char * uchar_ptr;
  volatile uchar_ptr shared;
ist das gleiche wie
  unsigned char * volatile shared;

Du kannst natürlich auch gleich schreiben:
  typedef unsigned char * volatile uchar_ptr;
  uchar_ptr shared;

von (prx) A. K. (prx)


Lesenswert?

Tim wrote:

> Wenn etwas "wegoptimiert" wird, dann doch überall und nicht mal hier
> oder mal da.....

So einfach ist es nicht.

Aus
  while (shared == 0)
    ;
wird ohne volatile eine Totschleife, weil der Compiler weiss das sich 
unterwegs nichts ändert.

Anders sieht es bei
  while (shared == 0)
    printf("waiting\n");
aus, weil der Compiler hier davon ausgehen muss, dass die ihm inhaltlich 
nicht bekannte Funktion printf() die globale Variable "shared" 
möglicherweise ändert.

von Tim (Gast)


Lesenswert?

Hallo,

erstmal Danke für die schnelle Antwort.

Vielleicht habe ich das Problem nicht genug konkretisiert....

Wir müssen das ganze Programmieren im Zuge eine Praktikums und der Code 
wird durch TestTasks getestet...auf diese Tasks haben wir keinerlei 
Einfluss....

sie müssen nur durchlaufen....

wenn sie das nun bei "vielen" anderen tun und bei uns nicht...stellt 
sich ja die Frage warum es das bei uns nicht tut...

Jetzt war die Frage, ob es Gründe für dieses Verhalten gibt, bei einem 
Programm ...und ob man darauf Einfluss nehmen kann.

Gruß Tim

von Tim (Gast)


Lesenswert?

EDIT: (da ich die zweite Antwort erst nachher gelesen habe)

Bei anderen Studenten mit den gleichen Funktionen, aber anders 
implementiert läuft der Code aber genauso durch !

Das ist das was ich einfach nicht verstehen kann.

Gruß Tim

von Stefan E. (sternst)


Lesenswert?

Tim wrote:

> wenn sie das nun bei "vielen" anderen tun und bei uns nicht...stellt
> sich ja die Frage warum es das bei uns nicht tut...
>
> Jetzt war die Frage, ob es Gründe für dieses Verhalten gibt, bei einem
> Programm ...und ob man darauf Einfluss nehmen kann.

Zum einen schreibt ihr ja nicht alle 100%ig den gleichen Code.
Zum anderen kann natürlich auch fehlerhafter Code zufällig meistens 
richtig funktionieren.

von Peter (Gast)


Lesenswert?

WENN der Testtask aufgrund eines fehlenden volatile nicht richtig läuft, 
is das nunmal ein Fehler im Task. Ihr könnt doch nicht mit eurem OS 
sicherstellen, dass fehlerhaft programmierte Tasks trotzem richtig 
ausgeführt werden :)

von Jan S. (eichhoernchen)


Lesenswert?

^^ Das Problem ist nur dass wir nichts machen können, wenn wir falschen 
Code bekommen und dieser richtig laufen muss!

von Tim (Gast)


Lesenswert?

Aber im Grunsatz habe ihr natürlich Recht.

So einen Fall kann man nicht abfangen, man muss ja nicht falschen Code 
zum laufen bringen (hoffe ich)....

Mich wundert es einfach nur, das es bei 2 anderen "Teams" einfach läuft 
und bei uns nicht.

Natürlich haben die nicht den gleichen Code, aber der Fehler des 
"wegoptimierens" müsste doch auch bei denen bestehen bleiben....

Das ist mein Verständnisproblem :)

Trotzdem natürlich Danke für die Hilfen...

Gruß Tim

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


Lesenswert?

A. K. wrote:

> Anders sieht es bei
>   while (shared == 0)
>     printf("waiting\n");
> aus, weil der Compiler hier davon ausgehen muss, dass die ihm inhaltlich
> nicht bekannte Funktion printf() die globale Variable "shared"
> möglicherweise ändert.

Schlechtes Beispiel: das Verhalten von printf() darf dem Compiler
sehr wohl bekannt sein (sofern er in einem "hosted environment"
arbeitet).  Beispielsweise ersetzt der GCC ein printf("Hi!\n")
locker durch puts("Hi!").  Auch die Annahme, dass der Compiler nichts
über die Interna einer externen Funktion weiß (und daher den Zugriff
auf "shared" nicht weglassen darf) geht spätestens dann den Bach
runter, wenn man mit -fwhole-program --combine eine globale Optimierung
über alle Quelldateien (die dann auf einer Kommandozeile stehen müssen)
erreichen möchte.

von Stefan E. (sternst)


Lesenswert?

> Mich wundert es einfach nur, das es bei 2 anderen "Teams" einfach läuft
> und bei uns nicht.

> Natürlich haben die nicht den gleichen Code, aber der Fehler des
> "wegoptimierens" müsste doch auch bei denen bestehen bleiben....

Ja, aber dieser Fehler führt ja nicht in jedem Fall zu fehlerhaften 
Verhalten. Wenn an der Stelle mit der while-Schleife shared bereits 
ungleich Null ist, dann funktioniert der Code ja trotz des Fehlers. Und 
ob shared dort Null ist oder nicht, hängt wiederum davon ab, in welcher 
Reihenfolge und mit welcher Zeitscheibengröße die Tasks ausgeführt 
werden.

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch wrote:

> Schlechtes Beispiel: das Verhalten von printf() darf dem Compiler
> sehr wohl bekannt sein

Ja, er kann es wissen. Ich weiss auch dass ein globaler Optimizer das 
diesbezügliche Verhalten aller Funktionen vom gesamten Quellcode kennen 
kann. Nur wollte ich Tim nicht gleich erschlagen. Mir ging es darum, ein 
einfaches Beispiel zu bringen, warum 2 Leute bei gleichen Problem zu 
unterschiedlichem Verhalten kommen können.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. wrote:

> einfaches Beispiel zu bringen, warum 2 Leute bei gleichen Problem zu
> unterschiedlichem Verhalten kommen können.

Manche übersetzen auch (effektiv) mit -O0 ;-)

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Tim wrote:

> Mich wundert es einfach nur, das es bei 2 anderen "Teams" einfach läuft
> und bei uns nicht.
>
> Natürlich haben die nicht den gleichen Code, aber der Fehler des
> "wegoptimierens" müsste doch auch bei denen bestehen bleiben....

Dann zeigt doch mal den echten Code.

Der C-Sourcecode ganz oben passt ja nicht zu den beiden ASM-Listings...

C-Sourcecode:
  while (!shared);
                 ^

ASM-Listing #1:
while (!shared)
               ^
+000007B0:   91800177    LDS     R24,0x0177       Load direct from data 
space

ASM-Listing #2:
27:         while (!shared)
                           ^
+000007B0:   91800177    LDS     R24,0x0177       Load direct from data 
space

Wenn im ausführbaren Programm tatsächlich das ; fehlt (bzw. dessen 
Umsetzung), wird es zur Laufzeit

a/ bei noch ungültigem shared (NULL) zur Ausführung des 
Schreiben/Lesen while(1)s kommen.

b/ bei bereits gültigem shared (~NULL) zur Beendigung von task2() 
kommen. Was ja nicht vorkommen darf!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Du erwartest nicht wirklich, daß im Ausführbaren Programm ein ; steht 
oder die Kommentare in einem aggressiv optimierten Programm aufs Byte 
genau platziert sind?

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Nein, ich erwarte es nicht.

Ich kann allerdings nur zu dem meinen Senf abgeben, was der OP 
geschrieben hat. Das was ich geschrieben habe, könnte das Problem 
umzingeln. So gut oder so wenig wie die anderen Antworten auch.

In die Optimierungstheorien habe ich mich nicht eingelesen. Das Feld 
überlasse ich anderen Experten u.a. auch Dir (no pun intended). 
Allerdings weiss ich nicht welche Optimierung der OP verwendet. Für mich 
lesen sich die Ausführungen des OP dazu wie Spekulationen, solange nicht 
die Optimierungsstufe angegeben ist bzw. ein Übersetzungsversuch ohne 
Optimierung (wie von dir vorgeschlagen?) ein anderes Ergebnis bringt.

Ob mein Elaborat tatsächlich zutreffend ist, kann im Moment nur der OP 
prüfen. Wenn er möchte, dass ich/andere es prüfe/n, kann er mir/uns den 
echten Sourcecode und am besten auch die ASM-Listings anhängen, darum 
bitte ich ja oben, und dann prüfe ich das selber.

ADD: Was ich erwarte ist, dass im gemischten C/ASM Listing komplette 
Sourcecodezeilen enthalten sind, d.h. auch ein ; am Zeilenende enthalten 
ist. Was hätte das gemischte Listing sonst für einen Sinn?

von Jan S. (eichhoernchen)


Lesenswert?

Keine Angst, da steht schon ein ; im Sourcecode. Ich programmier zwar 
noch nicht so lange in C, aber Programmieren können wir schon. Die 
Optimierungsstufe egal welche hat diesen Code erzeugt, außer O0 
natürlich, dort wurde die Schleifen, wie man sie auch per Hand in asm 
implementieren würde umgesetzt, also jedesmal bed. geprüft. Mir ist nun 
auch klar, wie das ganze abgelaufen ist und warum das wegoptimiert 
wurde.

Also weiß ich gar nicht warum immernoch so viel diskutiert wird

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Stefan B. wrote:
> In die Optimierungstheorien habe ich mich nicht eingelesen. Das Feld
> überlasse ich anderen Experten u.a. auch Dir (no pun intended).
> Allerdings weiss ich nicht welche Optimierung der OP verwendet. Für mich
> lesen sich die Ausführungen des OP dazu wie Spekulationen, solange nicht
> die Optimierungsstufe angegeben ist bzw. ein Übersetzungsversuch ohne
> Optimierung (wie von dir vorgeschlagen?) ein anderes Ergebnis bringt.

-O0 ist leider nicht immer möglich, weil uU das Programm dann zu groß 
wird.

Mit hilft meistens ein Blick in die Compilerausgabe mit "-fverbose-asm 
-dp -save-temps", weil da mehr info drin ist als in das asm-Ausgabe des 
Assemblers (-Wa,-...)

Was mit schon mal spanisch vorkommt ist

> while (shared == 0);

und alle dazu gleichbedeutenden Formulierungen, weil auf shared nicht 
atomar zugegriffen wird. Das sollte zwar nur sporadisch zu Fehlern 
führen, aber denonch:
1
unsigned char * volatile shared;
2
...
3
{
4
   unsigned char *s;
5
6
   do
7
   {
8
        ATOMIC_START;
9
        s = shared;
10
        ATOMIC_END;
11
   } while (!s);
12
}

von Tim (Gast)


Lesenswert?

Nabend,

mit
1
 unsigned char * volatile shared;

funktioniert auch alles wunderbar.

Es war ja eigentlich nur die Frage danach warum man den Fehler den wir 
bekommen nicht immer bekommt....andere Projekte mit den gleichen 
Funktionen weisen diesen "Compiler-Fehler" oder "Optimierungs-Fehler" 
nicht auf...

das ist das einzig verwirrende was bleibt....

mit
1
while(!shared)
oder
1
while(shared == NULL)
oder
1
while(shared == 0)
 fragen wir ja nur ab ob der Pointer gesetzt wurde...

wie gesagt, programmieren ist nicht das Problem :)

Gruß Tim

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Tim wrote:
> Nabend,
>
> mit
>
1
 unsigned char * volatile shared;
>
> funktioniert auch alles wunderbar.

Was nicht heisst, daß es korrekt ist ;-)

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Interrupt-Routinen_und_Registerzugriffe


> Es war ja eigentlich nur die Frage danach warum man den Fehler den wir
> bekommen nicht immer bekommt....andere Projekte mit den gleichen
> Funktionen weisen diesen "Compiler-Fehler" oder "Optimierungs-Fehler"
> nicht auf...

Es sind ziemlich sicher keine Fehler, behaupte ich jetzt mal. Es ist C.

Die Sprache C hat keine Vorstellung vom Interrupts oder kennt Konzepte 
wie synchronized in Java!

Der Compiler arbeitet deterministisch. Wenn iht also die gleiche 
Compilerversion mit den gleichen Schaltern und Kommandozeilen-Parametern 
auf die gleiche Quelle anwendet, kommt auch das gleiche raus. Und wenn 
ihr diese Compilerausgabe mit der gleichen binutils-Version und den 
gleichen binutils-Schaltern assembliert und linkt und das elf und hex 
daraus erstellt, sind die auch identisch. Sofern in beiden Fällen die 
verwendeten Betriebbsystembibliotheken (.dll, .a, .so, ...) ebenfalls 
gleich arbeiten.

Falls ihr Hinweise auf einen gcc-Fehler habt, dann hilft eine 
präcompilierte Quelle und die Compilerausgabe; beides bekommst bei 
zusätzlicher Angabe von -v -E bei den gcc-Optionen.

> das ist das einzig verwirrende was bleibt....
>
> mit
1
while(!shared)
> oder
1
while(shared == NULL)
> oder
1
while(shared == 0)
 fragen wir ja nur ab ob der Pointer
> gesetzt wurde...

Nein, ihr verlasst euch später auch darauf, daß shared einen sinnvollen 
Wert hat, was nicht unbedingt gegeben ist. Siehe "atomic" oben.

Die while-Schleife hat nur ein paar Instruktionen, es ist also nicht 
unwahrscheinlich, daß zu einem ungünstigen Zeitpunkt geschedult wird und 
os_cWrite() mit einem korrupten shared aufgerufen wird.

Zudem: ohne volatile darf ein compiler
1
while (!a1);

transformieren zu
1
if (a1)
2
   goto L2;
3
4
L1:
5
   goto L1;
6
7
L2:

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.