x86-64's memory model is strong and mostly implicit: a program is sequentially consistent unless it says otherwise, and the exceptions are three opcodes — MFENCE, LFENCE, SFENCE — which a program sprinkles at the places it wants to be weaker.
AArch64's model is weak, and the only tool for strengthening it is an access mode written on the access itself. LDAR is a load that also acquires. STLR is a store that also releases. A C11 acquire load is one of them, rather than a load followed by a barrier.
So the same correct-looking C11 program needs different instructions on the two architectures, and the compiler is what translates. Which means the compiler's choices are the reference material, and they are measurable — and what they measure is not what a reader who has only read the manual expects.
function the C11 operation what clang emitted barriers
------------------ ------------------------ ---------------------------------------- ---------
load_acq acquire load ldar x0, [x0] 0
load_seqcst seq_cst load ldar x0, [x0] 0
store_rel release store stlr x1, [x0] 0
store_seqcst seq_cst store stlr x1, [x0] 0
fence_seqcst seq_cst fence dmb ish 1
fence_acquire acquire fence dmb ishld 1
fence_release release fence dmb ish 1
fence_relaxed relaxed fence (nothing) 0
load_then_load two seq_cst loads ldar x8, [x0] | ldar x9, [x1] | add x0, x9, x8 0
store_then_store two seq_cst stores stlr x2, [x0] | stlr x3, [x1] 0
bump a seq_cst fetch-add mov x8, x0 | ldaxr x0, [x8] | add x9, x0, #1
| stlxr w10, x9, [x8] | cbnz w10, .LBB23_1 0
publish a plain store, then a
seq_cst store mov w8, #1 | str x1, [x0, #8] | stlr x8, [x0] 0
[MEASURED] and the number that decides the concept:
3 barriers in 12 functions that perform C11 atomic operations.
Read the first two rows again. A C11 acquire load and a C11 sequentially consistent load are the same instruction — one ldar x0, [x0], 0xc8dffc01 — and so are a release store and a seq_cst store, both one stlr, 0xc89ffc01. There is no dmb ish in either, and there is no wider instruction for the stronger order.
That is not a compiler shortcut. [QUOTED] On AArch64 the sequentially consistent order on a single access is exactly acquire-or-release: the extra constraint a seq_cst load carries over an acquire load is on its relation to other operations, and it is discharged by the other operations being acquire or release too. (The Architecture Reference Manual's C/C++ mapping appendix, which is a mapping and not a proof.) And the measured consequence is the table above: zero barriers in every atomic in the corpus.
And the asymmetry in the three that do appear is the second finding on this page:
fence_seqcst dmb ish | ret
fence_acquire dmb ishld | ret
fence_release dmb ish | ret
An acquire fence narrows to dmb ishld — the load variant. A release fence is a full dmb ish and is not narrowed to dmb ishst. [MEASURED], and the course does not claim it is wrong: a release fence in C11 has to order against later loads as well as later stores, [QUOTED], so the full barrier may be exactly what the mapping requires. What is measured is that the compiler narrows one and not the other, and a reader who expected symmetry gets a measurement instead.
[QUOTED] The three barriers: DMB is a data memory barrier and orders the memory accesses either side of it, and does not make the instruction stream see anything new. DSB is as DMB and additionally completes before any instruction after it is executed — so it waits for stores to become observable, not merely performed. ISB is an instruction synchronisation barrier: it flushes the pipeline and the instruction-fetch side, it is how a context switch to a different exception level or a different PC is made to take effect, and it has no effect on the data side at all. (ARM DDI 0597, the pseudocode for the three instruction groups, and the "Memory ordering" chapter of the Architecture Reference Manual.)
They are not interchangeable and the difference is not a matter of degree. A DMB does not stop an instruction fetch; an ISB does not order a load. That is QUOTED. What is MEASURED is that the encoding expresses the difference in two separate fields, and that the second one is the one everybody forgets:
option dmb dsb isb the option field the id field
-------- ----------- ----------- ----------- ------------------ --------------
oshld 0xd50331bf 0xd503319f REFUSED CRm = 0001 op2 = 101
oshst 0xd50332bf 0xd503329f REFUSED CRm = 0010 op2 = 101
osh 0xd50333bf 0xd503339f REFUSED CRm = 0011 op2 = 101
nshld 0xd50335bf 0xd503359f REFUSED CRm = 0101 op2 = 101
nshst 0xd50336bf 0xd503369f REFUSED CRm = 0110 op2 = 101
nsh 0xd50337bf 0xd503379f REFUSED CRm = 0111 op2 = 101
ishld 0xd50339bf 0xd503399f REFUSED CRm = 1001 op2 = 101
ishst 0xd5033abf 0xd5033a9f REFUSED CRm = 1010 op2 = 101
ish 0xd5033bbf 0xd5033b9f REFUSED CRm = 1011 op2 = 101
ld 0xd5033dbf 0xd5033d9f REFUSED CRm = 1101 op2 = 101
st 0xd5033ebf 0xd5033e9f REFUSED CRm = 1110 op2 = 101
sy 0xd5033fbf 0xd5033f9f 0xd5033fdf CRm = 1111 op2 = 101
[MEASURED] DMB and DSB accept 12 of the 12 options, and the
option field is bits[11:8] -- FOUR bits carrying TWELVE names, so
four of the sixteen values are unallocated. ISB accepts ONE.
So "DMB, DSB and ISB differ in one field" is true and is half the answer. The identity is a three-bit op2 at bits[7:5]:
dmb sy 0xd5033fbf dsb sy 0xd5033f9f isb 0xd5033fdf
dmb xor dsb = 0x00000020 -> bit 5 only
dmb xor isb = 0x00000060 -> bit 5 and bit 6
dmb xor clrex = 0x000000e0 -> bits[7:5]
and the one that is NOT a barrier and shares the group:
clrex 0xd5033f5f op2 = 010 Rt = 11111
MEASURED: 0b010, and none of the three barriers uses it.
CLREX is the fourth instruction in that group, at an op2 value no barrier uses, and it has no ordering effect at all — it exists to make an LL/SC pair stop being a loop. And note the shape of the last row: four instructions share bits[31:8] and differ in three bits, and one of the four is not a barrier. A decoder that reads a group boundary as "these are the barriers" has missed an instruction that belongs to the group.
dmb osh CRm = 0011 = 3 -- the domain it orders
dmb nsh CRm = 0111 = 7 -- the domain it orders
dmb ish CRm = 1011 = 11 -- the domain it orders
dmb sy CRm = 1111 = 15 -- the domain it orders
dmb ishld CRm = 1001 = 9 -- the inner domain, loads only
[QUOTED] osh orders accesses to the outer shareable domain, ish the inner one, sy the whole system. MEASURED: the domain bits and the direction bits (ld, st) are the same four bits, so there is no way to say "inner, loads only" without spending a code — and ishldis that code, 1001. Twelve names in sixteen codes, allocated as four domains times two directions plus four combinations, and the combinations are the ones you actually want.
The last measurement in the course is a refusal, and it is the right place to end a page about ordering — because it is a control over the privilege level, it is not an ordering instruction, and the assembler will not spell its name at all.
msr PSTATE.PAN, x1 -> REFUSED: expected writable system
register or pstate
msr S3_3_C4_C0_2, x1 -> 0xd51b4041
mrs x0, S3_3_C4_C0_2 -> 0xd53b4040
msr xor mrs = bit 21
The assembler refuses the architectural name of a register it will accept the encoding of. [QUOTED] PSTATE.PAN — Privileged Access Never — when set, forbids the level below from using SVC and forbids SP_EL0, so that a kernel can hand a pointer to user space knowing user space cannot use it to reach the kernel.
And note the shared bit: msr and mrs differ in bit 21 — which is the bit that is a constant across the whole LSE group, measured on the previous page. A bit that means "write" in one group and "nothing at all" in another, in the same 32-bit word format, is a good reminder that a bit position is not a property.
-O2, grep the .s for dmb, dsb and isb. (Expect three, all in fence functions. Then delete the three fence functions and compile again: expect zero. The measurement is not "AArch64 needs no barriers" — it is "these twelve operations needed none", and the difference between those two sentences is the whole discipline of this course.)dmb oshld … dmb sy, all twelve; then isb oshld … isb sy. (Expect twelve and ONE. isb accepts only sy, and isb sy is the same word as bare isb — 0xd5033fdf — so an option field with one live value in one member of its group is a field that is not a field in that member.)0xd5033f5f in a hex editor. (Expect clrex, at op2 = 0b010, a value none of the three barriers uses. It shares bits[31:8] with all of them and has no ordering effect: it exists so a program can decide to stop retrying. A group named for what its members do will eventually contain a member that does not do it.)Backwards, immediately. The exclusive monitor measured the cost of an ordering mode — one bit on an exclusive, two bits on an LSE operation, and a 128-bit fetch-add that is two instructions because LSE has no pair arithmetic. This page is where the meaning of those bits is, and the two pages together are the argument that ordering on AArch64 is a property of the access and a barrier is the exception.
Back to the machine course, and this is the connection that makes the section a whole. Four Levels, and Why a Page Needs a Pair of Relocations established dsb + isb as the sequence that makes a context switch take effect — and it is quoted, because there is no EL1 here and no way to run it. This page measured that isb accepts one option and that clrex is in the same group, which is what turns that quoted sequence from a sentence into two words you can hex-dump.
Sideways, and it is the contrast the whole section was building toward. The x86-64 SIMD course measured its atomics — 3.89×, 4.92×, 27.65× — on a machine it could time, and it measured them with mfence in the picture. The two architectures agree that an atomic needs an ordering story and disagree about where the story lives: on x86-64 in a prefix and an explicit fence, on AArch64 in a bit on the access. Neither is the modern one. AArch64's is the more expressive design and it is also the one that punishes a reader who arrives expecting the other — which is why this page ends on a register the assembler will not name rather than on a summary.
Outward, and this is the last idea the section has to give you. An ordering mode on an access is a declaration about a relationship, and a barrier is a declaration about a point. That difference is why AArch64 needs fewer barriers for the same program and needs a reader who has internalised it more: the guarantee is distributed across the accesses, so a reader who looks only at the fences sees nothing, and a reader who looks only at one access sees half. The x86-64 reader's habit — find the fence, that is where the ordering is — is not wrong on x86-64 and is actively misleading here, and the cheapest way to notice is to compile a C11 program and look for the instruction that is not there.