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

从函数返回struct时可能出现GCC错误,c,gcc,assembly,x86-64,compiler-bug,C,Gcc,Assembly,X86 64,Compiler Bug,我相信我在实现O'Neill的PCG PRNG时在GCC中发现了一个bug。() 将oldstate乘以MULTIPLIER(结果存储在rdi中)后,GCC不会将该结果添加到INCREMENT,而是将INCREMENT移动到rdx,然后将其用作rand32_ret.state的返回值 最小可复制示例(): #包括 结构retstruct{ uint32_t a; uint64_t b; }; 结构retstruct fn(uint64\u t输入) { struct-retstruct-ret;

我相信我在实现O'Neill的PCG PRNG时在GCC中发现了一个bug。()

oldstate
乘以
MULTIPLIER
(结果存储在rdi中)后,GCC不会将该结果添加到
INCREMENT
,而是将
INCREMENT
移动到rdx,然后将其用作rand32_ret.state的返回值

最小可复制示例():

#包括
结构retstruct{
uint32_t a;
uint64_t b;
};
结构retstruct fn(uint64\u t输入)
{
struct-retstruct-ret;
ret.a=0;
ret.b=输入*11111+111111;
返回ret;
}
生成的程序集(GCC 9.2,x86_64,-O3):

有趣的是,修改结构使uint64\u t成为第一个成员

x86-64 SystemV在RDX:RAX中确实返回小于16字节的结构,而这些结构是可以复制的。在这种情况下,第二个成员在RDX中,因为RAX的高半部分是对齐的填充,或者
.b
时.a
是较窄的类型。(
sizeof(retstruct)
无论哪种方式都是16;我们没有使用
\uuuu属性(packed))
所以它考虑了alignof(uint64\u t)=8。)

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

如果没有,则应就此进行报告

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


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

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

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

Clang也能正确编译它

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

您可以从问题中使用MCVE报告此情况


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

这已在
主干上修复

这是你的电话号码

这是为了解决这个问题


根据补丁中的注释,
reload\u combine\u recognize\u pattern
函数试图进行调整。

GCC确实生成了该函数的独立定义;这就是我们所看到的,不管当您在翻译单元中编译它和其他函数时是否会运行它。通过在翻译单元中自行编译并在没有LTO的情况下链接,或者通过使用
-fPIC
进行编译(这意味着所有全局符号(默认情况下)都是可插入的,因此不能内联到调用方中),您可以轻松地测试它,而无需实际使用
\uu属性((noinline))
。但事实上,不管调用方是谁,只要查看生成的asm就可以发现问题。很公平,@PeterCordes,尽管我有理由相信该细节在Godbolt中是从我下面更改的。问题的第1版链接到Godbolt,在翻译单元中只包含函数本身,就像你回答问题时的状态一样。我没有检查你本可以看到的所有修订或评论。但是我不认为有人声称独立的asm定义只有在源代码使用
\uuuuu属性((noinline))
时才被破坏。(这将是令人震惊的,不仅仅是让GCC的正确性缺陷如此令人惊讶)。可能只在生成打印结果的测试调用方时提到了它。@vitorhnn看起来像是在
master
上修复的。注释不用于扩展讨论;这段对话已经结束。
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