Possible GCC bug when returning struct from a function
Asked 07 September, 2021
Viewed 2.8K times
  • 60
Votes

I believe I found a bug in GCC while implementing O'Neill's PCG PRNG. (Initial code on Godbolt's Compiler Explorer)

After multiplying oldstate by MULTIPLIER, (result stored in rdi), GCC doesn't add that result to INCREMENT, movabs'ing INCREMENT to rdx instead, which then gets used as the return value of rand32_ret.state

A minimum reproducible example (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;
}

Generated assembly (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

Interestingly, modifying the struct to have the uint64_t as the first member produces correct code, as does changing both members to be uint64_t

x86-64 System V does return structs smaller than 16 bytes in RDX:RAX, when they're trivially copyable. In this case the 2nd member is in RDX because the high half of RAX is the padding for alignment or .b when .a is a narrower type. (sizeof(retstruct) is 16 either way; we're not using __attribute__((packed)) so it respects alignof(uint64_t) = 8.)

Does this code contain any undefined behaviour that would allow GCC to emit the "incorrect" assembly?

If not, this should get reported on https://gcc.gnu.org/bugzilla/

3 Answer