Bogue GCC possible lors du retour d'une structure à partir d'une fonction

133

Je crois avoir trouvé un bogue dans GCC lors de l'implémentation du PCG PRNG d'O'Neill. ( Code initial sur Godbolt's Compiler Explorer )

Après la multiplication oldstatepar MULTIPLIER, (résultat stocké dans rdi), GCC n'ajoute pas ce résultat à INCREMENT, movabs'ing INCREMENTà rdx à la place, qui est ensuite utilisé comme valeur de retour de rand32_ret.state

Un exemple reproductible minimum ( Explorateur du compilateur ):

#include <stdint.h>

struct retstruct {
    uint32_t a;
    uint64_t b;
};

struct retstruct fn(uint64_t input)
{
    struct retstruct ret;

    ret.a = 0;
    ret.b = input * 11111111111 + 111111111111;

    return ret;
}

Assemblage généré (GCC 9.2, x86_64, -O3):

fn:
  movabs rdx, 11111111111     # multiplier constant (doesn't fit in imm32)
  xor eax, eax                # ret.a = 0
  imul rdi, rdx
  movabs rdx, 111111111111    # add constant; one more 1 than multiplier
     # missing   add rdx, rdi   # ret.b=... that we get with clang or older gcc
  ret
# returns RDX:RAX = constant 111111111111 : 0
# independent of input RDI, and not using the imul result it just computed

Fait intéressant, la modification de la structure pour avoir le uint64_t comme premier membre produit le code correct , tout comme le changement des deux membres pour être uint64_t

x86-64 System V renvoie des structures inférieures à 16 octets dans RDX: RAX, lorsqu'elles sont copiables de manière triviale. Dans ce cas, le 2ème membre est en RDX parce que la moitié haute de RAX est le rembourrage pour l'alignement ou .bquand .aest un type plus étroit. ( sizeof(retstruct)est 16 de toute façon; nous n'utilisons pas, __attribute__((packed))donc il respecte alignof (uint64_t) = 8.)

Ce code contient-il un comportement non défini qui permettrait à GCC d'émettre l'assembly "incorrect"?

Sinon, cela devrait être signalé sur https://gcc.gnu.org/bugzilla/

vitorhnn
la source
Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
Samuel Liew

Réponses:

102

Je ne vois aucun UB ici; vos types ne sont pas signés donc UB à débordement signé est impossible, et il n'y a rien de bizarre. (Et même s'il est signé, il devrait produire des sorties correctes pour les entrées qui ne provoquent pas de débordement UB, comme rdi=1). Il est également rompu avec le frontal C ++ de GCC.

En outre, GCC8.2 le compile correctement pour AArch64 et RISC-V (à une maddinstruction après avoir utilisé movkpour construire des constantes, ou à RISC-V mul et à ajouter après avoir chargé les constantes). Si c'est UB que GCC a trouvé, nous nous attendons généralement à ce qu'il le trouve et casse également votre code pour d'autres ISA, au moins ceux qui ont des largeurs de type et des largeurs de registre similaires.

Clang le compile également correctement.

Cela semble être une régression de GCC 5 à 6; GCC5.4 se compile correctement, 6.1 et plus tard non. ( Godbolt ).

Vous pouvez le signaler sur le bugzilla de GCC en utilisant le MCVE de votre question.

Il semble vraiment que c'est un bogue dans la gestion des retours de structure System V x86-64, peut-être des structures contenant un remplissage. Cela expliquerait pourquoi cela fonctionne lors de l'inline et lors de l'élargissement aà uint64_t (en évitant le remplissage).

Peter Cordes
la source
34
Je l'ai signalé
vitorhnn
11
@vitorhnn On dirait qu'il a été corrigé master.
SS Anne
19

Cela a été corrigé sur trunk/master .

Voici le commit pertinent .

Et c'est un patch pour résoudre le problème.

Sur la base d'un commentaire dans le patch, la reload_combine_recognize_patternfonction essayait d'ajuster les insns USE .

SS Anne
la source
14

Ce code contient-il un comportement non défini qui permettrait à GCC d'émettre l'assembly "incorrect"?

Le comportement du code présenté dans la question est bien défini par rapport aux normes de langage C99 et C ultérieures. En particulier, C permet aux fonctions de renvoyer des valeurs de structure sans restriction.

John Bollinger
la source
2
GCC produit une définition autonome de la fonction; c'est ce que nous examinons, peu importe si c'est ce qui fonctionne lorsque vous le compilez dans une unité de traduction avec d'autres fonctions. Vous pouvez tout aussi facilement le tester sans l'utiliser réellement __attribute__((noinline))en le compilant dans une unité de traduction par lui-même et en le liant sans LTO, ou en compilant avec -fPICce qui implique que tous les symboles globaux sont (par défaut) interposables donc ne peuvent pas être intégrés dans les appelants. Mais vraiment, le problème est détectable simplement en regardant l'asm généré, quels que soient les appelants.
Peter Cordes
Très bien, @PeterCordes, bien que je sois raisonnablement convaincu que ce détail a été changé sous moi dans Godbolt.
John Bollinger
Version 1 de la question liée à Godbolt avec juste la fonction par elle-même dans une unité de traduction, comme l'état de la question elle-même lorsque vous avez répondu. Je n'ai pas vérifié toutes les révisions ou commentaires que vous auriez pu consulter. De l'eau sous le pont, mais je ne pense pas que l'on ait jamais prétendu que la définition asm autonome n'était rompue que lorsque la source était utilisée __attribute__((noinline)). (Ce serait choquant, pas seulement surprenant la façon dont un bug de correction GCC est). Cela a probablement été mentionné uniquement pour avoir effectué un appel de test qui imprime le résultat.
Peter Cordes