Forum: Compiler & IDEs Was genau macht die Clobber-Liste ?


von Daniel B. (khani)


Lesenswert?

Hallo Leute,

ich habe die im Betreff gestellte Frage, die ich noch ein bißchen
präzisieren möchte. Ich habe mir zu dem (von mir häufig benutzten)
inline-assembly wiederholt die Dokumentation von avr-libc durchgelesen.
Die Syntax einer inline-assembly-Verwendung schaut ja so aus :
asm volatile ( Anweisungen : Outputs : Inputs : Clobbers)

So weit so gut. Die Anweisungen enthalten Stringkonstanten (oder eben
nur eine), die outputs und inputs bestehen aus Listen von durch Kommas
getrennten Ausdrücken - auch klar. Mit der Clobber-Liste verhält sich
das genauso.

Wenn man nun Eingabe- und Ausgabevariablen in die inputs/outputs
einträgt, dann sucht sich der Compiler passende Register (nach der
Spezifikation in der input/output-Liste) aus und trägt diese in den
Assembler-Code ein. Wenn man den Wert eines Registers ändert, das nicht
auf diese Weise in der input/output-Liste steht, dann muss man das dem
Compiler mitteilen, damit dieser auf diesen Umstand reagieren kann.

Da kommt dann mein Verständnis : Wenn ich ein Register verbiege, dann
sage ich dem Compiler welches und er rettet vorher den Inhalt und
stellt ihn nach dem inline-assembly wieder her.
-> Ist das richtig so weit ?

-> Falls das stimmt, gilt das für alle Register, die in der
Clobber-Liste stehen oder werden manche Register nicht gerettet, obwohl
sie drin stehen ?

Mein Problem war folgendes: Bei einer Programmierung, bei der explizit
r0 und r1 verwendet werden mussten (hat was mit SPM zu tun), habe ich
im inline assembly die beiden Register verwendet und auch in die
Clobber-Liste eingetragen. Dass r0 nicht gerettet wird ist ja völlig
OK, denn diese kann ja auch unter dem Namen _tmp_reg_ verwendet
werden. Warum wird aber der Inhalt von r1 (_zero_reg_) nicht
wiederhergestellt, auch wenn man es in die Clobber-Liste schreibt ?!
-> Ist das ein Bug oder welchen Sinn hat das ?

Falls es ein Bug ist, dann ist er bekannt, denn die Routinen in
"boot.h" löschen das Register nach der Verwendung wieder (ist eben
das _zero_reg_). In der FAQ zur avr-libc steht, dass man r1 zum
merken in eigenen Funktionen usw. verwenden darf, man es aber
wiederherstellen muss. Meiner Meinung sollte diese Wiederherstellung
erledigt werden, wenn man das Register in die Clobber-Liste aufnimmt,
bei den anderen Register, die man nicht ungestraft verclobbern kann ist
das ja auch der Fall.

MfG, Daniel

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


Lesenswert?

> Da kommt dann mein Verständnis : Wenn ich ein Register verbiege,
> dann sage ich dem Compiler welches und er rettet vorher den Inhalt
> und stellt ihn nach dem inline-assembly wieder her.  -> Ist das
> richtig so weit ?

Im Prinzip ja, wobei er sich normalerweise wohl zuerst einmal Mühe
geben wird, das Register gar nicht erst selbst zu benutzen.

> -> Falls das stimmt, gilt das für alle Register, die in der
> Clobber-Liste stehen oder werden manche Register nicht gerettet,
> obwohl sie drin stehen ?

Sollte für alle sein, aber du hast ja wohl eine rhetorische Frage
gestellt, da du ja das Gegenteil soeben selbst beobachtet hast.

>  Warum wird aber der Inhalt von r1 (_zero_reg_) nicht
> wiederhergestellt, auch wenn man es in die Clobber-Liste schreibt ?!
> -> Ist das ein Bug oder welchen Sinn hat das ?

Könnte ein Bug sein.

Die Sonderbedeutung von r0 und r1 beim AVR-GCC scheint sich in den
GCC-Quellen nicht sonderlich gut ,,herumgesprochen'' zu haben.  Wir
hatten ja neulich auch schon Peter Danneggers Fall, dass der Compiler
nicht meckert, wenn man versucht, eins davon via ... asm("r1") an
eine
Variable zu binden.

von Daniel B. (khani)


Lesenswert?

Hallo Jörg,

danke für Deine Antwort. Ich interpretiere das so, dass es keine
dokumentierten Ausnahmen für die Behandlung von Elementen der
Clobber-Liste gibt.

Eine andere Merkwürdigkeit habe ich noch festgestellt : Wenn man als
input den Z-Pointer ("z"(pv)) angibt und anschließend r30/r31 in die
Clobber-Liste schreibt, dann erzählt mir der Compiler, dass er r30 und
r31 nicht kennt. Lässt man den Z-Pointer als input weg, dann kennt er
sie wieder. Ich habe das Gefühl, dass die Clobber-Liste nocht nicht so
wirklich ausgereifte Ergebnisse bringt. Eine Kontrollempfehlung für das
Assembler-Listing ist auf jeden Fall sinnvoll (hat mich jetzt bald eine
Woche Schweiß gekostet ;-)). Ich habe aber viele andere Sachen bei der
Fehlersuche gelerent - in so fern ist das fast ein Gewinn.

MfG, Daniel.

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


Lesenswert?

> Ich interpretiere das so, dass es keine dokumentierten Ausnahmen für
> die Behandlung von Elementen der Clobber-Liste gibt.

So sehe ich das.

