Jaka jest różnica między "asm", " asm " i "asm"?
Z tego co wiem, jedyną różnicą między __asm { ... };
i __asm__("...");
jest to, że pierwszy używa mov eax, var
, a drugi używa movl %0, %%eax
z :"=r" (var)
na końcu. Jakie są inne różnice? A co z po prostu asm
?
4 answers
To, którego używasz, zależy od Twojego kompilatora. To nie jest standard jak w języku C.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-24 01:33:31
Istnieje ogromna różnica między MSVC inline asm i GNU C inline asm. Składnia GCC jest zaprojektowana z myślą o optymalnym wyjściu bez marnowania instrukcji, do owijania pojedynczej instrukcji czy czegoś takiego. Składnia MSVC jest zaprojektowana tak, aby była dość prosta, ale AFAICT jest niemożliwa do użycia bez opóźnień i dodatkowych instrukcji z pamięci round trip through dla Twoich wejść i wyjść.
Jeśli używasz wbudowanego asm ze względu na wydajność, to MSVC inline asm jest opłacalne tylko wtedy, gdy zapis całej pętli w całości w asm, Nie w celu zawijania krótkich sekwencji w funkcję wbudowaną. Poniższy przykład (zawijanie idiv
z Funkcją) to coś, co MSVC jest złe w: ~8 dodatkowych instrukcji store/load.
- patrzy na Twój asm, aby dowiedzieć się, który rejestruje Twój kod.
- może przesyłać dane tylko poprzez pamięć. Dane, które były żywe w rejestrach to przechowywany przez kompilator, aby przygotować się do
mov ecx, shift_count
, na przykład. Więc używanie pojedynczej instrukcji asm, której kompilator nie wygeneruje, wiąże się z przelotem przez pamięć w drodze wejścia i wyjścia. - bardziej przyjazny dla początkujących, ale często niemożliwe, aby uniknąć napowietrznych pobieranie danych w/out . Nawet poza ograniczeniami składni, optymalizator w obecnych wersjach MSVC nie jest dobry w optymalizacji wokół wbudowanych bloków asm.
GNU C inline asm nie jest dobrym sposobem na naukę asm . Musisz bardzo dobrze zrozumieć asm, aby móc powiedzieć kompilatorowi o swoim kodzie. I musisz zrozumieć, co Kompilatory muszą wiedzieć. Ta odpowiedź zawiera również linki do innych przewodników inline-asm oraz Q&As. x86 tag wiki ma wiele dobrych rzeczy dla asm w ogóle, ale tylko linki do nich dla GNU inline asm. (Rzeczy zawarte w tej odpowiedzi dotyczą GNU inline asm również na platformach innych niż x86.)
Składnia GNU C inline asm jest używany przez gcc, clang, icc i być może niektóre komercyjne Kompilatory implementujące GNU C: {]}
- musisz powiedzieć kompilatorowi, co trzepoczysz. Niewykonanie tego spowoduje złamanie otaczającego kodu w nieoczywisty, trudny do debugowania sposób.
- potężna, ale trudna do odczytania, nauczenia się i użycia składnia do mówienia kompilatorowi, jak dostarczać dane wejściowe i gdzie szukać danych wyjściowych. np.
"c" (shift_count)
spowoduje, że kompilator umieści zmiennąshift_count
wecx
przed Twoim ASM biegnie. -
Extra clunky dla dużych bloków kodu, ponieważ asm musi być wewnątrz stałej ciągu. Więc zazwyczaj potrzebujesz
"insn %[inputvar], %%reg\n\t" // comment "insn2 %%reg, %[outputvar]\n\t"
Bardzo niewybaczalne / trudniejsze , ale pozwala na obniżenie napowietrznych esp. do pakowania pojedynczych instrukcji . (owijanie pojedynczych instrukcji było pierwotnym założeniem projektowym, dlatego musisz specjalnie powiedzieć kompilatorowi o wczesnych clobberach, aby przestał używać tego samego rejestru do wprowadzania i wyjścia, jeśli jest to problem.)
Przykład: podział liczby całkowitej o pełnej szerokości(div
)
Na 32-bitowym procesorze, dzieląc 64-bitową liczbę całkowitą przez 32-bitową liczbę całkowitą, lub wykonując pełne mnożenie (32x32 - >64), może korzystać z wbudowanego asm. gcc i clang nie wykorzystują idiv
dla (int64_t)a / (int32_t)b
, prawdopodobnie dlatego, że instrukcja jest błędna, jeśli wynik nie mieści się w rejestrze 32-bitowym. Więc w przeciwieństwie do tego pytania o uzyskanie ilorazu i reszty z jednego div
, jest to przypadek użycia wbudowanego asm. (Chyba że istnieje sposób poinformowania kompilatora, że wynik będzie pasował, więc idiv nie będzie zawinił.)
Użyjemy konwencji wywołujących, które umieszczają niektóre args w rejestrach (z hi
nawet w rejestrzepo prawej ), aby pokazać sytuację, która jest bliższa temu, co można zobaczyć podczas wstawiania małej funkcji takiej jak ta.
MSVC
Należy uważać na konwencje wywołujące register-arg podczas używania inline-asm. Najwyraźniej obsługa inline-asm jest tak źle zaprojektowana/wdrożona, że Kompilator może nie zapisywać/przywracać rejestrów arg wokół wbudowanego asm, jeśli te args nie są używane w wbudowanym asm . Dzięki @ RossRidge za wskazanie tego.
// MSVC. Be careful with _vectorcall & inline-asm: see above
// we could return a struct, but that would complicate things
int _vectorcall div64(int hi, int lo, int divisor, int *premainder) {
int quotient, tmp;
__asm {
mov edx, hi;
mov eax, lo;
idiv divisor
mov quotient, eax
mov tmp, edx;
// mov ecx, premainder // Or this I guess?
// mov [ecx], edx
}
*premainder = tmp;
return quotient; // or omit the return with a value in eax
}
Update: widocznie zostawiając wartość w eax
lub edx:eax
, a następnie spadając z końca funkcji nie-void (bez return
) jest obsługiwany, nawet podczas inliningu. Zakładam, że to działa tylko wtedy, gdy nie ma kodu po asm
instrukcji. Zobacz czy _ _ asm {}; Zwraca wartość eax? pozwala to uniknąć magazynowania / przeładowywania danych wyjściowych( przynajmniej dla quotient
), ale nie możemy nic zrobić z danymi wejściowymi. W funkcji nie-inline z args stosu, będą one już w pamięci, ale w tym przypadku użycia piszemy małą funkcję, która może być użytecznie inline.
Skompilowany z MSVC 19.00.23026 /O2
na rextesterze (Z main()
, który znajduje katalog exe i zrzuca wyjście kompilatora ASM do stdout ).
## My added comments use. ##
; ... define some symbolic constants for stack offsets of parameters
; 48 : int ABI div64(int hi, int lo, int divisor, int *premainder) {
sub esp, 16 ; 00000010H
mov DWORD PTR _lo$[esp+16], edx ## these symbolic constants match up with the names of the stack args and locals
mov DWORD PTR _hi$[esp+16], ecx
## start of __asm {
mov edx, DWORD PTR _hi$[esp+16]
mov eax, DWORD PTR _lo$[esp+16]
idiv DWORD PTR _divisor$[esp+12]
mov DWORD PTR _quotient$[esp+16], eax ## store to a local temporary, not *premainder
mov DWORD PTR _tmp$[esp+16], edx
## end of __asm block
mov ecx, DWORD PTR _premainder$[esp+12]
mov eax, DWORD PTR _tmp$[esp+16]
mov DWORD PTR [ecx], eax ## I guess we should have done this inside the inline asm so this would suck slightly less
mov eax, DWORD PTR _quotient$[esp+16] ## but this one is unavoidable
add esp, 16 ; 00000010H
ret 8
Jest mnóstwo dodatkowych instrukcji mov, a kompilator nawet nie zbliża się do optymalizacji żadnej z nich. Pomyślałem, że może zobaczy i zrozumie mov tmp, edx
wewnątrz ASM inline, i zrobi z tego sklep do premainder
. Ale to wymagałoby załadowania premainder
ze stosu do rejestru przed wbudowanym blokiem asm, jak sądzę.
Ta funkcja jest w rzeczywistości gorsza z _vectorcall
niż z normalnym all-on-the-stack ABI. Z dwoma wejścia w rejestrach, przechowuje je w pamięci, więc wbudowany asm może ładować je z nazwanych zmiennych. Gdyby to było inlined, nawet więcej parametrów mogłoby potencjalnie być w regs, i musiałoby przechowywać je wszystkie, więc asm miałby operands pamięci! Więc w przeciwieństwie do gcc, nie zyskujemy wiele z tego.
Robienie *premainder = tmp
wewnątrz bloku asm oznacza więcej kodu napisanego w asm, ale unika ścieżki totally braindead store / load/store dla pozostałej części. Zmniejsza to Liczba instrukcji w sumie 2, do 11 (nie licząc ret
).
Staram się uzyskać jak najlepszy kod z MSVC, a nie "użyć go źle" i stworzyć argument słomkowego człowieka. Ale AFAICT jest okropny do owijania bardzo krótkich sekwencji. prawdopodobnie istnieje wewnętrzna funkcja podziału 64/32 -> 32, która pozwala kompilatorowi wygenerować dobry kod dla tego konkretnego przypadku, więc cała przesłanka użycia wbudowanego asm do tego w MSVC może być argumentem straw-man . Ale to czy pokazuje, że wewnętrzne są dużo lepsze niż wbudowane asm dla MSVC.
GNU C (gcc/clang/icc)
Gcc robi jeszcze lepiej niż wyjście pokazane tutaj podczas inlining div64, ponieważ zazwyczaj może zorganizować dla poprzedzającego kodu wygenerować 64-bitową liczbę całkowitą w edx: eax w pierwszej kolejności.
Nie mogę zmusić gcc do kompilacji dla 32-bitowego vectorcall ABI. Clang może, ale jest do bani w inline asm z ograniczeniami "rm"
(spróbuj na link godbolt: odbija funkcja arg poprzez pamięć zamiast używania opcji rejestr w ograniczeniu). 64-bitowa konwencja wywołująca MS jest zbliżona do 32-bitowej vectorcall, z dwoma pierwszymi paramami w edx, ecx. Różnica polega na tym, że 2 kolejne paramy przechodzą do regs przed użyciem stosu (i że callee nie usuwa args ze stosu, o co chodziło ret 8
na wyjściu MSVC.)
// GNU C
// change everything to int64_t to do 128b/64b -> 64b division
// MSVC doesn't do x86-64 inline asm, so we'll use 32bit to be comparable
int div64(int lo, int hi, int *premainder, int divisor) {
int quotient, rem;
asm ("idivl %[divsrc]"
: "=a" (quotient), "=d" (rem) // a means eax, d means edx
: "d" (hi), "a" (lo),
[divsrc] "rm" (divisor) // Could have just used %0 instead of naming divsrc
// note the "rm" to allow the src to be in a register or not, whatever gcc chooses.
// "rmi" would also allow an immediate, but unlike adc, idiv doesn't have an immediate form
: // no clobbers
);
*premainder = rem;
return quotient;
}
Opracowano na podstawie materiału źródłowego.gcc -m64 -O3 -mabi=ms -fverbose-asm
. Z -m32 dostajesz tylko 3 ładunki, idiv i sklep, jak widać od zmiany rzeczy w tym linku godbolt.
mov eax, ecx # lo, lo
idivl r9d # divisor
mov DWORD PTR [r8], edx # *premainder_7(D), rem
ret
Dla 32bit vectorcall, gcc zrobiłby coś takiego
## Not real compiler output, but probably similar to what you'd get
mov eax, ecx # lo, lo
mov ecx, [esp+12] # premainder
idivl [esp+16] # divisor
mov DWORD PTR [ecx], edx # *premainder_7(D), rem
ret 8
MSVC używa 13 instrukcji (nie wliczając ret), w porównaniu do 4 instrukcji gcc. Z inliningiem, jak powiedziałem, potencjalnie kompiluje się do tylko jednego, podczas gdy MSVC nadal używałby prawdopodobnie 9. (Nie będzie musiał rezerwować miejsca na stosie ani ładować premainder
; zakładam, że nadal musi przechowywać około 2 z 3 wejść. Następnie przeładowuje je wewnątrz asm, uruchamia idiv
, przechowuje dwa wyjścia, i przeładowuje je poza asm. Czyli 4 ładunki / magazyny dla wejścia i kolejne 4 dla wyjścia.)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-15 23:09:43
asm
vs __asm__
w GCC
asm
nie działa z -std=c99
, masz dwie alternatywy:
- użyj
__asm__
- użycie
-std=gnu99
Więcej szczegółów: błąd:' asm ' (pierwsze użycie w tej funkcji)
__asm
vs __asm__
w GCC
Nie mogłem znaleźć gdzie __asm
jest udokumentowane (szczególnie nie wymienione w https://gcc.gnu.org/onlinedocs/gcc-7.2.0/gcc/Alternate-Keywords.html#Alternate-Keywords ), ale ze źródła GCC 8.1 są dokładnie takie same:
{ "__asm", RID_ASM, 0 },
{ "__asm__", RID_ASM, 0 },
Więc użyłbym __asm__
co jest udokumentowane.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-28 19:02:09
Z kompilatorem gcc nie jest to duża różnica. asm
LUB __asm
lub __asm__
są takie same, po prostu używają, aby uniknąć konfliktu przeznaczenia przestrzeni nazw (istnieje funkcja zdefiniowana przez użytkownika o nazwie asm, itp.)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-07 20:26:44