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?
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.
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.
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.