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

Podobne dokumenty