関数から構造体を返すときのGCCバグの可能性


133

O'NeillのPCG PRNGの実装中にGCCのバグを見つけたと思います。(Godboltのコンパイラエクスプローラの初期コード

、(rdiに格納された結果)を乗算oldstateした後MULTIPLIER、GCCはその結果をINCREMENTに追加せず、INCREMENT代わりにrdxに移動し、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は、簡単にコピーできる場合、RDX:RAXで16バイトより小さい構造体を返します。この場合、2番目のメンバーはRDXにあります。これは、RAXの上位半分が位置合わせのためのパディングであるか.b、または.a幅が狭いタイプだからです。(sizeof(retstruct)どちらの方法でも16です。使用していない__attribute__((packed))ため、alignof(uint64_t)= 8を尊重します。)

このコードには、GCCが「誤った」アセンブリを発行できるようにする未定義の動作が含まれていますか?

そうでない場合は、https://gcc.gnu.org/bugzilla/で報告されます。


コメントは詳細な議論のためのものではありません。この会話はチャットに移動さました
Samuel Liew

回答:


102

ここにはUBはありません。あなたのタイプは署名されていないので、signed-overflow UBは不可能であり、奇妙なことは何もありません。(そして、署名されている場合でも、オーバーフローUBを引き起こさない入力に対して正しい出力を生成する必要がありますrdi=1)。GCCのC ++フロントエンドでも壊れています。

また、GCC8.2はそれをAArch64およびRISC-V用正しくコンパイルします(定数の構築maddに使用movkした後の命令、またはRISC-V mulと定数のロード後に追加します)。GCCが検出していたのがUBである場合、通常はそれが検出され、他のISA(少なくとも同様のタイプ幅とレジスタ幅を持つISA)のコードが壊れると予想されます。

Clangはそれも正しくコンパイルします。

これは、GCC 5から6への回帰のようです。GCC5.4は正しくコンパイルされますが、6.1以降ではコンパイルされません。(Godbolt)。

質問のMCVEを使用して、GCCのバグジラでこれを報告できます。

これは、x86-64 System Vのstruct-return処理、おそらくパディングを含むstructのバグのようです。 これは、インライン化時、およびauint64_tへの拡張時に(パディングを回避して)機能する理由を説明します。



11
@vitorhnnは修正されたようmasterです。
SSアン

19

これはtrunk/で修正されましたmaster

これが関連するコミットです。

そして、これは問題を修正するためのパッチです。

パッチのコメントに基づいて、reload_combine_recognize_pattern関数はUSE insnsを調整しようとしました。


14

このコードには、GCCが「誤った」アセンブリを発行できるようにする未定義の動作が含まれていますか?

質問で提示されたコードの動作は、C99以降のC言語標準に関して明確に定義されています。特に、Cは関数が制限なしに構造体の値を返すことを許可します。


2
GCCは関数のスタンドアロン定義を生成します。それは、他の関数と共に翻訳単位でコンパイルしたときに実行されるかどうかに関係なく、私たちが見ているものです。実際に使用せずに__attribute__((noinline))、それ自体を翻訳単位でコンパイルし、LTOなしでリンクするか、-fPICすべてのグローバルシンボルが(デフォルトで)挿入可能であるため、呼び出し元にインライン化できないことを意味するコンパイルによって、簡単にテストできます。しかし、実際には問題は、呼び出し元に関係なく、生成されたasmを見ただけで検出できます。
Peter Cordes、

@PeterCordesは十分に公平ですが、その詳細がGodboltで自分の下から変更されたと私は合理的に確信しています。
ジョンボリンジャー

ゴッドボルトにリンクされた質問のバージョン1で、回答単位の質問自体の状態のように、翻訳単位の機能だけで機能します。私はあなたが見ていたかもしれないすべての改訂やコメントをチェックしませんでした。橋の下の水ですが、スタンドアロンのasmの定義が、ソースが使用されたときにのみ破られたという主張は今までになかったと思います__attribute__((noinline))。(それはGCCの正しさのバグの方法を驚くだけではなく、衝撃的です)。おそらくそれは、結果を出力するテスト呼び出し元を作成するためにのみ言及されました。
Peter Cordes、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.