> Eine andere Merkwürdigkeit habe ich noch festgestellt : Wenn man als
> input den Z-Pointer ("z"(pv)) angibt und anschließend r30/r31 in
die
> Clobber-Liste schreibt, dann erzählt mir der Compiler, dass er r30
> und r31 nicht kennt.

Das halte ich einfach mal für einen Bedienfehler.

> Ich habe das Gefühl, dass die Clobber-Liste nocht nicht so wirklich
> ausgereifte Ergebnisse bringt.

Das ist Quark.  Der inline-Assembler dürfte ein eher ausgereiftes und
ausgefeiltes Teil sein.  Ich habe mir neulich mal den vom IAR ansehen
dürfen (der ja ansonsten ein wirklich ausgereifter Compiler ist), der
hat vielleicht 10 % Funktionsumfang des GCC-inline-asm.

Die Geschichte mit r0/r1 ist sicher ein Problem, dass deren
Sonderbedeutung innerhalb des GCC nicht ordentlich definiert worden
ist, sodass die architekturunabhängigen Teile des GCC davon mal
einfach nichts wissen.  Daher wissen sie weder, dass man diese
Register nicht mittels ... asm(regname) vergeben darf noch, dass r1
beim Auftauchen in der clobber list auch dann wiederhergestellt werden
muss, wenn der Compiler es gerade eigentlich nicht verwendet hat.
Siehe meine vorhergehende Antwort: wenn du ein Register in der clobber
list angibst, wird sich der Compiler in allererster Linie wohl darum
bemühen, das Register gar nicht erst selbst zu verwenden, damit er es
eben nicht retten und restaurieren muss.  Genau das hat er ja bei dir
auch getan: er hat r1 gemieden und musste es daher nicht retten und
seiner Meinung nach auch nicht restaurieren.

> Eine Kontrollempfehlung für das Assembler-Listing ist auf jeden Fall
> sinnvoll

Das ist bei inline asm wohl immer der Fall.  Man nimmt es eigentlich
im eigenen Code nur im größten Ausnahmefall.  Es macht den Code
unwahrscheinlich schlecht les- und wartbar, kostet einen Haufen
Aufwand, bis man es in Sack und Tüten hat.  Nur selten lohnt sich das.
Meist ist es, wenn schon Assembler, dann effektiver, gleich eine
separate Assembler-Quelldatei zu nehmen.

Seine wirkliche Stärke und Berechtigung hat der Inline-Assembler für
die Implementierer einer C-Umgebung.  Viele der Dinge, die in den
Headerdateien der avr-libc stecken, wären ohne ihn gar nicht oder
nicht so schön elegant zu machen.  Dafür wiederum lohnt's aber auch
den Aufwand, da es hernach viele Leute nachnutzen können.

von Wolfgang (Gast)


Lesenswert?

Hallo, vielleicht hat einer der Spezialisten einen Tipp für mich:

Ich will ein Einzelbit-Fifo bauen, welches
a) nicht allzu tief ist,
b) variables tief sein soll,
c) aus gutem Grund nicht über pointer, sondern per Umkopieren realisiert 
ist.

Hierzu habe ich mir eine inline-asm geschrieben:
1
unsigned char bit_fifo(volatile uint8_t *buffer, uint8_t length, uint8_t newbit)
2
  {
3
      uint8_t shifted;
4
      asm volatile (
5
         "ror %3"                          "\n"
6
         "BIT_FF_LOOP%=:"                  "\n"
7
         "ld %0,  %a1"                     "\n"
8
         "adc %0, %0"                      "\n"
9
         "st %a1+, %0"                     "\n"
10
         "dec %2"                          "\n"
11
         "brne BIT_FF_LOOP%="              "\n"
12
            : "=&r" (shifted)                               // outputs
13
            : "e"  (buffer), "r" (length), "r" (newbit)     // inputs
14
            : "memory"                                      // clobbered
15
      );
16
    return(shifted);
17
   }
Innerhalb dieser inline wird der pointer auf buffer verschoben, 
allerdings geht gcc in der rufenden Routine davon aus, dass er 
unverändert bleibt und lädt ihn nicht nach.
Ich habe schon Attribut "z" auf buffer und R30, R31 in der clobberliste 
probiert, dann meckert der Compiler, dass er keine Platz im Z-Adressraum 
hätte. (Gleiches für X und Y).

Gibt es eine Möglichkeit, gcc zu sagen, dass *buffer void geworden ist?

Servus Wolfgang

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


Lesenswert?

Das liegt an deiner Funktionssignatur.  Du gibst den Zeiger ja nur
in die Funktion hinein und nicht zurück.  Du müsstest entweder die
Adresse des Zeigers übergeben (dann kann der Gerufene sie ändern),
oder aber du gibst einen geänderten Zeiger als Funktionsrückkehrwert
zurück.  (Das geht hier aber nicht, da du ja schon einen anderen
Rückgabewert hast.)

Die Benutzung des inline-Assemblers führt hier (ganze Funktion ist
in Assembler implementiert) übrigens nur zu unlesbarem Code.  Ich
würde sie stattdessen in eine separate Assemblerquelldatei schreiben.

von Wolfgang (Gast)


Lesenswert?

Danke,

mir schwante schon sowas. Wenn der übergebene Pointer auf buffer selbst 
als volatile definiert ist, dann geht es.

Ich werde es wohl auslagern - ich habe es bisher nicht geschafft, das 
mit den normalen Mitteln von C so knapp hinzukriegen.

Servus Wolfgang
(www.opendcc.de)

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Du hast buffer als Pointer auf char volatile definiert.
Es muss aber ein volatile Pointer auf char (ggf. volatile) sein ;-)

EDIT: Ich habe zu lange experimentiert, bist ja inzwischen selbst drauf 
gekommen ;-)

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.