Atualizado: 09/11/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Operações de Deslocamento SSE/AVX em Assembly NASM

As extensões SSE e AVX fornecem instruções para deslocamento lógico e aritmético de dados.

Deslocamento de Vetores por Bytes

Existem instruções que deslocam todo o conteúdo de um registrador XMM/YMM por um número específico de bytes.

A instrução pslldq desloca os dados em um registrador XMM para a esquerda em um número de bytes especificado pelo operando imediato imm8. Os bytes de ordem inferior que são liberados são preenchidos com zeros.

pslldq xmmdest, imm8

A versão AVX, vpslldq, utiliza um registrador de origem e um de destino. Ela desloca o valor do segundo operando (um registrador XMM ou YMM) para a esquerda pelo número de bytes indicado no terceiro operando. O resultado é armazenado no primeiro registrador.

vpslldq xmmdest, xmmsrc, imm8
vpslldq ymmdest, ymmsrc, imm8

Na sua variante de 128 bits, a instrução também preenche os bits de 128 a 255 do registrador YMM de destino com zeros.

De forma análoga, a instrução psrldq desloca os dados em um registrador XMM para a direita, preenchendo os bytes de ordem superior liberados com zeros.

psrldq xmmdest, imm8

A sua versão AVX, vpsrldq, desloca o valor do segundo operando para a direita e armazena o resultado no primeiro.

vpsrldq xmmdest, xmmsrc, imm8
vpsrldq ymmdest, ymmsrc, imm8

É importante notar que essas instruções deslocam o vetor inteiro contido no registrador por bytes, ou seja, a menor unidade de deslocamento é de 8 bits.

A seguir, um exemplo de programa em Linux que demonstra o uso do deslocamento.

global _start

section .data
    nums dd 3, 5, 8, 11

section .text
_start:
    movaps xmm0, [nums]   ; Carrega o vetor nums no registrador xmm0
    psrldq xmm0, 4        ; Desloca o registrador xmm0 4 bytes para a direita
    movd edi, xmm0        ; edi = 5
    mov rax, 60
    syscall

Neste código, o registrador xmm0 recebe o vetor nums, composto por quatro números dword, onde cada um ocupa 4 bytes. A instrução psrldq xmm0, 4 desloca o conteúdo de xmm0 em 4 bytes para a direita. O vetor original [11, 8, 5, 3] se torna [0, 11, 8, 5]. Após o deslocamento, o primeiro número do vetor em xmm0 (que passa a ser 5) é movido para o registrador edi.

Com as operações de deslocamento, é possível implementar uma função para exibir o conteúdo de um registrador xmm no console. O programa abaixo demonstra essa funcionalidade em Linux.

global _start

section .data
    nums dd 254, 5, 8, 11
    hexstr128 db "0x123456789ABCDEFG123456789ABCDEFG", 10, 0
    len equ $-hexstr128    ; Tamanho da string
    hexmap db 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70

section .text
_start:
    movaps xmm0, [nums]     ; Carrega o vetor nums no registrador xmm0
    call print_reg128
    mov rax, 60
    syscall

; Função para imprimir um registrador de 128 bits no console
; Parâmetros: XMM0 - valor a ser exibido
print_reg128:
    mov rsi, hexmap         ; Tabela de conversão
    mov rbx, hexstr128      ; Início da string de destino
    add rbx, 33             ; Aponta para o final do espaço do número na string

    mov rcx, 16             ; 16 bytes = 32 caracteres hexadecimais para imprimir
forloop128:
    movq rax, xmm0
    and rax, 0xf            ; Aplica uma máscara para obter o primeiro nibble (4 bits)
    mov al, byte [rsi + rax] ; Encontra o caractere correspondente na tabela hexmap
    mov byte [rbx], al      ; Armazena o caractere ASCII

    movq rax, xmm0
    shr rax, 4              ; Pega o segundo nibble de XMM0
    and rax, 0xf            ; Aplica a máscara
    mov al, byte [rsi + rax] ; Encontra o caractere correspondente
    mov byte [rbx-1], al    ; Armazena o segundo caractere ASCII

    sub rbx, 2              ; Move o ponteiro da string 2 posições para trás

    psrldq xmm0, 1          ; Desloca XMM0 1 byte para a direita para processar o próximo byte

    sub rcx, 1              ; Decrementa o contador
    jne forloop128          ; Repete o laço se RCX não for zero

    ; Imprime a string usando a chamada de sistema write
    mov rax, 1              ; Número da função de sistema (write)
    mov rdi, 1              ; Descritor de arquivo (saída padrão)
    mov rsi, hexstr128      ; Endereço da string
    mov rdx, len            ; Tamanho da string
    syscall                 ; Executa a chamada de sistema
    ret

