Forum: Compiler & IDEs Bug im avr-gcc bei Behandlung von SFRs als Bitfields?


von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Hallo zusammen,

Ich behandele gerade im Thread

  Beitrag "AVR-Register als Bitfields"

die "registersichere" Manipulation von Bits bzw. Bitfeldern in 
AVR-µC-Registern.

Registersicher heißt, dass automatisch vom Compiler verhindert wird, 
wenn man versucht, ein Bit, was nicht zum verwendeten Register gehört, 
in diesem zu setzen bzw. zu löschen.

Beispiel für so einen Fehler:

  TCCR0A |= (1<<COM2A0) | (1<<COM2A1)

Hier werden also Bits, die zum Timer2 gehören, in dem Register gesetzt, 
welches zu Timer0 gehört.

Mittels Makros, Structs und Unions die ab

  Beitrag "Re: AVR-Register als Bitfields"

behandelt werden, wird dieses verhindert, denn man kann dann einfach 
schreiben:

  BFM_COM0A = 0b11;

oder auch für die Einzel-Bits COM0A0 und COM0A1:

  BFM_COM0A0 = 1;

bzw.

  BFM_COM0A1 = 0;

Die Makros sorgen dafür, dass immer das korrekte Register verwendet wird 
und der Assembler-Output absolut gleich zur "klassischen" Variante 
mittels Shift-Operator ist.

Dabei ist mir aufgefallen, dass der avr-gcc einen kleinen Bug hat, der 
den Code in einem speziellen Fall ineffizient macht, nämlich bei den 
Operatoren | und &.

Ich versuche mal, dieses hier in Kürze zusammenzufassen:

   BFM_COM0A |= 0b11;

wird übersetzt mit:

   BFM_COM0A |= 0b11;
  5a:  84 b5         in  r24, 0x24  ; 36
  5c:  84 b5         in  r24, 0x24  ; 36
  5e:  80 6c         ori  r24, 0xC0  ; 192
  60:  84 bd         out  0x24, r24  ; 36

Hier wird der in-Befehl vom TCCR0A-Register überflüssigerweise 2x 
gemacht.

Ein wesentlich umständlicher erscheinendes Makro

   BF_OR(BFM_COM0A, 0b11);

jedoch erzeugt den optimalen Code.

   BF_OR(BFM_COM0A, 0b11);
  66:  84 b5         in  r24, 0x24  ; 36
  68:  80 6c         ori  r24, 0xC0  ; 192
  6a:  84 bd         out  0x24, r24  ; 36

Dasselbe gilt für

   BFM_COM0A &= ~0b11;

versus

   BF_AND(BFM_COM0A, ~0b11);

Hier der für ATmega88 o.ä. vollständig compilierbare Code:
1
#include <stdint.h>
2
#include <avr/io.h>
3
#include <util/delay.h>
4
5
struct TCCR0A_bits
6
{
7
    uint8_t wgm00:1;
8
    uint8_t wgm01:1;
9
    uint8_t :1;
10
    uint8_t :1;
11
    uint8_t com0b0:1;
12
    uint8_t com0b1:1;
13
    uint8_t com0a0:1;
14
    uint8_t com0a1:1;
15
} __attribute__((__packed__));
16
17
struct TCCR0A_grouped_bits
18
{
19
    uint8_t wgm0:2;     // WGM00 und WGM01
20
    uint8_t :2;
21
    uint8_t com0b:2;    // COM0B0 und COM0B1
22
    uint8_t com0a:2;    // COM0A0 und COM0A1
23
} __attribute__((__packed__));
24
25
union TCCR0A_union
26
{
27
   struct TCCR0A_bits           b;
28
   struct TCCR0A_grouped_bits   g;
29
   uint8_t                      v;
30
};
31
32
#define BF_TCCR0A               (*(volatile union TCCR0A_union *)&TCCR0A)
33
34
#define BFM_WGM0                BF_TCCR0A.g.wgm0
35
#define BFM_WGM00               BF_TCCR0A.b.wgm00
36
#define BFM_WGM01               BF_TCCR0A.b.wgm01
37
38
#define BFM_COM0B               BF_TCCR0A.g.com0b
39
#define BFM_COM0B0              BF_TCCR0A.b.com0b0
40
#define BFM_COM0B1              BF_TCCR0A.b.com0b1
41
42
#define BFM_COM0A               BF_TCCR0A.g.com0a
43
#define BFM_COM0A0              BF_TCCR0A.b.com0a0
44
#define BFM_COM0A1              BF_TCCR0A.b.com0a1
45
46
#define BFM_COM0A_R             TCCR0A
47
#define BFM_COM0A_U             union TCCR0A_union
48
#define BFM_COM0A_M             g.com0a
49
50
#define BFM_COM0A0_R            TCCR0A
51
#define BFM_COM0A0_U            union TCCR0A_union
52
#define BFM_COM0A0_M            b.com0a0
53
54
#define BFM_COM0A1_R            TCCR0A
55
#define BFM_COM0A1_U            union TCCR0A_union
56
#define BFM_COM0A1_M            b.com0a1
57
58
#define BF_SET(rbf, b)          rbf = (b);
59
#define BF_OR(rbf, b)           do { rbf##_U _x; _x.v = 0; _x.rbf##_M = (b); rbf##_R |= _x.v; } while(0)
60
#define BF_AND(rbf, b)          do { rbf##_U _x; _x.v = 0xFF; _x.rbf##_M = (b); rbf##_R &= _x.v; } while(0)
61
62
int main ()
63
{
64
    BFM_COM0A |= 0b11;          // erzeugt überflüssigen in-Befehl
65
    BF_OR(BFM_COM0A, 0b11);     // effizienter
66
67
    BFM_COM0A &= ~0b11;
68
    BF_AND(BFM_COM0A, ~0b11);
69
}

