Instrukcje SSE (Streaming SIMD Extensions)

Wprowadzenie

SSE: rozszerzenie SIMD wprowadzone w procesorach Intel Pentium III i AMD AthlonXP, znane też jako Katmai New Instructions (KNI). Wspierane od Windows98 i Linuxa z jądrem 2.2.

Osiem nowych 128-bitowych rejestrów, podzielonych na cztery 32-bitowe elementy zmiennopozycyjne pojedynczej precyzji, nazwanych xmm0-xmm7. Rejestr sterujący mxcsr.

Instrukcje

Mnóstwo nowych instrukcji operujących na tych rejestrach (a niektóre także na rejestrach MMX i rejestrach uniwersalnych. Wybór prezentowanych jest mocno fragmentaryczny.

Przesłania (load/store)

Przesłanie wartości 128-bitowych: Przesłania wartości 32-bitowych: Przesłania wartości 64-bitowych:

Tasowanie

Tasowanie (shuffling) służy do zmiany kolejności elementów w pojedynczym rejestrze SSE lub do pomieszania wartości z dwóch takich rejestrów. Argumentami instrukcji shufps są dwa rejestry SSE i 8-bitowa maska. Pierwsze dwa elementy rejestru docelowego są nadpisywane dowolnymi dwoma elementami tego rejestru. Trzeci i czwarty element są nadpisywane dwoma elementami rejestru źródłowego. Wyborem elementów sterują kolejne pary bitów maski, traktowane jako liczby w zakresie 0-3.

Przykłady:

  shufps xmm0,xmm0,0x1b  ;odwraca kolejność elementów (0x1B=00 01 10 11)

  shufps xmm0,xmm0,0xaa  ;cztery kopie trzeciego elementu (0xAA=10 10 10 10)

Instrukcje shufps i shufpd operują na spakowanych liczbach zmiennopozycyjnych. Dla zwykłych wartości są instrukcje pshufb, pshufw i pshufd.

Przykład na policzenie iloczynu (cross product), aby znaleźć wektor prostopadły do dwóch wektorów.

;; float *cross_product (float V1[4], float V2[4], float W[4])

;; Find the cross product of two constant vectors and return it.

;; W.x = V1.y * V2.z - V1.z * V2.y
;; W.y = V1.z * V2.x - V1.x * V2.z
;; W.z = V1.x * V2.y - V1.y * V2.x

        global cross_product
        section .text

cross_product:
        push ebp
        mov ebp,esp
        mov eax,[ebp+8]          ;Adresy argumentów do rejestrów
        mov ebx,[ebp+12]

        movups xmm0,[eax]        ;Jeśli wyrównane to movaps
        movups xmm1,[ebx]   

        movaps xmm2,xmm0         ;Kopie
        movaps xmm3,xmm1

        shufps xmm0,xmm0,0xd8    ;Zamieniamy miejscami 2 i 3 element (V1)
        shufps xmm1,xmm1,0xe1    ;Zamieniamy miejscami 1 i 2 element (V2)
        mulps  xmm0,xmm1
               
        shufps xmm2,xmm2,0xe1    ;Zamieniamy miejscami 1 i 2 element (V1)
        shufps xmm3,xmm3,0xd8    ;Zamieniamy miejscami 2 i 3 element (V2)
        mulps  xmm2,xmm3
              
        subps  xmm0,xmm2

        mov eax,[ebp+16]
        movups [eax],xmm0        ;Wynik
        pop ebp
        ret

Arytmetyka zmiennopozycyjna

Podstawowe operacje arytmetyczne mają dwa warianty, rozróżniane przyrostkami:

Dla podwójnej precyzji drugą literą jest 'd'.

Argumenty dla operacji zmiennopozycyjnych nie mogą być stałymi, dozwolone są tylko rejestry i komórki pamięci (adresy).

Dodawanie: addss, addps.
Odejmowanie: subss, subps.
Mnożenie: mulss, mulps.
Dzielenie: divss, divps.

Odwrotność (reciprocal, 1/x): rcpss, rcpps. Operacje binarne, np.

   rcpps xmm1,xmm2

Pierwiastek kwadratowy: sqrtss, sqrtps. Operacje binarne, np.

   sqrtss xmm1,xmm2

Odwrotność pierwiastka kwadratowego: rsqrtss, rsqrtps.

Maksimum: maxss, maxps.
Minimum: minss, minps.

Przykład: dodawanie 4-elementowych wektorów

;; float *vector_add (float V1[4], float V2[4], float W[4])

        global vector_add
        section .text

vector_add:
        push ebp
        mov ebp,esp
        mov eax,[ebp+8]          ;Adresy argumentów do rejestrów
        mov ebx,[ebp+12]

        movups xmm0,[eax]        ;Pobranie argumentów
        movups xmm1,[ebx]

        addps xmm0,xmm1

        mov eax,[ebp+16]
        movups [eax],xmm0        ;Wynik
        pop ebp
        ret

Instrukcja

        movmskps rax,xmm1
umieszcza bity znaku dla czterech 32-bitowych liczb zmiennopozycyjnych w podanym rejestrze (na 4 dolnych bitach, reszta wyzerowana).

Arytmetyka całkowita

Średnia arytmetyczna:

Maksima i minima

Egzotyka:

Logika

Porównania

Porównania nie ustawiają flag (bo mogłoby nie wystarczyć flag), zamiast tego wyniki przekazywane w pierwszym argumencie.

Dla pojedynczych liczb zmiennopozycyjnych są porównania ustawiające flagi:

Flagi są ustawiane jak dla porównania liczb całkowitych bez znaku, ale flaga parzystości PF jest ustawiana, gdy którymś argumentem jest NaN.

Konwersje

Dla liczb zmiennopozycyjnych:

Pojedyncze między liczbą całkowitą a zmiennopozycyjną, argument w zwykłym rejestrze lub w pamięci, wynik w rejestrze XMM (,,na dole''). Pozostałe pozycje nie ulegają zmianie:

Odwrotnie, wynik w zwykłym rejestrze, argument w rejestrze XMM lub w pamięci. Z obcięciem (truncation).

Równoległe:

Z nasycaniem:

Stan

Zachowywanie:

32-bitowy rejestr mxcsr zawiera flagi informujące o wynikach obliczeń i sterujące przebiegiem obliczeń dla instrukcji SSE. Bity 0-15 są zdefiniowane następująco

NazwaNumer bituOpis
FZ15Flush To Zero
R+14Round Positive
R-13Round Negative
PM12Precision Mask
UM11Underflow Mask
OM10Overflow Mask
ZM9Divide By Zero Mask
DM8Denormal Mask
IM7Invalid Operation Mask
DAZ6Denormals Are Zero
PE5Precision Flag
UE4Underflow Flag
OE3Overflow Flag
ZE2Divide By Zero Flag
DE1Denormal Flag
IE0Invalid Operation Flag

Ustawienie flagi FZ powoduje zwracanie zera w przypadku niedomiaru (underflow). Przyśpiesza obliczenia, ale czasem zmniejsza dokładność.

Flagi R+ i R- określają kierunek zaokrąglania dla najniższego bitu. Jeśli obie są ustawione (oznaczane czasem RZ: Round To Zero), to zaokrąglanie jest do zera. Normalnie obie są wyzerowane (RN: Round To Nearest) i wtedy zaokrągla się do bliższej wartości.

Flagi PM, UM, MM, ZM, DM i IM służą do maskowania odpowiednich wyjątków.

Flaga DAZ powoduje zamianę powstających Denormals (małych liczb, które nie mogą być znormalizowane) na zero. Występuje tylko w niektórych procesorach, przed zmienianiem trzeba upewnić się, że jest obsługiwany.

Flagi PE, UE, ME, ZE, DE i IE są ustawiane po wystąpieniu odpowiedniego wyjątku. Są one ,,lepkie'', tzn. raz ustawione pozostają w tym stanie i muszą być zerowane ręcznie. Umożliwia to jednokrotne sprawdzanie na koniec ciągu operacji, ale może powodować zgubienie informacji o tym, że pewien wyjątek wystąpił kilkakrotnie.

Sterowanie pamięcią buforową (cache)