A função print_reg128 recebe o valor a ser impresso através do registrador xmm0. Um laço processa cada um dos 16 bytes do registrador. Em cada iteração, os dois nibbles (4 bits cada) do byte de ordem mais baixa de xmm0 são isolados, convertidos para seus caracteres hexadecimais correspondentes usando a tabela hexmap e inseridos na string hexstr128 do final para o começo. Após processar um byte, o registrador xmm0 é deslocado 1 byte para a direita com psrldq para que o próximo byte possa ser processado na iteração seguinte. Finalizado o laço, a string resultante é exibida no console através da chamada de sistema write.

Saída do programa no console:

root@Programicio:~/asm; nasm -f elf64 hello.asm -o hello.o
root@Programicio:~/asm; ld hello.o -o hello
root@Programicio:~/asm; ./hello
0x0000000B0000000800000005000000FE
root@Programicio:~/asm;

O registrador XMM0, contendo o vetor de inteiros de 32 bits [254, 5, 8, 11], corresponde ao valor hexadecimal 0x0000000B0000000800000005000000FE.

A seguir, uma versão análoga do programa para Windows.

global _start

extern WriteFile        ; Importa a função WriteFile
extern GetStdHandle     ; Importa a função GetStdHandle

section .data
    nums dd 254, 5, 8, 11
    hexstr128 db "0x123456789ABCDEFG123456789ABCDEFG", 10, 0
    len equ $-hexstr128    ; Tamanho da string
    hexmap db 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70

section .text
_start:
    movaps xmm0, [rel nums]     ; Carrega o vetor nums no registrador xmm0
    call print_reg128
    ret

; Função para imprimir um registrador de 128 bits no console
; Parâmetros: XMM0 - valor a ser exibido
print_reg128:
    sub  rsp, 48                ; Reserva 40 bytes (5*8) para os parâmetros das funções e shadow space
    mov rsi, hexmap             ; Tabela de conversão
    mov rbx, hexstr128          ; Início da string de destino
    add rbx, 33                 ; Aponta para o final do espaço do número na string

    mov rcx, 16                 ; 16 bytes = 32 caracteres hexadecimais para imprimir
forloop128:
    movq rax, xmm0
    and rax, 0xf                ; Aplica uma máscara para obter o primeiro nibble
    mov al, byte [rsi + rax]    ; Encontra o caractere correspondente na tabela
    mov byte [rbx], al          ; Armazena o caractere ASCII

    movq rax, xmm0
    shr rax, 4                  ; Pega o segundo nibble de XMM0
    and rax, 0xf                ; Aplica a máscara
    mov al, byte [rsi + rax]    ; Encontra o caractere correspondente
    mov byte [rbx-1], al        ; Armazena o segundo caractere ASCII

    sub rbx, 2                  ; Move o ponteiro da string 2 posições para trás

    psrldq xmm0, 1              ; Desloca XMM0 1 byte para a direita

    sub rcx, 1                  ; Decrementa o contador
    jne forloop128              ; Repete o laço se RCX não for zero

    ; Impressão da string
    mov  rcx, -11               ; Argumento para GetStdHandle (STD_OUTPUT_HANDLE)
    call GetStdHandle           ; Chama GetStdHandle
    mov  rcx, rax               ; Primeiro parâmetro de WriteFile (handle do console)
    mov rdx, hexstr128          ; Segundo parâmetro (endereço da string)
    mov r8d, len                ; Terceiro parâmetro (tamanho da string)
    xor  r9, r9                 ; Quarto parâmetro (bytes escritos)
    mov  qword [rsp + 32], 0    ; Quinto parâmetro
    call WriteFile              ; Chama a função WriteFile
    add  rsp, 48
    ret

Exibição de Dados com a Função printf

Os dados também podem ser exibidos com a função printf da biblioteca C. O exemplo a seguir é para Linux.

global main

extern printf

section .data
    nums dd 255, 5, 8, 11
    format_str db "%d, %d, %d, %d", 10, 0

