<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Valentin Schneider</title>
    <link>https://people.kernel.org/vschneid/</link>
    <description></description>
    <pubDate>Wed, 08 Apr 2026 07:58:54 +0000</pubDate>
    <item>
      <title>Guards, guard locks &amp; friends</title>
      <link>https://people.kernel.org/vschneid/guards-guard-locks-and-friends</link>
      <description>&lt;![CDATA[Links&#xA;&#xA;https://lore.kernel.org/all/20230612093537.614161713@infradead.org/T/&#xA;&#xA;Intro&#xA;&#xA;As I was catching up with the scheduler&#39;s &#34;change pattern&#34; schedchange patches, I figured it was time I got up to speed with the guard zoology.&#xA;&#xA;The first part of this post is a code exploration with some of my own musings, the second part is a TL;DR with what each helper does and when you should use&#xA;them (\)&#xA;&#xA;(\) according to my own understanding, provided as-is without warranties of any kind, batteries not included.&#xA;&#xA;It&#39;s cleanup all the way down&#xA;&#xA;The docstring for cleanup kindly points us to the relevant gcc/clang documentation. Given I don&#39;t really speak clang, here&#39;s the relevant GCC bit:&#xA;&#xA;https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute&#xA;&#xA;cleanup (cleanupfunction)&#xA;    The cleanup attribute runs a function when the variable goes out of&#xA;    scope. This attribute can only be applied to auto function scope variables;&#xA;    it may not be applied to parameters or variables with static storage&#xA;    duration. The function must take one parameter, a pointer to a type&#xA;    compatible with the variable. The return value of the function (if any) is&#xA;    ignored.&#xA;&#xA;    When multiple variables in the same scope have cleanup attributes, at exit&#xA;    from the scope their associated cleanup functions are run in reverse order&#xA;    of definition (last defined, first cleanup).&#xA;So we get to write a function that takes a pointer to the variable and does cleanup for it whenever the variable goes out of scope. Neat.&#xA;&#xA;DEFINEFREE&#xA;&#xA;That&#39;s the first one we&#39;ll meet in include/linux/cleanup.h and the most straightforward.&#xA;&#xA; DEFINEFREE(name, type, free):&#xA; simple helper macro that defines the required wrapper for a _free()&#xA; based cleanup function. @free is an expression using &#39;T&#39; to access the&#xA; variable. @free should typically include a NULL test before calling a&#xA; function, see the example below.&#xA;Long story short, that&#39;s a cleanup variable definition with some extra sprinkles on top:&#xA;&#xA;define cleanup(func)&#x9;&#x9;&#x9;attribute((cleanup(func)))&#xA;#define _free(name)&#x9;cleanup(free##name)&#xA;&#xA;define DEFINEFREE(name, type, free) \&#xA;&#x9;static _alwaysinline void _free##name(void p) { type T = (type )p; free; }&#xA;So we can e.g. define a kfree() cleanup type and stick that onto any kmalloc()&#39;d variable to get automagic cleanup without any goto&#39;s. Some languages call that a smart pointer.&#xA;&#xA;DEFINEFREE(kfree, void , if (T) kfree(T))&#xA;&#xA;void allocobj(...)&#xA;{&#xA;     struct obj p free(kfree) = kmalloc(...);&#xA;     if (!p)&#xA;&#x9;return NULL;&#xA;&#xA;     if (!initobj(p))&#xA;&#x9;return NULL;&#xA;&#xA;     returnptr(p); // This does a pointer shuffle to prevent the kfree() from happening&#xA;}&#xA;I won&#39;t get into the returnptr() faff, but if you have a look at it and wonder what&#39;s going on, it&#39;s mostly going to be because of having to do the shuffle with no double evaluation. This is relevant: https://lore.kernel.org/lkml/CAHk-=wiOXePAqytCk6JuiP6MeePL6ksDYptE54hmztiGLYihjA@mail.gmail.com/&#xA;&#xA;DEFINECLASS&#xA;&#xA;This one is pretty much going to be DEFINEFREE() but with an added quality of life feature in the form of a constructor:&#xA;&#xA; DEFINECLASS(name, type, exit, init, initargs...):&#xA; helper to define the destructor and constructor for a type.&#xA; @exit is an expression using &#39;T&#39; -- similar to FREE above.&#xA; @init is an expression in @initargs resulting in @type&#xA;&#xA;define DEFINECLASS(name, type, exit, init, initargs...)&#x9;&#x9;\&#xA;typedef type class##name##t;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;static alwaysinline void class##name##destructor(type p)&#x9;\&#xA;{ type T = p; exit; }&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;static alwaysinline type class##name##constructor(initargs)&#x9;\&#xA;{ type t = init; return t; }&#xA;&#xA;define CLASS(name, var)&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;&#x9;class##name##t var _cleanup(class##name##destructor) =&#x9;\&#xA;&#x9;&#x9;class##name##constructor&#xA;You&#39;ll note that yes, it can be expressed purely as a DEFINEFREE(), but it saves us from a bit of repetition, and will enable us to craft stuff involving locks later on:&#xA;DEFINECLASS(fdget, struct fd, fdput(T), fdget(fd), int fd)&#xA;void foo(void)&#xA;{&#xA;&#x9;fd = ...;&#xA;&#x9;CLASS(fdget, f)(fd);&#xA;&#x9;if (fdempty(f))&#xA;&#x9;&#x9;return -EBADF;&#xA;&#xA;&#x9;// use &#39;f&#39; without concern&#xA;}&#xA;&#xA;DEFINEFREE(fdput, struct fd , if (T) fdput(T))&#xA;void foo(void)&#xA;{&#xA;&#x9;fd = ...;&#xA;&#x9;struct fd f _free(fdput) = fdget(fd);&#xA;&#x9;if (fdempty(f))&#xA;&#x9;&#x9;return -EBADF;&#xA;&#xA;&#x9;// use &#39;f&#39; without concern&#xA;Futex\hash\bucket case&#xA;&#xA;For a more complete example:&#xA;DEFINECLASS(hb, struct futexhashbucket ,&#xA;&#x9;     if (T) futexhashput(T),&#xA;&#x9;     futexhash(key), union futexkey key);&#xA;&#xA;int futexwake(u32 _user uaddr, unsigned int flags, int nrwake, u32 bitset)&#xA;{&#xA;&#x9;union futexkey key = FUTEXKEYINIT;&#xA;&#x9;DEFINEWAKEQ(wakeq);&#xA;&#x9;int ret;&#xA;&#xA;&#x9;ret = getfutexkey(uaddr, flags, &amp;key, FUTEXREAD);&#xA;&#x9;if (unlikely(ret != 0))&#xA;&#x9;&#x9;return ret;&#xA;&#xA;&#x9;CLASS(hb, hb)(&amp;key);&#xA;&#xA;&#x9;/ Make sure we really have tasks to wakeup /&#xA;&#x9;if (!futexhbwaiterspending(hb))&#xA;&#x9;&#x9;return ret;&#xA;&#xA;&#x9;spinlock(&amp;hb-  lock);&#xA;&#xA;&#x9;/ ... /&#xA;}&#xA;Using gcc -E to stop compilation after the preprocessor has expanded all of our fancy macros (\), the resulting code is fairly readable modulo the typedef:&#xA;typedef struct futexhashbucket  classhbt;&#xA;&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_))&#xA;void&#xA;classhbdestructor(struct futexhashbucket  p)&#xA;{&#xA;&#x9;struct futexhashbucket  T = p;&#xA;&#x9;if (T)&#xA;&#x9;&#x9;futexhashput(T);&#xA;}&#xA;&#xA;static inline attribute((_gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_))&#xA;struct futexhashbucket &#xA;classhbconstructor(union futexkey key)&#xA;{&#xA;&#x9;struct futexhashbucket  t = futexhash(key);&#xA;&#x9;return t;&#xA;}&#xA;&#xA;int futexwake(u32 uaddr, unsigned int flags, int nrwake, u32 bitset)&#xA;{&#xA;&#x9;struct futexq this, next;&#xA;&#x9;union futexkey key = (union futexkey) { .both = { .ptr = 0ULL } };&#xA;&#x9;struct wakeqhead wakeq = { ((struct wakeqnode ) 0x01), &amp;wakeq.first };&#xA;&#x9;int ret;&#xA;&#xA;&#x9;if (!bitset)&#xA;&#x9;&#x9;return -22;&#xA;&#xA;&#x9;ret = getfutexkey(uaddr, flags, &amp;key, FUTEXREAD);&#xA;&#x9;if (builtinexpect(!!(ret != 0), 0))&#xA;&#x9;&#x9;return ret;&#xA;&#xA;&#x9;if ((flags &amp; 0x0100) &amp;&amp; !nrwake)&#xA;&#x9;&#x9;return 0;&#xA;&#xA;&#x9;classhbt hb attribute((cleanup(classhbdestructor))) = classhbconstructor(&amp;key);&#xA;&#xA;&#x9;if (!futexhbwaiterspending(hb))&#xA;&#x9;&#x9;return ret;&#xA;&#xA;&#x9;spinlock(&amp;hb-  lock);&#xA;&#xA;&#x9;/ ... /&#xA;}&#xA;(\) I use make V=1 on the file I want to expand, copy the big command producing the .o, ditch the -Wp,-MMD,.o.d part and add a -E to it.&#xA;&#xA;DEFINEGUARD&#xA;&#xA;For now, ignore the CONDITIONAL and LOCKPTR stuff, this is only relevant to the scoped &amp; conditional guards which we&#39;ll get to later.&#xA;define DEFINECLASSISGUARD(name) \&#xA;&#x9;DEFINECLASSISCONDITIONAL(name, false); \&#xA;&#x9;DEFINEGUARDLOCKPTR(name, T)&#xA;&#xA;define DEFINEGUARD(name, type, lock, unlock) \&#xA;&#x9;DEFINECLASS(name, type, if (!_GUARDISERR(T)) { unlock; }, ({ lock; T; }), type T); \&#xA;&#x9;DEFINECLASSISGUARD(name)&#xA;&#xA;define guard(name) \&#xA;&#x9;CLASS(name, UNIQUEID(guard))&#xA;So it&#39;s a CLASS with a constructor and destructor, but the added bonus is the automagic cleanup variable definition.&#xA;&#xA;Why is that relevant? Well, consider locks. You don&#39;t declare a variable for a lock acquisition &amp; release, you manipulate an already-allocated object (e.g. a mutex). However, no variable declaration means no cleanup. So this just declares a variable to slap _cleanup onto it and have an automagic out-of-scope cleanup callback.&#xA;&#xA;Let&#39;s have a look at an example in the thermal subsystem with a mutex critical section:&#xA;DEFINEGUARD(coolingdev, struct thermalcoolingdevice , mutexlock(&amp;T-  lock),&#xA;&#x9;     mutexunlock(&amp;T-  lock))&#xA;&#xA;static ssizet&#xA;curstatestore(struct device dev, struct deviceattribute attr,&#xA;&#x9;&#x9;const char buf, sizet count)&#xA;{&#xA;&#x9;struct thermalcoolingdevice cdev = tocoolingdevice(dev);&#xA;&#x9;unsigned long state;&#xA;&#x9;int result;&#xA;&#x9;/ ... /&#xA;&#x9;if (state   cdev-  maxstate)&#xA;&#x9;&#x9;return -EINVAL;&#xA;&#xA;&#x9;guard(coolingdev)(cdev);&#xA;&#xA;&#x9;result = cdev-  ops-  setcurstate(cdev, state);&#xA;&#x9;if (result)&#xA;&#x9;&#x9;return result;&#xA;The preprocessor output looks like so:&#xA;typedef struct thermalcoolingdevice  classcoolingdevt;&#xA;&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_)) void&#xA;classcoolingdevdestructor(struct thermalcoolingdevice  p)&#xA;{&#xA;&#x9;struct thermalcoolingdevice  T = p;&#xA;&#x9;if (!({&#xA;&#x9;&#x9;&#x9;&#x9;unsigned long rc = (unsigned long)(T);&#xA;&#x9;&#x9;&#x9;&#x9;builtinexpect(!!((rc - 1)   = -4095 - 1), 0);&#xA;&#x9;&#x9;&#x9;})) {&#xA;&#x9;&#x9;mutexunlock(&amp;T-  lock);&#xA;&#x9;};&#xA;}&#xA;static inline attribute((gnuinline))&#xA;attribute((unused)) attribute_((noinstrumentfunction))&#xA;attribute((alwaysinline_)) struct thermalcoolingdevice &#xA;classcoolingdevconstructor(struct thermalcoolingdevice  T)&#xA;{&#xA;&#x9;struct thermalcoolingdevice  t =&#xA;&#x9;&#x9;({ mutexlock(&amp;T-  lock); T; });&#xA;&#x9;return t;&#xA;}&#xA;&#xA;static attribute((unused)) const bool classcoolingdevisconditional = false;&#xA;&#xA;static inline attribute((_gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_)) void &#xA;classcoolingdevlockptr(classcoolingdevt T)&#xA;{&#xA;&#x9;void ptr = (void )(unsigned long)(T);&#xA;&#x9;if (ISERR(ptr)) {&#xA;&#x9;&#x9;ptr = ((void )0);&#xA;&#x9;}&#xA;&#x9;return ptr;&#xA;}&#xA;&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_)) int&#xA;classcoolingdevlockerr(classcoolingdevt T)&#xA;{&#xA;&#x9;long rc = (unsigned long)(T);&#xA;&#x9;if (!rc) {&#xA;&#x9;&#x9;rc = -16;&#xA;&#x9;}&#xA;&#x9;if (!builtinexpect(!!((unsigned long)(void )(rc)   = (unsigned long)-4095), 0)) {&#xA;&#x9;&#x9;rc = 0;&#xA;&#x9;}&#xA;&#x9;return rc;&#xA;}&#xA;&#xA;static ssizet&#xA;curstatestore(struct device dev, struct deviceattribute attr,&#xA;&#x9;&#x9;const char buf, sizet count)&#xA;{&#xA;&#x9;struct thermalcoolingdevice cdev = ({ void _mptr = (void )(dev); Staticassert(builtintypescompatiblep(typeof((dev)), typeof(((struct thermalcoolingdevice )0)-  device)) || _builtintypescompatiblep(typeof((dev)), typeof(void)), &#34;pointer type mismatch in containerof()&#34;); ((struct thermalcoolingdevice )(mptr - builtinoffsetof(struct thermalcoolingdevice, device))); });&#xA;&#x9;unsigned long state;&#xA;&#x9;int result;&#xA;&#xA;&#x9;if (sscanf(buf, &#34;%ld\n&#34;, &amp;state) != 1)&#xA;&#x9;&#x9;return -22;&#xA;&#xA;&#x9;if ((long)state &lt; 0)&#xA;&#x9;&#x9;return -22;&#xA;&#xA;&#x9;if (state   cdev-  maxstate)&#xA;&#x9;&#x9;return -22;&#xA;&#xA;&#x9;classcoolingdevt _UNIQUEIDguard435 attribute((cleanup(classcoolingdevdestructor))) = classcoolingdevconstructor(cdev);&#xA;&#xA;&#x9;result = cdev-  ops-  setcurstate(cdev, state);&#xA;&#x9;if (result)&#xA;&#x9;&#x9;return result;&#xA;&#xA;&#x9;thermalcoolingdevicestatsupdate(cdev, state);&#xA;&#xA;&#x9;return count;&#xA;}&#xA;DEFINE\LOCK\GUARD&#xA;&#xA;Okay, we have sort-of-smart pointers, classes, guards for locks, what&#39;s next? Well, certain locks need more than just a pointer for the lock &amp; unlock operations. For instance, the scheduler&#39;s runqueue locks need both a struct rq pointer and a struct rqflags pointer.&#xA;&#xA;So LOCKGUARD&#39;s are going to be enhanced GUARD&#39;s manipulating a composite type instead of a single pointer:&#xA;define _DEFINEUNLOCKGUARD(name, type, unlock, ...)&#x9;&#x9;\&#xA;typedef struct {&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;&#x9;type lock;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;&#x9;VAARGS_;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;} class##name##t;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;Note that there is also the &#34;no pointer&#34; special case, which is when there is no accessible type for the manipulated lock - think preemptdisable(), migratedisable(), rcureadlock(); Just like for GUARD, we still declare a variable to slap _cleanup onto it.&#xA;&#xA;Let&#39;s look at the RCU case:&#xA;&#xA;DEFINELOCKGUARD0(rcu,&#xA;&#x9;do {&#xA;&#x9;&#x9;rcureadlock();&#xA;&#x9;&#x9;/&#xA;&#x9;&#x9; sparse doesn&#39;t call the cleanup function,&#xA;&#x9;&#x9; so just release immediately and don&#39;t track&#xA;&#x9;&#x9; the context. We don&#39;t need to anyway, since&#xA;&#x9;&#x9; the whole point of the guard is to not need&#xA;&#x9;&#x9; the explicit unlock.&#xA;&#x9;&#x9; /&#xA;&#x9;&#x9;_release(RCU);&#xA;&#x9;} while (0),&#xA;&#x9;rcureadunlock())&#xA;&#xA;void wakeupifidle(int cpu)&#xA;{&#xA;&#x9;struct rq rq = cpurq(cpu);&#xA;&#xA;&#x9;guard(rcu)();&#xA;&#x9;if (isidletask(rcudereference(rq-  curr))) {&#xA;&#x9;&#x9;// ....&#xA;&#x9;}&#xA;}&#xA;&#xA;static attribute((unused)) const bool classrcuisconditional = false;&#xA;typedef struct {&#xA;&#x9;void lock;&#xA;} classrcut;&#xA;&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_))&#xA;void&#xA;classrcudestructor(classrcut T)&#xA;{&#xA;&#x9;if (!({&#xA;&#x9;&#x9;&#x9;&#x9;unsigned long rc = ( unsigned long)(T-  lock);&#xA;&#x9;&#x9;&#x9;&#x9;_builtinexpect(!!((rc - 1)   = -4095 - 1), 0);&#xA;&#x9;&#x9;&#x9;})) {&#xA;&#x9;&#x9;rcureadunlock();&#xA;&#x9;}&#xA;}&#xA;&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_))&#xA;void &#xA;classrculockptr(classrcut T)&#xA;{&#xA;&#x9;void ptr = (void )( unsigned long)(&amp;T-  lock);&#xA;&#x9;if (ISERR(ptr)) {&#xA;&#x9;&#x9;ptr = ((void )0);&#xA;&#x9;}&#xA;&#x9;return ptr;&#xA;}&#xA;&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_))&#xA;int&#xA;classrculockerr(classrcut T)&#xA;{&#xA;&#x9;long rc = ( unsigned long)(&amp;T-  lock);&#xA;&#x9;if (!rc) {&#xA;&#x9;&#x9;rc = -16;&#xA;&#x9;}&#xA;&#x9;if (!builtinexpect(!!((unsigned long)(void )(rc)   = (unsigned long)-4095), 0)) {&#xA;&#x9;&#x9;rc = 0;&#xA;&#x9;}&#xA;&#x9;return rc;&#xA;}&#xA;&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_))&#xA;classrcut&#xA;classrcuconstructor(void)&#xA;{&#xA;&#x9;classrcut t = { .lock = (void)1 }, T attribute((unused)) = &amp;t;&#xA;&#x9;do {&#xA;&#x9;&#x9;rcureadlock();&#xA;&#x9;&#x9;(void)0; // _release(RCU); just for sparse, see comment in definition&#xA;&#x9;} while (0);&#xA;&#x9;return t;&#xA;}&#xA;&#xA;void wakeupifidle(int cpu)&#xA;{&#xA;&#x9;struct rq rq = (&amp;(({ do { const void seggs _vppverify = (typeof((&amp;(runqueues)) + 0))((void )0); (void)_vppverify; } while (0); ({ unsigned long ptr; asm (&#34;&#34; : &#34;=r&#34;(ptr) : &#34;0&#34;((_typeofunqual(((&amp;(runqueues)))) )(( unsigned long)((&amp;(runqueues)))))); (typeof((typeofunqual(((&amp;(runqueues)))) )(( unsigned long)((&amp;(runqueues)))))) (ptr + (((percpuoffset[((cpu))])))); }); })));&#xA;&#x9;classrcut UNIQUEIDguard1486 attribute((cleanup(classrcudestructor))) =&#xA;&#x9;&#x9;classrcuconstructor();&#xA;&#xA;&#x9;if (isidletask(...) {&#xA;&#x9;&#x9;// ...&#xA;&#x9;}&#xA;}&#xA;&#xA;Let&#39;s look at the runqueue lock:&#xA;DEFINELOCKGUARD1(rqlockirqsave, struct rq,&#xA;&#x9;&#x9;    rqlockirqsave(T-  lock, &amp;T-  rf),&#xA;&#x9;&#x9;    rqunlockirqrestore(T-  lock, &amp;T-  rf),&#xA;&#x9;&#x9;    struct rqflags rf)&#xA;&#xA;static void schedbalanceupdateblockedaverages(int cpu)&#xA;{&#xA;&#x9;struct rq rq = cpurq(cpu);&#xA;&#xA;&#x9;guard(rqlockirqsave)(rq);&#xA;&#x9;updaterqclock(rq);&#xA;&#x9;schedbalanceupdateblockedaverages(rq);&#xA;}&#xA;&#xA;static attribute((unused)) const bool classrqlockirqsaveisconditional = false;&#xA;&#xA;typedef struct {&#xA;&#x9;struct rq lock;&#xA;&#x9;struct rqflags rf;&#xA;} classrqlockirqsavet;&#xA;&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_))&#xA;void&#xA;classrqlockirqsavedestructor(classrqlockirqsavet T)&#xA;{&#xA;&#x9;if (!({&#xA;&#x9;&#x9;&#x9;&#x9;unsigned long rc = ( unsigned long)(T-  lock);&#xA;&#x9;&#x9;&#x9;&#x9;_builtinexpect(!!((rc - 1)   = -4095 - 1), 0);&#xA;&#x9;&#x9;&#x9;})) {&#xA;&#x9;&#x9;rqunlockirqrestore(T-  lock, &amp;T-  rf);&#xA;&#x9;}&#xA;}&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_))&#xA;void &#xA;classrqlockirqsavelockptr(classrqlockirqsavet T)&#xA;{&#xA;&#x9;void ptr = (void )( unsigned long)(&amp;T-  lock);&#xA;&#x9;if (ISERR(ptr)) {&#xA;&#x9;&#x9;ptr = ((void )0);&#xA;&#x9;}&#xA;&#x9;return ptr;&#xA;}&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_))&#xA;int&#xA;classrqlockirqsavelockerr(classrqlockirqsavet T)&#xA;{&#xA;&#x9;long rc = ( unsigned long)(&amp;T-  lock);&#xA;&#x9;if (!rc) {&#xA;&#x9;&#x9;rc = -16;&#xA;&#x9;}&#xA;&#x9;if (!builtinexpect(!!((unsigned long)(void )(rc)   = (unsigned long)-4095), 0)) {&#xA;&#x9;&#x9;rc = 0;&#xA;&#x9;}&#xA;&#x9;return rc;&#xA;}&#xA;&#xA;static inline attribute((gnuinline)) attribute((unused))&#xA;attribute_((noinstrumentfunction)) attribute((alwaysinline_))&#xA;classrqlockirqsavet&#xA;classrqlockirqsaveconstructor(struct rq l)&#xA;{&#xA;&#x9;classrqlockirqsavet t = { .lock = l }, T = &amp;t;&#xA;&#x9;rqlockirqsave(T-  lock, &amp;T-  rf);&#xA;&#x9;return t;&#xA;}&#xA;&#xA;static void schedbalanceupdateblockedaverages(int cpu)&#xA;{&#xA; struct rq rq = (&amp;(({ do { const void seggs _vppverify = (typeof((&amp;(runqueues)) + 0))((void )0); (void)_vppverify; } while (0); ({ unsigned long ptr; asm (&#34;&#34; : &#34;=r&#34;(ptr) : &#34;0&#34;((_typeofunqual(((&amp;(runqueues)))) )(( unsigned long)((&amp;(runqueues)))))); (typeof((typeofunqual(((&amp;(runqueues)))) )(( unsigned long)((&amp;(runqueues)))))) (ptr + (((percpuoffset[((cpu))])))); }); })));&#xA;&#xA; classrqlockirqsavet UNIQUEIDguard1377&#xA;&#x9; attribute((cleanup(classrqlockirqsavedestructor))) =&#xA;&#x9; classrqlockirqsaveconstructor(rq);&#xA;&#xA; updaterqclock(rq);&#xA; _schedbalanceupdateblockedaverages(rq);&#xA;}&#xA;&#xA;SCOPES&#xA;&#xA;Scope creation is slightly different for classes and guards, but follow the same principle.&#xA;&#xA;Class&#xA;&#xA;define scopedclass(name, var, label, args...)        \&#xA;&#x9;for (CLASS(name, var)(args); ; ({ goto label; })) \&#xA;&#x9;&#x9;if (0) {                                   \&#xA;label:                                                    \&#xA;&#x9;&#x9;&#x9;break;                             \&#xA;&#x9;&#x9;} else&#xA;&#xA;define scopedclass(name, var, args...) \&#xA;&#x9;scopedclass(name, var, UNIQUEID(label), args)&#xA;That for+if+goto trinity looks a bit unholy at first, but let&#39;s look at what the requirements are for a macro that lets us create a new scope:&#xA;create a new scope&#xA;declare the _cleanup variable in that new scope&#xA;make the macro usable either with a single statement, or with curly braces&#xA;&#xA;A for loop gives us the declaration and the scope. However that for loop needs to run once, and it&#39;d be a shame to have to declare a loop counter. The &#34;run exactly once&#34; mechanism is thus encoded in the form of the if+goto.&#xA;&#xA;Consider:&#xA;&#x9;for (CLASS(name, var)(args); ; ({ goto label; }))&#xA;&#x9;&#x9;if (0) {&#xA;label:&#xA;&#x9;&#x9;&#x9;break;&#xA;&#x9;&#x9;} else {&#xA;&#x9;&#x9;   stmt;&#xA;&#x9;&#x9;}&#xA;The execution order will be:&#xA;CLASS(name, var)(args);&#xA;stmt;&#xA;goto label;&#xA;break;&#xA;We thus save ourselves the need for an extra variable at the cost of mild code reader confusion, a common trick used in the kernel.&#xA;&#xA;Guards&#xA;&#xA;For guard scopes, we find the same for+if+goto construct but with some added checks. For regular (unconditional) guards, this is pretty much the same as for CLASS&#39;es:&#xA;/&#xA; Helper macro for scopedguard().&#xA;  Note that the &#34;!iscondptr(name)&#34; part of the condition ensures that&#xA; compiler would be sure that for the unconditional locks the body of the&#xA; loop (caller-provided code glued to the else clause) could not be skipped.&#xA; It is needed because the other part - &#34;_guardptr(name)(&amp;scope)&#34; - is too&#xA; hard to deduce (even if could be proven true for unconditional locks).&#xA; /&#xA;define scopedguard(name, label, args...)&#x9;&#x9;&#x9;&#x9;\&#xA;&#x9;for (CLASS(name, scope)(args);&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;&#x9;     guardptr(name)(&amp;scope) || !iscondptr(name);&#x9;\&#xA;&#x9;     ({ goto label; }))&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;&#x9;&#x9;if (0) {&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;label:&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;&#x9;&#x9;&#x9;break;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;&#x9;&#x9;} else&#xA;&#xA;define scopedguard(name, args...)&#x9;\&#xA;&#x9;_scopedguard(name, UNIQUEID(label), args)&#xA;For conditional guards, we mainly factor in the fact that the constructor can &#34;fail&#34;. This is relevant for e.g. trylocks where the lock acquisition isn&#39;t guaranteed to succeed.&#xA;define _scopedcondguard(name, fail, label, args...)&#x9;&#x9;\&#xA;&#x9;for (CLASS(name, scope)(args); true; ({ goto label; }))&#x9;\&#xA;&#x9;&#x9;if (!_guardptr(name)(&amp;scope)) {&#x9;&#x9;&#x9;\&#xA;&#x9;&#x9;&#x9;BUILDBUGON(!iscondptr(name));&#x9;&#x9;\&#xA;&#x9;&#x9;&#x9;fail;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;label:&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;&#x9;&#x9;&#x9;break;&#x9;&#x9;&#x9;&#x9;&#x9;&#x9;\&#xA;&#x9;&#x9;} else&#xA;&#xA;define scopedcondguard(name, fail, args...)&#x9;\&#xA;&#x9;_scopedcondguard(name, fail, UNIQUEID(label), args)&#xA;So in the end, that _DEFINECLASSISCONDITIONAL() faff is there:&#xA;To help optimize unconditional guard scopes&#xA;To ensure conditional guard scopes are used correctly (i.e. the lock acquisition failure is expected)&#xA;&#xA;Debuggability&#xA;&#xA;You&#39;ll note that while guards delete an entire class of error associated with goto&#39;s, they shuffle the code around.&#xA;&#xA;From my experimentation, if you put the constructor and the destructor on a separate line in the CLASS/GUARD definition, you&#39;ll at least be able to tell them apart during a splat:&#xA;&#xA;DEFINELOCKGUARD1(rawspinlockirqsavebug, rawspinlockt,&#xA;&#x9;&#x9;    rawspinlockirqsave(T-  lock, T-  flags),&#xA;spinlock.h:571:&#x9;    rawspinunlockirqrestorebug(T-  lock, T-  flags),&#xA;&#x9;&#x9;    unsigned long flags)&#xA;&#xA;int trytowakeup(struct taskstruct p, unsigned int state, int wakeflags)&#xA;{&#xA;&#x9;        ...&#xA;core.c:4108&#x9;scopedguard (rawspinlockirqsavebug, &amp;p-  pilock) {&#xA;&#x9;        }&#xA;&#x9;        ...&#xA;}&#xA;&#xA;[    0.216287] kernel BUG at ./include/linux/spinlock.h:571!&#xA;[    0.217115] Oops: invalid opcode: 0000 [#1] SMP PTI&#xA;[    0.217285] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014&#xA;[    0.217285] RIP: 0010:trytowakeup (./include/linux/spinlock.h:569 (discriminator 6) kernel/sched/core.c:4108 (discriminator 6))&#xA;[    0.217285] Call Trace:&#xA;[    0.217285]  TASK&#xA;[    0.217285]  ? _pfxkthreadworkerfn (kernel/kthread.c:966)&#xA;[    0.217285]  _kthreadcreateonnode (kernel/kthread.c:535)&#xA;[    0.217285]  kthreadcreateworkeronnode (kernel/kthread.c:1043 (discriminator 1) kernel/kthread.c:1073 (discriminator 1))&#xA;[    0.217285]  ? vprintkemit (kernel/printk/printk.c:4625 kernel/printk/printk.c:2433)&#xA;[    0.217285]  workqueueinit (kernel/workqueue.c:7873 kernel/workqueue.c:7922)&#xA;[    0.217285]  kernelinitfreeable (init/main.c:1675)&#xA;[    0.217285]  ? _pfxkernelinit (init/main.c:1570)&#xA;[    0.217285]  kernelinit (init/main.c:1580)&#xA;[    0.217285]  retfromfork (arch/x86/kernel/process.c:164)&#xA;[    0.217285]  ? _pfxkernelinit (init/main.c:1570)&#xA;[    0.217285]  retfromforkasm (arch/x86/entry/entry64.S:259)&#xA;[    0.217285]  /TASK&#xA;&#xA;TL;DR&#xA;&#xA;DEFINEFREE()&#xA;    Sort-of-smart pointer&#xA;    Definition tied to the freeing function, e.g. DEFINEKFREE(kfree,...)&#xA;&#xA;DEFINECLASS()&#xA;    Like DEFINEFREE() but with factorized initialization.&#xA;&#xA;DEFINEGUARD()&#xA;    Like DEFINECLASS() but you don&#39;t need the underlying variable&#xA;    e.g. locks don&#39;t require declaring a variable, you just lock and unlock them.&#xA;&#xA;DEFINE\LOCK\GUARD()&#xA;    Like DEFINEGUARD() but when a single pointer isn&#39;t sufficient for lock/unlock operations.&#xA;    Also for &#34;special&#34; locks with no underlying type such as RCU, preempt or migrate_disable.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<h2 id="links">Links</h2>

<p><a href="https://lore.kernel.org/all/20230612093537.614161713@infradead.org/T/" rel="nofollow">https://lore.kernel.org/all/20230612093537.614161713@infradead.org/T/</a></p>

<h2 id="intro">Intro</h2>

<p>As I was catching up with the scheduler&#39;s “change pattern” <code>sched_change</code> patches, I figured it was time I got up to speed with the guard zoology.</p>

<p>The first part of this post is a code exploration with some of my own musings, the second part is a TL;DR with what each helper does and when you should use
them (*)</p>

<p>(*) according to my own understanding, provided as-is without warranties of any kind, batteries not included.</p>

<h2 id="it-s-cleanup-all-the-way-down">It&#39;s <code>__cleanup__</code> all the way down</h2>

<p>The docstring for <code>__cleanup</code> kindly points us to the relevant gcc/clang documentation. Given I don&#39;t really speak clang, here&#39;s the relevant GCC bit:</p>

<p><a href="https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute" rel="nofollow">https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute</a></p>

<pre><code>cleanup (cleanup_function)
    The cleanup attribute runs a function when the variable goes out of
    scope. This attribute can only be applied to auto function scope variables;
    it may not be applied to parameters or variables with static storage
    duration. The function must take one parameter, a pointer to a type
    compatible with the variable. The return value of the function (if any) is
    ignored.

    When multiple variables in the same scope have cleanup attributes, at exit
    from the scope their associated cleanup functions are run in reverse order
    of definition (last defined, first cleanup).
</code></pre>

<p>So we get to write a function that takes a pointer to the variable and does cleanup for it whenever the variable goes out of scope. Neat.</p>

<h2 id="define-free">DEFINE_FREE</h2>

<p>That&#39;s the first one we&#39;ll meet in <code>include/linux/cleanup.h</code> and the most straightforward.</p>

<pre><code> * DEFINE_FREE(name, type, free):
 *	simple helper macro that defines the required wrapper for a __free()
 *	based cleanup function. @free is an expression using &#39;_T&#39; to access the
 *	variable. @free should typically include a NULL test before calling a
 *	function, see the example below.
</code></pre>

<p>Long story short, that&#39;s a <code>__cleanup</code> variable definition with some extra sprinkles on top:</p>

<pre><code class="language-C">#define __cleanup(func)			__attribute__((__cleanup__(func)))
#define __free(_name)	__cleanup(__free_##_name)

#define DEFINE_FREE(_name, _type, _free) \
	static __always_inline void __free_##_name(void *p) { _type _T = *(_type *)p; _free; }
</code></pre>

<p>So we can e.g. define a <code>kfree()</code> cleanup type and stick that onto any <code>kmalloc()</code>&#39;d variable to get automagic cleanup without any <code>goto</code>&#39;s. Some languages call that a smart pointer.</p>

<pre><code class="language-C">DEFINE_FREE(kfree, void *, if (_T) kfree(_T))

void *alloc_obj(...)
{
     struct obj *p __free(kfree) = kmalloc(...);
     if (!p)
	return NULL;

     if (!init_obj(p))
	return NULL;

     return_ptr(p); // This does a pointer shuffle to prevent the kfree() from happening
}
</code></pre>

<p>I won&#39;t get into the <code>return_ptr()</code> faff, but if you have a look at it and wonder what&#39;s going on, it&#39;s mostly going to be because of having to do the shuffle with no double evaluation. This is relevant: <a href="https://lore.kernel.org/lkml/CAHk-=wiOXePAqytCk6JuiP6MeePL6ksDYptE54hmztiGLYihjA@mail.gmail.com/" rel="nofollow">https://lore.kernel.org/lkml/CAHk-=wiOXePAqytCk6JuiP6MeePL6ksDYptE54hmztiGLYihjA@mail.gmail.com/</a></p>

<h2 id="define-class">DEFINE_CLASS</h2>

<p>This one is pretty much going to be <code>DEFINE_FREE()</code> but with an added quality of life feature in the form of a constructor:</p>

<pre><code> * DEFINE_CLASS(name, type, exit, init, init_args...):
 *	helper to define the destructor and constructor for a type.
 *	@exit is an expression using &#39;_T&#39; -- similar to FREE above.
 *	@init is an expression in @init_args resulting in @type
</code></pre>

<pre><code class="language-C">#define DEFINE_CLASS(_name, _type, _exit, _init, _init_args...)		\
typedef _type class_##_name##_t;					\
static __always_inline void class_##_name##_destructor(_type *p)	\
{ _type _T = *p; _exit; }						\
static __always_inline _type class_##_name##_constructor(_init_args)	\
{ _type t = _init; return t; }

#define CLASS(_name, var)						\
	class_##_name##_t var __cleanup(class_##_name##_destructor) =	\
		class_##_name##_constructor
</code></pre>

<p>You&#39;ll note that yes, it can be expressed purely as a <code>DEFINE_FREE()</code>, but it saves us from a bit of repetition, and will enable us to craft stuff involving locks later on:</p>

<pre><code class="language-C">DEFINE_CLASS(fdget, struct fd, fdput(_T), fdget(fd), int fd)
void foo(void)
{
	fd = ...;
	CLASS(fdget, f)(fd);
	if (fd_empty(f))
		return -EBADF;

	// use &#39;f&#39; without concern
}

DEFINE_FREE(fdput, struct fd *, if (_T) fdput(_T))
void foo(void)
{
	fd = ...;
	struct fd *f __free(fdput) = fdget(fd);
	if (fd_empty(f))
		return -EBADF;

	// use &#39;f&#39; without concern
</code></pre>

<h3 id="futex-hash-bucket-case">Futex_hash_bucket case</h3>

<p>For a more complete example:</p>

<pre><code class="language-C">DEFINE_CLASS(hb, struct futex_hash_bucket *,
	     if (_T) futex_hash_put(_T),
	     futex_hash(key), union futex_key *key);

int futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset)
{
	union futex_key key = FUTEX_KEY_INIT;
	DEFINE_WAKE_Q(wake_q);
	int ret;

	ret = get_futex_key(uaddr, flags, &amp;key, FUTEX_READ);
	if (unlikely(ret != 0))
		return ret;

	CLASS(hb, hb)(&amp;key);

	/* Make sure we really have tasks to wakeup */
	if (!futex_hb_waiters_pending(hb))
		return ret;

	spin_lock(&amp;hb-&gt;lock);

	/* ... */
}
</code></pre>

<p>Using <code>gcc -E</code> to stop compilation after the preprocessor has expanded all of our fancy macros (*), the resulting code is fairly readable modulo the <code>typedef</code>:</p>

<pre><code class="language-C">typedef struct futex_hash_bucket * class_hb_t;

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__))
void
class_hb_destructor(struct futex_hash_bucket * *p)
{
	struct futex_hash_bucket * _T = *p;
	if (_T)
		futex_hash_put(_T);
}

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__))
struct futex_hash_bucket *
class_hb_constructor(union futex_key *key)
{
	struct futex_hash_bucket * t = futex_hash(key);
	return t;
}
</code></pre>

