Логические операции над векторами

Последнее обновление: 02.10.2023

Расширения SSE/AVX предоставляют следующие поразрядные логические операции, которые выполняются над векторами чисел:

  • andpd: логическое умножение dest = dest and source (128-разрядные операнды)

  • vandpd: логическое умножение dest = source1 and source2 (128- или 256-разрядные операнды)

  • andnpd: логическое умножение с отрицанием (NAND) dest = dest and ~source (128-разрядные операнды)

  • vandnpd: логическое умножение с отрицанием (NAND) dest = source1 and ~source2 (128- или 256-разрядные операнды)

  • orpd: логическое сложение dest = dest | source (128-разрядные операнды)

  • vorpd: логическое сложение dest = source1 | source2 (128- или 256-разрядные операнды)

  • xorpd: операция XOR dest = dest ^ source (128-разрядные операнды)

  • vxorpd: операция XOR dest = source1 ^ source2 (128- или 256-разрядные операнды)

Синтаксис инструкций:

andpd xmmsrc/mem128, xmmdest
vandpd xmmsrc2/mem128, xmmsrc1, xmmdest
vandpd ymmsrc2/mem256, ymmsrc1, ymmdest
andnpd xmmsrc/mem128, xmmdest
vandnpd xmmsrc2/mem128, xmmsrc1, xmmdest
vandnpd ymmsrc2/mem256, ymmsrc1, ymmdest
orpd xmmsrc/mem128, xmmdest
vorpd xmmsrc2/mem128, xmmsrc1, xmmdest
vorpd ymmsrc2/mem256, ymmsrc1, ymmdest
xorpd xmmsrc/mem128, xmmdest
vxorpd xmmsrc2/mem128, xmmsrc1, xmmdest
vxorpd ymmsrc2/mem256, ymmsrc1, ymmdest

Инструкции SSE (без префикса v) оставляют старшие биты в целевом регистре YMM без изменений. Инструкции AVX (с префиксом v), которые имеют 128-битные операнды, заполняют старшие 128 бит регистра YMM нулями. Если первый операнд является переменной, он должен быть выровнен по соответствующей границе (например, 16 байтов для значений mem128 и 32 байта для значений mem256). Невыполнение этого требования приведет к ошибке выравнивания памяти во время выполнения.

Пример применения:

.globl _start

.data
nums0: .long 0, 1, 0, 1
nums1: .long 0, 1, 1, 0
.text
_start: 
    movaps nums0, %xmm0     # вектор num0 в регистр xmm0
    movaps nums1, %xmm1     # вектор num1 в регистр xmm1
    andpd %xmm1, %xmm0      # XMM0 = 0, 1, 0, 0

    movq $60, %rax
    syscall

Здесь в регистры XMM0 и XMM1 загружаются соответственно векторы nums0 и nums1. Далее к элементам этих векторов применяется логическая операции AND, которая возвращает 1, если только обы разряда двух операндов равны 1. То есть мы получим следующие вычисления:

0, 1, 0, 1
*
0, 1, 1, 0
=
0, 1, 0, 0

В итоге в регистре XMM0 будет содержаться вектор 0, 1, 0, 0

Мы можем это проверить, выведя значения на консоль:

.globl main

.data
nums0: .long 0, 1, 0, 1
nums1: .long 0, 1, 1, 0
format_str: .asciz "%d, %d, %d, %d\n"
.text
main: 
    subq $8, %rsp
    movaps nums0, %xmm0     # вектор num0 в регистр xmm0
    movaps nums1, %xmm1     # вектор num1 в регистр xmm1
    andpd %xmm1, %xmm0      # XMM0 = 0, 1, 0, 0

    movd %xmm0, %esi        # помещаем первое число в %rsi
    psrldq $4, %xmm0        # сдвигаем вправо на 4 бита - к следующему числу в векторе в xmm0
    movd %xmm0, %edx        # помещаем первое число в %rdx
    psrldq $4, %xmm0        # сдвигаем вправо на 4 бита - к следующему числу в векторе в xmm0
    movd %xmm0, %ecx        # помещаем первое число в %rcx
    psrldq $4, %xmm0        # сдвигаем вправо на 4 бита - к следующему числу в векторе в xmm0
    movd %xmm0, %r8d        # помещаем первое число в %r8

    movq $format_str, %rdi
    call printf

    addq $8, %rsp
    ret

