Operações Lógicas em Vetores com SSE e AVX em Assembly NASM
As extensões SSE e AVX oferecem um conjunto de instruções para a execução de operações lógicas bit a bit sobre vetores de dados.
- andpd: Executa uma operação AND bit a bit (
dest = dest AND source) em operandos de 128 bits. - vandpd: Realiza uma operação AND bit a bit (
dest = source1 AND source2) em operandos de 128 ou 256 bits. - andnpd: Executa uma operação AND NOT bit a bit (
dest = (~dest) AND source) em operandos de 128 bits. - vandnpd: Realiza uma operação AND NOT bit a bit (
dest = (~source1) AND source2) em operandos de 128 ou 256 bits. - orpd: Executa uma operação OR bit a bit (
dest = dest OR source) em operandos de 128 bits. - vorpd: Realiza uma operação OR bit a bit (
dest = source1 OR source2) em operandos de 128 ou 256 bits. - xorpd: Executa uma operação XOR bit a bit (
dest = dest XOR source) em operandos de 128 bits. - vxorpd: Realiza uma operação XOR bit a bit (
dest = source1 XOR source2) em operandos de 128 ou 256 bits.
A sintaxe geral para essas instruções é a seguinte:
andpd xmmdest, xmmsrc/mem128
vandpd xmmdest, xmmsrc1, xmmsrc2/mem128
vandpd ymmdest, ymmsrc1, ymmsrc2/mem256
andnpd xmmdest, xmmsrc/mem128
vandnpd xmmdest, xmmsrc1, xmmsrc2/mem128
vandnpd ymmdest, ymmsrc1, ymmsrc2/mem256
orpd xmmdest, xmmsrc/mem128
vorpd xmmdest, xmmsrc1, xmmsrc2/mem128
vorpd ymmdest, ymmsrc1, ymmsrc2/mem256
xorpd xmmdest, xmmsrc/mem128
vxorpd xmmdest, xmmsrc1, xmmsrc2/mem128
vxorpd ymmdest, ymmsrc1, ymmsrc2/mem256As instruções SSE (sem o prefixo v) não alteram os bits superiores do registrador YMM de destino. Por outro lado, as instruções AVX (com o prefixo v) que operam em 128 bits preenchem os 128 bits superiores do registrador YMM de destino com zeros. Quando um operando é uma variável na memória, ele deve estar alinhado a uma fronteira apropriada (16 bytes para mem128 e 32 bytes para mem256) para evitar erros de alinhamento em tempo de execução.
Exemplo de Operação AND
O código a seguir demonstra a aplicação da operação AND.
global _start
section .data
nums0 dd 0, 1, 0, 1
nums1 dd 0, 1, 1, 0
section .text
_start:
movaps xmm0, [nums0] ; Carrega o vetor nums0 no registrador xmm0
movaps xmm1, [nums1] ; Carrega o vetor nums1 no registrador xmm1
andpd xmm0, xmm1 ; XMM0 = 0, 1, 0, 0
mov rax, 60
syscallNeste exemplo, os vetores nums0 e nums1 são carregados nos registradores xmm0 e xmm1, respectivamente. A instrução andpd aplica a operação lógica AND entre os elementos correspondentes dos dois vetores. A operação AND retorna 1 somente se os bits em ambos os operandos forem 1.
O cálculo realizado é o seguinte:
[0, 1, 0, 1] AND [0, 1, 1, 0] = [0, 1, 0, 0]
Como resultado, o registrador xmm0 conterá o vetor [0, 1, 0, 0].
Para verificar o resultado, podemos exibir os valores no console, como mostra o exemplo para Linux a seguir.
global main
extern printf
section .data
nums0 dd 0, 1, 0, 1
nums1 dd 0, 1, 1, 0
format_str db "%d, %d, %d, %d", 10, 0
section .text
main:
sub rsp, 8
movaps xmm0, [nums0] ; Carrega o vetor nums0 no registrador xmm0
movaps xmm1, [nums1] ; Carrega o vetor nums1 no registrador xmm1
andpd xmm0, xmm1 ; XMM0 = 0, 1, 0, 0
movd esi, xmm0 ; Move o primeiro número para esi
psrldq xmm0, 4 ; Desloca 4 bytes para a direita para acessar o próximo número
movd edx, xmm0 ; Move o segundo número para edx
psrldq xmm0, 4 ; Desloca novamente
movd ecx, xmm0 ; Move o terceiro número para ecx
psrldq xmm0, 4 ; Desloca novamente
movd r8d, xmm0 ; Move o quarto número para r8d
mov rdi, format_str
xor rax, rax
call printf
add rsp, 8
retNeste programa, a função printf da biblioteca C é usada para exibir os valores. Após a operação AND, cada elemento do vetor resultante em xmm0 é extraído e movido para os registradores apropriados (esi, edx, ecx, r8d) para serem passados como argumentos para a printf.
Compilação e execução do programa:
nasm -f elf64 hello.asm -o hello.o gcc -static hello.o -o hello ./hello 0, 1, 0, 0
A seguir, a versão análoga do programa para Windows.
global main
extern printf
section .data
nums0 dd 0, 1, 0, 1
nums1 dd 0, 1, 1, 0
format_str db "%d, %d, %d, %d", 10, 0
section .text
main:
sub rsp, 40
movaps xmm0, [rel nums0] ; Carrega o vetor nums0 em xmm0
movaps xmm1, [rel nums1] ; Carrega o vetor nums1 em xmm1
andpd xmm0, xmm1 ; XMM0 = 0, 1, 0, 0
movd edx, xmm0 ; Move o primeiro número para edx
psrldq xmm0, 4 ; Desloca 4 bytes para a direita
movd r8d, xmm0 ; Move o segundo número para r8d
psrldq xmm0, 4 ; Desloca novamente
movd r9d, xmm0 ; Move o terceiro número para r9d
psrldq xmm0, 4 ; Desloca novamente
movd eax, xmm0
mov dword [rsp+32], eax ; Move o quarto número para a pilha
mov rcx, format_str
call printf
add rsp, 40
retExemplo de Manipulação de Strings
Outro exemplo prático é a manipulação de strings. O código a seguir, para Linux, converte uma string para letras minúsculas.
global main
extern printf
section .data
message db "HeLLo",0
maxlen equ ($-message-1)
align 16
mask1: times maxlen db 0x20
times (16-maxlen) db 0
mask2: times maxlen db 0xDF
times (16-maxlen) db 0
format_str db "%s", 10, 0
section .text
main:
sub rsp, 8
movdqa xmm0, [message] ; Copia a string de message para o registrador xmm0
orpd xmm0, [mask1] ; Converte para minúsculas
; andpd xmm0, [mask2] ; Converte para maiúsculas (exemplo)
movdqa [message], xmm0 ; Copia a string de xmm0 de volta para message
mov rdi, format_str
mov rsi, message
xor rax, rax
call printf
add rsp, 8
retNa tabela ASCII, as letras minúsculas diferem das maiúsculas pelo estado do bit 5 (contando a partir do zero). Por exemplo, "A" é 01000001 e "a" é 01100001. Para converter uma letra para minúscula, basta definir o bit 5 como 1. Para isso, uma máscara é definida.
A máscara mask1 contém uma sequência de bytes com o valor 0x20 (binário 00100000), que possui apenas o bit 5 definido. A string é carregada no registrador xmm0 e, com a instrução orpd xmm0, [mask1], o bit 5 de cada caractere da string é ativado através da operação OR, convertendo os caracteres para minúsculo.
Por fim, a string modificada é copiada de volta para a memória e exibida no console. Este é um exemplo simplificado, pois não trata caracteres não alfabéticos ou strings maiores que 16 bytes. No entanto, ilustra como as extensões SSE/AVX podem simplificar operações em strings.
Resultado da execução do programa:
$ nasm -f elf64 hello.asm -o hello.o $ gcc -static hello.o -o hello $ ./hello hello
De forma análoga, para converter uma letra para maiúscula, é necessário zerar o bit 5. Isso pode ser feito com uma operação AND e a máscara mask2, que contém o valor 0xDF (binário 11011111).
andpd xmm0, [mask2]Resumo
- As extensões SSE/AVX incluem instruções para operações lógicas bit a bit (
AND,OR,XOR,AND NOT) em vetores. - As versões AVX (prefixo
v) geralmente operam com três operandos e gerenciam os bits superiores dos registradores YMM de forma diferente das versões SSE. - Essas operações são eficientes para processar múltiplos dados simultaneamente, como na verificação de condições ou na manipulação de strings.
- É crucial garantir o alinhamento de memória ao usar operandos que estão na memória para evitar falhas de segmentação.