Pourquoi le compilateur GCC a omis du code?

9

Je ne comprends pas pourquoi le compilateur GCC coupe une partie de mon code alors qu'il conserve absolument le même dans le quartier?

Le code C:

#define setb_SYNCO do{(PORTA|= (1<<0));} while(0);

ISR(INT0_vect){
    unsigned char i;

    i = 10;
    while(i>0)i--;   // first pause - omitted

    setb_SYNCO;
    setb_GATE;
    i=30;
    clrb_SYNCO;
    while(i>0)i--;  // second pause - preserved
    clrb_GATE;
}

La partie correspondante de LSS (fichier assembleur, créé par le compilateur):

ISR(INT0_vect){
  a4:   1f 92           push    r1
  a6:   0f 92           push    r0
  a8:   0f b6           in  r0, 0x3f    ; 63
  aa:   0f 92           push    r0
  ac:   11 24           eor r1, r1
  ae:   8f 93           push    r24
    unsigned char i;

    i = 10;
    while(i>0)i--;

    setb_SYNCO;
  b0:   d8 9a           sbi 0x1b, 0 ; 27
    setb_GATE;
  b2:   d9 9a           sbi 0x1b, 1 ; 27
    i=30;
    clrb_SYNCO;
  b4:   d8 98           cbi 0x1b, 0 ; 27
  b6:   8e e1           ldi r24, 0x1E   ; 30
  b8:   81 50           subi    r24, 0x01   ; 1
    while(i>0)i--;
  ba:   f1 f7           brne    .-4         ; 0xb8 <__vector_1+0x14>
    clrb_GATE;
  bc:   d9 98           cbi 0x1b, 1 ; 27
}
  be:   8f 91           pop r24
  c0:   0f 90           pop r0
  c2:   0f be           out 0x3f, r0    ; 63
  c4:   0f 90           pop r0
  c6:   1f 90           pop r1
  c8:   18 95           reti

Je pourrais supposer que le compilateur a compris qu'un tel code est factice et qu'il le supprime, mais pourquoi conserve-t-il le même à la fin du code?

Existe-t-il des instructions du compilateur pour empêcher une telle optimisation?

Roman Matveev
la source
1
Vous pouvez également dire au compilateur de ne pas optimiser une seule fonction, il vaut peut-être la peine d'essayer cette méthode avec votre ISR. Voir cette question sur stackoverflow.
Vladimir Cravero
2
Hé Roman, j'ai ajouté la balise "c" à ta question en supprimant atmega. J'ai dû supprimer une balise car il y a une limite (cinq), et lorsque vous posez une question liée au code, ajouter le nom de la langue comme balise est génial car tout le code (Q & As) est mis en surbrillance.
Vladimir Cravero
5
De manière générale, les langages de niveau supérieur (comme C) sont explicitement conçus pour ne pas être liés à une relation 1: 1 avec leur assemblage résultant. Si vous devez compter les instructions pour obtenir le bon timing, vous devez toujours vous fier à l'assemblage (comme l'ont fait certaines réponses). L'intérêt des langages de haut niveau est que le compilateur a une certaine liberté pour rendre votre code plus rapide, plus fort et meilleur qu'auparavant. Les détails comme les allocations de registre et les prédictions de branche sont beaucoup mieux laissés au compilateur ... sauf dans des moments comme celui-ci où vous, le programmeur, connaissez exactement les instructions que vous voulez.
Cort Ammon
5
La meilleure question est, pourquoi GCC n'optimise-t-il pas ces deux boucles?
Ilmari Karonen
5
Gcc déroule d'abord la boucle et remarque seulement que le code correspondant est inutile. Avec une taille de boucle de 30, le déroulement serait stupide et gcc ne le fait pas. À un niveau d'optimisation plus élevé, les deux sont optimisés.
Marc Glisse

Réponses:

9

Étant donné que dans un commentaire, vous dites que "chaque coche de CPU est digne", je suggère d'utiliser un assemblage en ligne pour faire votre boucle de retards comme vous le souhaitez. Cette solution est supérieure aux différentes volatileou -O0parce qu'elle indique clairement votre intention.

unsigned char i = 10;
__asm__ volatile ( "loop: subi    %0, 0x01\n\t"
                   "      brne    loop"
                   : "+rm" (i)
                   : /* no inputs */
                   : /* no dirty registers to decleare*/);

Cela devrait faire l'affaire. La chose volatile est là pour dire au compilateur "Je sais que cela ne fait rien, gardez-le et faites-moi confiance". Les trois "instructions" asm sont assez explicites, vous pouvez utiliser n'importe quel registre au lieu de r24, je crois que le compilateur aime les registres inférieurs, donc vous voudrez peut-être utiliser un registre élevé. Après la première, :vous devez répertorier les variables c en sortie (en lecture et en écriture), et il n'y en a pas, après la seconde, :vous devez répertorier les variables c en entrée (uniquement), encore une fois, il n'y en a pas, et le troisième paramètre est une liste séparée par des virgules de registres modifiés , dans ce cas r24. Je ne sais pas si vous devez également inclure le registre d'état puisque le ZEROdrapeau change bien sûr, je ne l'ai pas inclus.

