Как реализовать «_mm_storeu_epi64» без проблем с алиасами?

(Примечание. Хотя этот вопрос касается «магазина», случай «загрузки» имеет те же проблемы и является абсолютно симметричным.)

Встроенные функции SSE обеспечивают_mm_storeu_pd функция со следующей подписью:

void _mm_storeu_pd (double *p, __m128d a);

Так что, если у меня есть вектор двух двойных чисел, и я хочу сохранить его в массиве двух двойных чисел, я могу просто использовать эту встроенную функцию.

Тем не менее, мой вектор не два двойных; это два 64-разрядных целых числа, и я хочу сохранить его в массиве из двух 64-разрядных целых чисел. То есть мне нужна функция со следующей подписью:

void _mm_storeu_epi64 (int64_t *p, __m128i a);

Но внутренние функции не обеспечивают такой функции. Ближайший у них есть_mm_storeu_si128:

void _mm_storeu_si128 (__m128i *p, __m128i a);

Проблема в том, что эта функция принимает указатель на__m128iв то время как мой массив является массивомint64_t, Запись в объект через указатель неправильного типа является нарушениемстрогое наложение и определенно неопределенное поведение. Я обеспокоен тем, что мой компилятор, сейчас или в будущем, будет переупорядочивать или иным образом оптимизировать хранилище, таким образом, ломая мою программу странными способами.

Чтобы было ясно, я хочу вызвать функцию, подобную этой:

__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

Вот шесть попыток создать такую ​​функцию.

Попытка № 1
void _mm_storeu_epi64(int64_t *p, __m128i a) {
    _mm_storeu_si128(reinterpret_cast<__m128i *>(p), a);
}

Это, кажется, имеет строгую проблему с алиасами, о которой я беспокоюсь.

Попытка № 2
void _mm_storeu_epi64(int64_t *p, __m128i a) {
    _mm_storeu_si128(static_cast<__m128i *>(static_cast<void *>(p)), a);
}

Возможно лучше вообще, но я не думаю, что это имеет значение в этом случае.

Попытка № 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;
}

Это создает неправильный код на моем компиляторе (GCC 4.9.0), который выдает выровненныйmovaps инструкция вместо выравниванияmovups, (Союз выровнен, поэтомуreinterpret_cast обманывает GCC в предположенииp_u выравнивается тоже.)

Попытка № 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);
}

Это, кажется, испускает код, который я хочу. Трюк "типа наказания через союз", хотятехнически неопределенный в C ++, являетсяшироко поддерживается, Но является ли этот пример - где я передаю указатель на элемент объединения, а не доступ через сам союз - действительно ли правильный способ использовать объединение для наложения типов?

Попытка № 5
void _mm_storeu_epi64(int64_t *p, __m128i a) {
    p[0] = _mm_extract_epi64(a, 0);
    p[1] = _mm_extract_epi64(a, 1);
}

Это работает и совершенно правильно, но выдает две инструкции вместо одной.

Попытка № 6
void _mm_storeu_epi64(int64_t *p, __m128i a) {
    std::memcpy(p, &a, sizeof(a));
}

Это работает и совершенно правильно ... Я думаю. Но он излучает откровенно ужасный код в моей системе. GCC разливовa в выровненный слот стека через выровненное хранилище, затем вручную перемещает слова компонента в место назначения. (На самом деле это разливается дважды, по одному разу для каждого компонента. Очень странно.)

...

Есть ли способ написать эту функцию, которая (а) будет генерировать оптимальный код на типичном современном компиляторе и (б) будет иметь минимальный риск столкновения со строгим псевдонимом?

Ответы на вопрос(1)

Ваш ответ на вопрос