<div dir="ltr">Hi ARM experts,<br><br>I am trying to understand how CAS is implemented on arm; in particular, "MacroAssembler::atomic_cas_bool":<br><br>MacroAssembler::atomic_cas_bool<br><div><br></div><div>```<br></div>    assert_different_registers(tmp_reg, oldval, newval, base);<br>    Label loop;<br>    bind(loop);<br>A   ldrex(tmp_reg, Address(base, offset));<br>B   subs(tmp_reg, tmp_reg, oldval);<br>C   strex(tmp_reg, newval, Address(base, offset), eq);<br>D   cmp(tmp_reg, 1, eq);<br>E   b(loop, eq);<br>F   cmp(tmp_reg, 0);<br>    if (tmpreg == noreg) {<br>      pop(tmp_reg);<br>    }<br>```<br><br>It uses LDREX and STREX to perform a cas of *(base+offset) from oldval to newval. It does so in a loop. The code distinguishes two failures: STREX failing, and a "semantically failed" CAS.<br><br><div>Here is what I think this code does:<br></div><div><br></div>A) LDREX: tmp=*(base+offset)<br>B) tmp -= oldvalue <br>   If *(base+offset) was unchanged, tmp_reg is now 0 and Z is 1<br>C) If Z is 1: STREX the new value: *(base+offset)=newval. Otherwise, omit.<br>   After this, if the store succeeded, tmp_reg is 0, if the store failed its 1.<br>D) Here, tmp_reg is: 0 if the store succeeded, 1 if it failed, 1...n if *(base+offset) had been modified before LDREX.<br>   We now compare with 1 and ...<br>E) ...repeat the loop if tmp_reg was 1<br><br>So we loop until either *(base+offset) had been changed to some other value concurrently before out LDREX. Or until our store succeeded.<br><br>I wondered what the loop guards against. And why it would be okay sometimes to omit it.<br><br>IIUC, STREX fails if the core did lose its exclusive access to the memory location since the LDREX. This can be one of three things, right? :<br>- another core slipped in an LDREX+STREX to the same location between our LDREX and STREX<br>- Or we context switched to another thread or process. I assume it does a CLREX then, right? Because how could you prevent a sequence like "LDREX(p1) -> switch -> LDREX(p2) -> switch back STREX(p1)" - if I understand the ARM manual [1] correctly, a STREX to a different location than the preceding LDREX is undefined.<br>- Or we had a signal after LDREX and did a second LDREX in the signal handler. Does the kernel do a CLREX when invoking a signal handler?<br><br>More questions:<br><br>- If I got it right, at (D), tmp_reg value "1" has two meanings: either STREX failed or some thread increased the value concurrently by 1. We repeat the loop either way. Is this just accepted behavior? Increasing by 1 is maybe not that rare.<br><br>- If I understood this correctly, the loop guards us mainly against context switches. Without the loop a context switch would count as a "semantically failed" CAS. Why would that be okay? Should we not do this loop always?<br><br>- Do we not need an explicit CLREX after the operation? Or does the STREX also clear the hardware monitor? Or does it just not matter?<br><br>- We have VM_Version::supports_ldrex(). Code seems to me sometimes guarded by this (e.g MacroAssembler::atomic_cas_bool), sometimes code just executes ldrex/strex (e.g. the one-shot path of MacroAssembler::cas_for_lock_acquire). Am I mistaken? Or is LDREX now generally available? Does ARMv6 mean STREX and LDREX are available?<br><br><br>Thanks a lot!<br><br>Cheers, Thomas<br><br><br><br></div>