Bitte nicht erschrecken, ich habe die defines aus dem automatisch 
erzeugten bf_atmeg88a.h hier einfach mal reinkopiert, um es einfach 
übersetzbar zu halten.

Erzeugter Output ab main:
1
int main ()
2
{
3
    BFM_COM0A |= 0b11;          // erzeugt überflüssigen in-Befehl
4
  46:  84 b5         in  r24, 0x24  ; 36
5
  48:  84 b5         in  r24, 0x24  ; 36
6
  4a:  80 6c         ori  r24, 0xC0  ; 192
7
  4c:  84 bd         out  0x24, r24  ; 36
8
    BF_OR(BFM_COM0A, 0b11);     // effizienter
9
  4e:  84 b5         in  r24, 0x24  ; 36
10
  50:  80 6c         ori  r24, 0xC0  ; 192
11
  52:  84 bd         out  0x24, r24  ; 36
12
13
    BFM_COM0A &= ~0b11;
14
  54:  84 b5         in  r24, 0x24  ; 36
15
  56:  84 b5         in  r24, 0x24  ; 36
16
  58:  8f 73         andi  r24, 0x3F  ; 63
17
  5a:  84 bd         out  0x24, r24  ; 36
18
    BF_AND(BFM_COM0A, ~0b11);
19
  5c:  84 b5         in  r24, 0x24  ; 36
20
  5e:  8f 73         andi  r24, 0x3F  ; 63
21
  60:  84 bd         out  0x24, r24  ; 36
22
}

Wie man sieht, wird jedesmal der IN-Befehl bei der |= und &= Variante 
ein zweites Mal wiederholt.

Kann dazu vielleicht Johann (oder Jörg oder ein anderer avr-gcc-Experte) 
was sagen?

Achja, Compiler ist avr-gcc 4.7.2, Optimierung Os.

Gruß und Dank,

Frank

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Als erstes sticht eine Warnung ins Auge:
1
bfm.c: In function ‘main’:
2
bfm.c:68:1: warning: large integer implicitly truncated to unsigned type [-Woverflow]

Das ist die ~0b11.  Wenn man eine neuere GCC-Version nimmt, dann wird
der Compiler etwas spezifischer:
1
bfm.c: In function ‘main’:
2
bfm.c:60:76: warning: large integer implicitly truncated to unsigned type [-Woverflow]
3
 #define BF_AND(rbf, b)          do { rbf##_U _x; _x.v = 0xFF; _x.rbf##_M = (b); rbf##_R &= _x.v; } while(0)
4
                                                                            ^
5
bfm.c:68:5: note: in expansion of macro ‘BF_AND’
6
     BF_AND(BFM_COM0A, ~0b11);
7
     ^
Ansonsten ist das natürlich schon ein ziemlicher Bandwurm, den der
Präprozessor daraus macht:
1
(*(volatile union TCCR0A_union *)&(*(volatile uint8_t *)((0x24) + 0x20))).g.com0a |= 0b11;
Ich blick' da nicht mehr komplett durch ;-), gehe aber davon aus, dass
der Compiler einfach Recht hat und ihn dieser Bandwurm wirklich dazu
zwingt, dank der diversen "volatile"s die Adresse 0x24+0x20 zweimal
zu lesen.

Btw., “attribute((packed))” ist auf einer 8-bit-Architektur völlig
überflüssig.  Mehr als 8-bit-weise packen kann sie sowieso nicht,
und das wiederum macht sie aufgrund des Fehlens anderer
Alignment-Anforderungen schon freiwillig.

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


Lesenswert?

Jörg Wunsch schrieb:
> Ich blick' da nicht mehr komplett durch ;-), gehe aber davon aus, dass
> der Compiler einfach Recht hat und ihn dieser Bandwurm wirklich dazu
> zwingt, dank der diversen "volatile"s die Adresse 0x24+0x20 zweimal zu
> lesen.

Ja, es stehen da zwei Dereferenzierungs-Operatoren drin.  Das wird
der Grund sein.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Nachtrag:

