Subtração Vetorial com Instruções SSE e AVX em Assembly NASM
Para a subtração de elementos correspondentes em vetores, as extensões SSE e AVX oferecem um conjunto de instruções análogo ao da adição:
- psubb / vpsubb: Subtração de bytes em 16 pistas (SSE/AVX-128) ou 32 pistas (AVX-256).
- psubw / vpsubw: Subtração de words (16 bits) em 8 pistas (SSE/AVX-128) ou 16 pistas (AVX-256).
- psubd / vpsubd: Subtração de double words (32 bits) em 4 pistas (SSE/AVX-128) ou 8 pistas (AVX-256).
- psubq / vpsubq: Subtração de quad words (64 bits) em 2 pistas (SSE/AVX-128) ou 4 pistas (AVX-256).
A sintaxe geral para essas instruções é a seguinte:
psubb xmmdest, xmmsrc/mem128
vpsubb xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubb ymmdest, ymmsrc1, ymmsrc2/mem256
psubw xmmdest, xmmsrc/mem128
vpsubw xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubw ymmdest, ymmsrc1, ymmsrc2/mem256
psubd xmmdest, xmmsrc/mem128
vpsubd xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubd ymmdest, ymmsrc1, ymmsrc2/mem256
psubq xmmdest, xmmsrc/mem128
vpsubq xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubq ymmdest, ymmsrc1, ymmsrc2/mem256As instruções com dois operandos subtraem as pistas do segundo operando das pistas correspondentes do primeiro, armazenando o resultado no primeiro operando (dest = dest - src). As instruções com três operandos subtraem o terceiro do segundo e guardam o resultado no primeiro (dest = src1 - src2).
O exemplo a seguir para Linux demonstra uma subtração simples.
global main
extern printf
section .data
nums0 dd 1, 4, 3, 9
nums1 dd 2, 3, 5, 6
format_str db "%d, %d, %d, %d", 10, 0
section .text
main:
sub rsp, 8
movaps xmm0, [nums0]
movaps xmm1, [nums1]
psubd xmm0, xmm1 ; XMM0 = XMM0 - XMM1
; Resultado em XMM0: [-1, 1, -2, 3]
; 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
retResultado da execução do programa:
$ nasm -f elf64 hello.asm -o hello.o $ gcc -static hello.o -o hello $ ./hello -1, 1, -2, 3
A seguir, a versão análoga do programa para Windows.
global main
extern printf
section .data
nums0 dd 1, 4, 3, 9
nums1 dd 2, 3, 5, 6
format_str db "%d, %d, %d, %d", 10, 0
section .text
main:
sub rsp, 40
movaps xmm0, [rel nums0]
movaps xmm1, [rel nums1]
psubd xmm0, xmm1 ; XMM0 = XMM0 - XMM1
; Resultado em XMM0: [-1, 1, -2, 3]
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
retAssim como nas instruções de adição, as instruções de subtração não afetam nenhum flag de status. Qualquer informação sobre borrow, overflow ou underflow é perdida, o que pode levar a resultados incorretos.
Subtração com Aritmética de Saturação
De forma análoga à adição, as extensões SSE/AVX fornecem instruções para subtração com saturação, que evitam o comportamento de wrap-around.
- psubsb / vpsubsb: Subtração de bytes com sinal e saturação.
- psubsw / vpsubsw: Subtração de words com sinal e saturação.
- psubusb / vpsubusb: Subtração de bytes sem sinal e saturação.
- psubusw / vpsubusw: Subtração de words sem sinal e saturação.
A sintaxe dessas instruções é idêntica à de suas contrapartes sem saturação.
psubsb xmmdest, xmmsrc/mem128
vpsubsb xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubsb ymmdest, ymmsrc1, ymmsrc2/mem256
psubsw xmmdest, xmmsrc/mem128
vpsubsw xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubsw ymmdest, ymmsrc1, ymmsrc2/mem256
psubusb xmmdest, xmmsrc/mem128
vpsubusb xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubusb ymmdest, ymmsrc1, ymmsrc2/mem256
psubusw xmmdest, xmmsrc/mem128
vpsubusw xmmdest, xmmsrc1, xmmsrc2/mem128
vpsubusw ymmdest, ymmsrc1, ymmsrc2/mem256Para operandos com sinal, o resultado é "saturado" ou fixado nos limites do tipo de dado:
- Bytes (8 bits): Satura em
+127(0x7F) para overflow positivo e-128(0x80) para overflow negativo. - Words (16 bits): Satura em
+32767(0x7FFF) e-32768(0x8000).
Para operandos sem sinal, a saturação ocorre no valor máximo do tipo de dado para overflow positivo e em 0 para overflow negativo.
Vejamos como a saturação afeta o resultado. Primeiro, um exemplo com subtração padrão que causa overflow.
global main
extern printf
section .data
nums0 dw -32768, 0, 0, 0, 0, 0, 0, 0
nums1 dw 10, 0, 0, 0, 0, 0, 0, 0
format_str db "%d", 10, 0
section .text
main:
sub rsp, 8
movaps xmm0, [nums0]
movaps xmm1, [nums1]
psubw xmm0, xmm1 ; XMM0 = XMM0 - XMM1
movd esi, xmm0
movsx esi, si ; Extensão de sinal
mov rdi, format_str
xor rax, rax
call printf
add rsp, 8
retNeste código, a operação -32768 - 10 é realizada. Matematicamente, o resultado seria -32778, mas este valor está fora do intervalo de um word com sinal.
Resultado da execução:
$ nasm -f elf64 hello.asm -o hello.o $ gcc -static hello.o -o hello $ ./hello 32758
O valor 32758 é claramente incorreto. Agora, vamos aplicar a subtração com saturação.
global main
extern printf
section .data
nums0 dw -32768, 0, 0, 0, 0, 0, 0, 0
nums1 dw 10, 0, 0, 0, 0, 0, 0, 0
format_str db "%d", 10, 0
section .text
main:
sub rsp, 8
movaps xmm0, [nums0]
movaps xmm1, [nums1]
psubsw xmm0, xmm1 ; Subtração com saturação
movd esi, xmm0
movsx esi, si ; Extensão de sinal
mov rdi, format_str
xor rax, rax
call printf
add rsp, 8
retResultado da execução com saturação:
$ nasm -f elf64 hello.asm -o hello.o $ gcc -static hello.o -o hello $ ./hello -32768
O resultado -32768, embora não seja matematicamente exato, é o valor mínimo possível para o tipo de dado. Ele está muito mais próximo do resultado desejado e, em muitos cenários, como no processamento de imagens ou áudio, é um resultado aceitável e previsível.
Resumo
- As instruções
psubrealizam a subtração paralela de elementos em vetores (bytes, words, dwords, qwords). - A subtração padrão não gerencia overflow ou underflow, o que pode levar a resultados incorretos.
- A aritmética de saturação, implementada pelas instruções
psub*sepsub*us, evita o wrap-around fixando o resultado nos valores mínimo ou máximo permitidos pelo tipo de dado.