https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127699

            Bug ID: 127699
           Summary: Clearing of small padded aggregates before SRA forces
                    stack round-trips
           Product: gcc
           Version: 16.2.1
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: chfast at gmail dot com
  Target Milestone: ---

Initializing a small struct with padding through aggregate initialization
(return R{s, c};) produces much worse code since
r15-3328-g673a448aa24efedd5ac140ebf7bfe652d7a6a846 ("Optimize initialization of
small padded objects").

https://godbolt.org/z/5csvafs3W

    struct R
    {
        unsigned long value;
        bool carry;
    };

    static inline R add(unsigned long x, unsigned long y)
    {
        const unsigned long s = x + y;
        return R{s, s < x};
    }

    unsigned long f(unsigned long x, unsigned long y, unsigned long z, unsigned
long d)
    {
        const R r1 = add(x, y);
        const R r2 = add(r1.value, z);
        return r2.value - (d & -(unsigned long)r1.carry) - (d & -(unsigned
long)r2.carry);
    }

GCC 14.2 -O2:

        add     rdi, rsi
        mov     eax, 0
        setc    al
        add     rdx, rdi
        adc     rax, 0
        imul    rax, rcx
        sub     rdx, rax
        mov     rax, rdx
        ret

GCC 15.1, 15.2 and trunk -O2:

        mov     DWORD PTR [rsp-24], 0
        xor     eax, eax
        add     rdi, rsi
        movabs  rsi, 72057594037927935
        mov     DWORD PTR [rsp-21], 0
        mov     r8, QWORD PTR [rsp-24]
        setc    al
        mov     DWORD PTR [rsp-24], 0
        and     r8, rsi
        mov     DWORD PTR [rsp-21], 0
        sal     r8, 8
        mov     r9, r8
        xor     r8d, r8d
        add     rdx, rdi
        setc    r8b
        and     rsi, QWORD PTR [rsp-24]
        mov     r9b, al
        sal     rsi, 8
        movzx   eax, r9b
        mov     rdi, rsi
        mov     dil, r8b
        movzx   esi, dil
        add     rax, rsi
        imul    rax, rcx
        sub     rdx, rax
        mov     rax, rdx
        ret

Since r15-3328 gimplify_init_constructor clears the object first because it has
padding:

      s = x + y;
      D.3034 = {};
      D.3034.value = s;
      _1 = s < x;
      D.3034.carry = _1;
      return D.3034;

The padding bytes now have a value that must be preserved. ESRA creates
replacements for value (offset 0, size 64) and carry (offset 64, size 8), but
the rest of the second 8 bytes stays in memory (MEM <char[8]> [(struct R
*)&D.3042 + 8B], grp_unscalarized_data = 1). This gives the stack stores of
zeros, the reload and the and/sal 8/byte merge for every R.

The comment of the change says "if there aren't any [padding bits], the
compiler is smart enough to optimize the clearing out", but here the clearing
is what creates the padding contents that cannot be optimized out.

-fno-tree-sra avoids the stack round-trips (17 instructions). So does
initializing the members another way: a constructor R(unsigned long v, bool c)
: value{v}, carry{c} {} (11 instructions) or assigning the members to a
default-initialized R. The C++ semantics of these is the same as the aggregate
initialization above: the padding is not zero-initialized in any of them.

The same change also made the testcase of PR106786 worse (7 -> 14 instructions
with stack accesses in GCC 15). This pattern (a carry flag returned as
{uint64_t, bool}) is common in multi-precision arithmetic; e.g. the intx
library is affected.

Reply via email to