Leider wird es noch schlimmer, wenn man den OR- bzw. AND-Operator auf 
SFR-Bitfelder loslässt, welche aus 3 Bits bestehen.

Ich habe das obige Beispiel mal testweise auf TCCR0B erweitert:
1
struct TCCR0B_bits
2
{
3
    uint8_t cs00:1;
4
    uint8_t cs01:1;
5
    uint8_t cs02:1;
6
    uint8_t wgm02:1;
7
    uint8_t :1;
8
    uint8_t :1;
9
    uint8_t foc0b:1;
10
    uint8_t foc0a:1;
11
} __attribute__((__packed__));
12
13
struct TCCR0B_grouped_bits
14
{
15
    uint8_t cs0:3;
16
    uint8_t wgm02:1;
17
    uint8_t :2;
18
    uint8_t foc0b:1;
19
    uint8_t foc0a:1;
20
} __attribute__((__packed__));
21
22
union TCCR0B_union
23
{
24
   struct TCCR0B_bits           b;
25
   struct TCCR0B_grouped_bits   g;
26
   uint8_t                      v;
27
};
28
29
#define BF_TCCR0B               (*(volatile union TCCR0B_union *)&TCCR0B)
30
31
#define BFM_CS0                 BF_TCCR0B.g.cs0
32
#define BFM_CS00                BF_TCCR0B.b.cs00
33
#define BFM_CS01                BF_TCCR0B.b.cs01
34
#define BFM_CS02                BF_TCCR0B.b.cs02
35
36
#define BFM_CS0_R               TCCR0B
37
#define BFM_CS0_U               union TCCR0B_union
38
#define BFM_CS0_M               g.cs0

Wenn man nun in main() schreibt:
1
    BFM_CS0 = 0b010;
2
    BF_OR(BFM_CS0, 0b010);
3
    BFM_CS0 |= 0b010;


Dann wird folgener Assembler-Code daraus erzeugt:
1
   BFM_CS0 = 0b010;
2
  46:  85 b5         in  r24, 0x25  ; 37
3
  48:  88 7f         andi  r24, 0xF8  ; 248
4
  4a:  82 60         ori  r24, 0x02  ; 2
5
  4c:  85 bd         out  0x25, r24  ; 37

Das ist perfekt. Es werden 2 Bit zurückgesetzt und eines gesetzt.
1
    BF_OR(BFM_CS0, 0b010);
2
  4e:  85 b5         in  r24, 0x25  ; 37
3
  50:  82 60         ori  r24, 0x02  ; 2
4
  52:  85 bd         out  0x25, r24  ; 37

Auch das Makro arbeitet so, wie es sein sollte: Es wird 1 Bit gesetzt. 
Die beiden anderen bleiben unberührt.

Jetzt der native OR-Operator:
1
    BFM_CS0 |= 0b010;
2
  54:  95 b5         in  r25, 0x25  ; 37
3
  56:  97 70         andi  r25, 0x07  ; 7
4
  58:  92 60         ori  r25, 0x02  ; 2
5
  5a:  85 b5         in  r24, 0x25  ; 37
6
  5c:  88 7f         andi  r24, 0xF8  ; 248
7
  5e:  89 2b         or  r24, r25
8
  60:  85 bd         out  0x25, r24  ; 37

Das ist suboptimal und ist mehr als doppelt so lang wie das Ergebnis des 
Makros.

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:
> Als erstes sticht eine Warnung ins Auge:

Ja, ich weiß, das liegt an ~0b11, welches ohne cast größer als ein Byte 
ist. Das ist aber hier vollkommen irrelevant, glaub mir. :-)

> Ansonsten ist das natürlich schon ein ziemlicher Bandwurm, den der
> Präprozessor daraus macht:
> [...]
> Ich blick' da nicht mehr komplett durch ;-), gehe aber davon aus, dass
> der Compiler einfach Recht hat und ihn dieser Bandwurm wirklich dazu
> zwingt, dank der diversen "volatile"s die Adresse 0x24+0x20 zweimal
> zu lesen.

STOP! Der "Bandwurm" arbeitet perfekt! Den brauchst Du gar nicht zu 
betrachten. Der schaffts mit drei Assembler-Befehlen. :-)

Das, was nicht perfekt arbeitet, ist der Befehl:

    BFM_COM0A |= 0b11;

Da ist nix mit Bandwurm :-)

Ich spiele mal Preprocessor und löse den Ausdruck schrittweise auf:

    BF_TCCR0A.g.com0a |= 0b11;

    (*(volatile union TCCR0A_union *)&TCCR0A).BF_TCCR0A.g.com0a |= 0b11;

Das ist der Ausdruck, der übrigbleibt. Ein volatile-cast auf TCCR0A, 
nicht zwei. Das ist es ja, was mich ratlos macht. Aber vielleicht hast 
Du recht und die zwei Dereferenzierungen .g und .g.com0a sind der Grund. 
Ich teste das.