section .text
main:
    sub rsp, 8
    movaps xmm0, [nums]     ; Carrega o vetor nums no registrador xmm0

    movd esi, xmm0        ; Move o primeiro número para esi
    psrldq xmm0, 4        ; Desloca para a direita em 4 bytes 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          ; Indica que nenhum registrador XMM é usado para argumentos
    call printf

    add rsp, 8
    ret

Para extrair cada número do vetor em xmm0, a instrução de deslocamento para a direita é aplicada sucessivamente. A cada deslocamento, o número na posição de ordem mais baixa do vetor é movido para um dos registradores usados pela função printf para passar argumentos, conforme a convenção de chamada do Linux x86-64.

Compilação e execução do programa:

root@Programicio:~/asm; nasm -f elf64 hello.asm -o hello.o
root@Programicio:~/asm; gcc -static hello.o -o hello
root@Programicio:~/asm; ./hello
255, 5, 8, 11
root@Programicio:~/asm;

Exibição de Números de Ponto Flutuante

A exibição de números de ponto flutuante é um pouco mais complexa, como demonstrado no programa para Linux a seguir.

global main

extern printf

section .data
    nums dd 1.2, 2.3, 3.4, 4.5
    format_str db "%.1f, %.1f, %.1f, %.1f", 10, 0

section .text
main:
    sub rsp, 8
    movaps xmm5, [nums]     ; Carrega o vetor no registrador xmm5

    movss xmm0, xmm5        ; Move o primeiro número para xmm0
    cvtss2sd xmm0, xmm0     ; Converte de float para double
    psrldq xmm5, 4          ; Desloca o vetor 4 bytes para a direita
    movss xmm1, xmm5        ; Move o segundo número para xmm1
    cvtss2sd xmm1, xmm1     ; Converte de float para double
    psrldq xmm5, 4          ; Desloca o vetor
    movss xmm2, xmm5        ; Move o terceiro número para xmm2
    cvtss2sd xmm2, xmm2     ; Converte de float para double
    psrldq xmm5, 4          ; Desloca o vetor
    movss xmm3, xmm5        ; Move o quarto número para xmm3
    cvtss2sd xmm3, xmm3     ; Converte de float para double

    mov rdi, format_str
    mov rax, 4              ; Indica que 4 registradores XMM são usados para argumentos
    call printf

    add rsp, 8
    ret

Neste caso, a função printf também é utilizada. Os argumentos de ponto flutuante são passados através dos registradores xmm0 a xmm7. O vetor é carregado em xmm5 para não interferir com os registradores de argumento. Cada elemento do vetor (que é um float de precisão simples) é movido para um registrador de argumento (xmm0 a xmm3) e convertido para double (precisão dupla) com a instrução cvtss2sd, pois printf espera doubles para o formato %.1f. Entre cada extração, o vetor em xmm5 é deslocado 4 bytes para a direita.

Resultado do programa:

root@Programicio:~/asm; nasm -f elf64 hello.asm -o hello.o
root@Programicio:~/asm; gcc -static hello.o -o hello
root@Programicio:~/asm; ./hello
1.2, 2.3, 3.4, 4.5
root@Programicio:~/asm;

Versão análoga para Windows:

global main

extern printf

section .data
align 16
    nums dd 1.2, 2.3, 3.4, 4.5
    format_str db "%.1f, %.1f, %.1f, %.1f", 10, 0

section .text
main:
    sub rsp, 40
    movaps xmm5, [rel nums]     ; Carrega o vetor no registrador xmm5

    movss xmm1, xmm5            ; Move o primeiro número para xmm1
    cvtss2sd xmm1, xmm1         ; Converte de float para double
    movq rdx, xmm1              ; Duplica em rdx para a convenção de chamada

    psrldq xmm5, 4              ; Desloca o vetor 4 bytes para a direita
    movss xmm2, xmm5            ; Move o segundo número para xmm2
    cvtss2sd xmm2, xmm2         ; Converte de float para double
    movq r8, xmm2               ; Duplica em r8

    psrldq xmm5, 4              ; Desloca o vetor
    movss xmm3, xmm5            ; Move o terceiro número para xmm3
    cvtss2sd xmm3, xmm3         ; Converte de float para double
    movq r9, xmm3               ; Duplica em r9

    psrldq xmm5, 4              ; Desloca o vetor
    movss xmm4, xmm5            ; Move o quarto número para xmm4
    cvtss2sd xmm4, xmm4         ; Converte de float para double
    movq [rsp+32], xmm4         ; Passa o quarto argumento via pilha

    mov rcx, format_str
    call printf

    add rsp, 40
    ret

