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

            Bug ID: 128066
           Summary: Missed load merge: `p->a || p->b` on two adjacent bool
                    members
           Product: gcc
           Version: 14.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: rockeet at gmail dot com
  Target Milestone: ---

Two adjacent bool members inside one aligned 4-byte chunk:

    struct alignas(4) Flags { bool a, b, c, d; };

    bool any_or (const Flags* p) { return p->a |  p->b; }   // branchless
    bool any_sc (const Flags* p) { return p->a || p->b; }   // short-circuit
    bool any_loc(const Flags* p) { bool a = p->a, b = p->b; return a || b; }

With g++ 14.2.0, -O3 -S -masm=intel, x86_64-linux-gnu:

    _Z6any_orPK5Flags:
            movzx   eax, BYTE PTR [rdi]
            or      al, BYTE PTR 1[rdi]
            ret

    _Z6any_scPK5Flags:
            movzx   eax, BYTE PTR [rdi]
            test    al, al
            jne     .L3
            movzx   eax, BYTE PTR 1[rdi]
    .L3:
            ret

    _Z7any_locPK5Flags:
            movzx   eax, BYTE PTR [rdi]
            or      al, BYTE PTR 1[rdi]
            ret

Expected: one 16-bit load of the two adjacent bytes.

            cmp     WORD PTR [rdi], 0
            setne   al
            ret

Why this is valid:

  * Flags is alignas(4), so p is 4-byte aligned and a WORD PTR [p] load is
    naturally aligned; it cannot cross a cache line.
  * Both bytes are members of the same object, no store or call intervenes,
    and neither access is volatile.
  * bool loads are 0/1 in a valid program, so (a | b) != 0 is exactly
    a || b; the short-circuit is not observable here.
  * GCC already considers the second load speculatable: any_loc is
    if-converted into an unconditional OR, and any_or is branchless
    already. So the only missing piece is the load merge itself.

Reproduced with g++ 11.5.0, 12.4.0, 13.3.0 and 14.2.0.

Related: PR 98953 (adjacent loads with an offset / bswap). This case is
distinct: there is no index or offset, and the OR feeds a comparison instead
of assembling a wider value, so the bswap pass does not match it.

Clang has the same missed optimization; reported upstream as
https://github.com/llvm/llvm-project/issues/187854

Reply via email to