Wie kann ich "_mm_storeu_epi64" ohne Aliasing-Probleme implementieren?

(Hinweis: Obwohl es sich bei dieser Frage um "Speichern" handelt, hat der "Laden" -Fall dieselben Probleme und ist perfekt symmetrisch.)

Die SSE-Intrinsics bieten eine_mm_storeu_pd Funktion mit folgender Signatur:

void _mm_storeu_pd (double *p, __m128d a);

Wenn ich also einen Vektor aus zwei Doppeln habe und ihn in einem Array aus zwei Doppeln speichern möchte, kann ich einfach diese Eigenschaft verwenden.

Mein Vektor besteht jedoch nicht aus zwei Doppelwerten. Es sind zwei 64-Bit-Ganzzahlen, und ich möchte sie in einem Array mit zwei 64-Bit-Ganzzahlen speichern. Das heißt, ich möchte eine Funktion mit der folgenden Signatur:

void _mm_storeu_epi64 (int64_t *p, __m128i a);

Aber die inneren Eigenschaften bieten keine solche Funktion. Das nächste, das sie haben, ist_mm_storeu_si128:

void _mm_storeu_si128 (__m128i *p, __m128i a);

Das Problem ist, dass diese Funktion einen Zeiger auf nimmt__m128i, während mein Array ein Array von istint64_t. Das Beschreiben eines Objekts mit dem falschen Zeigertyp ist eine Verletzung vonstriktes Aliasing und ist definitiv undefiniertes Verhalten. Ich mache mir Sorgen, dass mein Compiler jetzt oder in Zukunft den Speicher neu ordnet oder auf andere Weise optimiert, wodurch mein Programm auf seltsame Weise beschädigt wird.

Um klar zu sein, was ich möchte, ist eine Funktion, die ich wie folgt aufrufen kann:

__m128i v = _mm_set_epi64x(2,1);
int64_t ra[2];
_mm_storeu_epi64(&ra[0], v); // does not exist, so I want to implement it

Hier sind sechs Versuche, eine solche Funktion zu erstellen.

Versuch 1
void _mm_storeu_epi64(int64_t *p, __m128i a) {
    _mm_storeu_si128(reinterpret_cast<__m128i *>(p), a);
}

Dies scheint das strikte Aliasing-Problem zu haben, über das ich mir Sorgen mache.

Versuch 2
void _mm_storeu_epi64(int64_t *p, __m128i a) {
    _mm_storeu_si128(static_cast<__m128i *>(static_cast<void *>(p)), a);
}

Möglicherweise besser im Allgemeinen, aber ich glaube nicht, dass es in diesem Fall einen Unterschied macht.

Versuch 3
void _mm_storeu_epi64(int64_t *p, __m128i a) {
    union TypePun {
        int64_t a[2];
        __m128i v;
     };
    TypePun *p_u = reinterpret_cast<TypePun *>(p);
    p_u->v = a;
}

Dies erzeugt falschen Code auf meinem Compiler (GCC 4.9.0), der ein Alignment ausgibtmovaps Anweisung anstelle eines nicht ausgerichtetenmovups. (Die Gewerkschaft ist ausgerichtet, so dass diereinterpret_cast Tricks GCC in die Annahmep_u ist auch ausgerichtet.)

Versuch 4
void _mm_storeu_epi64(int64_t *p, __m128i a) {
    union TypePun {
        int64_t a[2];
        __m128i v;
     };
    TypePun *p_u = reinterpret_cast<TypePun *>(p);
    _mm_storeu_si128(&p_u->v, a);
}

Dies scheint den von mir gewünschten Code auszugeben. Der Trick "Typ-Punning via Union"technisch undefiniert in C ++istweit verbreitet. Aber ist dieses Beispiel, in dem ich einen Zeiger auf ein Element einer Union übergebe, anstatt über die Union selbst darauf zuzugreifen, wirklich eine gültige Methode, um die Union für das Typ-Punning zu verwenden?

Versuch 5
void _mm_storeu_epi64(int64_t *p, __m128i a) {
    p[0] = _mm_extract_epi64(a, 0);
    p[1] = _mm_extract_epi64(a, 1);
}

Das funktioniert und ist vollkommen gültig, aber es gibt zwei Anweisungen anstelle von einer aus.

Versuch 6
void _mm_storeu_epi64(int64_t *p, __m128i a) {
    std::memcpy(p, &a, sizeof(a));
}

Das funktioniert und ist absolut gültig ... denke ich. Aber es gibt ehrlich gesagt schrecklichen Code auf meinem System aus. GCC verschütteta über einen ausgerichteten Speicher an einen ausgerichteten Stapelplatz und verschiebt dann die Komponentenwörter manuell an das Ziel. (Eigentlich wird es zweimal verschüttet, einmal für jede Komponente. Sehr seltsam.)

...

Gibt es eine Möglichkeit, diese Funktion so zu schreiben, dass (a) auf einem typischen modernen Compiler ein optimaler Code generiert wird und (b) nur ein minimales Risiko besteht, dass ein striktes Aliasing vorliegt?

Antworten auf die Frage(1)

Ihre Antwort auf die Frage