Du hast einen neueren avr-gcc 4.7.2? Könntest Du den Code aus dem 
Ausgangposting da mal durchschicken und hier den Auszug aus der 
lss-Datei angeben?




> Btw., “attribute((packed))” ist auf einer 8-bit-Architektur völlig
> überflüssig.  Mehr als 8-bit-weise packen kann sie sowieso nicht,
> und das wiederum macht sie aufgrund des Fehlens anderer
> Alignment-Anforderungen schon freiwillig.

: Bearbeitet durch Moderator
von (prx) A. K. (prx)


Lesenswert?

Frank M. schrieb:
>     (*(volatile union TCCR0A_union *)&TCCR0A).BF_TCCR0A.g.com0a |= 0b11;
>
> Das ist der Ausdruck, der übrigbleibt.

Nein. TCCR0A ist wiederum ein Makro, das aufgelöst werden muss. Nämlich 
sowas wie (vereinfacht):
#define TCCR0A (*(volatile uint8_t *)0x1234)

Schau dir das Ergebnis von gcc -E an. Das ist es, was der Compiler 
hinter dem Präprozessor sieht. Und das ist es, was Jörg gepostet hat.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Frank M. schrieb:
>>     (*(volatile union TCCR0A_union *)&TCCR0A).BF_TCCR0A.g.com0a |= 0b11;
>>
>> Das ist der Ausdruck, der übrigbleibt.
>
> Nein. TCCR0A ist wiederum ein Makro, das aufgelöst werden muss.

Das weiß ich doch :-). Ich sagte, ich löse "schrittweise" auf - gemeint 
war "bis zu meinem Horizont", sprich meiner eigenen Definitionen.

In meiner Auflösung war noch ein Fehler, der Ausdruck ist kürzer:

(*(volatile union TCCR0A_union *)&TCCR0A).g.com0a |= 0b11;

> Schau dir das Ergebnis von gcc -E an. Das ist es, was der Compiler
> hinter dem Präprozessor sieht. Und das ist es, was Jörg gepostet hat.

Upps, ja, Du hast Recht. Ich habe wohl Tomaten auf den Augen gehabt. 
Jörgs Bemerkung zum "Bandwurm" hatte mich verleitet zu glauben, er hätte 
das Makro aufgelöst.

Sorry, Jörg.

Ich konzentriere mich jetzt auf Jörgs komplett-Auflösung
1
(*(volatile union TCCR0A_union *)&(*(volatile uint8_t *)((0x24) + 0x20))).g.com0a |= 0b11;

und versuche hier, den Grund zu finden.

von (prx) A. K. (prx)


Lesenswert?

Der Witz ist allerdings, dass bei
 #define BF_TCCR0A (*(volatile union TCCR0A_union *)0x1234)
statt
 #define TCCR0A (*(volatile uint8_t *)0x1234)
 #define BF_TCCR0A (*(volatile union TCCR0A_union *)&TCCR0A)
das Problem keineswegs verschwindet (gcc 4.1, 4.7).

Die doppelte Deferenzierung ist also nicht die Ursache.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Der Witz ist allerdings, dass bei
>  #define BF_TCCR0A (*(volatile union TCCR0A_union *)0x1234)
> statt
>  #define TCCR0A (*(volatile uint8_t *)0x1234)
>  #define BF_TCCR0A (*(volatile union TCCR0A_union *)&TCCR0A)
> das Problem keineswegs verschwindet (gcc 4.1, 4.7).
>
> Die doppelte Deferenzierung ist also nicht die Ursache.

Ja. Wenn ich statt |= einfach nur = schreibe, dann passiert auch kein 
doppelter IN.
1
(*(volatile union TCCR0A_union *)&(*(volatile uint8_t *)((0x24) + 0x20))).g.com0a = 0b11;

Ich habe auch mal testweise beide volatiles aus dem Ausdruck 
rausgenommen. Kein Unterschied.

Das wird knifflig :-)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Das hier:
1
(*(volatile union TCCR0A_union *)&(*(volatile uint8_t *)((0x24) + 0x20))).v |= 0b11;
2
  46:  84 b5         in  r24, 0x24  ; 36
3
  48:  83 60         ori  r24, 0x03  ; 3
4
  4a:  84 bd         out  0x24, r24  ; 36

arbeitet richtig gut (auch wenn das Ergebnis natürlich falsch ist).

Unterschied: es wird nicht mehr auf eine Sub-Struct innerhalb der union 
zugegriffen. Vielleicht ist das ja die "doppelte Dereferenzierung" :-)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

An der Union liegts auch nicht, ich habe die mal im Ausdruck eliminiert 
und greife direkt auf die TCCR0A_grouped_bits-Struct zu. Damit wird 
.g.com0a einfach zu .com0a.

Leider kein Unterschied:
1
(*(volatile struct TCCR0A_grouped_bits *)&(*(volatile uint8_t *)((0x24) + 0x20))).com0a |= 0b11;
2
  46:  84 b5         in  r24, 0x24  ; 36
