Assembly Language DISCUSSION

Is INC on a memory operand atomic on x86, and what about counter++ on ARM Cortex-M?

Started by bosbos atomic incrementLOCK prefixLDREX STREXrace conditionread-modify-write
4 replies 248 views 5 participants
Latest activity · 30 Sep 2026

Is INC on a memory operand atomic on x86, and what about counter++ on ARM Cortex-M?

bosbos Assembly Language Forum
#1

My C line counter++; compiles to a single instruction on x86, add dword ptr [counter], 1, so I assumed it could not be interrupted halfway and was therefore safe to use from two threads. Yet with two threads each doing one million increments, the total comes out short on a multi-core PC.

On a Cortex-M4 the same line becomes three instructions: ldr, adds, str. When is a read-modify-write like this atomic, and what does the correct version look like in assembly on each architecture?

Community replies 4

Re: Is INC on a memory operand atomic on x86, and what about counter++ on ARM Cortex-M?

#2

One instruction is not one atomic operation. add dword ptr [counter], 1 is executed internally as a load, an add and a store. On a single core it is atomic with respect to interrupts, because x86 takes interrupts only between instructions, so a handler or another thread on the same core sees the state before or after, never the middle.

With two cores nothing prevents both from loading the same old value. If the counter is 41, both load 41, both add one and both store 42, where the result should be 43. That is where your missing counts go. The fix is the LOCK prefix: lock add dword ptr [counter], 1 makes the core keep exclusive ownership of the cache line for the whole read-modify-write.

Re: Is INC on a memory operand atomic on x86, and what about counter++ on ARM Cortex-M?

#3

LOCK is only valid on certain instructions with a memory destination, among them ADD, SUB, INC, DEC, AND, OR, XOR, XADD and CMPXCHG. xchg with a memory operand is locked implicitly. lock xadd gives you fetch-and-add and lock cmpxchg gives compare-and-swap, the building block of most lock-free algorithms.

It is not free. A locked instruction also acts as a full memory barrier and is several times slower than the plain form even without contention, so compilers only emit it when asked: _Atomic int or atomic_fetch_add in C11, std::atomic<int> in C++. With either of those your two threads will report exactly 2,000,000.

Re: Is INC on a memory operand atomic on x86, and what about counter++ on ARM Cortex-M?

#4

ARM is a load/store architecture with no instruction that modifies memory in place, so counter++ is always LDR, ADDS, STR, and an interrupt can arrive between any two of them. If the handler increments the same variable in that window, its update is overwritten when the main code executes its STR.

Cortex-M3, M4 and M7 provide exclusive-access instructions for this: ldrex r1, [r0], adds r1, #1, strex r2, r1, [r0], cmp r2, #0, bne retry. STREX performs the store only if the exclusive monitor is still set and writes 0 to r2 on success or 1 on failure. Taking an exception clears the monitor, so an interrupt in the middle makes the store fail and the loop retries with a fresh value.

Re: Is INC on a memory operand atomic on x86, and what about counter++ on ARM Cortex-M?

#5

Cortex-M0 and M0+ (ARMv6-M) have no LDREX/STREX. The standard approach there is a short critical section: mrs r3, PRIMASK, cpsid i, then the load, add and store, then msr PRIMASK, r3. Saving and restoring PRIMASK, rather than ending with cpsie i, keeps the code correct when it is called with interrupts already disabled. Three instructions with interrupts masked add well under a microsecond of latency at typical clock rates.

A single aligned 32-bit load or store is atomic on these cores, so a counter written only by the ISR and read by main needs no protection. It is read-modify-write from both sides, or multi-word values such as a 64-bit counter on a 32-bit core, that need it. Confirm with objdump -d whether your atomic type turned into lock add, an LDREX loop or a library call.

TEP COMMUNITY