Kemungkinan bug GCC saat mengembalikan struct dari suatu fungsi

133

Saya yakin saya menemukan bug di GCC saat menerapkan O'Neill's PCG PRNG. ( Kode awal pada Penjelajah Kompiler Godbolt )

Setelah dikalikan oldstatedengan MULTIPLIER, (hasil disimpan dalam rdi), GCC tidak menambahkan hasil itu ke INCREMENT, INCREMENTsebagai 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 .bketika .aadalah 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/

vitorhnn
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Samuel Liew

Jawaban:

102

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 maddinstruksi setelah menggunakan movkuntuk 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 ake uint64_t (menghindari bantalan).

Peter Cordes
sumber
11
@vitorhnn Sepertinya sudah diperbaiki master.
SS Anne
19

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_patternfungsi mencoba menyesuaikan USE insns .

SS Anne
sumber
14

Apakah kode ini mengandung perilaku tidak terdefinisi yang memungkinkan GCC memancarkan unit "salah"?

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.

John Bollinger
sumber
2
GCC memang menghasilkan definisi fungsi yang berdiri sendiri; itulah yang kami lihat, terlepas dari apakah itu yang berjalan saat Anda mengompilasinya di unit terjemahan bersama dengan fungsi lainnya. Anda bisa dengan mudah mengujinya tanpa benar-benar menggunakan __attribute__((noinline))dengan mengkompilasinya dalam unit terjemahan dengan sendirinya dan menghubungkan tanpa KPP, atau dengan mengkompilasi dengan -fPICyang 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.
Peter Cordes
Cukup adil, @PeterCordes, meskipun saya cukup yakin bahwa detail diubah dari bawah saya di Godbolt.
John Bollinger
Versi 1 dari pertanyaan yang dikaitkan dengan Godbolt hanya dengan fungsi itu sendiri di unit terjemahan, seperti keadaan pertanyaan itu sendiri ketika Anda menjawab. Saya tidak memeriksa semua revisi atau komentar yang bisa Anda lihat. Air di bawah jembatan, tapi saya tidak berpikir pernah ada klaim bahwa definisi asm yang berdiri sendiri hanya rusak ketika sumbernya digunakan __attribute__((noinline)). (Itu akan mengejutkan, tidak hanya mengejutkan cara bug kebenaran GCC). Mungkin yang disebutkan hanya untuk membuat penelepon tes yang mencetak hasilnya.
Peter Cordes