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?

Author: Peter Cordes, 2010-07-24

4 answers

To, którego używasz, zależy od Twojego kompilatora. To nie jest standard jak w języku C.

 14
Author: Ben Voigt,
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.

MSVC inline asm (używany przez MSVC i prawdopodobnie icc, być może również dostępny w niektórych komercyjnych kompilatorach):
  • 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 w ecx 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.)

 38
Author: Peter Cordes,
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.

 6
Author: Ciro Santilli TRUMP BAN IS BAD,
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.)

 5
Author: oDisPo,
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