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 oldstate
por MULTIPLIER
, (resultado almacenado en rdi), GCC no agrega ese resultado a INCREMENT
, movabs'ing INCREMENT
a 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 .b
cuando .a
es 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/
Respuestas:
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
madd
instrucción después de usarmovk
para 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
a
a uint64_t (evitando el relleno).fuente
master
.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_pattern
función estaba tratando de ajustar INSns de USE .fuente
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.
fuente
__attribute__((noinline))
compilándolo en una unidad de traducción por sí mismo y vinculando sin LTO, o compilando con lo-fPIC
que 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.__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.