Keyboard Shortcuts

Ctrl+K
Open search
Esc
Close search / dialog
↑
Back to top

Acquire and Release Are Access Modes, Not Barriers

26 minadvancedModule 2: Atomics and Ordering: Access Modes, and One Bit With Two MeaningsContinues from a64-atomic
26 min · The AArch64 Data Path: NEON, Atomics and Ordering

Why this matters

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.

The measurement: twelve functions, three barriers

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.

R20, and the sentence a reader arriving from x86-64 needs CLAIMED a C11 sequential consistency order needs a barrier somewhere MEASURED the whole corpus emits ZERO barriers outside the three functions whose source asks for a fence FOUND BY a census over thirteen generated .s files at four optimisation levels WHY acquire and release are ACCESS MODES, and a mode is not a barrier. A reader arriving from x86-64 will look for an mfence and this architecture does not have one.

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.

DMB, DSB, ISB: two fields, not one

[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.

And the option field is not decoration

     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.

Worked: the one control you may not name

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.

Apply it

  1. Count the barriers yourself, and then take them away. Compile the twelve functions at -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.)
  2. Ask for the twelve options and count what you get.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.)
  3. Find the instruction in the barrier group that is not a barrier. Look at 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.)
  4. Write down what you would need to publish the claim this page cannot. "DMB orders memory accesses, DSB waits for stores to become observable, ISB flushes the fetch side." Which sentence needs hardware? (Expect all three, and expect the middle one to be the most surprising: "observable" is a statement about what another processor can see, and there is no second processor here. The four option bits are measured; the meaning of the option is quoted; and the page says so at the point where a reader expects a number.)

Connect

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.

render_lesson_js(&mut page)