Wydaje mi się, że znalazłem błąd w GCC podczas wdrażania PCN PRNG O'Neilla. ( Kod początkowy w Godbolt's Compiler Explorer )
Po pomnożeniu oldstate
przez MULTIPLIER
(wynik przechowywany w rdi), GCC nie dodaje tego wyniku do INCREMENT
movabs'ing INCREMENT
zamiast do rdx, który następnie jest wykorzystywany jako wartość zwracana rand32_ret.state
Minimalny odtwarzalny przykład ( Eksplorator kompilatorów ):
#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;
}
Wygenerowany zespół (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
Co ciekawe, modyfikacja struktury tak, aby miała uint64_t jako pierwszy element, powoduje wygenerowanie poprawnego kodu , podobnie jak zmiana obu elementów na uint64_t
x86-64 System V zwraca struktury mniejsze niż 16 bajtów w RDX: RAX, gdy można je w prosty sposób skopiować. W tym przypadku drugi element jest w RDX, ponieważ górna połowa RAX jest wypełnieniem do wyrównania lub .b
gdy .a
jest węższy. ( sizeof(retstruct)
ma 16 w obu kierunkach; nie korzystamy, __attribute__((packed))
więc respektuje alignof (uint64_t) = 8.)
Czy ten kod zawiera jakieś niezdefiniowane zachowanie, które pozwoliłoby GCC na wysłanie „niepoprawnego” zestawu?
Jeśli nie, należy to zgłosić na https://gcc.gnu.org/bugzilla/
Odpowiedzi:
Nie widzę tu żadnego UB; twoje typy są niepodpisane, więc UB z podpisem jest niemożliwe i nie ma w tym nic dziwnego. (I nawet jeśli podpisany, musiałby generować prawidłowe dane wyjściowe dla danych wejściowych, które nie powodują przepełnienia UB, jak
rdi=1
). Jest również zepsuty w interfejsie GCC C ++.Ponadto GCC8.2 kompiluje go poprawnie dla AArch64 i RISC-V (do
madd
instrukcji po użyciumovk
do konstruowania stałych, lub mul RISC-V i dodawania po załadowaniu stałych). Jeśli GCC znajdowałby UB, zwykle spodziewalibyśmy się, że go znajdzie i złamie kod również dla innych ISA, przynajmniej tych, które mają podobne szerokości typów i szerokości rejestrów.Clang również kompiluje go poprawnie.
Wydaje się, że jest to regresja z GCC 5 do 6; Kompilacje GCC5.4 są poprawne, 6.1 i nowsze nie. ( Godbolt ).
Możesz to zgłosić w Bugzilli GCC za pomocą MCVE z twojego pytania.
Wygląda na to, że jest to błąd w obsłudze struktur-return w systemie x86-64 System V, być może struktur zawierających padding. To by wyjaśniało, dlaczego działa podczas wstawiania i przy rozszerzaniu
a
do uint64_t (unikanie wypełniania).źródło
master
.Zostało to naprawione w
trunk
/master
.Oto odpowiednie zatwierdzenie .
To jest łatka do rozwiązania problemu.
Na podstawie komentarza w łatce
reload_combine_recognize_pattern
funkcja próbowała dostosować ustawienia USE .źródło
Zachowanie kodu przedstawionego w pytaniu jest dobrze określone w odniesieniu do standardów języka C99 i późniejszych C. W szczególności C umożliwia funkcjom zwracanie wartości struktury bez ograniczeń.
źródło
__attribute__((noinline))
, kompilując go sam w jednostce tłumaczeniowej i łącząc bez LTO, lub kompilując, z-fPIC
czego wynika, że wszystkie symbole globalne są (domyślnie) interpolowalne, więc nie można ich wstawiać w wywołujące. Ale tak naprawdę problem można wykryć po spojrzeniu na wygenerowany asm, niezależnie od dzwoniących.__attribute__((noinline))
. (Byłoby to szokujące, a nie tylko zaskakujące jak błąd poprawności GCC). Prawdopodobnie wspomniano o tym tylko w przypadku wywołania testowego, które drukuje wynik.