éditez la réponse éditée comme OP demandé. Quelques notes.

Le "+rm"avant (i)signifie que vous laisser le compilateur décident de placer i en m Emory ou dans un r egistrecanadiendesins. C'est une bonne chose dans la plupart des cas, car le compilateur peut mieux optimiser s'il est gratuit. Dans votre cas, je pense que vous ne souhaitez conserver que la contrainte r pour forcer i à être un registre.

Vladimir Cravero
la source
On dirait que c'est une chose dont j'ai vraiment besoin. Mais pourriez-vous modifier votre réponse pour accepter n'importe quelle cvariable au lieu du littéral que 10j'ai mentionné dans la réponse d'origine? J'essaie de lire les manuels du GCC concernant l'utilisation correcte de la construction asm mais c'est un peu obscurci pour moi maintenant. J'apprécierais beaucoup!
Roman Matveev,
1
@RomanMatveev modifié comme vous l'avez demandé
Vladimir Cravero
13

Vous pouvez essayer de faire en sorte que la boucle fasse quelque chose. En l'état, le compilateur dit à juste titre "Cette boucle ne fait rien - je m'en débarrasserai".

Vous pouvez donc essayer une construction que j'utilise fréquemment:

int i;
for (i = 0; i < 10; i++) {
    asm volatile ("nop");
}

Remarque: toutes les cibles du compilateur gcc n'utilisent pas la même syntaxe d'assemblage en ligne - vous devrez peut-être la modifier pour votre cible.

Majenko
la source
Votre solution semble beaucoup plus élégante que la mienne ... peut-être que la mienne est meilleure lorsque le nombre de cycles PRECISE est requis? Je veux dire, le tout n'est pas garanti d'être compilé d'une certaine manière, n'est-ce pas?
Vladimir Cravero
8
Le simple fait qu'il utilise C signifie que vous ne pouvez pas garantir les cycles. Si vous avez besoin d'un comptage de cycle précis, l'ASM est la seule solution. Je veux dire, vous obtenez un timing différent avec la boucle C si vous avez -funroll-loops activé que si vous ne le faites pas, etc.
Majenko
Ouais, c'est ce que je pensais. Lorsque vous effectuez des retards matériels avec des valeurs i suffisamment élevées (100 ou plus), je suppose que votre solution donne pratiquement les mêmes résultats tout en améliorant la lisibilité.
Vladimir Cravero
6

Oui, vous pouvez supposer cela. Si vous déclarez la variable i comme volatile, vous dites au compilateur de ne pas optimiser sur i.

Ambiorix
la source
1
Ce n'est pas entièrement vrai à mon avis.
Vladimir Cravero
1
@ VladimirCravero, que voulez-vous dire en disant cela? Pourriez-vous être plus clair?
Roman Matveev
2
Ce que je voulais dire, c'est que je ne serais pas si sûr de ce que fait un compilateur. La déclaration d'une variable volatile indique au compilateur qu'elle peut changer ailleurs, donc elle devrait vraiment faire cela pendant.
Vladimir Cravero
1
@Roman Matveev register unsigned char volatile i __asm__("r1");peut-être?
a3f
2
Déclarer ivolatile résout tout. Ceci est garanti par la norme C 5.1.2.3. Un compilateur conforme ne doit pas optimiser ces boucles s'il iest volatil. Heureusement, GCC est un compilateur conforme. Il existe malheureusement de nombreux compilateurs C potentiels qui ne sont pas conformes à la norme, mais ce n'est pas pertinent pour cette question particulière. Il n'y a absolument aucun besoin d'assembleur en ligne.
Lundin
1

Après la première boucle iest une constante. L'initialisation de iet la boucle ne font que produire une valeur constante. Rien dans la norme ne spécifie que cette boucle doit être compilée telle quelle. La norme ne dit rien non plus sur le timing. Le code compilé doit se comporter comme si la boucle était présente et c'est le cas. Vous ne pouvez pas dire de manière fiable que cette optimisation a été effectuée sous la norme (le timing ne compte pas).

La deuxième boucle doit également être supprimée. Je considère que c'est un bug (ou une optimisation manquante) que ce n'est pas le cas. Après la boucle iest zéro constant. Le code doit être remplacé par une mise ià zéro.

Je pense que GCC reste ipurement pour la raison que vous effectuez un accès au port (opaque) pourrait affecter i.

Utilisation

asm volatile ("nop");

pour faire croire à GCC que la boucle fait quelque chose.

usr
la source