从函数返回结构时可能的GCC错误


133

我相信我在实施O'Neill的PCG PRNG时发现了GCC中的错误。(Godbolt的Compiler Explorer上的初始代码

相乘后oldstate通过MULTIPLIER,(存储在RDI结果),GCC不该结果添加到INCREMENT,movabs'ing INCREMENT到RDX代替,然后把它用作rand32_ret.state的返回值

一个最小的可复制示例(Compiler Explorer):

#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个字节的结构。在这种情况下,第二构件是在RDX因为RAX的上半部是用于对准填充或.b.a是较窄的类型。(sizeof(retstruct)无论哪种方式都是16;我们没有使用__attribute__((packed))它,所以它遵循alignof(uint64_t)=8。)

此代码是否包含任何未定义的行为,这些行为将允许GCC发出“错误的”程序集?

如果没有,应该在https://gcc.gnu.org/bugzilla/上报告


评论不作进一步讨论;此对话已转移至聊天
塞缪尔·柳

Answers:


102

我在这里看不到任何UB。您的类型是未签名的,因此不可能有签名溢出的UB,也没有什么奇怪的。(即使已签名,也必须为不会引起UB溢出的输入产生正确的输出,例如rdi=1)。它也被GCC的C ++前端破坏了。

同样,GCC8.2可以正确编译AArch64和RISC-V(用于maddmovk构造常量后使用指令,或在加载常量后添加RISC-V mul并添加指令)。如果GCC正在查找的是UB,我们通常希望它能够找到它,并破坏其他ISA的代码,至少是具有相同类型宽度和寄存器宽度的ISA。

Clang也可以正确编译。

这似乎是从GCC 5到6的回归;GCC5.4编译正确,而6.1和更高版本编译不正确。(Godbolt)。

您可以使用问题中的MCVE 在GCC的bugzilla中报告此问题。

它看起来确实是x86-64 System V结构返回结构中的一个错误,可能是包含填充的结构。 这将解释为什么它在内联时以及扩展a到uint64_t(避免填充)时起作用。



11
@vitorhnn似乎已固定在上master
SS安妮


14

此代码是否包含任何未定义的行为,这些行为将允许GCC发出“错误的”程序集?

问题中提出的代码的行为相对于C99和更高版本的C语言标准已得到很好的定义。特别是,C允许函数不受限制地返回结构值。


2
GCC确实生成了该功能的独立定义;这就是我们正在查看的内容,无论您在翻译单元中将其与其他功能一起编译时是否运行该内容。您可以__attribute__((noinline))通过实际地使用它来测试它,而无需实际使用,只需将其本身编译在翻译单元中并在没有LTO的情况下进行链接,或者通过编译-fPIC来暗示所有全局符号都是可插入的(默认情况下),因此不能内联到调用方中。但是实际上,仅通过查看生成的asm即可发现问题,而与调用者无关。
Peter Cordes

相当公平,@ PeterCordes,尽管我有足够的信心相信在Godbolt中这个细节已经从我下面改变了。
John Bollinger

问题的第1版与Godbolt关联,仅在翻译单元中具有功能本身,就像您回答时问题本身的状态一样。我没有检查您可能一直在查看的所有修订或评论。桥下有水,但我认为没有人声称独立的asm定义仅在使用源时才被破坏__attribute__((noinline))。(这将是令人震惊的,不仅使人惊讶地发现了GCC正确性错误)。可能只是因为使打印结果的测试调用程序而被提及。
Peter Cordes
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.