Comparação Vetorial de Inteiros com SSE e AVX em Assembly NASM
As instruções (v)pcmpeq*, (v)pcmpgt* comparam inteiros nas pistas correspondentes de dois operandos. O resultado da comparação, que é uma máscara de bits, é armazenado no primeiro operando.
Comparação de Igualdade
As instruções pcmpeq* testam a igualdade entre os elementos dos vetores.
pcmpeqb xmmdest, xmmsrc/mem128 ; Compara 16 pistas de bytes
pcmpeqw xmmdest, xmmsrc/mem128 ; Compara 8 pistas de words
pcmpeqd xmmdest, xmmsrc/mem128 ; Compara 4 pistas de dwords
pcmpeqq xmmdest, xmmsrc/mem128 ; Compara 2 pistas de qwordsSe os dois valores em uma pista são iguais, a instrução preenche todos os bits daquela pista no registrador de destino com 1. Caso contrário, preenche com 0. Um valor com todos os bits 1 é representado como -1 em notação de complemento de dois.
O programa a seguir para Linux demonstra essa operação.
global main
extern printf
section .data
nums0 dd 1, 2, 4, 8
nums1 dd 1, 2, 3, 8
format_str db "%d, %d, %d, %d", 10, 0
section .text
main:
sub rsp, 8
movaps xmm0, [nums0]
movaps xmm1, [nums1]
pcmpeqd xmm0, xmm1 ; XMM0 = (XMM0 == XMM1)
; Resultado em XMM0: [-1, -1, 0, -1]
; Exibe os dados no console
movd esi, xmm0
psrldq xmm0, 4
movd edx, xmm0
psrldq xmm0, 4
movd ecx, xmm0
psrldq xmm0, 4
movd r8d, xmm0
mov rdi, format_str
xor rax, rax
call printf
add rsp, 8
retComo os elementos nas pistas 0, 1 e 3 são iguais, essas pistas em xmm0 são preenchidas com 0xFFFFFFFF (-1). A pista 2, onde os valores são diferentes, é preenchida com 0.
Resultado da execução:
$ nasm -f elf64 hello.asm -o hello.o $ gcc -static hello.o -o hello $ ./hello -1, -1, 0, -1
A seguir, a versão análoga do programa para Windows.
global main
extern printf
section .data
nums0 dd 1, 2, 4, 8
nums1 dd 1, 2, 3, 8
format_str db "%d, %d, %d, %d", 10, 0
section .text
main:
sub rsp, 40
movaps xmm0, [rel nums0]
movaps xmm1, [rel nums1]
pcmpeqd xmm0, xmm1 ; XMM0 = (XMM0 == XMM1)
; Resultado em XMM0: [-1, -1, 0, -1]
movd edx, xmm0
psrldq xmm0, 4
movd r8d, xmm0
psrldq xmm0, 4
movd r9d, xmm0
psrldq xmm0, 4
movd eax, xmm0
mov dword [rsp+32], eax
mov rcx, format_str
call printf
add rsp, 40
retComparação "Maior Que"
As instruções pcmpgt* verificam se o valor no primeiro operando é maior que o valor no segundo.
pcmpgtb xmmdest, xmmsrc/mem128 ; Compara 16 pistas de bytes
pcmpgtw xmmdest, xmmsrc/mem128 ; Compara 8 pistas de words
pcmpgtd xmmdest, xmmsrc/mem128 ; Compara 4 pistas de dwords
pcmpgtq xmmdest, xmmsrc/mem128 ; Compara 2 pistas de qwordsA lógica é a mesma: a pista de destino é preenchida com 1s se a condição dest > src for verdadeira, e com 0s caso contrário.
global main
extern printf
section .data
nums0 dd 0, 2, 4, 5
nums1 dd 0, 1, 2, 7
format_str db "%d, %d, %d, %d", 10, 0
section .text
main:
sub rsp, 8
movaps xmm0, [nums0]
movaps xmm1, [nums1]
pcmpgtd xmm0, xmm1 ; XMM0 = (XMM0 > XMM1)
; Resultado em XMM0: [0, -1, -1, 0]
; Exibe os dados no console
movd esi, xmm0
psrldq xmm0, 4
movd edx, xmm0
psrldq xmm0, 4
movd ecx, xmm0
psrldq xmm0, 4
movd r8d, xmm0
mov rdi, format_str
xor rax, rax
call printf
add rsp, 8
retAs versões AVX (vpcmpeq* e vpcmpgt*) usam três operandos (dest = src1 op src2) e as variantes de 256 bits dobram o número de pistas processadas. É importante notar que essas instruções de comparação não afetam os flags de status do processador.
Verificação do Resultado com (v)pmovmskb
A instrução (v)pmovmskb é uma ferramenta para analisar o resultado de uma comparação vetorial. Ela extrai o bit mais significativo (MSB) de cada byte em um registrador XMM ou YMM e os compacta em um registrador de propósito geral.
pmovmskb reg, xmm
vpmovmskb reg, xmm
vpmovmskb reg, ymmApós uma comparação, cada pista do registrador de resultado contém 0x00 (falso) ou 0xFF (verdadeiro). A instrução pmovmskb cria uma máscara de bits onde cada bit corresponde ao resultado da comparação de um byte.
O exemplo a seguir para Linux demonstra seu uso.
global main
extern printf
section .data
nums0 dd 0, 2, 4, 5
nums1 dd 0, 1, 2, 7
format_str db "%#x", 10, 0
section .text
main:
sub rsp, 8
movaps xmm0, [nums0]
movaps xmm1, [nums1]
pcmpgtd xmm0, xmm1 ; Resultado em XMM0: [0, -1, -1, 0]
pmovmskb esi, xmm0
; Exibe os dados no console
mov rdi, format_str
xor rax, rax
call printf
add rsp, 8
retResultado da execução:
$ nasm -f elf64 hello.asm -o hello.o $ gcc -static hello.o -o hello $ ./hello 0xff0
O registrador xmm0 contém o vetor [0, -1, -1, 0], que em bytes é [00 00 00 00] [FF FF FF FF] [FF FF FF FF] [00 00 00 00]. A instrução pmovmskb extrai o bit de sinal de cada um desses 16 bytes. Os 4 primeiros bytes têm MSB 0, os 8 seguintes têm MSB 1, e os 4 últimos têm MSB 0. Isso forma a máscara binária 0000111111110000, que em hexadecimal é 0x0FF0.
Aplicação Prática: Comparação de Strings
Essas instruções são muito eficientes para comparar strings de até 16 bytes de uma só vez.
global main
extern printf
section .data
str0 db "Hello World",0
align 16
str1 db "Hello World", 0
align 16
mask dw 0xffff
equal_str db "String are equal", 10, 0
notequal_str db "String are not equal", 10, 0
section .text
main:
sub rsp, 8
movdqa xmm0, [str0]
movdqa xmm1, [str1]
pcmpeqb xmm0, xmm1 ; Compara os bytes (caracteres) das strings
vpmovmskb edi, xmm0 ; Copia o resultado para edi
cmp di, [mask] ; Compara a máscara de resultado com 0xffff
jz equal ; Se forem iguais, as strings são idênticas
mov rdi, notequal_str ; Se as strings não são iguais
jmp exit
equal:
mov rdi, equal_str ; Se as strings são iguais
exit:
xor rax, rax
call printf
add rsp, 8
retO código compara 16 bytes das strings com pcmpeqb. Se todos os bytes forem iguais, vpmovmskb produzirá o valor 0xFFFF. Esse valor é então comparado com uma máscara para determinar se as strings são idênticas.
Aplicação Avançada: Conversão Condicional de Caracteres
As operações de comparação permitem manipulações condicionais, como converter apenas as letras minúsculas de uma string para maiúsculas, sem afetar outros caracteres.
O código a seguir implementa essa lógica:
- Encontra todos os caracteres no intervalo de 'a' a 'z' usando duas comparações (
>='a'e<='z'). - Combina os resultados com
ANDpara criar uma máscara que identifica apenas as letras minúsculas. - Usa essa máscara para zerar o 5º bit (o bit que diferencia maiúsculas de minúsculas no ASCII) apenas nos caracteres selecionados.
global _start
section .data
msg db "Hello World", 10, 0
msglen equ $-msg
align 16
mask_ge_a times 16 db 'a' - 1 ; Caracteres >= 'a'
mask_le_z times 16 db 'z' + 1 ; Caracteres <= 'z'
mask_case times 16 db 0b00100000 ; Máscara do 5º bit
section .text
_start:
movdqa xmm0, [msg] ; Carrega a string em xmm0
; Cria máscara para letras minúsculas (>= 'a' E < 'z')
vpcmpgtb xmm1, xmm0, [mask_ge_a] ; Máscara 1: Caracteres > ('a'-1)
movdqa xmm2, [mask_le_z]
vpcmpgtb xmm2, xmm2, xmm0 ; Máscara 2: Caracteres < ('z'+1)
pand xmm1, xmm2 ; Interseção das máscaras
; Isola o 5º bit apenas para as letras minúsculas
pand xmm1, [mask_case]
; Zera o 5º bit nos caracteres da string original
pxor xmm0, xmm1
movdqa [msg], xmm0 ; Salva a string convertida
; Imprime a string com a chamada de sistema write
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, msglen
syscall
mov rax, 60
xor rdi, rdi
syscallResultado da execução:
$ nasm -f elf64 hello.asm -o hello.o $ ld hello.o -o hello $ ./hello HELLO WORLD
Resumo
- As instruções
pcmpeqepcmpgtrealizam comparações paralelas, gerando uma máscara de1s (verdadeiro) ou0s (falso) para cada pista. - A instrução
pmovmskbé usada para converter a máscara vetorial resultante em um único registrador inteiro, facilitando a verificação do resultado com instruções tradicionais. - Essas ferramentas são eficientes para operações como comparação de strings e manipulação condicional de dados em larga escala.