Forum: Compiler & IDEs GCC 7.2.0, -Os auf armv7e-m target -> unaligned access


von Vincent H. (vinci)


Lesenswert?

Guten Abend

Ich bin heut Abend über einen möglichen Bug in der aktuellen GCC Version 
7.2.0 gestolpert. Und zwar erzeugt mir ein Build mit der Optimierung -Os 
einen "Double Word Store (STRD)" mit einer ungeraden (!%4) Adresse.

Die (wichtigen) Flags sind jene hier:
1
arm-none-eabi-g++ -mcpu=cortex-m4 -march=armv7e-m -mthumb -mlittle-endian -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Os


Und irgendein SIMD Befehl schmeißt dann folgendes raus:
1
-os (r3 = 536903210 0x20007e2a)
2
strd    r4, r4, [r3, #-16]

Zum Vergleich die selbe Codestelle mit -O2:
1
-o2 (r3 = 536902524 0x20007b7c)
2
strd    r5, r5, [r3, #-8]


Ist da zufällig schonmal wer drüber gestolpert?
Der Unterschied zw. -O2 und -Os ist ja relativ klein, laut GCC Doku sind 
leidiglich folgende Flags betroffen:
1
-falign-functions  -falign-jumps  -falign-loops 
2
-falign-labels  -freorder-blocks  -freorder-blocks-algorithm=stc 
3
-freorder-blocks-and-partition  -fprefetch-loop-arrays


Hat da vielleicht wer eine Idee was da für die falsche Adresse beim 
Store zutreffen könnte? Vielleicht das -falign-labels...?


lg
Vincent

von Dr. Sommer (Gast)


Lesenswert?

Vincent H. schrieb:
> Ist da zufällig schonmal wer drüber gestolpert?
Schon viele. Das ist wahrscheinlich kein Fehler im GCC, sondern in 
deinem Programm. Klassischer Kandidat wäre sowas:
1
double get (char* x) {
2
  return *((double*) x);
3
}
Glücklicherweise ist so ein Code verboten, weshalb so etwas "eigentlich" 
nicht auftritt. Zeig mal deinen Code, vielleicht lässt sich ja direkt 
das Problem erkennen.

von Vincent H. (vinci)


Lesenswert?

Type-punning, undefined behavior und aliasing sind mir keine Fremdworte. 
Der Code ist aber amüsanterweise eine CMSIS-Funktion namens 
"arm_fill_q15".

Und wie du richtig vermutet hast findet sich in Zeile 75, 76 der 
Übeltäter:
1
    *__SIMD32(pDst)++ = packedValue;
2
    *__SIMD32(pDst)++ = packedValue;

Soweit sogut. Die Sache is aber, dass -O2 und -O3, die ja ebenfalls 
"-fstrict-aliasing" einschalten, den Fehler nicht produzieren. 
Vermutlich, weil man wohl aus Geschwindigkeitsgründen hier komplett 
aligned.

Das Problem ist scheinbar, dass die beiden Instruktionen hintereinander 
bei -Os den besagten "Double Word Store" provizieren... Ganz verstehen 
tu ich das aber trotzdem nicht. Die Instruktion darf es ja per Reference 
Manual der v7 Architektur aus in der Form überhaupt nicht geben.

Aus dem Grund kann man das in meinen Augen auch nicht mit dem 
"klassischen" load/store unaligned/aligned Problem vergleichen. Ein 
normales ldr/str kann je nach M0/M3 und Einstellungen halt gehen... oder 
halt auch nicht. Ein strd mit einer ungeraden Adresse gibts aber nie, 
auf gar keinem Core. Weshalb forciert der Compiler das nicht?

von Dr. Sommer (Gast)


Lesenswert?

Vincent H. schrieb:
> Die Instruktion darf es ja per Reference
> Manual der v7 Architektur aus in der Form überhaupt nicht geben.
Die Instruktion schreibt 2 Wörter an eine per Register übergebene 
variable Adresse. Was soll es daran nicht geben?

Vincent H. schrieb:
> Ein strd mit einer ungeraden Adresse gibts aber nie,
> auf gar keinem Core. Weshalb forciert der Compiler das nicht?
Weil der Compiler nicht die Adresse eines jeden Pointers im Code kennt? 
Die wird ja hier als Funktionsargument übergeben.

Vincent H. schrieb:
> Aus dem Grund kann man das in meinen Augen auch nicht mit dem
> "klassischen" load/store unaligned/aligned Problem vergleichen.
Es ist aber unter den klassischen alignment Definitionen im ARMv7M 
Architecture Reference Manual, auf S. 65, definiert.

Prüfe doch mal den Wert des pDst Paramters, vielleicht ist der ja schon 
verkehrt.

STRD ist auch keine SIMD-Instruktion, sondern ein schnödes Store-Double.

von Vincent H. (vinci)


Lesenswert?

Stimmt natürlich, der häßliche C-Cast hat mich da verleitet und so 
frisch bin ich auch nicht mehr.

Zeit die Daten mit ein wenig Alignment zu beglücken.

Danke!

von Adib (Gast)


Lesenswert?

Hallo Dr. Sommer,

Wie würde in dem Beispiel der korrekte Code aussehen.

Ich benutze dieses Art des castings, um zB gepackte Structures auf 
Bytearrays zu legen und umgekehrt.
Die Bytearrays werden dann zB per Uart gesendet/empfangen.

Bis jetzt hatte ich zum Glück noch keine Probleme.
Ich benutze CortexM4.

Danke, Adib.

von Nop (Gast)


Lesenswert?

Adib schrieb:

> Ich benutze dieses Art des castings, um zB gepackte Structures auf
> Bytearrays zu legen und umgekehrt.
> Die Bytearrays werden dann zB per Uart gesendet/empfangen.

Wenn die Struktur so aufgebaut ist, daß die ints auf geraden Adressen 
landen, etwa indem die Variablen von groß nach klein angeordnet sind, 
dann passiert da auch nichts.

Man sollte sich aber Gedanken um Endianess machen.

von Markus F. (mfro)


Lesenswert?

Adib schrieb:
> Ich benutze dieses Art des castings, um zB gepackte Structures auf
> Bytearrays zu legen

das darf man.

> und umgekehrt.

das nicht.

von Adib (Gast)


Lesenswert?

Markus F. schrieb:
>> und umgekehrt.
>
> das nicht.

Hmm, aber wie macht man es jetzt "richtig" also ohne Fehler. ???

Ich mache das eigentlich immer so wie im Code unten.
Der Parser bekommt die Byte-Daten von der serielllen Schnittstelle.
Wenn er ein komplettes Protokoll erkennt, übergibt er den pointer auf 
die Daten an den Decoder, der sich dann die Daten anschaut.

Nach dem Thread hier sollte genau das ja falsch sein.

Ich habe schon gesehen, dass der deserialiser die Daten in eine lokale 
Variable (Struktur) per memcpy kopiert. Möchte aber unnötiges memcpy 
vermeiden.
Würde es nicht auch reichen, sicherzustellen dass der parserbuff auf 
einer durch 4-teilbaren Addresse liegt?

Danke und Grüße, Adib.
1
struct data_struct_t
2
{
3
  uint8_t    framestart1;
4
  uint8_t    framestart2;
5
  uint8_t    byte_data;
6
  uint16_t  int_data;
7
  float  float_data;
8
} __attribute__((__packed__));
9
typedef struct data_struct_t data_t;
10
11
12
void deserialiser(uint8_t *buffer)
13
{
14
    data_t *ptr = (data_t *)buffer;
15
16
    myfunct(ptr->int_data, ptr->float_data);
17
}
18
19
void parser(uint8_t bytedata)
20
{
21
    static uint8_t parserbuf[100];
22
    static int parseridx = 0;
23
    
24
    parserbuf[parseridx++] = bytedata;
25
    
26
    if(datacomplete()) {
27
        deserialiser(parserbuf);
28
        parseridx = 0;
29
    }
30
}

von Markus F. (mfro)


Lesenswert?

Ein "Downcast" nach unsigned char * ist für jeden Zeigertyp erlaubt.

Wenn Du also beispielsweise deinen Empfangsbuffer im Hauptprogramm als 
struct-Array deklarierst und deiner Empfangsroutine auf unsigned char * 
gecasted übergibst, ist alles gut.

Das Problem ist hier nicht nur Alignment. Pointer aliasing kann 
durchaus auch bewirken, dass der Compiler in optimiertem Code 
Zuweisungen über "gealiaste" Zeiger (die ja nicht sein dürfen) einfach 
wegoptimiert. Wenn Du das verhindern willst, kannst/musst Du bei gcc mit 
-fno-strict-aliasing arbeiten und damit auf das letzte Quäntchen 
Optimierung verzichten.

von Nop (Gast)


Lesenswert?

Markus F. schrieb:
> Wenn Du das verhindern willst, kannst/musst Du bei gcc mit
> -fno-strict-aliasing arbeiten und damit auf das letzte Quäntchen
> Optimierung verzichten.

Wobei man auch ausmessen sollte, ob die Performance überhaupt beeinflußt 
wird - meiner Erfahrung nach nicht. Zumindest für Release-Builds würde 
ich das daher sicherheitshalber immer abschalten.

Anders mag es aussehen, wenn man mathematische Berechnungen mit Vektoren 
und Matrizen hat, wo im Prinzip alles letztlich auf float oder double 
zeigt, aber das kriegt man mit Aliasing auch nicht in den Griff, weil 
das ja erlaubt ist; dafür gibt's dann restrict.

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.