从函数返回 struct 时可能出现 GCC 错误

我相信我在实现奥尼尔的 PCGPRNG 时在 GCC 中发现了一个错误

在将 oldstate乘以 MULTIPLIER(结果存储在 rdi 中)之后,GCC 不会将这个结果添加到 INCREMENT,而是将 INCREMENT移动到 rdx,然后使用 rdx 作为 rand32 _ ret 的返回值。国家

最小可重复示例(编译器资源管理器) :

#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

有趣的是,修改 struct 使 uint64 _ t 作为第一个成员 产生正确的代码将两个成员都更改为 uint64 _ t也是如此

X86-64 System V 在 RDX: RAX 中返回小于16字节的结构,当它们可以轻松复制时。在这种情况下,第二个成员在 RDX 中,因为 RAX 的高半部分是用于对齐的填充,或者当 .a是较窄的类型时,用于填充 .b。(sizeof(retstruct)是16; 我们没有使用 __attribute__((packed)),所以它尊重 alignof (uint64 _ t) = 8。)

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

如果没有,这应该得到报告的 https://gcc.gnu.org/bugzilla/

6498 次浏览

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

问题中提出的代码的行为已经根据 C99和以后的 C 语言标准得到了很好的定义。特别是,C 允许函数不受限制地返回结构值。

我没有看到任何 UB 在这里; 你的类型是无符号的,所以签名-溢出 UB 是不可能的,没有什么奇怪的。(即使签名,它也必须为 不要导致溢出 UB (如 rdi=1)的输入产生正确的输出。)。它也被 GCC 的 C + + 前端打破了。

此外,GCC8.2编译它 正确的 AArch64和 RISC-V(在使用 movk构造常数之后转换为 madd指令,或者 RISC-V mul 并在加载常数之后添加)。如果 GCC 找到的是 UB,我们通常希望它能找到它,并为其他 ISA 破解您的代码,至少是那些具有相似类型宽度和注册宽度的 ISA。

Clang 也正确地编译了它。

这似乎是从 GCC5到6的回归; GCC5.4编译是正确的,6.1以后不会。(戈德博尔特).

你可以从你的问题使用 MCVE 在 海湾合作委员会的疯子上报告这一点。

它看起来确实像是 x86-64 System V struct-return 处理中的 bug,可能是包含填充的 struct。 这可以解释为什么它在内联时工作,以及在将 a扩大到 uint64 _ t (避免填充)时工作。

这个已经固定在 trunk/master上了。

这是 相关的承诺

这是 补丁来解决这个问题。

根据补丁中的注释,reload_combine_recognize_pattern功能试图调整 使用 insns