Co ASM volatile robi w C?

Zajrzałem do jakiegoś kodu C z

Http://www.mcs.anl.gov / ~kazutomo/rdtsc.html

Używają rzeczy typu " inline", "asm " itd jak poniżej:

Kod1:

static __inline__ tick gettick (void) {
    unsigned a, d;
    __asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) );
    return (((tick)a) | (((tick)d) << 32));
}

Code2:

volatile int  __attribute__((noinline)) foo2 (int a0, int a1) {
    __asm__ __volatile__ ("");
}

Zastanawiałem się, co robi code1 i code2?

Author: Deduplicator, 2014-10-20

3 answers

Modyfikator __volatile__ na bloku __asm__ zmusza optymalizator kompilatora do wykonania kodu w stanie takim, w jakim jest. Bez niego optymalizator może myśleć, że można go usunąć bezpośrednio lub wyciągnąć z pętli i buforować.

Jest to przydatne dla rdtsc instrukcji jak tak:

__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) )

To nie wymaga zależności, więc kompilator może założyć, że wartość może być buforowana. Lotny jest używany, aby zmusić go do odczytania świeżej znacznika czasu.

Gdy używane samodzielnie, tak:

__asm__ __volatile__ ("")

It niczego nie wykona. Można to jednak rozszerzyć, aby uzyskać barierę pamięci w czasie kompilacji, która nie pozwoli na zmianę kolejności instrukcji dostępu do pamięci: {]}

__asm__ __volatile__ ("":::"memory")

Instrukcja rdtsc jest dobrym przykładem dla lotnych. rdtsc jest zwykle używany, gdy potrzebujesz czasu, jaki zajmuje wykonanie niektórych instrukcji. Wyobraź sobie taki kod, w którym chcesz wykonać r1 i r2:

__asm__ ("rdtsc": "=a" (a0), "=d" (d0) )
r1 = x1 + y1;
__asm__ ("rdtsc": "=a" (a1), "=d" (d1) )
r2 = x2 + y2;
__asm__ ("rdtsc": "=a" (a2), "=d" (d2) )

Tutaj kompilator ma prawo buforować znacznik czasu i jest poprawny wyjście może pokazywać, że każda linia wymagała dokładnie 0 zegarów do wykonania. Oczywiście nie jest to to, czego chcesz, więc wprowadzasz __volatile__, aby zapobiec buforowaniu:

__asm__ __volatile__("rdtsc": "=a" (a0), "=d" (d0))
r1 = x1 + y1;
__asm__ __volatile__("rdtsc": "=a" (a1), "=d" (d1))
r2 = x2 + y2;
__asm__ __volatile__("rdtsc": "=a" (a2), "=d" (d2))

Teraz otrzymasz nowy znacznik czasu za każdym razem, ale nadal ma problem, że zarówno kompilator, jak i procesor mogą zmienić kolejność wszystkich tych instrukcji. Może skończyć się wykonaniem bloków asm po tym, jak r1 i r2 zostały już obliczone. Aby to obejść, dodałbyś pewne bariery, które wymuszają serializacja:

__asm__ __volatile__("mfence;rdtsc": "=a" (a0), "=d" (d0) :: "memory")
r1 = x1 + y1;
__asm__ __volatile__("mfence;rdtsc": "=a" (a1), "=d" (d1) :: "memory")
r2 = x2 + y2;
__asm__ __volatile__("mfence;rdtsc": "=a" (a2), "=d" (d2) :: "memory")

Zwróć uwagę na instrukcję mfence, która wymusza barierę po stronie CPU, oraz specyfikację "pamięci" w bloku lotnym, która wymusza barierę czasu kompilacji. Na nowoczesnych procesorach możesz zastąpić mfence:rdtsc rdtscp czymś bardziej wydajnym.

 51
Author: Cory Nelson,
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
2017-12-12 16:25:17

asm służy do włączania natywnego kodu asemblera do kodu źródłowego C. Np.

int a = 2;
asm("mov a, 3");
printf("%i", a); // will print 3

Kompilatory mają różne jego warianty. __asm__ powinien być synonimem, może z pewnymi różnicami specyficznymi dla kompilatora.

volatile oznacza, że zmienna może być modyfikowana z zewnątrz (aka nie przez program C). Na przykład podczas programowania mikrokontrolera, gdzie adres pamięci 0x0000x1234 jest mapowany do interfejsu określonego urządzenia (np. podczas kodowania dla Gameboya, przyciski / screen / etc są / align = "left" / )

volatile std::uint8_t* const button1 = 0x00001111;

Ta wyłączona optymalizacja kompilatora, która opiera się na *button1 nie zmienia się, chyba że zostanie zmieniona przez kod.

Jest również używany w programowaniu wielowątkowym (obecnie już nie jest potrzebny?), gdzie zmienna może być modyfikowana przez inny wątek.

inline jest wskazówką dla kompilatora do" inline " wywołań funkcji.

inline int f(int a) {
    return a + 1
}

int a;
int b = f(a);

To nie powinno być kompilowane do wywołania funkcji f, ale do int b = a + 1. Jakby f gdzie makro. Kompilatory najczęściej optymalizacja ta odbywa się automatycznie w zależności od użycia funkcji/zawartości. __inline__ w tym przykładzie może mieć bardziej konkretne znaczenie.

Podobnie __attribute__((noinline)) (składnia specyficzna dla GCC) zapobiega inlinowaniu funkcji.

 3
Author: tmlen,
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
2014-10-19 23:37:53

Atrybut __asm__ określa nazwę, która ma być użyta w kodzie asemblera dla funkcji lub zmiennej.

Kwalifikator __volatile__, zwykle używany w obliczeniach w czasie rzeczywistym systemów wbudowanych, rozwiązuje problem z testami kompilatora status register dla bitu ERROR lub READY, powodując problemy podczas optymalizacji. __volatile__ został wprowadzony jako sposób mówienia kompilatorowi, że obiekt podlega szybkiej zmianie i wymusić, aby każde odniesienie obiektu było prawdziwym odniesieniem.

 -1
Author: David C. Rankin,
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
2017-10-24 23:23:28