我在实现O'Neill的PCG PRNG时,发现了GCC中的一个bug。(在Godbolt's Compiler Explorer上的初始代码)
在将oldstate
乘以MULTIPLIER
之后(结果存储在rdi中),GCC没有将该结果加到INCREMENT
上,而是将INCREMENT
movabs到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作为第一个成员会产生正确的代码,同时将两个成员都改为uint64_t也可以。
当结构体可以被简单复制时,x86-64 System V会返回小于16字节的结构体到RDX:RAX寄存器中。在这种情况下,第二个成员位于RDX寄存器中,因为RAX的高半部分是对齐或
.b
的填充,当.a
是更窄的类型时。(sizeof(retstruct)
无论如何都是16;我们没有使用__attribute__((packed))
,所以它遵循alignof(uint64_t) = 8。)
这段代码是否包含任何未定义的行为,使得GCC能够生成“不正确”的汇编代码?
如果没有,应该在https://gcc.gnu.org/bugzilla/上报告此问题。