2010-10-10 6 views
15

Je n'arrive pas à comprendre le rôle joué par les contraintes dans l'assemblage en ligne GCC (x86). J'ai read the manual, qui explique exactement ce que chaque contrainte fait. Le problème est que même si je comprends ce que chaque contrainte fait, je comprends très peu pourquoi vous utiliseriez une contrainte plutôt qu'une autre, ou quelles seraient les implications. Je me rends compte que c'est un sujet très vaste, donc un petit exemple devrait aider à affiner le focus. Ce qui suit est une routine asm simple qui ajoute simplement deux nombres. Si un débordement d'entier se produit, il écrit une valeur de 1 dans une variable de sortie C.Assemblage en ligne GCC: contraintes

int32_t a = 10, b = 5; 
int32_t c = 0; // overflow flag 

__asm__ 
(
    "addl %2,%3;"  // Do a + b (the result goes into b) 
    "jno 0f;"   // Jump ahead if an overflow occurred 
    "movl $1, %1;"  // Copy 1 into c 
    "0:"     // We're done. 

    :"=r"(b), "=m"(c) // Output list 
    :"r"(a), "0"(b)  // Input list 
); 

Maintenant, cela fonctionne très bien, sauf que je devais jouer du violon arbitraire avec les contraintes jusqu'à ce que je l'ai eu à travailler correctement. A l'origine, je les contraintes suivantes:

:"=r"(b), "=m"(c) // Output list 
    :"r"(a), "m"(b)  // Input list 

Notez qu'au lieu d'un « 0 », j'utilise une contrainte « m » pour b. Cela a eu un effet secondaire étrange où si je compilé avec des indicateurs d'optimisation et appelé la fonction deux fois, pour une raison quelconque le résultat de l'opération d'addition serait également stocké dans c. J'ai fini par lire "matching constraints", ce qui vous permet de spécifier qu'une variable doit être utilisée comme opérande d'entrée et de sortie. Quand j'ai changé "m"(b) à "0"(b) cela a fonctionné.

Mais je ne comprends pas vraiment pourquoi vous utiliseriez une contrainte plutôt qu'une autre. Je veux dire oui, je comprends que "r" signifie que la variable devrait être dans un registre et "m" signifie qu'il devrait être en mémoire - mais je ne comprends pas vraiment les implications de choisir un sur un autre, ou pourquoi l'opération d'addition ne fonctionne pas correctement si je choisis une certaine combinaison de contraintes.

Questions: 1) Dans l'exemple de code ci-dessus, pourquoi la contrainte "m" sur b a-t-elle entraîné l'écriture de c? 2) Existe-t-il des tutoriels ou des ressources en ligne qui détaillent les contraintes?

Répondre

13

Voici un exemple pour mieux illustrer pourquoi vous devriez choisir des contraintes soigneusement (même fonction que le vôtre, mais peut-être écrit un peu plus succinctement):

bool add_and_check_overflow(int32_t& a, int32_t b) 
{ 
    bool result; 
    __asm__("addl %2, %1; seto %b0" 
      : "=q" (result), "+g" (a) 
      : "r" (b)); 
    return result; 
} 

Ainsi, les contraintes utilisées étaient: q, r et g.

  • qeax signifie que, ecx, edx ou ebx pourrait être sélectionné. En effet, les instructions set* doivent écrire dans un registre adressable sur 8 bits (al, ah, ...). L'utilisation de b dans les moyens %b0, utilise la partie la plus basse de 8 bits (al, cl, ...).
  • Pour la plupart des instructions à deux opérandes, au moins un des opérandes doit être un registre. N'utilisez donc pas m ou g pour les deux; utilisez r pour au moins un des opérandes. Pour l'opérande final, peu importe que ce soit le registre ou la mémoire, utilisez g (général).

Dans l'exemple ci-dessus, j'ai choisi d'utiliser g (plutôt que r) pour a parce que les références sont généralement mis en œuvre en tant que pointeurs de mémoire, donc en utilisant une contrainte r aurait exigé la copie du référent à un registre d'abord, puis copier en arrière. En utilisant g, le référent pourrait être mis à jour directement.


Quant à savoir pourquoi votre version originale avec c votre écrasait la valeur de l'ajout, c'est parce que vous avez spécifié dans =m la fente de sortie, plutôt que (par exemple) +m; cela signifie que le compilateur est autorisé à réutiliser le même emplacement de mémoire pour l'entrée et la sortie.

Dans votre cas, cela signifie que deux résultats (depuis le même emplacement de mémoire a été utilisé pour b et c):

  • L'addition ne déborde pas: alors, c mais j'ai remplacée par la valeur de b (le résultat de l'addition).
  • L'addition a fait un débordement: alors, c est devenu 1 (et b peut devenir 1 aussi, selon la façon dont le code a été généré).
+0

Merci - c'est une excellente réponse. Juste une précision: pourquoi le modificateur de contrainte '=' (en écriture seule) donne-t-il au compilateur le droit de réutiliser le même emplacement mémoire, même si 'b' et' c' sont des variables différentes avec des emplacements différents en mémoire? – Channel72

+0

@ Channel72: "même si' b' et 'c' sont des variables différentes avec des emplacements différents en mémoire" --- c'est en fait une hypothèse majeure, qui souvent ne s'applique pas. Si 'b' et' c' sont des variables locales, il y a de fortes chances qu'ils soient tous deux sauvegardés par des registres, plutôt que par un emplacement mémoire. Dans ce cas, l'emplacement de mémoire est simplement un emplacement temporaire qui est configuré uniquement pour s'adapter à votre contrainte 'm' --- dans ce cas,' b' et 'c' pourraient très bien utiliser le même emplacement temporaire. –

+0

Maintenant, si 'b' et' c' étaient réellement tous les deux vraiment sauvegardés par des emplacements de mémoire, alors vous auriez raison, normalement ils ne devraient pas se chevaucher du tout. Et, si l'un est soutenu par la mémoire et l'autre est soutenu par le registre ... alors l'un ou l'autre de ces scénarios est possible. –

Questions connexes