Que fait ASM volatile dans C?

J'ai regardé dans un code C de

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

Ils utilisent des trucs comme "inline", "asm" etc comme suit:

Code1:

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__ ("");
}

Je me demandais ce que font les code1 et code2?

33
demandé sur Deduplicator 2014-10-20 03:25:29

3 réponses

Le modificateur __volatile__ sur un bloc __asm__ force l'optimiseur du compilateur à exécuter le code tel quel. Sans cela, l'optimiseur peut penser qu'il peut être supprimé purement et simplement, ou retiré d'une boucle et mis en cache.

Ceci est utile pour l'instruction rdtsc comme ceci:

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

Cela ne prend aucune dépendance, donc le compilateur peut supposer que la valeur peut être mise en cache. Volatile est utilisé pour le forcer à lire un nouvel horodatage.

Lorsqu'il est utilisé seul, comme ceci:

__asm__ __volatile__ ("")

, Il ne sera pas réellement exécuter quoi que ce soit. Vous pouvez cependant étendre cela pour obtenir une barrière de mémoire à la compilation qui ne permettra pas de réorganiser les instructions d'accès à la mémoire:

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

L'instruction rdtsc est un bon exemple pour volatile. rdtsc est généralement utilisé lorsque vous devez chronométrer le temps d'exécution de certaines instructions. Imaginez un code comme celui-ci, où vous voulez chronométrer l'exécution de r1 et 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) )

Ici, le compilateur est effectivement autorisé à mettre en cache l'horodatage, et valide sortie peut montrer que chaque ligne a pris exactement 0 horloges à exécuter. Évidemment, ce n'est pas ce que vous voulez, alors vous introduisez __volatile__ pour empêcher la mise en cache:

__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))

Maintenant, vous obtiendrez un nouvel horodatage à chaque fois, mais il y a toujours un problème que le compilateur et le CPU sont autorisés à réorganiser toutes ces instructions. Il pourrait finir par exécuter les blocs asm après que r1 et r2 ont déjà été calculés. Pour contourner cela, vous ajouteriez des barrières qui forcent sérialisation:

__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")

Notez l'instruction mfence ici, qui impose une barrière côté CPU, et le spécificateur "memory" dans le bloc volatile qui impose une barrière au moment de la compilation. Sur les processeurs modernes, vous pouvez remplacer mfence:rdtsc par rdtscp pour quelque chose de plus efficace.

51
répondu Cory Nelson 2017-12-12 16:25:17

asm est pour inclure le code D'assemblage natif dans le code source C. Par exemple

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

Les compilateurs en ont différentes variantes. __asm__ devrait être synonyme, peut-être avec quelques différences spécifiques au compilateur.

volatile signifie que la variable peut être modifiée de l'extérieur (aka pas par le programme C). Par exemple, lors de la programmation d'un microcontrôleur où l'adresse mémoire 0x0000x1234 est mappée à une interface spécifique à un périphérique (c'est-à-dire lors du codage pour le GameBoy, les boutons / écran / etc sont accessibles de cette manière.)

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

Cela a désactivé les optimisations du compilateur qui reposent sur *button1 ne changeant pas à moins d'être modifié par le code.

Il est également utilisé dans la programmation multi-thread (plus nécessaire aujourd'hui?) où une variable peut être modifiée par un autre thread.

inline est un indice pour le compilateur d'appels "en ligne" à une fonction.

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

int a;
int b = f(a);

Cela ne doit pas être compilé dans un appel de fonction à f mais dans int b = a + 1. Comme si f était une macro. Compilateur la plupart du temps faire cette optimisation automatiquement en fonction de l'utilisation de la fonction/contenu. __inline__ dans cet exemple pourrait avoir une signification plus spécifique.

De manière similaire __attribute__((noinline)) (syntaxe spécifique à GCC) empêche une fonction d'être en ligne.

3
répondu tmlen 2014-10-19 23:37:53

L'attribut __asm__ spécifie le nom à utiliser dans le code assembleur pour la fonction ou la variable.

Le qualificatif __volatile__, généralement utilisé dans le calcul en temps réel des systèmes embarqués, résout un problème avec les tests du compilateur du status register pour le bit ERROR ou READY causant des problèmes lors de l'optimisation. {[1] } a été introduit comme un moyen de dire au compilateur que l'objet est sujet à un changement rapide et de forcer chaque référence de l'objet à être une véritable référence.

-1
répondu David C. Rankin 2017-10-24 23:23:28