Saya yakin saya menemukan bug di GCC saat menerapkan O'Neill's PCG PRNG. ( Kode awal pada Penjelajah Kompiler Godbolt )
Setelah dikalikan oldstate
dengan MULTIPLIER
, (hasil disimpan dalam rdi), GCC tidak menambahkan hasil itu ke INCREMENT
, INCREMENT
sebagai gantinya pindah ke rdx, yang kemudian digunakan sebagai nilai pengembalian rand32_ret.state
Contoh direproduksi minimum ( Penjelajah Kompiler ):
#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;
}
Rakitan yang dihasilkan (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
Menariknya, memodifikasi struct untuk memiliki uint64_t sebagai anggota pertama menghasilkan kode yang benar , seperti halnya mengubah kedua anggota menjadi uint64_t
x86-64 Sistem V tidak mengembalikan struct lebih kecil dari 16 byte di RDX: RAX, ketika mereka sepele disalin. Dalam hal ini anggota ke-2 berada di RDX karena bagian tinggi dari RAX adalah padding untuk perataan atau .b
ketika .a
adalah tipe yang lebih sempit. (dengan sizeof(retstruct)
16 cara; kami tidak menggunakan __attribute__((packed))
sehingga menghormati alignof (uint64_t) = 8.)
Apakah kode ini mengandung perilaku tidak terdefinisi yang memungkinkan GCC memancarkan unit "salah"?
Jika tidak, ini harus dilaporkan di https://gcc.gnu.org/bugzilla/
Jawaban:
Saya tidak melihat UB di sini; tipe Anda tidak ditandai sehingga UB yang masuk-limpah tidak mungkin, dan tidak ada yang aneh. (Dan bahkan jika ditandatangani, itu harus menghasilkan output yang benar untuk input yang tidak menyebabkan UB meluap, seperti
rdi=1
). Ini rusak dengan front-end C ++ GCC juga.Juga, GCC8.2 mengkompilasinya dengan benar untuk AArch64 dan RISC-V (ke
madd
instruksi setelah menggunakanmovk
untuk membangun konstanta, atau RISC-V mul dan menambahkan setelah memuat konstanta). Jika itu adalah UB yang GCC temukan, kami biasanya mengharapkannya untuk menemukannya dan memecahkan kode Anda untuk SPA lain juga, setidaknya yang memiliki lebar jenis yang sama dan lebar register.Dentang juga mengkompilasinya dengan benar.
Ini tampaknya merupakan regresi dari GCC 5 ke 6; Kompilasi GCC5.4 benar, 6.1 dan kemudian tidak. ( Godbolt ).
Anda dapat melaporkan ini di bugzilla GCC menggunakan MCVE dari pertanyaan Anda.
Ini benar-benar terlihat seperti bug di x86-64 System-struct penanganan-kembali, mungkin dari struct yang mengandung padding. Itu akan menjelaskan mengapa itu bekerja saat inlining, dan kapan melebar
a
ke uint64_t (menghindari bantalan).sumber
master
.Ini telah diperbaiki pada
trunk
/master
.Berikut adalah komit yang relevan .
Dan ini adalah tambalan untuk memperbaiki masalah.
Berdasarkan komentar di tambalan,
reload_combine_recognize_pattern
fungsi mencoba menyesuaikan USE insns .sumber
Perilaku kode yang disajikan dalam pertanyaan didefinisikan dengan baik sehubungan dengan standar bahasa C99 dan yang lebih baru. Secara khusus, C mengizinkan fungsi untuk mengembalikan nilai struktur tanpa batasan.
sumber
__attribute__((noinline))
dengan mengkompilasinya dalam unit terjemahan dengan sendirinya dan menghubungkan tanpa KPP, atau dengan mengkompilasi dengan-fPIC
yang menyiratkan semua simbol global yang (secara default) interposable sehingga tidak dapat dimasukkan ke dalam penelepon. Tapi sebenarnya masalahnya bisa dideteksi hanya dari melihat asm yang dihasilkan, terlepas dari penelepon.__attribute__((noinline))
. (Itu akan mengejutkan, tidak hanya mengejutkan cara bug kebenaran GCC). Mungkin yang disebutkan hanya untuk membuat penelepon tes yang mencetak hasilnya.