Сохранение состояния регистров SSE/AVX

Последнее обновление: 20.10.2023

При работе с инструкциями расширений SSE/AVX нам может потребоваться сохранить значения регистров XMM/YMM. Рассмотрим на примере программы на Linux сохранение первых 6 регистров XMM/YMM:

global _start

section .data
AVXSupport equ 0x10000000  ; проверяем бит 28

section .text
_start:
    mov rax, 22
    movq xmm0, rax    ; для теста помещаем некоторое число в xmm0

    ; Резервируем в стеке место  для регистров AVX/SSE
    ; Для 6 регистров SSE необходимо только 96 байт
    ; Для 6 регистров AVX 196 байт 
    ; Выделяем одно пространство для двух типов расширений - 196 байт
    ; Тогда SSE просто игнорируют дополнительные 64 байта
    
    sub rsp, 192
    ; Определяем, доступны ли расширения AVX и соответственно регистры YMM
    mov rax, 1
    cpuid
    and ecx, AVXSupport     ; проверяем на поддержку AVX бит 28
    jnz saveAVX         ; если AVX доступно, переходим к метке preserveAVX
    ; Если поддержка расширений AVX отсутствует, сохраняет состояние регистров XMM0-XMM5
     
    movdqu [rsp + 0], xmm0
    movdqu [rsp + 16], xmm1
    movdqu [rsp + 32], xmm2
    movdqu [rsp + 48], xmm3
    movdqu [rsp + 64], xmm4
    movdqu [rsp + 80], xmm5
    jmp afterSave
    ; сохраняем состояние регистров YMM0-YMM5
saveAVX: 
    vmovdqu [rsp + 0], ymm0
    vmovdqu [rsp + 32], ymm1
    vmovdqu [rsp + 64], ymm2
    vmovdqu [rsp + 96], ymm3
    vmovdqu [rsp + 128], ymm4
    vmovdqu [rsp + 160], ymm5

afterSave:      ; здесь какие-нибудь действия в программе
    mov rax, 15      ; например, поместим в регистр RAX какое-нибудь число
    movq xmm0, rax    ; и из регистра rax в xmm0, чтобы он изменился

; восстанавливаем значения регистров - в регистре ECX по прежнему значение для AVXSupport
; можно его использовать, чтобы заново не вызывать инструкцию cpuid
    and ecx, AVXSupport     ; проверяем на поддержку AVX бит 28
    jnz restoreAVX         ; если AVX доступно, переходим к метке restoreAVX

; если не доступно AVX, восстанавливаем регистры XMM
    movdqu xmm0, [rsp] 
    movdqu xmm1, [rsp + 16] 
    movdqu xmm2, [rsp + 32] 
    movdqu xmm3, [rsp + 48] 
    movdqu xmm4, [rsp + 64] 
    movdqu xmm5, [rsp + 80]
    jmp exit
; восстанавливаем регистры YMM
restoreAVX:
    vmovdqu ymm0, [rsp + 0]
    vmovdqu ymm1, [rsp + 32]
    vmovdqu ymm2, [rsp + 64]
    vmovdqu ymm3, [rsp + 96]
    vmovdqu ymm4, [rsp + 128]
    vmovdqu ymm5, [rsp + 160]
exit:
    add rsp, 192

    movq rdi, xmm0    ; проверяем восстановленное значение - должно быть число 22
    mov rax, 60
    syscall

Как и в общем случае, проблема может заключаться в том, что мы можем не знать точно, какие именно расширения поддерживаются на текущей машине. Поэтому сначала с помощью инструкции cpuid проверяем наличие поддержки регистров YMM. Для этого к результату инструкции в регистре ECX применяем маску AVXSupport, которая выявляет установку 28 бита (который собственно и говорит о поддержке AVX):

mov rax, 1
cpuid
and ecx, AVXSupport     ; проверяем на поддержку AVX бит 28
jnz saveAVX 

Если бит установлен, соответственно после выполнения инструкции AND результат ненулевой, то переходим к метке saveAVX для сохранения состояния регистров YMM. Если бит не установлен, то сохраняем регистры XMM.

