Główne różnice pomiędzy składnią Intela a AT&T
OpCode źrodlo, cel
$1, $322, $0xffff
movl zmienna, %ebx
movl (%ebx), %eax
[base + index*scale + disp]w AT&T odpowiada
disp(base, index, scale)np.
movl %eax, -0xf4(%ebx, %ecx, 4)Tutaj stałych nie należy poprzedzać znakiem $.
Do umieszczania prostych wstawek używamy instrukcji
asm( "Instrukcje asemblerowe");
Powoduje to proste wklejenie danego tekstu (kodu asemblerowego) do pliku generowanego przez gcc, bez żadnego sprawdzenia poprawności, np.
asm("movl %ecx, %eax \n\t addl %ebx, %eax");
Dlatego też konieczne jest np. wstawienie znaków nowej linii po każdej instrukcji.
Można też kolejne instrukcje oddzielać średnikiem i umieszczać je w osobnych łancuchach tekstowych.
asm("movl %ecx, %eax ;" "addl %ebx %eax;");
Jeżeli nasz kod modyfikuje jakiś rejestr i nie przywraca jego wartości może to prowadzić do błędnego działania programu. Kompilator (zwłaszcza podczas optymalizacji) może umieszczać pewne zmienne w rejestrach i zakładać, że one się nie zmienią.
Ogólna postać wstawki asemblerowej ma postać
asm ( "szablon instrukcji asemblerowych" : wyjściowe operandy /* optional */ : wejściowe operandy /* optional */ : lista niszczonych obiektów /* optional */ );
"xxx" (wartosc)gdzie xxx określa wymagania co do miejsca umieszczenia wartości np.
int x=2, y=3, result; asm( "addl %%ebx, %%eax" // : "=a"(result) // : "a"(x), "b"(y) // );
Ze względów optymalizacyjnych dobrze jest wybór rejestru do przekazywania parametrów pozostawić kompilatorowi, można też przekazywać zmienne w pamięci.
int x=2, y=3, result; asm( "movl %1, %%eax;" "addl %2, %%eax;" "movl %%eax, %0" : "=r"(result) : "r"(x), "m"(y) : "eax" );
Jeżeli chcemy zmodyfikować tylko zawartość rejestru bez niekoniecznego przepisywania jego wartości do rejestru wyjściowego, możemy powiązać rejestr wejściowy z wyjściowym. W tym celu w wymaganiach dla rejestru wejściowego podać numer rejestru wyjściowego np.
int x = 12345; asm("and %2, %0 \n\t" : "=r"(x) : "0"(x), "i"(0xffffff00) : );
Wynik
subq $16, %rsp # int x = 12345; movl $12345, -4(%rbp) movl -4(%rbp), %eax and $-256, %eax # wstawka movl %eax, -4(%rbp)
Jeżeli tak jak w kodzie poniżej używamy rejestrów wyjściowych do obliczeń mogą wystąpić problemy
int x=2, y=3, result; asm( "movl %1, %0;" "addl %2, %0;" : "=r"(result) : "r"(x), "r"(y) : );
Wyjściem jest poinformowanie kompilatora, że rejestr wyjściowy jest wcześnie nadpisywany i w takim razie nie powinien umieszczać w nim danych wejściowych. W tym celu używamy modyfikatora &
int x=2, y=3, result; asm( "movl %1, %0;" "addl %2, %0;" : "=&r"(result) : "r"(x), "r"(y) );
Należy nie nadużywać tego modyfikatora gdyż może to uniemożliwić optymalizację kodu.
; Rejestry ogólnego przeznaczenia "a" = eax, ax, al "b" = ebx, bx, bl "c" = ecx, cx, cl "d" = edx, dx, dl "S" = esi, si "D" = edi, di "A" = para rejestrów EDX:EAX (użyteczne np. przy zwracaniu 64 bitowych wyników) "r" = dowolny rejestr ogólnego przeznaczenia "q" = jeden z rejestrów a, b, c, d ; Adres pamięci "m" = zmienna w pamięci, operacje są wykonywane bezpośrednio na pamięci, powinno być używane gdy nie chcemy przechowywać wartości w rejestrze ; Rejestry zmiennoprzecinkowe "f" = dowolny rejestr zmiennoprzecinkowy "t" = ST0 "u" = ST1 ; Stałe "i" = stała całkowita, "I" = stała z zakresu 0..31 (dla przesunięć) "N" = stała z zakresu 0..255 "E" = stała zmiennoprzecinkowa
Wstawki można pisać w składni Intela. W tym celu należy we wstawce umieścić dyrektywę
.intel_syntax noprefix, a na końcu przywrócić składnię AT&T poprzez dyrektywę .att_syntax prefix
asm(".intel_syntax noprefix\n" "mov eax, ecx \n" "add eax, ebx \n" ".att_syntax prefix");
Obecnie odchodzi się od wstawek assemblerowych na rzecz funkcji wewnętrznych (intristic functions). Są to funkcje podobne do funkcji inline, zazwyczaj wykonujące konkretną operację assemblerową. The Intel Intrinsics Guide