1. ホーム
  2. c

[解決済み] 関数から構造体を返す際のGCCバグの可能性

2022-07-02 06:22:25

質問

GCCでO'NeillのPCG PRNGを実装しているときにバグを見つけたと思うのですが、これは何ですか?( Godbolt のコンパイラエクスプローラでの初期コード )

を掛けた後 oldstateMULTIPLIER で、(結果はrdiに格納されます)、GCCはその結果を INCREMENT に追加せず、movabs'ing 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 では、16 バイトより小さい構造体がコピー可能な場合、RDX:RAX で返されます。 この場合、RAX の上位半分がアライメントのためのパディングであるため、2 番目のメンバは RDX にあります。 .b の場合 .a は狭義のタイプです。 ( sizeof(retstruct) はどちらにしても16です。 __attribute__((packed)) を使用していないので、alignof(uint64_t) = 8 を尊重しています)。

このコードはGCCが"incorrect"アセンブリを出すことを可能にするどんな未定義の行動も含んでいますか?

もしそうでなければ、これは https://gcc.gnu.org/bugzilla/

どのように解決するのですか?

あなたの型は符号なしなので、符号ありオーバーフローのUBは不可能であり、何も奇妙なことはありません。 (そして、たとえ符号付きであっても、以下のような入力に対して正しい出力を生成する必要があります。 でない入力に対して正しい出力を生成しなければなりません。 のようなオーバーフローUBを引き起こす入力に対して正しい出力を生成しなければなりません。 rdi=1 ). これは、GCCのC++フロントエンドでも同様に壊れています。

また、GCC8.2はこれをコンパイルします。 は AArch64 と RISC-V に対して正しくコンパイルされます。 (そして madd を使用した後に movk を使って定数を構築した後、あるいは、RISC-Vのmulと定数をロードした後に追加してください)。 GCCが発見したのがUBであった場合、一般的には、他のISA、少なくとも同様の型幅とレジスタ幅を持つものについても、それを発見してコードを破壊することが予想されます。

Clang もまた、それを正しくコンパイルします。

これはGCC 5から6へのリグレッションのようです。GCC5.4は正しくコンパイルされますが、6.1以降ではされません。 ( ゴッドボルト ).

で報告することができます。 GCCのバグジラ で、あなたの質問のMCVEを使って報告してください。

それは本当に x86-64 System V の構造体の戻り値の処理のバグのように見えますが、おそらくパディングを含む構造体のものです。 それはなぜインライン化したときに動作し、ワイド化したときに動作するかを説明します。 a を uint64_t (パディングを避ける) に広げるときに動作する理由を説明します。