<pre><code class="language-C">int futex_wake(u32 *uaddr, unsigned int flags, int nr_wake, u32 bitset)
{
	struct futex_q *this, *next;
	union futex_key key = (union futex_key) { .both = { .ptr = 0ULL } };
	struct wake_q_head wake_q = { ((struct wake_q_node *) 0x01), &amp;wake_q.first };
	int ret;

	if (!bitset)
		return -22;

	ret = get_futex_key(uaddr, flags, &amp;key, FUTEX_READ);
	if (__builtin_expect(!!(ret != 0), 0))
		return ret;

	if ((flags &amp; 0x0100) &amp;&amp; !nr_wake)
		return 0;

	class_hb_t hb __attribute__((__cleanup__(class_hb_destructor))) = class_hb_constructor(&amp;key);


	if (!futex_hb_waiters_pending(hb))
		return ret;

	spin_lock(&amp;hb-&gt;lock);

	/* ... */
}
</code></pre>

<p>(*) I use <code>make V=1</code> on the file I want to expand, copy the big command producing the .o, ditch the <code>-Wp,-MMD,**.o.d</code> part and add a -E to it.</p>

<h2 id="define-guard">DEFINE_GUARD</h2>

<p>For now, ignore the <code>CONDITIONAL</code> and <code>LOCK_PTR</code> stuff, this is only relevant to the scoped &amp; conditional guards which we&#39;ll get to later.</p>

