Posible error de GCC al devolver struct desde una función

133

Creo que encontré un error en GCC al implementar PCG PRNG de O'Neill. ( Código inicial en el Explorador del compilador de Godbolt )

Después de multiplicar oldstatepor MULTIPLIER, (resultado almacenado en rdi), GCC no agrega ese resultado a INCREMENT, movabs'ing INCREMENTa rdx, que luego se usa como el valor de retorno de rand32_ret.state

Un ejemplo mínimo reproducible ( 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;
}

Ensamblaje generado (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, la modificación de la estructura para tener el uint64_t como primer miembro produce el código correcto , al igual que el cambio de ambos miembros para que sea uint64_t

x86-64 System V devuelve estructuras de menos de 16 bytes en RDX: RAX, cuando se pueden copiar trivialmente. En este caso, el segundo miembro está en RDX porque la mitad alta de RAX es el relleno para la alineación o .bcuando .aes un tipo más estrecho. ( sizeof(retstruct)es 16 de cualquier manera; no lo estamos utilizando, __attribute__((packed))por lo que respeta alignof (uint64_t) = 8.)

¿Este código contiene algún comportamiento indefinido que permitiría a GCC emitir el ensamblado "incorrecto"?

Si no, esto debería ser informado en https://gcc.gnu.org/bugzilla/

vitorhnn
fuente
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Samuel Liew

Respuestas:

102

No veo ninguna UB aquí; sus tipos no están firmados, por lo que UB de desbordamiento firmado es imposible, y no hay nada extraño. (E incluso si está firmado, tendría que producir salidas correctas para las entradas que no causan desbordamiento UB, como rdi=1). También está roto con el front-end C ++ de GCC.

Además, GCC8.2 lo compila correctamente para AArch64 y RISC-V (a una maddinstrucción después de usar movkpara construir constantes, o RISC-V mul y agregar después de cargar las constantes). Si fue UB lo que GCC estaba encontrando, generalmente esperamos que lo encuentre y rompa su código para otros ISA también, al menos los que tienen anchos de tipo y anchos de registro similares.

Clang también lo compila correctamente.

Esto parece ser una regresión de GCC 5 a 6; Las compilaciones de GCC5.4 son correctas, 6.1 y posteriores no. ( Godbolt )

Puede informar esto en el bugzilla de GCC usando el MCVE de su pregunta.

Realmente parece que es un error en el manejo de estructura-retorno x86-64 del Sistema V, quizás de estructuras que contienen relleno. Eso explicaría por qué funciona al alinear y al ampliar aa uint64_t (evitando el relleno).

Peter Cordes
fuente
34
Lo he informado
vitorhnn
11
@vitorhnn Parece que se ha solucionado master.
SS Anne
19

Esto se ha corregido en trunk/ master.

Aquí está el compromiso relevante .

Y este es un parche para solucionar el problema.

Basado en un comentario en el parche, la reload_combine_recognize_patternfunción estaba tratando de ajustar INSns de USE .

SS Anne
fuente
14

¿Este código contiene algún comportamiento indefinido que permitiría a GCC emitir el ensamblado "incorrecto"?

El comportamiento del código presentado en la pregunta está bien definido con respecto a los estándares de lenguaje C99 y posteriores. En particular, C permite que las funciones devuelvan valores de estructura sin restricción.

John Bollinger
fuente
2
GCC produce una definición independiente de la función; eso es lo que estamos viendo, independientemente de si eso es lo que se ejecuta cuando lo compilas en una unidad de traducción junto con otras funciones. También podría probarlo fácilmente sin usarlo __attribute__((noinline))compilándolo en una unidad de traducción por sí mismo y vinculando sin LTO, o compilando con lo -fPICque implica que todos los símbolos globales son (por defecto) interpuestos, por lo que no se pueden insertar en las personas que llaman. Pero realmente el problema es detectable simplemente mirando el asm generado, independientemente de las personas que llaman.
Peter Cordes
Justo, @PeterCordes, aunque estoy bastante seguro de que ese detalle fue cambiado por debajo de mí en Godbolt.
John Bollinger
La versión 1 de la pregunta está vinculada a Godbolt con solo la función en sí misma en una unidad de traducción, como el estado de la pregunta en sí cuando respondiste. No revisé todas las revisiones o comentarios que podrías haber estado mirando. Agua debajo del puente, pero no creo que haya habido nunca una afirmación de que la definición asm independiente solo se rompió cuando se utilizó la fuente __attribute__((noinline)). (Eso sería sorprendente, no solo sorprendente la forma en que es un error de corrección de CCG). Probablemente eso se mencionó solo por hacer una llamada de prueba que imprime el resultado.
Peter Cordes