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 oldstate
par 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 .b
quand .a
est 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/
Réponses:
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
madd
instruction après avoir utilisémovk
pour 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).la source
master
.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_pattern
fonction essayait d'ajuster les insns USE .la source
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.
la source
__attribute__((noinline))
en le compilant dans une unité de traduction par lui-même et en le liant sans LTO, ou en compilant avec-fPIC
ce 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.__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.