<pre><code class="language-C">#define DEFINE_CLASS_IS_GUARD(_name) \
	__DEFINE_CLASS_IS_CONDITIONAL(_name, false); \
	__DEFINE_GUARD_LOCK_PTR(_name, _T)

#define DEFINE_GUARD(_name, _type, _lock, _unlock) \
	DEFINE_CLASS(_name, _type, if (!__GUARD_IS_ERR(_T)) { _unlock; }, ({ _lock; _T; }), _type _T); \
	DEFINE_CLASS_IS_GUARD(_name)

#define guard(_name) \
	CLASS(_name, __UNIQUE_ID(guard))
</code></pre>

<p>So it&#39;s a <code>CLASS</code> with a constructor and destructor, but the added bonus is the automagic <code>__cleanup</code> variable definition.</p>

<p>Why is that relevant? Well, consider locks. You don&#39;t declare a variable for a lock acquisition &amp; release, you manipulate an already-allocated object (e.g. a mutex). However, no variable declaration means no <code>__cleanup</code>. So this just declares a variable to slap <code>__cleanup</code> onto it and have an automagic out-of-scope cleanup callback.</p>

<p>Let&#39;s have a look at an example in the thermal subsystem with a mutex critical section:</p>

<pre><code class="language-C">DEFINE_GUARD(cooling_dev, struct thermal_cooling_device *, mutex_lock(&amp;_T-&gt;lock),
	     mutex_unlock(&amp;_T-&gt;lock))

