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

标签 c gcc assembly x86-64 compiler-bug

我相信我在实现 O'Neill 的 PCG PRNG 时发现了 GCC 中的一个错误。 ( Initial code on Godbolt's 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 作为第一个成员 produces correct code ,正如 changing both members to be 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/ 上报告

最佳答案

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

此外,GCC8.2 编译它 correctly for AArch64 and RISC-V (在使用 madd 构造常量后的 movk 指令,或 RISC-V mul 并在加载常量后添加)。如果 GCC 找到的是 UB,我们通常希望它找到它并破坏其他 ISA 的代码,至少那些具有相似类型宽度和寄存器宽度的代码。

Clang 也能正确编译它。

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

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

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

关于c - 从函数返回结构时可能存在 GCC 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59871171/

相关文章:

android - 使用动态库交叉编译 C 代码时出错

c - Dirent结构的成员

c++ - 如何调试 STL/C++ 的 GCC/LD 链接过程

c - gcc 开关 -Wc++0x-compat 用于 C 代码

c - 为什么GCC在编译C代码时不使用更多寄存器

c# - 如何在 C# 中使用 p/invoke 将指针传递给数组?

linux - 为什么 gcc 默认链接 '-z now',尽管延迟绑定(bind)是 ld 的默认值?

c - 特定计算机的软件堆栈

c - 如何将文件指针从 c 传递到 asm 中的调用

c - 守护进程如何在启动时自动运行