3
  48:  84 b5         in  r24, 0x24  ; 36
4
  4a:  80 6c         ori  r24, 0xC0  ; 192
5
  4c:  84 bd         out  0x24, r24  ; 36

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


Lesenswert?

Frank M. schrieb:
> Du hast einen neueren avr-gcc 4.7.2?

gcc version 4.10.0 20140629 (experimental) (GCC)

> Könntest Du den Code aus dem
> Ausgangposting da mal durchschicken und hier den Auszug aus der
> lss-Datei angeben?

lss mag ich nicht. ;-)  Hier ist der generierte Assemblercode, der
eigentlich keine Besonderheiten bietet.
1
        .file   "bfm.c"
2
__SP_H__ = 0x3e
3
__SP_L__ = 0x3d
4
__SREG__ = 0x3f
5
__tmp_reg__ = 0
6
__zero_reg__ = 1
7
        .section        .text.startup,"ax",@progbits
8
.global main
9
        .type   main, @function
10
main:
11
/* prologue: function */
12
/* frame size = 0 */
13
/* stack size = 0 */
14
.L__stack_usage = 0
15
        in r24,0x24
16
        in r24,0x24
17
        ori r24,lo8(-64)
18
        out 0x24,r24
19
        in r24,0x24
20
        ori r24,lo8(-64)
21
        out 0x24,r24
22
        in r24,0x24
23
        in r24,0x24
24
        andi r24,lo8(63)
25
        out 0x24,r24
26
        in r24,0x24
27
        andi r24,lo8(63)
28
        out 0x24,r24
29
        ret
30
        .size   main, .-main
31
        .ident  "GCC: (GNU) 4.10.0 20140629 (experimental)"

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:

> gcc version 4.10.0 20140629 (experimental) (GCC)

Wow :-)

> lss mag ich nicht. ;-)  Hier ist der generierte Assemblercode, der
> eigentlich keine Besonderheiten bietet.

Danke! Leider sind auch hier die doppelten INs drinne.

Aber ich habe jetzt einen Ausdruck hinbekommen, der es auch in 3 
Assembler-Befehlen schafft:
1
(*(struct TCCR0A_grouped_bits *)&(*(volatile uint8_t *)((0x24) + 0x20))).com0a |= 0b11;
2
  46:  84 b5         in  r24, 0x24  ; 36
3
  48:  80 6c         ori  r24, 0xC0  ; 192
4
  4a:  84 bd         out  0x24, r24  ; 36

Nötig war dafür eine Kombination aus zwei Korrekturen, beide einzeln 
anzuwenden hatte keinen Effekt.

1. Die Union wegzulassen und direkt auf die struct grouped_bits
   zuzugreifen.

2. Das volatile im vordersten cast wegzunehmen.

Schritt 1 kann ich durch Anpassen der Makros in den Griff bekommen - 
auch wenn sie dann nicht so symmetrisch sind - egal.

Ob der Schritt 2 jetzt zu invalidem Code führt, weil der Compiler nun 
vielleicht bei der Optimierung zu frech werden könnte, werde ich nun 
testen.

Nachtrag:

