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

Posted

技术标签:

【中文标题】从函数返回结构时可能出现 GCC 错误【英文标题】:Possible GCC bug when returning struct from a function 【发布时间】:2020-05-09 06:56:20 【问题描述】:

我相信我在实施 O'Neill 的 PCG PRNG 时在 GCC 中发现了一个错误。 (Initial code on Godbolt's Compiler Explorer)

在将oldstate 乘以MULTIPLIER 之后(结果存储在rdi 中),GCC 不会将该结果添加到INCREMENT,而是将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 的高半部分是对齐的填充,或者当.a 是更窄的类型时,.b。 (sizeof(retstruct) 无论哪种方式都是 16;我们没有使用 __attribute__((packed)) 所以它尊重 alignof(uint64_t) = 8。)

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

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

【问题讨论】:

评论不用于扩展讨论;这个对话是moved to chat。 【参考方案1】:

此问题已在 trunk/master 上修复。

这里是relevant commit。

这是a patch 来解决这个问题。

根据补丁中的评论,reload_combine_recognize_pattern 函数正在尝试调整 USE insns。

【讨论】:

【参考方案2】:

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

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

【讨论】:

GCC 确实产生了一个独立的函数定义;这就是我们正在研究的内容,无论您在翻译单元中与其他功能一起编译它时是否运行它。您可以在不实际使用 __attribute__((noinline)) 的情况下轻松测试它,方法是在翻译单元中自行编译它并在没有 LTO 的情况下链接,或者使用 -fPIC 编译,这意味着所有全局符号(默认情况下)都是可插入的,因此不能内联到调用者。但实际上,无论调用者如何,只要查看生成的 asm 就可以发现问题。 很公平,@PeterCordes,尽管我有理由相信这个细节是在 Godbolt 中从我手下改变的。 问题的版本 1 与 Godbolt 相关联,仅具有翻译单元中的功能,例如您回答时问题本身的状态。我没有检查您可能一直在查看的所有修订版或 cmets。桥下的水,但我认为从来没有人声称只有当源使用 __attribute__((noinline)) 时,独立的 asm 定义才被破坏。 (这将是令人震惊的,而不仅仅是令人惊讶的 GCC 正确性错误)。可能只是为了制作一个打印结果的测试调用者而提到的。【参考方案3】:

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

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

Clang 也能正确编译。

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

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

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

【讨论】:

I've reported it @vitorhnn 好像已经修复了master

以上是关于从函数返回结构时可能出现 GCC 错误的主要内容,如果未能解决你的问题,请参考以下文章

collect2:错误:ld 返回 1 退出状态,gcc

linux下使用gcc/g++编译代码时gets函数有错误

在 C++ 中为结构读取二进制文件的运行时错误

从SDL教程构建示例程序时的GCC错误

GCC 生成的程序集 - C 函数调用时的段错误

为啥 C++ 从 C# 中为返回自定义结构的函数获取错误的参数值