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