我相信我在实施O'Neill的PCG PRNG时发现了GCC中的错误。(Godbolt的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作为第一个成员可以生成正确的代码,将两个成员都更改为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/上报告