Wstawki asemblerowe - Wydział Fizyki i Informatyki Stosowanej
Transkrypt
Wstawki asemblerowe - Wydział Fizyki i Informatyki Stosowanej
Wstawki asemblerowe Michał Wasek, ˛ Remigiusz Rohulko SWiR Wydział Fizyki i Informatyki Stosowanej 25.04.2013 M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 1 / 35 Spis treści 1 Spis treści 2 Wstep ˛ 3 Problem atomowości 4 Troche˛ o składni 5 Zmienne z C w asm 6 Extended asm 7 Optymalizacja M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 2 / 35 Co to właściwie jest? Wstawka asemblerowa Jest to potoczna nazwa kodu asemblerowego osadzonego w kodzie jezyka ˛ wysokiego poziomu. Wiele jezyków ˛ wysokiego poziomu pozwala na taka˛ wstawk˛e. Przykład wstawki w C: __asm__ { "instrukcja \n" "instrukcja \n" }; __ Przed i po asm nie sa˛ konieczne, kompilator GNU ropoznaje obie formy. Kompilator Visual Studio akceptuje __asm. Faktycznie jest to wywołanie funkcji asm, która przyjmuje jeden argument typu const char const* M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 3 / 35 Wykonanie wstawki Instrukcje musza˛ być od siebie oddzielone znakiem nowej linii lub średnikiem. Moga˛ być umieszczone w jednym zestawie " " lub każda linia w osobnych " ". Wykonanie wstawki przypomina procedure˛ przetwarzania funkcji inline. Kod asemblerowy wstawiony jest dokładnie* w to miejsce, pod którym byśmy sie˛ go spodziewali, tzn. w trakcie wykonania programu nie wystapi ˛ narzut zwiazany ˛ z przeskokiem pod odległy adres. M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 4 / 35 Zalety wstawek Kiedy warto użyć wstawki? z kiedy potrzebujemy dostepu ˛ do zależnych od maszyny portów i rejestrów, z kiedy po analizie kodu wygenerowanego przez kompilator dochodzimy do wniosku, że można to zrobić lepiej (optymalizacja), z kiedy może wystapić ˛ problem z atomowościa˛ operacji. M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 5 / 35 Problem atomowości Atomowość operacji Jest to gwarancja, że ciag ˛ instrukcji zostanie wykonany w pełni lub w ogóle. Piszac ˛ kod w C nigdy nie ma gwarancji, że wykonywana operacja (np. a=a+1) wykona sie˛ w sposób niepodzielny (a++ też nie). Łatwo może dojść do sytuacji, w której jeden proces odczyta wartość a aby dodać do niej 1, ale zanim doda, to drugi proces również odczyta a i doda do niej 1, nastepnie ˛ jeden zapisze swoja˛ wartość i drugi zapisze. Efektem bedzie ˛ to, że skutek odniesie tylko jedna z operacji a oba procesy beda ˛ przekonane, ze ich operacja wykonała sie˛ prawidłowo. M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 6 / 35 Problem atomowości, c.d. Pożadanym ˛ byłoby posiadanie operacji realizujacych ˛ instrukcje w sposób atomowy. O pewnych instrukcjach asemblera z góry wiemy, że sa˛ atomowe. Instrukcje asemblera dla procesorów 80x86 sa˛ niepodzielne gdy: z pobieraja˛ zawartość pamieci ˛ 0 lub 1 raz z pobieraja˛ zawartość pamieci ˛ i nastepnie ˛ zapisuja˛ do pamieci ˛ (np. dec, inc) i żaden inny procesor nie przejał˛ szyny danych miedzy ˛ wczytaniem i zapisem, w systemie jednoprocesorowym sa˛ zawsze niepodzielne z instrukcje j.w. ale zaczynaja˛ sie˛ od instrukcji lock, wtedy sa˛ niepodzielne również w systemie wieloprocesorowym, szyna danych jest wówczas blokowana na czas wykonania całej instrukcji M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 7 / 35 Rozwiazanie ˛ tego problemu w kodzie linuksa Jadro ˛ Linuksa dostarcza zestawu operacji pozwalajacych ˛ wykonać operacje niepodzielne na atomowym typie danych: typedef struct { volatile int counter; } atomic_t; Operacje inicjalizacji, odczytu i zapisu wartości sa˛ operacjami pobierajacymi ˛ coś z pamieci ˛ 0 lub 1 razy zatem bed ˛ a˛ atomowe bez dodatkowej pomocy. W przypadku operacji dodawania do licznika mamy do czynienia z operacja˛ siegaj ˛ ac ˛ a˛ do pamieci ˛ po stara˛ wartość i nastepnie ˛ zapisujac ˛ a˛ nowa, ˛ wiec istnieje tutaj problem opisany wyżej na przykładzie a=a+1. Operacja jest realizowana poprzez nastepuj ˛ acy ˛ kod: M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 8 / 35 Rozwiazanie ˛ tego problemu w kodzie linuksa static __inline__ void atomic_add(int i, atomic_t *v) { __asm__ __volatile__( LOCK "addl %1,%0" :"=m" (v->counter) :"ir" (i), "m" (v->counter)); } Procedura wykonuje zwykłe asemblerowe dodawanie i dla systemu jednoprocesorowego działa to dobrze. Działa to również dobrze dla systemu wieloprocesorowego dzieki ˛ makru LOCK, jakie poprzedza instrukcje˛ addl, a które zdefiniowane jest na poczatku ˛ pliku: M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 9 / 35 Rozwiazanie ˛ tego problemu w kodzie linuksa #ifdef CONFIG_SMP #define LOCK "lock ; " #else #define LOCK "" #endif Jeżeli jadro ˛ zostało skompilowane dla systemu wieloprocesorowego (Symmetrical multiprocessing), to makro LOCK definiowane jest jako lock ; i dodawane jest do instrukcji addl. Instrukcja lock jak zostało opisane wyżej, blokuje szyne˛ danych, dzieki ˛ czemu addl wykonuje sie˛ niepodzielnie również na systemach wieloprocesorowych. M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 10 / 35 Troche˛ o składni Kompilator GNU jezyka ˛ C generuje kod asemblerowy w składni AT&T, dlatego też jest ona preferowana we wstawkach. Istnieje jednak możliwość zmiany składni na intelowska, ˛ tak jak to jest możliwe w przypadku "czystego" asemblera. asm(".intel_syntax noprefix"); asm("mov eax, ebx"); Powrót do składni AT&T: asm(".att_syntax prefix"); asm("mov %ebx, %eax"); M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 11 / 35 Zmienne z C w asm W standardowym asm, we wstawkach, mozemy korzystac ze zmiennych z C, ale musza byc one zadeklarowane jako globalne! Przykład: globalc_asm.c M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 12 / 35 Extended asm Wstawka Extended asm wyglada ˛ tak: asm ("assembly code" : output locations : input operands : changed registers); Gdzie: 1 assembly code - kod asemblerowy używajacy składni standardowego asm 2 output locations - lista rejestrów i miejsc w pamieci, ˛ które bed ˛ a˛ zawierać zmienne wyjściowe 3 input operands - lista rejestrów i miejsc w pamieci, ˛ które zawieraja˛ zmienne wejściowe 4 changed registers lub clobbered registers - lista zmiennych rejestrów M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 13 / 35 Extended asm - zmienne lokalne Ważna rzecz: w Extended asm możemy używać zmiennych z C zadeklarowanych lokalnie! Przykład: io_asm.c M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 14 / 35 Extended asm - constraint Zmienne wyjścia i wejścia w standardowym asm sa˛ automatycznie ustawiane do rejestrów i miejsc w pamieci. ˛ W rozszerzonym asm sytuacja wyglada ˛ inaczej. "constraint"(variable); Gdzie: 1 constraint - miejsce, gdzie ma sie˛ znaleźć zmienna 2 variable - zmienna M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 15 / 35 Extended asm - tabela rejestrów M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 16 / 35 Extended asm - rejestry W extended asm troszk˛e inaczej używamy rejestrów. Używamy podwójnego znaku procenta - %% rejestr. Nie musimy zawsze wykorzystywać rejestru wyjściowego - niektóre instrukcje same zauważaja, ˛ że input zawiera wartości wyjściowe. Przykład: input_asm.c M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 17 / 35 Extended asm - placeholders Placeholdery pozwalaja˛ na wpisywanie zmiennych bezpośrednio do rejestrów. Sa˛ to liczby, ktore poprzedza znak procentu. Każda zmienna zapisana we wstawce asm dostaje numer uzależniony od tego, kiedy pojawiła sie˛ w tekście - numerujemy od 0. asm ("assembly code" : "=r"(result) : "r"(data1), "r"(data2)); Majac ˛ taki kawałek kodu możemy zauważyć, że zmienna result bedzie ˛ przypisana do %0, zmienna data1 do %1, natomiast do data2 bedzie ˛ można sie˛ odwoływać za pomoca˛ %2. Przykład: placeholders_asm.c M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 18 / 35 Extended asm - placeholders Używajac ˛ placeholderów możemy użyć tej samej zmiennej jako wejściowej i wynikowej. asm ("imull %1, %0" : "=r"(data2) : "r"(data1), "0"(data2)); Przykład: placeholders_asm2.c M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 19 / 35 Extended asm - alternative placeholders Extended asm pozwala na używanie placeholderów również w alternatywny sposób. Zamiast liczb możemy deklarować własne nazwy dla placeholderów. %[name] "constraint"(variable) name jest tutaj identyfikatorem placeholdera, w którym znajduje sie˛ zmienna. Przykład: altplaceholders_asm.c M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 20 / 35 Extended asm - flagi dla listy zmiennych rejestrów Jeżeli w liście zmiennych rejestrów chcemy używać miejsc w pamieci, ˛ to musimy ustawić flage˛ ”memory”. Jeżeli zostanie zmieniony kod musimy dodać słowo ”cc”. asm ("movl %1, %%eax\n\t" "addl %%eax, %0" : "=r"(result) : "r"(data1), "0"(result) : "%eax"); M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 21 / 35 Extended asm - FPU Ponieważ FPU(Floating Point Unit) używa rejestrów jako stosu, to musimy z tym uważać. Do wartości zmiennoprzecinkowych używamy 3 różnych ograniczeń: 1 f - wszelkie dostepne ˛ referencje do rejestru zmiennoprzecinkowego 2 t - referencje do głównego rejestru zmiennoprzecinkowego 3 u - referencje do drugiego rejestru zmiennoprzecinkowego Dla zmiennych wyjściowych nie możemy korzystać z ograniczenia f. Przykład: fpvalues_asm.c M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 22 / 35 Extended asm - jump W Extended asm możemy używać etykiet, dzieki ˛ którym możemy obsługiwać JUMP’y. Istnieja˛ dwie reguły, którymi trzeba sie˛ kierować: 1 można użyć jump’a tylko do etykiety znajdujacej ˛ sie˛ w tej samej sekcji asm 2 majac ˛ wiecej ˛ wstawek asemblerowych nie można używać etykiet z poprzednich - skończy sie˛ to błedem. ˛ Jako zmiennych możemy używać liczb. Przykład: jump_asm.c M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 23 / 35 Optymalizacja Generalnie rzecz biorac, ˛ optymalizacja wykonana przez kompilator bedzie ˛ lepsza, niż wykonana recznie. ˛ Ale tylko jeśli mówimy o skali całego programu. Nie sposób zapisać recznie ˛ tysiecy ˛ linii kodu C w asemblerze. Jeśli jednak rozpatrzymy mniejsze fragmenty kodu, istnieje tutaj duże pole do popisu, gdyż optymalizacja automatyczna cz˛esto nie jest wysokiej jakości. M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 24 / 35 Optymalizacja Uwaga! Asemblerowy kod wstawki również podlega optymalizacji! (Chyba, że powiemy, że ma nie podlegać). Należy pamietać ˛ o tym, że nasz kod, który piszemy aby zrobić coś lepiej, niż zrobiłby kompilator, również może zostać "zoptymalizowany", co może zniweczyć efekty naszej pracy. Przykładem optymalizacji może być kod wykonujacy ˛ rotacje˛ bitów. asm("mov %[result], %[value], ror #1" : [result] "=r" (y) : [value] "r" (x)); M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 25 / 35 Przykład automatycznej optymalizacji Poniżej przykłady dwóch różnych kodów wygenerowanych po optymalizacji: 00309DE5 E330A0E1 04308DE5 ldr mov str r3, [sp, #0] r3, r3, ror #1 r3, [sp, #4] @ x, x @ tmp, x @ tmp, y i: E420A0E1 mov r2, r4, ror #1 M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe @ y, x 2013 26 / 35 Przykład automatycznej optymalizacji Może być niestety gorzej. Kompilator może zdecydować, aby w ogóle nie właczać ˛ kodu naszej wstawki do kodu wynikowego. Decyzja o właczeniu ˛ lub nie naszego kodu podejmowana jest na podstawie kontekstu. Jeśli nasza wstawka nie ma wpływu na kod poza nia˛ (np. nie używa wyjściowych operandów) istnieje duże prawdopodobieństwo, że zostanie usunieta. ˛ Kompilator może uznać nasze instrukcje jako zbedne, ˛ jedynie spowalniajace ˛ wykonanie programu. M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 27 / 35 Przykład automatycznej optymalizacji Jak można temu zaradzić? asm volatile("mov r0, r0"); Modyfikator volatile mówi, żeby kompilator nie ważył sie˛ ruszać naszego kodu. I kompilator nas posłucha. Ale to nadal nie gwarantuje, że wszystko bedzie ˛ cacy. Dla kompilatora "nie ruszać", znaczy nie ingerować w strukture˛ wewnetrzn ˛ a˛ != nie przenosić... Rozważmy przykład: i++; if (j == 1) x += 3; i++; M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 28 / 35 Przykład automatycznej optymalizacji Istnieje duża szansa, że podczas optymalizacji powstanie coś takiego: if (j == 1) x += 3; i += 2; Ponieważ nie wyglada, ˛ żeby wartość zmiennej i miała wpływ na zawartość instrukcji if. Modyfikator volatile w dokumentacji GCC The volatile keyword indicates that the instruction has important side-effects. GCC will not delete a volatile asm if it is reachable. M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 29 / 35 Przykład automatycznej optymalizacji Rozważmy kolejny przykład: chcemy pomnożyć przez siebie dwie liczby, tyle że obie moga˛ być w dowolnym momencie zmienione poprzez przerwanie. Co robimy? Wyłaczamy ˛ przerwania a potem zaraz właczamy. ˛ asm volatile("mrs r12, cpsr\n\t" "orr r12, r12, #0xC0\n\t" "msr cpsr_c, r12\n\t" ::: "r12", "cc"); c *= b; asm volatile("mrs r12, cpsr\n" "bic r12, r12, #0xC0\n" "msr cpsr_c, r12" ::: "r12", "cc"); Czy to już wystarczy? Nie. Kompilator może najpierw wykonać mnożenie a potem wyłaczyć ˛ i zaraz właczyć ˛ przerwania... M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 30 / 35 Przykład automatycznej optymalizacji Z rozwiazaniem ˛ przychodzi nam modyfikator "memory" w sekcji "clobbered list". __asm__("cli": : :"memory"); // Will cause the stateme __asm__ __volatile__("cli": : :"memory"); // Will caus Należy go użyć w przypadku kiedy nie do końca mamy świadomość, które rejestry zostana˛ użyte przez nasza˛ wstawk˛e. Zmusza to kompilator to ostrożnego obchodzenia sie˛ z naszym kodem i co ważne - uniemożliwia przesuniecie ˛ go w inne miejsce, ponieważ może sie˛ zdarzyć, że wynik wstawki jest zależny od miejsca gdzie ja˛ wykonamy. M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 31 / 35 Wstawki jako funkcje Jeśli nasza wstawka jest użyteczna w wiecej ˛ niż jednym miejscu, możemy wstawić jej treść w funkcje. ˛ Unikniemy konieczności kopiowania i utrzymywania treści wstawki w wielu miejscach. unsigned long ByteSwap(unsigned long val) { asm volatile ( "eor r3, %1, %1, ror #16\n\t" "bic r3, r3, #0x00FF0000\n\t" "mov %0, %1, ror #8\n\t" "eor %0, %0, r3, lsr #8" : "=r" (val) : "0"(val) : "r3" ); return val; } M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 32 / 35 Wymuszanie użycia konkretnych rejestrów Zmienne lokalne moga˛ być przechowywane w rejestrach. Definiujac ˛ zmienna˛ lokalna˛ możemy wskazać rejestr, w którym bedzie ˛ ona przechowywana. void Count(void) { { register unsigned char counter asm("r3"); asm volatile("eor r3, r3, r3"); } } Kompilator gwarantuje nam, że rejestr bedzie ˛ zarezerwowany dla naszej zmiennej jedynie do czasu, kiedy uzna, że jest ona jeszcze potrzebna. To kompilator decyduje, kiedy użyje rejestru do innych celów. Należy tutaj "mieć umiar", ponieważ jeśli bedziemy ˛ każda˛ zmienna˛ w tej sposób deklarować, może sie˛ okazać, że kompilator bedzie ˛ zmuszony korzystać z pamieci ˛ podrecznej ˛ a nie z rejestrów, co wpłynie negatywnie na szybkość działania programu. M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 33 / 35 Operacje wektorowe Kolejna˛ możliwościa˛ przyspieszenia wykonywania kodu zawierajacego ˛ wiele petli ˛ może być wektoryzacja. Używajac ˛ gcc i "typowego" procesora w sporej mierze można polegać na kompilatorze, dzieki ˛ tzw. builtin’om. Sa˛ to rozszerzenia kompilatora, dzieki ˛ którym możemy mu wskazać, które operacje moga˛ być traktowane wektorowo. Jeśli jednak sami chcemy taki kod napisać, to nie pozostaje nam nic innego jak skorzystać ze wstawki asemblerowej. Przykład: vector.c M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 34 / 35 Źródła z http://gcc.gnu.org/ z http://www.ethernut.de/en/documents/arm-inline-asm.html z http://wiki.osdev.org/Inline_Assembly z http://www.ibm.com/developerworks/rational/library/inlineassembly-C-Cpp-guide/ z http://asm.sourceforge.net/articles/rmiyagi-inline-asm.txt z http://www.codeproject.com/Articles/15971/Using-InlineAssembly-in-C-C z http://locklessinc.com/articles/vectorize/ M. Wasek, ˛ R. Rohulko (AGH, WFiS) Wstawki asemblerowe 2013 35 / 35