Possibile bug GCC quando si restituisce struct da una funzione


133

Credo di aver trovato un bug in GCC durante l'implementazione del PRNG PCG di O'Neill. ( Codice iniziale su Godbolt's Compiler Explorer )

Dopo aver moltiplicato oldstateper MULTIPLIER, (risultato memorizzato in rdi), GCC non aggiunge quel risultato a INCREMENT, INCREMENTmovabs'ing invece a rdx, che viene quindi utilizzato come valore di ritorno di rand32_ret.state

Un esempio riproducibile minimo ( 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;
}

Assemblea generata (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

È interessante notare che modificando la struttura in modo che uint64_t come primo membro produca il codice corretto , così come cambiando entrambi i membri in uint64_t

x86-64 System V restituisce le strutture più piccole di 16 byte in RDX: RAX, quando sono banalmente copiabili. In questo caso il 2o membro è in RDX perché la metà alta di RAX è l'imbottitura per l'allineamento o .bquando .aè un tipo più stretto. ( sizeof(retstruct)è 16 in entrambi i modi; non lo stiamo usando, __attribute__((packed))quindi rispetta alignof (uint64_t) = 8.)

Questo codice contiene comportamenti indefiniti che consentirebbero a GCC di emettere l'assembly "errato"?

In caso contrario, questo dovrebbe essere segnalato su https://gcc.gnu.org/bugzilla/


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Samuel Liew

Risposte:


102

Non vedo nessun UB qui; i tuoi tipi sono senza segno, quindi UB con overflow firmato è impossibile e non c'è niente di strano. (E anche se firmato, dovrebbe produrre output corretti per input che non causano overflow UB, come rdi=1). È rotto anche con il front-end C ++ di GCC.

Inoltre, GCC8.2 lo compila correttamente per AArch64 e RISC-V (in maddun'istruzione dopo aver usato movkper costruire costanti, o RISC-V mul e aggiungerlo dopo aver caricato le costanti). Se fosse UB a trovare GCC, generalmente ci aspetteremmo che lo trovasse e rompesse il codice anche per altri ISA, almeno quelli con larghezze di tipo e larghezze di registro simili.

Anche Clang lo compila correttamente.

Questa sembra essere una regressione da GCC 5 a 6; La compilazione di GCC5.4 è corretta, 6.1 e successive no. ( Godbolt ).

Puoi segnalarlo sul bugzilla di GCC usando l'MCVE dalla tua domanda.

Sembra davvero che sia un bug nella gestione strutt-return del sistema V x86-64, forse di strutture contenenti padding. Ciò spiegherebbe perché funziona quando si allinea, e quando si allarga aa uint64_t (evitando il riempimento).



11
@vitorhnn Sembra che sia stato corretto master.
SS Anne,

19

Questo problema è stato risolto in trunk/ master.

Ecco il commit pertinente .

E questa è una patch per risolvere il problema.

Sulla base di un commento nella patch, la reload_combine_recognize_patternfunzione stava cercando di regolare gli insn USE .


14

Questo codice contiene comportamenti indefiniti che consentirebbero a GCC di emettere l'assembly "errato"?

Il comportamento del codice presentato nella domanda è ben definito rispetto agli standard del linguaggio C99 e successivi. In particolare, C consente alle funzioni di restituire i valori della struttura senza restrizioni.


2
GCC produce una definizione autonoma della funzione; questo è ciò che stiamo guardando, indipendentemente dal fatto che sia ciò che viene eseguito quando lo compili in un'unità di traduzione insieme ad altre funzioni. Potresti anche testarlo facilmente senza effettivamente usarlo __attribute__((noinline))compilandolo in un'unità di traduzione da solo e collegandolo senza LTO, o compilando con -fPICciò implica che tutti i simboli globali sono (per impostazione predefinita) interponibili quindi non possono essere incorporati nei chiamanti. Ma davvero il problema è rilevabile solo guardando l'asm generato, indipendentemente dai chiamanti.
Peter Cordes,

Abbastanza giusto, @PeterCordes, anche se sono abbastanza sicuro che quel dettaglio sia stato cambiato da sotto di me in Godbolt.
John Bollinger,

La versione 1 della domanda era collegata a Godbolt con la sola funzione da sola in un'unità di traduzione, come lo stato della domanda stessa quando hai risposto. Non ho controllato tutte le revisioni o i commenti che avresti potuto guardare. Acqua sotto il ponte, ma non credo che ci sia mai stata un'affermazione secondo cui la definizione asm autonoma fosse rotta solo quando la fonte usava __attribute__((noinline)). (Sarebbe scioccante, non solo sorprendente il modo in cui è un bug di correttezza GCC). Probabilmente questo è stato menzionato solo per aver effettuato un test caller che stampa il risultato.
Peter Cordes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.