<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>RDMSR</title>
    <link rel="self" type="application/atom+xml" href="https://rdmsr.github.io/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://rdmsr.github.io"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-10-02T00:00:00+00:00</updated>
    <id>https://rdmsr.github.io/atom.xml</id>
    <entry xml:lang="en">
        <title>FreeBSD GUS</title>
        <published>2026-10-02T00:00:00+00:00</published>
        <updated>2026-10-02T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://rdmsr.github.io/writing/gus/"/>
        <id>https://rdmsr.github.io/writing/gus/</id>
        
        <content type="html" xml:base="https://rdmsr.github.io/writing/gus/">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;FreeBSD has a pretty neat safe memory reclamation scheme (something akin to RCU), that&#39;s also been adopted by XNU that hasn&#39;t really been well-documented outside of code comments, and I thought it would&#39;ve been interesting to write about it.&lt;/p&gt;
&lt;h2 id=&quot;what-safe-memory-reclamation-is-all-about&quot;&gt;What safe memory reclamation is all about&lt;/h2&gt;
&lt;p&gt;The problem of &lt;em&gt;safe memory reclamation&lt;/em&gt; (SMR) shows up notably when working with lock-free data structures. Lock-free data structures are the bread and butter of modern scalability in operating systems, and they often allow much higher throughput than their finely grained locked counterparts, but they come with their own issues.&lt;/p&gt;
&lt;p&gt;Lock-free data structure code in unmanaged languages needs to carefully manage the liveness of the objects it manages, i.e it must guarantee that memory is freed if and only if no one is able to access it. This arises because there is no synchronization around the access of the data structure, so an object that is removed can still be visible concurrently by another CPU (who might have made a local copy).&lt;/p&gt;
&lt;p&gt;More broadly, SMR schemes can also be used for other things than memory reclamation, I like to think of them as providing barriers ensuring that all participants (read: CPUs or threads) have passed through a certain point.&lt;/p&gt;
&lt;h2 id=&quot;prior-art&quot;&gt;Prior art&lt;/h2&gt;
&lt;p&gt;SMR schemes broadly fall in two families: &lt;em&gt;Epoch-based reclamation&lt;/em&gt; (EBR) and &lt;em&gt;Quiescent-state based reclamation&lt;/em&gt; (QSBR). You might have also heard of &lt;em&gt;Hazard Pointers&lt;/em&gt; (HP), which are useful on their own but are seldom seen in operating systems (at least, that I know of!).&lt;/p&gt;
&lt;p&gt;QSBR is notable because you might&#39;ve heard of it called a different name by the Linux project, &lt;em&gt;RCU&lt;/em&gt; (Read-Copy-Update)! RCU&#39;s author, Paul McKenney, actually invented QSBR for the DYNIX operating system.&lt;/p&gt;
&lt;p&gt;RCU has been described extensively, and I would recommend reading McKenney&#39;s excellent &lt;a rel=&quot;external&quot; href=&quot;https://lwn.net/Articles/262464/&quot;&gt;LWN Articles&lt;/a&gt; to learn more. The TL;DR is that there are three operations: &lt;code&gt;rcu_read_lock&lt;/code&gt;, &lt;code&gt;rcu_read_unlock&lt;/code&gt; and &lt;code&gt;rcu_synchronize&lt;/code&gt;, the &lt;code&gt;read&lt;/code&gt; operations are used to guard reads around potentially-freed memory (i.e to enter a &lt;em&gt;read section&lt;/em&gt;) and the &lt;code&gt;synchronize&lt;/code&gt; function waits until there is no CPU in a read section (this could be used to wait until memory is safe to be freed).&lt;/p&gt;
&lt;p&gt;The gist of the algorithm is that memory is known to be safe to free when all CPUs have gone through at least one &lt;em&gt;quiescent point&lt;/em&gt; ever since the object was removed from its data structure (a &lt;em&gt;grace period&lt;/em&gt;), and read section operations block those quiescent points from ever happening.&lt;/p&gt;
&lt;p&gt;In practice, this can be implemented by having a quiescent point on context switches and inhibiting preemption during read operations. The core of the algorithm is actually pretty simple, and much of the complexity of the Linux code stems from making this scalable (Tree RCU) and handling other RCU flavors (e.g preemptible RCU).&lt;/p&gt;
&lt;p&gt;FreeBSD&#39;s safe memory reclamation scheme is called &lt;em&gt;GUS&lt;/em&gt; and is part of the EBR family.&lt;/p&gt;
&lt;h2 id=&quot;epoch-based-reclamation&quot;&gt;Epoch-based reclamation&lt;/h2&gt;
&lt;p&gt;Epoch-based reclamation was first described by Keir Fraser in his now-famous thesis titled &lt;em&gt;Practical Lock-freedom&lt;/em&gt; (&lt;a rel=&quot;external&quot; href=&quot;https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf&quot;&gt;link&lt;/a&gt;). The way it works is that there is a global &lt;em&gt;epoch&lt;/em&gt; which moves through 0, 1 and 2. When a thread enters a read section, it pins itself by reading the global epoch and copying it into its local slot.&lt;/p&gt;
&lt;p&gt;When a writer retires an object, it goes into a &quot;limbo&quot; bag tagged with the current epoch (really, the epoch is used as an index into an array of three linked lists). The global epoch is advanced by &lt;em&gt;polling&lt;/em&gt; all other threads and checking whether or not they have acknowledged the current epoch by reading their local slot; if it so happens that they have, then the global epoch is advanced and memory that was retired two epochs ago can now be freed.&lt;/p&gt;
&lt;p&gt;The main issues that arise:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Epochs are &lt;em&gt;bounded&lt;/em&gt;, i.e they always cycle through 0, 1 and 2, this means that writers have to synchronize with epoch advancement before retiring more objects; and&lt;/li&gt;
&lt;li&gt;Doing an O(n) scan touching remote memory is very expensive!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It is now time to reveal that GUS really means &lt;em&gt;Global Unbounded Sequences&lt;/em&gt;, and it tackles on issue #1 by making epochs unbounded (hence the name) and issue #2 is somewhat mitigated by clever integration with the broader system.&lt;/p&gt;
&lt;h2 id=&quot;gus&quot;&gt;GUS&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I am using sequence and epoch interchangeably here. Confusing, I know!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The way GUS works is that there is a global, monotonically increasing write sequence, as well as a global read sequence, which represents the last minimum observed sequence across CPUs.&lt;/p&gt;
&lt;p&gt;Each CPU also keeps track of its own local sequence when it is pinned, akin to how EBR does things.&lt;/p&gt;
&lt;p&gt;The algorithm goes as follows, in pseudocode:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;enter():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    store(cpu.cur_seq, load(global_write_seq, relaxed), relaxed)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    fence()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;exit():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    store(cpu.cur_seq, SEQ_INVALID, release)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;advance():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    fetch_add(global_write_seq, 1, release)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;// Returns whether a sequence `goal` is reached&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;poll(goal):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    if goal &amp;lt;= global_read_seq:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        // Goal already reached!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        return true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    min_seq = global_read_seq&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    for each cpu:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        min_seq = min(min_seq, cpu.cur_seq)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    if goal &amp;lt;= min_seq:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        // Goal reached across all CPUs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Exchange global_read_seq with the new one.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        return true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    return false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The basic idea is therefore that a sequence is essentially a timestamp&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-1-1&quot;&gt;&lt;a href=&quot;https://rdmsr.github.io/writing/gus/#fn-1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, and that the read sequence is always playing catch-up with the write sequence, but never actually reaching it,
(i.e there is always at least a separation of 1 between both). Obviously, in practice, there is a bit more to it especially w.r.t memory orderings, but the whole thing is still freakishingly simple
and can be implemented in a few hundred lines, see the FreeBSD implementation &lt;a rel=&quot;external&quot; href=&quot;https://elixir.bootlin.com/freebsd/v15.1/source/sys/kern/subr_smr.c&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The unbounded epochs solved the issue #1 that was presented earlier, but what about issue #2: how is polling amortized?&lt;/p&gt;
&lt;p&gt;Jeff Roberson, the author of GUS, also wrote the UMA (universal memory allocator) implementation, and both tie nicely together.&lt;/p&gt;
&lt;h2 id=&quot;integration-with-the-rest-of-the-system&quot;&gt;Integration with the rest of the system&lt;/h2&gt;
&lt;p&gt;To me, the way GUS is integrated with the allocator is really its distinguishing feature and what makes the whole thing work well.&lt;/p&gt;
&lt;p&gt;The UMA allocator is essentially a modernized slab allocator, which was first implemented in Solaris and described in Jeff Bonwick&#39;s influential papers&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-2-1&quot;&gt;&lt;a href=&quot;https://rdmsr.github.io/writing/gus/#fn-2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-3-1&quot;&gt;&lt;a href=&quot;https://rdmsr.github.io/writing/gus/#fn-3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; in the early 2000s.
To make things scale on SMP systems, it relies on per-CPU &quot;buckets&quot; (magazines in Bonwickspeak), which are essentially an array of &lt;em&gt;N&lt;/em&gt; objects on which objects are popped from and pushed onto quickly.
A certain amount of pre-allocated buckets are stored in a per-zone (&lt;code&gt;kmem_cache&lt;/code&gt; in Bonwickspeak) &quot;depot&quot; from which empty buckets and full buckets are pulled. &lt;em&gt;N&lt;/em&gt;, i.e the size of buckets, is dynamically resized
according to measured contention on the depot lock; high contention needs to be reduced by increasing the bucket size,
which provides amortization over the (potentially) slow and contended operation of going to the depot.&lt;/p&gt;
&lt;p&gt;GUS and the allocator join together by allowing the creation of &quot;SMR zones&quot; which are zones that do careful handling around the freeing of objects.
Someone wanting to write a lock-free data structure could create a SMR zone for their object and do allocations and frees effectively transparently.&lt;/p&gt;
&lt;p&gt;On SMR zones, when the allocator notices a bucket is completely free (i.e full of objects), it advances the write sequence and stamps the bucket with it,
then puts it back at the end of the depot free list.&lt;/p&gt;
&lt;p&gt;When the allocator then needs to get a free bucket from the depot, it gets the bucket from the head of the free list,
and &lt;strong&gt;then&lt;/strong&gt; polls other CPUs for the sequence that is stamped on that bucket. If the goal has already been reached, then the bucket can be reused, otherwise,
the allocator will fallback on allocating another bucket itself.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;/zone-depot.svg&quot; alt=&quot;&quot; width=&quot;100%&quot;&gt;
  &lt;figcaption&gt;Diagram of the zone depot&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;What&#39;s cool about this is that buckets naturally provide amortization for those SMR operations;
