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
structTCCR0A_bits
6
{
7
uint8_twgm00:1;
8
uint8_twgm01:1;
9
uint8_t:1;
10
uint8_t:1;
11
uint8_tcom0b0:1;
12
uint8_tcom0b1:1;
13
uint8_tcom0a0:1;
14
uint8_tcom0a1:1;
15
}__attribute__((__packed__));
16
17
structTCCR0A_grouped_bits
18
{
19
uint8_twgm0:2;// WGM00 und WGM01
20
uint8_t:2;
21
uint8_tcom0b:2;// COM0B0 und COM0B1
22
uint8_tcom0a:2;// COM0A0 und COM0A1
23
}__attribute__((__packed__));
24
25
unionTCCR0A_union
26
{
27
structTCCR0A_bitsb;
28
structTCCR0A_grouped_bitsg;
29
uint8_tv;
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)
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:
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
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.
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.
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
structTCCR0B_bits
2
{
3
uint8_tcs00:1;
4
uint8_tcs01:1;
5
uint8_tcs02:1;
6
uint8_twgm02:1;
7
uint8_t:1;
8
uint8_t:1;
9
uint8_tfoc0b:1;
10
uint8_tfoc0a:1;
11
}__attribute__((__packed__));
12
13
structTCCR0B_grouped_bits
14
{
15
uint8_tcs0:3;
16
uint8_twgm02:1;
17
uint8_t:2;
18
uint8_tfoc0b:1;
19
uint8_tfoc0a:1;
20
}__attribute__((__packed__));
21
22
unionTCCR0B_union
23
{
24
structTCCR0B_bitsb;
25
structTCCR0B_grouped_bitsg;
26
uint8_tv;
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.
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.
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.
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;
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.
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 :-)
(*(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" :-)
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:
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.
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:
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. :-(
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:
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.
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.
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 ...
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.
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 :-)
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;
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.
> (*(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.
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?
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.
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 ;-)
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.
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?
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.
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.
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.
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.
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.
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 :-)