static ssize_t
cur_state_store(struct device *dev, struct device_attribute *attr,
		const char *buf, size_t count)
{
	struct thermal_cooling_device *cdev = to_cooling_device(dev);
	unsigned long state;
	int result;
	/* ... */
	if (state &gt; cdev-&gt;max_state)
		return -EINVAL;

	guard(cooling_dev)(cdev);

	result = cdev-&gt;ops-&gt;set_cur_state(cdev, state);
	if (result)
		return result;
</code></pre>

<p>The preprocessor output looks like so:</p>

<pre><code class="language-C">typedef struct thermal_cooling_device * class_cooling_dev_t;

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__)) void
class_cooling_dev_destructor(struct thermal_cooling_device * *p)
{
	struct thermal_cooling_device * _T = *p;
	if (!({
				unsigned long _rc = (unsigned long)(_T);
				__builtin_expect(!!((_rc - 1) &gt;= -4095 - 1), 0);
			})) {
		mutex_unlock(&amp;_T-&gt;lock);
	};
}
static inline __attribute__((__gnu_inline__))
__attribute__((__unused__)) __attribute__((no_instrument_function))
__attribute__((__always_inline__)) struct thermal_cooling_device *
class_cooling_dev_constructor(struct thermal_cooling_device * _T)
{
	struct thermal_cooling_device * t =
		({ mutex_lock(&amp;_T-&gt;lock); _T; });
	return t;
}

