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

vit*_*hnn 131 c assembly gcc x86-64 compiler-bug

我相信我在实现 O'Neill 的 PCG PRNG 时在 GCC 中发现了一个错误。(Godbolt 编译器资源管理器上的初始代码

相乘后oldstate通过MULTIPLIER,(存储在RDI结果),GCC不该结果添加到INCREMENT,movabs'ingINCREMENT到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;
}
Run Code Online (Sandbox Code Playgroud)

生成的程序集(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
Run Code Online (Sandbox Code Playgroud)

有趣的是,修改结构以将 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/ 上报告

Pet*_*des 103

我在这里没有看到任何 UB;您的类型是未签名的,因此签名溢出 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 及更高版本则不正确。(天马行空)。

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

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

  • [我已经举报了](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93402) (34认同)
  • @vitorhnn 看起来它已经被修复在“master”上。 (11认同)

JL2*_*210 21

这已在trunk/ 上修复master

这是相关的提交

这是解决问题的补丁

根据补丁中的注释,该reload_combine_recognize_pattern函数试图调整USE insns


Joh*_*ger 14

这段代码是否包含任何允许 GCC 发出“不正确”程序集的未定义行为?

问题中提供的代码的行为根据 C99 和更高版本的 C 语言标准进行了很好的定义。特别是,C 允许函数不受限制地返回结构值。

  • GCC 确实生成了该函数的独立定义;这就是我们正在研究的,无论当您在翻译单元中将其与其他函数一起编译时是否运行。您可以轻松地测试它,而无需实际使用“__attribute__((noinline))”,方法是将其单独编译到翻译单元中并在没有 LTO 的情况下进行链接,或者使用“-fPIC”进行编译,这意味着所有全局符号都是(默认情况下)可插入,因此无法内联到调用者中。但实际上,无论调用者是谁,只要查看生成的汇编就可以检测到问题。 (2认同)