Как работать с 128 бит C переменной и xmm 128 бит asm?

в gcc, я хочу сделать 128 бит xor с двумя переменными C, через asm-код: как?

asm (
    "movdqa %1, %%xmm1;"
    "movdqa %0, %%xmm0;"
     "pxor %%xmm1,%%xmm0;"
     "movdqa %%xmm0, %0;"

    :"=x"(buff) /* output operand */
    :"x"(bu), "x"(buff)
    :"%xmm0","%xmm1"
    );

но я имею ошибку ошибки сегментации; это вывод objdump:

movq   -0x80(%rbp),%xmm2

movq   -0x88(%rbp),%xmm3

movdqa %xmm2,%xmm1

movdqa %xmm2,%xmm0

pxor   %xmm1,%xmm0

movdqa %xmm0,%xmm2

movq   %xmm2,-0x78(%rbp)

Ответ 1

Вы увидите проблемы segfault, если переменные не выровнены по 16 байт. ЦП не может MOVDQA в/из неаудированных адресов памяти и будет генерировать "исключение GP" на уровне процессора, предлагая ОС segfault your приложение.

C переменные, которые вы объявляете (стек, глобальные) или выделяете в куче, обычно не привязаны к границе 16 байтов, хотя иногда вы можете получить выровненный по одному. Вы можете направить компилятор для обеспечения правильного выравнивания с использованием типов данных __m128 или __m128i. Каждый из них объявляет правильно выровненное 128-битное значение.

Далее, прочитав objdump, похоже, что компилятор завернул последовательность asm с кодом, чтобы скопировать операнды из стека в регистры xmm2 и xmm3, используя инструкцию MOVQ, только чтобы ваш код asm затем скопировал значения в xmm0 и xmm1. После xor-ing в xmm0 оболочка копирует результат в xmm2, а затем копирует его обратно в стек. В целом, не очень эффективно. MOVQ копирует 8 байтов за раз, и ожидает (при некоторых обстоятельствах) 8-байтового выровненного адреса. Получив неравномерный адрес, он может потерпеть неудачу, как MOVDQA. Однако код-оболочка добавляет выровненное смещение (-0x80, -0x88 и later -0x78) в регистр ВР, который может содержать или не содержать выровненное значение. В целом, нет никакой гарантии выравнивания в сгенерированном коде.

Следующее гарантирует, что аргументы и результат сохраняются в правильно выровненных ячейках памяти и, кажется, работают нормально:

#include <stdio.h>
#include <emmintrin.h>

void print128(__m128i value) {
    int64_t *v64 = (int64_t*) &value;
    printf("%.16llx %.16llx\n", v64[1], v64[0]);
}

void main() {
    __m128i a = _mm_setr_epi32(0x00ffff00, 0x00ffff00, 0x00ffff00, 0x10ffff00), /* low dword first! */
            b = _mm_setr_epi32(0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff),
            x;

    asm (
        "movdqa %1, %%xmm0;"      /* xmm0 <- a */
        "movdqa %2, %%xmm1;"      /* xmm1 <- b */
        "pxor %%xmm1, %%xmm0;"    /* xmm0 <- xmm0 xor xmm1 */
        "movdqa %%xmm0, %0;"      /* x <- xmm0 */

        :"=x"(x)          /* output operand, %0 */
        :"x"(a), "x"(b)   /* input operands, %1, %2 */
        :"%xmm0","%xmm1"  /* clobbered registers */
    );

    /* printf the arguments and result as 2 64-bit hex values */
    print128(a);
    print128(b);
    print128(x);
}

скомпилировать с (gcc, ubuntu 32 бит)

gcc -msse2 -o app app.c

выход:

10ffff0000ffff00 00ffff0000ffff00
0000ffff0000ffff 0000ffff0000ffff
10ff00ff00ff00ff 00ff00ff00ff00ff

В приведенном выше коде _mm_setr_epi32 используется для инициализации a и b со 128-битными значениями, поскольку компилятор может не поддерживать 128 целых литералов.

print128 записывает шестнадцатеричное представление целочисленного 128-битного числа, поскольку printf не может этого сделать.


Ниже приведено краткое и позволяет избежать дублирования копирования. Компилятор добавляет скрытую оболочку movdqa, чтобы сделать magor% 2,% 0 волшебным образом работать без необходимости загружать регистры самостоятельно:

#include <stdio.h>
#include <emmintrin.h>

void print128(__m128i value) {
    int64_t *px = (int64_t*) &value;
    printf("%.16llx %.16llx\n", px[1], px[0]);
}

void main() {
    __m128i a = _mm_setr_epi32(0x00ffff00, 0x00ffff00, 0x00ffff00, 0x10ffff00),
            b = _mm_setr_epi32(0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff);

    asm (
        "pxor %2, %0;"    /* a <- b xor a  */

        :"=x"(a)          /* output operand, %0 */
        :"x"(a), "x"(b)   /* input operands, %1, %2 */
        );

    print128(a);
}

скомпилировать по-прежнему:

gcc -msse2 -o app app.c

выход:

10ff00ff00ff00ff 00ff00ff00ff00ff

В качестве альтернативы, если вы хотите избежать встроенной сборки, вы можете использовать SSE intrinsics вместо (PDF). Это встроенные функции/макросы, которые инкапсулируют инструкции MMX/SSE синтаксисом типа C. _mm_xor_si128 уменьшает вашу задачу до одного вызова:

#include <stdio.h>
#include <emmintrin.h>

void print128(__m128i value) {
    int64_t *v64 = (int64_t*) &value;
    printf("%.16llx %.16llx\n", v64[1], v64[0]);
}

void main()
{
    __m128i x = _mm_xor_si128(
        _mm_setr_epi32(0x00ffff00, 0x00ffff00, 0x00ffff00, 0x10ffff00), /* low dword first !*/
        _mm_setr_epi32(0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff));

    print128(x);
}

компиляции:

gcc -msse2 -o app app.c

выход:

10ff00ff00ff00ff 00ff00ff00ff00ff

Ответ 2

Умм, почему бы не использовать встроенный __builtin_ia32_pxor?

Ответ 3

В поздней модели gcc (шахта - 4.5.5) опция -O2 или выше подразумевает -fstrict-aliasing, которая приводит к жалобе, указанному выше:

supersuds.cpp:31: warning: dereferencing pointer ‘v64’ does break strict-aliasing rules
supersuds.cpp:30: note: initialized from here

Это можно устранить, предоставив дополнительные атрибуты типа следующим образом:

typedef int64_t __attribute__((__may_alias__)) alias_int64_t; 
void print128(__m128i value) {
    alias_int64_t *v64 = (int64_t*)  &value;
    printf("%.16lx %.16lx\n", v64[1], v64[0]); 
}

Сначала я попробовал атрибут напрямую без typedef. Это было принято, но я все еще получил предупреждение. Кажется, что typedef является необходимой частью волшебства.

Кстати, это мой второй ответ здесь, и я все еще ненавижу тот факт, что еще не могу сказать, где мне разрешено редактировать, поэтому я не смог опубликовать его там, где он был.

И еще одно: в AMD64 спецификатор формата% llx нужно изменить на% lx.