static __attribute__((__unused__)) const bool class_cooling_dev_is_conditional = false;

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__)) void *
class_cooling_dev_lock_ptr(class_cooling_dev_t *_T)
{
	void *_ptr = (void *)(unsigned long)*(_T);
	if (IS_ERR(_ptr)) {
		_ptr = ((void *)0);
	}
	return _ptr;
}

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__)) int
class_cooling_dev_lock_err(class_cooling_dev_t *_T)
{
	long _rc = (unsigned long)*(_T);
	if (!_rc) {
		_rc = -16;
	}
	if (!__builtin_expect(!!((unsigned long)(void *)(_rc) &gt;= (unsigned long)-4095), 0)) {
		_rc = 0;
	}
	return _rc;
}
</code></pre>

<pre><code class="language-C">static ssize_t
cur_state_store(struct device *dev, struct device_attribute *attr,
		const char *buf, size_t count)
{
	struct thermal_cooling_device *cdev = ({ void *__mptr = (void *)(dev); _Static_assert(__builtin_types_compatible_p(typeof(*(dev)), typeof(((struct thermal_cooling_device *)0)-&gt;device)) || __builtin_types_compatible_p(typeof(*(dev)), typeof(void)), &#34;pointer type mismatch in container_of()&#34;); ((struct thermal_cooling_device *)(__mptr - __builtin_offsetof(struct thermal_cooling_device, device))); });
	unsigned long state;
	int result;

	if (sscanf(buf, &#34;%ld\n&#34;, &amp;state) != 1)
		return -22;

	if ((long)state &lt; 0)
		return -22;

	if (state &gt; cdev-&gt;max_state)
		return -22;

	class_cooling_dev_t __UNIQUE_ID_guard_435 __attribute__((__cleanup__(class_cooling_dev_destructor))) = class_cooling_dev_constructor(cdev);

	result = cdev-&gt;ops-&gt;set_cur_state(cdev, state);
	if (result)
		return result;

	thermal_cooling_device_stats_update(cdev, state);

	return count;
}
</code></pre>

<h2 id="define-lock-guard">DEFINE_LOCK_GUARD</h2>

<p>Okay, we have sort-of-smart pointers, classes, guards for locks, what&#39;s next? Well, certain locks need more than just a pointer for the lock &amp; unlock operations. For instance, the scheduler&#39;s runqueue locks need both a <code>struct rq</code> pointer and a <code>struct rq_flags</code> pointer.</p>

<p>So <code>LOCK_GUARD</code>&#39;s are going to be enhanced <code>GUARD</code>&#39;s manipulating a composite type instead of a single pointer:</p>

<pre><code class="language-C">#define __DEFINE_UNLOCK_GUARD(_name, _type, _unlock, ...)		\
typedef struct {							\
	_type *lock;							\
	__VA_ARGS__;							\
} class_##_name##_t;							\
</code></pre>

<p>Note that there is also the “no pointer” special case, which is when there is no accessible type for the manipulated lock – think <code>preempt_disable()</code>, <code>migrate_disable()</code>, <code>rcu_read_lock()</code>; Just like for <code>GUARD</code>, we still declare a variable to slap <code>__cleanup</code> onto it.</p>

<h3 id="let-s-look-at-the-rcu-case">Let&#39;s look at the RCU case:</h3>

<pre><code class="language-C">DEFINE_LOCK_GUARD_0(rcu,
	do {
		rcu_read_lock();
		/*
		 * sparse doesn&#39;t call the cleanup function,
		 * so just release immediately and don&#39;t track
		 * the context. We don&#39;t need to anyway, since
		 * the whole point of the guard is to not need
		 * the explicit unlock.
		 */
		__release(RCU);
	} while (0),
	rcu_read_unlock())
</code></pre>

<pre><code class="language-C">void wake_up_if_idle(int cpu)
{
	struct rq *rq = cpu_rq(cpu);

	guard(rcu)();
	if (is_idle_task(rcu_dereference(rq-&gt;curr))) {
		// ....
	}
}
</code></pre>

<pre><code class="language-C">static __attribute__((__unused__)) const bool class_rcu_is_conditional = false;
typedef struct {
	void *lock;
} class_rcu_t;

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__))
void
class_rcu_destructor(class_rcu_t *_T)
{
	if (!({
				unsigned long _rc = ( unsigned long)(_T-&gt;lock);
				__builtin_expect(!!((_rc - 1) &gt;= -4095 - 1), 0);
			})) {
		rcu_read_unlock();
	}
}

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__))
void *
class_rcu_lock_ptr(class_rcu_t *_T)
{
	void *_ptr = (void *)( unsigned long)*(&amp;_T-&gt;lock);
	if (IS_ERR(_ptr)) {
		_ptr = ((void *)0);
	}
	return _ptr;
}

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__))
int
class_rcu_lock_err(class_rcu_t *_T)
{
	long _rc = ( unsigned long)*(&amp;_T-&gt;lock);
	if (!_rc) {
		_rc = -16;
	}
	if (!__builtin_expect(!!((unsigned long)(void *)(_rc) &gt;= (unsigned long)-4095), 0)) {
		_rc = 0;
	}
	return _rc;
}

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__))
class_rcu_t
class_rcu_constructor(void)
{
	class_rcu_t _t = { .lock = (void*)1 }, *_T __attribute__((__unused__)) = &amp;_t;
	do {
		rcu_read_lock();
		(void)0; // __release(RCU); just for sparse, see comment in definition
	} while (0);
	return _t;
}
</code></pre>

