Cópia de Dados com Instruções SIMD em Assembly NASM
A arquitetura x86-64 oferece diversas instruções para cópia de dados, que permitem mover informações entre registradores (SSE ou AVX), carregar valores de registradores de uso geral ou variáveis, e gravar novamente esses valores em variáveis ou outros registradores.
Cópia de um único valor
As extensões SSE incluem as instruções movd (para cópia de números de 32 bits) e movq (para números de 64 bits):
movd xmm, reg32/mem32 movq xmm, reg64/mem64
A instrução movd copia um valor de um registrador de 32 bits ou de uma variável para os 32 bits inferiores do registrador XMM.
A instrução movq faz o mesmo para valores de 64 bits, copiando-os para os 64 bits inferiores do registrador XMM.
Essas instruções também realizam a operação inversa — ou seja, transferem dados de um registrador XMM para um registrador de uso geral ou para uma variável:
movd reg32/mem32, xmm movq reg64/mem64, xmm
A instrução movq também permite copiar os 64 bits inferiores de um registrador XMM para outro:
movq xmm, xmm
As extensões AVX apresentam versões equivalentes dessas instruções, com o prefixo v:
vmovd xmm, reg32/mem32 vmovq xmm, reg64/mem64 vmovd reg32/mem32, xmm vmovq reg64/mem64, xmm
Essas instruções servem tanto para inteiros quanto para números de ponto flutuante.
Exemplo em Linux
global _start
section .data
num dw 22
section .text
_start:
mov rax, 7
; para XMM
movq xmm0, rax ; XMM0 = 7
movd xmm1, [num] ; XMM1 = 22
; de XMM
movq rdi, xmm1 ; RDI = 22
movd [num], xmm0 ; num = 7
; resultado de verificação
add rdi, [num] ; RDI = 22 + 7 = 29
mov rax, 60
syscallExemplo em Windows
global _start
section .data
num dw 22
section .text
_start:
mov rax, 7
movq xmm0, rax ; XMM0 = 7
movd xmm1, [rel num] ; XMM1 = 22
movq rax, xmm1 ; RAX = 22
movd [rel num], xmm0 ; num = 7
add rax, [rel num] ; RAX = 29
retO mesmo princípio é aplicado a números de ponto flutuante.
global _start
section .data
num0 dq 2.13
num1 dw 3.14
section .text
_start:
movq xmm0, [num0] ; XMM0 = 2.13
movd xmm1, [num1] ; XMM1 = 3.14
mov rax, 60
syscallCópia de números de ponto flutuante
As instruções movss e movsd permitem copiar valores de ponto flutuante individuais.
movss xmmn, mem32 movss mem32, xmmn movss xmmsrc, xmmdest movsd xmmn, mem64 movsd mem64, xmmn movsd xmmsrc, xmmdest
Essas instruções copiam dados de variáveis para registradores XMM ou de registradores para variáveis.
Para maior desempenho, as variáveis usadas em movss devem estar alinhadas em endereços múltiplos de 4 bytes, e as usadas em movsd devem estar alinhadas em endereços múltiplos de 8 bytes.
Cópia de vetores de dados
Para copiar vetores inteiros de dados, a arquitetura oferece instruções que distinguem entre cópia alinhada e não alinhada.
Cópia de dados alinhados
As extensões SSE fornecem:
movaps: cópia de conjuntos de números de 32 bits.movapd: cópia de conjuntos de números de 64 bits.movdqa: cópia de valores inteiros de 128 bits.
Essas instruções transferem 16 bytes entre variáveis e registradores XMM ou entre dois registradores XMM.
As extensões AVX oferecem versões equivalentes com prefixo v, que transferem 16 ou 32 bytes entre variáveis e registradores XMM/YMM, ou entre dois registradores XMM/YMM.
Durante a cópia em registradores XMM, os bits superiores dos registradores YMM correspondentes são zerados.
Principais variações:
movaps mem128, xmm vmovaps mem128, xmm vmovaps mem256, ymm movaps xmm, mem128 vmovaps xmm, mem128 vmovaps ymm, mem256 movaps xmm, xmm vmovaps xmm, xmm vmovaps ymm, ymm movapd mem128, xmm vmovapd mem128, xmm vmovapd mem256, ymm movapd xmm, mem128 vmovapd xmm, mem128 vmovapd ymm, mem256 movdqa mem128, xmm vmovdqa mem128, xmm vmovdqa mem256, ymm movdqa xmm, mem128 vmovdqa xmm, mem128 vmovdqa ymm, mem256
Essas instruções exigem que os dados estejam alinhados em fronteiras de 16 ou 32 bytes. Caso contrário, ocorre uma exceção de acesso não alinhado. A letra “a” em seus nomes deriva de aligned (alinhado).
Exemplo em Linux
global _start
section .data
align 16
source dd 12, 13, 14, 15
section .bss
align 16
dest resd 4
section .text
_start:
movaps xmm0, [source]
movaps [dest], xmm0
mov edi, [dest] ; RDI = 12
mov rax, 60
syscallExemplo em Windows
global _start
section .data
align 16
source dd 12, 13, 14, 15
section .bss
align 16
dest resd 4
section .text
_start:
movaps xmm0, [rel source]
movaps [rel dest], xmm0
mov eax, [rel dest]
retNesse exemplo, os vetores source e dest são alinhados a 16 bytes. Os quatro números de source são copiados para o registrador XMM0, e então transferidos para dest.
Quando um vetor contém mais dados do que o registrador comporta (por exemplo, sete números de 32 bits), apenas os quatro primeiros serão copiados — o tamanho máximo que cabe em um registrador de 128 bits.
Cópia de strings
global _start
section .data
align 16
source db "Hello PROGRAMICIO.COM", 10, 0
len equ $ - source
section .bss
align 16
dest resb len
section .text
_start:
vmovapd ymm0, [source]
vmovapd [dest], ymm0
mov rdi, 1
mov rsi, dest
mov rdx, len
mov rax, 1
syscall
mov rax, 60
syscallNeste caso, a string source é copiada para o registrador YMM0 e depois para dest.
O alinhamento em 16 bytes garante o funcionamento correto das instruções vmovapd.
Exemplo em Windows
global _start
extern GetStdHandle
extern WriteFile
section .data
align 16
source db "Hello PROGRAMICIO.COM", 10, 0
len equ $ - source
section .bss
align 16
dest resb len
section .text
_start:
sub rsp, 40
vmovapd ymm0, [rel source]
vmovapd [rel dest], ymm0
mov rcx, -11
call GetStdHandle
mov rcx, rax
mov rdx, dest
mov r8d, len
xor r9, r9
mov qword [rsp + 32], 0
call WriteFile
add rsp, 40
retÉ importante lembrar que essas instruções exigem alinhamento. Por exemplo, se antes de um vetor houver uma variável de 1 byte, ele deixará de estar alinhado e poderá causar falhas:
section .data
temp1 db 1
source dd 12, 13, 14, 15Para garantir alinhamento:
section .data
temp1 db 1
align 16
source dd 12, 13, 14, 15Como alternativa, se possível, a ordem das variáveis pode ser alterada para evitar necessidade de realinhamento:
section .data
source dd 12, 13, 14, 15
dest resd 4
temp1 db 1Cópia de dados não alinhados
Quando o alinhamento não pode ser garantido, é possível usar instruções equivalentes que funcionam com dados não alinhados:
(v)movups: cópia de valores de 4 bytes.(v)movupd: cópia de valores de 8 bytes.(v)movdqu: cópia de blocos de 16 bytes.
A letra “u” vem de unaligned (não alinhado). Essas instruções operam da mesma forma, mas toleram endereços não múltiplos de 16 ou 32 bytes, embora geralmente apresentem desempenho inferior.
Exemplo em Linux
global _start
section .data
source dq 22, 23, 24, 25
section .bss
dest resd 8
section .text
_start:
movupd xmm0, [source]
movupd [dest], xmm0
mov rdi, [dest] ; RDI = 22
mov rax, 60
syscallEssas instruções são úteis quando não é possível garantir o alinhamento da memória, mas ainda assim é necessário copiar blocos de dados vetoriais.
Resumo
movdemovqrealizam cópias simples entre registradores e memória.movssemovsdcopiam valores individuais de ponto flutuante.movaps,movapdemovdqacopiam vetores alinhados.movups,movupdemovdqucopiam vetores não alinhados.- O alinhamento em memória é essencial para o desempenho e a segurança das instruções SIMD.