{"id":106963,"date":"2022-08-11T07:00:00","date_gmt":"2022-08-11T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106963"},"modified":"2022-08-11T07:47:39","modified_gmt":"2022-08-11T14:47:39","slug":"20220811-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220811-00\/?p=106963","title":{"rendered":"The AArch64 processor (aka arm64), part 13: Atomic access"},"content":{"rendered":"<p>Atomic operations are performed by the traditional RISC-style <i>load locked<\/i>\/<i>store conditional<\/i> pattern.<\/p>\n<pre>    ; load exclusive register byte\r\n    ldxrb   Rd\/zr, [Xn\/sp]\r\n\r\n    ; load exclusive register halfword\r\n    ldxrh   Rd\/zr, [Xn\/sp]\r\n\r\n    ; load exclusive register\r\n    ldxr    Rd\/zr, [Xn\/sp]\r\n\r\n    ; load exclusive register pair\r\n    ldxp    Rd1\/zr, Rd2\/zr, [Xn\/sp]\r\n<\/pre>\n<p>These instructions atomically load a byte, halfword, word, doubleword, or pair of registers from memory. The instruction also tells the processor to monitor the memory address to see if any other processor writes to that same address, or addresses in the same &#8220;exclusive reservation granule&#8221;. (Implementations are allowed to have granules as large as 2KB.)<\/p>\n<p>Note that the atomicity guarantee is only partial if you use <code>LDXP<\/code> to load a pair of 64-bit registers.\u00b9 The entire 128-bit value is not loaded atomically; instead, each 64-bit portion is loaded atomically separately. You can still get tearing between the two registers.<\/p>\n<p>The only supported addressing mode is register indirect. No offsets or indexes allowed.<\/p>\n<p>After an exclusive load, you can attempt to store a value back to the same address:<\/p>\n<pre>    ; store exclusive register byte\r\n    stxrb   Rs\/zr, Rt\/zr, [Xn\/sp]\r\n\r\n    ; store exclusive register halfword\r\n    stxrh   Rs\/zr, Rt\/zr, [Xn\/sp]\r\n\r\n    ; store exclusive register\r\n    stxr    Rs\/zr, Rt\/zr, [Xn\/sp]\r\n\r\n    ; store exclusive register pair\r\n    stxp    Rs\/zr, Rt1\/zr, Rt2\/zr, [Xn\/sp]\r\n<\/pre>\n<p>If the reservation obtained by the previous <code>LDX<\/code> instruction is still valid, then the value in <var>Rt\/zr<\/var> is stored to memory, and <var>Rs<\/var> is set to 0. Otherwise, no store is performed, and <var>Rs<\/var> is set to 1.<\/p>\n<p>Whether the store succeeds or fails, the <code>STX<\/code> instructions clears the reservation.<\/p>\n<p>For these exclusive load and store instructions, the address must be a multiple of the number of bytes being loaded. If not, then the behavior is undefined: There is no requirement that an exception be raised.<\/p>\n<p>So don&#8217;t do that.<\/p>\n<p>It is also required that the <code>STX<\/code> match the <code>LDX<\/code> both in address and operand sizes. You cannot perform an <code>LDX<\/code> for one address and follow up with a <code>STX<\/code> to a different address. You also cannot perform a <code>LDXR<\/code> and follow up with a <code>STXRH<\/code> to the same address. You aren&#8217;t even allowed to do a <code>LDXP<\/code> with two 32-bit registers and follow up with a <code>STXR<\/code> with a single 64-bit register. Again, the behavior is undefined if you break this rule.<\/p>\n<p>The last instruction allows you to hit the reset button:<\/p>\n<pre>    ; clear exclusive\r\n    clrex\r\n<\/pre>\n<p>The <code>CLREX<\/code> discards any active reservation, and forces any subsequent <code>STX<\/code> to fail. This typically happens as part of interrupt handling or context switching to ensure that undefined behavior doesn&#8217;t occur if the thread was interrupted while it was in the middle of a <code>LDX<\/code>\/<code>STX<\/code> sequence.<\/p>\n<p>These instructions are usually coupled with memory barriers, which we&#8217;ll look at soon, but the next entry will be a little diversion.<\/p>\n<p><b>Bonus chatter<\/b>: There is an optional instruction set extension (mandatory starting in version 8.4) which includes a large set of atomic read-modify-write operations.<\/p>\n<pre>    ; atomic read-modify-write operation\r\n    ; Rt = previous value of [Xr]\r\n    ; [Xr] = Rt op Rs\r\n    ldadd   Rs\/zr, Rt\/zr, [Xr\/sp]       ; add\r\n    ldclr   Rs\/zr, Rt\/zr, [Xr\/sp]       ; and not\r\n    ldeor   Rs\/zr, Rt\/zr, [Xr\/sp]       ; exclusive or\r\n    ldset   Rs\/zr, Rt\/zr, [Xr\/sp]       ; or\r\n    ldsmax  Rs\/zr, Rt\/zr, [Xr\/sp]       ; signed maximum\r\n    ldsmin  Rs\/zr, Rt\/zr, [Xr\/sp]       ; signed minimum\r\n    ldumax  Rs\/zr, Rt\/zr, [Xr\/sp]       ; unsigned maximum\r\n    ldumin  Rs\/zr, Rt\/zr, [Xr\/sp]       ; unsigned minimum\r\n<\/pre>\n<p>By default, there is no memory ordering. You can add the suffix <code>a<\/code> to load with acquire, the suffix <code>l<\/code> to store with release, or the suffix <code>al<\/code> to get both. Note, however, that the acquire suffix is ignored if the destination register <var>Rt<\/var> is <var>zr<\/var>.<\/p>\n<p>Furthermore, you can suffix <code>b<\/code> for byte memory access or <code>h<\/code> for halfword memory access.<\/p>\n<p>The overall syntax is therefore<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Prefix<\/th>\n<th>Op<\/th>\n<th>Acquire<\/th>\n<th>Release<\/th>\n<th>Size<\/th>\n<\/tr>\n<tr>\n<td><code>ld<\/code><\/td>\n<td><code>add<\/code><br \/>\n<code>clr<\/code><br \/>\n<code>eor<\/code><br \/>\n<code>set<\/code><br \/>\n<code>smax<\/code><br \/>\n<code>smin<\/code><br \/>\n<code>umax<\/code><br \/>\n<code>umin<\/code><\/td>\n<td>(none)<br \/>\n<code>a<\/code><\/td>\n<td>(none)<br \/>\n<code>l<\/code><\/td>\n<td>(none)<br \/>\n<code>b<\/code><br \/>\n<code>h<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For example, the instruction <code>ldclrlh<\/code> means<\/p>\n<ul>\n<li><code>ld<\/code>: Atomic load\/modify\/store<\/li>\n<li><code>clr<\/code>: Clear bits<\/li>\n<li>(blank): No acquire on load<\/li>\n<li><code>l<\/code>: Release on store<\/li>\n<li><code>h<\/code>: Halfword size.<\/li>\n<\/ul>\n<p>If you don&#8217;t care about the previous value, then you can use a pseudo-instruction that uses <var>zr<\/var> as the destination.<\/p>\n<pre>    ; atomic read-modify-write operation\r\n    ; [Xr] = [Xr] op Rs\r\n    stadd   Rs\/zr, [Xr\/sp]       ; add\r\n    stclr   Rs\/zr, [Xr\/sp]       ; and not\r\n    steor   Rs\/zr, [Xr\/sp]       ; exclusive or\r\n    stset   Rs\/zr, [Xr\/sp]       ; or\r\n    stsmax  Rs\/zr, [Xr\/sp]       ; signed maximum\r\n    stsmin  Rs\/zr, [Xr\/sp]       ; signed minimum\r\n    stumax  Rs\/zr, [Xr\/sp]       ; unsigned maximum\r\n    stumin  Rs\/zr, [Xr\/sp]       ; unsigned minimum\r\n<\/pre>\n<p>You can add the <code>l<\/code> suffix for store with release, and you can add <code>b<\/code> and <code>h<\/code> suffixes to operate on smaller sizes. You cannot request acquire on load for these instructions because the acquire is ignored due to the destination being <var>zr<\/var>.<\/p>\n<p>The optional instruction set extension also provides for atomic exchanges:<\/p>\n<pre>    ; swap\r\n    ; write Rs and return previous value in Rt (atomic)\r\n    swp     Rs\/zr, Rt\/zr, [Xn\/sp]       ; word or doubleword\r\n    swpb    Ws\/zr, Wt\/zr, [Xn\/sp]       ; byte\r\n    swph    Ws\/zr, Wt\/zr, [Xn\/sp]       ; halfword\r\n\r\n    ; compare and swap\r\n    ; if value is Rs, then write Rt; Rs receives previous value\r\n    ; (atomic)\r\n    cas     Rs\/zr, Rt\/zr, [Xn\/sp]       ; word or doubleword\r\n    casb    Ws\/zr, Wt\/zr, [Xn\/sp]       ; byte\r\n    cash    Ws\/zr, Wt\/zr, [Xn\/sp]       ; halfword\r\n    casp    Rs\/zr, Rt\/zr, [Xn\/sp]       ; register pair\r\n                                        ; Rs,R(s+1) and Rt,R(t+1)\r\n\r\n    ; also a, l, and al versions for acquire\/release semantics\r\n<\/pre>\n<p>The memory order modifiers go between the <code>swp<\/code>\/<code>cas<\/code> prefix and the size suffix, <i>except<\/i> that they go after the <code>p<\/code>. So you have <code>casab<\/code> (compare and swap with acquire, byte size) but <code>caspa<\/code> (compare and swap pair with acquire).<\/p>\n<p>As with the <code>ld<\/code> instructions, requests to aquire on load are ignored if the destination register is <var>zr<\/var>.<\/p>\n<p>The memory operand must be writable, even if the comparison fails. If no value is stored, then any requested release semantics are ignored.<\/p>\n<p><b>Bonus reading<\/b>: <a href=\"https:\/\/cpufun.substack.com\/p\/atomics-in-aarch64\"> Atomics in AArch64<\/a>.<\/p>\n<p>\u00b9 The load is required to be fully atomic starting with version 8.4 of the AArch64. On older processors, Windows uses <code>CASP<\/code> instead of <code>LDXP<\/code>\/<code>STXP<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Don&#8217;t let someone else get a word in edgewise.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[2],"class_list":["post-106963","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-history"],"acf":[],"blog_post_summary":"<p>Don&#8217;t let someone else get a word in edgewise.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106963","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=106963"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106963\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=106963"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106963"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106963"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}