Partage
  • Partager sur Facebook
  • Partager sur Twitter

bug clang ?

Sujet résolu
    9 juin 2021 à 15:25:34

    salut à toutes et tous

    j'ai un code qui fonctione en debug mais pas en optimisé et uniquement avec clang pas avec gcc. je voulais savoir votre avis si c'est un bug ou si c'est normal ou je sais pas. j'ai fait un code minimal pour vous faire la demo.

    #include <stdio.h>
    
    int test()
    {
    	while(1)
    	{
    		for(int i=10; i<100; i++)
    			if ( i*i > i*i*i )
    				return 1;
    	}
    	return 0;
    }
    
    int main()
    {
    	printf("On test : ");
    	if (test()) printf("VRAI\n");
    	else        printf("FAUX\n");
    }
    

    comme un carré est toujours plus petit qu'un cube si c'est plus grand que 10 je dois jamais sortir du while et ca doit tourné en rond. c'est ce que ça fait avec clang en mode debug mais si je demande un O3 alors ça plante plus et ça m'affiche vrai.

    je compile avec clang comme ça

    clang -Werror -Wall -Wextra -O3 -o simple_clang_O3 simple.c et clang -Werror -Wall -Wextra -g -o simple_clang_g simple.c

    j'ai pas de message et si je lance simple_clang_O3 il m'affiche On test : VRAI et il m'affiche rien avec l'autre et s'arrête pas.

    ca le fait pas avec gcc.

    vous avez des idées ? le O3 de clang est cassé  et je dois plus l'utilisé ou je passe avec gcc qui est plus sure ?

    • Partager sur Facebook
    • Partager sur Twitter
      9 juin 2021 à 17:10:58

      gcc est beaucoup plus sûre comparé à clang vu que c'est fait POUR LE C , il n'y a pas d'erreur dans ta commande ?

      peut-être que les fichiers compilé avec le compilateur clang est pas pris en charge par gcc

      -
      Edité par AntoineBarbier12 9 juin 2021 à 17:12:26

      • Partager sur Facebook
      • Partager sur Twitter
        9 juin 2021 à 17:14:57

        Si on regarde le code assembleur généré
        test:                                   # @test
        	.cfi_startproc
        # %bb.0:
        .LBB0_1:                                # =>This Loop Header: Depth=1
                                                #     Child Loop BB0_2 Depth 2
        	movl	$10, %eax
        	.p2align	4, 0x90
        .LBB0_2:                                #   Parent Loop BB0_1 Depth=1
                                                # =>  This Inner Loop Header: Depth=2
        	movl	%eax, %ecx
        	imull	%eax, %ecx
        	movl	%ecx, %edx
        	imull	%eax, %edx
        	cmpl	%edx, %ecx
        	ja	.LBB0_8
        # %bb.3:                                #   in Loop: Header=BB0_2 Depth=2
        	leal	1(%rax), %ecx
        	movl	%ecx, %edx
        	imull	%ecx, %edx
        	imull	%edx, %ecx
        	cmpl	%ecx, %edx
        	ja	.LBB0_8
        # %bb.4:                                #   in Loop: Header=BB0_2 Depth=2
        	leal	2(%rax), %ecx
        	movl	%ecx, %edx
        	imull	%ecx, %edx
        	imull	%edx, %ecx
        	cmpl	%ecx, %edx
        	ja	.LBB0_8
        # %bb.5:                                #   in Loop: Header=BB0_2 Depth=2
        	leal	3(%rax), %ecx
        	movl	%ecx, %edx
        	imull	%ecx, %edx
        	imull	%edx, %ecx
        	cmpl	%ecx, %edx
        	ja	.LBB0_8
        # %bb.6:                                #   in Loop: Header=BB0_2 Depth=2
        	leal	4(%rax), %ecx
        	movl	%ecx, %edx
        	imull	%ecx, %edx
        	imull	%edx, %ecx
        	cmpl	%ecx, %edx
        	ja	.LBB0_8
        # %bb.7:                                #   in Loop: Header=BB0_2 Depth=2
        	addl	$5, %eax
        	cmpl	$100, %eax
        	jb	.LBB0_2
        	jmp	.LBB0_1
        .LBB0_8:
        	movl	$1, %eax
        	retq
        
        • il y a  eu un dépliage (la boucle se fait sur eax qui va de 5 en 5)
        • la boucle extérieure a sauté.
        • et de toutes façon en retourne 1

        Ca ressemble a une optimisation un peu agressive, qui a un résultat observable équivalent si ça ne boucle pas.  C'est à dire que le compilateur laisse tomber les comportements qui bouclent sans produire de résultat visible.

        Idées :

        • les instructions de la boucle for ne peuvent que boucler indéfiniment, ou retourner 1.
        • la boucle while(true) fait toujours la même chose : boucler ou retourner 1. On peut "donc" ne la faire qu'une seule fois.
        • dommage qu'il n'ait pas poussé plus loin :  la fonction aurait pu juste retourner 1
        Si on bidouille un peu la fonction elle revient à un comportement conforme. Par exemple
        • si on faire un return i au lieu de return 1.
        • si on ajoute un printf("...") à côté du return
        parce que là, l' "optimisation" ne peut plus se faire.
        ----
        il faut quand même dire qu'un code de la forme
        while (true) {
          ...
        }
        return qqchose;
        
        et qui ne contient pas de break dans la boucle, ce n'est certainement pas la meilleure façon d'exprimer  quelque chose de sensé.
        Certes, il fait quelque chose quand il s'exécute, mais bon...

        -
        Edité par michelbillaud 10 juin 2021 à 8:29:11

        • Partager sur Facebook
        • Partager sur Twitter
          9 juin 2021 à 17:41:21

          alors c'est un bug parceque le comportement est pas le même avec ou sans O3, on est bien d'accord? et la version bug c'est celle du O3 parceque en plus le bug saute si on rajoute un printf ?
          • Partager sur Facebook
          • Partager sur Twitter
            9 juin 2021 à 17:49:48

            Entièrement d'accord avec @michelbillaud. C'est ce qu'on appelle une optimisation agressive, tellement agressive qu'elle en produit un exécutable faux.

            Donc oui, je dirai que c'est bel un bug clang. Quelle version de clang as-tu ? J'ai réussi à reproduire le souci avec clang 11.1.0 x86_64-pc-linux-gnu et la gnu libc.

            Tout comme Michel, j'imagine que l'optimisation est :

            • on part du principe que le code fourni par le dèv est correct (erreur de la part de clang) ;
            • il remarque qu'il y a une boucle infinie et qu'on ne peut en sortir que par le return 1, il n'y a aucun autre moyen.

            avec ça il se dit que comme le code est correct, il sort de la boucle et comme sortir de la boucle renvoie 1 c'est ce qu'il fait. Il doit le faire également parce que ton code n'a aucun effet de bord, ce qui expliquerait que rajouter un printf modifie l'optimisation, et que c'est une fonction constante = quel que soit l'état du programme il renverra toujours la même valeur, 1 en l'occurence.

            Mais bon, il faudrait vérifier tout mes dires … c'est à vue de nez.

            • Partager sur Facebook
            • Partager sur Twitter
              9 juin 2021 à 17:52:10

              Quand on commence une phrase par "on est bien d'accord que", c'est une provocation au chipotage
              • ça dépend de ce qu'on appelle un bug.  
              • Est-ce que le programme marche plus mal dans un cas que dans l'autre ? :-)
              • C'est clairement écrit un peu partout dans la doc que, quand on met des options d'optimisation , ça peut s'éloigner de ce qui est écrit dans le standard "that may violate  strict  compliance with language standards"
              Le problème, c'est pas gcc ou clang, c'est que le langage C est mal foutu, et que c'est très difficile de s'assurer qu'une optimisation "avantageuse" est vraiment valide dans tous les cas (problème d'aliasing sur les paramètres, par exemple, quand deux pointeurs désignent en fait la même chose).   Si on veut être sûr, on n'optimise pas ou presque (c'est le cas de la compilation du noyau linux, par exemple).

              Upstream Linux Developers Against "-O3" Optimizing The Kernel
              Written by Michael Larabel in Linux Kernel on 13 May 2020 at 08:45 AM EDT. 23 Comments
              LINUX KERNEL --
              The upstream Linux kernel developers have come out against a proposal to begin using the "-O3" optimization level when compiling the open-source code-base with the GCC 10 compiler or newer.
              Il s'y ajoute le fait que le code généré avec -O3 n'est parfois plus lent et plus gros que celui de -O2.

              -
              Edité par michelbillaud 9 juin 2021 à 18:00:51

              • Partager sur Facebook
              • Partager sur Twitter
                9 juin 2021 à 18:07:07

                Michel … le code généré est clairement faux … il n'est pas plus gros ou plus lent (quoi que je n'ai pas regardé de près), il est faux si tu l'optimises en -O3 ou -O2 ou -O ou -Os)et correct sinon …

                Et tu ne pas dire qu'un compilo qui transforme une boucle infinie en un programme qui termine est bugué …

                Imagine le même code mais avec une vérification d'une conjecture quelconque, genre goldbach … le programme peut-être correct, le code généré ne le sera pas.

                • Partager sur Facebook
                • Partager sur Twitter
                  9 juin 2021 à 18:40:42

                  Faut que j'y aille, merci d'expliquer à notre cher auditoire en quoi il est clairement faux.

                  Un code qui teste une conjecture aura probablement une branche qui retourne le contre-exemple trouvé.

                  Tu peux essayer sur goldbach, ça ne doit pas être très long.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    9 juin 2021 à 19:12:35

                    Michel, on va la fire courte, un code comme :

                    int main(void)
                    {
                        while(1);
                        return 0;
                    }

                    doit donner après compilation un exécutable qui fait une boucle infinie. Cela me semble plus que normal, non ?

                    Si l'exécutable ne boucle pas indéfiniment, c'est qu'il y a un problème lors de la compilation car l'exécutable produit n'est pas conforme au code source. Point, ça ne va pas plus loin que ça.

                    D'ailleurs, sans vouloir pousser le bouchon très loin, le code proposé par le PO est un code qui teste s'il existe un entier compris entre 10 et 99 inclus dont le carré est plus grand que le cube. Clairement le code produit avec optimisation est faux car l'exécutable finit par imprimer VRAI …

                    Si pour toi ce n'est pas un bug, je ne vois pas ce que c'est. Où alors il faut que tu m'expliques ce qu'est un bug pour toi.

                    • Partager sur Facebook
                    • Partager sur Twitter
                      9 juin 2021 à 22:03:14

                      Voir  plus haut, les "optimisations" faites par les compilateurs ne préservent malheureusement pas la sémantique. Et sont donc, pour la plupart, fausses (parce qu'elles font l'impasse sur des cas plus ou moins tordus).

                      Pas la peine de me le reprocher, j'y suis pour rien !

                      Question : est ce que supprimer 

                      for (int  i=1; i !=0 ; i++)
                         for ( int j = 1; j != 0; j++)
                             continue;

                      Donne un programme équivalent, ou pas ?

                      Ps: non le programme ne teste pas si il existe un entier dans l'intervalle tel que.  Ca serait une procedure de décision qui répond vrai ou faux au bout d'un temps fini. Là c'est une semi-decision, qui répond dans un cas, et boucle indéfiniment sinon.

                      -
                      Edité par michelbillaud 10 juin 2021 à 8:30:37

                      • Partager sur Facebook
                      • Partager sur Twitter
                        9 juin 2021 à 22:15:47

                        Je dirais que c'est un bug sur un bug.

                        En effet, un code qui boucle est de base un code mal fait. Ce que fait cette optimisation, c'est corriger (mais mal) ce bug de code.

                        Mais moi je suis d'accord, clang ne devrait pas faire ça et laisser boucler. 

                        ... mais quel code tordu en amont !

                        • Partager sur Facebook
                        • Partager sur Twitter

                        Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

                          9 juin 2021 à 22:39:52

                          rholala on se calme les gars. ici ou sur un autre site on me dit que mon code c'est de la merde mais non c'est un exemple minimaliste qui fait le même bug qu'un code plus gros que je vais pas mettre ici parceque il est trop gros.

                          mais même si le code est mal foutu, le programme est pire parceque il fait pas ce que le code demande de faire. clang a un probleme et je me dis clang c'est de la merde si on utilise O3. si j'ai bien compris ce que michel dit.

                          gcc c'est mieux dans ce cas 3et je suis d'accort avec fvirtman et whitecrow.

                          • Partager sur Facebook
                          • Partager sur Twitter
                            10 juin 2021 à 6:02:24

                            Je crois bien que tout le monde est d'accord en fait. Le code généré par clang -O3 sur cet exemple n'est pas correct. Ce qui est intéressant c'est de comprendre pourquoi il a été produit, c'est a dire quelles "recettes d'optimisation" ont été utilisées (indûment) pour produire ça. 

                            Si ça peut te rassurer (?), gcc a aussi son lot de problèmes (= bugs) avec les optimisations. On n'est pas à l'abri de problèmes du même genre, sur d'autres exemples.

                            Ici ça se révèle sur un  exemple "retourner une constante ou boucler" qui est un peu spécial, dans le sens où on considère généralement que du code qui boucle sans rien faire d'utile (de visible) ou qui fait plusieurs fois la même chose peut être "simplifié" sans dommage. Hypothèse qui est fausse dans la programmation bas niveau, par exemple on peut avoir de bonnes raisons de lire plusieurs fois de suite la même adresse en mémoire, parce qu'elle correspond en fait à un dispositif  périphérique "mappé" en mémoire.

                            Autrement dit, on a tort de croire que les adresses, ça désigne de la mémoire.

                            Voir plus haut les fortes réticences (NIET !) pour changer le niveau d'optimisation dans le noyau Linux.

                            -
                            Edité par michelbillaud 10 juin 2021 à 8:31:29

                            • Partager sur Facebook
                            • Partager sur Twitter
                              10 juin 2021 à 6:40:36

                              Tu t'égares Michel, même si on est à deux doigts de te voir dire que c'est effectivement un bug.

                              Quel que soit le code source, la compilation doit donner un programme conforme. C'est la tâche principale du compilateur.

                              michelbillaud a écrit:

                              Voir  plus haut, les "optimisations" faites par les compilateurs ne préservent malheureusement pas la sémantique. Et sont donc, pour la plupart, fausses (parce qu'elles font l'impasse sur des cas plus ou moins tordus).

                              Pas la peine de me le reprocher, j'y suis pour rien !

                              Question : est ce que supprimer 

                              for (int  i=1; i !=0 ; i++)
                                 for ( int j = 1; j != 0; j++)
                                     continue;

                              Donne un programme équivalent, ou pas ?


                              Est-ce que le fait de supprimer un while(1) qui boucle indéfiniment donne un programme équivalent ?

                              euh … non.

                              michelbillaud a écrit:

                              Ps: non le programme ne teste pas si il existe un entier dans l'intervalle tel que.  Ca serait une procedure de decision qui repond vrai ou faux au bout d'un temps fini. Là cest une semi-decision, qui repond dans un cas, et boucle indefiniment sinon.

                              -
                              Edité par michelbillaud il y a environ 7 heures

                              Et le programme répond vrai immédiatement, donc bien que le code source soit correct le programme généré ne l'est pas :-°

                              Je suppose qu'on clairement peut appeler ça un bug.

                              • Partager sur Facebook
                              • Partager sur Twitter
                                10 juin 2021 à 8:43:40

                                Stp, fais pas le pénible.  J'ai toujours dit que le code était incorrect.

                                et qu'il est écrit partout dans la documentation que les optimisations "peuvent ne pas respecter le standard du langage". C'est à dire produire du code incorrect. C'est à dire que les compilateurs sont - quelle surprise - à peu près tous buggés.

                                Il se trouve que les programmeurs C prennent souvent le risque d'activer quand même ces optimisations. Ils ne sont pas à ça près, il faut croire.

                                Le fragment de code que je donnais (double boucle) ne boucle pas indéfiniment, il s'arrête quand les entiers ont fait le tour. Il prend très longtemps, par contre (2^64 tours pour des entiers sur 32 bits). ça fait une boucle d'attente assez longue. Est-ce que la supprimer pose un problème ? C'est un souci dans l'embarqué, quand on fait - idée discutable - un délai par une boucle. Si on supprime la boucle, il n'y a plus de délai, donc le programme ne fonctionne plus de la même façon. Est-ce souhaitable ? Est-ce normal ? Est-ce la faute au compilateur si le comportement attendu est supprimé par le compilateur ? Si un compilateur dérécursive une fonction alors que je l'ai écrit pour qu'il explose la pile dans certains cas, est-ce anormal ?

                                Je parlais de semi-décision parce que les gens qui font les compilateurs (et les optimisations) pensent parfois que le rôle d'une fonction C est de retourner forcément quelque chose au bout d'un certain temps. Ce qui explique certains bugs d'optimisation. Expliquer, c'est pas dire que c'est souhaitable, c'est juste constater que ça arrive, et qu'il y a des raisons.

                                -
                                Edité par michelbillaud 10 juin 2021 à 9:21:02

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  10 juin 2021 à 13:01:39

                                  La leçon à retenir est sans doutes qu'il faut être très méfiant sur les optimisations et les traiter comme s'il s'agissait d'une refactorisation de code.

                                  Si je dois activer les optimisations, je ne le fais qu'une fois le programme au point et seulement pour de très bonnes raisons liées à la criticité du temps d'exécution et à un gain significatif que l'on peut mesurer. Ces cas sont, au final, très rares. Si le temps consommé par le processeur est à ce point précieux qu'il faut surveiller cette consommation à mesure que l'on développe, il faudrait que le cycle de développement comprenne l'exécution des tests unitaires avec une compilation sans optimisations et une compilation avec. C'est une galère supplémentaire.

                                  Dans tous les cas, on ne devrait se risquer à introduire les optimisations qu'avec une batterie de tests unitaires permettant de vérifier que le programme continue de faire ce qu'il et sensé faire en cas de changements de niveau d'optimisations. Si l'on développe en TDD, c'est quelque chose qui vient naturellement, car les tests sont exécutés à chaque ajout de code ou refactorisation. Il faut juste envisager l'ajout de l'option d'optimisation comme un changement au code (objet), pour lequel il faut vérifier l'absence de régressions ou effets de bord indésirables tout comme si une refactorisation de code source était faite par un collègue "optimiseur" et dont les changements doivent passer les tests unitaires, comme toute refactorisation (ce que fait le compilateur au niveau de la production du code objet lorsqu'il utilise ses heuristiques pour produire une "optimisation" supposée "intelligente").

                                  -
                                  Edité par Dlks 10 juin 2021 à 13:05:27

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    10 juin 2021 à 14:37:54

                                    michelbillaud a écrit:

                                    Stp, fais pas le pénible.  J'ai toujours dit que le code était incorrect.

                                    [...]

                                    Le code n'est pas incorrect. C'est l'exécutable produit qui est incorrect.
                                    Que le code soit, selon ton propre jugement, correct ou non l'exécutable doit produit ce que le code décrit. Ce n'est pas le cas.
                                    Et ce qu'on demande en premier et qui est extrêmement important est qu'un compilateur produise ce qu'on lui demande (consciemment ou pas). Ce n'est pas le cas.

                                    C'est pourquoi j'appelle ce comportement un bug de clang.

                                    J'ai souvent encensé clang pour ses capacités d'optimisations qui vont souvent au-delà de celles de gcc. Mais sur le coup, c'est un coup à la confiance que je donne à clang sur sa capacité à être un compilateur digne de confiance (ok, j'exagère mais à peine).

                                    Finalement je ne donne pas tort à Dlks quand il dit

                                    Dlks a écrit:

                                    La leçon à retenir est sans doutes qu'il faut être très méfiant sur les optimisations et les traiter comme s'il s'agissait d'une refactorisation de code.
                                    [...]

                                    Et malheureusement  c'est dommage de ne pas pouvoir faire une confiance aveugle en un simple -O.

                                    Après le plus simple serait sans doute de demander directement des explications à clang … remplir un bug report …

                                    Le PO pourra sans doute s'en charger puisque la découverte lui revient.

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      10 juin 2021 à 16:35:22

                                      @whitecrow Jusqu'ici tu avais bien compris, avec le contexte, quand l'expression "le code" apparaissait, si il s'agissait du code source ou, le plus souvent en fait, du code généré par le compilateur. Surtout assaisonné du mot "incorrect".

                                      Et tu étais parfaitement d'accord. En plus.

                                      Qu'est-ce qui t'est arrivé depuis ? Tu as été optimisé ? :-)

                                      -
                                      Edité par michelbillaud 10 juin 2021 à 16:39:32

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        10 juin 2021 à 16:48:24

                                        bah je ne sais pas Michel … Quand je te lis :

                                        White Crow a écrit:

                                        Michel … le code généré est clairement faux … [...]

                                        michelbillaud a écrit:

                                        Faut que j'y aille, merci d'expliquer à notre cher auditoire en quoi il est clairement faux.

                                        [...]


                                        Malheureusement je n'ai pas vu que tu as modifié ton message deux heures après ma réponse …

                                        michelbillaud a écrit:

                                        Je crois bien que tout le monde est d'accord en fait. Le code généré par clang -O3 sur cet exemple n'est pas correct. [...]
                                        -

                                        Edité par michelbillaud il y a environ 8 heures


                                        Je n'ai donc pas vu que tu es d'accord pour considérer ce comportement comme un bug.

                                        Au temps pour moi.

                                        Nous sommes donc d'accord au moins sur ce point.

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          10 juin 2021 à 17:14:46

                                          Récemment, j'ai corrigé quelques fautes de frappes, accents qui manquaient etc quand je suis passé de la tablette sur l'ordinateur, dans un esprit du respect du lecteur, qui n'est pas obligé de se bouffer du français incorrect quand on peut arranger ça. On a ses manies. Je ne m'amuse pas à réécrire l'histoire.

                                          --


                                          Après, si on cherche un peu, on trouve dans le standard des choses qui peuvent faire douter.

                                          https://stackoverflow.com/questions/59925618/how-do-i-make-an-infinite-empty-loop-that-wont-be-optimized-away

                                          Par exemple, est-ce que supprimer une boucle infinie serait si incorrect que ça ?


                                          (draft standard c11).

                                          et la note d'après précise bien

                                          157) This is intended to allow compiler transformations such as removal of empty loops even when termination cannot be proven.



                                          ça ne s'appliquerait pas au  for(;;) de l'exemple, précisément ?

                                          En d'autres termes :  maybe this is not a bug, that's a feature.


                                          --- pendant qu'on y est, si je peux me permettre d'éditer ce message, le source suivant

                                          void loop(void) { loop(); }
                                          
                                          int main() {
                                             loop();
                                          }
                                          

                                          quand il est compilé avec -O3

                                          •  plante avec gcc (8.3.0) - par débordement de la pile.
                                          •  se termine avec clang (7.0.1)


                                          qui n'ont pas la même notion de "code inutile qu'on peut légalement supprimer".

                                          PS: gcc qui est de mauvaise humeur n'a même pas été foutu de dérécursiver loop. c'est bien la peine de lui demander d'optimiser...

                                          loop:
                                          .LFB0:
                                          	.cfi_startproc
                                          	subq	$8, %rsp
                                          	.cfi_def_cfa_offset 16
                                          	call	loop
                                          	.cfi_endproc
                                          
                                          main:
                                          .LFB1:
                                          	.cfi_startproc
                                          	subq	$8, %rsp
                                          	.cfi_def_cfa_offset 16
                                          	call	loop
                                          	.cfi_endproc
                                          




                                          -
                                          Edité par michelbillaud 10 juin 2021 à 17:56:13

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            10 juin 2021 à 17:33:55

                                            hummm … not a constant expression … et l'exemple c'est plus

                                            while (1) {
                                               blablabla
                                            }

                                            1 est une constant expression …

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              10 juin 2021 à 17:59:53

                                              White Crow a écrit:

                                              hummm … not a constant expression … et l'exemple c'est plus

                                              while (1) {
                                                 blablabla
                                              }

                                              1 est une constant expression …

                                               for(int i=10; i<100; i++)
                                                          if ( i*i > i*i*i )
                                                              return 1;



                                              la boucle for est une iteration statement

                                              C'est la combinaison du for (qui ne pourrait retourner que la valeur 1 ou se terminer - c'est là que la supposition intervient - sans avoir rien fait d'utile) et de la boucle infinie autour d'une instruction qui se termine mais n'aura rien fait d'utile, qui conduit à cette suprenante et douteuse optimisation : bon les gars, soit votre programme se barre en choucroute, soit il retourne forcément 1. Alors on va retourner 1, parce que sinon votre programme ne sert à rien.

                                               Y a une logique, quand même. Avant d'aller pousser de hauts cris chez clang que leur machin est tout pété, je me méfierai :-) Apparemment le standard C n'exclue pas qu'on puisse optimiser en virant des boucles qui ne font rien d'utile, y compris si elles sont infinies (cf la note 157).

                                              On pourrait mettre tout le monde d'accord en décrétant que le comportement de code qui fait une boucle infinie sans résultats observables est indéfini.

                                              Le hic, c'est que, pour des raisons bien connues, ça ne peut pas être détecté par des outils dans le cas général. Il ne faut pas sous-estimer le poids des marchands d'outils dans la définition des normes et standards.

                                              -
                                              Edité par michelbillaud 10 juin 2021 à 18:23:49

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                10 juin 2021 à 21:42:03

                                                ah ok ok … certainement ce dont on parlait dès le départ non ?

                                                White Crow a écrit:

                                                [...]
                                                Tout comme Michel, j'imagine que l'optimisation est :

                                                • on part du principe que le code fourni par le dèv est correct (erreur de la part de clang) ;
                                                • il remarque qu'il y a une boucle infinie et qu'on ne peut en sortir que par le return 1, il n'y a aucun autre moyen.

                                                avec ça il se dit que comme le code est correct, il sort de la boucle et comme sortir de la boucle renvoie 1 c'est ce qu'il fait. Il doit le faire également parce que ton code n'a aucun effet de bord, ce qui expliquerait que rajouter un printf modifie l'optimisation, et que c'est une fonction constante = quel que soit l'état du programme il renverra toujours la même valeur, 1 en l'occurence.

                                                Mais bon, il faudrait vérifier tout mes dires … c'est à vue de nez.

                                                Bon à la vue de la norme c'est toujours un bug, y compris pour le for interne car le return 1 n'est pas la seule manière de sortir du for (de considérer que la boucle termine). Et cette section ne concerne absolument pas le while ⇒ même optimisé le code doit boucler indéfiniment.

                                                Et le plus simple pour mettre tout le monde d'accord est de résoudre le bug … l'optimiseur n'a pas à virer ce qu'il vire.
                                                Comme tu parles du problème de l'arrêt c'est clair que clang l'approche hyper mal …

                                                michelbillaud a écrit:

                                                [...]
                                                --- pendant qu'on y est, si je peux me permettre d'éditer ce message, le source suivant

                                                void loop(void) { loop(); }
                                                
                                                int main() {
                                                   loop();
                                                }
                                                

                                                quand il est compilé avec -O3

                                                •  plante avec gcc (8.3.0) - par débordement de la pile.
                                                •  se termine avec clang (7.0.1)


                                                qui n'ont pas la même notion de "code inutile qu'on peut légalement supprimer".

                                                PS: gcc qui est de mauvaise humeur n'a même pas été foutu de dérécursiver loop. c'est bien la peine de lui demander d'optimiser...

                                                loop:
                                                .LFB0:
                                                	.cfi_startproc
                                                	subq	$8, %rsp
                                                	.cfi_def_cfa_offset 16
                                                	call	loop
                                                	.cfi_endproc
                                                
                                                main:
                                                .LFB1:
                                                	.cfi_startproc
                                                	subq	$8, %rsp
                                                	.cfi_def_cfa_offset 16
                                                	call	loop
                                                	.cfi_endproc
                                                




                                                -
                                                Edité par michelbillaud il y a environ 3 heures




                                                Ah ben tu vois, tu donnes encore d'autres exemples de bug … que gcc 11.1 «optimise correctement»

                                                loop():
                                                .L2:
                                                        jmp     .L2
                                                main:
                                                .L5:
                                                        jmp     .L5



                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  11 juin 2021 à 0:12:00

                                                  merci tt le monde

                                                  je retiens ça : gcc a un comportement constant et cohérent, clang non. clang a un bug qui n'est pas un bug mais qui fait ièch parceque c'est un bug quand même. la plupart d'entre vous défendes le compilo en disant que mon code est de la merde mais c'est qu'un code démo. les autres sont moins fermé

                                                  je ferme mon sujet car j'ai mes réponses

                                                  merci pour vos efforts

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    11 juin 2021 à 3:30:46

                                                    Peut-être que clang a été compilé lui-même avec clang en -O3 ...
                                                    • Partager sur Facebook
                                                    • Partager sur Twitter

                                                    Le Tout est souvent plus grand que la somme de ses parties.

                                                      11 juin 2021 à 6:04:13

                                                      Un article intéressant (2010) de J Regehr sur ce qu'il conviendrait d'appeler la préservation de la non terminaison.

                                                      https://blog.regehr.org/archives/161

                                                      La moralité de l'histoire me semble être dans un de ses billets précédents https://blog.regehr.org/archives/140 : c'est la faute au standard :

                                                      The compiler is given considerable freedom in how it implements the C program, but its output must have the same externally visible behavior that the program would have when interpreted by the “C abstract machine” that is described in the standard.   Many knowledgeable people (including me) read this as saying that the termination behavior of a program must not be changed.  Obviously some compiler writers disagree, or else don’t believe that it matters.  The fact that reasonable people disagree on the interpretation would seem to indicate that the C standard is flawed. 

                                                      -
                                                      Edité par michelbillaud 11 juin 2021 à 8:17:25

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        11 juin 2021 à 8:17:01

                                                        c'est de la bonne lecture que tu proposes Michel.

                                                        Le premier point, un peu rigolo, est qu'au final nous sommes tous les même, ici ou ailleurs. Les mêmes «oppositions» se constatent dans cette discussion que dans celle des commentaires de l'article que tu proposes ou de l'article qui lui a donné naissance : C Compilers Disprove Fermat’s Last Theorem. Pourtant 11 ans nous en séparent.
                                                        Il y a deux camps. Ceux des «si tu proposes un code débile tu auras une sortie carrément débile si tu lui demande de faire des trucs intelligents dessus» et ceux des «le compilo a visiblement changé le comportement observable d'un programme C conforme, ce qui est interdit». Je me place clairement dans le second.

                                                        Il y a cependant un truc que je ne comprends pas et que je devrais creuser si le temps le permet. Dans l'article on peut lire :

                                                        «Hall of Shame
                                                        These C compilers known to not preserve termination properties of code: Sun CC 5.10, Intel CC 11.1, LLVM 2.7, Open64 4.2.3, and Microsoft Visual C 2008 and 2010.  The LLVM developers consider this behavior a bug and have since fixed it. As far as I know, the other compiler vendors have no plans to change the behavior.»

                                                        Effectivement on trouve un (très vieux) bug report qui remonte à 2006. Il se termine en avril 2021 avec un :

                                                        «This issue is fixed in LLVM 12 by the introduction of the mustprogress attribute and loop metadata, inference of willreturn and finally limitation of call DCE to willreturn functions.

                                                        There might still be some incorrect assumptions left over here and there, but the core issue is fixed now, and we should track remaining issues separately.»

                                                        Et comme l'intitulé du BR965 concerne C et Rust, j'ai réessayé le code du PO avec clang 12. Je n'ai constaté aucun changement par rapport à clang11, le bug apparaît toujours.

                                                        'fin bref, merci pour le lien.

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          11 juin 2021 à 8:25:03

                                                          Si on écrit des conneries, l'ordi devrait exécuter des conneries. Garbage in, garbage out.
                                                          • Partager sur Facebook
                                                          • Partager sur Twitter

                                                          Le Tout est souvent plus grand que la somme de ses parties.

                                                            11 juin 2021 à 10:11:58

                                                            C'est à dire qu'il y une contradiction flagrante dans le standard (C11 au moins) entre

                                                            • le principe selon lequel l'exécutable devrait fonctionner comme la "machine abstraite" (*)
                                                            • "allow compiler transformations such as removal of empty loops even when termination cannot be proven".
                                                            Donc c'est délicat de dire "tout le monde est d'accord que c'est clairement incorrect". Faut rester prudent.
                                                            L'explication du "bug toujours non résolu" est peut être dans la phrase
                                                            > There might still be some incorrect assumptions left over here and there,
                                                            --
                                                            (*) surtout que ladite machine abstraite n'est définie formellement nulle part, pas plus que la traduction "standard" du source C en instructions de la machine abstraite. Ca reste très informel tout ça. Et donc ambigu.

                                                            -
                                                            Edité par michelbillaud 11 juin 2021 à 10:18:24

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                              11 juin 2021 à 10:43:04

                                                              Je ne dis pas que c'est incorrect, je dis que c'est un bug … bug, comme dans bug report … bug report qui a amené une correction de bug … apparemment imparfaite.

                                                              Tu admettras tout de même que dans l'exemple de ton article (enfin surtout celui qui a amené la création de celui-ci) montre que l'on peut, avec clang, produire des programmes faux à partir de source correctes, mais imparfaite. Ça c'est mon point de vue originel.

                                                              Mais surtout, il y a le problème du changement de comportement. Je m'explique, dans le standard en 5.1.2.3.1 :

                                                              «The semantic descriptions in this International Standard describe the behavior of an abstract machine in which issues of optimization are irrelevant.»

                                                              un peu plus loin en 5.1.2.3.6 :

                                                              «The least requirements on a conforming implementation are:

                                                              — Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.

                                                              — At program termination, all data written into files shall be identical to the result that execution of the program according to the abstract semantics would have produced.

                                                              — The input and output dynamics of interactive devices shall take place as specified in 7.21.3. The intent of these requirements is that unbuffered or line-buffered output appear as soon as possible, to ensure that prompting messages actually appear prior to a program waiting for input.

                                                              This is the observable behavior of the program.»

                                                              L'optimisation dont nous parlons change le comportement observable du programme. Disons plutôt qu'il y a deux comportement observable pour le même code source … lequel des deux est correct ? lequel des deux ne l'est pas ? pour celui qui ne l'est pas quel en est la cause ?

                                                              Je pense que cette cause peut être appelée, comme dans le BR965 de LLVM,  un bug ; que ce soit un cas ou l'autre.

                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              bug clang ?

                                                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                              • Editeur
                                                              • Markdown