https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70168
Bug ID: 70168
Summary: [5 Regression] Wrong code generation in
__sync_val_compare_and_swap on PowerPC
Product: gcc
Version: 5.4.0
Status: UNCONFIRMED
Severity: major
Priority: P3
Component: rtl-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: uweigand at gcc dot gnu.org
CC: amodra at gcc dot gnu.org, dje at gcc dot gnu.org
Target Milestone: ---
Target: powerpc64le-linux
Building the following test case on powerpc64le-linux with -O2 using the
current GCC 5 branch:
unsigned long atomicAND (volatile unsigned long *memRef, unsigned long mask)
{
unsigned long oldValue, newValue, prevValue;
for (oldValue = *memRef; ; oldValue = prevValue)
{
newValue = oldValue & mask;
if (newValue == oldValue)
break;
prevValue = __sync_val_compare_and_swap (memRef, oldValue, newValue);
if (prevValue == oldValue)
break;
}
return oldValue;
}
results in this assembler output:
atomicAND:
ld 9,0(3)
b .L6
.p2align 4,,15
.L10:
sync
.L3:
ldarx 10,0,3
cmpd 0,10,9
bne- 0,.L4
stdcx. 9,0,3
bne- 0,.L3
.L4:
isync
cmpld 7,9,10
beq 7,.L7
mr 9,10
.L6:
and 10,9,4
cmpld 7,9,10
bne 7,.L10
.L7:
mr 3,10
blr
Note how the stdcx. stores r9, which holds the original value, not the masked
value. Therefore, this will usually succeed without updating the memory
location.
Debugging this problem, it turns out that rs6000_expand_atomic_compare_and_swap
is called with retval (operands[1]) equal to newval (operands[4]), and the
expander then proceeds to clobber newval before using it.
There is code to detect overlap of retval with oldval (operands[3]), but not
with newval. Adding the equivalent detection code fixes the problem for me.
For some reason, I can reproduce this problem only with GCC 5; the same problem
should still be latently present in mainline since there's still no overlap
check, but I seem unable to construct a test case that would actually cause
retval == newval with mainline.