polling is done on a batch of objects at once, and not on a per-object basis (which avoids starting one write sequence per object) and growing the buckets lengthens the time spent before touching the depot again, which gives the time for other buckets to age.&lt;/p&gt;
&lt;p&gt;Remember that the GUS algorithm relies on finding the lowest sequence across all CPUs, which means that oftentimes, if buckets are aged as expected,
then a single poll operation could allow the reuse of many buckets at the same time, e.g if the depot free list contains buckets stamped with &lt;code&gt;100, 101, 102&lt;/code&gt; and we notice via polling that the new minimum sequence is &lt;code&gt;103&lt;/code&gt;,
then all of those buckets can be reused immediately, which avoids having to do future polls for those buckets.&lt;/p&gt;
&lt;p&gt;That is what makes everything click together and the polling not so bad anymore!&lt;/p&gt;
&lt;h2 id=&quot;xnu&quot;&gt;XNU&lt;/h2&gt;
&lt;p&gt;Apple&#39;s XNU has also adopted a very similar scheme but with many goodies on top. For instance, they support preemptible read sections and a &lt;code&gt;call_rcu&lt;/code&gt;-style API.
The way it integrates with their zone allocator, however, is quite interesting and its implementation differs enough that I deem it worthy to be described here as well.&lt;/p&gt;
&lt;p&gt;XNU&#39;s zone allocator (&lt;code&gt;zalloc&lt;/code&gt;) is, like UMA, also essentially a modernized slab allocator;
I will not try to explain it in depth here because it is quite sophisticated (over 10k lines!), and I am keeping this in reserve for another post, but I will go over the details of how their buckets are reused.&lt;/p&gt;
&lt;p&gt;What&#39;s interesting is that zalloc does not rely on resizing buckets at all, instead, buckets are kept at a fixed size (something like 8 objects),
and a per-CPU depot is installed on top of the per-zone depot, which they call the &quot;recirculation depot&quot; (this should make sense later why such a name was used).
This per-CPU depot is then resized with an algorithm similar to the one used to resize buckets, providing the same kind of amortization.&lt;/p&gt;
&lt;p&gt;When a bucket becomes full and ready to be recycled, it is stamped with a &lt;em&gt;deferred&lt;/em&gt; advance of the write sequence and put onto the per-CPU depot.
When allocation is in need of a bucket, it polls the head of the per-CPU depot, and grabs it if it has expired. Otherwise, it does four things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Append all the buckets from the per-CPU depot to the zone depot&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since the head of the per-CPU depot hasn&#39;t expired yet, this means that all buckets that sit in the per-CPU depot are not yet ready.
All buckets are therefore put at the &lt;em&gt;end&lt;/em&gt; of the zone depot, as to let them age behind the buckets that were already in depot.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;Commit the sequence of the last bucket in the zone depot&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The last bucket in the depot is the newest, its sequence is taken and the global write sequence is advanced to that one immediately,
this allows to do one write sequence write for many buckets.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;Pull back a bunch of buckets from the head of zone depot into the per-CPU depot&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A batch of buckets is then pulled from the head of the zone depot, these are the oldest buckets, so they are more likely to have expired.
The batching here avoids having to constantly thrash on the depot and avoids contention on it.&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;strong&gt;Retry to allocate from per-CPU depot&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Finally, the oldest bucket we just freshly pulled from the zone depot is polled again, if it still hasn&#39;t expired, then
there is no choice but to fallback on allocating new memory.&lt;/p&gt;
&lt;p&gt;What&#39;s all this jazz about deferring and committing? I like to think of a deferred advance as a &quot;fake advance&quot;, it snapshots the current sequence but does not increment it,
which avoids a global read-modify-write (RMW) operation on the global write sequence. The key idea is that there is no need to advance the sequence, i.e commit it, until we actually need to, and
this also serves as an amortization feature because a sequence can be jumped to instantly, for instance, from 1 to 5 instead of individually advancing through 1, 2, 3, 4, and 5.&lt;/p&gt;
&lt;p&gt;The recirculation depot is hence called as such because it allows a given bucket to &lt;em&gt;recirculate&lt;/em&gt; and age before given the chance to be reused again.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;/recirc-depot.svg&quot; alt=&quot;&quot; width=&quot;100%&quot;&gt;
  &lt;figcaption&gt;Diagram of the recirculation depot&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;pros-cons&quot;&gt;Pros &amp;amp; Cons&lt;/h2&gt;
