===============================
Zajęcia 2: wstawki assemblerowe
===============================

Data: 03.03.2020

.. contents::

.. toctree::
   :hidden:

   zadanie


Materiały dodatkowe
===================

- :ref:`02-zadanie`
- https://gcc.gnu.org/onlinedocs/gcc-8.3.0/gcc/Using-Assembly-Language-with-C.html


Jak używać assemblera w programach napisanych w C
=================================================

Jeśli chcemy użyć instrukcji assemblera w kodzie napisanym w języku C, mamy kilka możliwości:

1. Uznać, że wcale nie chcemy dotykać assemblera, napisać przenośny kod i zaufać optymalizatorowi.

2. Użyć rozszerzeń kompilatora niezależnych od architektury (``__vector__``, ``__builtin_popcount``, ...).

3. Użyć rozszerzeń kompilatora zależnych od architektury (tzw. intrinsics, np. ``_mm_aesenc_si128``).

4. Użyć wstawki assemblerowej (``__asm__``).

5. Napisać całą funkcję w assemblerze i wywołać ją w C.


Wstawki assemblerowe są najbardziej delikatnym i niebezpiecznym z powyższych mechanizmów.


Wstawki assemblerowe w gcc
==========================

Składnia wstawki assemblerowej jest zależna od kompilatora.  W przypadku gcc i clanga jest
ona następująca::


   __asm__ <opcjonalnie volatile> (
      "<kod assemblera>"
      : <lista wyjść>
      : <lista wejść>
      : <lista nadpisanych rejestrĂłw>
   );

W gcc, pisząc wstawkę assemblerową efektywnie definiujemy nową instrukcję assemblera
w kompilatorze, która będzie traktowana przez backend kompilatora na równi ze zwykłymi.
Tak jak w przypadku zwykłych instrukcji, kompilator musi dokładnie znać zachowanie naszej
wstawki -- jej wejścia, wyjścia oraz możliwości optymalizacji.

Domyślnie, kod wstawki podlega optymalizacjom przepływu danych -- gcc zakłada, że wstawka
assemblera nie ma skutków ubocznych (innych niż zapisanie wyjść) i może usunąć wstawkę
gdy jej wyjścia nie są używane, przestawić ją z innym kodem, bądź zduplikować.  Żeby tego
uniknąć, możemy użyć słowa ``volatile`` -- to zagwarantuje, że nasza wstawka wykona się
dokładnie jeden raz, gdy zostanie używa.

Kod assemblera we wstawce jest szablonem tekstu, który zostanie wyemitowany bezpośrednio
do pliku assemblerowego będącego wyjściem kompilatora.  W tym szablonie gcc podstawi
zaalokowane wejścia i wyjścia w odpowiednie miejsca.

Listy wejść i wyjść opisują dane, których będzie używać nasza wstawka.  Każdy wpis opisuje
zmienną (bądź wyrażenie) w C, która zostanie podstawiona w odpowiednim miejscu szablonu
oraz ograniczenia na miejsce, w którym powinna zostać umieszczona -- np. ``"g"`` oznacza
rejestr ogólnego przeznaczenia, a ``"m"`` oznacza pamięć.

Lista nadpisanych rejestrĂłw opisuje, co nasza wstawka modyfikuje jako skutki uboczne.  MoĹźemy
tam użyć nazw rejestrów, napisać ``"cc"`` jeśli nasza wstawka modyfikuje rejestr znaczników
(``rflags`` i jego odpowiedniki), bądź napisać ``"memory"``, jeśli nasza wstawka modyfikuje
struktury w pamięci (inne niż te przekazane jako wyjścia).

Przykład (bezużyteczny) wstawki assemblerowej::

    int a, b, c;

    // a = b + c;
    __asm__ (
        "addl %1, %0"
        : "=g"(a)
        : "gm"(b), "0"(c)
        : "cc"
    );

W tym wypadku mówimy, że gcc może umieścić ``b`` w rejestrze bądź pamięci (dwa różne ograniczenia),
``a`` musi umieścić w rejestrze, a ``c`` musi umieścić w tym samym rejestrze, co argument 0
(czyli a).  Kompilator wykona odpowiednią alokację rejestrów i podstawi wybrane rejestry
(bądź adres pamięci) w miejsce ``%0`` i ``%1`` w trakcie emitowania kodu assemblera.

Czasem potrzebujemy dokładniejszego ograniczenia lokalizacji wejścia/wyjścia do konkretnego
rejestru (np. w przypadku syscalli).  Żeby to zrobić, używamy ograniczenia ``"g"``
w połączeniu ze zmienną zdefiniowaną w następujący sposób::

    register int b __asm__("eax");
    // a = b + c;
    __asm__ (
        "addl %%eax, %0"
        : "=g"(a)
        : "g"(b), "0"(c)
        : "cc"
    );

Wstawki assemblerowe mogą zawierać więcej niż jedną instrukcję (choć należy tego unikać)
-- oddzielamy je wtedy przez ``;`` bądź ``\n`` w tekście szablonu.  Jeśli potrzebujemy
zdefiniować etykietę wewnątrz wstawki, nie powinniśmy nadawać jej nazwy (będzie ona kolidować,
jeśli kompilator postanowi zduplikować kod wstawki) -- zamiast tego, powinniśmy użyć etykiet
lokalnych (https://sourceware.org/binutils/docs/as/Symbol-Names.html).

Dla przykładu, wykonanie syscalla ``read`` na x86_64::

   ssize_t read (int fd_, void *ptr_, size_t len_) {
       register int sys_nr __asm__("eax") = __NR_read;
       register ssize_t res __asm__("rax");
       register int fd __asm__("edi") = fd_;
       register void *ptr __asm__("rsi") = ptr_;
       register size_t len __asm__("rdx") = len_;
       __asm__ volatile (
           "syscall"
           : "=g"(res)
           : "g"(sys_nr), "g"(fd), "g"(ptr), "g"(len)
           : "cc", "rcx", "r11", "memory"
       );
       return res;
   }