<pre><code class="language-C">void wake_up_if_idle(int cpu)
{
	struct rq *rq = (&amp;(*({ do { const void __seg_gs *__vpp_verify = (typeof((&amp;(runqueues)) + 0))((void *)0); (void)__vpp_verify; } while (0); ({ unsigned long __ptr; __asm__ (&#34;&#34; : &#34;=r&#34;(__ptr) : &#34;0&#34;((__typeof_unqual__(*((&amp;(runqueues)))) *)(( unsigned long)((&amp;(runqueues)))))); (typeof((__typeof_unqual__(*((&amp;(runqueues)))) *)(( unsigned long)((&amp;(runqueues)))))) (__ptr + (((__per_cpu_offset[((cpu))])))); }); })));
	class_rcu_t __UNIQUE_ID_guard_1486 __attribute__((__cleanup__(class_rcu_destructor))) =
		class_rcu_constructor();

	if (is_idle_task(...) {
		// ...
	}
}
</code></pre>

<h3 id="let-s-look-at-the-runqueue-lock">Let&#39;s look at the runqueue lock:</h3>

<pre><code class="language-C">DEFINE_LOCK_GUARD_1(rq_lock_irqsave, struct rq,
		    rq_lock_irqsave(_T-&gt;lock, &amp;_T-&gt;rf),
		    rq_unlock_irqrestore(_T-&gt;lock, &amp;_T-&gt;rf),
		    struct rq_flags rf)
</code></pre>

<pre><code class="language-C">static void sched_balance_update_blocked_averages(int cpu)
{
	struct rq *rq = cpu_rq(cpu);

	guard(rq_lock_irqsave)(rq);
	update_rq_clock(rq);
	__sched_balance_update_blocked_averages(rq);
}
</code></pre>

<pre><code class="language-C">static __attribute__((__unused__)) const bool class_rq_lock_irqsave_is_conditional = false;

typedef struct {
	struct rq *lock;
	struct rq_flags rf;
} class_rq_lock_irqsave_t;

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__))
void
class_rq_lock_irqsave_destructor(class_rq_lock_irqsave_t *_T)
{
	if (!({
				unsigned long _rc = ( unsigned long)(_T-&gt;lock);
				__builtin_expect(!!((_rc - 1) &gt;= -4095 - 1), 0);
			})) {
		rq_unlock_irqrestore(_T-&gt;lock, &amp;_T-&gt;rf);
	}
}
static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__))
void *
class_rq_lock_irqsave_lock_ptr(class_rq_lock_irqsave_t *_T)
{
	void *_ptr = (void *)( unsigned long)*(&amp;_T-&gt;lock);
	if (IS_ERR(_ptr)) {
		_ptr = ((void *)0);
	}
	return _ptr;
}
static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__))
int
class_rq_lock_irqsave_lock_err(class_rq_lock_irqsave_t *_T)
{
	long _rc = ( unsigned long)*(&amp;_T-&gt;lock);
	if (!_rc) {
		_rc = -16;
	}
	if (!__builtin_expect(!!((unsigned long)(void *)(_rc) &gt;= (unsigned long)-4095), 0)) {
		_rc = 0;
	}
	return _rc;
}

static inline __attribute__((__gnu_inline__)) __attribute__((__unused__))
__attribute__((no_instrument_function)) __attribute__((__always_inline__))
class_rq_lock_irqsave_t
class_rq_lock_irqsave_constructor(struct rq *l)
{
	class_rq_lock_irqsave_t _t = { .lock = l }, *_T = &amp;_t;
	rq_lock_irqsave(_T-&gt;lock, &amp;_T-&gt;rf);
	return _t;
}
</code></pre>

<pre><code class="language-C">static void sched_balance_update_blocked_averages(int cpu)
{
 struct rq *rq = (&amp;(*({ do { const void __seg_gs *__vpp_verify = (typeof((&amp;(runqueues)) + 0))((void *)0); (void)__vpp_verify; } while (0); ({ unsigned long __ptr; __asm__ (&#34;&#34; : &#34;=r&#34;(__ptr) : &#34;0&#34;((__typeof_unqual__(*((&amp;(runqueues)))) *)(( unsigned long)((&amp;(runqueues)))))); (typeof((__typeof_unqual__(*((&amp;(runqueues)))) *)(( unsigned long)((&amp;(runqueues)))))) (__ptr + (((__per_cpu_offset[((cpu))])))); }); })));

 class_rq_lock_irqsave_t __UNIQUE_ID_guard_1377
	 __attribute__((__cleanup__(class_rq_lock_irqsave_destructor))) =
	 class_rq_lock_irqsave_constructor(rq);

 update_rq_clock(rq);
 __sched_balance_update_blocked_averages(rq);
}
</code></pre>

<h2 id="scopes">SCOPES</h2>

<p>Scope creation is slightly different for classes and guards, but follow the same principle.</p>

<h3 id="class">Class</h3>

<pre><code class="language-C">#define __scoped_class(_name, var, _label, args...)        \
	for (CLASS(_name, var)(args); ; ({ goto _label; })) \
		if (0) {                                   \
_label:                                                    \
			break;                             \
		} else

#define scoped_class(_name, var, args...) \
	__scoped_class(_name, var, __UNIQUE_ID(label), args)
</code></pre>

<p>That for+if+goto trinity looks a bit unholy at first, but let&#39;s look at what the requirements are for a macro that lets us create a new scope:
– create a new scope
– declare the <code>__cleanup</code> variable in that new scope
– make the macro usable either with a single statement, or with curly braces</p>

<p>A for loop gives us the declaration and the scope. However that for loop needs to run once, and it&#39;d be a shame to have to declare a loop counter. The “run exactly once” mechanism is thus encoded in the form of the if+goto.</p>

<p>Consider:</p>

<pre><code>	for (CLASS(_name, var)(args); ; ({ goto _label; }))
		if (0) {
_label:
			break;
		} else {
		   stmt;
		}
</code></pre>

<p>The execution order will be:</p>

<pre><code>CLASS(_name, var)(args);
stmt;
goto _label;
break;
</code></pre>

<p>We thus save ourselves the need for an extra variable at the cost of mild code reader confusion, a common trick used in the kernel.</p>

<h3 id="guards">Guards</h3>

<p>For guard scopes, we find the same for+if+goto construct but with some added checks. For regular (unconditional) guards, this is pretty much the same as for <code>CLASS</code>&#39;es:</p>

<pre><code class="language-C">/*
 * Helper macro for scoped_guard().
 *
 * Note that the &#34;!__is_cond_ptr(_name)&#34; part of the condition ensures that
 * compiler would be sure that for the unconditional locks the body of the
 * loop (caller-provided code glued to the else clause) could not be skipped.
 * It is needed because the other part - &#34;__guard_ptr(_name)(&amp;scope)&#34; - is too
 * hard to deduce (even if could be proven true for unconditional locks).
 */
#define __scoped_guard(_name, _label, args...)				\
	for (CLASS(_name, scope)(args);					\
	     __guard_ptr(_name)(&amp;scope) || !__is_cond_ptr(_name);	\
	     ({ goto _label; }))					\
		if (0) {						\
_label:									\
			break;						\
		} else

#define scoped_guard(_name, args...)	\
	__scoped_guard(_name, __UNIQUE_ID(label), args)
</code></pre>

<p>For conditional guards, we mainly factor in the fact that the constructor can “fail”. This is relevant for e.g. trylocks where the lock acquisition isn&#39;t guaranteed to succeed.</p>

<pre><code class="language-C">#define __scoped_cond_guard(_name, _fail, _label, args...)		\
	for (CLASS(_name, scope)(args); true; ({ goto _label; }))	\
		if (!__guard_ptr(_name)(&amp;scope)) {			\
			BUILD_BUG_ON(!__is_cond_ptr(_name));		\
			_fail;						\
_label:									\
			break;						\
		} else

#define scoped_cond_guard(_name, _fail, args...)	\
	__scoped_cond_guard(_name, _fail, __UNIQUE_ID(label), args)
</code></pre>

<p>So in the end, that <code>__DEFINE_CLASS_IS_CONDITIONAL()</code> faff is there:
– To help optimize unconditional guard scopes
– To ensure conditional guard scopes are used correctly (i.e. the lock acquisition failure is expected)</p>

<h2 id="debuggability">Debuggability</h2>

<p>You&#39;ll note that while guards delete an entire class of error associated with goto&#39;s, they shuffle the code around.</p>

<p>From my experimentation, if you put the constructor and the destructor on a separate line in the <code>CLASS</code>/<code>GUARD</code> definition, you&#39;ll at least be able to tell them apart during a splat:</p>

<pre><code class="language-C">DEFINE_LOCK_GUARD_1(raw_spinlock_irqsave_bug, raw_spinlock_t,
		    raw_spin_lock_irqsave(_T-&gt;lock, _T-&gt;flags),
spinlock.h:571:	    raw_spin_unlock_irqrestore_bug(_T-&gt;lock, _T-&gt;flags),
		    unsigned long flags)

int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
	        ...
core.c:4108	scoped_guard (raw_spinlock_irqsave_bug, &amp;p-&gt;pi_lock) {
	        }
	        ...
}
</code></pre>

<pre><code>[    0.216287] kernel BUG at ./include/linux/spinlock.h:571!
[    0.217115] Oops: invalid opcode: 0000 [#1] SMP PTI
[    0.217285] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014
[    0.217285] RIP: 0010:try_to_wake_up (./include/linux/spinlock.h:569 (discriminator 6) kernel/sched/core.c:4108 (discriminator 6))
[    0.217285] Call Trace:
[    0.217285]  &lt;TASK&gt;
[    0.217285]  ? __pfx_kthread_worker_fn (kernel/kthread.c:966)
[    0.217285]  __kthread_create_on_node (kernel/kthread.c:535)
[    0.217285]  kthread_create_worker_on_node (kernel/kthread.c:1043 (discriminator 1) kernel/kthread.c:1073 (discriminator 1))
[    0.217285]  ? vprintk_emit (kernel/printk/printk.c:4625 kernel/printk/printk.c:2433)
[    0.217285]  workqueue_init (kernel/workqueue.c:7873 kernel/workqueue.c:7922)
[    0.217285]  kernel_init_freeable (init/main.c:1675)
[    0.217285]  ? __pfx_kernel_init (init/main.c:1570)
[    0.217285]  kernel_init (init/main.c:1580)
[    0.217285]  ret_from_fork (arch/x86/kernel/process.c:164)
[    0.217285]  ? __pfx_kernel_init (init/main.c:1570)
[    0.217285]  ret_from_fork_asm (arch/x86/entry/entry_64.S:259)
[    0.217285]  &lt;/TASK&gt;
</code></pre>

<h2 id="tl-dr">TL;DR</h2>
<ul><li><p>DEFINE_FREE()</p>
<ul><li>Sort-of-smart pointer</li>
<li>Definition tied to the freeing function, e.g. <code>DEFINE_KFREE(kfree,...)</code></li></ul></li>

<li><p>DEFINE_CLASS()</p>
<ul><li>Like <code>DEFINE_FREE()</code> but with factorized initialization.</li></ul></li>

<li><p>DEFINE_GUARD()</p>
<ul><li>Like <code>DEFINE_CLASS()</code> but you don&#39;t need the underlying variable</li>
<li>e.g. locks don&#39;t require declaring a variable, you just lock and unlock them.</li></ul></li>

<li><p>DEFINE_LOCK_GUARD()</p>
<ul><li>Like <code>DEFINE_GUARD()</code> but when a single pointer isn&#39;t sufficient for lock/unlock operations.</li>
<li>Also for “special” locks with no underlying type such as RCU, preempt or <code>migrate_disable</code>.</li></ul></li></ul>
]]></content:encoded>
      <guid>https://people.kernel.org/vschneid/guards-guard-locks-and-friends</guid>
      <pubDate>Fri, 30 Jan 2026 10:39:06 +0000</pubDate>
    </item>
  </channel>
</rss>