Acredito que encontrei um bug no GCC ao implementar o PCG PRNG de O'Neill. ( Código inicial no Godbolt's Compiler Explorer )
Após multiplicar oldstate
por MULTIPLIER
(resultado armazenado em rdi), o GCC não adiciona esse resultado a INCREMENT
, movabs'ing INCREMENT
para rdx, que é usado como o valor de retorno de rand32_ret.state
Um exemplo mínimo reproduzível ( Compiler Explorer ):
#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;
}
Conjunto gerado (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
Curiosamente, modificar a estrutura para ter o uint64_t como o primeiro membro produz o código correto , assim como alterar os dois membros para serem uint64_t
O x86-64 System V retorna estruturas menores que 16 bytes em RDX: RAX, quando são trivialmente copiáveis. Nesse caso, o segundo membro está no RDX porque a metade alta do RAX é o preenchimento para alinhamento ou .b
quando .a
é do tipo mais estreito. ( sizeof(retstruct)
é 16 de qualquer maneira; não estamos usando, __attribute__((packed))
por isso respeita o alignof (uint64_t) = 8.)
Esse código contém algum comportamento indefinido que permitiria ao GCC emitir o assembly "incorreto"?
Caso contrário, isso deve ser relatado em https://gcc.gnu.org/bugzilla/
Respostas:
Não vejo nenhum UB aqui; seus tipos não são assinados, portanto o UB com estouro de assinaturas é impossível e não há nada de estranho. (E mesmo que assinado, ele teria que produzir saídas corretas para entradas que não causam excesso de UB, como
rdi=1
). Ele também está quebrado com o front-end C ++ do GCC.Além disso, o GCC8.2 o compila corretamente para AArch64 e RISC-V (para uma
madd
instrução após o usomovk
para construir constantes, ou RISC-V mul e adiciona após o carregamento das constantes). Se foi o UB que o GCC estava encontrando, geralmente esperamos que ele o encontre e quebre seu código para outros ISAs também, pelo menos aqueles que tenham larguras de tipo e larguras de registro semelhantes.Clang também o compila corretamente.
Isso parece ser uma regressão do GCC 5 a 6; As compilações do GCC5.4 estão corretas, 6.1 e posteriores não. ( Godbolt ).
Você pode denunciar isso no bugzilla do GCC usando o MCVE da sua pergunta.
Realmente parece que é um bug no processamento de retorno de estrutura x86-64 do System V, talvez de estruturas que contêm preenchimento. Isso explicaria por que ele funciona quando embutido e ao ampliar
a
para uint64_t (evitando o preenchimento).fonte
master
.Isso foi corrigido em
trunk
/master
.Aqui está o commit relevante .
E este é um patch para corrigir o problema.
Com base em um comentário no patch, a
reload_combine_recognize_pattern
função estava tentando ajustar USE insns .fonte
O comportamento do código apresentado na pergunta está bem definido com relação aos padrões da linguagem C99 e posterior C. Em particular, C permite que as funções retornem valores de estrutura sem restrição.
fonte
__attribute__((noinline))
lo, compilando-o em uma unidade de tradução por si só e vinculando-o sem LTO, ou compilando com o-fPIC
que implica que todos os símbolos globais são (por padrão) interponíveis, para que não possam ser incorporados aos chamadores. Mas, na verdade, o problema é detectável apenas olhando para o asm gerado, independentemente dos chamadores.__attribute__((noinline))
. (Isso seria chocante, não apenas surpreendente como é um bug de correção do GCC). Provavelmente isso foi mencionado apenas para fazer um chamador de teste que imprime o resultado.