Se o vetor contivesse números de ponto flutuante de dupla precisão (8 bytes cada), a conversão não seria necessária. O exemplo a seguir para Linux ilustra isso.

global main

extern printf

section .data
    nums dq 2.3, 4.5
    format_str db "%.1f, %.1f", 10, 0

section .text
main:
    sub rsp, 8
    movaps xmm5, [nums]     ; Carrega o vetor no registrador xmm5

    movsd xmm0, xmm5        ; Move o primeiro número para xmm0
    psrldq xmm5, 8          ; Desloca o vetor 8 bytes para a direita
    movsd xmm1, xmm5        ; Move o segundo número para xmm1

    mov rdi, format_str
    mov rax, 2              ; Indica que 2 registradores XMM são usados
    call printf

    add rsp, 8
    ret

Deslocamento de Elementos Individuais no Vetor

Existem também instruções que deslocam bits de elementos individuais dentro de um vetor, em vez de deslocar o registrador inteiro.

  • (v)psllw, (v)pslld, (v)psllq: Deslocam para a esquerda, respectivamente, word (16 bits), dword (32 bits) e qword (64 bits).
  • (v)psrlw, (v)psrld, (v)psrlq: Deslocam para a direita, respectivamente, word, dword e qword.
  • (v)psraw, (v)psrad, (v)psraq: Realizam um deslocamento aritmético para a direita em word, dword e qword.

A sintaxe, exemplificada com (v)psllw, é similar para as outras instruções:

psllw xmmdest, imm8
psllw xmmdest, xmmsrc/mem128
vpsllw xmmdest, xmmsrc, imm8
vpsllw xmmdest, xmmsrc, mem128
vpsllw ymmdest, ymmsrc, imm8
vpsllw ymmdest, ymmsrc, xmm/mem128

Nas versões de dois operandos, o primeiro é o valor a ser deslocado, e o segundo especifica a quantidade de bits para o deslocamento. Nas versões de três operandos, o segundo operando é o valor de origem, o terceiro é a quantidade de bits, e o primeiro é o destino do resultado.

O programa a seguir para Linux carrega um vetor de quatro números e desloca cada um deles 1 bit para a esquerda, o que equivale a uma multiplicação por 2.

global main

extern printf

section .data
    nums dd 1, 2, 4, 8
    format_str db "%d, %d, %d, %d", 10, 0

section .text
main:
    sub rsp, 8
    movaps xmm0, [nums]     ; Carrega o vetor no registrador xmm0

    pslld xmm0, 1           ; Desloca cada dword em xmm0 1 bit para a esquerda

    movd esi, xmm0          ; Move o primeiro número para esi
    psrldq xmm0, 4          ; Desloca o vetor 4 bytes para a direita
    movd edx, xmm0          ; Move o segundo número para edx
    psrldq xmm0, 4          ; Desloca o vetor
    movd ecx, xmm0          ; Move o terceiro número para ecx
    psrldq xmm0, 4          ; Desloca o vetor
    movd r8d, xmm0          ; Move o quarto número para r8d

    mov rdi, format_str
    xor rax, rax
    call printf

    add rsp, 8
    ret

Compilação e resultado:

root@Programicio:~/asm; nasm -f elf64 hello.asm -o hello.o
root@Programicio:~/asm; gcc -static hello.o -o hello
root@Programicio:~/asm; ./hello
2, 4, 8, 16
root@Programicio:~/asm;

Resumo

  • As instruções psrldq e pslldq deslocam todo o conteúdo de um registrador XMM/YMM por um número de bytes.
  • É possível usar psrldq para iterar sobre os elementos de um vetor, extraindo um de cada vez.
  • Instruções como (v)pslld e (v)psrld deslocam os bits de cada elemento individualmente dentro do vetor.
  • A impressão de valores pode ser feita com chamadas de sistema ou com funções da biblioteca C como printf.
  • Ao chamar printf, é necessário respeitar a convenção de chamada da plataforma (x86-64 Linux ou Windows), especialmente para argumentos de ponto flutuante.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com