Ja, führt zu invalidem Code. Schon ein zweimaliger Aufruf des oberen 
Ausdrucks führt dazu, dass der Compiler den zweiten wegoptimiert. :-(

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Die doppelte Deferenzierung ist also nicht die Ursache.

Ja, ich habe den Ausdruck weiter vereinfacht, so dass er funktional 
immer noch dasselbe tut und Deinem vereinfachtem Ausdruck entspricht:
1
(*(volatile struct TCCR0A_grouped_bits *)((0x24) + 0x20)).com0a |= 0b11;
2
  46:  84 b5         in  r24, 0x24  ; 36
3
  48:  84 b5         in  r24, 0x24  ; 36
4
  4a:  80 6c         ori  r24, 0xC0  ; 192
5
  4c:  84 bd         out  0x24, r24  ; 36

Jetzt gibt es definitiv keine doppelte Dereferenzierung mehr. Ich wüsste 
auch nicht, was man daran noch optimieren könnte. Die union ist raus, 
der register-cast ist raus. Trotzdem bleibt der Fehler. Kürzer geht 
nicht.

Bin jetzt ratlos.

: Bearbeitet durch Moderator
von (prx) A. K. (prx)


Lesenswert?

Frank M. schrieb:
> Das ist suboptimal und ist mehr als doppelt so lang wie das Ergebnis des
> Makros.

Erklärt aber den seltsam anmutenden redundanten Load bei Konstante 3. 
Die Operation enthält 2 Zweige. Einen für das gewünschte Bitfeld selbst, 
und einen für den Rest des Bytes, die anschliessend für den Store 
kombiniert werden. Bei der Konstanten 3 degeneriert bei optimiertem Code 
einer der beiden Zweige, bleibt aber dank volatile rudimentär in Form 
des Loads erhalten.

Man kann nun drüber streiten, ob die im Standard nicht näher 
spezifizierte "abstract machine", die in der Definition von volatile 
wichtig ist, den Wert dieser beiden Zweige wirklich getrennt laden muss.

Und man kann zum Ergebnis kommen, dass volatile Bitfelder schon immer 
ein Minenfeld waren und besser vermieden werden sollten.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Bin jetzt ratlos.

Jetzt kannst du dir nur alle Zwischenschritte des Compilers
angucken (avr-gcc -da).

Vielleicht guckt ja Johann mal irgendwann hier in diesen Thread rein ...

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Man kann nun trefflich drüber streiten, ob die "abstract machine", die
> in der Definition von volatile wichtig ist, den Wert dieser beiden
> Zweige wirklich getrennt laden muss.

Ja, so oder so ähnlich muss es sein. Ich kann das auch soweit 
nachvollziehen. Vielleicht klappt aus diesem Grunde das dazu alternative 
Macro BF_OR() so vorzüglich. Es arbeitet nämlich mit einer lokalen 
Bitfeld-Variablen, die nicht volatile ist und daher am Ende 
wegoptimiert werden kann. Was übrig bleibt, ist dann Load, Change, 
Store.

> Man kann zum Ergebnis kommen, dass volatile Bitfelder schon immer ein
> Minenfeld waren und besser vermieden werden sollten.

Sieht so aus. Ich gebe trotzdem nicht auf und will das Projekt 
"registersichere Manipulationen für alle AVRs" auf jeden Fall zu einem 
guten Ende führen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:
> Jetzt kannst du dir nur alle Zwischenschritte des Compilers
> angucken (avr-gcc -da).

Danke, habe ich direkt mal gemacht. Ist eine Menge Stoff, was man da 
sieht. Bin aber mit den Innereien nicht so sehr vertraut. Ist schon 
zweieinhalb Jahrzehnte her, dass ich mich das letzte mal in den gcc 
gekniet habe.

> Vielleicht guckt ja Johann mal irgendwann hier in diesen Thread rein ...

Das hoffe ich auch :-)

: Bearbeitet durch Moderator
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Ich konzentriere mich jetzt auf Jörgs komplett-Auflösung
> und versuche hier, den Grund zu finden.

Sind da nicht 2 dereferenzierungen drinne?
1
(*(volatile union TCCR0A_union *)&(*(volatile uint8_t *)((0x24) + (0x20))).g.com0a |= 0b11;
2
_^_________________________________^

von (prx) A. K. (prx)


Lesenswert?

Ja. Es ändert aber nichts, wenn rechts kein Port sondern direkt eine 
Konstante steht, somit nur einmal dereferenziert wird.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

So, nun hab ich wieder etwas Zeit und eine anständige Tastatur vor der 
Nase... Etwas vereinfacht, ein paar Shifts und Ands weggelassen:

Eine Operation
  bitfield |= value
entspricht per Definition
  bitfield = bitfield | value

Zunächst die rechte Seite:
  temp1 = bitfield | value
abstract machine:
  load temp1 = mem
  temp1 = temp1 & mask | value

Dann die linke Seite:
  bitfield = temp1
abstract machine:
  load temp2 = mem
  temp2 = temp2 & ~mask | temp1
  store mem = temp2

Da sind also 2 Loads und ein Store drin.
Mit volatile bleibt das auch so.

Bei der Konstante 3 erkennt der Compiler, dass die rechte Seite auf 
ebendiese Konstante eindampft, weshalb der Load etwas einsam rumsteht. 
Weglassen ist nicht drin, weil volatile.

Bei
  bitfield = value
entfällt das Bitfeld auf der rechten Seite, daher gibts nur einen Load.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Sind da nicht 2 dereferenzierungen drinne?
>
>
1
> (*(volatile union TCCR0A_union *)&(*(volatile uint8_t *)((0x24) + 
2
> (0x20))).g.com0a |= 0b11;
3
> _^_________________________________^
4
>

Ja, aber auch dieser Code:

(*(volatile struct TCCR0A_grouped_bits *)((0x24) + 0x20)).com0a |= 0b11;
  46:  84 b5         in  r24, 0x24  ; 36
  48:  84 b5         in  r24, 0x24  ; 36
  4a:  80 6c         ori  r24, 0xC0  ; 192
  4c:  84 bd         out  0x24, r24  ; 36

macht 2x IN. Und da ist nur eine Dereferenzierung drin.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> So, nun hab ich wieder etwas Zeit und eine anständige Tastatur vor der
> Nase... Etwas vereinfacht, ein paar Shifts und Ands weggelassen:
>
> Eine Operation
>   bitfield |= value
> entspricht per Definition
>   bitfield = bitfield | value

Ja, das scheint der Knackpunkt zu sein. Sowohl links und rechts das 
bitfield, welches jeweils nach seinen Bitpositionen aufgelöst werden 
will.

Aber: Würde es gegen die volatile-Regeln verstoßen, wenn dieses bitfield 
nur einmal geladen und auf der temp-Variablen die Operation ausgeführt 
wird? So arbeitet jedenfalls das Macro BF_OR().

Schließlich führt ein

TCCR0A |= (1<<COM0A0);

auch nicht dazu, dass TCCR0A 2x per IN geladen wird. Okay, das ist ja 
auch kein Bitfield :-)

Noch eine Frage an Johann: Gäbe es einen Weg, dem Compiler beizubringen, 
dass ein mehrfacher IN von demselben SFR in dasselbe Register (z.B. r24) 
auf einen einzigen IN-Befehl eingedampft werden könnte?

von (prx) A. K. (prx)


Lesenswert?

Frank M. schrieb:
> auch nicht dazu, dass TCCR0A 2x per IN geladen wird. Okay, das ist ja
> auch kein Bitfield :-)