&lt;p&gt;This SMR scheme, like anything else, has its own tradeoffs.
It is relevant to compare it with RCU because both are analogous, compared to RCU:&lt;/p&gt;
&lt;p&gt;Pros:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only CPUs actively in a read-section need to participate during polling.&lt;/li&gt;
&lt;li&gt;Simple implementation&lt;/li&gt;
&lt;li&gt;Lower free-to-reuse latency&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-4-1&quot;&gt;&lt;a href=&quot;https://rdmsr.github.io/writing/gus/#fn-4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Even though polling is amortized, it is still an expensive operation, and might even be a no-go on large NUMA systems.&lt;/li&gt;
&lt;li&gt;Higher overhead on entering read sections (needs a fence)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;p&gt;The best resource, as always, is reading the actual code, which is thankfully very well commented (&lt;a rel=&quot;external&quot; href=&quot;https://elixir.bootlin.com/freebsd/v15.1/source/sys/kern/subr_smr.c&quot;&gt;GUS link&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://elixir.bootlin.com/freebsd/v15.1/source/sys/vm/uma_core.c&quot;&gt;UMA link&lt;/a&gt;).
The XNU code is also heavily commented (&lt;a rel=&quot;external&quot; href=&quot;https://github.com/apple-oss-distributions/xnu/blob/main/osfmk/kern/smr.c&quot;&gt;SMR link&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://github.com/apple-oss-distributions/xnu/blob/main/osfmk/kern/zalloc.c&quot;&gt;zalloc link&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Additionally, these are useful resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jeff Roberson has made a short &lt;a rel=&quot;external&quot; href=&quot;https://www.youtube.com/watch?v=ZXUIFj4nRjk&quot;&gt;talk&lt;/a&gt; about GUS and UMA.&lt;/li&gt;
&lt;li&gt;Comparison with RCU by &lt;a rel=&quot;external&quot; href=&quot;https://people.kernel.org/joelfernandes/gus-vs-rcu&quot;&gt;Joel Fernandes&lt;/a&gt;, a Linux developer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have also implemented GUS and an allocator inspired by XNU&#39;s and UMA in my own operating system, zag (&lt;a rel=&quot;external&quot; href=&quot;https://github.com/rdmsr/zag/blob/master/src/kern/smr.zig&quot;&gt;SMR link&lt;/a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https://github.com/rdmsr/zag/blob/master/src/mm/zone.zig&quot;&gt;allocator link&lt;/a&gt;)&lt;/p&gt;
&lt;h2 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h2&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;In fact, one could use a timestamp counter such as the TSC for this, as seen in &lt;a rel=&quot;external&quot; href=&quot;https://faculty.cs.gwu.edu/gparmer/publications/eurosys16ps.pdf&quot;&gt;&lt;em&gt;Parallel Sequences&lt;/em&gt;&lt;/a&gt;. &lt;a href=&quot;https://rdmsr.github.io/writing/gus/#fr-1-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://people.eecs.berkeley.edu/~kubitron/courses/cs194-24-S14/hand-outs/bonwick_slab.pdf&quot;&gt;&lt;em&gt;The Slab Allocator: An Object-Caching Kernel Memory Allocator&lt;/em&gt;&lt;/a&gt; &lt;a href=&quot;https://rdmsr.github.io/writing/gus/#fr-2-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-3&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;http://www.parrot.org/sites/www.parrot.org/files/vmem.pdf&quot;&gt;&lt;em&gt;Magazines and Vmem: Extending the Slab Allocator to Many CPUs and Arbitrary Resources&lt;/em&gt;&lt;/a&gt; &lt;a href=&quot;https://rdmsr.github.io/writing/gus/#fr-3-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-4&quot;&gt;
&lt;p&gt;This was actually measured by the FreeBSD people, see the talk I mentioned above. &lt;a href=&quot;https://rdmsr.github.io/writing/gus/#fr-4-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Poor man&#39;s traits in Zig</title>
        <published>2026-09-09T00:00:00+00:00</published>
        <updated>2026-09-09T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://rdmsr.github.io/writing/zig-traits/"/>
        <id>https://rdmsr.github.io/writing/zig-traits/</id>
        
        <content type="html" xml:base="https://rdmsr.github.io/writing/zig-traits/">&lt;p&gt;Zig is a pretty nice programming language, but it unfortunately does not support compiler-enforced interfaces or traits. Instead, the language relies on duck typing to operate on generic types.&lt;/p&gt;
&lt;p&gt;Luckily, Zig supports static reflection and compile-time code evaluation, so it is quite easy to implement &quot;Poor man&#39;s traits&quot;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;/// Asserts that a given type `T` matches the schema declared by `I`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;/// This includes public methods and fields.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; fn&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; assert_interface&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; I&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; type&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; tinfo&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeInfo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    inline for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;tinfo&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;@&amp;quot;struct&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fields&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;|&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@hasField&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; f&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;            @compileError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;comptimePrint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt;&amp;quot;Expected field &amp;#39;{s}&amp;#39; of type &amp;#39;{s}&amp;#39; in type &amp;#39;{s}&amp;#39; required by &amp;#39;{s}&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;, .&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;type&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;) }))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@FieldType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; f&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; f&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;type&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;                @compileError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;comptimePrint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt;&amp;quot;Expected field &amp;#39;{s}&amp;#39; of type &amp;#39;{s}&amp;#39; in type &amp;#39;{s}&amp;#39; required by &amp;#39;{s}&amp;#39;, got &amp;#39;{s}&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;, .&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;type&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@FieldType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; f&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span&gt;)) }))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    inline for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;tinfo&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;@&amp;quot;struct&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;decls&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;decl&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;|&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;        const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; member&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@typeInfo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@TypeOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;member&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; .&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;@&amp;quot;fn&amp;quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@hasDecl&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;                @compileError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;comptimePrint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt;&amp;quot;Expected method &amp;#39;{s}&amp;#39; in type &amp;#39;{s}&amp;#39; required by &amp;#39;{s}&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;, .&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;) }))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; impl_member&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; IfaceFnType&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @TypeOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;member&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; ImplFnType&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @TypeOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;impl_member&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;IfaceFnType&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; ImplFnType&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;                const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; iface_fn&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeInfo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;IfaceFnType&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;@&amp;quot;fn&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;                const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; impl_fn&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeInfo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;ImplFnType&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;@&amp;quot;fn&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;                if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;iface_fn&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; impl_fn&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;len&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;                    @compileError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;comptimePrint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt;&amp;quot;Parameter count mismatch in method &amp;#39;{s}&amp;#39; in type &amp;#39;{s}&amp;#39; required by &amp;#39;{s}&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;, .&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;) }))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;                if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;iface_fn&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;return_type&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; impl_fn&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;return_type&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;                    @compileError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;comptimePrint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt;&amp;quot;Return type mismatch in method &amp;#39;{s}&amp;#39; in type &amp;#39;{s}&amp;#39; required by &amp;#39;{s}&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;, .&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;) }))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;                for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;iface_fn&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;len&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;|&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;                    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;iface_fn&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;params&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;i&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; impl_fn&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;params&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;i&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;type&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;                        @compileError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;comptimePrint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt;&amp;quot;Parameter type mismatch in method &amp;#39;{s}&amp;#39; in type &amp;#39;{s}&amp;#39; required by &amp;#39;{s}&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;, .&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;) }))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;else if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@TypeOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;member&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; type&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@hasDecl&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;                @compileError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;comptimePrint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt;&amp;quot;Expected type declaration &amp;#39;{s}&amp;#39; in type &amp;#39;{s}&amp;#39; required by &amp;#39;{s}&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;, .&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;) }))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; impl_member&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@TypeOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;impl_member&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; type&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;                @compileError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;comptimePrint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt;&amp;quot;Declaration &amp;#39;{s}&amp;#39; in type &amp;#39;{s}&amp;#39; is required to be a type by &amp;#39;{s}&amp;#39;, but got &amp;#39;{s}&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;, .&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@TypeOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;impl_member&lt;/span&gt;&lt;span&gt;)) }))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@hasDecl&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;                @compileError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;comptimePrint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt;&amp;quot;Expected variable declaration &amp;#39;{s}&amp;#39; in type &amp;#39;{s}&amp;#39; required by &amp;#39;{s}&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;, .&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;) }))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; impl_member&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@TypeOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;impl_member&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @TypeOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;member&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-13&quot;&gt;                @compileError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;comptimePrint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt;&amp;quot;Declaration &amp;#39;{s}&amp;#39; in type &amp;#39;{s}&amp;#39; is required to be a variable of type &amp;#39;{s}&amp;#39; by &amp;#39;{s}&amp;#39;, but got &amp;#39;{s}&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;, .&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;decl&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@TypeOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;member&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;I&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt; @typeName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-13&quot;&gt;@TypeOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;impl_member&lt;/span&gt;&lt;span&gt;)) }))&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would then be used by declaring a &quot;schema&quot; and asserting against it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;zig&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; AnimalSchema&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    pub const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; sound&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;:&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;const u8&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt; &amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    pub&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; fn&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; pet&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;void&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; Dog&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    pub const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; sound&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;:&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;const u8&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt; &amp;quot;bark&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    pub&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; fn&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; pet&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;        // Do something&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; Cat&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    pub const&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; sound&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;:&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;const u8&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-23&quot;&gt; &amp;quot;meow&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    pub&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; fn&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; pet&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;        // Do something&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; pet_animal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;comptime&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; type&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;    assert_interface&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;AnimalSchema&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    T&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;pet&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;pet_animal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;Dog&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;pet_animal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;Cat&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Solaris Turnstiles</title>
        <published>2026-09-08T00:00:00+00:00</published>
        <updated>2026-09-08T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://rdmsr.github.io/writing/turnstiles/"/>
        <id>https://rdmsr.github.io/writing/turnstiles/</id>
        
        <content type="html" xml:base="https://rdmsr.github.io/writing/turnstiles/">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Sun microsystems&#39; Solaris was once widely regarded as having some of the best symmetric multiprocessing (SMP) support among the operating systems of its time.
Much of this technical strength came from innovations developed within the project.&lt;/p&gt;
&lt;p&gt;Although Solaris is now mostly defunct &lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-1-1&quot;&gt;&lt;a href=&quot;https://rdmsr.github.io/writing/turnstiles/#fn-1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, its influence remains substantial; technologies pioneered by Solaris can still be found across a &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/ZFS&quot;&gt;wide&lt;/a&gt; &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Slab_allocation&quot;&gt;range&lt;/a&gt; &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Solaris_Containers&quot;&gt;of&lt;/a&gt; &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/DTrace&quot;&gt;software&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A lot of Solaris&#39; inventions have been described and talked about &lt;em&gt;ad nauseam&lt;/em&gt; (such as the Slab Allocator), but one I rarely see discussed is its use of &lt;strong&gt;turnstiles&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Despite being relatively obscure, the idea has quietly spread far beyond Solaris. Variations of it can now be found in major operating systems, web browsers, and language runtimes. In fact, you’re probably using several implementations of the same basic concept right now!&lt;/p&gt;
&lt;h2 id=&quot;why-turnstiles-exist&quot;&gt;Why turnstiles exist&lt;/h2&gt;
&lt;p&gt;Before going in depth on turnstiles, let&#39;s take a step back and figure out what problems Sun engineers were trying to solve.&lt;/p&gt;
&lt;p&gt;One of the key design hallmarks of Solaris was that it made heavy use of blocking mutexes (or locks), in part because they could provide much better latency for high-priority tasks, which is important if you wanted something approaching soft real-time behavior.&lt;/p&gt;
&lt;p&gt;The catch was that blocking mutexes came with problems of their own, two of which turnstiles were designed to address.&lt;/p&gt;
&lt;h3 id=&quot;mutexes-are-big&quot;&gt;Mutexes are big&lt;/h3&gt;
&lt;p&gt;A blocking mutex needs more than just a single bit saying whether it’s locked. Somewhere, the kernel also needs to keep track of things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Who currently owns the lock;&lt;/li&gt;
&lt;li&gt;which threads are waiting for it; and&lt;/li&gt;
&lt;li&gt;The state needed to coordinate blocking and waking those threads.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You could store all of this directly in every mutex, but that gets expensive pretty quickly.&lt;/p&gt;
&lt;p&gt;Highly scalable software tends to rely on fine-grained locking, i.e having lots of locks protecting relatively small pieces of state. The problem is that most of those locks will be uncontended most of the time, so dedicating a large amount of bookkeeping to every single one is mostly wasted space.&lt;/p&gt;
&lt;p&gt;Keeping mutexes small makes fine-grained locking much cheaper. If adding another lock only costs a handful of bytes, developers can afford to use more of them instead of combining unrelated state behind a smaller number of coarse-grained locks.&lt;/p&gt;
&lt;h3 id=&quot;priority-inversion&quot;&gt;Priority Inversion&lt;/h3&gt;
&lt;p&gt;Another significant problem that arises with the heavy use of blocking locks is &lt;em&gt;priority inversion&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Typically, when a thread acquires a spinlock, it disables preemption (via mechanisms like &lt;code&gt;spl&lt;/code&gt;, &lt;code&gt;Irql&lt;/code&gt; or &lt;code&gt;preempt_disable&lt;/code&gt;).
Disabling preemption effectively puts the thread at the highest priority on the system, blocking any other task that might want to interrupt its work from doing so until the spinlock is released.&lt;/p&gt;
&lt;p&gt;This is great for throughput, but latency can suffer if preemption stays disabled for too long.&lt;/p&gt;
&lt;p&gt;Blocking locks, on the other hand, do not disable preemption, which allows for better latency behavior; high priority tasks can interrupt other lower priority tasks. This can also reduce throughput however, so there is no single best solution (though this can partly be worked around through the use of &lt;em&gt;adaptive spinning&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;Since threads keep their priority when holding a lock, an especially ugly situation can occur: What if high priority task A tries to acquire a lock currently held by low priority task B?&lt;/p&gt;
&lt;p&gt;A blocks, waiting for B to release the lock. So far, so good. The problem is that B is still a low-priority task. Any medium-priority task that becomes runnable can preempt B, preventing it from making progress and releasing the lock.&lt;/p&gt;
&lt;p&gt;In effect, our high-priority task is now stuck waiting behind work that should never have been able to delay it in the first place. This is called &lt;em&gt;priority inversion&lt;/em&gt;, and in the worst case it can delay A for an unbounded amount of time.&lt;/p&gt;
&lt;p&gt;So much for those latency guarantees!&lt;/p&gt;
&lt;h3 id=&quot;priority-inheritance&quot;&gt;Priority inheritance&lt;/h3&gt;
&lt;p&gt;Luckily, a bunch of smart people figured out a neat solution to this: &lt;strong&gt;priority inheritance&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In the same situation, B would temporarily &lt;em&gt;inherit&lt;/em&gt; A&#39;s priority until it finishes its critical section, allowing it to run ahead of any medium-priority task that might otherwise get into its way.&lt;/p&gt;
&lt;p&gt;At first glance, this seems pretty simple: when a high-priority thread blocks on a lock, boost the priority of whoever owns it. Things get more interesting, though, once locks start depending on other locks.&lt;/p&gt;
&lt;p&gt;Consider the following scenario: A waits on B, but B itself is waiting on C. A could propagate its priority to B, but it would still have to wait for a potentially lower-priority C to release the lock. To correct this, A&#39;s priority needs to be propagated through the &lt;em&gt;owner chain&lt;/em&gt; until it reaches C. This is called &lt;em&gt;multi-hop&lt;/em&gt; priority inheritance.&lt;/p&gt;
&lt;p&gt;What gets tricky is keeping track of these chains efficiently in the kernel.&lt;/p&gt;
&lt;p&gt;Different operating systems have come up with different machinery for keeping track of these dependency chains.
Turnstiles are the mechanism Solaris and a bunch of UNIX-derived operating systems use to do exactly that.&lt;/p&gt;
&lt;p&gt;A few other notable approaches are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AutoBoost on Windows; and&lt;/li&gt;
&lt;li&gt;Linux’s rt-mutex priority-inheritance machinery.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;turnstiles&quot;&gt;Turnstiles&lt;/h2&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢀⣴⣿⣧⣤⣤⣤⣤⣼⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢸⣿⣿⣿⣿⣿⣿⣿⡿⢋⣴⣿⠀⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⡄⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢸⣿⣿⣿⣿⣿⣿⠋⠰⠿⠟⠃⣀⣉⡉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢸⣿⣿⣿⣿⣿⣿⠀⠀⠰⣦⡄⠉⠻⢿⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢸⣿⣿⣿⣿⣿⣿⠀⠀⠀⢻⣿⡄⠀⠀⠈⠙⠻⣿⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢸⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⢿⣷⡀⠀⠀⠀⠀⠀⠙⠻⢿⣷⣦⣄⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢸⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠈⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢸⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠘⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢸⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠸⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢸⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠹⡿⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⢸⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;⠀⠈⠉⠉⠉⠉⠉⠉⠀⠀&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At a high level, a turnstile is a data structure associated with a contended lock. It keeps track of the threads waiting on that lock, along with the information needed to propagate priority through the lock’s owner chain.&lt;/p&gt;
&lt;p&gt;The clever part is that this state doesn’t have to live inside the lock itself.&lt;/p&gt;
&lt;p&gt;Instead, each thread gets its own turnstile when it is created, in case it might contend on a blocking lock. This hinges on the fact that a thread can only be blocked on one lock at a time.&lt;/p&gt;
&lt;p&gt;When a thread does block, it effectively donates its turnstile to the lock it is waiting on. That turnstile then becomes the place where the kernel keeps the lock’s waiters and priority-inheritance state.&lt;/p&gt;
&lt;p&gt;This raises an obvious question: if the turnstile doesn’t live inside the lock, how does the kernel find it?&lt;/p&gt;
&lt;p&gt;The answer is a hash table. The lock&#39;s address is hashed and is used to locate the turnstile currently associated with that lock. Keep in mind that the diagram below logically shows the hash table as having one turnstile per hash bucket, but in practice each bucket contains a chain of them: finding the appropriate turnstile then requires iterating over that bucket&#39;s chain to find the turnstile that points to the desired lock.&lt;/p&gt;
&lt;p&gt;When a thread blocks on an already contended lock, it donates its turnstile onto a per-turnstile free list.
When the lock is finally released, each thread simply grabs a turnstile from the freelist, it does not matter which; a turnstile is not bound to a specific thread.&lt;/p&gt;
&lt;p&gt;Each thread keeps track of the turnstile it is currently blocked on, and each turnstile knows the current owner of the lock.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;/turnstile.svg&quot; alt=&quot;&quot; width=&quot;100%&quot;&gt;
  &lt;figcaption&gt;Turnstile diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;owner-chains&quot;&gt;Owner chains&lt;/h3&gt;
&lt;p&gt;To do &lt;em&gt;multi-hop&lt;/em&gt; priority inheritance, the priority boosting code needs to follow each successive lock dependency. Conceptually, this amounts to walking a chain that looks like:
&lt;code&gt;turnstile-&amp;gt;owner-&amp;gt;turnstile-&amp;gt;owner...&lt;/code&gt; until it reaches an owner that is not blocked on a synchronization object (this could be kept track by a &lt;code&gt;waiting_on&lt;/code&gt; field in the thread structure).&lt;/p&gt;
&lt;p&gt;The locking required to do this actually gets quite nasty, so I advise you read the comments in the &lt;a rel=&quot;external&quot; href=&quot;https://github.com/illumos/illumos-gate/blob/master/usr/src/uts/common/os/turnstile.c&quot;&gt;Illumos implementation&lt;/a&gt;&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-2-1&quot;&gt;&lt;a href=&quot;https://rdmsr.github.io/writing/turnstiles/#fn-2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; if you&#39;re hungry for more details. The gist of it is that there is a &lt;code&gt;bucket lock -&amp;gt; thread lock&lt;/code&gt; locking hierarchy, and the successive bucket locks are trylocked. If the acquisition fails then everything is dropped and tried again as to avoid a deadlock between two CPUs concurrently doing a PI walk.&lt;/p&gt;
&lt;h3 id=&quot;limitations&quot;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;Keen observers may have noticed a glaring limitation with this scheme: a turnstile has a single inheritor. So what about locks with multiple owners, such as reader-writer locks?&lt;/p&gt;
&lt;p&gt;This is where things get awkward. If a writer is blocked behind several readers, there is no single owner for it to donate its priority to.&lt;/p&gt;
&lt;p&gt;Instead, kernels that use turnstiles rely on heuristics or &quot;good enough&quot; solutions, such as picking the first thread that acquired the lock as the inheritor, or straight up giving up on priority inheritance for multi-owner locks altogether.
Another possible solution is simply boosting a thread&#39;s priority to some given ceiling before taking a shared lock.&lt;/p&gt;
&lt;p&gt;I believe that the only priority inheritance scheme that &lt;em&gt;does&lt;/em&gt; support multiple inheritors is NT&#39;s AutoBoost, but its internals are not widely documented.&lt;/p&gt;
&lt;h2 id=&quot;influence&quot;&gt;Influence&lt;/h2&gt;
&lt;p&gt;Many UNIX-derived operating systems started taking (copying) ideas from Solaris once they moved towards SMP support. As such, turnstiles are now found in many kernels of the same family.&lt;/p&gt;
&lt;p&gt;Turnstiles are currently used in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Solaris, where they originated;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/illumos/illumos-gate/blob/master/usr/src/uts/common/os/turnstile.c&quot;&gt;Illumos&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;macOS&#39;s kernel, &lt;a rel=&quot;external&quot; href=&quot;https://github.com/apple-oss-distributions/xnu/blob/main/osfmk/kern/turnstile.c&quot;&gt;XNU&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/freebsd/freebsd-src/blob/main/sys/kern/subr_turnstile.c&quot;&gt;FreeBSD&lt;/a&gt;; and&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/NetBSD/src/blob/trunk/sys/kern/kern_turnstile.c&quot;&gt;NetBSD&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, some hobby OSes have adopted them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My own, &lt;a rel=&quot;external&quot; href=&quot;https://github.com/rdmsr/zag/blob/master/src/kern/turnstile.zig&quot;&gt;zag&lt;/a&gt;&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-3-1&quot;&gt;&lt;a href=&quot;https://rdmsr.github.io/writing/turnstiles/#fn-3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/xrarch/mintia2/blob/main/OS/Executive/Ke/KeTurnstile.jkl&quot;&gt;MINTIA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/Keyronex/Keyronex/blob/master/kernel/common/kern/turnstil.c&quot;&gt;Keyronex&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;outside-of-operating-systems&quot;&gt;Outside of operating systems&lt;/h3&gt;
&lt;p&gt;Interestingly, the same basic idea shows up outside of operating-system kernels as well. A few notable examples are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/golang/go/blob/master/src/runtime/sema.go&quot;&gt;Go&#39;s runtime semaphores&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://webkit.org/blog/6161/locking-in-webkit/&quot;&gt;WTF::ParkingLot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;WTF::ParkingLot&lt;/code&gt; is particularly interesting because it is widely used and looks remarkably similar to the space-saving half of turnstiles.&lt;/p&gt;
&lt;p&gt;Just like a turnstile, a parking lot keeps the expensive waiter machinery outside of the lock itself. When a thread needs to block, the address of the lock is used to find the corresponding wait queue in a global table.&lt;/p&gt;
&lt;p&gt;The important difference is that a parking lot is primarily concerned with putting threads to sleep and waking them back up; it does not provide the priority-inheritance machinery that made Solaris turnstiles special, as userspace does not need to concern itself with managing priority inheritance, and instead lets the kernel do it through mechanisms like PI-futex on Linux.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;p&gt;Turnstiles are well described in the following books:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;The Design and Implementation of the FreeBSD Operating System&lt;/em&gt;; and&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Solaris Internals&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, they were described in a Solaris magazine &lt;a rel=&quot;external&quot; href=&quot;http://sunsite.uakom.sk/sunworldonline/swol-08-1999/swol-08-insidesolaris.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h2&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;It survives to some extent through &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Illumos&quot;&gt;Illumos&lt;/a&gt; &lt;a href=&quot;https://rdmsr.github.io/writing/turnstiles/#fr-1-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;
&lt;p&gt;See &lt;code&gt;turnstile_interlock()&lt;/code&gt;. &lt;a href=&quot;https://rdmsr.github.io/writing/turnstiles/#fr-2-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-3&quot;&gt;
&lt;p&gt;I actually did manage to do multi-owner PI for a specific case of SMR read-sections, AFAIK this is novel! &lt;a href=&quot;https://rdmsr.github.io/writing/turnstiles/#fr-3-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Linux qspinlocks</title>
        <published>2026-03-17T00:00:00+00:00</published>
        <updated>2026-03-17T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://rdmsr.github.io/writing/qspinlocks/"/>
        <id>https://rdmsr.github.io/writing/qspinlocks/</id>
        
        <content type="html" xml:base="https://rdmsr.github.io/writing/qspinlocks/">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The Linux kernel has a variety of synchronization mechanisms, each suited to a different context and set of constraints.
The simplest one of them all is the spinlock; spinlocks are used everywhere short, fast mutual exclusion is needed and blocking is either too costly or outright forbidden, such as in interrupt handlers and scheduler code.
Today, Linux spinlocks are implemented through a mechanism called &lt;code&gt;qspinlock&lt;/code&gt;.
This article traces the evolution of spinlock designs that led to &lt;code&gt;qspinlock&lt;/code&gt;, and presents a sample userspace C++ implementation along with benchmarks.&lt;/p&gt;
&lt;h2 id=&quot;spinlocks&quot;&gt;Spinlocks&lt;/h2&gt;
&lt;p&gt;Spinlocks are the simplest synchronization primitive to implement; as their name indicates, they spin on the lock until it is released.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://media.tenor.com/4L1o9fDxI4wAAAAM/omg-why.gif&quot; alt=&quot;&quot; width=&quot;300&quot;&gt;
  &lt;figcaption&gt;Artist&#39;s impression of a CPU spinning on a lock&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Here is a sample implementation using the Test-and-Test-And-Set (TTAS) pattern, which is generally considered to be the most efficient:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;struct&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; SpinLock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;  std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;atomic&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  void&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; lock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    for&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (;;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;      uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; expected&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;      if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;compare_exchange_weak&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_acquire&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;                                  std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_relaxed&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;        return&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;      while&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_relaxed&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;        cpu_relax&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  void&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; unlock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;() {&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_release&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;); }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is important to note that spinlocks are &lt;strong&gt;inherently unfair&lt;/strong&gt;: when the lock is released, all waiting CPUs race for it simultaneously, and there is no guarantee that the longest-waiting CPU will win. A CPU that is topologically closer to the releasing CPU, such as one sharing an L3 cache or sitting on the same NUMA node, will tend to win repeatedly, potentially starving remote CPUs indefinitely under sustained contention.&lt;/p&gt;
&lt;h3 id=&quot;ticket-spinlocks&quot;&gt;Ticket spinlocks&lt;/h3&gt;
&lt;p&gt;One way to implement fairness in spinlocks is using &lt;em&gt;Ticket spinlocks&lt;/em&gt;, such locks work similarly to a deli counter: each waiter draws a ticket and is served strictly in order, providing a First-In-First-Out (FIFO) ordering.&lt;/p&gt;
&lt;p&gt;These locks have the advantage that they provide a good bang for the buck: they are simple to implement, performant and provide guaranteed fairness.&lt;/p&gt;
&lt;p&gt;Here is a sample implementation:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;struct&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; TicketLock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;  std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;atomic&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; next&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;  std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;atomic&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; owner&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  void&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; lock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; my&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; next&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;fetch_add&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_relaxed&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    while&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_acquire&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; my&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;      cpu_relax&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  void&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; unlock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;() {&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; owner&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;fetch_add&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_release&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;); }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before moving to &lt;code&gt;qspinlock&lt;/code&gt;, Linux used something similar for its locks, albeit fitting them in a single 32-bit value.&lt;/p&gt;
&lt;h2 id=&quot;qspinlock&quot;&gt;qspinlock&lt;/h2&gt;
&lt;h3 id=&quot;motivations&quot;&gt;Motivations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;If ticket spinlocks are so great, why did Linux switch away from them?&lt;/strong&gt;
The answer to this question lies in scalability: when a large number of cores contend on the same lock, this causes &lt;em&gt;cache line bouncing&lt;/em&gt;, where the cache line of the lock is repeatedly transferred between CPUs, and each unlock invalidates every waiting CPU&#39;s cached copy simultaneously. With &lt;em&gt;N&lt;/em&gt;
waiters, a single unlock triggers &lt;em&gt;N&lt;/em&gt; cache line transfers, increasing coherence traffic and causing latency to degrade as contention grows.&lt;/p&gt;
&lt;h3 id=&quot;mcs-locks&quot;&gt;MCS Locks&lt;/h3&gt;
&lt;p&gt;Note: MCS Locks are better explained in this &lt;a rel=&quot;external&quot; href=&quot;https://lwn.net/Articles/590243/&quot;&gt;LWN Article&lt;/a&gt; and most of the information presented here comes from it.&lt;/p&gt;
&lt;p&gt;One mechanism that solves this is the &lt;em&gt;MCS lock&lt;/em&gt; (named after its authors), which eliminates cache line bouncing by giving each waiter its own cache line to spin on.&lt;/p&gt;
&lt;p&gt;An MCS lock (or node) can be defined as such:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;struct&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; McsNode&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;  std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;atomic&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;McsNode&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; next&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;  std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;atomic&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; locked&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; // 1 when acquired&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The way MCS locks work is via a sort of linked list, where each new waiter atomically appends its node to the tail of the main lock (called &lt;code&gt;next&lt;/code&gt;), then spins on its own locked field until its predecessor wakes it up.&lt;/p&gt;
&lt;p&gt;Let us walk through the process of acquiring and releasing an MCS lock.&lt;/p&gt;
&lt;p&gt;First, CPU0 atomically swaps the tail pointer with a pointer to its own node and notices the previous value was &lt;code&gt;NULL&lt;/code&gt;, indicating the queue was empty and the lock is now held by CPU0:&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;/qspinlock-graph1.svg&quot; alt=&quot;&quot; width=&quot;50%&quot;&gt;
  &lt;figcaption&gt;Uncontended case&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now, CPU1 tries to acquire the lock, but it is contended: it notices the return value of the atomic exchange is non-&lt;code&gt;NULL&lt;/code&gt; and sets the old tail&#39;s &lt;code&gt;next&lt;/code&gt; value to its &lt;em&gt;own&lt;/em&gt; &lt;code&gt;McsNode&lt;/code&gt;, and spins on its own &lt;code&gt;locked&lt;/code&gt; value until it is 1, ensuring spinning on CPU-local data:&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;/qspinlock-graph2.svg&quot; alt=&quot;&quot; width=&quot;50%&quot;&gt;
  &lt;figcaption&gt;Contended case&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When CPU0 releases the lock, it attempts to compare-and-swap (CAS) the tail pointer back to &lt;code&gt;NULL&lt;/code&gt;. If the CAS succeeds, the queue is empty and the lock is free. If it fails, a successor has arrived, CPU0 then sets CPU1-&amp;gt;locked = 1, waking CPU1 and handing off the lock.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;/qspinlock-graph3.svg&quot; alt=&quot;&quot; width=&quot;50%&quot;&gt;
  &lt;figcaption&gt;CPU1 has successfully taken the lock&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;mechanism&quot;&gt;Mechanism&lt;/h3&gt;
&lt;p&gt;At their core, qspinlocks are based on MCS locks, but they do not have MCS nodes directly embedded within them, as a means to save space and keep the lock to a single 32-bit word. That word is structured as such:&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;/qspinlock-structure.svg&quot; alt=&quot;&quot; width=&quot;80%&quot;&gt;
  &lt;figcaption&gt;Layout of a qspinlock&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In this implementation, we will define a qspinlock as:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;struct&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; QSpinLock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  union&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;    std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;atomic&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; val&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    struct&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;      std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;atomic&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint8_t&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; locked&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;      std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;atomic&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint8_t&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; pending&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; low_word&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    struct&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;      std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;atomic&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint16_t&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; locked_pending&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;      std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt;atomic&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint16_t&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; split&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Here be dragons 🐉! Accessing multiple fields of a union is undefined behavior (UB) in C++. However, in practice all major compilers (GCC, Clang) treat this as well-defined. We accept the UB and move on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&#39;s go through every field one by one:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;locked&lt;/code&gt;: Set to 1 when the lock is held, and cleared on release. Waiters at the head of the queue spin on this byte waiting for it to reach 0 before taking the lock.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pending&lt;/code&gt;: Used as an optimization when there is only one CPU waiting for the lock to be freed. Rather than setting up a full MCS queue node, the first waiter simply sets this bit and spins on locked.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tail&lt;/code&gt;: Split into two subfields: the lowest two bits encode the &lt;code&gt;qnode&lt;/code&gt; slot index and the remaining bits encode the CPU ID of the tail node.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;qnodes&quot;&gt;qnodes&lt;/h4&gt;
&lt;p&gt;The value encoded in the tail field indexes into a per-CPU array of MCS nodes called &lt;code&gt;qnodes&lt;/code&gt;. Linux allocates 4 nodes per CPU.
The reason there is an array rather than a single node is to handle nested acquisition: if an interrupt fires on a CPU that is already waiting on the lock, it needs a separate node, so each CPU maintains a small array of nodes instead of just one (though it is discouraged to take locks in interrupts anyway!).&lt;/p&gt;
&lt;p&gt;Let&#39;s now go through the implementation of locking.&lt;/p&gt;
&lt;h3 id=&quot;locking&quot;&gt;Locking&lt;/h3&gt;
&lt;p&gt;Note: This implementation is mostly based on the actual Linux code, adapted for C++ and omitting extraneous checks&lt;/p&gt;
&lt;h4 id=&quot;constants&quot;&gt;Constants&lt;/h4&gt;
&lt;p&gt;Let us first define a few useful constants related to extracting fields from the 32-bit word:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  static constexpr uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; LOCKED_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; 0x&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;000000FF&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  static constexpr uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; LOCKED_VAL&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; 0x&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;00000001&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  static constexpr uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; PENDING_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; 0x&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;0000FF00&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  static constexpr uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; PENDING_VAL&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; 0x&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;00000100&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  static constexpr uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_IDX_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; 0x&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;00030000&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  static constexpr uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_CPU_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; 0x&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;FFFC0000&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  static constexpr uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_IDX_MASK&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_CPU_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  static constexpr uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_IDX_OFFSET&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 16&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  static constexpr uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_CPU_OFFSET&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 18&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each &lt;code&gt;_MASK&lt;/code&gt; constant isolates its field via a bitwise AND, for example, &lt;code&gt;val &amp;amp; LOCKED_MASK&lt;/code&gt; extracts the locked byte. Each &lt;code&gt;_VAL&lt;/code&gt; constant is the value written to set that field, &lt;code&gt;LOCKED_VAL&lt;/code&gt; is 1 in bit 0, &lt;code&gt;PENDING_VAL&lt;/code&gt; is 1 in bit 8. The &lt;code&gt;_OFFSET&lt;/code&gt; constants are the shift amounts used when encoding and decoding the tail field: the slot index sits at bit 16, and the CPU ID at bit 18.&lt;/p&gt;
&lt;h4 id=&quot;the-fastest-path&quot;&gt;The fastest path&lt;/h4&gt;
&lt;p&gt;The fastest path happens when we try to grab an uncontended lock. In that case, we can just set the &lt;code&gt;locked&lt;/code&gt; bit and be done:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; lock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;    // Fastest path&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;compare_exchange_strong&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; LOCKED_VAL&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_acquire&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;                                    std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_relaxed&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;      return&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&quot;the-medium-path&quot;&gt;The medium path&lt;/h4&gt;
&lt;p&gt;The medium path happens when we try to grab a contended lock with no waiter.
First, we check whether the only bit set is &lt;code&gt;pending&lt;/code&gt;, this indicates that a pending waiter is mid-transition, in the process of atomically clearing pending and setting locked to take ownership. We spin for up to &lt;code&gt;PENDING_LOOPS&lt;/code&gt; iterations to let that transition complete:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; PENDING_VAL&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; cnt&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; PENDING_LOOPS&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; // 512 on Linux&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    while&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;cnt&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;        v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; val&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_relaxed&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;        if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; PENDING_VAL&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;            break&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;        cpu_relax&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we check whether any bit but locked is set (i.e. either &lt;code&gt;tail&lt;/code&gt; or &lt;code&gt;pending&lt;/code&gt; is non-zero), which would indicate another waiter, in that case we go to the slow path:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;amp; ~&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;LOCKED_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;    queue&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; // Go to slow path&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    return&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As far as we know, there is no one contending on the lock, so we can set the pending bit:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; val&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;fetch_or&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;PENDING_VAL&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_acquire&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, fetch_or is not a CAS, another CPU may have raced us and modified the lock word between our initial check and this store. So we validate the value returned by &lt;code&gt;fetch_or&lt;/code&gt; and check for contention once more:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;amp; ~&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;LOCKED_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) [[&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;unlikely&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;]] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; ((&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; PENDING_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;        // We set the pending bit but we shouldn&amp;#39;t have, undo it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;        val&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;fetch_and&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;PENDING_VAL&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_release&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;    queue&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;span class=&quot;z-16&quot;&gt; // Go to slow path&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    return&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, if the lock is still held, we spin on &lt;code&gt;locked&lt;/code&gt; until it is cleared by the current holder:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; LOCKED_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    while&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;low_word&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;locked&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_acquire&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;        cpu_relax&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once &lt;code&gt;locked&lt;/code&gt; is clear, we atomically clear pending and set locked in a single 16-bit store:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;locked_pending&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_release&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&quot;the-slow-path&quot;&gt;The slow path&lt;/h4&gt;
&lt;p&gt;The slow path occurs when we have no choice but to queue on the lock.
We first attempt to allocate an &lt;code&gt;McsNode&lt;/code&gt; from our per-CPU stash by claiming a slot index from &lt;code&gt;curr_idx&lt;/code&gt;,
if there is none available we have no choice but to spin on &lt;code&gt;locked&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; queue&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; idx&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; my_cpu&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;curr_idx&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    struct&lt;/span&gt;&lt;span class=&quot;z-6&quot;&gt; IdxGuard&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;      int &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;      ~IdxGuard&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;() {&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; v&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; guard&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;my_cpu&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;curr_idx&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;gt;=&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; MAX_IDX&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;      while&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;try_lock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;        cpu_relax&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;      return&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;IdxGuard&lt;/code&gt; RAII pattern ensures the slot is released when &lt;code&gt;queue()&lt;/code&gt; returns, regardless of which path we exit through. If all slots are exhausted, which should very rarely happen in practice, we fall back to spinning directly on &lt;code&gt;try_lock()&lt;/code&gt; (which simply tries to CAS &lt;code&gt;val&lt;/code&gt; with &lt;code&gt;LOCKED_VAL&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;We then initialize the node and make one last opportunistic attempt to grab the lock. As the Linux source notes, we have just touched a (possibly cold) cache line to initialize the node and someone may have released the lock while we weren&#39;t watching, so it is worth trying once more before committing to the queue:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;McsNode&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;cpus&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;my_cpu&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;]-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;qnodes&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;locked&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_relaxed&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;nullptr&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_relaxed&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;// Try one last time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;try_lock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;()) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    return&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have absolutely no choice but to queue, first encode the tail index:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; encode_tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;my_cpu&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; idx&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;encode_tail&lt;/code&gt; is:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; encode_tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; cpu_id&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; idx&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    return&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; ((&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;cpu_id&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_CPU_OFFSET&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_IDX_OFFSET&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the &lt;code&gt;cpu_id + 1&lt;/code&gt;: CPU IDs start at 0, so an ID of 0 would be indistinguishable from an empty tail field. Adding 1 ensures that a non-zero tail always indicates a real waiter.&lt;/p&gt;
&lt;p&gt;We then atomically exchange the tail field with our new value, retrieving the previous tail:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; old&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; xchg_tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;xchg_tail&lt;/code&gt; operates only on the upper 16 bits of the lock word, leaving locked and pending untouched:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; xchg_tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    uint16_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; tail16&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; tail&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 16&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    uint16_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; old_tail16&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;        split&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;exchange&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;tail16&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_acq_rel&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    return&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; static_cast&amp;lt;uint32_t&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;old_tail16&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 16&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we need to check whether or not we have a predecessor, and if so, we have to let it know of our presence by setting its &lt;code&gt;next&lt;/code&gt; field to point to our node.
We then enter phase 1 of the two-phase wait, spinning on our own &lt;code&gt;node-&amp;gt;locked&lt;/code&gt; until our predecessor wakes us:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;// Someone else was already waiting, set their next&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;old&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-6&quot;&gt;    McsNode&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;prev&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; decode_tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;old&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    prev&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_release&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    while&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;locked&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_acquire&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;        cpu_relax&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The release store to &lt;code&gt;prev-&amp;gt;next&lt;/code&gt; and the acquire load of &lt;code&gt;node-&amp;gt;locked&lt;/code&gt; ensures the predecessor will not write to &lt;code&gt;node-&amp;gt;locked&lt;/code&gt; until it has observed our node via its own &lt;code&gt;next&lt;/code&gt; pointer.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;decode_tail&lt;/code&gt; simply undoes the encoding to recover the CPU ID and slot index:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-6&quot;&gt;McsNode&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;decode_tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; cpu_id&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; = (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;tail&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_CPU_OFFSET&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; idx&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; = (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;tail&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_IDX_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_IDX_OFFSET&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    return&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;cpus&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;cpu_id&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;]-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;qnodes&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once our predecessor has woken us, we are now the head of the queue. We enter phase 2: spinning on the main lock word until both locked and pending are clear, meaning the current holder has released the lock and no pending waiter is mid-transition:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (((&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; val&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_acquire&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;        (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;LOCKED_MASK&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; PENDING_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;    cpu_relax&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we attempt to take ownership. If we are the only waiter in the queue, meaning the tail still points to our node, we try to atomically claim the lock and clear the tail in one CAS, leaving the lock word clean:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; ((&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; TAIL_MASK&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; tail&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;    uint32_t&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; expected&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; v&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;compare_exchange_strong&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; LOCKED_VAL&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;                                      std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_acquire&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;                                      std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_relaxed&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-16&quot;&gt;        // No contention, we&amp;#39;re done&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;        return&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the CAS fails, a new waiter has joined the queue behind us since we last checked. In that case we cannot clear the tail, so we take a different approach: we set &lt;code&gt;locked&lt;/code&gt; directly to signal ownership, then promote our successor to queue head by setting their &lt;code&gt;node-&amp;gt;locked&lt;/code&gt; to 1:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;low_word&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;locked&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_release&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;McsNode&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; nullptr&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; ((&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; node&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_acquire&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt; nullptr&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-10&quot;&gt;    cpu_relax&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;locked&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_release&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The loop on &lt;code&gt;node-&amp;gt;next&lt;/code&gt; handles the inherent race in the two-step enqueue: a successor may have already swapped the tail but not yet written their next pointer. We simply wait for them to finish. Once next is visible, a single store to &lt;code&gt;next-&amp;gt;locked&lt;/code&gt; wakes the successor and hands off the queue head, completing the acquisition.&lt;/p&gt;
&lt;h4 id=&quot;summary&quot;&gt;Summary&lt;/h4&gt;
&lt;p&gt;To summarize, the lock() function has three paths of increasing complexity:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fast path: the lock is uncontended, a single CAS is sufficient.&lt;/li&gt;
&lt;li&gt;Medium path: the lock is held but the queue is empty, we set the pending bit and spin on locked without touching the MCS queue.&lt;/li&gt;
&lt;li&gt;Slow path: the queue is occupied, we enqueue an MCS node, wait for our predecessor to promote us to head, then spin on the main lock word before taking ownership.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;unlocking&quot;&gt;Unlocking&lt;/h3&gt;
&lt;p&gt;Phew, that was a lot to uncover for locking! Thankfully &lt;code&gt;unlock()&lt;/code&gt; is very simple:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; unlock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;() {&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    low_word&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;locked&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_release&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is actually the reason why we use a whole byte for &lt;code&gt;locked&lt;/code&gt; instead of a bit: &lt;code&gt;unlock()&lt;/code&gt; is as simple as an atomic byte store!&lt;/p&gt;
&lt;h2 id=&quot;benchmarks&quot;&gt;Benchmarks&lt;/h2&gt;
&lt;p&gt;It&#39;s fun to nerd out on implementation details but how much faster is &lt;code&gt;qspinlock&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;I have set up a simple benchmark, measuring &lt;code&gt;SpinLock&lt;/code&gt; against &lt;code&gt;TicketLock&lt;/code&gt; and &lt;code&gt;QSpinLock&lt;/code&gt;, it consists of creating N threads (up to &lt;code&gt;nproc&lt;/code&gt;) all pinned to their own CPU cores taking the lock and pushing 500 000 elements to a &lt;code&gt;std::vector&amp;lt;int&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; j&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; j&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; OPS_PER_THREAD&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; j&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    lock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    shared_vector&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;push_back&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    lock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;unlock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then wait for each thread to complete and measure the time taken:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  auto&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; t0&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;chrono&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;steady_clock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;  start&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_release&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3&quot;&gt;  for&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;auto &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; threads&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    t&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;  auto&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt; t1&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;chrono&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;steady_clock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are the results:&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;/qspinlock-throughput.png&quot; alt=&quot;&quot; width=&quot;100%&quot;&gt;
  &lt;figcaption&gt;Benchmark executed on a Ryzen 5 5600X&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Threads&lt;/th&gt;&lt;th&gt;Spinlock (ops/s)&lt;/th&gt;&lt;th&gt;Ticket Spinlock (ops/s)&lt;/th&gt;&lt;th&gt;qspinlock (ops/s)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;551,081,387&lt;/td&gt;&lt;td&gt;283,366,874&lt;/td&gt;&lt;td&gt;159,177,979&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;31,101,649&lt;/td&gt;&lt;td&gt;15,260,200&lt;/td&gt;&lt;td&gt;16,609,980&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;14,855,526&lt;/td&gt;&lt;td&gt;7,864,148&lt;/td&gt;&lt;td&gt;16,271,328&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;9,575,916&lt;/td&gt;&lt;td&gt;4,925,312&lt;/td&gt;&lt;td&gt;16,911,329&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;12&lt;/td&gt;&lt;td&gt;5,384,959&lt;/td&gt;&lt;td&gt;3,107,082&lt;/td&gt;&lt;td&gt;13,454,086&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;We can see the naive spinlock is much faster uncontended, which is expected since it is the simplest (albeit unfair).
We can also see &lt;code&gt;qspinlock&lt;/code&gt; seems to scale better than both the naive spinlock and the ticket spinlock, maintaining relatively stable throughput as thread count increases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why is it so slow uncontended though?&lt;/strong&gt;
In theory, &lt;code&gt;qspinlock&lt;/code&gt; should be as fast as a spinlock in the uncontended case, so why is it not? &lt;strong&gt;Honestly, I&#39;m not sure&lt;/strong&gt;, but my best guess is that it is because of the very short critical section causing a &lt;em&gt;store-to-load forwarding stall&lt;/em&gt;:
since we write to &lt;code&gt;locked&lt;/code&gt; as a byte in &lt;code&gt;unlock()&lt;/code&gt;, but access the value as a 32-bit integer in &lt;code&gt;lock()&lt;/code&gt;, the CPU pipeline stalls and we end up with poor performance.
In fact, changing &lt;code&gt;unlock()&lt;/code&gt; to:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;cpp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-18&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; unlock&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-21&quot;&gt;    val&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt;fetch_and&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;z-18&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;LOCKED_VAL&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-10&quot;&gt; std&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-21&quot;&gt;memory_order_release&lt;/span&gt;&lt;span class=&quot;z-15&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-15&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yields the same uncontended performance as the ticket lock, which is expected since both need a Read-Modify-Write (RMW) operation. This is slower overall, though.
Changing this to a simple store over the whole value (which is actually incorrect), yields the same uncontended performance as spinlock.&lt;/p&gt;
&lt;p&gt;In practice, real critical sections are rarely that short, the additional work done while holding the lock gives the pipeline enough time to resolve the stall, so this might not be an actual concern.
(Also, it only happens uncontended!)&lt;/p&gt;
&lt;h2 id=&quot;summary-1&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;qspinlock&lt;/code&gt; is not a silver bullet and its implementation is significantly more complex than either a plain spinlock or a ticket lock.
However, on a machine with many cores, as many as Linux needs to run on, the CPU-local spinning does make a measurable difference under sustained contention, even on my puny 6c/12t machine.&lt;/p&gt;
&lt;p&gt;If you wish to dig deeper, I highly recommend:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reading the &lt;a rel=&quot;external&quot; href=&quot;https://elixir.bootlin.com/linux/v6.19.8/source/kernel/locking/qspinlock.c&quot;&gt;Linux code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Reading this &lt;a rel=&quot;external&quot; href=&quot;https://lwn.net/Articles/590243/&quot;&gt;LWN Article&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code written for this article is available &lt;a rel=&quot;external&quot; href=&quot;https://gist.github.com/rdmsr/9188066462ab7039b60c09b10c25a75b&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thank you for making it this far!&lt;/p&gt;
</content>
        
    </entry>
</feed>
