Possível bug do GCC ao retornar struct de uma função

133

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 oldstatepor MULTIPLIER(resultado armazenado em rdi), o GCC não adiciona esse resultado a INCREMENT, movabs'ing INCREMENTpara 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 .bquando .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/

vitorhnn
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Samuel Liew

Respostas:

102

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 maddinstrução após o uso movkpara 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 apara uint64_t (evitando o preenchimento).

Peter Cordes
fonte
34
Eu relatei
vitorhnn 23/01
11
@vitorhnn Parece que foi corrigido master.
SS Anne
19

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_patternfunção estava tentando ajustar USE insns .

SS Anne
fonte
14

Esse código contém algum comportamento indefinido que permitiria ao GCC emitir o assembly "incorreto"?

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.

John Bollinger
fonte
2
O GCC produz uma definição independente da função; é isso que estamos vendo, independentemente de ser o que é executado quando você o compila em uma unidade de tradução junto com outras funções. Você poderia facilmente testá-lo sem usá- __attribute__((noinline))lo, compilando-o em uma unidade de tradução por si só e vinculando-o sem LTO, ou compilando com o -fPICque 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.
Peter Cordes
É justo, @ PeterCordes, embora eu esteja razoavelmente confiante de que esse detalhe foi alterado de baixo de mim no Godbolt.
John Bollinger
A versão 1 da pergunta estava vinculada ao Godbolt apenas com a função em uma unidade de tradução, como o estado da pergunta em si quando você respondeu. Não verifiquei todas as revisões ou comentários que você poderia estar vendo. Água debaixo da ponte, mas acho que nunca houve uma alegação de que a definição de ASM autônoma só foi quebrada quando a fonte foi usada __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.
Peter Cordes