Eben. Bytes können direkt per store geschrieben werden, ohne auf 
load-modify-store Operationen zurückgreifen zu müssen. Bitfelder 
meistens nicht.

> Noch eine Frage an Johann: Gäbe es einen Weg, dem Compiler beizubringen,
> dass ein mehrfacher IN von demselben SFR in dasselbe Register (z.B. r24)
> auf einen einzigen IN-Befehl eingedampft werden könnte?

Klar. Lass das "volatile" in der Definition des SFRs weg. ;-)
Das hast du ja selber schon ausprobiert und die Folgen erlebt.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Klar. Lass das "volatile" in der Definition des SFRs weg. ;-)
> Das hast du ja selber schon ausprobiert und die Folgen erlebt.

Man bräuchte ein "halbes" volatile ;-)

von (prx) A. K. (prx)


Lesenswert?

Es gibt übrigens Prozessoren, die per Befehl auf Bitfelder im Speicher 
zugreifen können. Bei denen kriegst du unvermeidlich den gleichen 
Effekt, nur siehst du das nicht direkt:
  temp = bitfield-extract(mem, offset, length) | 3
  bitfield-insert(mem, offset, length) = temp
Auch hier sind effektiv 2 Leseoperationen drin.

Einzige mir bekannte Ausnahme: Der I/O-Bereich der TI 990 Familie, denn 
darin wurden keine Bytes oder Worte, sondern direkt Bits adressiert. 
Dementsprechend war eine Schreiboperation darin tatsächlich nur eine 
Schreiboperation von N Bits.

von (prx) A. K. (prx)


Lesenswert?

Frank M. schrieb:
> Man bräuchte ein "halbes" volatile ;-)

Ich frage mich schon ein Weilchen, weshalb du in Verbindung mit 
Bitfeldern so sehr auf assign-ops bestehst. Eigentlich besteht der Sinn 
von Bitfeldern in diesem Kontext doch genau darin, diese unnötig zu 
machen.

Also warum willst du unbedingt
  bitfield-length-2 |= 3
statt
  bitfield-length-2 = 3
schreiben?

von (prx) A. K. (prx)


Lesenswert?

Frank M. schrieb:
> Man bräuchte ein "halbes" volatile ;-)

Das dann an den Grenzen der sequence point wirksam würde?
Denn andere gibts nicht. Schlag es dem Komitee vor.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Frank M. schrieb:
> Aber: Würde es gegen die volatile-Regeln verstoßen, wenn dieses bitfield
> nur einmal geladen und auf der temp-Variablen die Operation ausgeführt
> wird?

Ich denke schon. Ich hatte meine Beschreibung nicht zufällig mit dem 
Text "abstract machine" versehen. Das ist jene abstrahierte 
Befehlsfolge, die sich ganz natürlich und frei jeder Optimierung aus dem 
Quellcode ergibt, wenn man hinter ihm Maschinencode sieht. Und volatile 
definiert per C-Standard, dass die real erfolgenden Zugriffe dieser 
"abstract machine" folgen müssen, nicht wegoptimiert werden dürfen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Frank M. schrieb:
>> Man bräuchte ein "halbes" volatile ;-)
>
> Ich frage mich schon ein Weilchen, weshalb du in Verbindung mit
> Bitfeldern so sehr auf assign-ops bestehst.

Du hast schon recht, gerade bei Bitfeldern reicht die Zuweisung. 
Bitweise Operatoren wir OR und AND braucht man damit eigentlich nicht 
mehr. Das war bei mir reiner Wunsch nach Symmetrie.

> Eigentlich besteht der Sinn
> von Bitfeldern in diesem Kontext doch genau darin, diese unnötig zu
> machen.
>
> Also warum willst du unbedingt
>   bitfield-length-2 |= 3
> statt
>   bitfield-length-2 = 3
> schreiben?

Wie gesagt: ein reiner Wunsch nach vollkommener Schönheit. ;-)

Aber ich sollte mich damit zufrieden geben, dass immerhin das Resultat 
korrekt ist.