Для вывода значений на консоль здесь используется библиотечная функция языка Си - printf. Соответственно для взаимодействия с Си программа определена как функция main. После выполнения операции AND каждое отдельное число вектора в %xmm0 помещаем в регистры %rsi, %edx, %ecx, %r8d для передачи чисел в функцию printf. Пример компиляции и работы программы:

root@Eugene:~/asm# gcc -static hello.s -o hello
root@Eugene:~/asm# ./hello
0, 1, 0, 0
root@Eugene:~/asm#

Другой пример - манипуляция со строками. Например, переведем строку в нижний регистр:

.globl main

.data
str: .asciz "HeLLo"
maxlen = .-str - 1  # вычитаем также концевой нулевой байт
.balign 16  # для заполнения оставшегося пространства строки str нулями

mask1:  .fill maxlen, 1, 0x20
        .fill 16-maxlen, 1, 0

mask2:  .fill maxlen, 1, 0xDF
        .fill 16-maxlen, 1, 0
       
format_str: .asciz "%s\n"
.text
main: 
    subq $8, %rsp
    movdqa str, %xmm0     # копируем строку из str в регистр xmm0
    orpd mask1, %xmm0  # переводим в нижний регистр
    # andpd mask2, %xmm0  # переводим в верхний регистр

    movdqa %xmm0, str     # копируем строку из регистр xmm0 в str

    movq $format_str, %rdi
    movq $str, %rsi
    call printf

    addq $8, %rsp
    ret

Тестовая строка в нашем случае представляет переменную str и имеет значение "HeLLo". Для простоты примера предположим, что строка не больше 16 байт, с учетом концевого нулевого байта. Буквы в нижнем регистре отличаются от букв в верхнем регистре в таблице ASCII установленным битом 5 (нумерация битов с нуля). Например, буква "A" имеет двоичный код 01000001, а буква "a" - 01100001. То есть, чтобы перейти от буквы в верхнем регистре к букве в нижнем регистре, нам надо устровить бит 5. И для этого определяем маску:

mask1:  .fill maxlen, 1, 0x20
        .fill 16-maxlen, 1, 0

То есть здесь определяем набор из maxlen байтов, каждый из которых равен 0x20 или 0b00100000 в двоичной системе, то есть каждое из чисел маски имеет установленный 5-й битю Остальное пространство маски заполнено нулями.

Строку str загружаем в регистр xmm0 и с помощью инструкции

orpd mask1, %xmm0

Для каждого символа (байта) в строке устанавливаем 5-й бит

Затем сохраняем строку обратно в переменную str и выводим на консоль. Конечно, для полноценной работы со строками это немного наивный пример, поскольку мы не учитываем неалфавиитные символы и строки, которые больше 16 байт. Тем не менее данный пример показывает, что благодаря расширениям SSE/AVX мы можем проще проворачивать операции со строками. Пример компиляции и работы программы:

root@Eugene:~/asm# gcc -static hello.s -o hello
root@Eugene:~/asm# ./hello
hello
root@Eugene:~/asm#

Аналогично для перевода буквы в верхний регистр нам надо обнулить 5-й бит. Для этого применяем маску

mask2:  .fill maxlen, 1, 0xDF
        .fill 16-maxlen, 1, 0

Число 0xDF (0b11011111 в двоичной системе) с помощью операции AND позволяет обнулить 5-й бит символа (байта):

andpd mask2, %xmm0
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850