O'Neill の PCG PRNG を実装しているときに、GCC にバグを見つけたと思います。Godbolt のコンパイラ エクスプローラーの初期コード)
oldstate
を乗算した後MULTIPLIER
(結果はrdiに格納されます)、GCCはその結果を に追加せずINCREMENT
、INCREMENT
代わりにrdxにmovabsし、それがrand32_ret.stateの戻り値として使用されます。
最小限の再現可能な例(コンパイラエクスプローラ):
#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;
}
生成されたアセンブリ (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
興味深いことに、構造体を最初のメンバーとしてuint64_tを持つように変更すると正しいコードを生成する、同様に両方のメンバーをuint64_tに変更する
x86-64 System V は、簡単にコピーできる場合、16 バイト未満の構造体を RDX:RAX で返します。この場合、2 番目のメンバーは RDX にあります。これは、RAX の上位半分がアライメントのパディングであるか、または がより狭い型である.b
場合です。(はいずれにしても 16 です。 は使用していないため、alignof(uint64_t) = 8 が尊重されます。).a
sizeof(retstruct)
__attribute__((packed))
このコードには、GCC が「不正な」アセンブリを生成する可能性がある未定義の動作が含まれていますか?
そうでなければ、これは報告されるべきである参考:
ベストアンサー1
ここではUBは見当たりません。あなたの型は符号なしなので、符号付きオーバーフローUBは不可能であり、何もおかしくありません。(そして、たとえ符号付きであっても、入力に対して正しい出力を生成する必要があります。しないオーバーフロー UB を引き起こします ( などrdi=1
)。これは、GCC の C++ フロントエンドでも壊れています。
また、GCC8.2はこれをコンパイルしますAArch64とRISC-Vに正しく対応(定数を構築するためにmadd
使用した後の命令movk
、または定数をロードした後の RISC-V mul と add に)。GCC が検出したのが UB だった場合、通常は GCC がそれを見つけて、少なくとも同様の型幅とレジスタ幅を持つ他の ISA のコードも破壊すると予想されます。
Clang でも正しくコンパイルされます。
これは GCC 5 から 6 への回帰のようです。GCC5.4 は正しくコンパイルされますが、6.1 以降はコンパイルされません。(ゴッドボルト)。
これを報告できるのはGCC のバグジラ質問の MCVE を使用します。
これは、おそらくパディングを含む構造体の x86-64 System V 構造体戻り処理のバグであるように見えます。a
これにより、インライン化時やuint64_t に拡張する場合 (パディングを回避) に機能する理由が説明されます。