Ausserdem gibt es ja dank der union noch jedes Bit einzeln. Ich kann 
auch mit

  COM0A0 = 1;

ein einzelnes Bit setzen statt

  COM0A |= 0b01;

zu schreiben.

: Bearbeitet durch Moderator
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Ich denke schon. Ich hatte meine Beschreibung nicht zufällig mit dem
> Text "abstract machine" versehen.

Ich muss Dir - wenn auch zähneknirschend - recht geben.

Das angesprochene SFR-Bitfield könnte ja auch ein HW-Zähler sein, 
welcher bei jedem Zugriff inkrementiert. Von daher ist es logisch nur 
richtig, dass hier 2 IN-Befehle ausgeführt werden.

Mich kratzt halt nur die mangelnde Symmetrie. Wenn das Bitfield so breit 
wie ein uint8_t wäre, dann müsste es konsequenterweise ebenso zu 2 x IN 
kommen.

Tut es aber nicht:

struct TCCR0A_grouped_bits
{
    uint8_t wgm0:8;     // Phantasie-Struct
};

Ergebnis:

    BFM_WGM0 |= 0x01;
  46:  84 b5         in  r24, 0x24  ; 36
  48:  81 60         ori  r24, 0x01  ; 1
  4a:  84 bd         out  0x24, r24  ; 36

Perfekt!

Jetzt mit 7 Bits:

struct TCCR0A_grouped_bits
{
    uint8_t wgm0:7;     // Phantasie-Struct
};

Ergebnis:

    BFM_WGM0 |= 0x01;
  46:  94 b5         in  r25, 0x24  ; 36
  48:  9f 77         andi  r25, 0x7F  ; 127
  4a:  91 60         ori  r25, 0x01  ; 1
  4c:  84 b5         in  r24, 0x24  ; 36
  4e:  80 78         andi  r24, 0x80  ; 128
  50:  89 2b         or  r24, r25
  52:  84 bd         out  0x24, r24  ; 36

Fazit: Je breiter das Bitfield ist, desto kruder ist der Weg, den gcc 
nimmt. Erst bei der Breite von 8 ist wieder die Welt in Ordnung.

Irgendwie inkonsequent, oder? ;-)

Nachtrag:

Dennoch kann ich es verstehen. gcc muss hier Rücksicht nehmen auf das 
8te Bit, welches nicht angetastet werden darf bzw. so aktuell wie 
möglich sein muss. Deshalb ist es auch hier richtig, vor dem Schreiben 
nochmal den aktuellen Wert des SFRs zu holen und mit diesem zu agieren.

Bei vollem 8-Bit-Zugriff muss hier keine Rücksicht auf etwaige 
verbliebene Bits genommen werden. Das erklärt die daraus resultierende 
Asymmetrie.

: Bearbeitet durch Moderator
von (prx) A. K. (prx)


Lesenswert?

Frank M. schrieb:
> Irgendwie inkonsequent, oder? ;-)

Ja. Aber was wird hier draus, wenns kein AVR ist? ;-)
  unsigned a: 8,
           b: 16,
           c: 8;

Ich schrieb oben auch nicht ganz zufällig, dass Bitfelder ein Minenfeld 
sind. GCC hatte seine liebe Not damit. Beispielsweise darin, die 
richtige Zugriffsbreite zu wählen: 
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=23623

Bitfelder verbergen auch den Umstand, dass technisch vielleicht auf 
Felder zugegriffen wird, die im Statement nicht drinstehen. Wer also auf 
einer 32-Bit CPU sein Device als
   unsigned b1 : 4,
            b2 : 4,  // dein Hardware-Counter
            b3 : 8,
            b4 : 16;
definiert der darf sich nun fragen, ob auf b3 und b4 nun als 32-Bit Wort 
zugegriffen wird (das wäre vmtl. korrekt), oder doch nicht, und ob das 
bei 2 Compilern und deren Versionen nicht zu 3 Varianten führt.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Ich schrieb oben auch nicht ganz zufällig, dass Bitfelder ein Minenfeld
> sind. GCC hatte seine liebe Not damit. Beispielsweise darin, die
> richtige Zugriffsbreite zu wählen:
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=23623

Ich plädiere für die 1-Bit-CPU! :-)

>
> Bitfelder verbergen auch den Umstand, dass technisch vielleicht auf
> Felder zugegriffen wird, die im Statement nicht drinstehen. Wer also auf
> einer 32-Bit CPU sein Device als
>    unsigned b1 : 4,
>             b2 : 4,  // dein Hardware-Counter
>             b3 : 8,
>             b4 : 16;
> definiert der darf sich nun fragen, ob auf b3 und b4 nun als 32-Bit Wort
> zugegriffen wird (das wäre vmtl. korrekt), oder doch nicht, und ob das
> bei 2 Compilern und deren Versionen nicht zu 3 Varianten führt.

Auch hier lösen sich alle Probleme in Luft auf, wenn die "natürliche 
Breite" der CPU 1 Bit wäre :-)

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.