Для сохранения 6 регистров XMM нужно 16 байт * 6 = 96 байт. На сохранение 6 регистров YMM - 196 байт. В данном случае для простоты для обоих типов регистров в стеке выделяется 196 байт. Затем последовательно сохраняем регистры:

; Если поддержка расширений AVX отсутствует, сохраняет состояние регистров XMM0-XMM5
    movdqu [rsp + 0], xmm0
    movdqu [rsp + 16], xmm1
    movdqu [rsp + 32], xmm2
    movdqu [rsp + 48], xmm3
    movdqu [rsp + 64], xmm4
    movdqu [rsp + 80], xmm5
    jmp afterSave
    ; сохраняем состояние регистров YMM0-YMM5
saveAVX: 
    vmovdqu [rsp + 0], ymm0
    vmovdqu [rsp + 32], ymm1
    vmovdqu [rsp + 64], ymm2
    vmovdqu [rsp + 96], ymm3
    vmovdqu [rsp + 128], ymm4
    vmovdqu [rsp + 160], ymm5

При завершении программы аналогично восстанавливаем регистры.

Аналогичная программа на Windows:

global _start

section .data
AVXSupport equ 0x10000000  ; проверяем бит 28

section .text
_start:
    mov rax, 22
    movq xmm0, rax    ; для теста помещаем некоторое число в xmm0

    ; Резервируем в стеке место  для регистров AVX/SSE
    ; Для 6 регистров SSE необходимо только 96 байт
    ; Для 6 регистров AVX 196 байт 
    ; Выделяем одно пространство для двух типов расширений - 196 байт
    ; Тогда SSE просто игнорируют дополнительные 64 байта
    
    sub rsp, 192
    ; Определяем, доступны ли расширения AVX и соответственно регистры YMM
    mov rax, 1
    cpuid
    and ecx, AVXSupport     ; проверяем на поддержку AVX бит 28
    jnz saveAVX         ; если AVX доступно, переходим к метке preserveAVX
    ; Если поддержка расширений AVX отсутствует, сохраняет состояние регистров XMM0-XMM5
     
    movdqu [rsp + 0], xmm0
    movdqu [rsp + 16], xmm1
    movdqu [rsp + 32], xmm2
    movdqu [rsp + 48], xmm3
    movdqu [rsp + 64], xmm4
    movdqu [rsp + 80], xmm5
    jmp afterSave
    ; сохраняем состояние регистров YMM0-YMM5
saveAVX: 
    vmovdqu [rsp + 0], ymm0
    vmovdqu [rsp + 32], ymm1
    vmovdqu [rsp + 64], ymm2
    vmovdqu [rsp + 96], ymm3
    vmovdqu [rsp + 128], ymm4
    vmovdqu [rsp + 160], ymm5

afterSave:      ; здесь какие-нибудь действия в программе
    mov rax, 15      ; например, поместим в регистр RAX какое-нибудь число
    movq xmm0, rax    ; и из регистра rax в xmm0, чтобы он изменился

; восстанавливаем значения регистров - в регистре ECX по прежнему значение для AVXSupport
; можно его использовать, чтобы заново не вызывать инструкцию cpuid
    and ecx, AVXSupport     ; проверяем на поддержку AVX бит 28
    jnz restoreAVX         ; если AVX доступно, переходим к метке restoreAVX

; если не доступно AVX, восстанавливаем регистры XMM
    movdqu xmm0, [rsp] 
    movdqu xmm1, [rsp + 16] 
    movdqu xmm2, [rsp + 32] 
    movdqu xmm3, [rsp + 48] 
    movdqu xmm4, [rsp + 64] 
    movdqu xmm5, [rsp + 80]
    jmp exit
; восстанавливаем регистры YMM
restoreAVX:
    vmovdqu ymm0, [rsp + 0]
    vmovdqu ymm1, [rsp + 32]
    vmovdqu ymm2, [rsp + 64]
    vmovdqu ymm3, [rsp + 96]
    vmovdqu ymm4, [rsp + 128]
    vmovdqu ymm5, [rsp + 160]
exit:
    add rsp, 192

    movq rax, xmm0    ; проверяем восстановленное значение - должно быть число 22
    ret
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850